UncountablePythonSDK 0.0.73__py3-none-any.whl → 0.0.74__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.

Potentially problematic release.


This version of UncountablePythonSDK might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: UncountablePythonSDK
3
- Version: 0.0.73
3
+ Version: 0.0.74
4
4
  Summary: Uncountable SDK
5
5
  Project-URL: Homepage, https://github.com/uncountableinc/uncountable-python-sdk
6
6
  Project-URL: Repository, https://github.com/uncountableinc/uncountable-python-sdk.git
@@ -26,10 +26,10 @@ examples/integration-server/jobs/materials_auto/example_cron.py,sha256=7VVQ-UJsq
26
26
  examples/integration-server/jobs/materials_auto/profile.yaml,sha256=MiqT9AHWoFz-rcpAHfiFTXuCP-18DaFipUaceati0-0,365
27
27
  pkgs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
28
  pkgs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
- pkgs/argument_parser/__init__.py,sha256=CsQ6QoPKSLLRVl-z6URAmPkiUL9ZPZoV4rJHgy_-RjA,385
29
+ pkgs/argument_parser/__init__.py,sha256=JRfZkC0-q6axr8F5_TKrjSprJ7d7chfcPvf-iMQqFg0,447
30
30
  pkgs/argument_parser/_is_enum.py,sha256=Gw6jJa8nBwYGqXwwCZbSnWL8Rvr5alkg5lSVAqXtOZM,257
31
31
  pkgs/argument_parser/_is_namedtuple.py,sha256=Rjc1bKanIPPogl3qG5JPBxglG1TqWYOo1nxxhBASQWY,265
32
- pkgs/argument_parser/argument_parser.py,sha256=uI_fSs7XmiQf9kt4ztWqqF9v8w4dZmQRy3tssPYVRDE,17330
32
+ pkgs/argument_parser/argument_parser.py,sha256=Z8lR_2L_N0zuxdOsK91D_9mjlsCcR_FJwtCLIfJz5oI,17713
33
33
  pkgs/argument_parser/case_convert.py,sha256=NuJLJUJRbyVb6_Slen4uqaStEHbcOS1d-hBBfDrrw-c,605
34
34
  pkgs/filesystem_utils/__init__.py,sha256=NSsQrUCoGISBCqCCyq6_583sYHTVEQeDjDO8hvZn3ag,1261
35
35
  pkgs/filesystem_utils/_gdrive_session.py,sha256=GJuZYJq1W4QQ_7OLvZIMK99FgRq8FxJHg6cMUx9prtA,11077
@@ -41,7 +41,7 @@ pkgs/filesystem_utils/filesystem_session.py,sha256=BQ2Go8Mu9-GcnaWh2Pm4x7ugLVsre
41
41
  pkgs/serialization/__init__.py,sha256=LifasRW0a50A3qRFmo2bf3FQ6TXhZWOTz2-CVTgPjcQ,753
42
42
  pkgs/serialization/missing_sentry.py,sha256=aM_9KxbCk9dVvXvcOKgkIQBqFWvLhv8QlIUCiuFEXMo,806
43
43
  pkgs/serialization/opaque_key.py,sha256=FIfXEE0DA1U8R_taFbQ1RCoTSgehrPjP06-qvo-GeNQ,177
44
- pkgs/serialization/serial_class.py,sha256=tD9kNotc6BOzkGi8yew89Wp_fSIA3a0VW7I8cSS_jHI,5607
44
+ pkgs/serialization/serial_class.py,sha256=D7vSnfJw4rWEDFbDd07pxgzyfTFZT5SKeQEv4C1c4H0,6057
45
45
  pkgs/serialization/serial_union.py,sha256=xpdeqCrRd0sNCaUwBQRzje6V40ndCbJpZrLX2K0d5xo,2741
46
46
  pkgs/serialization/yaml.py,sha256=yoJtu7_ixnJV6uTxA_U1PpK5F_ixT08AKVh5ocyYwXM,1466
47
47
  pkgs/serialization_util/__init__.py,sha256=MVKqHTUl2YnWZAFG9xCxu1SgmkQ5xPofrAGlYg6h7rI,330
@@ -52,25 +52,25 @@ pkgs/strenum_compat/__init__.py,sha256=wXRFeNvBm8RU6dy1PFJ5sRLgUIEeH_DVR95Sv5qpG
52
52
  pkgs/strenum_compat/strenum_compat.py,sha256=uOUAgpYTjHs1MX8dG81jRlyTkt3KNbkV_25zp7xTX2s,36
53
53
  pkgs/type_spec/__init__.py,sha256=h5DmJTca4QVV10sZR1x0-MlkZfuGYDfapR3zHvXfzto,19
54
54
  pkgs/type_spec/__main__.py,sha256=5bJaX9Y_-FavP0qwzhk-z-V97UY7uaezJTa1zhO_HHQ,1048
55
- pkgs/type_spec/builder.py,sha256=EUMEc0Mj0p9vb5QYojpe7Eu1UQB0Mg0p3oGzqTrtWZA,46888
55
+ pkgs/type_spec/builder.py,sha256=nuHdVNOmwbHbHV5_8nqtsrxOfzIII6jcQO7eLOljT4c,48856
56
56
  pkgs/type_spec/config.py,sha256=ZUmPWCzTwjesAqlqeL1_E_yoIUZE_8g0kI2yXtbU0Zc,4811
