UncountablePythonSDK 0.0.15__py3-none-any.whl → 0.0.17__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 (57) hide show
  1. {UncountablePythonSDK-0.0.15.dist-info → UncountablePythonSDK-0.0.17.dist-info}/METADATA +14 -1
  2. {UncountablePythonSDK-0.0.15.dist-info → UncountablePythonSDK-0.0.17.dist-info}/RECORD +57 -20
  3. {UncountablePythonSDK-0.0.15.dist-info → UncountablePythonSDK-0.0.17.dist-info}/top_level.txt +1 -0
  4. docs/.gitignore +1 -0
  5. docs/conf.py +52 -0
  6. docs/index.md +13 -0
  7. docs/justfile +12 -0
  8. docs/quickstart.md +19 -0
  9. docs/requirements.txt +7 -0
  10. docs/static/favicons/android-chrome-192x192.png +0 -0
  11. docs/static/favicons/android-chrome-512x512.png +0 -0
  12. docs/static/favicons/apple-touch-icon.png +0 -0
  13. docs/static/favicons/browserconfig.xml +9 -0
  14. docs/static/favicons/favicon-16x16.png +0 -0
  15. docs/static/favicons/favicon-32x32.png +0 -0
  16. docs/static/favicons/manifest.json +18 -0
  17. docs/static/favicons/mstile-150x150.png +0 -0
  18. docs/static/favicons/safari-pinned-tab.svg +32 -0
  19. docs/static/logo_blue.png +0 -0
  20. examples/create_entity.py +23 -16
  21. pkgs/argument_parser/argument_parser.py +13 -6
  22. pkgs/type_spec/actions_registry/__init__.py +0 -0
  23. pkgs/type_spec/actions_registry/__main__.py +108 -0
  24. pkgs/type_spec/actions_registry/emit_typescript.py +106 -0
  25. pkgs/type_spec/builder.py +4 -0
  26. pkgs/type_spec/emit_python.py +35 -1
  27. pkgs/type_spec/emit_typescript.py +6 -7
  28. pkgs/type_spec/value_spec/emit_python.py +1 -0
  29. type_spec/external/api/chemical/convert_chemical_formats.yaml +33 -0
  30. type_spec/external/api/entity/create_entities.yaml +1 -1
  31. type_spec/external/api/entity/create_entity.yaml +1 -1
  32. type_spec/external/api/recipe_links/create_recipe_link.yaml +25 -0
  33. type_spec/external/api/recipes/associate_recipe_as_input.yaml +19 -0
  34. type_spec/external/api/recipes/associate_recipe_as_lot.yaml +19 -0
  35. type_spec/external/api/recipes/create_recipe.yaml +35 -0
  36. type_spec/external/api/recipes/set_recipe_inputs.yaml +3 -0
  37. type_spec/external/api/recipes/set_recipe_metadata.yaml +19 -0
  38. uncountable/core/client.py +13 -14
  39. uncountable/integration/executors/script_executor.py +7 -6
  40. uncountable/types/__init__.py +20 -0
  41. uncountable/types/api/chemical/__init__.py +1 -0
  42. uncountable/types/api/chemical/convert_chemical_formats.py +50 -0
  43. uncountable/types/api/entity/create_entities.py +1 -1
  44. uncountable/types/api/entity/create_entity.py +1 -1
  45. uncountable/types/api/recipe_links/__init__.py +1 -0
  46. uncountable/types/api/recipe_links/create_recipe_link.py +39 -0
  47. uncountable/types/api/recipes/associate_recipe_as_input.py +35 -0
  48. uncountable/types/api/recipes/associate_recipe_as_lot.py +36 -0
  49. uncountable/types/api/recipes/create_recipe.py +41 -0
  50. uncountable/types/api/recipes/set_recipe_inputs.py +1 -0
  51. uncountable/types/api/recipes/set_recipe_metadata.py +36 -0
  52. uncountable/types/async_batch.py +23 -0
  53. uncountable/types/chemical_structure.py +27 -0
  54. uncountable/types/client_base.py +303 -2
  55. uncountable/types/identifier.py +54 -0
  56. uncountable/types/recipe_identifiers.py +62 -0
  57. {UncountablePythonSDK-0.0.15.dist-info → UncountablePythonSDK-0.0.17.dist-info}/WHEEL +0 -0
