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/builder.py
CHANGED
|
@@ -10,12 +10,36 @@ import re
|
|
|
10
10
|
from collections import defaultdict
|
|
11
11
|
from dataclasses import MISSING, dataclass
|
|
12
12
|
from enum import Enum, StrEnum, auto
|
|
13
|
-
from typing import Any,
|
|
13
|
+
from typing import Any, Self
|
|
14
14
|
|
|
15
15
|
from . import util
|
|
16
|
-
from .
|
|
16
|
+
from .builder_types import CrossOutputPaths
|
|
17
|
+
from .non_discriminated_union_exceptions import NON_DISCRIMINATED_UNION_EXCEPTIONS
|
|
18
|
+
from .util import parse_type_str
|
|
17
19
|
|
|
18
20
|
RawDict = dict[Any, Any]
|
|
21
|
+
EndpointKey = str
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class PathMapping(StrEnum):
|
|
25
|
+
NO_MAPPING = "no_mapping"
|
|
26
|
+
DEFAULT_MAPPING = "default_mapping"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(kw_only=True)
|
|
30
|
+
class APIEndpointInfo:
|
|
31
|
+
root_path: str
|
|
32
|
+
path_mapping: PathMapping
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class StabilityLevel(StrEnum):
|
|
36
|
+
"""These are currently used for open api,
|
|
37
|
+
see: https://github.com/Tufin/oasdiff/blob/main/docs/STABILITY.md
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
draft = "draft"
|
|
41
|
+
beta = "beta"
|
|
42
|
+
stable = "stable"
|
|
19
43
|
|
|
20
44
|
|
|
21
45
|
class PropertyExtant(StrEnum):
|
|
@@ -36,7 +60,7 @@ class PropertyConvertValue(StrEnum):
|
|
|
36
60
|
@dataclass
|
|
37
61
|
class SpecProperty:
|
|
38
62
|
name: str
|
|
39
|
-
label:
|
|
63
|
+
label: str | None
|
|
40
64
|
spec_type: SpecType
|
|
41
65
|
extant: PropertyExtant
|
|
42
66
|
convert_value: PropertyConvertValue
|
|
@@ -50,6 +74,7 @@ class SpecProperty:
|
|
|
50
74
|
# Holds extra information that will be emitted along with type_info. The builder knows nothing
|
|
51
75
|
# about the contents of this information.
|
|
52
76
|
ext_info: Any = None
|
|
77
|
+
explicit_default: bool = False
|
|
53
78
|
|
|
54
79
|
|
|
55
80
|
class NameCase(StrEnum):
|
|
@@ -102,6 +127,8 @@ class DefnTypeName(StrEnum):
|
|
|
102
127
|
s_string_enum = "StringEnum"
|
|
103
128
|
# a particular literal value
|
|
104
129
|
s_string_literal = "_StringLiteral"
|
|
130
|
+
# A union of several other types
|
|
131
|
+
s_union = "Union"
|
|
105
132
|
|
|
106
133
|
|
|
107
134
|
base_namespace_name = "base"
|
|
@@ -195,6 +222,7 @@ class SpecEndpointExample:
|
|
|
195
222
|
|
|
196
223
|
@dataclass(kw_only=True)
|
|
197
224
|
class SpecGuide:
|
|
225
|
+
ref_name: str
|
|
198
226
|
title: str
|
|
199
227
|
markdown_content: str
|
|
200
228
|
html_content: str
|
|
@@ -241,7 +269,7 @@ class SpecTypeLiteralWrapper(SpecType):
|
|
|
241
269
|
return [self.value_type]
|
|
242
270
|
|
|
243
271
|
|
|
244
|
-
def unwrap_literal_type(stype: SpecType) ->
|
|
272
|
+
def unwrap_literal_type(stype: SpecType) -> SpecTypeLiteralWrapper | None:
|
|
245
273
|
if isinstance(stype, SpecTypeInstance) and stype.defn_type.is_base_type(
|
|
246
274
|
BaseTypeName.s_literal
|
|
247
275
|
):
|
|
@@ -269,7 +297,7 @@ class SpecTypeDefn(SpecType):
|
|
|
269
297
|
) -> None:
|
|
270
298
|
self.namespace = namespace
|
|
271
299
|
self.name = name
|
|
272
|
-
self.label:
|
|
300
|
+
self.label: str | None = None
|
|
273
301
|
|
|
274
302
|
self.is_predefined = is_predefined
|
|
275
303
|
self.name_case = NameCase.convert
|
|
@@ -279,6 +307,8 @@ class SpecTypeDefn(SpecType):
|
|
|
279
307
|
self._is_value_converted = _is_value_converted
|
|
280
308
|
self._is_value_to_string = False
|
|
281
309
|
self._is_valid_parameter = True
|
|
310
|
+
self._is_dynamic_allowed = False
|
|
311
|
+
self._default_extant: PropertyExtant | None = None
|
|
282
312
|
self.ext_info: Any = None
|
|
283
313
|
|
|
284
314
|
def is_value_converted(self) -> bool:
|
|
@@ -290,20 +320,43 @@ class SpecTypeDefn(SpecType):
|
|
|
290
320
|
def is_valid_parameter(self) -> bool:
|
|
291
321
|
return self._is_valid_parameter
|
|
292
322
|
|
|
323
|
+
def is_dynamic_allowed(self) -> bool:
|
|
324
|
+
return self._is_dynamic_allowed
|
|
325
|
+
|
|
293
326
|
def is_base_type(self, type_: BaseTypeName) -> bool:
|
|
294
327
|
return self.is_base and self.name == type_
|
|
295
328
|
|
|
329
|
+
def can_process(self, builder: SpecBuilder, data: RawDict) -> bool:
|
|
330
|
+
return True
|
|
331
|
+
|
|
296
332
|
@abc.abstractmethod
|
|
297
333
|
def process(self, builder: SpecBuilder, data: RawDict) -> None: ...
|
|
298
334
|
|
|
299
335
|
def base_process(
|
|
300
336
|
self, builder: SpecBuilder, data: RawDict, extra_names: list[str]
|
|
301
337
|
) -> None:
|
|
302
|
-
util.check_fields(
|
|
338
|
+
util.check_fields(
|
|
339
|
+
data,
|
|
340
|
+
[
|
|
341
|
+
"ext_info",
|
|
342
|
+
"label",
|
|
343
|
+
"is_dynamic_allowed",
|
|
344
|
+
"default_extant",
|
|
345
|
+
]
|
|
346
|
+
+ extra_names,
|
|
347
|
+
)
|
|
303
348
|
|
|
304
349
|
self.ext_info = data.get("ext_info")
|
|
305
350
|
self.label = data.get("label")
|
|
306
351
|
|
|
352
|
+
is_dynamic_allowed = data.get("is_dynamic_allowed", False)
|
|
353
|
+
assert isinstance(is_dynamic_allowed, bool)
|
|
354
|
+
self._is_dynamic_allowed = is_dynamic_allowed
|
|
355
|
+
|
|
356
|
+
default_extant = data.get("default_extant")
|
|
357
|
+
if default_extant is not None:
|
|
358
|
+
self._default_extant = PropertyExtant(default_extant)
|
|
359
|
+
|
|
307
360
|
def _process_property(
|
|
308
361
|
self, builder: SpecBuilder, spec_name: str, data: RawDict
|
|
309
362
|
) -> SpecProperty:
|
|
@@ -322,18 +375,18 @@ class SpecTypeDefn(SpecType):
|
|
|
322
375
|
],
|
|
323
376
|
)
|
|
324
377
|
try:
|
|
325
|
-
|
|
378
|
+
extant_type_str = data.get("extant")
|
|
379
|
+
extant_type = (
|
|
380
|
+
PropertyExtant(extant_type_str) if extant_type_str is not None else None
|
|
381
|
+
)
|
|
382
|
+
extant = extant_type or self._default_extant
|
|
326
383
|
if spec_name.endswith("?"):
|
|
327
|
-
if
|
|
384
|
+
if extant is not None:
|
|
328
385
|
raise Exception("cannot specify extant with ?")
|
|
329
386
|
extant = PropertyExtant.optional
|
|
330
387
|
name = spec_name[:-1]
|
|
331
388
|
else:
|
|
332
|
-
extant =
|
|
333
|
-
PropertyExtant.required
|
|
334
|
-
if extant_type is None
|
|
335
|
-
else PropertyExtant(extant_type)
|
|
336
|
-
)
|
|
389
|
+
extant = extant or PropertyExtant.required
|
|
337
390
|
name = spec_name
|
|
338
391
|
|
|
339
392
|
property_name_case = self.name_case
|
|
@@ -342,9 +395,9 @@ class SpecTypeDefn(SpecType):
|
|
|
342
395
|
property_name_case = NameCase(name_case_raw)
|
|
343
396
|
|
|
344
397
|
if property_name_case != NameCase.preserve:
|
|
345
|
-
assert util.is_valid_property_name(
|
|
346
|
-
name
|
|
347
|
-
)
|
|
398
|
+
assert util.is_valid_property_name(name), (
|
|
399
|
+
f"{name} is not a valid property name"
|
|
400
|
+
)
|
|
348
401
|
|
|
349
402
|
data_type = data.get("type")
|
|
350
403
|
builder.ensure(data_type is not None, "missing `type` entry")
|
|
@@ -355,6 +408,7 @@ class SpecTypeDefn(SpecType):
|
|
|
355
408
|
ptype = builder.parse_type(self.namespace, data_type, scope=self)
|
|
356
409
|
|
|
357
410
|
default_spec = data.get("default", MISSING)
|
|
411
|
+
explicit_default = default_spec != MISSING
|
|
358
412
|
if default_spec == MISSING:
|
|
359
413
|
has_default = False
|
|
360
414
|
default = None
|
|
@@ -362,7 +416,10 @@ class SpecTypeDefn(SpecType):
|
|
|
362
416
|
has_default = True
|
|
363
417
|
# IMPROVE: check the type against the ptype
|
|
364
418
|
default = default_spec
|
|
365
|
-
|
|
419
|
+
if extant == PropertyExtant.missing and explicit_default:
|
|
420
|
+
raise Exception(
|
|
421
|
+
f"cannot have extant missing and default for property {name}"
|
|
422
|
+
)
|
|
366
423
|
parse_require = False
|
|
367
424
|
literal = unwrap_literal_type(ptype)
|
|
368
425
|
if literal is not None:
|
|
@@ -385,6 +442,7 @@ class SpecTypeDefn(SpecType):
|
|
|
385
442
|
parse_require=parse_require,
|
|
386
443
|
desc=data.get("desc", None),
|
|
387
444
|
ext_info=ext_info,
|
|
445
|
+
explicit_default=explicit_default,
|
|
388
446
|
)
|
|
389
447
|
finally:
|
|
390
448
|
builder.pop_where()
|
|
@@ -422,7 +480,7 @@ class SpecTypeGenericParameter(SpecType):
|
|
|
422
480
|
|
|
423
481
|
|
|
424
482
|
class SpecTypeDefnObject(SpecTypeDefn):
|
|
425
|
-
base:
|
|
483
|
+
base: SpecTypeDefnObject | None
|
|
426
484
|
parameters: list[str]
|
|
427
485
|
|
|
428
486
|
def __init__(
|
|
@@ -430,7 +488,7 @@ class SpecTypeDefnObject(SpecTypeDefn):
|
|
|
430
488
|
namespace: SpecNamespace,
|
|
431
489
|
name: str,
|
|
432
490
|
*,
|
|
433
|
-
parameters:
|
|
491
|
+
parameters: list[str] | None = None,
|
|
434
492
|
is_base: bool = False,
|
|
435
493
|
is_predefined: bool = False,
|
|
436
494
|
is_hashable: bool = False,
|
|
@@ -447,7 +505,7 @@ class SpecTypeDefnObject(SpecTypeDefn):
|
|
|
447
505
|
self.parameters = parameters if parameters is not None else []
|
|
448
506
|
self.is_hashable = is_hashable
|
|
449
507
|
self.base = None
|
|
450
|
-
self.properties:
|
|
508
|
+
self.properties: dict[str, SpecProperty] | None = None
|
|
451
509
|
self._kw_only: bool = True
|
|
452
510
|
self.desc: str | None = None
|
|
453
511
|
|
|
@@ -518,13 +576,8 @@ class SpecTypeDefnObject(SpecTypeDefn):
|
|
|
518
576
|
base_type: list[SpecType] = [self.base] if self.base is not None else []
|
|
519
577
|
return base_type + prop_types
|
|
520
578
|
|
|
521
|
-
def
|
|
522
|
-
|
|
523
|
-
assert (
|
|
524
|
-
len(self.parameters) == 1
|
|
525
|
-
), "Only single generic parameters current supported"
|
|
526
|
-
return self.parameters[0]
|
|
527
|
-
return None
|
|
579
|
+
def get_generics(self) -> list[str]:
|
|
580
|
+
return self.parameters
|
|
528
581
|
|
|
529
582
|
|
|
530
583
|
class SpecTypeDefnAlias(SpecTypeDefn):
|
|
@@ -546,13 +599,75 @@ class SpecTypeDefnAlias(SpecTypeDefn):
|
|
|
546
599
|
super().base_process(builder, data, ["type", "desc", "alias", "discriminator"])
|
|
547
600
|
self.alias = builder.parse_type(self.namespace, data["alias"])
|
|
548
601
|
self.desc = data.get("desc", None)
|
|
549
|
-
# Should be limited to Union type aliases
|
|
550
602
|
self.discriminator = data.get("discriminator", None)
|
|
551
603
|
|
|
552
604
|
def get_referenced_types(self) -> list[SpecType]:
|
|
553
605
|
return [self.alias]
|
|
554
606
|
|
|
555
607
|
|
|
608
|
+
class SpecTypeDefnUnion(SpecTypeDefn):
|
|
609
|
+
def __init__(self, namespace: SpecNamespace, name: str) -> None:
|
|
610
|
+
super().__init__(namespace, name)
|
|
611
|
+
self.discriminator: str | None = None
|
|
612
|
+
self.types: list[SpecType] = []
|
|
613
|
+
self._alias_type: SpecType | None = None
|
|
614
|
+
self.discriminator_map: dict[str, SpecType] | None = None
|
|
615
|
+
self.desc: str | None = None
|
|
616
|
+
|
|
617
|
+
def process(self, builder: SpecBuilder, data: RawDict) -> None:
|
|
618
|
+
super().base_process(builder, data, ["type", "desc", "types", "discriminator"])
|
|
619
|
+
|
|
620
|
+
self.desc = data.get("desc", None)
|
|
621
|
+
self.discriminator = data.get("discriminator", None)
|
|
622
|
+
|
|
623
|
+
for sub_type_str in data["types"]:
|
|
624
|
+
sub_type = builder.parse_type(self.namespace, sub_type_str)
|
|
625
|
+
self.types.append(sub_type)
|
|
626
|
+
|
|
627
|
+
base_type = builder.namespaces[base_namespace_name].types[BaseTypeName.s_union]
|
|
628
|
+
self._backing_type = SpecTypeInstance(base_type, self.types)
|
|
629
|
+
|
|
630
|
+
if self.discriminator is not None:
|
|
631
|
+
self.discriminator_map = {}
|
|
632
|
+
for sub_type in self.types:
|
|
633
|
+
builder.push_where(sub_type.name)
|
|
634
|
+
assert isinstance(sub_type, SpecTypeDefnObject), (
|
|
635
|
+
"union-type-must-be-object"
|
|
636
|
+
)
|
|
637
|
+
assert sub_type.properties is not None
|
|
638
|
+
discriminator_type = sub_type.properties.get(self.discriminator)
|
|
639
|
+
assert discriminator_type is not None, (
|
|
640
|
+
f"missing-discriminator-field: {sub_type}"
|
|
641
|
+
)
|
|
642
|
+
prop_type = unwrap_literal_type(discriminator_type.spec_type)
|
|
643
|
+
assert prop_type is not None
|
|
644
|
+
assert prop_type.is_value_to_string()
|
|
645
|
+
value_type = prop_type.value_type
|
|
646
|
+
if isinstance(value_type, SpecTypeDefnStringEnum):
|
|
647
|
+
assert isinstance(prop_type.value, str)
|
|
648
|
+
discriminant = value_type.values[prop_type.value].value
|
|
649
|
+
else:
|
|
650
|
+
discriminant = str(prop_type.value)
|
|
651
|
+
assert discriminant not in self.discriminator_map, (
|
|
652
|
+
f"duplicated-discriminant, {discriminant} in {sub_type}"
|
|
653
|
+
)
|
|
654
|
+
self.discriminator_map[discriminant] = sub_type
|
|
655
|
+
|
|
656
|
+
builder.pop_where()
|
|
657
|
+
elif (
|
|
658
|
+
f"{self.namespace.name}.{self.name}"
|
|
659
|
+
not in NON_DISCRIMINATED_UNION_EXCEPTIONS
|
|
660
|
+
):
|
|
661
|
+
raise Exception(f"union requires a discriminator: {self.name}")
|
|
662
|
+
|
|
663
|
+
def get_referenced_types(self) -> list[SpecType]:
|
|
664
|
+
return self.types
|
|
665
|
+
|
|
666
|
+
def get_backing_type(self) -> SpecType:
|
|
667
|
+
assert self._backing_type is not None
|
|
668
|
+
return self._backing_type
|
|
669
|
+
|
|
670
|
+
|
|
556
671
|
class SpecTypeDefnExternal(SpecTypeDefn):
|
|
557
672
|
external_map: dict[str, str]
|
|
558
673
|
|
|
@@ -582,7 +697,7 @@ class SpecTypeDefnExternal(SpecTypeDefn):
|
|
|
582
697
|
class StringEnumEntry:
|
|
583
698
|
name: str
|
|
584
699
|
value: str
|
|
585
|
-
label:
|
|
700
|
+
label: str | None = None
|
|
586
701
|
deprecated: bool = False
|
|
587
702
|
|
|
588
703
|
|
|
@@ -598,17 +713,32 @@ class SpecTypeDefnStringEnum(SpecTypeDefn):
|
|
|
598
713
|
)
|
|
599
714
|
self.values: dict[str, StringEnumEntry] = {}
|
|
600
715
|
self.desc: str | None = None
|
|
601
|
-
self.sql_type_name:
|
|
716
|
+
self.sql_type_name: str | None = None
|
|
602
717
|
self.emit_id_source = False
|
|
718
|
+
self.source_enums: list[SpecType] = []
|
|
719
|
+
|
|
720
|
+
def can_process(self, builder: SpecBuilder, data: dict[Any, Any]) -> bool:
|
|
721
|
+
source_enums = data.get("source_enums")
|
|
722
|
+
try:
|
|
723
|
+
for sub_type_str in source_enums or []:
|
|
724
|
+
sub_type = builder.parse_type(self.namespace, sub_type_str)
|
|
725
|
+
assert isinstance(sub_type, SpecTypeDefnStringEnum)
|
|
726
|
+
assert len(sub_type.values) > 0
|
|
727
|
+
except AssertionError:
|
|
728
|
+
return False
|
|
729
|
+
return super().can_process(builder, data)
|
|
603
730
|
|
|
604
731
|
def process(self, builder: SpecBuilder, data: RawDict) -> None:
|
|
605
732
|
super().base_process(
|
|
606
|
-
builder,
|
|
733
|
+
builder,
|
|
734
|
+
data,
|
|
735
|
+
["type", "desc", "values", "name_case", "sql", "emit", "source_enums"],
|
|
607
736
|
)
|
|
608
737
|
self.name_case = NameCase(data.get("name_case", "convert"))
|
|
609
738
|
self.values = {}
|
|
610
|
-
data_values = data
|
|
739
|
+
data_values = data.get("values")
|
|
611
740
|
self.desc = data.get("desc", None)
|
|
741
|
+
source_enums = data.get("source_enums", None)
|
|
612
742
|
if isinstance(data_values, dict):
|
|
613
743
|
for name, value in data_values.items():
|
|
614
744
|
builder.push_where(name)
|
|
@@ -621,6 +751,8 @@ class SpecTypeDefnStringEnum(SpecTypeDefn):
|
|
|
621
751
|
builder.ensure(
|
|
622
752
|
isinstance(enum_value, str), "enum value should be string"
|
|
623
753
|
)
|
|
754
|
+
assert isinstance(enum_value, str)
|
|
755
|
+
|
|
624
756
|
deprecated = value.get("deprecated", False)
|
|
625
757
|
builder.ensure(
|
|
626
758
|
isinstance(deprecated, bool),
|
|
@@ -646,10 +778,13 @@ class SpecTypeDefnStringEnum(SpecTypeDefn):
|
|
|
646
778
|
elif isinstance(data_values, list):
|
|
647
779
|
for value in data_values:
|
|
648
780
|
if value in self.values:
|
|
649
|
-
raise Exception(
|
|
781
|
+
raise Exception(
|
|
782
|
+
"duplicate value in typespec enum", self.name, value
|
|
783
|
+
)
|
|
650
784
|
self.values[value] = StringEnumEntry(name=value, value=value)
|
|
651
785
|
else:
|
|
652
|
-
|
|
786
|
+
if source_enums is None or data_values is not None:
|
|
787
|
+
raise Exception("unsupported values type")
|
|
653
788
|
|
|
654
789
|
sql_data = data.get("sql")
|
|
655
790
|
if sql_data is not None:
|
|
@@ -671,14 +806,24 @@ class SpecTypeDefnStringEnum(SpecTypeDefn):
|
|
|
671
806
|
builder.ensure(
|
|
672
807
|
entry.label is not None, f"need-label-for-id-source:{entry.name}"
|
|
673
808
|
)
|
|
809
|
+
for sub_type_str in source_enums or []:
|
|
810
|
+
sub_type = builder.parse_type(self.namespace, sub_type_str)
|
|
811
|
+
self.source_enums.append(sub_type)
|
|
812
|
+
|
|
813
|
+
for sub_type in self.source_enums:
|
|
814
|
+
builder.push_where(sub_type.name)
|
|
815
|
+
if isinstance(sub_type, SpecTypeDefnStringEnum):
|
|
816
|
+
self.values.update(sub_type.values)
|
|
817
|
+
builder.pop_where()
|
|
674
818
|
|
|
675
819
|
def get_referenced_types(self) -> list[SpecType]:
|
|
676
|
-
return
|
|
820
|
+
return self.source_enums
|
|
677
821
|
|
|
678
822
|
|
|
679
823
|
TOKEN_ENDPOINT = "$endpoint"
|
|
680
824
|
TOKEN_EMIT_IO_TS = "$emit_io_ts"
|
|
681
825
|
TOKEN_EMIT_TYPE_INFO = "$emit_type_info"
|
|
826
|
+
TOKEN_EMIT_TYPE_INFO_PYTHON = "$emit_type_info_python"
|
|
682
827
|
# The import token is only for explicit ordering of the files, to process constants
|
|
683
828
|
# and enums correctly. It does not impact the final generation of files, or the
|
|
684
829
|
# language imports. Those are still auto-resolved.
|
|
@@ -703,13 +848,13 @@ RE_ENDPOINT_ROOT = re.compile(r"\${([_a-z]+)}")
|
|
|
703
848
|
|
|
704
849
|
@dataclass(kw_only=True, frozen=True)
|
|
705
850
|
class _EndpointPathDetails:
|
|
706
|
-
root:
|
|
851
|
+
root: EndpointKey
|
|
707
852
|
root_path: str
|
|
708
853
|
resolved_path: str
|
|
709
854
|
|
|
710
855
|
|
|
711
856
|
def _resolve_endpoint_path(
|
|
712
|
-
path: str, api_endpoints: dict[
|
|
857
|
+
path: str, api_endpoints: dict[EndpointKey, APIEndpointInfo]
|
|
713
858
|
) -> _EndpointPathDetails:
|
|
714
859
|
root_path_source = path.split("/")[0]
|
|
715
860
|
root_match = RE_ENDPOINT_ROOT.fullmatch(root_path_source)
|
|
@@ -717,7 +862,7 @@ def _resolve_endpoint_path(
|
|
|
717
862
|
raise Exception(f"invalid-api-path-root:{root_path_source}")
|
|
718
863
|
|
|
719
864
|
root_var = root_match.group(1)
|
|
720
|
-
root_path = api_endpoints[root_var]
|
|
865
|
+
root_path = api_endpoints[root_var].root_path
|
|
721
866
|
|
|
722
867
|
_, *rest_path = path.split("/", 1)
|
|
723
868
|
resolved_path = "/".join([root_path] + rest_path)
|
|
@@ -727,22 +872,75 @@ def _resolve_endpoint_path(
|
|
|
727
872
|
)
|
|
728
873
|
|
|
729
874
|
|
|
730
|
-
class
|
|
731
|
-
|
|
732
|
-
|
|
875
|
+
class EndpointEmitType(StrEnum):
|
|
876
|
+
EMIT_ENDPOINT = "emit_endpoint"
|
|
877
|
+
EMIT_TYPES = "emit_types"
|
|
878
|
+
EMIT_NOTHING = "emit_nothing"
|
|
879
|
+
|
|
880
|
+
|
|
881
|
+
@dataclass(kw_only=True, frozen=True)
|
|
882
|
+
class EndpointSpecificPath:
|
|
883
|
+
root: EndpointKey
|
|
733
884
|
path_root: str
|
|
734
885
|
path_dirname: str
|
|
735
886
|
path_basename: str
|
|
887
|
+
function: str | None
|
|
888
|
+
|
|
889
|
+
|
|
890
|
+
def parse_endpoint_specific_path(
|
|
891
|
+
builder: SpecBuilder,
|
|
892
|
+
data_per_endpoint: RawDict | None,
|
|
893
|
+
) -> EndpointSpecificPath | None:
|
|
894
|
+
if data_per_endpoint is None:
|
|
895
|
+
return None
|
|
896
|
+
util.check_fields(
|
|
897
|
+
data_per_endpoint,
|
|
898
|
+
[
|
|
899
|
+
"path",
|
|
900
|
+
"function",
|
|
901
|
+
],
|
|
902
|
+
)
|
|
903
|
+
|
|
904
|
+
if "path" not in data_per_endpoint or data_per_endpoint["path"] is None:
|
|
905
|
+
return None
|
|
906
|
+
|
|
907
|
+
path = data_per_endpoint["path"].split("/")
|
|
908
|
+
|
|
909
|
+
assert len(path) > 1, "invalid-endpoint-path"
|
|
910
|
+
|
|
911
|
+
path_details = _resolve_endpoint_path(
|
|
912
|
+
data_per_endpoint["path"], builder.api_endpoints
|
|
913
|
+
)
|
|
914
|
+
|
|
915
|
+
result = EndpointSpecificPath(
|
|
916
|
+
function=data_per_endpoint.get("function"),
|
|
917
|
+
path_dirname="/".join(path[1:-1]),
|
|
918
|
+
path_basename=path[-1],
|
|
919
|
+
root=path_details.root,
|
|
920
|
+
path_root=path_details.root_path,
|
|
921
|
+
)
|
|
922
|
+
|
|
923
|
+
return result
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
class SpecEndpoint:
|
|
927
|
+
method: RouteMethod
|
|
736
928
|
data_loader: bool
|
|
737
|
-
is_sdk:
|
|
738
|
-
|
|
929
|
+
is_sdk: EndpointEmitType
|
|
930
|
+
stability_level: StabilityLevel | None
|
|
739
931
|
# Don't emit TypeScript endpoint code
|
|
740
932
|
suppress_ts: bool
|
|
741
|
-
|
|
933
|
+
deprecated: bool = False
|
|
742
934
|
async_batch_path: str | None = None
|
|
743
935
|
result_type: ResultType = ResultType.json
|
|
744
936
|
has_attachment: bool = False
|
|
745
937
|
desc: str | None = None
|
|
938
|
+
account_type: str | None
|
|
939
|
+
route_group: str | None
|
|
940
|
+
|
|
941
|
+
# function, path details per api endpoint
|
|
942
|
+
path_per_api_endpoint: dict[str, EndpointSpecificPath]
|
|
943
|
+
default_endpoint_key: EndpointKey
|
|
746
944
|
|
|
747
945
|
is_external: bool = False
|
|
748
946
|
|
|
@@ -750,15 +948,15 @@ class SpecEndpoint:
|
|
|
750
948
|
pass
|
|
751
949
|
|
|
752
950
|
def process(self, builder: SpecBuilder, data: RawDict) -> None:
|
|
753
|
-
unused(builder)
|
|
754
951
|
util.check_fields(
|
|
755
952
|
data,
|
|
756
953
|
[
|
|
757
954
|
"method",
|
|
758
955
|
"path",
|
|
759
956
|
"data_loader",
|
|
957
|
+
"deprecated",
|
|
760
958
|
"is_sdk",
|
|
761
|
-
"
|
|
959
|
+
"stability_level",
|
|
762
960
|
"async_batch_path",
|
|
763
961
|
"function",
|
|
764
962
|
"suppress_ts",
|
|
@@ -766,58 +964,121 @@ class SpecEndpoint:
|
|
|
766
964
|
"deprecated",
|
|
767
965
|
"result_type",
|
|
768
966
|
"has_attachment",
|
|
769
|
-
|
|
967
|
+
"account_type",
|
|
968
|
+
"route_group",
|
|
969
|
+
]
|
|
970
|
+
+ list(builder.api_endpoints.keys()),
|
|
770
971
|
)
|
|
771
972
|
self.method = RouteMethod(data["method"])
|
|
772
973
|
|
|
773
|
-
path = data["path"].split("/")
|
|
774
|
-
|
|
775
|
-
assert len(path) > 1, "invalid-endpoint-path"
|
|
776
|
-
|
|
777
|
-
# handle ${external} in the same way we handle ${materials} for now
|
|
778
|
-
self.path_dirname = "/".join(path[1:-1])
|
|
779
|
-
self.path_basename = path[-1]
|
|
780
|
-
|
|
781
974
|
data_loader = data.get("data_loader", False)
|
|
782
975
|
assert isinstance(data_loader, bool)
|
|
783
976
|
self.data_loader = data_loader
|
|
977
|
+
self.deprecated = data.get("deprecated", False)
|
|
978
|
+
|
|
979
|
+
is_sdk = data.get("is_sdk", EndpointEmitType.EMIT_NOTHING)
|
|
980
|
+
|
|
981
|
+
# backwards compatibility
|
|
982
|
+
if isinstance(is_sdk, bool):
|
|
983
|
+
if is_sdk is True:
|
|
984
|
+
is_sdk = EndpointEmitType.EMIT_ENDPOINT
|
|
985
|
+
else:
|
|
986
|
+
is_sdk = EndpointEmitType.EMIT_NOTHING
|
|
987
|
+
elif isinstance(is_sdk, str):
|
|
988
|
+
try:
|
|
989
|
+
is_sdk = EndpointEmitType(is_sdk)
|
|
990
|
+
except ValueError as e:
|
|
991
|
+
raise ValueError(f"Invalid value for is_sdk: {is_sdk}") from e
|
|
992
|
+
|
|
993
|
+
assert isinstance(is_sdk, EndpointEmitType)
|
|
784
994
|
|
|
785
|
-
is_sdk = data.get("is_sdk", False)
|
|
786
|
-
assert isinstance(is_sdk, bool)
|
|
787
995
|
self.is_sdk = is_sdk
|
|
788
996
|
|
|
789
|
-
|
|
790
|
-
assert isinstance(
|
|
791
|
-
self.
|
|
997
|
+
route_group = data.get("route_group")
|
|
998
|
+
assert route_group is None or isinstance(route_group, str)
|
|
999
|
+
self.route_group = route_group
|
|
1000
|
+
|
|
1001
|
+
account_type = data.get("account_type")
|
|
1002
|
+
assert account_type is None or isinstance(account_type, str)
|
|
1003
|
+
self.account_type = account_type
|
|
1004
|
+
|
|
1005
|
+
stability_level_raw = data.get("stability_level")
|
|
1006
|
+
assert stability_level_raw is None or isinstance(stability_level_raw, str)
|
|
1007
|
+
self.stability_level = (
|
|
1008
|
+
StabilityLevel(stability_level_raw)
|
|
1009
|
+
if stability_level_raw is not None
|
|
1010
|
+
else None
|
|
1011
|
+
)
|
|
792
1012
|
|
|
793
1013
|
async_batch_path = data.get("async_batch_path")
|
|
794
1014
|
if async_batch_path is not None:
|
|
795
1015
|
assert isinstance(async_batch_path, str)
|
|
796
1016
|
self.async_batch_path = async_batch_path
|
|
797
1017
|
|
|
798
|
-
self.function = data.get("function")
|
|
799
|
-
|
|
800
1018
|
suppress_ts = data.get("suppress_ts", False)
|
|
801
1019
|
assert isinstance(suppress_ts, bool)
|
|
802
1020
|
self.suppress_ts = suppress_ts
|
|
803
1021
|
|
|
804
1022
|
self.result_type = ResultType(data.get("result_type", ResultType.json.value))
|
|
805
|
-
|
|
806
|
-
path_details = _resolve_endpoint_path(data["path"], builder.api_endpoints)
|
|
807
|
-
self.root = path_details.root
|
|
808
|
-
self.path_root = path_details.root_path
|
|
1023
|
+
self.has_attachment = data.get("has_attachment", False)
|
|
809
1024
|
self.desc = data.get("desc")
|
|
1025
|
+
|
|
1026
|
+
# compatibility with single-endpoint files
|
|
1027
|
+
default_endpoint_path = parse_endpoint_specific_path(
|
|
1028
|
+
builder,
|
|
1029
|
+
{"path": data.get("path"), "function": data.get("function")},
|
|
1030
|
+
)
|
|
1031
|
+
if default_endpoint_path is not None:
|
|
1032
|
+
assert default_endpoint_path.root in builder.api_endpoints, (
|
|
1033
|
+
"Default endpoint is not a valid API endpoint"
|
|
1034
|
+
)
|
|
1035
|
+
self.default_endpoint_key = default_endpoint_path.root
|
|
1036
|
+
self.path_per_api_endpoint = {
|
|
1037
|
+
self.default_endpoint_key: default_endpoint_path,
|
|
1038
|
+
}
|
|
1039
|
+
else:
|
|
1040
|
+
self.path_per_api_endpoint = {}
|
|
1041
|
+
shared_function_name = None
|
|
1042
|
+
for endpoint_key in builder.api_endpoints:
|
|
1043
|
+
endpoint_specific_path = parse_endpoint_specific_path(
|
|
1044
|
+
builder,
|
|
1045
|
+
data.get(endpoint_key),
|
|
1046
|
+
)
|
|
1047
|
+
if endpoint_specific_path is not None:
|
|
1048
|
+
self.path_per_api_endpoint[endpoint_key] = endpoint_specific_path
|
|
1049
|
+
if endpoint_specific_path.function is not None:
|
|
1050
|
+
fn_name = endpoint_specific_path.function.split(".")[-1]
|
|
1051
|
+
if shared_function_name is None:
|
|
1052
|
+
shared_function_name = fn_name
|
|
1053
|
+
assert shared_function_name == fn_name
|
|
1054
|
+
|
|
1055
|
+
if builder.top_namespace in self.path_per_api_endpoint:
|
|
1056
|
+
self.default_endpoint_key = builder.top_namespace
|
|
1057
|
+
elif len(self.path_per_api_endpoint) == 1:
|
|
1058
|
+
self.default_endpoint_key = next(
|
|
1059
|
+
iter(self.path_per_api_endpoint.keys())
|
|
1060
|
+
)
|
|
1061
|
+
else:
|
|
1062
|
+
raise RuntimeError("no clear default endpoint")
|
|
1063
|
+
|
|
1064
|
+
assert len(self.path_per_api_endpoint) > 0, (
|
|
1065
|
+
"Missing API endpoint path and function definitions for API call"
|
|
1066
|
+
)
|
|
1067
|
+
|
|
810
1068
|
# IMPROVE: remove need for is_external flag
|
|
811
|
-
self.is_external =
|
|
812
|
-
|
|
1069
|
+
self.is_external = (
|
|
1070
|
+
self.path_per_api_endpoint[self.default_endpoint_key].path_root
|
|
1071
|
+
== "api/external"
|
|
1072
|
+
)
|
|
813
1073
|
|
|
814
|
-
assert (
|
|
815
|
-
|
|
816
|
-
)
|
|
1074
|
+
assert self.is_sdk != EndpointEmitType.EMIT_ENDPOINT or self.desc is not None, (
|
|
1075
|
+
f"Endpoint description required for SDK endpoints, missing: {self.resolved_path}"
|
|
1076
|
+
)
|
|
817
1077
|
|
|
818
1078
|
@property
|
|
819
1079
|
def resolved_path(self: Self) -> str:
|
|
820
|
-
|
|
1080
|
+
default_endpoint_path = self.path_per_api_endpoint[self.default_endpoint_key]
|
|
1081
|
+
return f"{default_endpoint_path.path_root}/{default_endpoint_path.path_dirname}/{default_endpoint_path.path_basename}"
|
|
821
1082
|
|
|
822
1083
|
|
|
823
1084
|
def _parse_const(
|
|
@@ -840,7 +1101,7 @@ def _parse_const(
|
|
|
840
1101
|
elif const_type.defn_type.name == BaseTypeName.s_dict:
|
|
841
1102
|
assert isinstance(value, dict)
|
|
842
1103
|
builder.ensure(
|
|
843
|
-
len(const_type.parameters) == 2, "constant-dict-expects-
|
|
1104
|
+
len(const_type.parameters) == 2, "constant-dict-expects-two-types"
|
|
844
1105
|
)
|
|
845
1106
|
key_type = const_type.parameters[0]
|
|
846
1107
|
value_type = const_type.parameters[1]
|
|
@@ -884,7 +1145,14 @@ def _parse_const(
|
|
|
884
1145
|
return value
|
|
885
1146
|
|
|
886
1147
|
if const_type.name == BaseTypeName.s_boolean:
|
|
887
|
-
builder.ensure(
|
|
1148
|
+
builder.ensure(
|
|
1149
|
+
isinstance(value, bool), "invalid value for boolean constant"
|
|
1150
|
+
)
|
|
1151
|
+
return value
|
|
1152
|
+
|
|
1153
|
+
if not const_type.is_base:
|
|
1154
|
+
# IMPROVE: validate the object type properties before emission stage
|
|
1155
|
+
builder.ensure(isinstance(value, dict), "invalid value for object constant")
|
|
888
1156
|
return value
|
|
889
1157
|
|
|
890
1158
|
raise Exception("unsupported-const-scalar-type", const_type)
|
|
@@ -916,7 +1184,9 @@ class SpecConstant:
|
|
|
916
1184
|
assert isinstance(self.value, dict)
|
|
917
1185
|
# the parsing checks that the values are correct, so a simple length check
|
|
918
1186
|
# should be enough to check completeness
|
|
919
|
-
builder.ensure(
|
|
1187
|
+
builder.ensure(
|
|
1188
|
+
len(key_type.values) == len(self.value), "incomplete-enum-map"
|
|
1189
|
+
)
|
|
920
1190
|
|
|
921
1191
|
|
|
922
1192
|
class SpecNamespace:
|
|
@@ -926,14 +1196,15 @@ class SpecNamespace:
|
|
|
926
1196
|
):
|
|
927
1197
|
self.types: dict[str, SpecTypeDefn] = {}
|
|
928
1198
|
self.constants: dict[str, SpecConstant] = {}
|
|
929
|
-
self.endpoint:
|
|
1199
|
+
self.endpoint: SpecEndpoint | None = None
|
|
930
1200
|
self.emit_io_ts = False
|
|
931
1201
|
self.emit_type_info = False
|
|
1202
|
+
self.emit_type_info_python = False
|
|
932
1203
|
self.derive_types_from_io_ts = False
|
|
933
|
-
self._imports:
|
|
1204
|
+
self._imports: list[str] | None = None
|
|
934
1205
|
self.path = name.split(".")
|
|
935
1206
|
self.name = self.path[-1]
|
|
936
|
-
self._order:
|
|
1207
|
+
self._order: int | None = None
|
|
937
1208
|
|
|
938
1209
|
def _update_order(self, builder: SpecBuilder, recurse: int = 0) -> int:
|
|
939
1210
|
if self._order is not None:
|
|
@@ -983,6 +1254,11 @@ class SpecNamespace:
|
|
|
983
1254
|
self.emit_type_info = defn
|
|
984
1255
|
continue
|
|
985
1256
|
|
|
1257
|
+
if name == TOKEN_EMIT_TYPE_INFO_PYTHON:
|
|
1258
|
+
assert defn in (True, False)
|
|
1259
|
+
self.emit_type_info_python = defn
|
|
1260
|
+
continue
|
|
1261
|
+
|
|
986
1262
|
if name == TOKEN_IMPORT:
|
|
987
1263
|
assert self._imports is None
|
|
988
1264
|
imports = [defn] if isinstance(defn, str) else defn
|
|
@@ -991,26 +1267,33 @@ class SpecNamespace:
|
|
|
991
1267
|
continue
|
|
992
1268
|
|
|
993
1269
|
if "value" in defn:
|
|
994
|
-
assert util.is_valid_property_name(
|
|
995
|
-
name
|
|
996
|
-
)
|
|
1270
|
+
assert util.is_valid_property_name(name), (
|
|
1271
|
+
f"{name} is not a valid constant name"
|
|
1272
|
+
)
|
|
997
1273
|
spec_constant = SpecConstant(self, name)
|
|
998
1274
|
self.constants[name] = spec_constant
|
|
999
1275
|
continue
|
|
1000
1276
|
|
|
1001
1277
|
assert util.is_valid_type_name(name), f"{name} is not a valid type name"
|
|
1002
1278
|
assert name not in self.types, f"{name} is duplicate"
|
|
1003
|
-
defn_type = defn
|
|
1279
|
+
defn_type = defn.get("type")
|
|
1280
|
+
assert isinstance(defn_type, str), f"{name} requires a string type"
|
|
1004
1281
|
spec_type: SpecTypeDefn
|
|
1005
1282
|
if defn_type == DefnTypeName.s_alias:
|
|
1006
1283
|
spec_type = SpecTypeDefnAlias(self, name)
|
|
1284
|
+
elif defn_type == DefnTypeName.s_union:
|
|
1285
|
+
spec_type = SpecTypeDefnUnion(self, name)
|
|
1007
1286
|
elif defn_type == DefnTypeName.s_external:
|
|
1008
1287
|
spec_type = SpecTypeDefnExternal(self, name)
|
|
1009
1288
|
elif defn_type == DefnTypeName.s_string_enum:
|
|
1010
1289
|
spec_type = SpecTypeDefnStringEnum(self, name)
|
|
1011
1290
|
else:
|
|
1012
1291
|
parameters = (
|
|
1013
|
-
[
|
|
1292
|
+
[
|
|
1293
|
+
parameter.name
|
|
1294
|
+
for name_parameters in parsed_name.parameters
|
|
1295
|
+
for parameter in name_parameters
|
|
1296
|
+
]
|
|
1014
1297
|
if parsed_name.parameters is not None
|
|
1015
1298
|
else None
|
|
1016
1299
|
)
|
|
@@ -1026,28 +1309,46 @@ class SpecNamespace:
|
|
|
1026
1309
|
Complete the definition of each type.
|
|
1027
1310
|
"""
|
|
1028
1311
|
builder.push_where(self.name)
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1312
|
+
items_to_process: list[NameDataPair] = [
|
|
1313
|
+
NameDataPair(full_name=full_name, data=defn)
|
|
1314
|
+
for full_name, defn in data.items()
|
|
1315
|
+
]
|
|
1316
|
+
while len(items_to_process) > 0:
|
|
1317
|
+
deferred_items: list[NameDataPair] = []
|
|
1318
|
+
for item in items_to_process:
|
|
1319
|
+
full_name = item.full_name
|
|
1320
|
+
defn = item.data
|
|
1321
|
+
parsed_name = parse_type_str(full_name)[0]
|
|
1322
|
+
name = parsed_name.name
|
|
1323
|
+
|
|
1324
|
+
if name in [
|
|
1325
|
+
TOKEN_EMIT_IO_TS,
|
|
1326
|
+
TOKEN_EMIT_TYPE_INFO,
|
|
1327
|
+
TOKEN_IMPORT,
|
|
1328
|
+
TOKEN_EMIT_TYPE_INFO_PYTHON,
|
|
1329
|
+
]:
|
|
1330
|
+
continue
|
|
1032
1331
|
|
|
1033
|
-
|
|
1034
|
-
continue
|
|
1332
|
+
builder.push_where(name)
|
|
1035
1333
|
|
|
1036
|
-
|
|
1334
|
+
if "value" in defn:
|
|
1335
|
+
spec_constant = self.constants[name]
|
|
1336
|
+
spec_constant.process(builder, defn)
|
|
1037
1337
|
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1338
|
+
elif name == TOKEN_ENDPOINT:
|
|
1339
|
+
assert self.endpoint
|
|
1340
|
+
self.endpoint.process(builder, defn)
|
|
1041
1341
|
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
spec_type.process(builder, defn)
|
|
1342
|
+
else:
|
|
1343
|
+
spec_type = self.types[name]
|
|
1344
|
+
if spec_type.can_process(builder, defn):
|
|
1345
|
+
spec_type.process(builder, defn)
|
|
1346
|
+
else:
|
|
1347
|
+
deferred_items.append(item)
|
|
1049
1348
|
|
|
1050
|
-
|
|
1349
|
+
builder.pop_where()
|
|
1350
|
+
assert len(deferred_items) < len(items_to_process)
|
|
1351
|
+
items_to_process = [deferred for deferred in deferred_items]
|
|
1051
1352
|
|
|
1052
1353
|
builder.pop_where()
|
|
1053
1354
|
|
|
@@ -1062,8 +1363,21 @@ class NamespaceDataPair:
|
|
|
1062
1363
|
data: RawDict
|
|
1063
1364
|
|
|
1064
1365
|
|
|
1366
|
+
@dataclass(kw_only=True)
|
|
1367
|
+
class NameDataPair:
|
|
1368
|
+
full_name: str
|
|
1369
|
+
data: RawDict
|
|
1370
|
+
|
|
1371
|
+
|
|
1065
1372
|
class SpecBuilder:
|
|
1066
|
-
def __init__(
|
|
1373
|
+
def __init__(
|
|
1374
|
+
self,
|
|
1375
|
+
*,
|
|
1376
|
+
api_endpoints: dict[EndpointKey, APIEndpointInfo],
|
|
1377
|
+
top_namespace: str,
|
|
1378
|
+
cross_output_paths: CrossOutputPaths | None,
|
|
1379
|
+
) -> None:
|
|
1380
|
+
self.top_namespace = top_namespace
|
|
1067
1381
|
self.where: list[str] = []
|
|
1068
1382
|
self.namespaces = {}
|
|
1069
1383
|
self.pending: list[NamespaceDataPair] = []
|
|
@@ -1072,6 +1386,7 @@ class SpecBuilder:
|
|
|
1072
1386
|
self.examples: dict[str, list[SpecEndpointExample]] = defaultdict(list)
|
|
1073
1387
|
self.guides: dict[SpecGuideKey, list[SpecGuide]] = defaultdict(list)
|
|
1074
1388
|
self.api_endpoints = api_endpoints
|
|
1389
|
+
self.cross_output_paths = cross_output_paths
|
|
1075
1390
|
base_namespace = SpecNamespace(name=base_namespace_name)
|
|
1076
1391
|
for base_type in BaseTypeName:
|
|
1077
1392
|
defn = SpecTypeDefnObject(base_namespace, base_type, is_base=True)
|
|
@@ -1089,9 +1404,13 @@ class SpecBuilder:
|
|
|
1089
1404
|
self.emit_id_source_enums: set[SpecTypeDefnStringEnum] = set()
|
|
1090
1405
|
|
|
1091
1406
|
this_dir = os.path.dirname(os.path.realpath(__file__))
|
|
1092
|
-
with open(
|
|
1407
|
+
with open(
|
|
1408
|
+
f"{this_dir}/parts/base.py.prepart", encoding="utf-8"
|
|
1409
|
+
) as py_base_part:
|
|
1093
1410
|
self.preparts["python"][base_namespace_name] = py_base_part.read()
|
|
1094
|
-
with open(
|
|
1411
|
+
with open(
|
|
1412
|
+
f"{this_dir}/parts/base.ts.prepart", encoding="utf-8"
|
|
1413
|
+
) as ts_base_part:
|
|
1095
1414
|
self.preparts["typescript"][base_namespace_name] = ts_base_part.read()
|
|
1096
1415
|
|
|
1097
1416
|
base_namespace.types["ObjectId"] = SpecTypeDefnObject(
|
|
@@ -1160,7 +1479,7 @@ class SpecBuilder:
|
|
|
1160
1479
|
self,
|
|
1161
1480
|
path: util.ParsedTypePath,
|
|
1162
1481
|
namespace: SpecNamespace,
|
|
1163
|
-
scope:
|
|
1482
|
+
scope: SpecTypeDefn | None = None,
|
|
1164
1483
|
top: bool = False,
|
|
1165
1484
|
) -> SpecType:
|
|
1166
1485
|
"""
|
|
@@ -1218,8 +1537,10 @@ class SpecBuilder:
|
|
|
1218
1537
|
if len(path) == 2:
|
|
1219
1538
|
if isinstance(defn_type, SpecTypeDefnStringEnum):
|
|
1220
1539
|
assert path[1].parameters is None
|
|
1540
|
+
statement = f"$import: [{defn_type.namespace.name}]"
|
|
1221
1541
|
self.ensure(
|
|
1222
|
-
path[1].name in defn_type.values,
|
|
1542
|
+
path[1].name in defn_type.values,
|
|
1543
|
+
f"missing-enum-value: {path} have you specified the dependency in an import statement: {statement}",
|
|
1223
1544
|
)
|
|
1224
1545
|
return SpecTypeLiteralWrapper(
|
|
1225
1546
|
value=path[1].name,
|
|
@@ -1241,11 +1562,13 @@ class SpecBuilder:
|
|
|
1241
1562
|
)
|
|
1242
1563
|
|
|
1243
1564
|
def parse_type(
|
|
1244
|
-
self, namespace: SpecNamespace, spec: str, scope:
|
|
1565
|
+
self, namespace: SpecNamespace, spec: str, scope: SpecTypeDefn | None = None
|
|
1245
1566
|
) -> SpecType:
|
|
1246
1567
|
self.push_where(spec)
|
|
1247
1568
|
parsed_type = util.parse_type_str(spec)
|
|
1248
|
-
result = self._convert_parsed_type(
|
|
1569
|
+
result = self._convert_parsed_type(
|
|
1570
|
+
parsed_type, namespace, top=True, scope=scope
|
|
1571
|
+
)
|
|
1249
1572
|
self.pop_where()
|
|
1250
1573
|
return result
|
|
1251
1574
|
|
|
@@ -1287,16 +1610,22 @@ class SpecBuilder:
|
|
|
1287
1610
|
meta: dict[str, list[str]] = md.Meta # type: ignore[attr-defined]
|
|
1288
1611
|
title_meta: list[str] | None = meta.get("title")
|
|
1289
1612
|
if title_meta is None:
|
|
1290
|
-
raise Exception("guides
|
|
1613
|
+
raise Exception("guides require a title in the meta section")
|
|
1614
|
+
id_meta: list[str] | None = meta.get("id")
|
|
1615
|
+
if id_meta is None:
|
|
1616
|
+
raise Exception("guides require an id in the meta section")
|
|
1291
1617
|
|
|
1292
1618
|
path_meta: list[str] | None = meta.get("path")
|
|
1293
1619
|
guide_key: SpecGuideKey = RootGuideKey()
|
|
1294
1620
|
if path_meta is not None:
|
|
1295
|
-
path_details = _resolve_endpoint_path(
|
|
1621
|
+
path_details = _resolve_endpoint_path(
|
|
1622
|
+
"".join(path_meta), self.api_endpoints
|
|
1623
|
+
)
|
|
1296
1624
|
guide_key = EndpointGuideKey(path=path_details.resolved_path)
|
|
1297
1625
|
|
|
1298
1626
|
self.guides[guide_key].append(
|
|
1299
1627
|
SpecGuide(
|
|
1628
|
+
ref_name="".join(id_meta),
|
|
1300
1629
|
title="".join(title_meta),
|
|
1301
1630
|
html_content=html,
|
|
1302
1631
|
markdown_content=file_content,
|