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

Files changed (55) hide show
  1. {UncountablePythonSDK-0.0.20.dist-info → UncountablePythonSDK-0.0.22.dist-info}/METADATA +3 -1
  2. {UncountablePythonSDK-0.0.20.dist-info → UncountablePythonSDK-0.0.22.dist-info}/RECORD +55 -34
  3. examples/async_batch.py +36 -0
  4. examples/upload_files.py +19 -0
  5. pkgs/type_spec/actions_registry/__main__.py +35 -23
  6. pkgs/type_spec/actions_registry/emit_typescript.py +71 -9
  7. pkgs/type_spec/builder.py +125 -8
  8. pkgs/type_spec/config.py +1 -0
  9. pkgs/type_spec/emit_open_api.py +197 -16
  10. pkgs/type_spec/emit_open_api_util.py +18 -0
  11. pkgs/type_spec/emit_python.py +241 -55
  12. pkgs/type_spec/load_types.py +48 -5
  13. pkgs/type_spec/open_api_util.py +13 -33
  14. pkgs/type_spec/type_info/emit_type_info.py +129 -8
  15. type_spec/external/api/entity/create_entities.yaml +13 -1
  16. type_spec/external/api/entity/create_entity.yaml +13 -1
  17. type_spec/external/api/entity/transition_entity_phase.yaml +44 -0
  18. type_spec/external/api/permissions/set_core_permissions.yaml +69 -0
  19. type_spec/external/api/recipes/associate_recipe_as_input.yaml +4 -4
  20. type_spec/external/api/recipes/create_recipe.yaml +2 -1
  21. type_spec/external/api/recipes/disassociate_recipe_as_input.yaml +16 -0
  22. type_spec/external/api/recipes/edit_recipe_inputs.yaml +86 -0
  23. type_spec/external/api/recipes/get_curve.yaml +4 -1
  24. type_spec/external/api/recipes/get_recipes_data.yaml +6 -0
  25. type_spec/external/api/recipes/set_recipe_metadata.yaml +1 -0
  26. type_spec/external/api/recipes/set_recipe_tags.yaml +62 -0
  27. uncountable/core/__init__.py +3 -1
  28. uncountable/core/async_batch.py +22 -0
  29. uncountable/core/client.py +84 -10
  30. uncountable/core/file_upload.py +95 -0
  31. uncountable/core/types.py +22 -0
  32. uncountable/types/__init__.py +18 -0
  33. uncountable/types/api/entity/create_entities.py +1 -1
  34. uncountable/types/api/entity/create_entity.py +1 -1
  35. uncountable/types/api/entity/transition_entity_phase.py +66 -0
  36. uncountable/types/api/permissions/__init__.py +1 -0
  37. uncountable/types/api/permissions/set_core_permissions.py +89 -0
  38. uncountable/types/api/recipes/associate_recipe_as_input.py +4 -3
  39. uncountable/types/api/recipes/create_recipe.py +1 -1
  40. uncountable/types/api/recipes/disassociate_recipe_as_input.py +35 -0
  41. uncountable/types/api/recipes/edit_recipe_inputs.py +106 -0
  42. uncountable/types/api/recipes/get_curve.py +2 -1
  43. uncountable/types/api/recipes/get_recipes_data.py +2 -0
  44. uncountable/types/api/recipes/set_recipe_tags.py +91 -0
  45. uncountable/types/async_batch.py +10 -0
  46. uncountable/types/async_batch_processor.py +154 -0
  47. uncountable/types/client_base.py +113 -48
  48. uncountable/types/identifier.py +3 -3
  49. uncountable/types/permissions.py +46 -0
  50. uncountable/types/post_base.py +30 -0
  51. uncountable/types/recipe_inputs.py +30 -0
  52. uncountable/types/recipe_metadata.py +2 -0
  53. uncountable/types/recipe_workflow_steps.py +77 -0
  54. {UncountablePythonSDK-0.0.20.dist-info → UncountablePythonSDK-0.0.22.dist-info}/WHEEL +0 -0
  55. {UncountablePythonSDK-0.0.20.dist-info → UncountablePythonSDK-0.0.22.dist-info}/top_level.txt +0 -0
