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/builder.py
CHANGED
|
@@ -9,22 +9,34 @@ import os
|
|
|
9
9
|
import re
|
|
10
10
|
from collections import defaultdict
|
|
11
11
|
from dataclasses import MISSING, dataclass
|
|
12
|
-
from enum import Enum, auto
|
|
13
|
-
from typing import Any,
|
|
12
|
+
from enum import Enum, StrEnum, auto
|
|
13
|
+
from typing import Any, Self
|
|
14
14
|
|
|
15
15
|
from . import util
|
|
16
16
|
from .util import parse_type_str, unused
|
|
17
17
|
|
|
18
|
-
RawDict =
|
|
18
|
+
RawDict = dict[Any, Any]
|
|
19
|
+
EndpointKey = str
|
|
19
20
|
|
|
20
21
|
|
|
21
|
-
class
|
|
22
|
+
class StabilityLevel(StrEnum):
|
|
23
|
+
"""These are currently used for open api,
|
|
24
|
+
see: https://github.com/Tufin/oasdiff/blob/main/docs/STABILITY.md
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
draft = "draft"
|
|
28
|
+
alpha = "alpha"
|
|
29
|
+
beta = "beta"
|
|
30
|
+
stable = "stable"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class PropertyExtant(StrEnum):
|
|
22
34
|
required = "required"
|
|
23
35
|
optional = "optional"
|
|
24
36
|
missing = "missing"
|
|
25
37
|
|
|
26
38
|
|
|
27
|
-
class PropertyConvertValue(
|
|
39
|
+
class PropertyConvertValue(StrEnum):
|
|
28
40
|
# Base conversion on underlying types
|
|
29
41
|
auto = "auto"
|
|
30
42
|
# Always convert the value (Not needed yet, thus not supported)
|
|
@@ -36,7 +48,7 @@ class PropertyConvertValue(str, Enum):
|
|
|
36
48
|
@dataclass
|
|
37
49
|
class SpecProperty:
|
|
38
50
|
name: str
|
|
39
|
-
label:
|
|
51
|
+
label: str | None
|
|
40
52
|
spec_type: SpecType
|
|
41
53
|
extant: PropertyExtant
|
|
42
54
|
convert_value: PropertyConvertValue
|
|
@@ -52,7 +64,7 @@ class SpecProperty:
|
|
|
52
64
|
ext_info: Any = None
|
|
53
65
|
|
|
54
66
|
|
|
55
|
-
class NameCase(
|
|
67
|
+
class NameCase(StrEnum):
|
|
56
68
|
convert = "convert"
|
|
57
69
|
preserve = "preserve"
|
|
58
70
|
# Upper-case in JavaScript, convert otherwise. This is a compatibilty
|
|
@@ -60,7 +72,7 @@ class NameCase(str, Enum):
|
|
|
60
72
|
js_upper = "js_upper"
|
|
61
73
|
|
|
62
74
|
|
|
63
|
-
class BaseTypeName(
|
|
75
|
+
class BaseTypeName(StrEnum):
|
|
64
76
|
"""
|
|
65
77
|
Base types that are supported.
|
|
66
78
|
"""
|
|
@@ -86,13 +98,14 @@ class BaseTypeName(str, Enum):
|
|
|
86
98
|
s_optional = "Optional"
|
|
87
99
|
s_string = "String"
|
|
88
100
|
s_tuple = "Tuple"
|
|
101
|
+
s_readonly_array = "ReadonlyArray"
|
|
89
102
|
s_union = "Union"
|
|
90
103
|
|
|
91
104
|
# For a root class that defines properties
|
|
92
105
|
s_object = "Object"
|
|
93
106
|
|
|
94
107
|
|
|
95
|
-
class DefnTypeName(
|
|
108
|
+
class DefnTypeName(StrEnum):
|
|
96
109
|
# Type is a named alias of another type
|
|
97
110
|
s_alias = "Alias"
|
|
98
111
|
# Type is imported from an external source (opaque to type_spec)
|
|
@@ -101,6 +114,8 @@ class DefnTypeName(str, Enum):
|
|
|
101
114
|
s_string_enum = "StringEnum"
|
|
102
115
|
# a particular literal value
|
|
103
116
|
s_string_literal = "_StringLiteral"
|
|
117
|
+
# A union of several other types
|
|
118
|
+
s_union = "Union"
|
|
104
119
|
|
|
105
120
|
|
|
106
121
|
base_namespace_name = "base"
|
|
@@ -184,6 +199,35 @@ class SpecTypeInstance(SpecType):
|
|
|
184
199
|
return defn_type + self.parameters
|
|
185
200
|
|
|
186
201
|
|
|
202
|
+
@dataclass(kw_only=True)
|
|
203
|
+
class SpecEndpointExample:
|
|
204
|
+
summary: str
|
|
205
|
+
description: str
|
|
206
|
+
arguments: dict[str, object]
|
|
207
|
+
data: dict[str, object]
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@dataclass(kw_only=True)
|
|
211
|
+
class SpecGuide:
|
|
212
|
+
ref_name: str
|
|
213
|
+
title: str
|
|
214
|
+
markdown_content: str
|
|
215
|
+
html_content: str
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@dataclass(kw_only=True, frozen=True)
|
|
219
|
+
class RootGuideKey:
|
|
220
|
+
pass
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@dataclass(kw_only=True, frozen=True)
|
|
224
|
+
class EndpointGuideKey:
|
|
225
|
+
path: str
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
SpecGuideKey = RootGuideKey | EndpointGuideKey
|
|
229
|
+
|
|
230
|
+
|
|
187
231
|
class SpecTypeLiteralWrapper(SpecType):
|
|
188
232
|
def __init__(
|
|
189
233
|
self,
|
|
@@ -212,7 +256,7 @@ class SpecTypeLiteralWrapper(SpecType):
|
|
|
212
256
|
return [self.value_type]
|
|
213
257
|
|
|
214
258
|
|
|
215
|
-
def unwrap_literal_type(stype: SpecType) ->
|
|
259
|
+
def unwrap_literal_type(stype: SpecType) -> SpecTypeLiteralWrapper | None:
|
|
216
260
|
if isinstance(stype, SpecTypeInstance) and stype.defn_type.is_base_type(
|
|
217
261
|
BaseTypeName.s_literal
|
|
218
262
|
):
|
|
@@ -240,7 +284,7 @@ class SpecTypeDefn(SpecType):
|
|
|
240
284
|
) -> None:
|
|
241
285
|
self.namespace = namespace
|
|
242
286
|
self.name = name
|
|
243
|
-
self.label:
|
|
287
|
+
self.label: str | None = None
|
|
244
288
|
|
|
245
289
|
self.is_predefined = is_predefined
|
|
246
290
|
self.name_case = NameCase.convert
|
|
@@ -250,6 +294,7 @@ class SpecTypeDefn(SpecType):
|
|
|
250
294
|
self._is_value_converted = _is_value_converted
|
|
251
295
|
self._is_value_to_string = False
|
|
252
296
|
self._is_valid_parameter = True
|
|
297
|
+
self._is_dynamic_allowed = False
|
|
253
298
|
self.ext_info: Any = None
|
|
254
299
|
|
|
255
300
|
def is_value_converted(self) -> bool:
|
|
@@ -261,20 +306,38 @@ class SpecTypeDefn(SpecType):
|
|
|
261
306
|
def is_valid_parameter(self) -> bool:
|
|
262
307
|
return self._is_valid_parameter
|
|
263
308
|
|
|
309
|
+
def is_dynamic_allowed(self) -> bool:
|
|
310
|
+
return self._is_dynamic_allowed
|
|
311
|
+
|
|
264
312
|
def is_base_type(self, type_: BaseTypeName) -> bool:
|
|
265
313
|
return self.is_base and self.name == type_
|
|
266
314
|
|
|
315
|
+
def can_process(self, builder: SpecBuilder, data: RawDict) -> bool:
|
|
316
|
+
return True
|
|
317
|
+
|
|
267
318
|
@abc.abstractmethod
|
|
268
319
|
def process(self, builder: SpecBuilder, data: RawDict) -> None: ...
|
|
269
320
|
|
|
270
321
|
def base_process(
|
|
271
322
|
self, builder: SpecBuilder, data: RawDict, extra_names: list[str]
|
|
272
323
|
) -> None:
|
|
273
|
-
util.check_fields(
|
|
324
|
+
util.check_fields(
|
|
325
|
+
data,
|
|
326
|
+
[
|
|
327
|
+
"ext_info",
|
|
328
|
+
"label",
|
|
329
|
+
"is_dynamic_allowed",
|
|
330
|
+
]
|
|
331
|
+
+ extra_names,
|
|
332
|
+
)
|
|
274
333
|
|
|
275
334
|
self.ext_info = data.get("ext_info")
|
|
276
335
|
self.label = data.get("label")
|
|
277
336
|
|
|
337
|
+
is_dynamic_allowed = data.get("is_dynamic_allowed", False)
|
|
338
|
+
assert isinstance(is_dynamic_allowed, bool)
|
|
339
|
+
self._is_dynamic_allowed = is_dynamic_allowed
|
|
340
|
+
|
|
278
341
|
def _process_property(
|
|
279
342
|
self, builder: SpecBuilder, spec_name: str, data: RawDict
|
|
280
343
|
) -> SpecProperty:
|
|
@@ -313,9 +376,9 @@ class SpecTypeDefn(SpecType):
|
|
|
313
376
|
property_name_case = NameCase(name_case_raw)
|
|
314
377
|
|
|
315
378
|
if property_name_case != NameCase.preserve:
|
|
316
|
-
assert util.is_valid_property_name(
|
|
317
|
-
name
|
|
318
|
-
)
|
|
379
|
+
assert util.is_valid_property_name(name), (
|
|
380
|
+
f"{name} is not a valid property name"
|
|
381
|
+
)
|
|
319
382
|
|
|
320
383
|
data_type = data.get("type")
|
|
321
384
|
builder.ensure(data_type is not None, "missing `type` entry")
|
|
@@ -393,7 +456,7 @@ class SpecTypeGenericParameter(SpecType):
|
|
|
393
456
|
|
|
394
457
|
|
|
395
458
|
class SpecTypeDefnObject(SpecTypeDefn):
|
|
396
|
-
base:
|
|
459
|
+
base: SpecTypeDefnObject | None
|
|
397
460
|
parameters: list[str]
|
|
398
461
|
|
|
399
462
|
def __init__(
|
|
@@ -401,7 +464,7 @@ class SpecTypeDefnObject(SpecTypeDefn):
|
|
|
401
464
|
namespace: SpecNamespace,
|
|
402
465
|
name: str,
|
|
403
466
|
*,
|
|
404
|
-
parameters:
|
|
467
|
+
parameters: list[str] | None = None,
|
|
405
468
|
is_base: bool = False,
|
|
406
469
|
is_predefined: bool = False,
|
|
407
470
|
is_hashable: bool = False,
|
|
@@ -418,7 +481,7 @@ class SpecTypeDefnObject(SpecTypeDefn):
|
|
|
418
481
|
self.parameters = parameters if parameters is not None else []
|
|
419
482
|
self.is_hashable = is_hashable
|
|
420
483
|
self.base = None
|
|
421
|
-
self.properties:
|
|
484
|
+
self.properties: dict[str, SpecProperty] | None = None
|
|
422
485
|
self._kw_only: bool = True
|
|
423
486
|
self.desc: str | None = None
|
|
424
487
|
|
|
@@ -489,13 +552,8 @@ class SpecTypeDefnObject(SpecTypeDefn):
|
|
|
489
552
|
base_type: list[SpecType] = [self.base] if self.base is not None else []
|
|
490
553
|
return base_type + prop_types
|
|
491
554
|
|
|
492
|
-
def
|
|
493
|
-
|
|
494
|
-
assert (
|
|
495
|
-
len(self.parameters) == 1
|
|
496
|
-
), "Only single generic parameters current supported"
|
|
497
|
-
return self.parameters[0]
|
|
498
|
-
return None
|
|
555
|
+
def get_generics(self) -> list[str]:
|
|
556
|
+
return self.parameters
|
|
499
557
|
|
|
500
558
|
|
|
501
559
|
class SpecTypeDefnAlias(SpecTypeDefn):
|
|
@@ -517,13 +575,65 @@ class SpecTypeDefnAlias(SpecTypeDefn):
|
|
|
517
575
|
super().base_process(builder, data, ["type", "desc", "alias", "discriminator"])
|
|
518
576
|
self.alias = builder.parse_type(self.namespace, data["alias"])
|
|
519
577
|
self.desc = data.get("desc", None)
|
|
520
|
-
# Should be limited to Union type aliases
|
|
521
578
|
self.discriminator = data.get("discriminator", None)
|
|
522
579
|
|
|
523
580
|
def get_referenced_types(self) -> list[SpecType]:
|
|
524
581
|
return [self.alias]
|
|
525
582
|
|
|
526
583
|
|
|
584
|
+
class SpecTypeDefnUnion(SpecTypeDefn):
|
|
585
|
+
def __init__(self, namespace: SpecNamespace, name: str) -> None:
|
|
586
|
+
super().__init__(namespace, name)
|
|
587
|
+
self.discriminator: str | None = None
|
|
588
|
+
self.types: list[SpecType] = []
|
|
589
|
+
self._alias_type: SpecType | None = None
|
|
590
|
+
self.discriminator_map: dict[str, SpecType] | None = None
|
|
591
|
+
self.desc: str | None = None
|
|
592
|
+
|
|
593
|
+
def process(self, builder: SpecBuilder, data: RawDict) -> None:
|
|
594
|
+
super().base_process(builder, data, ["type", "desc", "types", "discriminator"])
|
|
595
|
+
|
|
596
|
+
self.desc = data.get("desc", None)
|
|
597
|
+
self.discriminator = data.get("discriminator", None)
|
|
598
|
+
|
|
599
|
+
for sub_type_str in data["types"]:
|
|
600
|
+
sub_type = builder.parse_type(self.namespace, sub_type_str)
|
|
601
|
+
self.types.append(sub_type)
|
|
602
|
+
|
|
603
|
+
base_type = builder.namespaces[base_namespace_name].types[BaseTypeName.s_union]
|
|
604
|
+
self._backing_type = SpecTypeInstance(base_type, self.types)
|
|
605
|
+
|
|
606
|
+
if self.discriminator is not None:
|
|
607
|
+
self.discriminator_map = {}
|
|
608
|
+
for sub_type in self.types:
|
|
609
|
+
builder.push_where(sub_type.name)
|
|
610
|
+
assert isinstance(sub_type, SpecTypeDefnObject), (
|
|
611
|
+
"union-type-must-be-object"
|
|
612
|
+
)
|
|
613
|
+
assert sub_type.properties is not None
|
|
614
|
+
discriminator_type = sub_type.properties.get(self.discriminator)
|
|
615
|
+
assert discriminator_type is not None, (
|
|
616
|
+
f"missing-discriminator-field: {sub_type}"
|
|
617
|
+
)
|
|
618
|
+
prop_type = unwrap_literal_type(discriminator_type.spec_type)
|
|
619
|
+
assert prop_type is not None
|
|
620
|
+
assert prop_type.is_value_to_string()
|
|
621
|
+
discriminant = str(prop_type.value)
|
|
622
|
+
assert discriminant not in self.discriminator_map, (
|
|
623
|
+
f"duplicated-discriminant, {discriminant} in {sub_type}"
|
|
624
|
+
)
|
|
625
|
+
self.discriminator_map[discriminant] = sub_type
|
|
626
|
+
|
|
627
|
+
builder.pop_where()
|
|
628
|
+
|
|
629
|
+
def get_referenced_types(self) -> list[SpecType]:
|
|
630
|
+
return self.types
|
|
631
|
+
|
|
632
|
+
def get_backing_type(self) -> SpecType:
|
|
633
|
+
assert self._backing_type is not None
|
|
634
|
+
return self._backing_type
|
|
635
|
+
|
|
636
|
+
|
|
527
637
|
class SpecTypeDefnExternal(SpecTypeDefn):
|
|
528
638
|
external_map: dict[str, str]
|
|
529
639
|
|
|
@@ -553,7 +663,7 @@ class SpecTypeDefnExternal(SpecTypeDefn):
|
|
|
553
663
|
class StringEnumEntry:
|
|
554
664
|
name: str
|
|
555
665
|
value: str
|
|
556
|
-
label:
|
|
666
|
+
label: str | None = None
|
|
557
667
|
deprecated: bool = False
|
|
558
668
|
|
|
559
669
|
|
|
@@ -569,17 +679,32 @@ class SpecTypeDefnStringEnum(SpecTypeDefn):
|
|
|
569
679
|
)
|
|
570
680
|
self.values: dict[str, StringEnumEntry] = {}
|
|
571
681
|
self.desc: str | None = None
|
|
572
|
-
self.sql_type_name:
|
|
682
|
+
self.sql_type_name: str | None = None
|
|
573
683
|
self.emit_id_source = False
|
|
684
|
+
self.source_enums: list[SpecType] = []
|
|
685
|
+
|
|
686
|
+
def can_process(self, builder: SpecBuilder, data: dict[Any, Any]) -> bool:
|
|
687
|
+
source_enums = data.get("source_enums")
|
|
688
|
+
try:
|
|
689
|
+
for sub_type_str in source_enums or []:
|
|
690
|
+
sub_type = builder.parse_type(self.namespace, sub_type_str)
|
|
691
|
+
assert isinstance(sub_type, SpecTypeDefnStringEnum)
|
|
692
|
+
assert len(sub_type.values) > 0
|
|
693
|
+
except AssertionError:
|
|
694
|
+
return False
|
|
695
|
+
return super().can_process(builder, data)
|
|
574
696
|
|
|
575
697
|
def process(self, builder: SpecBuilder, data: RawDict) -> None:
|
|
576
698
|
super().base_process(
|
|
577
|
-
builder,
|
|
699
|
+
builder,
|
|
700
|
+
data,
|
|
701
|
+
["type", "desc", "values", "name_case", "sql", "emit", "source_enums"],
|
|
578
702
|
)
|
|
579
703
|
self.name_case = NameCase(data.get("name_case", "convert"))
|
|
580
704
|
self.values = {}
|
|
581
|
-
data_values = data
|
|
705
|
+
data_values = data.get("values")
|
|
582
706
|
self.desc = data.get("desc", None)
|
|
707
|
+
source_enums = data.get("source_enums", None)
|
|
583
708
|
if isinstance(data_values, dict):
|
|
584
709
|
for name, value in data_values.items():
|
|
585
710
|
builder.push_where(name)
|
|
@@ -617,10 +742,13 @@ class SpecTypeDefnStringEnum(SpecTypeDefn):
|
|
|
617
742
|
elif isinstance(data_values, list):
|
|
618
743
|
for value in data_values:
|
|
619
744
|
if value in self.values:
|
|
620
|
-
raise Exception(
|
|
745
|
+
raise Exception(
|
|
746
|
+
"duplicate value in typespec enum", self.name, value
|
|
747
|
+
)
|
|
621
748
|
self.values[value] = StringEnumEntry(name=value, value=value)
|
|
622
749
|
else:
|
|
623
|
-
|
|
750
|
+
if source_enums is None or data_values is not None:
|
|
751
|
+
raise Exception("unsupported values type")
|
|
624
752
|
|
|
625
753
|
sql_data = data.get("sql")
|
|
626
754
|
if sql_data is not None:
|
|
@@ -642,9 +770,18 @@ class SpecTypeDefnStringEnum(SpecTypeDefn):
|
|
|
642
770
|
builder.ensure(
|
|
643
771
|
entry.label is not None, f"need-label-for-id-source:{entry.name}"
|
|
644
772
|
)
|
|
773
|
+
for sub_type_str in source_enums or []:
|
|
774
|
+
sub_type = builder.parse_type(self.namespace, sub_type_str)
|
|
775
|
+
self.source_enums.append(sub_type)
|
|
776
|
+
|
|
777
|
+
for sub_type in self.source_enums:
|
|
778
|
+
builder.push_where(sub_type.name)
|
|
779
|
+
if isinstance(sub_type, SpecTypeDefnStringEnum):
|
|
780
|
+
self.values.update(sub_type.values)
|
|
781
|
+
builder.pop_where()
|
|
645
782
|
|
|
646
783
|
def get_referenced_types(self) -> list[SpecType]:
|
|
647
|
-
return
|
|
784
|
+
return self.source_enums
|
|
648
785
|
|
|
649
786
|
|
|
650
787
|
TOKEN_ENDPOINT = "$endpoint"
|
|
@@ -656,7 +793,7 @@ TOKEN_EMIT_TYPE_INFO = "$emit_type_info"
|
|
|
656
793
|
TOKEN_IMPORT = "$import"
|
|
657
794
|
|
|
658
795
|
|
|
659
|
-
class RouteMethod(
|
|
796
|
+
class RouteMethod(StrEnum):
|
|
660
797
|
post = "post"
|
|
661
798
|
get = "get"
|
|
662
799
|
delete = "delete"
|
|
@@ -664,7 +801,7 @@ class RouteMethod(str, Enum):
|
|
|
664
801
|
put = "put"
|
|
665
802
|
|
|
666
803
|
|
|
667
|
-
class ResultType(
|
|
804
|
+
class ResultType(StrEnum):
|
|
668
805
|
json = "json"
|
|
669
806
|
binary = "binary"
|
|
670
807
|
|
|
@@ -672,20 +809,108 @@ class ResultType(str, Enum):
|
|
|
672
809
|
RE_ENDPOINT_ROOT = re.compile(r"\${([_a-z]+)}")
|
|
673
810
|
|
|
674
811
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
root:
|
|
812
|
+
@dataclass(kw_only=True, frozen=True)
|
|
813
|
+
class _EndpointPathDetails:
|
|
814
|
+
root: EndpointKey
|
|
815
|
+
root_path: str
|
|
816
|
+
resolved_path: str
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
def _resolve_endpoint_path(
|
|
820
|
+
path: str, api_endpoints: dict[EndpointKey, str]
|
|
821
|
+
) -> _EndpointPathDetails:
|
|
822
|
+
root_path_source = path.split("/")[0]
|
|
823
|
+
root_match = RE_ENDPOINT_ROOT.fullmatch(root_path_source)
|
|
824
|
+
if root_match is None:
|
|
825
|
+
raise Exception(f"invalid-api-path-root:{root_path_source}")
|
|
826
|
+
|
|
827
|
+
root_var = root_match.group(1)
|
|
828
|
+
root_path = api_endpoints[root_var]
|
|
829
|
+
|
|
830
|
+
_, *rest_path = path.split("/", 1)
|
|
831
|
+
resolved_path = "/".join([root_path] + rest_path)
|
|
832
|
+
|
|
833
|
+
return _EndpointPathDetails(
|
|
834
|
+
root=root_var, root_path=root_path, resolved_path=resolved_path
|
|
835
|
+
)
|
|
836
|
+
|
|
837
|
+
|
|
838
|
+
class EndpointEmitType(StrEnum):
|
|
839
|
+
EMIT_ENDPOINT = "emit_endpoint"
|
|
840
|
+
EMIT_TYPES = "emit_types"
|
|
841
|
+
EMIT_NOTHING = "emit_nothing"
|
|
842
|
+
|
|
843
|
+
|
|
844
|
+
@dataclass(kw_only=True, frozen=True)
|
|
845
|
+
class EndpointSpecificPath:
|
|
846
|
+
root: EndpointKey
|
|
678
847
|
path_root: str
|
|
679
848
|
path_dirname: str
|
|
680
849
|
path_basename: str
|
|
850
|
+
function: str | None
|
|
851
|
+
|
|
852
|
+
|
|
853
|
+
def parse_endpoint_specific_path(
|
|
854
|
+
builder: SpecBuilder,
|
|
855
|
+
data_per_endpoint: RawDict | None,
|
|
856
|
+
) -> EndpointSpecificPath | None:
|
|
857
|
+
if data_per_endpoint is None:
|
|
858
|
+
return None
|
|
859
|
+
util.check_fields(
|
|
860
|
+
data_per_endpoint,
|
|
861
|
+
[
|
|
862
|
+
"path",
|
|
863
|
+
"function",
|
|
864
|
+
],
|
|
865
|
+
)
|
|
866
|
+
|
|
867
|
+
if "path" not in data_per_endpoint or data_per_endpoint["path"] is None:
|
|
868
|
+
return None
|
|
869
|
+
|
|
870
|
+
path = data_per_endpoint["path"].split("/")
|
|
871
|
+
|
|
872
|
+
assert len(path) > 1, "invalid-endpoint-path"
|
|
873
|
+
|
|
874
|
+
path_details = _resolve_endpoint_path(
|
|
875
|
+
data_per_endpoint["path"], builder.api_endpoints
|
|
876
|
+
)
|
|
877
|
+
|
|
878
|
+
result = EndpointSpecificPath(
|
|
879
|
+
function=data_per_endpoint.get("function"),
|
|
880
|
+
path_dirname="/".join(path[1:-1]),
|
|
881
|
+
path_basename=path[-1],
|
|
882
|
+
root=path_details.root,
|
|
883
|
+
path_root=path_details.root_path,
|
|
884
|
+
)
|
|
885
|
+
|
|
886
|
+
return result
|
|
887
|
+
|
|
888
|
+
|
|
889
|
+
class SpecEndpoint:
|
|
890
|
+
method: RouteMethod
|
|
681
891
|
data_loader: bool
|
|
682
|
-
is_sdk:
|
|
892
|
+
is_sdk: EndpointEmitType
|
|
893
|
+
is_beta: bool
|
|
894
|
+
stability_level: StabilityLevel | None
|
|
683
895
|
# Don't emit TypeScript endpoint code
|
|
684
896
|
suppress_ts: bool
|
|
685
|
-
|
|
897
|
+
async_batch_path: str | None = None
|
|
686
898
|
result_type: ResultType = ResultType.json
|
|
687
899
|
has_attachment: bool = False
|
|
688
900
|
desc: str | None = None
|
|
901
|
+
account_type: str | None
|
|
902
|
+
route_group: str | None
|
|
903
|
+
|
|
904
|
+
# to be deprecated in favor of path_per_api_endpoint:
|
|
905
|
+
# default function, path details
|
|
906
|
+
function: str | None
|
|
907
|
+
root: EndpointKey
|
|
908
|
+
path_root: str
|
|
909
|
+
path_dirname: str
|
|
910
|
+
path_basename: str
|
|
911
|
+
|
|
912
|
+
# function, path details per api endpoint
|
|
913
|
+
path_per_api_endpoint: dict[str, EndpointSpecificPath]
|
|
689
914
|
|
|
690
915
|
is_external: bool = False
|
|
691
916
|
|
|
@@ -701,51 +926,122 @@ class SpecEndpoint:
|
|
|
701
926
|
"path",
|
|
702
927
|
"data_loader",
|
|
703
928
|
"is_sdk",
|
|
929
|
+
"is_beta",
|
|
930
|
+
"stability_level",
|
|
931
|
+
"async_batch_path",
|
|
704
932
|
"function",
|
|
705
933
|
"suppress_ts",
|
|
706
934
|
"desc",
|
|
707
935
|
"deprecated",
|
|
708
936
|
"result_type",
|
|
709
937
|
"has_attachment",
|
|
710
|
-
|
|
938
|
+
"account_type",
|
|
939
|
+
"route_group",
|
|
940
|
+
]
|
|
941
|
+
+ list(builder.api_endpoints.keys()),
|
|
711
942
|
)
|
|
712
943
|
self.method = RouteMethod(data["method"])
|
|
713
944
|
|
|
714
|
-
path = data["path"].split("/")
|
|
715
|
-
|
|
716
|
-
assert len(path) > 1, "invalid-endpoint-path"
|
|
717
|
-
|
|
718
|
-
# handle ${external} in the same way we handle ${materials} for now
|
|
719
|
-
self.path_dirname = "/".join(path[1:-1])
|
|
720
|
-
self.path_basename = path[-1]
|
|
721
|
-
|
|
722
945
|
data_loader = data.get("data_loader", False)
|
|
723
946
|
assert isinstance(data_loader, bool)
|
|
724
947
|
self.data_loader = data_loader
|
|
725
948
|
|
|
726
|
-
is_sdk = data.get("is_sdk",
|
|
727
|
-
|
|
949
|
+
is_sdk = data.get("is_sdk", EndpointEmitType.EMIT_NOTHING)
|
|
950
|
+
|
|
951
|
+
# backwards compatibility
|
|
952
|
+
if isinstance(is_sdk, bool):
|
|
953
|
+
if is_sdk is True:
|
|
954
|
+
is_sdk = EndpointEmitType.EMIT_ENDPOINT
|
|
955
|
+
else:
|
|
956
|
+
is_sdk = EndpointEmitType.EMIT_NOTHING
|
|
957
|
+
elif isinstance(is_sdk, str):
|
|
958
|
+
try:
|
|
959
|
+
is_sdk = EndpointEmitType(is_sdk)
|
|
960
|
+
except ValueError:
|
|
961
|
+
raise ValueError(f"Invalid value for is_sdk: {is_sdk}")
|
|
962
|
+
|
|
963
|
+
assert isinstance(is_sdk, EndpointEmitType)
|
|
964
|
+
|
|
728
965
|
self.is_sdk = is_sdk
|
|
729
966
|
|
|
730
|
-
|
|
967
|
+
route_group = data.get("route_group")
|
|
968
|
+
assert route_group is None or isinstance(route_group, str)
|
|
969
|
+
self.route_group = route_group
|
|
970
|
+
|
|
971
|
+
account_type = data.get("account_type")
|
|
972
|
+
assert account_type is None or isinstance(account_type, str)
|
|
973
|
+
self.account_type = account_type
|
|
974
|
+
|
|
975
|
+
is_beta = data.get("is_beta", False)
|
|
976
|
+
assert isinstance(is_beta, bool)
|
|
977
|
+
self.is_beta = is_beta
|
|
978
|
+
|
|
979
|
+
stability_level_raw = data.get("stability_level")
|
|
980
|
+
assert stability_level_raw is None or isinstance(stability_level_raw, str)
|
|
981
|
+
self.stability_level = (
|
|
982
|
+
StabilityLevel(stability_level_raw)
|
|
983
|
+
if stability_level_raw is not None
|
|
984
|
+
else None
|
|
985
|
+
)
|
|
986
|
+
|
|
987
|
+
async_batch_path = data.get("async_batch_path")
|
|
988
|
+
if async_batch_path is not None:
|
|
989
|
+
assert isinstance(async_batch_path, str)
|
|
990
|
+
self.async_batch_path = async_batch_path
|
|
731
991
|
|
|
732
992
|
suppress_ts = data.get("suppress_ts", False)
|
|
733
993
|
assert isinstance(suppress_ts, bool)
|
|
734
994
|
self.suppress_ts = suppress_ts
|
|
735
995
|
|
|
736
996
|
self.result_type = ResultType(data.get("result_type", ResultType.json.value))
|
|
737
|
-
|
|
997
|
+
self.has_attachment = data.get("has_attachment", False)
|
|
738
998
|
self.desc = data.get("desc")
|
|
739
999
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
1000
|
+
default_endpoint_path = parse_endpoint_specific_path(
|
|
1001
|
+
builder,
|
|
1002
|
+
{"path": data.get("path"), "function": data.get("function")},
|
|
1003
|
+
)
|
|
1004
|
+
if default_endpoint_path is not None:
|
|
1005
|
+
self.root = default_endpoint_path.root
|
|
1006
|
+
self.path_per_api_endpoint = {
|
|
1007
|
+
self.root: default_endpoint_path,
|
|
1008
|
+
}
|
|
1009
|
+
else:
|
|
1010
|
+
self.path_per_api_endpoint = {}
|
|
1011
|
+
shared_function_name = None
|
|
1012
|
+
for endpoint_key in builder.api_endpoints:
|
|
1013
|
+
endpoint_specific_path = parse_endpoint_specific_path(
|
|
1014
|
+
builder,
|
|
1015
|
+
data.get(endpoint_key),
|
|
1016
|
+
)
|
|
1017
|
+
if endpoint_specific_path is not None:
|
|
1018
|
+
self.path_per_api_endpoint[endpoint_key] = endpoint_specific_path
|
|
1019
|
+
if endpoint_specific_path.function is not None:
|
|
1020
|
+
fn_name = endpoint_specific_path.function.split(".")[-1]
|
|
1021
|
+
if shared_function_name is None:
|
|
1022
|
+
shared_function_name = fn_name
|
|
1023
|
+
assert shared_function_name == fn_name
|
|
1024
|
+
assert self.path_per_api_endpoint != {}
|
|
1025
|
+
|
|
1026
|
+
assert builder.top_namespace in self.path_per_api_endpoint
|
|
1027
|
+
self.root = builder.top_namespace
|
|
1028
|
+
|
|
1029
|
+
default_endpoint_path = self.path_per_api_endpoint[self.root]
|
|
1030
|
+
self.function = default_endpoint_path.function
|
|
1031
|
+
self.path_dirname = default_endpoint_path.path_dirname
|
|
1032
|
+
self.path_basename = default_endpoint_path.path_basename
|
|
1033
|
+
self.path_root = default_endpoint_path.path_root
|
|
743
1034
|
|
|
744
|
-
self.root = root_match.group(1)
|
|
745
|
-
self.path_root = builder.api_endpoints[self.root]
|
|
746
1035
|
# IMPROVE: remove need for is_external flag
|
|
747
1036
|
self.is_external = self.path_root == "api/external"
|
|
748
|
-
|
|
1037
|
+
|
|
1038
|
+
assert self.is_sdk != EndpointEmitType.EMIT_ENDPOINT or self.desc is not None, (
|
|
1039
|
+
f"Endpoint description required for SDK endpoints, missing: {self.path_dirname}/{self.path_basename}"
|
|
1040
|
+
)
|
|
1041
|
+
|
|
1042
|
+
@property
|
|
1043
|
+
def resolved_path(self: Self) -> str:
|
|
1044
|
+
return f"{self.path_root}/{self.path_dirname}/{self.path_basename}"
|
|
749
1045
|
|
|
750
1046
|
|
|
751
1047
|
def _parse_const(
|
|
@@ -812,7 +1108,9 @@ def _parse_const(
|
|
|
812
1108
|
return value
|
|
813
1109
|
|
|
814
1110
|
if const_type.name == BaseTypeName.s_boolean:
|
|
815
|
-
builder.ensure(
|
|
1111
|
+
builder.ensure(
|
|
1112
|
+
isinstance(value, bool), "invalid value for boolean constant"
|
|
1113
|
+
)
|
|
816
1114
|
return value
|
|
817
1115
|
|
|
818
1116
|
raise Exception("unsupported-const-scalar-type", const_type)
|
|
@@ -844,7 +1142,9 @@ class SpecConstant:
|
|
|
844
1142
|
assert isinstance(self.value, dict)
|
|
845
1143
|
# the parsing checks that the values are correct, so a simple length check
|
|
846
1144
|
# should be enough to check completeness
|
|
847
|
-
builder.ensure(
|
|
1145
|
+
builder.ensure(
|
|
1146
|
+
len(key_type.values) == len(self.value), "incomplete-enum-map"
|
|
1147
|
+
)
|
|
848
1148
|
|
|
849
1149
|
|
|
850
1150
|
class SpecNamespace:
|
|
@@ -854,14 +1154,14 @@ class SpecNamespace:
|
|
|
854
1154
|
):
|
|
855
1155
|
self.types: dict[str, SpecTypeDefn] = {}
|
|
856
1156
|
self.constants: dict[str, SpecConstant] = {}
|
|
857
|
-
self.endpoint:
|
|
1157
|
+
self.endpoint: SpecEndpoint | None = None
|
|
858
1158
|
self.emit_io_ts = False
|
|
859
1159
|
self.emit_type_info = False
|
|
860
1160
|
self.derive_types_from_io_ts = False
|
|
861
|
-
self._imports:
|
|
1161
|
+
self._imports: list[str] | None = None
|
|
862
1162
|
self.path = name.split(".")
|
|
863
1163
|
self.name = self.path[-1]
|
|
864
|
-
self._order:
|
|
1164
|
+
self._order: int | None = None
|
|
865
1165
|
|
|
866
1166
|
def _update_order(self, builder: SpecBuilder, recurse: int = 0) -> int:
|
|
867
1167
|
if self._order is not None:
|
|
@@ -919,9 +1219,9 @@ class SpecNamespace:
|
|
|
919
1219
|
continue
|
|
920
1220
|
|
|
921
1221
|
if "value" in defn:
|
|
922
|
-
assert util.is_valid_property_name(
|
|
923
|
-
name
|
|
924
|
-
)
|
|
1222
|
+
assert util.is_valid_property_name(name), (
|
|
1223
|
+
f"{name} is not a valid constant name"
|
|
1224
|
+
)
|
|
925
1225
|
spec_constant = SpecConstant(self, name)
|
|
926
1226
|
self.constants[name] = spec_constant
|
|
927
1227
|
continue
|
|
@@ -932,13 +1232,19 @@ class SpecNamespace:
|
|
|
932
1232
|
spec_type: SpecTypeDefn
|
|
933
1233
|
if defn_type == DefnTypeName.s_alias:
|
|
934
1234
|
spec_type = SpecTypeDefnAlias(self, name)
|
|
1235
|
+
elif defn_type == DefnTypeName.s_union:
|
|
1236
|
+
spec_type = SpecTypeDefnUnion(self, name)
|
|
935
1237
|
elif defn_type == DefnTypeName.s_external:
|
|
936
1238
|
spec_type = SpecTypeDefnExternal(self, name)
|
|
937
1239
|
elif defn_type == DefnTypeName.s_string_enum:
|
|
938
1240
|
spec_type = SpecTypeDefnStringEnum(self, name)
|
|
939
1241
|
else:
|
|
940
1242
|
parameters = (
|
|
941
|
-
[
|
|
1243
|
+
[
|
|
1244
|
+
parameter.name
|
|
1245
|
+
for name_parameters in parsed_name.parameters
|
|
1246
|
+
for parameter in name_parameters
|
|
1247
|
+
]
|
|
942
1248
|
if parsed_name.parameters is not None
|
|
943
1249
|
else None
|
|
944
1250
|
)
|
|
@@ -954,28 +1260,41 @@ class SpecNamespace:
|
|
|
954
1260
|
Complete the definition of each type.
|
|
955
1261
|
"""
|
|
956
1262
|
builder.push_where(self.name)
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
1263
|
+
items_to_process: list[NameDataPair] = [
|
|
1264
|
+
NameDataPair(full_name=full_name, data=defn)
|
|
1265
|
+
for full_name, defn in data.items()
|
|
1266
|
+
]
|
|
1267
|
+
while len(items_to_process) > 0:
|
|
1268
|
+
deferred_items: list[NameDataPair] = []
|
|
1269
|
+
for item in items_to_process:
|
|
1270
|
+
full_name = item.full_name
|
|
1271
|
+
defn = item.data
|
|
1272
|
+
parsed_name = parse_type_str(full_name)[0]
|
|
1273
|
+
name = parsed_name.name
|
|
1274
|
+
|
|
1275
|
+
if name in [TOKEN_EMIT_IO_TS, TOKEN_EMIT_TYPE_INFO, TOKEN_IMPORT]:
|
|
1276
|
+
continue
|
|
960
1277
|
|
|
961
|
-
|
|
962
|
-
continue
|
|
1278
|
+
builder.push_where(name)
|
|
963
1279
|
|
|
964
|
-
|
|
1280
|
+
if "value" in defn:
|
|
1281
|
+
spec_constant = self.constants[name]
|
|
1282
|
+
spec_constant.process(builder, defn)
|
|
965
1283
|
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
1284
|
+
elif name == TOKEN_ENDPOINT:
|
|
1285
|
+
assert self.endpoint
|
|
1286
|
+
self.endpoint.process(builder, defn)
|
|
969
1287
|
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
spec_type.process(builder, defn)
|
|
1288
|
+
else:
|
|
1289
|
+
spec_type = self.types[name]
|
|
1290
|
+
if spec_type.can_process(builder, defn):
|
|
1291
|
+
spec_type.process(builder, defn)
|
|
1292
|
+
else:
|
|
1293
|
+
deferred_items.append(item)
|
|
977
1294
|
|
|
978
|
-
|
|
1295
|
+
builder.pop_where()
|
|
1296
|
+
assert len(deferred_items) < len(items_to_process)
|
|
1297
|
+
items_to_process = [deferred for deferred in deferred_items]
|
|
979
1298
|
|
|
980
1299
|
builder.pop_where()
|
|
981
1300
|
|
|
@@ -990,13 +1309,24 @@ class NamespaceDataPair:
|
|
|
990
1309
|
data: RawDict
|
|
991
1310
|
|
|
992
1311
|
|
|
1312
|
+
@dataclass(kw_only=True)
|
|
1313
|
+
class NameDataPair:
|
|
1314
|
+
full_name: str
|
|
1315
|
+
data: RawDict
|
|
1316
|
+
|
|
1317
|
+
|
|
993
1318
|
class SpecBuilder:
|
|
994
|
-
def __init__(
|
|
1319
|
+
def __init__(
|
|
1320
|
+
self, *, api_endpoints: dict[EndpointKey, str], top_namespace: str
|
|
1321
|
+
) -> None:
|
|
1322
|
+
self.top_namespace = top_namespace
|
|
995
1323
|
self.where: list[str] = []
|
|
996
1324
|
self.namespaces = {}
|
|
997
1325
|
self.pending: list[NamespaceDataPair] = []
|
|
998
1326
|
self.parts: dict[str, dict[str, str]] = defaultdict(dict)
|
|
999
1327
|
self.preparts: dict[str, dict[str, str]] = defaultdict(dict)
|
|
1328
|
+
self.examples: dict[str, list[SpecEndpointExample]] = defaultdict(list)
|
|
1329
|
+
self.guides: dict[SpecGuideKey, list[SpecGuide]] = defaultdict(list)
|
|
1000
1330
|
self.api_endpoints = api_endpoints
|
|
1001
1331
|
base_namespace = SpecNamespace(name=base_namespace_name)
|
|
1002
1332
|
for base_type in BaseTypeName:
|
|
@@ -1086,7 +1416,7 @@ class SpecBuilder:
|
|
|
1086
1416
|
self,
|
|
1087
1417
|
path: util.ParsedTypePath,
|
|
1088
1418
|
namespace: SpecNamespace,
|
|
1089
|
-
scope:
|
|
1419
|
+
scope: SpecTypeDefn | None = None,
|
|
1090
1420
|
top: bool = False,
|
|
1091
1421
|
) -> SpecType:
|
|
1092
1422
|
"""
|
|
@@ -1167,11 +1497,13 @@ class SpecBuilder:
|
|
|
1167
1497
|
)
|
|
1168
1498
|
|
|
1169
1499
|
def parse_type(
|
|
1170
|
-
self, namespace: SpecNamespace, spec: str, scope:
|
|
1500
|
+
self, namespace: SpecNamespace, spec: str, scope: SpecTypeDefn | None = None
|
|
1171
1501
|
) -> SpecType:
|
|
1172
1502
|
self.push_where(spec)
|
|
1173
1503
|
parsed_type = util.parse_type_str(spec)
|
|
1174
|
-
result = self._convert_parsed_type(
|
|
1504
|
+
result = self._convert_parsed_type(
|
|
1505
|
+
parsed_type, namespace, top=True, scope=scope
|
|
1506
|
+
)
|
|
1175
1507
|
self.pop_where()
|
|
1176
1508
|
return result
|
|
1177
1509
|
|
|
@@ -1181,5 +1513,59 @@ class SpecBuilder:
|
|
|
1181
1513
|
def add_prepart_file(self, target: str, name: str, data: str) -> None:
|
|
1182
1514
|
self.preparts[target][name] = data
|
|
1183
1515
|
|
|
1516
|
+
def add_example_file(self, data: dict[str, object]) -> None:
|
|
1517
|
+
path_details = _resolve_endpoint_path(str(data["path"]), self.api_endpoints)
|
|
1518
|
+
|
|
1519
|
+
examples_data = data["examples"]
|
|
1520
|
+
if not isinstance(examples_data, list):
|
|
1521
|
+
raise Exception(
|
|
1522
|
+
f"'examples' in example files are expected to be a list, endpoint_path={path_details.resolved_path}"
|
|
1523
|
+
)
|
|
1524
|
+
for example in examples_data:
|
|
1525
|
+
arguments = example["arguments"]
|
|
1526
|
+
data_example = example["data"]
|
|
1527
|
+
if not isinstance(arguments, dict) or not isinstance(data_example, dict):
|
|
1528
|
+
raise Exception(
|
|
1529
|
+
f"'arguments' and 'data' fields must be dictionaries for each endpoint example, endpoint={path_details.resolved_path}"
|
|
1530
|
+
)
|
|
1531
|
+
self.examples[path_details.resolved_path].append(
|
|
1532
|
+
SpecEndpointExample(
|
|
1533
|
+
summary=str(example["summary"]),
|
|
1534
|
+
description=str(example["description"]),
|
|
1535
|
+
arguments=arguments,
|
|
1536
|
+
data=data_example,
|
|
1537
|
+
)
|
|
1538
|
+
)
|
|
1539
|
+
|
|
1540
|
+
def add_guide_file(self, file_content: str) -> None:
|
|
1541
|
+
import markdown
|
|
1542
|
+
|
|
1543
|
+
md = markdown.Markdown(extensions=["meta"])
|
|
1544
|
+
html = md.convert(file_content)
|
|
1545
|
+
meta: dict[str, list[str]] = md.Meta # type: ignore[attr-defined]
|
|
1546
|
+
title_meta: list[str] | None = meta.get("title")
|
|
1547
|
+
if title_meta is None:
|
|
1548
|
+
raise Exception("guides require a title in the meta section")
|
|
1549
|
+
id_meta: list[str] | None = meta.get("id")
|
|
1550
|
+
if id_meta is None:
|
|
1551
|
+
raise Exception("guides require an id in the meta section")
|
|
1552
|
+
|
|
1553
|
+
path_meta: list[str] | None = meta.get("path")
|
|
1554
|
+
guide_key: SpecGuideKey = RootGuideKey()
|
|
1555
|
+
if path_meta is not None:
|
|
1556
|
+
path_details = _resolve_endpoint_path(
|
|
1557
|
+
"".join(path_meta), self.api_endpoints
|
|
1558
|
+
)
|
|
1559
|
+
guide_key = EndpointGuideKey(path=path_details.resolved_path)
|
|
1560
|
+
|
|
1561
|
+
self.guides[guide_key].append(
|
|
1562
|
+
SpecGuide(
|
|
1563
|
+
ref_name="".join(id_meta),
|
|
1564
|
+
title="".join(title_meta),
|
|
1565
|
+
html_content=html,
|
|
1566
|
+
markdown_content=file_content,
|
|
1567
|
+
)
|
|
1568
|
+
)
|
|
1569
|
+
|
|
1184
1570
|
def resolve_proper_name(self, stype: SpecTypeDefn) -> str:
|
|
1185
1571
|
return f"{'.'.join(stype.namespace.path)}.{stype.name}"
|