UncountablePythonSDK 0.0.192__py3-none-any.whl → 0.0.194__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. examples/integration-server/pyproject.toml +1 -1
  2. pkgs/argument_parser/__init__.py +2 -0
  3. pkgs/type_spec/builder.py +98 -10
  4. pkgs/type_spec/builder_types.py +0 -6
  5. pkgs/type_spec/emit_open_api.py +50 -33
  6. pkgs/type_spec/emit_open_api_util.py +1 -0
  7. pkgs/type_spec/emit_python.py +159 -194
  8. pkgs/type_spec/emit_typescript.py +3 -2
  9. pkgs/type_spec/emit_typescript_util.py +8 -0
  10. pkgs/type_spec/python/__init__.py +14 -0
  11. pkgs/type_spec/python/dataclass_fields.py +9 -0
  12. pkgs/type_spec/python/names.py +33 -0
  13. pkgs/type_spec/python/serial_class_options.py +45 -0
  14. pkgs/type_spec/type_info/emit_type_info.py +1 -1
  15. pkgs/type_spec/value_spec/emit_python.py +2 -2
  16. uncountable/core/client.py +3 -3
  17. uncountable/core/types.py +4 -4
  18. uncountable/integration/queue_runner/command_handlers.py +144 -0
  19. uncountable/integration/queue_runner/job_dispatcher.py +44 -0
  20. uncountable/integration/queue_runner/job_scheduler.py +51 -172
  21. uncountable/integration/queue_runner/queue_runner.py +5 -1
  22. uncountable/types/__init__.py +4 -0
  23. uncountable/types/api/files/download_file.py +18 -2
  24. uncountable/types/api/notebooks/add_notebook_content.py +1 -1
  25. uncountable/types/api/notebooks/clear_notebook_content.py +83 -0
  26. uncountable/types/api/recipes/remove_recipe_workflow_step.py +61 -0
  27. uncountable/types/async_batch_processor.py +73 -0
  28. uncountable/types/async_batch_t.py +2 -0
  29. uncountable/types/client_base.py +51 -2
  30. uncountable/types/entity_t.py +4 -0
  31. {uncountablepythonsdk-0.0.192.dist-info → uncountablepythonsdk-0.0.194.dist-info}/METADATA +2 -2
  32. {uncountablepythonsdk-0.0.192.dist-info → uncountablepythonsdk-0.0.194.dist-info}/RECORD +34 -26
  33. {uncountablepythonsdk-0.0.192.dist-info → uncountablepythonsdk-0.0.194.dist-info}/WHEEL +1 -1
  34. {uncountablepythonsdk-0.0.192.dist-info → uncountablepythonsdk-0.0.194.dist-info}/top_level.txt +0 -0
@@ -16,7 +16,7 @@ dependencies = [
16
16
  "types-simplejson ==4.*",
17
17
  "pandas-stubs",
18
18
  "xlrd == 2.*",
19
- "msgspec ==0.21.*",
19
+ "msgspec ==0.22.*",
20
20
  "websockets==17.0",
21
21
  ]
22
22
 
@@ -19,6 +19,7 @@ __all__ = [
19
19
  "is_string_enum_class",
20
20
  "is_union",
21
21
  "kebab_to_pascal_case",
22
+ "parse_fallback_legacy_type",
22
23
  "resolve_serialized_field_name",
23
24
  "snake_to_camel_case",
24
25
  ]
