UncountablePythonSDK 0.0.7__py3-none-any.whl → 0.0.92__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of UncountablePythonSDK might be problematic. Click here for more details.
- UncountablePythonSDK-0.0.92.dist-info/METADATA +61 -0
- UncountablePythonSDK-0.0.92.dist-info/RECORD +301 -0
- {UncountablePythonSDK-0.0.7.dist-info → UncountablePythonSDK-0.0.92.dist-info}/WHEEL +1 -1
- {UncountablePythonSDK-0.0.7.dist-info → UncountablePythonSDK-0.0.92.dist-info}/top_level.txt +1 -1
- docs/.gitignore +1 -0
- docs/conf.py +57 -0
- docs/index.md +13 -0
- docs/justfile +12 -0
- docs/quickstart.md +19 -0
- docs/requirements.txt +7 -0
- docs/static/favicons/android-chrome-192x192.png +0 -0
- docs/static/favicons/android-chrome-512x512.png +0 -0
- docs/static/favicons/apple-touch-icon.png +0 -0
- docs/static/favicons/browserconfig.xml +9 -0
- docs/static/favicons/favicon-16x16.png +0 -0
- docs/static/favicons/favicon-32x32.png +0 -0
- docs/static/favicons/manifest.json +18 -0
- docs/static/favicons/mstile-150x150.png +0 -0
- docs/static/favicons/safari-pinned-tab.svg +32 -0
- docs/static/logo_blue.png +0 -0
- examples/async_batch.py +35 -0
- examples/create_entity.py +22 -17
- examples/download_files.py +26 -0
- examples/edit_recipe_inputs.py +50 -0
- examples/integration-server/jobs/materials_auto/example_cron.py +18 -0
- examples/integration-server/jobs/materials_auto/example_wh.py +15 -0
- examples/integration-server/jobs/materials_auto/profile.yaml +43 -0
- examples/integration-server/pyproject.toml +224 -0
- examples/invoke_uploader.py +26 -0
- examples/set_recipe_metadata_file.py +40 -0
- examples/set_recipe_output_file_sdk.py +26 -0
- examples/upload_files.py +18 -0
- pkgs/argument_parser/__init__.py +5 -0
- pkgs/argument_parser/_is_enum.py +1 -6
- pkgs/argument_parser/argument_parser.py +232 -76
- pkgs/argument_parser/case_convert.py +4 -3
- pkgs/filesystem_utils/__init__.py +20 -0
- pkgs/filesystem_utils/_blob_session.py +137 -0
- pkgs/filesystem_utils/_gdrive_session.py +309 -0
- pkgs/filesystem_utils/_local_session.py +69 -0
- pkgs/filesystem_utils/_s3_session.py +117 -0
- pkgs/filesystem_utils/_sftp_session.py +147 -0
- pkgs/filesystem_utils/file_type_utils.py +91 -0
- pkgs/filesystem_utils/filesystem_session.py +39 -0
- pkgs/py.typed +0 -0
- pkgs/serialization/__init__.py +8 -1
- pkgs/serialization/annotation.py +64 -0
- pkgs/serialization/opaque_key.py +1 -1
- pkgs/serialization/serial_alias.py +47 -0
- pkgs/serialization/serial_class.py +65 -50
- pkgs/serialization/serial_generic.py +16 -0
- pkgs/serialization/serial_union.py +84 -0
- pkgs/serialization/yaml.py +57 -0
- pkgs/serialization_util/__init__.py +7 -7
- pkgs/serialization_util/_get_type_for_serialization.py +1 -3
- pkgs/serialization_util/convert_to_snakecase.py +27 -0
- pkgs/serialization_util/dataclasses.py +14 -0
- pkgs/serialization_util/serialization_helpers.py +118 -73
- pkgs/strenum_compat/strenum_compat.py +1 -9
- pkgs/type_spec/actions_registry/__init__.py +0 -0
- pkgs/type_spec/actions_registry/__main__.py +126 -0
- pkgs/type_spec/actions_registry/emit_typescript.py +182 -0
- pkgs/type_spec/builder.py +475 -89
- pkgs/type_spec/config.py +24 -19
- pkgs/type_spec/emit_io_ts.py +5 -2
- pkgs/type_spec/emit_open_api.py +266 -32
- pkgs/type_spec/emit_open_api_util.py +32 -13
- pkgs/type_spec/emit_python.py +601 -150
- pkgs/type_spec/emit_typescript.py +74 -273
- pkgs/type_spec/emit_typescript_util.py +239 -5
- pkgs/type_spec/load_types.py +55 -10
- pkgs/type_spec/open_api_util.py +30 -41
- pkgs/type_spec/parts/base.py.prepart +4 -3
- pkgs/type_spec/type_info/emit_type_info.py +178 -16
- pkgs/type_spec/util.py +11 -11
- pkgs/type_spec/value_spec/__main__.py +3 -3
- pkgs/type_spec/value_spec/convert_type.py +8 -1
- pkgs/type_spec/value_spec/emit_python.py +13 -4
- uncountable/__init__.py +1 -2
- uncountable/core/__init__.py +12 -2
- uncountable/core/async_batch.py +37 -0
- uncountable/core/client.py +293 -43
- uncountable/core/environment.py +41 -0
- uncountable/core/file_upload.py +135 -0
- uncountable/core/types.py +17 -0
- uncountable/integration/__init__.py +0 -0
- uncountable/integration/cli.py +49 -0
- uncountable/integration/construct_client.py +51 -0
- uncountable/integration/cron.py +29 -0
- uncountable/integration/db/__init__.py +0 -0
- uncountable/integration/db/connect.py +18 -0
- uncountable/integration/db/session.py +25 -0
- uncountable/integration/entrypoint.py +13 -0
- uncountable/integration/executors/__init__.py +0 -0
- uncountable/integration/executors/executors.py +148 -0
- uncountable/integration/executors/generic_upload_executor.py +284 -0
- uncountable/integration/executors/script_executor.py +25 -0
- uncountable/integration/job.py +87 -0
- uncountable/integration/queue_runner/__init__.py +0 -0
- uncountable/integration/queue_runner/command_server/__init__.py +24 -0
- uncountable/integration/queue_runner/command_server/command_client.py +68 -0
- uncountable/integration/queue_runner/command_server/command_server.py +64 -0
- uncountable/integration/queue_runner/command_server/protocol/__init__.py +0 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server.proto +22 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.py +40 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.pyi +38 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2_grpc.py +129 -0
- uncountable/integration/queue_runner/command_server/types.py +52 -0
- uncountable/integration/queue_runner/datastore/__init__.py +3 -0
- uncountable/integration/queue_runner/datastore/datastore_sqlite.py +93 -0
- uncountable/integration/queue_runner/datastore/interface.py +19 -0
- uncountable/integration/queue_runner/datastore/model.py +17 -0
- uncountable/integration/queue_runner/job_scheduler.py +163 -0
- uncountable/integration/queue_runner/queue_runner.py +26 -0
- uncountable/integration/queue_runner/types.py +7 -0
- uncountable/integration/queue_runner/worker.py +119 -0
- uncountable/integration/scan_profiles.py +67 -0
- uncountable/integration/scheduler.py +150 -0
- uncountable/integration/secret_retrieval/__init__.py +3 -0
- uncountable/integration/secret_retrieval/retrieve_secret.py +93 -0
- uncountable/integration/server.py +117 -0
- uncountable/integration/telemetry.py +209 -0
- uncountable/integration/webhook_server/entrypoint.py +170 -0
- uncountable/types/__init__.py +151 -5
- uncountable/types/api/batch/execute_batch.py +15 -7
- uncountable/types/api/batch/execute_batch_load_async.py +42 -0
- uncountable/types/api/chemical/__init__.py +1 -0
- uncountable/types/api/chemical/convert_chemical_formats.py +63 -0
- uncountable/types/api/entity/create_entities.py +23 -10
- uncountable/types/api/entity/create_entity.py +21 -12
- uncountable/types/api/entity/get_entities_data.py +19 -29
- uncountable/types/api/entity/grant_entity_permissions.py +48 -0
- uncountable/types/api/entity/list_entities.py +28 -20
- uncountable/types/api/entity/lock_entity.py +45 -0
- uncountable/types/api/entity/resolve_entity_ids.py +19 -7
- uncountable/types/api/entity/set_entity_field_values.py +44 -0
- uncountable/types/api/entity/set_values.py +13 -28
- uncountable/types/api/entity/transition_entity_phase.py +80 -0
- uncountable/types/api/entity/unlock_entity.py +44 -0
- uncountable/types/api/equipment/__init__.py +1 -0
- uncountable/types/api/equipment/associate_equipment_input.py +44 -0
- uncountable/types/api/field_options/__init__.py +1 -0
- uncountable/types/api/field_options/upsert_field_options.py +55 -0
- uncountable/types/api/files/__init__.py +1 -0
- uncountable/types/api/files/download_file.py +77 -0
- uncountable/types/api/id_source/__init__.py +1 -0
- uncountable/types/api/id_source/list_id_source.py +56 -0
- uncountable/types/api/id_source/match_id_source.py +54 -0
- uncountable/types/api/input_groups/get_input_group_names.py +18 -7
- uncountable/types/api/inputs/create_inputs.py +25 -24
- uncountable/types/api/inputs/get_input_data.py +37 -31
- uncountable/types/api/inputs/get_input_names.py +20 -9
- uncountable/types/api/inputs/get_inputs_data.py +33 -27
- uncountable/types/api/inputs/set_input_attribute_values.py +18 -13
- uncountable/types/api/inputs/set_input_category.py +44 -0
- uncountable/types/api/inputs/set_input_subcategories.py +45 -0
- uncountable/types/api/inputs/set_intermediate_type.py +50 -0
- uncountable/types/api/material_families/__init__.py +1 -0
- uncountable/types/api/material_families/update_entity_material_families.py +48 -0
- uncountable/types/api/outputs/get_output_data.py +38 -29
- uncountable/types/api/outputs/get_output_names.py +20 -9
- uncountable/types/api/outputs/resolve_output_conditions.py +23 -10
- uncountable/types/api/permissions/__init__.py +1 -0
- uncountable/types/api/permissions/set_core_permissions.py +105 -0
- uncountable/types/api/project/get_projects.py +23 -19
- uncountable/types/api/project/get_projects_data.py +26 -43
- uncountable/types/api/recipe_links/__init__.py +1 -0
- uncountable/types/api/recipe_links/create_recipe_link.py +46 -0
- uncountable/types/api/recipe_links/remove_recipe_link.py +45 -0
- uncountable/types/api/recipe_metadata/get_recipe_metadata_data.py +21 -10
- uncountable/types/api/recipes/add_recipe_to_project.py +42 -0
- uncountable/types/api/recipes/archive_recipes.py +42 -0
- uncountable/types/api/recipes/associate_recipe_as_input.py +44 -0
- uncountable/types/api/recipes/associate_recipe_as_lot.py +43 -0
- uncountable/types/api/recipes/clear_recipe_outputs.py +42 -0
- uncountable/types/api/recipes/create_recipe.py +51 -0
- uncountable/types/api/recipes/create_recipes.py +25 -24
- uncountable/types/api/recipes/disassociate_recipe_as_input.py +42 -0
- uncountable/types/api/recipes/edit_recipe_inputs.py +283 -0
- uncountable/types/api/recipes/get_column_calculation_values.py +58 -0
- uncountable/types/api/recipes/get_curve.py +13 -27
- uncountable/types/api/recipes/get_recipe_calculations.py +21 -21
- uncountable/types/api/recipes/get_recipe_links.py +14 -6
- uncountable/types/api/recipes/get_recipe_names.py +18 -7
- uncountable/types/api/recipes/get_recipe_output_metadata.py +18 -19
- uncountable/types/api/recipes/get_recipes_data.py +83 -144
- uncountable/types/api/recipes/lock_recipes.py +63 -0
- uncountable/types/api/recipes/remove_recipe_from_project.py +42 -0
- uncountable/types/api/recipes/set_recipe_inputs.py +21 -11
- uncountable/types/api/recipes/set_recipe_metadata.py +43 -0
- uncountable/types/api/recipes/set_recipe_output_annotations.py +115 -0
- uncountable/types/api/recipes/set_recipe_output_file.py +56 -0
- uncountable/types/api/recipes/set_recipe_outputs.py +28 -15
- uncountable/types/api/recipes/set_recipe_tags.py +109 -0
- uncountable/types/api/recipes/unarchive_recipes.py +41 -0
- uncountable/types/api/recipes/unlock_recipes.py +50 -0
- uncountable/types/api/triggers/__init__.py +1 -0
- uncountable/types/api/triggers/run_trigger.py +43 -0
- uncountable/types/api/uploader/__init__.py +1 -0
- uncountable/types/api/uploader/invoke_uploader.py +47 -0
- uncountable/types/async_batch.py +13 -0
- uncountable/types/async_batch_processor.py +384 -0
- uncountable/types/async_batch_t.py +97 -0
- uncountable/types/async_jobs.py +9 -0
- uncountable/types/async_jobs_t.py +53 -0
- uncountable/types/auth_retrieval.py +12 -0
- uncountable/types/auth_retrieval_t.py +75 -0
- uncountable/types/base.py +5 -78
- uncountable/types/base_t.py +85 -0
- uncountable/types/calculations.py +8 -0
- uncountable/types/calculations_t.py +27 -0
- uncountable/types/chemical_structure.py +8 -0
- uncountable/types/chemical_structure_t.py +28 -0
- uncountable/types/client_base.py +1115 -76
- uncountable/types/client_config.py +8 -0
- uncountable/types/client_config_t.py +26 -0
- uncountable/types/curves.py +10 -0
- uncountable/types/curves_t.py +51 -0
- uncountable/types/entity.py +8 -266
- uncountable/types/entity_t.py +393 -0
- uncountable/types/experiment_groups.py +8 -0
- uncountable/types/experiment_groups_t.py +27 -0
- uncountable/types/field_values.py +17 -23
- uncountable/types/field_values_t.py +204 -0
- uncountable/types/fields.py +8 -0
- uncountable/types/fields_t.py +28 -0
- uncountable/types/generic_upload.py +15 -0
- uncountable/types/generic_upload_t.py +119 -0
- uncountable/types/id_source.py +12 -0
- uncountable/types/id_source_t.py +68 -0
- uncountable/types/identifier.py +11 -0
- uncountable/types/identifier_t.py +63 -0
- uncountable/types/input_attributes.py +8 -0
- uncountable/types/input_attributes_t.py +30 -0
- uncountable/types/inputs.py +11 -0
- uncountable/types/inputs_t.py +83 -0
- uncountable/types/integration_server.py +9 -0
- uncountable/types/integration_server_t.py +42 -0
- uncountable/types/job_definition.py +27 -0
- uncountable/types/job_definition_t.py +260 -0
- uncountable/types/outputs.py +8 -0
- uncountable/types/outputs_t.py +30 -0
- uncountable/types/overrides.py +10 -0
- uncountable/types/overrides_t.py +49 -0
- uncountable/types/permissions.py +8 -0
- uncountable/types/permissions_t.py +46 -0
- uncountable/types/phases.py +8 -0
- uncountable/types/phases_t.py +27 -0
- uncountable/types/post_base.py +8 -0
- uncountable/types/post_base_t.py +30 -0
- uncountable/types/queued_job.py +16 -0
- uncountable/types/queued_job_t.py +123 -0
- uncountable/types/recipe_identifiers.py +12 -0
- uncountable/types/recipe_identifiers_t.py +76 -0
- uncountable/types/recipe_inputs.py +9 -0
- uncountable/types/recipe_inputs_t.py +30 -0
- uncountable/types/recipe_links.py +4 -44
- uncountable/types/recipe_links_t.py +54 -0
- uncountable/types/recipe_metadata.py +10 -0
- uncountable/types/recipe_metadata_t.py +58 -0
- uncountable/types/recipe_output_metadata.py +8 -0
- uncountable/types/recipe_output_metadata_t.py +28 -0
- uncountable/types/recipe_tags.py +8 -0
- uncountable/types/recipe_tags_t.py +27 -0
- uncountable/types/recipe_workflow_steps.py +14 -0
- uncountable/types/recipe_workflow_steps_t.py +95 -0
- uncountable/types/recipes.py +8 -0
- uncountable/types/recipes_t.py +25 -0
- uncountable/types/response.py +8 -0
- uncountable/types/response_t.py +26 -0
- uncountable/types/secret_retrieval.py +12 -0
- uncountable/types/secret_retrieval_t.py +75 -0
- uncountable/types/units.py +8 -0
- uncountable/types/units_t.py +27 -0
- uncountable/types/users.py +8 -0
- uncountable/types/users_t.py +28 -0
- uncountable/types/webhook_job.py +9 -0
- uncountable/types/webhook_job_t.py +37 -0
- uncountable/types/workflows.py +9 -0
- uncountable/types/workflows_t.py +39 -0
- UncountablePythonSDK-0.0.7.dist-info/METADATA +0 -27
- UncountablePythonSDK-0.0.7.dist-info/RECORD +0 -119
- examples/recipe-import/importer.py +0 -39
- type_spec/external/api/batch/execute_batch.yaml +0 -56
- type_spec/external/api/entity/create_entities.yaml +0 -33
- type_spec/external/api/entity/create_entity.yaml +0 -39
- type_spec/external/api/entity/get_entities_data.yaml +0 -55
- type_spec/external/api/entity/list_entities.yaml +0 -62
- type_spec/external/api/entity/resolve_entity_ids.yaml +0 -29
- type_spec/external/api/entity/set_values.yaml +0 -45
- type_spec/external/api/input_groups/get_input_group_names.yaml +0 -29
- type_spec/external/api/inputs/create_inputs.yaml +0 -61
- type_spec/external/api/inputs/get_input_data.yaml +0 -108
- type_spec/external/api/inputs/get_input_names.yaml +0 -38
- type_spec/external/api/inputs/get_inputs_data.yaml +0 -95
- type_spec/external/api/inputs/set_input_attribute_values.yaml +0 -37
- type_spec/external/api/outputs/get_output_data.yaml +0 -103
- type_spec/external/api/outputs/get_output_names.yaml +0 -35
- type_spec/external/api/outputs/resolve_output_conditions.yaml +0 -50
- type_spec/external/api/project/get_projects.yaml +0 -52
- type_spec/external/api/project/get_projects_data.yaml +0 -86
- type_spec/external/api/recipe_metadata/get_recipe_metadata_data.yaml +0 -41
- type_spec/external/api/recipes/create_recipes.yaml +0 -60
- type_spec/external/api/recipes/get_curve.yaml +0 -50
- type_spec/external/api/recipes/get_recipe_calculations.yaml +0 -49
- type_spec/external/api/recipes/get_recipe_links.yaml +0 -26
- type_spec/external/api/recipes/get_recipe_names.yaml +0 -29
- type_spec/external/api/recipes/get_recipe_output_metadata.yaml +0 -49
- type_spec/external/api/recipes/get_recipes_data.yaml +0 -372
- type_spec/external/api/recipes/set_recipe_inputs.yaml +0 -36
- type_spec/external/api/recipes/set_recipe_outputs.yaml +0 -56
pkgs/type_spec/emit_python.py
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
|
+
import dataclasses
|
|
1
2
|
import io
|
|
2
3
|
import os
|
|
3
|
-
from dataclasses import dataclass, field
|
|
4
4
|
from decimal import Decimal
|
|
5
|
-
from typing import Any
|
|
5
|
+
from typing import Any
|
|
6
6
|
|
|
7
7
|
from . import builder, util
|
|
8
|
+
from .builder import EndpointEmitType
|
|
8
9
|
from .config import PythonConfig
|
|
9
10
|
|
|
10
11
|
INDENT = " "
|
|
11
12
|
LINE_BREAK = "\n"
|
|
12
13
|
MODIFY_NOTICE = "# DO NOT MODIFY -- This file is generated by type_spec\n"
|
|
13
14
|
# Turn excess line length warning and turn off ruff formatting
|
|
14
|
-
LINT_HEADER =
|
|
15
|
+
LINT_HEADER = (
|
|
16
|
+
"# flake8: noqa: F821\n# ruff: noqa: E402 Q003\n# fmt: off\n# isort: skip_file\n"
|
|
17
|
+
)
|
|
15
18
|
LINT_FOOTER = "# fmt: on\n"
|
|
16
19
|
ROUTE_NOTICE = """# Routes are generated from $endpoint specifications in the
|
|
17
20
|
# type_spec API YAML files. Refer to the section on endpoints in the type_spec/README"""
|
|
@@ -21,29 +24,44 @@ __all__: list[str] = [
|
|
|
21
24
|
"""
|
|
22
25
|
END_ALL_EXPORTS = "]\n"
|
|
23
26
|
|
|
27
|
+
ASYNC_BATCH_TYPE_NAMESPACE = builder.SpecNamespace(name="async_batch")
|
|
28
|
+
ASYNC_BATCH_REQUEST_PATH_STYPE = builder.SpecTypeDefnStringEnum(
|
|
29
|
+
namespace=ASYNC_BATCH_TYPE_NAMESPACE, name="AsyncBatchRequestPath"
|
|
30
|
+
)
|
|
31
|
+
ASYNC_BATCH_REQUEST_STYPE = builder.SpecTypeDefnObject(
|
|
32
|
+
namespace=ASYNC_BATCH_TYPE_NAMESPACE, name="AsyncBatchRequest"
|
|
33
|
+
)
|
|
34
|
+
QUEUED_BATCH_REQUEST_STYPE = builder.SpecTypeDefnObject(
|
|
35
|
+
namespace=ASYNC_BATCH_TYPE_NAMESPACE, name="QueuedAsyncBatchRequest"
|
|
36
|
+
)
|
|
24
37
|
|
|
25
|
-
|
|
38
|
+
|
|
39
|
+
@dataclasses.dataclass(kw_only=True)
|
|
26
40
|
class TrackingContext:
|
|
27
|
-
namespace:
|
|
28
|
-
namespaces:
|
|
29
|
-
names:
|
|
41
|
+
namespace: builder.SpecNamespace | None = None
|
|
42
|
+
namespaces: set[builder.SpecNamespace] = dataclasses.field(default_factory=set)
|
|
43
|
+
names: set[str] = dataclasses.field(default_factory=set)
|
|
30
44
|
|
|
31
45
|
use_enum: bool = False
|
|
32
46
|
use_serial_string_enum: bool = False
|
|
33
47
|
use_dataclass: bool = False
|
|
34
|
-
|
|
48
|
+
use_serial_union: bool = False
|
|
49
|
+
use_serial_alias: bool = False
|
|
35
50
|
use_missing: bool = False
|
|
36
51
|
use_opaque_key: bool = False
|
|
37
52
|
|
|
38
53
|
|
|
39
|
-
@dataclass(kw_only=True)
|
|
54
|
+
@dataclasses.dataclass(kw_only=True)
|
|
40
55
|
class Context(TrackingContext):
|
|
41
56
|
out: io.StringIO
|
|
42
57
|
namespace: builder.SpecNamespace
|
|
58
|
+
builder: builder.SpecBuilder
|
|
43
59
|
|
|
44
60
|
|
|
45
61
|
def _resolve_namespace_name(namespace: builder.SpecNamespace) -> str:
|
|
46
|
-
|
|
62
|
+
if len(namespace.path) > 1:
|
|
63
|
+
return namespace.name
|
|
64
|
+
return f"{namespace.name}_t"
|
|
47
65
|
|
|
48
66
|
|
|
49
67
|
def _resolve_namespace_ref(namespace: builder.SpecNamespace) -> str:
|
|
@@ -119,6 +137,9 @@ def _emit_value(ctx: TrackingContext, stype: builder.SpecType, value: Any) -> st
|
|
|
119
137
|
# Note that decimal requires the `!decimal 123.12` style notation in the YAML
|
|
120
138
|
# file since PyYaml parses numbers as float, unfortuantely
|
|
121
139
|
assert isinstance(value, (Decimal, int))
|
|
140
|
+
if isinstance(value, int):
|
|
141
|
+
# skip quotes for integers
|
|
142
|
+
return f"Decimal({value})"
|
|
122
143
|
return f'Decimal("{value}")'
|
|
123
144
|
elif isinstance(stype, builder.SpecTypeInstance):
|
|
124
145
|
if stype.defn_type.is_base_type(builder.BaseTypeName.s_list):
|
|
@@ -191,6 +212,7 @@ def emit_python(builder: builder.SpecBuilder, *, config: PythonConfig) -> None:
|
|
|
191
212
|
_emit_api_stubs(builder=builder, config=config)
|
|
192
213
|
_emit_api_argument_lookup(builder=builder, config=config)
|
|
193
214
|
_emit_client_class(spec_builder=builder, config=config)
|
|
215
|
+
_emit_async_batch_processor(spec_builder=builder, config=config)
|
|
194
216
|
|
|
195
217
|
|
|
196
218
|
def _emit_types_imports(*, out: io.StringIO, ctx: Context) -> None:
|
|
@@ -201,9 +223,12 @@ def _emit_types_imports(*, out: io.StringIO, ctx: Context) -> None:
|
|
|
201
223
|
if ctx.use_enum:
|
|
202
224
|
out.write("from pkgs.strenum_compat import StrEnum\n")
|
|
203
225
|
if ctx.use_dataclass:
|
|
204
|
-
out.write("
|
|
205
|
-
if ctx.use_serial_class:
|
|
226
|
+
out.write("import dataclasses\n")
|
|
206
227
|
out.write("from pkgs.serialization import serial_class\n")
|
|
228
|
+
if ctx.use_serial_union:
|
|
229
|
+
out.write("from pkgs.serialization import serial_union_annotation\n")
|
|
230
|
+
if ctx.use_serial_alias:
|
|
231
|
+
out.write("from pkgs.serialization import serial_alias_annotation\n")
|
|
207
232
|
if ctx.use_serial_string_enum:
|
|
208
233
|
out.write("from pkgs.serialization import serial_string_enum\n")
|
|
209
234
|
if ctx.use_missing:
|
|
@@ -220,7 +245,7 @@ def _emit_types(*, builder: builder.SpecBuilder, config: PythonConfig) -> None:
|
|
|
220
245
|
exports_out = io.StringIO()
|
|
221
246
|
exports_out.write(START_ALL_EXPORTS)
|
|
222
247
|
|
|
223
|
-
all_dirs:
|
|
248
|
+
all_dirs: set[str] = set()
|
|
224
249
|
|
|
225
250
|
for namespace in sorted(
|
|
226
251
|
builder.namespaces.values(),
|
|
@@ -228,7 +253,7 @@ def _emit_types(*, builder: builder.SpecBuilder, config: PythonConfig) -> None:
|
|
|
228
253
|
):
|
|
229
254
|
if (
|
|
230
255
|
namespace.endpoint is not None
|
|
231
|
-
and
|
|
256
|
+
and namespace.endpoint.is_sdk == EndpointEmitType.EMIT_NOTHING
|
|
232
257
|
and config.sdk_endpoints_only is True
|
|
233
258
|
):
|
|
234
259
|
continue
|
|
@@ -236,6 +261,7 @@ def _emit_types(*, builder: builder.SpecBuilder, config: PythonConfig) -> None:
|
|
|
236
261
|
ctx = Context(
|
|
237
262
|
out=io.StringIO(),
|
|
238
263
|
namespace=namespace,
|
|
264
|
+
builder=builder,
|
|
239
265
|
)
|
|
240
266
|
|
|
241
267
|
_emit_namespace(ctx, namespace)
|
|
@@ -274,17 +300,22 @@ def _emit_types(*, builder: builder.SpecBuilder, config: PythonConfig) -> None:
|
|
|
274
300
|
full.write(f"# === END section from {namespace.name}.part.py ===\n")
|
|
275
301
|
|
|
276
302
|
basename = "/".join(namespace.path)
|
|
277
|
-
filename = f"{config.types_output}/{basename}.py"
|
|
303
|
+
filename = f"{config.types_output}/{basename}{'' if len(namespace.path) > 1 else '_t'}.py"
|
|
278
304
|
util.rewrite_file(filename, full.getvalue())
|
|
279
305
|
|
|
306
|
+
# Deprecated SDK support
|
|
307
|
+
if config.all_named_type_exports and len(namespace.path) == 1:
|
|
308
|
+
compat_out = _create_sdk_compat_namespace(namespace)
|
|
309
|
+
compat_filename = f"{config.types_output}/{basename}.py"
|
|
310
|
+
util.rewrite_file(compat_filename, compat_out.getvalue())
|
|
311
|
+
|
|
280
312
|
path_to = os.path.dirname(basename)
|
|
281
313
|
while path_to != "":
|
|
282
314
|
all_dirs.add(path_to)
|
|
283
315
|
path_to = os.path.dirname(path_to)
|
|
284
316
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
):
|
|
317
|
+
# Deprecated SDK support
|
|
318
|
+
if config.all_named_type_exports:
|
|
288
319
|
index_out.write("from ")
|
|
289
320
|
if len(namespace.path) == 1:
|
|
290
321
|
index_out.write(".")
|
|
@@ -313,6 +344,9 @@ def _emit_types(*, builder: builder.SpecBuilder, config: PythonConfig) -> None:
|
|
|
313
344
|
|
|
314
345
|
ENDPOINT_METHOD = "ENDPOINT_METHOD"
|
|
315
346
|
ENDPOINT_PATH = "ENDPOINT_PATH"
|
|
347
|
+
# will be removed in Q1 2025 when ENDPOINT_PATH is made api_endpoint-agnostic
|
|
348
|
+
# is used when the API call has multiple endpoints for the one endpoint that isn't equal to the top_namespace
|
|
349
|
+
ENDPOINT_PATH_ALTERNATE = "ENDPOINT_PATH_ALTERNATE"
|
|
316
350
|
|
|
317
351
|
|
|
318
352
|
def _emit_namespace(ctx: Context, namespace: builder.SpecNamespace) -> None:
|
|
@@ -320,9 +354,20 @@ def _emit_namespace(ctx: Context, namespace: builder.SpecNamespace) -> None:
|
|
|
320
354
|
if endpoint is not None:
|
|
321
355
|
ctx.out.write("\n")
|
|
322
356
|
ctx.out.write(f'{ENDPOINT_METHOD} = "{endpoint.method.upper()}"\n')
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
)
|
|
357
|
+
for endpoint_specific_path in sorted(
|
|
358
|
+
endpoint.path_per_api_endpoint.values(), key=lambda epf: epf.root
|
|
359
|
+
):
|
|
360
|
+
endpoint_path_name = ENDPOINT_PATH
|
|
361
|
+
|
|
362
|
+
if (
|
|
363
|
+
len(endpoint.path_per_api_endpoint.keys()) > 1
|
|
364
|
+
and endpoint_specific_path.root != ctx.builder.top_namespace
|
|
365
|
+
):
|
|
366
|
+
endpoint_path_name = ENDPOINT_PATH_ALTERNATE
|
|
367
|
+
ctx.names.add(ENDPOINT_PATH_ALTERNATE)
|
|
368
|
+
ctx.out.write(
|
|
369
|
+
f'{endpoint_path_name} = "{endpoint_specific_path.path_root}/{endpoint_specific_path.path_dirname}/{endpoint_specific_path.path_basename}"\n'
|
|
370
|
+
)
|
|
326
371
|
|
|
327
372
|
ctx.names.add(ENDPOINT_METHOD)
|
|
328
373
|
ctx.names.add(ENDPOINT_PATH)
|
|
@@ -334,75 +379,243 @@ def _emit_namespace(ctx: Context, namespace: builder.SpecNamespace) -> None:
|
|
|
334
379
|
_emit_constant(ctx, sconst)
|
|
335
380
|
|
|
336
381
|
|
|
382
|
+
def _create_sdk_compat_namespace(namespace: builder.SpecNamespace) -> io.StringIO:
|
|
383
|
+
compat_out = io.StringIO()
|
|
384
|
+
compat_out.write(LINT_HEADER)
|
|
385
|
+
compat_out.write(MODIFY_NOTICE)
|
|
386
|
+
compat_out.write("# Kept only for SDK backwards compatibility\n")
|
|
387
|
+
|
|
388
|
+
# This mostly an prepart import, thus has no high-level knowledge. Since this onl
|
|
389
|
+
# needs backwards copmatibiltiy from when written we can just hardcode what was there
|
|
390
|
+
# (only those in __all__ assuming that worked, which it might not)
|
|
391
|
+
if namespace.name == "base":
|
|
392
|
+
compat_out.write("""
|
|
393
|
+
from .base_t import JsonScalar as JsonScalar
|
|
394
|
+
from .base_t import JsonValue as JsonValue
|
|
395
|
+
from .base_t import ObjectId as ObjectId
|
|
396
|
+
""")
|
|
397
|
+
else:
|
|
398
|
+
for stype in namespace.types.values():
|
|
399
|
+
compat_out.write(
|
|
400
|
+
f"from .{namespace.path[-1]}_t import {stype.name} as {stype.name}\n"
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
compat_out.write(MODIFY_NOTICE)
|
|
404
|
+
|
|
405
|
+
return compat_out
|
|
406
|
+
|
|
407
|
+
|
|
337
408
|
def _validate_supports_handler_generation(
|
|
338
|
-
stype: builder.SpecTypeDefn, name: str
|
|
409
|
+
stype: builder.SpecTypeDefn, name: str, supports_inheritance: bool = False
|
|
339
410
|
) -> builder.SpecTypeDefnObject:
|
|
340
|
-
assert isinstance(
|
|
341
|
-
|
|
342
|
-
)
|
|
343
|
-
|
|
344
|
-
stype.base is None or stype.base.is_base
|
|
345
|
-
|
|
411
|
+
assert isinstance(stype, builder.SpecTypeDefnObject), (
|
|
412
|
+
f"External api {name} must be an object"
|
|
413
|
+
)
|
|
414
|
+
if not supports_inheritance:
|
|
415
|
+
assert stype.base is None or stype.base.is_base, (
|
|
416
|
+
f"Inheritance not supported in external api {name}"
|
|
417
|
+
)
|
|
346
418
|
return stype
|
|
347
419
|
|
|
348
420
|
|
|
349
|
-
def
|
|
350
|
-
ctx: Context,
|
|
421
|
+
def _emit_endpoint_invocation_docstring(
|
|
422
|
+
ctx: Context,
|
|
423
|
+
endpoint: builder.SpecEndpoint,
|
|
424
|
+
properties: list[builder.SpecProperty],
|
|
351
425
|
) -> None:
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
if not endpoint.is_sdk:
|
|
426
|
+
has_argument_desc = any(prop.desc is not None for prop in properties)
|
|
427
|
+
has_endpoint_desc = endpoint.desc
|
|
428
|
+
if not has_argument_desc and not has_endpoint_desc:
|
|
356
429
|
return
|
|
357
430
|
|
|
358
|
-
|
|
359
|
-
|
|
431
|
+
FULL_INDENT = INDENT * 2
|
|
432
|
+
ctx.out.write(FULL_INDENT)
|
|
433
|
+
ctx.out.write('"""')
|
|
360
434
|
|
|
361
|
-
|
|
362
|
-
|
|
435
|
+
if endpoint.desc is not None and has_endpoint_desc:
|
|
436
|
+
ctx.out.write(f"{endpoint.desc}\n")
|
|
437
|
+
ctx.out.write("\n")
|
|
363
438
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
)
|
|
439
|
+
if has_argument_desc:
|
|
440
|
+
for prop in properties:
|
|
441
|
+
if prop.desc:
|
|
442
|
+
ctx.out.write(f"{FULL_INDENT}:param {prop.name}: {prop.desc}\n")
|
|
443
|
+
|
|
444
|
+
ctx.out.write(f'{FULL_INDENT}"""\n')
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def _emit_endpoint_invocation_function_signature(
|
|
448
|
+
ctx: Context,
|
|
449
|
+
endpoint: builder.SpecEndpoint,
|
|
450
|
+
arguments_type: builder.SpecTypeDefnObject,
|
|
451
|
+
data_type: builder.SpecTypeDefnObject,
|
|
452
|
+
extra_params: list[builder.SpecProperty] | None = None,
|
|
453
|
+
) -> None:
|
|
454
|
+
all_arguments = (
|
|
455
|
+
list(arguments_type.properties.values())
|
|
456
|
+
if arguments_type.properties is not None
|
|
457
|
+
else []
|
|
458
|
+
) + (extra_params if extra_params is not None else [])
|
|
370
459
|
|
|
371
|
-
has_arguments = (
|
|
372
|
-
arguments_type.properties is not None
|
|
373
|
-
and len(arguments_type.properties.values()) > 0
|
|
374
|
-
)
|
|
375
460
|
assert endpoint.function is not None
|
|
376
461
|
function_name = endpoint.function.split(".")[-1]
|
|
377
|
-
ctx.out.write("\n")
|
|
378
462
|
ctx.out.write(
|
|
379
463
|
f"""
|
|
380
464
|
def {function_name}(
|
|
381
465
|
self,\n"""
|
|
382
466
|
)
|
|
383
|
-
if
|
|
467
|
+
if len(all_arguments) > 0:
|
|
384
468
|
ctx.out.write(f"{INDENT}{INDENT}*,\n")
|
|
385
|
-
|
|
469
|
+
_emit_properties(
|
|
386
470
|
ctx=ctx,
|
|
387
|
-
|
|
471
|
+
properties=all_arguments,
|
|
388
472
|
num_indent=2,
|
|
389
473
|
separator=",\n",
|
|
390
474
|
class_out=ctx.out,
|
|
391
475
|
)
|
|
392
476
|
ctx.out.write(f"{INDENT}) -> {refer_to(ctx=ctx, stype=data_type)}:")
|
|
393
|
-
|
|
394
477
|
ctx.out.write("\n")
|
|
395
|
-
|
|
396
|
-
if
|
|
397
|
-
|
|
478
|
+
|
|
479
|
+
if len(all_arguments) > 0:
|
|
480
|
+
_emit_endpoint_invocation_docstring(
|
|
481
|
+
ctx=ctx, endpoint=endpoint, properties=all_arguments
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def _emit_instantiate_type_from_locals(
|
|
486
|
+
ctx: Context, variable_name: str, variable_type: builder.SpecTypeDefnObject
|
|
487
|
+
) -> None:
|
|
488
|
+
ctx.out.write(
|
|
489
|
+
f"{INDENT}{INDENT}{variable_name} = {refer_to(ctx=ctx, stype=variable_type)}("
|
|
490
|
+
)
|
|
491
|
+
if variable_type.properties is not None and len(variable_type.properties) > 0:
|
|
398
492
|
ctx.out.write("\n")
|
|
399
|
-
for prop in
|
|
493
|
+
for prop in variable_type.properties.values():
|
|
400
494
|
ctx.out.write(f"{INDENT}{INDENT}{INDENT}{prop.name}={prop.name},")
|
|
401
495
|
ctx.out.write("\n")
|
|
402
496
|
ctx.out.write(f"{INDENT}{INDENT})")
|
|
403
497
|
else:
|
|
404
498
|
ctx.out.write(")")
|
|
405
499
|
|
|
500
|
+
|
|
501
|
+
def _emit_async_batch_invocation_function(
|
|
502
|
+
ctx: Context, namespace: builder.SpecNamespace
|
|
503
|
+
) -> None:
|
|
504
|
+
endpoint = namespace.endpoint
|
|
505
|
+
if endpoint is None:
|
|
506
|
+
return
|
|
507
|
+
if (
|
|
508
|
+
endpoint.async_batch_path is None
|
|
509
|
+
or endpoint.is_sdk != EndpointEmitType.EMIT_ENDPOINT
|
|
510
|
+
):
|
|
511
|
+
return
|
|
512
|
+
|
|
513
|
+
ctx.out.write("\n")
|
|
514
|
+
arguments_type = namespace.types["Arguments"]
|
|
515
|
+
arguments_type = _validate_supports_handler_generation(arguments_type, "arguments")
|
|
516
|
+
data_type = QUEUED_BATCH_REQUEST_STYPE
|
|
517
|
+
|
|
518
|
+
list_str_params: list[builder.SpecType] = []
|
|
519
|
+
list_str_params.append(
|
|
520
|
+
builder.SpecTypeGenericParameter(
|
|
521
|
+
name="str",
|
|
522
|
+
spec_type_definition=builder.SpecTypeDefnObject(
|
|
523
|
+
namespace=namespace, name=builder.BaseTypeName.s_string, is_base=True
|
|
524
|
+
),
|
|
525
|
+
)
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
_emit_endpoint_invocation_function_signature(
|
|
529
|
+
ctx=ctx,
|
|
530
|
+
endpoint=endpoint,
|
|
531
|
+
arguments_type=arguments_type,
|
|
532
|
+
data_type=data_type,
|
|
533
|
+
extra_params=[
|
|
534
|
+
builder.SpecProperty(
|
|
535
|
+
name="depends_on",
|
|
536
|
+
extant=builder.PropertyExtant.optional,
|
|
537
|
+
convert_value=builder.PropertyConvertValue.auto,
|
|
538
|
+
name_case=builder.NameCase.convert,
|
|
539
|
+
label="depends_on",
|
|
540
|
+
desc="A list of batch reference keys to process before processing this request",
|
|
541
|
+
spec_type=builder.SpecTypeInstance(
|
|
542
|
+
defn_type=builder.SpecTypeDefnObject(
|
|
543
|
+
name=builder.BaseTypeName.s_list,
|
|
544
|
+
is_base=True,
|
|
545
|
+
namespace=namespace,
|
|
546
|
+
),
|
|
547
|
+
parameters=list_str_params,
|
|
548
|
+
),
|
|
549
|
+
)
|
|
550
|
+
],
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
# Emit function body
|
|
554
|
+
_emit_instantiate_type_from_locals(
|
|
555
|
+
ctx=ctx, variable_name="args", variable_type=arguments_type
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
path = _emit_value(
|
|
559
|
+
ctx=ctx,
|
|
560
|
+
stype=ASYNC_BATCH_REQUEST_PATH_STYPE,
|
|
561
|
+
value=endpoint.async_batch_path,
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
ctx.out.write(
|
|
565
|
+
f"""
|
|
566
|
+
json_data = serialize_for_api(args)
|
|
567
|
+
|
|
568
|
+
batch_reference = str(uuid.uuid4())
|
|
569
|
+
|
|
570
|
+
req = {refer_to(ctx=ctx, stype=ASYNC_BATCH_REQUEST_STYPE)}(
|
|
571
|
+
path={path},
|
|
572
|
+
data=json_data,
|
|
573
|
+
depends_on=depends_on,
|
|
574
|
+
batch_reference=batch_reference,
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
self._enqueue(req)
|
|
578
|
+
|
|
579
|
+
return {refer_to(ctx=ctx, stype=data_type)}(
|
|
580
|
+
path=req.path,
|
|
581
|
+
batch_reference=req.batch_reference,
|
|
582
|
+
)"""
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
def _emit_endpoint_invocation_function(
|
|
587
|
+
ctx: Context, namespace: builder.SpecNamespace
|
|
588
|
+
) -> None:
|
|
589
|
+
endpoint = namespace.endpoint
|
|
590
|
+
if endpoint is None:
|
|
591
|
+
return
|
|
592
|
+
if endpoint.is_sdk != EndpointEmitType.EMIT_ENDPOINT or endpoint.is_beta:
|
|
593
|
+
return
|
|
594
|
+
|
|
595
|
+
ctx.out.write("\n")
|
|
596
|
+
arguments_type = namespace.types["Arguments"]
|
|
597
|
+
data_type = namespace.types["Data"]
|
|
598
|
+
arguments_type = _validate_supports_handler_generation(arguments_type, "arguments")
|
|
599
|
+
data_type = _validate_supports_handler_generation(
|
|
600
|
+
data_type, "response", supports_inheritance=True
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
_emit_endpoint_invocation_function_signature(
|
|
604
|
+
ctx=ctx, endpoint=endpoint, arguments_type=arguments_type, data_type=data_type
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
endpoint_method_stype = builder.SpecTypeDefnObject(
|
|
608
|
+
namespace=arguments_type.namespace, name=ENDPOINT_METHOD
|
|
609
|
+
)
|
|
610
|
+
endpoint_path_stype = builder.SpecTypeDefnObject(
|
|
611
|
+
namespace=arguments_type.namespace, name=ENDPOINT_PATH
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
# Emit function body
|
|
615
|
+
_emit_instantiate_type_from_locals(
|
|
616
|
+
ctx=ctx, variable_name="args", variable_type=arguments_type
|
|
617
|
+
)
|
|
618
|
+
|
|
406
619
|
ctx.out.write(
|
|
407
620
|
f"""
|
|
408
621
|
api_request = APIRequest(
|
|
@@ -412,7 +625,6 @@ def _emit_endpoint_invocation_function(
|
|
|
412
625
|
)
|
|
413
626
|
return self.do_request(api_request=api_request, return_type={refer_to(ctx=ctx, stype=data_type)})"""
|
|
414
627
|
)
|
|
415
|
-
ctx.out.write("\n")
|
|
416
628
|
|
|
417
629
|
|
|
418
630
|
def _emit_string_enum(ctx: Context, stype: builder.SpecTypeDefnStringEnum) -> None:
|
|
@@ -430,7 +642,9 @@ def _emit_string_enum(ctx: Context, stype: builder.SpecTypeDefnStringEnum) -> No
|
|
|
430
642
|
ctx.out.write(f"{INDENT}labels={{\n")
|
|
431
643
|
for entry in stype.values.values():
|
|
432
644
|
if entry.label is not None:
|
|
433
|
-
ctx.out.write(
|
|
645
|
+
ctx.out.write(
|
|
646
|
+
f'{INDENT}{INDENT}"{entry.value}": "{entry.label}",\n'
|
|
647
|
+
)
|
|
434
648
|
|
|
435
649
|
ctx.out.write(f"{INDENT}}},\n")
|
|
436
650
|
if need_deprecated:
|
|
@@ -461,12 +675,12 @@ def _emit_string_enum(ctx: Context, stype: builder.SpecTypeDefnStringEnum) -> No
|
|
|
461
675
|
)
|
|
462
676
|
|
|
463
677
|
|
|
464
|
-
@dataclass
|
|
678
|
+
@dataclasses.dataclass
|
|
465
679
|
class EmittedPropertiesMetadata:
|
|
466
|
-
unconverted_keys:
|
|
467
|
-
unconverted_values:
|
|
468
|
-
to_string_values:
|
|
469
|
-
parse_require:
|
|
680
|
+
unconverted_keys: set[str]
|
|
681
|
+
unconverted_values: set[str]
|
|
682
|
+
to_string_values: set[str]
|
|
683
|
+
parse_require: set[str]
|
|
470
684
|
|
|
471
685
|
|
|
472
686
|
def _emit_type_properties(
|
|
@@ -477,16 +691,31 @@ def _emit_type_properties(
|
|
|
477
691
|
num_indent: int = 1,
|
|
478
692
|
separator: str = "\n",
|
|
479
693
|
) -> EmittedPropertiesMetadata:
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
694
|
+
return _emit_properties(
|
|
695
|
+
ctx=ctx,
|
|
696
|
+
class_out=class_out,
|
|
697
|
+
properties=list((stype.properties or {}).values()),
|
|
698
|
+
num_indent=num_indent,
|
|
699
|
+
separator=separator,
|
|
700
|
+
)
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
def _emit_properties(
|
|
704
|
+
*,
|
|
705
|
+
ctx: Context,
|
|
706
|
+
class_out: io.StringIO,
|
|
707
|
+
properties: list[builder.SpecProperty],
|
|
708
|
+
num_indent: int = 1,
|
|
709
|
+
separator: str = "\n",
|
|
710
|
+
) -> EmittedPropertiesMetadata:
|
|
711
|
+
unconverted_keys: set[str] = set()
|
|
712
|
+
unconverted_values: set[str] = set()
|
|
713
|
+
to_string_values: set[str] = set()
|
|
714
|
+
parse_require: set[str] = set()
|
|
484
715
|
|
|
485
|
-
if
|
|
716
|
+
if len(properties) > 0:
|
|
486
717
|
|
|
487
718
|
def write_field(prop: builder.SpecProperty) -> None:
|
|
488
|
-
# Checked in outer function, MyPy doens't track the check inside here
|
|
489
|
-
assert isinstance(stype, builder.SpecTypeDefn)
|
|
490
719
|
if prop.name_case == builder.NameCase.preserve:
|
|
491
720
|
unconverted_keys.add(prop.name)
|
|
492
721
|
py_name = python_field_name(prop.name, prop.name_case)
|
|
@@ -509,20 +738,30 @@ def _emit_type_properties(
|
|
|
509
738
|
default = "MISSING_SENTRY"
|
|
510
739
|
ctx.use_missing = True
|
|
511
740
|
elif prop.extant == builder.PropertyExtant.optional:
|
|
512
|
-
ref_type
|
|
741
|
+
if ref_type != "None":
|
|
742
|
+
ref_type = f"{ref_type} | None"
|
|
513
743
|
default = "None"
|
|
514
744
|
elif prop.has_default:
|
|
515
745
|
default = _emit_value(ctx, prop.spec_type, prop.default)
|
|
516
|
-
|
|
746
|
+
if (
|
|
747
|
+
isinstance(prop.spec_type, builder.SpecTypeInstance)
|
|
748
|
+
and (
|
|
749
|
+
prop.spec_type.defn_type.is_base_type(
|
|
750
|
+
builder.BaseTypeName.s_list
|
|
751
|
+
)
|
|
752
|
+
)
|
|
753
|
+
and default == "[]"
|
|
754
|
+
):
|
|
755
|
+
default = "dataclasses.field(default_factory=list)"
|
|
517
756
|
class_out.write(f"{INDENT * num_indent}{py_name}: {ref_type}")
|
|
518
757
|
if default:
|
|
519
758
|
class_out.write(f" = {default}")
|
|
520
759
|
class_out.write(separator)
|
|
521
760
|
|
|
522
|
-
for prop in
|
|
761
|
+
for prop in properties:
|
|
523
762
|
if prop.extant == builder.PropertyExtant.required:
|
|
524
763
|
write_field(prop)
|
|
525
|
-
for prop in
|
|
764
|
+
for prop in properties:
|
|
526
765
|
if prop.extant != builder.PropertyExtant.required:
|
|
527
766
|
write_field(prop)
|
|
528
767
|
else:
|
|
@@ -536,6 +775,12 @@ def _emit_type_properties(
|
|
|
536
775
|
)
|
|
537
776
|
|
|
538
777
|
|
|
778
|
+
def _named_type_path(ctx: Context, stype: builder.SpecTypeDefn) -> str:
|
|
779
|
+
parts = [] if stype.is_base else stype.namespace.path.copy()
|
|
780
|
+
parts.append(stype.name)
|
|
781
|
+
return f"{ctx.builder.top_namespace}.{'.'.join(parts)}"
|
|
782
|
+
|
|
783
|
+
|
|
539
784
|
def _emit_type(ctx: Context, stype: builder.SpecType) -> None:
|
|
540
785
|
if not isinstance(stype, builder.SpecTypeDefn):
|
|
541
786
|
return
|
|
@@ -556,7 +801,42 @@ def _emit_type(ctx: Context, stype: builder.SpecType) -> None:
|
|
|
556
801
|
return
|
|
557
802
|
|
|
558
803
|
if isinstance(stype, builder.SpecTypeDefnAlias):
|
|
559
|
-
ctx.
|
|
804
|
+
ctx.use_serial_alias = True
|
|
805
|
+
ctx.out.write(f"{stype.name} = typing.Annotated[\n")
|
|
806
|
+
ctx.out.write(f"{INDENT}{refer_to(ctx, stype.alias)},\n")
|
|
807
|
+
ctx.out.write(f"{INDENT}serial_alias_annotation(\n")
|
|
808
|
+
ctx.out.write(
|
|
809
|
+
f"{INDENT}named_type_path={util.encode_common_string(_named_type_path(ctx, stype))},\n"
|
|
810
|
+
)
|
|
811
|
+
if stype.is_dynamic_allowed():
|
|
812
|
+
ctx.out.write(f"{INDENT}is_dynamic_allowed=True,\n")
|
|
813
|
+
ctx.out.write(f"{INDENT}),\n")
|
|
814
|
+
ctx.out.write("]\n")
|
|
815
|
+
return
|
|
816
|
+
|
|
817
|
+
if isinstance(stype, builder.SpecTypeDefnUnion):
|
|
818
|
+
ctx.use_serial_union = True
|
|
819
|
+
ctx.out.write(f"{stype.name} = typing.Annotated[\n")
|
|
820
|
+
ctx.out.write(f"{INDENT}{refer_to(ctx, stype.get_backing_type())},\n")
|
|
821
|
+
ctx.out.write(f"{INDENT}serial_union_annotation(\n")
|
|
822
|
+
ctx.out.write(
|
|
823
|
+
f"{INDENT}named_type_path={util.encode_common_string(_named_type_path(ctx, stype))},\n"
|
|
824
|
+
)
|
|
825
|
+
if stype.is_dynamic_allowed():
|
|
826
|
+
ctx.out.write(f"{INDENT}is_dynamic_allowed=True,\n")
|
|
827
|
+
if stype.discriminator is not None:
|
|
828
|
+
ctx.out.write(
|
|
829
|
+
f"{INDENT * 2}discriminator={util.encode_common_string(stype.discriminator)},\n"
|
|
830
|
+
)
|
|
831
|
+
if stype.discriminator_map is not None:
|
|
832
|
+
ctx.out.write(f"{INDENT * 2}discriminator_map={{\n")
|
|
833
|
+
for key, value in stype.discriminator_map.items():
|
|
834
|
+
ctx.out.write(
|
|
835
|
+
f"{INDENT * 3}{util.encode_common_string(key)}: {refer_to(ctx, value)},\n"
|
|
836
|
+
)
|
|
837
|
+
ctx.out.write(f"{INDENT * 2}}},\n")
|
|
838
|
+
ctx.out.write(f"{INDENT}),\n")
|
|
839
|
+
ctx.out.write("]\n")
|
|
560
840
|
return
|
|
561
841
|
|
|
562
842
|
if isinstance(stype, builder.SpecTypeDefnStringEnum):
|
|
@@ -568,11 +848,11 @@ def _emit_type(ctx: Context, stype: builder.SpecType) -> None:
|
|
|
568
848
|
|
|
569
849
|
class_out = io.StringIO()
|
|
570
850
|
base_class = ""
|
|
571
|
-
|
|
851
|
+
generics = stype.get_generics()
|
|
572
852
|
if not stype.base.is_base:
|
|
573
853
|
base_class = f"({refer_to(ctx, stype.base)})"
|
|
574
|
-
elif
|
|
575
|
-
base_class = f"(typing.Generic[{
|
|
854
|
+
elif len(generics) > 0:
|
|
855
|
+
base_class = f"(typing.Generic[{', '.join(generics)}])"
|
|
576
856
|
class_out.write(f"class {stype.name}{base_class}:\n")
|
|
577
857
|
|
|
578
858
|
emitted_properties_metadata = _emit_type_properties(
|
|
@@ -583,45 +863,45 @@ def _emit_type(ctx: Context, stype: builder.SpecType) -> None:
|
|
|
583
863
|
to_string_values = emitted_properties_metadata.to_string_values
|
|
584
864
|
parse_require = emitted_properties_metadata.parse_require
|
|
585
865
|
|
|
586
|
-
|
|
866
|
+
_emit_generics(ctx, generics)
|
|
587
867
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
):
|
|
594
|
-
ctx.
|
|
595
|
-
ctx.out.write("@serial_class(\n")
|
|
868
|
+
# Emit serial_class decorator
|
|
869
|
+
ctx.out.write("@serial_class(\n")
|
|
870
|
+
ctx.out.write(
|
|
871
|
+
f"{INDENT}named_type_path={util.encode_common_string(_named_type_path(ctx, stype))},\n"
|
|
872
|
+
)
|
|
873
|
+
if stype.is_dynamic_allowed():
|
|
874
|
+
ctx.out.write(f"{INDENT}is_dynamic_allowed=True,\n")
|
|
596
875
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
876
|
+
def write_values(key: str, values: set[str]) -> None:
|
|
877
|
+
if len(values) == 0:
|
|
878
|
+
return
|
|
879
|
+
value_str = ", ".join([f'"{name}"' for name in sorted(values)])
|
|
880
|
+
ctx.out.write(f"{INDENT}{key}={{{value_str}}},\n")
|
|
602
881
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
882
|
+
write_values("unconverted_keys", unconverted_keys)
|
|
883
|
+
write_values("unconverted_values", unconverted_values)
|
|
884
|
+
write_values("to_string_values", to_string_values)
|
|
885
|
+
write_values("parse_require", parse_require)
|
|
607
886
|
|
|
608
|
-
|
|
887
|
+
ctx.out.write(")\n")
|
|
609
888
|
|
|
610
|
-
|
|
889
|
+
# Emit dataclass decorator
|
|
890
|
+
dataclass = "@dataclasses.dataclass"
|
|
611
891
|
dc_args = []
|
|
612
892
|
if stype.is_kw_only():
|
|
613
893
|
dc_args.append("kw_only=True")
|
|
614
894
|
if stype.is_hashable:
|
|
615
895
|
dc_args.extend(["frozen=True", "eq=True"])
|
|
616
896
|
if len(dc_args) > 0:
|
|
617
|
-
dataclass += f
|
|
897
|
+
dataclass += f"({', '.join(dc_args)})"
|
|
618
898
|
|
|
619
899
|
ctx.out.write(f"{dataclass}\n")
|
|
620
900
|
ctx.out.write(class_out.getvalue())
|
|
621
901
|
|
|
622
902
|
|
|
623
|
-
def
|
|
624
|
-
|
|
903
|
+
def _emit_generics(ctx: Context, generics: list[str]) -> None:
|
|
904
|
+
for generic in generics:
|
|
625
905
|
ctx.out.write(f'{generic} = typing.TypeVar("{generic}")\n')
|
|
626
906
|
ctx.out.write(f"{LINE_BREAK}{LINE_BREAK}")
|
|
627
907
|
|
|
@@ -651,6 +931,7 @@ base_name_map = {
|
|
|
651
931
|
builder.BaseTypeName.s_opaque_key: "OpaqueKey",
|
|
652
932
|
builder.BaseTypeName.s_string: "str",
|
|
653
933
|
builder.BaseTypeName.s_tuple: "tuple",
|
|
934
|
+
builder.BaseTypeName.s_readonly_array: "tuple",
|
|
654
935
|
builder.BaseTypeName.s_union: "typing.Union",
|
|
655
936
|
builder.BaseTypeName.s_literal: "typing.Literal",
|
|
656
937
|
}
|
|
@@ -659,6 +940,11 @@ base_name_map = {
|
|
|
659
940
|
def refer_to(ctx: TrackingContext, stype: builder.SpecType) -> str:
|
|
660
941
|
if isinstance(stype, builder.SpecTypeInstance):
|
|
661
942
|
params = ", ".join([refer_to(ctx, p) for p in stype.parameters])
|
|
943
|
+
|
|
944
|
+
if stype.defn_type.is_base_type(builder.BaseTypeName.s_readonly_array):
|
|
945
|
+
assert len(stype.parameters) == 1, "Read Only Array takes one parameter"
|
|
946
|
+
params = f"{params}, ..."
|
|
947
|
+
|
|
662
948
|
return f"{refer_to(ctx, stype.defn_type)}[{params}]"
|
|
663
949
|
|
|
664
950
|
if isinstance(stype, builder.SpecTypeLiteralWrapper):
|
|
@@ -685,23 +971,21 @@ def refer_to(ctx: TrackingContext, stype: builder.SpecType) -> str:
|
|
|
685
971
|
SpecEndpoint = builder.SpecEndpoint
|
|
686
972
|
|
|
687
973
|
|
|
688
|
-
def _route_identifier(endpoint: builder.SpecEndpoint) -> tuple[str, str, str]:
|
|
689
|
-
return (endpoint.path_dirname, endpoint.path_basename, endpoint.method)
|
|
690
|
-
|
|
691
|
-
|
|
692
974
|
def _emit_routes(*, builder: builder.SpecBuilder, config: PythonConfig) -> None:
|
|
693
975
|
for endpoint_root in builder.api_endpoints:
|
|
694
976
|
endpoints: list[SpecEndpoint] = []
|
|
695
977
|
output = config.routes_output.get(endpoint_root)
|
|
696
978
|
if output is None:
|
|
697
979
|
continue
|
|
980
|
+
last_endpoint: SpecEndpoint | None = None
|
|
698
981
|
for namespace in builder.namespaces.values():
|
|
699
982
|
endpoint = namespace.endpoint
|
|
983
|
+
last_endpoint = endpoint
|
|
700
984
|
if endpoint is None:
|
|
701
985
|
continue
|
|
702
|
-
if endpoint.
|
|
986
|
+
if endpoint_root not in endpoint.path_per_api_endpoint:
|
|
703
987
|
continue
|
|
704
|
-
if endpoint.function is None:
|
|
988
|
+
if endpoint.path_per_api_endpoint[endpoint_root].function is None:
|
|
705
989
|
continue
|
|
706
990
|
|
|
707
991
|
endpoints.append(endpoint)
|
|
@@ -714,25 +998,38 @@ def _emit_routes(*, builder: builder.SpecBuilder, config: PythonConfig) -> None:
|
|
|
714
998
|
from main.site.framework.types import StaticRouteType
|
|
715
999
|
"""
|
|
716
1000
|
)
|
|
1001
|
+
|
|
1002
|
+
def _route_identifier(endpoint: SpecEndpoint) -> tuple[str, str, str]:
|
|
1003
|
+
endpoint_specific_path = endpoint.path_per_api_endpoint[endpoint_root]
|
|
1004
|
+
return (
|
|
1005
|
+
endpoint_specific_path.path_dirname,
|
|
1006
|
+
endpoint_specific_path.path_basename,
|
|
1007
|
+
endpoint.method,
|
|
1008
|
+
)
|
|
1009
|
+
|
|
717
1010
|
sorted_endpoints = sorted(endpoints, key=_route_identifier)
|
|
718
1011
|
|
|
719
|
-
assert len(endpoints) == len(
|
|
720
|
-
|
|
721
|
-
)
|
|
1012
|
+
assert len(endpoints) == len(set(map(_route_identifier, endpoints))), (
|
|
1013
|
+
"Endpoints are not unique"
|
|
1014
|
+
)
|
|
722
1015
|
|
|
723
1016
|
path_set = set()
|
|
724
1017
|
for endpoint in sorted_endpoints:
|
|
725
|
-
|
|
726
|
-
|
|
1018
|
+
last_endpoint = endpoint
|
|
1019
|
+
endpoint_function_path = endpoint.path_per_api_endpoint[endpoint_root]
|
|
1020
|
+
assert endpoint_function_path.function
|
|
1021
|
+
func_bits = endpoint_function_path.function.split(".")
|
|
727
1022
|
path = ".".join(func_bits[:-1])
|
|
728
1023
|
if path in path_set:
|
|
729
1024
|
continue
|
|
730
1025
|
path_set.add(path)
|
|
731
1026
|
static_out.write(f"import {path}\n")
|
|
732
1027
|
|
|
1028
|
+
assert last_endpoint is not None
|
|
1029
|
+
|
|
733
1030
|
static_out.write(
|
|
734
1031
|
f"""
|
|
735
|
-
ROUTE_PREFIX = "/{
|
|
1032
|
+
ROUTE_PREFIX = "/{last_endpoint.path_per_api_endpoint[endpoint_root].path_root}"
|
|
736
1033
|
|
|
737
1034
|
ROUTES: list[StaticRouteType] = [
|
|
738
1035
|
"""
|
|
@@ -746,20 +1043,21 @@ ROUTES: list[StaticRouteType] = [
|
|
|
746
1043
|
|
|
747
1044
|
from main.site.framework.types import DynamicRouteType
|
|
748
1045
|
|
|
749
|
-
ROUTE_PREFIX = "/{
|
|
1046
|
+
ROUTE_PREFIX = "/{last_endpoint.path_per_api_endpoint[endpoint_root].path_root}"
|
|
750
1047
|
|
|
751
1048
|
ROUTES: list[DynamicRouteType] = [
|
|
752
1049
|
"""
|
|
753
1050
|
)
|
|
754
1051
|
|
|
755
1052
|
for endpoint in sorted_endpoints:
|
|
1053
|
+
endpoint_function_path = endpoint.path_per_api_endpoint[endpoint_root]
|
|
756
1054
|
dynamic_out.write(
|
|
757
|
-
f'{INDENT}("{
|
|
1055
|
+
f'{INDENT}("{endpoint_function_path.path_dirname}/{endpoint_function_path.path_basename}", "{endpoint_function_path.function}", ["{endpoint.method.upper()}"]),\n'
|
|
758
1056
|
)
|
|
759
1057
|
|
|
760
|
-
assert
|
|
1058
|
+
assert endpoint_function_path.function
|
|
761
1059
|
static_out.write(
|
|
762
|
-
f'{INDENT}("{
|
|
1060
|
+
f'{INDENT}("{endpoint_function_path.path_dirname}/{endpoint_function_path.path_basename}", {endpoint_function_path.function}, ["{endpoint.method.upper()}"]),\n'
|
|
763
1061
|
)
|
|
764
1062
|
|
|
765
1063
|
dynamic_out.write(f"{MODIFY_NOTICE}]\n")
|
|
@@ -774,16 +1072,22 @@ ROUTES: list[DynamicRouteType] = [
|
|
|
774
1072
|
def _emit_namespace_imports(
|
|
775
1073
|
*,
|
|
776
1074
|
out: io.StringIO,
|
|
777
|
-
namespaces:
|
|
778
|
-
from_namespace:
|
|
1075
|
+
namespaces: set[builder.SpecNamespace],
|
|
1076
|
+
from_namespace: builder.SpecNamespace | None,
|
|
779
1077
|
config: PythonConfig,
|
|
1078
|
+
skip_non_sdk: bool = False,
|
|
780
1079
|
) -> None:
|
|
781
1080
|
for ns in sorted(
|
|
782
1081
|
namespaces,
|
|
783
1082
|
key=lambda name: _resolve_namespace_name(name),
|
|
784
1083
|
):
|
|
1084
|
+
if (
|
|
1085
|
+
skip_non_sdk
|
|
1086
|
+
and ns.endpoint is not None
|
|
1087
|
+
and ns.endpoint.is_sdk != EndpointEmitType.EMIT_ENDPOINT
|
|
1088
|
+
):
|
|
1089
|
+
continue
|
|
785
1090
|
resolved = _resolve_namespace_name(ns)
|
|
786
|
-
ref = _resolve_namespace_ref(ns)
|
|
787
1091
|
if ns.endpoint is not None:
|
|
788
1092
|
import_alias = "_".join(ns.path[2:]) + "_t"
|
|
789
1093
|
out.write(
|
|
@@ -795,7 +1099,7 @@ def _emit_namespace_imports(
|
|
|
795
1099
|
else:
|
|
796
1100
|
from_path = config.types_package
|
|
797
1101
|
|
|
798
|
-
out.write(f"from {from_path} import {resolved}
|
|
1102
|
+
out.write(f"from {from_path} import {resolved}\n")
|
|
799
1103
|
|
|
800
1104
|
|
|
801
1105
|
def _emit_id_source(*, builder: builder.SpecBuilder, config: PythonConfig) -> None:
|
|
@@ -807,7 +1111,7 @@ def _emit_id_source(*, builder: builder.SpecBuilder, config: PythonConfig) -> No
|
|
|
807
1111
|
return None
|
|
808
1112
|
enum_out = io.StringIO()
|
|
809
1113
|
enum_out.write(f"{LINT_HEADER}{MODIFY_NOTICE}\n")
|
|
810
|
-
enum_out.write("from typing import Literal,
|
|
1114
|
+
enum_out.write("from typing import Literal, Union\n")
|
|
811
1115
|
enum_out.write("from pkgs.strenum_compat import StrEnum\n")
|
|
812
1116
|
|
|
813
1117
|
ctx = TrackingContext()
|
|
@@ -822,13 +1126,13 @@ def _emit_id_source(*, builder: builder.SpecBuilder, config: PythonConfig) -> No
|
|
|
822
1126
|
)
|
|
823
1127
|
|
|
824
1128
|
known_keys = []
|
|
825
|
-
enum_out.write("\nENUM_MAP: dict[str,
|
|
1129
|
+
enum_out.write("\nENUM_MAP: dict[str, type[StrEnum]] = {\n")
|
|
826
1130
|
for key in sorted(named_enums.keys()):
|
|
827
1131
|
enum_out.write(f'"{key}": {named_enums[key]},\n')
|
|
828
1132
|
known_keys.append(f'Literal["{key}"]')
|
|
829
1133
|
enum_out.write(f"}}\n{MODIFY_NOTICE}\n")
|
|
830
1134
|
|
|
831
|
-
enum_out.write(f"\nKnownEnumsType = Union[
|
|
1135
|
+
enum_out.write(f"\nKnownEnumsType = Union[\n{INDENT}")
|
|
832
1136
|
enum_out.write(f",\n{INDENT}".join(known_keys))
|
|
833
1137
|
enum_out.write(f"\n]\n{MODIFY_NOTICE}\n")
|
|
834
1138
|
|
|
@@ -845,21 +1149,36 @@ def _emit_api_stubs(*, builder: builder.SpecBuilder, config: PythonConfig) -> No
|
|
|
845
1149
|
|
|
846
1150
|
if endpoint is None:
|
|
847
1151
|
continue
|
|
848
|
-
if endpoint.
|
|
1152
|
+
if endpoint_root not in endpoint.path_per_api_endpoint:
|
|
849
1153
|
continue
|
|
850
|
-
|
|
1154
|
+
|
|
1155
|
+
endpoint_function = endpoint.path_per_api_endpoint[endpoint_root].function
|
|
1156
|
+
if endpoint_function is None:
|
|
851
1157
|
continue
|
|
852
1158
|
|
|
853
|
-
module_dir, file_name, func_name =
|
|
1159
|
+
module_dir, file_name, func_name = endpoint_function.rsplit(".", 2)
|
|
854
1160
|
module_path = os.path.abspath(module_dir.replace(".", "/"))
|
|
855
1161
|
api_stub_file = f"{module_path}/{file_name}.py"
|
|
856
1162
|
if os.path.isfile(api_stub_file):
|
|
857
1163
|
continue
|
|
858
|
-
_create_api_stub(
|
|
1164
|
+
_create_api_stub(
|
|
1165
|
+
api_stub_file=api_stub_file,
|
|
1166
|
+
file_name=file_name,
|
|
1167
|
+
endpoint=endpoint,
|
|
1168
|
+
config=config,
|
|
1169
|
+
endpoint_root=endpoint_root,
|
|
1170
|
+
top_namespace=builder.top_namespace,
|
|
1171
|
+
)
|
|
859
1172
|
|
|
860
1173
|
|
|
861
1174
|
def _create_api_stub(
|
|
862
|
-
|
|
1175
|
+
*,
|
|
1176
|
+
api_stub_file: str,
|
|
1177
|
+
file_name: str,
|
|
1178
|
+
endpoint: SpecEndpoint,
|
|
1179
|
+
config: PythonConfig,
|
|
1180
|
+
endpoint_root: str,
|
|
1181
|
+
top_namespace: str,
|
|
863
1182
|
) -> None:
|
|
864
1183
|
assert (
|
|
865
1184
|
endpoint.method == builder.RouteMethod.post
|
|
@@ -867,7 +1186,13 @@ def _create_api_stub(
|
|
|
867
1186
|
or endpoint.method == builder.RouteMethod.delete
|
|
868
1187
|
or endpoint.method == builder.RouteMethod.patch
|
|
869
1188
|
)
|
|
870
|
-
api_out = _create_api_function(
|
|
1189
|
+
api_out = _create_api_function(
|
|
1190
|
+
file_name=file_name,
|
|
1191
|
+
endpoint=endpoint,
|
|
1192
|
+
config=config,
|
|
1193
|
+
endpoint_root=endpoint_root,
|
|
1194
|
+
top_namespace=top_namespace,
|
|
1195
|
+
)
|
|
871
1196
|
util.rewrite_file(api_stub_file, api_out.getvalue())
|
|
872
1197
|
|
|
873
1198
|
|
|
@@ -876,15 +1201,22 @@ WRAP_ARGS_END = "\n"
|
|
|
876
1201
|
|
|
877
1202
|
|
|
878
1203
|
def _create_api_function(
|
|
879
|
-
|
|
1204
|
+
*,
|
|
1205
|
+
file_name: str,
|
|
1206
|
+
endpoint: SpecEndpoint,
|
|
1207
|
+
config: PythonConfig,
|
|
1208
|
+
endpoint_root: str,
|
|
1209
|
+
top_namespace: str,
|
|
880
1210
|
) -> io.StringIO:
|
|
1211
|
+
endpoint_specific_path = endpoint.path_per_api_endpoint[endpoint_root]
|
|
1212
|
+
assert endpoint_specific_path is not None
|
|
881
1213
|
api_out = io.StringIO()
|
|
882
1214
|
python_api_type_root = f"{config.types_package}.api"
|
|
883
|
-
dot_dirname =
|
|
1215
|
+
dot_dirname = endpoint_specific_path.path_dirname.replace("/", ".")
|
|
884
1216
|
api_import = (
|
|
885
1217
|
f"{python_api_type_root}.{dot_dirname}.{file_name}"
|
|
886
1218
|
if dot_dirname != ""
|
|
887
|
-
else f"{python_api_type_root}.{
|
|
1219
|
+
else f"{python_api_type_root}.{endpoint_specific_path.path_basename}"
|
|
888
1220
|
)
|
|
889
1221
|
|
|
890
1222
|
if endpoint.method == builder.RouteMethod.post:
|
|
@@ -896,7 +1228,20 @@ def _create_api_function(
|
|
|
896
1228
|
elif endpoint.method == builder.RouteMethod.patch:
|
|
897
1229
|
validated_method = "validated_patch"
|
|
898
1230
|
|
|
899
|
-
ruff_requires_wrap = len(
|
|
1231
|
+
ruff_requires_wrap = len(endpoint_specific_path.path_basename) > 14
|
|
1232
|
+
|
|
1233
|
+
account_type = (
|
|
1234
|
+
endpoint_specific_path.root
|
|
1235
|
+
if endpoint_specific_path.root not in ["external", "portal"]
|
|
1236
|
+
else "materials"
|
|
1237
|
+
)
|
|
1238
|
+
|
|
1239
|
+
endpoint_path_name = (
|
|
1240
|
+
ENDPOINT_PATH_ALTERNATE
|
|
1241
|
+
if len(endpoint.path_per_api_endpoint.keys()) > 1
|
|
1242
|
+
and endpoint_specific_path.root != top_namespace
|
|
1243
|
+
else ENDPOINT_PATH
|
|
1244
|
+
)
|
|
900
1245
|
|
|
901
1246
|
api_out.write(
|
|
902
1247
|
f"""import {api_import} as api
|
|
@@ -904,8 +1249,8 @@ from main.db.session import Session, SessionMaker
|
|
|
904
1249
|
from main.site.decorators import APIError, APIResponse, {validated_method}
|
|
905
1250
|
|
|
906
1251
|
|
|
907
|
-
@{validated_method}(api.
|
|
908
|
-
def {
|
|
1252
|
+
@{validated_method}(api.{endpoint_path_name}, "{account_type}", api.Arguments)
|
|
1253
|
+
def {endpoint_specific_path.path_basename}({WRAP_ARGS_START if ruff_requires_wrap else ""}args: api.Arguments, client_sm: SessionMaker{WRAP_ARGS_END if ruff_requires_wrap else ""}) -> APIResponse[api.Data]:
|
|
909
1254
|
with Session(client_sm) as session:
|
|
910
1255
|
# return APIResponse(data=api.Data())
|
|
911
1256
|
pass
|
|
@@ -927,7 +1272,7 @@ def _emit_api_argument_lookup(
|
|
|
927
1272
|
for endpoint_root in builder.api_endpoints:
|
|
928
1273
|
routes_output = config.routes_output[endpoint_root]
|
|
929
1274
|
|
|
930
|
-
imports = []
|
|
1275
|
+
imports = ["import typing", "import dataclasses"]
|
|
931
1276
|
mappings = []
|
|
932
1277
|
for namespace in sorted(
|
|
933
1278
|
builder.namespaces.values(),
|
|
@@ -937,17 +1282,38 @@ def _emit_api_argument_lookup(
|
|
|
937
1282
|
|
|
938
1283
|
if endpoint is None:
|
|
939
1284
|
continue
|
|
940
|
-
if endpoint.
|
|
1285
|
+
if endpoint_root not in endpoint.path_per_api_endpoint:
|
|
941
1286
|
continue
|
|
942
|
-
if endpoint.function is None:
|
|
1287
|
+
if endpoint.path_per_api_endpoint[endpoint_root].function is None:
|
|
943
1288
|
continue
|
|
944
|
-
if "Arguments" not in namespace.types:
|
|
1289
|
+
if "Arguments" not in namespace.types or "Data" not in namespace.types:
|
|
945
1290
|
continue
|
|
946
1291
|
|
|
947
1292
|
import_alias = "_".join(namespace.path[1:])
|
|
948
1293
|
api_import = f"{config.types_package}.{'.'.join(namespace.path)}"
|
|
949
1294
|
imports.append(f"import {api_import} as {import_alias}")
|
|
950
|
-
|
|
1295
|
+
|
|
1296
|
+
route_group = (
|
|
1297
|
+
f'"{endpoint.route_group}"'
|
|
1298
|
+
if endpoint.route_group is not None
|
|
1299
|
+
else "None"
|
|
1300
|
+
)
|
|
1301
|
+
account_type = (
|
|
1302
|
+
f'"{endpoint.account_type}"'
|
|
1303
|
+
if endpoint.account_type is not None
|
|
1304
|
+
else "None"
|
|
1305
|
+
)
|
|
1306
|
+
|
|
1307
|
+
mapping = f"{INDENT}ApiEndpointKey(route={import_alias}.ENDPOINT_PATH, method={import_alias}.ENDPOINT_METHOD): ApiEndpointSpec(\n"
|
|
1308
|
+
mapping += f"{INDENT}{INDENT}arguments_type={import_alias}.Arguments,\n"
|
|
1309
|
+
mapping += f"{INDENT}{INDENT}data_type={import_alias}.Data,\n"
|
|
1310
|
+
mapping += f"{INDENT}{INDENT}route_group={route_group},\n"
|
|
1311
|
+
mapping += f"{INDENT}{INDENT}account_type={account_type},\n"
|
|
1312
|
+
mapping += f"{INDENT}{INDENT}route={import_alias}.ENDPOINT_PATH,\n"
|
|
1313
|
+
mapping += f'{INDENT}{INDENT}handler="{endpoint.path_per_api_endpoint[endpoint_root].function}",\n'
|
|
1314
|
+
mapping += f"{INDENT}{INDENT}method={import_alias}.ENDPOINT_METHOD,\n"
|
|
1315
|
+
mapping += f"{INDENT})"
|
|
1316
|
+
mappings.append(mapping)
|
|
951
1317
|
|
|
952
1318
|
argument_lookup_out = io.StringIO()
|
|
953
1319
|
argument_lookup_out.write(MODIFY_NOTICE)
|
|
@@ -955,8 +1321,29 @@ def _emit_api_argument_lookup(
|
|
|
955
1321
|
argument_lookup_out.write(
|
|
956
1322
|
f"""{LINE_BREAK.join(imports)}
|
|
957
1323
|
|
|
958
|
-
|
|
959
|
-
|
|
1324
|
+
AT = typing.TypeVar("AT")
|
|
1325
|
+
DT = typing.TypeVar("DT")
|
|
1326
|
+
|
|
1327
|
+
|
|
1328
|
+
@dataclasses.dataclass(kw_only=True, frozen=True)
|
|
1329
|
+
class ApiEndpointKey:
|
|
1330
|
+
method: str
|
|
1331
|
+
route: str
|
|
1332
|
+
|
|
1333
|
+
|
|
1334
|
+
@dataclasses.dataclass(kw_only=True)
|
|
1335
|
+
class ApiEndpointSpec(typing.Generic[AT, DT]):
|
|
1336
|
+
route: str
|
|
1337
|
+
arguments_type: type[AT]
|
|
1338
|
+
data_type: type[DT]
|
|
1339
|
+
route_group: str | None
|
|
1340
|
+
account_type: str | None
|
|
1341
|
+
handler: str
|
|
1342
|
+
method: str
|
|
1343
|
+
|
|
1344
|
+
|
|
1345
|
+
{API_ARGUMENTS_NAME}: dict[ApiEndpointKey, ApiEndpointSpec] = {{
|
|
1346
|
+
{f",{LINE_BREAK}".join(mappings)},
|
|
960
1347
|
}}
|
|
961
1348
|
|
|
962
1349
|
__all__ = ["{API_ARGUMENTS_NAME}"]
|
|
@@ -972,10 +1359,69 @@ __all__ = ["{API_ARGUMENTS_NAME}"]
|
|
|
972
1359
|
CLIENT_CLASS_FILENAME = "client_base"
|
|
973
1360
|
CLIENT_CLASS_IMPORTS = [
|
|
974
1361
|
"from abc import ABC, abstractmethod",
|
|
975
|
-
"
|
|
1362
|
+
"import dataclasses",
|
|
1363
|
+
]
|
|
1364
|
+
ASYNC_BATCH_PROCESSOR_FILENAME = "async_batch_processor"
|
|
1365
|
+
ASYNC_BATCH_PROCESSOR_IMPORTS = [
|
|
1366
|
+
"import uuid",
|
|
1367
|
+
"from abc import ABC, abstractmethod",
|
|
1368
|
+
"from pkgs.serialization_util import serialize_for_api",
|
|
976
1369
|
]
|
|
977
1370
|
|
|
978
1371
|
|
|
1372
|
+
def _emit_async_batch_processor(
|
|
1373
|
+
*, spec_builder: builder.SpecBuilder, config: PythonConfig
|
|
1374
|
+
) -> None:
|
|
1375
|
+
if not config.emit_async_batch_processor:
|
|
1376
|
+
return
|
|
1377
|
+
|
|
1378
|
+
async_batch_processor_out = io.StringIO()
|
|
1379
|
+
ctx = Context(
|
|
1380
|
+
out=io.StringIO(),
|
|
1381
|
+
namespace=builder.SpecNamespace("async_batch_processor"),
|
|
1382
|
+
builder=spec_builder,
|
|
1383
|
+
)
|
|
1384
|
+
|
|
1385
|
+
for namespace in sorted(
|
|
1386
|
+
spec_builder.namespaces.values(),
|
|
1387
|
+
key=lambda ns: _resolve_namespace_name(ns),
|
|
1388
|
+
):
|
|
1389
|
+
_emit_async_batch_invocation_function(ctx=ctx, namespace=namespace)
|
|
1390
|
+
|
|
1391
|
+
async_batch_processor_out.write(MODIFY_NOTICE)
|
|
1392
|
+
async_batch_processor_out.write(LINT_HEADER)
|
|
1393
|
+
async_batch_processor_out.write("# ruff: noqa: PLR0904\n")
|
|
1394
|
+
|
|
1395
|
+
_emit_types_imports(out=async_batch_processor_out, ctx=ctx)
|
|
1396
|
+
_emit_namespace_imports(
|
|
1397
|
+
out=async_batch_processor_out,
|
|
1398
|
+
namespaces=ctx.namespaces,
|
|
1399
|
+
from_namespace=None,
|
|
1400
|
+
config=config,
|
|
1401
|
+
)
|
|
1402
|
+
|
|
1403
|
+
async_batch_processor_out.write(
|
|
1404
|
+
f"""{LINE_BREAK.join(ASYNC_BATCH_PROCESSOR_IMPORTS)}
|
|
1405
|
+
|
|
1406
|
+
|
|
1407
|
+
class AsyncBatchProcessorBase(ABC):
|
|
1408
|
+
@abstractmethod
|
|
1409
|
+
def _enqueue(self, req: {refer_to(ctx=ctx, stype=ASYNC_BATCH_REQUEST_STYPE)}) -> None:
|
|
1410
|
+
...
|
|
1411
|
+
|
|
1412
|
+
@abstractmethod
|
|
1413
|
+
def send(self) -> base_t.ObjectId:
|
|
1414
|
+
..."""
|
|
1415
|
+
)
|
|
1416
|
+
async_batch_processor_out.write(ctx.out.getvalue())
|
|
1417
|
+
async_batch_processor_out.write("\n")
|
|
1418
|
+
|
|
1419
|
+
util.rewrite_file(
|
|
1420
|
+
f"{config.types_output}/{ASYNC_BATCH_PROCESSOR_FILENAME}.py",
|
|
1421
|
+
async_batch_processor_out.getvalue(),
|
|
1422
|
+
)
|
|
1423
|
+
|
|
1424
|
+
|
|
979
1425
|
def _emit_client_class(
|
|
980
1426
|
*, spec_builder: builder.SpecBuilder, config: PythonConfig
|
|
981
1427
|
) -> None:
|
|
@@ -983,7 +1429,11 @@ def _emit_client_class(
|
|
|
983
1429
|
return
|
|
984
1430
|
|
|
985
1431
|
client_base_out = io.StringIO()
|
|
986
|
-
ctx = Context(
|
|
1432
|
+
ctx = Context(
|
|
1433
|
+
out=io.StringIO(),
|
|
1434
|
+
builder=spec_builder,
|
|
1435
|
+
namespace=builder.SpecNamespace("client_base"),
|
|
1436
|
+
)
|
|
987
1437
|
for namespace in sorted(
|
|
988
1438
|
spec_builder.namespaces.values(),
|
|
989
1439
|
key=lambda ns: _resolve_namespace_name(ns),
|
|
@@ -998,8 +1448,9 @@ def _emit_client_class(
|
|
|
998
1448
|
_emit_namespace_imports(
|
|
999
1449
|
out=client_base_out,
|
|
1000
1450
|
namespaces=ctx.namespaces,
|
|
1001
|
-
from_namespace=
|
|
1451
|
+
from_namespace=None,
|
|
1002
1452
|
config=config,
|
|
1453
|
+
skip_non_sdk=True,
|
|
1003
1454
|
)
|
|
1004
1455
|
|
|
1005
1456
|
client_base_out.write(
|
|
@@ -1008,7 +1459,7 @@ def _emit_client_class(
|
|
|
1008
1459
|
DT = typing.TypeVar("DT")
|
|
1009
1460
|
|
|
1010
1461
|
|
|
1011
|
-
@dataclass(kw_only=True)
|
|
1462
|
+
@dataclasses.dataclass(kw_only=True)
|
|
1012
1463
|
class APIRequest:
|
|
1013
1464
|
method: str
|
|
1014
1465
|
endpoint: str
|
|
@@ -1019,10 +1470,10 @@ class ClientMethods(ABC):
|
|
|
1019
1470
|
|
|
1020
1471
|
@abstractmethod
|
|
1021
1472
|
def do_request(self, *, api_request: APIRequest, return_type: type[DT]) -> DT:
|
|
1022
|
-
...
|
|
1023
|
-
"""
|
|
1473
|
+
..."""
|
|
1024
1474
|
)
|
|
1025
1475
|
client_base_out.write(ctx.out.getvalue())
|
|
1476
|
+
client_base_out.write("\n")
|
|
1026
1477
|
|
|
1027
1478
|
util.rewrite_file(
|
|
1028
1479
|
f"{config.types_output}/{CLIENT_CLASS_FILENAME}.py", client_base_out.getvalue()
|