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/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):
|
|
@@ -86,18 +117,19 @@ class OpenAPIConfig(BaseLanguageConfig):
|
|
|
86
117
|
|
|
87
118
|
@dataclass(kw_only=True)
|
|
88
119
|
class Config:
|
|
120
|
+
top_namespace: str
|
|
89
121
|
type_spec_types: list[str] # folders containing the yaml type spec definitions
|
|
90
|
-
api_endpoint: dict[str,
|
|
122
|
+
api_endpoint: dict[str, APIEndpointInfo]
|
|
91
123
|
# languages
|
|
92
124
|
typescript: TypeScriptConfig | None
|
|
93
125
|
python: PythonConfig
|
|
94
126
|
open_api: OpenAPIConfig | None
|
|
95
127
|
|
|
96
128
|
|
|
97
|
-
|
|
129
|
+
T = TypeVar("T")
|
|
98
130
|
|
|
99
131
|
|
|
100
|
-
def _parse_language(config_class: type[
|
|
132
|
+
def _parse_language(config_class: type[T], raw_value: ConfigValueType) -> T:
|
|
101
133
|
assert isinstance(raw_value, dict), "expecting language config to have key/values."
|
|
102
134
|
return config_class(**raw_value)
|
|
103
135
|
|
|
@@ -107,13 +139,15 @@ def parse_yaml_config(config_file: str) -> Config:
|
|
|
107
139
|
raw_config: dict[str, ConfigValueType] = yaml.safe_load(input)
|
|
108
140
|
|
|
109
141
|
raw_type_spec_types = raw_config["type_spec_types"]
|
|
110
|
-
assert isinstance(
|
|
111
|
-
|
|
112
|
-
)
|
|
142
|
+
assert isinstance(raw_type_spec_types, list), (
|
|
143
|
+
"type_spec_types, must be a list of folders"
|
|
144
|
+
)
|
|
113
145
|
type_spec_types = [os.path.abspath(folder) for folder in raw_type_spec_types]
|
|
114
146
|
|
|
115
|
-
api_endpoint =
|
|
116
|
-
"api_endpoint",
|
|
147
|
+
api_endpoint = _parse_data_lookup(
|
|
148
|
+
"api_endpoint",
|
|
149
|
+
raw_config.get("api_endpoint", {}),
|
|
150
|
+
APIEndpointInfo,
|
|
117
151
|
)
|
|
118
152
|
|
|
119
153
|
raw_typescript = raw_config.get("typescript")
|
|
@@ -125,10 +159,16 @@ def parse_yaml_config(config_file: str) -> Config:
|
|
|
125
159
|
python = _parse_language(PythonConfig, raw_config["python"])
|
|
126
160
|
raw_open_api = raw_config.get("open_api")
|
|
127
161
|
open_api = (
|
|
128
|
-
_parse_language(OpenAPIConfig, raw_open_api)
|
|
162
|
+
_parse_language(OpenAPIConfig, raw_open_api)
|
|
163
|
+
if raw_open_api is not None
|
|
164
|
+
else None
|
|
129
165
|
)
|
|
130
166
|
|
|
167
|
+
top_namespace = raw_config["top_namespace"]
|
|
168
|
+
assert isinstance(top_namespace, str)
|
|
169
|
+
|
|
131
170
|
return Config(
|
|
171
|
+
top_namespace=top_namespace,
|
|
132
172
|
type_spec_types=type_spec_types,
|
|
133
173
|
api_endpoint=api_endpoint,
|
|
134
174
|
typescript=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_io_ts.py
CHANGED
|
@@ -125,7 +125,7 @@ def refer_to_io_ts(
|
|
|
125
125
|
spec = refer_to_io_ts(ctx, stype.parameters[0])
|
|
126
126
|
return f"IO.array({spec})"
|
|
127
127
|
if stype.defn_type.name == builder.BaseTypeName.s_union:
|
|
128
|
-
return f
|
|
128
|
+
return f"IO.union([{', '.join([refer_to_io_ts(ctx, p) for p in stype.parameters])}])"
|
|
129
129
|
if stype.defn_type.name == builder.BaseTypeName.s_optional:
|
|
130
130
|
return f"IO.optional({refer_to_io_ts(ctx, stype.parameters[0])})"
|
|
131
131
|
if stype.defn_type.name == builder.BaseTypeName.s_tuple:
|
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(
|
|
@@ -286,7 +312,9 @@ def _emit_endpoint_request_body(
|
|
|
286
312
|
"type": "object",
|
|
287
313
|
"title": "Body",
|
|
288
314
|
"required": ["data"],
|
|
289
|
-
"properties": {
|
|
315
|
+
"properties": {
|
|
316
|
+
"data": {"$ref": "#/components/schema/Arguments"}
|
|
317
|
+
},
|
|
290
318
|
}
|
|
291
319
|
}
|
|
292
320
|
| _emit_endpoint_argument_examples(examples)
|
|
@@ -311,18 +339,57 @@ def _emit_endpoint_response_examples(
|
|
|
311
339
|
return {"examples": response_examples}
|
|
312
340
|
|
|
313
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
|
+
|
|
314
375
|
def _emit_endpoint_description(
|
|
315
|
-
description: str,
|
|
376
|
+
description: str,
|
|
377
|
+
guides: list[EmitOpenAPIGuide],
|
|
378
|
+
stability_level: EmitOpenAPIStabilityLevel | None = None,
|
|
316
379
|
) -> dict[str, str]:
|
|
380
|
+
stability_warning = _get_stability_warning(stability_level)
|
|
381
|
+
|
|
317
382
|
full_guides = "<br/>".join([
|
|
318
383
|
_write_guide_as_html(guide, is_open=False)
|
|
319
384
|
for guide in sorted(guides, key=lambda g: g.ref_name)
|
|
320
385
|
])
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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}
|
|
326
393
|
|
|
327
394
|
|
|
328
395
|
def _emit_namespace(
|
|
@@ -357,11 +424,15 @@ def _emit_namespace(
|
|
|
357
424
|
"tags": endpoint.tags,
|
|
358
425
|
"summary": endpoint.summary,
|
|
359
426
|
}
|
|
360
|
-
|
|
|
361
|
-
|
|
|
427
|
+
| _emit_endpoint_deprecated(endpoint.deprecated)
|
|
428
|
+
| _emit_endpoint_description(
|
|
429
|
+
endpoint.description, ctx.endpoint.guides, endpoint.stability_level
|
|
430
|
+
)
|
|
362
431
|
| _emit_stability_level(endpoint.stability_level)
|
|
363
432
|
| _emit_endpoint_parameters(endpoint, argument_type, ctx.endpoint.examples)
|
|
364
|
-
| _emit_endpoint_request_body(
|
|
433
|
+
| _emit_endpoint_request_body(
|
|
434
|
+
endpoint, argument_type, ctx.endpoint.examples
|
|
435
|
+
)
|
|
365
436
|
| {
|
|
366
437
|
"responses": {
|
|
367
438
|
"200": {
|
|
@@ -417,6 +488,18 @@ def _emit_namespace(
|
|
|
417
488
|
_rewrite_with_notice(path, yaml.dumps(oa_namespace, sort_keys=False))
|
|
418
489
|
|
|
419
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
|
+
|
|
420
503
|
def _emit_type(
|
|
421
504
|
ctx: EmitOpenAPIContext,
|
|
422
505
|
stype: builder.SpecType,
|
|
@@ -442,8 +525,18 @@ def _emit_type(
|
|
|
442
525
|
return
|
|
443
526
|
|
|
444
527
|
if isinstance(stype, builder.SpecTypeDefnUnion):
|
|
445
|
-
|
|
446
|
-
|
|
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,
|
|
447
540
|
)
|
|
448
541
|
return
|
|
449
542
|
|
|
@@ -480,6 +573,16 @@ def _emit_type(
|
|
|
480
573
|
# arguments, thus treat like extant==missing
|
|
481
574
|
# IMPROVE: if we can decide they are meant as output instead, then
|
|
482
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
|
|
483
586
|
properties[prop_name] = ref_type
|
|
484
587
|
elif prop.extant == builder.PropertyExtant.missing:
|
|
485
588
|
# Unlike optional below, missing does not imply null is possible. They
|
|
@@ -507,18 +610,6 @@ def _emit_type(
|
|
|
507
610
|
ctx.types[stype.name] = final_type
|
|
508
611
|
|
|
509
612
|
|
|
510
|
-
def _emit_constant(ctx: EmitOpenAPIContext, sconst: builder.SpecConstant) -> None:
|
|
511
|
-
if sconst.value_type.is_base_type(builder.BaseTypeName.s_string):
|
|
512
|
-
value = util.encode_common_string(cast(str, sconst.value))
|
|
513
|
-
elif sconst.value_type.is_base_type(builder.BaseTypeName.s_integer):
|
|
514
|
-
value = str(sconst.value)
|
|
515
|
-
else:
|
|
516
|
-
raise Exception("invalid constant type", sconst.name)
|
|
517
|
-
|
|
518
|
-
const_name = sconst.name.upper()
|
|
519
|
-
print("_emit_constant", value, const_name)
|
|
520
|
-
|
|
521
|
-
|
|
522
613
|
def _emit_endpoint(
|
|
523
614
|
gctx: EmitOpenAPIGlobalContext,
|
|
524
615
|
ctx: EmitOpenAPIContext,
|
|
@@ -559,7 +650,7 @@ def _emit_endpoint(
|
|
|
559
650
|
ep = namespace.endpoint
|
|
560
651
|
gctx.paths.append(
|
|
561
652
|
EmitOpenAPIPath(
|
|
562
|
-
path=f"/{ep.
|
|
653
|
+
path=f"/{ep.resolved_path}",
|
|
563
654
|
ref=ref_path,
|
|
564
655
|
)
|
|
565
656
|
)
|
|
@@ -575,7 +666,7 @@ def _emit_endpoint(
|
|
|
575
666
|
tags=[tag_name],
|
|
576
667
|
summary=f"{'/'.join(namespace.path[path_cutoff:])}",
|
|
577
668
|
description=description,
|
|
578
|
-
|
|
669
|
+
deprecated=namespace.endpoint.deprecated,
|
|
579
670
|
stability_level=namespace.endpoint.stability_level,
|
|
580
671
|
examples=[
|
|
581
672
|
EmitOpenAPIEndpointExample(
|
|
@@ -6,18 +6,17 @@ WORK-IN-PROGRESS, DON'T USE!
|
|
|
6
6
|
|
|
7
7
|
from collections import defaultdict
|
|
8
8
|
from dataclasses import dataclass, field
|
|
9
|
-
from typing import TypeAlias
|
|
10
9
|
|
|
11
|
-
from pkgs.serialization_util
|
|
10
|
+
from pkgs.serialization_util import JsonValue
|
|
12
11
|
|
|
13
12
|
from . import builder
|
|
14
13
|
from .open_api_util import OpenAPIType
|
|
15
14
|
|
|
16
15
|
MODIFY_NOTICE = "# DO NOT MODIFY -- This file is generated by type_spec"
|
|
17
16
|
|
|
18
|
-
GlobalContextInfo
|
|
19
|
-
TagGroupToNamedTags
|
|
20
|
-
TagPathsToRef
|
|
17
|
+
GlobalContextInfo = dict[str, str | dict[str, str]]
|
|
18
|
+
TagGroupToNamedTags = dict[str, str | list[str]]
|
|
19
|
+
TagPathsToRef = dict[str, dict[str, str]]
|
|
21
20
|
|
|
22
21
|
|
|
23
22
|
@dataclass
|
|
@@ -83,7 +82,7 @@ class EmitOpenAPIEndpoint:
|
|
|
83
82
|
tags: list[str]
|
|
84
83
|
summary: str
|
|
85
84
|
description: str
|
|
86
|
-
|
|
85
|
+
deprecated: bool
|
|
87
86
|
stability_level: EmitOpenAPIStabilityLevel | None
|
|
88
87
|
examples: list[EmitOpenAPIEndpointExample]
|
|
89
88
|
guides: list[EmitOpenAPIGuide]
|