@@ -23,7 +23,7 @@ def type_path_of(stype: builder.SpecType) -> object: # NamePath
23
23
  extended scopes, generics, and enum literal values.
24
24
  - Scoped Type: [ (namespace-string)..., type-string ]
25
25
  - Instance Type: [ "$instance", Scoped-Type-Base, [TypePath-Parameters...] ]
26
- - Literal Type: [ "$literal", [ "$value", value ]... ]
26
+ - Literal Type: [ "$literal", [ "$value", value, value-type-string ]... ]
27
27
 
28
28
  @return (string-specific, multiple-types)
29
29
  """
@@ -34,11 +34,15 @@ def type_path_of(stype: builder.SpecType) -> object: # NamePath
34
34
 
35
35
  if isinstance(stype, builder.SpecTypeInstance):
36
36
  if stype.defn_type.name == builder.BaseTypeName.s_literal:
37
- parts: list[str | list[str | bool]] = ["$literal"]
37
+ parts: list[object] = ["$literal"]
38
38
  for parameter in stype.parameters:
39
39
  assert isinstance(parameter, builder.SpecTypeLiteralWrapper)
40
40
  # This allows expansion to enum literal values later
41
- parts.append(["$value", parameter.value])
41
+ parts.append([
42
+ "$value",
43
+ parameter.value,
44
+ type_path_of(parameter.value_type),
45
+ ])
42
46
  return parts
43
47
 
44
48
  return [
@@ -148,6 +152,114 @@ def _build_map_all(build: builder.SpecBuilder) -> MapAll:
148
152
  return map_all
149
153
 
150
154
 
155
+ @dataclasses.dataclass(kw_only=True)
156
+ class InheritablePropertyParts:
157
+ """This uses only the "soft" information for now, things that aren't relevant
158
+ to the language emitted types. There are some fields that should be inherited
159
+ at that level, but that needs to be done in builder. When that is done, the
160
+ "label" and "desc" could probably be removed from this list."""
161
+
162
+ label: Optional[str] = None
163
+ desc: Optional[str] = None
164
+ ext_info: Optional[data_t.ExtInfo] = None
165
+
166
+
167
+ def _extract_inheritable_property_parts(
168
+ stype: builder.SpecTypeDefnObject,
169
+ prop: builder.SpecProperty,
170
+ ) -> InheritablePropertyParts:
171
+ if not stype.is_base and isinstance(stype.base, builder.SpecTypeDefn):
172
+ base_prop = (stype.base.properties or {}).get(prop.name)
173
+ if base_prop is None:
174
+ base_parts = InheritablePropertyParts()
175
+ else:
176
+ base_parts = _extract_inheritable_property_parts(stype.base, base_prop)
177
+ # Layout should not be inherited, as it'd end up hiding properties in the derived type
178
+ if base_parts.ext_info is not None:
179
+ base_parts.ext_info.layout = None
180
+ else:
181
+ base_parts = InheritablePropertyParts()
182
+
183
+ label = prop.label or base_parts.label
184
+ desc = prop.desc or base_parts.desc
185
+ local_ext_info = _parse_ext_info(prop.ext_info)
186
+ if local_ext_info is None:
187
+ ext_info = base_parts.ext_info
188
+ elif base_parts.ext_info is None:
189
+ ext_info = local_ext_info
190
+ else:
191
+ ext_info = data_t.ExtInfo(
192
+ **(local_ext_info.__dict__ | base_parts.ext_info.__dict__)
193
+ )
194
+
195
+ return InheritablePropertyParts(label=label, desc=desc, ext_info=ext_info)
196
+
197
+
198
+ ExtInfoLayout = dict[str, set[str]]
199
+ ALL_FIELDS_GROUP = "*all_fields"
200
+
201
+
202
+ def _extract_and_validate_layout(
203
+ stype: builder.SpecTypeDefnObject,
204
+ ext_info: data_t.ExtInfo,
205
+ base_layout: ExtInfoLayout | None,
206
+ ) -> ExtInfoLayout:
207
+ """
208
+ Produce a map of groups to fields, for validation.
209
+ """
210
+ if ext_info.layout is None:
211
+ return {}
212
+ assert stype.properties is not None
213
+
214
+ all_fields_group: set[str] = set()
215
+ layout: ExtInfoLayout = {ALL_FIELDS_GROUP: all_fields_group}
216
+
217
+ for group in ext_info.layout.groups:
218
+ fields = set(group.fields or [])
219
+ for field in fields:
220
+ assert field in stype.properties, f"layout-refers-to-missing-field:{field}"
221
+
222
+ local_ref_name = None
223
+ if group.ref_name is not None:
224
+ assert (
225
+ base_layout is None or base_layout.get(group.ref_name) is None
226
+ ), f"group-name-duplicate-in-base:{group.ref_name}"
227
+ local_ref_name = group.ref_name
228
+
229
+ if group.extends:
230
+ assert base_layout is not None, "missing-base-layout"
231
+ base_group = base_layout.get(group.extends)
232
+ assert base_group is not None, f"missing-base-group:{group.extends}"
233
+ fields.update(base_group)
234
+ local_ref_name = group.extends
235
+
236
+ assert local_ref_name not in layout, f"duplicate-group:{local_ref_name}"
237
+ if local_ref_name is not None:
238
+ layout[local_ref_name] = fields
239
+ all_fields_group.update(fields)
240
+
241
+ for group_ref_name in base_layout or {}:
242
+ assert group_ref_name in layout, f"missing-base-group:{group_ref_name}"
243
+
244
+ for prop_ref_name in stype.properties:
245
+ assert prop_ref_name in all_fields_group, f"layout-missing-field:{prop_ref_name}"
246
+
247
+ return layout
248
+
249
+
250
+ def _validate_type_ext_info(stype: builder.SpecTypeDefnObject) -> ExtInfoLayout | None:
251
+ ext_info = _parse_ext_info(stype.ext_info)
252
+ if ext_info is None:
253
+ return None
254
+
255
+ if not stype.is_base and isinstance(stype.base, builder.SpecTypeDefnObject):
256
+ base_layout = _validate_type_ext_info(stype.base)
257
+ else:
258
+ base_layout = None
259
+
260
+ return _extract_and_validate_layout(stype, ext_info, base_layout)
261
+
262
+
151
263
  def _build_map_type(
152
264
  build: builder.SpecBuilder, stype: builder.SpecTypeDefn
153
265
  ) -> MapType | None:
@@ -158,6 +270,8 @@ def _build_map_type(
158
270
  and not stype.is_base
159
271
  and stype.base is not None
160
272
  ):
273
+ _validate_type_ext_info(stype)
274
+
161
275
  properties: dict[str, MapProperty] = {}
162
276
  map_type = MapTypeObject(
163
277
  type_name=stype.name,
@@ -170,14 +284,17 @@ def _build_map_type(
170
284
 
171
285
  if stype.properties is not None:
172
286
  for prop in stype.properties.values():
287
+ parts = _extract_inheritable_property_parts(stype, prop)
288
+ # Propertis can't have layouts
289
+ assert parts.ext_info is None or parts.ext_info.layout is None
173
290
  map_property = MapProperty(
174
291
  type_name=prop.name,
175
- label=prop.label,
292
+ label=parts.label,
176
293
  api_name=ts_name(prop.name, prop.name_case),
177
294
  extant=prop.extant,
178
295
  type_path=type_path_of(prop.spec_type),
179
- ext_info=_convert_ext_info(prop.ext_info),
180
- desc=prop.desc,
296
+ ext_info=serialize_for_api(parts.ext_info),
297
+ desc=parts.desc,
181
298
  default=prop.default,
182
299
  )
183
300
  map_type.properties[prop.name] = map_property
@@ -211,7 +328,7 @@ def _build_map_type(
211
328
  return None
212
329
 
213
330
 
214
- def _convert_ext_info(in_ext: Any) -> Optional[PureJsonValue]:
331
+ def _parse_ext_info(in_ext: Any) -> Optional[data_t.ExtInfo]:
215
332
  if in_ext is None:
216
333
  return None
217
334
  assert isinstance(in_ext, dict)
@@ -229,6 +346,10 @@ def _convert_ext_info(in_ext: Any) -> Optional[PureJsonValue]:
229
346
  df["result_type"] = serialize_for_storage(converted)
230
347
  mod_ext["data_format"] = df
231
348
 
232
- parsed = ext_info_parser.parse_storage(mod_ext)
349
+ return ext_info_parser.parse_storage(mod_ext)
350
+
351
+
352
+ def _convert_ext_info(in_ext: Any) -> Optional[PureJsonValue]:
233
353
  # we need to convert this to API storage since it'll be used as-is in the UI
354
+ parsed = _parse_ext_info(in_ext)
234
355
  return cast(PureJsonValue, serialize_for_api(parsed))
@@ -19,7 +19,19 @@ Arguments:
19
19
  type: ObjectId
20
20
  desc: "Definition id for the entities to create"
21
21
  entity_type:
22
- type: Union<Literal<entity.EntityType.lab_request>, Literal<entity.EntityType.approval>, Literal<entity.EntityType.custom_entity>, Literal<entity.EntityType.task>, Literal<entity.EntityType.project>, Literal<entity.EntityType.equipment>, Literal<entity.EntityType.inv_local_locations>>
22
+ type: |
23
+ Union<
24
+ Literal<entity.EntityType.lab_request>,
25
+ Literal<entity.EntityType.approval>,
26
+ Literal<entity.EntityType.custom_entity>,
27
+ Literal<entity.EntityType.inventory_amount>,
28
+ Literal<entity.EntityType.task>,
29
+ Literal<entity.EntityType.project>,
30
+ Literal<entity.EntityType.equipment>,
31
+ Literal<entity.EntityType.inv_local_locations>,
32
+ Literal<entity.EntityType.field_option_set>,
33
+ Literal<entity.EntityType.webhook>
34
+ >
23
35
  desc: "The type of the entities to create"
24
36
  entities_to_create:
25
37
  type: List<EntityToCreate>
@@ -26,7 +26,19 @@ Arguments:
26
26
  type: ObjectId
27
27
  desc: "Definition id of the entity to create"
28
28
  entity_type:
29
- type: Union<Literal<entity.EntityType.lab_request>, Literal<entity.EntityType.approval>, Literal<entity.EntityType.custom_entity>, Literal<entity.EntityType.task>, Literal<entity.EntityType.project>, Literal<entity.EntityType.equipment>, Literal<entity.EntityType.inv_local_locations>>
29
+ type: |
30
+ Union<
31
+ Literal<entity.EntityType.lab_request>,
32
+ Literal<entity.EntityType.approval>,
33
+ Literal<entity.EntityType.custom_entity>,
34
+ Literal<entity.EntityType.inventory_amount>,
35
+ Literal<entity.EntityType.task>,
36
+ Literal<entity.EntityType.project>,
37
+ Literal<entity.EntityType.equipment>,
38
+ Literal<entity.EntityType.inv_local_locations>,
39
+ Literal<entity.EntityType.field_option_set>,
40
+ Literal<entity.EntityType.webhook>
41
+ >
30
42
  desc: "The type of the entities requested"
31
43
  field_values?:
32
44
  type: Optional<List<field_values.FieldRefNameValue>>
@@ -0,0 +1,44 @@
1
+ $endpoint:
2
+ is_sdk: true
3
+ method: post
4
+ path: ${external}/entity/transition_entity_phase
5
+ function: main.site.app.external.entity.transition_entity_phase.transition_entity_phase
6
+ desc: "Transitions an entity from one phase to another"
7
+
8
+
9
+ TransitionIdentifierPhases:
10
+ type: Object
11
+ properties:
12
+ type:
13
+ type: Literal<'phases'>
14
+ phase_from_key:
15
+ type: identifier.IdentifierKey
16
+ phase_to_key:
17
+ type: identifier.IdentifierKey
18
+
19
+ TransitionIdentifierTransition:
20
+ type: Object
21
+ properties:
22
+ type:
23
+ type: Literal<'transition'>
24
+ transition_key:
25
+ type: identifier.IdentifierKey
26
+
27
+
28
+ TransitionIdentifier:
29
+ type: Alias
30
+ alias: |
31
+ Union<TransitionIdentifierPhases, TransitionIdentifierTransition>
32
+
33
+
34
+ Arguments:
35
+ type: Object
36
+ properties:
37
+ entity:
38
+ type: entity.Entity
39
+ transition:
40
+ type: TransitionIdentifier
41
+
42
+ Data:
43
+ type: response.Response
44
+ properties:
@@ -0,0 +1,69 @@
1
+ $endpoint:
2
+ is_sdk: true
3
+ method: post
4
+ path: ${external}/permissions/external_set_core_permissions
5
+ function: main.site.app.external.permissions.set_core_permissions.set_core_permissions
6
+ desc: "Sets recipe related permissions"
7
+
8
+
9
+ PermissionsScopeProject:
10
+ type: Object
11
+ properties:
12
+ type:
13
+ type: Literal<'project'>
14
+ project_key:
15
+ type: identifier.IdentifierKey
16
+
17
+ PermissionsScopeRecipe:
18
+ type: Object
19
+ properties:
20
+ type:
21
+ type: Literal<'recipe'>
22
+ recipe_key:
23
+ type: identifier.IdentifierKey
24
+
25
+ PermissionsScopeMaterialFamily:
26
+ type: Object
27
+ properties:
28
+ type:
29
+ type: Literal<'material_family'>
30
+ material_family_key:
31
+ type: identifier.IdentifierKey
32
+
33
+ PermissionsScopeAllMaterialFamilies:
34
+ type: Object
35
+ properties:
36
+ type:
37
+ type: Literal<'all_material_families'>
38
+
39
+
40
+ PermissionsScope:
41
+ type: Alias
42
+ alias: |
43
+ Union<
44
+ PermissionsScopeProject,
45
+ PermissionsScopeRecipe,
46
+ PermissionsScopeMaterialFamily,
47
+ PermissionsScopeAllMaterialFamilies
48
+ >
49
+
50
+ Arguments:
51
+ type: Object
52
+ properties:
53
+ scope:
54
+ type: PermissionsScope
55
+ desc: Scope of permissions to change
56
+ user_group_ids?:
57
+ type: List<Integer>
58
+ desc: User group ids to grant permission to
59
+ user_ids?:
60
+ type: List<Integer>
61
+ desc: User ids to grant permission to
62
+ permissions_types:
63
+ type: List<permissions.CorePermissionType>
64
+ update_type:
65
+ type: post_base.UpdateType
66
+ desc: The type of update to perform
67
+
68
+ Data:
69
+ type: Object
@@ -11,9 +11,9 @@ Arguments:
11
11
  recipe_key:
12
12
  type: identifier.IdentifierKey
13
13
  desc: "Identifier for the recipe"
14
+ input_key?:
15
+ type: identifier.IdentifierKey
16
+ desc: "Identifier for an input to use for the association. Optionally supplied. If not supplied, one is created"
14
17
 
15
18
  Data:
16
- type: Object
17
- properties:
18
- result_id:
19
- type: ObjectId
19
+ type: async_batch.AsyncBatchActionReturn
@@ -1,5 +1,6 @@
1
1
  $endpoint:
2
2
  is_sdk: true
3
+ async_batch_path: create_recipe
3
4
  method: post
4
5
  path: ${external}/recipes/create_recipe
5
6
  function: main.site.app.external.recipes.create_recipe.create_recipe
@@ -24,7 +25,7 @@ Arguments:
24
25
  recipe_metadata?:
25
26
  type: List<recipe_metadata.MetadataValue>
26
27
  desc: Metadata values to populate the recipe with
27
- identifiers:
28
+ identifiers?:
28
29
  type: recipe_identifiers.RecipeIdentifiers
29
30
  desc: A recipe won't be created if it matches the identifier. An identifier must be unique in the schema
30
31
  definition_key?:
@@ -0,0 +1,16 @@
1
+ $endpoint:
2
+ is_sdk: true
3
+ method: post
4
+ path: ${external}/recipes/disassociate_recipe_as_input
5
+ function: main.site.app.external.recipes.disassociate_recipe_as_input.disassociate_recipe_as_input
6
+ desc: Remove any association between a recipe and ingredients
7
+
8
+ Arguments:
9
+ type: Object
10
+ properties:
11
+ recipe_key:
12
+ type: identifier.IdentifierKey
13
+ desc: "Identifier for the recipe"
14
+
15
+ Data:
16
+ type: async_batch.AsyncBatchActionReturn
@@ -0,0 +1,86 @@
1
+ $endpoint:
2
+ is_sdk: true
3
+ is_beta: true
4
+ async_batch_path: edit_recipe_inputs
5
+ method: post
6
+ path: ${external}/recipes/edit_recipe_inputs
7
+ function: main.site.app.external.recipes.edit_recipe_inputs.edit_recipe_inputs
8
+ desc: Clear, update, or add inputs on a recipe
9
+
10
+ RecipeInputEditType:
11
+ type: StringEnum
12
+ values:
13
+ - clear_inputs
14
+ - upsert_input
15
+ - add_input
16
+
17
+ RecipeInputEditBase:
18
+ type: Object
19
+ properties:
20
+ type:
21
+ type: RecipeInputEditType
22
+
23
+ RecipeInputEditClearInputs:
24
+ type: RecipeInputEditBase
25
+ desc: "Clears all inputs and steps from recipe workflow step to start from scratch"
26
+ properties:
27
+ type:
28
+ type: Literal<RecipeInputEditType.clear_inputs>
29
+
30
+ RecipeInputEditInputBase:
31
+ type: RecipeInputEditBase
32
+ properties:
33
+ ingredient_key:
34
+ type: identifier.IdentifierKey
35
+ quantity_basis:
36
+ type: recipe_inputs.QuantityBasis
37
+ desc: "The basis to set for this edit. Normally this should be mass for ingredients and arbitrary for process parameters"
38
+ default: mass
39
+ input_value_type:
40
+ type: recipe_inputs.InputValueType
41
+ default: value
42
+ value_numeric?:
43
+ type: Decimal
44
+ value_str?:
45
+ type: String
46
+ calculation_key?:
47
+ type: identifier.IdentifierKey
48
+ desc: The basis calculation to set
49
+
50
+ RecipeInputEditUpsertInput:
51
+ type: RecipeInputEditInputBase
52
+ desc: "If the input is already present in the recipe, modifies it otherwise adds a new one"
53
+ properties:
54
+ type:
55
+ type: Literal<RecipeInputEditType.upsert_input>
56
+ clear_first:
57
+ type: Boolean
58
+ desc: whether to clear the current value first or modify in place
59
+
60
+ RecipeInputEditAddInput:
61
+ type: RecipeInputEditInputBase
62
+ desc: "Add the input into the recipe. If it's already present and the recipe is not a mixorder, this call will fail"
63
+ properties:
64
+ type:
65
+ type: Literal<RecipeInputEditType.add_input>
66
+
67
+
68
+ RecipeInputEdit:
69
+ type: Alias
70
+ alias: Union<RecipeInputEditClearInputs, RecipeInputEditUpsertInput, RecipeInputEditAddInput>
71
+ discriminator: type
72
+
73
+ Arguments:
74
+ type: Object
75
+ properties:
76
+ recipe_key:
77
+ type: identifier.IdentifierKey
78
+ desc: "Identifier for the recipe"
79
+ recipe_workflow_step_identifier:
80
+ type: recipe_workflow_steps.RecipeWorkflowStepIdentifier
81
+ edits:
82
+ type: List<RecipeInputEdit>
83
+
84
+ Data:
85
+ type: Object
86
+ properties:
@@ -7,9 +7,12 @@ $endpoint:
7
7
  Arguments:
8
8
  type: Object
9
9
  properties:
10
- recipe_output_id:
10
+ recipe_output_id?:
11
11
  type: ObjectId
12
12
  desc: "The recipe output ID to fetch the curve for. This must be a curve recipe output. Recipe Outputs can be found from external_get_recipes_data"
13
+ recipe_input_id?:
14
+ type: ObjectId
15
+ desc: "The recipe input ID to fetch the curve for. This must be a curve recipe input. Recipe Inputs can be found from external_get_recipes_data"
13
16
 
14
17
  Data:
15
18
  type: Object
@@ -77,6 +77,9 @@ SimpleOutputCondition:
77
77
  RecipeInput:
78
78
  type: Object
79
79
  properties:
80
+ id:
81
+ type: ObjectId
82
+ desc: "The globally unique id for the recipe input"
80
83
  input_id:
81
84
  type: ObjectId
82
85
  desc: "The globally unique ID for the input, allowing for cross reference elsewhere"
@@ -92,6 +95,9 @@ RecipeInput:
92
95
  quantity_json:
93
96
  type: JsonValue
94
97
  desc: "The quantity of the input if it is not numeric or recipe type. If this is filled in, quantity_dec will not be filled in"
98
+ curve_id:
99
+ type: Optional<ObjectId>
100
+ desc: "The id of the curve associated with the input if the input is of curve type."
95
101
  actual_quantity_dec:
96
102
  type: Decimal
97
103
  desc: "The actual quantity of the input if it is numeric or recipe type. If this is filled in, actual_quantity_json will not be filled in"
@@ -1,5 +1,6 @@
1
1
  $endpoint:
2
2
  is_sdk: true
3
+ async_batch_path: set_recipe_metadata
3
4
  method: post
4
5
  path: ${external}/recipes/set_recipe_metadata
5
6
  function: main.site.app.external.recipes.set_recipe_metadata.set_recipe_metadata
@@ -0,0 +1,62 @@
1
+ $endpoint:
2
+ is_sdk: true
3
+ method: post
4
+ path: ${external}/recipes/set_recipe_tags
5
+ function: main.site.app.external.recipes.set_recipe_tags.set_recipe_tags
6
+ desc: Modifies recipes tags for a recipe
7
+
8
+ RecipeTagUpdateType:
9
+ type: StringEnum
10
+ values:
11
+ - append
12
+ - override
13
+ - remove
14
+
15
+ RecipeTagUpdateBase:
16
+ type: Object
17
+ properties:
18
+ type:
19
+ type: RecipeTagUpdateType
20
+
21
+ RecipeTagAppend:
22
+ type: RecipeTagUpdateBase
23
+ properties:
24
+ type:
25
+ type: Literal<RecipeTagUpdateType.append>
26
+ recipe_tag_ids:
27
+ type: List<ObjectId>
28
+
29
+ RecipeTagRemove:
30
+ type: RecipeTagUpdateBase
31
+ properties:
32
+ type:
33
+ type: Literal<RecipeTagUpdateType.remove>
34
+ recipe_tag_ids:
35
+ type: List<ObjectId>
36
+
37
+ RecipeTagOverride:
38
+ type: RecipeTagUpdateBase
39
+ properties:
40
+ type:
41
+ type: Literal<RecipeTagUpdateType.override>
42
+ recipe_tag_ids:
43
+ type: List<ObjectId>
44
+
45
+
46
+ RecipeTagUpdate:
47
+ type: Alias
48
+ alias: Union<RecipeTagAppend, RecipeTagRemove, RecipeTagOverride>
49
+
50
+
51
+ Arguments:
52
+ type: Object
53
+ properties:
54
+ recipe_key:
55
+ type: identifier.IdentifierKey
56
+ desc: Identifier for the recipe
57
+ recipe_tag_update:
58
+ type: RecipeTagUpdate
59
+ desc: The update to perform on the recipe tags
60
+
61
+ Data:
62
+ type: Object
@@ -1,3 +1,5 @@
1
1
  from .client import AuthDetailsApiKey, Client
2
+ from .file_upload import MediaFileUpload, UploadedFile
3
+ from .async_batch import AsyncBatchProcessor
2
4
 
3
- __all__: list[str] = ["AuthDetailsApiKey", "Client"]
5
+ __all__: list[str] = ["AuthDetailsApiKey", "AsyncBatchProcessor", "Client", "MediaFileUpload", "UploadedFile"]
@@ -0,0 +1,22 @@
1
+ from uncountable.core.client import Client
2
+ from uncountable.types.async_batch import AsyncBatchRequest
3
+ from uncountable.types.async_batch_processor import AsyncBatchProcessorBase
4
+ from uncountable.types import async_batch_t, base_t
5
+
6
+
7
+ class AsyncBatchProcessor(AsyncBatchProcessorBase):
8
+ _client: Client
9
+ _queue: list[AsyncBatchRequest]
10
+
11
+ def __init__(self, *, client: Client) -> None:
12
+ super().__init__()
13
+ self._client = client
14
+ self._queue = []
15
+
16
+ def _enqueue(self, req: async_batch_t.AsyncBatchRequest) -> None:
17
+ self._queue.append(req)
18
+
19
+ def send(self) -> base_t.ObjectId:
20
+ job_id = self._client.execute_batch_load_async(requests=self._queue).job_id
21
+ self._queue = []
22
+ return job_id