UncountablePythonSDK 0.0.7__py3-none-any.whl → 0.0.92__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.
- UncountablePythonSDK-0.0.92.dist-info/METADATA +61 -0
- UncountablePythonSDK-0.0.92.dist-info/RECORD +301 -0
- {UncountablePythonSDK-0.0.7.dist-info → UncountablePythonSDK-0.0.92.dist-info}/WHEEL +1 -1
- {UncountablePythonSDK-0.0.7.dist-info → UncountablePythonSDK-0.0.92.dist-info}/top_level.txt +1 -1
- docs/.gitignore +1 -0
- docs/conf.py +57 -0
- docs/index.md +13 -0
- docs/justfile +12 -0
- docs/quickstart.md +19 -0
- docs/requirements.txt +7 -0
- docs/static/favicons/android-chrome-192x192.png +0 -0
- docs/static/favicons/android-chrome-512x512.png +0 -0
- docs/static/favicons/apple-touch-icon.png +0 -0
- docs/static/favicons/browserconfig.xml +9 -0
- docs/static/favicons/favicon-16x16.png +0 -0
- docs/static/favicons/favicon-32x32.png +0 -0
- docs/static/favicons/manifest.json +18 -0
- docs/static/favicons/mstile-150x150.png +0 -0
- docs/static/favicons/safari-pinned-tab.svg +32 -0
- docs/static/logo_blue.png +0 -0
- examples/async_batch.py +35 -0
- examples/create_entity.py +22 -17
- examples/download_files.py +26 -0
- examples/edit_recipe_inputs.py +50 -0
- examples/integration-server/jobs/materials_auto/example_cron.py +18 -0
- examples/integration-server/jobs/materials_auto/example_wh.py +15 -0
- examples/integration-server/jobs/materials_auto/profile.yaml +43 -0
- examples/integration-server/pyproject.toml +224 -0
- examples/invoke_uploader.py +26 -0
- examples/set_recipe_metadata_file.py +40 -0
- examples/set_recipe_output_file_sdk.py +26 -0
- examples/upload_files.py +18 -0
- pkgs/argument_parser/__init__.py +5 -0
- pkgs/argument_parser/_is_enum.py +1 -6
- pkgs/argument_parser/argument_parser.py +232 -76
- pkgs/argument_parser/case_convert.py +4 -3
- pkgs/filesystem_utils/__init__.py +20 -0
- pkgs/filesystem_utils/_blob_session.py +137 -0
- pkgs/filesystem_utils/_gdrive_session.py +309 -0
- pkgs/filesystem_utils/_local_session.py +69 -0
- pkgs/filesystem_utils/_s3_session.py +117 -0
- pkgs/filesystem_utils/_sftp_session.py +147 -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/opaque_key.py +1 -1
- pkgs/serialization/serial_alias.py +47 -0
- pkgs/serialization/serial_class.py +65 -50
- 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/_get_type_for_serialization.py +1 -3
- pkgs/serialization_util/convert_to_snakecase.py +27 -0
- pkgs/serialization_util/dataclasses.py +14 -0
- pkgs/serialization_util/serialization_helpers.py +118 -73
- pkgs/strenum_compat/strenum_compat.py +1 -9
- pkgs/type_spec/actions_registry/__init__.py +0 -0
- pkgs/type_spec/actions_registry/__main__.py +126 -0
- pkgs/type_spec/actions_registry/emit_typescript.py +182 -0
- pkgs/type_spec/builder.py +475 -89
- pkgs/type_spec/config.py +24 -19
- pkgs/type_spec/emit_io_ts.py +5 -2
- pkgs/type_spec/emit_open_api.py +266 -32
- pkgs/type_spec/emit_open_api_util.py +32 -13
- pkgs/type_spec/emit_python.py +601 -150
- pkgs/type_spec/emit_typescript.py +74 -273
- pkgs/type_spec/emit_typescript_util.py +239 -5
- pkgs/type_spec/load_types.py +55 -10
- pkgs/type_spec/open_api_util.py +30 -41
- pkgs/type_spec/parts/base.py.prepart +4 -3
- pkgs/type_spec/type_info/emit_type_info.py +178 -16
- pkgs/type_spec/util.py +11 -11
- pkgs/type_spec/value_spec/__main__.py +3 -3
- pkgs/type_spec/value_spec/convert_type.py +8 -1
- pkgs/type_spec/value_spec/emit_python.py +13 -4
- uncountable/__init__.py +1 -2
- uncountable/core/__init__.py +12 -2
- uncountable/core/async_batch.py +37 -0
- uncountable/core/client.py +293 -43
- uncountable/core/environment.py +41 -0
- uncountable/core/file_upload.py +135 -0
- uncountable/core/types.py +17 -0
- uncountable/integration/__init__.py +0 -0
- uncountable/integration/cli.py +49 -0
- uncountable/integration/construct_client.py +51 -0
- uncountable/integration/cron.py +29 -0
- uncountable/integration/db/__init__.py +0 -0
- uncountable/integration/db/connect.py +18 -0
- uncountable/integration/db/session.py +25 -0
- uncountable/integration/entrypoint.py +13 -0
- uncountable/integration/executors/__init__.py +0 -0
- uncountable/integration/executors/executors.py +148 -0
- uncountable/integration/executors/generic_upload_executor.py +284 -0
- uncountable/integration/executors/script_executor.py +25 -0
- uncountable/integration/job.py +87 -0
- uncountable/integration/queue_runner/__init__.py +0 -0
- uncountable/integration/queue_runner/command_server/__init__.py +24 -0
- uncountable/integration/queue_runner/command_server/command_client.py +68 -0
- uncountable/integration/queue_runner/command_server/command_server.py +64 -0
- uncountable/integration/queue_runner/command_server/protocol/__init__.py +0 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server.proto +22 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.py +40 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.pyi +38 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2_grpc.py +129 -0
- uncountable/integration/queue_runner/command_server/types.py +52 -0
- uncountable/integration/queue_runner/datastore/__init__.py +3 -0
- uncountable/integration/queue_runner/datastore/datastore_sqlite.py +93 -0
- uncountable/integration/queue_runner/datastore/interface.py +19 -0
- uncountable/integration/queue_runner/datastore/model.py +17 -0
- uncountable/integration/queue_runner/job_scheduler.py +163 -0
- uncountable/integration/queue_runner/queue_runner.py +26 -0
- uncountable/integration/queue_runner/types.py +7 -0
- uncountable/integration/queue_runner/worker.py +119 -0
- uncountable/integration/scan_profiles.py +67 -0
- uncountable/integration/scheduler.py +150 -0
- uncountable/integration/secret_retrieval/__init__.py +3 -0
- uncountable/integration/secret_retrieval/retrieve_secret.py +93 -0
- uncountable/integration/server.py +117 -0
- uncountable/integration/telemetry.py +209 -0
- uncountable/integration/webhook_server/entrypoint.py +170 -0
- uncountable/types/__init__.py +151 -5
- uncountable/types/api/batch/execute_batch.py +15 -7
- uncountable/types/api/batch/execute_batch_load_async.py +42 -0
- uncountable/types/api/chemical/__init__.py +1 -0
- uncountable/types/api/chemical/convert_chemical_formats.py +63 -0
- uncountable/types/api/entity/create_entities.py +23 -10
- uncountable/types/api/entity/create_entity.py +21 -12
- uncountable/types/api/entity/get_entities_data.py +19 -29
- uncountable/types/api/entity/grant_entity_permissions.py +48 -0
- uncountable/types/api/entity/list_entities.py +28 -20
- uncountable/types/api/entity/lock_entity.py +45 -0
- uncountable/types/api/entity/resolve_entity_ids.py +19 -7
- uncountable/types/api/entity/set_entity_field_values.py +44 -0
- uncountable/types/api/entity/set_values.py +13 -28
- uncountable/types/api/entity/transition_entity_phase.py +80 -0
- 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 +44 -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/__init__.py +1 -0
- uncountable/types/api/id_source/list_id_source.py +56 -0
- uncountable/types/api/id_source/match_id_source.py +54 -0
- uncountable/types/api/input_groups/get_input_group_names.py +18 -7
- uncountable/types/api/inputs/create_inputs.py +25 -24
- uncountable/types/api/inputs/get_input_data.py +37 -31
- uncountable/types/api/inputs/get_input_names.py +20 -9
- uncountable/types/api/inputs/get_inputs_data.py +33 -27
- uncountable/types/api/inputs/set_input_attribute_values.py +18 -13
- 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/material_families/__init__.py +1 -0
- uncountable/types/api/material_families/update_entity_material_families.py +48 -0
- uncountable/types/api/outputs/get_output_data.py +38 -29
- uncountable/types/api/outputs/get_output_names.py +20 -9
- uncountable/types/api/outputs/resolve_output_conditions.py +23 -10
- uncountable/types/api/permissions/__init__.py +1 -0
- uncountable/types/api/permissions/set_core_permissions.py +105 -0
- uncountable/types/api/project/get_projects.py +23 -19
- uncountable/types/api/project/get_projects_data.py +26 -43
- uncountable/types/api/recipe_links/__init__.py +1 -0
- uncountable/types/api/recipe_links/create_recipe_link.py +46 -0
- uncountable/types/api/recipe_links/remove_recipe_link.py +45 -0
- uncountable/types/api/recipe_metadata/get_recipe_metadata_data.py +21 -10
- uncountable/types/api/recipes/add_recipe_to_project.py +42 -0
- uncountable/types/api/recipes/archive_recipes.py +42 -0
- uncountable/types/api/recipes/associate_recipe_as_input.py +44 -0
- uncountable/types/api/recipes/associate_recipe_as_lot.py +43 -0
- uncountable/types/api/recipes/clear_recipe_outputs.py +42 -0
- uncountable/types/api/recipes/create_recipe.py +51 -0
- uncountable/types/api/recipes/create_recipes.py +25 -24
- uncountable/types/api/recipes/disassociate_recipe_as_input.py +42 -0
- uncountable/types/api/recipes/edit_recipe_inputs.py +283 -0
- uncountable/types/api/recipes/get_column_calculation_values.py +58 -0
- uncountable/types/api/recipes/get_curve.py +13 -27
- uncountable/types/api/recipes/get_recipe_calculations.py +21 -21
- uncountable/types/api/recipes/get_recipe_links.py +14 -6
- uncountable/types/api/recipes/get_recipe_names.py +18 -7
- uncountable/types/api/recipes/get_recipe_output_metadata.py +18 -19
- uncountable/types/api/recipes/get_recipes_data.py +83 -144
- uncountable/types/api/recipes/lock_recipes.py +63 -0
- uncountable/types/api/recipes/remove_recipe_from_project.py +42 -0
- uncountable/types/api/recipes/set_recipe_inputs.py +21 -11
- uncountable/types/api/recipes/set_recipe_metadata.py +43 -0
- uncountable/types/api/recipes/set_recipe_output_annotations.py +115 -0
- uncountable/types/api/recipes/set_recipe_output_file.py +56 -0
- uncountable/types/api/recipes/set_recipe_outputs.py +28 -15
- uncountable/types/api/recipes/set_recipe_tags.py +109 -0
- uncountable/types/api/recipes/unarchive_recipes.py +41 -0
- uncountable/types/api/recipes/unlock_recipes.py +50 -0
- uncountable/types/api/triggers/__init__.py +1 -0
- uncountable/types/api/triggers/run_trigger.py +43 -0
- uncountable/types/api/uploader/__init__.py +1 -0
- uncountable/types/api/uploader/invoke_uploader.py +47 -0
- uncountable/types/async_batch.py +13 -0
- uncountable/types/async_batch_processor.py +384 -0
- uncountable/types/async_batch_t.py +97 -0
- uncountable/types/async_jobs.py +9 -0
- uncountable/types/async_jobs_t.py +53 -0
- uncountable/types/auth_retrieval.py +12 -0
- uncountable/types/auth_retrieval_t.py +75 -0
- uncountable/types/base.py +5 -78
- uncountable/types/base_t.py +85 -0
- uncountable/types/calculations.py +8 -0
- uncountable/types/calculations_t.py +27 -0
- uncountable/types/chemical_structure.py +8 -0
- uncountable/types/chemical_structure_t.py +28 -0
- uncountable/types/client_base.py +1115 -76
- uncountable/types/client_config.py +8 -0
- uncountable/types/client_config_t.py +26 -0
- uncountable/types/curves.py +10 -0
- uncountable/types/curves_t.py +51 -0
- uncountable/types/entity.py +8 -266
- uncountable/types/entity_t.py +393 -0
- uncountable/types/experiment_groups.py +8 -0
- uncountable/types/experiment_groups_t.py +27 -0
- uncountable/types/field_values.py +17 -23
- uncountable/types/field_values_t.py +204 -0
- uncountable/types/fields.py +8 -0
- uncountable/types/fields_t.py +28 -0
- uncountable/types/generic_upload.py +15 -0
- uncountable/types/generic_upload_t.py +119 -0
- uncountable/types/id_source.py +12 -0
- uncountable/types/id_source_t.py +68 -0
- uncountable/types/identifier.py +11 -0
- uncountable/types/identifier_t.py +63 -0
- uncountable/types/input_attributes.py +8 -0
- uncountable/types/input_attributes_t.py +30 -0
- uncountable/types/inputs.py +11 -0
- uncountable/types/inputs_t.py +83 -0
- uncountable/types/integration_server.py +9 -0
- uncountable/types/integration_server_t.py +42 -0
- uncountable/types/job_definition.py +27 -0
- uncountable/types/job_definition_t.py +260 -0
- uncountable/types/outputs.py +8 -0
- uncountable/types/outputs_t.py +30 -0
- uncountable/types/overrides.py +10 -0
- uncountable/types/overrides_t.py +49 -0
- uncountable/types/permissions.py +8 -0
- uncountable/types/permissions_t.py +46 -0
- uncountable/types/phases.py +8 -0
- uncountable/types/phases_t.py +27 -0
- uncountable/types/post_base.py +8 -0
- uncountable/types/post_base_t.py +30 -0
- uncountable/types/queued_job.py +16 -0
- uncountable/types/queued_job_t.py +123 -0
- uncountable/types/recipe_identifiers.py +12 -0
- uncountable/types/recipe_identifiers_t.py +76 -0
- uncountable/types/recipe_inputs.py +9 -0
- uncountable/types/recipe_inputs_t.py +30 -0
- uncountable/types/recipe_links.py +4 -44
- uncountable/types/recipe_links_t.py +54 -0
- uncountable/types/recipe_metadata.py +10 -0
- uncountable/types/recipe_metadata_t.py +58 -0
- uncountable/types/recipe_output_metadata.py +8 -0
- uncountable/types/recipe_output_metadata_t.py +28 -0
- uncountable/types/recipe_tags.py +8 -0
- uncountable/types/recipe_tags_t.py +27 -0
- uncountable/types/recipe_workflow_steps.py +14 -0
- uncountable/types/recipe_workflow_steps_t.py +95 -0
- uncountable/types/recipes.py +8 -0
- uncountable/types/recipes_t.py +25 -0
- uncountable/types/response.py +8 -0
- uncountable/types/response_t.py +26 -0
- uncountable/types/secret_retrieval.py +12 -0
- uncountable/types/secret_retrieval_t.py +75 -0
- uncountable/types/units.py +8 -0
- uncountable/types/units_t.py +27 -0
- uncountable/types/users.py +8 -0
- uncountable/types/users_t.py +28 -0
- uncountable/types/webhook_job.py +9 -0
- uncountable/types/webhook_job_t.py +37 -0
- uncountable/types/workflows.py +9 -0
- uncountable/types/workflows_t.py +39 -0
- UncountablePythonSDK-0.0.7.dist-info/METADATA +0 -27
- UncountablePythonSDK-0.0.7.dist-info/RECORD +0 -119
- examples/recipe-import/importer.py +0 -39
- type_spec/external/api/batch/execute_batch.yaml +0 -56
- type_spec/external/api/entity/create_entities.yaml +0 -33
- type_spec/external/api/entity/create_entity.yaml +0 -39
- type_spec/external/api/entity/get_entities_data.yaml +0 -55
- type_spec/external/api/entity/list_entities.yaml +0 -62
- type_spec/external/api/entity/resolve_entity_ids.yaml +0 -29
- type_spec/external/api/entity/set_values.yaml +0 -45
- type_spec/external/api/input_groups/get_input_group_names.yaml +0 -29
- type_spec/external/api/inputs/create_inputs.yaml +0 -61
- type_spec/external/api/inputs/get_input_data.yaml +0 -108
- type_spec/external/api/inputs/get_input_names.yaml +0 -38
- type_spec/external/api/inputs/get_inputs_data.yaml +0 -95
- type_spec/external/api/inputs/set_input_attribute_values.yaml +0 -37
- type_spec/external/api/outputs/get_output_data.yaml +0 -103
- 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/project/get_projects.yaml +0 -52
- type_spec/external/api/project/get_projects_data.yaml +0 -86
- type_spec/external/api/recipe_metadata/get_recipe_metadata_data.yaml +0 -41
- type_spec/external/api/recipes/create_recipes.yaml +0 -60
- type_spec/external/api/recipes/get_curve.yaml +0 -50
- type_spec/external/api/recipes/get_recipe_calculations.yaml +0 -49
- 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 -49
- type_spec/external/api/recipes/get_recipes_data.yaml +0 -372
- type_spec/external/api/recipes/set_recipe_inputs.yaml +0 -36
- type_spec/external/api/recipes/set_recipe_outputs.yaml +0 -56
pkgs/type_spec/config.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import os
|
|
2
|
+
from collections.abc import Callable, Mapping
|
|
2
3
|
from dataclasses import dataclass
|
|
3
|
-
from
|
|
4
|
-
from typing import Callable, Mapping, Self, Type, TypeVar
|
|
4
|
+
from typing import Self, TypeVar
|
|
5
5
|
|
|
6
|
-
import yaml
|
|
6
|
+
from pkgs.serialization import yaml
|
|
7
7
|
|
|
8
8
|
ConfigValueType = str | None | Mapping[str, str | None] | list[str]
|
|
9
9
|
|
|
@@ -34,6 +34,9 @@ class TypeScriptConfig(BaseLanguageConfig):
|
|
|
34
34
|
routes_output: str # folder for generate route files will be located.
|
|
35
35
|
type_info_output: str # folder for generated type info files
|
|
36
36
|
id_source_output: str | None = None # folder for emitted id source maps.
|
|
37
|
+
endpoint_to_frontend_app_type: dict[
|
|
38
|
+
str, str
|
|
39
|
+
] # map from api_endpoint to frontend app type
|
|
37
40
|
|
|
38
41
|
def __post_init__(self: Self) -> None:
|
|
39
42
|
self.routes_output = self.routes_output
|
|
@@ -43,6 +46,11 @@ class TypeScriptConfig(BaseLanguageConfig):
|
|
|
43
46
|
if self.id_source_output is not None
|
|
44
47
|
else None
|
|
45
48
|
)
|
|
49
|
+
self.endpoint_to_frontend_app_type = _parse_string_lookup(
|
|
50
|
+
"typescript_endpoint_to_frontend_app_type",
|
|
51
|
+
self.endpoint_to_frontend_app_type,
|
|
52
|
+
lambda x: x,
|
|
53
|
+
)
|
|
46
54
|
|
|
47
55
|
|
|
48
56
|
@dataclass(kw_only=True)
|
|
@@ -55,6 +63,7 @@ class PythonConfig(BaseLanguageConfig):
|
|
|
55
63
|
emit_api_argument_lookup: bool = (
|
|
56
64
|
False # emit a lookup for api endpoint path to argument type.
|
|
57
65
|
)
|
|
66
|
+
emit_async_batch_processor: bool = False # emit the async batch wrapping functions
|
|
58
67
|
emit_client_class: bool = False # emit the base class for the api client
|
|
59
68
|
all_named_type_exports: bool = False # emit __all__ for all named type exports
|
|
60
69
|
sdk_endpoints_only: bool = False # only emit is_sdk endpoints
|
|
@@ -85,6 +94,7 @@ class OpenAPIConfig(BaseLanguageConfig):
|
|
|
85
94
|
|
|
86
95
|
@dataclass(kw_only=True)
|
|
87
96
|
class Config:
|
|
97
|
+
top_namespace: str
|
|
88
98
|
type_spec_types: list[str] # folders containing the yaml type spec definitions
|
|
89
99
|
api_endpoint: dict[str, str]
|
|
90
100
|
# languages
|
|
@@ -96,30 +106,19 @@ class Config:
|
|
|
96
106
|
_T = TypeVar("_T")
|
|
97
107
|
|
|
98
108
|
|
|
99
|
-
def _parse_language(config_class:
|
|
109
|
+
def _parse_language(config_class: type[_T], raw_value: ConfigValueType) -> _T:
|
|
100
110
|
assert isinstance(raw_value, dict), "expecting language config to have key/values."
|
|
101
111
|
return config_class(**raw_value)
|
|
102
112
|
|
|
103
113
|
|
|
104
|
-
def _decimal_constructor(loader, node): # type:ignore
|
|
105
|
-
value = loader.construct_scalar(node)
|
|
106
|
-
return Decimal(value)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
# A semi-acceptable patch to force a number to be parsed as a decimal, since pyyaml
|
|
110
|
-
# parses them as lossy floats otherwise. Though a bit ugly, at least this way we have
|
|
111
|
-
# support for decimal constants
|
|
112
|
-
yaml.SafeLoader.add_constructor("!decimal", _decimal_constructor)
|
|
113
|
-
|
|
114
|
-
|
|
115
114
|
def parse_yaml_config(config_file: str) -> Config:
|
|
116
115
|
with open(config_file, encoding="utf-8") as input:
|
|
117
116
|
raw_config: dict[str, ConfigValueType] = yaml.safe_load(input)
|
|
118
117
|
|
|
119
118
|
raw_type_spec_types = raw_config["type_spec_types"]
|
|
120
|
-
assert isinstance(
|
|
121
|
-
|
|
122
|
-
)
|
|
119
|
+
assert isinstance(raw_type_spec_types, list), (
|
|
120
|
+
"type_spec_types, must be a list of folders"
|
|
121
|
+
)
|
|
123
122
|
type_spec_types = [os.path.abspath(folder) for folder in raw_type_spec_types]
|
|
124
123
|
|
|
125
124
|
api_endpoint = _parse_string_lookup(
|
|
@@ -135,10 +134,16 @@ def parse_yaml_config(config_file: str) -> Config:
|
|
|
135
134
|
python = _parse_language(PythonConfig, raw_config["python"])
|
|
136
135
|
raw_open_api = raw_config.get("open_api")
|
|
137
136
|
open_api = (
|
|
138
|
-
_parse_language(OpenAPIConfig, raw_open_api)
|
|
137
|
+
_parse_language(OpenAPIConfig, raw_open_api)
|
|
138
|
+
if raw_open_api is not None
|
|
139
|
+
else None
|
|
139
140
|
)
|
|
140
141
|
|
|
142
|
+
top_namespace = raw_config["top_namespace"]
|
|
143
|
+
assert isinstance(top_namespace, str)
|
|
144
|
+
|
|
141
145
|
return Config(
|
|
146
|
+
top_namespace=top_namespace,
|
|
142
147
|
type_spec_types=type_spec_types,
|
|
143
148
|
api_endpoint=api_endpoint,
|
|
144
149
|
typescript=typescript,
|
pkgs/type_spec/emit_io_ts.py
CHANGED
|
@@ -118,11 +118,14 @@ def refer_to_io_ts(
|
|
|
118
118
|
stype: builder.SpecType,
|
|
119
119
|
) -> str:
|
|
120
120
|
if isinstance(stype, builder.SpecTypeInstance):
|
|
121
|
-
if
|
|
121
|
+
if (
|
|
122
|
+
stype.defn_type.name == builder.BaseTypeName.s_list
|
|
123
|
+
or stype.defn_type.name == builder.BaseTypeName.s_readonly_array
|
|
124
|
+
):
|
|
122
125
|
spec = refer_to_io_ts(ctx, stype.parameters[0])
|
|
123
126
|
return f"IO.array({spec})"
|
|
124
127
|
if stype.defn_type.name == builder.BaseTypeName.s_union:
|
|
125
|
-
return f
|
|
128
|
+
return f"IO.union([{', '.join([refer_to_io_ts(ctx, p) for p in stype.parameters])}])"
|
|
126
129
|
if stype.defn_type.name == builder.BaseTypeName.s_optional:
|
|
127
130
|
return f"IO.optional({refer_to_io_ts(ctx, stype.parameters[0])})"
|
|
128
131
|
if stype.defn_type.name == builder.BaseTypeName.s_tuple:
|
pkgs/type_spec/emit_open_api.py
CHANGED
|
@@ -5,20 +5,26 @@ WORK-IN-PROGRESS, DON'T USE!
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import dataclasses
|
|
8
|
+
import json
|
|
8
9
|
import re
|
|
9
|
-
from typing import cast
|
|
10
|
+
from typing import Collection, cast
|
|
10
11
|
|
|
11
|
-
import yaml
|
|
12
|
+
from pkgs.serialization import yaml
|
|
13
|
+
from pkgs.serialization_util import serialize_for_api
|
|
12
14
|
|
|
13
15
|
from . import builder, util
|
|
16
|
+
from .builder import EndpointGuideKey, RootGuideKey
|
|
14
17
|
from .config import OpenAPIConfig
|
|
15
18
|
from .emit_open_api_util import (
|
|
16
19
|
MODIFY_NOTICE,
|
|
17
20
|
EmitOpenAPIContext,
|
|
18
21
|
EmitOpenAPIEndpoint,
|
|
22
|
+
EmitOpenAPIEndpointExample,
|
|
19
23
|
EmitOpenAPIGlobalContext,
|
|
24
|
+
EmitOpenAPIGuide,
|
|
20
25
|
EmitOpenAPIPath,
|
|
21
26
|
EmitOpenAPIServer,
|
|
27
|
+
EmitOpenAPIStabilityLevel,
|
|
22
28
|
EmitOpenAPITag,
|
|
23
29
|
GlobalContextInfo,
|
|
24
30
|
TagGroupToNamedTags,
|
|
@@ -59,7 +65,7 @@ base_name_map = {
|
|
|
59
65
|
def _rewrite_with_notice(
|
|
60
66
|
file_path: str, file_content: str, *, notice: str = MODIFY_NOTICE
|
|
61
67
|
) -> bool:
|
|
62
|
-
pattern = re.compile("^\S", re.MULTILINE)
|
|
68
|
+
pattern = re.compile(r"^\S", re.MULTILINE)
|
|
63
69
|
|
|
64
70
|
file_lines = file_content.split("\n")
|
|
65
71
|
comment_lines = []
|
|
@@ -74,12 +80,26 @@ def _rewrite_with_notice(
|
|
|
74
80
|
return util.rewrite_file(file_path, f"{notice}\n{modified_file_content}")
|
|
75
81
|
|
|
76
82
|
|
|
77
|
-
def
|
|
78
|
-
|
|
83
|
+
def _write_guide_as_html(guide: EmitOpenAPIGuide, *, is_open: bool) -> str:
|
|
84
|
+
return f"""
|
|
85
|
+
<details id="{guide.ref_name}" {"open" if is_open else ""}>
|
|
86
|
+
<summary>{guide.title}</summary>
|
|
87
|
+
{guide.html_content}
|
|
88
|
+
</details>"""
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _open_api_info(
|
|
92
|
+
config: OpenAPIConfig, guides: list[EmitOpenAPIGuide]
|
|
93
|
+
) -> GlobalContextInfo:
|
|
94
|
+
full_guides = "<br/>".join([
|
|
95
|
+
_write_guide_as_html(guide, is_open=True)
|
|
96
|
+
for guide in sorted(guides, key=lambda g: g.ref_name)
|
|
97
|
+
])
|
|
98
|
+
full_description = f"{config.description}<br/>{full_guides}"
|
|
79
99
|
info: GlobalContextInfo = dict()
|
|
80
100
|
info["version"] = "1.0.0"
|
|
81
101
|
info["title"] = "Uncountable API Documentation"
|
|
82
|
-
info["description"] =
|
|
102
|
+
info["description"] = full_description
|
|
83
103
|
info["x-logo"] = {"url": "../static/images/logo_blue.png", "altText": "Logo"}
|
|
84
104
|
return info
|
|
85
105
|
|
|
@@ -90,15 +110,25 @@ def _open_api_servers(config: OpenAPIConfig) -> list[EmitOpenAPIServer]:
|
|
|
90
110
|
|
|
91
111
|
|
|
92
112
|
def emit_open_api(builder: builder.SpecBuilder, *, config: OpenAPIConfig) -> None:
|
|
113
|
+
root_guides = builder.guides.get(RootGuideKey(), [])
|
|
114
|
+
openapi_guides = [
|
|
115
|
+
EmitOpenAPIGuide(
|
|
116
|
+
ref_name=guide.ref_name, title=guide.title, html_content=guide.html_content
|
|
117
|
+
)
|
|
118
|
+
for guide in root_guides
|
|
119
|
+
]
|
|
93
120
|
gctx = EmitOpenAPIGlobalContext(
|
|
94
121
|
version="3.0.0",
|
|
95
|
-
info=_open_api_info(config),
|
|
122
|
+
info=_open_api_info(config, openapi_guides),
|
|
96
123
|
servers=_open_api_servers(config),
|
|
97
124
|
)
|
|
98
125
|
|
|
99
126
|
for namespace in sorted(builder.namespaces.values(), key=lambda ns: ns.name):
|
|
100
127
|
ctx = EmitOpenAPIContext(namespace=namespace)
|
|
101
128
|
|
|
129
|
+
if ctx.namespace.endpoint is not None and ctx.namespace.endpoint.is_beta:
|
|
130
|
+
continue
|
|
131
|
+
|
|
102
132
|
if ctx.namespace.name == "base":
|
|
103
133
|
# TODO: add additional base defintions here
|
|
104
134
|
ctx.types["ObjectId"] = OpenAPIIntegerT()
|
|
@@ -109,6 +139,8 @@ def emit_open_api(builder: builder.SpecBuilder, *, config: OpenAPIConfig) -> Non
|
|
|
109
139
|
ctx,
|
|
110
140
|
namespace=namespace,
|
|
111
141
|
config=config,
|
|
142
|
+
examples=builder.examples,
|
|
143
|
+
guides=builder.guides,
|
|
112
144
|
)
|
|
113
145
|
|
|
114
146
|
_rewrite_with_notice(
|
|
@@ -138,7 +170,161 @@ def _serialize_global_context(ctx: EmitOpenAPIGlobalContext) -> str:
|
|
|
138
170
|
oa_paths[path.path] = {"$ref": path.ref}
|
|
139
171
|
oa_root["paths"] = oa_paths
|
|
140
172
|
|
|
141
|
-
return yaml.
|
|
173
|
+
return yaml.dumps(oa_root, sort_keys=False)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _is_empty_object_type(typ: OpenAPIType) -> bool:
|
|
177
|
+
if not isinstance(typ, OpenAPIObjectType):
|
|
178
|
+
return False
|
|
179
|
+
return len(typ.properties) == 0
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
_QUERY_PARM_METHODS = ("get", "head", "options")
|
|
183
|
+
_REQUEST_BODY_METHODS = ("put", "post", "patch", "delete")
|
|
184
|
+
|
|
185
|
+
ApiSchema = dict[str, "ApiSchema"] | Collection["ApiSchema"] | str | bool
|
|
186
|
+
DictApiSchema = dict[str, ApiSchema]
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _emit_endpoint_argument_examples(
|
|
190
|
+
examples: list[EmitOpenAPIEndpointExample],
|
|
191
|
+
) -> DictApiSchema:
|
|
192
|
+
if len(examples) == 0:
|
|
193
|
+
return {}
|
|
194
|
+
|
|
195
|
+
response_examples = {}
|
|
196
|
+
for example in examples:
|
|
197
|
+
response_examples[example.ref_name] = {
|
|
198
|
+
"summary": example.summary,
|
|
199
|
+
"description": example.description,
|
|
200
|
+
"value": example.arguments,
|
|
201
|
+
}
|
|
202
|
+
return {"examples": response_examples}
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _emit_endpoint_parameter_examples(
|
|
206
|
+
examples: list[EmitOpenAPIEndpointExample],
|
|
207
|
+
) -> DictApiSchema:
|
|
208
|
+
if len(examples) == 0:
|
|
209
|
+
return {}
|
|
210
|
+
|
|
211
|
+
paramater_examples = []
|
|
212
|
+
comment_new_line = "\n// "
|
|
213
|
+
new_line = "\n"
|
|
214
|
+
for example in examples:
|
|
215
|
+
javascript_description = (
|
|
216
|
+
f"// {comment_new_line.join(example.description.split(new_line))}"
|
|
217
|
+
)
|
|
218
|
+
javascript_json_payload = f"{json.dumps(example.arguments, indent=2)}"
|
|
219
|
+
paramater_examples.append({
|
|
220
|
+
"lang": "JavaScript",
|
|
221
|
+
"label": f"Payload - {example.summary}",
|
|
222
|
+
"source": f"{javascript_description}\n{javascript_json_payload}",
|
|
223
|
+
})
|
|
224
|
+
return {"x-codeSamples": paramater_examples}
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _emit_endpoint_parameters(
|
|
228
|
+
endpoint: EmitOpenAPIEndpoint,
|
|
229
|
+
argument_type: OpenAPIType | None,
|
|
230
|
+
examples: list[EmitOpenAPIEndpointExample],
|
|
231
|
+
) -> DictApiSchema:
|
|
232
|
+
if (
|
|
233
|
+
endpoint.method.lower() not in _QUERY_PARM_METHODS
|
|
234
|
+
or argument_type is None
|
|
235
|
+
or _is_empty_object_type(argument_type)
|
|
236
|
+
):
|
|
237
|
+
return {}
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
"parameters": [
|
|
241
|
+
{
|
|
242
|
+
"name": "data",
|
|
243
|
+
"required": True,
|
|
244
|
+
"in": "query",
|
|
245
|
+
"content": {
|
|
246
|
+
"application/json": {
|
|
247
|
+
"schema": {"$ref": "#/components/schema/Arguments"}
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
}
|
|
251
|
+
]
|
|
252
|
+
} | _emit_endpoint_parameter_examples(examples)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _emit_is_beta(is_beta: bool) -> DictApiSchema:
|
|
256
|
+
if is_beta:
|
|
257
|
+
return {"x-beta": True}
|
|
258
|
+
return {}
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _emit_stability_level(
|
|
262
|
+
stability_level: EmitOpenAPIStabilityLevel | None,
|
|
263
|
+
) -> DictApiSchema:
|
|
264
|
+
if stability_level is not None:
|
|
265
|
+
return {"x-stability-level": str(stability_level)}
|
|
266
|
+
return {}
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _emit_endpoint_request_body(
|
|
270
|
+
endpoint: EmitOpenAPIEndpoint,
|
|
271
|
+
arguments_type: OpenAPIType | None,
|
|
272
|
+
examples: list[EmitOpenAPIEndpointExample],
|
|
273
|
+
) -> DictApiSchema:
|
|
274
|
+
if (
|
|
275
|
+
endpoint.method.lower() not in _REQUEST_BODY_METHODS
|
|
276
|
+
or arguments_type is None
|
|
277
|
+
or _is_empty_object_type(arguments_type)
|
|
278
|
+
):
|
|
279
|
+
return {}
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
"requestBody": {
|
|
283
|
+
"content": {
|
|
284
|
+
"application/json": {
|
|
285
|
+
"schema": {
|
|
286
|
+
"type": "object",
|
|
287
|
+
"title": "Body",
|
|
288
|
+
"required": ["data"],
|
|
289
|
+
"properties": {
|
|
290
|
+
"data": {"$ref": "#/components/schema/Arguments"}
|
|
291
|
+
},
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
| _emit_endpoint_argument_examples(examples)
|
|
295
|
+
},
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _emit_endpoint_response_examples(
|
|
301
|
+
examples: list[EmitOpenAPIEndpointExample],
|
|
302
|
+
) -> dict[str, dict[str, object]]:
|
|
303
|
+
if len(examples) == 0:
|
|
304
|
+
return {}
|
|
305
|
+
|
|
306
|
+
response_examples: dict[str, object] = {}
|
|
307
|
+
for example in examples:
|
|
308
|
+
response_examples[example.ref_name] = {
|
|
309
|
+
"summary": example.summary,
|
|
310
|
+
"description": example.description,
|
|
311
|
+
"value": example.data,
|
|
312
|
+
}
|
|
313
|
+
return {"examples": response_examples}
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def _emit_endpoint_description(
|
|
317
|
+
description: str, guides: list[EmitOpenAPIGuide]
|
|
318
|
+
) -> dict[str, str]:
|
|
319
|
+
full_guides = "<br/>".join([
|
|
320
|
+
_write_guide_as_html(guide, is_open=False)
|
|
321
|
+
for guide in sorted(guides, key=lambda g: g.ref_name)
|
|
322
|
+
])
|
|
323
|
+
return {
|
|
324
|
+
"description": description
|
|
325
|
+
if len(guides) == 0
|
|
326
|
+
else f"{description}<br/>{full_guides}"
|
|
327
|
+
}
|
|
142
328
|
|
|
143
329
|
|
|
144
330
|
def _emit_namespace(
|
|
@@ -147,35 +333,53 @@ def _emit_namespace(
|
|
|
147
333
|
namespace: builder.SpecNamespace,
|
|
148
334
|
*,
|
|
149
335
|
config: OpenAPIConfig,
|
|
336
|
+
examples: dict[str, list[builder.SpecEndpointExample]],
|
|
337
|
+
guides: dict[builder.SpecGuideKey, list[builder.SpecGuide]],
|
|
150
338
|
) -> None:
|
|
151
339
|
for stype in namespace.types.values():
|
|
152
340
|
_emit_type(ctx, stype, config=config)
|
|
153
341
|
|
|
154
342
|
if namespace.endpoint is not None:
|
|
155
|
-
|
|
343
|
+
endpoint_examples = examples.get(namespace.endpoint.resolved_path, [])
|
|
344
|
+
endpoint_guides = guides.get(
|
|
345
|
+
EndpointGuideKey(path=namespace.endpoint.resolved_path), []
|
|
346
|
+
)
|
|
347
|
+
_emit_endpoint(
|
|
348
|
+
gctx, ctx, namespace, namespace.endpoint, endpoint_examples, endpoint_guides
|
|
349
|
+
)
|
|
156
350
|
|
|
157
351
|
oa_components: dict[str, object] = dict()
|
|
158
352
|
|
|
159
353
|
if ctx.endpoint is not None:
|
|
160
354
|
endpoint = ctx.endpoint
|
|
161
|
-
|
|
355
|
+
argument_type = ctx.types.get("Arguments")
|
|
162
356
|
oa_endpoint = dict()
|
|
163
|
-
oa_endpoint[endpoint.method] =
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
357
|
+
oa_endpoint[endpoint.method] = (
|
|
358
|
+
{
|
|
359
|
+
"tags": endpoint.tags,
|
|
360
|
+
"summary": endpoint.summary,
|
|
361
|
+
}
|
|
362
|
+
| _emit_endpoint_description(endpoint.description, ctx.endpoint.guides)
|
|
363
|
+
| _emit_is_beta(endpoint.is_beta)
|
|
364
|
+
| _emit_stability_level(endpoint.stability_level)
|
|
365
|
+
| _emit_endpoint_parameters(endpoint, argument_type, ctx.endpoint.examples)
|
|
366
|
+
| _emit_endpoint_request_body(
|
|
367
|
+
endpoint, argument_type, ctx.endpoint.examples
|
|
368
|
+
)
|
|
369
|
+
| {
|
|
370
|
+
"responses": {
|
|
371
|
+
"200": {
|
|
372
|
+
"description": "OK",
|
|
373
|
+
"content": {
|
|
374
|
+
"application/json": {
|
|
375
|
+
"schema": {"$ref": "#/components/schema/Data"}
|
|
376
|
+
}
|
|
377
|
+
| _emit_endpoint_response_examples(ctx.endpoint.examples)
|
|
378
|
+
},
|
|
379
|
+
}
|
|
380
|
+
},
|
|
381
|
+
}
|
|
382
|
+
)
|
|
179
383
|
oa_components["endpoint"] = oa_endpoint
|
|
180
384
|
|
|
181
385
|
types = ctx.types
|
|
@@ -209,15 +413,12 @@ def _emit_namespace(
|
|
|
209
413
|
|
|
210
414
|
oa_components["schema"] = cast(
|
|
211
415
|
object,
|
|
212
|
-
{
|
|
213
|
-
name: (value.asdict() if name != "Arguments" else value.asarguments())
|
|
214
|
-
for name, value in types.items()
|
|
215
|
-
},
|
|
416
|
+
{name: value.asdict() for name, value in types.items()},
|
|
216
417
|
)
|
|
217
418
|
|
|
218
419
|
path = f"{config.types_output}/common/{'/'.join(namespace.path)}.yaml"
|
|
219
420
|
oa_namespace = {"components": oa_components}
|
|
220
|
-
_rewrite_with_notice(path, yaml.
|
|
421
|
+
_rewrite_with_notice(path, yaml.dumps(oa_namespace, sort_keys=False))
|
|
221
422
|
|
|
222
423
|
|
|
223
424
|
def _emit_type(
|
|
@@ -244,6 +445,12 @@ def _emit_type(
|
|
|
244
445
|
ctx.types[stype.name] = open_api_type(ctx, stype.alias, config=config)
|
|
245
446
|
return
|
|
246
447
|
|
|
448
|
+
if isinstance(stype, builder.SpecTypeDefnUnion):
|
|
449
|
+
ctx.types[stype.name] = open_api_type(
|
|
450
|
+
ctx, stype.get_backing_type(), config=config
|
|
451
|
+
)
|
|
452
|
+
return
|
|
453
|
+
|
|
247
454
|
if isinstance(stype, builder.SpecTypeDefnStringEnum):
|
|
248
455
|
# TODO: check that these are always string enums
|
|
249
456
|
# IMPROVE: reflect the enum names in the description
|
|
@@ -321,6 +528,8 @@ def _emit_endpoint(
|
|
|
321
528
|
ctx: EmitOpenAPIContext,
|
|
322
529
|
namespace: builder.SpecNamespace,
|
|
323
530
|
endpoint: builder.SpecEndpoint,
|
|
531
|
+
endpoint_examples: list[builder.SpecEndpointExample],
|
|
532
|
+
endpoint_guides: list[builder.SpecGuide],
|
|
324
533
|
) -> None:
|
|
325
534
|
assert namespace.endpoint is not None
|
|
326
535
|
assert namespace.path[0] == "api"
|
|
@@ -364,11 +573,32 @@ def _emit_endpoint(
|
|
|
364
573
|
description = f"**[External API-Endpoint]** <br/> {description}"
|
|
365
574
|
|
|
366
575
|
path_cutoff = min(3, len(namespace.path) - 1)
|
|
576
|
+
|
|
367
577
|
ctx.endpoint = EmitOpenAPIEndpoint(
|
|
368
578
|
method=namespace.endpoint.method.lower(),
|
|
369
579
|
tags=[tag_name],
|
|
370
580
|
summary=f"{'/'.join(namespace.path[path_cutoff:])}",
|
|
371
581
|
description=description,
|
|
582
|
+
is_beta=namespace.endpoint.is_beta,
|
|
583
|
+
stability_level=namespace.endpoint.stability_level,
|
|
584
|
+
examples=[
|
|
585
|
+
EmitOpenAPIEndpointExample(
|
|
586
|
+
ref_name=f"ex_{i}",
|
|
587
|
+
summary=example.summary,
|
|
588
|
+
description=example.description,
|
|
589
|
+
arguments=serialize_for_api(example.arguments),
|
|
590
|
+
data=serialize_for_api(example.data),
|
|
591
|
+
)
|
|
592
|
+
for i, example in enumerate(endpoint_examples)
|
|
593
|
+
],
|
|
594
|
+
guides=[
|
|
595
|
+
EmitOpenAPIGuide(
|
|
596
|
+
ref_name=guide.ref_name,
|
|
597
|
+
title=guide.title,
|
|
598
|
+
html_content=guide.html_content,
|
|
599
|
+
)
|
|
600
|
+
for guide in endpoint_guides
|
|
601
|
+
],
|
|
372
602
|
)
|
|
373
603
|
|
|
374
604
|
|
|
@@ -436,6 +666,10 @@ def open_api_type(
|
|
|
436
666
|
[open_api_type(ctx, p, config=config) for p in stype.parameters],
|
|
437
667
|
description="TupleType",
|
|
438
668
|
)
|
|
669
|
+
if stype.defn_type.name == builder.BaseTypeName.s_readonly_array:
|
|
670
|
+
return OpenAPIArrayType(
|
|
671
|
+
open_api_type(ctx, stype.parameters[0], config=config)
|
|
672
|
+
)
|
|
439
673
|
|
|
440
674
|
# TODO: generics are not supported by OpenAPI
|
|
441
675
|
# map to Free-Form Object and add description
|
|
@@ -460,5 +694,5 @@ def open_api_type(
|
|
|
460
694
|
ctx.namespaces.add(stype.namespace)
|
|
461
695
|
# external namespace resolution
|
|
462
696
|
return OpenAPIRefType(
|
|
463
|
-
source=f"{resolve_namespace_ref(ctx, stype.namespace,
|
|
697
|
+
source=f"{resolve_namespace_ref(source_path=ctx.namespace.path, ref_path=stype.namespace.path, ref='/components/schema')}/{stype.name}"
|
|
464
698
|
)
|
|
@@ -6,17 +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
|
-
from .config import OpenAPIConfig
|
|
13
13
|
from .open_api_util import OpenAPIType
|
|
14
14
|
|
|
15
15
|
MODIFY_NOTICE = "# DO NOT MODIFY -- This file is generated by type_spec"
|
|
16
16
|
|
|
17
|
-
GlobalContextInfo
|
|
18
|
-
TagGroupToNamedTags
|
|
19
|
-
TagPathsToRef
|
|
17
|
+
type GlobalContextInfo = dict[str, str | dict[str, str]]
|
|
18
|
+
type TagGroupToNamedTags = dict[str, str | list[str]]
|
|
19
|
+
type TagPathsToRef = dict[str, dict[str, str]]
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
@dataclass
|
|
@@ -44,6 +44,13 @@ class EmitOpenAPIServer:
|
|
|
44
44
|
url: str
|
|
45
45
|
|
|
46
46
|
|
|
47
|
+
@dataclass(kw_only=True)
|
|
48
|
+
class EmitOpenAPIGuide:
|
|
49
|
+
ref_name: str
|
|
50
|
+
title: str
|
|
51
|
+
html_content: str
|
|
52
|
+
|
|
53
|
+
|
|
47
54
|
@dataclass
|
|
48
55
|
class EmitOpenAPIGlobalContext:
|
|
49
56
|
version: str
|
|
@@ -57,12 +64,28 @@ class EmitOpenAPIGlobalContext:
|
|
|
57
64
|
paths: list[EmitOpenAPIPath] = field(default_factory=list)
|
|
58
65
|
|
|
59
66
|
|
|
67
|
+
@dataclass(kw_only=True)
|
|
68
|
+
class EmitOpenAPIEndpointExample:
|
|
69
|
+
ref_name: str
|
|
70
|
+
summary: str
|
|
71
|
+
description: str
|
|
72
|
+
arguments: dict[str, JsonValue]
|
|
73
|
+
data: dict[str, JsonValue]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
EmitOpenAPIStabilityLevel = builder.StabilityLevel
|
|
77
|
+
|
|
78
|
+
|
|
60
79
|
@dataclass
|
|
61
80
|
class EmitOpenAPIEndpoint:
|
|
62
81
|
method: str
|
|
63
82
|
tags: list[str]
|
|
64
83
|
summary: str
|
|
65
84
|
description: str
|
|
85
|
+
is_beta: bool
|
|
86
|
+
stability_level: EmitOpenAPIStabilityLevel | None
|
|
87
|
+
examples: list[EmitOpenAPIEndpointExample]
|
|
88
|
+
guides: list[EmitOpenAPIGuide]
|
|
66
89
|
|
|
67
90
|
|
|
68
91
|
@dataclass
|
|
@@ -75,12 +98,8 @@ class EmitOpenAPIContext:
|
|
|
75
98
|
|
|
76
99
|
|
|
77
100
|
def resolve_namespace_ref(
|
|
78
|
-
|
|
79
|
-
namespace: builder.SpecNamespace,
|
|
80
|
-
*,
|
|
81
|
-
config: OpenAPIConfig,
|
|
101
|
+
*, source_path: list[str], ref_path: list[str], ref: str
|
|
82
102
|
) -> str:
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
return f"{config.static_url_path}/common/{namespace.name}.yaml#/components/schema"
|
|
103
|
+
to_root = "/".join(".." for _ in source_path)
|
|
104
|
+
location = "/".join(ref_path)
|
|
105
|
+
return f"{to_root}/common/{location}.yaml#{ref}"
|