UncountablePythonSDK 0.0.73__py3-none-any.whl → 0.0.75__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.75
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=PL1h2UvTp1PRMkTXH893ZYS3cPUrrupjnLg_9ndZGDQ,8838
62
+ pkgs/type_spec/emit_typescript_util.py,sha256=e2rGSs9OTD-iXwcHfU4V9E35jwMc5qVshhOKMknGrJ8,10319
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
@@ -105,7 +105,7 @@ uncountable/integration/queue_runner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeu
105
105
  uncountable/integration/queue_runner/job_scheduler.py,sha256=n6bM6ZqVOPD0PoJuZV5Y5tuhmw2gI-_p6JbnVlK42uI,5016
106
106
  uncountable/integration/queue_runner/queue_runner.py,sha256=0BmYu5zHdothTevGsB-nXg6MBd1UD-WkP3h1WCKMdQg,710
107
107
  uncountable/integration/queue_runner/types.py,sha256=8qTq29BTSa5rmW6CBlBntP0pNIiDcwu1wHa78pjroS0,219
108
- uncountable/integration/queue_runner/worker.py,sha256=wS_Mv12MtG9ULz2K4nUdivUAxJdZmd6B6GEUhqysG2A,4403
108
+ uncountable/integration/queue_runner/worker.py,sha256=Bwp7_OD4AughY01QQtZxlmZ5yrDmDk1GuijH09xhUkk,4455
109
109
  uncountable/integration/queue_runner/command_server/__init__.py,sha256=gQPVILGpWzCr2i5GJyoqna7AOSFvtn4tav69gB78mTQ,571
110
110
  uncountable/integration/queue_runner/command_server/command_client.py,sha256=DJb0TUVFkiiLBEQzHSN94sTRnuEbutNEgdN39XmnOXI,2046
111
111
  uncountable/integration/queue_runner/command_server/command_server.py,sha256=yyXryhiEC2eGS0yFElLGsVzSKwOuYvj-zp22jQorkv0,2138
@@ -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=qTifyQNBOwlspjWccQXg4gOhuwyQDy5z77rlreDynBU,67603
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
@@ -204,8 +204,8 @@ uncountable/types/api/batch/execute_batch_load_async.py,sha256=j5a5dk0_lTJ-YslrB
204
204
  uncountable/types/api/chemical/__init__.py,sha256=gCgbynxG3jA8FQHzercKtrHKHkiIKr8APdZYUniAor8,55
205
205
  uncountable/types/api/chemical/convert_chemical_formats.py,sha256=xLpma1W1O9MzgxM4CCl5GPnpj3dpqRHhKcXr3b_ToAo,1589
206
206
  uncountable/types/api/entity/__init__.py,sha256=gCgbynxG3jA8FQHzercKtrHKHkiIKr8APdZYUniAor8,55
207
- uncountable/types/api/entity/create_entities.py,sha256=3zBPO3_CM9fU2R1EFWQcFcXxCVCNNU2NIsnJLYnojzI,2219
208
- uncountable/types/api/entity/create_entity.py,sha256=elMRdi8VT7CTrPhypQsL9P5gvCLlk5_D_1CUxulnXzA,2331
207
+ uncountable/types/api/entity/create_entities.py,sha256=hG81k_nKD4orTtb5jKIkCarkSbUa3fXAxES6IZVkhL0,2276
208
+ uncountable/types/api/entity/create_entity.py,sha256=BV99ZfkxPfMEGD0YvuMizK25Nll0o0GFho8_Mzb0Clk,2388
209
209
  uncountable/types/api/entity/get_entities_data.py,sha256=gTEZ7Z7T-DWP8BZPNDF4c__EHtf9kAb1sGtHmiGOgnM,1454
210
210
  uncountable/types/api/entity/list_entities.py,sha256=ykbdq4DD31uiRz4i8LH-8LLeA2Lpp_5fWfb5fdyx248,2000
211
211
  uncountable/types/api/entity/lock_entity.py,sha256=mMZx2tWOtuYg0sIftdPsFWgZO5LCav2ubqTw97dCtDU,1197
