UncountablePythonSDK 0.0.33__py3-none-any.whl → 0.0.35__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.33
3
+ Version: 0.0.35
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
@@ -16,6 +16,7 @@ docs/static/favicons/mstile-150x150.png,sha256=eAK4QdEofhdLtfmjuPTpnX3MJqYnvGXsH
16
16
  docs/static/favicons/safari-pinned-tab.svg,sha256=S84fRnz0ZxLnQrKtmmFZytiRyu1xLtMR_RVy5jmwU7k,1926
17
17
  examples/async_batch.py,sha256=CffQ8O9drJ-Mdd6S5DnMIOBsHv5aVkTZrD3l3xBnB4s,1094
18
18
  examples/create_entity.py,sha256=noZdtJ5f9Wfiob3zUH-8bDVbrCPJnFtXFk_W9pSjvUA,664
19
+ examples/edit_recipe_inputs.py,sha256=f1zpK2T00JNQdbh7R01Sns1D_3TTgj-u8-YInoczRX8,1541
19
20
  examples/upload_files.py,sha256=tUfKFqiqwnw08OL5Y8_e4j5pSRhp94cFex8XTuVa_ig,487
20
21
  pkgs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
22
  pkgs/argument_parser/__init__.py,sha256=CsQ6QoPKSLLRVl-z6URAmPkiUL9ZPZoV4rJHgy_-RjA,385
@@ -31,7 +32,7 @@ pkgs/serialization/serial_union.py,sha256=z8Ptj4bVHyb1ROfg0UPTwZ6Ef6iXLr0YJfAH5o
31
32
  pkgs/serialization_util/__init__.py,sha256=MVKqHTUl2YnWZAFG9xCxu1SgmkQ5xPofrAGlYg6h7rI,330
32
33
  pkgs/serialization_util/_get_type_for_serialization.py,sha256=dW5_W9MFd6wgWfW5qlWork-GBb-QFLtiOZkjk2Zqn2M,1177
33
34
  pkgs/serialization_util/convert_to_snakecase.py,sha256=H2BAo5ZdcCDN77RpLb-uP0s7-FQ5Ukwnsd3VYc1vD0M,583
34
- pkgs/serialization_util/serialization_helpers.py,sha256=byotZo00SVREW_aM0sbKQEzuI7O3zwjk_nB_OQ6S5M8,4999
35
+ pkgs/serialization_util/serialization_helpers.py,sha256=B8W82-KT10nQYmDk5uhAqB5QNS-dsxzXMFhtTmooMqw,6365
35
36
  pkgs/strenum_compat/__init__.py,sha256=wXRFeNvBm8RU6dy1PFJ5sRLgUIEeH_DVR95Sv5qpGbk,59
36
37
  pkgs/strenum_compat/strenum_compat.py,sha256=uOUAgpYTjHs1MX8dG81jRlyTkt3KNbkV_25zp7xTX2s,36
37
38
  pkgs/type_spec/__init__.py,sha256=h5DmJTca4QVV10sZR1x0-MlkZfuGYDfapR3zHvXfzto,19
@@ -43,7 +44,7 @@ pkgs/type_spec/emit_open_api.py,sha256=Aw7Ct1itmAqhb_nsM9yDz87EoF0XWHM56MhKqtOLO
43
44
  pkgs/type_spec/emit_open_api_util.py,sha256=XAA6zH59aZWLVl0BvKAICXXl4sdBqx01QAtv5oB0bMI,2266
44
45
  pkgs/type_spec/emit_python.py,sha256=zP3AWJ5u0vzDcnvzSehCUgvXM0J9ZUtfLBVHerW6_wI,45164
45
46
  pkgs/type_spec/emit_typescript.py,sha256=cdr5h8N70PuwORcvhURUujzwH9r1LVwJB8V2EoipGkw,17917
46
- pkgs/type_spec/emit_typescript_util.py,sha256=93FzJnpYse4PKFzgdw4DGV4zFTi5tF4WR-CIi7cW498,873
47
+ pkgs/type_spec/emit_typescript_util.py,sha256=sR7ys3Ilnh6SQiXJbfYk4pxfOu0bDjbUFTEYEW-ud6c,863
47
48
  pkgs/type_spec/load_types.py,sha256=xEHwdB_miR3vNs161Oy1luafE0VC-yk9-utAyCJmbEo,3629
48
49
  pkgs/type_spec/open_api_util.py,sha256=IGh-_snGPST_P_8FdYtO8MTEa9PUxRW6Rzg9X9EgQik,7114
49
50
  pkgs/type_spec/test.py,sha256=4ueujBq-pEgnX3Z69HyPmD-bullFXmpixcpVzfOkhP4,489
@@ -78,13 +79,13 @@ uncountable/integration/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
78
79
  uncountable/integration/db/connect.py,sha256=YtQHJ1DBGPhxKFRCfiXqohOYUceKSxMVOJ88aPI48Ug,181
79
80
  uncountable/integration/executors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
80
81
  uncountable/integration/executors/script_executor.py,sha256=hM8E-aU8zyM6ZcBtqAqInKZ_BF93RLqEA0dU7y5FhWQ,841
81
- uncountable/types/__init__.py,sha256=0UllKeQhqDy1AD5UH6tMPx0QqIuW7KFlVe1SnJkD3ro,7197
82
+ uncountable/types/__init__.py,sha256=fTUKIaZddDoFku6qycdhJkOZIBCypDGlmSWF8PV0WbA,7404
82
83
  uncountable/types/async_batch.py,sha256=aWiul1fK3-cXaCESUUJ_92FF-NuuwxSjn9n3jC9vY5o,1618
83
84
  uncountable/types/async_batch_processor.py,sha256=eNXKPOh-sCiarPvreLDEu4XbieWLe_5KXmiY984eZ5I,6083