57
57
  pkgs/type_spec/emit_io_ts.py,sha256=U03sQBpgRqYOaMKrPCRnYb70YboiCgaZfseCXSzW5NY,5707
58
58
  pkgs/type_spec/emit_open_api.py,sha256=5a0iAHBbgFD4wfKuyjPvxCYYHNTjKxEHA0aYjMGSqe4,24596
59
59
  pkgs/type_spec/emit_open_api_util.py,sha256=x4GCiZSGdypJ9Qtm6I5W_3UvwdJyMs8_OGhJ8_THznA,2401
60
- pkgs/type_spec/emit_python.py,sha256=u09ueuF5ypUsyNkxnBFGsS8tivVsjvmUCkPLM_RIask,47466
61
- pkgs/type_spec/emit_typescript.py,sha256=Tv8EbXBQewZN9q3zRKfTy9lWwElBmIV6fprIjA4RmJQ,18010
62
- pkgs/type_spec/emit_typescript_util.py,sha256=sR7ys3Ilnh6SQiXJbfYk4pxfOu0bDjbUFTEYEW-ud6c,863
60
+ pkgs/type_spec/emit_python.py,sha256=eBct7PMYgcv35POo2JU089lLggPrgLfTOrKpqAukn1E,47370
61
+ pkgs/type_spec/emit_typescript.py,sha256=w9DUpksc4QP0CatUU9NYSZN4IBNNXVXkMnKMCFQmL58,18274
62
+ pkgs/type_spec/emit_typescript_util.py,sha256=StCDbJ5mU23MBb2LGu847yJN2fuiogjoQqZu6q3E6FY,797
63
63
  pkgs/type_spec/load_types.py,sha256=vO8VLI7aTKzzHQIla-WO-5Z_mfTuwUqH4ZSKN9E9n5U,3688
64
64
  pkgs/type_spec/open_api_util.py,sha256=IGh-_snGPST_P_8FdYtO8MTEa9PUxRW6Rzg9X9EgQik,7114
65
65
  pkgs/type_spec/test.py,sha256=4ueujBq-pEgnX3Z69HyPmD-bullFXmpixcpVzfOkhP4,489
66
- pkgs/type_spec/util.py,sha256=6m6MPfY-SwjyZf2FWQKclswWB5o7gcdd-3tdpViPYOQ,4844
66
+ pkgs/type_spec/util.py,sha256=79SLJsSPVnBe2_3CTF6J-7-QD9nRr6o8MKvfjyx53eI,4864
67
67
  pkgs/type_spec/actions_registry/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
68
68
  pkgs/type_spec/actions_registry/__main__.py,sha256=JGwKxcAmrQdbpVR2vwknoimN1Q-r5h4SADw1cYLYzgk,4331
69
69
  pkgs/type_spec/actions_registry/emit_typescript.py,sha256=Z1ZM4zOw26tvLspvW6Emg79-jxjhNBse-8yaionbmeo,6066
70
70
  pkgs/type_spec/parts/base.py.prepart,sha256=wGNoDyQnLolHRZGRwHQX5TrPfKnu558NXCocYvqyroc,2174
71
71
  pkgs/type_spec/parts/base.ts.prepart,sha256=2FJJvpg2olCcavxj0nbYWdwKl6KeScour2JjSvN42l8,1001
72
72
  pkgs/type_spec/type_info/__main__.py,sha256=pmVjVqXyVh8vKTNCTFgz80Sg74C5BKToP3E6GS-X_So,857
73
- pkgs/type_spec/type_info/emit_type_info.py,sha256=1R1ygKbGBIrKDCh3NDiBB6w9ofRCoCjujhHZx9A4_Wc,13289
73
+ pkgs/type_spec/type_info/emit_type_info.py,sha256=C4Rq5z22c3IJTyQriNkQWgGV7I3ZgTDjwKm_JM_sOYI,13362
74
74
  pkgs/type_spec/value_spec/__init__.py,sha256=Z-grlcZtxAfEXhPHsK0nD7PFLGsv4eqvunaPN7_TA84,83
75
75
  pkgs/type_spec/value_spec/__main__.py,sha256=6bzP85p_Cm4bPp5tXz8D_4p64wMn5SKsXC7SqSZquYc,8318
76
76
  pkgs/type_spec/value_spec/convert_type.py,sha256=Tg5YsYOwvmf_EqbCAtCmqy3-dud8OwdbEOzAaRN7cCs,2286
@@ -124,7 +124,7 @@ uncountable/integration/secret_retrieval/retrieve_secret.py,sha256=eoPWbkUtCn_63
124
124
  uncountable/integration/webhook_server/entrypoint.py,sha256=hgbEtdVo3QU3odlHSygxmrsxR9g7rgU0yKt-o9nVAHE,5686
125
125
  uncountable/types/__init__.py,sha256=KSsSEBnGhn88NPex5q9MZtY4kj8PRqVTbaKtko4hxUU,8561
