UncountablePythonSDK 0.0.24__py3-none-any.whl → 0.0.131__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 +60 -8
- 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 +7 -5
- examples/async_batch.py +5 -6
- examples/basic_auth.py +7 -0
- examples/create_entity.py +4 -6
- examples/create_ingredient_sdk.py +34 -0
- examples/download_files.py +26 -0
- examples/edit_recipe_inputs.py +50 -0
- examples/integration-server/jobs/materials_auto/concurrent_cron.py +11 -0
- examples/integration-server/jobs/materials_auto/example_cron.py +21 -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 +23 -0
- examples/integration-server/jobs/materials_auto/profile.yaml +104 -0
- examples/integration-server/pyproject.toml +224 -0
- examples/invoke_uploader.py +26 -0
- examples/oauth.py +7 -0
- examples/set_recipe_metadata_file.py +40 -0
- examples/set_recipe_output_file_sdk.py +26 -0
- examples/upload_files.py +2 -3
- pkgs/argument_parser/__init__.py +9 -0
- pkgs/argument_parser/_is_namedtuple.py +3 -0
- pkgs/argument_parser/argument_parser.py +295 -74
- pkgs/argument_parser/case_convert.py +4 -3
- pkgs/filesystem_utils/__init__.py +20 -0
- pkgs/filesystem_utils/_blob_session.py +144 -0
- pkgs/filesystem_utils/_gdrive_session.py +309 -0
- pkgs/filesystem_utils/_local_session.py +69 -0
- pkgs/filesystem_utils/_s3_session.py +118 -0
- pkgs/filesystem_utils/_sftp_session.py +151 -0
- pkgs/filesystem_utils/file_type_utils.py +91 -0
- pkgs/filesystem_utils/filesystem_session.py +39 -0
- pkgs/py.typed +0 -0
- pkgs/serialization/__init__.py +8 -1
- 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 +69 -54
- pkgs/serialization/serial_generic.py +16 -0
- pkgs/serialization/serial_union.py +84 -0
- pkgs/serialization/yaml.py +57 -0
- pkgs/serialization_util/__init__.py +7 -7
- pkgs/serialization_util/convert_to_snakecase.py +27 -0
- pkgs/serialization_util/dataclasses.py +14 -0
- pkgs/serialization_util/serialization_helpers.py +117 -71
- pkgs/type_spec/actions_registry/__main__.py +0 -4
- pkgs/type_spec/actions_registry/emit_typescript.py +5 -5
- pkgs/type_spec/builder.py +438 -109
- pkgs/type_spec/builder_types.py +9 -0
- pkgs/type_spec/config.py +52 -24
- pkgs/type_spec/cross_output_links.py +99 -0
- pkgs/type_spec/emit_io_ts.py +1 -1
- pkgs/type_spec/emit_open_api.py +160 -41
- pkgs/type_spec/emit_open_api_util.py +13 -7
- pkgs/type_spec/emit_python.py +450 -136
- pkgs/type_spec/emit_typescript.py +117 -250
- pkgs/type_spec/emit_typescript_util.py +293 -4
- pkgs/type_spec/load_types.py +20 -5
- 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 +13 -10
- 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 +161 -32
- 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 +4 -4
- pkgs/type_spec/value_spec/__main__.py +27 -10
- pkgs/type_spec/value_spec/convert_type.py +21 -1
- pkgs/type_spec/value_spec/emit_python.py +25 -7
- pkgs/type_spec/value_spec/types.py +1 -1
- uncountable/__init__.py +1 -2
- uncountable/core/__init__.py +11 -3
- uncountable/core/async_batch.py +16 -1
- uncountable/core/client.py +247 -52
- uncountable/core/environment.py +41 -0
- uncountable/core/file_upload.py +67 -22
- uncountable/core/types.py +8 -13
- uncountable/integration/cli.py +142 -0
- uncountable/integration/construct_client.py +43 -27
- uncountable/integration/cron.py +12 -11
- uncountable/integration/db/connect.py +12 -2
- uncountable/integration/db/session.py +25 -0
- uncountable/integration/entrypoint.py +4 -34
- uncountable/integration/executors/executors.py +147 -0
- uncountable/integration/executors/generic_upload_executor.py +336 -0
- uncountable/integration/executors/script_executor.py +15 -9
- uncountable/integration/http_server/__init__.py +5 -0
- uncountable/integration/http_server/types.py +69 -0
- uncountable/integration/job.py +246 -19
- uncountable/integration/queue_runner/__init__.py +0 -0
- uncountable/integration/queue_runner/command_server/__init__.py +28 -0
- uncountable/integration/queue_runner/command_server/command_client.py +133 -0
- uncountable/integration/queue_runner/command_server/command_server.py +142 -0
- uncountable/integration/queue_runner/command_server/constants.py +4 -0
- uncountable/integration/queue_runner/command_server/protocol/__init__.py +0 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server.proto +58 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.py +57 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.pyi +114 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2_grpc.py +264 -0
- uncountable/integration/queue_runner/command_server/types.py +75 -0
- uncountable/integration/queue_runner/datastore/__init__.py +3 -0
- uncountable/integration/queue_runner/datastore/datastore_sqlite.py +250 -0
- uncountable/integration/queue_runner/datastore/interface.py +29 -0
- uncountable/integration/queue_runner/datastore/model.py +24 -0
- uncountable/integration/queue_runner/job_scheduler.py +200 -0
- uncountable/integration/queue_runner/queue_runner.py +34 -0
- uncountable/integration/queue_runner/types.py +7 -0
- uncountable/integration/queue_runner/worker.py +116 -0
- uncountable/integration/scan_profiles.py +67 -0
- uncountable/integration/scheduler.py +199 -0
- uncountable/integration/secret_retrieval/__init__.py +3 -0
- uncountable/integration/secret_retrieval/retrieve_secret.py +93 -0
- uncountable/integration/server.py +103 -54
- uncountable/integration/telemetry.py +251 -0
- uncountable/integration/webhook_server/entrypoint.py +97 -0
- uncountable/types/__init__.py +149 -30
- uncountable/types/api/batch/execute_batch.py +16 -9
- uncountable/types/api/batch/execute_batch_load_async.py +13 -7
- uncountable/types/api/chemical/convert_chemical_formats.py +20 -8
- 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 +24 -12
- uncountable/types/api/entity/create_entity.py +22 -13
- 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 +18 -9
- uncountable/types/api/entity/grant_entity_permissions.py +48 -0
- uncountable/types/api/entity/list_aggregate.py +79 -0
- uncountable/types/api/entity/list_entities.py +53 -14
- uncountable/types/api/entity/lock_entity.py +45 -0
- uncountable/types/api/entity/lookup_entity.py +116 -0
- uncountable/types/api/entity/resolve_entity_ids.py +19 -10
- uncountable/types/api/entity/set_entity_field_values.py +44 -0
- uncountable/types/api/entity/set_values.py +15 -8
- uncountable/types/api/entity/transition_entity_phase.py +27 -12
- uncountable/types/api/entity/unlock_entity.py +44 -0
- uncountable/types/api/equipment/__init__.py +1 -0
- uncountable/types/api/equipment/associate_equipment_input.py +43 -0
- uncountable/types/api/field_options/__init__.py +1 -0
- uncountable/types/api/field_options/upsert_field_options.py +55 -0
- 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 +20 -11
- uncountable/types/api/id_source/match_id_source.py +15 -10
- uncountable/types/api/input_groups/get_input_group_names.py +16 -7
- uncountable/types/api/inputs/create_inputs.py +28 -14
- uncountable/types/api/inputs/get_input_data.py +34 -16
- uncountable/types/api/inputs/get_input_names.py +19 -10
- uncountable/types/api/inputs/get_inputs_data.py +29 -11
- uncountable/types/api/inputs/set_input_attribute_values.py +16 -10
- uncountable/types/api/inputs/set_input_category.py +44 -0
- uncountable/types/api/inputs/set_input_subcategories.py +45 -0
- uncountable/types/api/inputs/set_intermediate_type.py +50 -0
- 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/__init__.py +1 -0
- uncountable/types/api/material_families/update_entity_material_families.py +47 -0
- 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 +32 -17
- uncountable/types/api/outputs/get_output_names.py +18 -9
- uncountable/types/api/outputs/get_output_organization.py +173 -0
- uncountable/types/api/outputs/resolve_output_conditions.py +23 -11
- uncountable/types/api/permissions/set_core_permissions.py +31 -15
- uncountable/types/api/project/get_projects.py +20 -11
- uncountable/types/api/project/get_projects_data.py +23 -14
- uncountable/types/api/recipe_links/create_recipe_link.py +17 -10
- uncountable/types/api/recipe_links/remove_recipe_link.py +45 -0
- uncountable/types/api/recipe_metadata/get_recipe_metadata_data.py +19 -10
- uncountable/types/api/recipes/add_recipe_to_project.py +42 -0
- uncountable/types/api/recipes/add_time_series_data.py +64 -0
- uncountable/types/api/recipes/archive_recipes.py +14 -7
- uncountable/types/api/recipes/associate_recipe_as_input.py +16 -8
- uncountable/types/api/recipes/associate_recipe_as_lot.py +14 -7
- uncountable/types/api/recipes/clear_recipe_outputs.py +42 -0
- uncountable/types/api/recipes/create_mix_order.py +44 -0
- uncountable/types/api/recipes/create_recipe.py +21 -14
- uncountable/types/api/recipes/create_recipes.py +25 -13
- uncountable/types/api/recipes/disassociate_recipe_as_input.py +14 -7
- uncountable/types/api/recipes/edit_recipe_inputs.py +208 -19
- uncountable/types/api/recipes/get_column_calculation_values.py +57 -0
- uncountable/types/api/recipes/get_curve.py +15 -9
- uncountable/types/api/recipes/get_recipe_calculations.py +17 -11
- uncountable/types/api/recipes/get_recipe_links.py +14 -8
- uncountable/types/api/recipes/get_recipe_names.py +16 -7
- uncountable/types/api/recipes/get_recipe_output_metadata.py +16 -10
- uncountable/types/api/recipes/get_recipes_data.py +96 -45
- uncountable/types/api/recipes/lock_recipes.py +64 -0
- uncountable/types/api/recipes/remove_recipe_from_project.py +42 -0
- uncountable/types/api/recipes/set_recipe_inputs.py +19 -13
- uncountable/types/api/recipes/set_recipe_metadata.py +14 -7
- uncountable/types/api/recipes/set_recipe_output_annotations.py +114 -0
- uncountable/types/api/recipes/set_recipe_output_file.py +55 -0
- uncountable/types/api/recipes/set_recipe_outputs.py +40 -15
- uncountable/types/api/recipes/set_recipe_tags.py +30 -13
- uncountable/types/api/recipes/set_recipe_total.py +59 -0
- uncountable/types/api/recipes/unarchive_recipes.py +41 -0
- uncountable/types/api/recipes/unlock_recipes.py +51 -0
- 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 +15 -8
- uncountable/types/api/uploader/__init__.py +1 -0
- uncountable/types/api/uploader/complete_async_parse.py +46 -0
- uncountable/types/api/uploader/invoke_uploader.py +46 -0
- uncountable/types/api/user/__init__.py +1 -0
- uncountable/types/api/user/get_current_user_info.py +40 -0
- uncountable/types/async_batch.py +8 -52
- uncountable/types/async_batch_processor.py +694 -18
- uncountable/types/async_batch_t.py +108 -0
- uncountable/types/async_jobs.py +8 -0
- uncountable/types/async_jobs_t.py +52 -0
- uncountable/types/auth_retrieval.py +11 -0
- uncountable/types/auth_retrieval_t.py +75 -0
- uncountable/types/base.py +5 -80
- uncountable/types/base_t.py +87 -0
- uncountable/types/calculations.py +3 -19
- uncountable/types/calculations_t.py +26 -0
- uncountable/types/chemical_structure.py +3 -23
- uncountable/types/chemical_structure_t.py +28 -0
- uncountable/types/client_base.py +1170 -88
- uncountable/types/client_config.py +8 -0
- uncountable/types/client_config_t.py +36 -0
- uncountable/types/curves.py +5 -43
- uncountable/types/curves_t.py +50 -0
- uncountable/types/data.py +12 -0
- uncountable/types/data_t.py +103 -0
- uncountable/types/entity.py +8 -270
- uncountable/types/entity_t.py +446 -0
- uncountable/types/experiment_groups.py +3 -19
- uncountable/types/experiment_groups_t.py +26 -0
- uncountable/types/exports.py +8 -0
- uncountable/types/exports_t.py +34 -0
- uncountable/types/field_values.py +25 -61
- uncountable/types/field_values_t.py +302 -0
- uncountable/types/fields.py +3 -20
- uncountable/types/fields_t.py +27 -0
- uncountable/types/generic_upload.py +14 -0
- uncountable/types/generic_upload_t.py +119 -0
- uncountable/types/id_source.py +7 -45
- uncountable/types/id_source_t.py +68 -0
- uncountable/types/identifier.py +6 -50
- uncountable/types/identifier_t.py +62 -0
- uncountable/types/input_attributes.py +3 -25
- uncountable/types/input_attributes_t.py +29 -0
- uncountable/types/inputs.py +6 -57
- uncountable/types/inputs_t.py +82 -0
- uncountable/types/integration_server.py +8 -0
- uncountable/types/integration_server_t.py +46 -0
- 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 +28 -0
- uncountable/types/job_definition_t.py +285 -0
- 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 +3 -22
- uncountable/types/outputs_t.py +29 -0
- uncountable/types/overrides.py +9 -0
- uncountable/types/overrides_t.py +49 -0
- uncountable/types/permissions.py +3 -42
- uncountable/types/permissions_t.py +45 -0
- uncountable/types/phases.py +3 -19
- uncountable/types/phases_t.py +26 -0
- uncountable/types/post_base.py +3 -26
- uncountable/types/post_base_t.py +29 -0
- uncountable/types/queued_job.py +17 -0
- uncountable/types/queued_job_t.py +140 -0
- uncountable/types/recipe_identifiers.py +7 -58
- uncountable/types/recipe_identifiers_t.py +75 -0
- uncountable/types/recipe_inputs.py +4 -26
- uncountable/types/recipe_inputs_t.py +29 -0
- uncountable/types/recipe_links.py +4 -46
- uncountable/types/recipe_links_t.py +53 -0
- uncountable/types/recipe_metadata.py +5 -48
- uncountable/types/recipe_metadata_t.py +57 -0
- uncountable/types/recipe_output_metadata.py +3 -20
- uncountable/types/recipe_output_metadata_t.py +27 -0
- uncountable/types/recipe_tags.py +3 -19
- uncountable/types/recipe_tags_t.py +26 -0
- uncountable/types/recipe_workflow_steps.py +9 -73
- uncountable/types/recipe_workflow_steps_t.py +95 -0
- uncountable/types/recipes.py +7 -0
- uncountable/types/recipes_t.py +25 -0
- uncountable/types/response.py +3 -21
- uncountable/types/response_t.py +26 -0
- uncountable/types/secret_retrieval.py +11 -0
- uncountable/types/secret_retrieval_t.py +75 -0
- 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 +3 -19
- uncountable/types/units_t.py +26 -0
- uncountable/types/uploader.py +24 -0
- uncountable/types/uploader_t.py +222 -0
- uncountable/types/users.py +3 -20
- uncountable/types/users_t.py +27 -0
- uncountable/types/webhook_job.py +9 -0
- uncountable/types/webhook_job_t.py +48 -0
- uncountable/types/workflows.py +4 -28
- uncountable/types/workflows_t.py +38 -0
- uncountablepythonsdk-0.0.131.dist-info/METADATA +64 -0
- uncountablepythonsdk-0.0.131.dist-info/RECORD +363 -0
- {UncountablePythonSDK-0.0.24.dist-info → uncountablepythonsdk-0.0.131.dist-info}/WHEEL +1 -1
- {UncountablePythonSDK-0.0.24.dist-info → uncountablepythonsdk-0.0.131.dist-info}/top_level.txt +0 -1
- UncountablePythonSDK-0.0.24.dist-info/METADATA +0 -47
- UncountablePythonSDK-0.0.24.dist-info/RECORD +0 -216
- docs/quickstart.md +0 -19
- examples/recipe-import/importer.py +0 -39
- type_spec/external/api/batch/execute_batch.yaml +0 -56
- type_spec/external/api/batch/execute_batch_load_async.yaml +0 -18
- type_spec/external/api/chemical/convert_chemical_formats.yaml +0 -33
- type_spec/external/api/entity/create_entities.yaml +0 -45
- type_spec/external/api/entity/create_entity.yaml +0 -51
- type_spec/external/api/entity/get_entities_data.yaml +0 -29
- type_spec/external/api/entity/list_entities.yaml +0 -52
- type_spec/external/api/entity/resolve_entity_ids.yaml +0 -29
- type_spec/external/api/entity/set_values.yaml +0 -18
- type_spec/external/api/entity/transition_entity_phase.yaml +0 -44
- type_spec/external/api/id_source/list_id_source.yaml +0 -35
- type_spec/external/api/id_source/match_id_source.yaml +0 -32
- type_spec/external/api/input_groups/get_input_group_names.yaml +0 -29
- type_spec/external/api/inputs/create_inputs.yaml +0 -48
- type_spec/external/api/inputs/get_input_data.yaml +0 -95
- type_spec/external/api/inputs/get_input_names.yaml +0 -38
- type_spec/external/api/inputs/get_inputs_data.yaml +0 -82
- type_spec/external/api/inputs/set_input_attribute_values.yaml +0 -33
- type_spec/external/api/outputs/get_output_data.yaml +0 -92
- type_spec/external/api/outputs/get_output_names.yaml +0 -35
- type_spec/external/api/outputs/resolve_output_conditions.yaml +0 -50
- type_spec/external/api/permissions/set_core_permissions.yaml +0 -69
- type_spec/external/api/project/get_projects.yaml +0 -42
- type_spec/external/api/project/get_projects_data.yaml +0 -50
- type_spec/external/api/recipe_links/create_recipe_link.yaml +0 -25
- type_spec/external/api/recipe_metadata/get_recipe_metadata_data.yaml +0 -41
- type_spec/external/api/recipes/archive_recipes.yaml +0 -20
- type_spec/external/api/recipes/associate_recipe_as_input.yaml +0 -19
- type_spec/external/api/recipes/associate_recipe_as_lot.yaml +0 -19
- type_spec/external/api/recipes/create_recipe.yaml +0 -39
- type_spec/external/api/recipes/create_recipes.yaml +0 -47
- type_spec/external/api/recipes/disassociate_recipe_as_input.yaml +0 -16
- type_spec/external/api/recipes/edit_recipe_inputs.yaml +0 -85
- type_spec/external/api/recipes/get_curve.yaml +0 -21
- type_spec/external/api/recipes/get_recipe_calculations.yaml +0 -39
- type_spec/external/api/recipes/get_recipe_links.yaml +0 -26
- type_spec/external/api/recipes/get_recipe_names.yaml +0 -29
- type_spec/external/api/recipes/get_recipe_output_metadata.yaml +0 -36
- type_spec/external/api/recipes/get_recipes_data.yaml +0 -244
- type_spec/external/api/recipes/set_recipe_inputs.yaml +0 -42
- type_spec/external/api/recipes/set_recipe_metadata.yaml +0 -20
- type_spec/external/api/recipes/set_recipe_outputs.yaml +0 -52
- type_spec/external/api/recipes/set_recipe_tags.yaml +0 -62
- type_spec/external/api/triggers/run_trigger.yaml +0 -18
- uncountable/integration/types.py +0 -89
uncountable/integration/job.py
CHANGED
|
@@ -1,45 +1,272 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import hmac
|
|
3
|
+
import typing
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
1
5
|
from dataclasses import dataclass
|
|
2
|
-
from uncountable.core.client import Client
|
|
3
|
-
from uncountable.integration.types import JobDefinition
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
import simplejson
|
|
8
|
+
|
|
9
|
+
from pkgs.argument_parser import CachedParser
|
|
10
|
+
from pkgs.serialization_util import serialize_for_api
|
|
11
|
+
from uncountable.core.async_batch import AsyncBatchProcessor
|
|
12
|
+
from uncountable.core.client import Client
|
|
13
|
+
from uncountable.core.environment import get_local_admin_server_port
|
|
14
|
+
from uncountable.core.file_upload import FileUpload
|
|
15
|
+
from uncountable.core.types import AuthDetailsOAuth
|
|
16
|
+
from uncountable.integration.http_server import (
|
|
17
|
+
GenericHttpRequest,
|
|
18
|
+
GenericHttpResponse,
|
|
19
|
+
HttpException,
|
|
20
|
+
)
|
|
21
|
+
from uncountable.integration.queue_runner.command_server.command_client import (
|
|
22
|
+
send_job_queue_message,
|
|
23
|
+
)
|
|
24
|
+
from uncountable.integration.queue_runner.command_server.types import (
|
|
25
|
+
CommandServerException,
|
|
26
|
+
)
|
|
27
|
+
from uncountable.integration.secret_retrieval.retrieve_secret import retrieve_secret
|
|
28
|
+
from uncountable.integration.telemetry import JobLogger
|
|
29
|
+
from uncountable.types import (
|
|
30
|
+
base_t,
|
|
31
|
+
job_definition_t,
|
|
32
|
+
queued_job_t,
|
|
33
|
+
webhook_job_t,
|
|
34
|
+
)
|
|
35
|
+
from uncountable.types.job_definition_t import (
|
|
36
|
+
HttpJobDefinitionBase,
|
|
37
|
+
JobDefinition,
|
|
38
|
+
JobResult,
|
|
39
|
+
ProfileMetadata,
|
|
40
|
+
)
|
|
6
41
|
|
|
7
42
|
|
|
8
|
-
@dataclass
|
|
9
|
-
class
|
|
43
|
+
@dataclass(kw_only=True)
|
|
44
|
+
class JobArguments:
|
|
10
45
|
job_definition: JobDefinition
|
|
46
|
+
profile_metadata: ProfileMetadata
|
|
11
47
|
client: Client
|
|
48
|
+
batch_processor: AsyncBatchProcessor
|
|
49
|
+
logger: JobLogger
|
|
50
|
+
payload: base_t.JsonValue
|
|
51
|
+
job_uuid: str
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# only for compatibility:
|
|
55
|
+
CronJobArguments = JobArguments
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class Job[PT](ABC):
|
|
59
|
+
_unc_job_registered: bool = False
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
@abstractmethod
|
|
63
|
+
def payload_type(self) -> type[PT]: ...
|
|
64
|
+
|
|
65
|
+
@abstractmethod
|
|
66
|
+
def run_outer(self, args: JobArguments) -> JobResult: ...
|
|
67
|
+
|
|
68
|
+
@functools.cached_property
|
|
69
|
+
def _cached_payload_parser(self) -> CachedParser[PT]:
|
|
70
|
+
return CachedParser(self.payload_type)
|
|
71
|
+
|
|
72
|
+
def get_payload(self, payload: base_t.JsonValue) -> PT:
|
|
73
|
+
return self._cached_payload_parser.parse_storage(payload)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class CronJob(Job):
|
|
77
|
+
@property
|
|
78
|
+
def payload_type(self) -> type[None]:
|
|
79
|
+
return type(None)
|
|
80
|
+
|
|
81
|
+
def run_outer(self, args: JobArguments) -> JobResult:
|
|
82
|
+
assert isinstance(args, CronJobArguments)
|
|
83
|
+
return self.run(args)
|
|
84
|
+
|
|
85
|
+
@abstractmethod
|
|
86
|
+
def run(self, args: JobArguments) -> JobResult: ...
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
WPT = typing.TypeVar("WPT")
|
|
12
90
|
|
|
13
91
|
|
|
14
|
-
@dataclass
|
|
15
|
-
class
|
|
16
|
-
# can imagine passing additional data such as in the sftp or webhook cases
|
|
92
|
+
@dataclass(kw_only=True)
|
|
93
|
+
class WebhookResponse:
|
|
17
94
|
pass
|
|
18
95
|
|
|
19
96
|
|
|
20
|
-
|
|
97
|
+
class _RequestValidatorClient(Client):
|
|
98
|
+
def __init__(self, *, base_url: str, oauth_bearer_token: str):
|
|
99
|
+
super().__init__(
|
|
100
|
+
base_url=base_url,
|
|
101
|
+
auth_details=AuthDetailsOAuth(refresh_token=""),
|
|
102
|
+
config=None,
|
|
103
|
+
)
|
|
104
|
+
self._oauth_bearer_token = oauth_bearer_token
|
|
21
105
|
|
|
106
|
+
def _get_oauth_bearer_token(self, *, oauth_details: AuthDetailsOAuth) -> str:
|
|
107
|
+
return self._oauth_bearer_token
|
|
22
108
|
|
|
23
|
-
@dataclass
|
|
24
|
-
class JobResult:
|
|
25
|
-
success: bool
|
|
26
109
|
|
|
110
|
+
class CustomHttpJob(Job[GenericHttpRequest]):
|
|
111
|
+
@property
|
|
112
|
+
def payload_type(self) -> type[GenericHttpRequest]:
|
|
113
|
+
return GenericHttpRequest
|
|
27
114
|
|
|
28
|
-
|
|
29
|
-
|
|
115
|
+
@staticmethod
|
|
116
|
+
@abstractmethod
|
|
117
|
+
def validate_request(
|
|
118
|
+
*,
|
|
119
|
+
request: GenericHttpRequest,
|
|
120
|
+
job_definition: HttpJobDefinitionBase,
|
|
121
|
+
profile_meta: ProfileMetadata,
|
|
122
|
+
) -> None:
|
|
123
|
+
"""
|
|
124
|
+
Validate that the request is valid. If the request is invalid, raise an
|
|
125
|
+
exception.
|
|
126
|
+
"""
|
|
127
|
+
...
|
|
30
128
|
|
|
129
|
+
@staticmethod
|
|
130
|
+
def get_validated_oauth_request_user_id(
|
|
131
|
+
*, profile_metadata: ProfileMetadata, request: GenericHttpRequest
|
|
132
|
+
) -> base_t.ObjectId:
|
|
133
|
+
token = request.headers.get("Authorization", "").replace("Bearer ", "")
|
|
134
|
+
if token == "":
|
|
135
|
+
raise HttpException(
|
|
136
|
+
message="unauthorized; no bearer token in request", error_code=401
|
|
137
|
+
)
|
|
138
|
+
return (
|
|
139
|
+
_RequestValidatorClient(
|
|
140
|
+
base_url=profile_metadata.base_url,
|
|
141
|
+
oauth_bearer_token=token,
|
|
142
|
+
)
|
|
143
|
+
.get_current_user_info()
|
|
144
|
+
.user_id
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
@staticmethod
|
|
31
148
|
@abstractmethod
|
|
32
|
-
def
|
|
149
|
+
def handle_request(
|
|
150
|
+
*,
|
|
151
|
+
request: GenericHttpRequest,
|
|
152
|
+
job_definition: HttpJobDefinitionBase,
|
|
153
|
+
profile_meta: ProfileMetadata,
|
|
154
|
+
) -> GenericHttpResponse:
|
|
155
|
+
"""
|
|
156
|
+
Handle the request synchronously. Normally this should just enqueue a job
|
|
157
|
+
and return immediately (see WebhookJob as an example).
|
|
158
|
+
"""
|
|
33
159
|
...
|
|
34
160
|
|
|
161
|
+
def run_outer(self, args: JobArguments) -> JobResult:
|
|
162
|
+
args.logger.log_warning(
|
|
163
|
+
message=f"Unexpected call to run_outer for CustomHttpJob: {args.job_definition.id}"
|
|
164
|
+
)
|
|
165
|
+
return JobResult(success=False)
|
|
35
166
|
|
|
36
|
-
class CronJob(Job):
|
|
37
167
|
|
|
168
|
+
class WebhookJob[WPT](Job[webhook_job_t.WebhookEventPayload]):
|
|
169
|
+
@property
|
|
170
|
+
def payload_type(self) -> type[webhook_job_t.WebhookEventPayload]:
|
|
171
|
+
return webhook_job_t.WebhookEventPayload
|
|
172
|
+
|
|
173
|
+
@property
|
|
38
174
|
@abstractmethod
|
|
39
|
-
def
|
|
40
|
-
|
|
175
|
+
def webhook_payload_type(self) -> type[WPT]: ...
|
|
176
|
+
|
|
177
|
+
@staticmethod
|
|
178
|
+
def validate_request(
|
|
179
|
+
*,
|
|
180
|
+
request: GenericHttpRequest,
|
|
181
|
+
job_definition: job_definition_t.HttpJobDefinitionBase,
|
|
182
|
+
profile_meta: ProfileMetadata,
|
|
183
|
+
) -> None:
|
|
184
|
+
assert isinstance(job_definition, job_definition_t.WebhookJobDefinition)
|
|
185
|
+
signature_key = retrieve_secret(
|
|
186
|
+
profile_metadata=profile_meta,
|
|
187
|
+
secret_retrieval=job_definition.signature_key_secret,
|
|
188
|
+
)
|
|
189
|
+
passed_signature = request.headers.get("Uncountable-Webhook-Signature")
|
|
190
|
+
if passed_signature is None:
|
|
191
|
+
raise HttpException.no_signature_passed()
|
|
192
|
+
|
|
193
|
+
request_body_signature = hmac.new(
|
|
194
|
+
signature_key.encode("utf-8"), msg=request.body_bytes, digestmod="sha256"
|
|
195
|
+
).hexdigest()
|
|
196
|
+
|
|
197
|
+
if request_body_signature != passed_signature:
|
|
198
|
+
raise HttpException.payload_failed_signature()
|
|
199
|
+
|
|
200
|
+
@staticmethod
|
|
201
|
+
def handle_request(
|
|
202
|
+
*,
|
|
203
|
+
request: GenericHttpRequest,
|
|
204
|
+
job_definition: job_definition_t.HttpJobDefinitionBase,
|
|
205
|
+
profile_meta: ProfileMetadata, # noqa: ARG004
|
|
206
|
+
) -> GenericHttpResponse:
|
|
207
|
+
try:
|
|
208
|
+
request_body = simplejson.loads(request.body_text)
|
|
209
|
+
webhook_payload = typing.cast(base_t.JsonValue, request_body)
|
|
210
|
+
except (simplejson.JSONDecodeError, ValueError) as e:
|
|
211
|
+
raise HttpException.body_parse_error() from e
|
|
212
|
+
|
|
213
|
+
try:
|
|
214
|
+
send_job_queue_message(
|
|
215
|
+
job_ref_name=job_definition.id,
|
|
216
|
+
payload=queued_job_t.QueuedJobPayload(
|
|
217
|
+
invocation_context=queued_job_t.InvocationContextWebhook(
|
|
218
|
+
webhook_payload=webhook_payload
|
|
219
|
+
)
|
|
220
|
+
),
|
|
221
|
+
port=get_local_admin_server_port(),
|
|
222
|
+
)
|
|
223
|
+
except CommandServerException as e:
|
|
224
|
+
raise HttpException.unknown_error() from e
|
|
41
225
|
|
|
226
|
+
return GenericHttpResponse(
|
|
227
|
+
response=simplejson.dumps(serialize_for_api(WebhookResponse())),
|
|
228
|
+
status_code=200,
|
|
229
|
+
)
|
|
42
230
|
|
|
43
|
-
def
|
|
231
|
+
def run_outer(self, args: JobArguments) -> JobResult:
|
|
232
|
+
webhook_body = self.get_payload(args.payload)
|
|
233
|
+
inner_payload = CachedParser(self.webhook_payload_type).parse_api(
|
|
234
|
+
webhook_body.data
|
|
235
|
+
)
|
|
236
|
+
return self.run(args, inner_payload)
|
|
237
|
+
|
|
238
|
+
@abstractmethod
|
|
239
|
+
def run(self, args: JobArguments, payload: WPT) -> JobResult: ...
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def register_job(cls: type[Job]) -> type[Job]:
|
|
44
243
|
cls._unc_job_registered = True
|
|
45
244
|
return cls
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
class RunsheetWebhookJob(WebhookJob[webhook_job_t.RunsheetWebhookPayload]):
|
|
248
|
+
@property
|
|
249
|
+
def webhook_payload_type(self) -> type:
|
|
250
|
+
return webhook_job_t.RunsheetWebhookPayload
|
|
251
|
+
|
|
252
|
+
@abstractmethod
|
|
253
|
+
def build_runsheet(
|
|
254
|
+
self,
|
|
255
|
+
*,
|
|
256
|
+
args: JobArguments,
|
|
257
|
+
payload: webhook_job_t.RunsheetWebhookPayload,
|
|
258
|
+
) -> FileUpload: ...
|
|
259
|
+
|
|
260
|
+
def run(
|
|
261
|
+
self, args: JobArguments, payload: webhook_job_t.RunsheetWebhookPayload
|
|
262
|
+
) -> JobResult:
|
|
263
|
+
runsheet = self.build_runsheet(args=args, payload=payload)
|
|
264
|
+
|
|
265
|
+
files = args.client.upload_files(file_uploads=[runsheet])
|
|
266
|
+
args.client.complete_async_upload(
|
|
267
|
+
async_job_id=payload.async_job_id, file_id=files[0].file_id
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
return JobResult(
|
|
271
|
+
success=True,
|
|
272
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from .command_client import check_health, send_job_queue_message
|
|
2
|
+
from .command_server import serve
|
|
3
|
+
from .types import (
|
|
4
|
+
CommandEnqueueJob,
|
|
5
|
+
CommandEnqueueJobResponse,
|
|
6
|
+
CommandQueue,
|
|
7
|
+
CommandRetryJob,
|
|
8
|
+
CommandRetryJobResponse,
|
|
9
|
+
CommandServerBadResponse,
|
|
10
|
+
CommandServerException,
|
|
11
|
+
CommandServerTimeout,
|
|
12
|
+
CommandTask,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__: list[str] = [
|
|
16
|
+
"serve",
|
|
17
|
+
"check_health",
|
|
18
|
+
"send_job_queue_message",
|
|
19
|
+
"CommandEnqueueJob",
|
|
20
|
+
"CommandEnqueueJobResponse",
|
|
21
|
+
"CommandRetryJob",
|
|
22
|
+
"CommandRetryJobResponse",
|
|
23
|
+
"CommandTask",
|
|
24
|
+
"CommandQueue",
|
|
25
|
+
"CommandServerTimeout",
|
|
26
|
+
"CommandServerException",
|
|
27
|
+
"CommandServerBadResponse",
|
|
28
|
+
]
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
from contextlib import contextmanager
|
|
2
|
+
from typing import Generator
|
|
3
|
+
|
|
4
|
+
import grpc
|
|
5
|
+
import simplejson as json
|
|
6
|
+
|
|
7
|
+
from pkgs.serialization_util import serialize_for_api
|
|
8
|
+
from uncountable.integration.queue_runner.command_server.protocol.command_server_pb2 import (
|
|
9
|
+
CheckHealthRequest,
|
|
10
|
+
CheckHealthResult,
|
|
11
|
+
EnqueueJobRequest,
|
|
12
|
+
EnqueueJobResult,
|
|
13
|
+
ListQueuedJobsRequest,
|
|
14
|
+
ListQueuedJobsResult,
|
|
15
|
+
RetryJobRequest,
|
|
16
|
+
RetryJobResult,
|
|
17
|
+
VaccuumQueuedJobsRequest,
|
|
18
|
+
VaccuumQueuedJobsResult,
|
|
19
|
+
)
|
|
20
|
+
from uncountable.integration.queue_runner.command_server.types import (
|
|
21
|
+
CommandServerBadResponse,
|
|
22
|
+
CommandServerTimeout,
|
|
23
|
+
)
|
|
24
|
+
from uncountable.types import queued_job_t
|
|
25
|
+
|
|
26
|
+
from .protocol.command_server_pb2_grpc import CommandServerStub
|
|
27
|
+
|
|
28
|
+
_LOCAL_RPC_HOST = "localhost"
|
|
29
|
+
_DEFAULT_MESSAGE_TIMEOUT_SECS = 2
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@contextmanager
|
|
33
|
+
def command_server_connection(
|
|
34
|
+
host: str, port: int
|
|
35
|
+
) -> Generator[CommandServerStub, None, None]:
|
|
36
|
+
try:
|
|
37
|
+
with grpc.insecure_channel(f"{host}:{port}") as channel:
|
|
38
|
+
stub = CommandServerStub(channel)
|
|
39
|
+
yield stub
|
|
40
|
+
except grpc._channel._InactiveRpcError as e:
|
|
41
|
+
raise CommandServerTimeout() from e
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def send_job_queue_message(
|
|
45
|
+
*,
|
|
46
|
+
job_ref_name: str,
|
|
47
|
+
payload: queued_job_t.QueuedJobPayload,
|
|
48
|
+
host: str = "localhost",
|
|
49
|
+
port: int,
|
|
50
|
+
) -> str:
|
|
51
|
+
with command_server_connection(host=host, port=port) as stub:
|
|
52
|
+
request = EnqueueJobRequest(
|
|
53
|
+
job_ref_name=job_ref_name,
|
|
54
|
+
serialized_payload=json.dumps(serialize_for_api(payload)),
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
response = stub.EnqueueJob(request, timeout=_DEFAULT_MESSAGE_TIMEOUT_SECS)
|
|
58
|
+
|
|
59
|
+
assert isinstance(response, EnqueueJobResult)
|
|
60
|
+
if not response.successfully_queued:
|
|
61
|
+
raise CommandServerBadResponse("queue operation was not successful")
|
|
62
|
+
|
|
63
|
+
return response.queued_job_uuid
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def send_retry_job_message(
|
|
67
|
+
*,
|
|
68
|
+
job_uuid: str,
|
|
69
|
+
host: str = "localhost",
|
|
70
|
+
port: int,
|
|
71
|
+
) -> str:
|
|
72
|
+
with command_server_connection(host=host, port=port) as stub:
|
|
73
|
+
request = RetryJobRequest(uuid=job_uuid)
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
response = stub.RetryJob(request, timeout=_DEFAULT_MESSAGE_TIMEOUT_SECS)
|
|
77
|
+
assert isinstance(response, RetryJobResult)
|
|
78
|
+
if not response.successfully_queued:
|
|
79
|
+
raise CommandServerBadResponse("queue operation was not successful")
|
|
80
|
+
|
|
81
|
+
return response.queued_job_uuid
|
|
82
|
+
except grpc.RpcError as e:
|
|
83
|
+
raise ValueError(e.details()) # type: ignore
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def check_health(*, host: str = _LOCAL_RPC_HOST, port: int) -> bool:
|
|
87
|
+
with command_server_connection(host=host, port=port) as stub:
|
|
88
|
+
request = CheckHealthRequest()
|
|
89
|
+
|
|
90
|
+
response = stub.CheckHealth(request, timeout=_DEFAULT_MESSAGE_TIMEOUT_SECS)
|
|
91
|
+
|
|
92
|
+
assert isinstance(response, CheckHealthResult)
|
|
93
|
+
|
|
94
|
+
return response.success
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def send_list_queued_jobs_message(
|
|
98
|
+
*,
|
|
99
|
+
offset: int,
|
|
100
|
+
limit: int,
|
|
101
|
+
host: str = "localhost",
|
|
102
|
+
port: int,
|
|
103
|
+
) -> list[ListQueuedJobsResult.ListQueuedJobsResultItem]:
|
|
104
|
+
with command_server_connection(host=host, port=port) as stub:
|
|
105
|
+
request = ListQueuedJobsRequest(
|
|
106
|
+
offset=offset,
|
|
107
|
+
limit=limit,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
response = stub.ListQueuedJobs(
|
|
112
|
+
request, timeout=_DEFAULT_MESSAGE_TIMEOUT_SECS
|
|
113
|
+
)
|
|
114
|
+
except grpc.RpcError as e:
|
|
115
|
+
raise ValueError(e.details()) # type: ignore
|
|
116
|
+
|
|
117
|
+
assert isinstance(response, ListQueuedJobsResult)
|
|
118
|
+
return list(response.queued_jobs)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def send_vaccuum_queued_jobs_message(*, host: str = "localhost", port: int) -> None:
|
|
122
|
+
with command_server_connection(host=host, port=port) as stub:
|
|
123
|
+
request = VaccuumQueuedJobsRequest()
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
response = stub.VaccuumQueuedJobs(
|
|
127
|
+
request, timeout=_DEFAULT_MESSAGE_TIMEOUT_SECS
|
|
128
|
+
)
|
|
129
|
+
except grpc.RpcError as e:
|
|
130
|
+
raise ValueError(e.details()) # type: ignore
|
|
131
|
+
|
|
132
|
+
assert isinstance(response, VaccuumQueuedJobsResult)
|
|
133
|
+
return None
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
import grpc.aio as grpc_aio
|
|
4
|
+
import simplejson as json
|
|
5
|
+
from google.protobuf.timestamp_pb2 import Timestamp
|
|
6
|
+
from grpc import StatusCode
|
|
7
|
+
|
|
8
|
+
from pkgs.argument_parser import CachedParser
|
|
9
|
+
from uncountable.core.environment import get_local_admin_server_port
|
|
10
|
+
from uncountable.integration.queue_runner.command_server.protocol.command_server_pb2 import (
|
|
11
|
+
CheckHealthRequest,
|
|
12
|
+
CheckHealthResult,
|
|
13
|
+
EnqueueJobRequest,
|
|
14
|
+
EnqueueJobResult,
|
|
15
|
+
ListQueuedJobsRequest,
|
|
16
|
+
ListQueuedJobsResult,
|
|
17
|
+
RetryJobRequest,
|
|
18
|
+
RetryJobResult,
|
|
19
|
+
VaccuumQueuedJobsRequest,
|
|
20
|
+
VaccuumQueuedJobsResult,
|
|
21
|
+
)
|
|
22
|
+
from uncountable.integration.queue_runner.command_server.types import (
|
|
23
|
+
CommandEnqueueJob,
|
|
24
|
+
CommandEnqueueJobResponse,
|
|
25
|
+
CommandQueue,
|
|
26
|
+
CommandRetryJob,
|
|
27
|
+
CommandRetryJobResponse,
|
|
28
|
+
CommandVaccuumQueuedJobs,
|
|
29
|
+
CommandVaccuumQueuedJobsResponse,
|
|
30
|
+
)
|
|
31
|
+
from uncountable.integration.queue_runner.datastore import DatastoreSqlite
|
|
32
|
+
from uncountable.types import queued_job_t
|
|
33
|
+
|
|
34
|
+
from .constants import ListQueuedJobsConstants
|
|
35
|
+
from .protocol.command_server_pb2_grpc import (
|
|
36
|
+
CommandServerServicer,
|
|
37
|
+
add_CommandServerServicer_to_server,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
queued_job_payload_parser = CachedParser(queued_job_t.QueuedJobPayload)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
async def serve(command_queue: CommandQueue, datastore: DatastoreSqlite) -> None:
|
|
44
|
+
server = grpc_aio.server()
|
|
45
|
+
|
|
46
|
+
class CommandServerHandler(CommandServerServicer):
|
|
47
|
+
async def EnqueueJob(
|
|
48
|
+
self, request: EnqueueJobRequest, context: grpc_aio.ServicerContext
|
|
49
|
+
) -> EnqueueJobResult:
|
|
50
|
+
payload_json = json.loads(request.serialized_payload)
|
|
51
|
+
payload = queued_job_payload_parser.parse_api(payload_json)
|
|
52
|
+
response_queue: asyncio.Queue[CommandEnqueueJobResponse] = asyncio.Queue()
|
|
53
|
+
await command_queue.put(
|
|
54
|
+
CommandEnqueueJob(
|
|
55
|
+
job_ref_name=request.job_ref_name,
|
|
56
|
+
payload=payload,
|
|
57
|
+
response_queue=response_queue,
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
response = await response_queue.get()
|
|
61
|
+
result = EnqueueJobResult(
|
|
62
|
+
successfully_queued=True, queued_job_uuid=response.queued_job_uuid
|
|
63
|
+
)
|
|
64
|
+
return result
|
|
65
|
+
|
|
66
|
+
async def RetryJob(
|
|
67
|
+
self, request: RetryJobRequest, context: grpc_aio.ServicerContext
|
|
68
|
+
) -> RetryJobResult:
|
|
69
|
+
response_queue: asyncio.Queue[CommandRetryJobResponse] = asyncio.Queue()
|
|
70
|
+
await command_queue.put(
|
|
71
|
+
CommandRetryJob(
|
|
72
|
+
queued_job_uuid=request.uuid, response_queue=response_queue
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
response = await response_queue.get()
|
|
76
|
+
if response.queued_job_uuid is not None:
|
|
77
|
+
return RetryJobResult(
|
|
78
|
+
successfully_queued=True, queued_job_uuid=response.queued_job_uuid
|
|
79
|
+
)
|
|
80
|
+
else:
|
|
81
|
+
return RetryJobResult(successfully_queued=False, queued_job_uuid="")
|
|
82
|
+
|
|
83
|
+
async def CheckHealth(
|
|
84
|
+
self, request: CheckHealthRequest, context: grpc_aio.ServicerContext
|
|
85
|
+
) -> CheckHealthResult:
|
|
86
|
+
return CheckHealthResult(success=True)
|
|
87
|
+
|
|
88
|
+
async def ListQueuedJobs(
|
|
89
|
+
self, request: ListQueuedJobsRequest, context: grpc_aio.ServicerContext
|
|
90
|
+
) -> ListQueuedJobsResult:
|
|
91
|
+
if (
|
|
92
|
+
request.limit < ListQueuedJobsConstants.LIMIT_MIN
|
|
93
|
+
or request.limit > ListQueuedJobsConstants.LIMIT_MAX
|
|
94
|
+
):
|
|
95
|
+
await context.abort(
|
|
96
|
+
StatusCode.INVALID_ARGUMENT, "Limit must be between 1 and 100."
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
if request.offset < ListQueuedJobsConstants.OFFSET_MIN:
|
|
100
|
+
await context.abort(
|
|
101
|
+
StatusCode.INVALID_ARGUMENT, "Offset cannot be negative."
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
queued_job_metadata = datastore.list_queued_job_metadata(
|
|
105
|
+
offset=request.offset, limit=request.limit
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
response_list: list[ListQueuedJobsResult.ListQueuedJobsResultItem] = []
|
|
109
|
+
for item in queued_job_metadata:
|
|
110
|
+
proto_timestamp = Timestamp()
|
|
111
|
+
proto_timestamp.FromDatetime(item.submitted_at)
|
|
112
|
+
|
|
113
|
+
response_list.append(
|
|
114
|
+
ListQueuedJobsResult.ListQueuedJobsResultItem(
|
|
115
|
+
uuid=item.queued_job_uuid,
|
|
116
|
+
job_ref_name=item.job_ref_name,
|
|
117
|
+
num_attempts=item.num_attempts,
|
|
118
|
+
submitted_at=proto_timestamp,
|
|
119
|
+
status=item.status,
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
return ListQueuedJobsResult(queued_jobs=response_list)
|
|
123
|
+
|
|
124
|
+
async def VaccuumQueuedJobs(
|
|
125
|
+
self, request: VaccuumQueuedJobsRequest, context: grpc_aio.ServicerContext
|
|
126
|
+
) -> VaccuumQueuedJobsResult:
|
|
127
|
+
response_queue: asyncio.Queue[CommandVaccuumQueuedJobsResponse] = (
|
|
128
|
+
asyncio.Queue()
|
|
129
|
+
)
|
|
130
|
+
await command_queue.put(
|
|
131
|
+
CommandVaccuumQueuedJobs(response_queue=response_queue)
|
|
132
|
+
)
|
|
133
|
+
return VaccuumQueuedJobsResult()
|
|
134
|
+
|
|
135
|
+
add_CommandServerServicer_to_server(CommandServerHandler(), server)
|
|
136
|
+
|
|
137
|
+
listen_addr = f"[::]:{get_local_admin_server_port()}"
|
|
138
|
+
|
|
139
|
+
server.add_insecure_port(listen_addr)
|
|
140
|
+
|
|
141
|
+
await server.start()
|
|
142
|
+
await server.wait_for_termination()
|
|
File without changes
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
syntax = "proto3";
|
|
2
|
+
import "google/protobuf/timestamp.proto";
|
|
3
|
+
|
|
4
|
+
service CommandServer {
|
|
5
|
+
rpc EnqueueJob(EnqueueJobRequest) returns (EnqueueJobResult) {}
|
|
6
|
+
rpc RetryJob(RetryJobRequest) returns (RetryJobResult) {}
|
|
7
|
+
rpc CheckHealth(CheckHealthRequest) returns (CheckHealthResult) {}
|
|
8
|
+
rpc ListQueuedJobs(ListQueuedJobsRequest) returns (ListQueuedJobsResult) {}
|
|
9
|
+
rpc VaccuumQueuedJobs(VaccuumQueuedJobsRequest) returns (VaccuumQueuedJobsResult) {}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
message EnqueueJobRequest {
|
|
13
|
+
string job_ref_name = 1;
|
|
14
|
+
string serialized_payload = 2;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
message EnqueueJobResult {
|
|
18
|
+
bool successfully_queued = 1;
|
|
19
|
+
string queued_job_uuid = 2;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
message RetryJobRequest {
|
|
23
|
+
string uuid = 1;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
message RetryJobResult {
|
|
27
|
+
bool successfully_queued = 1;
|
|
28
|
+
string queued_job_uuid = 2;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
message VaccuumQueuedJobsRequest {
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
message VaccuumQueuedJobsResult {
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
message CheckHealthRequest {}
|
|
38
|
+
|
|
39
|
+
message CheckHealthResult {
|
|
40
|
+
bool success = 1;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
message ListQueuedJobsRequest {
|
|
44
|
+
uint32 offset = 1;
|
|
45
|
+
uint32 limit = 2;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
message ListQueuedJobsResult {
|
|
49
|
+
message ListQueuedJobsResultItem {
|
|
50
|
+
string uuid = 1;
|
|
51
|
+
string job_ref_name = 2;
|
|
52
|
+
int64 num_attempts = 3;
|
|
53
|
+
google.protobuf.Timestamp submitted_at = 4;
|
|
54
|
+
string status = 5;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
repeated ListQueuedJobsResultItem queued_jobs = 1;
|
|
58
|
+
}
|