UncountablePythonSDK 0.0.193__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.
@@ -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.
@@ -417,6 +418,7 @@ class SpecTypeDefn(SpecType):
417
418
  "extant",
418
419
  "label",
419
420
  "name_case",
421
+ "sensitive",
420
422
  "type",
421
423
  ],
422
424
  )
@@ -499,6 +501,11 @@ class SpecTypeDefn(SpecType):
499
501
 
500
502
  ext_info = data.get("ext_info")
501
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
+ )
502
509
 
503
510
  return SpecProperty(
504
511
  name=name,
@@ -511,6 +518,7 @@ class SpecTypeDefn(SpecType):
511
518
  has_default=has_default,
512
519
  default=default,
513
520
  parse_require=parse_require,
521
+ sensitive=sensitive,
514
522
  desc=data.get("desc", None),
515
523
  ext_info=ext_info,
516
524
  explicit_default=explicit_default,
@@ -532,6 +540,13 @@ class SpecTypeDefn(SpecType):
532
540
  return self.namespace.usage
533
541
 
534
542
 
543
+ @dataclass(kw_only=True, frozen=True)
544
+ class PythonParseFallback:
545
+ legacy_property: str
546
+ convert_namespace: SpecNamespace
547
+ convert: str
548
+
549
+
535
550
  class SpecTypeGenericParameter(SpecType):
536
551
  def __init__(
537
552
  self,
@@ -697,13 +712,22 @@ class SpecTypeDefnObject(SpecTypeDefn):
697
712
  isinstance(legacy_property, str) and isinstance(convert, str),
698
713
  f"python_parse_fallbacks: {property_name} legacy_property and convert must be strings",
699
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
700
722
  builder.ensure_python_prepart_function(
701
- namespace_name=self.namespace.name,
702
- function_name=convert,
723
+ namespace=convert_namespace,
724
+ function_name=convert_function,
703
725
  option_name="python_parse_fallbacks",
704
726
  )
705
727
  parse_fallbacks[property_name] = PythonParseFallback(
706
- legacy_property=legacy_property, convert=convert
728
+ legacy_property=legacy_property,
729
+ convert_namespace=convert_namespace,
730
+ convert=convert_function,
707
731
  )
708
732
  return parse_fallbacks
709
733
 
@@ -1097,6 +1121,16 @@ class SpecTypeDefnStringEnum(SpecTypeDefn):
1097
1121
  return self.source_enums
1098
1122
 
1099
1123
 
1124
+ SpecTypeDefnVariant = (
1125
+ SpecTypeDefnAlias
1126
+ | SpecTypeDefnExternal
1127
+ | SpecTypeDefnNewType
1128
+ | SpecTypeDefnObject
1129
+ | SpecTypeDefnStringEnum
1130
+ | SpecTypeDefnUnion
1131
+ )
1132
+
1133
+
1100
1134
  TOKEN_ENDPOINT = "$endpoint"
1101
1135
  TOKEN_EMIT_IO_TS = "$emit_io_ts"
1102
1136
  TOKEN_EMIT_TYPE_INFO = "$emit_type_info"
@@ -1202,6 +1236,7 @@ def parse_endpoint_specific_path(
1202
1236
 
1203
1237
  class SpecEndpoint:
1204
1238
  method: RouteMethod
1239
+ alternate_methods: list[RouteMethod]
1205
1240
  data_loader: bool
1206
1241
  is_sdk: EndpointEmitType
1207
1242
  stability_level: StabilityLevel | None
@@ -1230,6 +1265,7 @@ class SpecEndpoint:
1230
1265
  data,
1231
1266
  [
1232
1267
  "method",
1268
+ "alternate_methods",
1233
1269
  "path",
1234
1270
  "data_loader",
1235
1271
  "deprecated",
@@ -1250,6 +1286,26 @@ class SpecEndpoint:
1250
1286
  )
1251
1287
  self.method = RouteMethod(data["method"])
1252
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
+
1253
1309
  data_loader = data.get("data_loader", False)
1254
1310
  assert isinstance(data_loader, bool)
1255
1311
  self.data_loader = data_loader
@@ -1477,7 +1533,7 @@ class SpecNamespace:
1477
1533
  self,
1478
1534
  name: str,
1479
1535
  ):
1480
- self.types: dict[str, SpecTypeDefn] = {}
1536
+ self.types: dict[str, SpecTypeDefnVariant] = {}
1481
1537
  self.constants: dict[str, SpecConstant] = {}
1482
1538
  self.endpoint: SpecEndpoint | None = None
1483
1539
  self.emit_io_ts = False
@@ -1575,7 +1631,7 @@ class SpecNamespace:
1575
1631
  assert name not in self.types, f"{name} is duplicate"
1576
1632
  defn_type = defn.get("type")
1577
1633
  assert isinstance(defn_type, str), f"{name} requires a string type"
1578
- spec_type: SpecTypeDefn
1634
+ spec_type: SpecTypeDefnVariant
1579
1635
  named_parameters = (
1580
1636
  [
1581
1637
  parameter.name
@@ -1806,13 +1862,14 @@ class SpecBuilder:
1806
1862
  raise BuilderError()
1807
1863
 
1808
1864
  def ensure_python_prepart_function(
1809
- self, *, namespace_name: str, function_name: str, option_name: str
1865
+ self, *, namespace: SpecNamespace, function_name: str, option_name: str
1810
1866
  ) -> None:
1811
- python_prepart = self.preparts["python"].get(namespace_name, "")
1867
+ namespace_key = ".".join(namespace.path)
1868
+ python_prepart = self.preparts["python"].get(namespace_key, "")
1812
1869
  def_prefix = f"def {function_name}"
1813
1870
  self.ensure(
1814
1871
  f"{def_prefix}(" in python_prepart or f"{def_prefix}[" in python_prepart,
1815
- 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}",
1816
1873
  )
1817
1874
 
1818
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