UncountablePythonSDK 0.0.91__py3-none-any.whl → 0.0.93__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of UncountablePythonSDK might be problematic. Click here for more details.
- {UncountablePythonSDK-0.0.91.dist-info → UncountablePythonSDK-0.0.93.dist-info}/METADATA +2 -2
- UncountablePythonSDK-0.0.93.dist-info/RECORD +301 -0
- {UncountablePythonSDK-0.0.91.dist-info → UncountablePythonSDK-0.0.93.dist-info}/WHEEL +1 -1
- examples/set_recipe_metadata_file.py +1 -1
- examples/upload_files.py +1 -2
- pkgs/argument_parser/__init__.py +3 -0
- pkgs/argument_parser/argument_parser.py +52 -23
- pkgs/filesystem_utils/__init__.py +1 -0
- pkgs/filesystem_utils/_gdrive_session.py +4 -4
- pkgs/filesystem_utils/_s3_session.py +2 -1
- pkgs/filesystem_utils/_sftp_session.py +2 -3
- pkgs/filesystem_utils/file_type_utils.py +10 -10
- pkgs/serialization/annotation.py +5 -5
- pkgs/serialization/missing_sentry.py +1 -1
- pkgs/serialization/serial_alias.py +3 -3
- pkgs/serialization/serial_class.py +10 -10
- pkgs/serialization/serial_generic.py +1 -1
- pkgs/serialization/serial_union.py +10 -10
- pkgs/serialization_util/__init__.py +2 -0
- pkgs/serialization_util/serialization_helpers.py +1 -4
- pkgs/type_spec/actions_registry/__main__.py +0 -4
- pkgs/type_spec/builder.py +121 -40
- pkgs/type_spec/config.py +10 -5
- pkgs/type_spec/emit_open_api.py +2 -2
- pkgs/type_spec/emit_open_api_util.py +1 -1
- pkgs/type_spec/emit_python.py +145 -63
- pkgs/type_spec/emit_typescript.py +57 -10
- pkgs/type_spec/load_types.py +1 -2
- pkgs/type_spec/open_api_util.py +1 -2
- pkgs/type_spec/parts/base.py.prepart +2 -0
- pkgs/type_spec/type_info/emit_type_info.py +8 -8
- pkgs/type_spec/util.py +5 -7
- pkgs/type_spec/value_spec/__main__.py +15 -5
- pkgs/type_spec/value_spec/emit_python.py +5 -2
- pkgs/type_spec/value_spec/types.py +1 -1
- uncountable/core/client.py +16 -15
- uncountable/core/file_upload.py +39 -15
- uncountable/integration/construct_client.py +3 -3
- uncountable/integration/executors/generic_upload_executor.py +1 -1
- uncountable/integration/job.py +2 -2
- uncountable/integration/queue_runner/command_server/types.py +1 -1
- uncountable/integration/queue_runner/worker.py +1 -1
- uncountable/integration/server.py +4 -4
- uncountable/integration/telemetry.py +11 -0
- uncountable/types/__init__.py +0 -1
- uncountable/types/api/batch/execute_batch.py +1 -2
- uncountable/types/api/batch/execute_batch_load_async.py +0 -1
- uncountable/types/api/chemical/convert_chemical_formats.py +0 -1
- uncountable/types/api/entity/create_entities.py +3 -4
- uncountable/types/api/entity/create_entity.py +4 -5
- uncountable/types/api/entity/get_entities_data.py +0 -1
- uncountable/types/api/entity/grant_entity_permissions.py +3 -4
- uncountable/types/api/entity/list_entities.py +5 -6
- uncountable/types/api/entity/lock_entity.py +1 -2
- uncountable/types/api/entity/resolve_entity_ids.py +2 -3
- uncountable/types/api/entity/set_entity_field_values.py +0 -1
- uncountable/types/api/entity/set_values.py +0 -1
- uncountable/types/api/entity/transition_entity_phase.py +1 -2
- uncountable/types/api/entity/unlock_entity.py +0 -1
- uncountable/types/api/equipment/associate_equipment_input.py +0 -1
- uncountable/types/api/field_options/upsert_field_options.py +3 -4
- uncountable/types/api/files/download_file.py +1 -2
- uncountable/types/api/id_source/list_id_source.py +3 -4
- uncountable/types/api/id_source/match_id_source.py +1 -2
- uncountable/types/api/input_groups/get_input_group_names.py +0 -1
- uncountable/types/api/inputs/create_inputs.py +4 -5
- uncountable/types/api/inputs/get_input_data.py +5 -6
- uncountable/types/api/inputs/get_input_names.py +3 -4
- uncountable/types/api/inputs/get_inputs_data.py +0 -1
- uncountable/types/api/inputs/set_input_attribute_values.py +2 -3
- uncountable/types/api/inputs/set_input_category.py +2 -3
- uncountable/types/api/inputs/set_input_subcategories.py +0 -1
- uncountable/types/api/inputs/set_intermediate_type.py +1 -2
- uncountable/types/api/material_families/update_entity_material_families.py +1 -2
- uncountable/types/api/outputs/get_output_data.py +6 -7
- uncountable/types/api/outputs/get_output_names.py +2 -3
- uncountable/types/api/outputs/resolve_output_conditions.py +2 -3
- uncountable/types/api/permissions/set_core_permissions.py +3 -4
- uncountable/types/api/project/get_projects.py +3 -4
- uncountable/types/api/project/get_projects_data.py +4 -5
- uncountable/types/api/recipe_links/create_recipe_link.py +1 -2
- uncountable/types/api/recipe_links/remove_recipe_link.py +1 -2
- uncountable/types/api/recipe_metadata/get_recipe_metadata_data.py +3 -4
- uncountable/types/api/recipes/add_recipe_to_project.py +0 -1
- uncountable/types/api/recipes/archive_recipes.py +1 -2
- uncountable/types/api/recipes/associate_recipe_as_input.py +2 -3
- uncountable/types/api/recipes/associate_recipe_as_lot.py +0 -1
- uncountable/types/api/recipes/clear_recipe_outputs.py +0 -1
- uncountable/types/api/recipes/create_recipe.py +6 -7
- uncountable/types/api/recipes/create_recipes.py +4 -5
- uncountable/types/api/recipes/disassociate_recipe_as_input.py +0 -1
- uncountable/types/api/recipes/edit_recipe_inputs.py +9 -10
- uncountable/types/api/recipes/get_column_calculation_values.py +1 -2
- uncountable/types/api/recipes/get_curve.py +2 -3
- uncountable/types/api/recipes/get_recipe_calculations.py +3 -4
- uncountable/types/api/recipes/get_recipe_links.py +1 -2
- uncountable/types/api/recipes/get_recipe_names.py +0 -1
- uncountable/types/api/recipes/get_recipe_output_metadata.py +0 -1
- uncountable/types/api/recipes/get_recipes_data.py +22 -23
- uncountable/types/api/recipes/lock_recipes.py +3 -4
- uncountable/types/api/recipes/remove_recipe_from_project.py +0 -1
- uncountable/types/api/recipes/set_recipe_inputs.py +6 -7
- uncountable/types/api/recipes/set_recipe_metadata.py +0 -1
- uncountable/types/api/recipes/set_recipe_output_annotations.py +5 -6
- uncountable/types/api/recipes/set_recipe_output_file.py +2 -3
- uncountable/types/api/recipes/set_recipe_outputs.py +8 -9
- uncountable/types/api/recipes/set_recipe_tags.py +2 -3
- uncountable/types/api/recipes/unarchive_recipes.py +0 -1
- uncountable/types/api/recipes/unlock_recipes.py +2 -3
- uncountable/types/api/triggers/run_trigger.py +1 -2
- uncountable/types/api/uploader/invoke_uploader.py +2 -3
- uncountable/types/async_batch.py +0 -1
- uncountable/types/async_batch_processor.py +23 -24
- uncountable/types/async_batch_t.py +5 -6
- 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 +2 -3
- uncountable/types/base.py +0 -1
- uncountable/types/base_t.py +2 -1
- uncountable/types/calculations.py +0 -1
- uncountable/types/calculations_t.py +0 -1
- uncountable/types/chemical_structure.py +0 -1
- uncountable/types/chemical_structure_t.py +3 -4
- uncountable/types/client_base.py +65 -66
- uncountable/types/client_config.py +0 -1
- uncountable/types/client_config_t.py +1 -2
- uncountable/types/curves.py +0 -1
- uncountable/types/curves_t.py +4 -5
- uncountable/types/entity.py +0 -1
- uncountable/types/entity_t.py +3 -4
- uncountable/types/experiment_groups.py +0 -1
- uncountable/types/experiment_groups_t.py +0 -1
- uncountable/types/field_values.py +0 -1
- uncountable/types/field_values_t.py +6 -7
- uncountable/types/fields.py +0 -1
- uncountable/types/fields_t.py +0 -1
- uncountable/types/generic_upload.py +0 -1
- uncountable/types/generic_upload_t.py +7 -8
- uncountable/types/id_source.py +0 -1
- uncountable/types/id_source_t.py +2 -3
- uncountable/types/identifier.py +0 -1
- uncountable/types/identifier_t.py +1 -2
- uncountable/types/input_attributes.py +0 -1
- uncountable/types/input_attributes_t.py +2 -3
- uncountable/types/inputs.py +0 -1
- uncountable/types/inputs_t.py +2 -3
- uncountable/types/integration_server.py +0 -1
- uncountable/types/integration_server_t.py +2 -3
- uncountable/types/job_definition.py +0 -1
- uncountable/types/job_definition_t.py +16 -17
- uncountable/types/outputs.py +0 -1
- uncountable/types/outputs_t.py +1 -2
- uncountable/types/overrides.py +0 -1
- uncountable/types/overrides_t.py +0 -1
- uncountable/types/permissions.py +0 -1
- uncountable/types/permissions_t.py +1 -2
- uncountable/types/phases.py +0 -1
- uncountable/types/phases_t.py +0 -1
- uncountable/types/post_base.py +0 -1
- uncountable/types/post_base_t.py +1 -2
- uncountable/types/queued_job.py +0 -1
- uncountable/types/queued_job_t.py +2 -3
- uncountable/types/recipe_identifiers.py +0 -1
- uncountable/types/recipe_identifiers_t.py +3 -4
- 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 +2 -3
- uncountable/types/recipe_metadata.py +0 -1
- uncountable/types/recipe_metadata_t.py +6 -7
- uncountable/types/recipe_output_metadata.py +0 -1
- uncountable/types/recipe_output_metadata_t.py +0 -1
- uncountable/types/recipe_tags.py +0 -1
- uncountable/types/recipe_tags_t.py +0 -1
- uncountable/types/recipe_workflow_steps.py +0 -1
- uncountable/types/recipe_workflow_steps_t.py +2 -3
- uncountable/types/recipes.py +0 -1
- uncountable/types/recipes_t.py +0 -1
- uncountable/types/response.py +0 -1
- uncountable/types/response_t.py +0 -1
- uncountable/types/secret_retrieval.py +0 -1
- uncountable/types/secret_retrieval_t.py +3 -4
- uncountable/types/units.py +0 -1
- uncountable/types/units_t.py +0 -1
- uncountable/types/users.py +0 -1
- uncountable/types/users_t.py +0 -1
- uncountable/types/webhook_job.py +0 -1
- uncountable/types/webhook_job_t.py +0 -1
- uncountable/types/workflows.py +0 -1
- uncountable/types/workflows_t.py +1 -2
- UncountablePythonSDK-0.0.91.dist-info/RECORD +0 -301
- {UncountablePythonSDK-0.0.91.dist-info → UncountablePythonSDK-0.0.93.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from dataclasses import dataclass
|
|
3
3
|
from io import BytesIO
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import Union
|
|
5
5
|
|
|
6
6
|
import paramiko
|
|
7
7
|
from azure.core.credentials import (
|
|
@@ -17,9 +17,9 @@ class FileObjectData:
|
|
|
17
17
|
file_data: bytes
|
|
18
18
|
file_IO: BytesIO
|
|
19
19
|
filename: str
|
|
20
|
-
filepath:
|
|
21
|
-
mime_type:
|
|
22
|
-
metadata:
|
|
20
|
+
filepath: str | None = None
|
|
21
|
+
mime_type: str | None = None
|
|
22
|
+
metadata: dict[str, str] | None = None
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
@dataclass
|
|
@@ -39,7 +39,7 @@ class FileSystemFileReference:
|
|
|
39
39
|
class RemoteObjectReference:
|
|
40
40
|
file_id: str
|
|
41
41
|
mime_type: str
|
|
42
|
-
filename:
|
|
42
|
+
filename: str | None = None
|
|
43
43
|
|
|
44
44
|
@property
|
|
45
45
|
def is_dir(self) -> bool:
|
|
@@ -63,7 +63,7 @@ class FileSystemSFTPConfig:
|
|
|
63
63
|
pem_path: str | None
|
|
64
64
|
pem_key: paramiko.RSAKey | None = None
|
|
65
65
|
password: str | None = None
|
|
66
|
-
valid_extensions:
|
|
66
|
+
valid_extensions: tuple[str] | None = None
|
|
67
67
|
recursive: bool = True
|
|
68
68
|
|
|
69
69
|
|
|
@@ -71,10 +71,10 @@ class FileSystemSFTPConfig:
|
|
|
71
71
|
class FileSystemS3Config:
|
|
72
72
|
endpoint_url: str
|
|
73
73
|
bucket_name: str
|
|
74
|
-
region_name:
|
|
75
|
-
access_key_id:
|
|
76
|
-
secret_access_key:
|
|
77
|
-
session_token:
|
|
74
|
+
region_name: str | None
|
|
75
|
+
access_key_id: str | None
|
|
76
|
+
secret_access_key: str | None
|
|
77
|
+
session_token: str | None
|
|
78
78
|
|
|
79
79
|
|
|
80
80
|
@dataclass(kw_only=True)
|
pkgs/serialization/annotation.py
CHANGED
|
@@ -6,7 +6,7 @@ T = typing.TypeVar("T")
|
|
|
6
6
|
|
|
7
7
|
@dataclasses.dataclass(kw_only=True, frozen=True, eq=True)
|
|
8
8
|
class SerialBase:
|
|
9
|
-
named_type_path:
|
|
9
|
+
named_type_path: str | None = None
|
|
10
10
|
# Indicates this type is allowed in dynamic lookups, such as via a named_type_path
|
|
11
11
|
# This isn't meant to be limting, but to catalog all the types where we need it
|
|
12
12
|
is_dynamic_allowed: bool = False
|
|
@@ -16,7 +16,7 @@ class SerialBase:
|
|
|
16
16
|
from_decorator: bool = False
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
def get_serial_annotation(parsed_type: type[T]) -> SerialBase | None:
|
|
19
|
+
def get_serial_annotation[T](parsed_type: type[T]) -> SerialBase | None:
|
|
20
20
|
if not hasattr(parsed_type, "__metadata__"):
|
|
21
21
|
return None
|
|
22
22
|
metadata = parsed_type.__metadata__ # type:ignore[attr-defined]
|
|
@@ -28,13 +28,13 @@ def get_serial_annotation(parsed_type: type[T]) -> SerialBase | None:
|
|
|
28
28
|
return serial
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
class SerialInspector
|
|
31
|
+
class SerialInspector[T]:
|
|
32
32
|
def __init__(self, parsed_type: type[T], serial_base: SerialBase) -> None:
|
|
33
33
|
self._parsed_type = parsed_type
|
|
34
34
|
self._serial_base = serial_base
|
|
35
35
|
|
|
36
36
|
@property
|
|
37
|
-
def named_type_path(self) ->
|
|
37
|
+
def named_type_path(self) -> str | None:
|
|
38
38
|
return self._serial_base.named_type_path
|
|
39
39
|
|
|
40
40
|
@property
|
|
@@ -53,7 +53,7 @@ class SerialInspector(typing.Generic[T]):
|
|
|
53
53
|
return self._serial_base.is_dynamic_allowed
|
|
54
54
|
|
|
55
55
|
|
|
56
|
-
def unwrap_annotated(parsed_type: type[T]) -> type[T]:
|
|
56
|
+
def unwrap_annotated[T](parsed_type: type[T]) -> type[T]:
|
|
57
57
|
"""
|
|
58
58
|
If the type is an annotated type then return the origin of it.
|
|
59
59
|
Otherwise return the original type.
|
|
@@ -26,5 +26,5 @@ MISSING_SENTRY = MissingSentryType()
|
|
|
26
26
|
MissingType = Union[MissingSentryType, ClassT]
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
def coalesce_missing_sentry(value: MissingType[ClassT]) ->
|
|
29
|
+
def coalesce_missing_sentry[ClassT](value: MissingType[ClassT]) -> ClassT | None:
|
|
30
30
|
return None if isinstance(value, MissingSentryType) else value
|
|
@@ -16,7 +16,7 @@ class _SerialAlias(SerialBase):
|
|
|
16
16
|
|
|
17
17
|
def serial_alias_annotation(
|
|
18
18
|
*,
|
|
19
|
-
named_type_path:
|
|
19
|
+
named_type_path: str | None = None,
|
|
20
20
|
is_dynamic_allowed: bool = False,
|
|
21
21
|
) -> _SerialAlias:
|
|
22
22
|
return _SerialAlias(
|
|
@@ -26,7 +26,7 @@ def serial_alias_annotation(
|
|
|
26
26
|
)
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
def _get_serial_alias(parsed_type: type[T]) -> _SerialAlias | None:
|
|
29
|
+
def _get_serial_alias[T](parsed_type: type[T]) -> _SerialAlias | None:
|
|
30
30
|
serial = get_serial_annotation(parsed_type)
|
|
31
31
|
if not isinstance(serial, _SerialAlias):
|
|
32
32
|
return None
|
|
@@ -39,7 +39,7 @@ class SerialAliasInspector(SerialInspector[T]):
|
|
|
39
39
|
self._serial_alias = serial_alias
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
def get_serial_alias_data(parsed_type: type[T]) -> SerialAliasInspector[T] | None:
|
|
42
|
+
def get_serial_alias_data[T](parsed_type: type[T]) -> SerialAliasInspector[T] | None:
|
|
43
43
|
serial = _get_serial_alias(parsed_type)
|
|
44
44
|
if serial is None:
|
|
45
45
|
return None
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import dataclasses
|
|
4
4
|
from collections.abc import Callable
|
|
5
5
|
from enum import StrEnum
|
|
6
|
-
from typing import Any,
|
|
6
|
+
from typing import Any, TypeVar, cast
|
|
7
7
|
|
|
8
8
|
from .annotation import SerialBase, SerialInspector
|
|
9
9
|
|
|
@@ -16,7 +16,7 @@ class _SerialClassData(SerialBase):
|
|
|
16
16
|
unconverted_values: set[str] = dataclasses.field(default_factory=set)
|
|
17
17
|
to_string_values: set[str] = dataclasses.field(default_factory=set)
|
|
18
18
|
parse_require: set[str] = dataclasses.field(default_factory=set)
|
|
19
|
-
named_type_path:
|
|
19
|
+
named_type_path: str | None = None
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
EMPTY_SERIAL_CLASS_DATA = _SerialClassData()
|
|
@@ -24,11 +24,11 @@ EMPTY_SERIAL_CLASS_DATA = _SerialClassData()
|
|
|
24
24
|
|
|
25
25
|
def serial_class(
|
|
26
26
|
*,
|
|
27
|
-
unconverted_keys:
|
|
28
|
-
unconverted_values:
|
|
29
|
-
to_string_values:
|
|
30
|
-
parse_require:
|
|
31
|
-
named_type_path:
|
|
27
|
+
unconverted_keys: set[str] | None = None,
|
|
28
|
+
unconverted_values: set[str] | None = None,
|
|
29
|
+
to_string_values: set[str] | None = None,
|
|
30
|
+
parse_require: set[str] | None = None,
|
|
31
|
+
named_type_path: str | None = None,
|
|
32
32
|
is_dynamic_allowed: bool = False,
|
|
33
33
|
) -> Callable[[_ClassT], _ClassT]:
|
|
34
34
|
"""
|
|
@@ -119,7 +119,7 @@ def get_merged_serial_class_data(type_class: type[Any]) -> _SerialClassData | No
|
|
|
119
119
|
return base_class_data
|
|
120
120
|
|
|
121
121
|
|
|
122
|
-
def get_serial_class_data(
|
|
122
|
+
def get_serial_class_data[_ClassT](
|
|
123
123
|
type_class: type[_ClassT],
|
|
124
124
|
) -> SerialClassDataInspector[_ClassT]:
|
|
125
125
|
return SerialClassDataInspector(
|
|
@@ -134,7 +134,7 @@ class _SerialStringEnumData:
|
|
|
134
134
|
|
|
135
135
|
|
|
136
136
|
def serial_string_enum(
|
|
137
|
-
*, labels:
|
|
137
|
+
*, labels: dict[str, str] | None = None, deprecated: set[str] | None = None
|
|
138
138
|
) -> Callable[[_ClassT], _ClassT]:
|
|
139
139
|
"""
|
|
140
140
|
A decorator for enums to provide serialization data, including labels.
|
|
@@ -153,7 +153,7 @@ class SerialStringEnumInspector:
|
|
|
153
153
|
def __init__(self, current: _SerialStringEnumData) -> None:
|
|
154
154
|
self.current = current
|
|
155
155
|
|
|
156
|
-
def get_label(self, value: str) ->
|
|
156
|
+
def get_label(self, value: str) -> str | None:
|
|
157
157
|
return self.current.labels.get(value)
|
|
158
158
|
|
|
159
159
|
def get_deprecated(self, value: str) -> bool:
|
|
@@ -6,7 +6,7 @@ from .serial_class import get_merged_serial_class_data
|
|
|
6
6
|
T = typing.TypeVar("T")
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
def get_serial_data(parsed_type: type[T]) -> SerialInspector[T] | None:
|
|
9
|
+
def get_serial_data[T](parsed_type: type[T]) -> SerialInspector[T] | None:
|
|
10
10
|
serial = get_serial_annotation(parsed_type)
|
|
11
11
|
if serial is None:
|
|
12
12
|
serial = get_merged_serial_class_data(parsed_type)
|
|
@@ -6,7 +6,7 @@ from .annotation import SerialBase, SerialInspector, get_serial_annotation
|
|
|
6
6
|
T = typing.TypeVar("T")
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
class IdentityHashWrapper
|
|
9
|
+
class IdentityHashWrapper[T]:
|
|
10
10
|
"""This allows unhashable types to be used in the SerialUnion, like dict.
|
|
11
11
|
Since we have only one copy of the types themselves, we rely on
|
|
12
12
|
object identity for the hashing."""
|
|
@@ -27,15 +27,15 @@ class _SerialUnion(SerialBase):
|
|
|
27
27
|
|
|
28
28
|
# If specified, indicates the Union has a discriminator which should be used to
|
|
29
29
|
# determine which type to parse.
|
|
30
|
-
discriminator:
|
|
31
|
-
discriminator_map:
|
|
30
|
+
discriminator: str | None = None
|
|
31
|
+
discriminator_map: IdentityHashWrapper[dict[str, type]] | None = None
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
def serial_union_annotation(
|
|
35
35
|
*,
|
|
36
|
-
discriminator:
|
|
37
|
-
discriminator_map:
|
|
38
|
-
named_type_path:
|
|
36
|
+
discriminator: str | None = None,
|
|
37
|
+
discriminator_map: dict[str, type] | None = None,
|
|
38
|
+
named_type_path: str | None = None,
|
|
39
39
|
is_dynamic_allowed: bool = False,
|
|
40
40
|
) -> _SerialUnion:
|
|
41
41
|
return _SerialUnion(
|
|
@@ -49,7 +49,7 @@ def serial_union_annotation(
|
|
|
49
49
|
)
|
|
50
50
|
|
|
51
51
|
|
|
52
|
-
def _get_serial_union(parsed_type: type[T]) -> _SerialUnion | None:
|
|
52
|
+
def _get_serial_union[T](parsed_type: type[T]) -> _SerialUnion | None:
|
|
53
53
|
serial = get_serial_annotation(parsed_type)
|
|
54
54
|
if not isinstance(serial, _SerialUnion):
|
|
55
55
|
return None
|
|
@@ -66,17 +66,17 @@ class SerialClassInspector(SerialInspector[T]):
|
|
|
66
66
|
return typing.get_args(self._parsed_type)[0] # type:ignore[no-any-return]
|
|
67
67
|
|
|
68
68
|
@property
|
|
69
|
-
def discriminator(self) ->
|
|
69
|
+
def discriminator(self) -> str | None:
|
|
70
70
|
return self._serial_union.discriminator
|
|
71
71
|
|
|
72
72
|
@property
|
|
73
|
-
def discriminator_map(self) ->
|
|
73
|
+
def discriminator_map(self) -> dict[str, type] | None:
|
|
74
74
|
if self._serial_union.discriminator_map is None:
|
|
75
75
|
return None
|
|
76
76
|
return self._serial_union.discriminator_map.inner
|
|
77
77
|
|
|
78
78
|
|
|
79
|
-
def get_serial_union_data(parsed_type: type[T]) -> SerialClassInspector[T] | None:
|
|
79
|
+
def get_serial_union_data[T](parsed_type: type[T]) -> SerialClassInspector[T] | None:
|
|
80
80
|
serial = _get_serial_union(parsed_type)
|
|
81
81
|
if serial is None:
|
|
82
82
|
return None
|
|
@@ -2,6 +2,7 @@ from .convert_to_snakecase import convert_dict_to_snake_case
|
|
|
2
2
|
from .dataclasses import dict_fields as dict_fields
|
|
3
3
|
from .dataclasses import iterate_fields as iterate_fields
|
|
4
4
|
from .serialization_helpers import (
|
|
5
|
+
JsonValue,
|
|
5
6
|
serialize_for_api,
|
|
6
7
|
serialize_for_storage,
|
|
7
8
|
serialize_for_storage_dict,
|
|
@@ -14,4 +15,5 @@ __all__: list[str] = [
|
|
|
14
15
|
"serialize_for_storage_dict",
|
|
15
16
|
"iterate_fields",
|
|
16
17
|
"dict_fields",
|
|
18
|
+
"JsonValue",
|
|
17
19
|
]
|
|
@@ -12,7 +12,6 @@ from typing import (
|
|
|
12
12
|
Any,
|
|
13
13
|
ClassVar,
|
|
14
14
|
Protocol,
|
|
15
|
-
TypeVar,
|
|
16
15
|
Union,
|
|
17
16
|
overload,
|
|
18
17
|
)
|
|
@@ -34,14 +33,12 @@ if TYPE_CHECKING:
|
|
|
34
33
|
else:
|
|
35
34
|
JsonValue = Union[JsonScalar, dict[str, Any], list[Any]]
|
|
36
35
|
|
|
37
|
-
T = TypeVar("T")
|
|
38
|
-
|
|
39
36
|
|
|
40
37
|
class Dataclass(Protocol):
|
|
41
38
|
__dataclass_fields__: ClassVar[dict] # type: ignore[type-arg,unused-ignore]
|
|
42
39
|
|
|
43
40
|
|
|
44
|
-
def identity(x: T) -> T:
|
|
41
|
+
def identity[T](x: T) -> T:
|
|
45
42
|
return x
|
|
46
43
|
|
|
47
44
|
|
|
@@ -6,7 +6,6 @@ import os
|
|
|
6
6
|
import sys
|
|
7
7
|
from collections import defaultdict
|
|
8
8
|
from dataclasses import dataclass
|
|
9
|
-
from typing import TypeVar
|
|
10
9
|
|
|
11
10
|
from main.base.types import actions_registry_t
|
|
12
11
|
from pkgs.type_spec import builder
|
|
@@ -22,9 +21,6 @@ key_short_description = "short_description"
|
|
|
22
21
|
key_description = "description"
|
|
23
22
|
|
|
24
23
|
|
|
25
|
-
TypeT = TypeVar("TypeT")
|
|
26
|
-
|
|
27
|
-
|
|
28
24
|
class InvalidSpecException(Exception):
|
|
29
25
|
pass
|
|
30
26
|
|
pkgs/type_spec/builder.py
CHANGED
|
@@ -10,12 +10,13 @@ import re
|
|
|
10
10
|
from collections import defaultdict
|
|
11
11
|
from dataclasses import MISSING, dataclass
|
|
12
12
|
from enum import Enum, StrEnum, auto
|
|
13
|
-
from typing import Any,
|
|
13
|
+
from typing import Any, Self
|
|
14
14
|
|
|
15
15
|
from . import util
|
|
16
16
|
from .util import parse_type_str, unused
|
|
17
17
|
|
|
18
18
|
RawDict = dict[Any, Any]
|
|
19
|
+
EndpointKey = str
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
class StabilityLevel(StrEnum):
|
|
@@ -47,7 +48,7 @@ class PropertyConvertValue(StrEnum):
|
|
|
47
48
|
@dataclass
|
|
48
49
|
class SpecProperty:
|
|
49
50
|
name: str
|
|
50
|
-
label:
|
|
51
|
+
label: str | None
|
|
51
52
|
spec_type: SpecType
|
|
52
53
|
extant: PropertyExtant
|
|
53
54
|
convert_value: PropertyConvertValue
|
|
@@ -255,7 +256,7 @@ class SpecTypeLiteralWrapper(SpecType):
|
|
|
255
256
|
return [self.value_type]
|
|
256
257
|
|
|
257
258
|
|
|
258
|
-
def unwrap_literal_type(stype: SpecType) ->
|
|
259
|
+
def unwrap_literal_type(stype: SpecType) -> SpecTypeLiteralWrapper | None:
|
|
259
260
|
if isinstance(stype, SpecTypeInstance) and stype.defn_type.is_base_type(
|
|
260
261
|
BaseTypeName.s_literal
|
|
261
262
|
):
|
|
@@ -283,7 +284,7 @@ class SpecTypeDefn(SpecType):
|
|
|
283
284
|
) -> None:
|
|
284
285
|
self.namespace = namespace
|
|
285
286
|
self.name = name
|
|
286
|
-
self.label:
|
|
287
|
+
self.label: str | None = None
|
|
287
288
|
|
|
288
289
|
self.is_predefined = is_predefined
|
|
289
290
|
self.name_case = NameCase.convert
|
|
@@ -455,7 +456,7 @@ class SpecTypeGenericParameter(SpecType):
|
|
|
455
456
|
|
|
456
457
|
|
|
457
458
|
class SpecTypeDefnObject(SpecTypeDefn):
|
|
458
|
-
base:
|
|
459
|
+
base: SpecTypeDefnObject | None
|
|
459
460
|
parameters: list[str]
|
|
460
461
|
|
|
461
462
|
def __init__(
|
|
@@ -463,7 +464,7 @@ class SpecTypeDefnObject(SpecTypeDefn):
|
|
|
463
464
|
namespace: SpecNamespace,
|
|
464
465
|
name: str,
|
|
465
466
|
*,
|
|
466
|
-
parameters:
|
|
467
|
+
parameters: list[str] | None = None,
|
|
467
468
|
is_base: bool = False,
|
|
468
469
|
is_predefined: bool = False,
|
|
469
470
|
is_hashable: bool = False,
|
|
@@ -480,7 +481,7 @@ class SpecTypeDefnObject(SpecTypeDefn):
|
|
|
480
481
|
self.parameters = parameters if parameters is not None else []
|
|
481
482
|
self.is_hashable = is_hashable
|
|
482
483
|
self.base = None
|
|
483
|
-
self.properties:
|
|
484
|
+
self.properties: dict[str, SpecProperty] | None = None
|
|
484
485
|
self._kw_only: bool = True
|
|
485
486
|
self.desc: str | None = None
|
|
486
487
|
|
|
@@ -662,7 +663,7 @@ class SpecTypeDefnExternal(SpecTypeDefn):
|
|
|
662
663
|
class StringEnumEntry:
|
|
663
664
|
name: str
|
|
664
665
|
value: str
|
|
665
|
-
label:
|
|
666
|
+
label: str | None = None
|
|
666
667
|
deprecated: bool = False
|
|
667
668
|
|
|
668
669
|
|
|
@@ -678,7 +679,7 @@ class SpecTypeDefnStringEnum(SpecTypeDefn):
|
|
|
678
679
|
)
|
|
679
680
|
self.values: dict[str, StringEnumEntry] = {}
|
|
680
681
|
self.desc: str | None = None
|
|
681
|
-
self.sql_type_name:
|
|
682
|
+
self.sql_type_name: str | None = None
|
|
682
683
|
self.emit_id_source = False
|
|
683
684
|
self.source_enums: list[SpecType] = []
|
|
684
685
|
|
|
@@ -810,13 +811,13 @@ RE_ENDPOINT_ROOT = re.compile(r"\${([_a-z]+)}")
|
|
|
810
811
|
|
|
811
812
|
@dataclass(kw_only=True, frozen=True)
|
|
812
813
|
class _EndpointPathDetails:
|
|
813
|
-
root:
|
|
814
|
+
root: EndpointKey
|
|
814
815
|
root_path: str
|
|
815
816
|
resolved_path: str
|
|
816
817
|
|
|
817
818
|
|
|
818
819
|
def _resolve_endpoint_path(
|
|
819
|
-
path: str, api_endpoints: dict[
|
|
820
|
+
path: str, api_endpoints: dict[EndpointKey, str]
|
|
820
821
|
) -> _EndpointPathDetails:
|
|
821
822
|
root_path_source = path.split("/")[0]
|
|
822
823
|
root_match = RE_ENDPOINT_ROOT.fullmatch(root_path_source)
|
|
@@ -840,19 +841,59 @@ class EndpointEmitType(StrEnum):
|
|
|
840
841
|
EMIT_NOTHING = "emit_nothing"
|
|
841
842
|
|
|
842
843
|
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
root:
|
|
844
|
+
@dataclass(kw_only=True, frozen=True)
|
|
845
|
+
class EndpointSpecificPath:
|
|
846
|
+
root: EndpointKey
|
|
846
847
|
path_root: str
|
|
847
848
|
path_dirname: str
|
|
848
849
|
path_basename: str
|
|
850
|
+
function: str | None
|
|
851
|
+
|
|
852
|
+
|
|
853
|
+
def parse_endpoint_specific_path(
|
|
854
|
+
builder: SpecBuilder,
|
|
855
|
+
data_per_endpoint: RawDict | None,
|
|
856
|
+
) -> EndpointSpecificPath | None:
|
|
857
|
+
if data_per_endpoint is None:
|
|
858
|
+
return None
|
|
859
|
+
util.check_fields(
|
|
860
|
+
data_per_endpoint,
|
|
861
|
+
[
|
|
862
|
+
"path",
|
|
863
|
+
"function",
|
|
864
|
+
],
|
|
865
|
+
)
|
|
866
|
+
|
|
867
|
+
if "path" not in data_per_endpoint or data_per_endpoint["path"] is None:
|
|
868
|
+
return None
|
|
869
|
+
|
|
870
|
+
path = data_per_endpoint["path"].split("/")
|
|
871
|
+
|
|
872
|
+
assert len(path) > 1, "invalid-endpoint-path"
|
|
873
|
+
|
|
874
|
+
path_details = _resolve_endpoint_path(
|
|
875
|
+
data_per_endpoint["path"], builder.api_endpoints
|
|
876
|
+
)
|
|
877
|
+
|
|
878
|
+
result = EndpointSpecificPath(
|
|
879
|
+
function=data_per_endpoint.get("function"),
|
|
880
|
+
path_dirname="/".join(path[1:-1]),
|
|
881
|
+
path_basename=path[-1],
|
|
882
|
+
root=path_details.root,
|
|
883
|
+
path_root=path_details.root_path,
|
|
884
|
+
)
|
|
885
|
+
|
|
886
|
+
return result
|
|
887
|
+
|
|
888
|
+
|
|
889
|
+
class SpecEndpoint:
|
|
890
|
+
method: RouteMethod
|
|
849
891
|
data_loader: bool
|
|
850
892
|
is_sdk: EndpointEmitType
|
|
851
893
|
is_beta: bool
|
|
852
894
|
stability_level: StabilityLevel | None
|
|
853
895
|
# Don't emit TypeScript endpoint code
|
|
854
896
|
suppress_ts: bool
|
|
855
|
-
function: Optional[str]
|
|
856
897
|
async_batch_path: str | None = None
|
|
857
898
|
result_type: ResultType = ResultType.json
|
|
858
899
|
has_attachment: bool = False
|
|
@@ -860,6 +901,10 @@ class SpecEndpoint:
|
|
|
860
901
|
account_type: str | None
|
|
861
902
|
route_group: str | None
|
|
862
903
|
|
|
904
|
+
# function, path details per api endpoint
|
|
905
|
+
path_per_api_endpoint: dict[str, EndpointSpecificPath]
|
|
906
|
+
default_endpoint_key: EndpointKey
|
|
907
|
+
|
|
863
908
|
is_external: bool = False
|
|
864
909
|
|
|
865
910
|
def __init__(self) -> None:
|
|
@@ -885,18 +930,11 @@ class SpecEndpoint:
|
|
|
885
930
|
"has_attachment",
|
|
886
931
|
"account_type",
|
|
887
932
|
"route_group",
|
|
888
|
-
]
|
|
933
|
+
]
|
|
934
|
+
+ list(builder.api_endpoints.keys()),
|
|
889
935
|
)
|
|
890
936
|
self.method = RouteMethod(data["method"])
|
|
891
937
|
|
|
892
|
-
path = data["path"].split("/")
|
|
893
|
-
|
|
894
|
-
assert len(path) > 1, "invalid-endpoint-path"
|
|
895
|
-
|
|
896
|
-
# handle ${external} in the same way we handle ${materials} for now
|
|
897
|
-
self.path_dirname = "/".join(path[1:-1])
|
|
898
|
-
self.path_basename = path[-1]
|
|
899
|
-
|
|
900
938
|
data_loader = data.get("data_loader", False)
|
|
901
939
|
assert isinstance(data_loader, bool)
|
|
902
940
|
self.data_loader = data_loader
|
|
@@ -944,29 +982,70 @@ class SpecEndpoint:
|
|
|
944
982
|
assert isinstance(async_batch_path, str)
|
|
945
983
|
self.async_batch_path = async_batch_path
|
|
946
984
|
|
|
947
|
-
self.function = data.get("function")
|
|
948
|
-
|
|
949
985
|
suppress_ts = data.get("suppress_ts", False)
|
|
950
986
|
assert isinstance(suppress_ts, bool)
|
|
951
987
|
self.suppress_ts = suppress_ts
|
|
952
988
|
|
|
953
989
|
self.result_type = ResultType(data.get("result_type", ResultType.json.value))
|
|
954
|
-
|
|
955
|
-
path_details = _resolve_endpoint_path(data["path"], builder.api_endpoints)
|
|
956
|
-
self.root = path_details.root
|
|
957
|
-
self.path_root = path_details.root_path
|
|
990
|
+
self.has_attachment = data.get("has_attachment", False)
|
|
958
991
|
self.desc = data.get("desc")
|
|
992
|
+
|
|
993
|
+
# compatibility with single-endpoint files
|
|
994
|
+
default_endpoint_path = parse_endpoint_specific_path(
|
|
995
|
+
builder,
|
|
996
|
+
{"path": data.get("path"), "function": data.get("function")},
|
|
997
|
+
)
|
|
998
|
+
if default_endpoint_path is not None:
|
|
999
|
+
assert default_endpoint_path.root in builder.api_endpoints, (
|
|
1000
|
+
"Default endpoint is not a valid API endpoint"
|
|
1001
|
+
)
|
|
1002
|
+
self.default_endpoint_key = default_endpoint_path.root
|
|
1003
|
+
self.path_per_api_endpoint = {
|
|
1004
|
+
self.default_endpoint_key: default_endpoint_path,
|
|
1005
|
+
}
|
|
1006
|
+
else:
|
|
1007
|
+
self.path_per_api_endpoint = {}
|
|
1008
|
+
shared_function_name = None
|
|
1009
|
+
for endpoint_key in builder.api_endpoints:
|
|
1010
|
+
endpoint_specific_path = parse_endpoint_specific_path(
|
|
1011
|
+
builder,
|
|
1012
|
+
data.get(endpoint_key),
|
|
1013
|
+
)
|
|
1014
|
+
if endpoint_specific_path is not None:
|
|
1015
|
+
self.path_per_api_endpoint[endpoint_key] = endpoint_specific_path
|
|
1016
|
+
if endpoint_specific_path.function is not None:
|
|
1017
|
+
fn_name = endpoint_specific_path.function.split(".")[-1]
|
|
1018
|
+
if shared_function_name is None:
|
|
1019
|
+
shared_function_name = fn_name
|
|
1020
|
+
assert shared_function_name == fn_name
|
|
1021
|
+
|
|
1022
|
+
if builder.top_namespace in self.path_per_api_endpoint:
|
|
1023
|
+
self.default_endpoint_key = builder.top_namespace
|
|
1024
|
+
elif len(self.path_per_api_endpoint) == 1:
|
|
1025
|
+
self.default_endpoint_key = next(
|
|
1026
|
+
iter(self.path_per_api_endpoint.keys())
|
|
1027
|
+
)
|
|
1028
|
+
else:
|
|
1029
|
+
raise RuntimeError("no clear default endpoint")
|
|
1030
|
+
|
|
1031
|
+
assert len(self.path_per_api_endpoint) > 0, (
|
|
1032
|
+
"Missing API endpoint path and function definitions for API call"
|
|
1033
|
+
)
|
|
1034
|
+
|
|
959
1035
|
# IMPROVE: remove need for is_external flag
|
|
960
|
-
self.is_external =
|
|
961
|
-
|
|
1036
|
+
self.is_external = (
|
|
1037
|
+
self.path_per_api_endpoint[self.default_endpoint_key].path_root
|
|
1038
|
+
== "api/external"
|
|
1039
|
+
)
|
|
962
1040
|
|
|
963
1041
|
assert self.is_sdk != EndpointEmitType.EMIT_ENDPOINT or self.desc is not None, (
|
|
964
|
-
f"Endpoint description required for SDK endpoints, missing: {
|
|
1042
|
+
f"Endpoint description required for SDK endpoints, missing: {self.resolved_path}"
|
|
965
1043
|
)
|
|
966
1044
|
|
|
967
1045
|
@property
|
|
968
1046
|
def resolved_path(self: Self) -> str:
|
|
969
|
-
|
|
1047
|
+
default_endpoint_path = self.path_per_api_endpoint[self.default_endpoint_key]
|
|
1048
|
+
return f"{default_endpoint_path.path_root}/{default_endpoint_path.path_dirname}/{default_endpoint_path.path_basename}"
|
|
970
1049
|
|
|
971
1050
|
|
|
972
1051
|
def _parse_const(
|
|
@@ -1079,14 +1158,14 @@ class SpecNamespace:
|
|
|
1079
1158
|
):
|
|
1080
1159
|
self.types: dict[str, SpecTypeDefn] = {}
|
|
1081
1160
|
self.constants: dict[str, SpecConstant] = {}
|
|
1082
|
-
self.endpoint:
|
|
1161
|
+
self.endpoint: SpecEndpoint | None = None
|
|
1083
1162
|
self.emit_io_ts = False
|
|
1084
1163
|
self.emit_type_info = False
|
|
1085
1164
|
self.derive_types_from_io_ts = False
|
|
1086
|
-
self._imports:
|
|
1165
|
+
self._imports: list[str] | None = None
|
|
1087
1166
|
self.path = name.split(".")
|
|
1088
1167
|
self.name = self.path[-1]
|
|
1089
|
-
self._order:
|
|
1168
|
+
self._order: int | None = None
|
|
1090
1169
|
|
|
1091
1170
|
def _update_order(self, builder: SpecBuilder, recurse: int = 0) -> int:
|
|
1092
1171
|
if self._order is not None:
|
|
@@ -1241,7 +1320,9 @@ class NameDataPair:
|
|
|
1241
1320
|
|
|
1242
1321
|
|
|
1243
1322
|
class SpecBuilder:
|
|
1244
|
-
def __init__(
|
|
1323
|
+
def __init__(
|
|
1324
|
+
self, *, api_endpoints: dict[EndpointKey, str], top_namespace: str
|
|
1325
|
+
) -> None:
|
|
1245
1326
|
self.top_namespace = top_namespace
|
|
1246
1327
|
self.where: list[str] = []
|
|
1247
1328
|
self.namespaces = {}
|
|
@@ -1339,7 +1420,7 @@ class SpecBuilder:
|
|
|
1339
1420
|
self,
|
|
1340
1421
|
path: util.ParsedTypePath,
|
|
1341
1422
|
namespace: SpecNamespace,
|
|
1342
|
-
scope:
|
|
1423
|
+
scope: SpecTypeDefn | None = None,
|
|
1343
1424
|
top: bool = False,
|
|
1344
1425
|
) -> SpecType:
|
|
1345
1426
|
"""
|
|
@@ -1420,7 +1501,7 @@ class SpecBuilder:
|
|
|
1420
1501
|
)
|
|
1421
1502
|
|
|
1422
1503
|
def parse_type(
|
|
1423
|
-
self, namespace: SpecNamespace, spec: str, scope:
|
|
1504
|
+
self, namespace: SpecNamespace, spec: str, scope: SpecTypeDefn | None = None
|
|
1424
1505
|
) -> SpecType:
|
|
1425
1506
|
self.push_where(spec)
|
|
1426
1507
|
parsed_type = util.parse_type_str(spec)
|
pkgs/type_spec/config.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from collections.abc import Callable, Mapping
|
|
3
3
|
from dataclasses import dataclass
|
|
4
|
-
from typing import Self
|
|
4
|
+
from typing import Self
|
|
5
5
|
|
|
6
6
|
from pkgs.serialization import yaml
|
|
7
7
|
|
|
@@ -34,6 +34,9 @@ class TypeScriptConfig(BaseLanguageConfig):
|
|
|
34
34
|
routes_output: str # folder for generate route files will be located.
|
|
35
35
|
type_info_output: str # folder for generated type info files
|
|
36
36
|
id_source_output: str | None = None # folder for emitted id source maps.
|
|
37
|
+
endpoint_to_frontend_app_type: dict[
|
|
38
|
+
str, str
|
|
39
|
+
] # map from api_endpoint to frontend app type
|
|
37
40
|
|
|
38
41
|
def __post_init__(self: Self) -> None:
|
|
39
42
|
self.routes_output = self.routes_output
|
|
@@ -43,6 +46,11 @@ class TypeScriptConfig(BaseLanguageConfig):
|
|
|
43
46
|
if self.id_source_output is not None
|
|
44
47
|
else None
|
|
45
48
|
)
|
|
49
|
+
self.endpoint_to_frontend_app_type = _parse_string_lookup(
|
|
50
|
+
"typescript_endpoint_to_frontend_app_type",
|
|
51
|
+
self.endpoint_to_frontend_app_type,
|
|
52
|
+
lambda x: x,
|
|
53
|
+
)
|
|
46
54
|
|
|
47
55
|
|
|
48
56
|
@dataclass(kw_only=True)
|
|
@@ -95,10 +103,7 @@ class Config:
|
|
|
95
103
|
open_api: OpenAPIConfig | None
|
|
96
104
|
|
|
97
105
|
|
|
98
|
-
_T
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def _parse_language(config_class: type[_T], raw_value: ConfigValueType) -> _T:
|
|
106
|
+
def _parse_language[_T](config_class: type[_T], raw_value: ConfigValueType) -> _T:
|
|
102
107
|
assert isinstance(raw_value, dict), "expecting language config to have key/values."
|
|
103
108
|
return config_class(**raw_value)
|
|
104
109
|
|