84
85
  uncountable/types/base.py,sha256=w3BRf8SAvYPlKrcJtJcQ_WhCU3A9zy0VuRTRWRFKVUA,2709
85
86
  uncountable/types/calculations.py,sha256=16J-KKMp-I8ZQUkYNmKCHfAn6DGb99cFinALcDIdGHY,562
86
87
  uncountable/types/chemical_structure.py,sha256=zQKl53DGtQQONIUHFXuwjWLQaG7FPZY7x6SBSOzkGV0,758
87
- uncountable/types/client_base.py,sha256=UpIXr8JvrOuA9TNHeU1paqTNiE4KHsSpzA6RCAS7bO8,58694
88
+ uncountable/types/client_base.py,sha256=nDxjHLF6F4Z4ZQWPdEEIX9JKw9A4NbTjYekNM-Vu0ys,60351
88
89
  uncountable/types/curves.py,sha256=qYyRntMmFNonEwTrGhquMLbgMqjyP1moQflNTP0FMec,1308
89
90
  uncountable/types/entity.py,sha256=NjMZrqBwQ7sZe_oUuJqy9IEG7dWZmFMkQQXJ0_odcnA,11637
90
91
  uncountable/types/experiment_groups.py,sha256=ZBEk06F4n98Jz3oEA09WaDmw5rqPs7iVAm_Ysr4gc_o,599
@@ -120,7 +121,7 @@ uncountable/types/api/entity/__init__.py,sha256=gCgbynxG3jA8FQHzercKtrHKHkiIKr8A
120
121
  uncountable/types/api/entity/create_entities.py,sha256=vzo5hS1qcmjQdfyCMarSu8MRcRGSiholOVSCfjXlA1k,1703
121
122
  uncountable/types/api/entity/create_entity.py,sha256=ausozCQ3qPM9YUQ87bOTCKOm-zkhn4CSLJr9jLc9n2U,1873
122
123
  uncountable/types/api/entity/get_entities_data.py,sha256=XjrJGZucIn1TYUlDLRnRA0JTQw-vXHIAT-m0H9hk37A,1170
123
- uncountable/types/api/entity/list_entities.py,sha256=Bd_Ppy2bzWve8RbplyMkEHkg07CxEIskeiTdb9Ntlfo,1673
124
+ uncountable/types/api/entity/list_entities.py,sha256=nsOs5qsHNJFBDH1UK1Vk2IJGPY5rx764Xnn-FFLUg_A,1697
124
125
  uncountable/types/api/entity/lock_entity.py,sha256=SbRaKDbJfoPD9uVYiGlnrsPF_HZ_6m0hPAlalZwGyag,1029
125
126
  uncountable/types/api/entity/resolve_entity_ids.py,sha256=AidGpPmI9ATDv0E7vd9LDOl3n3beGxUlRojh5uZrkl4,1086
126
127
  uncountable/types/api/entity/set_values.py,sha256=LcYrKQm5ItYLK1Vx7rRq5i6jkMLDhfEBhF0FD1GowQs,958
@@ -130,7 +131,7 @@ uncountable/types/api/field_options/__init__.py,sha256=gCgbynxG3jA8FQHzercKtrHKH
130
131
  uncountable/types/api/field_options/upsert_field_options.py,sha256=xYtC68AabmTrYn_yV19C91yZv9tfohaRxmvCjMQ5vy8,1144
131
132
  uncountable/types/api/id_source/__init__.py,sha256=gCgbynxG3jA8FQHzercKtrHKHkiIKr8APdZYUniAor8,55
132
133
  uncountable/types/api/id_source/list_id_source.py,sha256=S_NdAd1FIgMtCfpRK9bs4ZIJH7HdyHWImD1qiPuAKMg,1157
133
- uncountable/types/api/id_source/match_id_source.py,sha256=1Mlw0XGBw7UeWxmwelUEi7Co8Oy15jyhetfKG1BFpIM,1145
134
+ uncountable/types/api/id_source/match_id_source.py,sha256=6aaAXcuOIy0FqKw0CK4xde8o9YmryDNhX46WUEnMrRk,1048
134
135
  uncountable/types/api/input_groups/__init__.py,sha256=gCgbynxG3jA8FQHzercKtrHKHkiIKr8APdZYUniAor8,55
135
136
  uncountable/types/api/input_groups/get_input_group_names.py,sha256=LdHWWEfVNGys6Tudienjich56Zz4bj7uXznpyYitCYA,1033
136
137
  uncountable/types/api/inputs/__init__.py,sha256=gCgbynxG3jA8FQHzercKtrHKHkiIKr8APdZYUniAor8,55
@@ -165,22 +166,24 @@ uncountable/types/api/recipes/associate_recipe_as_lot.py,sha256=bTYjbnY3B7GKz4MV
165
166
  uncountable/types/api/recipes/create_recipe.py,sha256=jizKdsc761zrJXOi0xlmge7-Z9QlzRQdbLNtUoVLQCI,1420
166
167
  uncountable/types/api/recipes/create_recipes.py,sha256=qwIYa8hfcjY7_VOFt9lxmVtJ-HOJqQN3GDNSbZsRCZU,1544
167
168
  uncountable/types/api/recipes/disassociate_recipe_as_input.py,sha256=L25fpiK1Y5PByPVVgsZy9t4podz3xSSLIwKHj8CUrSg,913
168
- uncountable/types/api/recipes/edit_recipe_inputs.py,sha256=_dLulVZLqi-CrFIVMRts8h0OHx-nUG3vFOSKS1juGUc,4568
169
+ uncountable/types/api/recipes/edit_recipe_inputs.py,sha256=iUbbMASOVN2m7ywEeaLoEaJOkceoyjLzaRuMeoEFS_0,5513
169
170
  uncountable/types/api/recipes/get_curve.py,sha256=UIWfpqtU5sQokaxwYfQFNFl6HMyzWEF_Sjd8UMz0U88,939