126
126
  uncountable/types/async_batch.py,sha256=_OhT25_dEVts_z_n1kqfJH3xlZg3btLqR6TNkfFLlXE,609
127
- uncountable/types/async_batch_processor.py,sha256=obVzN-PcYLV2pHScszfCGjSq6-Xc34WM1ysx6Fv6tZk,11293
127
+ uncountable/types/async_batch_processor.py,sha256=esKBP56RiRH-VjAFBr8wgfcgKsRkUfiLBmQmYGx4XXY,11462
128
128
  uncountable/types/async_batch_t.py,sha256=CZ-rltFUiKVowvL5BhMfWaFxgf-Z0KPsghvjsg-PweY,2493
129
129
  uncountable/types/base.py,sha256=xVSjWvA_fUUnkCg83EjoYEFvAfmskinKFMeYFOxNc9E,359
130
130
  uncountable/types/base_t.py,sha256=XXjZXexx0xWFUxMMhW8i9nIL6n8dsZVsHwdgnhZ0zJ4,2714
@@ -132,13 +132,13 @@ uncountable/types/calculations.py,sha256=FFO_D3BbKoGDZnqWvTKpW4KF359i2vrKjpdFCLY
132
132
  uncountable/types/calculations_t.py,sha256=157qD0VqijD5kNDF5BRsfGli3WaPGnNjoo2o2CPX-Ik,669
133
133
  uncountable/types/chemical_structure.py,sha256=E-LnikTFDoVQ1b2zKaVUIO_PAKm-7aZZYJi8I8SDSic,302
134
134
  uncountable/types/chemical_structure_t.py,sha256=zDJ6WkeT3YwWZRZT21znQn2ZYelv3L7yv7kJiGoNZCw,824
135
- uncountable/types/client_base.py,sha256=KfeZ_PrfHO0U-4cUDXA9Kfy7slHfuxsANakKQJaFHV8,67320
135
+ uncountable/types/client_base.py,sha256=16W0EnH7kyy_sRL_fNn4NBvNhE8NCmmrSad-grKhLus,67489
136
136
  uncountable/types/client_config.py,sha256=4h5Liko9uKCo9_0gdbPhoK6Jr2Kv7tioLiQ8iKeq-_4,301
137
137
  uncountable/types/client_config_t.py,sha256=6dStfR0IEHiPW8f9_aF3DD_tHmXXw2rEVrgpebzq8Fg,747
138
138
  uncountable/types/curves.py,sha256=W6uMpG5SyW1MS82szNpxkFEn1MnxNpBFyFbQb2Ysfng,366
139
139
  uncountable/types/curves_t.py,sha256=lKhRM-2cZ_sFaW7pa_I_Ipz_pJhm3_yTFehRXI79pKk,1416
140
140
  uncountable/types/entity.py,sha256=3XhLteFDRDZvHejDuYh-KvB65hpwrBygljFfiUcOAM8,315
141
- uncountable/types/entity_t.py,sha256=2F6zIxffc6l0hgjPtNUr3X0LKC4lxRMJZCT6T788GlI,14929
141
+ uncountable/types/entity_t.py,sha256=8OkFVgvrItdA1ysyWB21mLL85JTPdcdzAmb6CNBUVI0,15003
142
142
  uncountable/types/experiment_groups.py,sha256=_0OXcPzSAbkE-rfKt5tPx178YJ4pcEKZvrCxUHgDnvw,309
143
143
  uncountable/types/experiment_groups_t.py,sha256=qEs8YW0eJOJ_sCOObT5v9QRx9wsjLYpJqJhCJXa-vNA,721
144
144
  uncountable/types/field_values.py,sha256=uuIWX-xmfvcinYPdfkWJeb56zzQY01mc9rmotMPMh24,503
@@ -276,8 +276,8 @@ uncountable/types/api/recipes/unlock_recipes.py,sha256=RaC5N5rz6f3FAIaQM3NgLuXEM
276
276
  uncountable/types/api/triggers/__init__.py,sha256=gCgbynxG3jA8FQHzercKtrHKHkiIKr8APdZYUniAor8,55
277
277
  uncountable/types/api/triggers/run_trigger.py,sha256=-oZgPyn43xEKSCs81DVNzwaYMCdRJxbM9GY6fsqKwf4,1090
278
278
  uncountable/types/api/uploader/__init__.py,sha256=gCgbynxG3jA8FQHzercKtrHKHkiIKr8APdZYUniAor8,55
