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
|
@@ -3,15 +3,15 @@ import dataclasses
|
|
|
3
3
|
import decimal
|
|
4
4
|
import io
|
|
5
5
|
import json
|
|
6
|
-
from
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import Any, cast
|
|
8
|
+
|
|
9
|
+
import yaml
|
|
7
10
|
|
|
8
11
|
from main.base.types import data_t, type_info_t
|
|
9
12
|
from main.base.types.base_t import PureJsonValue
|
|
10
13
|
from pkgs.argument_parser import CachedParser
|
|
11
|
-
from pkgs.serialization_util import
|
|
12
|
-
serialize_for_api,
|
|
13
|
-
serialize_for_storage,
|
|
14
|
-
)
|
|
14
|
+
from pkgs.serialization_util import serialize_for_api, serialize_for_storage
|
|
15
15
|
|
|
16
16
|
from .. import builder, util
|
|
17
17
|
from ..emit_typescript_util import MODIFY_NOTICE, ts_name
|
|
@@ -41,10 +41,23 @@ def type_path_of(stype: builder.SpecType) -> object: # NamePath
|
|
|
41
41
|
parts: list[object] = ["$literal"]
|
|
42
42
|
for parameter in stype.parameters:
|
|
43
43
|
assert isinstance(parameter, builder.SpecTypeLiteralWrapper)
|
|
44
|
+
emit_value = parameter.value
|
|
45
|
+
if isinstance(parameter.value_type, builder.SpecTypeDefnObject):
|
|
46
|
+
emit_value = parameter.value
|
|
47
|
+
assert isinstance(emit_value, (str, bool)), (
|
|
48
|
+
f"invalid-literal-value:{emit_value}"
|
|
49
|
+
)
|
|
50
|
+
elif isinstance(parameter.value_type, builder.SpecTypeDefnStringEnum):
|
|
51
|
+
key = parameter.value
|
|
52
|
+
assert isinstance(key, str)
|
|
53
|
+
emit_value = parameter.value_type.values[key].value
|
|
54
|
+
else:
|
|
55
|
+
raise Exception("unhandled-literal-type")
|
|
56
|
+
|
|
44
57
|
# This allows expansion to enum literal values later
|
|
45
58
|
parts.append([
|
|
46
59
|
"$value",
|
|
47
|
-
|
|
60
|
+
emit_value,
|
|
48
61
|
type_path_of(parameter.value_type),
|
|
49
62
|
])
|
|
50
63
|
return parts
|
|
@@ -95,6 +108,35 @@ def emit_type_info(build: builder.SpecBuilder, output: str) -> None:
|
|
|
95
108
|
util.rewrite_file(f"{output}/type_map.ts", type_map_out.getvalue())
|
|
96
109
|
|
|
97
110
|
|
|
111
|
+
def _convert_value_for_yaml_dump(value: Any) -> Any:
|
|
112
|
+
if isinstance(value, Enum):
|
|
113
|
+
return value.value
|
|
114
|
+
elif isinstance(value, list):
|
|
115
|
+
return [_convert_value_for_yaml_dump(item) for item in value]
|
|
116
|
+
elif isinstance(value, dict):
|
|
117
|
+
return {k: _convert_value_for_yaml_dump(v) for k, v in value.items()}
|
|
118
|
+
elif isinstance(value, decimal.Decimal):
|
|
119
|
+
return str(value)
|
|
120
|
+
else:
|
|
121
|
+
return value
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def asdict_for_yaml_dump(dataclass_instance: Any) -> Any:
|
|
125
|
+
return {
|
|
126
|
+
k: _convert_value_for_yaml_dump(v)
|
|
127
|
+
for k, v in dataclasses.asdict(dataclass_instance).items()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def emit_type_info_python(build: builder.SpecBuilder, output: str) -> None:
|
|
132
|
+
type_map = _build_map_all(build, python=True)
|
|
133
|
+
|
|
134
|
+
stripped = _dict_null_strip(asdict_for_yaml_dump(type_map))
|
|
135
|
+
|
|
136
|
+
yaml_content = yaml.dump(stripped, default_flow_style=False, sort_keys=True)
|
|
137
|
+
util.rewrite_file(f"{output}/type_map.yaml", yaml_content)
|
|
138
|
+
|
|
139
|
+
|
|
98
140
|
@dataclasses.dataclass
|
|
99
141
|
class MapProperty:
|
|
100
142
|
api_name: str
|
|
@@ -106,7 +148,7 @@ class MapProperty:
|
|
|
106
148
|
desc: str | None
|
|
107
149
|
# We don't have typing on defaults yet, relying on emitters to check it. Limit
|
|
108
150
|
# use of this field, as it'll necessarily change when adding type info
|
|
109
|
-
default:
|
|
151
|
+
default: PureJsonValue
|
|
110
152
|
|
|
111
153
|
|
|
112
154
|
@dataclasses.dataclass
|
|
@@ -129,12 +171,19 @@ class MapTypeAlias(MapTypeBase):
|
|
|
129
171
|
discriminator: str | None
|
|
130
172
|
|
|
131
173
|
|
|
174
|
+
@dataclasses.dataclass
|
|
175
|
+
class StringEnumValue:
|
|
176
|
+
value: str
|
|
177
|
+
label: str
|
|
178
|
+
deprecated: bool = False
|
|
179
|
+
|
|
180
|
+
|
|
132
181
|
@dataclasses.dataclass
|
|
133
182
|
class MapStringEnum(MapTypeBase):
|
|
134
|
-
values: dict[str,
|
|
183
|
+
values: dict[str, StringEnumValue]
|
|
135
184
|
|
|
136
185
|
|
|
137
|
-
|
|
186
|
+
MapType = MapTypeObject | MapTypeAlias | MapStringEnum
|
|
138
187
|
|
|
139
188
|
|
|
140
189
|
@dataclasses.dataclass
|
|
@@ -147,11 +196,14 @@ class MapAll:
|
|
|
147
196
|
namespaces: dict[str, MapNamespace]
|
|
148
197
|
|
|
149
198
|
|
|
150
|
-
def _build_map_all(build: builder.SpecBuilder) -> MapAll:
|
|
199
|
+
def _build_map_all(build: builder.SpecBuilder, *, python: bool = False) -> MapAll:
|
|
151
200
|
map_all = MapAll(namespaces={})
|
|
152
201
|
|
|
153
202
|
for namespace in build.namespaces.values():
|
|
154
|
-
if not namespace.emit_type_info:
|
|
203
|
+
if not python and not namespace.emit_type_info:
|
|
204
|
+
continue
|
|
205
|
+
|
|
206
|
+
if python and not namespace.emit_type_info_python:
|
|
155
207
|
continue
|
|
156
208
|
|
|
157
209
|
map_namespace = MapNamespace(types={})
|
|
@@ -172,9 +224,9 @@ class InheritablePropertyParts:
|
|
|
172
224
|
at that level, but that needs to be done in builder. When that is done, the
|
|
173
225
|
"label" and "desc" could probably be removed from this list."""
|
|
174
226
|
|
|
175
|
-
label:
|
|
176
|
-
desc:
|
|
177
|
-
ext_info:
|
|
227
|
+
label: str | None = None
|
|
228
|
+
desc: str | None = None
|
|
229
|
+
ext_info: type_info_t.ExtInfo | None = None
|
|
178
230
|
|
|
179
231
|
|
|
180
232
|
def _extract_inheritable_property_parts(
|
|
@@ -201,8 +253,13 @@ def _extract_inheritable_property_parts(
|
|
|
201
253
|
elif base_parts.ext_info is None:
|
|
202
254
|
ext_info = local_ext_info
|
|
203
255
|
else:
|
|
204
|
-
ext_info =
|
|
205
|
-
|
|
256
|
+
ext_info = dataclasses.replace(
|
|
257
|
+
local_ext_info,
|
|
258
|
+
**{
|
|
259
|
+
field.name: getattr(base_parts.ext_info, field.name)
|
|
260
|
+
for field in dataclasses.fields(type_info_t.ExtInfo)
|
|
261
|
+
if getattr(base_parts.ext_info, field.name) is not None
|
|
262
|
+
},
|
|
206
263
|
)
|
|
207
264
|
|
|
208
265
|
return InheritablePropertyParts(label=label, desc=desc, ext_info=ext_info)
|
|
@@ -230,7 +287,9 @@ def _extract_and_validate_layout(
|
|
|
230
287
|
for group in ext_info.layout.groups:
|
|
231
288
|
fields = set(group.fields or [])
|
|
232
289
|
for field in fields:
|
|
233
|
-
assert field in stype.properties,
|
|
290
|
+
assert field in stype.properties or field == DISCRIMINATOR_COMMON_NAME, (
|
|
291
|
+
f"layout-refers-to-missing-field:{field}"
|
|
292
|
+
)
|
|
234
293
|
|
|
235
294
|
local_ref_name = None
|
|
236
295
|
if group.ref_name is not None:
|
|
@@ -262,9 +321,27 @@ def _extract_and_validate_layout(
|
|
|
262
321
|
return layout
|
|
263
322
|
|
|
264
323
|
|
|
324
|
+
def _pull_property_from_type_recursively(
|
|
325
|
+
stype: builder.SpecTypeDefnObject,
|
|
326
|
+
property_name: str,
|
|
327
|
+
) -> builder.SpecProperty | None:
|
|
328
|
+
assert stype.properties is not None
|
|
329
|
+
prop = stype.properties.get(property_name)
|
|
330
|
+
if prop is not None:
|
|
331
|
+
return prop
|
|
332
|
+
|
|
333
|
+
if stype.base is None:
|
|
334
|
+
return None
|
|
335
|
+
|
|
336
|
+
return _pull_property_from_type_recursively(stype.base, property_name)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
DISCRIMINATOR_COMMON_NAME = "type"
|
|
340
|
+
|
|
341
|
+
|
|
265
342
|
def _validate_type_ext_info(
|
|
266
343
|
stype: builder.SpecTypeDefnObject,
|
|
267
|
-
) -> tuple[ExtInfoLayout | None,
|
|
344
|
+
) -> tuple[ExtInfoLayout | None, type_info_t.ExtInfo | None]:
|
|
268
345
|
ext_info = _parse_ext_info(stype.ext_info)
|
|
269
346
|
if ext_info is None:
|
|
270
347
|
return None, None
|
|
@@ -272,9 +349,19 @@ def _validate_type_ext_info(
|
|
|
272
349
|
if ext_info.label_fields is not None:
|
|
273
350
|
assert stype.properties is not None
|
|
274
351
|
for name in ext_info.label_fields:
|
|
275
|
-
|
|
352
|
+
if name == DISCRIMINATOR_COMMON_NAME:
|
|
353
|
+
continue
|
|
354
|
+
prop = _pull_property_from_type_recursively(stype, name)
|
|
276
355
|
assert prop is not None, f"missing-label-field:{name}"
|
|
277
356
|
|
|
357
|
+
if ext_info.actions is not None:
|
|
358
|
+
assert stype.properties is not None
|
|
359
|
+
for action in ext_info.actions:
|
|
360
|
+
if action.property == DISCRIMINATOR_COMMON_NAME:
|
|
361
|
+
continue
|
|
362
|
+
prop = _pull_property_from_type_recursively(stype, action.property)
|
|
363
|
+
assert prop is not None, f"missing-action-field:{action.property}"
|
|
364
|
+
|
|
278
365
|
if not stype.is_base and isinstance(stype.base, builder.SpecTypeDefnObject):
|
|
279
366
|
base_layout, _ = _validate_type_ext_info(stype.base)
|
|
280
367
|
else:
|
|
@@ -356,7 +443,11 @@ def _build_map_type(
|
|
|
356
443
|
# IMPROVE: We probably want the label here, but this requires a change
|
|
357
444
|
# to the front-end type-info and form code to handle
|
|
358
445
|
values={
|
|
359
|
-
entry.value: (
|
|
446
|
+
entry.value: StringEnumValue(
|
|
447
|
+
value=entry.value,
|
|
448
|
+
label=entry.label or entry.name,
|
|
449
|
+
deprecated=entry.deprecated,
|
|
450
|
+
)
|
|
360
451
|
for entry in stype.values.values()
|
|
361
452
|
},
|
|
362
453
|
)
|
|
@@ -364,7 +455,7 @@ def _build_map_type(
|
|
|
364
455
|
return None
|
|
365
456
|
|
|
366
457
|
|
|
367
|
-
def _parse_ext_info(in_ext: Any) ->
|
|
458
|
+
def _parse_ext_info(in_ext: Any) -> type_info_t.ExtInfo | None:
|
|
368
459
|
if in_ext is None:
|
|
369
460
|
return None
|
|
370
461
|
assert isinstance(in_ext, dict)
|
|
@@ -382,10 +473,12 @@ def _parse_ext_info(in_ext: Any) -> Optional[type_info_t.ExtInfo]:
|
|
|
382
473
|
df["result_type"] = serialize_for_storage(converted)
|
|
383
474
|
mod_ext["data_format"] = df
|
|
384
475
|
|
|
476
|
+
if "open_api" in mod_ext:
|
|
477
|
+
del mod_ext["open_api"]
|
|
385
478
|
return ext_info_parser.parse_storage(mod_ext)
|
|
386
479
|
|
|
387
480
|
|
|
388
|
-
def _convert_ext_info(in_ext: Any) ->
|
|
481
|
+
def _convert_ext_info(in_ext: Any) -> PureJsonValue | None:
|
|
389
482
|
# we need to convert this to API storage since it'll be used as-is in the UI
|
|
390
483
|
parsed = _parse_ext_info(in_ext)
|
|
391
484
|
return cast(PureJsonValue, serialize_for_api(parsed))
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import re
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from io import StringIO
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import assert_never
|
|
7
|
+
|
|
8
|
+
from main.base.types import (
|
|
9
|
+
base_t,
|
|
10
|
+
ui_entry_actions_t,
|
|
11
|
+
)
|
|
12
|
+
from pkgs.serialization_util import serialize_for_api
|
|
13
|
+
from pkgs.type_spec import emit_typescript_util
|
|
14
|
+
from pkgs.type_spec.builder import (
|
|
15
|
+
BaseTypeName,
|
|
16
|
+
NameCase,
|
|
17
|
+
RawDict,
|
|
18
|
+
SpecBuilder,
|
|
19
|
+
SpecNamespace,
|
|
20
|
+
SpecTypeDefnObject,
|
|
21
|
+
)
|
|
22
|
+
from pkgs.type_spec.config import Config
|
|
23
|
+
from pkgs.type_spec.load_types import load_types
|
|
24
|
+
from pkgs.type_spec.util import rewrite_file
|
|
25
|
+
from pkgs.type_spec.value_spec.convert_type import convert_from_value_spec_type
|
|
26
|
+
|
|
27
|
+
_INIT_ACTION_INDEX_TYPE_DATA = {
|
|
28
|
+
"EntryActionInfo<InputT, OutputT>": {
|
|
29
|
+
"type": BaseTypeName.s_object,
|
|
30
|
+
"properties": {"inputs": {"type": "InputT"}, "outputs": {"type": "OutputT"}},
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
_TYPES_ROOT = "unc_types"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(kw_only=True)
|
|
37
|
+
class EntryActionTypeInfo:
|
|
38
|
+
inputs_type: SpecTypeDefnObject
|
|
39
|
+
outputs_type: SpecTypeDefnObject
|
|
40
|
+
name: str
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def ui_entry_variable_to_type_spec_type(
|
|
44
|
+
variable: ui_entry_actions_t.UiEntryActionVariable,
|
|
45
|
+
) -> str:
|
|
46
|
+
match variable:
|
|
47
|
+
case ui_entry_actions_t.UiEntryActionVariableString():
|
|
48
|
+
return BaseTypeName.s_string
|
|
49
|
+
case ui_entry_actions_t.UiEntryActionVariableSingleEntity():
|
|
50
|
+
return "ObjectId"
|
|
51
|
+
case _:
|
|
52
|
+
assert_never(variable)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def construct_inputs_type_data(
|
|
56
|
+
vars: dict[str, ui_entry_actions_t.UiEntryActionVariable],
|
|
57
|
+
) -> RawDict:
|
|
58
|
+
if len(vars) == 0:
|
|
59
|
+
return {"type": BaseTypeName.s_object}
|
|
60
|
+
properties: dict[str, dict[str, str]] = {}
|
|
61
|
+
for input_name, input_defn in (vars).items():
|
|
62
|
+
properties[f"{input_name}"] = {
|
|
63
|
+
"type": ui_entry_variable_to_type_spec_type(input_defn)
|
|
64
|
+
}
|
|
65
|
+
return {"type": BaseTypeName.s_object, "properties": properties}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def construct_outputs_type_data(
|
|
69
|
+
vars: dict[str, ui_entry_actions_t.UiEntryActionOutput],
|
|
70
|
+
) -> RawDict:
|
|
71
|
+
if len(vars) == 0:
|
|
72
|
+
return {"type": BaseTypeName.s_object}
|
|
73
|
+
properties: dict[str, dict[str, str]] = {}
|
|
74
|
+
for output_name, output_defn in (vars).items():
|
|
75
|
+
# All outputs are optional
|
|
76
|
+
properties[f"{output_name}"] = {
|
|
77
|
+
"type": f"Optional<{convert_from_value_spec_type(output_defn.vs_type)}>"
|
|
78
|
+
}
|
|
79
|
+
return {"type": BaseTypeName.s_object, "properties": properties}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def construct_outputs_type(
|
|
83
|
+
*,
|
|
84
|
+
action_scope: ui_entry_actions_t.ActionScope,
|
|
85
|
+
vars: dict[str, ui_entry_actions_t.UiEntryActionOutput],
|
|
86
|
+
builder: SpecBuilder,
|
|
87
|
+
namespace: SpecNamespace,
|
|
88
|
+
) -> SpecTypeDefnObject:
|
|
89
|
+
stype = SpecTypeDefnObject(
|
|
90
|
+
namespace=namespace,
|
|
91
|
+
name=emit_typescript_util.ts_type_name(f"{action_scope}_outputs"),
|
|
92
|
+
)
|
|
93
|
+
namespace.types[stype.name] = stype
|
|
94
|
+
stype.process(
|
|
95
|
+
builder=builder,
|
|
96
|
+
data=construct_outputs_type_data(vars=vars),
|
|
97
|
+
)
|
|
98
|
+
return stype
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def construct_inputs_type(
|
|
102
|
+
*,
|
|
103
|
+
action_scope: ui_entry_actions_t.ActionScope,
|
|
104
|
+
vars: dict[str, ui_entry_actions_t.UiEntryActionVariable],
|
|
105
|
+
builder: SpecBuilder,
|
|
106
|
+
namespace: SpecNamespace,
|
|
107
|
+
) -> SpecTypeDefnObject:
|
|
108
|
+
stype = SpecTypeDefnObject(
|
|
109
|
+
namespace=namespace,
|
|
110
|
+
name=emit_typescript_util.ts_type_name(f"{action_scope}_inputs"),
|
|
111
|
+
)
|
|
112
|
+
stype.process(builder=builder, data=construct_inputs_type_data(vars))
|
|
113
|
+
namespace.types[stype.name] = stype
|
|
114
|
+
return stype
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _get_types_root(destination_root: Path) -> Path:
|
|
118
|
+
return destination_root / "types"
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def emit_imports_ts(
|
|
122
|
+
namespaces: set[SpecNamespace],
|
|
123
|
+
out: StringIO,
|
|
124
|
+
) -> None:
|
|
125
|
+
for ns in sorted(
|
|
126
|
+
namespaces,
|
|
127
|
+
key=lambda ns: ns.name,
|
|
128
|
+
):
|
|
129
|
+
import_as = emit_typescript_util.resolve_namespace_ref(ns)
|
|
130
|
+
import_from = f"{_TYPES_ROOT}/{ns.name}"
|
|
131
|
+
out.write(f'import * as {import_as} from "{import_from}"\n')
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def emit_entry_action_definition(
|
|
135
|
+
*,
|
|
136
|
+
ctx: emit_typescript_util.EmitTypescriptContext,
|
|
137
|
+
defn: ui_entry_actions_t.UiEntryActionDefinition,
|
|
138
|
+
builder: SpecBuilder,
|
|
139
|
+
action_scope: ui_entry_actions_t.ActionScope,
|
|
140
|
+
) -> EntryActionTypeInfo:
|
|
141
|
+
inputs_type = construct_inputs_type(
|
|
142
|
+
action_scope=action_scope,
|
|
143
|
+
vars=defn.inputs,
|
|
144
|
+
builder=builder,
|
|
145
|
+
namespace=ctx.namespace,
|
|
146
|
+
)
|
|
147
|
+
outputs_type = construct_outputs_type(
|
|
148
|
+
action_scope=action_scope,
|
|
149
|
+
vars=defn.outputs,
|
|
150
|
+
builder=builder,
|
|
151
|
+
namespace=ctx.namespace,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
return EntryActionTypeInfo(
|
|
155
|
+
inputs_type=inputs_type,
|
|
156
|
+
outputs_type=outputs_type,
|
|
157
|
+
name=action_scope,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _validate_input(input: ui_entry_actions_t.UiEntryActionVariable) -> None:
|
|
162
|
+
if "_" in input.vs_var_name:
|
|
163
|
+
raise ValueError(f"Expected camelCase for variable {input.vs_var_name}")
|
|
164
|
+
if not re.fullmatch(base_t.REF_NAME_STRICT_REGEX, input.vs_var_name):
|
|
165
|
+
raise ValueError(
|
|
166
|
+
f"Variable {input.vs_var_name} has invalid syntax. See REF_NAME_STRICT_REGEX"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def emit_query_index(
|
|
171
|
+
ctx: emit_typescript_util.EmitTypescriptContext,
|
|
172
|
+
defn_infos: list[EntryActionTypeInfo],
|
|
173
|
+
index_path: Path,
|
|
174
|
+
builder: SpecBuilder,
|
|
175
|
+
definitions: dict[
|
|
176
|
+
ui_entry_actions_t.ActionScope, ui_entry_actions_t.UiEntryActionDefinition
|
|
177
|
+
],
|
|
178
|
+
) -> bool:
|
|
179
|
+
query_index_type_data = {
|
|
180
|
+
**_INIT_ACTION_INDEX_TYPE_DATA,
|
|
181
|
+
"EntityActionTypeLookup": {
|
|
182
|
+
"type": BaseTypeName.s_object,
|
|
183
|
+
"properties": {
|
|
184
|
+
defn_info.name: {
|
|
185
|
+
"type": f"EntryActionInfo<{defn_info.inputs_type.name},{defn_info.outputs_type.name}>",
|
|
186
|
+
"name_case": NameCase.preserve,
|
|
187
|
+
}
|
|
188
|
+
for defn_info in defn_infos
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
"InputInfo": {
|
|
192
|
+
"type": BaseTypeName.s_object,
|
|
193
|
+
"properties": {
|
|
194
|
+
"value_spec_var": {"type": "String"},
|
|
195
|
+
"type": {"type": "ui_entry_actions.UiEntryActionDataType"},
|
|
196
|
+
"variable": {"type": "ui_entry_actions.UiEntryActionVariable"},
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
"OutputInfo": {
|
|
200
|
+
"type": BaseTypeName.s_object,
|
|
201
|
+
"properties": {
|
|
202
|
+
"name": {"type": "String"},
|
|
203
|
+
"desc": {"type": "String"},
|
|
204
|
+
"type": {"type": "value_spec.BaseType"},
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
"DefinitionInfo": {
|
|
208
|
+
"type": BaseTypeName.s_object,
|
|
209
|
+
"properties": {
|
|
210
|
+
"inputs": {
|
|
211
|
+
"type": "ReadonlyArray<InputInfo>",
|
|
212
|
+
},
|
|
213
|
+
"outputs": {
|
|
214
|
+
"type": "ReadonlyArray<OutputInfo>",
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
}
|
|
219
|
+
ctx.namespace.prescan(query_index_type_data)
|
|
220
|
+
ctx.namespace.process(
|
|
221
|
+
builder=builder,
|
|
222
|
+
data=query_index_type_data,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
defn_lookup_info = {}
|
|
226
|
+
for scope, defn in definitions.items():
|
|
227
|
+
inputs = []
|
|
228
|
+
outputs = []
|
|
229
|
+
for input in defn.inputs.values():
|
|
230
|
+
_validate_input(input)
|
|
231
|
+
inputs.append(
|
|
232
|
+
serialize_for_api({
|
|
233
|
+
"value_spec_var": input.vs_var_name,
|
|
234
|
+
"type": input.type,
|
|
235
|
+
"variable": input,
|
|
236
|
+
})
|
|
237
|
+
)
|
|
238
|
+
for name, output in defn.outputs.items():
|
|
239
|
+
outputs.append(
|
|
240
|
+
serialize_for_api({
|
|
241
|
+
"name": name,
|
|
242
|
+
"desc": output.description,
|
|
243
|
+
"type": output.vs_type,
|
|
244
|
+
})
|
|
245
|
+
)
|
|
246
|
+
defn_lookup_info[scope] = {"inputs": inputs, "outputs": outputs}
|
|
247
|
+
|
|
248
|
+
defn_lookup_out = f"export const DEFINITION_LOOKUP = {json.dumps(defn_lookup_info, sort_keys=True, indent=2)} as const\n\nexport const DEFINITION_LOOKUP_TYPED = DEFINITION_LOOKUP as Record<UiEntryActionsT.ActionScope, DefinitionInfo>\n"
|
|
249
|
+
|
|
250
|
+
for stype in ctx.namespace.types.values():
|
|
251
|
+
emit_typescript_util.emit_type_ts(
|
|
252
|
+
ctx=ctx,
|
|
253
|
+
stype=stype,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
import_buffer = StringIO()
|
|
257
|
+
emit_typescript_util.emit_namespace_imports_from_root_ts(
|
|
258
|
+
namespaces=ctx.namespaces,
|
|
259
|
+
out=import_buffer,
|
|
260
|
+
root=_TYPES_ROOT,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
return rewrite_file(
|
|
264
|
+
content=import_buffer.getvalue() + ctx.out.getvalue() + defn_lookup_out,
|
|
265
|
+
filename=str(index_path),
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def generate_entry_actions_typescript(
|
|
270
|
+
*,
|
|
271
|
+
definitions: dict[
|
|
272
|
+
ui_entry_actions_t.ActionScope, ui_entry_actions_t.UiEntryActionDefinition
|
|
273
|
+
],
|
|
274
|
+
destination_root: Path,
|
|
275
|
+
materials_type_spec_config: Config,
|
|
276
|
+
) -> None:
|
|
277
|
+
builder = load_types(materials_type_spec_config)
|
|
278
|
+
assert builder is not None
|
|
279
|
+
|
|
280
|
+
definition_buffer = StringIO()
|
|
281
|
+
index_namespace = SpecNamespace(name="index")
|
|
282
|
+
ctx = emit_typescript_util.EmitTypescriptContext(
|
|
283
|
+
out=definition_buffer,
|
|
284
|
+
namespace=index_namespace,
|
|
285
|
+
api_endpoints={},
|
|
286
|
+
)
|
|
287
|
+
builder.namespaces[index_namespace.name] = index_namespace
|
|
288
|
+
|
|
289
|
+
defn_infos: list[EntryActionTypeInfo] = []
|
|
290
|
+
|
|
291
|
+
for action_scope, defn in definitions.items():
|
|
292
|
+
defn_infos.append(
|
|
293
|
+
emit_entry_action_definition(
|
|
294
|
+
action_scope=action_scope,
|
|
295
|
+
ctx=ctx,
|
|
296
|
+
defn=defn,
|
|
297
|
+
builder=builder,
|
|
298
|
+
)
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
index_path = _get_types_root(destination_root) / "index.ts"
|
|
302
|
+
emit_query_index(
|
|
303
|
+
ctx=ctx,
|
|
304
|
+
builder=builder,
|
|
305
|
+
defn_infos=defn_infos,
|
|
306
|
+
definitions=definitions,
|
|
307
|
+
index_path=index_path,
|
|
308
|
+
)
|
pkgs/type_spec/util.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import os
|
|
3
3
|
from dataclasses import dataclass
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import TypeVar, Union
|
|
5
5
|
|
|
6
6
|
import regex as re
|
|
7
7
|
|
|
@@ -29,8 +29,8 @@ LiteralTypeValue = Union[str, bool]
|
|
|
29
29
|
class ParsedTypePart:
|
|
30
30
|
name: str
|
|
31
31
|
# An empty list is distinct from None
|
|
32
|
-
parameters:
|
|
33
|
-
literal_value:
|
|
32
|
+
parameters: list["ParsedTypePath"] | None = None
|
|
33
|
+
literal_value: LiteralTypeValue | None = None
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
ParsedTypePath = list[ParsedTypePart]
|
|
@@ -13,11 +13,14 @@ One of the following can be specified on the name of a argument:
|
|
|
13
13
|
After that you can also specify a `!` indicating the argument may not be null.
|
|
14
14
|
If this is not specified, then a null input on this argument should produce a null output.
|
|
15
15
|
We prefer not to use `!` as we want to encourage null pass-through where possible.
|
|
16
|
-
|
|
16
|
+
|
|
17
|
+
If null is allowed as a legitimate value, such as in conditionals like `is_null`,
|
|
18
|
+
then `!usenull` must be specified, this distinguishes it from the pass-through case.
|
|
19
|
+
The accepted argument type must accept "None", it is not implied.
|
|
17
20
|
"""
|
|
18
21
|
|
|
19
22
|
import sys
|
|
20
|
-
from typing import TypeVar, cast
|
|
23
|
+
from typing import Match, Pattern, TypeVar, cast
|
|
21
24
|
|
|
22
25
|
import regex as re
|
|
23
26
|
|
|
@@ -53,7 +56,7 @@ class Source:
|
|
|
53
56
|
def has_more(self) -> bool:
|
|
54
57
|
return self._at < len(self._text)
|
|
55
58
|
|
|
56
|
-
def match(self, expression:
|
|
59
|
+
def match(self, expression: Pattern[str]) -> Match[str] | None:
|
|
57
60
|
self.skip_space()
|
|
58
61
|
m = expression.match(self._text, self._at)
|
|
59
62
|
if m is not None:
|
|
@@ -84,7 +87,7 @@ class Source:
|
|
|
84
87
|
return self._text[start : self._at]
|
|
85
88
|
|
|
86
89
|
|
|
87
|
-
_re_argument_name = re.compile(r"([a-z_]+)(\?|\+)?(
|
|
90
|
+
_re_argument_name = re.compile(r"([a-z_]+)(\?|\+)?(!|!usenull)?:")
|
|
88
91
|
|
|
89
92
|
|
|
90
93
|
def parse_function_signature(text: str) -> ParsedFunctionSignature:
|
|
@@ -101,11 +104,18 @@ def parse_function_signature(text: str) -> ParsedFunctionSignature:
|
|
|
101
104
|
|
|
102
105
|
type_str = source.extract_type()
|
|
103
106
|
ref_name = arg_group.group(1)
|
|
104
|
-
is_missing = arg_group.group(2) == "?"
|
|
105
|
-
is_repeating = arg_group.group(2) == "+"
|
|
106
|
-
pass_null = arg_group.group(3) is None
|
|
107
|
+
# is_missing = arg_group.group(2) == "?"
|
|
108
|
+
# is_repeating = arg_group.group(2) == "+"
|
|
107
109
|
type_path = parse_type_str(type_str)
|
|
108
110
|
|
|
111
|
+
match arg_group.group(3):
|
|
112
|
+
case "!":
|
|
113
|
+
on_null = value_spec_t.OnNull.DISALLOW
|
|
114
|
+
case "!usenull":
|
|
115
|
+
on_null = value_spec_t.OnNull.USE
|
|
116
|
+
case _:
|
|
117
|
+
on_null = value_spec_t.OnNull.PASS
|
|
118
|
+
|
|
109
119
|
extant = value_spec_t.ArgumentExtant.REQUIRED
|
|
110
120
|
extant_marker = arg_group.group(2)
|
|
111
121
|
if extant_marker == "?":
|
|
@@ -116,7 +126,7 @@ def parse_function_signature(text: str) -> ParsedFunctionSignature:
|
|
|
116
126
|
arguments.append(
|
|
117
127
|
ParsedFunctionArgument(
|
|
118
128
|
ref_name=ref_name,
|
|
119
|
-
|
|
129
|
+
on_null=on_null,
|
|
120
130
|
extant=extant,
|
|
121
131
|
type_path=type_path,
|
|
122
132
|
)
|
|
@@ -145,6 +155,7 @@ key_return = "return"
|
|
|
145
155
|
key_description = "description"
|
|
146
156
|
key_brief = "brief"
|
|
147
157
|
key_name = "name"
|
|
158
|
+
key_draft = "draft"
|
|
148
159
|
|
|
149
160
|
|
|
150
161
|
TypeT = TypeVar("TypeT")
|
|
@@ -208,7 +219,7 @@ def main() -> None:
|
|
|
208
219
|
name=arg_name,
|
|
209
220
|
description=arg_description,
|
|
210
221
|
type=convert_to_value_spec_type(in_argument.type_path),
|
|
211
|
-
|
|
222
|
+
on_null=in_argument.on_null,
|
|
212
223
|
extant=in_argument.extant,
|
|
213
224
|
)
|
|
214
225
|
)
|
|
@@ -219,6 +230,11 @@ def main() -> None:
|
|
|
219
230
|
|
|
220
231
|
brief = get_as(spec, key_brief, str)
|
|
221
232
|
description = get_as(spec, key_description, str)
|
|
233
|
+
draft = (
|
|
234
|
+
get_as(spec, key_draft, bool)
|
|
235
|
+
if spec.get(key_draft) is not None
|
|
236
|
+
else None
|
|
237
|
+
)
|
|
222
238
|
|
|
223
239
|
return_value = get(spec, key_return)
|
|
224
240
|
where.append("return")
|
|
@@ -235,6 +251,7 @@ def main() -> None:
|
|
|
235
251
|
type=convert_to_value_spec_type(parsed.return_type_path),
|
|
236
252
|
description=return_description,
|
|
237
253
|
),
|
|
254
|
+
draft=draft,
|
|
238
255
|
)
|
|
239
256
|
)
|
|
240
257
|
where.pop()
|