170
171
  uncountable/types/api/recipes/get_recipe_calculations.py,sha256=eQmkdZzCEuq8S2f_kf_7GPvDLX1pTnY1CRmkK0SkMCI,1472
171
172
  uncountable/types/api/recipes/get_recipe_links.py,sha256=hk5dfQjv7yU2r-S9b8vwWEJLPHqU0-M6SFiTLMR3fVk,985
172
173
  uncountable/types/api/recipes/get_recipe_names.py,sha256=uCpXZq5oWjr9a_Vf-yYPaVS72XOlLHgAlju6KHeQ3UA,986
173
174
  uncountable/types/api/recipes/get_recipe_output_metadata.py,sha256=L9s2ykPP4pd02Pc98LDisY8bgV8CToS6t6fXKTWqGRw,1464
174
- uncountable/types/api/recipes/get_recipes_data.py,sha256=TbJo4pkp5ePgbeH1oldThpq9jWLDYRUbrYMAv8T_LsI,5518
175
+ uncountable/types/api/recipes/get_recipes_data.py,sha256=nX4sCRY_RxztVqV-DGVpAvpayy6pn6cumS2pD1xmC5k,5429
176
+ uncountable/types/api/recipes/lock_recipes.py,sha256=vxpc52TgFzKxw1fEuCNPpFLVwcWClVXCLtDwJZWvdG0,2271
175
177
  uncountable/types/api/recipes/remove_recipe_from_project.py,sha256=cr-VnqgBNek_WInmJln0UBn1GHMNQtRw3gsFTY_G91M,872
176
178
  uncountable/types/api/recipes/set_recipe_inputs.py,sha256=lFVfv-o_O5wHuMZdH63qlG4exFTlJM078oSAtb3XNxA,1426
177
179
  uncountable/types/api/recipes/set_recipe_metadata.py,sha256=Ba6ttd1JuS_Ypt-KpckSviWtOcQ-OTdTEJiaSYyoQL8,933
180
+ uncountable/types/api/recipes/set_recipe_output_annotations.py,sha256=8JSWlHcPOJfqjjmIxNbJYlJipXaLNsmYqUXi6JM6uEo,2890
178
181
  uncountable/types/api/recipes/set_recipe_outputs.py,sha256=VfUL59O21qPrgD7qQ9t5OVSGcGZ2r17lW_yEzuUkrtw,1722
179
182
  uncountable/types/api/recipes/set_recipe_tags.py,sha256=U710hgq9-t6QZGRB-ZGHskpt4iXwYEjIRb67eh3P518,2453
180
183
  uncountable/types/api/recipes/unarchive_recipes.py,sha256=WcwFYbBsX2SKXnoBQ8locnRn7Bj1rHdtrURQVOfqgfU,814
181
184
  uncountable/types/api/triggers/__init__.py,sha256=gCgbynxG3jA8FQHzercKtrHKHkiIKr8APdZYUniAor8,55
182
185
  uncountable/types/api/triggers/run_trigger.py,sha256=9m9M8-nlGB_sAU2Qm2lWugp4h4Osqj6QpjNfU8osd1U,901