279
- uncountable/types/api/uploader/invoke_uploader.py,sha256=EMtTBwcFXDde5Y2LpubyjAcXcDpBp8yT4HAPR4o95JE,1230
280
- UncountablePythonSDK-0.0.73.dist-info/METADATA,sha256=P9sk2jp7gGaHwQv7hTMwFDdszfZDNnlYOes3gpcMPIM,2051
281
- UncountablePythonSDK-0.0.73.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
282
- UncountablePythonSDK-0.0.73.dist-info/top_level.txt,sha256=1UVGjAU-6hJY9qw2iJ7nCBeEwZ793AEN5ZfKX9A1uj4,31
283
- UncountablePythonSDK-0.0.73.dist-info/RECORD,,
279
+ uncountable/types/api/uploader/invoke_uploader.py,sha256=6mwVG136oLp9JcbB2I-kZnrcm3aeZzYZB-SFjEImY2o,1314
280
+ UncountablePythonSDK-0.0.74.dist-info/METADATA,sha256=cvY7ALGHYsY6rqK3qLdaXGE_8joz1ouiGFKtbPaOsFw,2051
281
+ UncountablePythonSDK-0.0.74.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
282
+ UncountablePythonSDK-0.0.74.dist-info/top_level.txt,sha256=1UVGjAU-6hJY9qw2iJ7nCBeEwZ793AEN5ZfKX9A1uj4,31
283
+ UncountablePythonSDK-0.0.74.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.1.0)
2
+ Generator: setuptools (75.2.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,4 +1,5 @@
1
1
  from .argument_parser import CachedParser as CachedParser
2
+ from .argument_parser import ParserFunction as ParserFunction
2
3
  from .argument_parser import ParserOptions as ParserOptions
3
4
  from .argument_parser import build_parser as build_parser
4
5
  from .case_convert import camel_to_snake_case as camel_to_snake_case
@@ -1,4 +1,5 @@
1
1
  import dataclasses
2
+ import math
2
3
  import types
3
4
  import typing
4
5
  from collections import defaultdict
@@ -299,7 +300,18 @@ def _build_parser_inner(
299
300
 
300
301
  return parse_str
301
302
 
302
- if parsed_type in (float, dict, bool, Decimal) or is_string_enum_class(parsed_type):
303
+ if parsed_type in (float, Decimal):
304
+
305
+ def parse_as_numeric_type(value: typing.Any) -> T:
306
+ numeric_value: Decimal | float = parsed_type(value) # type: ignore
307
+ if math.isnan(numeric_value):
308
+ raise ValueError(f"Invalid numeric value: {numeric_value}")
309
+
310
+ return numeric_value # type: ignore
311
+
312
+ return parse_as_numeric_type
313
+
314
+ if parsed_type in (dict, bool) or is_string_enum_class(parsed_type):
303
315
  return lambda value: parsed_type(value) # type: ignore
304
316
 
305
317
  if parsed_type is MissingSentryType:
@@ -15,6 +15,10 @@ class _SerialClassData:
15
15
  to_string_values: set[str] = dataclasses.field(default_factory=set)
16
16
  parse_require: set[str] = dataclasses.field(default_factory=set)
17
17
  named_type_path: Optional[str] = None
18
+ # Tracks if this data was provided as a decorator to the type.
19
+ # This is used to track "proper types" which are appropriate
20
+ # for serialization and/or dynamic discovery
21
+ from_decorator: bool = False
18
22
 
19
23
 
20
24
  EMPTY_SERIAL_CLASS_DATA = _SerialClassData()
@@ -58,6 +62,7 @@ def serial_class(
58
62
  to_string_values=to_string_values or set(),
59
63
  parse_require=parse_require or set(),
60
64
  named_type_path=named_type_path,
65
+ from_decorator=True,
61
66
  )
62
67
  return orig_class
63
68
 
@@ -83,6 +88,14 @@ class SerialClassDataInspector:
83
88
  def has_parse_require(self, key: str) -> bool:
84
89
  return key in self.current.parse_require
85
90
 
91
+ @property
92
+ def from_decorator(self) -> bool:
93
+ return self.current.from_decorator
94
+
95
+ @property
96
+ def named_type_path(self) -> Optional[str]:
97
+ return self.current.named_type_path
98
+
86
99
 
87
100
  def _get_merged_serial_class_data(type_class: type[Any]) -> _SerialClassData | None:
88
101
  base_class_data = (
pkgs/type_spec/builder.py CHANGED
@@ -307,6 +307,9 @@ class SpecTypeDefn(SpecType):
307
307
  def is_base_type(self, type_: BaseTypeName) -> bool:
308
308
  return self.is_base and self.name == type_
309
309
 
310
+ def can_process(self, builder: SpecBuilder, data: RawDict) -> bool:
311
+ return True
312
+
310
313
  @abc.abstractmethod
311
314
  def process(self, builder: SpecBuilder, data: RawDict) -> None: ...
312
315
 
@@ -666,15 +669,30 @@ class SpecTypeDefnStringEnum(SpecTypeDefn):
666
669
  self.desc: str | None = None
667
670
  self.sql_type_name: Optional[str] = None
668
671
  self.emit_id_source = False
672
+ self.source_enums: list[SpecType] = []
673
+
674
+ def can_process(self, builder: SpecBuilder, data: dict[Any, Any]) -> bool:
675
+ source_enums = data.get("source_enums")
676
+ try:
677
+ for sub_type_str in source_enums or []:
678
+ sub_type = builder.parse_type(self.namespace, sub_type_str)
679
+ assert isinstance(sub_type, SpecTypeDefnStringEnum)
680
+ assert len(sub_type.values) > 0
681
+ except AssertionError:
682
+ return False
683
+ return super().can_process(builder, data)
669
684
 
670
685
  def process(self, builder: SpecBuilder, data: RawDict) -> None:
671
686
  super().base_process(
672
- builder, data, ["type", "desc", "values", "name_case", "sql", "emit"]
687
+ builder,
688
+ data,
689
+ ["type", "desc", "values", "name_case", "sql", "emit", "source_enums"],
673
690
  )
674
691
  self.name_case = NameCase(data.get("name_case", "convert"))
675
692
  self.values = {}
676
- data_values = data["values"]
693
+ data_values = data.get("values")
677
694
  self.desc = data.get("desc", None)
695
+ source_enums = data.get("source_enums", None)
678
696
  if isinstance(data_values, dict):
679
697
  for name, value in data_values.items():
680
698
  builder.push_where(name)
@@ -717,7 +735,8 @@ class SpecTypeDefnStringEnum(SpecTypeDefn):
717
735
  )
718
736
  self.values[value] = StringEnumEntry(name=value, value=value)
719
737
  else:
720
- raise Exception("unsupported values type")
738
+ if source_enums is None or data_values is not None:
739
+ raise Exception("unsupported values type")
721
740
 
722
741
  sql_data = data.get("sql")
723
742
  if sql_data is not None:
@@ -739,9 +758,18 @@ class SpecTypeDefnStringEnum(SpecTypeDefn):
739
758
  builder.ensure(
740
759
  entry.label is not None, f"need-label-for-id-source:{entry.name}"
741
760
  )
761
+ for sub_type_str in source_enums or []:
762
+ sub_type = builder.parse_type(self.namespace, sub_type_str)
763
+ self.source_enums.append(sub_type)
764
+
765
+ for sub_type in self.source_enums:
766
+ builder.push_where(sub_type.name)
767
+ if isinstance(sub_type, SpecTypeDefnStringEnum):
768
+ self.values.update(sub_type.values)
769
+ builder.pop_where()
742
770
 
743
771
  def get_referenced_types(self) -> list[SpecType]:
744
- return []
772
+ return self.source_enums
745
773
 
746
774
 
747
775
  TOKEN_ENDPOINT = "$endpoint"
@@ -1122,28 +1150,41 @@ class SpecNamespace:
1122
1150
  Complete the definition of each type.
1123
1151
  """
1124
1152
  builder.push_where(self.name)
1125
- for full_name, defn in data.items():
1126
- parsed_name = parse_type_str(full_name)[0]
1127
- name = parsed_name.name
1153
+ items_to_process: list[NameDataPair] = [
1154
+ NameDataPair(full_name=full_name, data=defn)
1155
+ for full_name, defn in data.items()
1156
+ ]
1157
+ while len(items_to_process) > 0:
1158
+ deferred_items: list[NameDataPair] = []
1159
+ for item in items_to_process:
1160
+ full_name = item.full_name
1161
+ defn = item.data
1162
+ parsed_name = parse_type_str(full_name)[0]
1163
+ name = parsed_name.name
1164
+
1165
+ if name in [TOKEN_EMIT_IO_TS, TOKEN_EMIT_TYPE_INFO, TOKEN_IMPORT]:
1166
+ continue
1128
1167
 
1129
- if name in [TOKEN_EMIT_IO_TS, TOKEN_EMIT_TYPE_INFO, TOKEN_IMPORT]:
1130
- continue
1168
+ builder.push_where(name)
1131
1169
 
1132
- builder.push_where(name)
1170
+ if "value" in defn:
1171
+ spec_constant = self.constants[name]
1172
+ spec_constant.process(builder, defn)
1133
1173
 
1134
- if "value" in defn:
1135
- spec_constant = self.constants[name]
1136
- spec_constant.process(builder, defn)
1174
+ elif name == TOKEN_ENDPOINT:
1175
+ assert self.endpoint
1176
+ self.endpoint.process(builder, defn)
1137
1177
 
1138
- elif name == TOKEN_ENDPOINT:
1139
- assert self.endpoint
1140
- self.endpoint.process(builder, defn)
1141
-
1142
- else:
1143
- spec_type = self.types[name]
1144
- spec_type.process(builder, defn)
1178
+ else:
1179
+ spec_type = self.types[name]
1180
+ if spec_type.can_process(builder, defn):
1181
+ spec_type.process(builder, defn)
1182
+ else:
1183
+ deferred_items.append(item)
1145
1184
 
1146
- builder.pop_where()
1185
+ builder.pop_where()
1186
+ assert len(deferred_items) < len(items_to_process)
1187
+ items_to_process = [deferred for deferred in deferred_items]
1147
1188
 
1148
1189
  builder.pop_where()
1149
1190
 
@@ -1158,6 +1199,12 @@ class NamespaceDataPair:
1158
1199
  data: RawDict
1159
1200
 
1160
1201
 
1202
+ @dataclass(kw_only=True)
1203
+ class NameDataPair:
1204
+ full_name: str
1205
+ data: RawDict
1206
+
1207
+
1161
1208
  class SpecBuilder:
1162
1209
  def __init__(self, *, api_endpoints: dict[str, str], top_namespace: str) -> None:
1163
1210
  self.top_namespace = top_namespace
@@ -44,7 +44,6 @@ class TrackingContext:
44
44
  use_enum: bool = False
45
45
  use_serial_string_enum: bool = False
46
46
  use_dataclass: bool = False
47
- use_serial_class: bool = False
48
47
  use_serial_union: bool = False
49
48
  use_missing: bool = False
50
49
  use_opaque_key: bool = False
@@ -223,7 +222,6 @@ def _emit_types_imports(*, out: io.StringIO, ctx: Context) -> None:
223
222
  out.write("from pkgs.strenum_compat import StrEnum\n")
224
223
  if ctx.use_dataclass:
225
224
  out.write("import dataclasses\n")
226
- if ctx.use_serial_class:
227
225
  out.write("from pkgs.serialization import serial_class\n")
228
226
  if ctx.use_serial_union:
229
227
  out.write("from pkgs.serialization import serial_union_annotation\n")
@@ -834,7 +832,6 @@ def _emit_type(ctx: Context, stype: builder.SpecType) -> None:
834
832
  _emit_generic(ctx, stype.get_generic())
835
833
 
836
834
  # Emit serial_class decorator
837
- ctx.use_serial_class = True
838
835
  ctx.out.write("@serial_class(\n")
839
836
  ctx.out.write(
840
837
  f"{INDENT}named_type_path={util.encode_common_string(_named_type_path(ctx, stype))},\n"
@@ -82,6 +82,25 @@ def emit_typescript(builder: builder.SpecBuilder, config: TypeScriptConfig) -> N
82
82
  _emit_id_source(builder, config)
83
83
 
84
84
 
85
+ def emit_namespace_imports_ts(
86
+ namespaces: set[builder.SpecNamespace],
87
+ out: io.StringIO,
88
+ current_namespace: builder.SpecNamespace,
89
+ ) -> None:
90
+ for ns in sorted(
91
+ namespaces,
92
+ key=lambda name: _resolve_namespace_name(name),
93
+ ):
94
+ import_as = resolve_namespace_ref(ns)
95
+ import_path = (
96
+ "./"
97
+ if len(current_namespace.path) == 1
98
+ else "../" * (len(current_namespace.path) - 1)
99
+ )
100
+ import_from = f"{import_path}{_resolve_namespace_name(ns)}"
101
+ out.write(f'import * as {import_as} from "{import_from}"\n') # noqa: E501
102
+
103
+
85
104
  def _emit_types(builder: builder.SpecBuilder, config: TypeScriptConfig) -> None:
86
105
  index_out = io.StringIO()
87
106
  index_out.write(MODIFY_NOTICE)
@@ -92,11 +111,9 @@ def _emit_types(builder: builder.SpecBuilder, config: TypeScriptConfig) -> None:
92
111
  builder.namespaces.values(),
93
112
  key=lambda ns: _resolve_namespace_name(ns),
94
113
  ):
95
- ctx = EmitTypescriptContext(
96
- out=io.StringIO(), namespace=namespace, config=config
97
- )
114
+ ctx = EmitTypescriptContext(out=io.StringIO(), namespace=namespace)
98
115
 
99
- _emit_namespace(ctx, namespace)
116
+ _emit_namespace(ctx, config, namespace)
100
117
 
101
118
  prepart = builder.preparts["typescript"].get(namespace.name)
102
119
  part = builder.parts["typescript"].get(namespace.name)
@@ -123,16 +140,7 @@ def _emit_types(builder: builder.SpecBuilder, config: TypeScriptConfig) -> None:
123
140
  full.write(f"// === END section from {namespace.name}.ts.prepart ===\n")
124
141
  full.write("\n")
125
142
 
126
- for ns in sorted(
127
- ctx.namespaces,
128
- key=lambda name: _resolve_namespace_name(name),
129
- ):
130
- import_as = resolve_namespace_ref(ns)
131
- import_path = (
132
- "./" if len(namespace.path) == 1 else "../" * (len(namespace.path) - 1)
133
- )
134
- import_from = f"{import_path}{_resolve_namespace_name(ns)}"
135
- full.write(f'import * as {import_as} from "{import_from}"\n') # noqa: E501
143
+ emit_namespace_imports_ts(ctx.namespaces, out=full, current_namespace=namespace)
136
144
  if namespace.emit_io_ts:
137
145
  full.write("import * as IO from 'io-ts';")
138
146
  full.write(ctx.out.getvalue())
@@ -164,23 +172,26 @@ def _emit_types(builder: builder.SpecBuilder, config: TypeScriptConfig) -> None:
164
172
 
165
173
 
166
174
  def _emit_namespace(
167
- ctx: EmitTypescriptContext, namespace: builder.SpecNamespace
175
+ ctx: EmitTypescriptContext,
176
+ config: TypeScriptConfig,
177
+ namespace: builder.SpecNamespace,
168
178
  ) -> None:
169
179
  for stype in namespace.types.values():
170
180
  if namespace.emit_io_ts:
171
181
  emit_type_io_ts(ctx, stype, namespace.derive_types_from_io_ts)
172
182
  if not namespace.emit_io_ts or not namespace.derive_types_from_io_ts:
173
- _emit_type(ctx, stype)
183
+ emit_type_ts(ctx, stype)
174
184
 
175
185
  for sconst in namespace.constants.values():
176
186
  _emit_constant(ctx, sconst)
177
187
 
178
188
  if namespace.endpoint is not None:
179
- _emit_endpoint(ctx, namespace, namespace.endpoint)
189
+ _emit_endpoint(ctx, config, namespace, namespace.endpoint)
180
190
 
181
191
 
182
192
  def _emit_endpoint(
183
193
  ctx: EmitTypescriptContext,
194
+ config: TypeScriptConfig,
184
195
  namespace: builder.SpecNamespace,
185
196
  endpoint: builder.SpecEndpoint,
186
197
  ) -> None:
@@ -265,14 +276,12 @@ export const apiCall = {wrap_call}(
265
276
  )
266
277
  {data_loader_body}"""
267
278
 
268
- output = f"{ctx.config.routes_output}/{"/".join(namespace.path)}.tsx"
279
+ output = f"{config.routes_output}/{"/".join(namespace.path)}.tsx"
269
280
  util.rewrite_file(output, tsx_api)
270
281
 
271
282
  # Hacky index support, until enough is migrated to regen entirely
272
283
  # Emits the import into the UI API index file
273
- index_path = (
274
- f"{ctx.config.routes_output}/{"/".join(namespace.path[0:-1])}/index.tsx"
275
- )
284
+ index_path = f"{config.routes_output}/{"/".join(namespace.path[0:-1])}/index.tsx"
276
285
  api_name = f"Api{ts_type_name(namespace.path[0 - 1])}"
277
286
  if os.path.exists(index_path):
278
287
  with open(index_path) as index:
@@ -288,7 +297,7 @@ export const apiCall = {wrap_call}(
288
297
  index.write(f"export {{ {api_name} }}\n")
289
298
 
290
299
 
291
- def _emit_type(ctx: EmitTypescriptContext, stype: builder.SpecType) -> None:
300
+ def emit_type_ts(ctx: EmitTypescriptContext, stype: builder.SpecType) -> None:
292
301
  if not isinstance(stype, builder.SpecTypeDefn):
293
302
  return
294
303
 
@@ -2,7 +2,6 @@ import io
2
2
  from dataclasses import dataclass, field
3
3
 
4
4
  from . import builder, util
5
- from .config import TypeScriptConfig
6
5
 
7
6
  INDENT = " "
8
7
 
@@ -11,7 +10,6 @@ MODIFY_NOTICE = "// DO NOT MODIFY -- This file is generated by type_spec\n"
11
10
 
12
11
  @dataclass(kw_only=True)
13
12
  class EmitTypescriptContext:
14
- config: TypeScriptConfig
15
13
  out: io.StringIO
16
14
  namespace: builder.SpecNamespace
17
15
  namespaces: set[builder.SpecNamespace] = field(default_factory=set)
@@ -5,7 +5,7 @@ import io
5
5
  import json
6
6
  from typing import Any, Optional, Union, cast
7
7
 
8
- from main.base.types import data_t
8
+ from main.base.types import data_t, type_info_t
9
9
  from main.base.types.base_t import PureJsonValue
10
10
  from pkgs.argument_parser import CachedParser
11
11
  from pkgs.serialization_util import (
@@ -17,7 +17,7 @@ from .. import builder, util
17
17
  from ..emit_typescript_util import MODIFY_NOTICE, ts_name
18
18
  from ..value_spec import convert_to_value_spec_type
19
19
 
20
- ext_info_parser = CachedParser(data_t.ExtInfo)
20
+ ext_info_parser = CachedParser(type_info_t.ExtInfo, strict_property_parsing=True)
21
21
 
22
22
 
23
23
  def type_path_of(stype: builder.SpecType) -> object: # NamePath
@@ -174,7 +174,7 @@ class InheritablePropertyParts:
174
174
 
175
175
  label: Optional[str] = None
176
176
  desc: Optional[str] = None
177
- ext_info: Optional[data_t.ExtInfo] = None
177
+ ext_info: Optional[type_info_t.ExtInfo] = None
178
178
 
179
179
 
180
180
  def _extract_inheritable_property_parts(
@@ -201,7 +201,7 @@ def _extract_inheritable_property_parts(
201
201
  elif base_parts.ext_info is None:
202
202
  ext_info = local_ext_info
203
203
  else:
204
- ext_info = data_t.ExtInfo(
204
+ ext_info = type_info_t.ExtInfo(
205
205
  **(local_ext_info.__dict__ | base_parts.ext_info.__dict__)
206
206
  )
207
207
 
@@ -214,7 +214,7 @@ ALL_FIELDS_GROUP = "*all_fields"
214
214
 
215
215
  def _extract_and_validate_layout(
216
216
  stype: builder.SpecTypeDefnObject,
217
- ext_info: data_t.ExtInfo,
217
+ ext_info: type_info_t.ExtInfo,
218
218
  base_layout: ExtInfoLayout | None,
219
219
  ) -> ExtInfoLayout:
220
220
  """
@@ -264,7 +264,7 @@ def _extract_and_validate_layout(
264
264
 
265
265
  def _validate_type_ext_info(
266
266
  stype: builder.SpecTypeDefnObject,
267
- ) -> tuple[ExtInfoLayout | None, Optional[data_t.ExtInfo]]:
267
+ ) -> tuple[ExtInfoLayout | None, Optional[type_info_t.ExtInfo]]:
268
268
  ext_info = _parse_ext_info(stype.ext_info)
269
269
  if ext_info is None:
270
270
  return None, None
@@ -364,7 +364,7 @@ def _build_map_type(
364
364
  return None
365
365
 
366
366
 
367
- def _parse_ext_info(in_ext: Any) -> Optional[data_t.ExtInfo]:
367
+ def _parse_ext_info(in_ext: Any) -> Optional[type_info_t.ExtInfo]:
368
368
  if in_ext is None:
369
369
  return None
370
370
  assert isinstance(in_ext, dict)
pkgs/type_spec/util.py CHANGED
@@ -159,7 +159,7 @@ def is_valid_property_name(name: str) -> bool:
159
159
  def check_fields(data: dict[str, T], allowed: list[str]) -> None:
160
160
  for key in data:
161
161
  if key not in allowed:
162
- raise Exception(f"unexpected-field: {key}")
162
+ raise Exception(f"unexpected-field: {key}. Allowed: {allowed}")
163
163
 
164
164
 
165
165
  def split_any_name(name: str) -> list[str]:
@@ -31,9 +31,10 @@ ENDPOINT_PATH = "api/external/uploader/invoke_uploader"
31
31
  )
32
32
  @dataclasses.dataclass(kw_only=True)
33
33
  class Arguments:
34
- file_id: base_t.ObjectId
35
34
  uploader_key: identifier_t.IdentifierKey
36
35
  destination: generic_upload_t.UploadDestination
36
+ file_id: typing.Optional[base_t.ObjectId] = None
37
+ file_ids: typing.Optional[list[base_t.ObjectId]] = None
37
38
 
38
39
 
39
40
  # DO NOT MODIFY -- This file is generated by type_spec
@@ -233,17 +233,20 @@ class AsyncBatchProcessorBase(ABC):
233
233
  def invoke_uploader(
234
234
  self,
235
235
  *,
236
- file_id: base_t.ObjectId,
237
236
  uploader_key: identifier_t.IdentifierKey,
238
237
  destination: generic_upload_t.UploadDestination,
238
+ file_id: typing.Optional[base_t.ObjectId] = None,
239
+ file_ids: typing.Optional[list[base_t.ObjectId]] = None,
239
240
  depends_on: typing.Optional[list[str]] = None,
240
241
  ) -> async_batch_t.QueuedAsyncBatchRequest:
241
242
  """Runs a file through an uploader.
242
243
 
244
+ :param file_id: DEPRECATED: use file_ids
243
245
  :param depends_on: A list of batch reference keys to process before processing this request
244
246
  """
245
247
  args = invoke_uploader_t.Arguments(
246
248
  file_id=file_id,
249
+ file_ids=file_ids,
247
250
  uploader_key=uploader_key,
248
251
  destination=destination,
249
252
  )
@@ -876,15 +876,18 @@ class ClientMethods(ABC):
876
876
  def invoke_uploader(
877
877
  self,
878
878
  *,
879
- file_id: base_t.ObjectId,
880
879
  uploader_key: identifier_t.IdentifierKey,
881
880
  destination: generic_upload_t.UploadDestination,
881
+ file_id: typing.Optional[base_t.ObjectId] = None,
882
+ file_ids: typing.Optional[list[base_t.ObjectId]] = None,
882
883
  ) -> invoke_uploader_t.Data:
883
884
  """Runs a file through an uploader.
884
885
 
886
+ :param file_id: DEPRECATED: use file_ids
885
887
  """
886
888
  args = invoke_uploader_t.Arguments(
887
889
  file_id=file_id,
890
+ file_ids=file_ids,
888
891
  uploader_key=uploader_key,
889
892
  destination=destination,
890
893
  )
@@ -111,6 +111,7 @@ __all__: list[str] = [
111
111
  "recipe_calculation": "Recipe Calculation",
112
112
  "recipe_check": "Experiment Check",
113
113
  "recipe_export": "Recipe Export",
114
+ "recipe_goal": "Experiment Goal",
114
115
  "recipe_ingredient": "Recipe Ingredient",
115
116
  "recipe_ingredient_actual": "Recipe Ingredient Actual",
116
117
  "recipe_ingredients_compounded": "Recipe Ingredients Compounded",
@@ -265,6 +266,7 @@ class EntityType(StrEnum):
265
266
  RECIPE_CALCULATION = "recipe_calculation"
266
267
  RECIPE_CHECK = "recipe_check"
267
268
  RECIPE_EXPORT = "recipe_export"
269
+ RECIPE_GOAL = "recipe_goal"
268
270
  RECIPE_INGREDIENT = "recipe_ingredient"
269
271
  RECIPE_INGREDIENT_ACTUAL = "recipe_ingredient_actual"
270
272
  RECIPE_INGREDIENTS_COMPOUNDED = "recipe_ingredients_compounded"