UncountablePythonSDK 0.0.83__py3-none-any.whl → 0.0.132__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.
- docs/conf.py +54 -7
- docs/index.md +107 -4
- docs/integration_examples/create_ingredient.md +43 -0
- docs/integration_examples/create_output.md +56 -0
- docs/integration_examples/index.md +6 -0
- docs/justfile +2 -2
- docs/requirements.txt +6 -4
- examples/basic_auth.py +7 -0
- examples/create_ingredient_sdk.py +34 -0
- examples/download_files.py +26 -0
- examples/integration-server/jobs/materials_auto/concurrent_cron.py +11 -0
- examples/integration-server/jobs/materials_auto/example_cron.py +3 -0
- examples/integration-server/jobs/materials_auto/example_http.py +47 -0
- examples/integration-server/jobs/materials_auto/example_instrument.py +100 -0
- examples/integration-server/jobs/materials_auto/example_parse.py +140 -0
- examples/integration-server/jobs/materials_auto/example_predictions.py +61 -0
- examples/integration-server/jobs/materials_auto/example_runsheet_wh.py +39 -0
- examples/integration-server/jobs/materials_auto/example_wh.py +17 -9
- examples/integration-server/jobs/materials_auto/profile.yaml +61 -0
- examples/integration-server/pyproject.toml +10 -10
- examples/oauth.py +7 -0
- examples/set_recipe_metadata_file.py +1 -1
- examples/upload_files.py +1 -2
- pkgs/argument_parser/__init__.py +8 -0
- pkgs/argument_parser/_is_namedtuple.py +3 -0
- pkgs/argument_parser/argument_parser.py +196 -63
- pkgs/filesystem_utils/__init__.py +1 -0
- pkgs/filesystem_utils/_blob_session.py +144 -0
- pkgs/filesystem_utils/_gdrive_session.py +5 -5
- pkgs/filesystem_utils/_s3_session.py +2 -1
- pkgs/filesystem_utils/_sftp_session.py +6 -3
- pkgs/filesystem_utils/file_type_utils.py +30 -10
- pkgs/serialization/__init__.py +7 -2
- pkgs/serialization/annotation.py +64 -0
- pkgs/serialization/missing_sentry.py +1 -1
- pkgs/serialization/opaque_key.py +1 -1
- pkgs/serialization/serial_alias.py +47 -0
- pkgs/serialization/serial_class.py +40 -48
- pkgs/serialization/serial_generic.py +16 -0
- pkgs/serialization/serial_union.py +16 -16
- pkgs/serialization_util/__init__.py +6 -0
- pkgs/serialization_util/dataclasses.py +14 -0
- pkgs/serialization_util/serialization_helpers.py +15 -5
- pkgs/type_spec/actions_registry/__main__.py +0 -4
- pkgs/type_spec/actions_registry/emit_typescript.py +2 -4
- pkgs/type_spec/builder.py +248 -70
- pkgs/type_spec/builder_types.py +9 -0
- pkgs/type_spec/config.py +40 -7
- pkgs/type_spec/cross_output_links.py +99 -0
- pkgs/type_spec/emit_open_api.py +121 -34
- pkgs/type_spec/emit_open_api_util.py +5 -5
- pkgs/type_spec/emit_python.py +277 -86
- pkgs/type_spec/emit_typescript.py +102 -29
- pkgs/type_spec/emit_typescript_util.py +66 -10
- pkgs/type_spec/load_types.py +16 -3
- pkgs/type_spec/non_discriminated_union_exceptions.py +14 -0
- pkgs/type_spec/open_api_util.py +29 -4
- pkgs/type_spec/parts/base.py.prepart +11 -8
- pkgs/type_spec/parts/base.ts.prepart +4 -0
- pkgs/type_spec/type_info/__main__.py +3 -1
- pkgs/type_spec/type_info/emit_type_info.py +115 -22
- pkgs/type_spec/ui_entry_actions/__init__.py +4 -0
- pkgs/type_spec/ui_entry_actions/generate_ui_entry_actions.py +308 -0
- pkgs/type_spec/util.py +3 -3
- pkgs/type_spec/value_spec/__main__.py +26 -9
- pkgs/type_spec/value_spec/convert_type.py +18 -0
- pkgs/type_spec/value_spec/emit_python.py +13 -3
- pkgs/type_spec/value_spec/types.py +1 -1
- uncountable/core/async_batch.py +1 -1
- uncountable/core/client.py +133 -34
- uncountable/core/environment.py +3 -3
- uncountable/core/file_upload.py +39 -15
- uncountable/integration/cli.py +116 -23
- uncountable/integration/construct_client.py +3 -3
- uncountable/integration/executors/executors.py +12 -2
- uncountable/integration/executors/generic_upload_executor.py +66 -14
- uncountable/integration/http_server/__init__.py +5 -0
- uncountable/integration/http_server/types.py +69 -0
- uncountable/integration/job.py +192 -7
- uncountable/integration/queue_runner/command_server/__init__.py +4 -0
- uncountable/integration/queue_runner/command_server/command_client.py +65 -0
- uncountable/integration/queue_runner/command_server/command_server.py +83 -5
- uncountable/integration/queue_runner/command_server/constants.py +4 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server.proto +36 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.py +28 -11
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.pyi +77 -1
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2_grpc.py +135 -0
- uncountable/integration/queue_runner/command_server/types.py +25 -2
- uncountable/integration/queue_runner/datastore/datastore_sqlite.py +168 -11
- uncountable/integration/queue_runner/datastore/interface.py +10 -0
- uncountable/integration/queue_runner/datastore/model.py +8 -1
- uncountable/integration/queue_runner/job_scheduler.py +63 -23
- uncountable/integration/queue_runner/queue_runner.py +10 -2
- uncountable/integration/queue_runner/worker.py +3 -5
- uncountable/integration/scan_profiles.py +1 -1
- uncountable/integration/scheduler.py +74 -25
- uncountable/integration/secret_retrieval/retrieve_secret.py +1 -1
- uncountable/integration/server.py +42 -12
- uncountable/integration/telemetry.py +63 -10
- uncountable/integration/webhook_server/entrypoint.py +39 -112
- uncountable/types/__init__.py +58 -1
- uncountable/types/api/batch/execute_batch.py +5 -6
- uncountable/types/api/batch/execute_batch_load_async.py +2 -3
- uncountable/types/api/chemical/convert_chemical_formats.py +10 -5
- uncountable/types/api/condition_parameters/__init__.py +1 -0
- uncountable/types/api/condition_parameters/upsert_condition_match.py +72 -0
- uncountable/types/api/entity/create_entities.py +7 -7
- uncountable/types/api/entity/create_entity.py +8 -8
- uncountable/types/api/entity/create_or_update_entity.py +48 -0
- uncountable/types/api/entity/export_entities.py +59 -0
- uncountable/types/api/entity/get_entities_data.py +3 -4
- uncountable/types/api/entity/grant_entity_permissions.py +6 -6
- uncountable/types/api/entity/list_aggregate.py +79 -0
- uncountable/types/api/entity/list_entities.py +34 -10
- uncountable/types/api/entity/lock_entity.py +4 -4
- uncountable/types/api/entity/lookup_entity.py +116 -0
- uncountable/types/api/entity/resolve_entity_ids.py +5 -6
- uncountable/types/api/entity/set_entity_field_values.py +44 -0
- uncountable/types/api/entity/set_values.py +3 -3
- uncountable/types/api/entity/transition_entity_phase.py +14 -7
- uncountable/types/api/entity/unlock_entity.py +3 -3
- uncountable/types/api/equipment/associate_equipment_input.py +2 -3
- uncountable/types/api/field_options/upsert_field_options.py +7 -7
- uncountable/types/api/files/__init__.py +1 -0
- uncountable/types/api/files/download_file.py +77 -0
- uncountable/types/api/id_source/list_id_source.py +6 -7
- uncountable/types/api/id_source/match_id_source.py +4 -5
- uncountable/types/api/input_groups/get_input_group_names.py +3 -4
- uncountable/types/api/inputs/create_inputs.py +10 -9
- uncountable/types/api/inputs/get_input_data.py +11 -12
- uncountable/types/api/inputs/get_input_names.py +6 -7
- uncountable/types/api/inputs/get_inputs_data.py +6 -7
- uncountable/types/api/inputs/set_input_attribute_values.py +5 -6
- uncountable/types/api/inputs/set_input_category.py +5 -5
- uncountable/types/api/inputs/set_input_subcategories.py +3 -3
- uncountable/types/api/inputs/set_intermediate_type.py +4 -4
- uncountable/types/api/integrations/__init__.py +1 -0
- uncountable/types/api/integrations/publish_realtime_data.py +41 -0
- uncountable/types/api/integrations/push_notification.py +49 -0
- uncountable/types/api/integrations/register_sockets_token.py +41 -0
- uncountable/types/api/listing/__init__.py +1 -0
- uncountable/types/api/listing/fetch_listing.py +58 -0
- uncountable/types/api/material_families/update_entity_material_families.py +3 -4
- uncountable/types/api/notebooks/__init__.py +1 -0
- uncountable/types/api/notebooks/add_notebook_content.py +119 -0
- uncountable/types/api/outputs/get_output_data.py +12 -13
- uncountable/types/api/outputs/get_output_names.py +5 -6
- uncountable/types/api/outputs/get_output_organization.py +173 -0
- uncountable/types/api/outputs/resolve_output_conditions.py +7 -8
- uncountable/types/api/permissions/set_core_permissions.py +16 -10
- uncountable/types/api/project/get_projects.py +6 -7
- uncountable/types/api/project/get_projects_data.py +7 -8
- uncountable/types/api/recipe_links/create_recipe_link.py +5 -5
- uncountable/types/api/recipe_links/remove_recipe_link.py +4 -4
- uncountable/types/api/recipe_metadata/get_recipe_metadata_data.py +6 -7
- uncountable/types/api/recipes/add_recipe_to_project.py +3 -3
- uncountable/types/api/recipes/add_time_series_data.py +64 -0
- uncountable/types/api/recipes/archive_recipes.py +4 -4
- uncountable/types/api/recipes/associate_recipe_as_input.py +5 -5
- uncountable/types/api/recipes/associate_recipe_as_lot.py +3 -3
- uncountable/types/api/recipes/clear_recipe_outputs.py +3 -3
- uncountable/types/api/recipes/create_mix_order.py +44 -0
- uncountable/types/api/recipes/create_recipe.py +8 -9
- uncountable/types/api/recipes/create_recipes.py +8 -9
- uncountable/types/api/recipes/disassociate_recipe_as_input.py +3 -3
- uncountable/types/api/recipes/edit_recipe_inputs.py +101 -24
- uncountable/types/api/recipes/get_column_calculation_values.py +4 -5
- uncountable/types/api/recipes/get_curve.py +4 -5
- uncountable/types/api/recipes/get_recipe_calculations.py +6 -7
- uncountable/types/api/recipes/get_recipe_links.py +3 -4
- uncountable/types/api/recipes/get_recipe_names.py +3 -4
- uncountable/types/api/recipes/get_recipe_output_metadata.py +5 -6
- uncountable/types/api/recipes/get_recipes_data.py +62 -34
- uncountable/types/api/recipes/lock_recipes.py +9 -8
- uncountable/types/api/recipes/remove_recipe_from_project.py +3 -3
- uncountable/types/api/recipes/set_recipe_inputs.py +9 -10
- uncountable/types/api/recipes/set_recipe_metadata.py +3 -3
- uncountable/types/api/recipes/set_recipe_output_annotations.py +11 -12
- uncountable/types/api/recipes/set_recipe_output_file.py +5 -6
- uncountable/types/api/recipes/set_recipe_outputs.py +24 -13
- uncountable/types/api/recipes/set_recipe_tags.py +14 -9
- uncountable/types/api/recipes/set_recipe_total.py +59 -0
- uncountable/types/api/recipes/unarchive_recipes.py +3 -3
- uncountable/types/api/recipes/unlock_recipes.py +7 -6
- uncountable/types/api/runsheet/__init__.py +1 -0
- uncountable/types/api/runsheet/complete_async_upload.py +41 -0
- uncountable/types/api/triggers/run_trigger.py +4 -4
- uncountable/types/api/uploader/complete_async_parse.py +46 -0
- uncountable/types/api/uploader/invoke_uploader.py +4 -5
- uncountable/types/api/user/__init__.py +1 -0
- uncountable/types/api/user/get_current_user_info.py +40 -0
- uncountable/types/async_batch.py +1 -1
- uncountable/types/async_batch_processor.py +506 -23
- uncountable/types/async_batch_t.py +35 -8
- uncountable/types/async_jobs.py +0 -1
- uncountable/types/async_jobs_t.py +1 -2
- uncountable/types/auth_retrieval.py +0 -1
- uncountable/types/auth_retrieval_t.py +6 -6
- uncountable/types/base.py +0 -1
- uncountable/types/base_t.py +11 -9
- uncountable/types/calculations.py +0 -1
- uncountable/types/calculations_t.py +1 -2
- uncountable/types/chemical_structure.py +0 -1
- uncountable/types/chemical_structure_t.py +5 -5
- uncountable/types/client_base.py +614 -69
- uncountable/types/client_config.py +1 -1
- uncountable/types/client_config_t.py +13 -3
- uncountable/types/curves.py +0 -1
- uncountable/types/curves_t.py +6 -7
- uncountable/types/data.py +12 -0
- uncountable/types/data_t.py +103 -0
- uncountable/types/entity.py +1 -1
- uncountable/types/entity_t.py +90 -10
- uncountable/types/experiment_groups.py +0 -1
- uncountable/types/experiment_groups_t.py +1 -2
- uncountable/types/exports.py +8 -0
- uncountable/types/exports_t.py +34 -0
- uncountable/types/field_values.py +19 -1
- uncountable/types/field_values_t.py +242 -9
- uncountable/types/fields.py +0 -1
- uncountable/types/fields_t.py +1 -2
- uncountable/types/generic_upload.py +0 -1
- uncountable/types/generic_upload_t.py +14 -14
- uncountable/types/id_source.py +0 -1
- uncountable/types/id_source_t.py +13 -7
- uncountable/types/identifier.py +0 -1
- uncountable/types/identifier_t.py +10 -5
- uncountable/types/input_attributes.py +0 -1
- uncountable/types/input_attributes_t.py +3 -4
- uncountable/types/inputs.py +0 -1
- uncountable/types/inputs_t.py +3 -4
- uncountable/types/integration_server.py +0 -1
- uncountable/types/integration_server_t.py +13 -4
- uncountable/types/integration_session.py +10 -0
- uncountable/types/integration_session_t.py +60 -0
- uncountable/types/integrations.py +10 -0
- uncountable/types/integrations_t.py +62 -0
- uncountable/types/job_definition.py +2 -1
- uncountable/types/job_definition_t.py +57 -32
- uncountable/types/listing.py +9 -0
- uncountable/types/listing_t.py +51 -0
- uncountable/types/notices.py +8 -0
- uncountable/types/notices_t.py +37 -0
- uncountable/types/notifications.py +11 -0
- uncountable/types/notifications_t.py +74 -0
- uncountable/types/outputs.py +0 -1
- uncountable/types/outputs_t.py +2 -3
- uncountable/types/overrides.py +0 -1
- uncountable/types/overrides_t.py +10 -4
- uncountable/types/permissions.py +0 -1
- uncountable/types/permissions_t.py +1 -2
- uncountable/types/phases.py +0 -1
- uncountable/types/phases_t.py +1 -2
- uncountable/types/post_base.py +0 -1
- uncountable/types/post_base_t.py +1 -2
- uncountable/types/queued_job.py +2 -1
- uncountable/types/queued_job_t.py +29 -12
- uncountable/types/recipe_identifiers.py +0 -1
- uncountable/types/recipe_identifiers_t.py +18 -8
- uncountable/types/recipe_inputs.py +0 -1
- uncountable/types/recipe_inputs_t.py +1 -2
- uncountable/types/recipe_links.py +0 -1
- uncountable/types/recipe_links_t.py +3 -4
- uncountable/types/recipe_metadata.py +0 -1
- uncountable/types/recipe_metadata_t.py +9 -10
- uncountable/types/recipe_output_metadata.py +0 -1
- uncountable/types/recipe_output_metadata_t.py +1 -2
- uncountable/types/recipe_tags.py +0 -1
- uncountable/types/recipe_tags_t.py +1 -2
- uncountable/types/recipe_workflow_steps.py +0 -1
- uncountable/types/recipe_workflow_steps_t.py +7 -7
- uncountable/types/recipes.py +0 -1
- uncountable/types/recipes_t.py +2 -2
- uncountable/types/response.py +0 -1
- uncountable/types/response_t.py +2 -2
- uncountable/types/secret_retrieval.py +0 -1
- uncountable/types/secret_retrieval_t.py +7 -7
- uncountable/types/sockets.py +20 -0
- uncountable/types/sockets_t.py +169 -0
- uncountable/types/structured_filters.py +25 -0
- uncountable/types/structured_filters_t.py +248 -0
- uncountable/types/units.py +0 -1
- uncountable/types/units_t.py +1 -2
- uncountable/types/uploader.py +24 -0
- uncountable/types/uploader_t.py +222 -0
- uncountable/types/users.py +0 -1
- uncountable/types/users_t.py +1 -2
- uncountable/types/webhook_job.py +1 -1
- uncountable/types/webhook_job_t.py +14 -3
- uncountable/types/workflows.py +0 -1
- uncountable/types/workflows_t.py +3 -4
- uncountablepythonsdk-0.0.132.dist-info/METADATA +64 -0
- uncountablepythonsdk-0.0.132.dist-info/RECORD +363 -0
- {UncountablePythonSDK-0.0.83.dist-info → uncountablepythonsdk-0.0.132.dist-info}/WHEEL +1 -1
- UncountablePythonSDK-0.0.83.dist-info/METADATA +0 -60
- UncountablePythonSDK-0.0.83.dist-info/RECORD +0 -292
- docs/quickstart.md +0 -19
- {UncountablePythonSDK-0.0.83.dist-info → uncountablepythonsdk-0.0.132.dist-info}/top_level.txt +0 -0
docs/conf.py
CHANGED
|
@@ -6,10 +6,15 @@
|
|
|
6
6
|
# -- Project information -----------------------------------------------------
|
|
7
7
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
import datetime
|
|
10
|
+
|
|
11
|
+
from docutils import nodes # type: ignore[import-untyped]
|
|
12
|
+
from sphinx.addnodes import pending_xref # type: ignore[import-not-found]
|
|
13
|
+
from sphinx.application import Sphinx # type: ignore[import-not-found]
|
|
14
|
+
from sphinx.environment import BuildEnvironment # type: ignore[import-not-found]
|
|
10
15
|
|
|
11
16
|
project = "Uncountable SDK"
|
|
12
|
-
copyright = f"{
|
|
17
|
+
copyright = f"{datetime.datetime.now(tz=datetime.UTC).date().year}, Uncountable Inc"
|
|
13
18
|
author = "Uncountable Inc"
|
|
14
19
|
|
|
15
20
|
# -- General configuration ---------------------------------------------------
|
|
@@ -22,22 +27,23 @@ extensions = [
|
|
|
22
27
|
"sphinx_copybutton",
|
|
23
28
|
"sphinx_favicon",
|
|
24
29
|
]
|
|
25
|
-
myst_enable_extensions = ["fieldlist", "deflist"]
|
|
30
|
+
myst_enable_extensions = ["fieldlist", "deflist", "colon_fence"]
|
|
26
31
|
|
|
27
32
|
autoapi_dirs = ["../uncountable"]
|
|
28
33
|
autoapi_options = [
|
|
29
34
|
"members",
|
|
35
|
+
"inherited-members",
|
|
30
36
|
"undoc-members",
|
|
31
|
-
"show-inheritance",
|
|
32
|
-
"show-module-summary",
|
|
33
|
-
"imported-members",
|
|
34
37
|
]
|
|
38
|
+
autoapi_root = "api"
|
|
35
39
|
autoapi_ignore = ["*integration*"]
|
|
36
40
|
autodoc_typehints = "description"
|
|
41
|
+
autoapi_member_order = "groupwise"
|
|
42
|
+
autoapi_own_page_level = "class"
|
|
37
43
|
|
|
38
44
|
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
|
|
39
45
|
|
|
40
|
-
|
|
46
|
+
python_use_unqualified_type_names = True
|
|
41
47
|
# -- Options for HTML output -------------------------------------------------
|
|
42
48
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
|
43
49
|
|
|
@@ -55,3 +61,44 @@ favicons = [
|
|
|
55
61
|
"favicons/mstile-150x150.png",
|
|
56
62
|
"favicons/safari-pinned-tab.svg",
|
|
57
63
|
]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _hook_missing_reference(
|
|
67
|
+
_app: Sphinx, _env: BuildEnvironment, node: pending_xref, contnode: nodes.Text
|
|
68
|
+
) -> nodes.reference | None:
|
|
69
|
+
"""
|
|
70
|
+
Manually resolve reference when autoapi reference resolution fails.
|
|
71
|
+
This is necessary because autoapi does not fully support type aliases.
|
|
72
|
+
"""
|
|
73
|
+
# example reftarget value: uncountable.types.identifier_t.IdentifierKey
|
|
74
|
+
target = node.get("reftarget", "")
|
|
75
|
+
|
|
76
|
+
# example refdoc value: api/uncountable/types/generic_upload_t/GenericUploadStrategy
|
|
77
|
+
current_doc = node.get("refdoc", "")
|
|
78
|
+
|
|
79
|
+
if not target.startswith("uncountable"):
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
target_module, target_name = target.rsplit(".", 1)
|
|
83
|
+
|
|
84
|
+
# construct relative path from current doc page to target page
|
|
85
|
+
relative_segments_to_root = [".." for _ in current_doc.split("/")]
|
|
86
|
+
relative_segments_to_target = target_module.split(".")
|
|
87
|
+
|
|
88
|
+
# example full relative path: ../../../../../api/uncountable/types/identifier_t/#uncountable.types.identifier_t.IdentifierKey
|
|
89
|
+
full_relative_path = "/".join([
|
|
90
|
+
*relative_segments_to_root,
|
|
91
|
+
autoapi_root,
|
|
92
|
+
*relative_segments_to_target,
|
|
93
|
+
f"#{target}",
|
|
94
|
+
])
|
|
95
|
+
|
|
96
|
+
return nodes.reference(
|
|
97
|
+
text=target_name if python_use_unqualified_type_names else target,
|
|
98
|
+
children=[contnode],
|
|
99
|
+
refuri=full_relative_path,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def setup(app: Sphinx) -> None:
|
|
104
|
+
app.connect("missing-reference", _hook_missing_reference)
|
docs/index.md
CHANGED
|
@@ -3,11 +3,114 @@
|
|
|
3
3
|

|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
The Uncountable Python SDK is a python package that provides a wrapper around the Uncountable REST API.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
Using this SDK provides the following advantages:
|
|
9
|
+
|
|
10
|
+
- In-editor parameter/type safety
|
|
11
|
+
- Automatic parsing of response data.
|
|
12
|
+
- Reduced code boilerplate
|
|
13
|
+
- Helper methods
|
|
14
|
+
|
|
15
|
+
## Getting Started
|
|
16
|
+
The first step in any integration is to create a [Client](uncountable.core.client.Client) object. The client provides access to all available SDK methods, and includes built-in request authentication & error propagation.
|
|
17
|
+
|
|
18
|
+
### Creating a Client
|
|
19
|
+
Create a client using one of the supported authentication mechanisms. API credentials can be generated by a member of Uncountable staff.
|
|
20
|
+
|
|
21
|
+
::::{tab-set}
|
|
22
|
+
:::{tab-item} Basic Auth
|
|
23
|
+
```{literalinclude} ../examples/basic_auth.py
|
|
24
|
+
```
|
|
25
|
+
:::
|
|
26
|
+
|
|
27
|
+
:::{tab-item} OAuth
|
|
28
|
+
```{literalinclude} ../examples/oauth.py
|
|
29
|
+
```
|
|
30
|
+
:::
|
|
31
|
+
::::
|
|
32
|
+
|
|
33
|
+
The provided code examples assume that a Client has been created and stored in the `client` variable
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
### Basic Usage
|
|
37
|
+
|
|
38
|
+
:::{dropdown} List Ingredient Names & IDs
|
|
39
|
+
```{code-block} python
|
|
40
|
+
from uncountable.types import entity_t, id_source_t
|
|
41
|
+
|
|
42
|
+
client.list_id_source(
|
|
43
|
+
spec=id_source_t.IdSourceSpecEntity(entity_type=entity_t.EntityType.INGREDIENT),
|
|
44
|
+
search_label="",
|
|
45
|
+
)
|
|
46
|
+
```
|
|
47
|
+
Example Response:
|
|
48
|
+
```code
|
|
49
|
+
Data(
|
|
50
|
+
results=[
|
|
51
|
+
IdName(id=1, name='Filler'),
|
|
52
|
+
IdName(id=2, name='Calcium Oxide 2'),
|
|
53
|
+
IdName(id=3, name='Carbon Black'),
|
|
54
|
+
]
|
|
55
|
+
)
|
|
56
|
+
```
|
|
57
|
+
:::
|
|
58
|
+
|
|
59
|
+
:::{dropdown} Create an Experiment
|
|
60
|
+
```{code-block} python
|
|
61
|
+
client.create_recipe(material_family_id=1, workflow_id=1, name="Example Recipe")
|
|
62
|
+
```
|
|
63
|
+
Example Response:
|
|
64
|
+
```code
|
|
65
|
+
Data(result_id=52271)
|
|
66
|
+
```
|
|
67
|
+
:::
|
|
68
|
+
|
|
69
|
+
:::{dropdown} Upload a file
|
|
70
|
+
```{code-block} python
|
|
71
|
+
from uncountable.core.file_upload import MediaFileUpload
|
|
72
|
+
|
|
73
|
+
client.upload_files(file_uploads=[MediaFileUpload(path="/path/to/local/example_file.pdf")])
|
|
74
|
+
```
|
|
75
|
+
Example Response:
|
|
76
|
+
```code
|
|
77
|
+
[
|
|
78
|
+
UploadedFile(name='example_file.pdf', file_id=718)
|
|
79
|
+
]
|
|
80
|
+
```
|
|
81
|
+
:::
|
|
82
|
+
|
|
83
|
+
[More examples](integration_examples/index)
|
|
84
|
+
|
|
85
|
+
## Errors
|
|
86
|
+
Client methods will raise Exceptions when the API returns codes in the `3xx`, `4xx` or `5xx` ranges. Ensure all method calls are wrapped in Exception handling logic.
|
|
87
|
+
|
|
88
|
+
## Pagination
|
|
89
|
+
Many of the Uncountable APIs require pagination to fetch more than 100 results at once. The following code snippet implements pagination to fetch the Names & IDs of all Projects:
|
|
90
|
+
:::{dropdown} Pagination Example
|
|
91
|
+
```{code-block} python
|
|
92
|
+
from uncountable.types import entity_t, id_source_t
|
|
93
|
+
from uncountable.types.api.id_source.list_id_source import IdName
|
|
10
94
|
|
|
11
|
-
|
|
95
|
+
def fetch_all_projects(client: Client) -> list[IdName]:
|
|
96
|
+
projects: list[IdName] = []
|
|
97
|
+
while True:
|
|
98
|
+
response = client.list_id_source(
|
|
99
|
+
spec=IdSourceSpecEntity(entity_type=entity_t.EntityType.PROJECT),
|
|
100
|
+
search_label="",
|
|
101
|
+
offset=len(projects),
|
|
102
|
+
)
|
|
103
|
+
projects.extend(response.results)
|
|
104
|
+
if len(response.results) < 100:
|
|
105
|
+
return projects
|
|
12
106
|
```
|
|
107
|
+
:::
|
|
13
108
|
|
|
109
|
+
|
|
110
|
+
```{toctree}
|
|
111
|
+
:hidden:
|
|
112
|
+
Overview <self>
|
|
113
|
+
Available SDK Methods <api/uncountable/core/client/Client>
|
|
114
|
+
integration_examples/index
|
|
115
|
+
SDK Reference <api/uncountable/index>
|
|
116
|
+
```
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Create an Ingredient
|
|
2
|
+
|
|
3
|
+
Use the `create_or_update_entity` method to create Ingredients.
|
|
4
|
+
|
|
5
|
+
The following fields are required when creating an Ingredient:
|
|
6
|
+
- `name`: The name of the Ingredient
|
|
7
|
+
- `core_ingredient_ingredientMaterialFamilies`: The list of material families in which to include the Ingredient
|
|
8
|
+
|
|
9
|
+
The reference name of the default definition of Ingredients is `uncIngredient`
|
|
10
|
+
|
|
11
|
+
This is an example of a minimal ingredient creation call
|
|
12
|
+
|
|
13
|
+
```{code-block} python
|
|
14
|
+
from uncountable.types import entity_t, field_values_t, identifier_t
|
|
15
|
+
|
|
16
|
+
client.create_or_update_entity(
|
|
17
|
+
entity_type=entity_t.EntityType.INGREDIENT,
|
|
18
|
+
definition_key=identifier_t.IdentifierKeyRefName(ref_name="uncIngredient"),
|
|
19
|
+
field_values=[
|
|
20
|
+
field_values_t.FieldArgumentValue(
|
|
21
|
+
field_key=identifier_t.IdentifierKeyRefName(
|
|
22
|
+
ref_name="core_ingredient_ingredientMaterialFamilies"
|
|
23
|
+
),
|
|
24
|
+
value=field_values_t.FieldValueIds(
|
|
25
|
+
entity_type=entity_t.EntityType.MATERIAL_FAMILY,
|
|
26
|
+
identifier_keys=[identifier_t.IdentifierKeyId(id=1)],
|
|
27
|
+
),
|
|
28
|
+
),
|
|
29
|
+
field_values_t.FieldArgumentValue(
|
|
30
|
+
field_key=identifier_t.IdentifierKeyRefName(ref_name="name"),
|
|
31
|
+
value=field_values_t.FieldValueText(value="Example Ingredient"),
|
|
32
|
+
),
|
|
33
|
+
],
|
|
34
|
+
)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Example Response:
|
|
38
|
+
```{code}
|
|
39
|
+
Data(modification_made=True, result_id=3124, entity=None, result_values=None)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Optional fields:
|
|
43
|
+
- `core_ingredient_quantityType`: The quantity type of the ingredient (default is `numeric`)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Create an Output
|
|
2
|
+
|
|
3
|
+
Use the `create_or_update_entity` method to create Outputs.
|
|
4
|
+
|
|
5
|
+
The following fields are required when creating an Output:
|
|
6
|
+
- `name`: The name of the Output
|
|
7
|
+
- `core_output_unitsId`: The unit the output is measured in
|
|
8
|
+
- `core_output_outputMaterialFamilies`: The list of material families in which to include the Output
|
|
9
|
+
- `core_output_quantityType`: The quantity type of the output
|
|
10
|
+
|
|
11
|
+
The reference name of the default definition of Ingredients is `unc_output_definition`
|
|
12
|
+
|
|
13
|
+
This is an example of a minimal output creation call
|
|
14
|
+
|
|
15
|
+
```{code-block} python
|
|
16
|
+
from uncountable.types import entity_t, field_values_t, identifier_t
|
|
17
|
+
|
|
18
|
+
client.create_or_update_entity(
|
|
19
|
+
entity_type=entity_t.EntityType.OUTPUT,
|
|
20
|
+
definition_key=identifier_t.IdentifierKeyRefName(ref_name="unc_output_definition"),
|
|
21
|
+
field_values=[
|
|
22
|
+
field_values_t.FieldArgumentValue(
|
|
23
|
+
field_key=identifier_t.IdentifierKeyRefName(ref_name="name"),
|
|
24
|
+
value=field_values_t.FieldValueText(value="Example Output"),
|
|
25
|
+
),
|
|
26
|
+
field_values_t.FieldArgumentValue(
|
|
27
|
+
field_key=identifier_t.IdentifierKeyRefName(ref_name="core_output_unitsId"),
|
|
28
|
+
value=field_values_t.FieldValueId(
|
|
29
|
+
entity_type=entity_t.EntityType.UNITS,
|
|
30
|
+
identifier_key=identifier_t.IdentifierKeyId(id=1),
|
|
31
|
+
),
|
|
32
|
+
),
|
|
33
|
+
field_values_t.FieldArgumentValue(
|
|
34
|
+
field_key=identifier_t.IdentifierKeyRefName(
|
|
35
|
+
ref_name="core_output_outputMaterialFamilies"
|
|
36
|
+
),
|
|
37
|
+
value=field_values_t.FieldValueIds(
|
|
38
|
+
entity_type=entity_t.EntityType.MATERIAL_FAMILY,
|
|
39
|
+
identifier_keys=[identifier_t.IdentifierKeyId(id=1)],
|
|
40
|
+
),
|
|
41
|
+
),
|
|
42
|
+
field_values_t.FieldArgumentValue(
|
|
43
|
+
field_key=identifier_t.IdentifierKeyRefName(
|
|
44
|
+
ref_name="core_output_quantityType"
|
|
45
|
+
),
|
|
46
|
+
value=field_values_t.FieldValueFieldOption(value="numeric"),
|
|
47
|
+
),
|
|
48
|
+
],
|
|
49
|
+
)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Example Response:
|
|
53
|
+
|
|
54
|
+
```{code}
|
|
55
|
+
Data(modification_made=True, result_id=653, entity=None, result_values=None)
|
|
56
|
+
```
|
docs/justfile
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
docs-setup-python:
|
|
2
|
-
|
|
2
|
+
curl -LsSf https://astral.sh/uv/0.8.4/install.sh | sh
|
|
3
|
+
uv pip install -r requirements.txt
|
|
3
4
|
|
|
4
5
|
docs-clean:
|
|
5
6
|
rm -rf _build/
|
|
@@ -9,4 +10,3 @@ docs-build: docs-clean
|
|
|
9
10
|
|
|
10
11
|
docs-serve: docs-build
|
|
11
12
|
npx serve _build/dirhtml
|
|
12
|
-
|
docs/requirements.txt
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
furo==
|
|
2
|
-
myst-parser==4.0.
|
|
3
|
-
sphinx-autoapi==3.
|
|
1
|
+
furo==2025.9.25
|
|
2
|
+
myst-parser==4.0.1
|
|
3
|
+
sphinx-autoapi==3.6.0
|
|
4
4
|
sphinx-copybutton==0.5.2
|
|
5
|
-
Sphinx==8.
|
|
5
|
+
Sphinx==8.2.0
|
|
6
6
|
sphinx_design==0.6.1
|
|
7
7
|
sphinx-favicon==1.0.1
|
|
8
|
+
astroid==3.3.8
|
|
9
|
+
docutils==0.21.2
|
examples/basic_auth.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
import uncountable.types.api.inputs.create_inputs as create_inputs
|
|
4
|
+
from uncountable.core import AuthDetailsApiKey, Client
|
|
5
|
+
from uncountable.types import field_values_t, inputs_t
|
|
6
|
+
|
|
7
|
+
client = Client(
|
|
8
|
+
base_url="http://localhost:5000",
|
|
9
|
+
auth_details=AuthDetailsApiKey(
|
|
10
|
+
api_id=os.environ["UNC_API_ID"],
|
|
11
|
+
api_secret_key=os.environ["UNC_API_SECRET_KEY"],
|
|
12
|
+
),
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
client.external_create_inputs(
|
|
16
|
+
inputs_to_create=[
|
|
17
|
+
create_inputs.InputToCreate(
|
|
18
|
+
name="sdk test ing",
|
|
19
|
+
material_family_ids=[1],
|
|
20
|
+
quantity_type=inputs_t.IngredientQuantityType.NUMERIC,
|
|
21
|
+
type=inputs_t.IngredientType.INGREDIENT,
|
|
22
|
+
field_values=[
|
|
23
|
+
field_values_t.FieldRefNameValue(
|
|
24
|
+
field_ref_name="carrieTestNumericField",
|
|
25
|
+
value="10",
|
|
26
|
+
),
|
|
27
|
+
field_values_t.FieldRefNameValue(
|
|
28
|
+
field_ref_name="carrieTestCheckboxField",
|
|
29
|
+
value=True,
|
|
30
|
+
),
|
|
31
|
+
],
|
|
32
|
+
)
|
|
33
|
+
]
|
|
34
|
+
)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pprint import pprint
|
|
3
|
+
|
|
4
|
+
from uncountable.core import AuthDetailsApiKey, Client
|
|
5
|
+
from uncountable.types import download_file_t, entity_t, identifier_t
|
|
6
|
+
|
|
7
|
+
client = Client(
|
|
8
|
+
base_url="http://localhost:5000",
|
|
9
|
+
auth_details=AuthDetailsApiKey(
|
|
10
|
+
api_id=os.environ["UNC_API_ID"],
|
|
11
|
+
api_secret_key=os.environ["UNC_API_SECRET_KEY"],
|
|
12
|
+
),
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
file_query = download_file_t.FileDownloadQueryEntityField(
|
|
16
|
+
entity=entity_t.EntityIdentifier(
|
|
17
|
+
type=entity_t.EntityType.LAB_REQUEST,
|
|
18
|
+
identifier_key=identifier_t.IdentifierKeyId(id=2375),
|
|
19
|
+
),
|
|
20
|
+
field_key=identifier_t.IdentifierKeyRefName(ref_name="attachments"),
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
downloaded = client.download_files(
|
|
24
|
+
file_query=file_query,
|
|
25
|
+
)
|
|
26
|
+
pprint(downloaded)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
from uncountable.integration.job import CronJob, JobArguments, register_job
|
|
4
|
+
from uncountable.types.job_definition_t import JobResult
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@register_job
|
|
8
|
+
class MyConcurrentCronJob(CronJob):
|
|
9
|
+
def run(self, args: JobArguments) -> JobResult:
|
|
10
|
+
time.sleep(10)
|
|
11
|
+
return JobResult(success=True)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
1
3
|
from uncountable.integration.job import CronJob, JobArguments, register_job
|
|
2
4
|
from uncountable.types import entity_t
|
|
3
5
|
from uncountable.types.job_definition_t import JobResult
|
|
@@ -15,4 +17,5 @@ class MyCronJob(CronJob):
|
|
|
15
17
|
if field_val.field_ref_name == "name":
|
|
16
18
|
name = field_val.value
|
|
17
19
|
args.logger.log_info(f"material family found with name: {name}")
|
|
20
|
+
time.sleep(1.5)
|
|
18
21
|
return JobResult(success=True)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from uncountable.integration.http_server import (
|
|
4
|
+
GenericHttpRequest,
|
|
5
|
+
GenericHttpResponse,
|
|
6
|
+
)
|
|
7
|
+
from uncountable.integration.http_server.types import HttpException
|
|
8
|
+
from uncountable.integration.job import CustomHttpJob, register_job
|
|
9
|
+
from uncountable.types import job_definition_t
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(kw_only=True)
|
|
13
|
+
class ExampleWebhookPayload:
|
|
14
|
+
id: int
|
|
15
|
+
message: str
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
_EXPECTED_USER_ID = 1
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@register_job
|
|
22
|
+
class HttpExample(CustomHttpJob):
|
|
23
|
+
@staticmethod
|
|
24
|
+
def validate_request(
|
|
25
|
+
*,
|
|
26
|
+
request: GenericHttpRequest, # noqa: ARG004
|
|
27
|
+
job_definition: job_definition_t.HttpJobDefinitionBase, # noqa: ARG004
|
|
28
|
+
profile_meta: job_definition_t.ProfileMetadata, # noqa: ARG004
|
|
29
|
+
) -> None:
|
|
30
|
+
if (
|
|
31
|
+
CustomHttpJob.get_validated_oauth_request_user_id(
|
|
32
|
+
request=request, profile_metadata=profile_meta
|
|
33
|
+
)
|
|
34
|
+
!= _EXPECTED_USER_ID
|
|
35
|
+
):
|
|
36
|
+
raise HttpException(
|
|
37
|
+
message="unauthorized; invalid oauth token", error_code=401
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
@staticmethod
|
|
41
|
+
def handle_request(
|
|
42
|
+
*,
|
|
43
|
+
request: GenericHttpRequest, # noqa: ARG004
|
|
44
|
+
job_definition: job_definition_t.HttpJobDefinitionBase, # noqa: ARG004
|
|
45
|
+
profile_meta: job_definition_t.ProfileMetadata, # noqa: ARG004
|
|
46
|
+
) -> GenericHttpResponse:
|
|
47
|
+
return GenericHttpResponse(response="OK", status_code=200)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import time
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from decimal import Decimal
|
|
5
|
+
|
|
6
|
+
from uncountable.integration.job import JobArguments, WebhookJob, register_job
|
|
7
|
+
from uncountable.types import (
|
|
8
|
+
base_t,
|
|
9
|
+
identifier_t,
|
|
10
|
+
job_definition_t,
|
|
11
|
+
sockets_t,
|
|
12
|
+
)
|
|
13
|
+
from uncountable.types.integration_session_t import IntegrationSessionInstrument
|
|
14
|
+
from websockets.sync.client import connect
|
|
15
|
+
from websockets.typing import Data
|
|
16
|
+
|
|
17
|
+
from pkgs.argument_parser.argument_parser import CachedParser
|
|
18
|
+
from pkgs.serialization_util import serialize_for_api
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(kw_only=True)
|
|
22
|
+
class InstrumentPayload:
|
|
23
|
+
equipment_id: base_t.ObjectId
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@register_job
|
|
27
|
+
class InstrumentExample(WebhookJob[InstrumentPayload]):
|
|
28
|
+
def run(
|
|
29
|
+
self, args: JobArguments, payload: InstrumentPayload
|
|
30
|
+
) -> job_definition_t.JobResult:
|
|
31
|
+
parser: CachedParser[sockets_t.SocketResponse] = CachedParser(
|
|
32
|
+
sockets_t.SocketResponse # type:ignore[arg-type]
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
def parse_message(message: Data) -> sockets_t.SocketEventData | None:
|
|
36
|
+
try:
|
|
37
|
+
return parser.parse_api(json.loads(message)).data
|
|
38
|
+
except ValueError:
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
integration_session = IntegrationSessionInstrument(
|
|
42
|
+
equipment_key=identifier_t.IdentifierKeyId(id=payload.equipment_id)
|
|
43
|
+
)
|
|
44
|
+
registration_info = args.client.register_sockets_token(
|
|
45
|
+
socket_request=sockets_t.SocketRequestIntegrationSession(
|
|
46
|
+
integration_session=integration_session
|
|
47
|
+
)
|
|
48
|
+
).response
|
|
49
|
+
token = registration_info.token
|
|
50
|
+
room_key = registration_info.room_key
|
|
51
|
+
args.logger.log_info(f"Token: {token}")
|
|
52
|
+
|
|
53
|
+
with connect(
|
|
54
|
+
"ws://host.docker.internal:8765",
|
|
55
|
+
additional_headers={
|
|
56
|
+
"Authorization": f"Bearer {token}",
|
|
57
|
+
"X-UNC-EXTERNAL": "true",
|
|
58
|
+
},
|
|
59
|
+
) as ws:
|
|
60
|
+
ws.send(
|
|
61
|
+
json.dumps(
|
|
62
|
+
serialize_for_api(
|
|
63
|
+
sockets_t.JoinRoomWithTokenSocketClientMessage(token=token)
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
)
|
|
67
|
+
for i in range(10):
|
|
68
|
+
args.logger.log_info("Sending reading...")
|
|
69
|
+
ws.send(
|
|
70
|
+
json.dumps(
|
|
71
|
+
serialize_for_api(
|
|
72
|
+
sockets_t.SendInstrumentReadingClientMessage(
|
|
73
|
+
value=Decimal(i * 100), room_key=room_key
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
)
|
|
78
|
+
time.sleep(0.75)
|
|
79
|
+
|
|
80
|
+
while True:
|
|
81
|
+
message = parse_message(ws.recv())
|
|
82
|
+
match message:
|
|
83
|
+
case sockets_t.UsersInRoomUpdatedEventData():
|
|
84
|
+
num_users = len(message.user_ids)
|
|
85
|
+
if num_users <= 1:
|
|
86
|
+
break
|
|
87
|
+
else:
|
|
88
|
+
args.logger.log_info(
|
|
89
|
+
f"Session still open, {num_users} users in room."
|
|
90
|
+
)
|
|
91
|
+
case _:
|
|
92
|
+
args.logger.log_info("Session still open...")
|
|
93
|
+
continue
|
|
94
|
+
|
|
95
|
+
args.logger.log_info("Session closed.")
|
|
96
|
+
return job_definition_t.JobResult(success=True)
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def webhook_payload_type(self) -> type:
|
|
100
|
+
return InstrumentPayload
|