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
|
@@ -31,6 +31,11 @@ TYPE_MAP = {
|
|
|
31
31
|
# not part of type_spec's types now
|
|
32
32
|
"Symbol": MappedType(base_type=value_spec_t.BaseType.SYMBOL),
|
|
33
33
|
"Any": MappedType(base_type=value_spec_t.BaseType.ANY),
|
|
34
|
+
"None": MappedType(base_type=value_spec_t.BaseType.NONE),
|
|
35
|
+
"Tuple": MappedType(
|
|
36
|
+
base_type=value_spec_t.BaseType.TUPLE, variable_param_count=True
|
|
37
|
+
),
|
|
38
|
+
"Never": MappedType(base_type=value_spec_t.BaseType.NEVER),
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
|
|
@@ -56,3 +61,16 @@ def convert_to_value_spec_type(parsed: ParsedTypePath) -> value_spec_t.ValueType
|
|
|
56
61
|
return value_spec_t.ValueType(base_type=mapped.base_type, parameters=parameters)
|
|
57
62
|
|
|
58
63
|
# Our formatter was duplicating the previous line for an unknown reason, this comment blocks that
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def convert_from_value_spec_type(
|
|
67
|
+
base_type: value_spec_t.BaseType,
|
|
68
|
+
) -> str:
|
|
69
|
+
for type_spec_type, mapped_type in TYPE_MAP.items():
|
|
70
|
+
if (
|
|
71
|
+
mapped_type.base_type == base_type
|
|
72
|
+
and mapped_type.param_count == 0
|
|
73
|
+
and mapped_type.variable_param_count is False
|
|
74
|
+
):
|
|
75
|
+
return type_spec_type
|
|
76
|
+
raise ValueError(f"invalid value spec type {base_type}")
|
|
@@ -70,14 +70,19 @@ def _emit_function_wrapper(function: value_spec_t.Function) -> str:
|
|
|
70
70
|
else:
|
|
71
71
|
python_type = _emit_python_type(argument.type)
|
|
72
72
|
if (
|
|
73
|
-
argument.
|
|
73
|
+
argument.on_null == value_spec_t.OnNull.PASS
|
|
74
74
|
or argument.extant == value_spec_t.ArgumentExtant.MISSING
|
|
75
75
|
):
|
|
76
76
|
python_type += " | None"
|
|
77
77
|
any_pass_null = True
|
|
78
|
+
|
|
79
|
+
if python_type.startswith("base_t.ExtJsonValue"):
|
|
80
|
+
return_statement = f"self._extract({index})"
|
|
81
|
+
else:
|
|
82
|
+
return_statement = f"cast({python_type}, self._extract({index}))"
|
|
78
83
|
out.write(
|
|
79
84
|
f"""{INDENT}def get_{argument.ref_name}(self) -> {python_type}:
|
|
80
|
-
{INDENT}{INDENT}return
|
|
85
|
+
{INDENT}{INDENT}return {return_statement}
|
|
81
86
|
"""
|
|
82
87
|
)
|
|
83
88
|
out.write("\n")
|
|
@@ -164,6 +169,8 @@ def _emit_function(function: value_spec_t.Function, indent: str) -> str:
|
|
|
164
169
|
f"{sub_indent}description={encode_common_string(function.description)},\n"
|
|
165
170
|
)
|
|
166
171
|
out.write(f"{sub_indent}brief={encode_common_string(function.brief)},\n")
|
|
172
|
+
if function.draft:
|
|
173
|
+
out.write(f"{sub_indent}draft={function.draft},\n")
|
|
167
174
|
out.write(
|
|
168
175
|
f"{sub_indent}return_value={_emit_function_return(function.return_value, sub_indent)},\n"
|
|
169
176
|
)
|
|
@@ -189,7 +196,10 @@ def _emit_argument(argument: value_spec_t.FunctionArgument, indent: str) -> str:
|
|
|
189
196
|
out.write(
|
|
190
197
|
f"{sub_indent}description={encode_common_string(argument.description)},\n"
|
|
191
198
|
)
|
|
192
|
-
|
|
199
|
+
# Quick enum emit since we have only one such type here
|
|
200
|
+
out.write(
|
|
201
|
+
f"{sub_indent}on_null=value_spec_t.OnNull.{str(argument.on_null).upper()},\n"
|
|
202
|
+
)
|
|
193
203
|
out.write(
|
|
194
204
|
f"{sub_indent}extant=value_spec_t.ArgumentExtant.{argument.extant.name},\n"
|
|
195
205
|
)
|
uncountable/core/async_batch.py
CHANGED
uncountable/core/client.py
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import base64
|
|
2
|
+
import datetime
|
|
3
|
+
import re
|
|
2
4
|
import typing
|
|
3
5
|
from dataclasses import dataclass
|
|
4
|
-
from datetime import
|
|
6
|
+
from datetime import UTC, timedelta
|
|
5
7
|
from enum import StrEnum
|
|
6
|
-
from
|
|
8
|
+
from io import BytesIO
|
|
9
|
+
from urllib.parse import unquote, urljoin
|
|
7
10
|
from uuid import uuid4
|
|
8
11
|
|
|
9
12
|
import requests
|
|
@@ -12,10 +15,10 @@ from opentelemetry.sdk.resources import Attributes
|
|
|
12
15
|
from requests.exceptions import JSONDecodeError
|
|
13
16
|
|
|
14
17
|
from pkgs.argument_parser import CachedParser
|
|
15
|
-
from pkgs.serialization_util import serialize_for_api
|
|
16
|
-
from pkgs.serialization_util.serialization_helpers import JsonValue
|
|
18
|
+
from pkgs.serialization_util import JsonValue, serialize_for_api
|
|
17
19
|
from uncountable.core.environment import get_version
|
|
18
|
-
from uncountable.integration.telemetry import
|
|
20
|
+
from uncountable.integration.telemetry import Logger, push_scope_optional
|
|
21
|
+
from uncountable.types import download_file_t
|
|
19
22
|
from uncountable.types.client_base import APIRequest, ClientMethods
|
|
20
23
|
from uncountable.types.client_config import ClientConfigOptions
|
|
21
24
|
|
|
@@ -41,14 +44,14 @@ class HTTPRequestBase:
|
|
|
41
44
|
|
|
42
45
|
@dataclass(kw_only=True)
|
|
43
46
|
class HTTPGetRequest(HTTPRequestBase):
|
|
44
|
-
method = EndpointMethod.GET
|
|
47
|
+
method: EndpointMethod = EndpointMethod.GET
|
|
45
48
|
query_params: dict[str, str]
|
|
46
49
|
|
|
47
50
|
|
|
48
51
|
@dataclass(kw_only=True)
|
|
49
52
|
class HTTPPostRequest(HTTPRequestBase):
|
|
50
|
-
method = EndpointMethod.POST
|
|
51
|
-
body:
|
|
53
|
+
method: EndpointMethod = EndpointMethod.POST
|
|
54
|
+
body: str | dict[str, str]
|
|
52
55
|
|
|
53
56
|
|
|
54
57
|
HTTPRequest = HTTPPostRequest | HTTPGetRequest
|
|
@@ -59,7 +62,7 @@ class ClientConfig(ClientConfigOptions):
|
|
|
59
62
|
transform_request: typing.Callable[[requests.Request], requests.Request] | None = (
|
|
60
63
|
None
|
|
61
64
|
)
|
|
62
|
-
|
|
65
|
+
logger: Logger | None = None
|
|
63
66
|
|
|
64
67
|
|
|
65
68
|
OAUTH_REFRESH_WINDOW_SECONDS = 60 * 5
|
|
@@ -71,16 +74,24 @@ class APIResponseError(Exception):
|
|
|
71
74
|
extra_details: dict[str, JsonValue] | None
|
|
72
75
|
|
|
73
76
|
def __init__(
|
|
74
|
-
self,
|
|
77
|
+
self,
|
|
78
|
+
status_code: int,
|
|
79
|
+
message: str,
|
|
80
|
+
extra_details: dict[str, JsonValue] | None,
|
|
81
|
+
request_id: str,
|
|
75
82
|
) -> None:
|
|
76
83
|
super().__init__(status_code, message, extra_details)
|
|
77
84
|
self.status_code = status_code
|
|
78
85
|
self.message = message
|
|
79
86
|
self.extra_details = extra_details
|
|
87
|
+
self.request_id = request_id
|
|
80
88
|
|
|
81
89
|
@classmethod
|
|
82
90
|
def construct_error(
|
|
83
|
-
cls,
|
|
91
|
+
cls,
|
|
92
|
+
status_code: int,
|
|
93
|
+
extra_details: dict[str, JsonValue] | None,
|
|
94
|
+
request_id: str,
|
|
84
95
|
) -> "APIResponseError":
|
|
85
96
|
message: str
|
|
86
97
|
match status_code:
|
|
@@ -103,9 +114,21 @@ class APIResponseError(Exception):
|
|
|
103
114
|
case _:
|
|
104
115
|
message = "unknown error"
|
|
105
116
|
return APIResponseError(
|
|
106
|
-
status_code=status_code,
|
|
117
|
+
status_code=status_code,
|
|
118
|
+
message=message,
|
|
119
|
+
extra_details=extra_details,
|
|
120
|
+
request_id=request_id,
|
|
107
121
|
)
|
|
108
122
|
|
|
123
|
+
def __str__(self) -> str:
|
|
124
|
+
details_obj = {
|
|
125
|
+
"request_id": self.request_id,
|
|
126
|
+
"status_code": self.status_code,
|
|
127
|
+
"extra_details": self.extra_details,
|
|
128
|
+
}
|
|
129
|
+
details = json.dumps(details_obj)
|
|
130
|
+
return f"API response error ({self.status_code}): '{self.message}'. Details: {details}"
|
|
131
|
+
|
|
109
132
|
|
|
110
133
|
class SDKError(Exception):
|
|
111
134
|
message: str
|
|
@@ -123,7 +146,7 @@ class SDKError(Exception):
|
|
|
123
146
|
@dataclass(kw_only=True)
|
|
124
147
|
class OAuthBearerTokenCache:
|
|
125
148
|
token: str
|
|
126
|
-
expires_at: datetime
|
|
149
|
+
expires_at: datetime.datetime
|
|
127
150
|
|
|
128
151
|
|
|
129
152
|
@dataclass(kw_only=True)
|
|
@@ -137,6 +160,16 @@ class GetOauthBearerTokenData:
|
|
|
137
160
|
oauth_bearer_token_data_parser = CachedParser(GetOauthBearerTokenData)
|
|
138
161
|
|
|
139
162
|
|
|
163
|
+
@dataclass
|
|
164
|
+
class DownloadedFile:
|
|
165
|
+
name: str
|
|
166
|
+
size: int
|
|
167
|
+
data: BytesIO
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
DownloadedFiles = list[DownloadedFile]
|
|
171
|
+
|
|
172
|
+
|
|
140
173
|
class Client(ClientMethods):
|
|
141
174
|
_parser_map: dict[type, CachedParser] = {}
|
|
142
175
|
_auth_details: AuthDetailsAll
|
|
@@ -159,35 +192,49 @@ class Client(ClientMethods):
|
|
|
159
192
|
self._session = requests.Session()
|
|
160
193
|
self._session.verify = not self._cfg.allow_insecure_tls
|
|
161
194
|
self._file_uploader = FileUploader(
|
|
162
|
-
self._base_url,
|
|
195
|
+
self._base_url,
|
|
196
|
+
self._auth_details,
|
|
197
|
+
self._cfg.allow_insecure_tls,
|
|
198
|
+
logger=self._cfg.logger,
|
|
163
199
|
)
|
|
164
200
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
201
|
+
@classmethod
|
|
202
|
+
def _validate_response_status(
|
|
203
|
+
cls, response: requests.Response, request_id: str
|
|
204
|
+
) -> None:
|
|
168
205
|
if response.status_code < 200 or response.status_code > 299:
|
|
169
206
|
extra_details: dict[str, JsonValue] | None = None
|
|
170
207
|
try:
|
|
171
208
|
data = response.json()
|
|
172
|
-
|
|
173
|
-
extra_details = data
|
|
209
|
+
extra_details = data
|
|
174
210
|
except JSONDecodeError:
|
|
175
|
-
|
|
211
|
+
extra_details = {
|
|
212
|
+
"body": response.text,
|
|
213
|
+
}
|
|
176
214
|
raise APIResponseError.construct_error(
|
|
177
|
-
status_code=response.status_code,
|
|
215
|
+
status_code=response.status_code,
|
|
216
|
+
extra_details=extra_details,
|
|
217
|
+
request_id=request_id,
|
|
178
218
|
)
|
|
219
|
+
|
|
220
|
+
def _get_response_json(
|
|
221
|
+
self, response: requests.Response, request_id: str
|
|
222
|
+
) -> dict[str, JsonValue]:
|
|
223
|
+
self._validate_response_status(response, request_id)
|
|
179
224
|
try:
|
|
180
225
|
return typing.cast(dict[str, JsonValue], response.json())
|
|
181
226
|
except JSONDecodeError as e:
|
|
182
227
|
raise SDKError("unable to process response", request_id=request_id) from e
|
|
183
228
|
|
|
184
|
-
def _send_request(
|
|
229
|
+
def _send_request(
|
|
230
|
+
self, request: requests.Request, *, timeout: float | None = None
|
|
231
|
+
) -> requests.Response:
|
|
185
232
|
if self._cfg.extra_headers is not None:
|
|
186
233
|
request.headers = {**request.headers, **self._cfg.extra_headers}
|
|
187
234
|
if self._cfg.transform_request is not None:
|
|
188
235
|
request = self._cfg.transform_request(request)
|
|
189
236
|
prepared_request = request.prepare()
|
|
190
|
-
response = self._session.send(prepared_request)
|
|
237
|
+
response = self._session.send(prepared_request, timeout=timeout)
|
|
191
238
|
return response
|
|
192
239
|
|
|
193
240
|
def do_request(self, *, api_request: APIRequest, return_type: type[DT]) -> DT:
|
|
@@ -205,15 +252,19 @@ class Client(ClientMethods):
|
|
|
205
252
|
case _:
|
|
206
253
|
typing.assert_never(http_request)
|
|
207
254
|
request.headers = http_request.headers
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
255
|
+
attributes: Attributes = {
|
|
256
|
+
"method": http_request.method,
|
|
257
|
+
"endpoint": api_request.endpoint,
|
|
258
|
+
}
|
|
259
|
+
with push_scope_optional(self._cfg.logger, "api_call", attributes=attributes):
|
|
260
|
+
if self._cfg.logger is not None:
|
|
261
|
+
self._cfg.logger.log_info(api_request.endpoint, attributes=attributes)
|
|
262
|
+
timeout = (
|
|
263
|
+
api_request.request_options.timeout_secs
|
|
264
|
+
if api_request.request_options is not None
|
|
265
|
+
else None
|
|
266
|
+
)
|
|
267
|
+
response = self._send_request(request, timeout=timeout)
|
|
217
268
|
response_data = self._get_response_json(response, request_id=request_id)
|
|
218
269
|
cached_parser = self._get_cached_parser(return_type)
|
|
219
270
|
try:
|
|
@@ -231,7 +282,8 @@ class Client(ClientMethods):
|
|
|
231
282
|
if (
|
|
232
283
|
self._oauth_bearer_token_cache is None
|
|
233
284
|
or (
|
|
234
|
-
self._oauth_bearer_token_cache.expires_at
|
|
285
|
+
self._oauth_bearer_token_cache.expires_at
|
|
286
|
+
- datetime.datetime.now(tz=UTC)
|
|
235
287
|
).total_seconds()
|
|
236
288
|
< OAUTH_REFRESH_WINDOW_SECONDS
|
|
237
289
|
):
|
|
@@ -247,7 +299,8 @@ class Client(ClientMethods):
|
|
|
247
299
|
token_data = oauth_bearer_token_data_parser.parse_storage(data)
|
|
248
300
|
self._oauth_bearer_token_cache = OAuthBearerTokenCache(
|
|
249
301
|
token=token_data.access_token,
|
|
250
|
-
expires_at=datetime.now(
|
|
302
|
+
expires_at=datetime.datetime.now(tz=UTC)
|
|
303
|
+
+ timedelta(seconds=token_data.expires_in),
|
|
251
304
|
)
|
|
252
305
|
|
|
253
306
|
return self._oauth_bearer_token_cache.token
|
|
@@ -290,6 +343,52 @@ class Client(ClientMethods):
|
|
|
290
343
|
case _:
|
|
291
344
|
raise ValueError(f"unsupported request method: {method}")
|
|
292
345
|
|
|
346
|
+
def _get_downloaded_filename(self, *, cd: str | None) -> str:
|
|
347
|
+
if not cd:
|
|
348
|
+
return "Unknown"
|
|
349
|
+
|
|
350
|
+
fname = re.findall(r"filename\*=UTF-8''(.+)", cd)
|
|
351
|
+
if fname:
|
|
352
|
+
return unquote(fname[0])
|
|
353
|
+
|
|
354
|
+
fname = re.findall(r'filename="?(.+)"?', cd)
|
|
355
|
+
if fname:
|
|
356
|
+
return str(fname[0].strip('"'))
|
|
357
|
+
|
|
358
|
+
return "Unknown"
|
|
359
|
+
|
|
360
|
+
def download_files(
|
|
361
|
+
self, *, file_query: download_file_t.FileDownloadQuery
|
|
362
|
+
) -> DownloadedFiles:
|
|
363
|
+
"""Download a file from uncountable."""
|
|
364
|
+
request_id = str(uuid4())
|
|
365
|
+
api_request = APIRequest(
|
|
366
|
+
method=download_file_t.ENDPOINT_METHOD,
|
|
367
|
+
endpoint=download_file_t.ENDPOINT_PATH,
|
|
368
|
+
args=download_file_t.Arguments(
|
|
369
|
+
file_query=file_query,
|
|
370
|
+
),
|
|
371
|
+
)
|
|
372
|
+
http_request = self._build_http_request(
|
|
373
|
+
api_request=api_request, request_id=request_id
|
|
374
|
+
)
|
|
375
|
+
request = requests.Request(http_request.method.value, http_request.url)
|
|
376
|
+
request.headers = http_request.headers
|
|
377
|
+
assert isinstance(http_request, HTTPGetRequest)
|
|
378
|
+
request.params = http_request.query_params
|
|
379
|
+
response = self._send_request(request)
|
|
380
|
+
self._validate_response_status(response, request_id)
|
|
381
|
+
|
|
382
|
+
content = response.content
|
|
383
|
+
content_disposition = response.headers.get("Content-Disposition", None)
|
|
384
|
+
return [
|
|
385
|
+
DownloadedFile(
|
|
386
|
+
name=self._get_downloaded_filename(cd=content_disposition),
|
|
387
|
+
size=len(content),
|
|
388
|
+
data=BytesIO(content),
|
|
389
|
+
)
|
|
390
|
+
]
|
|
391
|
+
|
|
293
392
|
def upload_files(
|
|
294
393
|
self: typing.Self, *, file_uploads: list[FileUpload]
|
|
295
394
|
) -> list[UploadedFile]:
|
uncountable/core/environment.py
CHANGED
|
@@ -18,12 +18,12 @@ def get_server_env() -> str | None:
|
|
|
18
18
|
return os.environ.get("UNC_SERVER_ENV")
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
def
|
|
22
|
-
return int(os.environ.get("UNC_WEBHOOK_SERVER_PORT", 5001))
|
|
21
|
+
def get_http_server_port() -> int:
|
|
22
|
+
return int(os.environ.get("UNC_WEBHOOK_SERVER_PORT", "5001"))
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
def get_local_admin_server_port() -> int:
|
|
26
|
-
return int(os.environ.get("UNC_ADMIN_SERVER_PORT", 50051))
|
|
26
|
+
return int(os.environ.get("UNC_ADMIN_SERVER_PORT", "50051"))
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
def get_otel_enabled() -> bool:
|
uncountable/core/file_upload.py
CHANGED
|
@@ -4,11 +4,13 @@ from dataclasses import dataclass
|
|
|
4
4
|
from enum import StrEnum
|
|
5
5
|
from io import BytesIO
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import Generator, Literal, Self
|
|
7
|
+
from typing import Generator, Literal, Self, assert_never
|
|
8
8
|
|
|
9
9
|
import aiohttp
|
|
10
10
|
import aiotus
|
|
11
11
|
|
|
12
|
+
from uncountable.integration.telemetry import Logger, push_scope_optional
|
|
13
|
+
|
|
12
14
|
from .types import AuthDetailsAll, AuthDetailsApiKey
|
|
13
15
|
|
|
14
16
|
_CHUNK_SIZE = 5 * 1024 * 1024 # s3 requires 5MiB minimum
|
|
@@ -75,10 +77,12 @@ class FileUploader:
|
|
|
75
77
|
base_url: str,
|
|
76
78
|
auth_details: AuthDetailsAll,
|
|
77
79
|
allow_insecure_tls: bool = False,
|
|
80
|
+
logger: Logger | None = None,
|
|
78
81
|
) -> None:
|
|
79
82
|
self._base_url = base_url
|
|
80
83
|
self._auth_details = auth_details
|
|
81
84
|
self._allow_insecure_tls = allow_insecure_tls
|
|
85
|
+
self._logger = logger
|
|
82
86
|
|
|
83
87
|
async def _upload_file(self: Self, file_upload: FileUpload) -> UploadedFile:
|
|
84
88
|
creation_url = f"{self._base_url}/api/external/file_upload/files"
|
|
@@ -93,20 +97,40 @@ class FileUploader:
|
|
|
93
97
|
auth=auth, headers={"Origin": self._base_url}
|
|
94
98
|
) as session,
|
|
95
99
|
):
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
)
|
|
100
|
+
attributes = {}
|
|
101
|
+
match file_upload:
|
|
102
|
+
case MediaFileUpload():
|
|
103
|
+
attributes["file_path"] = file_upload.path
|
|
104
|
+
case DataFileUpload():
|
|
105
|
+
attributes["file_name"] = file_upload.name
|
|
106
|
+
case _:
|
|
107
|
+
assert_never(file_upload)
|
|
108
|
+
with push_scope_optional(
|
|
109
|
+
self._logger, "upload_file", attributes=attributes
|
|
110
|
+
):
|
|
111
|
+
if self._logger is not None:
|
|
112
|
+
self._logger.log_info("Uploading file", attributes=attributes)
|
|
113
|
+
with file_upload_data(file_upload) as file_bytes:
|
|
114
|
+
if file_bytes.bytes_data.read(1) == b"":
|
|
115
|
+
raise UploadFailed(
|
|
116
|
+
f"Failed to upload empty file: {file_bytes.name}"
|
|
117
|
+
)
|
|
118
|
+
file_bytes.bytes_data.seek(0)
|
|
119
|
+
location = await aiotus.upload(
|
|
120
|
+
creation_url,
|
|
121
|
+
file_bytes.bytes_data,
|
|
122
|
+
{"filename": file_bytes.name.encode()},
|
|
123
|
+
client_session=session,
|
|
124
|
+
config=aiotus.RetryConfiguration(
|
|
125
|
+
ssl=not self._allow_insecure_tls
|
|
126
|
+
),
|
|
127
|
+
chunksize=_CHUNK_SIZE,
|
|
128
|
+
)
|
|
129
|
+
if location is None:
|
|
130
|
+
raise UploadFailed(f"Failed to upload: {file_bytes.name}")
|
|
131
|
+
return UploadedFile(
|
|
132
|
+
name=file_bytes.name, file_id=int(location.path.split("/")[-1])
|
|
133
|
+
)
|
|
110
134
|
|
|
111
135
|
def upload_files(
|
|
112
136
|
self: Self, *, file_uploads: list[FileUpload]
|
uncountable/integration/cli.py
CHANGED
|
@@ -1,49 +1,142 @@
|
|
|
1
1
|
import argparse
|
|
2
2
|
|
|
3
|
+
from dateutil import tz
|
|
3
4
|
from opentelemetry.trace import get_current_span
|
|
5
|
+
from tabulate import tabulate
|
|
4
6
|
|
|
5
7
|
from uncountable.core.environment import get_local_admin_server_port
|
|
6
8
|
from uncountable.integration.queue_runner.command_server.command_client import (
|
|
7
9
|
send_job_queue_message,
|
|
10
|
+
send_list_queued_jobs_message,
|
|
11
|
+
send_retry_job_message,
|
|
8
12
|
)
|
|
9
13
|
from uncountable.integration.telemetry import Logger
|
|
10
14
|
from uncountable.types import queued_job_t
|
|
11
15
|
|
|
12
16
|
|
|
13
|
-
def
|
|
14
|
-
|
|
17
|
+
def register_enqueue_job_parser(
|
|
18
|
+
sub_parser_manager: argparse._SubParsersAction,
|
|
19
|
+
parents: list[argparse.ArgumentParser],
|
|
20
|
+
) -> None:
|
|
21
|
+
run_parser = sub_parser_manager.add_parser(
|
|
22
|
+
"run",
|
|
23
|
+
parents=parents,
|
|
24
|
+
help="Process a job with a given host and job ID",
|
|
25
|
+
description="Process a job with a given host and job ID",
|
|
26
|
+
)
|
|
27
|
+
run_parser.add_argument("job_id", type=str, help="The ID of the job to process")
|
|
28
|
+
|
|
29
|
+
def _handle_enqueue_job(args: argparse.Namespace) -> None:
|
|
30
|
+
send_job_queue_message(
|
|
31
|
+
job_ref_name=args.job_id,
|
|
32
|
+
payload=queued_job_t.QueuedJobPayload(
|
|
33
|
+
invocation_context=queued_job_t.InvocationContextManual()
|
|
34
|
+
),
|
|
35
|
+
host=args.host,
|
|
36
|
+
port=get_local_admin_server_port(),
|
|
37
|
+
)
|
|
15
38
|
|
|
16
|
-
|
|
17
|
-
|
|
39
|
+
run_parser.set_defaults(func=_handle_enqueue_job)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def register_list_queued_jobs(
|
|
43
|
+
sub_parser_manager: argparse._SubParsersAction,
|
|
44
|
+
parents: list[argparse.ArgumentParser],
|
|
45
|
+
) -> None:
|
|
46
|
+
list_queued_jobs_parser = sub_parser_manager.add_parser(
|
|
47
|
+
"list-queued-jobs",
|
|
48
|
+
parents=parents,
|
|
49
|
+
help="List all jobs queued on the integration server",
|
|
50
|
+
description="List all jobs queued on the integration server",
|
|
18
51
|
)
|
|
19
52
|
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
type=
|
|
23
|
-
|
|
24
|
-
help="
|
|
53
|
+
list_queued_jobs_parser.add_argument(
|
|
54
|
+
"--offset",
|
|
55
|
+
type=int,
|
|
56
|
+
default=0,
|
|
57
|
+
help="Number of jobs to skip. Should be non-negative.",
|
|
58
|
+
)
|
|
59
|
+
list_queued_jobs_parser.add_argument(
|
|
60
|
+
"--limit",
|
|
61
|
+
type=int,
|
|
62
|
+
default=100,
|
|
63
|
+
help="A number between 1 and 100 specifying the number of jobs to return in the result set.",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def _handle_list_queued_jobs(args: argparse.Namespace) -> None:
|
|
67
|
+
queued_jobs = send_list_queued_jobs_message(
|
|
68
|
+
offset=args.offset,
|
|
69
|
+
limit=args.limit,
|
|
70
|
+
host=args.host,
|
|
71
|
+
port=get_local_admin_server_port(),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
headers = ["UUID", "Job Ref Name", "Attempts", "Status", "Submitted At"]
|
|
75
|
+
rows = [
|
|
76
|
+
[
|
|
77
|
+
job.uuid,
|
|
78
|
+
job.job_ref_name,
|
|
79
|
+
job.num_attempts,
|
|
80
|
+
job.status,
|
|
81
|
+
job.submitted_at.ToDatetime(tz.UTC).astimezone(tz.tzlocal()),
|
|
82
|
+
]
|
|
83
|
+
for job in queued_jobs
|
|
84
|
+
]
|
|
85
|
+
print(tabulate(rows, headers=headers, tablefmt="grid"))
|
|
86
|
+
|
|
87
|
+
list_queued_jobs_parser.set_defaults(func=_handle_list_queued_jobs)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def register_retry_job_parser(
|
|
91
|
+
sub_parser_manager: argparse._SubParsersAction,
|
|
92
|
+
parents: list[argparse.ArgumentParser],
|
|
93
|
+
) -> None:
|
|
94
|
+
retry_failed_jobs_parser = sub_parser_manager.add_parser(
|
|
95
|
+
"retry-job",
|
|
96
|
+
parents=parents,
|
|
97
|
+
help="Retry failed job on the integration server",
|
|
98
|
+
description="Retry failed job on the integration server",
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
retry_failed_jobs_parser.add_argument(
|
|
102
|
+
"job_uuid", type=str, help="The uuid of the job to retry"
|
|
25
103
|
)
|
|
26
104
|
|
|
27
|
-
|
|
105
|
+
def _handle_retry_job(args: argparse.Namespace) -> None:
|
|
106
|
+
send_retry_job_message(
|
|
107
|
+
job_uuid=args.job_uuid,
|
|
108
|
+
host=args.host,
|
|
109
|
+
port=get_local_admin_server_port(),
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
retry_failed_jobs_parser.set_defaults(func=_handle_retry_job)
|
|
28
113
|
|
|
29
|
-
|
|
114
|
+
|
|
115
|
+
def main() -> None:
|
|
116
|
+
logger = Logger(get_current_span())
|
|
117
|
+
|
|
118
|
+
main_parser = argparse.ArgumentParser(
|
|
119
|
+
description="Execute a given integrations server command."
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
base_parser = argparse.ArgumentParser(add_help=False)
|
|
123
|
+
base_parser.add_argument(
|
|
30
124
|
"--host", type=str, default="localhost", nargs="?", help="The host to run on"
|
|
31
125
|
)
|
|
32
126
|
|
|
33
|
-
|
|
127
|
+
subparser_action = main_parser.add_subparsers(
|
|
128
|
+
dest="command",
|
|
129
|
+
required=True,
|
|
130
|
+
help="The command to execute (e.g., 'run')",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
register_enqueue_job_parser(subparser_action, parents=[base_parser])
|
|
134
|
+
register_retry_job_parser(subparser_action, parents=[base_parser])
|
|
135
|
+
register_list_queued_jobs(subparser_action, parents=[base_parser])
|
|
34
136
|
|
|
137
|
+
args = main_parser.parse_args()
|
|
35
138
|
with logger.push_scope(args.command):
|
|
36
|
-
|
|
37
|
-
send_job_queue_message(
|
|
38
|
-
job_ref_name=args.job_id,
|
|
39
|
-
payload=queued_job_t.QueuedJobPayload(
|
|
40
|
-
invocation_context=queued_job_t.InvocationContextManual()
|
|
41
|
-
),
|
|
42
|
-
host=args.host,
|
|
43
|
-
port=get_local_admin_server_port(),
|
|
44
|
-
)
|
|
45
|
-
else:
|
|
46
|
-
parser.print_usage()
|
|
139
|
+
args.func(args)
|
|
47
140
|
|
|
48
141
|
|
|
49
142
|
main()
|
|
@@ -37,15 +37,15 @@ def _construct_client_config(
|
|
|
37
37
|
return ClientConfig(
|
|
38
38
|
allow_insecure_tls=profile_meta.client_options.allow_insecure_tls,
|
|
39
39
|
extra_headers=profile_meta.client_options.extra_headers,
|
|
40
|
-
|
|
40
|
+
logger=job_logger,
|
|
41
41
|
)
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
def construct_uncountable_client(
|
|
45
|
-
profile_meta: ProfileMetadata,
|
|
45
|
+
profile_meta: ProfileMetadata, logger: JobLogger
|
|
46
46
|
) -> Client:
|
|
47
47
|
return Client(
|
|
48
48
|
base_url=profile_meta.base_url,
|
|
49
49
|
auth_details=_construct_auth_details(profile_meta),
|
|
50
|
-
config=_construct_client_config(profile_meta,
|
|
50
|
+
config=_construct_client_config(profile_meta, logger),
|
|
51
51
|
)
|