UncountablePythonSDK 0.0.52__py3-none-any.whl → 0.0.131__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of UncountablePythonSDK might be problematic. Click here for more details.
- docs/conf.py +54 -7
- docs/index.md +107 -4
- docs/integration_examples/create_ingredient.md +43 -0
- docs/integration_examples/create_output.md +56 -0
- docs/integration_examples/index.md +6 -0
- docs/justfile +2 -2
- docs/requirements.txt +6 -4
- examples/async_batch.py +3 -3
- examples/basic_auth.py +7 -0
- examples/create_entity.py +3 -1
- examples/create_ingredient_sdk.py +34 -0
- examples/download_files.py +26 -0
- examples/edit_recipe_inputs.py +4 -2
- examples/integration-server/jobs/materials_auto/concurrent_cron.py +11 -0
- examples/integration-server/jobs/materials_auto/example_cron.py +21 -0
- examples/integration-server/jobs/materials_auto/example_http.py +47 -0
- examples/integration-server/jobs/materials_auto/example_instrument.py +100 -0
- examples/integration-server/jobs/materials_auto/example_parse.py +140 -0
- examples/integration-server/jobs/materials_auto/example_predictions.py +61 -0
- examples/integration-server/jobs/materials_auto/example_runsheet_wh.py +39 -0
- examples/integration-server/jobs/materials_auto/example_wh.py +23 -0
- examples/integration-server/jobs/materials_auto/profile.yaml +104 -0
- examples/integration-server/pyproject.toml +224 -0
- examples/invoke_uploader.py +4 -1
- examples/oauth.py +7 -0
- examples/set_recipe_metadata_file.py +40 -0
- examples/set_recipe_output_file_sdk.py +26 -0
- examples/upload_files.py +1 -2
- pkgs/argument_parser/__init__.py +9 -0
- pkgs/argument_parser/_is_namedtuple.py +3 -0
- pkgs/argument_parser/argument_parser.py +217 -70
- pkgs/filesystem_utils/__init__.py +1 -0
- pkgs/filesystem_utils/_blob_session.py +144 -0
- pkgs/filesystem_utils/_gdrive_session.py +10 -7
- pkgs/filesystem_utils/_s3_session.py +15 -13
- pkgs/filesystem_utils/_sftp_session.py +11 -7
- pkgs/filesystem_utils/file_type_utils.py +30 -10
- pkgs/py.typed +0 -0
- pkgs/serialization/__init__.py +7 -2
- pkgs/serialization/annotation.py +64 -0
- pkgs/serialization/missing_sentry.py +1 -1
- pkgs/serialization/opaque_key.py +1 -1
- pkgs/serialization/serial_alias.py +47 -0
- pkgs/serialization/serial_class.py +47 -26
- pkgs/serialization/serial_generic.py +16 -0
- pkgs/serialization/serial_union.py +17 -14
- pkgs/serialization/yaml.py +4 -1
- pkgs/serialization_util/__init__.py +6 -0
- pkgs/serialization_util/dataclasses.py +14 -0
- pkgs/serialization_util/serialization_helpers.py +15 -5
- pkgs/type_spec/actions_registry/__main__.py +0 -4
- pkgs/type_spec/actions_registry/emit_typescript.py +5 -5
- pkgs/type_spec/builder.py +354 -119
- pkgs/type_spec/builder_types.py +9 -0
- pkgs/type_spec/config.py +51 -11
- pkgs/type_spec/cross_output_links.py +99 -0
- pkgs/type_spec/emit_io_ts.py +1 -1
- pkgs/type_spec/emit_open_api.py +127 -36
- pkgs/type_spec/emit_open_api_util.py +5 -6
- pkgs/type_spec/emit_python.py +329 -121
- pkgs/type_spec/emit_typescript.py +117 -256
- pkgs/type_spec/emit_typescript_util.py +291 -2
- pkgs/type_spec/load_types.py +18 -4
- pkgs/type_spec/non_discriminated_union_exceptions.py +14 -0
- pkgs/type_spec/open_api_util.py +29 -4
- pkgs/type_spec/parts/base.py.prepart +13 -10
- pkgs/type_spec/parts/base.ts.prepart +4 -0
- pkgs/type_spec/type_info/__main__.py +3 -1
- pkgs/type_spec/type_info/emit_type_info.py +124 -29
- pkgs/type_spec/ui_entry_actions/__init__.py +4 -0
- pkgs/type_spec/ui_entry_actions/generate_ui_entry_actions.py +308 -0
- pkgs/type_spec/util.py +4 -4
- pkgs/type_spec/value_spec/__main__.py +26 -9
- pkgs/type_spec/value_spec/convert_type.py +21 -1
- pkgs/type_spec/value_spec/emit_python.py +25 -7
- pkgs/type_spec/value_spec/types.py +1 -1
- uncountable/core/async_batch.py +1 -1
- uncountable/core/client.py +142 -39
- uncountable/core/environment.py +41 -0
- uncountable/core/file_upload.py +52 -18
- uncountable/integration/cli.py +142 -0
- uncountable/integration/construct_client.py +8 -8
- uncountable/integration/cron.py +11 -37
- uncountable/integration/db/connect.py +12 -2
- uncountable/integration/db/session.py +25 -0
- uncountable/integration/entrypoint.py +8 -37
- uncountable/integration/executors/executors.py +125 -2
- uncountable/integration/executors/generic_upload_executor.py +87 -29
- uncountable/integration/executors/script_executor.py +3 -3
- uncountable/integration/http_server/__init__.py +5 -0
- uncountable/integration/http_server/types.py +69 -0
- uncountable/integration/job.py +242 -12
- uncountable/integration/queue_runner/__init__.py +0 -0
- uncountable/integration/queue_runner/command_server/__init__.py +28 -0
- uncountable/integration/queue_runner/command_server/command_client.py +133 -0
- uncountable/integration/queue_runner/command_server/command_server.py +142 -0
- uncountable/integration/queue_runner/command_server/constants.py +4 -0
- uncountable/integration/queue_runner/command_server/protocol/__init__.py +0 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server.proto +58 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.py +57 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.pyi +114 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2_grpc.py +264 -0
- uncountable/integration/queue_runner/command_server/types.py +75 -0
- uncountable/integration/queue_runner/datastore/__init__.py +3 -0
- uncountable/integration/queue_runner/datastore/datastore_sqlite.py +250 -0
- uncountable/integration/queue_runner/datastore/interface.py +29 -0
- uncountable/integration/queue_runner/datastore/model.py +24 -0
- uncountable/integration/queue_runner/job_scheduler.py +200 -0
- uncountable/integration/queue_runner/queue_runner.py +34 -0
- uncountable/integration/queue_runner/types.py +7 -0
- uncountable/integration/queue_runner/worker.py +116 -0
- uncountable/integration/scan_profiles.py +67 -0
- uncountable/integration/scheduler.py +199 -0
- uncountable/integration/secret_retrieval/retrieve_secret.py +26 -4
- uncountable/integration/server.py +94 -69
- uncountable/integration/telemetry.py +150 -34
- uncountable/integration/webhook_server/entrypoint.py +97 -0
- uncountable/types/__init__.py +78 -1
- uncountable/types/api/batch/execute_batch.py +13 -6
- uncountable/types/api/batch/execute_batch_load_async.py +9 -3
- uncountable/types/api/chemical/convert_chemical_formats.py +17 -5
- uncountable/types/api/condition_parameters/__init__.py +1 -0
- uncountable/types/api/condition_parameters/upsert_condition_match.py +72 -0
- uncountable/types/api/entity/create_entities.py +19 -7
- uncountable/types/api/entity/create_entity.py +17 -8
- uncountable/types/api/entity/create_or_update_entity.py +48 -0
- uncountable/types/api/entity/export_entities.py +59 -0
- uncountable/types/api/entity/get_entities_data.py +13 -4
- uncountable/types/api/entity/grant_entity_permissions.py +48 -0
- uncountable/types/api/entity/list_aggregate.py +79 -0
- uncountable/types/api/entity/list_entities.py +42 -10
- uncountable/types/api/entity/lock_entity.py +11 -4
- uncountable/types/api/entity/lookup_entity.py +116 -0
- uncountable/types/api/entity/resolve_entity_ids.py +15 -6
- uncountable/types/api/entity/set_entity_field_values.py +44 -0
- uncountable/types/api/entity/set_values.py +10 -3
- uncountable/types/api/entity/transition_entity_phase.py +22 -7
- uncountable/types/api/entity/unlock_entity.py +10 -3
- uncountable/types/api/equipment/associate_equipment_input.py +9 -3
- uncountable/types/api/field_options/upsert_field_options.py +17 -7
- uncountable/types/api/files/__init__.py +1 -0
- uncountable/types/api/files/download_file.py +77 -0
- uncountable/types/api/id_source/list_id_source.py +16 -7
- uncountable/types/api/id_source/match_id_source.py +14 -5
- uncountable/types/api/input_groups/get_input_group_names.py +13 -4
- uncountable/types/api/inputs/create_inputs.py +23 -9
- uncountable/types/api/inputs/get_input_data.py +30 -12
- uncountable/types/api/inputs/get_input_names.py +16 -7
- uncountable/types/api/inputs/get_inputs_data.py +25 -7
- uncountable/types/api/inputs/set_input_attribute_values.py +12 -6
- uncountable/types/api/inputs/set_input_category.py +12 -5
- uncountable/types/api/inputs/set_input_subcategories.py +10 -3
- uncountable/types/api/inputs/set_intermediate_type.py +11 -4
- uncountable/types/api/integrations/__init__.py +1 -0
- uncountable/types/api/integrations/publish_realtime_data.py +41 -0
- uncountable/types/api/integrations/push_notification.py +49 -0
- uncountable/types/api/integrations/register_sockets_token.py +41 -0
- uncountable/types/api/listing/__init__.py +1 -0
- uncountable/types/api/listing/fetch_listing.py +58 -0
- uncountable/types/api/material_families/update_entity_material_families.py +10 -4
- uncountable/types/api/notebooks/__init__.py +1 -0
- uncountable/types/api/notebooks/add_notebook_content.py +119 -0
- uncountable/types/api/outputs/get_output_data.py +28 -13
- uncountable/types/api/outputs/get_output_names.py +15 -6
- uncountable/types/api/outputs/get_output_organization.py +173 -0
- uncountable/types/api/outputs/resolve_output_conditions.py +20 -8
- uncountable/types/api/permissions/set_core_permissions.py +26 -10
- uncountable/types/api/project/get_projects.py +16 -7
- uncountable/types/api/project/get_projects_data.py +17 -8
- uncountable/types/api/recipe_links/create_recipe_link.py +12 -5
- uncountable/types/api/recipe_links/remove_recipe_link.py +11 -4
- uncountable/types/api/recipe_metadata/get_recipe_metadata_data.py +16 -7
- uncountable/types/api/recipes/add_recipe_to_project.py +10 -3
- uncountable/types/api/recipes/add_time_series_data.py +64 -0
- uncountable/types/api/recipes/archive_recipes.py +11 -4
- uncountable/types/api/recipes/associate_recipe_as_input.py +12 -5
- uncountable/types/api/recipes/associate_recipe_as_lot.py +10 -3
- uncountable/types/api/recipes/clear_recipe_outputs.py +42 -0
- uncountable/types/api/recipes/create_mix_order.py +44 -0
- uncountable/types/api/recipes/create_recipe.py +15 -9
- uncountable/types/api/recipes/create_recipes.py +21 -9
- uncountable/types/api/recipes/disassociate_recipe_as_input.py +10 -3
- uncountable/types/api/recipes/edit_recipe_inputs.py +134 -22
- uncountable/types/api/recipes/get_column_calculation_values.py +57 -0
- uncountable/types/api/recipes/get_curve.py +11 -5
- uncountable/types/api/recipes/get_recipe_calculations.py +13 -7
- uncountable/types/api/recipes/get_recipe_links.py +10 -4
- uncountable/types/api/recipes/get_recipe_names.py +13 -4
- uncountable/types/api/recipes/get_recipe_output_metadata.py +12 -6
- uncountable/types/api/recipes/get_recipes_data.py +87 -33
- uncountable/types/api/recipes/lock_recipes.py +19 -8
- uncountable/types/api/recipes/remove_recipe_from_project.py +10 -3
- uncountable/types/api/recipes/set_recipe_inputs.py +16 -10
- uncountable/types/api/recipes/set_recipe_metadata.py +10 -3
- uncountable/types/api/recipes/set_recipe_output_annotations.py +24 -12
- uncountable/types/api/recipes/set_recipe_output_file.py +55 -0
- uncountable/types/api/recipes/set_recipe_outputs.py +35 -12
- uncountable/types/api/recipes/set_recipe_tags.py +26 -9
- uncountable/types/api/recipes/set_recipe_total.py +59 -0
- uncountable/types/api/recipes/unarchive_recipes.py +10 -3
- uncountable/types/api/recipes/unlock_recipes.py +14 -6
- uncountable/types/api/runsheet/__init__.py +1 -0
- uncountable/types/api/runsheet/complete_async_upload.py +41 -0
- uncountable/types/api/triggers/run_trigger.py +11 -4
- uncountable/types/api/uploader/complete_async_parse.py +46 -0
- uncountable/types/api/uploader/invoke_uploader.py +13 -6
- uncountable/types/api/user/__init__.py +1 -0
- uncountable/types/api/user/get_current_user_info.py +40 -0
- uncountable/types/async_batch.py +2 -1
- uncountable/types/async_batch_processor.py +618 -18
- uncountable/types/async_batch_t.py +54 -7
- uncountable/types/async_jobs.py +8 -0
- uncountable/types/async_jobs_t.py +52 -0
- uncountable/types/auth_retrieval.py +11 -0
- uncountable/types/auth_retrieval_t.py +75 -0
- uncountable/types/base.py +0 -1
- uncountable/types/base_t.py +13 -11
- uncountable/types/calculations.py +0 -1
- uncountable/types/calculations_t.py +5 -2
- uncountable/types/chemical_structure.py +0 -1
- uncountable/types/chemical_structure_t.py +6 -5
- uncountable/types/client_base.py +751 -70
- uncountable/types/client_config.py +1 -1
- uncountable/types/client_config_t.py +17 -3
- uncountable/types/curves.py +0 -1
- uncountable/types/curves_t.py +10 -7
- uncountable/types/data.py +12 -0
- uncountable/types/data_t.py +103 -0
- uncountable/types/entity.py +4 -1
- uncountable/types/entity_t.py +125 -7
- uncountable/types/experiment_groups.py +0 -1
- uncountable/types/experiment_groups_t.py +5 -2
- uncountable/types/exports.py +8 -0
- uncountable/types/exports_t.py +34 -0
- uncountable/types/field_values.py +19 -1
- uncountable/types/field_values_t.py +246 -9
- uncountable/types/fields.py +0 -1
- uncountable/types/fields_t.py +5 -2
- uncountable/types/generic_upload.py +6 -1
- uncountable/types/generic_upload_t.py +88 -9
- uncountable/types/id_source.py +0 -1
- uncountable/types/id_source_t.py +26 -7
- uncountable/types/identifier.py +0 -1
- uncountable/types/identifier_t.py +13 -5
- uncountable/types/input_attributes.py +0 -1
- uncountable/types/input_attributes_t.py +4 -4
- uncountable/types/inputs.py +1 -1
- uncountable/types/inputs_t.py +24 -4
- uncountable/types/integration_server.py +8 -0
- uncountable/types/integration_server_t.py +46 -0
- uncountable/types/integration_session.py +10 -0
- uncountable/types/integration_session_t.py +60 -0
- uncountable/types/integrations.py +10 -0
- uncountable/types/integrations_t.py +62 -0
- uncountable/types/job_definition.py +4 -6
- uncountable/types/job_definition_t.py +96 -65
- uncountable/types/listing.py +9 -0
- uncountable/types/listing_t.py +51 -0
- uncountable/types/notices.py +8 -0
- uncountable/types/notices_t.py +37 -0
- uncountable/types/notifications.py +11 -0
- uncountable/types/notifications_t.py +74 -0
- uncountable/types/outputs.py +0 -1
- uncountable/types/outputs_t.py +6 -3
- uncountable/types/overrides.py +9 -0
- uncountable/types/overrides_t.py +49 -0
- uncountable/types/permissions.py +0 -1
- uncountable/types/permissions_t.py +1 -2
- uncountable/types/phases.py +0 -1
- uncountable/types/phases_t.py +5 -2
- uncountable/types/post_base.py +0 -1
- uncountable/types/post_base_t.py +1 -2
- uncountable/types/queued_job.py +17 -0
- uncountable/types/queued_job_t.py +140 -0
- uncountable/types/recipe_identifiers.py +0 -1
- uncountable/types/recipe_identifiers_t.py +21 -8
- uncountable/types/recipe_inputs.py +0 -1
- uncountable/types/recipe_inputs_t.py +1 -2
- uncountable/types/recipe_links.py +0 -1
- uncountable/types/recipe_links_t.py +7 -4
- uncountable/types/recipe_metadata.py +0 -1
- uncountable/types/recipe_metadata_t.py +14 -9
- uncountable/types/recipe_output_metadata.py +0 -1
- uncountable/types/recipe_output_metadata_t.py +5 -2
- uncountable/types/recipe_tags.py +0 -1
- uncountable/types/recipe_tags_t.py +5 -2
- uncountable/types/recipe_workflow_steps.py +0 -1
- uncountable/types/recipe_workflow_steps_t.py +14 -7
- uncountable/types/recipes.py +0 -1
- uncountable/types/recipes_t.py +6 -2
- uncountable/types/response.py +0 -1
- uncountable/types/response_t.py +3 -2
- uncountable/types/secret_retrieval.py +0 -1
- uncountable/types/secret_retrieval_t.py +13 -7
- uncountable/types/sockets.py +20 -0
- uncountable/types/sockets_t.py +169 -0
- uncountable/types/structured_filters.py +25 -0
- uncountable/types/structured_filters_t.py +248 -0
- uncountable/types/units.py +0 -1
- uncountable/types/units_t.py +5 -2
- uncountable/types/uploader.py +24 -0
- uncountable/types/uploader_t.py +222 -0
- uncountable/types/users.py +0 -1
- uncountable/types/users_t.py +5 -2
- uncountable/types/webhook_job.py +9 -0
- uncountable/types/webhook_job_t.py +48 -0
- uncountable/types/workflows.py +0 -1
- uncountable/types/workflows_t.py +10 -4
- uncountablepythonsdk-0.0.131.dist-info/METADATA +64 -0
- uncountablepythonsdk-0.0.131.dist-info/RECORD +363 -0
- {UncountablePythonSDK-0.0.52.dist-info → uncountablepythonsdk-0.0.131.dist-info}/WHEEL +1 -1
- UncountablePythonSDK-0.0.52.dist-info/METADATA +0 -56
- UncountablePythonSDK-0.0.52.dist-info/RECORD +0 -246
- docs/quickstart.md +0 -19
- uncountable/core/version.py +0 -11
- {UncountablePythonSDK-0.0.52.dist-info → uncountablepythonsdk-0.0.131.dist-info}/top_level.txt +0 -0
pkgs/type_spec/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,18 +35,23 @@ 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
|
|
|
44
50
|
use_enum: bool = False
|
|
45
51
|
use_serial_string_enum: bool = False
|
|
46
52
|
use_dataclass: bool = False
|
|
47
|
-
use_serial_class: bool = False
|
|
48
53
|
use_serial_union: bool = False
|
|
54
|
+
use_serial_alias: bool = False
|
|
49
55
|
use_missing: bool = False
|
|
50
56
|
use_opaque_key: bool = False
|
|
51
57
|
|
|
@@ -54,6 +60,7 @@ class TrackingContext:
|
|
|
54
60
|
class Context(TrackingContext):
|
|
55
61
|
out: io.StringIO
|
|
56
62
|
namespace: builder.SpecNamespace
|
|
63
|
+
builder: builder.SpecBuilder
|
|
57
64
|
|
|
58
65
|
|
|
59
66
|
def _resolve_namespace_name(namespace: builder.SpecNamespace) -> str:
|
|
@@ -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,13 +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
|
-
if ctx.use_serial_class:
|
|
226
270
|
out.write("from pkgs.serialization import serial_class\n")
|
|
227
271
|
if ctx.use_serial_union:
|
|
228
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")
|
|
229
275
|
if ctx.use_serial_string_enum:
|
|
230
276
|
out.write("from pkgs.serialization import serial_string_enum\n")
|
|
231
277
|
if ctx.use_missing:
|
|
@@ -250,7 +296,7 @@ def _emit_types(*, builder: builder.SpecBuilder, config: PythonConfig) -> None:
|
|
|
250
296
|
):
|
|
251
297
|
if (
|
|
252
298
|
namespace.endpoint is not None
|
|
253
|
-
and
|
|
299
|
+
and namespace.endpoint.is_sdk == EndpointEmitType.EMIT_NOTHING
|
|
254
300
|
and config.sdk_endpoints_only is True
|
|
255
301
|
):
|
|
256
302
|
continue
|
|
@@ -258,6 +304,7 @@ def _emit_types(*, builder: builder.SpecBuilder, config: PythonConfig) -> None:
|
|
|
258
304
|
ctx = Context(
|
|
259
305
|
out=io.StringIO(),
|
|
260
306
|
namespace=namespace,
|
|
307
|
+
builder=builder,
|
|
261
308
|
)
|
|
262
309
|
|
|
263
310
|
_emit_namespace(ctx, namespace)
|
|
@@ -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)
|
|
@@ -390,13 +465,13 @@ from .base_t import ObjectId as ObjectId
|
|
|
390
465
|
def _validate_supports_handler_generation(
|
|
391
466
|
stype: builder.SpecTypeDefn, name: str, supports_inheritance: bool = False
|
|
392
467
|
) -> builder.SpecTypeDefnObject:
|
|
393
|
-
assert isinstance(
|
|
394
|
-
|
|
395
|
-
)
|
|
468
|
+
assert isinstance(stype, builder.SpecTypeDefnObject), (
|
|
469
|
+
f"External api {name} must be an object"
|
|
470
|
+
)
|
|
396
471
|
if not supports_inheritance:
|
|
397
|
-
assert (
|
|
398
|
-
|
|
399
|
-
)
|
|
472
|
+
assert stype.base is None or stype.base.is_base, (
|
|
473
|
+
f"Inheritance not supported in external api {name}"
|
|
474
|
+
)
|
|
400
475
|
return stype
|
|
401
476
|
|
|
402
477
|
|
|
@@ -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
|
)
|
|
@@ -621,7 +718,9 @@ def _emit_string_enum(ctx: Context, stype: builder.SpecTypeDefnStringEnum) -> No
|
|
|
621
718
|
ctx.out.write(f"{INDENT}labels={{\n")
|
|
622
719
|
for entry in stype.values.values():
|
|
623
720
|
if entry.label is not None:
|
|
624
|
-
ctx.out.write(
|
|
721
|
+
ctx.out.write(
|
|
722
|
+
f'{INDENT}{INDENT}"{entry.value}": "{entry.label}",\n'
|
|
723
|
+
)
|
|
625
724
|
|
|
626
725
|
ctx.out.write(f"{INDENT}}},\n")
|
|
627
726
|
if need_deprecated:
|
|
@@ -693,37 +792,43 @@ def _emit_properties(
|
|
|
693
792
|
if len(properties) > 0:
|
|
694
793
|
|
|
695
794
|
def write_field(prop: builder.SpecProperty) -> None:
|
|
795
|
+
stype = prop.spec_type
|
|
696
796
|
if prop.name_case == builder.NameCase.preserve:
|
|
697
797
|
unconverted_keys.add(prop.name)
|
|
698
798
|
py_name = python_field_name(prop.name, prop.name_case)
|
|
699
799
|
|
|
700
800
|
if prop.convert_value == builder.PropertyConvertValue.no_convert:
|
|
701
801
|
unconverted_values.add(py_name)
|
|
702
|
-
elif not
|
|
802
|
+
elif not stype.is_value_converted():
|
|
703
803
|
assert prop.convert_value == builder.PropertyConvertValue.auto
|
|
704
804
|
unconverted_values.add(py_name)
|
|
705
|
-
if
|
|
805
|
+
if stype.is_value_to_string():
|
|
706
806
|
to_string_values.add(py_name)
|
|
707
807
|
|
|
708
808
|
if prop.parse_require:
|
|
709
809
|
parse_require.add(py_name)
|
|
710
810
|
|
|
711
|
-
ref_type = refer_to(ctx,
|
|
811
|
+
ref_type = refer_to(ctx, stype)
|
|
712
812
|
default = None
|
|
713
813
|
if prop.extant == builder.PropertyExtant.missing:
|
|
714
814
|
ref_type = f"MissingType[{ref_type}]"
|
|
715
815
|
default = "MISSING_SENTRY"
|
|
716
816
|
ctx.use_missing = True
|
|
717
817
|
elif prop.extant == builder.PropertyExtant.optional:
|
|
718
|
-
|
|
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"
|
|
719
826
|
default = "None"
|
|
720
827
|
elif prop.has_default:
|
|
721
|
-
default = _emit_value(ctx,
|
|
828
|
+
default = _emit_value(ctx, stype, prop.default)
|
|
722
829
|
if (
|
|
723
|
-
isinstance(
|
|
724
|
-
and (
|
|
725
|
-
prop.spec_type.defn_type.is_base_type(builder.BaseTypeName.s_list)
|
|
726
|
-
)
|
|
830
|
+
isinstance(stype, builder.SpecTypeInstance)
|
|
831
|
+
and (stype.defn_type.is_base_type(builder.BaseTypeName.s_list))
|
|
727
832
|
and default == "[]"
|
|
728
833
|
):
|
|
729
834
|
default = "dataclasses.field(default_factory=list)"
|
|
@@ -749,6 +854,12 @@ def _emit_properties(
|
|
|
749
854
|
)
|
|
750
855
|
|
|
751
856
|
|
|
857
|
+
def _named_type_path(ctx: Context, stype: builder.SpecTypeDefn) -> str:
|
|
858
|
+
parts = [] if stype.is_base else stype.namespace.path.copy()
|
|
859
|
+
parts.append(stype.name)
|
|
860
|
+
return f"{ctx.builder.top_namespace}.{'.'.join(parts)}"
|
|
861
|
+
|
|
862
|
+
|
|
752
863
|
def _emit_type(ctx: Context, stype: builder.SpecType) -> None:
|
|
753
864
|
if not isinstance(stype, builder.SpecTypeDefn):
|
|
754
865
|
return
|
|
@@ -769,7 +880,17 @@ def _emit_type(ctx: Context, stype: builder.SpecType) -> None:
|
|
|
769
880
|
return
|
|
770
881
|
|
|
771
882
|
if isinstance(stype, builder.SpecTypeDefnAlias):
|
|
772
|
-
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")
|
|
773
894
|
return
|
|
774
895
|
|
|
775
896
|
if isinstance(stype, builder.SpecTypeDefnUnion):
|
|
@@ -777,6 +898,11 @@ def _emit_type(ctx: Context, stype: builder.SpecType) -> None:
|
|
|
777
898
|
ctx.out.write(f"{stype.name} = typing.Annotated[\n")
|
|
778
899
|
ctx.out.write(f"{INDENT}{refer_to(ctx, stype.get_backing_type())},\n")
|
|
779
900
|
ctx.out.write(f"{INDENT}serial_union_annotation(\n")
|
|
901
|
+
ctx.out.write(
|
|
902
|
+
f"{INDENT}named_type_path={util.encode_common_string(_named_type_path(ctx, stype))},\n"
|
|
903
|
+
)
|
|
904
|
+
if stype.is_dynamic_allowed():
|
|
905
|
+
ctx.out.write(f"{INDENT}is_dynamic_allowed=True,\n")
|
|
780
906
|
if stype.discriminator is not None:
|
|
781
907
|
ctx.out.write(
|
|
782
908
|
f"{INDENT * 2}discriminator={util.encode_common_string(stype.discriminator)},\n"
|
|
@@ -801,11 +927,11 @@ def _emit_type(ctx: Context, stype: builder.SpecType) -> None:
|
|
|
801
927
|
|
|
802
928
|
class_out = io.StringIO()
|
|
803
929
|
base_class = ""
|
|
804
|
-
|
|
930
|
+
generics = stype.get_generics()
|
|
805
931
|
if not stype.base.is_base:
|
|
806
932
|
base_class = f"({refer_to(ctx, stype.base)})"
|
|
807
|
-
elif
|
|
808
|
-
base_class = f"
|
|
933
|
+
elif len(generics) > 0:
|
|
934
|
+
base_class = f"[{', '.join(generics)}]"
|
|
809
935
|
class_out.write(f"class {stype.name}{base_class}:\n")
|
|
810
936
|
|
|
811
937
|
emitted_properties_metadata = _emit_type_properties(
|
|
@@ -816,45 +942,51 @@ def _emit_type(ctx: Context, stype: builder.SpecType) -> None:
|
|
|
816
942
|
to_string_values = emitted_properties_metadata.to_string_values
|
|
817
943
|
parse_require = emitted_properties_metadata.parse_require
|
|
818
944
|
|
|
819
|
-
|
|
945
|
+
_emit_generics(ctx, generics)
|
|
820
946
|
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
):
|
|
827
|
-
ctx.
|
|
828
|
-
ctx.out.write("@serial_class(\n")
|
|
947
|
+
# Emit serial_class decorator
|
|
948
|
+
ctx.out.write("@serial_class(\n")
|
|
949
|
+
ctx.out.write(
|
|
950
|
+
f"{INDENT}named_type_path={util.encode_common_string(_named_type_path(ctx, stype))},\n"
|
|
951
|
+
)
|
|
952
|
+
if stype.is_dynamic_allowed():
|
|
953
|
+
ctx.out.write(f"{INDENT}is_dynamic_allowed=True,\n")
|
|
829
954
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
955
|
+
def write_values(key: str, values: set[str]) -> None:
|
|
956
|
+
if len(values) == 0:
|
|
957
|
+
return
|
|
958
|
+
value_str = ", ".join([f'"{name}"' for name in sorted(values)])
|
|
959
|
+
ctx.out.write(f"{INDENT}{key}={{{value_str}}},\n")
|
|
835
960
|
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
961
|
+
write_values("unconverted_keys", unconverted_keys)
|
|
962
|
+
write_values("unconverted_values", unconverted_values)
|
|
963
|
+
write_values("to_string_values", to_string_values)
|
|
964
|
+
write_values("parse_require", parse_require)
|
|
840
965
|
|
|
841
|
-
|
|
966
|
+
ctx.out.write(")\n")
|
|
842
967
|
|
|
968
|
+
# Emit dataclass decorator
|
|
843
969
|
dataclass = "@dataclasses.dataclass"
|
|
844
|
-
|
|
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"]
|
|
845
977
|
if stype.is_kw_only():
|
|
846
978
|
dc_args.append("kw_only=True")
|
|
847
979
|
if stype.is_hashable:
|
|
848
980
|
dc_args.extend(["frozen=True", "eq=True"])
|
|
849
981
|
if len(dc_args) > 0:
|
|
850
|
-
dataclass += f
|
|
982
|
+
dataclass += f"({', '.join(dc_args)}) # type: ignore[literal-required]"
|
|
851
983
|
|
|
852
984
|
ctx.out.write(f"{dataclass}\n")
|
|
853
985
|
ctx.out.write(class_out.getvalue())
|
|
854
986
|
|
|
855
987
|
|
|
856
|
-
def
|
|
857
|
-
|
|
988
|
+
def _emit_generics(ctx: Context, generics: list[str]) -> None:
|
|
989
|
+
for generic in generics:
|
|
858
990
|
ctx.out.write(f'{generic} = typing.TypeVar("{generic}")\n')
|
|
859
991
|
ctx.out.write(f"{LINE_BREAK}{LINE_BREAK}")
|
|
860
992
|
|
|
@@ -892,13 +1024,22 @@ base_name_map = {
|
|
|
892
1024
|
|
|
893
1025
|
def refer_to(ctx: TrackingContext, stype: builder.SpecType) -> str:
|
|
894
1026
|
if isinstance(stype, builder.SpecTypeInstance):
|
|
895
|
-
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)
|
|
896
1033
|
|
|
897
1034
|
if stype.defn_type.is_base_type(builder.BaseTypeName.s_readonly_array):
|
|
898
|
-
assert len(
|
|
899
|
-
|
|
1035
|
+
assert len(params) == 1, "Read Only Array takes one parameter"
|
|
1036
|
+
return f"tuple[{params[0]}, ...]"
|
|
1037
|
+
|
|
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"
|
|
900
1041
|
|
|
901
|
-
return f"{refer_to(ctx, stype.defn_type)}[{params}]"
|
|
1042
|
+
return f"{refer_to(ctx, stype.defn_type)}[{', '.join(params)}]"
|
|
902
1043
|
|
|
903
1044
|
if isinstance(stype, builder.SpecTypeLiteralWrapper):
|
|
904
1045
|
return _emit_value(ctx, stype.value_type, stype.value)
|
|
@@ -924,23 +1065,21 @@ def refer_to(ctx: TrackingContext, stype: builder.SpecType) -> str:
|
|
|
924
1065
|
SpecEndpoint = builder.SpecEndpoint
|
|
925
1066
|
|
|
926
1067
|
|
|
927
|
-
def _route_identifier(endpoint: builder.SpecEndpoint) -> tuple[str, str, str]:
|
|
928
|
-
return (endpoint.path_dirname, endpoint.path_basename, endpoint.method)
|
|
929
|
-
|
|
930
|
-
|
|
931
1068
|
def _emit_routes(*, builder: builder.SpecBuilder, config: PythonConfig) -> None:
|
|
932
1069
|
for endpoint_root in builder.api_endpoints:
|
|
933
1070
|
endpoints: list[SpecEndpoint] = []
|
|
934
1071
|
output = config.routes_output.get(endpoint_root)
|
|
935
1072
|
if output is None:
|
|
936
1073
|
continue
|
|
1074
|
+
last_endpoint: SpecEndpoint | None = None
|
|
937
1075
|
for namespace in builder.namespaces.values():
|
|
938
1076
|
endpoint = namespace.endpoint
|
|
1077
|
+
last_endpoint = endpoint
|
|
939
1078
|
if endpoint is None:
|
|
940
1079
|
continue
|
|
941
|
-
if endpoint.
|
|
1080
|
+
if endpoint_root not in endpoint.path_per_api_endpoint:
|
|
942
1081
|
continue
|
|
943
|
-
if endpoint.function is None:
|
|
1082
|
+
if endpoint.path_per_api_endpoint[endpoint_root].function is None:
|
|
944
1083
|
continue
|
|
945
1084
|
|
|
946
1085
|
endpoints.append(endpoint)
|
|
@@ -953,25 +1092,38 @@ def _emit_routes(*, builder: builder.SpecBuilder, config: PythonConfig) -> None:
|
|
|
953
1092
|
from main.site.framework.types import StaticRouteType
|
|
954
1093
|
"""
|
|
955
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
|
+
|
|
956
1104
|
sorted_endpoints = sorted(endpoints, key=_route_identifier)
|
|
957
1105
|
|
|
958
|
-
assert len(endpoints) == len(
|
|
959
|
-
|
|
960
|
-
)
|
|
1106
|
+
assert len(endpoints) == len(set(map(_route_identifier, endpoints))), (
|
|
1107
|
+
"Endpoints are not unique"
|
|
1108
|
+
)
|
|
961
1109
|
|
|
962
1110
|
path_set = set()
|
|
963
1111
|
for endpoint in sorted_endpoints:
|
|
964
|
-
|
|
965
|
-
|
|
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(".")
|
|
966
1116
|
path = ".".join(func_bits[:-1])
|
|
967
1117
|
if path in path_set:
|
|
968
1118
|
continue
|
|
969
1119
|
path_set.add(path)
|
|
970
1120
|
static_out.write(f"import {path}\n")
|
|
971
1121
|
|
|
1122
|
+
assert last_endpoint is not None
|
|
1123
|
+
|
|
972
1124
|
static_out.write(
|
|
973
1125
|
f"""
|
|
974
|
-
ROUTE_PREFIX = "/{
|
|
1126
|
+
ROUTE_PREFIX = "/{last_endpoint.path_per_api_endpoint[endpoint_root].path_root}"
|
|
975
1127
|
|
|
976
1128
|
ROUTES: list[StaticRouteType] = [
|
|
977
1129
|
"""
|
|
@@ -985,20 +1137,21 @@ ROUTES: list[StaticRouteType] = [
|
|
|
985
1137
|
|
|
986
1138
|
from main.site.framework.types import DynamicRouteType
|
|
987
1139
|
|
|
988
|
-
ROUTE_PREFIX = "/{
|
|
1140
|
+
ROUTE_PREFIX = "/{last_endpoint.path_per_api_endpoint[endpoint_root].path_root}"
|
|
989
1141
|
|
|
990
1142
|
ROUTES: list[DynamicRouteType] = [
|
|
991
1143
|
"""
|
|
992
1144
|
)
|
|
993
1145
|
|
|
994
1146
|
for endpoint in sorted_endpoints:
|
|
1147
|
+
endpoint_function_path = endpoint.path_per_api_endpoint[endpoint_root]
|
|
995
1148
|
dynamic_out.write(
|
|
996
|
-
f'{INDENT}("{
|
|
1149
|
+
f'{INDENT}("{endpoint_function_path.path_dirname}/{endpoint_function_path.path_basename}", "{endpoint_function_path.function}", ["{endpoint.method.upper()}"]),\n'
|
|
997
1150
|
)
|
|
998
1151
|
|
|
999
|
-
assert
|
|
1152
|
+
assert endpoint_function_path.function
|
|
1000
1153
|
static_out.write(
|
|
1001
|
-
f'{INDENT}("{
|
|
1154
|
+
f'{INDENT}("{endpoint_function_path.path_dirname}/{endpoint_function_path.path_basename}", {endpoint_function_path.function}, ["{endpoint.method.upper()}"]),\n'
|
|
1002
1155
|
)
|
|
1003
1156
|
|
|
1004
1157
|
dynamic_out.write(f"{MODIFY_NOTICE}]\n")
|
|
@@ -1014,15 +1167,21 @@ def _emit_namespace_imports(
|
|
|
1014
1167
|
*,
|
|
1015
1168
|
out: io.StringIO,
|
|
1016
1169
|
namespaces: set[builder.SpecNamespace],
|
|
1017
|
-
from_namespace:
|
|
1170
|
+
from_namespace: builder.SpecNamespace | None,
|
|
1018
1171
|
config: PythonConfig,
|
|
1172
|
+
skip_non_sdk: bool = False,
|
|
1019
1173
|
) -> None:
|
|
1020
1174
|
for ns in sorted(
|
|
1021
1175
|
namespaces,
|
|
1022
1176
|
key=lambda name: _resolve_namespace_name(name),
|
|
1023
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
|
|
1024
1184
|
resolved = _resolve_namespace_name(ns)
|
|
1025
|
-
ref = _resolve_namespace_ref(ns)
|
|
1026
1185
|
if ns.endpoint is not None:
|
|
1027
1186
|
import_alias = "_".join(ns.path[2:]) + "_t"
|
|
1028
1187
|
out.write(
|
|
@@ -1046,8 +1205,8 @@ def _emit_id_source(*, builder: builder.SpecBuilder, config: PythonConfig) -> No
|
|
|
1046
1205
|
return None
|
|
1047
1206
|
enum_out = io.StringIO()
|
|
1048
1207
|
enum_out.write(f"{LINT_HEADER}{MODIFY_NOTICE}\n")
|
|
1049
|
-
enum_out.write("
|
|
1050
|
-
enum_out.write("from
|
|
1208
|
+
enum_out.write("import typing\n")
|
|
1209
|
+
enum_out.write("from enum import StrEnum\n")
|
|
1051
1210
|
|
|
1052
1211
|
ctx = TrackingContext()
|
|
1053
1212
|
# In this context the propername
|
|
@@ -1063,11 +1222,11 @@ def _emit_id_source(*, builder: builder.SpecBuilder, config: PythonConfig) -> No
|
|
|
1063
1222
|
known_keys = []
|
|
1064
1223
|
enum_out.write("\nENUM_MAP: dict[str, type[StrEnum]] = {\n")
|
|
1065
1224
|
for key in sorted(named_enums.keys()):
|
|
1066
|
-
enum_out.write(f'"{key}": {named_enums[key]},\n')
|
|
1067
|
-
known_keys.append(f'
|
|
1225
|
+
enum_out.write(f'{INDENT}"{key}": {named_enums[key]},\n')
|
|
1226
|
+
known_keys.append(f'"{key}"')
|
|
1068
1227
|
enum_out.write(f"}}\n{MODIFY_NOTICE}\n")
|
|
1069
1228
|
|
|
1070
|
-
enum_out.write(f"\nKnownEnumsType =
|
|
1229
|
+
enum_out.write(f"\nKnownEnumsType = typing.Literal[\n{INDENT}")
|
|
1071
1230
|
enum_out.write(f",\n{INDENT}".join(known_keys))
|
|
1072
1231
|
enum_out.write(f"\n]\n{MODIFY_NOTICE}\n")
|
|
1073
1232
|
|
|
@@ -1084,21 +1243,36 @@ def _emit_api_stubs(*, builder: builder.SpecBuilder, config: PythonConfig) -> No
|
|
|
1084
1243
|
|
|
1085
1244
|
if endpoint is None:
|
|
1086
1245
|
continue
|
|
1087
|
-
if endpoint.
|
|
1246
|
+
if endpoint_root not in endpoint.path_per_api_endpoint:
|
|
1088
1247
|
continue
|
|
1089
|
-
|
|
1248
|
+
|
|
1249
|
+
endpoint_function = endpoint.path_per_api_endpoint[endpoint_root].function
|
|
1250
|
+
if endpoint_function is None:
|
|
1090
1251
|
continue
|
|
1091
1252
|
|
|
1092
|
-
module_dir, file_name,
|
|
1253
|
+
module_dir, file_name, _func_name = endpoint_function.rsplit(".", 2)
|
|
1093
1254
|
module_path = os.path.abspath(module_dir.replace(".", "/"))
|
|
1094
1255
|
api_stub_file = f"{module_path}/{file_name}.py"
|
|
1095
1256
|
if os.path.isfile(api_stub_file):
|
|
1096
1257
|
continue
|
|
1097
|
-
_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
|
+
)
|
|
1098
1266
|
|
|
1099
1267
|
|
|
1100
1268
|
def _create_api_stub(
|
|
1101
|
-
|
|
1269
|
+
*,
|
|
1270
|
+
api_stub_file: str,
|
|
1271
|
+
file_name: str,
|
|
1272
|
+
endpoint: SpecEndpoint,
|
|
1273
|
+
config: PythonConfig,
|
|
1274
|
+
endpoint_root: str,
|
|
1275
|
+
top_namespace: str,
|
|
1102
1276
|
) -> None:
|
|
1103
1277
|
assert (
|
|
1104
1278
|
endpoint.method == builder.RouteMethod.post
|
|
@@ -1106,7 +1280,13 @@ def _create_api_stub(
|
|
|
1106
1280
|
or endpoint.method == builder.RouteMethod.delete
|
|
1107
1281
|
or endpoint.method == builder.RouteMethod.patch
|
|
1108
1282
|
)
|
|
1109
|
-
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
|
+
)
|
|
1110
1290
|
util.rewrite_file(api_stub_file, api_out.getvalue())
|
|
1111
1291
|
|
|
1112
1292
|
|
|
@@ -1115,15 +1295,22 @@ WRAP_ARGS_END = "\n"
|
|
|
1115
1295
|
|
|
1116
1296
|
|
|
1117
1297
|
def _create_api_function(
|
|
1118
|
-
|
|
1298
|
+
*,
|
|
1299
|
+
file_name: str,
|
|
1300
|
+
endpoint: SpecEndpoint,
|
|
1301
|
+
config: PythonConfig,
|
|
1302
|
+
endpoint_root: str,
|
|
1303
|
+
top_namespace: str,
|
|
1119
1304
|
) -> io.StringIO:
|
|
1305
|
+
endpoint_specific_path = endpoint.path_per_api_endpoint[endpoint_root]
|
|
1306
|
+
assert endpoint_specific_path is not None
|
|
1120
1307
|
api_out = io.StringIO()
|
|
1121
1308
|
python_api_type_root = f"{config.types_package}.api"
|
|
1122
|
-
dot_dirname =
|
|
1309
|
+
dot_dirname = endpoint_specific_path.path_dirname.replace("/", ".")
|
|
1123
1310
|
api_import = (
|
|
1124
1311
|
f"{python_api_type_root}.{dot_dirname}.{file_name}"
|
|
1125
1312
|
if dot_dirname != ""
|
|
1126
|
-
else f"{python_api_type_root}.{
|
|
1313
|
+
else f"{python_api_type_root}.{endpoint_specific_path.path_basename}"
|
|
1127
1314
|
)
|
|
1128
1315
|
|
|
1129
1316
|
if endpoint.method == builder.RouteMethod.post:
|
|
@@ -1135,7 +1322,20 @@ def _create_api_function(
|
|
|
1135
1322
|
elif endpoint.method == builder.RouteMethod.patch:
|
|
1136
1323
|
validated_method = "validated_patch"
|
|
1137
1324
|
|
|
1138
|
-
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
|
+
)
|
|
1139
1339
|
|
|
1140
1340
|
api_out.write(
|
|
1141
1341
|
f"""import {api_import} as api
|
|
@@ -1143,8 +1343,8 @@ from main.db.session import Session, SessionMaker
|
|
|
1143
1343
|
from main.site.decorators import APIError, APIResponse, {validated_method}
|
|
1144
1344
|
|
|
1145
1345
|
|
|
1146
|
-
@{validated_method}(api.
|
|
1147
|
-
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]:
|
|
1148
1348
|
with Session(client_sm) as session:
|
|
1149
1349
|
# return APIResponse(data=api.Data())
|
|
1150
1350
|
pass
|
|
@@ -1166,7 +1366,7 @@ def _emit_api_argument_lookup(
|
|
|
1166
1366
|
for endpoint_root in builder.api_endpoints:
|
|
1167
1367
|
routes_output = config.routes_output[endpoint_root]
|
|
1168
1368
|
|
|
1169
|
-
imports = ["import
|
|
1369
|
+
imports = ["import dataclasses"]
|
|
1170
1370
|
mappings = []
|
|
1171
1371
|
for namespace in sorted(
|
|
1172
1372
|
builder.namespaces.values(),
|
|
@@ -1176,11 +1376,11 @@ def _emit_api_argument_lookup(
|
|
|
1176
1376
|
|
|
1177
1377
|
if endpoint is None:
|
|
1178
1378
|
continue
|
|
1179
|
-
if endpoint.
|
|
1379
|
+
if endpoint_root not in endpoint.path_per_api_endpoint:
|
|
1180
1380
|
continue
|
|
1181
|
-
if endpoint.function is None:
|
|
1381
|
+
if endpoint.path_per_api_endpoint[endpoint_root].function is None:
|
|
1182
1382
|
continue
|
|
1183
|
-
if "Arguments" not in namespace.types:
|
|
1383
|
+
if "Arguments" not in namespace.types or "Data" not in namespace.types:
|
|
1184
1384
|
continue
|
|
1185
1385
|
|
|
1186
1386
|
import_alias = "_".join(namespace.path[1:])
|
|
@@ -1204,7 +1404,7 @@ def _emit_api_argument_lookup(
|
|
|
1204
1404
|
mapping += f"{INDENT}{INDENT}route_group={route_group},\n"
|
|
1205
1405
|
mapping += f"{INDENT}{INDENT}account_type={account_type},\n"
|
|
1206
1406
|
mapping += f"{INDENT}{INDENT}route={import_alias}.ENDPOINT_PATH,\n"
|
|
1207
|
-
mapping += f'{INDENT}{INDENT}handler="{endpoint.function}",\n'
|
|
1407
|
+
mapping += f'{INDENT}{INDENT}handler="{endpoint.path_per_api_endpoint[endpoint_root].function}",\n'
|
|
1208
1408
|
mapping += f"{INDENT}{INDENT}method={import_alias}.ENDPOINT_METHOD,\n"
|
|
1209
1409
|
mapping += f"{INDENT})"
|
|
1210
1410
|
mappings.append(mapping)
|
|
@@ -1215,9 +1415,6 @@ def _emit_api_argument_lookup(
|
|
|
1215
1415
|
argument_lookup_out.write(
|
|
1216
1416
|
f"""{LINE_BREAK.join(imports)}
|
|
1217
1417
|
|
|
1218
|
-
AT = typing.TypeVar("AT")
|
|
1219
|
-
DT = typing.TypeVar("DT")
|
|
1220
|
-
|
|
1221
1418
|
|
|
1222
1419
|
@dataclasses.dataclass(kw_only=True, frozen=True)
|
|
1223
1420
|
class ApiEndpointKey:
|
|
@@ -1226,7 +1423,7 @@ class ApiEndpointKey:
|
|
|
1226
1423
|
|
|
1227
1424
|
|
|
1228
1425
|
@dataclasses.dataclass(kw_only=True)
|
|
1229
|
-
class ApiEndpointSpec
|
|
1426
|
+
class ApiEndpointSpec[AT, DT]:
|
|
1230
1427
|
route: str
|
|
1231
1428
|
arguments_type: type[AT]
|
|
1232
1429
|
data_type: type[DT]
|
|
@@ -1256,10 +1453,10 @@ CLIENT_CLASS_IMPORTS = [
|
|
|
1256
1453
|
"import dataclasses",
|
|
1257
1454
|
]
|
|
1258
1455
|
ASYNC_BATCH_PROCESSOR_FILENAME = "async_batch_processor"
|
|
1259
|
-
|
|
1456
|
+
ASYNC_BATCH_PROCESSOR_BASE_IMPORTS = [
|
|
1260
1457
|
"import uuid",
|
|
1261
1458
|
"from abc import ABC, abstractmethod",
|
|
1262
|
-
"from pkgs.serialization_util
|
|
1459
|
+
"from pkgs.serialization_util import serialize_for_api",
|
|
1263
1460
|
]
|
|
1264
1461
|
|
|
1265
1462
|
|
|
@@ -1271,7 +1468,9 @@ def _emit_async_batch_processor(
|
|
|
1271
1468
|
|
|
1272
1469
|
async_batch_processor_out = io.StringIO()
|
|
1273
1470
|
ctx = Context(
|
|
1274
|
-
out=io.StringIO(),
|
|
1471
|
+
out=io.StringIO(),
|
|
1472
|
+
namespace=builder.SpecNamespace("async_batch_processor"),
|
|
1473
|
+
builder=spec_builder,
|
|
1275
1474
|
)
|
|
1276
1475
|
|
|
1277
1476
|
for namespace in sorted(
|
|
@@ -1292,8 +1491,11 @@ def _emit_async_batch_processor(
|
|
|
1292
1491
|
config=config,
|
|
1293
1492
|
)
|
|
1294
1493
|
|
|
1494
|
+
imports = ASYNC_BATCH_PROCESSOR_BASE_IMPORTS.copy()
|
|
1495
|
+
if ctx.use_dataclass:
|
|
1496
|
+
imports.append("import dataclasses")
|
|
1295
1497
|
async_batch_processor_out.write(
|
|
1296
|
-
f"""{LINE_BREAK.join(
|
|
1498
|
+
f"""{LINE_BREAK.join(imports)}
|
|
1297
1499
|
|
|
1298
1500
|
|
|
1299
1501
|
class AsyncBatchProcessorBase(ABC):
|
|
@@ -1321,7 +1523,11 @@ def _emit_client_class(
|
|
|
1321
1523
|
return
|
|
1322
1524
|
|
|
1323
1525
|
client_base_out = io.StringIO()
|
|
1324
|
-
ctx = Context(
|
|
1526
|
+
ctx = Context(
|
|
1527
|
+
out=io.StringIO(),
|
|
1528
|
+
builder=spec_builder,
|
|
1529
|
+
namespace=builder.SpecNamespace("client_base"),
|
|
1530
|
+
)
|
|
1325
1531
|
for namespace in sorted(
|
|
1326
1532
|
spec_builder.namespaces.values(),
|
|
1327
1533
|
key=lambda ns: _resolve_namespace_name(ns),
|
|
@@ -1338,6 +1544,7 @@ def _emit_client_class(
|
|
|
1338
1544
|
namespaces=ctx.namespaces,
|
|
1339
1545
|
from_namespace=None,
|
|
1340
1546
|
config=config,
|
|
1547
|
+
skip_non_sdk=True,
|
|
1341
1548
|
)
|
|
1342
1549
|
|
|
1343
1550
|
client_base_out.write(
|
|
@@ -1351,6 +1558,7 @@ class APIRequest:
|
|
|
1351
1558
|
method: str
|
|
1352
1559
|
endpoint: str
|
|
1353
1560
|
args: typing.Any
|
|
1561
|
+
request_options: {refer_to(ctx=ctx, stype=REQUEST_OPTIONS_STYPE)} | None = None
|
|
1354
1562
|
|
|
1355
1563
|
|
|
1356
1564
|
class ClientMethods(ABC):
|