@@ -0,0 +1,106 @@
1
+ import io
2
+ from collections import defaultdict
3
+
4
+ from main.base.types import actions_registry_t
5
+
6
+ from ...type_spec import builder
7
+ from ..emit_typescript_util import INDENT, MODIFY_NOTICE, ts_name
8
+ from ..util import encode_common_string
9
+
10
+
11
+ def _action_symbol_name(action_definition: actions_registry_t.ActionDefinition) -> str:
12
+ return f"{ts_name(action_definition.ref_name, name_case=builder.NameCase.convert)}"
13
+
14
+
15
+ def _action_module_name(module: str) -> str:
16
+ return f"ActionsRegistryT.ActionsRegistryModule.{ts_name(module, name_case=builder.NameCase.convert)}"
17
+
18
+
19
+ def emit_action_definitions(
20
+ action_definitions: defaultdict[str, list[actions_registry_t.ActionDefinition]],
21
+ ) -> str:
22
+ out = io.StringIO()
23
+ out.write(MODIFY_NOTICE)
24
+ out.write("\n")
25
+ out.write('import { ActionsRegistryT } from "unc_mat/types"\n\n')
26
+ out.write(MODIFY_NOTICE)
27
+ out.write("export const actionDefinitions = {\n")
28
+ modules = []
29
+ for key, values in action_definitions.items():
30
+ out.write(MODIFY_NOTICE)
31
+ out.write(f"{INDENT}[{_action_module_name(key)}]: {{\n")
32
+ modules.append(key)
33
+ for action_definition in values:
34
+ out.write(_emit_action_definition(action_definition, INDENT * 2))
35
+ out.write(f"{INDENT}}},\n")
36
+
37
+ out.write("}\n")
38
+ out.write(MODIFY_NOTICE)
39
+ out.write("\n")
40
+ out.write(_emit_action_definition_types(modules, indent=""))
41
+ out.write(MODIFY_NOTICE)
42
+ out.write("\n")
43
+
44
+ return out.getvalue()
45
+
46
+
47
+ def _emit_action_definition(
48
+ action_definition: actions_registry_t.ActionDefinition, indent: str
49
+ ) -> str:
50
+ out = io.StringIO()
51
+
52
+ sub_indent = indent + INDENT
53
+ out.write(f"{indent}{_action_symbol_name(action_definition)}: {{\n")
54
+ out.write(f"{sub_indent}name: {encode_common_string(action_definition.name)},\n")
55
+ if action_definition.icon is not None:
56
+ out.write(f"{sub_indent}icon: {encode_common_string(action_definition.icon)},\n")
57
+ out.write(
58
+ f"{sub_indent}shortDescription: {encode_common_string(action_definition.short_description)},\n"
59
+ )
60
+ out.write(
61
+ f"{sub_indent}description: {encode_common_string(action_definition.description)},\n"
62
+ )
63
+ out.write(
64
+ f"{sub_indent}refName: {encode_common_string(action_definition.ref_name)},\n"
65
+ )
66
+ out.write(f"{sub_indent}module: {_action_module_name(action_definition.module)},\n")
67
+ if (
68
+ action_definition.visibility_scope is not None
69
+ and len(action_definition.visibility_scope) > 0
70
+ ):
71
+ out.write(
72
+ f"{sub_indent}visibilityScope: {_emit_visibility_scope(action_definition.visibility_scope)},\n"
73
+ )
74
+ out.write(f"{indent}}},\n")
75
+ return out.getvalue()
76
+
77
+
78
+ def _emit_action_definition_types(modules: list[str], indent: str) -> str:
79
+ out = io.StringIO()
80
+
81
+ sub_indent = indent + INDENT
82
+ out.write(
83
+ f"{indent}type RefNameKeys<M extends ActionsRegistryT.ActionsRegistryModule> = keyof (typeof actionDefinitions)[M]\n"
84
+ )
85
+ out.write(
86
+ f"{indent}type ActionDefinitionIdentifierGetter<M extends ActionsRegistryT.ActionsRegistryModule> = {{ module: M; refName: RefNameKeys<M> }}\n"
87
+ )
88
+ out.write(f"{indent}export type ActionDefinitionIdentifier =\n")
89
+ for module in modules:
90
+ out.write(
91
+ f"{sub_indent}| ActionDefinitionIdentifierGetter<{_action_module_name(module)}>\n"
92
+ )
93
+ out.write("\n")
94
+ return out.getvalue()
95
+
96
+
97
+ def _emit_visibility_scope(
98
+ visibility_scope: list[actions_registry_t.ActionDefinitionVisibilityScope],
99
+ ) -> str:
100
+ visibility_scope_types = ",".join([
101
+ f"ActionsRegistryT.ActionDefinitionVisibilityScope.{ts_name(visibility_item, name_case=builder.NameCase.convert)}"
102
+ for visibility_item in visibility_scope
103
+ if visibility_item is not None
104
+ ])
105
+
106
+ return f"[ {visibility_scope_types} ]"
pkgs/type_spec/builder.py CHANGED
@@ -747,6 +747,10 @@ class SpecEndpoint:
747
747
  self.is_external = self.path_root == "api/external"
