UncountablePythonSDK 0.0.83__py3-none-any.whl → 0.0.132__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/basic_auth.py +7 -0
- examples/create_ingredient_sdk.py +34 -0
- examples/download_files.py +26 -0
- examples/integration-server/jobs/materials_auto/concurrent_cron.py +11 -0
- examples/integration-server/jobs/materials_auto/example_cron.py +3 -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 +17 -9
- examples/integration-server/jobs/materials_auto/profile.yaml +61 -0
- examples/integration-server/pyproject.toml +10 -10
- examples/oauth.py +7 -0
- examples/set_recipe_metadata_file.py +1 -1
- examples/upload_files.py +1 -2
- pkgs/argument_parser/__init__.py +8 -0
- pkgs/argument_parser/_is_namedtuple.py +3 -0
- pkgs/argument_parser/argument_parser.py +196 -63
- pkgs/filesystem_utils/__init__.py +1 -0
- pkgs/filesystem_utils/_blob_session.py +144 -0
- pkgs/filesystem_utils/_gdrive_session.py +5 -5
- pkgs/filesystem_utils/_s3_session.py +2 -1
- pkgs/filesystem_utils/_sftp_session.py +6 -3
- pkgs/filesystem_utils/file_type_utils.py +30 -10
- 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 +40 -48
- pkgs/serialization/serial_generic.py +16 -0
- pkgs/serialization/serial_union.py +16 -16
- 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 +2 -4
- pkgs/type_spec/builder.py +248 -70
- pkgs/type_spec/builder_types.py +9 -0
- pkgs/type_spec/config.py +40 -7
- pkgs/type_spec/cross_output_links.py +99 -0
- pkgs/type_spec/emit_open_api.py +121 -34
- pkgs/type_spec/emit_open_api_util.py +5 -5
- pkgs/type_spec/emit_python.py +277 -86
- pkgs/type_spec/emit_typescript.py +102 -29
- pkgs/type_spec/emit_typescript_util.py +66 -10
- pkgs/type_spec/load_types.py +16 -3
- 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 +11 -8
- 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 +115 -22
- 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 +3 -3
- pkgs/type_spec/value_spec/__main__.py +26 -9
- pkgs/type_spec/value_spec/convert_type.py +18 -0
- pkgs/type_spec/value_spec/emit_python.py +13 -3
- pkgs/type_spec/value_spec/types.py +1 -1
- uncountable/core/async_batch.py +1 -1
- uncountable/core/client.py +133 -34
- uncountable/core/environment.py +3 -3
- uncountable/core/file_upload.py +39 -15
- uncountable/integration/cli.py +116 -23
- uncountable/integration/construct_client.py +3 -3
- uncountable/integration/executors/executors.py +12 -2
- uncountable/integration/executors/generic_upload_executor.py +66 -14
- uncountable/integration/http_server/__init__.py +5 -0
- uncountable/integration/http_server/types.py +69 -0
- uncountable/integration/job.py +192 -7
- uncountable/integration/queue_runner/command_server/__init__.py +4 -0
- uncountable/integration/queue_runner/command_server/command_client.py +65 -0
- uncountable/integration/queue_runner/command_server/command_server.py +83 -5
- uncountable/integration/queue_runner/command_server/constants.py +4 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server.proto +36 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.py +28 -11
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.pyi +77 -1
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2_grpc.py +135 -0
- uncountable/integration/queue_runner/command_server/types.py +25 -2
- uncountable/integration/queue_runner/datastore/datastore_sqlite.py +168 -11
- uncountable/integration/queue_runner/datastore/interface.py +10 -0
- uncountable/integration/queue_runner/datastore/model.py +8 -1
- uncountable/integration/queue_runner/job_scheduler.py +63 -23
- uncountable/integration/queue_runner/queue_runner.py +10 -2
- uncountable/integration/queue_runner/worker.py +3 -5
- uncountable/integration/scan_profiles.py +1 -1
- uncountable/integration/scheduler.py +74 -25
- uncountable/integration/secret_retrieval/retrieve_secret.py +1 -1
- uncountable/integration/server.py +42 -12
- uncountable/integration/telemetry.py +63 -10
- uncountable/integration/webhook_server/entrypoint.py +39 -112
- uncountable/types/__init__.py +58 -1
- uncountable/types/api/batch/execute_batch.py +5 -6
- uncountable/types/api/batch/execute_batch_load_async.py +2 -3
- uncountable/types/api/chemical/convert_chemical_formats.py +10 -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 +7 -7
- uncountable/types/api/entity/create_entity.py +8 -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 +3 -4
- uncountable/types/api/entity/grant_entity_permissions.py +6 -6
- uncountable/types/api/entity/list_aggregate.py +79 -0
- uncountable/types/api/entity/list_entities.py +34 -10
- uncountable/types/api/entity/lock_entity.py +4 -4
- uncountable/types/api/entity/lookup_entity.py +116 -0
- uncountable/types/api/entity/resolve_entity_ids.py +5 -6
- uncountable/types/api/entity/set_entity_field_values.py +44 -0
- uncountable/types/api/entity/set_values.py +3 -3
- uncountable/types/api/entity/transition_entity_phase.py +14 -7
- uncountable/types/api/entity/unlock_entity.py +3 -3
- uncountable/types/api/equipment/associate_equipment_input.py +2 -3
- uncountable/types/api/field_options/upsert_field_options.py +7 -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 +6 -7
- uncountable/types/api/id_source/match_id_source.py +4 -5
- uncountable/types/api/input_groups/get_input_group_names.py +3 -4
- uncountable/types/api/inputs/create_inputs.py +10 -9
- uncountable/types/api/inputs/get_input_data.py +11 -12
- uncountable/types/api/inputs/get_input_names.py +6 -7
- uncountable/types/api/inputs/get_inputs_data.py +6 -7
- uncountable/types/api/inputs/set_input_attribute_values.py +5 -6
- uncountable/types/api/inputs/set_input_category.py +5 -5
- uncountable/types/api/inputs/set_input_subcategories.py +3 -3
- uncountable/types/api/inputs/set_intermediate_type.py +4 -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 +3 -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 +12 -13
- uncountable/types/api/outputs/get_output_names.py +5 -6
- uncountable/types/api/outputs/get_output_organization.py +173 -0
- uncountable/types/api/outputs/resolve_output_conditions.py +7 -8
- uncountable/types/api/permissions/set_core_permissions.py +16 -10
- uncountable/types/api/project/get_projects.py +6 -7
- uncountable/types/api/project/get_projects_data.py +7 -8
- uncountable/types/api/recipe_links/create_recipe_link.py +5 -5
- uncountable/types/api/recipe_links/remove_recipe_link.py +4 -4
- uncountable/types/api/recipe_metadata/get_recipe_metadata_data.py +6 -7
- uncountable/types/api/recipes/add_recipe_to_project.py +3 -3
- uncountable/types/api/recipes/add_time_series_data.py +64 -0
- uncountable/types/api/recipes/archive_recipes.py +4 -4
- uncountable/types/api/recipes/associate_recipe_as_input.py +5 -5
- uncountable/types/api/recipes/associate_recipe_as_lot.py +3 -3
- uncountable/types/api/recipes/clear_recipe_outputs.py +3 -3
- uncountable/types/api/recipes/create_mix_order.py +44 -0
- uncountable/types/api/recipes/create_recipe.py +8 -9
- uncountable/types/api/recipes/create_recipes.py +8 -9
- uncountable/types/api/recipes/disassociate_recipe_as_input.py +3 -3
- uncountable/types/api/recipes/edit_recipe_inputs.py +101 -24
- uncountable/types/api/recipes/get_column_calculation_values.py +4 -5
- uncountable/types/api/recipes/get_curve.py +4 -5
- uncountable/types/api/recipes/get_recipe_calculations.py +6 -7
- uncountable/types/api/recipes/get_recipe_links.py +3 -4
- uncountable/types/api/recipes/get_recipe_names.py +3 -4
- uncountable/types/api/recipes/get_recipe_output_metadata.py +5 -6
- uncountable/types/api/recipes/get_recipes_data.py +62 -34
- uncountable/types/api/recipes/lock_recipes.py +9 -8
- uncountable/types/api/recipes/remove_recipe_from_project.py +3 -3
- uncountable/types/api/recipes/set_recipe_inputs.py +9 -10
- uncountable/types/api/recipes/set_recipe_metadata.py +3 -3
- uncountable/types/api/recipes/set_recipe_output_annotations.py +11 -12
- uncountable/types/api/recipes/set_recipe_output_file.py +5 -6
- uncountable/types/api/recipes/set_recipe_outputs.py +24 -13
- uncountable/types/api/recipes/set_recipe_tags.py +14 -9
- uncountable/types/api/recipes/set_recipe_total.py +59 -0
- uncountable/types/api/recipes/unarchive_recipes.py +3 -3
- uncountable/types/api/recipes/unlock_recipes.py +7 -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 +4 -4
- uncountable/types/api/uploader/complete_async_parse.py +46 -0
- uncountable/types/api/uploader/invoke_uploader.py +4 -5
- uncountable/types/api/user/__init__.py +1 -0
- uncountable/types/api/user/get_current_user_info.py +40 -0
- uncountable/types/async_batch.py +1 -1
- uncountable/types/async_batch_processor.py +506 -23
- uncountable/types/async_batch_t.py +35 -8
- uncountable/types/async_jobs.py +0 -1
- uncountable/types/async_jobs_t.py +1 -2
- uncountable/types/auth_retrieval.py +0 -1
- uncountable/types/auth_retrieval_t.py +6 -6
- uncountable/types/base.py +0 -1
- uncountable/types/base_t.py +11 -9
- uncountable/types/calculations.py +0 -1
- uncountable/types/calculations_t.py +1 -2
- uncountable/types/chemical_structure.py +0 -1
- uncountable/types/chemical_structure_t.py +5 -5
- uncountable/types/client_base.py +614 -69
- uncountable/types/client_config.py +1 -1
- uncountable/types/client_config_t.py +13 -3
- uncountable/types/curves.py +0 -1
- uncountable/types/curves_t.py +6 -7
- uncountable/types/data.py +12 -0
- uncountable/types/data_t.py +103 -0
- uncountable/types/entity.py +1 -1
- uncountable/types/entity_t.py +90 -10
- uncountable/types/experiment_groups.py +0 -1
- uncountable/types/experiment_groups_t.py +1 -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 +242 -9
- uncountable/types/fields.py +0 -1
- uncountable/types/fields_t.py +1 -2
- uncountable/types/generic_upload.py +0 -1
- uncountable/types/generic_upload_t.py +14 -14
- uncountable/types/id_source.py +0 -1
- uncountable/types/id_source_t.py +13 -7
- uncountable/types/identifier.py +0 -1
- uncountable/types/identifier_t.py +10 -5
- uncountable/types/input_attributes.py +0 -1
- uncountable/types/input_attributes_t.py +3 -4
- uncountable/types/inputs.py +0 -1
- uncountable/types/inputs_t.py +3 -4
- uncountable/types/integration_server.py +0 -1
- uncountable/types/integration_server_t.py +13 -4
- 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 +2 -1
- uncountable/types/job_definition_t.py +57 -32
- 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 +2 -3
- uncountable/types/overrides.py +0 -1
- uncountable/types/overrides_t.py +10 -4
- uncountable/types/permissions.py +0 -1
- uncountable/types/permissions_t.py +1 -2
- uncountable/types/phases.py +0 -1
- uncountable/types/phases_t.py +1 -2
- uncountable/types/post_base.py +0 -1
- uncountable/types/post_base_t.py +1 -2
- uncountable/types/queued_job.py +2 -1
- uncountable/types/queued_job_t.py +29 -12
- uncountable/types/recipe_identifiers.py +0 -1
- uncountable/types/recipe_identifiers_t.py +18 -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 +3 -4
- uncountable/types/recipe_metadata.py +0 -1
- uncountable/types/recipe_metadata_t.py +9 -10
- uncountable/types/recipe_output_metadata.py +0 -1
- uncountable/types/recipe_output_metadata_t.py +1 -2
- uncountable/types/recipe_tags.py +0 -1
- uncountable/types/recipe_tags_t.py +1 -2
- uncountable/types/recipe_workflow_steps.py +0 -1
- uncountable/types/recipe_workflow_steps_t.py +7 -7
- uncountable/types/recipes.py +0 -1
- uncountable/types/recipes_t.py +2 -2
- uncountable/types/response.py +0 -1
- uncountable/types/response_t.py +2 -2
- uncountable/types/secret_retrieval.py +0 -1
- uncountable/types/secret_retrieval_t.py +7 -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 +1 -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 +1 -2
- uncountable/types/webhook_job.py +1 -1
- uncountable/types/webhook_job_t.py +14 -3
- uncountable/types/workflows.py +0 -1
- uncountable/types/workflows_t.py +3 -4
- uncountablepythonsdk-0.0.132.dist-info/METADATA +64 -0
- uncountablepythonsdk-0.0.132.dist-info/RECORD +363 -0
- {UncountablePythonSDK-0.0.83.dist-info → uncountablepythonsdk-0.0.132.dist-info}/WHEEL +1 -1
- UncountablePythonSDK-0.0.83.dist-info/METADATA +0 -60
- UncountablePythonSDK-0.0.83.dist-info/RECORD +0 -292
- docs/quickstart.md +0 -19
- {UncountablePythonSDK-0.0.83.dist-info → uncountablepythonsdk-0.0.132.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,6 +320,9 @@ 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
|
|
|
@@ -316,11 +335,28 @@ class SpecTypeDefn(SpecType):
|
|
|
316
335
|
def base_process(
|
|
317
336
|
self, builder: SpecBuilder, data: RawDict, extra_names: list[str]
|
|
318
337
|
) -> None:
|
|
319
|
-
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
|
+
)
|
|
320
348
|
|
|
321
349
|
self.ext_info = data.get("ext_info")
|
|
322
350
|
self.label = data.get("label")
|
|
323
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
|
+
|
|
324
360
|
def _process_property(
|
|
325
361
|
self, builder: SpecBuilder, spec_name: str, data: RawDict
|
|
326
362
|
) -> SpecProperty:
|
|
@@ -339,18 +375,18 @@ class SpecTypeDefn(SpecType):
|
|
|
339
375
|
],
|
|
340
376
|
)
|
|
341
377
|
try:
|
|
342
|
-
|
|
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
|
|
343
383
|
if spec_name.endswith("?"):
|
|
344
|
-
if
|
|
384
|
+
if extant is not None:
|
|
345
385
|
raise Exception("cannot specify extant with ?")
|
|
346
386
|
extant = PropertyExtant.optional
|
|
347
387
|
name = spec_name[:-1]
|
|
348
388
|
else:
|
|
349
|
-
extant =
|
|
350
|
-
PropertyExtant.required
|
|
351
|
-
if extant_type is None
|
|
352
|
-
else PropertyExtant(extant_type)
|
|
353
|
-
)
|
|
389
|
+
extant = extant or PropertyExtant.required
|
|
354
390
|
name = spec_name
|
|
355
391
|
|
|
356
392
|
property_name_case = self.name_case
|
|
@@ -372,6 +408,7 @@ class SpecTypeDefn(SpecType):
|
|
|
372
408
|
ptype = builder.parse_type(self.namespace, data_type, scope=self)
|
|
373
409
|
|
|
374
410
|
default_spec = data.get("default", MISSING)
|
|
411
|
+
explicit_default = default_spec != MISSING
|
|
375
412
|
if default_spec == MISSING:
|
|
376
413
|
has_default = False
|
|
377
414
|
default = None
|
|
@@ -379,7 +416,10 @@ class SpecTypeDefn(SpecType):
|
|
|
379
416
|
has_default = True
|
|
380
417
|
# IMPROVE: check the type against the ptype
|
|
381
418
|
default = default_spec
|
|
382
|
-
|
|
419
|
+
if extant == PropertyExtant.missing and explicit_default:
|
|
420
|
+
raise Exception(
|
|
421
|
+
f"cannot have extant missing and default for property {name}"
|
|
422
|
+
)
|
|
383
423
|
parse_require = False
|
|
384
424
|
literal = unwrap_literal_type(ptype)
|
|
385
425
|
if literal is not None:
|
|
@@ -402,6 +442,7 @@ class SpecTypeDefn(SpecType):
|
|
|
402
442
|
parse_require=parse_require,
|
|
403
443
|
desc=data.get("desc", None),
|
|
404
444
|
ext_info=ext_info,
|
|
445
|
+
explicit_default=explicit_default,
|
|
405
446
|
)
|
|
406
447
|
finally:
|
|
407
448
|
builder.pop_where()
|
|
@@ -439,7 +480,7 @@ class SpecTypeGenericParameter(SpecType):
|
|
|
439
480
|
|
|
440
481
|
|
|
441
482
|
class SpecTypeDefnObject(SpecTypeDefn):
|
|
442
|
-
base:
|
|
483
|
+
base: SpecTypeDefnObject | None
|
|
443
484
|
parameters: list[str]
|
|
444
485
|
|
|
445
486
|
def __init__(
|
|
@@ -447,7 +488,7 @@ class SpecTypeDefnObject(SpecTypeDefn):
|
|
|
447
488
|
namespace: SpecNamespace,
|
|
448
489
|
name: str,
|
|
449
490
|
*,
|
|
450
|
-
parameters:
|
|
491
|
+
parameters: list[str] | None = None,
|
|
451
492
|
is_base: bool = False,
|
|
452
493
|
is_predefined: bool = False,
|
|
453
494
|
is_hashable: bool = False,
|
|
@@ -464,7 +505,7 @@ class SpecTypeDefnObject(SpecTypeDefn):
|
|
|
464
505
|
self.parameters = parameters if parameters is not None else []
|
|
465
506
|
self.is_hashable = is_hashable
|
|
466
507
|
self.base = None
|
|
467
|
-
self.properties:
|
|
508
|
+
self.properties: dict[str, SpecProperty] | None = None
|
|
468
509
|
self._kw_only: bool = True
|
|
469
510
|
self.desc: str | None = None
|
|
470
511
|
|
|
@@ -601,13 +642,23 @@ class SpecTypeDefnUnion(SpecTypeDefn):
|
|
|
601
642
|
prop_type = unwrap_literal_type(discriminator_type.spec_type)
|
|
602
643
|
assert prop_type is not None
|
|
603
644
|
assert prop_type.is_value_to_string()
|
|
604
|
-
|
|
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)
|
|
605
651
|
assert discriminant not in self.discriminator_map, (
|
|
606
652
|
f"duplicated-discriminant, {discriminant} in {sub_type}"
|
|
607
653
|
)
|
|
608
654
|
self.discriminator_map[discriminant] = sub_type
|
|
609
655
|
|
|
610
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}")
|
|
611
662
|
|
|
612
663
|
def get_referenced_types(self) -> list[SpecType]:
|
|
613
664
|
return self.types
|
|
@@ -646,7 +697,7 @@ class SpecTypeDefnExternal(SpecTypeDefn):
|
|
|
646
697
|
class StringEnumEntry:
|
|
647
698
|
name: str
|
|
648
699
|
value: str
|
|
649
|
-
label:
|
|
700
|
+
label: str | None = None
|
|
650
701
|
deprecated: bool = False
|
|
651
702
|
|
|
652
703
|
|
|
@@ -662,7 +713,7 @@ class SpecTypeDefnStringEnum(SpecTypeDefn):
|
|
|
662
713
|
)
|
|
663
714
|
self.values: dict[str, StringEnumEntry] = {}
|
|
664
715
|
self.desc: str | None = None
|
|
665
|
-
self.sql_type_name:
|
|
716
|
+
self.sql_type_name: str | None = None
|
|
666
717
|
self.emit_id_source = False
|
|
667
718
|
self.source_enums: list[SpecType] = []
|
|
668
719
|
|
|
@@ -700,6 +751,8 @@ class SpecTypeDefnStringEnum(SpecTypeDefn):
|
|
|
700
751
|
builder.ensure(
|
|
701
752
|
isinstance(enum_value, str), "enum value should be string"
|
|
702
753
|
)
|
|
754
|
+
assert isinstance(enum_value, str)
|
|
755
|
+
|
|
703
756
|
deprecated = value.get("deprecated", False)
|
|
704
757
|
builder.ensure(
|
|
705
758
|
isinstance(deprecated, bool),
|
|
@@ -770,6 +823,7 @@ class SpecTypeDefnStringEnum(SpecTypeDefn):
|
|
|
770
823
|
TOKEN_ENDPOINT = "$endpoint"
|
|
771
824
|
TOKEN_EMIT_IO_TS = "$emit_io_ts"
|
|
772
825
|
TOKEN_EMIT_TYPE_INFO = "$emit_type_info"
|
|
826
|
+
TOKEN_EMIT_TYPE_INFO_PYTHON = "$emit_type_info_python"
|
|
773
827
|
# The import token is only for explicit ordering of the files, to process constants
|
|
774
828
|
# and enums correctly. It does not impact the final generation of files, or the
|
|
775
829
|
# language imports. Those are still auto-resolved.
|
|
@@ -794,13 +848,13 @@ RE_ENDPOINT_ROOT = re.compile(r"\${([_a-z]+)}")
|
|
|
794
848
|
|
|
795
849
|
@dataclass(kw_only=True, frozen=True)
|
|
796
850
|
class _EndpointPathDetails:
|
|
797
|
-
root:
|
|
851
|
+
root: EndpointKey
|
|
798
852
|
root_path: str
|
|
799
853
|
resolved_path: str
|
|
800
854
|
|
|
801
855
|
|
|
802
856
|
def _resolve_endpoint_path(
|
|
803
|
-
path: str, api_endpoints: dict[
|
|
857
|
+
path: str, api_endpoints: dict[EndpointKey, APIEndpointInfo]
|
|
804
858
|
) -> _EndpointPathDetails:
|
|
805
859
|
root_path_source = path.split("/")[0]
|
|
806
860
|
root_match = RE_ENDPOINT_ROOT.fullmatch(root_path_source)
|
|
@@ -808,7 +862,7 @@ def _resolve_endpoint_path(
|
|
|
808
862
|
raise Exception(f"invalid-api-path-root:{root_path_source}")
|
|
809
863
|
|
|
810
864
|
root_var = root_match.group(1)
|
|
811
|
-
root_path = api_endpoints[root_var]
|
|
865
|
+
root_path = api_endpoints[root_var].root_path
|
|
812
866
|
|
|
813
867
|
_, *rest_path = path.split("/", 1)
|
|
814
868
|
resolved_path = "/".join([root_path] + rest_path)
|
|
@@ -818,19 +872,65 @@ def _resolve_endpoint_path(
|
|
|
818
872
|
)
|
|
819
873
|
|
|
820
874
|
|
|
821
|
-
class
|
|
822
|
-
|
|
823
|
-
|
|
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
|
|
824
884
|
path_root: str
|
|
825
885
|
path_dirname: str
|
|
826
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
|
|
827
928
|
data_loader: bool
|
|
828
|
-
is_sdk:
|
|
829
|
-
is_beta: bool
|
|
929
|
+
is_sdk: EndpointEmitType
|
|
830
930
|
stability_level: StabilityLevel | None
|
|
831
931
|
# Don't emit TypeScript endpoint code
|
|
832
932
|
suppress_ts: bool
|
|
833
|
-
|
|
933
|
+
deprecated: bool = False
|
|
834
934
|
async_batch_path: str | None = None
|
|
835
935
|
result_type: ResultType = ResultType.json
|
|
836
936
|
has_attachment: bool = False
|
|
@@ -838,21 +938,24 @@ class SpecEndpoint:
|
|
|
838
938
|
account_type: str | None
|
|
839
939
|
route_group: str | None
|
|
840
940
|
|
|
941
|
+
# function, path details per api endpoint
|
|
942
|
+
path_per_api_endpoint: dict[str, EndpointSpecificPath]
|
|
943
|
+
default_endpoint_key: EndpointKey
|
|
944
|
+
|
|
841
945
|
is_external: bool = False
|
|
842
946
|
|
|
843
947
|
def __init__(self) -> None:
|
|
844
948
|
pass
|
|
845
949
|
|
|
846
950
|
def process(self, builder: SpecBuilder, data: RawDict) -> None:
|
|
847
|
-
unused(builder)
|
|
848
951
|
util.check_fields(
|
|
849
952
|
data,
|
|
850
953
|
[
|
|
851
954
|
"method",
|
|
852
955
|
"path",
|
|
853
956
|
"data_loader",
|
|
957
|
+
"deprecated",
|
|
854
958
|
"is_sdk",
|
|
855
|
-
"is_beta",
|
|
856
959
|
"stability_level",
|
|
857
960
|
"async_batch_path",
|
|
858
961
|
"function",
|
|
@@ -863,24 +966,32 @@ class SpecEndpoint:
|
|
|
863
966
|
"has_attachment",
|
|
864
967
|
"account_type",
|
|
865
968
|
"route_group",
|
|
866
|
-
]
|
|
969
|
+
]
|
|
970
|
+
+ list(builder.api_endpoints.keys()),
|
|
867
971
|
)
|
|
868
972
|
self.method = RouteMethod(data["method"])
|
|
869
973
|
|
|
870
|
-
path = data["path"].split("/")
|
|
871
|
-
|
|
872
|
-
assert len(path) > 1, "invalid-endpoint-path"
|
|
873
|
-
|
|
874
|
-
# handle ${external} in the same way we handle ${materials} for now
|
|
875
|
-
self.path_dirname = "/".join(path[1:-1])
|
|
876
|
-
self.path_basename = path[-1]
|
|
877
|
-
|
|
878
974
|
data_loader = data.get("data_loader", False)
|
|
879
975
|
assert isinstance(data_loader, bool)
|
|
880
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)
|
|
881
994
|
|
|
882
|
-
is_sdk = data.get("is_sdk", False)
|
|
883
|
-
assert isinstance(is_sdk, bool)
|
|
884
995
|
self.is_sdk = is_sdk
|
|
885
996
|
|
|
886
997
|
route_group = data.get("route_group")
|
|
@@ -891,10 +1002,6 @@ class SpecEndpoint:
|
|
|
891
1002
|
assert account_type is None or isinstance(account_type, str)
|
|
892
1003
|
self.account_type = account_type
|
|
893
1004
|
|
|
894
|
-
is_beta = data.get("is_beta", False)
|
|
895
|
-
assert isinstance(is_beta, bool)
|
|
896
|
-
self.is_beta = is_beta
|
|
897
|
-
|
|
898
1005
|
stability_level_raw = data.get("stability_level")
|
|
899
1006
|
assert stability_level_raw is None or isinstance(stability_level_raw, str)
|
|
900
1007
|
self.stability_level = (
|
|
@@ -908,29 +1015,70 @@ class SpecEndpoint:
|
|
|
908
1015
|
assert isinstance(async_batch_path, str)
|
|
909
1016
|
self.async_batch_path = async_batch_path
|
|
910
1017
|
|
|
911
|
-
self.function = data.get("function")
|
|
912
|
-
|
|
913
1018
|
suppress_ts = data.get("suppress_ts", False)
|
|
914
1019
|
assert isinstance(suppress_ts, bool)
|
|
915
1020
|
self.suppress_ts = suppress_ts
|
|
916
1021
|
|
|
917
1022
|
self.result_type = ResultType(data.get("result_type", ResultType.json.value))
|
|
918
|
-
|
|
919
|
-
path_details = _resolve_endpoint_path(data["path"], builder.api_endpoints)
|
|
920
|
-
self.root = path_details.root
|
|
921
|
-
self.path_root = path_details.root_path
|
|
1023
|
+
self.has_attachment = data.get("has_attachment", False)
|
|
922
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
|
+
|
|
923
1068
|
# IMPROVE: remove need for is_external flag
|
|
924
|
-
self.is_external =
|
|
925
|
-
|
|
1069
|
+
self.is_external = (
|
|
1070
|
+
self.path_per_api_endpoint[self.default_endpoint_key].path_root
|
|
1071
|
+
== "api/external"
|
|
1072
|
+
)
|
|
926
1073
|
|
|
927
|
-
assert
|
|
928
|
-
f"Endpoint description required for SDK endpoints, missing: {
|
|
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}"
|
|
929
1076
|
)
|
|
930
1077
|
|
|
931
1078
|
@property
|
|
932
1079
|
def resolved_path(self: Self) -> str:
|
|
933
|
-
|
|
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}"
|
|
934
1082
|
|
|
935
1083
|
|
|
936
1084
|
def _parse_const(
|
|
@@ -953,7 +1101,7 @@ def _parse_const(
|
|
|
953
1101
|
elif const_type.defn_type.name == BaseTypeName.s_dict:
|
|
954
1102
|
assert isinstance(value, dict)
|
|
955
1103
|
builder.ensure(
|
|
956
|
-
len(const_type.parameters) == 2, "constant-dict-expects-
|
|
1104
|
+
len(const_type.parameters) == 2, "constant-dict-expects-two-types"
|
|
957
1105
|
)
|
|
958
1106
|
key_type = const_type.parameters[0]
|
|
959
1107
|
value_type = const_type.parameters[1]
|
|
@@ -1002,6 +1150,11 @@ def _parse_const(
|
|
|
1002
1150
|
)
|
|
1003
1151
|
return value
|
|
1004
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")
|
|
1156
|
+
return value
|
|
1157
|
+
|
|
1005
1158
|
raise Exception("unsupported-const-scalar-type", const_type)
|
|
1006
1159
|
|
|
1007
1160
|
|
|
@@ -1043,14 +1196,15 @@ class SpecNamespace:
|
|
|
1043
1196
|
):
|
|
1044
1197
|
self.types: dict[str, SpecTypeDefn] = {}
|
|
1045
1198
|
self.constants: dict[str, SpecConstant] = {}
|
|
1046
|
-
self.endpoint:
|
|
1199
|
+
self.endpoint: SpecEndpoint | None = None
|
|
1047
1200
|
self.emit_io_ts = False
|
|
1048
1201
|
self.emit_type_info = False
|
|
1202
|
+
self.emit_type_info_python = False
|
|
1049
1203
|
self.derive_types_from_io_ts = False
|
|
1050
|
-
self._imports:
|
|
1204
|
+
self._imports: list[str] | None = None
|
|
1051
1205
|
self.path = name.split(".")
|
|
1052
1206
|
self.name = self.path[-1]
|
|
1053
|
-
self._order:
|
|
1207
|
+
self._order: int | None = None
|
|
1054
1208
|
|
|
1055
1209
|
def _update_order(self, builder: SpecBuilder, recurse: int = 0) -> int:
|
|
1056
1210
|
if self._order is not None:
|
|
@@ -1100,6 +1254,11 @@ class SpecNamespace:
|
|
|
1100
1254
|
self.emit_type_info = defn
|
|
1101
1255
|
continue
|
|
1102
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
|
+
|
|
1103
1262
|
if name == TOKEN_IMPORT:
|
|
1104
1263
|
assert self._imports is None
|
|
1105
1264
|
imports = [defn] if isinstance(defn, str) else defn
|
|
@@ -1117,7 +1276,8 @@ class SpecNamespace:
|
|
|
1117
1276
|
|
|
1118
1277
|
assert util.is_valid_type_name(name), f"{name} is not a valid type name"
|
|
1119
1278
|
assert name not in self.types, f"{name} is duplicate"
|
|
1120
|
-
defn_type = defn
|
|
1279
|
+
defn_type = defn.get("type")
|
|
1280
|
+
assert isinstance(defn_type, str), f"{name} requires a string type"
|
|
1121
1281
|
spec_type: SpecTypeDefn
|
|
1122
1282
|
if defn_type == DefnTypeName.s_alias:
|
|
1123
1283
|
spec_type = SpecTypeDefnAlias(self, name)
|
|
@@ -1161,7 +1321,12 @@ class SpecNamespace:
|
|
|
1161
1321
|
parsed_name = parse_type_str(full_name)[0]
|
|
1162
1322
|
name = parsed_name.name
|
|
1163
1323
|
|
|
1164
|
-
if name in [
|
|
1324
|
+
if name in [
|
|
1325
|
+
TOKEN_EMIT_IO_TS,
|
|
1326
|
+
TOKEN_EMIT_TYPE_INFO,
|
|
1327
|
+
TOKEN_IMPORT,
|
|
1328
|
+
TOKEN_EMIT_TYPE_INFO_PYTHON,
|
|
1329
|
+
]:
|
|
1165
1330
|
continue
|
|
1166
1331
|
|
|
1167
1332
|
builder.push_where(name)
|
|
@@ -1205,7 +1370,13 @@ class NameDataPair:
|
|
|
1205
1370
|
|
|
1206
1371
|
|
|
1207
1372
|
class SpecBuilder:
|
|
1208
|
-
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:
|
|
1209
1380
|
self.top_namespace = top_namespace
|
|
1210
1381
|
self.where: list[str] = []
|
|
1211
1382
|
self.namespaces = {}
|
|
@@ -1215,6 +1386,7 @@ class SpecBuilder:
|
|
|
1215
1386
|
self.examples: dict[str, list[SpecEndpointExample]] = defaultdict(list)
|
|
1216
1387
|
self.guides: dict[SpecGuideKey, list[SpecGuide]] = defaultdict(list)
|
|
1217
1388
|
self.api_endpoints = api_endpoints
|
|
1389
|
+
self.cross_output_paths = cross_output_paths
|
|
1218
1390
|
base_namespace = SpecNamespace(name=base_namespace_name)
|
|
1219
1391
|
for base_type in BaseTypeName:
|
|
1220
1392
|
defn = SpecTypeDefnObject(base_namespace, base_type, is_base=True)
|
|
@@ -1232,9 +1404,13 @@ class SpecBuilder:
|
|
|
1232
1404
|
self.emit_id_source_enums: set[SpecTypeDefnStringEnum] = set()
|
|
1233
1405
|
|
|
1234
1406
|
this_dir = os.path.dirname(os.path.realpath(__file__))
|
|
1235
|
-
with open(
|
|
1407
|
+
with open(
|
|
1408
|
+
f"{this_dir}/parts/base.py.prepart", encoding="utf-8"
|
|
1409
|
+
) as py_base_part:
|
|
1236
1410
|
self.preparts["python"][base_namespace_name] = py_base_part.read()
|
|
1237
|
-
with open(
|
|
1411
|
+
with open(
|
|
1412
|
+
f"{this_dir}/parts/base.ts.prepart", encoding="utf-8"
|
|
1413
|
+
) as ts_base_part:
|
|
1238
1414
|
self.preparts["typescript"][base_namespace_name] = ts_base_part.read()
|
|
1239
1415
|
|
|
1240
1416
|
base_namespace.types["ObjectId"] = SpecTypeDefnObject(
|
|
@@ -1303,7 +1479,7 @@ class SpecBuilder:
|
|
|
1303
1479
|
self,
|
|
1304
1480
|
path: util.ParsedTypePath,
|
|
1305
1481
|
namespace: SpecNamespace,
|
|
1306
|
-
scope:
|
|
1482
|
+
scope: SpecTypeDefn | None = None,
|
|
1307
1483
|
top: bool = False,
|
|
1308
1484
|
) -> SpecType:
|
|
1309
1485
|
"""
|
|
@@ -1361,8 +1537,10 @@ class SpecBuilder:
|
|
|
1361
1537
|
if len(path) == 2:
|
|
1362
1538
|
if isinstance(defn_type, SpecTypeDefnStringEnum):
|
|
1363
1539
|
assert path[1].parameters is None
|
|
1540
|
+
statement = f"$import: [{defn_type.namespace.name}]"
|
|
1364
1541
|
self.ensure(
|
|
1365
|
-
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}",
|
|
1366
1544
|
)
|
|
1367
1545
|
return SpecTypeLiteralWrapper(
|
|
1368
1546
|
value=path[1].name,
|
|
@@ -1384,7 +1562,7 @@ class SpecBuilder:
|
|
|
1384
1562
|
)
|
|
1385
1563
|
|
|
1386
1564
|
def parse_type(
|
|
1387
|
-
self, namespace: SpecNamespace, spec: str, scope:
|
|
1565
|
+
self, namespace: SpecNamespace, spec: str, scope: SpecTypeDefn | None = None
|
|
1388
1566
|
) -> SpecType:
|
|
1389
1567
|
self.push_where(spec)
|
|
1390
1568
|
parsed_type = util.parse_type_str(spec)
|