UncountablePythonSDK 0.0.83__py3-none-any.whl → 0.0.132__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of UncountablePythonSDK might be problematic. Click here for more details.
- docs/conf.py +54 -7
- docs/index.md +107 -4
- docs/integration_examples/create_ingredient.md +43 -0
- docs/integration_examples/create_output.md +56 -0
- docs/integration_examples/index.md +6 -0
- docs/justfile +2 -2
- docs/requirements.txt +6 -4
- examples/basic_auth.py +7 -0
- examples/create_ingredient_sdk.py +34 -0
- examples/download_files.py +26 -0
- examples/integration-server/jobs/materials_auto/concurrent_cron.py +11 -0
- examples/integration-server/jobs/materials_auto/example_cron.py +3 -0
- examples/integration-server/jobs/materials_auto/example_http.py +47 -0
- examples/integration-server/jobs/materials_auto/example_instrument.py +100 -0
- examples/integration-server/jobs/materials_auto/example_parse.py +140 -0
- examples/integration-server/jobs/materials_auto/example_predictions.py +61 -0
- examples/integration-server/jobs/materials_auto/example_runsheet_wh.py +39 -0
- examples/integration-server/jobs/materials_auto/example_wh.py +17 -9
- examples/integration-server/jobs/materials_auto/profile.yaml +61 -0
- examples/integration-server/pyproject.toml +10 -10
- examples/oauth.py +7 -0
- examples/set_recipe_metadata_file.py +1 -1
- examples/upload_files.py +1 -2
- pkgs/argument_parser/__init__.py +8 -0
- pkgs/argument_parser/_is_namedtuple.py +3 -0
- pkgs/argument_parser/argument_parser.py +196 -63
- pkgs/filesystem_utils/__init__.py +1 -0
- pkgs/filesystem_utils/_blob_session.py +144 -0
- pkgs/filesystem_utils/_gdrive_session.py +5 -5
- pkgs/filesystem_utils/_s3_session.py +2 -1
- pkgs/filesystem_utils/_sftp_session.py +6 -3
- pkgs/filesystem_utils/file_type_utils.py +30 -10
- pkgs/serialization/__init__.py +7 -2
- pkgs/serialization/annotation.py +64 -0
- pkgs/serialization/missing_sentry.py +1 -1
- pkgs/serialization/opaque_key.py +1 -1
- pkgs/serialization/serial_alias.py +47 -0
- pkgs/serialization/serial_class.py +40 -48
- pkgs/serialization/serial_generic.py +16 -0
- pkgs/serialization/serial_union.py +16 -16
- pkgs/serialization_util/__init__.py +6 -0
- pkgs/serialization_util/dataclasses.py +14 -0
- pkgs/serialization_util/serialization_helpers.py +15 -5
- pkgs/type_spec/actions_registry/__main__.py +0 -4
- pkgs/type_spec/actions_registry/emit_typescript.py +2 -4
- pkgs/type_spec/builder.py +248 -70
- pkgs/type_spec/builder_types.py +9 -0
- pkgs/type_spec/config.py +40 -7
- pkgs/type_spec/cross_output_links.py +99 -0
- pkgs/type_spec/emit_open_api.py +121 -34
- pkgs/type_spec/emit_open_api_util.py +5 -5
- pkgs/type_spec/emit_python.py +277 -86
- pkgs/type_spec/emit_typescript.py +102 -29
- pkgs/type_spec/emit_typescript_util.py +66 -10
- pkgs/type_spec/load_types.py +16 -3
- pkgs/type_spec/non_discriminated_union_exceptions.py +14 -0
- pkgs/type_spec/open_api_util.py +29 -4
- pkgs/type_spec/parts/base.py.prepart +11 -8
- pkgs/type_spec/parts/base.ts.prepart +4 -0
- pkgs/type_spec/type_info/__main__.py +3 -1
- pkgs/type_spec/type_info/emit_type_info.py +115 -22
- pkgs/type_spec/ui_entry_actions/__init__.py +4 -0
- pkgs/type_spec/ui_entry_actions/generate_ui_entry_actions.py +308 -0
- pkgs/type_spec/util.py +3 -3
- pkgs/type_spec/value_spec/__main__.py +26 -9
- pkgs/type_spec/value_spec/convert_type.py +18 -0
- pkgs/type_spec/value_spec/emit_python.py +13 -3
- pkgs/type_spec/value_spec/types.py +1 -1
- uncountable/core/async_batch.py +1 -1
- uncountable/core/client.py +133 -34
- uncountable/core/environment.py +3 -3
- uncountable/core/file_upload.py +39 -15
- uncountable/integration/cli.py +116 -23
- uncountable/integration/construct_client.py +3 -3
- uncountable/integration/executors/executors.py +12 -2
- uncountable/integration/executors/generic_upload_executor.py +66 -14
- uncountable/integration/http_server/__init__.py +5 -0
- uncountable/integration/http_server/types.py +69 -0
- uncountable/integration/job.py +192 -7
- uncountable/integration/queue_runner/command_server/__init__.py +4 -0
- uncountable/integration/queue_runner/command_server/command_client.py +65 -0
- uncountable/integration/queue_runner/command_server/command_server.py +83 -5
- uncountable/integration/queue_runner/command_server/constants.py +4 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server.proto +36 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.py +28 -11
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.pyi +77 -1
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2_grpc.py +135 -0
- uncountable/integration/queue_runner/command_server/types.py +25 -2
- uncountable/integration/queue_runner/datastore/datastore_sqlite.py +168 -11
- uncountable/integration/queue_runner/datastore/interface.py +10 -0
- uncountable/integration/queue_runner/datastore/model.py +8 -1
- uncountable/integration/queue_runner/job_scheduler.py +63 -23
- uncountable/integration/queue_runner/queue_runner.py +10 -2
- uncountable/integration/queue_runner/worker.py +3 -5
- uncountable/integration/scan_profiles.py +1 -1
- uncountable/integration/scheduler.py +74 -25
- uncountable/integration/secret_retrieval/retrieve_secret.py +1 -1
- uncountable/integration/server.py +42 -12
- uncountable/integration/telemetry.py +63 -10
- uncountable/integration/webhook_server/entrypoint.py +39 -112
- uncountable/types/__init__.py +58 -1
- uncountable/types/api/batch/execute_batch.py +5 -6
- uncountable/types/api/batch/execute_batch_load_async.py +2 -3
- uncountable/types/api/chemical/convert_chemical_formats.py +10 -5
- uncountable/types/api/condition_parameters/__init__.py +1 -0
- uncountable/types/api/condition_parameters/upsert_condition_match.py +72 -0
- uncountable/types/api/entity/create_entities.py +7 -7
- uncountable/types/api/entity/create_entity.py +8 -8
- uncountable/types/api/entity/create_or_update_entity.py +48 -0
- uncountable/types/api/entity/export_entities.py +59 -0
- uncountable/types/api/entity/get_entities_data.py +3 -4
- uncountable/types/api/entity/grant_entity_permissions.py +6 -6
- uncountable/types/api/entity/list_aggregate.py +79 -0
- uncountable/types/api/entity/list_entities.py +34 -10
- uncountable/types/api/entity/lock_entity.py +4 -4
- uncountable/types/api/entity/lookup_entity.py +116 -0
- uncountable/types/api/entity/resolve_entity_ids.py +5 -6
- uncountable/types/api/entity/set_entity_field_values.py +44 -0
- uncountable/types/api/entity/set_values.py +3 -3
- uncountable/types/api/entity/transition_entity_phase.py +14 -7
- uncountable/types/api/entity/unlock_entity.py +3 -3
- uncountable/types/api/equipment/associate_equipment_input.py +2 -3
- uncountable/types/api/field_options/upsert_field_options.py +7 -7
- uncountable/types/api/files/__init__.py +1 -0
- uncountable/types/api/files/download_file.py +77 -0
- uncountable/types/api/id_source/list_id_source.py +6 -7
- uncountable/types/api/id_source/match_id_source.py +4 -5
- uncountable/types/api/input_groups/get_input_group_names.py +3 -4
- uncountable/types/api/inputs/create_inputs.py +10 -9
- uncountable/types/api/inputs/get_input_data.py +11 -12
- uncountable/types/api/inputs/get_input_names.py +6 -7
- uncountable/types/api/inputs/get_inputs_data.py +6 -7
- uncountable/types/api/inputs/set_input_attribute_values.py +5 -6
- uncountable/types/api/inputs/set_input_category.py +5 -5
- uncountable/types/api/inputs/set_input_subcategories.py +3 -3
- uncountable/types/api/inputs/set_intermediate_type.py +4 -4
- uncountable/types/api/integrations/__init__.py +1 -0
- uncountable/types/api/integrations/publish_realtime_data.py +41 -0
- uncountable/types/api/integrations/push_notification.py +49 -0
- uncountable/types/api/integrations/register_sockets_token.py +41 -0
- uncountable/types/api/listing/__init__.py +1 -0
- uncountable/types/api/listing/fetch_listing.py +58 -0
- uncountable/types/api/material_families/update_entity_material_families.py +3 -4
- uncountable/types/api/notebooks/__init__.py +1 -0
- uncountable/types/api/notebooks/add_notebook_content.py +119 -0
- uncountable/types/api/outputs/get_output_data.py +12 -13
- uncountable/types/api/outputs/get_output_names.py +5 -6
- uncountable/types/api/outputs/get_output_organization.py +173 -0
- uncountable/types/api/outputs/resolve_output_conditions.py +7 -8
- uncountable/types/api/permissions/set_core_permissions.py +16 -10
- uncountable/types/api/project/get_projects.py +6 -7
- uncountable/types/api/project/get_projects_data.py +7 -8
- uncountable/types/api/recipe_links/create_recipe_link.py +5 -5
- uncountable/types/api/recipe_links/remove_recipe_link.py +4 -4
- uncountable/types/api/recipe_metadata/get_recipe_metadata_data.py +6 -7
- uncountable/types/api/recipes/add_recipe_to_project.py +3 -3
- uncountable/types/api/recipes/add_time_series_data.py +64 -0
- uncountable/types/api/recipes/archive_recipes.py +4 -4
- uncountable/types/api/recipes/associate_recipe_as_input.py +5 -5
- uncountable/types/api/recipes/associate_recipe_as_lot.py +3 -3
- uncountable/types/api/recipes/clear_recipe_outputs.py +3 -3
- uncountable/types/api/recipes/create_mix_order.py +44 -0
- uncountable/types/api/recipes/create_recipe.py +8 -9
- uncountable/types/api/recipes/create_recipes.py +8 -9
- uncountable/types/api/recipes/disassociate_recipe_as_input.py +3 -3
- uncountable/types/api/recipes/edit_recipe_inputs.py +101 -24
- uncountable/types/api/recipes/get_column_calculation_values.py +4 -5
- uncountable/types/api/recipes/get_curve.py +4 -5
- uncountable/types/api/recipes/get_recipe_calculations.py +6 -7
- uncountable/types/api/recipes/get_recipe_links.py +3 -4
- uncountable/types/api/recipes/get_recipe_names.py +3 -4
- uncountable/types/api/recipes/get_recipe_output_metadata.py +5 -6
- uncountable/types/api/recipes/get_recipes_data.py +62 -34
- uncountable/types/api/recipes/lock_recipes.py +9 -8
- uncountable/types/api/recipes/remove_recipe_from_project.py +3 -3
- uncountable/types/api/recipes/set_recipe_inputs.py +9 -10
- uncountable/types/api/recipes/set_recipe_metadata.py +3 -3
- uncountable/types/api/recipes/set_recipe_output_annotations.py +11 -12
- uncountable/types/api/recipes/set_recipe_output_file.py +5 -6
- uncountable/types/api/recipes/set_recipe_outputs.py +24 -13
- uncountable/types/api/recipes/set_recipe_tags.py +14 -9
- uncountable/types/api/recipes/set_recipe_total.py +59 -0
- uncountable/types/api/recipes/unarchive_recipes.py +3 -3
- uncountable/types/api/recipes/unlock_recipes.py +7 -6
- uncountable/types/api/runsheet/__init__.py +1 -0
- uncountable/types/api/runsheet/complete_async_upload.py +41 -0
- uncountable/types/api/triggers/run_trigger.py +4 -4
- uncountable/types/api/uploader/complete_async_parse.py +46 -0
- uncountable/types/api/uploader/invoke_uploader.py +4 -5
- uncountable/types/api/user/__init__.py +1 -0
- uncountable/types/api/user/get_current_user_info.py +40 -0
- uncountable/types/async_batch.py +1 -1
- uncountable/types/async_batch_processor.py +506 -23
- uncountable/types/async_batch_t.py +35 -8
- uncountable/types/async_jobs.py +0 -1
- uncountable/types/async_jobs_t.py +1 -2
- uncountable/types/auth_retrieval.py +0 -1
- uncountable/types/auth_retrieval_t.py +6 -6
- uncountable/types/base.py +0 -1
- uncountable/types/base_t.py +11 -9
- uncountable/types/calculations.py +0 -1
- uncountable/types/calculations_t.py +1 -2
- uncountable/types/chemical_structure.py +0 -1
- uncountable/types/chemical_structure_t.py +5 -5
- uncountable/types/client_base.py +614 -69
- uncountable/types/client_config.py +1 -1
- uncountable/types/client_config_t.py +13 -3
- uncountable/types/curves.py +0 -1
- uncountable/types/curves_t.py +6 -7
- uncountable/types/data.py +12 -0
- uncountable/types/data_t.py +103 -0
- uncountable/types/entity.py +1 -1
- uncountable/types/entity_t.py +90 -10
- uncountable/types/experiment_groups.py +0 -1
- uncountable/types/experiment_groups_t.py +1 -2
- uncountable/types/exports.py +8 -0
- uncountable/types/exports_t.py +34 -0
- uncountable/types/field_values.py +19 -1
- uncountable/types/field_values_t.py +242 -9
- uncountable/types/fields.py +0 -1
- uncountable/types/fields_t.py +1 -2
- uncountable/types/generic_upload.py +0 -1
- uncountable/types/generic_upload_t.py +14 -14
- uncountable/types/id_source.py +0 -1
- uncountable/types/id_source_t.py +13 -7
- uncountable/types/identifier.py +0 -1
- uncountable/types/identifier_t.py +10 -5
- uncountable/types/input_attributes.py +0 -1
- uncountable/types/input_attributes_t.py +3 -4
- uncountable/types/inputs.py +0 -1
- uncountable/types/inputs_t.py +3 -4
- uncountable/types/integration_server.py +0 -1
- uncountable/types/integration_server_t.py +13 -4
- uncountable/types/integration_session.py +10 -0
- uncountable/types/integration_session_t.py +60 -0
- uncountable/types/integrations.py +10 -0
- uncountable/types/integrations_t.py +62 -0
- uncountable/types/job_definition.py +2 -1
- uncountable/types/job_definition_t.py +57 -32
- uncountable/types/listing.py +9 -0
- uncountable/types/listing_t.py +51 -0
- uncountable/types/notices.py +8 -0
- uncountable/types/notices_t.py +37 -0
- uncountable/types/notifications.py +11 -0
- uncountable/types/notifications_t.py +74 -0
- uncountable/types/outputs.py +0 -1
- uncountable/types/outputs_t.py +2 -3
- uncountable/types/overrides.py +0 -1
- uncountable/types/overrides_t.py +10 -4
- uncountable/types/permissions.py +0 -1
- uncountable/types/permissions_t.py +1 -2
- uncountable/types/phases.py +0 -1
- uncountable/types/phases_t.py +1 -2
- uncountable/types/post_base.py +0 -1
- uncountable/types/post_base_t.py +1 -2
- uncountable/types/queued_job.py +2 -1
- uncountable/types/queued_job_t.py +29 -12
- uncountable/types/recipe_identifiers.py +0 -1
- uncountable/types/recipe_identifiers_t.py +18 -8
- uncountable/types/recipe_inputs.py +0 -1
- uncountable/types/recipe_inputs_t.py +1 -2
- uncountable/types/recipe_links.py +0 -1
- uncountable/types/recipe_links_t.py +3 -4
- uncountable/types/recipe_metadata.py +0 -1
- uncountable/types/recipe_metadata_t.py +9 -10
- uncountable/types/recipe_output_metadata.py +0 -1
- uncountable/types/recipe_output_metadata_t.py +1 -2
- uncountable/types/recipe_tags.py +0 -1
- uncountable/types/recipe_tags_t.py +1 -2
- uncountable/types/recipe_workflow_steps.py +0 -1
- uncountable/types/recipe_workflow_steps_t.py +7 -7
- uncountable/types/recipes.py +0 -1
- uncountable/types/recipes_t.py +2 -2
- uncountable/types/response.py +0 -1
- uncountable/types/response_t.py +2 -2
- uncountable/types/secret_retrieval.py +0 -1
- uncountable/types/secret_retrieval_t.py +7 -7
- uncountable/types/sockets.py +20 -0
- uncountable/types/sockets_t.py +169 -0
- uncountable/types/structured_filters.py +25 -0
- uncountable/types/structured_filters_t.py +248 -0
- uncountable/types/units.py +0 -1
- uncountable/types/units_t.py +1 -2
- uncountable/types/uploader.py +24 -0
- uncountable/types/uploader_t.py +222 -0
- uncountable/types/users.py +0 -1
- uncountable/types/users_t.py +1 -2
- uncountable/types/webhook_job.py +1 -1
- uncountable/types/webhook_job_t.py +14 -3
- uncountable/types/workflows.py +0 -1
- uncountable/types/workflows_t.py +3 -4
- uncountablepythonsdk-0.0.132.dist-info/METADATA +64 -0
- uncountablepythonsdk-0.0.132.dist-info/RECORD +363 -0
- {UncountablePythonSDK-0.0.83.dist-info → uncountablepythonsdk-0.0.132.dist-info}/WHEEL +1 -1
- UncountablePythonSDK-0.0.83.dist-info/METADATA +0 -60
- UncountablePythonSDK-0.0.83.dist-info/RECORD +0 -292
- docs/quickstart.md +0 -19
- {UncountablePythonSDK-0.0.83.dist-info → uncountablepythonsdk-0.0.132.dist-info}/top_level.txt +0 -0
pkgs/type_spec/emit_python.py
CHANGED
|
@@ -2,18 +2,19 @@ import dataclasses
|
|
|
2
2
|
import io
|
|
3
3
|
import os
|
|
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, EndpointSpecificPath, base_namespace_name
|
|
8
9
|
from .config import PythonConfig
|
|
10
|
+
from .cross_output_links import get_path_links
|
|
11
|
+
from .emit_open_api_util import EmitOpenAPIStabilityLevel
|
|
9
12
|
|
|
10
13
|
INDENT = " "
|
|
11
14
|
LINE_BREAK = "\n"
|
|
12
15
|
MODIFY_NOTICE = "# DO NOT MODIFY -- This file is generated by type_spec\n"
|
|
13
16
|
# Turn excess line length warning and turn off ruff formatting
|
|
14
|
-
LINT_HEADER =
|
|
15
|
-
"# flake8: noqa: F821\n# ruff: noqa: E402 Q003\n# fmt: off\n# isort: skip_file\n"
|
|
16
|
-
)
|
|
17
|
+
LINT_HEADER = "# ruff: noqa: E402 Q003\n# fmt: off\n# isort: skip_file\n"
|
|
17
18
|
LINT_FOOTER = "# fmt: on\n"
|
|
18
19
|
ROUTE_NOTICE = """# Routes are generated from $endpoint specifications in the
|
|
19
20
|
# type_spec API YAML files. Refer to the section on endpoints in the type_spec/README"""
|
|
@@ -34,10 +35,15 @@ QUEUED_BATCH_REQUEST_STYPE = builder.SpecTypeDefnObject(
|
|
|
34
35
|
namespace=ASYNC_BATCH_TYPE_NAMESPACE, name="QueuedAsyncBatchRequest"
|
|
35
36
|
)
|
|
36
37
|
|
|
38
|
+
CLIENT_CONFIG_TYPE_NAMESPACE = builder.SpecNamespace(name="client_config")
|
|
39
|
+
REQUEST_OPTIONS_STYPE = builder.SpecTypeDefnObject(
|
|
40
|
+
namespace=CLIENT_CONFIG_TYPE_NAMESPACE, name="RequestOptions"
|
|
41
|
+
)
|
|
42
|
+
|
|
37
43
|
|
|
38
44
|
@dataclasses.dataclass(kw_only=True)
|
|
39
45
|
class TrackingContext:
|
|
40
|
-
namespace:
|
|
46
|
+
namespace: builder.SpecNamespace | None = None
|
|
41
47
|
namespaces: set[builder.SpecNamespace] = dataclasses.field(default_factory=set)
|
|
42
48
|
names: set[str] = dataclasses.field(default_factory=set)
|
|
43
49
|
|
|
@@ -45,6 +51,7 @@ class TrackingContext:
|
|
|
45
51
|
use_serial_string_enum: bool = False
|
|
46
52
|
use_dataclass: bool = False
|
|
47
53
|
use_serial_union: bool = False
|
|
54
|
+
use_serial_alias: bool = False
|
|
48
55
|
use_missing: bool = False
|
|
49
56
|
use_opaque_key: bool = False
|
|
50
57
|
|
|
@@ -115,26 +122,36 @@ def _check_type_match(stype: builder.SpecType, value: Any) -> bool:
|
|
|
115
122
|
raise Exception("invalid type", stype, value)
|
|
116
123
|
|
|
117
124
|
|
|
118
|
-
def _emit_value(
|
|
125
|
+
def _emit_value(
|
|
126
|
+
ctx: TrackingContext, stype: builder.SpecType, value: Any, indent: int = 0
|
|
127
|
+
) -> str:
|
|
119
128
|
literal = builder.unwrap_literal_type(stype)
|
|
120
129
|
if literal is not None:
|
|
121
130
|
return _emit_value(ctx, literal.value_type, literal.value)
|
|
122
131
|
|
|
123
132
|
if stype.is_base_type(builder.BaseTypeName.s_string):
|
|
124
|
-
assert isinstance(value, str)
|
|
133
|
+
assert isinstance(value, str), (
|
|
134
|
+
f"Expected str value for {stype.name} but got {value}"
|
|
135
|
+
)
|
|
125
136
|
return util.encode_common_string(value)
|
|
126
137
|
elif stype.is_base_type(builder.BaseTypeName.s_integer):
|
|
127
|
-
assert isinstance(value, int)
|
|
138
|
+
assert isinstance(value, int), (
|
|
139
|
+
f"Expected int value for {stype.name} but got {value}"
|
|
140
|
+
)
|
|
128
141
|
return str(value)
|
|
129
142
|
elif stype.is_base_type(builder.BaseTypeName.s_boolean):
|
|
130
|
-
assert isinstance(value, bool)
|
|
143
|
+
assert isinstance(value, bool), (
|
|
144
|
+
f"Expected bool value for {stype.name} but got {value}"
|
|
145
|
+
)
|
|
131
146
|
return "True" if value else "False"
|
|
132
147
|
elif stype.is_base_type(builder.BaseTypeName.s_decimal) or stype.is_base_type(
|
|
133
148
|
builder.BaseTypeName.s_lossy_decimal
|
|
134
149
|
):
|
|
135
150
|
# Note that decimal requires the `!decimal 123.12` style notation in the YAML
|
|
136
151
|
# file since PyYaml parses numbers as float, unfortuantely
|
|
137
|
-
assert isinstance(value, (Decimal, int))
|
|
152
|
+
assert isinstance(value, (Decimal, int)), (
|
|
153
|
+
f"Expected decimal value for {stype.name} but got {value} (type: {type(value)})"
|
|
154
|
+
)
|
|
138
155
|
if isinstance(value, int):
|
|
139
156
|
# skip quotes for integers
|
|
140
157
|
return f"Decimal({value})"
|
|
@@ -149,14 +166,14 @@ def _emit_value(ctx: TrackingContext, stype: builder.SpecType, value: Any) -> st
|
|
|
149
166
|
key_type = stype.parameters[0]
|
|
150
167
|
value_type = stype.parameters[1]
|
|
151
168
|
return (
|
|
152
|
-
"{\n
|
|
153
|
-
+ ",\n
|
|
169
|
+
f"{{\n{INDENT * (indent + 1)}"
|
|
170
|
+
+ f",\n{INDENT * (indent + 1)}".join(
|
|
154
171
|
_emit_value(ctx, key_type, dkey)
|
|
155
172
|
+ ": "
|
|
156
|
-
+ _emit_value(ctx, value_type, dvalue)
|
|
173
|
+
+ _emit_value(ctx, value_type, dvalue, indent=indent + 1)
|
|
157
174
|
for dkey, dvalue in value.items()
|
|
158
175
|
)
|
|
159
|
-
+ "\n}"
|
|
176
|
+
+ f"\n{INDENT * indent}}}"
|
|
160
177
|
)
|
|
161
178
|
|
|
162
179
|
if stype.defn_type.is_base_type(builder.BaseTypeName.s_optional):
|
|
@@ -182,6 +199,34 @@ def _emit_value(ctx: TrackingContext, stype: builder.SpecType, value: Any) -> st
|
|
|
182
199
|
return f"{refer_to(ctx, stype)}.{_resolve_enum_name(value, stype.name_case)}"
|
|
183
200
|
elif isinstance(stype, builder.SpecTypeDefnAlias):
|
|
184
201
|
return _emit_value(ctx, stype.alias, value)
|
|
202
|
+
elif isinstance(stype, builder.SpecTypeDefnObject):
|
|
203
|
+
assert isinstance(value, dict), (
|
|
204
|
+
f"Expected dict value for {stype.name} but got {value}"
|
|
205
|
+
)
|
|
206
|
+
if not stype.is_hashable:
|
|
207
|
+
raise Exception("invalid constant object type, non-hashable", value, stype)
|
|
208
|
+
obj_out = f"{refer_to(ctx, stype)}("
|
|
209
|
+
emitted_fields: set[str] = set()
|
|
210
|
+
for prop_name, prop in (stype.properties or {}).items():
|
|
211
|
+
if prop_name not in value:
|
|
212
|
+
continue
|
|
213
|
+
else:
|
|
214
|
+
value_to_emit = value[prop_name]
|
|
215
|
+
emitted_fields.add(prop_name)
|
|
216
|
+
py_name = python_field_name(prop.name, prop.name_case)
|
|
217
|
+
obj_out += f"\n{INDENT * (indent + 1)}{py_name}={_emit_value(ctx, prop.spec_type, value_to_emit, indent=indent + 1)},"
|
|
218
|
+
whitespace = f"\n{INDENT * indent}" if len(emitted_fields) > 0 else ""
|
|
219
|
+
obj_out += f"{whitespace})"
|
|
220
|
+
|
|
221
|
+
if emitted_fields != set(value.keys()):
|
|
222
|
+
raise Exception(
|
|
223
|
+
"invalid object type, extra fields found:",
|
|
224
|
+
value,
|
|
225
|
+
stype,
|
|
226
|
+
set(value.keys()) - emitted_fields,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
return obj_out
|
|
185
230
|
|
|
186
231
|
raise Exception("invalid constant type", value, stype)
|
|
187
232
|
|
|
@@ -219,12 +264,14 @@ def _emit_types_imports(*, out: io.StringIO, ctx: Context) -> None:
|
|
|
219
264
|
out.write("import datetime # noqa: F401\n")
|
|
220
265
|
out.write("from decimal import Decimal # noqa: F401\n")
|
|
221
266
|
if ctx.use_enum:
|
|
222
|
-
out.write("from
|
|
267
|
+
out.write("from enum import StrEnum\n")
|
|
223
268
|
if ctx.use_dataclass:
|
|
224
269
|
out.write("import dataclasses\n")
|
|
225
270
|
out.write("from pkgs.serialization import serial_class\n")
|
|
226
271
|
if ctx.use_serial_union:
|
|
227
272
|
out.write("from pkgs.serialization import serial_union_annotation\n")
|
|
273
|
+
if ctx.use_serial_alias:
|
|
274
|
+
out.write("from pkgs.serialization import serial_alias_annotation\n")
|
|
228
275
|
if ctx.use_serial_string_enum:
|
|
229
276
|
out.write("from pkgs.serialization import serial_string_enum\n")
|
|
230
277
|
if ctx.use_missing:
|
|
@@ -249,7 +296,7 @@ def _emit_types(*, builder: builder.SpecBuilder, config: PythonConfig) -> None:
|
|
|
249
296
|
):
|
|
250
297
|
if (
|
|
251
298
|
namespace.endpoint is not None
|
|
252
|
-
and
|
|
299
|
+
and namespace.endpoint.is_sdk == EndpointEmitType.EMIT_NOTHING
|
|
253
300
|
and config.sdk_endpoints_only is True
|
|
254
301
|
):
|
|
255
302
|
continue
|
|
@@ -340,16 +387,44 @@ def _emit_types(*, builder: builder.SpecBuilder, config: PythonConfig) -> None:
|
|
|
340
387
|
|
|
341
388
|
ENDPOINT_METHOD = "ENDPOINT_METHOD"
|
|
342
389
|
ENDPOINT_PATH = "ENDPOINT_PATH"
|
|
390
|
+
# will be removed in Q1 2025 when ENDPOINT_PATH is made api_endpoint-agnostic
|
|
391
|
+
# is used when the API call has multiple endpoints for the one endpoint that isn't equal to the top_namespace
|
|
392
|
+
ENDPOINT_PATH_ALTERNATE = "ENDPOINT_PATH_ALTERNATE"
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def _get_epf_root(endpoint_specific_path: EndpointSpecificPath) -> str:
|
|
396
|
+
return endpoint_specific_path.root
|
|
343
397
|
|
|
344
398
|
|
|
345
399
|
def _emit_namespace(ctx: Context, namespace: builder.SpecNamespace) -> None:
|
|
346
400
|
endpoint = namespace.endpoint
|
|
347
401
|
if endpoint is not None:
|
|
402
|
+
path_links = get_path_links(
|
|
403
|
+
ctx.builder.cross_output_paths,
|
|
404
|
+
namespace,
|
|
405
|
+
current_path_type="Python",
|
|
406
|
+
endpoint=endpoint,
|
|
407
|
+
)
|
|
408
|
+
if path_links != "":
|
|
409
|
+
ctx.out.write("\n")
|
|
410
|
+
ctx.out.write(path_links)
|
|
411
|
+
|
|
348
412
|
ctx.out.write("\n")
|
|
349
413
|
ctx.out.write(f'{ENDPOINT_METHOD} = "{endpoint.method.upper()}"\n')
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
)
|
|
414
|
+
for endpoint_specific_path in sorted(
|
|
415
|
+
endpoint.path_per_api_endpoint.values(), key=_get_epf_root
|
|
416
|
+
):
|
|
417
|
+
endpoint_path_name = ENDPOINT_PATH
|
|
418
|
+
|
|
419
|
+
if (
|
|
420
|
+
len(endpoint.path_per_api_endpoint.keys()) > 1
|
|
421
|
+
and endpoint_specific_path.root != ctx.builder.top_namespace
|
|
422
|
+
):
|
|
423
|
+
endpoint_path_name = ENDPOINT_PATH_ALTERNATE
|
|
424
|
+
ctx.names.add(ENDPOINT_PATH_ALTERNATE)
|
|
425
|
+
ctx.out.write(
|
|
426
|
+
f'{endpoint_path_name} = "{endpoint_specific_path.path_root}/{endpoint_specific_path.path_dirname}/{endpoint_specific_path.path_basename}"\n'
|
|
427
|
+
)
|
|
353
428
|
|
|
354
429
|
ctx.names.add(ENDPOINT_METHOD)
|
|
355
430
|
ctx.names.add(ENDPOINT_PATH)
|
|
@@ -439,8 +514,23 @@ def _emit_endpoint_invocation_function_signature(
|
|
|
439
514
|
else []
|
|
440
515
|
) + (extra_params if extra_params is not None else [])
|
|
441
516
|
|
|
442
|
-
|
|
443
|
-
|
|
517
|
+
request_options_property = builder.SpecProperty(
|
|
518
|
+
name="_request_options",
|
|
519
|
+
label="_request_options",
|
|
520
|
+
spec_type=REQUEST_OPTIONS_STYPE,
|
|
521
|
+
extant=builder.PropertyExtant.optional,
|
|
522
|
+
convert_value=builder.PropertyConvertValue.auto,
|
|
523
|
+
name_case=builder.NameCase.convert,
|
|
524
|
+
default=None,
|
|
525
|
+
has_default=True,
|
|
526
|
+
desc=None,
|
|
527
|
+
)
|
|
528
|
+
all_arguments.append(request_options_property)
|
|
529
|
+
|
|
530
|
+
# All endpoints share a function name
|
|
531
|
+
function = endpoint.path_per_api_endpoint[endpoint.default_endpoint_key].function
|
|
532
|
+
assert function is not None
|
|
533
|
+
function_name = function.split(".")[-1]
|
|
444
534
|
ctx.out.write(
|
|
445
535
|
f"""
|
|
446
536
|
def {function_name}(
|
|
@@ -486,7 +576,10 @@ def _emit_async_batch_invocation_function(
|
|
|
486
576
|
endpoint = namespace.endpoint
|
|
487
577
|
if endpoint is None:
|
|
488
578
|
return
|
|
489
|
-
if
|
|
579
|
+
if (
|
|
580
|
+
endpoint.async_batch_path is None
|
|
581
|
+
or endpoint.is_sdk != EndpointEmitType.EMIT_ENDPOINT
|
|
582
|
+
):
|
|
490
583
|
return
|
|
491
584
|
|
|
492
585
|
ctx.out.write("\n")
|
|
@@ -568,7 +661,10 @@ def _emit_endpoint_invocation_function(
|
|
|
568
661
|
endpoint = namespace.endpoint
|
|
569
662
|
if endpoint is None:
|
|
570
663
|
return
|
|
571
|
-
if
|
|
664
|
+
if (
|
|
665
|
+
endpoint.is_sdk != EndpointEmitType.EMIT_ENDPOINT
|
|
666
|
+
or endpoint.stability_level == EmitOpenAPIStabilityLevel.draft
|
|
667
|
+
):
|
|
572
668
|
return
|
|
573
669
|
|
|
574
670
|
ctx.out.write("\n")
|
|
@@ -601,6 +697,7 @@ def _emit_endpoint_invocation_function(
|
|
|
601
697
|
method={refer_to(ctx=ctx, stype=endpoint_method_stype)},
|
|
602
698
|
endpoint={refer_to(ctx=ctx, stype=endpoint_path_stype)},
|
|
603
699
|
args=args,
|
|
700
|
+
request_options=_request_options,
|
|
604
701
|
)
|
|
605
702
|
return self.do_request(api_request=api_request, return_type={refer_to(ctx=ctx, stype=data_type)})"""
|
|
606
703
|
)
|
|
@@ -695,39 +792,43 @@ def _emit_properties(
|
|
|
695
792
|
if len(properties) > 0:
|
|
696
793
|
|
|
697
794
|
def write_field(prop: builder.SpecProperty) -> None:
|
|
795
|
+
stype = prop.spec_type
|
|
698
796
|
if prop.name_case == builder.NameCase.preserve:
|
|
699
797
|
unconverted_keys.add(prop.name)
|
|
700
798
|
py_name = python_field_name(prop.name, prop.name_case)
|
|
701
799
|
|
|
702
800
|
if prop.convert_value == builder.PropertyConvertValue.no_convert:
|
|
703
801
|
unconverted_values.add(py_name)
|
|
704
|
-
elif not
|
|
802
|
+
elif not stype.is_value_converted():
|
|
705
803
|
assert prop.convert_value == builder.PropertyConvertValue.auto
|
|
706
804
|
unconverted_values.add(py_name)
|
|
707
|
-
if
|
|
805
|
+
if stype.is_value_to_string():
|
|
708
806
|
to_string_values.add(py_name)
|
|
709
807
|
|
|
710
808
|
if prop.parse_require:
|
|
711
809
|
parse_require.add(py_name)
|
|
712
810
|
|
|
713
|
-
ref_type = refer_to(ctx,
|
|
811
|
+
ref_type = refer_to(ctx, stype)
|
|
714
812
|
default = None
|
|
715
813
|
if prop.extant == builder.PropertyExtant.missing:
|
|
716
814
|
ref_type = f"MissingType[{ref_type}]"
|
|
717
815
|
default = "MISSING_SENTRY"
|
|
718
816
|
ctx.use_missing = True
|
|
719
817
|
elif prop.extant == builder.PropertyExtant.optional:
|
|
720
|
-
|
|
818
|
+
if isinstance(
|
|
819
|
+
stype, builder.SpecTypeInstance
|
|
820
|
+
) and stype.defn_type.is_base_type(builder.BaseTypeName.s_optional):
|
|
821
|
+
pass # base type already adds the None union
|
|
822
|
+
elif ref_type == "None":
|
|
823
|
+
pass # no need to add a None union to a none type
|
|
824
|
+
else:
|
|
825
|
+
ref_type = f"{ref_type} | None"
|
|
721
826
|
default = "None"
|
|
722
827
|
elif prop.has_default:
|
|
723
|
-
default = _emit_value(ctx,
|
|
828
|
+
default = _emit_value(ctx, stype, prop.default)
|
|
724
829
|
if (
|
|
725
|
-
isinstance(
|
|
726
|
-
and (
|
|
727
|
-
prop.spec_type.defn_type.is_base_type(
|
|
728
|
-
builder.BaseTypeName.s_list
|
|
729
|
-
)
|
|
730
|
-
)
|
|
830
|
+
isinstance(stype, builder.SpecTypeInstance)
|
|
831
|
+
and (stype.defn_type.is_base_type(builder.BaseTypeName.s_list))
|
|
731
832
|
and default == "[]"
|
|
732
833
|
):
|
|
733
834
|
default = "dataclasses.field(default_factory=list)"
|
|
@@ -779,7 +880,17 @@ def _emit_type(ctx: Context, stype: builder.SpecType) -> None:
|
|
|
779
880
|
return
|
|
780
881
|
|
|
781
882
|
if isinstance(stype, builder.SpecTypeDefnAlias):
|
|
782
|
-
ctx.
|
|
883
|
+
ctx.use_serial_alias = True
|
|
884
|
+
ctx.out.write(f"{stype.name} = typing.Annotated[\n")
|
|
885
|
+
ctx.out.write(f"{INDENT}{refer_to(ctx, stype.alias)},\n")
|
|
886
|
+
ctx.out.write(f"{INDENT}serial_alias_annotation(\n")
|
|
887
|
+
ctx.out.write(
|
|
888
|
+
f"{INDENT}named_type_path={util.encode_common_string(_named_type_path(ctx, stype))},\n"
|
|
889
|
+
)
|
|
890
|
+
if stype.is_dynamic_allowed():
|
|
891
|
+
ctx.out.write(f"{INDENT}is_dynamic_allowed=True,\n")
|
|
892
|
+
ctx.out.write(f"{INDENT}),\n")
|
|
893
|
+
ctx.out.write("]\n")
|
|
783
894
|
return
|
|
784
895
|
|
|
785
896
|
if isinstance(stype, builder.SpecTypeDefnUnion):
|
|
@@ -790,6 +901,8 @@ def _emit_type(ctx: Context, stype: builder.SpecType) -> None:
|
|
|
790
901
|
ctx.out.write(
|
|
791
902
|
f"{INDENT}named_type_path={util.encode_common_string(_named_type_path(ctx, stype))},\n"
|
|
792
903
|
)
|
|
904
|
+
if stype.is_dynamic_allowed():
|
|
905
|
+
ctx.out.write(f"{INDENT}is_dynamic_allowed=True,\n")
|
|
793
906
|
if stype.discriminator is not None:
|
|
794
907
|
ctx.out.write(
|
|
795
908
|
f"{INDENT * 2}discriminator={util.encode_common_string(stype.discriminator)},\n"
|
|
@@ -818,7 +931,7 @@ def _emit_type(ctx: Context, stype: builder.SpecType) -> None:
|
|
|
818
931
|
if not stype.base.is_base:
|
|
819
932
|
base_class = f"({refer_to(ctx, stype.base)})"
|
|
820
933
|
elif len(generics) > 0:
|
|
821
|
-
base_class = f"
|
|
934
|
+
base_class = f"[{', '.join(generics)}]"
|
|
822
935
|
class_out.write(f"class {stype.name}{base_class}:\n")
|
|
823
936
|
|
|
824
937
|
emitted_properties_metadata = _emit_type_properties(
|
|
@@ -836,6 +949,8 @@ def _emit_type(ctx: Context, stype: builder.SpecType) -> None:
|
|
|
836
949
|
ctx.out.write(
|
|
837
950
|
f"{INDENT}named_type_path={util.encode_common_string(_named_type_path(ctx, stype))},\n"
|
|
838
951
|
)
|
|
952
|
+
if stype.is_dynamic_allowed():
|
|
953
|
+
ctx.out.write(f"{INDENT}is_dynamic_allowed=True,\n")
|
|
839
954
|
|
|
840
955
|
def write_values(key: str, values: set[str]) -> None:
|
|
841
956
|
if len(values) == 0:
|
|
@@ -852,13 +967,19 @@ def _emit_type(ctx: Context, stype: builder.SpecType) -> None:
|
|
|
852
967
|
|
|
853
968
|
# Emit dataclass decorator
|
|
854
969
|
dataclass = "@dataclasses.dataclass"
|
|
855
|
-
|
|
970
|
+
refer_to(
|
|
971
|
+
ctx,
|
|
972
|
+
builder.SpecTypeDefnAlias(
|
|
973
|
+
namespace=ctx.builder.namespaces[base_namespace_name], name="ENABLE_SLOTS"
|
|
974
|
+
),
|
|
975
|
+
)
|
|
976
|
+
dc_args = ["slots=base_t.ENABLE_SLOTS"]
|
|
856
977
|
if stype.is_kw_only():
|
|
857
978
|
dc_args.append("kw_only=True")
|
|
858
979
|
if stype.is_hashable:
|
|
859
980
|
dc_args.extend(["frozen=True", "eq=True"])
|
|
860
981
|
if len(dc_args) > 0:
|
|
861
|
-
dataclass += f"({', '.join(dc_args)})"
|
|
982
|
+
dataclass += f"({', '.join(dc_args)}) # type: ignore[literal-required]"
|
|
862
983
|
|
|
863
984
|
ctx.out.write(f"{dataclass}\n")
|
|
864
985
|
ctx.out.write(class_out.getvalue())
|
|
@@ -903,13 +1024,22 @@ base_name_map = {
|
|
|
903
1024
|
|
|
904
1025
|
def refer_to(ctx: TrackingContext, stype: builder.SpecType) -> str:
|
|
905
1026
|
if isinstance(stype, builder.SpecTypeInstance):
|
|
906
|
-
params =
|
|
1027
|
+
params = [refer_to(ctx, p) for p in stype.parameters]
|
|
1028
|
+
|
|
1029
|
+
if stype.defn_type.is_base_type(builder.BaseTypeName.s_union):
|
|
1030
|
+
if len(stype.parameters) == 1:
|
|
1031
|
+
return f"typing.Union[{params[0]}]"
|
|
1032
|
+
return " | ".join(params)
|
|
907
1033
|
|
|
908
1034
|
if stype.defn_type.is_base_type(builder.BaseTypeName.s_readonly_array):
|
|
909
|
-
assert len(
|
|
910
|
-
|
|
1035
|
+
assert len(params) == 1, "Read Only Array takes one parameter"
|
|
1036
|
+
return f"tuple[{params[0]}, ...]"
|
|
911
1037
|
|
|
912
|
-
|
|
1038
|
+
if stype.defn_type.is_base_type(builder.BaseTypeName.s_optional):
|
|
1039
|
+
assert len(params) == 1, "Optional only takes one parameter"
|
|
1040
|
+
return f"{params[0]} | None"
|
|
1041
|
+
|
|
1042
|
+
return f"{refer_to(ctx, stype.defn_type)}[{', '.join(params)}]"
|
|
913
1043
|
|
|
914
1044
|
if isinstance(stype, builder.SpecTypeLiteralWrapper):
|
|
915
1045
|
return _emit_value(ctx, stype.value_type, stype.value)
|
|
@@ -935,23 +1065,21 @@ def refer_to(ctx: TrackingContext, stype: builder.SpecType) -> str:
|
|
|
935
1065
|
SpecEndpoint = builder.SpecEndpoint
|
|
936
1066
|
|
|
937
1067
|
|
|
938
|
-
def _route_identifier(endpoint: builder.SpecEndpoint) -> tuple[str, str, str]:
|
|
939
|
-
return (endpoint.path_dirname, endpoint.path_basename, endpoint.method)
|
|
940
|
-
|
|
941
|
-
|
|
942
1068
|
def _emit_routes(*, builder: builder.SpecBuilder, config: PythonConfig) -> None:
|
|
943
1069
|
for endpoint_root in builder.api_endpoints:
|
|
944
1070
|
endpoints: list[SpecEndpoint] = []
|
|
945
1071
|
output = config.routes_output.get(endpoint_root)
|
|
946
1072
|
if output is None:
|
|
947
1073
|
continue
|
|
1074
|
+
last_endpoint: SpecEndpoint | None = None
|
|
948
1075
|
for namespace in builder.namespaces.values():
|
|
949
1076
|
endpoint = namespace.endpoint
|
|
1077
|
+
last_endpoint = endpoint
|
|
950
1078
|
if endpoint is None:
|
|
951
1079
|
continue
|
|
952
|
-
if endpoint.
|
|
1080
|
+
if endpoint_root not in endpoint.path_per_api_endpoint:
|
|
953
1081
|
continue
|
|
954
|
-
if endpoint.function is None:
|
|
1082
|
+
if endpoint.path_per_api_endpoint[endpoint_root].function is None:
|
|
955
1083
|
continue
|
|
956
1084
|
|
|
957
1085
|
endpoints.append(endpoint)
|
|
@@ -964,6 +1092,15 @@ def _emit_routes(*, builder: builder.SpecBuilder, config: PythonConfig) -> None:
|
|
|
964
1092
|
from main.site.framework.types import StaticRouteType
|
|
965
1093
|
"""
|
|
966
1094
|
)
|
|
1095
|
+
|
|
1096
|
+
def _route_identifier(endpoint: SpecEndpoint) -> tuple[str, str, str]:
|
|
1097
|
+
endpoint_specific_path = endpoint.path_per_api_endpoint[endpoint_root]
|
|
1098
|
+
return (
|
|
1099
|
+
endpoint_specific_path.path_dirname,
|
|
1100
|
+
endpoint_specific_path.path_basename,
|
|
1101
|
+
endpoint.method,
|
|
1102
|
+
)
|
|
1103
|
+
|
|
967
1104
|
sorted_endpoints = sorted(endpoints, key=_route_identifier)
|
|
968
1105
|
|
|
969
1106
|
assert len(endpoints) == len(set(map(_route_identifier, endpoints))), (
|
|
@@ -972,17 +1109,21 @@ from main.site.framework.types import StaticRouteType
|
|
|
972
1109
|
|
|
973
1110
|
path_set = set()
|
|
974
1111
|
for endpoint in sorted_endpoints:
|
|
975
|
-
|
|
976
|
-
|
|
1112
|
+
last_endpoint = endpoint
|
|
1113
|
+
endpoint_function_path = endpoint.path_per_api_endpoint[endpoint_root]
|
|
1114
|
+
assert endpoint_function_path.function
|
|
1115
|
+
func_bits = endpoint_function_path.function.split(".")
|
|
977
1116
|
path = ".".join(func_bits[:-1])
|
|
978
1117
|
if path in path_set:
|
|
979
1118
|
continue
|
|
980
1119
|
path_set.add(path)
|
|
981
1120
|
static_out.write(f"import {path}\n")
|
|
982
1121
|
|
|
1122
|
+
assert last_endpoint is not None
|
|
1123
|
+
|
|
983
1124
|
static_out.write(
|
|
984
1125
|
f"""
|
|
985
|
-
ROUTE_PREFIX = "/{
|
|
1126
|
+
ROUTE_PREFIX = "/{last_endpoint.path_per_api_endpoint[endpoint_root].path_root}"
|
|
986
1127
|
|
|
987
1128
|
ROUTES: list[StaticRouteType] = [
|
|
988
1129
|
"""
|
|
@@ -996,20 +1137,21 @@ ROUTES: list[StaticRouteType] = [
|
|
|
996
1137
|
|
|
997
1138
|
from main.site.framework.types import DynamicRouteType
|
|
998
1139
|
|
|
999
|
-
ROUTE_PREFIX = "/{
|
|
1140
|
+
ROUTE_PREFIX = "/{last_endpoint.path_per_api_endpoint[endpoint_root].path_root}"
|
|
1000
1141
|
|
|
1001
1142
|
ROUTES: list[DynamicRouteType] = [
|
|
1002
1143
|
"""
|
|
1003
1144
|
)
|
|
1004
1145
|
|
|
1005
1146
|
for endpoint in sorted_endpoints:
|
|
1147
|
+
endpoint_function_path = endpoint.path_per_api_endpoint[endpoint_root]
|
|
1006
1148
|
dynamic_out.write(
|
|
1007
|
-
f'{INDENT}("{
|
|
1149
|
+
f'{INDENT}("{endpoint_function_path.path_dirname}/{endpoint_function_path.path_basename}", "{endpoint_function_path.function}", ["{endpoint.method.upper()}"]),\n'
|
|
1008
1150
|
)
|
|
1009
1151
|
|
|
1010
|
-
assert
|
|
1152
|
+
assert endpoint_function_path.function
|
|
1011
1153
|
static_out.write(
|
|
1012
|
-
f'{INDENT}("{
|
|
1154
|
+
f'{INDENT}("{endpoint_function_path.path_dirname}/{endpoint_function_path.path_basename}", {endpoint_function_path.function}, ["{endpoint.method.upper()}"]),\n'
|
|
1013
1155
|
)
|
|
1014
1156
|
|
|
1015
1157
|
dynamic_out.write(f"{MODIFY_NOTICE}]\n")
|
|
@@ -1025,15 +1167,21 @@ def _emit_namespace_imports(
|
|
|
1025
1167
|
*,
|
|
1026
1168
|
out: io.StringIO,
|
|
1027
1169
|
namespaces: set[builder.SpecNamespace],
|
|
1028
|
-
from_namespace:
|
|
1170
|
+
from_namespace: builder.SpecNamespace | None,
|
|
1029
1171
|
config: PythonConfig,
|
|
1172
|
+
skip_non_sdk: bool = False,
|
|
1030
1173
|
) -> None:
|
|
1031
1174
|
for ns in sorted(
|
|
1032
1175
|
namespaces,
|
|
1033
1176
|
key=lambda name: _resolve_namespace_name(name),
|
|
1034
1177
|
):
|
|
1178
|
+
if (
|
|
1179
|
+
skip_non_sdk
|
|
1180
|
+
and ns.endpoint is not None
|
|
1181
|
+
and ns.endpoint.is_sdk != EndpointEmitType.EMIT_ENDPOINT
|
|
1182
|
+
):
|
|
1183
|
+
continue
|
|
1035
1184
|
resolved = _resolve_namespace_name(ns)
|
|
1036
|
-
ref = _resolve_namespace_ref(ns)
|
|
1037
1185
|
if ns.endpoint is not None:
|
|
1038
1186
|
import_alias = "_".join(ns.path[2:]) + "_t"
|
|
1039
1187
|
out.write(
|
|
@@ -1057,8 +1205,8 @@ def _emit_id_source(*, builder: builder.SpecBuilder, config: PythonConfig) -> No
|
|
|
1057
1205
|
return None
|
|
1058
1206
|
enum_out = io.StringIO()
|
|
1059
1207
|
enum_out.write(f"{LINT_HEADER}{MODIFY_NOTICE}\n")
|
|
1060
|
-
enum_out.write("
|
|
1061
|
-
enum_out.write("from
|
|
1208
|
+
enum_out.write("import typing\n")
|
|
1209
|
+
enum_out.write("from enum import StrEnum\n")
|
|
1062
1210
|
|
|
1063
1211
|
ctx = TrackingContext()
|
|
1064
1212
|
# In this context the propername
|
|
@@ -1074,11 +1222,11 @@ def _emit_id_source(*, builder: builder.SpecBuilder, config: PythonConfig) -> No
|
|
|
1074
1222
|
known_keys = []
|
|
1075
1223
|
enum_out.write("\nENUM_MAP: dict[str, type[StrEnum]] = {\n")
|
|
1076
1224
|
for key in sorted(named_enums.keys()):
|
|
1077
|
-
enum_out.write(f'"{key}": {named_enums[key]},\n')
|
|
1078
|
-
known_keys.append(f'
|
|
1225
|
+
enum_out.write(f'{INDENT}"{key}": {named_enums[key]},\n')
|
|
1226
|
+
known_keys.append(f'"{key}"')
|
|
1079
1227
|
enum_out.write(f"}}\n{MODIFY_NOTICE}\n")
|
|
1080
1228
|
|
|
1081
|
-
enum_out.write(f"\nKnownEnumsType =
|
|
1229
|
+
enum_out.write(f"\nKnownEnumsType = typing.Literal[\n{INDENT}")
|
|
1082
1230
|
enum_out.write(f",\n{INDENT}".join(known_keys))
|
|
1083
1231
|
enum_out.write(f"\n]\n{MODIFY_NOTICE}\n")
|
|
1084
1232
|
|
|
@@ -1095,21 +1243,36 @@ def _emit_api_stubs(*, builder: builder.SpecBuilder, config: PythonConfig) -> No
|
|
|
1095
1243
|
|
|
1096
1244
|
if endpoint is None:
|
|
1097
1245
|
continue
|
|
1098
|
-
if endpoint.
|
|
1246
|
+
if endpoint_root not in endpoint.path_per_api_endpoint:
|
|
1099
1247
|
continue
|
|
1100
|
-
|
|
1248
|
+
|
|
1249
|
+
endpoint_function = endpoint.path_per_api_endpoint[endpoint_root].function
|
|
1250
|
+
if endpoint_function is None:
|
|
1101
1251
|
continue
|
|
1102
1252
|
|
|
1103
|
-
module_dir, file_name,
|
|
1253
|
+
module_dir, file_name, _func_name = endpoint_function.rsplit(".", 2)
|
|
1104
1254
|
module_path = os.path.abspath(module_dir.replace(".", "/"))
|
|
1105
1255
|
api_stub_file = f"{module_path}/{file_name}.py"
|
|
1106
1256
|
if os.path.isfile(api_stub_file):
|
|
1107
1257
|
continue
|
|
1108
|
-
_create_api_stub(
|
|
1258
|
+
_create_api_stub(
|
|
1259
|
+
api_stub_file=api_stub_file,
|
|
1260
|
+
file_name=file_name,
|
|
1261
|
+
endpoint=endpoint,
|
|
1262
|
+
config=config,
|
|
1263
|
+
endpoint_root=endpoint_root,
|
|
1264
|
+
top_namespace=builder.top_namespace,
|
|
1265
|
+
)
|
|
1109
1266
|
|
|
1110
1267
|
|
|
1111
1268
|
def _create_api_stub(
|
|
1112
|
-
|
|
1269
|
+
*,
|
|
1270
|
+
api_stub_file: str,
|
|
1271
|
+
file_name: str,
|
|
1272
|
+
endpoint: SpecEndpoint,
|
|
1273
|
+
config: PythonConfig,
|
|
1274
|
+
endpoint_root: str,
|
|
1275
|
+
top_namespace: str,
|
|
1113
1276
|
) -> None:
|
|
1114
1277
|
assert (
|
|
1115
1278
|
endpoint.method == builder.RouteMethod.post
|
|
@@ -1117,7 +1280,13 @@ def _create_api_stub(
|
|
|
1117
1280
|
or endpoint.method == builder.RouteMethod.delete
|
|
1118
1281
|
or endpoint.method == builder.RouteMethod.patch
|
|
1119
1282
|
)
|
|
1120
|
-
api_out = _create_api_function(
|
|
1283
|
+
api_out = _create_api_function(
|
|
1284
|
+
file_name=file_name,
|
|
1285
|
+
endpoint=endpoint,
|
|
1286
|
+
config=config,
|
|
1287
|
+
endpoint_root=endpoint_root,
|
|
1288
|
+
top_namespace=top_namespace,
|
|
1289
|
+
)
|
|
1121
1290
|
util.rewrite_file(api_stub_file, api_out.getvalue())
|
|
1122
1291
|
|
|
1123
1292
|
|
|
@@ -1126,15 +1295,22 @@ WRAP_ARGS_END = "\n"
|
|
|
1126
1295
|
|
|
1127
1296
|
|
|
1128
1297
|
def _create_api_function(
|
|
1129
|
-
|
|
1298
|
+
*,
|
|
1299
|
+
file_name: str,
|
|
1300
|
+
endpoint: SpecEndpoint,
|
|
1301
|
+
config: PythonConfig,
|
|
1302
|
+
endpoint_root: str,
|
|
1303
|
+
top_namespace: str,
|
|
1130
1304
|
) -> io.StringIO:
|
|
1305
|
+
endpoint_specific_path = endpoint.path_per_api_endpoint[endpoint_root]
|
|
1306
|
+
assert endpoint_specific_path is not None
|
|
1131
1307
|
api_out = io.StringIO()
|
|
1132
1308
|
python_api_type_root = f"{config.types_package}.api"
|
|
1133
|
-
dot_dirname =
|
|
1309
|
+
dot_dirname = endpoint_specific_path.path_dirname.replace("/", ".")
|
|
1134
1310
|
api_import = (
|
|
1135
1311
|
f"{python_api_type_root}.{dot_dirname}.{file_name}"
|
|
1136
1312
|
if dot_dirname != ""
|
|
1137
|
-
else f"{python_api_type_root}.{
|
|
1313
|
+
else f"{python_api_type_root}.{endpoint_specific_path.path_basename}"
|
|
1138
1314
|
)
|
|
1139
1315
|
|
|
1140
1316
|
if endpoint.method == builder.RouteMethod.post:
|
|
@@ -1146,7 +1322,20 @@ def _create_api_function(
|
|
|
1146
1322
|
elif endpoint.method == builder.RouteMethod.patch:
|
|
1147
1323
|
validated_method = "validated_patch"
|
|
1148
1324
|
|
|
1149
|
-
ruff_requires_wrap = len(
|
|
1325
|
+
ruff_requires_wrap = len(endpoint_specific_path.path_basename) > 14
|
|
1326
|
+
|
|
1327
|
+
account_type = (
|
|
1328
|
+
endpoint_specific_path.root
|
|
1329
|
+
if endpoint_specific_path.root not in ["external", "portal"]
|
|
1330
|
+
else "materials"
|
|
1331
|
+
)
|
|
1332
|
+
|
|
1333
|
+
endpoint_path_name = (
|
|
1334
|
+
ENDPOINT_PATH_ALTERNATE
|
|
1335
|
+
if len(endpoint.path_per_api_endpoint.keys()) > 1
|
|
1336
|
+
and endpoint_specific_path.root != top_namespace
|
|
1337
|
+
else ENDPOINT_PATH
|
|
1338
|
+
)
|
|
1150
1339
|
|
|
1151
1340
|
api_out.write(
|
|
1152
1341
|
f"""import {api_import} as api
|
|
@@ -1154,8 +1343,8 @@ from main.db.session import Session, SessionMaker
|
|
|
1154
1343
|
from main.site.decorators import APIError, APIResponse, {validated_method}
|
|
1155
1344
|
|
|
1156
1345
|
|
|
1157
|
-
@{validated_method}(api.
|
|
1158
|
-
def {
|
|
1346
|
+
@{validated_method}(api.{endpoint_path_name}, "{account_type}", api.Arguments)
|
|
1347
|
+
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]:
|
|
1159
1348
|
with Session(client_sm) as session:
|
|
1160
1349
|
# return APIResponse(data=api.Data())
|
|
1161
1350
|
pass
|
|
@@ -1177,7 +1366,7 @@ def _emit_api_argument_lookup(
|
|
|
1177
1366
|
for endpoint_root in builder.api_endpoints:
|
|
1178
1367
|
routes_output = config.routes_output[endpoint_root]
|
|
1179
1368
|
|
|
1180
|
-
imports = ["import
|
|
1369
|
+
imports = ["import dataclasses"]
|
|
1181
1370
|
mappings = []
|
|
1182
1371
|
for namespace in sorted(
|
|
1183
1372
|
builder.namespaces.values(),
|
|
@@ -1187,11 +1376,11 @@ def _emit_api_argument_lookup(
|
|
|
1187
1376
|
|
|
1188
1377
|
if endpoint is None:
|
|
1189
1378
|
continue
|
|
1190
|
-
if endpoint.
|
|
1379
|
+
if endpoint_root not in endpoint.path_per_api_endpoint:
|
|
1191
1380
|
continue
|
|
1192
|
-
if endpoint.function is None:
|
|
1381
|
+
if endpoint.path_per_api_endpoint[endpoint_root].function is None:
|
|
1193
1382
|
continue
|
|
1194
|
-
if "Arguments" not in namespace.types:
|
|
1383
|
+
if "Arguments" not in namespace.types or "Data" not in namespace.types:
|
|
1195
1384
|
continue
|
|
1196
1385
|
|
|
1197
1386
|
import_alias = "_".join(namespace.path[1:])
|
|
@@ -1215,7 +1404,7 @@ def _emit_api_argument_lookup(
|
|
|
1215
1404
|
mapping += f"{INDENT}{INDENT}route_group={route_group},\n"
|
|
1216
1405
|
mapping += f"{INDENT}{INDENT}account_type={account_type},\n"
|
|
1217
1406
|
mapping += f"{INDENT}{INDENT}route={import_alias}.ENDPOINT_PATH,\n"
|
|
1218
|
-
mapping += f'{INDENT}{INDENT}handler="{endpoint.function}",\n'
|
|
1407
|
+
mapping += f'{INDENT}{INDENT}handler="{endpoint.path_per_api_endpoint[endpoint_root].function}",\n'
|
|
1219
1408
|
mapping += f"{INDENT}{INDENT}method={import_alias}.ENDPOINT_METHOD,\n"
|
|
1220
1409
|
mapping += f"{INDENT})"
|
|
1221
1410
|
mappings.append(mapping)
|
|
@@ -1226,9 +1415,6 @@ def _emit_api_argument_lookup(
|
|
|
1226
1415
|
argument_lookup_out.write(
|
|
1227
1416
|
f"""{LINE_BREAK.join(imports)}
|
|
1228
1417
|
|
|
1229
|
-
AT = typing.TypeVar("AT")
|
|
1230
|
-
DT = typing.TypeVar("DT")
|
|
1231
|
-
|
|
1232
1418
|
|
|
1233
1419
|
@dataclasses.dataclass(kw_only=True, frozen=True)
|
|
1234
1420
|
class ApiEndpointKey:
|
|
@@ -1237,7 +1423,7 @@ class ApiEndpointKey:
|
|
|
1237
1423
|
|
|
1238
1424
|
|
|
1239
1425
|
@dataclasses.dataclass(kw_only=True)
|
|
1240
|
-
class ApiEndpointSpec
|
|
1426
|
+
class ApiEndpointSpec[AT, DT]:
|
|
1241
1427
|
route: str
|
|
1242
1428
|
arguments_type: type[AT]
|
|
1243
1429
|
data_type: type[DT]
|
|
@@ -1267,10 +1453,10 @@ CLIENT_CLASS_IMPORTS = [
|
|
|
1267
1453
|
"import dataclasses",
|
|
1268
1454
|
]
|
|
1269
1455
|
ASYNC_BATCH_PROCESSOR_FILENAME = "async_batch_processor"
|
|
1270
|
-
|
|
1456
|
+
ASYNC_BATCH_PROCESSOR_BASE_IMPORTS = [
|
|
1271
1457
|
"import uuid",
|
|
1272
1458
|
"from abc import ABC, abstractmethod",
|
|
1273
|
-
"from pkgs.serialization_util
|
|
1459
|
+
"from pkgs.serialization_util import serialize_for_api",
|
|
1274
1460
|
]
|
|
1275
1461
|
|
|
1276
1462
|
|
|
@@ -1305,8 +1491,11 @@ def _emit_async_batch_processor(
|
|
|
1305
1491
|
config=config,
|
|
1306
1492
|
)
|
|
1307
1493
|
|
|
1494
|
+
imports = ASYNC_BATCH_PROCESSOR_BASE_IMPORTS.copy()
|
|
1495
|
+
if ctx.use_dataclass:
|
|
1496
|
+
imports.append("import dataclasses")
|
|
1308
1497
|
async_batch_processor_out.write(
|
|
1309
|
-
f"""{LINE_BREAK.join(
|
|
1498
|
+
f"""{LINE_BREAK.join(imports)}
|
|
1310
1499
|
|
|
1311
1500
|
|
|
1312
1501
|
class AsyncBatchProcessorBase(ABC):
|
|
@@ -1355,6 +1544,7 @@ def _emit_client_class(
|
|
|
1355
1544
|
namespaces=ctx.namespaces,
|
|
1356
1545
|
from_namespace=None,
|
|
1357
1546
|
config=config,
|
|
1547
|
+
skip_non_sdk=True,
|
|
1358
1548
|
)
|
|
1359
1549
|
|
|
1360
1550
|
client_base_out.write(
|
|
@@ -1368,6 +1558,7 @@ class APIRequest:
|
|
|
1368
1558
|
method: str
|
|
1369
1559
|
endpoint: str
|
|
1370
1560
|
args: typing.Any
|
|
1561
|
+
request_options: {refer_to(ctx=ctx, stype=REQUEST_OPTIONS_STYPE)} | None = None
|
|
1371
1562
|
|
|
1372
1563
|
|
|
1373
1564
|
class ClientMethods(ABC):
|