183
- UncountablePythonSDK-0.0.33.dist-info/METADATA,sha256=bPMG0vmoieJ8foThqNXZK5DRWNQO0mZz3ypltJAx6Ck,1577
184
- UncountablePythonSDK-0.0.33.dist-info/WHEEL,sha256=cpQTJ5IWu9CdaPViMhC9YzF8gZuS5-vlfoFihTBC86A,91
185
- UncountablePythonSDK-0.0.33.dist-info/top_level.txt,sha256=1UVGjAU-6hJY9qw2iJ7nCBeEwZ793AEN5ZfKX9A1uj4,31
186
- UncountablePythonSDK-0.0.33.dist-info/RECORD,,
186
+ UncountablePythonSDK-0.0.35.dist-info/METADATA,sha256=kcqCHmpipLKzLrLpPYjcDY58l99wCYxOVFO94JoJhTY,1577
187
+ UncountablePythonSDK-0.0.35.dist-info/WHEEL,sha256=Z4pYXqR_rTB7OWNDYFOm1qRk0RX6GFP2o8LgvP453Hk,91
188
+ UncountablePythonSDK-0.0.35.dist-info/top_level.txt,sha256=1UVGjAU-6hJY9qw2iJ7nCBeEwZ793AEN5ZfKX9A1uj4,31
189
+ UncountablePythonSDK-0.0.35.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (70.1.0)
2
+ Generator: setuptools (70.3.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -0,0 +1,45 @@
1
+ import os
2
+ from decimal import Decimal
3
+
4
+ from uncountable.core import AsyncBatchProcessor, AuthDetailsApiKey, Client
5
+ from uncountable.types import (
6
+ edit_recipe_inputs_t,
7
+ recipe_workflow_steps_t,
8
+ )
9
+ from uncountable.types.identifier import IdentifierKeyBatchReference, IdentifierKeyId
10
+ from uncountable.types.recipe_identifiers import (
11
+ RecipeIdentifiers,
12
+ )
13
+ from uncountable.types.recipe_inputs import QuantityBasis
14
+
15
+ client = Client(
16
+ base_url=os.environ["UNC_BASE_URL"],
17
+ auth_details=AuthDetailsApiKey(
18
+ api_id=os.environ["UNC_API_ID"], api_secret_key=os.environ["UNC_API_SECRET_KEY"]
19
+ ),
20
+ )
21
+ batch_loader = AsyncBatchProcessor(client=client)
22
+ recipe_identifiers: RecipeIdentifiers = []
23
+ req = batch_loader.create_recipe(
24
+ material_family_id=1, workflow_id=1, identifiers=recipe_identifiers
25
+ )
26
+ created_recipe_reference = req.batch_reference
27
+ edits: list[edit_recipe_inputs_t.RecipeInputEdit] = []
28
+ edits.append(
29
+ edit_recipe_inputs_t.RecipeInputEditAddInput(
30
+ quantity_basis=QuantityBasis.MASS,
31
+ ingredient_key=IdentifierKeyId(id=1),
32
+ value_numeric=Decimal(56.7),
33
+ )
34
+ )
35
+ edits.append(
36
+ edit_recipe_inputs_t.RecipeInputEditChangeBasisViewed(
37
+ quantity_basis=QuantityBasis.VOLUME, ingredient_key=IdentifierKeyId(id=1)
38
+ )
39
+ )
40
+ batch_loader.edit_recipe_inputs(
41
+ recipe_key=IdentifierKeyBatchReference(reference=created_recipe_reference),
42
+ edits=edits,
43
+ recipe_workflow_step_identifier=recipe_workflow_steps_t.RecipeWorkflowStepIdentifierDefault(),
44
+ )
45
+ job_id = batch_loader.send()
@@ -1,5 +1,10 @@
1
+ # warnings -- types here assume that keys to dictionaries are strings
2
+ # this is true most of the time, but there are cases where we have integer indexes
3
+
4
+ import dataclasses
1
5
  import datetime
2
6
  import enum
7
+ import functools
3
8
  from collections.abc import Callable, Mapping, Sequence
4
9
  from decimal import Decimal
5
10
  from typing import (
@@ -39,14 +44,25 @@ def identity(x: T) -> T:
39
44
  return x
40
45
 
41
46
 
42
- def key_convert_to_camelcase(o: Any) -> str:
47
+ @overload
48
+ def key_convert_to_camelcase(o: str) -> str: ...
49
+
50
+
51
+ @overload
52
+ def key_convert_to_camelcase(o: int) -> int: ...
53
+
54
+
55
+ def key_convert_to_camelcase(o: Any) -> str | int:
43
56
  if isinstance(o, OpaqueKey):
44
57
  return o
45
- if isinstance(o, enum.Enum):
46
- return o.value # type: ignore[no-any-return]
58
+ if isinstance(o, enum.StrEnum):
59
+ return o.value
47
60
  if isinstance(o, str):
48
61
  return snake_to_camel_case(o)
49
- return o # type: ignore[no-any-return]
62
+ if isinstance(o, int):
63
+ # we allow dictionaries to use integer keys
64
+ return o
65
+ raise ValueError("Unexpected key type", o)
50
66
 
51
67
 
52
68
  def _convert_dict(d: dict[str, Any]) -> dict[str, JsonValue]:
@@ -61,27 +77,50 @@ def _serialize_dict(d: dict[str, Any]) -> dict[str, JsonValue]:
61
77
  return {k: serialize_for_storage(v) for k, v in d.items() if v != MISSING_SENTRY}
62
78
 
63
79
 
64
- def _convert_dataclass(d: Dataclass) -> dict[str, JsonValue]:
65
- dct = type(d)
66
- scd = get_serial_class_data(dct)
80
+ def _to_string_value(value: Any) -> str:
81
+ assert isinstance(value, (Decimal, int))
82
+ return str(value)
67
83
 
68
- def key_convert(key: str) -> str:
84
+
85
+ @dataclasses.dataclass(kw_only=True)
86
+ class DataclassConversions:
87
+ key_conversions: dict[str, str]
88
+ value_conversion_functions: dict[str, Callable[[Any], JsonValue]]
89
+
90
+
91
+ @functools.lru_cache(maxsize=10000)
92
+ def _get_dataclass_conversion_lookups(dataclass_type: Any) -> DataclassConversions:
93
+ scd = get_serial_class_data(dataclass_type)
94
+
95
+ key_conversions: dict[str, str] = {}
96
+ value_conversion_functions: dict[str, Callable[[Any], JsonValue]] = {}
97
+
98
+ for field in dataclasses.fields(dataclass_type):
99
+ key = field.name
69
100
  if scd.has_unconverted_key(key):
70
- return key
71
- return key_convert_to_camelcase(key)
101
+ key_conversions[key] = key
102
+ else:
103
+ key_conversions[key] = key_convert_to_camelcase(key)
72
104
 
73
- def value_convert(key: str, value: Any) -> JsonValue:
74
105
  if scd.has_to_string_value(key):
75
- # Limit to types we know we need to support to avoid surprises
76
- # Generics, like List/Dict would need to be per-value stringified
77
- assert isinstance(value, (Decimal, int))
78
- return str(value)
79
- if scd.has_unconverted_value(key):
80
- return value # type: ignore[no-any-return]
81
- return serialize_for_api(value) # type: ignore[no-any-return,unused-ignore]
106
+ value_conversion_functions[key] = _to_string_value
107
+ elif scd.has_unconverted_value(key):
108
+ value_conversion_functions[key] = identity
109
+ else:
110
+ value_conversion_functions[key] = serialize_for_api
111
+
112
+ return DataclassConversions(
113
+ key_conversions=key_conversions,
114
+ value_conversion_functions=value_conversion_functions,
115
+ )
116
+
82
117
 
118
+ def _convert_dataclass(d: Any) -> dict[str, JsonValue]:
119
+ conversions = _get_dataclass_conversion_lookups(type(d)) # type: ignore[arg-type]
83
120
  return {
84
- key_convert(k): (value_convert(k, v) if v is not None else None)
121
+ conversions.key_conversions[k]: (
122
+ conversions.value_conversion_functions[k](v) if v is not None else None
123
+ )
85
124
  for k, v in d.__dict__.items()
86
125
  if v != MISSING_SENTRY
87
126
  }
@@ -126,6 +165,10 @@ def serialize_for_api(obj: Any) -> JsonValue:
126
165
  Use the CachedParser.parse_api to parse this data.
127
166
  """
128
167
  serialization_type = get_serialization_type(type(obj)) # type: ignore
168
+ if (
169
+ serialization_type == SerializationType.UNKNOWN
170
+ ): # performance optimization to not do function lookup
171
+ return obj # type: ignore
129
172
  return _CONVERSION_SERIALIZATION_FUNCS[serialization_type](obj)
130
173
 
131
174
 
@@ -153,6 +196,10 @@ def serialize_for_storage(obj: Any) -> JsonValue:
153
196
  Use the CachedParser.parse_storage to parse this data.
154
197
  """
155
198
  serialization_type = get_serialization_type(type(obj)) # type: ignore
199
+ if (
200
+ serialization_type == SerializationType.UNKNOWN
201
+ ): # performance optimization to not do function lookup
202
+ return obj # type: ignore
156
203
  return _SERIALIZATION_FUNCS[serialization_type](obj)
157
204
 
158
205
 
@@ -18,7 +18,7 @@ class EmitTypescriptContext:
18
18
 
19
19
 
20
20
  def ts_type_name(name: str) -> str:
21
- return "".join([x.capitalize() for x in name.split("_")])
21
+ return "".join([x.title() for x in name.split("_")])
22
22
 
23
23
 
24
24
  def resolve_namespace_ref(namespace: builder.SpecNamespace) -> str:
@@ -29,4 +29,4 @@ def ts_name(name: str, name_case: builder.NameCase) -> str:
29
29
  if name_case == builder.NameCase.preserve:
30
30
  return name
31
31
  bits = util.split_any_name(name)
32
- return "".join([bits[0], *[x.capitalize() for x in bits[1:]]])
32
+ return "".join([bits[0], *[x.title() for x in bits[1:]]])
@@ -50,6 +50,7 @@ from . import inputs as inputs_t
50
50
  from .api.entity import list_entities as list_entities_t
51
51
  from .api.id_source import list_id_source as list_id_source_t
52
52
  from .api.entity import lock_entity as lock_entity_t
53
+ from .api.recipes import lock_recipes as lock_recipes_t
53
54
  from .api.id_source import match_id_source as match_id_source_t
54
55
  from . import outputs as outputs_t
55
56
  from . import permissions as permissions_t
@@ -75,6 +76,7 @@ from .api.inputs import set_input_subcategories as set_input_subcategories_t
75
76
  from .api.inputs import set_intermediate_type as set_intermediate_type_t
76
77
  from .api.recipes import set_recipe_inputs as set_recipe_inputs_t
77
78
  from .api.recipes import set_recipe_metadata as set_recipe_metadata_t
79
+ from .api.recipes import set_recipe_output_annotations as set_recipe_output_annotations_t
78
80
  from .api.recipes import set_recipe_outputs as set_recipe_outputs_t
79
81
  from .api.recipes import set_recipe_tags as set_recipe_tags_t
80
82
  from .api.entity import set_values as set_values_t
@@ -136,6 +138,7 @@ __all__: list[str] = [
136
138
  "list_entities_t",
137
139
  "list_id_source_t",
138
140
  "lock_entity_t",
141
+ "lock_recipes_t",
139
142
  "match_id_source_t",
140
143
  "outputs_t",
141
144
  "permissions_t",
@@ -161,6 +164,7 @@ __all__: list[str] = [
161
164
  "set_intermediate_type_t",
162
165
  "set_recipe_inputs_t",
163
166
  "set_recipe_metadata_t",
167
+ "set_recipe_output_annotations_t",
164
168
  "set_recipe_outputs_t",
165
169
  "set_recipe_tags_t",
166
170
  "set_values_t",
@@ -32,8 +32,8 @@ ENDPOINT_PATH = "api/external/entity/external_list_entities"
32
32
  )
33
33
  @dataclass(kw_only=True)
34
34
  class Arguments:
35
- entity_type: entity_t.EntityType
36
35
  config_reference: str
36
+ entity_type: typing.Optional[entity_t.EntityType] = None
37
37
  attributes: typing.Optional[dict[OpaqueKey, base_t.JsonValue]] = None
38
38
  offset: typing.Optional[typing.Optional[int]] = None
39
39
  limit: typing.Optional[typing.Optional[int]] = None
@@ -8,7 +8,6 @@ import typing # noqa: F401
8
8
  import datetime # noqa: F401
9
9
  from decimal import Decimal # noqa: F401
10
10
  from dataclasses import dataclass
11
- from pkgs.serialization import serial_class
12
11
  from ... import base as base_t
13
12
  from ... import id_source as id_source_t
14
13
 
@@ -39,9 +38,6 @@ class Match:
39
38
 
40
39
 
41
40
  # DO NOT MODIFY -- This file is generated by type_spec
42
- @serial_class(
43
- unconverted_values={"results"},
44
- )
45
41
  @dataclass(kw_only=True)
46
42
  class Data:
47
43
  results: list[Match]
@@ -24,8 +24,10 @@ __all__: list[str] = [
24
24
  "RecipeInputEdit",
25
25
  "RecipeInputEditAddInput",
26
26
  "RecipeInputEditBase",
27
+ "RecipeInputEditChangeBasisViewed",
27
28
  "RecipeInputEditClearInputs",
28
29
  "RecipeInputEditInputBase",
30
+ "RecipeInputEditSetLot",
29
31
  "RecipeInputEditType",
30
32
  "RecipeInputEditUpdateAnnotations",
31
33
  "RecipeInputEditUpsertInput",
@@ -41,6 +43,8 @@ class RecipeInputEditType(StrEnum):
41
43
  UPSERT_INPUT = "upsert_input"
42
44
  ADD_INPUT = "add_input"
43
45
  UPDATE_ANNOTATIONS = "update_annotations"
46
+ SET_LOT = "set_lot"
47
+ CHANGE_BASIS = "change_basis"
44
48
 
45
49
 
46
50
  # DO NOT MODIFY -- This file is generated by type_spec
@@ -72,6 +76,15 @@ class RecipeInputEditInputBase(RecipeInputEditBase):
72
76
  calculation_key: typing.Optional[identifier_t.IdentifierKey] = None
73
77
 
74
78
 
79
+ # DO NOT MODIFY -- This file is generated by type_spec
80
+ @serial_class(
81
+ parse_require={"type"},
82
+ )
83
+ @dataclass(kw_only=True)
84
+ class RecipeInputEditChangeBasisViewed(RecipeInputEditInputBase):
85
+ type: typing.Literal[RecipeInputEditType.CHANGE_BASIS] = RecipeInputEditType.CHANGE_BASIS
86
+
87
+
75
88
  # DO NOT MODIFY -- This file is generated by type_spec
76
89
  @serial_class(
77
90
  parse_require={"type"},
@@ -91,6 +104,17 @@ class RecipeInputEditAddInput(RecipeInputEditInputBase):
91
104
  type: typing.Literal[RecipeInputEditType.ADD_INPUT] = RecipeInputEditType.ADD_INPUT
92
105
 
93
106
 
107
+ # DO NOT MODIFY -- This file is generated by type_spec
108
+ @serial_class(
109
+ parse_require={"type"},
110
+ )
111
+ @dataclass(kw_only=True)
112
+ class RecipeInputEditSetLot(RecipeInputEditBase):
113
+ type: typing.Literal[RecipeInputEditType.SET_LOT] = RecipeInputEditType.SET_LOT
114
+ ingredient_key: identifier_t.IdentifierKey
115
+ ingredient_lot_recipe_key: identifier_t.IdentifierKey
116
+
117
+
94
118
  # DO NOT MODIFY -- This file is generated by type_spec
95
119
  @serial_class(
96
120
  to_string_values={"lower_value", "upper_value"},
@@ -116,7 +140,7 @@ class RecipeInputEditUpdateAnnotations(RecipeInputEditInputBase):
116
140
 
117
141
  # DO NOT MODIFY -- This file is generated by type_spec
118
142
  RecipeInputEdit = typing.Annotated[
119
- typing.Union[RecipeInputEditClearInputs, RecipeInputEditUpsertInput, RecipeInputEditAddInput, RecipeInputEditUpdateAnnotations],
143
+ typing.Union[RecipeInputEditClearInputs, RecipeInputEditUpsertInput, RecipeInputEditAddInput, RecipeInputEditUpdateAnnotations, RecipeInputEditSetLot, RecipeInputEditChangeBasisViewed],
120
144
  serial_union_annotation(
121
145
  discriminator="type",
122
146
  discriminator_map={
@@ -124,6 +148,8 @@ RecipeInputEdit = typing.Annotated[
124
148
  "upsert_input": RecipeInputEditUpsertInput,
125
149
  "add_input": RecipeInputEditAddInput,
126
150
  "update_annotations": RecipeInputEditUpdateAnnotations,
151
+ "set_lot": RecipeInputEditSetLot,
152
+ "change_basis": RecipeInputEditChangeBasisViewed,
127
153
  },
128
154
  ),
129
155
  ]
@@ -15,7 +15,6 @@ from ... import inputs as inputs_t
15
15
  from ... import outputs as outputs_t
16
16
  from ... import recipe_metadata as recipe_metadata_t
17
17
  from ... import recipe_tags as recipe_tags_t
18
- from ... import users as users_t
19
18
  from ... import workflows as workflows_t
20
19
 
21
20
  __all__: list[str] = [
@@ -97,7 +96,6 @@ class RecipeInput:
97
96
  quantity_json: base_t.JsonValue
98
97
  curve_id: typing.Optional[base_t.ObjectId]
99
98
  actual_quantity_json: base_t.JsonValue
100
- input_type: str
101
99
  behavior: str
102
100
  quantity_dec: typing.Optional[Decimal] = None
103
101
  actual_quantity_dec: typing.Optional[Decimal] = None
@@ -170,7 +168,6 @@ class Data:
170
168
  inputs: list[inputs_t.SimpleInput]
171
169
  outputs: list[outputs_t.SimpleOutput]
172
170
  output_conditions: list[SimpleOutputCondition]
173
- users: list[users_t.SimpleUser]
174
171
  recipe_tags: list[recipe_tags_t.SimpleRecipeTag]
175
172
  experiment_groups: list[experiment_groups_t.SimpleExperimentGroup]
176
173
  # DO NOT MODIFY -- This file is generated by type_spec
@@ -0,0 +1,89 @@
1
+ # DO NOT MODIFY -- This file is generated by type_spec
2
+ # flake8: noqa: F821
3
+ # ruff: noqa: E402
4
+ # fmt: off
5
+ # isort: skip_file
6
+ from __future__ import annotations
7
+ import typing # noqa: F401
8
+ import datetime # noqa: F401
9
+ from decimal import Decimal # noqa: F401
10
+ from pkgs.strenum_compat import StrEnum
11
+ from dataclasses import dataclass
12
+ from pkgs.serialization import serial_class
13
+ from pkgs.serialization import serial_union_annotation
14
+ from ... import identifier as identifier_t
15
+
16
+ __all__: list[str] = [
17
+ "Arguments",
18
+ "Data",
19
+ "ENDPOINT_METHOD",
20
+ "ENDPOINT_PATH",
21
+ "RecipeLock",
22
+ "RecipeLockAll",
23
+ "RecipeLockBase",
24
+ "RecipeLockInputs",
25
+ "RecipeLockType",
26
+ ]
27
+
28
+ ENDPOINT_METHOD = "POST"
29
+ ENDPOINT_PATH = "api/external/recipes/lock_recipes"
30
+
31
+
32
+ # DO NOT MODIFY -- This file is generated by type_spec
33
+ class RecipeLockType(StrEnum):
34
+ ALL = "all"
35
+ INPUTS_ONLY = "inputs_only"
36
+
37
+
38
+ # DO NOT MODIFY -- This file is generated by type_spec
39
+ @dataclass(kw_only=True)
40
+ class RecipeLockBase:
41
+ recipe: identifier_t.IdentifierKey
42
+ globally_removable: bool
43
+ type: RecipeLockType
44
+ lock_samples: typing.Optional[bool] = None
45
+ comments: typing.Optional[str] = None
46
+
47
+
48
+ # DO NOT MODIFY -- This file is generated by type_spec
49
+ @serial_class(
50
+ parse_require={"type"},
51
+ )
52
+ @dataclass(kw_only=True)
53
+ class RecipeLockAll(RecipeLockBase):
54
+ type: typing.Literal[RecipeLockType.ALL] = RecipeLockType.ALL
55
+
56
+
57
+ # DO NOT MODIFY -- This file is generated by type_spec
58
+ @serial_class(
59
+ parse_require={"type"},
60
+ )
61
+ @dataclass(kw_only=True)
62
+ class RecipeLockInputs(RecipeLockBase):
63
+ type: typing.Literal[RecipeLockType.INPUTS_ONLY] = RecipeLockType.INPUTS_ONLY
64
+
65
+
66
+ # DO NOT MODIFY -- This file is generated by type_spec
67
+ RecipeLock = typing.Annotated[
68
+ typing.Union[RecipeLockAll, RecipeLockInputs],
69
+ serial_union_annotation(
70
+ discriminator="type",
71
+ discriminator_map={
72
+ "all": RecipeLockAll,
73
+ "inputs_only": RecipeLockInputs,
74
+ },
75
+ ),
76
+ ]
77
+
78
+
79
+ # DO NOT MODIFY -- This file is generated by type_spec
80
+ @dataclass(kw_only=True)
81
+ class Arguments:
82
+ locks: list[RecipeLock]
83
+
84
+
85
+ # DO NOT MODIFY -- This file is generated by type_spec
86
+ @dataclass(kw_only=True)
87
+ class Data:
88
+ pass
89
+ # DO NOT MODIFY -- This file is generated by type_spec
@@ -0,0 +1,102 @@
1
+ # DO NOT MODIFY -- This file is generated by type_spec
2
+ # flake8: noqa: F821
3
+ # ruff: noqa: E402
4
+ # fmt: off
5
+ # isort: skip_file
6
+ from __future__ import annotations
7
+ import typing # noqa: F401
8
+ import datetime # noqa: F401
9
+ from decimal import Decimal # noqa: F401
10
+ from pkgs.strenum_compat import StrEnum
11
+ from dataclasses import dataclass
12
+ from pkgs.serialization import serial_class
13
+ from pkgs.serialization import serial_union_annotation
14
+ from ... import base as base_t
15
+ from ... import identifier as identifier_t
16
+
17
+ __all__: list[str] = [
18
+ "AnnotationEdit",
19
+ "AnnotationUpdateType",
20
+ "Arguments",
21
+ "Data",
22
+ "ENDPOINT_METHOD",
23
+ "ENDPOINT_PATH",
24
+ "RecipeOutputEditBase",
25
+ "RecipeOutputMergeAnnotations",
26
+ "RecipeOutputReplaceAnnotations",
27
+ "RecipeOutputUpdateAnnotations",
28
+ ]
29
+
30
+ ENDPOINT_METHOD = "POST"
31
+ ENDPOINT_PATH = "api/external/recipes/set_recipe_output_annotations"
32
+
33
+
34
+ # DO NOT MODIFY -- This file is generated by type_spec
35
+ @serial_class(
36
+ to_string_values={"lower_value", "upper_value"},
37
+ )
38
+ @dataclass(kw_only=True)
39
+ class AnnotationEdit:
40
+ annotation_type_key: identifier_t.IdentifierKey
41
+ lower_value: typing.Optional[Decimal] = None
42
+ upper_value: typing.Optional[Decimal] = None
43
+
44
+
45
+ # DO NOT MODIFY -- This file is generated by type_spec
46
+ class AnnotationUpdateType(StrEnum):
47
+ MERGE = "merge"
48
+ REPLACE = "replace"
49
+
50
+
51
+ # DO NOT MODIFY -- This file is generated by type_spec
52
+ @dataclass(kw_only=True)
53
+ class RecipeOutputEditBase:
54
+ recipe_id: base_t.ObjectId
55
+ output_id: base_t.ObjectId
56
+ experiment_num: int
57
+ annotations: list[AnnotationEdit]
58
+ condition_id: typing.Optional[base_t.ObjectId] = None
59
+
60
+
61
+ # DO NOT MODIFY -- This file is generated by type_spec
62
+ @serial_class(
63
+ parse_require={"type"},
64
+ )
65
+ @dataclass(kw_only=True)
66
+ class RecipeOutputMergeAnnotations(RecipeOutputEditBase):
67
+ type: typing.Literal[AnnotationUpdateType.MERGE] = AnnotationUpdateType.MERGE
68
+
69
+
70
+ # DO NOT MODIFY -- This file is generated by type_spec
71
+ @serial_class(
72
+ parse_require={"type"},
73
+ )
74
+ @dataclass(kw_only=True)
75
+ class RecipeOutputReplaceAnnotations(RecipeOutputEditBase):
76
+ type: typing.Literal[AnnotationUpdateType.REPLACE] = AnnotationUpdateType.REPLACE
77
+
78
+
79
+ # DO NOT MODIFY -- This file is generated by type_spec
80
+ RecipeOutputUpdateAnnotations = typing.Annotated[
81
+ typing.Union[RecipeOutputMergeAnnotations, RecipeOutputReplaceAnnotations],
82
+ serial_union_annotation(
83
+ discriminator="type",
84
+ discriminator_map={
85
+ "merge": RecipeOutputMergeAnnotations,
86
+ "replace": RecipeOutputReplaceAnnotations,
87
+ },
88
+ ),
89
+ ]
90
+
91
+
92
+ # DO NOT MODIFY -- This file is generated by type_spec
93
+ @dataclass(kw_only=True)
94
+ class Arguments:
95
+ updates: list[RecipeOutputUpdateAnnotations]
96
+
97
+
98
+ # DO NOT MODIFY -- This file is generated by type_spec
99
+ @dataclass(kw_only=True)
100
+ class Data:
101
+ pass
102
+ # DO NOT MODIFY -- This file is generated by type_spec
@@ -49,6 +49,7 @@ from uncountable.types import identifier as identifier_t
49
49
  import uncountable.types.api.entity.list_entities as list_entities_t
50
50
  import uncountable.types.api.id_source.list_id_source as list_id_source_t
51
51
  import uncountable.types.api.entity.lock_entity as lock_entity_t
52
+ import uncountable.types.api.recipes.lock_recipes as lock_recipes_t
52
53
  import uncountable.types.api.id_source.match_id_source as match_id_source_t
53
54
  from uncountable.types import permissions as permissions_t
54
55
  from uncountable.types import post_base as post_base_t
@@ -67,6 +68,7 @@ import uncountable.types.api.inputs.set_input_subcategories as set_input_subcate
67
68
  import uncountable.types.api.inputs.set_intermediate_type as set_intermediate_type_t
68
69
  import uncountable.types.api.recipes.set_recipe_inputs as set_recipe_inputs_t
69
70
  import uncountable.types.api.recipes.set_recipe_metadata as set_recipe_metadata_t
71
+ import uncountable.types.api.recipes.set_recipe_output_annotations as set_recipe_output_annotations_t
70
72
  import uncountable.types.api.recipes.set_recipe_outputs as set_recipe_outputs_t
71
73
  import uncountable.types.api.recipes.set_recipe_tags as set_recipe_tags_t
72
74
  import uncountable.types.api.entity.set_values as set_values_t
@@ -826,15 +828,15 @@ class ClientMethods(ABC):
826
828
  def list_entities(
827
829
  self,
828
830
  *,
829
- entity_type: entity_t.EntityType,
830
831
  config_reference: str,
832
+ entity_type: typing.Optional[entity_t.EntityType] = None,
831
833
  attributes: typing.Optional[dict[OpaqueKey, base_t.JsonValue]] = None,
832
834
  offset: typing.Optional[typing.Optional[int]] = None,
833
835
  limit: typing.Optional[typing.Optional[int]] = None,
834
836
  ) -> list_entities_t.Data:
835
837
  """Uses a structured loading configuration to list entities in the system
836
838
 
837
- :param entity_type: The type of the entities requested, e.g. lab_request, recipe
839
+ :param entity_type: DEPRECATED: The type of the entities requested, e.g. lab_request, recipe
838
840
  :param config_reference: The configuration reference name for the listing config
839
841
  :param attributes: Attributes to pass to the configuration for parameterizing filters
840
842
  :param offset: Used for pagination. Pagination is done based on the sorting of the config. [Pagination More Info](#pagination)
@@ -906,6 +908,25 @@ class ClientMethods(ABC):
906
908
  )
907
909
  return self.do_request(api_request=api_request, return_type=lock_entity_t.Data)
908
910
 
911
+ def lock_recipes(
912
+ self,
913
+ *,
914
+ locks: list[lock_recipes_t.RecipeLock],
915
+ ) -> lock_recipes_t.Data:
916
+ """Lock experiments. Experiments will require unlocking to be editable. Edits to the experiments are blocked while they are locked.
917
+
918
+ :param locks: The recipe locks to apply, a maximum of 100 can be sent
919
+ """
920
+ args = lock_recipes_t.Arguments(
921
+ locks=locks,
922
+ )
923
+ api_request = APIRequest(
924
+ method=lock_recipes_t.ENDPOINT_METHOD,
925
+ endpoint=lock_recipes_t.ENDPOINT_PATH,
926
+ args=args,
927
+ )
928
+ return self.do_request(api_request=api_request, return_type=lock_recipes_t.Data)
929
+
909
930
  def match_id_source(
910
931
  self,
911
932
  *,
@@ -1172,6 +1193,25 @@ class ClientMethods(ABC):
1172
1193
  )
1173
1194
  return self.do_request(api_request=api_request, return_type=set_recipe_metadata_t.Data)
1174
1195
 
1196
+ def set_recipe_output_annotations(
1197
+ self,
1198
+ *,
1199
+ updates: list[set_recipe_output_annotations_t.RecipeOutputUpdateAnnotations],
1200
+ ) -> set_recipe_output_annotations_t.Data:
1201
+ """Update annotations for an experiments outputs
1202
+
1203
+ :param updates: The output edits to perform. Must be at most 100 entries long
1204
+ """
1205
+ args = set_recipe_output_annotations_t.Arguments(
1206
+ updates=updates,
1207
+ )
1208
+ api_request = APIRequest(
1209
+ method=set_recipe_output_annotations_t.ENDPOINT_METHOD,
1210
+ endpoint=set_recipe_output_annotations_t.ENDPOINT_PATH,
1211
+ args=args,
1212
+ )
1213
+ return self.do_request(api_request=api_request, return_type=set_recipe_output_annotations_t.Data)
1214
+
1175
1215
  def set_recipe_outputs(
1176
1216
  self,
1177
1217
  *,