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/config.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from collections.abc import Callable, Mapping
|
|
3
3
|
from dataclasses import dataclass
|
|
4
|
-
from decimal import Decimal
|
|
5
4
|
from typing import Self, TypeVar
|
|
6
5
|
|
|
7
|
-
import yaml
|
|
6
|
+
from pkgs.serialization import yaml
|
|
7
|
+
from pkgs.type_spec.builder import APIEndpointInfo, EndpointKey
|
|
8
8
|
|
|
9
9
|
ConfigValueType = str | None | Mapping[str, str | None] | list[str]
|
|
10
10
|
|
|
@@ -20,6 +20,22 @@ def _parse_string_lookup(
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
|
|
23
|
+
VT = TypeVar("VT")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _parse_data_lookup(
|
|
27
|
+
key: str,
|
|
28
|
+
raw_value: ConfigValueType,
|
|
29
|
+
conv_func: type[VT],
|
|
30
|
+
) -> dict[str, VT]:
|
|
31
|
+
assert isinstance(raw_value, dict), f"{key} must be key/values"
|
|
32
|
+
return {
|
|
33
|
+
k: conv_func(**v)
|
|
34
|
+
for k, v in raw_value.items()
|
|
35
|
+
if v is not None and isinstance(v, dict)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
23
39
|
@dataclass(kw_only=True)
|
|
24
40
|
class BaseLanguageConfig:
|
|
25
41
|
types_output: (
|
|
@@ -32,18 +48,28 @@ class BaseLanguageConfig:
|
|
|
32
48
|
|
|
33
49
|
@dataclass(kw_only=True)
|
|
34
50
|
class TypeScriptConfig(BaseLanguageConfig):
|
|
35
|
-
|
|
51
|
+
endpoint_to_routes_output: dict[
|
|
52
|
+
EndpointKey, str
|
|
53
|
+
] # folder for generate route files will be located.
|
|
36
54
|
type_info_output: str # folder for generated type info files
|
|
37
55
|
id_source_output: str | None = None # folder for emitted id source maps.
|
|
56
|
+
endpoint_to_frontend_app_type: dict[
|
|
57
|
+
str, str
|
|
58
|
+
] # map from api_endpoint to frontend app type
|
|
38
59
|
|
|
39
60
|
def __post_init__(self: Self) -> None:
|
|
40
|
-
self.
|
|
61
|
+
self.endpoint_to_routes_output = self.endpoint_to_routes_output
|
|
41
62
|
self.type_info_output = os.path.abspath(self.type_info_output)
|
|
42
63
|
self.id_source_output = (
|
|
43
64
|
os.path.abspath(self.id_source_output)
|
|
44
65
|
if self.id_source_output is not None
|
|
45
66
|
else None
|
|
46
67
|
)
|
|
68
|
+
self.endpoint_to_frontend_app_type = _parse_string_lookup(
|
|
69
|
+
"typescript_endpoint_to_frontend_app_type",
|
|
70
|
+
self.endpoint_to_frontend_app_type,
|
|
71
|
+
lambda x: x,
|
|
72
|
+
)
|
|
47
73
|
|
|
48
74
|
|
|
49
75
|
@dataclass(kw_only=True)
|
|
@@ -60,6 +86,7 @@ class PythonConfig(BaseLanguageConfig):
|
|
|
60
86
|
emit_client_class: bool = False # emit the base class for the api client
|
|
61
87
|
all_named_type_exports: bool = False # emit __all__ for all named type exports
|
|
62
88
|
sdk_endpoints_only: bool = False # only emit is_sdk endpoints
|
|
89
|
+
type_info_output: str | None = None # folder for generated type info files
|
|
63
90
|
|
|
64
91
|
def __post_init__(self: Self) -> None:
|
|
65
92
|
self.routes_output = _parse_string_lookup(
|
|
@@ -71,6 +98,9 @@ class PythonConfig(BaseLanguageConfig):
|
|
|
71
98
|
else None
|
|
72
99
|
)
|
|
73
100
|
|
|
101
|
+
if self.type_info_output is not None:
|
|
102
|
+
self.type_info_output = os.path.abspath(self.type_info_output)
|
|
103
|
+
|
|
74
104
|
|
|
75
105
|
@dataclass(kw_only=True)
|
|
76
106
|
class OpenAPIConfig(BaseLanguageConfig):
|
|
@@ -87,45 +117,37 @@ class OpenAPIConfig(BaseLanguageConfig):
|
|
|
87
117
|
|
|
88
118
|
@dataclass(kw_only=True)
|
|
89
119
|
class Config:
|
|
120
|
+
top_namespace: str
|
|
90
121
|
type_spec_types: list[str] # folders containing the yaml type spec definitions
|
|
91
|
-
api_endpoint: dict[str,
|
|
122
|
+
api_endpoint: dict[str, APIEndpointInfo]
|
|
92
123
|
# languages
|
|
93
124
|
typescript: TypeScriptConfig | None
|
|
94
125
|
python: PythonConfig
|
|
95
126
|
open_api: OpenAPIConfig | None
|
|
96
127
|
|
|
97
128
|
|
|
98
|
-
|
|
129
|
+
T = TypeVar("T")
|
|
99
130
|
|
|
100
131
|
|
|
101
|
-
def _parse_language(config_class: type[
|
|
132
|
+
def _parse_language(config_class: type[T], raw_value: ConfigValueType) -> T:
|
|
102
133
|
assert isinstance(raw_value, dict), "expecting language config to have key/values."
|
|
103
134
|
return config_class(**raw_value)
|
|
104
135
|
|
|
105
136
|
|
|
106
|
-
def _decimal_constructor(loader, node): # type:ignore
|
|
107
|
-
value = loader.construct_scalar(node)
|
|
108
|
-
return Decimal(value)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
# A semi-acceptable patch to force a number to be parsed as a decimal, since pyyaml
|
|
112
|
-
# parses them as lossy floats otherwise. Though a bit ugly, at least this way we have
|
|
113
|
-
# support for decimal constants
|
|
114
|
-
yaml.SafeLoader.add_constructor("!decimal", _decimal_constructor)
|
|
115
|
-
|
|
116
|
-
|
|
117
137
|
def parse_yaml_config(config_file: str) -> Config:
|
|
118
138
|
with open(config_file, encoding="utf-8") as input:
|
|
119
139
|
raw_config: dict[str, ConfigValueType] = yaml.safe_load(input)
|
|
120
140
|
|
|
121
141
|
raw_type_spec_types = raw_config["type_spec_types"]
|
|
122
|
-
assert isinstance(
|
|
123
|
-
|
|
124
|
-
)
|
|
142
|
+
assert isinstance(raw_type_spec_types, list), (
|
|
143
|
+
"type_spec_types, must be a list of folders"
|
|
144
|
+
)
|
|
125
145
|
type_spec_types = [os.path.abspath(folder) for folder in raw_type_spec_types]
|
|
126
146
|
|
|
127
|
-
api_endpoint =
|
|
128
|
-
"api_endpoint",
|
|
147
|
+
api_endpoint = _parse_data_lookup(
|
|
148
|
+
"api_endpoint",
|
|
149
|
+
raw_config.get("api_endpoint", {}),
|
|
150
|
+
APIEndpointInfo,
|
|
129
151
|
)
|
|
130
152
|
|
|
131
153
|
raw_typescript = raw_config.get("typescript")
|
|
@@ -137,10 +159,16 @@ def parse_yaml_config(config_file: str) -> Config:
|
|
|
137
159
|
python = _parse_language(PythonConfig, raw_config["python"])
|
|
138
160
|
raw_open_api = raw_config.get("open_api")
|
|
139
161
|
open_api = (
|
|
140
|
-
_parse_language(OpenAPIConfig, raw_open_api)
|
|
162
|
+
_parse_language(OpenAPIConfig, raw_open_api)
|
|
163
|
+
if raw_open_api is not None
|
|
164
|
+
else None
|
|
141
165
|
)
|
|
142
166
|
|
|
167
|
+
top_namespace = raw_config["top_namespace"]
|
|
168
|
+
assert isinstance(top_namespace, str)
|
|
169
|
+
|
|
143
170
|
return Config(
|
|
171
|
+
top_namespace=top_namespace,
|
|
144
172
|
type_spec_types=type_spec_types,
|
|
145
173
|
api_endpoint=api_endpoint,
|
|
146
174
|
typescript=typescript,
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from . import builder
|
|
6
|
+
from .builder_types import CrossOutputPaths
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_python_stub_file_path(
|
|
10
|
+
function_name: str | None,
|
|
11
|
+
) -> str | None:
|
|
12
|
+
if function_name is None:
|
|
13
|
+
return None
|
|
14
|
+
module_dir, file_name, _func_name = function_name.rsplit(".", 2)
|
|
15
|
+
module_path = os.path.relpath(module_dir.replace(".", "/"))
|
|
16
|
+
api_stub_file = f"{module_path}/{file_name}.py"
|
|
17
|
+
return api_stub_file
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_python_api_file_path(
|
|
21
|
+
cross_output_paths: CrossOutputPaths,
|
|
22
|
+
namespace: builder.SpecNamespace,
|
|
23
|
+
) -> str:
|
|
24
|
+
return f"{cross_output_paths.python_types_output}/{'/'.join(namespace.path)}{'' if len(namespace.path) > 1 else '_t'}.py"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_typescript_api_file_path(
|
|
28
|
+
cross_output_paths: CrossOutputPaths,
|
|
29
|
+
namespace: builder.SpecNamespace,
|
|
30
|
+
endpoint_key: builder.EndpointKey,
|
|
31
|
+
) -> str:
|
|
32
|
+
return f"{cross_output_paths.typescript_routes_output_by_endpoint[endpoint_key]}/{'/'.join(namespace.path)}.tsx"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_yaml_api_file_path(
|
|
36
|
+
cross_output_paths: CrossOutputPaths,
|
|
37
|
+
namespace: builder.SpecNamespace,
|
|
38
|
+
) -> str:
|
|
39
|
+
abs_path = next(
|
|
40
|
+
(
|
|
41
|
+
path
|
|
42
|
+
for path in cross_output_paths.typespec_files_input
|
|
43
|
+
if (
|
|
44
|
+
namespace.endpoint is None
|
|
45
|
+
or namespace.endpoint.default_endpoint_key in path
|
|
46
|
+
)
|
|
47
|
+
),
|
|
48
|
+
cross_output_paths.typespec_files_input[0],
|
|
49
|
+
)
|
|
50
|
+
return f"{os.path.relpath(abs_path)}/{'/'.join(namespace.path)}.yaml"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_return_to_root_path(path: str) -> str:
|
|
54
|
+
return "../" * (path.count("/"))
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_path_links(
|
|
58
|
+
cross_output_paths: CrossOutputPaths | None,
|
|
59
|
+
namespace: builder.SpecNamespace,
|
|
60
|
+
*,
|
|
61
|
+
current_path_type: str,
|
|
62
|
+
endpoint: builder.SpecEndpoint,
|
|
63
|
+
) -> str:
|
|
64
|
+
if cross_output_paths is None:
|
|
65
|
+
return ""
|
|
66
|
+
|
|
67
|
+
api_paths = {
|
|
68
|
+
"Python": get_python_api_file_path(cross_output_paths, namespace),
|
|
69
|
+
"TypeScript": get_typescript_api_file_path(
|
|
70
|
+
cross_output_paths, namespace, endpoint.default_endpoint_key
|
|
71
|
+
),
|
|
72
|
+
"YAML": get_yaml_api_file_path(cross_output_paths, namespace),
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
assert current_path_type in api_paths
|
|
76
|
+
|
|
77
|
+
comment_prefix = "#"
|
|
78
|
+
if current_path_type == "TypeScript":
|
|
79
|
+
comment_prefix = "//"
|
|
80
|
+
|
|
81
|
+
return_to_root_path = get_return_to_root_path(api_paths[current_path_type])
|
|
82
|
+
del api_paths[current_path_type]
|
|
83
|
+
|
|
84
|
+
paths_string = ""
|
|
85
|
+
for path_name, path in api_paths.items():
|
|
86
|
+
paths_string += (
|
|
87
|
+
f"{comment_prefix} {path_name}: file://./{return_to_root_path}{path}\n"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
if namespace.endpoint is not None:
|
|
91
|
+
for (
|
|
92
|
+
endpoint_key,
|
|
93
|
+
path_specific_endpoint,
|
|
94
|
+
) in namespace.endpoint.path_per_api_endpoint.items():
|
|
95
|
+
path_from_root = get_python_stub_file_path(path_specific_endpoint.function)
|
|
96
|
+
if path_from_root is None:
|
|
97
|
+
continue
|
|
98
|
+
paths_string += f"{comment_prefix} Implementation for {endpoint_key}: file://./{return_to_root_path}{path_from_root}\n"
|
|
99
|
+
return paths_string
|
pkgs/type_spec/emit_io_ts.py
CHANGED
|
@@ -125,7 +125,7 @@ def refer_to_io_ts(
|
|
|
125
125
|
spec = refer_to_io_ts(ctx, stype.parameters[0])
|
|
126
126
|
return f"IO.array({spec})"
|
|
127
127
|
if stype.defn_type.name == builder.BaseTypeName.s_union:
|
|
128
|
-
return f
|
|
128
|
+
return f"IO.union([{', '.join([refer_to_io_ts(ctx, p) for p in stype.parameters])}])"
|
|
129
129
|
if stype.defn_type.name == builder.BaseTypeName.s_optional:
|
|
130
130
|
return f"IO.optional({refer_to_io_ts(ctx, stype.parameters[0])})"
|
|
131
131
|
if stype.defn_type.name == builder.BaseTypeName.s_tuple:
|
pkgs/type_spec/emit_open_api.py
CHANGED
|
@@ -7,9 +7,11 @@ WORK-IN-PROGRESS, DON'T USE!
|
|
|
7
7
|
import dataclasses
|
|
8
8
|
import json
|
|
9
9
|
import re
|
|
10
|
-
from
|
|
10
|
+
from enum import StrEnum
|
|
11
|
+
from typing import Collection, assert_never, cast
|
|
11
12
|
|
|
12
|
-
import yaml
|
|
13
|
+
from pkgs.serialization import yaml
|
|
14
|
+
from pkgs.serialization_util import serialize_for_api
|
|
13
15
|
|
|
14
16
|
from . import builder, util
|
|
15
17
|
from .builder import EndpointGuideKey, RootGuideKey
|
|
@@ -23,6 +25,7 @@ from .emit_open_api_util import (
|
|
|
23
25
|
EmitOpenAPIGuide,
|
|
24
26
|
EmitOpenAPIPath,
|
|
25
27
|
EmitOpenAPIServer,
|
|
28
|
+
EmitOpenAPIStabilityLevel,
|
|
26
29
|
EmitOpenAPITag,
|
|
27
30
|
GlobalContextInfo,
|
|
28
31
|
TagGroupToNamedTags,
|
|
@@ -60,10 +63,14 @@ base_name_map = {
|
|
|
60
63
|
}
|
|
61
64
|
|
|
62
65
|
|
|
66
|
+
class OpenAPIDefaultBehavior(StrEnum):
|
|
67
|
+
OPTIONAL_WITH_DEFAULT = "optional_with_default"
|
|
68
|
+
|
|
69
|
+
|
|
63
70
|
def _rewrite_with_notice(
|
|
64
71
|
file_path: str, file_content: str, *, notice: str = MODIFY_NOTICE
|
|
65
72
|
) -> bool:
|
|
66
|
-
pattern = re.compile("^\S", re.MULTILINE)
|
|
73
|
+
pattern = re.compile(r"^\S", re.MULTILINE)
|
|
67
74
|
|
|
68
75
|
file_lines = file_content.split("\n")
|
|
69
76
|
comment_lines = []
|
|
@@ -78,9 +85,9 @@ def _rewrite_with_notice(
|
|
|
78
85
|
return util.rewrite_file(file_path, f"{notice}\n{modified_file_content}")
|
|
79
86
|
|
|
80
87
|
|
|
81
|
-
def _write_guide_as_html(guide: EmitOpenAPIGuide) -> str:
|
|
88
|
+
def _write_guide_as_html(guide: EmitOpenAPIGuide, *, is_open: bool) -> str:
|
|
82
89
|
return f"""
|
|
83
|
-
<details>
|
|
90
|
+
<details id="{guide.ref_name}" {"open" if is_open else ""}>
|
|
84
91
|
<summary>{guide.title}</summary>
|
|
85
92
|
{guide.html_content}
|
|
86
93
|
</details>"""
|
|
@@ -89,7 +96,10 @@ def _write_guide_as_html(guide: EmitOpenAPIGuide) -> str:
|
|
|
89
96
|
def _open_api_info(
|
|
90
97
|
config: OpenAPIConfig, guides: list[EmitOpenAPIGuide]
|
|
91
98
|
) -> GlobalContextInfo:
|
|
92
|
-
full_guides = "<br/>".join([
|
|
99
|
+
full_guides = "<br/>".join([
|
|
100
|
+
_write_guide_as_html(guide, is_open=True)
|
|
101
|
+
for guide in sorted(guides, key=lambda g: g.ref_name)
|
|
102
|
+
])
|
|
93
103
|
full_description = f"{config.description}<br/>{full_guides}"
|
|
94
104
|
info: GlobalContextInfo = dict()
|
|
95
105
|
info["version"] = "1.0.0"
|
|
@@ -107,7 +117,9 @@ def _open_api_servers(config: OpenAPIConfig) -> list[EmitOpenAPIServer]:
|
|
|
107
117
|
def emit_open_api(builder: builder.SpecBuilder, *, config: OpenAPIConfig) -> None:
|
|
108
118
|
root_guides = builder.guides.get(RootGuideKey(), [])
|
|
109
119
|
openapi_guides = [
|
|
110
|
-
EmitOpenAPIGuide(
|
|
120
|
+
EmitOpenAPIGuide(
|
|
121
|
+
ref_name=guide.ref_name, title=guide.title, html_content=guide.html_content
|
|
122
|
+
)
|
|
111
123
|
for guide in root_guides
|
|
112
124
|
]
|
|
113
125
|
gctx = EmitOpenAPIGlobalContext(
|
|
@@ -119,7 +131,11 @@ def emit_open_api(builder: builder.SpecBuilder, *, config: OpenAPIConfig) -> Non
|
|
|
119
131
|
for namespace in sorted(builder.namespaces.values(), key=lambda ns: ns.name):
|
|
120
132
|
ctx = EmitOpenAPIContext(namespace=namespace)
|
|
121
133
|
|
|
122
|
-
if
|
|
134
|
+
if (
|
|
135
|
+
ctx.namespace.endpoint is not None
|
|
136
|
+
and ctx.namespace.endpoint.stability_level
|
|
137
|
+
== EmitOpenAPIStabilityLevel.draft
|
|
138
|
+
):
|
|
123
139
|
continue
|
|
124
140
|
|
|
125
141
|
if ctx.namespace.name == "base":
|
|
@@ -163,7 +179,7 @@ def _serialize_global_context(ctx: EmitOpenAPIGlobalContext) -> str:
|
|
|
163
179
|
oa_paths[path.path] = {"$ref": path.ref}
|
|
164
180
|
oa_root["paths"] = oa_paths
|
|
165
181
|
|
|
166
|
-
return yaml.
|
|
182
|
+
return yaml.dumps(oa_root, sort_keys=False)
|
|
167
183
|
|
|
168
184
|
|
|
169
185
|
def _is_empty_object_type(typ: OpenAPIType) -> bool:
|
|
@@ -245,10 +261,35 @@ def _emit_endpoint_parameters(
|
|
|
245
261
|
} | _emit_endpoint_parameter_examples(examples)
|
|
246
262
|
|
|
247
263
|
|
|
248
|
-
def
|
|
249
|
-
if
|
|
250
|
-
|
|
251
|
-
|
|
264
|
+
def _emit_endpoint_deprecated(deprecated: bool) -> DictApiSchema:
|
|
265
|
+
return {"deprecated": True} if deprecated else {}
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _emit_stability_level(
|
|
269
|
+
stability_level: EmitOpenAPIStabilityLevel | None,
|
|
270
|
+
) -> DictApiSchema:
|
|
271
|
+
stability_info: dict[str, ApiSchema] = {}
|
|
272
|
+
resolved_stability_level = (
|
|
273
|
+
stability_level
|
|
274
|
+
if stability_level is not None
|
|
275
|
+
else EmitOpenAPIStabilityLevel.stable
|
|
276
|
+
)
|
|
277
|
+
stability_info["x-stability-level"] = str(resolved_stability_level)
|
|
278
|
+
match resolved_stability_level:
|
|
279
|
+
case EmitOpenAPIStabilityLevel.draft:
|
|
280
|
+
stability_info["x-beta"] = True
|
|
281
|
+
case EmitOpenAPIStabilityLevel.beta:
|
|
282
|
+
stability_info["x-badges"] = [
|
|
283
|
+
{
|
|
284
|
+
"name": "Beta",
|
|
285
|
+
"color": "DarkOrange",
|
|
286
|
+
}
|
|
287
|
+
]
|
|
288
|
+
case EmitOpenAPIStabilityLevel.stable:
|
|
289
|
+
pass
|
|
290
|
+
case _:
|
|
291
|
+
assert_never(stability_level)
|
|
292
|
+
return stability_info
|
|
252
293
|
|
|
253
294
|
|
|
254
295
|
def _emit_endpoint_request_body(
|
|
@@ -271,7 +312,9 @@ def _emit_endpoint_request_body(
|
|
|
271
312
|
"type": "object",
|
|
272
313
|
"title": "Body",
|
|
273
314
|
"required": ["data"],
|
|
274
|
-
"properties": {
|
|
315
|
+
"properties": {
|
|
316
|
+
"data": {"$ref": "#/components/schema/Arguments"}
|
|
317
|
+
},
|
|
275
318
|
}
|
|
276
319
|
}
|
|
277
320
|
| _emit_endpoint_argument_examples(examples)
|
|
@@ -296,15 +339,57 @@ def _emit_endpoint_response_examples(
|
|
|
296
339
|
return {"examples": response_examples}
|
|
297
340
|
|
|
298
341
|
|
|
342
|
+
def _create_warning_banner(api_type: str, message: str) -> str:
|
|
343
|
+
return (
|
|
344
|
+
f'<div style="background-color: #fff3cd; border: 1px solid #ffeaa7; '
|
|
345
|
+
f'border-radius: 4px; padding: 12px; margin-bottom: 16px;">'
|
|
346
|
+
f"<strong>⚠️ {api_type} API:</strong> {message}"
|
|
347
|
+
f"</div>"
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def _get_stability_warning(
|
|
352
|
+
stability_level: EmitOpenAPIStabilityLevel | None,
|
|
353
|
+
) -> str:
|
|
354
|
+
resolved_stability_level = (
|
|
355
|
+
stability_level
|
|
356
|
+
if stability_level is not None
|
|
357
|
+
else EmitOpenAPIStabilityLevel.stable
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
match resolved_stability_level:
|
|
361
|
+
case EmitOpenAPIStabilityLevel.draft:
|
|
362
|
+
return _create_warning_banner(
|
|
363
|
+
"Draft",
|
|
364
|
+
"This endpoint is in draft status and may change significantly. Not recommended for production use.",
|
|
365
|
+
)
|
|
366
|
+
case EmitOpenAPIStabilityLevel.beta:
|
|
367
|
+
return _create_warning_banner(
|
|
368
|
+
"Beta",
|
|
369
|
+
"This endpoint is in beta and its required parameters may change. Use with caution in production environments.",
|
|
370
|
+
)
|
|
371
|
+
case EmitOpenAPIStabilityLevel.stable:
|
|
372
|
+
return ""
|
|
373
|
+
|
|
374
|
+
|
|
299
375
|
def _emit_endpoint_description(
|
|
300
|
-
description: str,
|
|
376
|
+
description: str,
|
|
377
|
+
guides: list[EmitOpenAPIGuide],
|
|
378
|
+
stability_level: EmitOpenAPIStabilityLevel | None = None,
|
|
301
379
|
) -> dict[str, str]:
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
380
|
+
stability_warning = _get_stability_warning(stability_level)
|
|
381
|
+
|
|
382
|
+
full_guides = "<br/>".join([
|
|
383
|
+
_write_guide_as_html(guide, is_open=False)
|
|
384
|
+
for guide in sorted(guides, key=lambda g: g.ref_name)
|
|
385
|
+
])
|
|
386
|
+
|
|
387
|
+
full_description_parts = [
|
|
388
|
+
part for part in [stability_warning, description, full_guides] if part
|
|
389
|
+
]
|
|
390
|
+
full_description = "<br/>".join(full_description_parts)
|
|
391
|
+
|
|
392
|
+
return {"description": full_description}
|
|
308
393
|
|
|
309
394
|
|
|
310
395
|
def _emit_namespace(
|
|
@@ -339,10 +424,15 @@ def _emit_namespace(
|
|
|
339
424
|
"tags": endpoint.tags,
|
|
340
425
|
"summary": endpoint.summary,
|
|
341
426
|
}
|
|
342
|
-
|
|
|
343
|
-
|
|
|
427
|
+
| _emit_endpoint_deprecated(endpoint.deprecated)
|
|
428
|
+
| _emit_endpoint_description(
|
|
429
|
+
endpoint.description, ctx.endpoint.guides, endpoint.stability_level
|
|
430
|
+
)
|
|
431
|
+
| _emit_stability_level(endpoint.stability_level)
|
|
344
432
|
| _emit_endpoint_parameters(endpoint, argument_type, ctx.endpoint.examples)
|
|
345
|
-
| _emit_endpoint_request_body(
|
|
433
|
+
| _emit_endpoint_request_body(
|
|
434
|
+
endpoint, argument_type, ctx.endpoint.examples
|
|
435
|
+
)
|
|
346
436
|
| {
|
|
347
437
|
"responses": {
|
|
348
438
|
"200": {
|
|
@@ -395,7 +485,19 @@ def _emit_namespace(
|
|
|
395
485
|
|
|
396
486
|
path = f"{config.types_output}/common/{'/'.join(namespace.path)}.yaml"
|
|
397
487
|
oa_namespace = {"components": oa_components}
|
|
398
|
-
_rewrite_with_notice(path, yaml.
|
|
488
|
+
_rewrite_with_notice(path, yaml.dumps(oa_namespace, sort_keys=False))
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def _get_openapi_default_behavior(
|
|
492
|
+
prop: builder.SpecProperty,
|
|
493
|
+
) -> OpenAPIDefaultBehavior | None:
|
|
494
|
+
if prop.ext_info is None or prop.ext_info.get("open_api") is None:
|
|
495
|
+
return None
|
|
496
|
+
value_passed = prop.ext_info["open_api"].get("default_required_behavior")
|
|
497
|
+
if value_passed is None:
|
|
498
|
+
return None
|
|
499
|
+
assert isinstance(value_passed, str)
|
|
500
|
+
return OpenAPIDefaultBehavior(value_passed)
|
|
399
501
|
|
|
400
502
|
|
|
401
503
|
def _emit_type(
|
|
@@ -422,6 +524,22 @@ def _emit_type(
|
|
|
422
524
|
ctx.types[stype.name] = open_api_type(ctx, stype.alias, config=config)
|
|
423
525
|
return
|
|
424
526
|
|
|
527
|
+
if isinstance(stype, builder.SpecTypeDefnUnion):
|
|
528
|
+
converted_discriminator_map: dict[str, OpenAPIRefType] = dict()
|
|
529
|
+
if stype.discriminator_map is not None:
|
|
530
|
+
for discriminator_value, base_type in stype.discriminator_map.items():
|
|
531
|
+
converted_base_type = open_api_type(ctx, base_type, config=config)
|
|
532
|
+
assert isinstance(converted_base_type, OpenAPIRefType)
|
|
533
|
+
converted_discriminator_map[discriminator_value] = converted_base_type
|
|
534
|
+
ctx.types[stype.name] = OpenAPIUnionType(
|
|
535
|
+
[open_api_type(ctx, p, config=config) for p in stype.types],
|
|
536
|
+
discriminator=stype.discriminator,
|
|
537
|
+
discriminator_map=converted_discriminator_map
|
|
538
|
+
if stype.discriminator_map is not None
|
|
539
|
+
else None,
|
|
540
|
+
)
|
|
541
|
+
return
|
|
542
|
+
|
|
425
543
|
if isinstance(stype, builder.SpecTypeDefnStringEnum):
|
|
426
544
|
# TODO: check that these are always string enums
|
|
427
545
|
# IMPROVE: reflect the enum names in the description
|
|
@@ -455,6 +573,16 @@ def _emit_type(
|
|
|
455
573
|
# arguments, thus treat like extant==missing
|
|
456
574
|
# IMPROVE: if we can decide they are meant as output instead, then
|
|
457
575
|
# they should be marked as required
|
|
576
|
+
openapi_default_beahvior = _get_openapi_default_behavior(prop)
|
|
577
|
+
match openapi_default_beahvior:
|
|
578
|
+
case None:
|
|
579
|
+
pass
|
|
580
|
+
case OpenAPIDefaultBehavior.OPTIONAL_WITH_DEFAULT:
|
|
581
|
+
ref_type.nullable = True
|
|
582
|
+
assert prop.default is not None, (
|
|
583
|
+
"optional_with_default requires default"
|
|
584
|
+
)
|
|
585
|
+
ref_type.default = prop.default
|
|
458
586
|
properties[prop_name] = ref_type
|
|
459
587
|
elif prop.extant == builder.PropertyExtant.missing:
|
|
460
588
|
# Unlike optional below, missing does not imply null is possible. They
|
|
@@ -482,18 +610,6 @@ def _emit_type(
|
|
|
482
610
|
ctx.types[stype.name] = final_type
|
|
483
611
|
|
|
484
612
|
|
|
485
|
-
def _emit_constant(ctx: EmitOpenAPIContext, sconst: builder.SpecConstant) -> None:
|
|
486
|
-
if sconst.value_type.is_base_type(builder.BaseTypeName.s_string):
|
|
487
|
-
value = util.encode_common_string(cast(str, sconst.value))
|
|
488
|
-
elif sconst.value_type.is_base_type(builder.BaseTypeName.s_integer):
|
|
489
|
-
value = str(sconst.value)
|
|
490
|
-
else:
|
|
491
|
-
raise Exception("invalid constant type", sconst.name)
|
|
492
|
-
|
|
493
|
-
const_name = sconst.name.upper()
|
|
494
|
-
print("_emit_constant", value, const_name)
|
|
495
|
-
|
|
496
|
-
|
|
497
613
|
def _emit_endpoint(
|
|
498
614
|
gctx: EmitOpenAPIGlobalContext,
|
|
499
615
|
ctx: EmitOpenAPIContext,
|
|
@@ -534,7 +650,7 @@ def _emit_endpoint(
|
|
|
534
650
|
ep = namespace.endpoint
|
|
535
651
|
gctx.paths.append(
|
|
536
652
|
EmitOpenAPIPath(
|
|
537
|
-
path=f"/{ep.
|
|
653
|
+
path=f"/{ep.resolved_path}",
|
|
538
654
|
ref=ref_path,
|
|
539
655
|
)
|
|
540
656
|
)
|
|
@@ -544,24 +660,27 @@ def _emit_endpoint(
|
|
|
544
660
|
description = f"**[External API-Endpoint]** <br/> {description}"
|
|
545
661
|
|
|
546
662
|
path_cutoff = min(3, len(namespace.path) - 1)
|
|
663
|
+
|
|
547
664
|
ctx.endpoint = EmitOpenAPIEndpoint(
|
|
548
665
|
method=namespace.endpoint.method.lower(),
|
|
549
666
|
tags=[tag_name],
|
|
550
667
|
summary=f"{'/'.join(namespace.path[path_cutoff:])}",
|
|
551
668
|
description=description,
|
|
552
|
-
|
|
669
|
+
deprecated=namespace.endpoint.deprecated,
|
|
670
|
+
stability_level=namespace.endpoint.stability_level,
|
|
553
671
|
examples=[
|
|
554
672
|
EmitOpenAPIEndpointExample(
|
|
555
673
|
ref_name=f"ex_{i}",
|
|
556
674
|
summary=example.summary,
|
|
557
675
|
description=example.description,
|
|
558
|
-
arguments=example.arguments,
|
|
559
|
-
data=example.data,
|
|
676
|
+
arguments=serialize_for_api(example.arguments),
|
|
677
|
+
data=serialize_for_api(example.data),
|
|
560
678
|
)
|
|
561
679
|
for i, example in enumerate(endpoint_examples)
|
|
562
680
|
],
|
|
563
681
|
guides=[
|
|
564
682
|
EmitOpenAPIGuide(
|
|
683
|
+
ref_name=guide.ref_name,
|
|
565
684
|
title=guide.title,
|
|
566
685
|
html_content=guide.html_content,
|
|
567
686
|
)
|
|
@@ -6,16 +6,17 @@ WORK-IN-PROGRESS, DON'T USE!
|
|
|
6
6
|
|
|
7
7
|
from collections import defaultdict
|
|
8
8
|
from dataclasses import dataclass, field
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
from pkgs.serialization_util import JsonValue
|
|
10
11
|
|
|
11
12
|
from . import builder
|
|
12
13
|
from .open_api_util import OpenAPIType
|
|
13
14
|
|
|
14
15
|
MODIFY_NOTICE = "# DO NOT MODIFY -- This file is generated by type_spec"
|
|
15
16
|
|
|
16
|
-
GlobalContextInfo
|
|
17
|
-
TagGroupToNamedTags
|
|
18
|
-
TagPathsToRef
|
|
17
|
+
GlobalContextInfo = dict[str, str | dict[str, str]]
|
|
18
|
+
TagGroupToNamedTags = dict[str, str | list[str]]
|
|
19
|
+
TagPathsToRef = dict[str, dict[str, str]]
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
@dataclass
|
|
@@ -45,6 +46,7 @@ class EmitOpenAPIServer:
|
|
|
45
46
|
|
|
46
47
|
@dataclass(kw_only=True)
|
|
47
48
|
class EmitOpenAPIGuide:
|
|
49
|
+
ref_name: str
|
|
48
50
|
title: str
|
|
49
51
|
html_content: str
|
|
50
52
|
|
|
@@ -67,8 +69,11 @@ class EmitOpenAPIEndpointExample:
|
|
|
67
69
|
ref_name: str
|
|
68
70
|
summary: str
|
|
69
71
|
description: str
|
|
70
|
-
arguments: dict[str,
|
|
71
|
-
data: dict[str,
|
|
72
|
+
arguments: dict[str, JsonValue]
|
|
73
|
+
data: dict[str, JsonValue]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
EmitOpenAPIStabilityLevel = builder.StabilityLevel
|
|
72
77
|
|
|
73
78
|
|
|
74
79
|
@dataclass
|
|
@@ -77,7 +82,8 @@ class EmitOpenAPIEndpoint:
|
|
|
77
82
|
tags: list[str]
|
|
78
83
|
summary: str
|
|
79
84
|
description: str
|
|
80
|
-
|
|
85
|
+
deprecated: bool
|
|
86
|
+
stability_level: EmitOpenAPIStabilityLevel | None
|
|
81
87
|
examples: list[EmitOpenAPIEndpointExample]
|
|
82
88
|
guides: list[EmitOpenAPIGuide]
|
|
83
89
|
|