UncountablePythonSDK 0.0.52__py3-none-any.whl → 0.0.131__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of UncountablePythonSDK might be problematic. Click here for more details.
- docs/conf.py +54 -7
- docs/index.md +107 -4
- docs/integration_examples/create_ingredient.md +43 -0
- docs/integration_examples/create_output.md +56 -0
- docs/integration_examples/index.md +6 -0
- docs/justfile +2 -2
- docs/requirements.txt +6 -4
- examples/async_batch.py +3 -3
- examples/basic_auth.py +7 -0
- examples/create_entity.py +3 -1
- examples/create_ingredient_sdk.py +34 -0
- examples/download_files.py +26 -0
- examples/edit_recipe_inputs.py +4 -2
- examples/integration-server/jobs/materials_auto/concurrent_cron.py +11 -0
- examples/integration-server/jobs/materials_auto/example_cron.py +21 -0
- examples/integration-server/jobs/materials_auto/example_http.py +47 -0
- examples/integration-server/jobs/materials_auto/example_instrument.py +100 -0
- examples/integration-server/jobs/materials_auto/example_parse.py +140 -0
- examples/integration-server/jobs/materials_auto/example_predictions.py +61 -0
- examples/integration-server/jobs/materials_auto/example_runsheet_wh.py +39 -0
- examples/integration-server/jobs/materials_auto/example_wh.py +23 -0
- examples/integration-server/jobs/materials_auto/profile.yaml +104 -0
- examples/integration-server/pyproject.toml +224 -0
- examples/invoke_uploader.py +4 -1
- examples/oauth.py +7 -0
- examples/set_recipe_metadata_file.py +40 -0
- examples/set_recipe_output_file_sdk.py +26 -0
- examples/upload_files.py +1 -2
- pkgs/argument_parser/__init__.py +9 -0
- pkgs/argument_parser/_is_namedtuple.py +3 -0
- pkgs/argument_parser/argument_parser.py +217 -70
- pkgs/filesystem_utils/__init__.py +1 -0
- pkgs/filesystem_utils/_blob_session.py +144 -0
- pkgs/filesystem_utils/_gdrive_session.py +10 -7
- pkgs/filesystem_utils/_s3_session.py +15 -13
- pkgs/filesystem_utils/_sftp_session.py +11 -7
- pkgs/filesystem_utils/file_type_utils.py +30 -10
- pkgs/py.typed +0 -0
- pkgs/serialization/__init__.py +7 -2
- pkgs/serialization/annotation.py +64 -0
- pkgs/serialization/missing_sentry.py +1 -1
- pkgs/serialization/opaque_key.py +1 -1
- pkgs/serialization/serial_alias.py +47 -0
- pkgs/serialization/serial_class.py +47 -26
- pkgs/serialization/serial_generic.py +16 -0
- pkgs/serialization/serial_union.py +17 -14
- pkgs/serialization/yaml.py +4 -1
- pkgs/serialization_util/__init__.py +6 -0
- pkgs/serialization_util/dataclasses.py +14 -0
- pkgs/serialization_util/serialization_helpers.py +15 -5
- pkgs/type_spec/actions_registry/__main__.py +0 -4
- pkgs/type_spec/actions_registry/emit_typescript.py +5 -5
- pkgs/type_spec/builder.py +354 -119
- pkgs/type_spec/builder_types.py +9 -0
- pkgs/type_spec/config.py +51 -11
- pkgs/type_spec/cross_output_links.py +99 -0
- pkgs/type_spec/emit_io_ts.py +1 -1
- pkgs/type_spec/emit_open_api.py +127 -36
- pkgs/type_spec/emit_open_api_util.py +5 -6
- pkgs/type_spec/emit_python.py +329 -121
- pkgs/type_spec/emit_typescript.py +117 -256
- pkgs/type_spec/emit_typescript_util.py +291 -2
- pkgs/type_spec/load_types.py +18 -4
- pkgs/type_spec/non_discriminated_union_exceptions.py +14 -0
- pkgs/type_spec/open_api_util.py +29 -4
- pkgs/type_spec/parts/base.py.prepart +13 -10
- pkgs/type_spec/parts/base.ts.prepart +4 -0
- pkgs/type_spec/type_info/__main__.py +3 -1
- pkgs/type_spec/type_info/emit_type_info.py +124 -29
- pkgs/type_spec/ui_entry_actions/__init__.py +4 -0
- pkgs/type_spec/ui_entry_actions/generate_ui_entry_actions.py +308 -0
- pkgs/type_spec/util.py +4 -4
- pkgs/type_spec/value_spec/__main__.py +26 -9
- pkgs/type_spec/value_spec/convert_type.py +21 -1
- pkgs/type_spec/value_spec/emit_python.py +25 -7
- pkgs/type_spec/value_spec/types.py +1 -1
- uncountable/core/async_batch.py +1 -1
- uncountable/core/client.py +142 -39
- uncountable/core/environment.py +41 -0
- uncountable/core/file_upload.py +52 -18
- uncountable/integration/cli.py +142 -0
- uncountable/integration/construct_client.py +8 -8
- uncountable/integration/cron.py +11 -37
- uncountable/integration/db/connect.py +12 -2
- uncountable/integration/db/session.py +25 -0
- uncountable/integration/entrypoint.py +8 -37
- uncountable/integration/executors/executors.py +125 -2
- uncountable/integration/executors/generic_upload_executor.py +87 -29
- uncountable/integration/executors/script_executor.py +3 -3
- uncountable/integration/http_server/__init__.py +5 -0
- uncountable/integration/http_server/types.py +69 -0
- uncountable/integration/job.py +242 -12
- uncountable/integration/queue_runner/__init__.py +0 -0
- uncountable/integration/queue_runner/command_server/__init__.py +28 -0
- uncountable/integration/queue_runner/command_server/command_client.py +133 -0
- uncountable/integration/queue_runner/command_server/command_server.py +142 -0
- uncountable/integration/queue_runner/command_server/constants.py +4 -0
- uncountable/integration/queue_runner/command_server/protocol/__init__.py +0 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server.proto +58 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.py +57 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.pyi +114 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2_grpc.py +264 -0
- uncountable/integration/queue_runner/command_server/types.py +75 -0
- uncountable/integration/queue_runner/datastore/__init__.py +3 -0
- uncountable/integration/queue_runner/datastore/datastore_sqlite.py +250 -0
- uncountable/integration/queue_runner/datastore/interface.py +29 -0
- uncountable/integration/queue_runner/datastore/model.py +24 -0
- uncountable/integration/queue_runner/job_scheduler.py +200 -0
- uncountable/integration/queue_runner/queue_runner.py +34 -0
- uncountable/integration/queue_runner/types.py +7 -0
- uncountable/integration/queue_runner/worker.py +116 -0
- uncountable/integration/scan_profiles.py +67 -0
- uncountable/integration/scheduler.py +199 -0
- uncountable/integration/secret_retrieval/retrieve_secret.py +26 -4
- uncountable/integration/server.py +94 -69
- uncountable/integration/telemetry.py +150 -34
- uncountable/integration/webhook_server/entrypoint.py +97 -0
- uncountable/types/__init__.py +78 -1
- uncountable/types/api/batch/execute_batch.py +13 -6
- uncountable/types/api/batch/execute_batch_load_async.py +9 -3
- uncountable/types/api/chemical/convert_chemical_formats.py +17 -5
- uncountable/types/api/condition_parameters/__init__.py +1 -0
- uncountable/types/api/condition_parameters/upsert_condition_match.py +72 -0
- uncountable/types/api/entity/create_entities.py +19 -7
- uncountable/types/api/entity/create_entity.py +17 -8
- uncountable/types/api/entity/create_or_update_entity.py +48 -0
- uncountable/types/api/entity/export_entities.py +59 -0
- uncountable/types/api/entity/get_entities_data.py +13 -4
- uncountable/types/api/entity/grant_entity_permissions.py +48 -0
- uncountable/types/api/entity/list_aggregate.py +79 -0
- uncountable/types/api/entity/list_entities.py +42 -10
- uncountable/types/api/entity/lock_entity.py +11 -4
- uncountable/types/api/entity/lookup_entity.py +116 -0
- uncountable/types/api/entity/resolve_entity_ids.py +15 -6
- uncountable/types/api/entity/set_entity_field_values.py +44 -0
- uncountable/types/api/entity/set_values.py +10 -3
- uncountable/types/api/entity/transition_entity_phase.py +22 -7
- uncountable/types/api/entity/unlock_entity.py +10 -3
- uncountable/types/api/equipment/associate_equipment_input.py +9 -3
- uncountable/types/api/field_options/upsert_field_options.py +17 -7
- uncountable/types/api/files/__init__.py +1 -0
- uncountable/types/api/files/download_file.py +77 -0
- uncountable/types/api/id_source/list_id_source.py +16 -7
- uncountable/types/api/id_source/match_id_source.py +14 -5
- uncountable/types/api/input_groups/get_input_group_names.py +13 -4
- uncountable/types/api/inputs/create_inputs.py +23 -9
- uncountable/types/api/inputs/get_input_data.py +30 -12
- uncountable/types/api/inputs/get_input_names.py +16 -7
- uncountable/types/api/inputs/get_inputs_data.py +25 -7
- uncountable/types/api/inputs/set_input_attribute_values.py +12 -6
- uncountable/types/api/inputs/set_input_category.py +12 -5
- uncountable/types/api/inputs/set_input_subcategories.py +10 -3
- uncountable/types/api/inputs/set_intermediate_type.py +11 -4
- uncountable/types/api/integrations/__init__.py +1 -0
- uncountable/types/api/integrations/publish_realtime_data.py +41 -0
- uncountable/types/api/integrations/push_notification.py +49 -0
- uncountable/types/api/integrations/register_sockets_token.py +41 -0
- uncountable/types/api/listing/__init__.py +1 -0
- uncountable/types/api/listing/fetch_listing.py +58 -0
- uncountable/types/api/material_families/update_entity_material_families.py +10 -4
- uncountable/types/api/notebooks/__init__.py +1 -0
- uncountable/types/api/notebooks/add_notebook_content.py +119 -0
- uncountable/types/api/outputs/get_output_data.py +28 -13
- uncountable/types/api/outputs/get_output_names.py +15 -6
- uncountable/types/api/outputs/get_output_organization.py +173 -0
- uncountable/types/api/outputs/resolve_output_conditions.py +20 -8
- uncountable/types/api/permissions/set_core_permissions.py +26 -10
- uncountable/types/api/project/get_projects.py +16 -7
- uncountable/types/api/project/get_projects_data.py +17 -8
- uncountable/types/api/recipe_links/create_recipe_link.py +12 -5
- uncountable/types/api/recipe_links/remove_recipe_link.py +11 -4
- uncountable/types/api/recipe_metadata/get_recipe_metadata_data.py +16 -7
- uncountable/types/api/recipes/add_recipe_to_project.py +10 -3
- uncountable/types/api/recipes/add_time_series_data.py +64 -0
- uncountable/types/api/recipes/archive_recipes.py +11 -4
- uncountable/types/api/recipes/associate_recipe_as_input.py +12 -5
- uncountable/types/api/recipes/associate_recipe_as_lot.py +10 -3
- uncountable/types/api/recipes/clear_recipe_outputs.py +42 -0
- uncountable/types/api/recipes/create_mix_order.py +44 -0
- uncountable/types/api/recipes/create_recipe.py +15 -9
- uncountable/types/api/recipes/create_recipes.py +21 -9
- uncountable/types/api/recipes/disassociate_recipe_as_input.py +10 -3
- uncountable/types/api/recipes/edit_recipe_inputs.py +134 -22
- uncountable/types/api/recipes/get_column_calculation_values.py +57 -0
- uncountable/types/api/recipes/get_curve.py +11 -5
- uncountable/types/api/recipes/get_recipe_calculations.py +13 -7
- uncountable/types/api/recipes/get_recipe_links.py +10 -4
- uncountable/types/api/recipes/get_recipe_names.py +13 -4
- uncountable/types/api/recipes/get_recipe_output_metadata.py +12 -6
- uncountable/types/api/recipes/get_recipes_data.py +87 -33
- uncountable/types/api/recipes/lock_recipes.py +19 -8
- uncountable/types/api/recipes/remove_recipe_from_project.py +10 -3
- uncountable/types/api/recipes/set_recipe_inputs.py +16 -10
- uncountable/types/api/recipes/set_recipe_metadata.py +10 -3
- uncountable/types/api/recipes/set_recipe_output_annotations.py +24 -12
- uncountable/types/api/recipes/set_recipe_output_file.py +55 -0
- uncountable/types/api/recipes/set_recipe_outputs.py +35 -12
- uncountable/types/api/recipes/set_recipe_tags.py +26 -9
- uncountable/types/api/recipes/set_recipe_total.py +59 -0
- uncountable/types/api/recipes/unarchive_recipes.py +10 -3
- uncountable/types/api/recipes/unlock_recipes.py +14 -6
- uncountable/types/api/runsheet/__init__.py +1 -0
- uncountable/types/api/runsheet/complete_async_upload.py +41 -0
- uncountable/types/api/triggers/run_trigger.py +11 -4
- uncountable/types/api/uploader/complete_async_parse.py +46 -0
- uncountable/types/api/uploader/invoke_uploader.py +13 -6
- uncountable/types/api/user/__init__.py +1 -0
- uncountable/types/api/user/get_current_user_info.py +40 -0
- uncountable/types/async_batch.py +2 -1
- uncountable/types/async_batch_processor.py +618 -18
- uncountable/types/async_batch_t.py +54 -7
- uncountable/types/async_jobs.py +8 -0
- uncountable/types/async_jobs_t.py +52 -0
- uncountable/types/auth_retrieval.py +11 -0
- uncountable/types/auth_retrieval_t.py +75 -0
- uncountable/types/base.py +0 -1
- uncountable/types/base_t.py +13 -11
- uncountable/types/calculations.py +0 -1
- uncountable/types/calculations_t.py +5 -2
- uncountable/types/chemical_structure.py +0 -1
- uncountable/types/chemical_structure_t.py +6 -5
- uncountable/types/client_base.py +751 -70
- uncountable/types/client_config.py +1 -1
- uncountable/types/client_config_t.py +17 -3
- uncountable/types/curves.py +0 -1
- uncountable/types/curves_t.py +10 -7
- uncountable/types/data.py +12 -0
- uncountable/types/data_t.py +103 -0
- uncountable/types/entity.py +4 -1
- uncountable/types/entity_t.py +125 -7
- uncountable/types/experiment_groups.py +0 -1
- uncountable/types/experiment_groups_t.py +5 -2
- uncountable/types/exports.py +8 -0
- uncountable/types/exports_t.py +34 -0
- uncountable/types/field_values.py +19 -1
- uncountable/types/field_values_t.py +246 -9
- uncountable/types/fields.py +0 -1
- uncountable/types/fields_t.py +5 -2
- uncountable/types/generic_upload.py +6 -1
- uncountable/types/generic_upload_t.py +88 -9
- uncountable/types/id_source.py +0 -1
- uncountable/types/id_source_t.py +26 -7
- uncountable/types/identifier.py +0 -1
- uncountable/types/identifier_t.py +13 -5
- uncountable/types/input_attributes.py +0 -1
- uncountable/types/input_attributes_t.py +4 -4
- uncountable/types/inputs.py +1 -1
- uncountable/types/inputs_t.py +24 -4
- uncountable/types/integration_server.py +8 -0
- uncountable/types/integration_server_t.py +46 -0
- uncountable/types/integration_session.py +10 -0
- uncountable/types/integration_session_t.py +60 -0
- uncountable/types/integrations.py +10 -0
- uncountable/types/integrations_t.py +62 -0
- uncountable/types/job_definition.py +4 -6
- uncountable/types/job_definition_t.py +96 -65
- uncountable/types/listing.py +9 -0
- uncountable/types/listing_t.py +51 -0
- uncountable/types/notices.py +8 -0
- uncountable/types/notices_t.py +37 -0
- uncountable/types/notifications.py +11 -0
- uncountable/types/notifications_t.py +74 -0
- uncountable/types/outputs.py +0 -1
- uncountable/types/outputs_t.py +6 -3
- uncountable/types/overrides.py +9 -0
- uncountable/types/overrides_t.py +49 -0
- uncountable/types/permissions.py +0 -1
- uncountable/types/permissions_t.py +1 -2
- uncountable/types/phases.py +0 -1
- uncountable/types/phases_t.py +5 -2
- uncountable/types/post_base.py +0 -1
- uncountable/types/post_base_t.py +1 -2
- uncountable/types/queued_job.py +17 -0
- uncountable/types/queued_job_t.py +140 -0
- uncountable/types/recipe_identifiers.py +0 -1
- uncountable/types/recipe_identifiers_t.py +21 -8
- uncountable/types/recipe_inputs.py +0 -1
- uncountable/types/recipe_inputs_t.py +1 -2
- uncountable/types/recipe_links.py +0 -1
- uncountable/types/recipe_links_t.py +7 -4
- uncountable/types/recipe_metadata.py +0 -1
- uncountable/types/recipe_metadata_t.py +14 -9
- uncountable/types/recipe_output_metadata.py +0 -1
- uncountable/types/recipe_output_metadata_t.py +5 -2
- uncountable/types/recipe_tags.py +0 -1
- uncountable/types/recipe_tags_t.py +5 -2
- uncountable/types/recipe_workflow_steps.py +0 -1
- uncountable/types/recipe_workflow_steps_t.py +14 -7
- uncountable/types/recipes.py +0 -1
- uncountable/types/recipes_t.py +6 -2
- uncountable/types/response.py +0 -1
- uncountable/types/response_t.py +3 -2
- uncountable/types/secret_retrieval.py +0 -1
- uncountable/types/secret_retrieval_t.py +13 -7
- uncountable/types/sockets.py +20 -0
- uncountable/types/sockets_t.py +169 -0
- uncountable/types/structured_filters.py +25 -0
- uncountable/types/structured_filters_t.py +248 -0
- uncountable/types/units.py +0 -1
- uncountable/types/units_t.py +5 -2
- uncountable/types/uploader.py +24 -0
- uncountable/types/uploader_t.py +222 -0
- uncountable/types/users.py +0 -1
- uncountable/types/users_t.py +5 -2
- uncountable/types/webhook_job.py +9 -0
- uncountable/types/webhook_job_t.py +48 -0
- uncountable/types/workflows.py +0 -1
- uncountable/types/workflows_t.py +10 -4
- uncountablepythonsdk-0.0.131.dist-info/METADATA +64 -0
- uncountablepythonsdk-0.0.131.dist-info/RECORD +363 -0
- {UncountablePythonSDK-0.0.52.dist-info → uncountablepythonsdk-0.0.131.dist-info}/WHEEL +1 -1
- UncountablePythonSDK-0.0.52.dist-info/METADATA +0 -56
- UncountablePythonSDK-0.0.52.dist-info/RECORD +0 -246
- docs/quickstart.md +0 -19
- uncountable/core/version.py +0 -11
- {UncountablePythonSDK-0.0.52.dist-info → uncountablepythonsdk-0.0.131.dist-info}/top_level.txt +0 -0
pkgs/type_spec/builder.py
CHANGED
|
@@ -10,12 +10,26 @@ import re
|
|
|
10
10
|
from collections import defaultdict
|
|
11
11
|
from dataclasses import MISSING, dataclass
|
|
12
12
|
from enum import Enum, StrEnum, auto
|
|
13
|
-
from typing import Any,
|
|
13
|
+
from typing import Any, Self
|
|
14
14
|
|
|
15
15
|
from . import util
|
|
16
|
-
from .
|
|
16
|
+
from .builder_types import CrossOutputPaths
|
|
17
|
+
from .non_discriminated_union_exceptions import NON_DISCRIMINATED_UNION_EXCEPTIONS
|
|
18
|
+
from .util import parse_type_str
|
|
17
19
|
|
|
18
20
|
RawDict = dict[Any, Any]
|
|
21
|
+
EndpointKey = str
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class PathMapping(StrEnum):
|
|
25
|
+
NO_MAPPING = "no_mapping"
|
|
26
|
+
DEFAULT_MAPPING = "default_mapping"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(kw_only=True)
|
|
30
|
+
class APIEndpointInfo:
|
|
31
|
+
root_path: str
|
|
32
|
+
path_mapping: PathMapping
|
|
19
33
|
|
|
20
34
|
|
|
21
35
|
class StabilityLevel(StrEnum):
|
|
@@ -24,7 +38,6 @@ class StabilityLevel(StrEnum):
|
|
|
24
38
|
"""
|
|
25
39
|
|
|
26
40
|
draft = "draft"
|
|
27
|
-
alpha = "alpha"
|
|
28
41
|
beta = "beta"
|
|
29
42
|
stable = "stable"
|
|
30
43
|
|
|
@@ -47,7 +60,7 @@ class PropertyConvertValue(StrEnum):
|
|
|
47
60
|
@dataclass
|
|
48
61
|
class SpecProperty:
|
|
49
62
|
name: str
|
|
50
|
-
label:
|
|
63
|
+
label: str | None
|
|
51
64
|
spec_type: SpecType
|
|
52
65
|
extant: PropertyExtant
|
|
53
66
|
convert_value: PropertyConvertValue
|
|
@@ -61,6 +74,7 @@ class SpecProperty:
|
|
|
61
74
|
# Holds extra information that will be emitted along with type_info. The builder knows nothing
|
|
62
75
|
# about the contents of this information.
|
|
63
76
|
ext_info: Any = None
|
|
77
|
+
explicit_default: bool = False
|
|
64
78
|
|
|
65
79
|
|
|
66
80
|
class NameCase(StrEnum):
|
|
@@ -255,7 +269,7 @@ class SpecTypeLiteralWrapper(SpecType):
|
|
|
255
269
|
return [self.value_type]
|
|
256
270
|
|
|
257
271
|
|
|
258
|
-
def unwrap_literal_type(stype: SpecType) ->
|
|
272
|
+
def unwrap_literal_type(stype: SpecType) -> SpecTypeLiteralWrapper | None:
|
|
259
273
|
if isinstance(stype, SpecTypeInstance) and stype.defn_type.is_base_type(
|
|
260
274
|
BaseTypeName.s_literal
|
|
261
275
|
):
|
|
@@ -283,7 +297,7 @@ class SpecTypeDefn(SpecType):
|
|
|
283
297
|
) -> None:
|
|
284
298
|
self.namespace = namespace
|
|
285
299
|
self.name = name
|
|
286
|
-
self.label:
|
|
300
|
+
self.label: str | None = None
|
|
287
301
|
|
|
288
302
|
self.is_predefined = is_predefined
|
|
289
303
|
self.name_case = NameCase.convert
|
|
@@ -293,6 +307,8 @@ class SpecTypeDefn(SpecType):
|
|
|
293
307
|
self._is_value_converted = _is_value_converted
|
|
294
308
|
self._is_value_to_string = False
|
|
295
309
|
self._is_valid_parameter = True
|
|
310
|
+
self._is_dynamic_allowed = False
|
|
311
|
+
self._default_extant: PropertyExtant | None = None
|
|
296
312
|
self.ext_info: Any = None
|
|
297
313
|
|
|
298
314
|
def is_value_converted(self) -> bool:
|
|
@@ -304,20 +320,43 @@ class SpecTypeDefn(SpecType):
|
|
|
304
320
|
def is_valid_parameter(self) -> bool:
|
|
305
321
|
return self._is_valid_parameter
|
|
306
322
|
|
|
323
|
+
def is_dynamic_allowed(self) -> bool:
|
|
324
|
+
return self._is_dynamic_allowed
|
|
325
|
+
|
|
307
326
|
def is_base_type(self, type_: BaseTypeName) -> bool:
|
|
308
327
|
return self.is_base and self.name == type_
|
|
309
328
|
|
|
329
|
+
def can_process(self, builder: SpecBuilder, data: RawDict) -> bool:
|
|
330
|
+
return True
|
|
331
|
+
|
|
310
332
|
@abc.abstractmethod
|
|
311
333
|
def process(self, builder: SpecBuilder, data: RawDict) -> None: ...
|
|
312
334
|
|
|
313
335
|
def base_process(
|
|
314
336
|
self, builder: SpecBuilder, data: RawDict, extra_names: list[str]
|
|
315
337
|
) -> None:
|
|
316
|
-
util.check_fields(
|
|
338
|
+
util.check_fields(
|
|
339
|
+
data,
|
|
340
|
+
[
|
|
341
|
+
"ext_info",
|
|
342
|
+
"label",
|
|
343
|
+
"is_dynamic_allowed",
|
|
344
|
+
"default_extant",
|
|
345
|
+
]
|
|
346
|
+
+ extra_names,
|
|
347
|
+
)
|
|
317
348
|
|
|
318
349
|
self.ext_info = data.get("ext_info")
|
|
319
350
|
self.label = data.get("label")
|
|
320
351
|
|
|
352
|
+
is_dynamic_allowed = data.get("is_dynamic_allowed", False)
|
|
353
|
+
assert isinstance(is_dynamic_allowed, bool)
|
|
354
|
+
self._is_dynamic_allowed = is_dynamic_allowed
|
|
355
|
+
|
|
356
|
+
default_extant = data.get("default_extant")
|
|
357
|
+
if default_extant is not None:
|
|
358
|
+
self._default_extant = PropertyExtant(default_extant)
|
|
359
|
+
|
|
321
360
|
def _process_property(
|
|
322
361
|
self, builder: SpecBuilder, spec_name: str, data: RawDict
|
|
323
362
|
) -> SpecProperty:
|
|
@@ -336,18 +375,18 @@ class SpecTypeDefn(SpecType):
|
|
|
336
375
|
],
|
|
337
376
|
)
|
|
338
377
|
try:
|
|
339
|
-
|
|
378
|
+
extant_type_str = data.get("extant")
|
|
379
|
+
extant_type = (
|
|
380
|
+
PropertyExtant(extant_type_str) if extant_type_str is not None else None
|
|
381
|
+
)
|
|
382
|
+
extant = extant_type or self._default_extant
|
|
340
383
|
if spec_name.endswith("?"):
|
|
341
|
-
if
|
|
384
|
+
if extant is not None:
|
|
342
385
|
raise Exception("cannot specify extant with ?")
|
|
343
386
|
extant = PropertyExtant.optional
|
|
344
387
|
name = spec_name[:-1]
|
|
345
388
|
else:
|
|
346
|
-
extant =
|
|
347
|
-
PropertyExtant.required
|
|
348
|
-
if extant_type is None
|
|
349
|
-
else PropertyExtant(extant_type)
|
|
350
|
-
)
|
|
389
|
+
extant = extant or PropertyExtant.required
|
|
351
390
|
name = spec_name
|
|
352
391
|
|
|
353
392
|
property_name_case = self.name_case
|
|
@@ -356,9 +395,9 @@ class SpecTypeDefn(SpecType):
|
|
|
356
395
|
property_name_case = NameCase(name_case_raw)
|
|
357
396
|
|
|
358
397
|
if property_name_case != NameCase.preserve:
|
|
359
|
-
assert util.is_valid_property_name(
|
|
360
|
-
name
|
|
361
|
-
)
|
|
398
|
+
assert util.is_valid_property_name(name), (
|
|
399
|
+
f"{name} is not a valid property name"
|
|
400
|
+
)
|
|
362
401
|
|
|
363
402
|
data_type = data.get("type")
|
|
364
403
|
builder.ensure(data_type is not None, "missing `type` entry")
|
|
@@ -369,6 +408,7 @@ class SpecTypeDefn(SpecType):
|
|
|
369
408
|
ptype = builder.parse_type(self.namespace, data_type, scope=self)
|
|
370
409
|
|
|
371
410
|
default_spec = data.get("default", MISSING)
|
|
411
|
+
explicit_default = default_spec != MISSING
|
|
372
412
|
if default_spec == MISSING:
|
|
373
413
|
has_default = False
|
|
374
414
|
default = None
|
|
@@ -376,7 +416,10 @@ class SpecTypeDefn(SpecType):
|
|
|
376
416
|
has_default = True
|
|
377
417
|
# IMPROVE: check the type against the ptype
|
|
378
418
|
default = default_spec
|
|
379
|
-
|
|
419
|
+
if extant == PropertyExtant.missing and explicit_default:
|
|
420
|
+
raise Exception(
|
|
421
|
+
f"cannot have extant missing and default for property {name}"
|
|
422
|
+
)
|
|
380
423
|
parse_require = False
|
|
381
424
|
literal = unwrap_literal_type(ptype)
|
|
382
425
|
if literal is not None:
|
|
@@ -399,6 +442,7 @@ class SpecTypeDefn(SpecType):
|
|
|
399
442
|
parse_require=parse_require,
|
|
400
443
|
desc=data.get("desc", None),
|
|
401
444
|
ext_info=ext_info,
|
|
445
|
+
explicit_default=explicit_default,
|
|
402
446
|
)
|
|
403
447
|
finally:
|
|
404
448
|
builder.pop_where()
|
|
@@ -436,7 +480,7 @@ class SpecTypeGenericParameter(SpecType):
|
|
|
436
480
|
|
|
437
481
|
|
|
438
482
|
class SpecTypeDefnObject(SpecTypeDefn):
|
|
439
|
-
base:
|
|
483
|
+
base: SpecTypeDefnObject | None
|
|
440
484
|
parameters: list[str]
|
|
441
485
|
|
|
442
486
|
def __init__(
|
|
@@ -444,7 +488,7 @@ class SpecTypeDefnObject(SpecTypeDefn):
|
|
|
444
488
|
namespace: SpecNamespace,
|
|
445
489
|
name: str,
|
|
446
490
|
*,
|
|
447
|
-
parameters:
|
|
491
|
+
parameters: list[str] | None = None,
|
|
448
492
|
is_base: bool = False,
|
|
449
493
|
is_predefined: bool = False,
|
|
450
494
|
is_hashable: bool = False,
|
|
@@ -461,7 +505,7 @@ class SpecTypeDefnObject(SpecTypeDefn):
|
|
|
461
505
|
self.parameters = parameters if parameters is not None else []
|
|
462
506
|
self.is_hashable = is_hashable
|
|
463
507
|
self.base = None
|
|
464
|
-
self.properties:
|
|
508
|
+
self.properties: dict[str, SpecProperty] | None = None
|
|
465
509
|
self._kw_only: bool = True
|
|
466
510
|
self.desc: str | None = None
|
|
467
511
|
|
|
@@ -532,13 +576,8 @@ class SpecTypeDefnObject(SpecTypeDefn):
|
|
|
532
576
|
base_type: list[SpecType] = [self.base] if self.base is not None else []
|
|
533
577
|
return base_type + prop_types
|
|
534
578
|
|
|
535
|
-
def
|
|
536
|
-
|
|
537
|
-
assert (
|
|
538
|
-
len(self.parameters) == 1
|
|
539
|
-
), "Only single generic parameters current supported"
|
|
540
|
-
return self.parameters[0]
|
|
541
|
-
return None
|
|
579
|
+
def get_generics(self) -> list[str]:
|
|
580
|
+
return self.parameters
|
|
542
581
|
|
|
543
582
|
|
|
544
583
|
class SpecTypeDefnAlias(SpecTypeDefn):
|
|
@@ -592,24 +631,34 @@ class SpecTypeDefnUnion(SpecTypeDefn):
|
|
|
592
631
|
self.discriminator_map = {}
|
|
593
632
|
for sub_type in self.types:
|
|
594
633
|
builder.push_where(sub_type.name)
|
|
595
|
-
assert isinstance(
|
|
596
|
-
|
|
597
|
-
)
|
|
634
|
+
assert isinstance(sub_type, SpecTypeDefnObject), (
|
|
635
|
+
"union-type-must-be-object"
|
|
636
|
+
)
|
|
598
637
|
assert sub_type.properties is not None
|
|
599
638
|
discriminator_type = sub_type.properties.get(self.discriminator)
|
|
600
|
-
assert (
|
|
601
|
-
|
|
602
|
-
)
|
|
639
|
+
assert discriminator_type is not None, (
|
|
640
|
+
f"missing-discriminator-field: {sub_type}"
|
|
641
|
+
)
|
|
603
642
|
prop_type = unwrap_literal_type(discriminator_type.spec_type)
|
|
604
643
|
assert prop_type is not None
|
|
605
644
|
assert prop_type.is_value_to_string()
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
645
|
+
value_type = prop_type.value_type
|
|
646
|
+
if isinstance(value_type, SpecTypeDefnStringEnum):
|
|
647
|
+
assert isinstance(prop_type.value, str)
|
|
648
|
+
discriminant = value_type.values[prop_type.value].value
|
|
649
|
+
else:
|
|
650
|
+
discriminant = str(prop_type.value)
|
|
651
|
+
assert discriminant not in self.discriminator_map, (
|
|
652
|
+
f"duplicated-discriminant, {discriminant} in {sub_type}"
|
|
653
|
+
)
|
|
610
654
|
self.discriminator_map[discriminant] = sub_type
|
|
611
655
|
|
|
612
656
|
builder.pop_where()
|
|
657
|
+
elif (
|
|
658
|
+
f"{self.namespace.name}.{self.name}"
|
|
659
|
+
not in NON_DISCRIMINATED_UNION_EXCEPTIONS
|
|
660
|
+
):
|
|
661
|
+
raise Exception(f"union requires a discriminator: {self.name}")
|
|
613
662
|
|
|
614
663
|
def get_referenced_types(self) -> list[SpecType]:
|
|
615
664
|
return self.types
|
|
@@ -648,7 +697,7 @@ class SpecTypeDefnExternal(SpecTypeDefn):
|
|
|
648
697
|
class StringEnumEntry:
|
|
649
698
|
name: str
|
|
650
699
|
value: str
|
|
651
|
-
label:
|
|
700
|
+
label: str | None = None
|
|
652
701
|
deprecated: bool = False
|
|
653
702
|
|
|
654
703
|
|
|
@@ -664,17 +713,32 @@ class SpecTypeDefnStringEnum(SpecTypeDefn):
|
|
|
664
713
|
)
|
|
665
714
|
self.values: dict[str, StringEnumEntry] = {}
|
|
666
715
|
self.desc: str | None = None
|
|
667
|
-
self.sql_type_name:
|
|
716
|
+
self.sql_type_name: str | None = None
|
|
668
717
|
self.emit_id_source = False
|
|
718
|
+
self.source_enums: list[SpecType] = []
|
|
719
|
+
|
|
720
|
+
def can_process(self, builder: SpecBuilder, data: dict[Any, Any]) -> bool:
|
|
721
|
+
source_enums = data.get("source_enums")
|
|
722
|
+
try:
|
|
723
|
+
for sub_type_str in source_enums or []:
|
|
724
|
+
sub_type = builder.parse_type(self.namespace, sub_type_str)
|
|
725
|
+
assert isinstance(sub_type, SpecTypeDefnStringEnum)
|
|
726
|
+
assert len(sub_type.values) > 0
|
|
727
|
+
except AssertionError:
|
|
728
|
+
return False
|
|
729
|
+
return super().can_process(builder, data)
|
|
669
730
|
|
|
670
731
|
def process(self, builder: SpecBuilder, data: RawDict) -> None:
|
|
671
732
|
super().base_process(
|
|
672
|
-
builder,
|
|
733
|
+
builder,
|
|
734
|
+
data,
|
|
735
|
+
["type", "desc", "values", "name_case", "sql", "emit", "source_enums"],
|
|
673
736
|
)
|
|
674
737
|
self.name_case = NameCase(data.get("name_case", "convert"))
|
|
675
738
|
self.values = {}
|
|
676
|
-
data_values = data
|
|
739
|
+
data_values = data.get("values")
|
|
677
740
|
self.desc = data.get("desc", None)
|
|
741
|
+
source_enums = data.get("source_enums", None)
|
|
678
742
|
if isinstance(data_values, dict):
|
|
679
743
|
for name, value in data_values.items():
|
|
680
744
|
builder.push_where(name)
|
|
@@ -687,6 +751,8 @@ class SpecTypeDefnStringEnum(SpecTypeDefn):
|
|
|
687
751
|
builder.ensure(
|
|
688
752
|
isinstance(enum_value, str), "enum value should be string"
|
|
689
753
|
)
|
|
754
|
+
assert isinstance(enum_value, str)
|
|
755
|
+
|
|
690
756
|
deprecated = value.get("deprecated", False)
|
|
691
757
|
builder.ensure(
|
|
692
758
|
isinstance(deprecated, bool),
|
|
@@ -712,10 +778,13 @@ class SpecTypeDefnStringEnum(SpecTypeDefn):
|
|
|
712
778
|
elif isinstance(data_values, list):
|
|
713
779
|
for value in data_values:
|
|
714
780
|
if value in self.values:
|
|
715
|
-
raise Exception(
|
|
781
|
+
raise Exception(
|
|
782
|
+
"duplicate value in typespec enum", self.name, value
|
|
783
|
+
)
|
|
716
784
|
self.values[value] = StringEnumEntry(name=value, value=value)
|
|
717
785
|
else:
|
|
718
|
-
|
|
786
|
+
if source_enums is None or data_values is not None:
|
|
787
|
+
raise Exception("unsupported values type")
|
|
719
788
|
|
|
720
789
|
sql_data = data.get("sql")
|
|
721
790
|
if sql_data is not None:
|
|
@@ -737,14 +806,24 @@ class SpecTypeDefnStringEnum(SpecTypeDefn):
|
|
|
737
806
|
builder.ensure(
|
|
738
807
|
entry.label is not None, f"need-label-for-id-source:{entry.name}"
|
|
739
808
|
)
|
|
809
|
+
for sub_type_str in source_enums or []:
|
|
810
|
+
sub_type = builder.parse_type(self.namespace, sub_type_str)
|
|
811
|
+
self.source_enums.append(sub_type)
|
|
812
|
+
|
|
813
|
+
for sub_type in self.source_enums:
|
|
814
|
+
builder.push_where(sub_type.name)
|
|
815
|
+
if isinstance(sub_type, SpecTypeDefnStringEnum):
|
|
816
|
+
self.values.update(sub_type.values)
|
|
817
|
+
builder.pop_where()
|
|
740
818
|
|
|
741
819
|
def get_referenced_types(self) -> list[SpecType]:
|
|
742
|
-
return
|
|
820
|
+
return self.source_enums
|
|
743
821
|
|
|
744
822
|
|
|
745
823
|
TOKEN_ENDPOINT = "$endpoint"
|
|
746
824
|
TOKEN_EMIT_IO_TS = "$emit_io_ts"
|
|
747
825
|
TOKEN_EMIT_TYPE_INFO = "$emit_type_info"
|
|
826
|
+
TOKEN_EMIT_TYPE_INFO_PYTHON = "$emit_type_info_python"
|
|
748
827
|
# The import token is only for explicit ordering of the files, to process constants
|
|
749
828
|
# and enums correctly. It does not impact the final generation of files, or the
|
|
750
829
|
# language imports. Those are still auto-resolved.
|
|
@@ -769,13 +848,13 @@ RE_ENDPOINT_ROOT = re.compile(r"\${([_a-z]+)}")
|
|
|
769
848
|
|
|
770
849
|
@dataclass(kw_only=True, frozen=True)
|
|
771
850
|
class _EndpointPathDetails:
|
|
772
|
-
root:
|
|
851
|
+
root: EndpointKey
|
|
773
852
|
root_path: str
|
|
774
853
|
resolved_path: str
|
|
775
854
|
|
|
776
855
|
|
|
777
856
|
def _resolve_endpoint_path(
|
|
778
|
-
path: str, api_endpoints: dict[
|
|
857
|
+
path: str, api_endpoints: dict[EndpointKey, APIEndpointInfo]
|
|
779
858
|
) -> _EndpointPathDetails:
|
|
780
859
|
root_path_source = path.split("/")[0]
|
|
781
860
|
root_match = RE_ENDPOINT_ROOT.fullmatch(root_path_source)
|
|
@@ -783,7 +862,7 @@ def _resolve_endpoint_path(
|
|
|
783
862
|
raise Exception(f"invalid-api-path-root:{root_path_source}")
|
|
784
863
|
|
|
785
864
|
root_var = root_match.group(1)
|
|
786
|
-
root_path = api_endpoints[root_var]
|
|
865
|
+
root_path = api_endpoints[root_var].root_path
|
|
787
866
|
|
|
788
867
|
_, *rest_path = path.split("/", 1)
|
|
789
868
|
resolved_path = "/".join([root_path] + rest_path)
|
|
@@ -793,19 +872,65 @@ def _resolve_endpoint_path(
|
|
|
793
872
|
)
|
|
794
873
|
|
|
795
874
|
|
|
796
|
-
class
|
|
797
|
-
|
|
798
|
-
|
|
875
|
+
class EndpointEmitType(StrEnum):
|
|
876
|
+
EMIT_ENDPOINT = "emit_endpoint"
|
|
877
|
+
EMIT_TYPES = "emit_types"
|
|
878
|
+
EMIT_NOTHING = "emit_nothing"
|
|
879
|
+
|
|
880
|
+
|
|
881
|
+
@dataclass(kw_only=True, frozen=True)
|
|
882
|
+
class EndpointSpecificPath:
|
|
883
|
+
root: EndpointKey
|
|
799
884
|
path_root: str
|
|
800
885
|
path_dirname: str
|
|
801
886
|
path_basename: str
|
|
887
|
+
function: str | None
|
|
888
|
+
|
|
889
|
+
|
|
890
|
+
def parse_endpoint_specific_path(
|
|
891
|
+
builder: SpecBuilder,
|
|
892
|
+
data_per_endpoint: RawDict | None,
|
|
893
|
+
) -> EndpointSpecificPath | None:
|
|
894
|
+
if data_per_endpoint is None:
|
|
895
|
+
return None
|
|
896
|
+
util.check_fields(
|
|
897
|
+
data_per_endpoint,
|
|
898
|
+
[
|
|
899
|
+
"path",
|
|
900
|
+
"function",
|
|
901
|
+
],
|
|
902
|
+
)
|
|
903
|
+
|
|
904
|
+
if "path" not in data_per_endpoint or data_per_endpoint["path"] is None:
|
|
905
|
+
return None
|
|
906
|
+
|
|
907
|
+
path = data_per_endpoint["path"].split("/")
|
|
908
|
+
|
|
909
|
+
assert len(path) > 1, "invalid-endpoint-path"
|
|
910
|
+
|
|
911
|
+
path_details = _resolve_endpoint_path(
|
|
912
|
+
data_per_endpoint["path"], builder.api_endpoints
|
|
913
|
+
)
|
|
914
|
+
|
|
915
|
+
result = EndpointSpecificPath(
|
|
916
|
+
function=data_per_endpoint.get("function"),
|
|
917
|
+
path_dirname="/".join(path[1:-1]),
|
|
918
|
+
path_basename=path[-1],
|
|
919
|
+
root=path_details.root,
|
|
920
|
+
path_root=path_details.root_path,
|
|
921
|
+
)
|
|
922
|
+
|
|
923
|
+
return result
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
class SpecEndpoint:
|
|
927
|
+
method: RouteMethod
|
|
802
928
|
data_loader: bool
|
|
803
|
-
is_sdk:
|
|
804
|
-
is_beta: bool
|
|
929
|
+
is_sdk: EndpointEmitType
|
|
805
930
|
stability_level: StabilityLevel | None
|
|
806
931
|
# Don't emit TypeScript endpoint code
|
|
807
932
|
suppress_ts: bool
|
|
808
|
-
|
|
933
|
+
deprecated: bool = False
|
|
809
934
|
async_batch_path: str | None = None
|
|
810
935
|
result_type: ResultType = ResultType.json
|
|
811
936
|
has_attachment: bool = False
|
|
@@ -813,21 +938,24 @@ class SpecEndpoint:
|
|
|
813
938
|
account_type: str | None
|
|
814
939
|
route_group: str | None
|
|
815
940
|
|
|
941
|
+
# function, path details per api endpoint
|
|
942
|
+
path_per_api_endpoint: dict[str, EndpointSpecificPath]
|
|
943
|
+
default_endpoint_key: EndpointKey
|
|
944
|
+
|
|
816
945
|
is_external: bool = False
|
|
817
946
|
|
|
818
947
|
def __init__(self) -> None:
|
|
819
948
|
pass
|
|
820
949
|
|
|
821
950
|
def process(self, builder: SpecBuilder, data: RawDict) -> None:
|
|
822
|
-
unused(builder)
|
|
823
951
|
util.check_fields(
|
|
824
952
|
data,
|
|
825
953
|
[
|
|
826
954
|
"method",
|
|
827
955
|
"path",
|
|
828
956
|
"data_loader",
|
|
957
|
+
"deprecated",
|
|
829
958
|
"is_sdk",
|
|
830
|
-
"is_beta",
|
|
831
959
|
"stability_level",
|
|
832
960
|
"async_batch_path",
|
|
833
961
|
"function",
|
|
@@ -838,24 +966,32 @@ class SpecEndpoint:
|
|
|
838
966
|
"has_attachment",
|
|
839
967
|
"account_type",
|
|
840
968
|
"route_group",
|
|
841
|
-
]
|
|
969
|
+
]
|
|
970
|
+
+ list(builder.api_endpoints.keys()),
|
|
842
971
|
)
|
|
843
972
|
self.method = RouteMethod(data["method"])
|
|
844
973
|
|
|
845
|
-
path = data["path"].split("/")
|
|
846
|
-
|
|
847
|
-
assert len(path) > 1, "invalid-endpoint-path"
|
|
848
|
-
|
|
849
|
-
# handle ${external} in the same way we handle ${materials} for now
|
|
850
|
-
self.path_dirname = "/".join(path[1:-1])
|
|
851
|
-
self.path_basename = path[-1]
|
|
852
|
-
|
|
853
974
|
data_loader = data.get("data_loader", False)
|
|
854
975
|
assert isinstance(data_loader, bool)
|
|
855
976
|
self.data_loader = data_loader
|
|
977
|
+
self.deprecated = data.get("deprecated", False)
|
|
978
|
+
|
|
979
|
+
is_sdk = data.get("is_sdk", EndpointEmitType.EMIT_NOTHING)
|
|
980
|
+
|
|
981
|
+
# backwards compatibility
|
|
982
|
+
if isinstance(is_sdk, bool):
|
|
983
|
+
if is_sdk is True:
|
|
984
|
+
is_sdk = EndpointEmitType.EMIT_ENDPOINT
|
|
985
|
+
else:
|
|
986
|
+
is_sdk = EndpointEmitType.EMIT_NOTHING
|
|
987
|
+
elif isinstance(is_sdk, str):
|
|
988
|
+
try:
|
|
989
|
+
is_sdk = EndpointEmitType(is_sdk)
|
|
990
|
+
except ValueError as e:
|
|
991
|
+
raise ValueError(f"Invalid value for is_sdk: {is_sdk}") from e
|
|
992
|
+
|
|
993
|
+
assert isinstance(is_sdk, EndpointEmitType)
|
|
856
994
|
|
|
857
|
-
is_sdk = data.get("is_sdk", False)
|
|
858
|
-
assert isinstance(is_sdk, bool)
|
|
859
995
|
self.is_sdk = is_sdk
|
|
860
996
|
|
|
861
997
|
route_group = data.get("route_group")
|
|
@@ -866,10 +1002,6 @@ class SpecEndpoint:
|
|
|
866
1002
|
assert account_type is None or isinstance(account_type, str)
|
|
867
1003
|
self.account_type = account_type
|
|
868
1004
|
|
|
869
|
-
is_beta = data.get("is_beta", False)
|
|
870
|
-
assert isinstance(is_beta, bool)
|
|
871
|
-
self.is_beta = is_beta
|
|
872
|
-
|
|
873
1005
|
stability_level_raw = data.get("stability_level")
|
|
874
1006
|
assert stability_level_raw is None or isinstance(stability_level_raw, str)
|
|
875
1007
|
self.stability_level = (
|
|
@@ -883,29 +1015,70 @@ class SpecEndpoint:
|
|
|
883
1015
|
assert isinstance(async_batch_path, str)
|
|
884
1016
|
self.async_batch_path = async_batch_path
|
|
885
1017
|
|
|
886
|
-
self.function = data.get("function")
|
|
887
|
-
|
|
888
1018
|
suppress_ts = data.get("suppress_ts", False)
|
|
889
1019
|
assert isinstance(suppress_ts, bool)
|
|
890
1020
|
self.suppress_ts = suppress_ts
|
|
891
1021
|
|
|
892
1022
|
self.result_type = ResultType(data.get("result_type", ResultType.json.value))
|
|
893
|
-
|
|
894
|
-
path_details = _resolve_endpoint_path(data["path"], builder.api_endpoints)
|
|
895
|
-
self.root = path_details.root
|
|
896
|
-
self.path_root = path_details.root_path
|
|
1023
|
+
self.has_attachment = data.get("has_attachment", False)
|
|
897
1024
|
self.desc = data.get("desc")
|
|
1025
|
+
|
|
1026
|
+
# compatibility with single-endpoint files
|
|
1027
|
+
default_endpoint_path = parse_endpoint_specific_path(
|
|
1028
|
+
builder,
|
|
1029
|
+
{"path": data.get("path"), "function": data.get("function")},
|
|
1030
|
+
)
|
|
1031
|
+
if default_endpoint_path is not None:
|
|
1032
|
+
assert default_endpoint_path.root in builder.api_endpoints, (
|
|
1033
|
+
"Default endpoint is not a valid API endpoint"
|
|
1034
|
+
)
|
|
1035
|
+
self.default_endpoint_key = default_endpoint_path.root
|
|
1036
|
+
self.path_per_api_endpoint = {
|
|
1037
|
+
self.default_endpoint_key: default_endpoint_path,
|
|
1038
|
+
}
|
|
1039
|
+
else:
|
|
1040
|
+
self.path_per_api_endpoint = {}
|
|
1041
|
+
shared_function_name = None
|
|
1042
|
+
for endpoint_key in builder.api_endpoints:
|
|
1043
|
+
endpoint_specific_path = parse_endpoint_specific_path(
|
|
1044
|
+
builder,
|
|
1045
|
+
data.get(endpoint_key),
|
|
1046
|
+
)
|
|
1047
|
+
if endpoint_specific_path is not None:
|
|
1048
|
+
self.path_per_api_endpoint[endpoint_key] = endpoint_specific_path
|
|
1049
|
+
if endpoint_specific_path.function is not None:
|
|
1050
|
+
fn_name = endpoint_specific_path.function.split(".")[-1]
|
|
1051
|
+
if shared_function_name is None:
|
|
1052
|
+
shared_function_name = fn_name
|
|
1053
|
+
assert shared_function_name == fn_name
|
|
1054
|
+
|
|
1055
|
+
if builder.top_namespace in self.path_per_api_endpoint:
|
|
1056
|
+
self.default_endpoint_key = builder.top_namespace
|
|
1057
|
+
elif len(self.path_per_api_endpoint) == 1:
|
|
1058
|
+
self.default_endpoint_key = next(
|
|
1059
|
+
iter(self.path_per_api_endpoint.keys())
|
|
1060
|
+
)
|
|
1061
|
+
else:
|
|
1062
|
+
raise RuntimeError("no clear default endpoint")
|
|
1063
|
+
|
|
1064
|
+
assert len(self.path_per_api_endpoint) > 0, (
|
|
1065
|
+
"Missing API endpoint path and function definitions for API call"
|
|
1066
|
+
)
|
|
1067
|
+
|
|
898
1068
|
# IMPROVE: remove need for is_external flag
|
|
899
|
-
self.is_external =
|
|
900
|
-
|
|
1069
|
+
self.is_external = (
|
|
1070
|
+
self.path_per_api_endpoint[self.default_endpoint_key].path_root
|
|
1071
|
+
== "api/external"
|
|
1072
|
+
)
|
|
901
1073
|
|
|
902
|
-
assert (
|
|
903
|
-
|
|
904
|
-
)
|
|
1074
|
+
assert self.is_sdk != EndpointEmitType.EMIT_ENDPOINT or self.desc is not None, (
|
|
1075
|
+
f"Endpoint description required for SDK endpoints, missing: {self.resolved_path}"
|
|
1076
|
+
)
|
|
905
1077
|
|
|
906
1078
|
@property
|
|
907
1079
|
def resolved_path(self: Self) -> str:
|
|
908
|
-
|
|
1080
|
+
default_endpoint_path = self.path_per_api_endpoint[self.default_endpoint_key]
|
|
1081
|
+
return f"{default_endpoint_path.path_root}/{default_endpoint_path.path_dirname}/{default_endpoint_path.path_basename}"
|
|
909
1082
|
|
|
910
1083
|
|
|
911
1084
|
def _parse_const(
|
|
@@ -928,7 +1101,7 @@ def _parse_const(
|
|
|
928
1101
|
elif const_type.defn_type.name == BaseTypeName.s_dict:
|
|
929
1102
|
assert isinstance(value, dict)
|
|
930
1103
|
builder.ensure(
|
|
931
|
-
len(const_type.parameters) == 2, "constant-dict-expects-
|
|
1104
|
+
len(const_type.parameters) == 2, "constant-dict-expects-two-types"
|
|
932
1105
|
)
|
|
933
1106
|
key_type = const_type.parameters[0]
|
|
934
1107
|
value_type = const_type.parameters[1]
|
|
@@ -972,7 +1145,14 @@ def _parse_const(
|
|
|
972
1145
|
return value
|
|
973
1146
|
|
|
974
1147
|
if const_type.name == BaseTypeName.s_boolean:
|
|
975
|
-
builder.ensure(
|
|
1148
|
+
builder.ensure(
|
|
1149
|
+
isinstance(value, bool), "invalid value for boolean constant"
|
|
1150
|
+
)
|
|
1151
|
+
return value
|
|
1152
|
+
|
|
1153
|
+
if not const_type.is_base:
|
|
1154
|
+
# IMPROVE: validate the object type properties before emission stage
|
|
1155
|
+
builder.ensure(isinstance(value, dict), "invalid value for object constant")
|
|
976
1156
|
return value
|
|
977
1157
|
|
|
978
1158
|
raise Exception("unsupported-const-scalar-type", const_type)
|
|
@@ -1004,7 +1184,9 @@ class SpecConstant:
|
|
|
1004
1184
|
assert isinstance(self.value, dict)
|
|
1005
1185
|
# the parsing checks that the values are correct, so a simple length check
|
|
1006
1186
|
# should be enough to check completeness
|
|
1007
|
-
builder.ensure(
|
|
1187
|
+
builder.ensure(
|
|
1188
|
+
len(key_type.values) == len(self.value), "incomplete-enum-map"
|
|
1189
|
+
)
|
|
1008
1190
|
|
|
1009
1191
|
|
|
1010
1192
|
class SpecNamespace:
|
|
@@ -1014,14 +1196,15 @@ class SpecNamespace:
|
|
|
1014
1196
|
):
|
|
1015
1197
|
self.types: dict[str, SpecTypeDefn] = {}
|
|
1016
1198
|
self.constants: dict[str, SpecConstant] = {}
|
|
1017
|
-
self.endpoint:
|
|
1199
|
+
self.endpoint: SpecEndpoint | None = None
|
|
1018
1200
|
self.emit_io_ts = False
|
|
1019
1201
|
self.emit_type_info = False
|
|
1202
|
+
self.emit_type_info_python = False
|
|
1020
1203
|
self.derive_types_from_io_ts = False
|
|
1021
|
-
self._imports:
|
|
1204
|
+
self._imports: list[str] | None = None
|
|
1022
1205
|
self.path = name.split(".")
|
|
1023
1206
|
self.name = self.path[-1]
|
|
1024
|
-
self._order:
|
|
1207
|
+
self._order: int | None = None
|
|
1025
1208
|
|
|
1026
1209
|
def _update_order(self, builder: SpecBuilder, recurse: int = 0) -> int:
|
|
1027
1210
|
if self._order is not None:
|
|
@@ -1071,6 +1254,11 @@ class SpecNamespace:
|
|
|
1071
1254
|
self.emit_type_info = defn
|
|
1072
1255
|
continue
|
|
1073
1256
|
|
|
1257
|
+
if name == TOKEN_EMIT_TYPE_INFO_PYTHON:
|
|
1258
|
+
assert defn in (True, False)
|
|
1259
|
+
self.emit_type_info_python = defn
|
|
1260
|
+
continue
|
|
1261
|
+
|
|
1074
1262
|
if name == TOKEN_IMPORT:
|
|
1075
1263
|
assert self._imports is None
|
|
1076
1264
|
imports = [defn] if isinstance(defn, str) else defn
|
|
@@ -1079,16 +1267,17 @@ class SpecNamespace:
|
|
|
1079
1267
|
continue
|
|
1080
1268
|
|
|
1081
1269
|
if "value" in defn:
|
|
1082
|
-
assert util.is_valid_property_name(
|
|
1083
|
-
name
|
|
1084
|
-
)
|
|
1270
|
+
assert util.is_valid_property_name(name), (
|
|
1271
|
+
f"{name} is not a valid constant name"
|
|
1272
|
+
)
|
|
1085
1273
|
spec_constant = SpecConstant(self, name)
|
|
1086
1274
|
self.constants[name] = spec_constant
|
|
1087
1275
|
continue
|
|
1088
1276
|
|
|
1089
1277
|
assert util.is_valid_type_name(name), f"{name} is not a valid type name"
|
|
1090
1278
|
assert name not in self.types, f"{name} is duplicate"
|
|
1091
|
-
defn_type = defn
|
|
1279
|
+
defn_type = defn.get("type")
|
|
1280
|
+
assert isinstance(defn_type, str), f"{name} requires a string type"
|
|
1092
1281
|
spec_type: SpecTypeDefn
|
|
1093
1282
|
if defn_type == DefnTypeName.s_alias:
|
|
1094
1283
|
spec_type = SpecTypeDefnAlias(self, name)
|
|
@@ -1100,7 +1289,11 @@ class SpecNamespace:
|
|
|
1100
1289
|
spec_type = SpecTypeDefnStringEnum(self, name)
|
|
1101
1290
|
else:
|
|
1102
1291
|
parameters = (
|
|
1103
|
-
[
|
|
1292
|
+
[
|
|
1293
|
+
parameter.name
|
|
1294
|
+
for name_parameters in parsed_name.parameters
|
|
1295
|
+
for parameter in name_parameters
|
|
1296
|
+
]
|
|
1104
1297
|
if parsed_name.parameters is not None
|
|
1105
1298
|
else None
|
|
1106
1299
|
)
|
|
@@ -1116,28 +1309,46 @@ class SpecNamespace:
|
|
|
1116
1309
|
Complete the definition of each type.
|
|
1117
1310
|
"""
|
|
1118
1311
|
builder.push_where(self.name)
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1312
|
+
items_to_process: list[NameDataPair] = [
|
|
1313
|
+
NameDataPair(full_name=full_name, data=defn)
|
|
1314
|
+
for full_name, defn in data.items()
|
|
1315
|
+
]
|
|
1316
|
+
while len(items_to_process) > 0:
|
|
1317
|
+
deferred_items: list[NameDataPair] = []
|
|
1318
|
+
for item in items_to_process:
|
|
1319
|
+
full_name = item.full_name
|
|
1320
|
+
defn = item.data
|
|
1321
|
+
parsed_name = parse_type_str(full_name)[0]
|
|
1322
|
+
name = parsed_name.name
|
|
1323
|
+
|
|
1324
|
+
if name in [
|
|
1325
|
+
TOKEN_EMIT_IO_TS,
|
|
1326
|
+
TOKEN_EMIT_TYPE_INFO,
|
|
1327
|
+
TOKEN_IMPORT,
|
|
1328
|
+
TOKEN_EMIT_TYPE_INFO_PYTHON,
|
|
1329
|
+
]:
|
|
1330
|
+
continue
|
|
1122
1331
|
|
|
1123
|
-
|
|
1124
|
-
continue
|
|
1332
|
+
builder.push_where(name)
|
|
1125
1333
|
|
|
1126
|
-
|
|
1334
|
+
if "value" in defn:
|
|
1335
|
+
spec_constant = self.constants[name]
|
|
1336
|
+
spec_constant.process(builder, defn)
|
|
1127
1337
|
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1338
|
+
elif name == TOKEN_ENDPOINT:
|
|
1339
|
+
assert self.endpoint
|
|
1340
|
+
self.endpoint.process(builder, defn)
|
|
1131
1341
|
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
spec_type.process(builder, defn)
|
|
1342
|
+
else:
|
|
1343
|
+
spec_type = self.types[name]
|
|
1344
|
+
if spec_type.can_process(builder, defn):
|
|
1345
|
+
spec_type.process(builder, defn)
|
|
1346
|
+
else:
|
|
1347
|
+
deferred_items.append(item)
|
|
1139
1348
|
|
|
1140
|
-
|
|
1349
|
+
builder.pop_where()
|
|
1350
|
+
assert len(deferred_items) < len(items_to_process)
|
|
1351
|
+
items_to_process = [deferred for deferred in deferred_items]
|
|
1141
1352
|
|
|
1142
1353
|
builder.pop_where()
|
|
1143
1354
|
|
|
@@ -1152,8 +1363,21 @@ class NamespaceDataPair:
|
|
|
1152
1363
|
data: RawDict
|
|
1153
1364
|
|
|
1154
1365
|
|
|
1366
|
+
@dataclass(kw_only=True)
|
|
1367
|
+
class NameDataPair:
|
|
1368
|
+
full_name: str
|
|
1369
|
+
data: RawDict
|
|
1370
|
+
|
|
1371
|
+
|
|
1155
1372
|
class SpecBuilder:
|
|
1156
|
-
def __init__(
|
|
1373
|
+
def __init__(
|
|
1374
|
+
self,
|
|
1375
|
+
*,
|
|
1376
|
+
api_endpoints: dict[EndpointKey, APIEndpointInfo],
|
|
1377
|
+
top_namespace: str,
|
|
1378
|
+
cross_output_paths: CrossOutputPaths | None,
|
|
1379
|
+
) -> None:
|
|
1380
|
+
self.top_namespace = top_namespace
|
|
1157
1381
|
self.where: list[str] = []
|
|
1158
1382
|
self.namespaces = {}
|
|
1159
1383
|
self.pending: list[NamespaceDataPair] = []
|
|
@@ -1162,6 +1386,7 @@ class SpecBuilder:
|
|
|
1162
1386
|
self.examples: dict[str, list[SpecEndpointExample]] = defaultdict(list)
|
|
1163
1387
|
self.guides: dict[SpecGuideKey, list[SpecGuide]] = defaultdict(list)
|
|
1164
1388
|
self.api_endpoints = api_endpoints
|
|
1389
|
+
self.cross_output_paths = cross_output_paths
|
|
1165
1390
|
base_namespace = SpecNamespace(name=base_namespace_name)
|
|
1166
1391
|
for base_type in BaseTypeName:
|
|
1167
1392
|
defn = SpecTypeDefnObject(base_namespace, base_type, is_base=True)
|
|
@@ -1179,9 +1404,13 @@ class SpecBuilder:
|
|
|
1179
1404
|
self.emit_id_source_enums: set[SpecTypeDefnStringEnum] = set()
|
|
1180
1405
|
|
|
1181
1406
|
this_dir = os.path.dirname(os.path.realpath(__file__))
|
|
1182
|
-
with open(
|
|
1407
|
+
with open(
|
|
1408
|
+
f"{this_dir}/parts/base.py.prepart", encoding="utf-8"
|
|
1409
|
+
) as py_base_part:
|
|
1183
1410
|
self.preparts["python"][base_namespace_name] = py_base_part.read()
|
|
1184
|
-
with open(
|
|
1411
|
+
with open(
|
|
1412
|
+
f"{this_dir}/parts/base.ts.prepart", encoding="utf-8"
|
|
1413
|
+
) as ts_base_part:
|
|
1185
1414
|
self.preparts["typescript"][base_namespace_name] = ts_base_part.read()
|
|
1186
1415
|
|
|
1187
1416
|
base_namespace.types["ObjectId"] = SpecTypeDefnObject(
|
|
@@ -1250,7 +1479,7 @@ class SpecBuilder:
|
|
|
1250
1479
|
self,
|
|
1251
1480
|
path: util.ParsedTypePath,
|
|
1252
1481
|
namespace: SpecNamespace,
|
|
1253
|
-
scope:
|
|
1482
|
+
scope: SpecTypeDefn | None = None,
|
|
1254
1483
|
top: bool = False,
|
|
1255
1484
|
) -> SpecType:
|
|
1256
1485
|
"""
|
|
@@ -1308,8 +1537,10 @@ class SpecBuilder:
|
|
|
1308
1537
|
if len(path) == 2:
|
|
1309
1538
|
if isinstance(defn_type, SpecTypeDefnStringEnum):
|
|
1310
1539
|
assert path[1].parameters is None
|
|
1540
|
+
statement = f"$import: [{defn_type.namespace.name}]"
|
|
1311
1541
|
self.ensure(
|
|
1312
|
-
path[1].name in defn_type.values,
|
|
1542
|
+
path[1].name in defn_type.values,
|
|
1543
|
+
f"missing-enum-value: {path} have you specified the dependency in an import statement: {statement}",
|
|
1313
1544
|
)
|
|
1314
1545
|
return SpecTypeLiteralWrapper(
|
|
1315
1546
|
value=path[1].name,
|
|
@@ -1331,11 +1562,13 @@ class SpecBuilder:
|
|
|
1331
1562
|
)
|
|
1332
1563
|
|
|
1333
1564
|
def parse_type(
|
|
1334
|
-
self, namespace: SpecNamespace, spec: str, scope:
|
|
1565
|
+
self, namespace: SpecNamespace, spec: str, scope: SpecTypeDefn | None = None
|
|
1335
1566
|
) -> SpecType:
|
|
1336
1567
|
self.push_where(spec)
|
|
1337
1568
|
parsed_type = util.parse_type_str(spec)
|
|
1338
|
-
result = self._convert_parsed_type(
|
|
1569
|
+
result = self._convert_parsed_type(
|
|
1570
|
+
parsed_type, namespace, top=True, scope=scope
|
|
1571
|
+
)
|
|
1339
1572
|
self.pop_where()
|
|
1340
1573
|
return result
|
|
1341
1574
|
|
|
@@ -1385,7 +1618,9 @@ class SpecBuilder:
|
|
|
1385
1618
|
path_meta: list[str] | None = meta.get("path")
|
|
1386
1619
|
guide_key: SpecGuideKey = RootGuideKey()
|
|
1387
1620
|
if path_meta is not None:
|
|
1388
|
-
path_details = _resolve_endpoint_path(
|
|
1621
|
+
path_details = _resolve_endpoint_path(
|
|
1622
|
+
"".join(path_meta), self.api_endpoints
|
|
1623
|
+
)
|
|
1389
1624
|
guide_key = EndpointGuideKey(path=path_details.resolved_path)
|
|
1390
1625
|
|
|
1391
1626
|
self.guides[guide_key].append(
|