UncountablePythonSDK 0.0.8__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.8.dist-info → UncountablePythonSDK-0.0.92.dist-info}/WHEEL +1 -1
- {UncountablePythonSDK-0.0.8.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/missing_sentry.py +1 -1
- 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 +116 -74
- 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 +599 -151
- 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 +136 -20
- 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 -11
- uncountable/types/api/entity/create_entity.py +21 -12
- uncountable/types/api/entity/get_entities_data.py +18 -8
- uncountable/types/api/entity/grant_entity_permissions.py +48 -0
- uncountable/types/api/entity/list_entities.py +27 -12
- uncountable/types/api/entity/lock_entity.py +45 -0
- uncountable/types/api/entity/resolve_entity_ids.py +17 -7
- uncountable/types/api/entity/set_entity_field_values.py +44 -0
- uncountable/types/api/entity/set_values.py +14 -7
- 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 +16 -6
- uncountable/types/api/inputs/create_inputs.py +24 -11
- uncountable/types/api/inputs/get_input_data.py +32 -13
- uncountable/types/api/inputs/get_input_names.py +18 -8
- uncountable/types/api/inputs/get_inputs_data.py +29 -10
- uncountable/types/api/inputs/set_input_attribute_values.py +16 -9
- 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 +32 -16
- uncountable/types/api/outputs/get_output_names.py +18 -8
- 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 +17 -7
- uncountable/types/api/project/get_projects_data.py +21 -11
- 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 +18 -8
- 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 -12
- 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 +15 -7
- uncountable/types/api/recipes/get_recipe_calculations.py +17 -10
- uncountable/types/api/recipes/get_recipe_links.py +13 -6
- uncountable/types/api/recipes/get_recipe_names.py +16 -6
- uncountable/types/api/recipes/get_recipe_output_metadata.py +14 -7
- uncountable/types/api/recipes/get_recipes_data.py +63 -38
- 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 +19 -10
- 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 +26 -12
- 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 +3 -18
- 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 +1093 -54
- uncountable/types/client_config.py +8 -0
- uncountable/types/client_config_t.py +26 -0
- uncountable/types/curves.py +5 -42
- uncountable/types/curves_t.py +51 -0
- uncountable/types/entity.py +8 -269
- uncountable/types/entity_t.py +393 -0
- uncountable/types/experiment_groups.py +3 -18
- uncountable/types/experiment_groups_t.py +27 -0
- uncountable/types/field_values.py +17 -60
- uncountable/types/field_values_t.py +204 -0
- uncountable/types/fields.py +3 -19
- 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 +3 -24
- uncountable/types/input_attributes_t.py +30 -0
- uncountable/types/inputs.py +6 -56
- 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 +3 -21
- 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 +3 -18
- 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 -45
- uncountable/types/recipe_links_t.py +54 -0
- uncountable/types/recipe_metadata.py +5 -45
- uncountable/types/recipe_metadata_t.py +58 -0
- uncountable/types/recipe_output_metadata.py +3 -19
- uncountable/types/recipe_output_metadata_t.py +28 -0
- uncountable/types/recipe_tags.py +3 -18
- 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 +3 -20
- 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 +3 -18
- uncountable/types/units_t.py +27 -0
- uncountable/types/users.py +3 -19
- 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 +4 -27
- uncountable/types/workflows_t.py +39 -0
- UncountablePythonSDK-0.0.8.dist-info/METADATA +0 -27
- UncountablePythonSDK-0.0.8.dist-info/RECORD +0 -134
- 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 -29
- type_spec/external/api/entity/list_entities.yaml +0 -52
- type_spec/external/api/entity/resolve_entity_ids.yaml +0 -29
- type_spec/external/api/entity/set_values.yaml +0 -18
- type_spec/external/api/input_groups/get_input_group_names.yaml +0 -29
- type_spec/external/api/inputs/create_inputs.yaml +0 -48
- type_spec/external/api/inputs/get_input_data.yaml +0 -95
- type_spec/external/api/inputs/get_input_names.yaml +0 -38
- type_spec/external/api/inputs/get_inputs_data.yaml +0 -82
- type_spec/external/api/inputs/set_input_attribute_values.yaml +0 -33
- type_spec/external/api/outputs/get_output_data.yaml +0 -92
- 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 -42
- type_spec/external/api/project/get_projects_data.yaml +0 -50
- type_spec/external/api/recipe_metadata/get_recipe_metadata_data.yaml +0 -41
- type_spec/external/api/recipes/create_recipes.yaml +0 -47
- type_spec/external/api/recipes/get_curve.yaml +0 -18
- type_spec/external/api/recipes/get_recipe_calculations.yaml +0 -39
- 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 -36
- type_spec/external/api/recipes/get_recipes_data.yaml +0 -238
- type_spec/external/api/recipes/set_recipe_inputs.yaml +0 -36
- type_spec/external/api/recipes/set_recipe_outputs.yaml +0 -52
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,78 +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
409
|
stype: builder.SpecTypeDefn, name: str, supports_inheritance: bool = False
|
|
339
410
|
) -> builder.SpecTypeDefnObject:
|
|
340
|
-
assert isinstance(
|
|
341
|
-
|
|
342
|
-
)
|
|
411
|
+
assert isinstance(stype, builder.SpecTypeDefnObject), (
|
|
412
|
+
f"External api {name} must be an object"
|
|
413
|
+
)
|
|
343
414
|
if not supports_inheritance:
|
|
344
|
-
assert (
|
|
345
|
-
|
|
346
|
-
)
|
|
415
|
+
assert stype.base is None or stype.base.is_base, (
|
|
416
|
+
f"Inheritance not supported in external api {name}"
|
|
417
|
+
)
|
|
347
418
|
return stype
|
|
348
419
|
|
|
349
420
|
|
|
350
|
-
def
|
|
351
|
-
ctx: Context,
|
|
421
|
+
def _emit_endpoint_invocation_docstring(
|
|
422
|
+
ctx: Context,
|
|
423
|
+
endpoint: builder.SpecEndpoint,
|
|
424
|
+
properties: list[builder.SpecProperty],
|
|
352
425
|
) -> None:
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
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:
|
|
357
429
|
return
|
|
358
430
|
|
|
359
|
-
|
|
360
|
-
|
|
431
|
+
FULL_INDENT = INDENT * 2
|
|
432
|
+
ctx.out.write(FULL_INDENT)
|
|
433
|
+
ctx.out.write('"""')
|
|
361
434
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
)
|
|
435
|
+
if endpoint.desc is not None and has_endpoint_desc:
|
|
436
|
+
ctx.out.write(f"{endpoint.desc}\n")
|
|
437
|
+
ctx.out.write("\n")
|
|
366
438
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
)
|
|
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 [])
|
|
373
459
|
|
|
374
|
-
has_arguments = (
|
|
375
|
-
arguments_type.properties is not None
|
|
376
|
-
and len(arguments_type.properties.values()) > 0
|
|
377
|
-
)
|
|
378
460
|
assert endpoint.function is not None
|
|
379
461
|
function_name = endpoint.function.split(".")[-1]
|
|
380
|
-
ctx.out.write("\n")
|
|
381
462
|
ctx.out.write(
|
|
382
463
|
f"""
|
|
383
464
|
def {function_name}(
|
|
384
465
|
self,\n"""
|
|
385
466
|
)
|
|
386
|
-
if
|
|
467
|
+
if len(all_arguments) > 0:
|
|
387
468
|
ctx.out.write(f"{INDENT}{INDENT}*,\n")
|
|
388
|
-
|
|
469
|
+
_emit_properties(
|
|
389
470
|
ctx=ctx,
|
|
390
|
-
|
|
471
|
+
properties=all_arguments,
|
|
391
472
|
num_indent=2,
|
|
392
473
|
separator=",\n",
|
|
393
474
|
class_out=ctx.out,
|
|
394
475
|
)
|
|
395
476
|
ctx.out.write(f"{INDENT}) -> {refer_to(ctx=ctx, stype=data_type)}:")
|
|
396
|
-
|
|
397
477
|
ctx.out.write("\n")
|
|
398
|
-
|
|
399
|
-
if
|
|
400
|
-
|
|
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:
|
|
401
492
|
ctx.out.write("\n")
|
|
402
|
-
for prop in
|
|
493
|
+
for prop in variable_type.properties.values():
|
|
403
494
|
ctx.out.write(f"{INDENT}{INDENT}{INDENT}{prop.name}={prop.name},")
|
|
404
495
|
ctx.out.write("\n")
|
|
405
496
|
ctx.out.write(f"{INDENT}{INDENT})")
|
|
406
497
|
else:
|
|
407
498
|
ctx.out.write(")")
|
|
408
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
|
+
|
|
409
619
|
ctx.out.write(
|
|
410
620
|
f"""
|
|
411
621
|
api_request = APIRequest(
|
|
@@ -415,7 +625,6 @@ def _emit_endpoint_invocation_function(
|
|
|
415
625
|
)
|
|
416
626
|
return self.do_request(api_request=api_request, return_type={refer_to(ctx=ctx, stype=data_type)})"""
|
|
417
627
|
)
|
|
418
|
-
ctx.out.write("\n")
|
|
419
628
|
|
|
420
629
|
|
|
421
630
|
def _emit_string_enum(ctx: Context, stype: builder.SpecTypeDefnStringEnum) -> None:
|
|
@@ -433,7 +642,9 @@ def _emit_string_enum(ctx: Context, stype: builder.SpecTypeDefnStringEnum) -> No
|
|
|
433
642
|
ctx.out.write(f"{INDENT}labels={{\n")
|
|
434
643
|
for entry in stype.values.values():
|
|
435
644
|
if entry.label is not None:
|
|
436
|
-
ctx.out.write(
|
|
645
|
+
ctx.out.write(
|
|
646
|
+
f'{INDENT}{INDENT}"{entry.value}": "{entry.label}",\n'
|
|
647
|
+
)
|
|
437
648
|
|
|
438
649
|
ctx.out.write(f"{INDENT}}},\n")
|
|
439
650
|
if need_deprecated:
|
|
@@ -464,12 +675,12 @@ def _emit_string_enum(ctx: Context, stype: builder.SpecTypeDefnStringEnum) -> No
|
|
|
464
675
|
)
|
|
465
676
|
|
|
466
677
|
|
|
467
|
-
@dataclass
|
|
678
|
+
@dataclasses.dataclass
|
|
468
679
|
class EmittedPropertiesMetadata:
|
|
469
|
-
unconverted_keys:
|
|
470
|
-
unconverted_values:
|
|
471
|
-
to_string_values:
|
|
472
|
-
parse_require:
|
|
680
|
+
unconverted_keys: set[str]
|
|
681
|
+
unconverted_values: set[str]
|
|
682
|
+
to_string_values: set[str]
|
|
683
|
+
parse_require: set[str]
|
|
473
684
|
|
|
474
685
|
|
|
475
686
|
def _emit_type_properties(
|
|
@@ -480,16 +691,31 @@ def _emit_type_properties(
|
|
|
480
691
|
num_indent: int = 1,
|
|
481
692
|
separator: str = "\n",
|
|
482
693
|
) -> EmittedPropertiesMetadata:
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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()
|
|
487
715
|
|
|
488
|
-
if
|
|
716
|
+
if len(properties) > 0:
|
|
489
717
|
|
|
490
718
|
def write_field(prop: builder.SpecProperty) -> None:
|
|
491
|
-
# Checked in outer function, MyPy doens't track the check inside here
|
|
492
|
-
assert isinstance(stype, builder.SpecTypeDefn)
|
|
493
719
|
if prop.name_case == builder.NameCase.preserve:
|
|
494
720
|
unconverted_keys.add(prop.name)
|
|
495
721
|
py_name = python_field_name(prop.name, prop.name_case)
|
|
@@ -512,20 +738,30 @@ def _emit_type_properties(
|
|
|
512
738
|
default = "MISSING_SENTRY"
|
|
513
739
|
ctx.use_missing = True
|
|
514
740
|
elif prop.extant == builder.PropertyExtant.optional:
|
|
515
|
-
ref_type
|
|
741
|
+
if ref_type != "None":
|
|
742
|
+
ref_type = f"{ref_type} | None"
|
|
516
743
|
default = "None"
|
|
517
744
|
elif prop.has_default:
|
|
518
745
|
default = _emit_value(ctx, prop.spec_type, prop.default)
|
|
519
|
-
|
|
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)"
|
|
520
756
|
class_out.write(f"{INDENT * num_indent}{py_name}: {ref_type}")
|
|
521
757
|
if default:
|
|
522
758
|
class_out.write(f" = {default}")
|
|
523
759
|
class_out.write(separator)
|
|
524
760
|
|
|
525
|
-
for prop in
|
|
761
|
+
for prop in properties:
|
|
526
762
|
if prop.extant == builder.PropertyExtant.required:
|
|
527
763
|
write_field(prop)
|
|
528
|
-
for prop in
|
|
764
|
+
for prop in properties:
|
|
529
765
|
if prop.extant != builder.PropertyExtant.required:
|
|
530
766
|
write_field(prop)
|
|
531
767
|
else:
|
|
@@ -539,6 +775,12 @@ def _emit_type_properties(
|
|
|
539
775
|
)
|
|
540
776
|
|
|
541
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
|
+
|
|
542
784
|
def _emit_type(ctx: Context, stype: builder.SpecType) -> None:
|
|
543
785
|
if not isinstance(stype, builder.SpecTypeDefn):
|
|
544
786
|
return
|
|
@@ -559,7 +801,42 @@ def _emit_type(ctx: Context, stype: builder.SpecType) -> None:
|
|
|
559
801
|
return
|
|
560
802
|
|
|
561
803
|
if isinstance(stype, builder.SpecTypeDefnAlias):
|
|
562
|
-
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")
|
|
563
840
|
return
|
|
564
841
|
|
|
565
842
|
if isinstance(stype, builder.SpecTypeDefnStringEnum):
|
|
@@ -571,11 +848,11 @@ def _emit_type(ctx: Context, stype: builder.SpecType) -> None:
|
|
|
571
848
|
|
|
572
849
|
class_out = io.StringIO()
|
|
573
850
|
base_class = ""
|
|
574
|
-
|
|
851
|
+
generics = stype.get_generics()
|
|
575
852
|
if not stype.base.is_base:
|
|
576
853
|
base_class = f"({refer_to(ctx, stype.base)})"
|
|
577
|
-
elif
|
|
578
|
-
base_class = f"(typing.Generic[{
|
|
854
|
+
elif len(generics) > 0:
|
|
855
|
+
base_class = f"(typing.Generic[{', '.join(generics)}])"
|
|
579
856
|
class_out.write(f"class {stype.name}{base_class}:\n")
|
|
580
857
|
|
|
581
858
|
emitted_properties_metadata = _emit_type_properties(
|
|
@@ -586,45 +863,45 @@ def _emit_type(ctx: Context, stype: builder.SpecType) -> None:
|
|
|
586
863
|
to_string_values = emitted_properties_metadata.to_string_values
|
|
587
864
|
parse_require = emitted_properties_metadata.parse_require
|
|
588
865
|
|
|
589
|
-
|
|
866
|
+
_emit_generics(ctx, generics)
|
|
590
867
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
):
|
|
597
|
-
ctx.
|
|
598
|
-
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")
|
|
599
875
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
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")
|
|
605
881
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
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)
|
|
610
886
|
|
|
611
|
-
|
|
887
|
+
ctx.out.write(")\n")
|
|
612
888
|
|
|
613
|
-
|
|
889
|
+
# Emit dataclass decorator
|
|
890
|
+
dataclass = "@dataclasses.dataclass"
|
|
614
891
|
dc_args = []
|
|
615
892
|
if stype.is_kw_only():
|
|
616
893
|
dc_args.append("kw_only=True")
|
|
617
894
|
if stype.is_hashable:
|
|
618
895
|
dc_args.extend(["frozen=True", "eq=True"])
|
|
619
896
|
if len(dc_args) > 0:
|
|
620
|
-
dataclass += f
|
|
897
|
+
dataclass += f"({', '.join(dc_args)})"
|
|
621
898
|
|
|
622
899
|
ctx.out.write(f"{dataclass}\n")
|
|
623
900
|
ctx.out.write(class_out.getvalue())
|
|
624
901
|
|
|
625
902
|
|
|
626
|
-
def
|
|
627
|
-
|
|
903
|
+
def _emit_generics(ctx: Context, generics: list[str]) -> None:
|
|
904
|
+
for generic in generics:
|
|
628
905
|
ctx.out.write(f'{generic} = typing.TypeVar("{generic}")\n')
|
|
629
906
|
ctx.out.write(f"{LINE_BREAK}{LINE_BREAK}")
|
|
630
907
|
|
|
@@ -654,6 +931,7 @@ base_name_map = {
|
|
|
654
931
|
builder.BaseTypeName.s_opaque_key: "OpaqueKey",
|
|
655
932
|
builder.BaseTypeName.s_string: "str",
|
|
656
933
|
builder.BaseTypeName.s_tuple: "tuple",
|
|
934
|
+
builder.BaseTypeName.s_readonly_array: "tuple",
|
|
657
935
|
builder.BaseTypeName.s_union: "typing.Union",
|
|
658
936
|
builder.BaseTypeName.s_literal: "typing.Literal",
|
|
659
937
|
}
|
|
@@ -662,6 +940,11 @@ base_name_map = {
|
|
|
662
940
|
def refer_to(ctx: TrackingContext, stype: builder.SpecType) -> str:
|
|
663
941
|
if isinstance(stype, builder.SpecTypeInstance):
|
|
664
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
|
+
|
|
665
948
|
return f"{refer_to(ctx, stype.defn_type)}[{params}]"
|
|
666
949
|
|
|
667
950
|
if isinstance(stype, builder.SpecTypeLiteralWrapper):
|
|
@@ -688,23 +971,21 @@ def refer_to(ctx: TrackingContext, stype: builder.SpecType) -> str:
|
|
|
688
971
|
SpecEndpoint = builder.SpecEndpoint
|
|
689
972
|
|
|
690
973
|
|
|
691
|
-
def _route_identifier(endpoint: builder.SpecEndpoint) -> tuple[str, str, str]:
|
|
692
|
-
return (endpoint.path_dirname, endpoint.path_basename, endpoint.method)
|
|
693
|
-
|
|
694
|
-
|
|
695
974
|
def _emit_routes(*, builder: builder.SpecBuilder, config: PythonConfig) -> None:
|
|
696
975
|
for endpoint_root in builder.api_endpoints:
|
|
697
976
|
endpoints: list[SpecEndpoint] = []
|
|
698
977
|
output = config.routes_output.get(endpoint_root)
|
|
699
978
|
if output is None:
|
|
700
979
|
continue
|
|
980
|
+
last_endpoint: SpecEndpoint | None = None
|
|
701
981
|
for namespace in builder.namespaces.values():
|
|
702
982
|
endpoint = namespace.endpoint
|
|
983
|
+
last_endpoint = endpoint
|
|
703
984
|
if endpoint is None:
|
|
704
985
|
continue
|
|
705
|
-
if endpoint.
|
|
986
|
+
if endpoint_root not in endpoint.path_per_api_endpoint:
|
|
706
987
|
continue
|
|
707
|
-
if endpoint.function is None:
|
|
988
|
+
if endpoint.path_per_api_endpoint[endpoint_root].function is None:
|
|
708
989
|
continue
|
|
709
990
|
|
|
710
991
|
endpoints.append(endpoint)
|
|
@@ -717,25 +998,38 @@ def _emit_routes(*, builder: builder.SpecBuilder, config: PythonConfig) -> None:
|
|
|
717
998
|
from main.site.framework.types import StaticRouteType
|
|
718
999
|
"""
|
|
719
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
|
+
|
|
720
1010
|
sorted_endpoints = sorted(endpoints, key=_route_identifier)
|
|
721
1011
|
|
|
722
|
-
assert len(endpoints) == len(
|
|
723
|
-
|
|
724
|
-
)
|
|
1012
|
+
assert len(endpoints) == len(set(map(_route_identifier, endpoints))), (
|
|
1013
|
+
"Endpoints are not unique"
|
|
1014
|
+
)
|
|
725
1015
|
|
|
726
1016
|
path_set = set()
|
|
727
1017
|
for endpoint in sorted_endpoints:
|
|
728
|
-
|
|
729
|
-
|
|
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(".")
|
|
730
1022
|
path = ".".join(func_bits[:-1])
|
|
731
1023
|
if path in path_set:
|
|
732
1024
|
continue
|
|
733
1025
|
path_set.add(path)
|
|
734
1026
|
static_out.write(f"import {path}\n")
|
|
735
1027
|
|
|
1028
|
+
assert last_endpoint is not None
|
|
1029
|
+
|
|
736
1030
|
static_out.write(
|
|
737
1031
|
f"""
|
|
738
|
-
ROUTE_PREFIX = "/{
|
|
1032
|
+
ROUTE_PREFIX = "/{last_endpoint.path_per_api_endpoint[endpoint_root].path_root}"
|
|
739
1033
|
|
|
740
1034
|
ROUTES: list[StaticRouteType] = [
|
|
741
1035
|
"""
|
|
@@ -749,20 +1043,21 @@ ROUTES: list[StaticRouteType] = [
|
|
|
749
1043
|
|
|
750
1044
|
from main.site.framework.types import DynamicRouteType
|
|
751
1045
|
|
|
752
|
-
ROUTE_PREFIX = "/{
|
|
1046
|
+
ROUTE_PREFIX = "/{last_endpoint.path_per_api_endpoint[endpoint_root].path_root}"
|
|
753
1047
|
|
|
754
1048
|
ROUTES: list[DynamicRouteType] = [
|
|
755
1049
|
"""
|
|
756
1050
|
)
|
|
757
1051
|
|
|
758
1052
|
for endpoint in sorted_endpoints:
|
|
1053
|
+
endpoint_function_path = endpoint.path_per_api_endpoint[endpoint_root]
|
|
759
1054
|
dynamic_out.write(
|
|
760
|
-
f'{INDENT}("{
|
|
1055
|
+
f'{INDENT}("{endpoint_function_path.path_dirname}/{endpoint_function_path.path_basename}", "{endpoint_function_path.function}", ["{endpoint.method.upper()}"]),\n'
|
|
761
1056
|
)
|
|
762
1057
|
|
|
763
|
-
assert
|
|
1058
|
+
assert endpoint_function_path.function
|
|
764
1059
|
static_out.write(
|
|
765
|
-
f'{INDENT}("{
|
|
1060
|
+
f'{INDENT}("{endpoint_function_path.path_dirname}/{endpoint_function_path.path_basename}", {endpoint_function_path.function}, ["{endpoint.method.upper()}"]),\n'
|
|
766
1061
|
)
|
|
767
1062
|
|
|
768
1063
|
dynamic_out.write(f"{MODIFY_NOTICE}]\n")
|
|
@@ -777,16 +1072,22 @@ ROUTES: list[DynamicRouteType] = [
|
|
|
777
1072
|
def _emit_namespace_imports(
|
|
778
1073
|
*,
|
|
779
1074
|
out: io.StringIO,
|
|
780
|
-
namespaces:
|
|
781
|
-
from_namespace:
|
|
1075
|
+
namespaces: set[builder.SpecNamespace],
|
|
1076
|
+
from_namespace: builder.SpecNamespace | None,
|
|
782
1077
|
config: PythonConfig,
|
|
1078
|
+
skip_non_sdk: bool = False,
|
|
783
1079
|
) -> None:
|
|
784
1080
|
for ns in sorted(
|
|
785
1081
|
namespaces,
|
|
786
1082
|
key=lambda name: _resolve_namespace_name(name),
|
|
787
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
|
|
788
1090
|
resolved = _resolve_namespace_name(ns)
|
|
789
|
-
ref = _resolve_namespace_ref(ns)
|
|
790
1091
|
if ns.endpoint is not None:
|
|
791
1092
|
import_alias = "_".join(ns.path[2:]) + "_t"
|
|
792
1093
|
out.write(
|
|
@@ -798,7 +1099,7 @@ def _emit_namespace_imports(
|
|
|
798
1099
|
else:
|
|
799
1100
|
from_path = config.types_package
|
|
800
1101
|
|
|
801
|
-
out.write(f"from {from_path} import {resolved}
|
|
1102
|
+
out.write(f"from {from_path} import {resolved}\n")
|
|
802
1103
|
|
|
803
1104
|
|
|
804
1105
|
def _emit_id_source(*, builder: builder.SpecBuilder, config: PythonConfig) -> None:
|
|
@@ -810,7 +1111,7 @@ def _emit_id_source(*, builder: builder.SpecBuilder, config: PythonConfig) -> No
|
|
|
810
1111
|
return None
|
|
811
1112
|
enum_out = io.StringIO()
|
|
812
1113
|
enum_out.write(f"{LINT_HEADER}{MODIFY_NOTICE}\n")
|
|
813
|
-
enum_out.write("from typing import Literal,
|
|
1114
|
+
enum_out.write("from typing import Literal, Union\n")
|
|
814
1115
|
enum_out.write("from pkgs.strenum_compat import StrEnum\n")
|
|
815
1116
|
|
|
816
1117
|
ctx = TrackingContext()
|
|
@@ -825,13 +1126,13 @@ def _emit_id_source(*, builder: builder.SpecBuilder, config: PythonConfig) -> No
|
|
|
825
1126
|
)
|
|
826
1127
|
|
|
827
1128
|
known_keys = []
|
|
828
|
-
enum_out.write("\nENUM_MAP: dict[str,
|
|
1129
|
+
enum_out.write("\nENUM_MAP: dict[str, type[StrEnum]] = {\n")
|
|
829
1130
|
for key in sorted(named_enums.keys()):
|
|
830
1131
|
enum_out.write(f'"{key}": {named_enums[key]},\n')
|
|
831
1132
|
known_keys.append(f'Literal["{key}"]')
|
|
832
1133
|
enum_out.write(f"}}\n{MODIFY_NOTICE}\n")
|
|
833
1134
|
|
|
834
|
-
enum_out.write(f"\nKnownEnumsType = Union[
|
|
1135
|
+
enum_out.write(f"\nKnownEnumsType = Union[\n{INDENT}")
|
|
835
1136
|
enum_out.write(f",\n{INDENT}".join(known_keys))
|
|
836
1137
|
enum_out.write(f"\n]\n{MODIFY_NOTICE}\n")
|
|
837
1138
|
|
|
@@ -848,21 +1149,36 @@ def _emit_api_stubs(*, builder: builder.SpecBuilder, config: PythonConfig) -> No
|
|
|
848
1149
|
|
|
849
1150
|
if endpoint is None:
|
|
850
1151
|
continue
|
|
851
|
-
if endpoint.
|
|
1152
|
+
if endpoint_root not in endpoint.path_per_api_endpoint:
|
|
852
1153
|
continue
|
|
853
|
-
|
|
1154
|
+
|
|
1155
|
+
endpoint_function = endpoint.path_per_api_endpoint[endpoint_root].function
|
|
1156
|
+
if endpoint_function is None:
|
|
854
1157
|
continue
|
|
855
1158
|
|
|
856
|
-
module_dir, file_name, func_name =
|
|
1159
|
+
module_dir, file_name, func_name = endpoint_function.rsplit(".", 2)
|
|
857
1160
|
module_path = os.path.abspath(module_dir.replace(".", "/"))
|
|
858
1161
|
api_stub_file = f"{module_path}/{file_name}.py"
|
|
859
1162
|
if os.path.isfile(api_stub_file):
|
|
860
1163
|
continue
|
|
861
|
-
_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
|
+
)
|
|
862
1172
|
|
|
863
1173
|
|
|
864
1174
|
def _create_api_stub(
|
|
865
|
-
|
|
1175
|
+
*,
|
|
1176
|
+
api_stub_file: str,
|
|
1177
|
+
file_name: str,
|
|
1178
|
+
endpoint: SpecEndpoint,
|
|
1179
|
+
config: PythonConfig,
|
|
1180
|
+
endpoint_root: str,
|
|
1181
|
+
top_namespace: str,
|
|
866
1182
|
) -> None:
|
|
867
1183
|
assert (
|
|
868
1184
|
endpoint.method == builder.RouteMethod.post
|
|
@@ -870,7 +1186,13 @@ def _create_api_stub(
|
|
|
870
1186
|
or endpoint.method == builder.RouteMethod.delete
|
|
871
1187
|
or endpoint.method == builder.RouteMethod.patch
|
|
872
1188
|
)
|
|
873
|
-
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
|
+
)
|
|
874
1196
|
util.rewrite_file(api_stub_file, api_out.getvalue())
|
|
875
1197
|
|
|
876
1198
|
|
|
@@ -879,15 +1201,22 @@ WRAP_ARGS_END = "\n"
|
|
|
879
1201
|
|
|
880
1202
|
|
|
881
1203
|
def _create_api_function(
|
|
882
|
-
|
|
1204
|
+
*,
|
|
1205
|
+
file_name: str,
|
|
1206
|
+
endpoint: SpecEndpoint,
|
|
1207
|
+
config: PythonConfig,
|
|
1208
|
+
endpoint_root: str,
|
|
1209
|
+
top_namespace: str,
|
|
883
1210
|
) -> io.StringIO:
|
|
1211
|
+
endpoint_specific_path = endpoint.path_per_api_endpoint[endpoint_root]
|
|
1212
|
+
assert endpoint_specific_path is not None
|
|
884
1213
|
api_out = io.StringIO()
|
|
885
1214
|
python_api_type_root = f"{config.types_package}.api"
|
|
886
|
-
dot_dirname =
|
|
1215
|
+
dot_dirname = endpoint_specific_path.path_dirname.replace("/", ".")
|
|
887
1216
|
api_import = (
|
|
888
1217
|
f"{python_api_type_root}.{dot_dirname}.{file_name}"
|
|
889
1218
|
if dot_dirname != ""
|
|
890
|
-
else f"{python_api_type_root}.{
|
|
1219
|
+
else f"{python_api_type_root}.{endpoint_specific_path.path_basename}"
|
|
891
1220
|
)
|
|
892
1221
|
|
|
893
1222
|
if endpoint.method == builder.RouteMethod.post:
|
|
@@ -899,7 +1228,20 @@ def _create_api_function(
|
|
|
899
1228
|
elif endpoint.method == builder.RouteMethod.patch:
|
|
900
1229
|
validated_method = "validated_patch"
|
|
901
1230
|
|
|
902
|
-
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
|
+
)
|
|
903
1245
|
|
|
904
1246
|
api_out.write(
|
|
905
1247
|
f"""import {api_import} as api
|
|
@@ -907,8 +1249,8 @@ from main.db.session import Session, SessionMaker
|
|
|
907
1249
|
from main.site.decorators import APIError, APIResponse, {validated_method}
|
|
908
1250
|
|
|
909
1251
|
|
|
910
|
-
@{validated_method}(api.
|
|
911
|
-
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]:
|
|
912
1254
|
with Session(client_sm) as session:
|
|
913
1255
|
# return APIResponse(data=api.Data())
|
|
914
1256
|
pass
|
|
@@ -930,7 +1272,7 @@ def _emit_api_argument_lookup(
|
|
|
930
1272
|
for endpoint_root in builder.api_endpoints:
|
|
931
1273
|
routes_output = config.routes_output[endpoint_root]
|
|
932
1274
|
|
|
933
|
-
imports = []
|
|
1275
|
+
imports = ["import typing", "import dataclasses"]
|
|
934
1276
|
mappings = []
|
|
935
1277
|
for namespace in sorted(
|
|
936
1278
|
builder.namespaces.values(),
|
|
@@ -940,17 +1282,38 @@ def _emit_api_argument_lookup(
|
|
|
940
1282
|
|
|
941
1283
|
if endpoint is None:
|
|
942
1284
|
continue
|
|
943
|
-
if endpoint.
|
|
1285
|
+
if endpoint_root not in endpoint.path_per_api_endpoint:
|
|
944
1286
|
continue
|
|
945
|
-
if endpoint.function is None:
|
|
1287
|
+
if endpoint.path_per_api_endpoint[endpoint_root].function is None:
|
|
946
1288
|
continue
|
|
947
|
-
if "Arguments" not in namespace.types:
|
|
1289
|
+
if "Arguments" not in namespace.types or "Data" not in namespace.types:
|
|
948
1290
|
continue
|
|
949
1291
|
|
|
950
1292
|
import_alias = "_".join(namespace.path[1:])
|
|
951
1293
|
api_import = f"{config.types_package}.{'.'.join(namespace.path)}"
|
|
952
1294
|
imports.append(f"import {api_import} as {import_alias}")
|
|
953
|
-
|
|
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)
|
|
954
1317
|
|
|
955
1318
|
argument_lookup_out = io.StringIO()
|
|
956
1319
|
argument_lookup_out.write(MODIFY_NOTICE)
|
|
@@ -958,8 +1321,29 @@ def _emit_api_argument_lookup(
|
|
|
958
1321
|
argument_lookup_out.write(
|
|
959
1322
|
f"""{LINE_BREAK.join(imports)}
|
|
960
1323
|
|
|
961
|
-
|
|
962
|
-
|
|
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)},
|
|
963
1347
|
}}
|
|
964
1348
|
|
|
965
1349
|
__all__ = ["{API_ARGUMENTS_NAME}"]
|
|
@@ -975,10 +1359,69 @@ __all__ = ["{API_ARGUMENTS_NAME}"]
|
|
|
975
1359
|
CLIENT_CLASS_FILENAME = "client_base"
|
|
976
1360
|
CLIENT_CLASS_IMPORTS = [
|
|
977
1361
|
"from abc import ABC, abstractmethod",
|
|
978
|
-
"
|
|
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",
|
|
979
1369
|
]
|
|
980
1370
|
|
|
981
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
|
+
|
|
982
1425
|
def _emit_client_class(
|
|
983
1426
|
*, spec_builder: builder.SpecBuilder, config: PythonConfig
|
|
984
1427
|
) -> None:
|
|
@@ -986,7 +1429,11 @@ def _emit_client_class(
|
|
|
986
1429
|
return
|
|
987
1430
|
|
|
988
1431
|
client_base_out = io.StringIO()
|
|
989
|
-
ctx = Context(
|
|
1432
|
+
ctx = Context(
|
|
1433
|
+
out=io.StringIO(),
|
|
1434
|
+
builder=spec_builder,
|
|
1435
|
+
namespace=builder.SpecNamespace("client_base"),
|
|
1436
|
+
)
|
|
990
1437
|
for namespace in sorted(
|
|
991
1438
|
spec_builder.namespaces.values(),
|
|
992
1439
|
key=lambda ns: _resolve_namespace_name(ns),
|
|
@@ -1001,8 +1448,9 @@ def _emit_client_class(
|
|
|
1001
1448
|
_emit_namespace_imports(
|
|
1002
1449
|
out=client_base_out,
|
|
1003
1450
|
namespaces=ctx.namespaces,
|
|
1004
|
-
from_namespace=
|
|
1451
|
+
from_namespace=None,
|
|
1005
1452
|
config=config,
|
|
1453
|
+
skip_non_sdk=True,
|
|
1006
1454
|
)
|
|
1007
1455
|
|
|
1008
1456
|
client_base_out.write(
|
|
@@ -1011,7 +1459,7 @@ def _emit_client_class(
|
|
|
1011
1459
|
DT = typing.TypeVar("DT")
|
|
1012
1460
|
|
|
1013
1461
|
|
|
1014
|
-
@dataclass(kw_only=True)
|
|
1462
|
+
@dataclasses.dataclass(kw_only=True)
|
|
1015
1463
|
class APIRequest:
|
|
1016
1464
|
method: str
|
|
1017
1465
|
endpoint: str
|
|
@@ -1022,10 +1470,10 @@ class ClientMethods(ABC):
|
|
|
1022
1470
|
|
|
1023
1471
|
@abstractmethod
|
|
1024
1472
|
def do_request(self, *, api_request: APIRequest, return_type: type[DT]) -> DT:
|
|
1025
|
-
...
|
|
1026
|
-
"""
|
|
1473
|
+
..."""
|
|
1027
1474
|
)
|
|
1028
1475
|
client_base_out.write(ctx.out.getvalue())
|
|
1476
|
+
client_base_out.write("\n")
|
|
1029
1477
|
|
|
1030
1478
|
util.rewrite_file(
|
|
1031
1479
|
f"{config.types_output}/{CLIENT_CLASS_FILENAME}.py", client_base_out.getvalue()
|