@@ -36,6 +37,7 @@ from .case_convert import (
36
37
  from .field_name_resolution import (
37
38
  resolve_serialized_field_name as resolve_serialized_field_name,
38
39
  )
40
+ from .parse_fallback import parse_fallback_legacy_type as parse_fallback_legacy_type
39
41
  from .parser_base import ParserBase as ParserBase
40
42
  from .parser_builder import build_parser as build_parser
41
43
  from .parser_error import (
pkgs/type_spec/builder.py CHANGED
@@ -17,7 +17,7 @@ import regex
17
17
  from pkgs.serialization import Usage
18
18
 
19
19
  from . import util
20
- from .builder_types import CrossOutputPaths, PythonParseFallback
20
+ from .builder_types import CrossOutputPaths
21
21
  from .non_discriminated_union_exceptions import NON_DISCRIMINATED_UNION_EXCEPTIONS
22
22
  from .util import parse_type_str
23
23
 
@@ -83,6 +83,7 @@ class SpecProperty:
83
83
  has_default: bool = False
84
84
  # Requires this value in parsing, even if it has a default
85
85
  parse_require: bool = False
86
+ sensitive: bool = False
86
87
  desc: str | None = None
87
88
  # Holds extra information that will be emitted along with type_info. The builder knows nothing
88
89
  # about the contents of this information.
@@ -103,6 +104,11 @@ class PropertyNameMode(StrEnum):
103
104
  deprecated_non_snake_case = "deprecated_non_snake_case"
104
105
 
105
106
 
107
+ class StringEnumEmitMode(StrEnum):
108
+ STANDARD = "standard"
109
+ SOURCE_ENUM_ALIAS = "source_enum_alias"
110
+
111
+
106
112
  class BaseTypeName(StrEnum):
107
113
  """
108
114
  Base types that are supported.
@@ -412,6 +418,7 @@ class SpecTypeDefn(SpecType):
412
418
  "extant",
413
419
  "label",
414
420
  "name_case",
421
+ "sensitive",
415
422
  "type",
416
423
  ],
417
424
  )
@@ -494,6 +501,11 @@ class SpecTypeDefn(SpecType):
494
501
 
495
502
  ext_info = data.get("ext_info")
496
503
  label = data.get("label")
504
+ sensitive = data.get("sensitive", False)
505
+ builder.ensure(
506
+ isinstance(sensitive, bool),
507
+ f"sensitive must be a boolean for property {name}",
508
+ )
497
509
 
498
510
  return SpecProperty(
499
511
  name=name,
@@ -506,6 +518,7 @@ class SpecTypeDefn(SpecType):
506
518
  has_default=has_default,
507
519
  default=default,
508
520
  parse_require=parse_require,
521
+ sensitive=sensitive,
509
522
  desc=data.get("desc", None),
510
523
  ext_info=ext_info,
511
524
  explicit_default=explicit_default,
@@ -527,6 +540,13 @@ class SpecTypeDefn(SpecType):
527
540
  return self.namespace.usage
528
541
 
529
542
 
543
+ @dataclass(kw_only=True, frozen=True)
544
+ class PythonParseFallback:
545
+ legacy_property: str
546
+ convert_namespace: SpecNamespace
547
+ convert: str
548
+
549
+
530
550
  class SpecTypeGenericParameter(SpecType):
531
551
  def __init__(
532
552
  self,
@@ -692,13 +712,22 @@ class SpecTypeDefnObject(SpecTypeDefn):
692
712
  isinstance(legacy_property, str) and isinstance(convert, str),
693
713
  f"python_parse_fallbacks: {property_name} legacy_property and convert must be strings",
694
714
  )
715
+ convert_namespace_path, _, convert_function = convert.rpartition(".")
716
+ convert_namespace = builder.namespaces.get(convert_namespace_path)
717
+ builder.ensure(
718
+ convert_namespace is not None,
719
+ f"python_parse_fallbacks: {property_name} convert must be <namespace>.<function> with a known namespace, got {convert}",
720
+ )
721
+ assert convert_namespace is not None
695
722
  builder.ensure_python_prepart_function(
696
- namespace_name=self.namespace.name,
697
- function_name=convert,
723
+ namespace=convert_namespace,
724
+ function_name=convert_function,
698
725
  option_name="python_parse_fallbacks",
699
726
  )
700
727
  parse_fallbacks[property_name] = PythonParseFallback(
701
- legacy_property=legacy_property, convert=convert
728
+ legacy_property=legacy_property,
729
+ convert_namespace=convert_namespace,
730
+ convert=convert_function,
702
731
  )
703
732
  return parse_fallbacks
704
733
 
@@ -912,6 +941,8 @@ class SpecTypeDefnStringEnum(SpecTypeDefn):
912
941
  self.desc: str | None = None
913
942
  self.sql_type_name: str | None = None
914
943
  self.emit_id_source = False
944
+ self.emit_mode = StringEnumEmitMode.STANDARD
945
+ self.alias_source_enum: SpecTypeDefnStringEnum | None = None
915
946
  self.source_enums: list[SpecType] = []
916
947
  self.property_name_mode = PropertyNameMode.strict
917
948
 
@@ -1036,12 +1067,15 @@ class SpecTypeDefnStringEnum(SpecTypeDefn):
1036
1067
 
1037
1068
  emit_data = data.get("emit")
1038
1069
  if emit_data is not None:
1039
- util.check_fields(emit_data, ["id_source"])
1070
+ util.check_fields(emit_data, ["id_source", "mode"])
1040
1071
  emit_id_source = emit_data.get("id_source", False)
1041
1072
  assert isinstance(emit_id_source, bool)
1042
1073
  self.emit_id_source = emit_id_source
1043
1074
  if emit_id_source:
1044
1075
  builder.emit_id_source_enums.add(self)
1076
+ self.emit_mode = StringEnumEmitMode(
1077
+ emit_data.get("mode", StringEnumEmitMode.STANDARD.value)
1078
+ )
1045
1079
 
1046
1080
  if self.emit_id_source:
1047
1081
  assert len(self.namespace.path) == 1
@@ -1062,10 +1096,41 @@ class SpecTypeDefnStringEnum(SpecTypeDefn):
1062
1096
  for name in self.values:
1063
1097
  _validate_enum_name(name)
1064
1098
 
1099
+ if self.emit_mode is StringEnumEmitMode.SOURCE_ENUM_ALIAS:
1100
+ builder.ensure(
1101
+ len(self.source_enums) == 1,
1102
+ "source enum alias must reference one StringEnum",
1103
+ )
1104
+ source_enum = self.source_enums[0]
1105
+ assert isinstance(source_enum, SpecTypeDefnStringEnum)
1106
+ self.alias_source_enum = source_enum
1107
+ builder.ensure(
1108
+ self.values == source_enum.values,
1109
+ "source enum alias must preserve all source values",
1110
+ )
1111
+ builder.ensure(
1112
+ data_values is None,
1113
+ "source enum alias cannot define local values",
1114
+ )
1115
+ builder.ensure(
1116
+ self.sql_type_name is None,
1117
+ "source enum alias cannot define a SQL type",
1118
+ )
1119
+
1065
1120
  def get_referenced_types(self) -> list[SpecType]:
1066
1121
  return self.source_enums
1067
1122
 
1068
1123
 
1124
+ SpecTypeDefnVariant = (
1125
+ SpecTypeDefnAlias
1126
+ | SpecTypeDefnExternal
1127
+ | SpecTypeDefnNewType
1128
+ | SpecTypeDefnObject
1129
+ | SpecTypeDefnStringEnum
1130
+ | SpecTypeDefnUnion
1131
+ )
1132
+
1133
+
1069
1134
  TOKEN_ENDPOINT = "$endpoint"
1070
1135
  TOKEN_EMIT_IO_TS = "$emit_io_ts"
1071
1136
  TOKEN_EMIT_TYPE_INFO = "$emit_type_info"
@@ -1171,6 +1236,7 @@ def parse_endpoint_specific_path(
1171
1236
 
1172
1237
  class SpecEndpoint:
1173
1238
  method: RouteMethod
1239
+ alternate_methods: list[RouteMethod]
1174
1240
  data_loader: bool
1175
1241
  is_sdk: EndpointEmitType
1176
1242
  stability_level: StabilityLevel | None
@@ -1199,6 +1265,7 @@ class SpecEndpoint:
1199
1265
  data,
1200
1266
  [
1201
1267
  "method",
1268
+ "alternate_methods",
1202
1269
  "path",
1203
1270
  "data_loader",
1204
1271
  "deprecated",
@@ -1219,6 +1286,26 @@ class SpecEndpoint:
1219
1286
  )
1220
1287
  self.method = RouteMethod(data["method"])
1221
1288
 
1289
+ alternate_methods_raw = data.get("alternate_methods", [])
1290
+ assert isinstance(alternate_methods_raw, list)
1291
+ self.alternate_methods = [
1292
+ RouteMethod(alternate_method) for alternate_method in alternate_methods_raw
1293
+ ]
1294
+ assert self.method not in self.alternate_methods, (
1295
+ "alternate-methods-must-not-repeat-method"
1296
+ )
1297
+ assert len(set(self.alternate_methods)) == len(self.alternate_methods), (
1298
+ "alternate-methods-must-be-unique"
1299
+ )
1300
+ if self.alternate_methods:
1301
+ assert self.method == RouteMethod.get, (
1302
+ "alternate-methods-require-get-primary-method"
1303
+ )
1304
+ assert all(
1305
+ alternate_method == RouteMethod.post
1306
+ for alternate_method in self.alternate_methods
1307
+ ), "alternate-methods-must-be-post"
1308
+
1222
1309
  data_loader = data.get("data_loader", False)
1223
1310
  assert isinstance(data_loader, bool)
1224
1311
  self.data_loader = data_loader
@@ -1446,7 +1533,7 @@ class SpecNamespace:
1446
1533
  self,
1447
1534
  name: str,
1448
1535
  ):
1449
- self.types: dict[str, SpecTypeDefn] = {}
1536
+ self.types: dict[str, SpecTypeDefnVariant] = {}
1450
1537
  self.constants: dict[str, SpecConstant] = {}
1451
1538
  self.endpoint: SpecEndpoint | None = None
1452
1539
  self.emit_io_ts = False
@@ -1544,7 +1631,7 @@ class SpecNamespace:
1544
1631
  assert name not in self.types, f"{name} is duplicate"
1545
1632
  defn_type = defn.get("type")
1546
1633
  assert isinstance(defn_type, str), f"{name} requires a string type"
1547
- spec_type: SpecTypeDefn
1634
+ spec_type: SpecTypeDefnVariant
1548
1635
  named_parameters = (
1549
1636
  [
1550
1637
  parameter.name
@@ -1775,13 +1862,14 @@ class SpecBuilder:
1775
1862
  raise BuilderError()
1776
1863
 
1777
1864
  def ensure_python_prepart_function(
1778
- self, *, namespace_name: str, function_name: str, option_name: str
1865
+ self, *, namespace: SpecNamespace, function_name: str, option_name: str
1779
1866
  ) -> None:
1780
- python_prepart = self.preparts["python"].get(namespace_name, "")
1867
+ namespace_key = ".".join(namespace.path)
1868
+ python_prepart = self.preparts["python"].get(namespace_key, "")
1781
1869
  def_prefix = f"def {function_name}"
1782
1870
  self.ensure(
1783
1871
  f"{def_prefix}(" in python_prepart or f"{def_prefix}[" in python_prepart,
1784
- f"{option_name}: the {namespace_name} python prepart file must define {function_name}",
1872
+ f"{option_name}: the {namespace.name} python prepart file must define {function_name}",
1785
1873
  )
1786
1874
 
1787
1875
  def prescan(self, namespace_path: str, data: RawDict) -> None:
@@ -7,9 +7,3 @@ class CrossOutputPaths:
7
7
  typescript_types_output: str
8
8
  typescript_routes_output_by_endpoint: dict[str, str]
9
9
  typespec_files_input: list[str]
10
-
11
-
12
- @dataclass(kw_only=True, frozen=True)
13
- class PythonParseFallback:
14
- legacy_property: str
15
- convert: str
@@ -4,6 +4,7 @@ Generates an OpenAPI specification based on the type_spec definitions
4
4
  WORK-IN-PROGRESS, DON'T USE!
5
5
  """
6
6
 
7
+ import copy
7
8
  import dataclasses
8
9
  import json
9
10
  import re
@@ -248,12 +249,12 @@ def _emit_endpoint_parameter_examples(
248
249
 
249
250
 
250
251
  def _emit_endpoint_parameters(
251
- endpoint: EmitOpenAPIEndpoint,
252
+ method: str,
252
253
  argument_type: OpenAPIType | None,
253
254
  examples: list[EmitOpenAPIEndpointExample],
254
255
  ) -> DictApiSchema:
255
256
  if (
256
- endpoint.method.lower() not in _QUERY_PARM_METHODS
257
+ method.lower() not in _QUERY_PARM_METHODS
257
258
  or argument_type is None
258
259
  or _is_empty_object_type(argument_type)
259
260
  ):
@@ -307,12 +308,12 @@ def _emit_stability_level(
307
308
 
308
309
 
309
310
  def _emit_endpoint_request_body(
310
- endpoint: EmitOpenAPIEndpoint,
311
+ method: str,
311
312
  arguments_type: OpenAPIType | None,
312
313
  examples: list[EmitOpenAPIEndpointExample],
313
314
  ) -> DictApiSchema:
314
315
  if (
315
- endpoint.method.lower() not in _REQUEST_BODY_METHODS
316
+ method.lower() not in _REQUEST_BODY_METHODS
316
317
  or arguments_type is None
317
318
  or _is_empty_object_type(arguments_type)
318
319
  ):
@@ -406,6 +407,40 @@ def _emit_endpoint_description(
406
407
  return {"description": full_description}
407
408
 
408
409
 
410
+ def _emit_endpoint_operation(
411
+ *,
412
+ endpoint: EmitOpenAPIEndpoint,
413
+ method: str,
414
+ argument_type: OpenAPIType | None,
415
+ ) -> DictApiSchema:
416
+ return (
417
+ {
418
+ "tags": endpoint.tags,
419
+ "summary": endpoint.summary,
420
+ }
421
+ | _emit_endpoint_deprecated(endpoint.deprecated)
422
+ | _emit_endpoint_description(
423
+ endpoint.description, endpoint.guides, endpoint.stability_level
424
+ )
425
+ | _emit_stability_level(endpoint.stability_level)
426
+ | _emit_endpoint_parameters(method, argument_type, endpoint.examples)
427
+ | _emit_endpoint_request_body(method, argument_type, endpoint.examples)
428
+ | {
429
+ "responses": {
430
+ "200": {
431
+ "description": "OK",
432
+ "content": {
433
+ "application/json": {
434
+ "schema": {"$ref": "#/components/schema/Data"}
435
+ }
436
+ | _emit_endpoint_response_examples(endpoint.examples)
437
+ },
438
+ }
439
+ },
440
+ }
441
+ )
442
+
443
+
409
444
  def _emit_namespace(
410
445
  gctx: EmitOpenAPIGlobalContext,
411
446
  ctx: EmitOpenAPIContext,
@@ -432,36 +467,14 @@ def _emit_namespace(
432
467
  if ctx.endpoint is not None:
433
468
  endpoint = ctx.endpoint
434
469
  argument_type = ctx.types.get(ARGUMENTS_TYPE_NAME)
435
- oa_endpoint = dict()
436
- oa_endpoint[endpoint.method] = (
437
- {
438
- "tags": endpoint.tags,
439
- "summary": endpoint.summary,
440
- }
441
- | _emit_endpoint_deprecated(endpoint.deprecated)
442
- | _emit_endpoint_description(
443
- endpoint.description, ctx.endpoint.guides, endpoint.stability_level
444
- )
445
- | _emit_stability_level(endpoint.stability_level)
446
- | _emit_endpoint_parameters(endpoint, argument_type, ctx.endpoint.examples)
447
- | _emit_endpoint_request_body(
448
- endpoint, argument_type, ctx.endpoint.examples
470
+ oa_components["endpoint"] = {
471
+ method: copy.deepcopy(
472
+ _emit_endpoint_operation(
473
+ endpoint=endpoint, method=method, argument_type=argument_type
474
+ )
449
475
  )
450
- | {
451
- "responses": {
452
- "200": {
453
- "description": "OK",
454
- "content": {
455
- "application/json": {
456
- "schema": {"$ref": "#/components/schema/Data"}
457
- }
458
- | _emit_endpoint_response_examples(ctx.endpoint.examples)
459
- },
460
- }
461
- },
462
- }
463
- )
464
- oa_components["endpoint"] = oa_endpoint
476
+ for method in [endpoint.method, *endpoint.alternate_methods]
477
+ }
465
478
 
466
479
  types = ctx.types
467
480
  if ctx.endpoint is not None:
@@ -682,6 +695,10 @@ def _emit_endpoint(
682
695
 
683
696
  ctx.endpoint = EmitOpenAPIEndpoint(
684
697
  method=namespace.endpoint.method.lower(),
698
+ alternate_methods=[
699
+ alternate_method.lower()
700
+ for alternate_method in namespace.endpoint.alternate_methods
701
+ ],
685
702
  tags=[tag_name],
686
703
  summary=f"{'/'.join(namespace.path[path_cutoff:])}",
687
704
  description=description,
@@ -79,6 +79,7 @@ EmitOpenAPIStabilityLevel = builder.StabilityLevel
79
79
  @dataclass
80
80
  class EmitOpenAPIEndpoint:
81
81
  method: str
82
+ alternate_methods: list[str]
82
83
  tags: list[str]
83
84
  summary: str
84
85
  description: str