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.
- {UncountablePythonSDK-0.0.15.dist-info → UncountablePythonSDK-0.0.17.dist-info}/METADATA +14 -1
- {UncountablePythonSDK-0.0.15.dist-info → UncountablePythonSDK-0.0.17.dist-info}/RECORD +57 -20
- {UncountablePythonSDK-0.0.15.dist-info → UncountablePythonSDK-0.0.17.dist-info}/top_level.txt +1 -0
- docs/.gitignore +1 -0
- docs/conf.py +52 -0
- docs/index.md +13 -0
- docs/justfile +12 -0
- docs/quickstart.md +19 -0
- docs/requirements.txt +7 -0
- docs/static/favicons/android-chrome-192x192.png +0 -0
- docs/static/favicons/android-chrome-512x512.png +0 -0
- docs/static/favicons/apple-touch-icon.png +0 -0
- docs/static/favicons/browserconfig.xml +9 -0
- docs/static/favicons/favicon-16x16.png +0 -0
- docs/static/favicons/favicon-32x32.png +0 -0
- docs/static/favicons/manifest.json +18 -0
- docs/static/favicons/mstile-150x150.png +0 -0
- docs/static/favicons/safari-pinned-tab.svg +32 -0
- docs/static/logo_blue.png +0 -0
- examples/create_entity.py +23 -16
- pkgs/argument_parser/argument_parser.py +13 -6
- pkgs/type_spec/actions_registry/__init__.py +0 -0
- pkgs/type_spec/actions_registry/__main__.py +108 -0
- pkgs/type_spec/actions_registry/emit_typescript.py +106 -0
- pkgs/type_spec/builder.py +4 -0
- pkgs/type_spec/emit_python.py +35 -1
- pkgs/type_spec/emit_typescript.py +6 -7
- pkgs/type_spec/value_spec/emit_python.py +1 -0
- type_spec/external/api/chemical/convert_chemical_formats.yaml +33 -0
- type_spec/external/api/entity/create_entities.yaml +1 -1
- type_spec/external/api/entity/create_entity.yaml +1 -1
- type_spec/external/api/recipe_links/create_recipe_link.yaml +25 -0
- type_spec/external/api/recipes/associate_recipe_as_input.yaml +19 -0
- type_spec/external/api/recipes/associate_recipe_as_lot.yaml +19 -0
- type_spec/external/api/recipes/create_recipe.yaml +35 -0
- type_spec/external/api/recipes/set_recipe_inputs.yaml +3 -0
- type_spec/external/api/recipes/set_recipe_metadata.yaml +19 -0
- uncountable/core/client.py +13 -14
- uncountable/integration/executors/script_executor.py +7 -6
- uncountable/types/__init__.py +20 -0
- uncountable/types/api/chemical/__init__.py +1 -0
- uncountable/types/api/chemical/convert_chemical_formats.py +50 -0
- uncountable/types/api/entity/create_entities.py +1 -1
- uncountable/types/api/entity/create_entity.py +1 -1
- uncountable/types/api/recipe_links/__init__.py +1 -0
- uncountable/types/api/recipe_links/create_recipe_link.py +39 -0
- uncountable/types/api/recipes/associate_recipe_as_input.py +35 -0
- uncountable/types/api/recipes/associate_recipe_as_lot.py +36 -0
- uncountable/types/api/recipes/create_recipe.py +41 -0
- uncountable/types/api/recipes/set_recipe_inputs.py +1 -0
- uncountable/types/api/recipes/set_recipe_metadata.py +36 -0
- uncountable/types/async_batch.py +23 -0
- uncountable/types/chemical_structure.py +27 -0
- uncountable/types/client_base.py +303 -2
- uncountable/types/identifier.py +54 -0
- uncountable/types/recipe_identifiers.py +62 -0
- {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,
|
pkgs/type_spec/emit_python.py
CHANGED
|
@@ -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
|
|
|
@@ -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
|
uncountable/core/client.py
CHANGED
|
@@ -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
|
-
|
|
6
|
-
import typing
|
|
7
|
+
|
|
7
8
|
import requests
|
|
8
|
-
|
|
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
|
-
|
|
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=
|
|
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
|
-
|
|
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
|
|
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]
|
uncountable/types/__init__.py
CHANGED
|
@@ -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
|
|