UncountablePythonSDK 0.0.82__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 +22 -17
- 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.82.dist-info → uncountablepythonsdk-0.0.132.dist-info}/WHEEL +1 -1
- UncountablePythonSDK-0.0.82.dist-info/METADATA +0 -60
- UncountablePythonSDK-0.0.82.dist-info/RECORD +0 -292
- docs/quickstart.md +0 -19
- {UncountablePythonSDK-0.0.82.dist-info → uncountablepythonsdk-0.0.132.dist-info}/top_level.txt +0 -0
pkgs/type_spec/config.py
CHANGED
|
@@ -4,6 +4,7 @@ from dataclasses import dataclass
|
|
|
4
4
|
from typing import Self, TypeVar
|
|
5
5
|
|
|
6
6
|
from pkgs.serialization import yaml
|
|
7
|
+
from pkgs.type_spec.builder import APIEndpointInfo, EndpointKey
|
|
7
8
|
|
|
8
9
|
ConfigValueType = str | None | Mapping[str, str | None] | list[str]
|
|
9
10
|
|
|
@@ -19,6 +20,22 @@ def _parse_string_lookup(
|
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
|
|
23
|
+
VT = TypeVar("VT")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _parse_data_lookup(
|
|
27
|
+
key: str,
|
|
28
|
+
raw_value: ConfigValueType,
|
|
29
|
+
conv_func: type[VT],
|
|
30
|
+
) -> dict[str, VT]:
|
|
31
|
+
assert isinstance(raw_value, dict), f"{key} must be key/values"
|
|
32
|
+
return {
|
|
33
|
+
k: conv_func(**v)
|
|
34
|
+
for k, v in raw_value.items()
|
|
35
|
+
if v is not None and isinstance(v, dict)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
22
39
|
@dataclass(kw_only=True)
|
|
23
40
|
class BaseLanguageConfig:
|
|
24
41
|
types_output: (
|
|
@@ -31,18 +48,28 @@ class BaseLanguageConfig:
|
|
|
31
48
|
|
|
32
49
|
@dataclass(kw_only=True)
|
|
33
50
|
class TypeScriptConfig(BaseLanguageConfig):
|
|
34
|
-
|
|
51
|
+
endpoint_to_routes_output: dict[
|
|
52
|
+
EndpointKey, str
|
|
53
|
+
] # folder for generate route files will be located.
|
|
35
54
|
type_info_output: str # folder for generated type info files
|
|
36
55
|
id_source_output: str | None = None # folder for emitted id source maps.
|
|
56
|
+
endpoint_to_frontend_app_type: dict[
|
|
57
|
+
str, str
|
|
58
|
+
] # map from api_endpoint to frontend app type
|
|
37
59
|
|
|
38
60
|
def __post_init__(self: Self) -> None:
|
|
39
|
-
self.
|
|
61
|
+
self.endpoint_to_routes_output = self.endpoint_to_routes_output
|
|
40
62
|
self.type_info_output = os.path.abspath(self.type_info_output)
|
|
41
63
|
self.id_source_output = (
|
|
42
64
|
os.path.abspath(self.id_source_output)
|
|
43
65
|
if self.id_source_output is not None
|
|
44
66
|
else None
|
|
45
67
|
)
|
|
68
|
+
self.endpoint_to_frontend_app_type = _parse_string_lookup(
|
|
69
|
+
"typescript_endpoint_to_frontend_app_type",
|
|
70
|
+
self.endpoint_to_frontend_app_type,
|
|
71
|
+
lambda x: x,
|
|
72
|
+
)
|
|
46
73
|
|
|
47
74
|
|
|
48
75
|
@dataclass(kw_only=True)
|
|
@@ -59,6 +86,7 @@ class PythonConfig(BaseLanguageConfig):
|
|
|
59
86
|
emit_client_class: bool = False # emit the base class for the api client
|
|
60
87
|
all_named_type_exports: bool = False # emit __all__ for all named type exports
|
|
61
88
|
sdk_endpoints_only: bool = False # only emit is_sdk endpoints
|
|
89
|
+
type_info_output: str | None = None # folder for generated type info files
|
|
62
90
|
|
|
63
91
|
def __post_init__(self: Self) -> None:
|
|
64
92
|
self.routes_output = _parse_string_lookup(
|
|
@@ -70,6 +98,9 @@ class PythonConfig(BaseLanguageConfig):
|
|
|
70
98
|
else None
|
|
71
99
|
)
|
|
72
100
|
|
|
101
|
+
if self.type_info_output is not None:
|
|
102
|
+
self.type_info_output = os.path.abspath(self.type_info_output)
|
|
103
|
+
|
|
73
104
|
|
|
74
105
|
@dataclass(kw_only=True)
|
|
75
106
|
class OpenAPIConfig(BaseLanguageConfig):
|
|
@@ -88,17 +119,17 @@ class OpenAPIConfig(BaseLanguageConfig):
|
|
|
88
119
|
class Config:
|
|
89
120
|
top_namespace: str
|
|
90
121
|
type_spec_types: list[str] # folders containing the yaml type spec definitions
|
|
91
|
-
api_endpoint: dict[str,
|
|
122
|
+
api_endpoint: dict[str, APIEndpointInfo]
|
|
92
123
|
# languages
|
|
93
124
|
typescript: TypeScriptConfig | None
|
|
94
125
|
python: PythonConfig
|
|
95
126
|
open_api: OpenAPIConfig | None
|
|
96
127
|
|
|
97
128
|
|
|
98
|
-
|
|
129
|
+
T = TypeVar("T")
|
|
99
130
|
|
|
100
131
|
|
|
101
|
-
def _parse_language(config_class: type[
|
|
132
|
+
def _parse_language(config_class: type[T], raw_value: ConfigValueType) -> T:
|
|
102
133
|
assert isinstance(raw_value, dict), "expecting language config to have key/values."
|
|
103
134
|
return config_class(**raw_value)
|
|
104
135
|
|
|
@@ -113,8 +144,10 @@ def parse_yaml_config(config_file: str) -> Config:
|
|
|
113
144
|
)
|
|
114
145
|
type_spec_types = [os.path.abspath(folder) for folder in raw_type_spec_types]
|
|
115
146
|
|
|
116
|
-
api_endpoint =
|
|
117
|
-
"api_endpoint",
|
|
147
|
+
api_endpoint = _parse_data_lookup(
|
|
148
|
+
"api_endpoint",
|
|
149
|
+
raw_config.get("api_endpoint", {}),
|
|
150
|
+
APIEndpointInfo,
|
|
118
151
|
)
|
|
119
152
|
|
|
120
153
|
raw_typescript = raw_config.get("typescript")
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from . import builder
|
|
6
|
+
from .builder_types import CrossOutputPaths
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_python_stub_file_path(
|
|
10
|
+
function_name: str | None,
|
|
11
|
+
) -> str | None:
|
|
12
|
+
if function_name is None:
|
|
13
|
+
return None
|
|
14
|
+
module_dir, file_name, _func_name = function_name.rsplit(".", 2)
|
|
15
|
+
module_path = os.path.relpath(module_dir.replace(".", "/"))
|
|
16
|
+
api_stub_file = f"{module_path}/{file_name}.py"
|
|
17
|
+
return api_stub_file
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_python_api_file_path(
|
|
21
|
+
cross_output_paths: CrossOutputPaths,
|
|
22
|
+
namespace: builder.SpecNamespace,
|
|
23
|
+
) -> str:
|
|
24
|
+
return f"{cross_output_paths.python_types_output}/{'/'.join(namespace.path)}{'' if len(namespace.path) > 1 else '_t'}.py"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_typescript_api_file_path(
|
|
28
|
+
cross_output_paths: CrossOutputPaths,
|
|
29
|
+
namespace: builder.SpecNamespace,
|
|
30
|
+
endpoint_key: builder.EndpointKey,
|
|
31
|
+
) -> str:
|
|
32
|
+
return f"{cross_output_paths.typescript_routes_output_by_endpoint[endpoint_key]}/{'/'.join(namespace.path)}.tsx"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_yaml_api_file_path(
|
|
36
|
+
cross_output_paths: CrossOutputPaths,
|
|
37
|
+
namespace: builder.SpecNamespace,
|
|
38
|
+
) -> str:
|
|
39
|
+
abs_path = next(
|
|
40
|
+
(
|
|
41
|
+
path
|
|
42
|
+
for path in cross_output_paths.typespec_files_input
|
|
43
|
+
if (
|
|
44
|
+
namespace.endpoint is None
|
|
45
|
+
or namespace.endpoint.default_endpoint_key in path
|
|
46
|
+
)
|
|
47
|
+
),
|
|
48
|
+
cross_output_paths.typespec_files_input[0],
|
|
49
|
+
)
|
|
50
|
+
return f"{os.path.relpath(abs_path)}/{'/'.join(namespace.path)}.yaml"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_return_to_root_path(path: str) -> str:
|
|
54
|
+
return "../" * (path.count("/"))
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_path_links(
|
|
58
|
+
cross_output_paths: CrossOutputPaths | None,
|
|
59
|
+
namespace: builder.SpecNamespace,
|
|
60
|
+
*,
|
|
61
|
+
current_path_type: str,
|
|
62
|
+
endpoint: builder.SpecEndpoint,
|
|
63
|
+
) -> str:
|
|
64
|
+
if cross_output_paths is None:
|
|
65
|
+
return ""
|
|
66
|
+
|
|
67
|
+
api_paths = {
|
|
68
|
+
"Python": get_python_api_file_path(cross_output_paths, namespace),
|
|
69
|
+
"TypeScript": get_typescript_api_file_path(
|
|
70
|
+
cross_output_paths, namespace, endpoint.default_endpoint_key
|
|
71
|
+
),
|
|
72
|
+
"YAML": get_yaml_api_file_path(cross_output_paths, namespace),
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
assert current_path_type in api_paths
|
|
76
|
+
|
|
77
|
+
comment_prefix = "#"
|
|
78
|
+
if current_path_type == "TypeScript":
|
|
79
|
+
comment_prefix = "//"
|
|
80
|
+
|
|
81
|
+
return_to_root_path = get_return_to_root_path(api_paths[current_path_type])
|
|
82
|
+
del api_paths[current_path_type]
|
|
83
|
+
|
|
84
|
+
paths_string = ""
|
|
85
|
+
for path_name, path in api_paths.items():
|
|
86
|
+
paths_string += (
|
|
87
|
+
f"{comment_prefix} {path_name}: file://./{return_to_root_path}{path}\n"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
if namespace.endpoint is not None:
|
|
91
|
+
for (
|
|
92
|
+
endpoint_key,
|
|
93
|
+
path_specific_endpoint,
|
|
94
|
+
) in namespace.endpoint.path_per_api_endpoint.items():
|
|
95
|
+
path_from_root = get_python_stub_file_path(path_specific_endpoint.function)
|
|
96
|
+
if path_from_root is None:
|
|
97
|
+
continue
|
|
98
|
+
paths_string += f"{comment_prefix} Implementation for {endpoint_key}: file://./{return_to_root_path}{path_from_root}\n"
|
|
99
|
+
return paths_string
|
pkgs/type_spec/emit_open_api.py
CHANGED
|
@@ -7,10 +7,11 @@ WORK-IN-PROGRESS, DON'T USE!
|
|
|
7
7
|
import dataclasses
|
|
8
8
|
import json
|
|
9
9
|
import re
|
|
10
|
-
from
|
|
10
|
+
from enum import StrEnum
|
|
11
|
+
from typing import Collection, assert_never, cast
|
|
11
12
|
|
|
12
13
|
from pkgs.serialization import yaml
|
|
13
|
-
from pkgs.serialization_util
|
|
14
|
+
from pkgs.serialization_util import serialize_for_api
|
|
14
15
|
|
|
15
16
|
from . import builder, util
|
|
16
17
|
from .builder import EndpointGuideKey, RootGuideKey
|
|
@@ -62,6 +63,10 @@ base_name_map = {
|
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
|
|
66
|
+
class OpenAPIDefaultBehavior(StrEnum):
|
|
67
|
+
OPTIONAL_WITH_DEFAULT = "optional_with_default"
|
|
68
|
+
|
|
69
|
+
|
|
65
70
|
def _rewrite_with_notice(
|
|
66
71
|
file_path: str, file_content: str, *, notice: str = MODIFY_NOTICE
|
|
67
72
|
) -> bool:
|
|
@@ -126,7 +131,11 @@ def emit_open_api(builder: builder.SpecBuilder, *, config: OpenAPIConfig) -> Non
|
|
|
126
131
|
for namespace in sorted(builder.namespaces.values(), key=lambda ns: ns.name):
|
|
127
132
|
ctx = EmitOpenAPIContext(namespace=namespace)
|
|
128
133
|
|
|
129
|
-
if
|
|
134
|
+
if (
|
|
135
|
+
ctx.namespace.endpoint is not None
|
|
136
|
+
and ctx.namespace.endpoint.stability_level
|
|
137
|
+
== EmitOpenAPIStabilityLevel.draft
|
|
138
|
+
):
|
|
130
139
|
continue
|
|
131
140
|
|
|
132
141
|
if ctx.namespace.name == "base":
|
|
@@ -252,18 +261,35 @@ def _emit_endpoint_parameters(
|
|
|
252
261
|
} | _emit_endpoint_parameter_examples(examples)
|
|
253
262
|
|
|
254
263
|
|
|
255
|
-
def
|
|
256
|
-
if
|
|
257
|
-
return {"x-beta": True}
|
|
258
|
-
return {}
|
|
264
|
+
def _emit_endpoint_deprecated(deprecated: bool) -> DictApiSchema:
|
|
265
|
+
return {"deprecated": True} if deprecated else {}
|
|
259
266
|
|
|
260
267
|
|
|
261
268
|
def _emit_stability_level(
|
|
262
269
|
stability_level: EmitOpenAPIStabilityLevel | None,
|
|
263
270
|
) -> DictApiSchema:
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
271
|
+
stability_info: dict[str, ApiSchema] = {}
|
|
272
|
+
resolved_stability_level = (
|
|
273
|
+
stability_level
|
|
274
|
+
if stability_level is not None
|
|
275
|
+
else EmitOpenAPIStabilityLevel.stable
|
|
276
|
+
)
|
|
277
|
+
stability_info["x-stability-level"] = str(resolved_stability_level)
|
|
278
|
+
match resolved_stability_level:
|
|
279
|
+
case EmitOpenAPIStabilityLevel.draft:
|
|
280
|
+
stability_info["x-beta"] = True
|
|
281
|
+
case EmitOpenAPIStabilityLevel.beta:
|
|
282
|
+
stability_info["x-badges"] = [
|
|
283
|
+
{
|
|
284
|
+
"name": "Beta",
|
|
285
|
+
"color": "DarkOrange",
|
|
286
|
+
}
|
|
287
|
+
]
|
|
288
|
+
case EmitOpenAPIStabilityLevel.stable:
|
|
289
|
+
pass
|
|
290
|
+
case _:
|
|
291
|
+
assert_never(stability_level)
|
|
292
|
+
return stability_info
|
|
267
293
|
|
|
268
294
|
|
|
269
295
|
def _emit_endpoint_request_body(
|
|
@@ -313,18 +339,57 @@ def _emit_endpoint_response_examples(
|
|
|
313
339
|
return {"examples": response_examples}
|
|
314
340
|
|
|
315
341
|
|
|
342
|
+
def _create_warning_banner(api_type: str, message: str) -> str:
|
|
343
|
+
return (
|
|
344
|
+
f'<div style="background-color: #fff3cd; border: 1px solid #ffeaa7; '
|
|
345
|
+
f'border-radius: 4px; padding: 12px; margin-bottom: 16px;">'
|
|
346
|
+
f"<strong>⚠️ {api_type} API:</strong> {message}"
|
|
347
|
+
f"</div>"
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def _get_stability_warning(
|
|
352
|
+
stability_level: EmitOpenAPIStabilityLevel | None,
|
|
353
|
+
) -> str:
|
|
354
|
+
resolved_stability_level = (
|
|
355
|
+
stability_level
|
|
356
|
+
if stability_level is not None
|
|
357
|
+
else EmitOpenAPIStabilityLevel.stable
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
match resolved_stability_level:
|
|
361
|
+
case EmitOpenAPIStabilityLevel.draft:
|
|
362
|
+
return _create_warning_banner(
|
|
363
|
+
"Draft",
|
|
364
|
+
"This endpoint is in draft status and may change significantly. Not recommended for production use.",
|
|
365
|
+
)
|
|
366
|
+
case EmitOpenAPIStabilityLevel.beta:
|
|
367
|
+
return _create_warning_banner(
|
|
368
|
+
"Beta",
|
|
369
|
+
"This endpoint is in beta and its required parameters may change. Use with caution in production environments.",
|
|
370
|
+
)
|
|
371
|
+
case EmitOpenAPIStabilityLevel.stable:
|
|
372
|
+
return ""
|
|
373
|
+
|
|
374
|
+
|
|
316
375
|
def _emit_endpoint_description(
|
|
317
|
-
description: str,
|
|
376
|
+
description: str,
|
|
377
|
+
guides: list[EmitOpenAPIGuide],
|
|
378
|
+
stability_level: EmitOpenAPIStabilityLevel | None = None,
|
|
318
379
|
) -> dict[str, str]:
|
|
380
|
+
stability_warning = _get_stability_warning(stability_level)
|
|
381
|
+
|
|
319
382
|
full_guides = "<br/>".join([
|
|
320
383
|
_write_guide_as_html(guide, is_open=False)
|
|
321
384
|
for guide in sorted(guides, key=lambda g: g.ref_name)
|
|
322
385
|
])
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
386
|
+
|
|
387
|
+
full_description_parts = [
|
|
388
|
+
part for part in [stability_warning, description, full_guides] if part
|
|
389
|
+
]
|
|
390
|
+
full_description = "<br/>".join(full_description_parts)
|
|
391
|
+
|
|
392
|
+
return {"description": full_description}
|
|
328
393
|
|
|
329
394
|
|
|
330
395
|
def _emit_namespace(
|
|
@@ -359,8 +424,10 @@ def _emit_namespace(
|
|
|
359
424
|
"tags": endpoint.tags,
|
|
360
425
|
"summary": endpoint.summary,
|
|
361
426
|
}
|
|
362
|
-
|
|
|
363
|
-
|
|
|
427
|
+
| _emit_endpoint_deprecated(endpoint.deprecated)
|
|
428
|
+
| _emit_endpoint_description(
|
|
429
|
+
endpoint.description, ctx.endpoint.guides, endpoint.stability_level
|
|
430
|
+
)
|
|
364
431
|
| _emit_stability_level(endpoint.stability_level)
|
|
365
432
|
| _emit_endpoint_parameters(endpoint, argument_type, ctx.endpoint.examples)
|
|
366
433
|
| _emit_endpoint_request_body(
|
|
@@ -421,6 +488,18 @@ def _emit_namespace(
|
|
|
421
488
|
_rewrite_with_notice(path, yaml.dumps(oa_namespace, sort_keys=False))
|
|
422
489
|
|
|
423
490
|
|
|
491
|
+
def _get_openapi_default_behavior(
|
|
492
|
+
prop: builder.SpecProperty,
|
|
493
|
+
) -> OpenAPIDefaultBehavior | None:
|
|
494
|
+
if prop.ext_info is None or prop.ext_info.get("open_api") is None:
|
|
495
|
+
return None
|
|
496
|
+
value_passed = prop.ext_info["open_api"].get("default_required_behavior")
|
|
497
|
+
if value_passed is None:
|
|
498
|
+
return None
|
|
499
|
+
assert isinstance(value_passed, str)
|
|
500
|
+
return OpenAPIDefaultBehavior(value_passed)
|
|
501
|
+
|
|
502
|
+
|
|
424
503
|
def _emit_type(
|
|
425
504
|
ctx: EmitOpenAPIContext,
|
|
426
505
|
stype: builder.SpecType,
|
|
@@ -446,8 +525,18 @@ def _emit_type(
|
|
|
446
525
|
return
|
|
447
526
|
|
|
448
527
|
if isinstance(stype, builder.SpecTypeDefnUnion):
|
|
449
|
-
|
|
450
|
-
|
|
528
|
+
converted_discriminator_map: dict[str, OpenAPIRefType] = dict()
|
|
529
|
+
if stype.discriminator_map is not None:
|
|
530
|
+
for discriminator_value, base_type in stype.discriminator_map.items():
|
|
531
|
+
converted_base_type = open_api_type(ctx, base_type, config=config)
|
|
532
|
+
assert isinstance(converted_base_type, OpenAPIRefType)
|
|
533
|
+
converted_discriminator_map[discriminator_value] = converted_base_type
|
|
534
|
+
ctx.types[stype.name] = OpenAPIUnionType(
|
|
535
|
+
[open_api_type(ctx, p, config=config) for p in stype.types],
|
|
536
|
+
discriminator=stype.discriminator,
|
|
537
|
+
discriminator_map=converted_discriminator_map
|
|
538
|
+
if stype.discriminator_map is not None
|
|
539
|
+
else None,
|
|
451
540
|
)
|
|
452
541
|
return
|
|
453
542
|
|
|
@@ -484,6 +573,16 @@ def _emit_type(
|
|
|
484
573
|
# arguments, thus treat like extant==missing
|
|
485
574
|
# IMPROVE: if we can decide they are meant as output instead, then
|
|
486
575
|
# they should be marked as required
|
|
576
|
+
openapi_default_beahvior = _get_openapi_default_behavior(prop)
|
|
577
|
+
match openapi_default_beahvior:
|
|
578
|
+
case None:
|
|
579
|
+
pass
|
|
580
|
+
case OpenAPIDefaultBehavior.OPTIONAL_WITH_DEFAULT:
|
|
581
|
+
ref_type.nullable = True
|
|
582
|
+
assert prop.default is not None, (
|
|
583
|
+
"optional_with_default requires default"
|
|
584
|
+
)
|
|
585
|
+
ref_type.default = prop.default
|
|
487
586
|
properties[prop_name] = ref_type
|
|
488
587
|
elif prop.extant == builder.PropertyExtant.missing:
|
|
489
588
|
# Unlike optional below, missing does not imply null is possible. They
|
|
@@ -511,18 +610,6 @@ def _emit_type(
|
|
|
511
610
|
ctx.types[stype.name] = final_type
|
|
512
611
|
|
|
513
612
|
|
|
514
|
-
def _emit_constant(ctx: EmitOpenAPIContext, sconst: builder.SpecConstant) -> None:
|
|
515
|
-
if sconst.value_type.is_base_type(builder.BaseTypeName.s_string):
|
|
516
|
-
value = util.encode_common_string(cast(str, sconst.value))
|
|
517
|
-
elif sconst.value_type.is_base_type(builder.BaseTypeName.s_integer):
|
|
518
|
-
value = str(sconst.value)
|
|
519
|
-
else:
|
|
520
|
-
raise Exception("invalid constant type", sconst.name)
|
|
521
|
-
|
|
522
|
-
const_name = sconst.name.upper()
|
|
523
|
-
print("_emit_constant", value, const_name)
|
|
524
|
-
|
|
525
|
-
|
|
526
613
|
def _emit_endpoint(
|
|
527
614
|
gctx: EmitOpenAPIGlobalContext,
|
|
528
615
|
ctx: EmitOpenAPIContext,
|
|
@@ -563,7 +650,7 @@ def _emit_endpoint(
|
|
|
563
650
|
ep = namespace.endpoint
|
|
564
651
|
gctx.paths.append(
|
|
565
652
|
EmitOpenAPIPath(
|
|
566
|
-
path=f"/{ep.
|
|
653
|
+
path=f"/{ep.resolved_path}",
|
|
567
654
|
ref=ref_path,
|
|
568
655
|
)
|
|
569
656
|
)
|
|
@@ -579,7 +666,7 @@ def _emit_endpoint(
|
|
|
579
666
|
tags=[tag_name],
|
|
580
667
|
summary=f"{'/'.join(namespace.path[path_cutoff:])}",
|
|
581
668
|
description=description,
|
|
582
|
-
|
|
669
|
+
deprecated=namespace.endpoint.deprecated,
|
|
583
670
|
stability_level=namespace.endpoint.stability_level,
|
|
584
671
|
examples=[
|
|
585
672
|
EmitOpenAPIEndpointExample(
|
|
@@ -7,16 +7,16 @@ WORK-IN-PROGRESS, DON'T USE!
|
|
|
7
7
|
from collections import defaultdict
|
|
8
8
|
from dataclasses import dataclass, field
|
|
9
9
|
|
|
10
|
-
from pkgs.serialization_util
|
|
10
|
+
from pkgs.serialization_util import JsonValue
|
|
11
11
|
|
|
12
12
|
from . import builder
|
|
13
13
|
from .open_api_util import OpenAPIType
|
|
14
14
|
|
|
15
15
|
MODIFY_NOTICE = "# DO NOT MODIFY -- This file is generated by type_spec"
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
GlobalContextInfo = dict[str, str | dict[str, str]]
|
|
18
|
+
TagGroupToNamedTags = dict[str, str | list[str]]
|
|
19
|
+
TagPathsToRef = dict[str, dict[str, str]]
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
@dataclass
|
|
@@ -82,7 +82,7 @@ class EmitOpenAPIEndpoint:
|
|
|
82
82
|
tags: list[str]
|
|
83
83
|
summary: str
|
|
84
84
|
description: str
|
|
85
|
-
|
|
85
|
+
deprecated: bool
|
|
86
86
|
stability_level: EmitOpenAPIStabilityLevel | None
|
|
87
87
|
examples: list[EmitOpenAPIEndpointExample]
|
|
88
88
|
guides: list[EmitOpenAPIGuide]
|