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
pkgs/type_spec/emit_python.py
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
|
+
import dataclasses
|
|
1
2
|
import io
|
|
2
3
|
import os
|
|
3
|
-
from dataclasses import dataclass, field
|
|
4
4
|
from decimal import Decimal
|
|
5
|
-
from typing import Any
|
|
5
|
+
from typing import Any
|
|
6
6
|
|
|
7
7
|
from . import builder, util
|
|
8
|
+
from .builder import EndpointEmitType, EndpointSpecificPath, base_namespace_name
|
|
8
9
|
from .config import PythonConfig
|
|
10
|
+
from .cross_output_links import get_path_links
|
|
11
|
+
from .emit_open_api_util import EmitOpenAPIStabilityLevel
|
|
9
12
|
|
|
10
13
|
INDENT = " "
|
|
11
14
|
LINE_BREAK = "\n"
|
|
12
15
|
MODIFY_NOTICE = "# DO NOT MODIFY -- This file is generated by type_spec\n"
|
|
13
16
|
# Turn excess line length warning and turn off ruff formatting
|
|
14
|
-
LINT_HEADER = "#
|
|
17
|
+
LINT_HEADER = "# ruff: noqa: E402 Q003\n# fmt: off\n# isort: skip_file\n"
|
|
15
18
|
LINT_FOOTER = "# fmt: on\n"
|
|
16
19
|
ROUTE_NOTICE = """# Routes are generated from $endpoint specifications in the
|
|
17
20
|
# type_spec API YAML files. Refer to the section on endpoints in the type_spec/README"""
|
|
@@ -21,7 +24,7 @@ __all__: list[str] = [
|
|
|
21
24
|
"""
|
|
22
25
|
END_ALL_EXPORTS = "]\n"
|
|
23
26
|
|
|
24
|
-
ASYNC_BATCH_TYPE_NAMESPACE = builder.SpecNamespace(name="
|
|
27
|
+
ASYNC_BATCH_TYPE_NAMESPACE = builder.SpecNamespace(name="async_batch")
|
|
25
28
|
ASYNC_BATCH_REQUEST_PATH_STYPE = builder.SpecTypeDefnStringEnum(
|
|
26
29
|
namespace=ASYNC_BATCH_TYPE_NAMESPACE, name="AsyncBatchRequestPath"
|
|
27
30
|
)
|
|
@@ -32,29 +35,38 @@ QUEUED_BATCH_REQUEST_STYPE = builder.SpecTypeDefnObject(
|
|
|
32
35
|
namespace=ASYNC_BATCH_TYPE_NAMESPACE, name="QueuedAsyncBatchRequest"
|
|
33
36
|
)
|
|
34
37
|
|
|
38
|
+
CLIENT_CONFIG_TYPE_NAMESPACE = builder.SpecNamespace(name="client_config")
|
|
39
|
+
REQUEST_OPTIONS_STYPE = builder.SpecTypeDefnObject(
|
|
40
|
+
namespace=CLIENT_CONFIG_TYPE_NAMESPACE, name="RequestOptions"
|
|
41
|
+
)
|
|
42
|
+
|
|
35
43
|
|
|
36
|
-
@dataclass(kw_only=True)
|
|
44
|
+
@dataclasses.dataclass(kw_only=True)
|
|
37
45
|
class TrackingContext:
|
|
38
|
-
namespace:
|
|
39
|
-
namespaces: set[builder.SpecNamespace] = field(default_factory=set)
|
|
40
|
-
names: set[str] = field(default_factory=set)
|
|
46
|
+
namespace: builder.SpecNamespace | None = None
|
|
47
|
+
namespaces: set[builder.SpecNamespace] = dataclasses.field(default_factory=set)
|
|
48
|
+
names: set[str] = dataclasses.field(default_factory=set)
|
|
41
49
|
|
|
42
50
|
use_enum: bool = False
|
|
43
51
|
use_serial_string_enum: bool = False
|
|
44
52
|
use_dataclass: bool = False
|
|
45
|
-
|
|
53
|
+
use_serial_union: bool = False
|
|
54
|
+
use_serial_alias: bool = False
|
|
46
55
|
use_missing: bool = False
|
|
47
56
|
use_opaque_key: bool = False
|
|
48
57
|
|
|
49
58
|
|
|
50
|
-
@dataclass(kw_only=True)
|
|
59
|
+
@dataclasses.dataclass(kw_only=True)
|
|
51
60
|
class Context(TrackingContext):
|
|
52
61
|
out: io.StringIO
|
|
53
62
|
namespace: builder.SpecNamespace
|
|
63
|
+
builder: builder.SpecBuilder
|
|
54
64
|
|
|
55
65
|
|
|
56
66
|
def _resolve_namespace_name(namespace: builder.SpecNamespace) -> str:
|
|
57
|
-
|
|
67
|
+
if len(namespace.path) > 1:
|
|
68
|
+
return namespace.name
|
|
69
|
+
return f"{namespace.name}_t"
|
|
58
70
|
|
|
59
71
|
|
|
60
72
|
def _resolve_namespace_ref(namespace: builder.SpecNamespace) -> str:
|
|
@@ -110,26 +122,36 @@ def _check_type_match(stype: builder.SpecType, value: Any) -> bool:
|
|
|
110
122
|
raise Exception("invalid type", stype, value)
|
|
111
123
|
|
|
112
124
|
|
|
113
|
-
def _emit_value(
|
|
125
|
+
def _emit_value(
|
|
126
|
+
ctx: TrackingContext, stype: builder.SpecType, value: Any, indent: int = 0
|
|
127
|
+
) -> str:
|
|
114
128
|
literal = builder.unwrap_literal_type(stype)
|
|
115
129
|
if literal is not None:
|
|
116
130
|
return _emit_value(ctx, literal.value_type, literal.value)
|
|
117
131
|
|
|
118
132
|
if stype.is_base_type(builder.BaseTypeName.s_string):
|
|
119
|
-
assert isinstance(value, str)
|
|
133
|
+
assert isinstance(value, str), (
|
|
134
|
+
f"Expected str value for {stype.name} but got {value}"
|
|
135
|
+
)
|
|
120
136
|
return util.encode_common_string(value)
|
|
121
137
|
elif stype.is_base_type(builder.BaseTypeName.s_integer):
|
|
122
|
-
assert isinstance(value, int)
|
|
138
|
+
assert isinstance(value, int), (
|
|
139
|
+
f"Expected int value for {stype.name} but got {value}"
|
|
140
|
+
)
|
|
123
141
|
return str(value)
|
|
124
142
|
elif stype.is_base_type(builder.BaseTypeName.s_boolean):
|
|
125
|
-
assert isinstance(value, bool)
|
|
143
|
+
assert isinstance(value, bool), (
|
|
144
|
+
f"Expected bool value for {stype.name} but got {value}"
|
|
145
|
+
)
|
|
126
146
|
return "True" if value else "False"
|
|
127
147
|
elif stype.is_base_type(builder.BaseTypeName.s_decimal) or stype.is_base_type(
|
|
128
148
|
builder.BaseTypeName.s_lossy_decimal
|
|
129
149
|
):
|
|
130
150
|
# Note that decimal requires the `!decimal 123.12` style notation in the YAML
|
|
131
151
|
# file since PyYaml parses numbers as float, unfortuantely
|
|
132
|
-
assert isinstance(value, (Decimal, int))
|
|
152
|
+
assert isinstance(value, (Decimal, int)), (
|
|
153
|
+
f"Expected decimal value for {stype.name} but got {value} (type: {type(value)})"
|
|
154
|
+
)
|
|
133
155
|
if isinstance(value, int):
|
|
134
156
|
# skip quotes for integers
|
|
135
157
|
return f"Decimal({value})"
|
|
@@ -144,14 +166,14 @@ def _emit_value(ctx: TrackingContext, stype: builder.SpecType, value: Any) -> st
|
|
|
144
166
|
key_type = stype.parameters[0]
|
|
145
167
|
value_type = stype.parameters[1]
|
|
146
168
|
return (
|
|
147
|
-
"{\n
|
|
148
|
-
+ ",\n
|
|
169
|
+
f"{{\n{INDENT * (indent + 1)}"
|
|
170
|
+
+ f",\n{INDENT * (indent + 1)}".join(
|
|
149
171
|
_emit_value(ctx, key_type, dkey)
|
|
150
172
|
+ ": "
|
|
151
|
-
+ _emit_value(ctx, value_type, dvalue)
|
|
173
|
+
+ _emit_value(ctx, value_type, dvalue, indent=indent + 1)
|
|
152
174
|
for dkey, dvalue in value.items()
|
|
153
175
|
)
|
|
154
|
-
+ "\n}"
|
|
176
|
+
+ f"\n{INDENT * indent}}}"
|
|
155
177
|
)
|
|
156
178
|
|
|
157
179
|
if stype.defn_type.is_base_type(builder.BaseTypeName.s_optional):
|
|
@@ -177,6 +199,34 @@ def _emit_value(ctx: TrackingContext, stype: builder.SpecType, value: Any) -> st
|
|
|
177
199
|
return f"{refer_to(ctx, stype)}.{_resolve_enum_name(value, stype.name_case)}"
|
|
178
200
|
elif isinstance(stype, builder.SpecTypeDefnAlias):
|
|
179
201
|
return _emit_value(ctx, stype.alias, value)
|
|
202
|
+
elif isinstance(stype, builder.SpecTypeDefnObject):
|
|
203
|
+
assert isinstance(value, dict), (
|
|
204
|
+
f"Expected dict value for {stype.name} but got {value}"
|
|
205
|
+
)
|
|
206
|
+
if not stype.is_hashable:
|
|
207
|
+
raise Exception("invalid constant object type, non-hashable", value, stype)
|
|
208
|
+
obj_out = f"{refer_to(ctx, stype)}("
|
|
209
|
+
emitted_fields: set[str] = set()
|
|
210
|
+
for prop_name, prop in (stype.properties or {}).items():
|
|
211
|
+
if prop_name not in value:
|
|
212
|
+
continue
|
|
213
|
+
else:
|
|
214
|
+
value_to_emit = value[prop_name]
|
|
215
|
+
emitted_fields.add(prop_name)
|
|
216
|
+
py_name = python_field_name(prop.name, prop.name_case)
|
|
217
|
+
obj_out += f"\n{INDENT * (indent + 1)}{py_name}={_emit_value(ctx, prop.spec_type, value_to_emit, indent=indent + 1)},"
|
|
218
|
+
whitespace = f"\n{INDENT * indent}" if len(emitted_fields) > 0 else ""
|
|
219
|
+
obj_out += f"{whitespace})"
|
|
220
|
+
|
|
221
|
+
if emitted_fields != set(value.keys()):
|
|
222
|
+
raise Exception(
|
|
223
|
+
"invalid object type, extra fields found:",
|
|
224
|
+
value,
|
|
225
|
+
stype,
|
|
226
|
+
set(value.keys()) - emitted_fields,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
return obj_out
|
|
180
230
|
|
|
181
231
|
raise Exception("invalid constant type", value, stype)
|
|
182
232
|
|
|
@@ -214,11 +264,14 @@ def _emit_types_imports(*, out: io.StringIO, ctx: Context) -> None:
|
|
|
214
264
|
out.write("import datetime # noqa: F401\n")
|
|
215
265
|
out.write("from decimal import Decimal # noqa: F401\n")
|
|
216
266
|
if ctx.use_enum:
|
|
217
|
-
out.write("from
|
|
267
|
+
out.write("from enum import StrEnum\n")
|
|
218
268
|
if ctx.use_dataclass:
|
|
219
|
-
out.write("
|
|
220
|
-
if ctx.use_serial_class:
|
|
269
|
+
out.write("import dataclasses\n")
|
|
221
270
|
out.write("from pkgs.serialization import serial_class\n")
|
|
271
|
+
if ctx.use_serial_union:
|
|
272
|
+
out.write("from pkgs.serialization import serial_union_annotation\n")
|
|
273
|
+
if ctx.use_serial_alias:
|
|
274
|
+
out.write("from pkgs.serialization import serial_alias_annotation\n")
|
|
222
275
|
if ctx.use_serial_string_enum:
|
|
223
276
|
out.write("from pkgs.serialization import serial_string_enum\n")
|
|
224
277
|
if ctx.use_missing:
|
|
@@ -243,7 +296,7 @@ def _emit_types(*, builder: builder.SpecBuilder, config: PythonConfig) -> None:
|
|
|
243
296
|
):
|
|
244
297
|
if (
|
|
245
298
|
namespace.endpoint is not None
|
|
246
|
-
and
|
|
299
|
+
and namespace.endpoint.is_sdk == EndpointEmitType.EMIT_NOTHING
|
|
247
300
|
and config.sdk_endpoints_only is True
|
|
248
301
|
):
|
|
249
302
|
continue
|
|
@@ -251,6 +304,7 @@ def _emit_types(*, builder: builder.SpecBuilder, config: PythonConfig) -> None:
|
|
|
251
304
|
ctx = Context(
|
|
252
305
|
out=io.StringIO(),
|
|
253
306
|
namespace=namespace,
|
|
307
|
+
builder=builder,
|
|
254
308
|
)
|
|
255
309
|
|
|
256
310
|
_emit_namespace(ctx, namespace)
|
|
@@ -289,17 +343,22 @@ def _emit_types(*, builder: builder.SpecBuilder, config: PythonConfig) -> None:
|
|
|
289
343
|
full.write(f"# === END section from {namespace.name}.part.py ===\n")
|
|
290
344
|
|
|
291
345
|
basename = "/".join(namespace.path)
|
|
292
|
-
filename = f"{config.types_output}/{basename}.py"
|
|
346
|
+
filename = f"{config.types_output}/{basename}{'' if len(namespace.path) > 1 else '_t'}.py"
|
|
293
347
|
util.rewrite_file(filename, full.getvalue())
|
|
294
348
|
|
|
349
|
+
# Deprecated SDK support
|
|
350
|
+
if config.all_named_type_exports and len(namespace.path) == 1:
|
|
351
|
+
compat_out = _create_sdk_compat_namespace(namespace)
|
|
352
|
+
compat_filename = f"{config.types_output}/{basename}.py"
|
|
353
|
+
util.rewrite_file(compat_filename, compat_out.getvalue())
|
|
354
|
+
|
|
295
355
|
path_to = os.path.dirname(basename)
|
|
296
356
|
while path_to != "":
|
|
297
357
|
all_dirs.add(path_to)
|
|
298
358
|
path_to = os.path.dirname(path_to)
|
|
299
359
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
):
|
|
360
|
+
# Deprecated SDK support
|
|
361
|
+
if config.all_named_type_exports:
|
|
303
362
|
index_out.write("from ")
|
|
304
363
|
if len(namespace.path) == 1:
|
|
305
364
|
index_out.write(".")
|
|
@@ -328,16 +387,44 @@ def _emit_types(*, builder: builder.SpecBuilder, config: PythonConfig) -> None:
|
|
|
328
387
|
|
|
329
388
|
ENDPOINT_METHOD = "ENDPOINT_METHOD"
|
|
330
389
|
ENDPOINT_PATH = "ENDPOINT_PATH"
|
|
390
|
+
# will be removed in Q1 2025 when ENDPOINT_PATH is made api_endpoint-agnostic
|
|
391
|
+
# is used when the API call has multiple endpoints for the one endpoint that isn't equal to the top_namespace
|
|
392
|
+
ENDPOINT_PATH_ALTERNATE = "ENDPOINT_PATH_ALTERNATE"
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def _get_epf_root(endpoint_specific_path: EndpointSpecificPath) -> str:
|
|
396
|
+
return endpoint_specific_path.root
|
|
331
397
|
|
|
332
398
|
|
|
333
399
|
def _emit_namespace(ctx: Context, namespace: builder.SpecNamespace) -> None:
|
|
334
400
|
endpoint = namespace.endpoint
|
|
335
401
|
if endpoint is not None:
|
|
402
|
+
path_links = get_path_links(
|
|
403
|
+
ctx.builder.cross_output_paths,
|
|
404
|
+
namespace,
|
|
405
|
+
current_path_type="Python",
|
|
406
|
+
endpoint=endpoint,
|
|
407
|
+
)
|
|
408
|
+
if path_links != "":
|
|
409
|
+
ctx.out.write("\n")
|
|
410
|
+
ctx.out.write(path_links)
|
|
411
|
+
|
|
336
412
|
ctx.out.write("\n")
|
|
337
413
|
ctx.out.write(f'{ENDPOINT_METHOD} = "{endpoint.method.upper()}"\n')
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
)
|
|
414
|
+
for endpoint_specific_path in sorted(
|
|
415
|
+
endpoint.path_per_api_endpoint.values(), key=_get_epf_root
|
|
416
|
+
):
|
|
417
|
+
endpoint_path_name = ENDPOINT_PATH
|
|
418
|
+
|
|
419
|
+
if (
|
|
420
|
+
len(endpoint.path_per_api_endpoint.keys()) > 1
|
|
421
|
+
and endpoint_specific_path.root != ctx.builder.top_namespace
|
|
422
|
+
):
|
|
423
|
+
endpoint_path_name = ENDPOINT_PATH_ALTERNATE
|
|
424
|
+
ctx.names.add(ENDPOINT_PATH_ALTERNATE)
|
|
425
|
+
ctx.out.write(
|
|
426
|
+
f'{endpoint_path_name} = "{endpoint_specific_path.path_root}/{endpoint_specific_path.path_dirname}/{endpoint_specific_path.path_basename}"\n'
|
|
427
|
+
)
|
|
341
428
|
|
|
342
429
|
ctx.names.add(ENDPOINT_METHOD)
|
|
343
430
|
ctx.names.add(ENDPOINT_PATH)
|
|
@@ -349,16 +436,42 @@ def _emit_namespace(ctx: Context, namespace: builder.SpecNamespace) -> None:
|
|
|
349
436
|
_emit_constant(ctx, sconst)
|
|
350
437
|
|
|
351
438
|
|
|
439
|
+
def _create_sdk_compat_namespace(namespace: builder.SpecNamespace) -> io.StringIO:
|
|
440
|
+
compat_out = io.StringIO()
|
|
441
|
+
compat_out.write(LINT_HEADER)
|
|
442
|
+
compat_out.write(MODIFY_NOTICE)
|
|
443
|
+
compat_out.write("# Kept only for SDK backwards compatibility\n")
|
|
444
|
+
|
|
445
|
+
# This mostly an prepart import, thus has no high-level knowledge. Since this onl
|
|
446
|
+
# needs backwards copmatibiltiy from when written we can just hardcode what was there
|
|
447
|
+
# (only those in __all__ assuming that worked, which it might not)
|
|
448
|
+
if namespace.name == "base":
|
|
449
|
+
compat_out.write("""
|
|
450
|
+
from .base_t import JsonScalar as JsonScalar
|
|
451
|
+
from .base_t import JsonValue as JsonValue
|
|
452
|
+
from .base_t import ObjectId as ObjectId
|
|
453
|
+
""")
|
|
454
|
+
else:
|
|
455
|
+
for stype in namespace.types.values():
|
|
456
|
+
compat_out.write(
|
|
457
|
+
f"from .{namespace.path[-1]}_t import {stype.name} as {stype.name}\n"
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
compat_out.write(MODIFY_NOTICE)
|
|
461
|
+
|
|
462
|
+
return compat_out
|
|
463
|
+
|
|
464
|
+
|
|
352
465
|
def _validate_supports_handler_generation(
|
|
353
466
|
stype: builder.SpecTypeDefn, name: str, supports_inheritance: bool = False
|
|
354
467
|
) -> builder.SpecTypeDefnObject:
|
|
355
|
-
assert isinstance(
|
|
356
|
-
|
|
357
|
-
)
|
|
468
|
+
assert isinstance(stype, builder.SpecTypeDefnObject), (
|
|
469
|
+
f"External api {name} must be an object"
|
|
470
|
+
)
|
|
358
471
|
if not supports_inheritance:
|
|
359
|
-
assert (
|
|
360
|
-
|
|
361
|
-
)
|
|
472
|
+
assert stype.base is None or stype.base.is_base, (
|
|
473
|
+
f"Inheritance not supported in external api {name}"
|
|
474
|
+
)
|
|
362
475
|
return stype
|
|
363
476
|
|
|
364
477
|
|
|
@@ -401,8 +514,23 @@ def _emit_endpoint_invocation_function_signature(
|
|
|
401
514
|
else []
|
|
402
515
|
) + (extra_params if extra_params is not None else [])
|
|
403
516
|
|
|
404
|
-
|
|
405
|
-
|
|
517
|
+
request_options_property = builder.SpecProperty(
|
|
518
|
+
name="_request_options",
|
|
519
|
+
label="_request_options",
|
|
520
|
+
spec_type=REQUEST_OPTIONS_STYPE,
|
|
521
|
+
extant=builder.PropertyExtant.optional,
|
|
522
|
+
convert_value=builder.PropertyConvertValue.auto,
|
|
523
|
+
name_case=builder.NameCase.convert,
|
|
524
|
+
default=None,
|
|
525
|
+
has_default=True,
|
|
526
|
+
desc=None,
|
|
527
|
+
)
|
|
528
|
+
all_arguments.append(request_options_property)
|
|
529
|
+
|
|
530
|
+
# All endpoints share a function name
|
|
531
|
+
function = endpoint.path_per_api_endpoint[endpoint.default_endpoint_key].function
|
|
532
|
+
assert function is not None
|
|
533
|
+
function_name = function.split(".")[-1]
|
|
406
534
|
ctx.out.write(
|
|
407
535
|
f"""
|
|
408
536
|
def {function_name}(
|
|
@@ -448,7 +576,10 @@ def _emit_async_batch_invocation_function(
|
|
|
448
576
|
endpoint = namespace.endpoint
|
|
449
577
|
if endpoint is None:
|
|
450
578
|
return
|
|
451
|
-
if
|
|
579
|
+
if (
|
|
580
|
+
endpoint.async_batch_path is None
|
|
581
|
+
or endpoint.is_sdk != EndpointEmitType.EMIT_ENDPOINT
|
|
582
|
+
):
|
|
452
583
|
return
|
|
453
584
|
|
|
454
585
|
ctx.out.write("\n")
|
|
@@ -530,7 +661,10 @@ def _emit_endpoint_invocation_function(
|
|
|
530
661
|
endpoint = namespace.endpoint
|
|
531
662
|
if endpoint is None:
|
|
532
663
|
return
|
|
533
|
-
if
|
|
664
|
+
if (
|
|
665
|
+
endpoint.is_sdk != EndpointEmitType.EMIT_ENDPOINT
|
|
666
|
+
or endpoint.stability_level == EmitOpenAPIStabilityLevel.draft
|
|
667
|
+
):
|
|
534
668
|
return
|
|
535
669
|
|
|
536
670
|
ctx.out.write("\n")
|
|
@@ -563,6 +697,7 @@ def _emit_endpoint_invocation_function(
|
|
|
563
697
|
method={refer_to(ctx=ctx, stype=endpoint_method_stype)},
|
|
564
698
|
endpoint={refer_to(ctx=ctx, stype=endpoint_path_stype)},
|
|
565
699
|
args=args,
|
|
700
|
+
request_options=_request_options,
|
|
566
701
|
)
|
|
567
702
|
return self.do_request(api_request=api_request, return_type={refer_to(ctx=ctx, stype=data_type)})"""
|
|
568
703
|
)
|
|
@@ -583,7 +718,9 @@ def _emit_string_enum(ctx: Context, stype: builder.SpecTypeDefnStringEnum) -> No
|
|
|
583
718
|
ctx.out.write(f"{INDENT}labels={{\n")
|
|
584
719
|
for entry in stype.values.values():
|
|
585
720
|
if entry.label is not None:
|
|
586
|
-
ctx.out.write(
|
|
721
|
+
ctx.out.write(
|
|
722
|
+
f'{INDENT}{INDENT}"{entry.value}": "{entry.label}",\n'
|
|
723
|
+
)
|
|
587
724
|
|
|
588
725
|
ctx.out.write(f"{INDENT}}},\n")
|
|
589
726
|
if need_deprecated:
|
|
@@ -614,7 +751,7 @@ def _emit_string_enum(ctx: Context, stype: builder.SpecTypeDefnStringEnum) -> No
|
|
|
614
751
|
)
|
|
615
752
|
|
|
616
753
|
|
|
617
|
-
@dataclass
|
|
754
|
+
@dataclasses.dataclass
|
|
618
755
|
class EmittedPropertiesMetadata:
|
|
619
756
|
unconverted_keys: set[str]
|
|
620
757
|
unconverted_values: set[str]
|
|
@@ -655,33 +792,46 @@ def _emit_properties(
|
|
|
655
792
|
if len(properties) > 0:
|
|
656
793
|
|
|
657
794
|
def write_field(prop: builder.SpecProperty) -> None:
|
|
795
|
+
stype = prop.spec_type
|
|
658
796
|
if prop.name_case == builder.NameCase.preserve:
|
|
659
797
|
unconverted_keys.add(prop.name)
|
|
660
798
|
py_name = python_field_name(prop.name, prop.name_case)
|
|
661
799
|
|
|
662
800
|
if prop.convert_value == builder.PropertyConvertValue.no_convert:
|
|
663
801
|
unconverted_values.add(py_name)
|
|
664
|
-
elif not
|
|
802
|
+
elif not stype.is_value_converted():
|
|
665
803
|
assert prop.convert_value == builder.PropertyConvertValue.auto
|
|
666
804
|
unconverted_values.add(py_name)
|
|
667
|
-
if
|
|
805
|
+
if stype.is_value_to_string():
|
|
668
806
|
to_string_values.add(py_name)
|
|
669
807
|
|
|
670
808
|
if prop.parse_require:
|
|
671
809
|
parse_require.add(py_name)
|
|
672
810
|
|
|
673
|
-
ref_type = refer_to(ctx,
|
|
811
|
+
ref_type = refer_to(ctx, stype)
|
|
674
812
|
default = None
|
|
675
813
|
if prop.extant == builder.PropertyExtant.missing:
|
|
676
814
|
ref_type = f"MissingType[{ref_type}]"
|
|
677
815
|
default = "MISSING_SENTRY"
|
|
678
816
|
ctx.use_missing = True
|
|
679
817
|
elif prop.extant == builder.PropertyExtant.optional:
|
|
680
|
-
|
|
818
|
+
if isinstance(
|
|
819
|
+
stype, builder.SpecTypeInstance
|
|
820
|
+
) and stype.defn_type.is_base_type(builder.BaseTypeName.s_optional):
|
|
821
|
+
pass # base type already adds the None union
|
|
822
|
+
elif ref_type == "None":
|
|
823
|
+
pass # no need to add a None union to a none type
|
|
824
|
+
else:
|
|
825
|
+
ref_type = f"{ref_type} | None"
|
|
681
826
|
default = "None"
|
|
682
827
|
elif prop.has_default:
|
|
683
|
-
default = _emit_value(ctx,
|
|
684
|
-
|
|
828
|
+
default = _emit_value(ctx, stype, prop.default)
|
|
829
|
+
if (
|
|
830
|
+
isinstance(stype, builder.SpecTypeInstance)
|
|
831
|
+
and (stype.defn_type.is_base_type(builder.BaseTypeName.s_list))
|
|
832
|
+
and default == "[]"
|
|
833
|
+
):
|
|
834
|
+
default = "dataclasses.field(default_factory=list)"
|
|
685
835
|
class_out.write(f"{INDENT * num_indent}{py_name}: {ref_type}")
|
|
686
836
|
if default:
|
|
687
837
|
class_out.write(f" = {default}")
|
|
@@ -704,6 +854,12 @@ def _emit_properties(
|
|
|
704
854
|
)
|
|
705
855
|
|
|
706
856
|
|
|
857
|
+
def _named_type_path(ctx: Context, stype: builder.SpecTypeDefn) -> str:
|
|
858
|
+
parts = [] if stype.is_base else stype.namespace.path.copy()
|
|
859
|
+
parts.append(stype.name)
|
|
860
|
+
return f"{ctx.builder.top_namespace}.{'.'.join(parts)}"
|
|
861
|
+
|
|
862
|
+
|
|
707
863
|
def _emit_type(ctx: Context, stype: builder.SpecType) -> None:
|
|
708
864
|
if not isinstance(stype, builder.SpecTypeDefn):
|
|
709
865
|
return
|
|
@@ -724,7 +880,42 @@ def _emit_type(ctx: Context, stype: builder.SpecType) -> None:
|
|
|
724
880
|
return
|
|
725
881
|
|
|
726
882
|
if isinstance(stype, builder.SpecTypeDefnAlias):
|
|
727
|
-
ctx.
|
|
883
|
+
ctx.use_serial_alias = True
|
|
884
|
+
ctx.out.write(f"{stype.name} = typing.Annotated[\n")
|
|
885
|
+
ctx.out.write(f"{INDENT}{refer_to(ctx, stype.alias)},\n")
|
|
886
|
+
ctx.out.write(f"{INDENT}serial_alias_annotation(\n")
|
|
887
|
+
ctx.out.write(
|
|
888
|
+
f"{INDENT}named_type_path={util.encode_common_string(_named_type_path(ctx, stype))},\n"
|
|
889
|
+
)
|
|
890
|
+
if stype.is_dynamic_allowed():
|
|
891
|
+
ctx.out.write(f"{INDENT}is_dynamic_allowed=True,\n")
|
|
892
|
+
ctx.out.write(f"{INDENT}),\n")
|
|
893
|
+
ctx.out.write("]\n")
|
|
894
|
+
return
|
|
895
|
+
|
|
896
|
+
if isinstance(stype, builder.SpecTypeDefnUnion):
|
|
897
|
+
ctx.use_serial_union = True
|
|
898
|
+
ctx.out.write(f"{stype.name} = typing.Annotated[\n")
|
|
899
|
+
ctx.out.write(f"{INDENT}{refer_to(ctx, stype.get_backing_type())},\n")
|
|
900
|
+
ctx.out.write(f"{INDENT}serial_union_annotation(\n")
|
|
901
|
+
ctx.out.write(
|
|
902
|
+
f"{INDENT}named_type_path={util.encode_common_string(_named_type_path(ctx, stype))},\n"
|
|
903
|
+
)
|
|
904
|
+
if stype.is_dynamic_allowed():
|
|
905
|
+
ctx.out.write(f"{INDENT}is_dynamic_allowed=True,\n")
|
|
906
|
+
if stype.discriminator is not None:
|
|
907
|
+
ctx.out.write(
|
|
908
|
+
f"{INDENT * 2}discriminator={util.encode_common_string(stype.discriminator)},\n"
|
|
909
|
+
)
|
|
910
|
+
if stype.discriminator_map is not None:
|
|
911
|
+
ctx.out.write(f"{INDENT * 2}discriminator_map={{\n")
|
|
912
|
+
for key, value in stype.discriminator_map.items():
|
|
913
|
+
ctx.out.write(
|
|
914
|
+
f"{INDENT * 3}{util.encode_common_string(key)}: {refer_to(ctx, value)},\n"
|
|
915
|
+
)
|
|
916
|
+
ctx.out.write(f"{INDENT * 2}}},\n")
|
|
917
|
+
ctx.out.write(f"{INDENT}),\n")
|
|
918
|
+
ctx.out.write("]\n")
|
|
728
919
|
return
|
|
729
920
|
|
|
730
921
|
if isinstance(stype, builder.SpecTypeDefnStringEnum):
|
|
@@ -736,11 +927,11 @@ def _emit_type(ctx: Context, stype: builder.SpecType) -> None:
|
|
|
736
927
|
|
|
737
928
|
class_out = io.StringIO()
|
|
738
929
|
base_class = ""
|
|
739
|
-
|
|
930
|
+
generics = stype.get_generics()
|
|
740
931
|
if not stype.base.is_base:
|
|
741
932
|
base_class = f"({refer_to(ctx, stype.base)})"
|
|
742
|
-
elif
|
|
743
|
-
base_class = f"
|
|
933
|
+
elif len(generics) > 0:
|
|
934
|
+
base_class = f"[{', '.join(generics)}]"
|
|
744
935
|
class_out.write(f"class {stype.name}{base_class}:\n")
|
|
745
936
|
|
|
746
937
|
emitted_properties_metadata = _emit_type_properties(
|
|
@@ -751,45 +942,51 @@ def _emit_type(ctx: Context, stype: builder.SpecType) -> None:
|
|
|
751
942
|
to_string_values = emitted_properties_metadata.to_string_values
|
|
752
943
|
parse_require = emitted_properties_metadata.parse_require
|
|
753
944
|
|
|
754
|
-
|
|
945
|
+
_emit_generics(ctx, generics)
|
|
755
946
|
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
):
|
|
762
|
-
ctx.
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
dataclass = "@dataclass"
|
|
779
|
-
|
|
947
|
+
# Emit serial_class decorator
|
|
948
|
+
ctx.out.write("@serial_class(\n")
|
|
949
|
+
ctx.out.write(
|
|
950
|
+
f"{INDENT}named_type_path={util.encode_common_string(_named_type_path(ctx, stype))},\n"
|
|
951
|
+
)
|
|
952
|
+
if stype.is_dynamic_allowed():
|
|
953
|
+
ctx.out.write(f"{INDENT}is_dynamic_allowed=True,\n")
|
|
954
|
+
|
|
955
|
+
def write_values(key: str, values: set[str]) -> None:
|
|
956
|
+
if len(values) == 0:
|
|
957
|
+
return
|
|
958
|
+
value_str = ", ".join([f'"{name}"' for name in sorted(values)])
|
|
959
|
+
ctx.out.write(f"{INDENT}{key}={{{value_str}}},\n")
|
|
960
|
+
|
|
961
|
+
write_values("unconverted_keys", unconverted_keys)
|
|
962
|
+
write_values("unconverted_values", unconverted_values)
|
|
963
|
+
write_values("to_string_values", to_string_values)
|
|
964
|
+
write_values("parse_require", parse_require)
|
|
965
|
+
|
|
966
|
+
ctx.out.write(")\n")
|
|
967
|
+
|
|
968
|
+
# Emit dataclass decorator
|
|
969
|
+
dataclass = "@dataclasses.dataclass"
|
|
970
|
+
refer_to(
|
|
971
|
+
ctx,
|
|
972
|
+
builder.SpecTypeDefnAlias(
|
|
973
|
+
namespace=ctx.builder.namespaces[base_namespace_name], name="ENABLE_SLOTS"
|
|
974
|
+
),
|
|
975
|
+
)
|
|
976
|
+
dc_args = ["slots=base_t.ENABLE_SLOTS"]
|
|
780
977
|
if stype.is_kw_only():
|
|
781
978
|
dc_args.append("kw_only=True")
|
|
782
979
|
if stype.is_hashable:
|
|
783
980
|
dc_args.extend(["frozen=True", "eq=True"])
|
|
784
981
|
if len(dc_args) > 0:
|
|
785
|
-
dataclass += f
|
|
982
|
+
dataclass += f"({', '.join(dc_args)}) # type: ignore[literal-required]"
|
|
786
983
|
|
|
787
984
|
ctx.out.write(f"{dataclass}\n")
|
|
788
985
|
ctx.out.write(class_out.getvalue())
|
|
789
986
|
|
|
790
987
|
|
|
791
|
-
def
|
|
792
|
-
|
|
988
|
+
def _emit_generics(ctx: Context, generics: list[str]) -> None:
|
|
989
|
+
for generic in generics:
|
|
793
990
|
ctx.out.write(f'{generic} = typing.TypeVar("{generic}")\n')
|
|
794
991
|
ctx.out.write(f"{LINE_BREAK}{LINE_BREAK}")
|
|
795
992
|
|
|
@@ -827,13 +1024,22 @@ base_name_map = {
|
|
|
827
1024
|
|
|
828
1025
|
def refer_to(ctx: TrackingContext, stype: builder.SpecType) -> str:
|
|
829
1026
|
if isinstance(stype, builder.SpecTypeInstance):
|
|
830
|
-
params =
|
|
1027
|
+
params = [refer_to(ctx, p) for p in stype.parameters]
|
|
1028
|
+
|
|
1029
|
+
if stype.defn_type.is_base_type(builder.BaseTypeName.s_union):
|
|
1030
|
+
if len(stype.parameters) == 1:
|
|
1031
|
+
return f"typing.Union[{params[0]}]"
|
|
1032
|
+
return " | ".join(params)
|
|
831
1033
|
|
|
832
1034
|
if stype.defn_type.is_base_type(builder.BaseTypeName.s_readonly_array):
|
|
833
|
-
assert len(
|
|
834
|
-
|
|
1035
|
+
assert len(params) == 1, "Read Only Array takes one parameter"
|
|
1036
|
+
return f"tuple[{params[0]}, ...]"
|
|
835
1037
|
|
|
836
|
-
|
|
1038
|
+
if stype.defn_type.is_base_type(builder.BaseTypeName.s_optional):
|
|
1039
|
+
assert len(params) == 1, "Optional only takes one parameter"
|
|
1040
|
+
return f"{params[0]} | None"
|
|
1041
|
+
|
|
1042
|
+
return f"{refer_to(ctx, stype.defn_type)}[{', '.join(params)}]"
|
|
837
1043
|
|
|
838
1044
|
if isinstance(stype, builder.SpecTypeLiteralWrapper):
|
|
839
1045
|
return _emit_value(ctx, stype.value_type, stype.value)
|
|
@@ -859,23 +1065,21 @@ def refer_to(ctx: TrackingContext, stype: builder.SpecType) -> str:
|
|
|
859
1065
|
SpecEndpoint = builder.SpecEndpoint
|
|
860
1066
|
|
|
861
1067
|
|
|
862
|
-
def _route_identifier(endpoint: builder.SpecEndpoint) -> tuple[str, str, str]:
|
|
863
|
-
return (endpoint.path_dirname, endpoint.path_basename, endpoint.method)
|
|
864
|
-
|
|
865
|
-
|
|
866
1068
|
def _emit_routes(*, builder: builder.SpecBuilder, config: PythonConfig) -> None:
|
|
867
1069
|
for endpoint_root in builder.api_endpoints:
|
|
868
1070
|
endpoints: list[SpecEndpoint] = []
|
|
869
1071
|
output = config.routes_output.get(endpoint_root)
|
|
870
1072
|
if output is None:
|
|
871
1073
|
continue
|
|
1074
|
+
last_endpoint: SpecEndpoint | None = None
|
|
872
1075
|
for namespace in builder.namespaces.values():
|
|
873
1076
|
endpoint = namespace.endpoint
|
|
1077
|
+
last_endpoint = endpoint
|
|
874
1078
|
if endpoint is None:
|
|
875
1079
|
continue
|
|
876
|
-
if endpoint.
|
|
1080
|
+
if endpoint_root not in endpoint.path_per_api_endpoint:
|
|
877
1081
|
continue
|
|
878
|
-
if endpoint.function is None:
|
|
1082
|
+
if endpoint.path_per_api_endpoint[endpoint_root].function is None:
|
|
879
1083
|
continue
|
|
880
1084
|
|
|
881
1085
|
endpoints.append(endpoint)
|
|
@@ -888,25 +1092,38 @@ def _emit_routes(*, builder: builder.SpecBuilder, config: PythonConfig) -> None:
|
|
|
888
1092
|
from main.site.framework.types import StaticRouteType
|
|
889
1093
|
"""
|
|
890
1094
|
)
|
|
1095
|
+
|
|
1096
|
+
def _route_identifier(endpoint: SpecEndpoint) -> tuple[str, str, str]:
|
|
1097
|
+
endpoint_specific_path = endpoint.path_per_api_endpoint[endpoint_root]
|
|
1098
|
+
return (
|
|
1099
|
+
endpoint_specific_path.path_dirname,
|
|
1100
|
+
endpoint_specific_path.path_basename,
|
|
1101
|
+
endpoint.method,
|
|
1102
|
+
)
|
|
1103
|
+
|
|
891
1104
|
sorted_endpoints = sorted(endpoints, key=_route_identifier)
|
|
892
1105
|
|
|
893
|
-
assert len(endpoints) == len(
|
|
894
|
-
|
|
895
|
-
)
|
|
1106
|
+
assert len(endpoints) == len(set(map(_route_identifier, endpoints))), (
|
|
1107
|
+
"Endpoints are not unique"
|
|
1108
|
+
)
|
|
896
1109
|
|
|
897
1110
|
path_set = set()
|
|
898
1111
|
for endpoint in sorted_endpoints:
|
|
899
|
-
|
|
900
|
-
|
|
1112
|
+
last_endpoint = endpoint
|
|
1113
|
+
endpoint_function_path = endpoint.path_per_api_endpoint[endpoint_root]
|
|
1114
|
+
assert endpoint_function_path.function
|
|
1115
|
+
func_bits = endpoint_function_path.function.split(".")
|
|
901
1116
|
path = ".".join(func_bits[:-1])
|
|
902
1117
|
if path in path_set:
|
|
903
1118
|
continue
|
|
904
1119
|
path_set.add(path)
|
|
905
1120
|
static_out.write(f"import {path}\n")
|
|
906
1121
|
|
|
1122
|
+
assert last_endpoint is not None
|
|
1123
|
+
|
|
907
1124
|
static_out.write(
|
|
908
1125
|
f"""
|
|
909
|
-
ROUTE_PREFIX = "/{
|
|
1126
|
+
ROUTE_PREFIX = "/{last_endpoint.path_per_api_endpoint[endpoint_root].path_root}"
|
|
910
1127
|
|
|
911
1128
|
ROUTES: list[StaticRouteType] = [
|
|
912
1129
|
"""
|
|
@@ -920,20 +1137,21 @@ ROUTES: list[StaticRouteType] = [
|
|
|
920
1137
|
|
|
921
1138
|
from main.site.framework.types import DynamicRouteType
|
|
922
1139
|
|
|
923
|
-
ROUTE_PREFIX = "/{
|
|
1140
|
+
ROUTE_PREFIX = "/{last_endpoint.path_per_api_endpoint[endpoint_root].path_root}"
|
|
924
1141
|
|
|
925
1142
|
ROUTES: list[DynamicRouteType] = [
|
|
926
1143
|
"""
|
|
927
1144
|
)
|
|
928
1145
|
|
|
929
1146
|
for endpoint in sorted_endpoints:
|
|
1147
|
+
endpoint_function_path = endpoint.path_per_api_endpoint[endpoint_root]
|
|
930
1148
|
dynamic_out.write(
|
|
931
|
-
f'{INDENT}("{
|
|
1149
|
+
f'{INDENT}("{endpoint_function_path.path_dirname}/{endpoint_function_path.path_basename}", "{endpoint_function_path.function}", ["{endpoint.method.upper()}"]),\n'
|
|
932
1150
|
)
|
|
933
1151
|
|
|
934
|
-
assert
|
|
1152
|
+
assert endpoint_function_path.function
|
|
935
1153
|
static_out.write(
|
|
936
|
-
f'{INDENT}("{
|
|
1154
|
+
f'{INDENT}("{endpoint_function_path.path_dirname}/{endpoint_function_path.path_basename}", {endpoint_function_path.function}, ["{endpoint.method.upper()}"]),\n'
|
|
937
1155
|
)
|
|
938
1156
|
|
|
939
1157
|
dynamic_out.write(f"{MODIFY_NOTICE}]\n")
|
|
@@ -949,15 +1167,21 @@ def _emit_namespace_imports(
|
|
|
949
1167
|
*,
|
|
950
1168
|
out: io.StringIO,
|
|
951
1169
|
namespaces: set[builder.SpecNamespace],
|
|
952
|
-
from_namespace:
|
|
1170
|
+
from_namespace: builder.SpecNamespace | None,
|
|
953
1171
|
config: PythonConfig,
|
|
1172
|
+
skip_non_sdk: bool = False,
|
|
954
1173
|
) -> None:
|
|
955
1174
|
for ns in sorted(
|
|
956
1175
|
namespaces,
|
|
957
1176
|
key=lambda name: _resolve_namespace_name(name),
|
|
958
1177
|
):
|
|
1178
|
+
if (
|
|
1179
|
+
skip_non_sdk
|
|
1180
|
+
and ns.endpoint is not None
|
|
1181
|
+
and ns.endpoint.is_sdk != EndpointEmitType.EMIT_ENDPOINT
|
|
1182
|
+
):
|
|
1183
|
+
continue
|
|
959
1184
|
resolved = _resolve_namespace_name(ns)
|
|
960
|
-
ref = _resolve_namespace_ref(ns)
|
|
961
1185
|
if ns.endpoint is not None:
|
|
962
1186
|
import_alias = "_".join(ns.path[2:]) + "_t"
|
|
963
1187
|
out.write(
|
|
@@ -969,7 +1193,7 @@ def _emit_namespace_imports(
|
|
|
969
1193
|
else:
|
|
970
1194
|
from_path = config.types_package
|
|
971
1195
|
|
|
972
|
-
out.write(f"from {from_path} import {resolved}
|
|
1196
|
+
out.write(f"from {from_path} import {resolved}\n")
|
|
973
1197
|
|
|
974
1198
|
|
|
975
1199
|
def _emit_id_source(*, builder: builder.SpecBuilder, config: PythonConfig) -> None:
|
|
@@ -981,8 +1205,8 @@ def _emit_id_source(*, builder: builder.SpecBuilder, config: PythonConfig) -> No
|
|
|
981
1205
|
return None
|
|
982
1206
|
enum_out = io.StringIO()
|
|
983
1207
|
enum_out.write(f"{LINT_HEADER}{MODIFY_NOTICE}\n")
|
|
984
|
-
enum_out.write("
|
|
985
|
-
enum_out.write("from
|
|
1208
|
+
enum_out.write("import typing\n")
|
|
1209
|
+
enum_out.write("from enum import StrEnum\n")
|
|
986
1210
|
|
|
987
1211
|
ctx = TrackingContext()
|
|
988
1212
|
# In this context the propername
|
|
@@ -998,11 +1222,11 @@ def _emit_id_source(*, builder: builder.SpecBuilder, config: PythonConfig) -> No
|
|
|
998
1222
|
known_keys = []
|
|
999
1223
|
enum_out.write("\nENUM_MAP: dict[str, type[StrEnum]] = {\n")
|
|
1000
1224
|
for key in sorted(named_enums.keys()):
|
|
1001
|
-
enum_out.write(f'"{key}": {named_enums[key]},\n')
|
|
1002
|
-
known_keys.append(f'
|
|
1225
|
+
enum_out.write(f'{INDENT}"{key}": {named_enums[key]},\n')
|
|
1226
|
+
known_keys.append(f'"{key}"')
|
|
1003
1227
|
enum_out.write(f"}}\n{MODIFY_NOTICE}\n")
|
|
1004
1228
|
|
|
1005
|
-
enum_out.write(f"\nKnownEnumsType =
|
|
1229
|
+
enum_out.write(f"\nKnownEnumsType = typing.Literal[\n{INDENT}")
|
|
1006
1230
|
enum_out.write(f",\n{INDENT}".join(known_keys))
|
|
1007
1231
|
enum_out.write(f"\n]\n{MODIFY_NOTICE}\n")
|
|
1008
1232
|
|
|
@@ -1019,21 +1243,36 @@ def _emit_api_stubs(*, builder: builder.SpecBuilder, config: PythonConfig) -> No
|
|
|
1019
1243
|
|
|
1020
1244
|
if endpoint is None:
|
|
1021
1245
|
continue
|
|
1022
|
-
if endpoint.
|
|
1246
|
+
if endpoint_root not in endpoint.path_per_api_endpoint:
|
|
1023
1247
|
continue
|
|
1024
|
-
|
|
1248
|
+
|
|
1249
|
+
endpoint_function = endpoint.path_per_api_endpoint[endpoint_root].function
|
|
1250
|
+
if endpoint_function is None:
|
|
1025
1251
|
continue
|
|
1026
1252
|
|
|
1027
|
-
module_dir, file_name,
|
|
1253
|
+
module_dir, file_name, _func_name = endpoint_function.rsplit(".", 2)
|
|
1028
1254
|
module_path = os.path.abspath(module_dir.replace(".", "/"))
|
|
1029
1255
|
api_stub_file = f"{module_path}/{file_name}.py"
|
|
1030
1256
|
if os.path.isfile(api_stub_file):
|
|
1031
1257
|
continue
|
|
1032
|
-
_create_api_stub(
|
|
1258
|
+
_create_api_stub(
|
|
1259
|
+
api_stub_file=api_stub_file,
|
|
1260
|
+
file_name=file_name,
|
|
1261
|
+
endpoint=endpoint,
|
|
1262
|
+
config=config,
|
|
1263
|
+
endpoint_root=endpoint_root,
|
|
1264
|
+
top_namespace=builder.top_namespace,
|
|
1265
|
+
)
|
|
1033
1266
|
|
|
1034
1267
|
|
|
1035
1268
|
def _create_api_stub(
|
|
1036
|
-
|
|
1269
|
+
*,
|
|
1270
|
+
api_stub_file: str,
|
|
1271
|
+
file_name: str,
|
|
1272
|
+
endpoint: SpecEndpoint,
|
|
1273
|
+
config: PythonConfig,
|
|
1274
|
+
endpoint_root: str,
|
|
1275
|
+
top_namespace: str,
|
|
1037
1276
|
) -> None:
|
|
1038
1277
|
assert (
|
|
1039
1278
|
endpoint.method == builder.RouteMethod.post
|
|
@@ -1041,7 +1280,13 @@ def _create_api_stub(
|
|
|
1041
1280
|
or endpoint.method == builder.RouteMethod.delete
|
|
1042
1281
|
or endpoint.method == builder.RouteMethod.patch
|
|
1043
1282
|
)
|
|
1044
|
-
api_out = _create_api_function(
|
|
1283
|
+
api_out = _create_api_function(
|
|
1284
|
+
file_name=file_name,
|
|
1285
|
+
endpoint=endpoint,
|
|
1286
|
+
config=config,
|
|
1287
|
+
endpoint_root=endpoint_root,
|
|
1288
|
+
top_namespace=top_namespace,
|
|
1289
|
+
)
|
|
1045
1290
|
util.rewrite_file(api_stub_file, api_out.getvalue())
|
|
1046
1291
|
|
|
1047
1292
|
|
|
@@ -1050,15 +1295,22 @@ WRAP_ARGS_END = "\n"
|
|
|
1050
1295
|
|
|
1051
1296
|
|
|
1052
1297
|
def _create_api_function(
|
|
1053
|
-
|
|
1298
|
+
*,
|
|
1299
|
+
file_name: str,
|
|
1300
|
+
endpoint: SpecEndpoint,
|
|
1301
|
+
config: PythonConfig,
|
|
1302
|
+
endpoint_root: str,
|
|
1303
|
+
top_namespace: str,
|
|
1054
1304
|
) -> io.StringIO:
|
|
1305
|
+
endpoint_specific_path = endpoint.path_per_api_endpoint[endpoint_root]
|
|
1306
|
+
assert endpoint_specific_path is not None
|
|
1055
1307
|
api_out = io.StringIO()
|
|
1056
1308
|
python_api_type_root = f"{config.types_package}.api"
|
|
1057
|
-
dot_dirname =
|
|
1309
|
+
dot_dirname = endpoint_specific_path.path_dirname.replace("/", ".")
|
|
1058
1310
|
api_import = (
|
|
1059
1311
|
f"{python_api_type_root}.{dot_dirname}.{file_name}"
|
|
1060
1312
|
if dot_dirname != ""
|
|
1061
|
-
else f"{python_api_type_root}.{
|
|
1313
|
+
else f"{python_api_type_root}.{endpoint_specific_path.path_basename}"
|
|
1062
1314
|
)
|
|
1063
1315
|
|
|
1064
1316
|
if endpoint.method == builder.RouteMethod.post:
|
|
@@ -1070,7 +1322,20 @@ def _create_api_function(
|
|
|
1070
1322
|
elif endpoint.method == builder.RouteMethod.patch:
|
|
1071
1323
|
validated_method = "validated_patch"
|
|
1072
1324
|
|
|
1073
|
-
ruff_requires_wrap = len(
|
|
1325
|
+
ruff_requires_wrap = len(endpoint_specific_path.path_basename) > 14
|
|
1326
|
+
|
|
1327
|
+
account_type = (
|
|
1328
|
+
endpoint_specific_path.root
|
|
1329
|
+
if endpoint_specific_path.root not in ["external", "portal"]
|
|
1330
|
+
else "materials"
|
|
1331
|
+
)
|
|
1332
|
+
|
|
1333
|
+
endpoint_path_name = (
|
|
1334
|
+
ENDPOINT_PATH_ALTERNATE
|
|
1335
|
+
if len(endpoint.path_per_api_endpoint.keys()) > 1
|
|
1336
|
+
and endpoint_specific_path.root != top_namespace
|
|
1337
|
+
else ENDPOINT_PATH
|
|
1338
|
+
)
|
|
1074
1339
|
|
|
1075
1340
|
api_out.write(
|
|
1076
1341
|
f"""import {api_import} as api
|
|
@@ -1078,8 +1343,8 @@ from main.db.session import Session, SessionMaker
|
|
|
1078
1343
|
from main.site.decorators import APIError, APIResponse, {validated_method}
|
|
1079
1344
|
|
|
1080
1345
|
|
|
1081
|
-
@{validated_method}(api.
|
|
1082
|
-
def {
|
|
1346
|
+
@{validated_method}(api.{endpoint_path_name}, "{account_type}", api.Arguments)
|
|
1347
|
+
def {endpoint_specific_path.path_basename}({WRAP_ARGS_START if ruff_requires_wrap else ""}args: api.Arguments, client_sm: SessionMaker{WRAP_ARGS_END if ruff_requires_wrap else ""}) -> APIResponse[api.Data]:
|
|
1083
1348
|
with Session(client_sm) as session:
|
|
1084
1349
|
# return APIResponse(data=api.Data())
|
|
1085
1350
|
pass
|
|
@@ -1101,7 +1366,7 @@ def _emit_api_argument_lookup(
|
|
|
1101
1366
|
for endpoint_root in builder.api_endpoints:
|
|
1102
1367
|
routes_output = config.routes_output[endpoint_root]
|
|
1103
1368
|
|
|
1104
|
-
imports = []
|
|
1369
|
+
imports = ["import dataclasses"]
|
|
1105
1370
|
mappings = []
|
|
1106
1371
|
for namespace in sorted(
|
|
1107
1372
|
builder.namespaces.values(),
|
|
@@ -1111,17 +1376,38 @@ def _emit_api_argument_lookup(
|
|
|
1111
1376
|
|
|
1112
1377
|
if endpoint is None:
|
|
1113
1378
|
continue
|
|
1114
|
-
if endpoint.
|
|
1379
|
+
if endpoint_root not in endpoint.path_per_api_endpoint:
|
|
1115
1380
|
continue
|
|
1116
|
-
if endpoint.function is None:
|
|
1381
|
+
if endpoint.path_per_api_endpoint[endpoint_root].function is None:
|
|
1117
1382
|
continue
|
|
1118
|
-
if "Arguments" not in namespace.types:
|
|
1383
|
+
if "Arguments" not in namespace.types or "Data" not in namespace.types:
|
|
1119
1384
|
continue
|
|
1120
1385
|
|
|
1121
1386
|
import_alias = "_".join(namespace.path[1:])
|
|
1122
1387
|
api_import = f"{config.types_package}.{'.'.join(namespace.path)}"
|
|
1123
1388
|
imports.append(f"import {api_import} as {import_alias}")
|
|
1124
|
-
|
|
1389
|
+
|
|
1390
|
+
route_group = (
|
|
1391
|
+
f'"{endpoint.route_group}"'
|
|
1392
|
+
if endpoint.route_group is not None
|
|
1393
|
+
else "None"
|
|
1394
|
+
)
|
|
1395
|
+
account_type = (
|
|
1396
|
+
f'"{endpoint.account_type}"'
|
|
1397
|
+
if endpoint.account_type is not None
|
|
1398
|
+
else "None"
|
|
1399
|
+
)
|
|
1400
|
+
|
|
1401
|
+
mapping = f"{INDENT}ApiEndpointKey(route={import_alias}.ENDPOINT_PATH, method={import_alias}.ENDPOINT_METHOD): ApiEndpointSpec(\n"
|
|
1402
|
+
mapping += f"{INDENT}{INDENT}arguments_type={import_alias}.Arguments,\n"
|
|
1403
|
+
mapping += f"{INDENT}{INDENT}data_type={import_alias}.Data,\n"
|
|
1404
|
+
mapping += f"{INDENT}{INDENT}route_group={route_group},\n"
|
|
1405
|
+
mapping += f"{INDENT}{INDENT}account_type={account_type},\n"
|
|
1406
|
+
mapping += f"{INDENT}{INDENT}route={import_alias}.ENDPOINT_PATH,\n"
|
|
1407
|
+
mapping += f'{INDENT}{INDENT}handler="{endpoint.path_per_api_endpoint[endpoint_root].function}",\n'
|
|
1408
|
+
mapping += f"{INDENT}{INDENT}method={import_alias}.ENDPOINT_METHOD,\n"
|
|
1409
|
+
mapping += f"{INDENT})"
|
|
1410
|
+
mappings.append(mapping)
|
|
1125
1411
|
|
|
1126
1412
|
argument_lookup_out = io.StringIO()
|
|
1127
1413
|
argument_lookup_out.write(MODIFY_NOTICE)
|
|
@@ -1129,8 +1415,26 @@ def _emit_api_argument_lookup(
|
|
|
1129
1415
|
argument_lookup_out.write(
|
|
1130
1416
|
f"""{LINE_BREAK.join(imports)}
|
|
1131
1417
|
|
|
1132
|
-
|
|
1133
|
-
|
|
1418
|
+
|
|
1419
|
+
@dataclasses.dataclass(kw_only=True, frozen=True)
|
|
1420
|
+
class ApiEndpointKey:
|
|
1421
|
+
method: str
|
|
1422
|
+
route: str
|
|
1423
|
+
|
|
1424
|
+
|
|
1425
|
+
@dataclasses.dataclass(kw_only=True)
|
|
1426
|
+
class ApiEndpointSpec[AT, DT]:
|
|
1427
|
+
route: str
|
|
1428
|
+
arguments_type: type[AT]
|
|
1429
|
+
data_type: type[DT]
|
|
1430
|
+
route_group: str | None
|
|
1431
|
+
account_type: str | None
|
|
1432
|
+
handler: str
|
|
1433
|
+
method: str
|
|
1434
|
+
|
|
1435
|
+
|
|
1436
|
+
{API_ARGUMENTS_NAME}: dict[ApiEndpointKey, ApiEndpointSpec] = {{
|
|
1437
|
+
{f",{LINE_BREAK}".join(mappings)},
|
|
1134
1438
|
}}
|
|
1135
1439
|
|
|
1136
1440
|
__all__ = ["{API_ARGUMENTS_NAME}"]
|
|
@@ -1146,14 +1450,13 @@ __all__ = ["{API_ARGUMENTS_NAME}"]
|
|
|
1146
1450
|
CLIENT_CLASS_FILENAME = "client_base"
|
|
1147
1451
|
CLIENT_CLASS_IMPORTS = [
|
|
1148
1452
|
"from abc import ABC, abstractmethod",
|
|
1149
|
-
"
|
|
1453
|
+
"import dataclasses",
|
|
1150
1454
|
]
|
|
1151
1455
|
ASYNC_BATCH_PROCESSOR_FILENAME = "async_batch_processor"
|
|
1152
|
-
|
|
1456
|
+
ASYNC_BATCH_PROCESSOR_BASE_IMPORTS = [
|
|
1153
1457
|
"import uuid",
|
|
1154
1458
|
"from abc import ABC, abstractmethod",
|
|
1155
|
-
"from
|
|
1156
|
-
"from pkgs.serialization_util.serialization_helpers import serialize_for_api",
|
|
1459
|
+
"from pkgs.serialization_util import serialize_for_api",
|
|
1157
1460
|
]
|
|
1158
1461
|
|
|
1159
1462
|
|
|
@@ -1165,7 +1468,9 @@ def _emit_async_batch_processor(
|
|
|
1165
1468
|
|
|
1166
1469
|
async_batch_processor_out = io.StringIO()
|
|
1167
1470
|
ctx = Context(
|
|
1168
|
-
out=io.StringIO(),
|
|
1471
|
+
out=io.StringIO(),
|
|
1472
|
+
namespace=builder.SpecNamespace("async_batch_processor"),
|
|
1473
|
+
builder=spec_builder,
|
|
1169
1474
|
)
|
|
1170
1475
|
|
|
1171
1476
|
for namespace in sorted(
|
|
@@ -1186,8 +1491,11 @@ def _emit_async_batch_processor(
|
|
|
1186
1491
|
config=config,
|
|
1187
1492
|
)
|
|
1188
1493
|
|
|
1494
|
+
imports = ASYNC_BATCH_PROCESSOR_BASE_IMPORTS.copy()
|
|
1495
|
+
if ctx.use_dataclass:
|
|
1496
|
+
imports.append("import dataclasses")
|
|
1189
1497
|
async_batch_processor_out.write(
|
|
1190
|
-
f"""{LINE_BREAK.join(
|
|
1498
|
+
f"""{LINE_BREAK.join(imports)}
|
|
1191
1499
|
|
|
1192
1500
|
|
|
1193
1501
|
class AsyncBatchProcessorBase(ABC):
|
|
@@ -1215,7 +1523,11 @@ def _emit_client_class(
|
|
|
1215
1523
|
return
|
|
1216
1524
|
|
|
1217
1525
|
client_base_out = io.StringIO()
|
|
1218
|
-
ctx = Context(
|
|
1526
|
+
ctx = Context(
|
|
1527
|
+
out=io.StringIO(),
|
|
1528
|
+
builder=spec_builder,
|
|
1529
|
+
namespace=builder.SpecNamespace("client_base"),
|
|
1530
|
+
)
|
|
1219
1531
|
for namespace in sorted(
|
|
1220
1532
|
spec_builder.namespaces.values(),
|
|
1221
1533
|
key=lambda ns: _resolve_namespace_name(ns),
|
|
@@ -1232,6 +1544,7 @@ def _emit_client_class(
|
|
|
1232
1544
|
namespaces=ctx.namespaces,
|
|
1233
1545
|
from_namespace=None,
|
|
1234
1546
|
config=config,
|
|
1547
|
+
skip_non_sdk=True,
|
|
1235
1548
|
)
|
|
1236
1549
|
|
|
1237
1550
|
client_base_out.write(
|
|
@@ -1240,11 +1553,12 @@ def _emit_client_class(
|
|
|
1240
1553
|
DT = typing.TypeVar("DT")
|
|
1241
1554
|
|
|
1242
1555
|
|
|
1243
|
-
@dataclass(kw_only=True)
|
|
1556
|
+
@dataclasses.dataclass(kw_only=True)
|
|
1244
1557
|
class APIRequest:
|
|
1245
1558
|
method: str
|
|
1246
1559
|
endpoint: str
|
|
1247
1560
|
args: typing.Any
|
|
1561
|
+
request_options: {refer_to(ctx=ctx, stype=REQUEST_OPTIONS_STYPE)} | None = None
|
|
1248
1562
|
|
|
1249
1563
|
|
|
1250
1564
|
class ClientMethods(ABC):
|