748
748
  self.has_attachment = data.get("has_attachment", False)
749
749
 
750
+ assert (
751
+ not is_sdk or self.desc is not None
752
+ ), f"Endpoint description required for SDK endpoints, missing: {path}"
753
+
750
754
 
751
755
  def _parse_const(
752
756
  builder: SpecBuilder,
@@ -347,6 +347,34 @@ def _validate_supports_handler_generation(
347
347
  return stype
348
348
 
349
349
 
350
+ def _emit_endpoint_invocation_docstring(
351
+ ctx: Context,
352
+ endpoint: builder.SpecEndpoint,
353
+ arguments_type: builder.SpecTypeDefnObject,
354
+ ) -> None:
355
+ has_argument_desc = arguments_type.properties is not None and any(
356
+ prop.desc is not None for prop in arguments_type.properties.values()
357
+ )
358
+ has_endpoint_desc = endpoint.desc
359
+ if not has_argument_desc and not has_endpoint_desc:
360
+ return
361
+
362
+ FULL_INDENT = INDENT * 2
363
+ ctx.out.write(FULL_INDENT)
364
+ ctx.out.write('"""')
365
+
366
+ if endpoint.desc is not None and has_endpoint_desc:
367
+ ctx.out.write(f"{endpoint.desc}\n")
368
+ ctx.out.write("\n")
369
+
370
+ if arguments_type.properties is not None and has_argument_desc:
371
+ for prop in arguments_type.properties.values():
372
+ if prop.desc:
373
+ ctx.out.write(f"{FULL_INDENT}:param {prop.name}: {prop.desc}\n")
374
+
375
+ ctx.out.write(f'{FULL_INDENT}"""\n')
376
+
377
+
350
378
  def _emit_endpoint_invocation_function(
351
379
  ctx: Context, namespace: builder.SpecNamespace
352
380
  ) -> None:
@@ -393,8 +421,14 @@ def _emit_endpoint_invocation_function(
393
421
  class_out=ctx.out,
394
422
  )
395
423
  ctx.out.write(f"{INDENT}) -> {refer_to(ctx=ctx, stype=data_type)}:")
396
-
397
424
  ctx.out.write("\n")
425
+
426
+ _emit_endpoint_invocation_docstring(
427
+ ctx=ctx,
428
+ endpoint=endpoint,
429
+ arguments_type=arguments_type,
430
+ )
431
+
398
432
  ctx.out.write(f"{INDENT}{INDENT}args = {refer_to(ctx=ctx, stype=arguments_type)}(")
399
433
  if has_arguments:
400
434
  assert arguments_type.properties is not None
@@ -226,20 +226,20 @@ def _emit_endpoint(
226
226
 
227
227
  if is_binary:
228
228
  tsx_response_part = f"""import {{ {wrap_name} }} from "unc_base/api"
229
- import {{ Arguments }} from "{type_path}"
229
+ import type {{ Arguments }} from "{type_path}"
230
230
 
231
231
  export type {{ Arguments }}
232
232
  """
233
233
  elif has_data and endpoint.has_attachment:
234
- tsx_response_part = f"""import {{ {wrap_name}, AttachmentResponse }} from "unc_base/api"
235
- import {{ Arguments, Data }} from "{type_path}"
234
+ tsx_response_part = f"""import {{ {wrap_name}, type AttachmentResponse }} from "unc_base/api"
235
+ import type {{ Arguments, Data }} from "{type_path}"
236
236
 
237
237
  export type {{ Arguments, Data }}
238
238
  export type Response = AttachmentResponse<Data>
239
239
  """
240
240
  elif has_data:
241
- tsx_response_part = f"""import {{ {wrap_name}, JsonResponse }} from "unc_base/api"
242
- import {{ Arguments, Data }} from "{type_path}"
241
+ tsx_response_part = f"""import {{ {wrap_name}, type JsonResponse }} from "unc_base/api"
242
+ import type {{ Arguments, Data }} from "{type_path}"
243
243
 
244
244
  export type {{ Arguments, Data }}
245
245
  export type Response = JsonResponse<Data>
@@ -248,7 +248,7 @@ export type Response = JsonResponse<Data>
248
248
  else:
249
249
  assert has_deprecated_result
250
250
  tsx_response_part = f"""import {{ {wrap_name} }} from "unc_base/api"
251
- import {{ Arguments, DeprecatedResult }} from "{type_path}"
251
+ import type {{ Arguments, DeprecatedResult }} from "{type_path}"
252
252
 
253
253
  export type {{ Arguments }}
254
254
  export type Response = DeprecatedResult
@@ -278,7 +278,6 @@ export const apiCall = {wrap_call}(
278
278
  if need_index:
279
279
  with open(index_path, "a") as index:
280
280
  print(f"Updated API Index {index_path}")
281
- index.write("\n// eslint-disable-next-line import/first\n")
282
281
  index.write(f'import * as {api_name} from "./{namespace.path[-1]}"\n\n')
283
282
  index.write(f"export {{ {api_name} }}\n")
284
283
 
@@ -22,6 +22,7 @@ def emit_functions(functions: list[value_spec_t.Function]) -> str:
22
22
  out.write(
23
23
  f"""{MODIFY_NOTICE}
24
24
  {LINT_HEADER}
25
+ import datetime
25
26
  from typing import cast, Union
26
27
 
27
28
  from decimal import Decimal
@@ -0,0 +1,33 @@
1
+ $endpoint:
2
+ is_sdk: true
3
+ method: post
4
+ path: ${external}/chemical/convert_chemical_formats
5
+ function: main.site.app.external.chemical.convert_chemical_formats.convert_chemical_formats
6
+ desc: Converts chemical formats, into the format used by Uncountable and usable in other APIs for eg. set_input_attribute_values
7
+
8
+
9
+ ChemicalStructureFile:
10
+ type: Object
11
+ properties:
12
+ struct_file:
13
+ type: String
14
+ desc: Stringified chemical structure. MOL v2000/3000, SMILES, Part of SDF.
15
+
16
+ Arguments:
17
+ type: Object
18
+ properties:
19
+ source_chemical_structures:
20
+ type: List<ChemicalStructureFile>
21
+
22
+ UncountableChemicalStructure:
23
+ type: Alias
24
+ alias: Dict<String, JsonValue>
25
+ desc: Uncountable format for chemical structures compatible with set ingredient attributes api etc.
26
+
27
+ Data:
28
+ type: Object
29
+ properties:
30
+ chemical_structures:
31
+ type: List<UncountableChemicalStructure>
32
+ desc: The parsed chemical structures in the Uncountable format
33
+ convert_value: no_convert
@@ -19,7 +19,7 @@ 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>>
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>>
23
23
  desc: "The type of the entities to create"
24
24
  entities_to_create:
25
25
  type: List<EntityToCreate>
@@ -26,7 +26,7 @@ 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>>
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>>
30
30
  desc: "The type of the entities requested"
31
31
  field_values?:
32
32
  type: Optional<List<field_values.FieldRefNameValue>>
@@ -0,0 +1,25 @@
1
+ $endpoint:
2
+ is_sdk: true
3
+ method: post
4
+ path: ${external}/recipe_links/create_recipe_link
5
+ function: main.site.app.external.recipe_links.create_recipe_link.create_recipe_link
6
+ desc: Create a link between two recipes. Skip if the link already exists
7
+
8
+ Arguments:
9
+ type: Object
10
+ properties:
11
+ recipe_from_key:
12
+ type: identifier.IdentifierKey
13
+ desc: "Identifier for the recipe the link comes from"
14
+ recipe_to_key:
15
+ type: identifier.IdentifierKey
16
+ desc: "Identifier for the recipe the link goes to"
17
+ link_type:
18
+ type: Union<Literal<recipe_links.RecipeLinkType.child>, Literal<recipe_links.RecipeLinkType.control>, Literal<recipe_links.RecipeLinkType.user_link>>
19
+ desc: "The type of link being created"
20
+ name:
21
+ type: String
22
+ desc: "The name used for the link"
23
+
24
+ Data:
25
+ type: async_batch.AsyncBatchActionReturn
@@ -0,0 +1,19 @@
1
+ $endpoint:
2
+ is_sdk: true
3
+ method: post
4
+ path: ${external}/recipes/associate_recipe_as_input
5
+ function: main.site.app.external.recipes.associate_recipe_as_input.associate_recipe_as_input
6
+ desc: Create or return the input association for a recipe
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: Object
17
+ properties:
18
+ result_id:
19
+ type: ObjectId
@@ -0,0 +1,19 @@
1
+ $endpoint:
2
+ is_sdk: true
3
+ method: post
4
+ path: ${external}/recipes/associate_recipe_as_lot
5
+ function: main.site.app.external.recipes.associate_recipe_as_lot.associate_recipe_as_lot
6
+ desc: Create a new lot association for the provided recipe with the provided ingredient
7
+
8
+ Arguments:
9
+ type: Object
10
+ properties:
11
+ recipe_key:
12
+ type: identifier.IdentifierKey
13
+ desc: "Identifier for the recipe"
14
+ ingredient_key:
15
+ type: identifier.IdentifierKey
16
+ desc: "Identifier for the ingredient"
17
+
18
+ Data:
19
+ type: async_batch.AsyncBatchActionReturn
@@ -0,0 +1,35 @@
1
+ $endpoint:
2
+ is_sdk: true
3
+ method: post
4
+ path: ${external}/recipes/create_recipe
5
+ function: main.site.app.external.recipes.create_recipe.create_recipe
6
+ desc: Returns the id of the recipe being created.
7
+
8
+
9
+ Arguments:
10
+ type: Object
11
+ properties:
12
+ name?:
13
+ type: String
14
+ desc: The name for the recipe
15
+ material_family_id:
16
+ type: ObjectId
17
+ desc: The material family for the recipe
18
+ workflow_id:
19
+ type: ObjectId
20
+ desc: The identifier of the workflow to create the recipe with
21
+ workflow_variant_id?:
22
+ type: Optional<ObjectId>
23
+ desc: The identifier of the workflow variant to create the recipe with
24
+ recipe_metadata?:
25
+ type: List<recipe_metadata.MetadataValue>
26
+ desc: Metadata values to populate the recipe with
27
+ identifiers:
28
+ type: recipe_identifiers.RecipeIdentifiers
29
+ desc: A recipe won't be created if it matches the identifier. An identifier must be unique in the schema
30
+
31
+ Data:
32
+ type: Object
33
+ properties:
34
+ result_id:
35
+ type: ObjectId
@@ -25,6 +25,9 @@ RecipeInputValue:
25
25
  set_actual_value?:
26
26
  type: Boolean
27
27
  desc: "If True, modify the actual value for the input. If not provided or False, modify the set value for the input."
28
+ lot_recipe_id?:
29
+ type: ObjectId
30
+ desc: The recipe id for a lot to be associated to this recipe input. If the recipe is not a lot, it will be created as a lot.
28
31
 
29
32
  Arguments:
30
33
  type: Object
@@ -0,0 +1,19 @@
1
+ $endpoint:
2
+ is_sdk: true
3
+ method: post
4
+ path: ${external}/recipes/set_recipe_metadata
5
+ function: main.site.app.external.recipes.set_recipe_metadata.set_recipe_metadata
6
+ desc: "Set metadata values on a recipe"
7
+
8
+ Arguments:
9
+ type: Object
10
+ properties:
11
+ recipe_key:
12
+ type: identifier.IdentifierKey
13
+ desc: "Identifier for the recipe"
14
+ recipe_metadata:
15
+ type: List<recipe_metadata.MetadataValue>
16
+ desc: "Metadata values to populate the recipe with"
17
+
18
+ Data:
19
+ type: Object
@@ -1,15 +1,15 @@
1
+ import base64
2
+ import json
3
+ import typing
1
4
  from dataclasses import dataclass
2
5
  from enum import StrEnum
3
- import json
4
6
  from urllib.parse import urljoin
5
- import base64
6
- import typing
7
+
7
8
  import requests
8
- from uncountable.types.base import JsonValue
9
- from uncountable.types.client_base import APIRequest, ClientMethods
9
+
10
10
  from pkgs.argument_parser import CachedParser
11
11
  from pkgs.serialization_util import serialize_for_api
12
-
12
+ from uncountable.types.client_base import APIRequest, ClientMethods
13
13
 
14
14
  DT = typing.TypeVar("DT")
15
15
 
@@ -24,8 +24,8 @@ class HTTPRequestBase:
24
24
  method: EndpointMethod
25
25
  url: str
26
26
  headers: dict[str, str]
27
- body: typing.Optional[str] = None
28
- query_params: typing.Optional[dict[str, str]]
27
+ body: typing.Optional[typing.Union[str, dict[str, str]]] = None
28
+ query_params: typing.Optional[dict[str, str]] = None
29
29
 
30
30
 
31
31
  @dataclass(kw_only=True)
@@ -37,6 +37,7 @@ class HTTPGetRequest(HTTPRequestBase):
37
37
  @dataclass(kw_only=True)
38
38
  class HTTPPostRequest(HTTPRequestBase):
39
39
  method: typing.Literal[EndpointMethod.POST]
40
+ body: typing.Union[str, dict[str, str]]
40
41
 
41
42
 
42
43
  HTTPRequest = HTTPPostRequest | HTTPGetRequest
@@ -98,9 +99,7 @@ class Client(ClientMethods):
98
99
  match self._auth_details:
99
100
  case AuthDetailsApiKey():
100
101
  encoded = base64.standard_b64encode(
101
- f"{self._auth_details.api_id}:{self._auth_details.api_secret_key}".encode(
102
- "utf-8"
103
- )
102
+ f"{self._auth_details.api_id}:{self._auth_details.api_secret_key}".encode()
104
103
  ).decode("utf-8")
105
104
  return {"Authorization": f"Basic {encoded}"}
106
105
  typing.assert_never(self._auth_details)
@@ -108,21 +107,21 @@ class Client(ClientMethods):
108
107
  def _build_http_request(self, *, api_request: APIRequest) -> HTTPRequest:
109
108
  headers = self._build_auth_headers()
110
109
  method = api_request.method.lower()
111
- query_params = {"data": json.dumps(serialize_for_api(api_request.args))}
110
+ data = {"data": json.dumps(serialize_for_api(api_request.args))}
112
111
  match method:
113
112
  case "get":
114
113
  return HTTPGetRequest(
115
114
  method=EndpointMethod.GET,
116
115
  url=urljoin(self._base_url, api_request.endpoint),
117
- query_params=query_params,
116
+ query_params=data,
118
117
  headers=headers,
119
118
  )
120
119
  case "post":
121
120
  return HTTPPostRequest(
122
121
  method=EndpointMethod.POST,
123
122
  url=urljoin(self._base_url, api_request.endpoint),
123
+ body=data,
124
124
  headers=headers,
125
- query_params=query_params
126
125
  )
127
126
  case _:
128
127
  raise ValueError(f"unsupported request method: {method}")
@@ -1,18 +1,19 @@
1
1
 
2
-
2
+ import os
3
3
  import importlib
4
4
  import inspect
5
5
  from uncountable.integration.job import Job
6
- from uncountable.integration.types import JobExecutorScript
6
+ from uncountable.integration.types import JobExecutorScript, ProfileMetadata
7
7
 
8
8
 
9
- def resolve_script_executor(executor: JobExecutorScript) -> type[Job]:
10
- job_module = importlib.import_module(executor.import_path)
9
+ def resolve_script_executor(executor: JobExecutorScript, profile_metadata: ProfileMetadata) -> type[Job]:
10
+ job_module_path = ".".join([os.environ["UNC_PROFILES_MODULE"], profile_metadata.name, executor.import_path])
11
+ job_module = importlib.import_module(job_module_path)
11
12
  found_jobs: list[type[Job]] = []
12
13
  for _, job_class in inspect.getmembers(job_module, inspect.isclass):
13
- if Job in job_class.__bases__:
14
+ if getattr(job_class, "_unc_job_registered", False):
14
15
  found_jobs.append(job_class())
15
16
  assert (
16
17
  len(found_jobs) == 1
17
- ), f"expected exactly one job class in {executor.import_path}"
18
+ ), f"expected exactly one job class in {executor.import_path}, found {len(found_jobs)}"
18
19
  return found_jobs[0]
@@ -3,11 +3,18 @@
3
3
  # ruff: noqa: E402
4
4
  # fmt: off
5
5
  # isort: skip_file
6
+ from .api.recipes import associate_recipe_as_input as associate_recipe_as_input_t
7
+ from .api.recipes import associate_recipe_as_lot as associate_recipe_as_lot_t
8
+ from . import async_batch as async_batch_t
6
9
  from . import base as base_t
7
10
  from . import calculations as calculations_t
11
+ from . import chemical_structure as chemical_structure_t
12
+ from .api.chemical import convert_chemical_formats as convert_chemical_formats_t
8
13
  from .api.entity import create_entities as create_entities_t
9
14
  from .api.entity import create_entity as create_entity_t
10
15
  from .api.inputs import create_inputs as create_inputs_t
16
+ from .api.recipes import create_recipe as create_recipe_t
17
+ from .api.recipe_links import create_recipe_link as create_recipe_link_t
11
18
  from .api.recipes import create_recipes as create_recipes_t
12
19
  from . import curves as curves_t
13
20
  from . import entity as entity_t
@@ -31,11 +38,13 @@ from .api.recipe_metadata import get_recipe_metadata_data as get_recipe_metadata
31
38
  from .api.recipes import get_recipe_names as get_recipe_names_t
32
39
  from .api.recipes import get_recipe_output_metadata as get_recipe_output_metadata_t
33
40
  from .api.recipes import get_recipes_data as get_recipes_data_t
41
+ from . import identifier as identifier_t
34
42
  from . import input_attributes as input_attributes_t
35
43
  from . import inputs as inputs_t
36
44
  from .api.entity import list_entities as list_entities_t
37
45
  from . import outputs as outputs_t
38
46
  from . import phases as phases_t
47
+ from . import recipe_identifiers as recipe_identifiers_t
39
48
  from . import recipe_links as recipe_links_t
40
49
  from . import recipe_metadata as recipe_metadata_t
41
50
  from . import recipe_output_metadata as recipe_output_metadata_t
@@ -45,6 +54,7 @@ from .api.outputs import resolve_output_conditions as resolve_output_conditions_
45
54
  from . import response as response_t
46
55
  from .api.inputs import set_input_attribute_values as set_input_attribute_values_t
47
56
  from .api.recipes import set_recipe_inputs as set_recipe_inputs_t
57
+ from .api.recipes import set_recipe_metadata as set_recipe_metadata_t
48
58
  from .api.recipes import set_recipe_outputs as set_recipe_outputs_t
49
59
  from .api.entity import set_values as set_values_t
50
60
  from . import units as units_t
@@ -53,11 +63,18 @@ from . import workflows as workflows_t
53
63
 
54
64
 
55
65
  __all__: list[str] = [
66
+ "associate_recipe_as_input_t",
67
+ "associate_recipe_as_lot_t",
68
+ "async_batch_t",
56
69
  "base_t",
57
70
  "calculations_t",
71
+ "chemical_structure_t",
72
+ "convert_chemical_formats_t",
58
73
  "create_entities_t",
59
74
  "create_entity_t",
60
75
  "create_inputs_t",
76
+ "create_recipe_t",
77
+ "create_recipe_link_t",
61
78
  "create_recipes_t",
62
79
  "curves_t",
63
80
  "entity_t",
@@ -81,11 +98,13 @@ __all__: list[str] = [
81
98
  "get_recipe_names_t",
82
99
  "get_recipe_output_metadata_t",
83
100
  "get_recipes_data_t",
101
+ "identifier_t",
84
102
  "input_attributes_t",
85
103
  "inputs_t",
86
104
  "list_entities_t",
87
105
  "outputs_t",
88
106
  "phases_t",
107
+ "recipe_identifiers_t",
89
108
  "recipe_links_t",
90
109
  "recipe_metadata_t",
91
110
  "recipe_output_metadata_t",
@@ -95,6 +114,7 @@ __all__: list[str] = [
95
114
  "response_t",
96
115
  "set_input_attribute_values_t",
97
116
  "set_recipe_inputs_t",
117
+ "set_recipe_metadata_t",
98
118
  "set_recipe_outputs_t",
99
119
  "set_values_t",
100
120
  "units_t",
@@ -0,0 +1 @@
1
+ # DO NOT MODIFY -- This file is generated by type_spec
@@ -0,0 +1,50 @@
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 dataclasses import dataclass
11
+ from pkgs.serialization import serial_class
12
+ from ... import base as base_t
13
+
14
+ __all__: list[str] = [
15
+ "Arguments",
16
+ "ChemicalStructureFile",
17
+ "Data",
18
+ "ENDPOINT_METHOD",
19
+ "ENDPOINT_PATH",
20
+ "UncountableChemicalStructure",
21
+ ]
22
+
23
+ ENDPOINT_METHOD = "POST"
24
+ ENDPOINT_PATH = "api/external/chemical/convert_chemical_formats"
25
+
26
+
27
+ # DO NOT MODIFY -- This file is generated by type_spec
28
+ @dataclass(kw_only=True)
29
+ class ChemicalStructureFile:
30
+ struct_file: str
31
+
32
+
33
+ # DO NOT MODIFY -- This file is generated by type_spec
34
+ @dataclass(kw_only=True)
35
+ class Arguments:
36
+ source_chemical_structures: list[ChemicalStructureFile]
37
+
38
+
39
+ # DO NOT MODIFY -- This file is generated by type_spec
40
+ UncountableChemicalStructure = dict[str, base_t.JsonValue]
41
+
42
+
43
+ # DO NOT MODIFY -- This file is generated by type_spec
44
+ @serial_class(
45
+ unconverted_values={"chemical_structures"},
46
+ )
47
+ @dataclass(kw_only=True)
48
+ class Data:
49
+ chemical_structures: list[UncountableChemicalStructure]
50
+ # DO NOT MODIFY -- This file is generated by type_spec
@@ -34,7 +34,7 @@ class EntityToCreate:
34
34
  @dataclass(kw_only=True)
35
35
  class Arguments:
36
36
  definition_id: base_t.ObjectId
37
- 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.TASK], typing.Literal[entity_t.EntityType.PROJECT]]
37
+ 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.TASK], typing.Literal[entity_t.EntityType.PROJECT], typing.Literal[entity_t.EntityType.EQUIPMENT], typing.Literal[entity_t.EntityType.INV_LOCAL_LOCATIONS]]
38
38
  entities_to_create: list[EntityToCreate]
39
39
 
40
40
 
@@ -40,7 +40,7 @@ class EntityFieldInitialValue:
40
40
  @dataclass(kw_only=True)
41
41
  class Arguments:
42
42
  definition_id: base_t.ObjectId
43
- 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.TASK], typing.Literal[entity_t.EntityType.PROJECT]]
43
+ 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.TASK], typing.Literal[entity_t.EntityType.PROJECT], typing.Literal[entity_t.EntityType.EQUIPMENT], typing.Literal[entity_t.EntityType.INV_LOCAL_LOCATIONS]]
44
44
  field_values: typing.Optional[typing.Optional[list[field_values_t.FieldRefNameValue]]] = None
45
45
 
46
46