@@ -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.75.dist-info/METADATA,sha256=__fXg5O7uTKPPzAFToY3pOV8EHnNl6uX7UWCNat0E8Y,2051
281
+ UncountablePythonSDK-0.0.75.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
282
+ UncountablePythonSDK-0.0.75.dist-info/top_level.txt,sha256=1UVGjAU-6hJY9qw2iJ7nCBeEwZ793AEN5ZfKX9A1uj4,31
283
+ UncountablePythonSDK-0.0.75.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.1.0)
2
+ Generator: setuptools (75.3.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"
@@ -1,82 +1,21 @@
1
1
  import io
2
2
  import os
3
- from typing import Any
4
3
 
5
4
  from . import builder, util
6
- from .builder import SpecTypeDefnObject
7
5
  from .config import TypeScriptConfig
8
6
  from .emit_io_ts import emit_type_io_ts
9
7
  from .emit_typescript_util import (
10
- INDENT,
11
8
  MODIFY_NOTICE,
12
9
  EmitTypescriptContext,
10
+ emit_namespace_imports_ts,
11
+ emit_type_ts,
12
+ emit_value_ts,
13
+ resolve_namespace_name,
13
14
  resolve_namespace_ref,
14
- ts_name,
15
15
  ts_type_name,
16
16
  )
17
17
 
18
18
 
19
- def ts_enum_name(name: str, name_case: builder.NameCase) -> str:
20
- if name_case == builder.NameCase.js_upper:
21
- return name.upper()
22
- return ts_name(name, name_case)
23
-
24
-
25
- def _resolve_namespace_name(namespace: builder.SpecNamespace) -> str:
26
- return namespace.name
27
-
28
-
29
- def _emit_value(ctx: EmitTypescriptContext, stype: builder.SpecType, value: Any) -> str:
30
- """Mimics emit_python even if not all types are used in TypeScript yet"""
31
- literal = builder.unwrap_literal_type(stype)
32
- if literal is not None:
33
- return _emit_value(ctx, literal.value_type, literal.value)
34
-
35
- if stype.is_base_type(builder.BaseTypeName.s_string):
36
- assert isinstance(value, str)
37
- return util.encode_common_string(value)
38
- elif stype.is_base_type(builder.BaseTypeName.s_integer):
39
- assert isinstance(value, int)
40
- return str(value)
41
- elif stype.is_base_type(builder.BaseTypeName.s_boolean):
42
- assert isinstance(value, bool)
43
- return "true" if value else "false"
44
- elif stype.is_base_type(builder.BaseTypeName.s_lossy_decimal):
45
- return str(value)
46
- elif stype.is_base_type(builder.BaseTypeName.s_decimal):
47
- return f"'{value}'"
48
- elif isinstance(stype, builder.SpecTypeInstance):
49
- if stype.defn_type.is_base_type(builder.BaseTypeName.s_list):
50
- sub_type = stype.parameters[0]
51
- return "[" + ", ".join([_emit_value(ctx, sub_type, x) for x in value]) + "]"
52
-
53
- if stype.defn_type.is_base_type(builder.BaseTypeName.s_dict):
54
- key_type = stype.parameters[0]
55
- value_type = stype.parameters[1]
56
- return (
57
- "{\n\t"
58
- + ",\n\t".join(
59
- "["
60
- + _emit_value(ctx, key_type, dkey)
61
- + "]: "
62
- + _emit_value(ctx, value_type, dvalue)
63
- for dkey, dvalue in value.items()
64
- )
65
- + "\n}"
66
- )
67
-
68
- if stype.defn_type.is_base_type(builder.BaseTypeName.s_optional):
69
- sub_type = stype.parameters[0]
70
- if value is None:
71
- return "null"
72
- return _emit_value(ctx, sub_type, value)
73
-
74
- elif isinstance(stype, builder.SpecTypeDefnStringEnum):
75
- return f"{refer_to(ctx, stype)}.{ts_enum_name(value, stype.name_case)}"
76
-
77
- raise Exception("invalid constant type", value, stype)
78
-
79
-
80
19
  def emit_typescript(builder: builder.SpecBuilder, config: TypeScriptConfig) -> None:
81
20
  _emit_types(builder, config)
82
21
  _emit_id_source(builder, config)
@@ -90,13 +29,11 @@ def _emit_types(builder: builder.SpecBuilder, config: TypeScriptConfig) -> None:
90
29
 
91
30
  for namespace in sorted(
92
31
  builder.namespaces.values(),
93
- key=lambda ns: _resolve_namespace_name(ns),
32
+ key=lambda ns: resolve_namespace_name(ns),
94
33
  ):
95
- ctx = EmitTypescriptContext(
96
- out=io.StringIO(), namespace=namespace, config=config
97
- )
34
+ ctx = EmitTypescriptContext(out=io.StringIO(), namespace=namespace)
98
35
 
99
- _emit_namespace(ctx, namespace)
36
+ _emit_namespace(ctx, config, namespace)
100
37
 
101
38
  prepart = builder.preparts["typescript"].get(namespace.name)
102
39
  part = builder.parts["typescript"].get(namespace.name)
@@ -123,16 +60,7 @@ def _emit_types(builder: builder.SpecBuilder, config: TypeScriptConfig) -> None:
123
60
  full.write(f"// === END section from {namespace.name}.ts.prepart ===\n")
124
61
  full.write("\n")
125
62
 
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
63
+ emit_namespace_imports_ts(ctx.namespaces, out=full, current_namespace=namespace)
136
64
  if namespace.emit_io_ts:
137
65
  full.write("import * as IO from 'io-ts';")
138
66
  full.write(ctx.out.getvalue())
@@ -152,7 +80,7 @@ def _emit_types(builder: builder.SpecBuilder, config: TypeScriptConfig) -> None:
152
80
 
153
81
  if len(namespace.path) == 1:
154
82
  index_out.write(
155
- f"import * as {resolve_namespace_ref(namespace)} from './{_resolve_namespace_name(namespace)}'\n"
83
+ f"import * as {resolve_namespace_ref(namespace)} from './{resolve_namespace_name(namespace)}'\n"
156
84
  ) # noqa: E501
157
85
  index_out_end.write(f"export {{{resolve_namespace_ref(namespace)}}}\n")
158
86
 
@@ -164,23 +92,26 @@ def _emit_types(builder: builder.SpecBuilder, config: TypeScriptConfig) -> None:
164
92
 
165
93
 
166
94
  def _emit_namespace(
167
- ctx: EmitTypescriptContext, namespace: builder.SpecNamespace
95
+ ctx: EmitTypescriptContext,
96
+ config: TypeScriptConfig,
97
+ namespace: builder.SpecNamespace,
168
98
  ) -> None:
169
99
  for stype in namespace.types.values():
170
100
  if namespace.emit_io_ts:
171
101
  emit_type_io_ts(ctx, stype, namespace.derive_types_from_io_ts)
172
102
  if not namespace.emit_io_ts or not namespace.derive_types_from_io_ts:
173
- _emit_type(ctx, stype)
103
+ emit_type_ts(ctx, stype)
174
104
 
175
105
  for sconst in namespace.constants.values():
176
106
  _emit_constant(ctx, sconst)
177
107
 
178
108
  if namespace.endpoint is not None:
179
- _emit_endpoint(ctx, namespace, namespace.endpoint)
109
+ _emit_endpoint(ctx, config, namespace, namespace.endpoint)
180
110
 
181
111
 
182
112
  def _emit_endpoint(
183
113
  ctx: EmitTypescriptContext,
114
+ config: TypeScriptConfig,
184
115
  namespace: builder.SpecNamespace,
185
116
  endpoint: builder.SpecEndpoint,
186
117
  ) -> None:
@@ -265,14 +196,12 @@ export const apiCall = {wrap_call}(
265
196
  )
266
197
  {data_loader_body}"""
267
198
 
268
- output = f"{ctx.config.routes_output}/{"/".join(namespace.path)}.tsx"
199
+ output = f"{config.routes_output}/{"/".join(namespace.path)}.tsx"
269
200
  util.rewrite_file(output, tsx_api)
270
201
 
271
202
  # Hacky index support, until enough is migrated to regen entirely
272
203
  # 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
- )
204
+ index_path = f"{config.routes_output}/{"/".join(namespace.path[0:-1])}/index.tsx"
276
205
  api_name = f"Api{ts_type_name(namespace.path[0 - 1])}"
277
206
  if os.path.exists(index_path):
278
207
  with open(index_path) as index:
@@ -288,164 +217,14 @@ export const apiCall = {wrap_call}(
288
217
  index.write(f"export {{ {api_name} }}\n")
289
218
 
290
219
 
291
- def _emit_type(ctx: EmitTypescriptContext, stype: builder.SpecType) -> None:
292
- if not isinstance(stype, builder.SpecTypeDefn):
293
- return
294
-
295
- if stype.is_base or stype.is_predefined:
296
- return
297
-
298
- ctx.out.write("\n")
299
- ctx.out.write(MODIFY_NOTICE)
300
-
301
- if isinstance(stype, builder.SpecTypeDefnExternal):
302
- assert not stype.is_exported, "expecting private names"
303
- ctx.out.write(stype.external_map["ts"])
304
- ctx.out.write("\n")
305
- return
306
-
307
- assert stype.is_exported, "expecting exported names"
308
- if isinstance(stype, builder.SpecTypeDefnAlias):
309
- ctx.out.write(f"export type {stype.name} = {refer_to(ctx, stype.alias)}\n")
310
- return
311
-
312
- if isinstance(stype, builder.SpecTypeDefnUnion):
313
- ctx.out.write(
314
- f"export type {stype.name} = {refer_to(ctx, stype.get_backing_type())}\n"
315
- )
316
- return
317
-
318
- if isinstance(stype, builder.SpecTypeDefnStringEnum):
319
- ctx.out.write(f"export enum {stype.name} {{\n")
320
- assert stype.values
321
- for name, entry in stype.values.items():
322
- ctx.out.write(
323
- f'{INDENT}{ts_enum_name(name, stype.name_case)} = "{entry.value}",\n'
324
- )
325
- ctx.out.write("}\n")
326
- return
327
-
328
- assert isinstance(stype, builder.SpecTypeDefnObject)
329
- assert stype.base is not None
330
-
331
- base_type = ""
332
- if not stype.base.is_base:
333
- base_type = f"{refer_to(ctx, stype.base)} & "
334
-
335
- if stype.properties is None and base_type == "":
336
- ctx.out.write(f"export type {stype.name} = TEmpty\n")
337
- elif stype.properties is None:
338
- ctx.out.write(f"export type {stype.name} = {base_type}{{}}\n")
339
- else:
340
- if isinstance(stype, SpecTypeDefnObject) and len(stype.parameters) > 0:
341
- full_type_name = f'{stype.name}<{", ".join(stype.parameters)}>'
342
- else:
343
- full_type_name = stype.name
344
- ctx.out.write(f"export type {full_type_name} = {base_type}{{")
345
- ctx.out.write("\n")
346
- for prop in stype.properties.values():
347
- ref_type = refer_to(ctx, prop.spec_type)
348
- prop_name = ts_name(prop.name, prop.name_case)
349
- if prop.has_default and not prop.parse_require:
350
- # For now, we'll assume the generated types with defaults are meant as
351
- # arguments, thus treat like extant==missing
352
- # IMPROVE: if we can decide they are meant as output instead, then
353
- # they should be marked as required
354
- ctx.out.write(f"{INDENT}{prop_name}?: {ref_type}")
355
- elif prop.extant == builder.PropertyExtant.missing:
356
- # Unlike optional below, missing does not imply null is possible. They
357
- # treated distinctly.
358
- ctx.out.write(f"{INDENT}{prop_name}?: {ref_type}")
359
- elif prop.extant == builder.PropertyExtant.optional:
360
- # Need to add in |null since Python side can produce null's right now
361
- # IMPROVE: It would be better if the serializer could instead omit the None's
362
- # Dropping the null should be forward compatible
363
- ctx.out.write(f"{INDENT}{prop_name}?: {ref_type} | null")
364
- else:
365
- ctx.out.write(f"{INDENT}{prop_name}: {ref_type}")
366
- ctx.out.write("\n")
367
- ctx.out.write("}\n")
368
-
369
-
370
220
  def _emit_constant(ctx: EmitTypescriptContext, sconst: builder.SpecConstant) -> None:
371
221
  ctx.out.write("\n\n")
372
222
  ctx.out.write(MODIFY_NOTICE)
373
- value = _emit_value(ctx, sconst.value_type, sconst.value)
223
+ value = emit_value_ts(ctx, sconst.value_type, sconst.value)
374
224
  const_name = sconst.name.upper()
375
225
  ctx.out.write(f"export const {const_name} = {value}\n")
376
226
 
377
227
 
378
- base_name_map = {
379
- builder.BaseTypeName.s_boolean: "boolean",
380
- builder.BaseTypeName.s_date: "string", # IMPROVE: Aliased DateStr
381
- builder.BaseTypeName.s_date_time: "string", # IMPROVE: Aliased DateTimeStr
382
- # Decimal's are marked as to_string_values thus are strings in the front-end
383
- builder.BaseTypeName.s_decimal: "string",
384
- builder.BaseTypeName.s_dict: "PartialRecord",
385
- builder.BaseTypeName.s_integer: "number",
386
- builder.BaseTypeName.s_lossy_decimal: "number",
387
- builder.BaseTypeName.s_opaque_key: "string",
388
- builder.BaseTypeName.s_none: "null",
389
- builder.BaseTypeName.s_string: "string",
390
- # UNC: global types
391
- builder.BaseTypeName.s_json_value: "JsonValue",
392
- }
393
-
394
-
395
- def refer_to(ctx: EmitTypescriptContext, stype: builder.SpecType) -> str:
396
- return refer_to_impl(ctx, stype)[0]
397
-
398
-
399
- def refer_to_impl(
400
- ctx: EmitTypescriptContext, stype: builder.SpecType
401
- ) -> tuple[str, bool]:
402
- """
403
- @return (string-specific, multiple-types)
404
- """
405
- if isinstance(stype, builder.SpecTypeInstance):
406
- if stype.defn_type.name == builder.BaseTypeName.s_list:
407
- spec, multi = refer_to_impl(ctx, stype.parameters[0])
408
- return f"({spec})[]" if multi else f"{spec}[]", False
409
- if stype.defn_type.name == builder.BaseTypeName.s_readonly_array:
410
- spec, multi = refer_to_impl(ctx, stype.parameters[0])
411
- return f"readonly ({spec})[]" if multi else f"readonly {spec}[]", False
412
- if stype.defn_type.name == builder.BaseTypeName.s_union:
413
- return (
414
- f'({" | ".join([refer_to(ctx, p) for p in stype.parameters])})',
415
- False,
416
- )
417
- if stype.defn_type.name == builder.BaseTypeName.s_literal:
418
- parts = []
419
- for parameter in stype.parameters:
420
- assert isinstance(parameter, builder.SpecTypeLiteralWrapper)
421
- parts.append(refer_to(ctx, parameter))
422
- return f'({" | ".join(parts)})', False
423
- if stype.defn_type.name == builder.BaseTypeName.s_optional:
424
- return f"{refer_to(ctx, stype.parameters[0])} | null", True
425
- if stype.defn_type.name == builder.BaseTypeName.s_tuple:
426
- return f"[{", ".join([refer_to(ctx, p) for p in stype.parameters])}]", False
427
- params = ", ".join([refer_to(ctx, p) for p in stype.parameters])
428
- return f"{refer_to(ctx, stype.defn_type)}<{params}>", False
429
-
430
- if isinstance(stype, builder.SpecTypeLiteralWrapper):
431
- return _emit_value(ctx, stype.value_type, stype.value), False
432
-
433
- if isinstance(stype, builder.SpecTypeGenericParameter):
434
- return stype.name, False
435
-
436
- assert isinstance(stype, builder.SpecTypeDefn)
437
- if stype.is_base: # assume correct namespace
438
- if stype.name == builder.BaseTypeName.s_list:
439
- return "any[]", False # TODO: generic type
440
- return base_name_map[builder.BaseTypeName(stype.name)], False
441
-
442
- if stype.namespace == ctx.namespace:
443
- return stype.name, False
444
-
445
- ctx.namespaces.add(stype.namespace)
446
- return f"{resolve_namespace_ref(stype.namespace)}.{stype.name}", False
447
-
448
-
449
228
  def _emit_id_source(builder: builder.SpecBuilder, config: TypeScriptConfig) -> None:
450
229
  id_source_output = config.id_source_output
451
230
  if id_source_output is None:
@@ -1,17 +1,33 @@
1
1
  import io
2
+ import typing
2
3
  from dataclasses import dataclass, field
3
4
 
4
5
  from . import builder, util
5
- from .config import TypeScriptConfig
6
6
 
7
7
  INDENT = " "
8
8
 
9
9
  MODIFY_NOTICE = "// DO NOT MODIFY -- This file is generated by type_spec\n"
10
10
 
11
11
 
12
+ base_name_map = {
13
+ builder.BaseTypeName.s_boolean: "boolean",
14
+ builder.BaseTypeName.s_date: "string", # IMPROVE: Aliased DateStr
15
+ builder.BaseTypeName.s_date_time: "string", # IMPROVE: Aliased DateTimeStr
16
+ # Decimal's are marked as to_string_values thus are strings in the front-end
17
+ builder.BaseTypeName.s_decimal: "string",
18
+ builder.BaseTypeName.s_dict: "PartialRecord",
19
+ builder.BaseTypeName.s_integer: "number",
20
+ builder.BaseTypeName.s_lossy_decimal: "number",
21
+ builder.BaseTypeName.s_opaque_key: "string",
22
+ builder.BaseTypeName.s_none: "null",
23
+ builder.BaseTypeName.s_string: "string",
24
+ # UNC: global types
25
+ builder.BaseTypeName.s_json_value: "JsonValue",
26
+ }
27
+
28
+
12
29
  @dataclass(kw_only=True)
13
30
  class EmitTypescriptContext:
14
- config: TypeScriptConfig
15
31
  out: io.StringIO
16
32
  namespace: builder.SpecNamespace
17
33
  namespaces: set[builder.SpecNamespace] = field(default_factory=set)
@@ -30,3 +46,220 @@ def ts_name(name: str, name_case: builder.NameCase) -> str:
30
46
  return name
31
47
  bits = util.split_any_name(name)
32
48
  return "".join([bits[0], *[x.title() for x in bits[1:]]])
49
+
50
+
51
+ def emit_value_ts(
52
+ ctx: EmitTypescriptContext, stype: builder.SpecType, value: typing.Any
53
+ ) -> str:
54
+ """Mimics emit_python even if not all types are used in TypeScript yet"""
55
+ literal = builder.unwrap_literal_type(stype)
56
+ if literal is not None:
57
+ return emit_value_ts(ctx, literal.value_type, literal.value)
58
+
59
+ if stype.is_base_type(builder.BaseTypeName.s_string):
60
+ assert isinstance(value, str)
61
+ return util.encode_common_string(value)
62
+ elif stype.is_base_type(builder.BaseTypeName.s_integer):
63
+ assert isinstance(value, int)
64
+ return str(value)
65
+ elif stype.is_base_type(builder.BaseTypeName.s_boolean):
66
+ assert isinstance(value, bool)
67
+ return "true" if value else "false"
68
+ elif stype.is_base_type(builder.BaseTypeName.s_lossy_decimal):
69
+ return str(value)
70
+ elif stype.is_base_type(builder.BaseTypeName.s_decimal):
71
+ return f"'{value}'"
72
+ elif isinstance(stype, builder.SpecTypeInstance):
73
+ if stype.defn_type.is_base_type(builder.BaseTypeName.s_list):
74
+ sub_type = stype.parameters[0]
75
+ return (
76
+ "[" + ", ".join([emit_value_ts(ctx, sub_type, x) for x in value]) + "]"
77
+ )
78
+
79
+ if stype.defn_type.is_base_type(builder.BaseTypeName.s_dict):
80
+ key_type = stype.parameters[0]
81
+ value_type = stype.parameters[1]
82
+ return (
83
+ "{\n\t"
84
+ + ",\n\t".join(
85
+ "["
86
+ + emit_value_ts(ctx, key_type, dkey)
87
+ + "]: "
88
+ + emit_value_ts(ctx, value_type, dvalue)
89
+ for dkey, dvalue in value.items()
90
+ )
91
+ + "\n}"
92
+ )
93
+
94
+ if stype.defn_type.is_base_type(builder.BaseTypeName.s_optional):
95
+ sub_type = stype.parameters[0]
96
+ if value is None:
97
+ return "null"
98
+ return emit_value_ts(ctx, sub_type, value)
99
+
100
+ elif isinstance(stype, builder.SpecTypeDefnStringEnum):
101
+ return f"{refer_to(ctx, stype)}.{ts_enum_name(value, stype.name_case)}"
102
+
103
+ raise Exception("invalid constant type", value, stype)
104
+
105
+
106
+ def emit_type_ts(ctx: EmitTypescriptContext, stype: builder.SpecType) -> None:
107
+ if not isinstance(stype, builder.SpecTypeDefn):
108
+ return
109
+
110
+ if stype.is_base or stype.is_predefined:
111
+ return
112
+
113
+ ctx.out.write("\n")
114
+ ctx.out.write(MODIFY_NOTICE)
115
+
116
+ if isinstance(stype, builder.SpecTypeDefnExternal):
117
+ assert not stype.is_exported, "expecting private names"
118
+ ctx.out.write(stype.external_map["ts"])
119
+ ctx.out.write("\n")
120
+ return
121
+
122
+ assert stype.is_exported, "expecting exported names"
123
+ if isinstance(stype, builder.SpecTypeDefnAlias):
124
+ ctx.out.write(f"export type {stype.name} = {refer_to(ctx, stype.alias)}\n")
125
+ return
126
+
127
+ if isinstance(stype, builder.SpecTypeDefnUnion):
128
+ ctx.out.write(
129
+ f"export type {stype.name} = {refer_to(ctx, stype.get_backing_type())}\n"
130
+ )
131
+ return
132
+
133
+ if isinstance(stype, builder.SpecTypeDefnStringEnum):
134
+ ctx.out.write(f"export enum {stype.name} {{\n")
135
+ assert stype.values
136
+ for name, entry in stype.values.items():
137
+ ctx.out.write(
138
+ f'{INDENT}{ts_enum_name(name, stype.name_case)} = "{entry.value}",\n'
139
+ )
140
+ ctx.out.write("}\n")
141
+ return
142
+
143
+ assert isinstance(stype, builder.SpecTypeDefnObject)
144
+ assert stype.base is not None
145
+
146
+ base_type = ""
147
+ if not stype.base.is_base:
148
+ base_type = f"{refer_to(ctx, stype.base)} & "
149
+
150
+ if stype.properties is None and base_type == "":
151
+ ctx.out.write(f"export type {stype.name} = TEmpty\n")
152
+ elif stype.properties is None:
153
+ ctx.out.write(f"export type {stype.name} = {base_type}{{}}\n")
154
+ else:
155
+ if isinstance(stype, builder.SpecTypeDefnObject) and len(stype.parameters) > 0:
156
+ full_type_name = f'{stype.name}<{", ".join(stype.parameters)}>'
157
+ else:
158
+ full_type_name = stype.name
159
+ ctx.out.write(f"export type {full_type_name} = {base_type}{{")
160
+ ctx.out.write("\n")
161
+ for prop in stype.properties.values():
162
+ ref_type = refer_to(ctx, prop.spec_type)
163
+ prop_name = ts_name(prop.name, prop.name_case)
164
+ if prop.has_default and not prop.parse_require:
165
+ # For now, we'll assume the generated types with defaults are meant as
166
+ # arguments, thus treat like extant==missing
167
+ # IMPROVE: if we can decide they are meant as output instead, then
168
+ # they should be marked as required
169
+ ctx.out.write(f"{INDENT}{prop_name}?: {ref_type}")
170
+ elif prop.extant == builder.PropertyExtant.missing:
171
+ # Unlike optional below, missing does not imply null is possible. They
172
+ # treated distinctly.
173
+ ctx.out.write(f"{INDENT}{prop_name}?: {ref_type}")
174
+ elif prop.extant == builder.PropertyExtant.optional:
175
+ # Need to add in |null since Python side can produce null's right now
176
+ # IMPROVE: It would be better if the serializer could instead omit the None's
177
+ # Dropping the null should be forward compatible
178
+ ctx.out.write(f"{INDENT}{prop_name}?: {ref_type} | null")
179
+ else:
180
+ ctx.out.write(f"{INDENT}{prop_name}: {ref_type}")
181
+ ctx.out.write("\n")
182
+ ctx.out.write("}\n")
183
+
184
+
185
+ def refer_to(ctx: EmitTypescriptContext, stype: builder.SpecType) -> str:
186
+ return refer_to_impl(ctx, stype)[0]
187
+
188
+
189
+ def refer_to_impl(
190
+ ctx: EmitTypescriptContext, stype: builder.SpecType
191
+ ) -> tuple[str, bool]:
192
+ """
193
+ @return (string-specific, multiple-types)
194
+ """
195
+ if isinstance(stype, builder.SpecTypeInstance):
196
+ if stype.defn_type.name == builder.BaseTypeName.s_list:
197
+ spec, multi = refer_to_impl(ctx, stype.parameters[0])
198
+ return f"({spec})[]" if multi else f"{spec}[]", False
199
+ if stype.defn_type.name == builder.BaseTypeName.s_readonly_array:
200
+ spec, multi = refer_to_impl(ctx, stype.parameters[0])
201
+ return f"readonly ({spec})[]" if multi else f"readonly {spec}[]", False
202
+ if stype.defn_type.name == builder.BaseTypeName.s_union:
203
+ return (
204
+ f'({" | ".join([refer_to(ctx, p) for p in stype.parameters])})',
205
+ False,
206
+ )
207
+ if stype.defn_type.name == builder.BaseTypeName.s_literal:
208
+ parts = []
209
+ for parameter in stype.parameters:
210
+ assert isinstance(parameter, builder.SpecTypeLiteralWrapper)
211
+ parts.append(refer_to(ctx, parameter))
212
+ return f'({" | ".join(parts)})', False
213
+ if stype.defn_type.name == builder.BaseTypeName.s_optional:
214
+ return f"{refer_to(ctx, stype.parameters[0])} | null", True
215
+ if stype.defn_type.name == builder.BaseTypeName.s_tuple:
216
+ return f"[{", ".join([refer_to(ctx, p) for p in stype.parameters])}]", False
217
+ params = ", ".join([refer_to(ctx, p) for p in stype.parameters])
218
+ return f"{refer_to(ctx, stype.defn_type)}<{params}>", False
219
+
220
+ if isinstance(stype, builder.SpecTypeLiteralWrapper):
221
+ return emit_value_ts(ctx, stype.value_type, stype.value), False
222
+
223
+ if isinstance(stype, builder.SpecTypeGenericParameter):
224
+ return stype.name, False
225
+
226
+ assert isinstance(stype, builder.SpecTypeDefn)
227
+ if stype.is_base: # assume correct namespace
228
+ if stype.name == builder.BaseTypeName.s_list:
229
+ return "any[]", False # TODO: generic type
230
+ return base_name_map[builder.BaseTypeName(stype.name)], False
231
+
232
+ if stype.namespace == ctx.namespace:
233
+ return stype.name, False
234
+
235
+ ctx.namespaces.add(stype.namespace)
236
+ return f"{resolve_namespace_ref(stype.namespace)}.{stype.name}", False
237
+
238
+
239
+ def ts_enum_name(name: str, name_case: builder.NameCase) -> str:
240
+ if name_case == builder.NameCase.js_upper:
241
+ return name.upper()
242
+ return ts_name(name, name_case)
243
+
244
+
245
+ def resolve_namespace_name(namespace: builder.SpecNamespace) -> str:
246
+ return namespace.name
247
+
248
+
249
+ def emit_namespace_imports_ts(
250
+ namespaces: set[builder.SpecNamespace],
251
+ out: io.StringIO,
252
+ current_namespace: builder.SpecNamespace,
253
+ ) -> None:
254
+ for ns in sorted(
255
+ namespaces,
256
+ key=lambda name: resolve_namespace_name(name),
257
+ ):
258
+ import_as = resolve_namespace_ref(ns)
259
+ import_path = (
260
+ "./"
261
+ if len(current_namespace.path) == 1
262
+ else "../" * (len(current_namespace.path) - 1)
263
+ )
264
+ import_from = f"{import_path}{resolve_namespace_name(ns)}"
265
+ out.write(f'import * as {import_as} from "{import_from}"\n') # noqa: E501
@@ -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]:
@@ -86,23 +86,23 @@ def run_queued_job(
86
86
  profile_metadata=job_details.profile_metadata,
87
87
  job_definition=job_details.job_definition,
88
88
  )
89
- client = construct_uncountable_client(
90
- profile_meta=job_details.profile_metadata, job_logger=job_logger
91
- )
92
- batch_processor = AsyncBatchProcessor(client=client)
89
+ try:
90
+ client = construct_uncountable_client(
91
+ profile_meta=job_details.profile_metadata, job_logger=job_logger
92
+ )
93
+ batch_processor = AsyncBatchProcessor(client=client)
93
94
 
94
- payload = _resolve_queued_job_payload(queued_job)
95
+ payload = _resolve_queued_job_payload(queued_job)
95
96
 
96
- args = JobArguments(
97
- job_definition=job_details.job_definition,
98
- client=client,
99
- batch_processor=batch_processor,
100
- profile_metadata=job_details.profile_metadata,
101
- logger=job_logger,
102
- payload=payload,
103
- )
97
+ args = JobArguments(
98
+ job_definition=job_details.job_definition,
99
+ client=client,
100
+ batch_processor=batch_processor,
101
+ profile_metadata=job_details.profile_metadata,
102
+ logger=job_logger,
103
+ payload=payload,
104
+ )
104
105
 
105
- try:
106
106
  return execute_job(
107
107
  args=args,
108
108
  profile_metadata=job_details.profile_metadata,
@@ -41,7 +41,7 @@ class EntityToCreate:
41
41
  @dataclasses.dataclass(kw_only=True)
42
42
  class Arguments:
43
43
  definition_id: base_t.ObjectId
44
- entity_type: typing.Union[typing.Literal[entity_t.EntityType.LAB_REQUEST], typing.Literal[entity_t.EntityType.APPROVAL], typing.Literal[entity_t.EntityType.CUSTOM_ENTITY], typing.Literal[entity_t.EntityType.INVENTORY_AMOUNT], typing.Literal[entity_t.EntityType.TASK], typing.Literal[entity_t.EntityType.PROJECT], typing.Literal[entity_t.EntityType.EQUIPMENT], typing.Literal[entity_t.EntityType.INV_LOCAL_LOCATIONS], typing.Literal[entity_t.EntityType.FIELD_OPTION_SET], typing.Literal[entity_t.EntityType.WEBHOOK], typing.Literal[entity_t.EntityType.SPECS], typing.Literal[entity_t.EntityType.GOAL], typing.Literal[entity_t.EntityType.INGREDIENT_TAG_MAP], typing.Literal[entity_t.EntityType.INGREDIENT_TAG], typing.Literal[entity_t.EntityType.OUTPUT]]
44
+ entity_type: typing.Union[typing.Literal[entity_t.EntityType.LAB_REQUEST], typing.Literal[entity_t.EntityType.APPROVAL], typing.Literal[entity_t.EntityType.CUSTOM_ENTITY], typing.Literal[entity_t.EntityType.INVENTORY_AMOUNT], typing.Literal[entity_t.EntityType.TASK], typing.Literal[entity_t.EntityType.PROJECT], typing.Literal[entity_t.EntityType.EQUIPMENT], typing.Literal[entity_t.EntityType.INV_LOCAL_LOCATIONS], typing.Literal[entity_t.EntityType.FIELD_OPTION_SET], typing.Literal[entity_t.EntityType.WEBHOOK], typing.Literal[entity_t.EntityType.SPECS], typing.Literal[entity_t.EntityType.GOAL], typing.Literal[entity_t.EntityType.INGREDIENT_TAG_MAP], typing.Literal[entity_t.EntityType.INGREDIENT_TAG], typing.Literal[entity_t.EntityType.CONDITION_PARAMETER], typing.Literal[entity_t.EntityType.OUTPUT]]
45
45
  entities_to_create: list[EntityToCreate]
46
46
 
47
47
 
@@ -44,7 +44,7 @@ class EntityFieldInitialValue:
44
44
  @dataclasses.dataclass(kw_only=True)
45
45
  class Arguments:
46
46
  definition_id: base_t.ObjectId
47
- entity_type: typing.Union[typing.Literal[entity_t.EntityType.LAB_REQUEST], typing.Literal[entity_t.EntityType.APPROVAL], typing.Literal[entity_t.EntityType.CUSTOM_ENTITY], typing.Literal[entity_t.EntityType.INVENTORY_AMOUNT], typing.Literal[entity_t.EntityType.TASK], typing.Literal[entity_t.EntityType.PROJECT], typing.Literal[entity_t.EntityType.EQUIPMENT], typing.Literal[entity_t.EntityType.INV_LOCAL_LOCATIONS], typing.Literal[entity_t.EntityType.FIELD_OPTION_SET], typing.Literal[entity_t.EntityType.WEBHOOK], typing.Literal[entity_t.EntityType.SPECS], typing.Literal[entity_t.EntityType.GOAL], typing.Literal[entity_t.EntityType.INGREDIENT_TAG_MAP], typing.Literal[entity_t.EntityType.INGREDIENT_TAG], typing.Literal[entity_t.EntityType.OUTPUT]]
47
+ entity_type: typing.Union[typing.Literal[entity_t.EntityType.LAB_REQUEST], typing.Literal[entity_t.EntityType.APPROVAL], typing.Literal[entity_t.EntityType.CUSTOM_ENTITY], typing.Literal[entity_t.EntityType.INVENTORY_AMOUNT], typing.Literal[entity_t.EntityType.TASK], typing.Literal[entity_t.EntityType.PROJECT], typing.Literal[entity_t.EntityType.EQUIPMENT], typing.Literal[entity_t.EntityType.INV_LOCAL_LOCATIONS], typing.Literal[entity_t.EntityType.FIELD_OPTION_SET], typing.Literal[entity_t.EntityType.WEBHOOK], typing.Literal[entity_t.EntityType.SPECS], typing.Literal[entity_t.EntityType.GOAL], typing.Literal[entity_t.EntityType.INGREDIENT_TAG_MAP], typing.Literal[entity_t.EntityType.INGREDIENT_TAG], typing.Literal[entity_t.EntityType.CONDITION_PARAMETER], typing.Literal[entity_t.EntityType.OUTPUT]]
48
48
  field_values: typing.Optional[typing.Optional[list[field_values_t.FieldRefNameValue]]] = None
49
49
 
50
50
 
@@ -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
  )
@@ -257,7 +257,7 @@ class ClientMethods(ABC):
257
257
  self,
258
258
  *,
259
259
  definition_id: base_t.ObjectId,
260
- entity_type: typing.Union[typing.Literal[entity_t.EntityType.LAB_REQUEST], typing.Literal[entity_t.EntityType.APPROVAL], typing.Literal[entity_t.EntityType.CUSTOM_ENTITY], typing.Literal[entity_t.EntityType.INVENTORY_AMOUNT], typing.Literal[entity_t.EntityType.TASK], typing.Literal[entity_t.EntityType.PROJECT], typing.Literal[entity_t.EntityType.EQUIPMENT], typing.Literal[entity_t.EntityType.INV_LOCAL_LOCATIONS], typing.Literal[entity_t.EntityType.FIELD_OPTION_SET], typing.Literal[entity_t.EntityType.WEBHOOK], typing.Literal[entity_t.EntityType.SPECS], typing.Literal[entity_t.EntityType.GOAL], typing.Literal[entity_t.EntityType.INGREDIENT_TAG_MAP], typing.Literal[entity_t.EntityType.INGREDIENT_TAG], typing.Literal[entity_t.EntityType.OUTPUT]],
260
+ entity_type: typing.Union[typing.Literal[entity_t.EntityType.LAB_REQUEST], typing.Literal[entity_t.EntityType.APPROVAL], typing.Literal[entity_t.EntityType.CUSTOM_ENTITY], typing.Literal[entity_t.EntityType.INVENTORY_AMOUNT], typing.Literal[entity_t.EntityType.TASK], typing.Literal[entity_t.EntityType.PROJECT], typing.Literal[entity_t.EntityType.EQUIPMENT], typing.Literal[entity_t.EntityType.INV_LOCAL_LOCATIONS], typing.Literal[entity_t.EntityType.FIELD_OPTION_SET], typing.Literal[entity_t.EntityType.WEBHOOK], typing.Literal[entity_t.EntityType.SPECS], typing.Literal[entity_t.EntityType.GOAL], typing.Literal[entity_t.EntityType.INGREDIENT_TAG_MAP], typing.Literal[entity_t.EntityType.INGREDIENT_TAG], typing.Literal[entity_t.EntityType.CONDITION_PARAMETER], typing.Literal[entity_t.EntityType.OUTPUT]],
261
261
  entities_to_create: list[create_entities_t.EntityToCreate],
262
262
  ) -> create_entities_t.Data:
263
263
  """Creates new Uncountable entities
@@ -282,7 +282,7 @@ class ClientMethods(ABC):
282
282
  self,
283
283
  *,
284
284
  definition_id: base_t.ObjectId,
285
- entity_type: typing.Union[typing.Literal[entity_t.EntityType.LAB_REQUEST], typing.Literal[entity_t.EntityType.APPROVAL], typing.Literal[entity_t.EntityType.CUSTOM_ENTITY], typing.Literal[entity_t.EntityType.INVENTORY_AMOUNT], typing.Literal[entity_t.EntityType.TASK], typing.Literal[entity_t.EntityType.PROJECT], typing.Literal[entity_t.EntityType.EQUIPMENT], typing.Literal[entity_t.EntityType.INV_LOCAL_LOCATIONS], typing.Literal[entity_t.EntityType.FIELD_OPTION_SET], typing.Literal[entity_t.EntityType.WEBHOOK], typing.Literal[entity_t.EntityType.SPECS], typing.Literal[entity_t.EntityType.GOAL], typing.Literal[entity_t.EntityType.INGREDIENT_TAG_MAP], typing.Literal[entity_t.EntityType.INGREDIENT_TAG], typing.Literal[entity_t.EntityType.OUTPUT]],
285
+ entity_type: typing.Union[typing.Literal[entity_t.EntityType.LAB_REQUEST], typing.Literal[entity_t.EntityType.APPROVAL], typing.Literal[entity_t.EntityType.CUSTOM_ENTITY], typing.Literal[entity_t.EntityType.INVENTORY_AMOUNT], typing.Literal[entity_t.EntityType.TASK], typing.Literal[entity_t.EntityType.PROJECT], typing.Literal[entity_t.EntityType.EQUIPMENT], typing.Literal[entity_t.EntityType.INV_LOCAL_LOCATIONS], typing.Literal[entity_t.EntityType.FIELD_OPTION_SET], typing.Literal[entity_t.EntityType.WEBHOOK], typing.Literal[entity_t.EntityType.SPECS], typing.Literal[entity_t.EntityType.GOAL], typing.Literal[entity_t.EntityType.INGREDIENT_TAG_MAP], typing.Literal[entity_t.EntityType.INGREDIENT_TAG], typing.Literal[entity_t.EntityType.CONDITION_PARAMETER], typing.Literal[entity_t.EntityType.OUTPUT]],
286
286
  field_values: typing.Optional[typing.Optional[list[field_values_t.FieldRefNameValue]]] = None,
287
287
  ) -> create_entity_t.Data:
288
288
  """Creates a new Uncountable entity
@@ -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"