UncountablePythonSDK 0.0.8__py3-none-any.whl → 0.0.92__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.92.dist-info/METADATA +61 -0
- UncountablePythonSDK-0.0.92.dist-info/RECORD +301 -0
- {UncountablePythonSDK-0.0.8.dist-info → UncountablePythonSDK-0.0.92.dist-info}/WHEEL +1 -1
- {UncountablePythonSDK-0.0.8.dist-info → UncountablePythonSDK-0.0.92.dist-info}/top_level.txt +1 -1
- docs/.gitignore +1 -0
- docs/conf.py +57 -0
- docs/index.md +13 -0
- docs/justfile +12 -0
- docs/quickstart.md +19 -0
- docs/requirements.txt +7 -0
- docs/static/favicons/android-chrome-192x192.png +0 -0
- docs/static/favicons/android-chrome-512x512.png +0 -0
- docs/static/favicons/apple-touch-icon.png +0 -0
- docs/static/favicons/browserconfig.xml +9 -0
- docs/static/favicons/favicon-16x16.png +0 -0
- docs/static/favicons/favicon-32x32.png +0 -0
- docs/static/favicons/manifest.json +18 -0
- docs/static/favicons/mstile-150x150.png +0 -0
- docs/static/favicons/safari-pinned-tab.svg +32 -0
- docs/static/logo_blue.png +0 -0
- examples/async_batch.py +35 -0
- examples/create_entity.py +22 -17
- examples/download_files.py +26 -0
- examples/edit_recipe_inputs.py +50 -0
- examples/integration-server/jobs/materials_auto/example_cron.py +18 -0
- examples/integration-server/jobs/materials_auto/example_wh.py +15 -0
- examples/integration-server/jobs/materials_auto/profile.yaml +43 -0
- examples/integration-server/pyproject.toml +224 -0
- examples/invoke_uploader.py +26 -0
- examples/set_recipe_metadata_file.py +40 -0
- examples/set_recipe_output_file_sdk.py +26 -0
- examples/upload_files.py +18 -0
- pkgs/argument_parser/__init__.py +5 -0
- pkgs/argument_parser/_is_enum.py +1 -6
- pkgs/argument_parser/argument_parser.py +232 -76
- pkgs/argument_parser/case_convert.py +4 -3
- pkgs/filesystem_utils/__init__.py +20 -0
- pkgs/filesystem_utils/_blob_session.py +137 -0
- pkgs/filesystem_utils/_gdrive_session.py +309 -0
- pkgs/filesystem_utils/_local_session.py +69 -0
- pkgs/filesystem_utils/_s3_session.py +117 -0
- pkgs/filesystem_utils/_sftp_session.py +147 -0
- pkgs/filesystem_utils/file_type_utils.py +91 -0
- pkgs/filesystem_utils/filesystem_session.py +39 -0
- pkgs/py.typed +0 -0
- pkgs/serialization/__init__.py +8 -1
- 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 +65 -50
- pkgs/serialization/serial_generic.py +16 -0
- pkgs/serialization/serial_union.py +84 -0
- pkgs/serialization/yaml.py +57 -0
- pkgs/serialization_util/__init__.py +7 -7
- pkgs/serialization_util/_get_type_for_serialization.py +1 -3
- pkgs/serialization_util/convert_to_snakecase.py +27 -0
- pkgs/serialization_util/dataclasses.py +14 -0
- pkgs/serialization_util/serialization_helpers.py +116 -74
- pkgs/strenum_compat/strenum_compat.py +1 -9
- pkgs/type_spec/actions_registry/__init__.py +0 -0
- pkgs/type_spec/actions_registry/__main__.py +126 -0
- pkgs/type_spec/actions_registry/emit_typescript.py +182 -0
- pkgs/type_spec/builder.py +475 -89
- pkgs/type_spec/config.py +24 -19
- pkgs/type_spec/emit_io_ts.py +5 -2
- pkgs/type_spec/emit_open_api.py +266 -32
- pkgs/type_spec/emit_open_api_util.py +32 -13
- pkgs/type_spec/emit_python.py +599 -151
- pkgs/type_spec/emit_typescript.py +74 -273
- pkgs/type_spec/emit_typescript_util.py +239 -5
- pkgs/type_spec/load_types.py +55 -10
- pkgs/type_spec/open_api_util.py +30 -41
- pkgs/type_spec/parts/base.py.prepart +4 -3
- pkgs/type_spec/type_info/emit_type_info.py +178 -16
- pkgs/type_spec/util.py +11 -11
- pkgs/type_spec/value_spec/__main__.py +3 -3
- pkgs/type_spec/value_spec/convert_type.py +8 -1
- pkgs/type_spec/value_spec/emit_python.py +13 -4
- uncountable/__init__.py +1 -2
- uncountable/core/__init__.py +12 -2
- uncountable/core/async_batch.py +37 -0
- uncountable/core/client.py +293 -43
- uncountable/core/environment.py +41 -0
- uncountable/core/file_upload.py +135 -0
- uncountable/core/types.py +17 -0
- uncountable/integration/__init__.py +0 -0
- uncountable/integration/cli.py +49 -0
- uncountable/integration/construct_client.py +51 -0
- uncountable/integration/cron.py +29 -0
- uncountable/integration/db/__init__.py +0 -0
- uncountable/integration/db/connect.py +18 -0
- uncountable/integration/db/session.py +25 -0
- uncountable/integration/entrypoint.py +13 -0
- uncountable/integration/executors/__init__.py +0 -0
- uncountable/integration/executors/executors.py +148 -0
- uncountable/integration/executors/generic_upload_executor.py +284 -0
- uncountable/integration/executors/script_executor.py +25 -0
- uncountable/integration/job.py +87 -0
- uncountable/integration/queue_runner/__init__.py +0 -0
- uncountable/integration/queue_runner/command_server/__init__.py +24 -0
- uncountable/integration/queue_runner/command_server/command_client.py +68 -0
- uncountable/integration/queue_runner/command_server/command_server.py +64 -0
- uncountable/integration/queue_runner/command_server/protocol/__init__.py +0 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server.proto +22 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.py +40 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.pyi +38 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2_grpc.py +129 -0
- uncountable/integration/queue_runner/command_server/types.py +52 -0
- uncountable/integration/queue_runner/datastore/__init__.py +3 -0
- uncountable/integration/queue_runner/datastore/datastore_sqlite.py +93 -0
- uncountable/integration/queue_runner/datastore/interface.py +19 -0
- uncountable/integration/queue_runner/datastore/model.py +17 -0
- uncountable/integration/queue_runner/job_scheduler.py +163 -0
- uncountable/integration/queue_runner/queue_runner.py +26 -0
- uncountable/integration/queue_runner/types.py +7 -0
- uncountable/integration/queue_runner/worker.py +119 -0
- uncountable/integration/scan_profiles.py +67 -0
- uncountable/integration/scheduler.py +150 -0
- uncountable/integration/secret_retrieval/__init__.py +3 -0
- uncountable/integration/secret_retrieval/retrieve_secret.py +93 -0
- uncountable/integration/server.py +117 -0
- uncountable/integration/telemetry.py +209 -0
- uncountable/integration/webhook_server/entrypoint.py +170 -0
- uncountable/types/__init__.py +136 -20
- uncountable/types/api/batch/execute_batch.py +15 -7
- uncountable/types/api/batch/execute_batch_load_async.py +42 -0
- uncountable/types/api/chemical/__init__.py +1 -0
- uncountable/types/api/chemical/convert_chemical_formats.py +63 -0
- uncountable/types/api/entity/create_entities.py +23 -11
- uncountable/types/api/entity/create_entity.py +21 -12
- uncountable/types/api/entity/get_entities_data.py +18 -8
- uncountable/types/api/entity/grant_entity_permissions.py +48 -0
- uncountable/types/api/entity/list_entities.py +27 -12
- uncountable/types/api/entity/lock_entity.py +45 -0
- uncountable/types/api/entity/resolve_entity_ids.py +17 -7
- uncountable/types/api/entity/set_entity_field_values.py +44 -0
- uncountable/types/api/entity/set_values.py +14 -7
- uncountable/types/api/entity/transition_entity_phase.py +80 -0
- uncountable/types/api/entity/unlock_entity.py +44 -0
- uncountable/types/api/equipment/__init__.py +1 -0
- uncountable/types/api/equipment/associate_equipment_input.py +44 -0
- uncountable/types/api/field_options/__init__.py +1 -0
- uncountable/types/api/field_options/upsert_field_options.py +55 -0
- uncountable/types/api/files/__init__.py +1 -0
- uncountable/types/api/files/download_file.py +77 -0
- uncountable/types/api/id_source/__init__.py +1 -0
- uncountable/types/api/id_source/list_id_source.py +56 -0
- uncountable/types/api/id_source/match_id_source.py +54 -0
- uncountable/types/api/input_groups/get_input_group_names.py +16 -6
- uncountable/types/api/inputs/create_inputs.py +24 -11
- uncountable/types/api/inputs/get_input_data.py +32 -13
- uncountable/types/api/inputs/get_input_names.py +18 -8
- uncountable/types/api/inputs/get_inputs_data.py +29 -10
- uncountable/types/api/inputs/set_input_attribute_values.py +16 -9
- uncountable/types/api/inputs/set_input_category.py +44 -0
- uncountable/types/api/inputs/set_input_subcategories.py +45 -0
- uncountable/types/api/inputs/set_intermediate_type.py +50 -0
- uncountable/types/api/material_families/__init__.py +1 -0
- uncountable/types/api/material_families/update_entity_material_families.py +48 -0
- uncountable/types/api/outputs/get_output_data.py +32 -16
- uncountable/types/api/outputs/get_output_names.py +18 -8
- uncountable/types/api/outputs/resolve_output_conditions.py +23 -10
- uncountable/types/api/permissions/__init__.py +1 -0
- uncountable/types/api/permissions/set_core_permissions.py +105 -0
- uncountable/types/api/project/get_projects.py +17 -7
- uncountable/types/api/project/get_projects_data.py +21 -11
- uncountable/types/api/recipe_links/__init__.py +1 -0
- uncountable/types/api/recipe_links/create_recipe_link.py +46 -0
- uncountable/types/api/recipe_links/remove_recipe_link.py +45 -0
- uncountable/types/api/recipe_metadata/get_recipe_metadata_data.py +18 -8
- uncountable/types/api/recipes/add_recipe_to_project.py +42 -0
- uncountable/types/api/recipes/archive_recipes.py +42 -0
- uncountable/types/api/recipes/associate_recipe_as_input.py +44 -0
- uncountable/types/api/recipes/associate_recipe_as_lot.py +43 -0
- uncountable/types/api/recipes/clear_recipe_outputs.py +42 -0
- uncountable/types/api/recipes/create_recipe.py +51 -0
- uncountable/types/api/recipes/create_recipes.py +25 -12
- uncountable/types/api/recipes/disassociate_recipe_as_input.py +42 -0
- uncountable/types/api/recipes/edit_recipe_inputs.py +283 -0
- uncountable/types/api/recipes/get_column_calculation_values.py +58 -0
- uncountable/types/api/recipes/get_curve.py +15 -7
- uncountable/types/api/recipes/get_recipe_calculations.py +17 -10
- uncountable/types/api/recipes/get_recipe_links.py +13 -6
- uncountable/types/api/recipes/get_recipe_names.py +16 -6
- uncountable/types/api/recipes/get_recipe_output_metadata.py +14 -7
- uncountable/types/api/recipes/get_recipes_data.py +63 -38
- uncountable/types/api/recipes/lock_recipes.py +63 -0
- uncountable/types/api/recipes/remove_recipe_from_project.py +42 -0
- uncountable/types/api/recipes/set_recipe_inputs.py +19 -10
- uncountable/types/api/recipes/set_recipe_metadata.py +43 -0
- uncountable/types/api/recipes/set_recipe_output_annotations.py +115 -0
- uncountable/types/api/recipes/set_recipe_output_file.py +56 -0
- uncountable/types/api/recipes/set_recipe_outputs.py +26 -12
- uncountable/types/api/recipes/set_recipe_tags.py +109 -0
- uncountable/types/api/recipes/unarchive_recipes.py +41 -0
- uncountable/types/api/recipes/unlock_recipes.py +50 -0
- uncountable/types/api/triggers/__init__.py +1 -0
- uncountable/types/api/triggers/run_trigger.py +43 -0
- uncountable/types/api/uploader/__init__.py +1 -0
- uncountable/types/api/uploader/invoke_uploader.py +47 -0
- uncountable/types/async_batch.py +13 -0
- uncountable/types/async_batch_processor.py +384 -0
- uncountable/types/async_batch_t.py +97 -0
- uncountable/types/async_jobs.py +9 -0
- uncountable/types/async_jobs_t.py +53 -0
- uncountable/types/auth_retrieval.py +12 -0
- uncountable/types/auth_retrieval_t.py +75 -0
- uncountable/types/base.py +5 -78
- uncountable/types/base_t.py +85 -0
- uncountable/types/calculations.py +3 -18
- uncountable/types/calculations_t.py +27 -0
- uncountable/types/chemical_structure.py +8 -0
- uncountable/types/chemical_structure_t.py +28 -0
- uncountable/types/client_base.py +1093 -54
- uncountable/types/client_config.py +8 -0
- uncountable/types/client_config_t.py +26 -0
- uncountable/types/curves.py +5 -42
- uncountable/types/curves_t.py +51 -0
- uncountable/types/entity.py +8 -269
- uncountable/types/entity_t.py +393 -0
- uncountable/types/experiment_groups.py +3 -18
- uncountable/types/experiment_groups_t.py +27 -0
- uncountable/types/field_values.py +17 -60
- uncountable/types/field_values_t.py +204 -0
- uncountable/types/fields.py +3 -19
- uncountable/types/fields_t.py +28 -0
- uncountable/types/generic_upload.py +15 -0
- uncountable/types/generic_upload_t.py +119 -0
- uncountable/types/id_source.py +12 -0
- uncountable/types/id_source_t.py +68 -0
- uncountable/types/identifier.py +11 -0
- uncountable/types/identifier_t.py +63 -0
- uncountable/types/input_attributes.py +3 -24
- uncountable/types/input_attributes_t.py +30 -0
- uncountable/types/inputs.py +6 -56
- uncountable/types/inputs_t.py +83 -0
- uncountable/types/integration_server.py +9 -0
- uncountable/types/integration_server_t.py +42 -0
- uncountable/types/job_definition.py +27 -0
- uncountable/types/job_definition_t.py +260 -0
- uncountable/types/outputs.py +3 -21
- uncountable/types/outputs_t.py +30 -0
- uncountable/types/overrides.py +10 -0
- uncountable/types/overrides_t.py +49 -0
- uncountable/types/permissions.py +8 -0
- uncountable/types/permissions_t.py +46 -0
- uncountable/types/phases.py +3 -18
- uncountable/types/phases_t.py +27 -0
- uncountable/types/post_base.py +8 -0
- uncountable/types/post_base_t.py +30 -0
- uncountable/types/queued_job.py +16 -0
- uncountable/types/queued_job_t.py +123 -0
- uncountable/types/recipe_identifiers.py +12 -0
- uncountable/types/recipe_identifiers_t.py +76 -0
- uncountable/types/recipe_inputs.py +9 -0
- uncountable/types/recipe_inputs_t.py +30 -0
- uncountable/types/recipe_links.py +4 -45
- uncountable/types/recipe_links_t.py +54 -0
- uncountable/types/recipe_metadata.py +5 -45
- uncountable/types/recipe_metadata_t.py +58 -0
- uncountable/types/recipe_output_metadata.py +3 -19
- uncountable/types/recipe_output_metadata_t.py +28 -0
- uncountable/types/recipe_tags.py +3 -18
- uncountable/types/recipe_tags_t.py +27 -0
- uncountable/types/recipe_workflow_steps.py +14 -0
- uncountable/types/recipe_workflow_steps_t.py +95 -0
- uncountable/types/recipes.py +8 -0
- uncountable/types/recipes_t.py +25 -0
- uncountable/types/response.py +3 -20
- uncountable/types/response_t.py +26 -0
- uncountable/types/secret_retrieval.py +12 -0
- uncountable/types/secret_retrieval_t.py +75 -0
- uncountable/types/units.py +3 -18
- uncountable/types/units_t.py +27 -0
- uncountable/types/users.py +3 -19
- uncountable/types/users_t.py +28 -0
- uncountable/types/webhook_job.py +9 -0
- uncountable/types/webhook_job_t.py +37 -0
- uncountable/types/workflows.py +4 -27
- uncountable/types/workflows_t.py +39 -0
- UncountablePythonSDK-0.0.8.dist-info/METADATA +0 -27
- UncountablePythonSDK-0.0.8.dist-info/RECORD +0 -134
- examples/recipe-import/importer.py +0 -39
- type_spec/external/api/batch/execute_batch.yaml +0 -56
- type_spec/external/api/entity/create_entities.yaml +0 -33
- type_spec/external/api/entity/create_entity.yaml +0 -39
- type_spec/external/api/entity/get_entities_data.yaml +0 -29
- type_spec/external/api/entity/list_entities.yaml +0 -52
- type_spec/external/api/entity/resolve_entity_ids.yaml +0 -29
- type_spec/external/api/entity/set_values.yaml +0 -18
- type_spec/external/api/input_groups/get_input_group_names.yaml +0 -29
- type_spec/external/api/inputs/create_inputs.yaml +0 -48
- type_spec/external/api/inputs/get_input_data.yaml +0 -95
- type_spec/external/api/inputs/get_input_names.yaml +0 -38
- type_spec/external/api/inputs/get_inputs_data.yaml +0 -82
- type_spec/external/api/inputs/set_input_attribute_values.yaml +0 -33
- type_spec/external/api/outputs/get_output_data.yaml +0 -92
- type_spec/external/api/outputs/get_output_names.yaml +0 -35
- type_spec/external/api/outputs/resolve_output_conditions.yaml +0 -50
- type_spec/external/api/project/get_projects.yaml +0 -42
- type_spec/external/api/project/get_projects_data.yaml +0 -50
- type_spec/external/api/recipe_metadata/get_recipe_metadata_data.yaml +0 -41
- type_spec/external/api/recipes/create_recipes.yaml +0 -47
- type_spec/external/api/recipes/get_curve.yaml +0 -18
- type_spec/external/api/recipes/get_recipe_calculations.yaml +0 -39
- type_spec/external/api/recipes/get_recipe_links.yaml +0 -26
- type_spec/external/api/recipes/get_recipe_names.yaml +0 -29
- type_spec/external/api/recipes/get_recipe_output_metadata.yaml +0 -36
- type_spec/external/api/recipes/get_recipes_data.yaml +0 -238
- type_spec/external/api/recipes/set_recipe_inputs.yaml +0 -36
- type_spec/external/api/recipes/set_recipe_outputs.yaml +0 -52
|
@@ -3,23 +3,38 @@ import typing
|
|
|
3
3
|
from dataclasses import dataclass, field
|
|
4
4
|
|
|
5
5
|
from . import builder, util
|
|
6
|
-
from .config import TypeScriptConfig
|
|
7
6
|
|
|
8
7
|
INDENT = " "
|
|
9
8
|
|
|
10
9
|
MODIFY_NOTICE = "// DO NOT MODIFY -- This file is generated by type_spec\n"
|
|
11
10
|
|
|
12
11
|
|
|
12
|
+
base_name_map = {
|
|
13
|
+
builder.BaseTypeName.s_boolean: "boolean",
|
|
14
|
+
builder.BaseTypeName.s_date: "string", # IMPROVE: Aliased DateStr
|
|
15
|
+
builder.BaseTypeName.s_date_time: "string", # IMPROVE: Aliased DateTimeStr
|
|
16
|
+
# Decimal's are marked as to_string_values thus are strings in the front-end
|
|
17
|
+
builder.BaseTypeName.s_decimal: "string",
|
|
18
|
+
builder.BaseTypeName.s_dict: "PartialRecord",
|
|
19
|
+
builder.BaseTypeName.s_integer: "number",
|
|
20
|
+
builder.BaseTypeName.s_lossy_decimal: "number",
|
|
21
|
+
builder.BaseTypeName.s_opaque_key: "string",
|
|
22
|
+
builder.BaseTypeName.s_none: "null",
|
|
23
|
+
builder.BaseTypeName.s_string: "string",
|
|
24
|
+
# UNC: global types
|
|
25
|
+
builder.BaseTypeName.s_json_value: "JsonValue",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
13
29
|
@dataclass(kw_only=True)
|
|
14
30
|
class EmitTypescriptContext:
|
|
15
|
-
config: TypeScriptConfig
|
|
16
31
|
out: io.StringIO
|
|
17
32
|
namespace: builder.SpecNamespace
|
|
18
|
-
namespaces:
|
|
33
|
+
namespaces: set[builder.SpecNamespace] = field(default_factory=set)
|
|
19
34
|
|
|
20
35
|
|
|
21
36
|
def ts_type_name(name: str) -> str:
|
|
22
|
-
return "".join([x.
|
|
37
|
+
return "".join([x.title() for x in name.split("_")])
|
|
23
38
|
|
|
24
39
|
|
|
25
40
|
def resolve_namespace_ref(namespace: builder.SpecNamespace) -> str:
|
|
@@ -30,4 +45,223 @@ def ts_name(name: str, name_case: builder.NameCase) -> str:
|
|
|
30
45
|
if name_case == builder.NameCase.preserve:
|
|
31
46
|
return name
|
|
32
47
|
bits = util.split_any_name(name)
|
|
33
|
-
return "".join([bits[0], *[x.
|
|
48
|
+
return "".join([bits[0], *[x.title() for x in bits[1:]]])
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def emit_value_ts(
|
|
52
|
+
ctx: EmitTypescriptContext, stype: builder.SpecType, value: typing.Any
|
|
53
|
+
) -> str:
|
|
54
|
+
"""Mimics emit_python even if not all types are used in TypeScript yet"""
|
|
55
|
+
literal = builder.unwrap_literal_type(stype)
|
|
56
|
+
if literal is not None:
|
|
57
|
+
return emit_value_ts(ctx, literal.value_type, literal.value)
|
|
58
|
+
|
|
59
|
+
if stype.is_base_type(builder.BaseTypeName.s_string):
|
|
60
|
+
assert isinstance(value, str)
|
|
61
|
+
return util.encode_common_string(value)
|
|
62
|
+
elif stype.is_base_type(builder.BaseTypeName.s_integer):
|
|
63
|
+
assert isinstance(value, int)
|
|
64
|
+
return str(value)
|
|
65
|
+
elif stype.is_base_type(builder.BaseTypeName.s_boolean):
|
|
66
|
+
assert isinstance(value, bool)
|
|
67
|
+
return "true" if value else "false"
|
|
68
|
+
elif stype.is_base_type(builder.BaseTypeName.s_lossy_decimal):
|
|
69
|
+
return str(value)
|
|
70
|
+
elif stype.is_base_type(builder.BaseTypeName.s_decimal):
|
|
71
|
+
return f"'{value}'"
|
|
72
|
+
elif isinstance(stype, builder.SpecTypeInstance):
|
|
73
|
+
if stype.defn_type.is_base_type(builder.BaseTypeName.s_list):
|
|
74
|
+
sub_type = stype.parameters[0]
|
|
75
|
+
return (
|
|
76
|
+
"[" + ", ".join([emit_value_ts(ctx, sub_type, x) for x in value]) + "]"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
if stype.defn_type.is_base_type(builder.BaseTypeName.s_dict):
|
|
80
|
+
key_type = stype.parameters[0]
|
|
81
|
+
value_type = stype.parameters[1]
|
|
82
|
+
return (
|
|
83
|
+
"{\n\t"
|
|
84
|
+
+ ",\n\t".join(
|
|
85
|
+
(
|
|
86
|
+
f"[{emit_value_ts(ctx, key_type, dkey)}]: "
|
|
87
|
+
if not key_type.is_base_type(builder.BaseTypeName.s_string)
|
|
88
|
+
else f"{dkey}: "
|
|
89
|
+
)
|
|
90
|
+
+ emit_value_ts(ctx, value_type, dvalue)
|
|
91
|
+
for dkey, dvalue in value.items()
|
|
92
|
+
)
|
|
93
|
+
+ "\n}"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
if stype.defn_type.is_base_type(builder.BaseTypeName.s_optional):
|
|
97
|
+
sub_type = stype.parameters[0]
|
|
98
|
+
if value is None:
|
|
99
|
+
return "null"
|
|
100
|
+
return emit_value_ts(ctx, sub_type, value)
|
|
101
|
+
|
|
102
|
+
elif isinstance(stype, builder.SpecTypeDefnStringEnum):
|
|
103
|
+
return f"{refer_to(ctx, stype)}.{ts_enum_name(value, stype.name_case)}"
|
|
104
|
+
|
|
105
|
+
raise Exception("invalid constant type", value, stype)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def emit_type_ts(ctx: EmitTypescriptContext, stype: builder.SpecType) -> None:
|
|
109
|
+
if not isinstance(stype, builder.SpecTypeDefn):
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
if stype.is_base or stype.is_predefined:
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
ctx.out.write("\n")
|
|
116
|
+
ctx.out.write(MODIFY_NOTICE)
|
|
117
|
+
|
|
118
|
+
if isinstance(stype, builder.SpecTypeDefnExternal):
|
|
119
|
+
assert not stype.is_exported, "expecting private names"
|
|
120
|
+
ctx.out.write(stype.external_map["ts"])
|
|
121
|
+
ctx.out.write("\n")
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
assert stype.is_exported, "expecting exported names"
|
|
125
|
+
if isinstance(stype, builder.SpecTypeDefnAlias):
|
|
126
|
+
ctx.out.write(f"export type {stype.name} = {refer_to(ctx, stype.alias)}\n")
|
|
127
|
+
return
|
|
128
|
+
|
|
129
|
+
if isinstance(stype, builder.SpecTypeDefnUnion):
|
|
130
|
+
ctx.out.write(
|
|
131
|
+
f"export type {stype.name} = {refer_to(ctx, stype.get_backing_type())}\n"
|
|
132
|
+
)
|
|
133
|
+
return
|
|
134
|
+
|
|
135
|
+
if isinstance(stype, builder.SpecTypeDefnStringEnum):
|
|
136
|
+
ctx.out.write(f"export enum {stype.name} {{\n")
|
|
137
|
+
assert stype.values
|
|
138
|
+
for name, entry in stype.values.items():
|
|
139
|
+
ctx.out.write(
|
|
140
|
+
f'{INDENT}{ts_enum_name(name, stype.name_case)} = "{entry.value}",\n'
|
|
141
|
+
)
|
|
142
|
+
ctx.out.write("}\n")
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
assert isinstance(stype, builder.SpecTypeDefnObject)
|
|
146
|
+
assert stype.base is not None
|
|
147
|
+
|
|
148
|
+
base_type = ""
|
|
149
|
+
if not stype.base.is_base:
|
|
150
|
+
base_type = f"{refer_to(ctx, stype.base)} & "
|
|
151
|
+
|
|
152
|
+
if stype.properties is None and base_type == "":
|
|
153
|
+
ctx.out.write(f"export type {stype.name} = TEmpty\n")
|
|
154
|
+
elif stype.properties is None:
|
|
155
|
+
ctx.out.write(f"export type {stype.name} = {base_type}{{}}\n")
|
|
156
|
+
else:
|
|
157
|
+
if isinstance(stype, builder.SpecTypeDefnObject) and len(stype.parameters) > 0:
|
|
158
|
+
full_type_name = f"{stype.name}<{', '.join(stype.parameters)}>"
|
|
159
|
+
else:
|
|
160
|
+
full_type_name = stype.name
|
|
161
|
+
ctx.out.write(f"export type {full_type_name} = {base_type}{{")
|
|
162
|
+
ctx.out.write("\n")
|
|
163
|
+
for prop in stype.properties.values():
|
|
164
|
+
ref_type = refer_to(ctx, prop.spec_type)
|
|
165
|
+
prop_name = ts_name(prop.name, prop.name_case)
|
|
166
|
+
if prop.has_default and not prop.parse_require:
|
|
167
|
+
# For now, we'll assume the generated types with defaults are meant as
|
|
168
|
+
# arguments, thus treat like extant==missing
|
|
169
|
+
# IMPROVE: if we can decide they are meant as output instead, then
|
|
170
|
+
# they should be marked as required
|
|
171
|
+
ctx.out.write(f"{INDENT}{prop_name}?: {ref_type}")
|
|
172
|
+
elif prop.extant == builder.PropertyExtant.missing:
|
|
173
|
+
# Unlike optional below, missing does not imply null is possible. They
|
|
174
|
+
# treated distinctly.
|
|
175
|
+
ctx.out.write(f"{INDENT}{prop_name}?: {ref_type}")
|
|
176
|
+
elif prop.extant == builder.PropertyExtant.optional:
|
|
177
|
+
# Need to add in |null since Python side can produce null's right now
|
|
178
|
+
# IMPROVE: It would be better if the serializer could instead omit the None's
|
|
179
|
+
# Dropping the null should be forward compatible
|
|
180
|
+
ctx.out.write(f"{INDENT}{prop_name}?: {ref_type} | null")
|
|
181
|
+
else:
|
|
182
|
+
ctx.out.write(f"{INDENT}{prop_name}: {ref_type}")
|
|
183
|
+
ctx.out.write("\n")
|
|
184
|
+
ctx.out.write("}\n")
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def refer_to(ctx: EmitTypescriptContext, stype: builder.SpecType) -> str:
|
|
188
|
+
return refer_to_impl(ctx, stype)[0]
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def refer_to_impl(
|
|
192
|
+
ctx: EmitTypescriptContext, stype: builder.SpecType
|
|
193
|
+
) -> tuple[str, bool]:
|
|
194
|
+
"""
|
|
195
|
+
@return (string-specific, multiple-types)
|
|
196
|
+
"""
|
|
197
|
+
if isinstance(stype, builder.SpecTypeInstance):
|
|
198
|
+
if stype.defn_type.name == builder.BaseTypeName.s_list:
|
|
199
|
+
spec, multi = refer_to_impl(ctx, stype.parameters[0])
|
|
200
|
+
return f"({spec})[]" if multi else f"{spec}[]", False
|
|
201
|
+
if stype.defn_type.name == builder.BaseTypeName.s_readonly_array:
|
|
202
|
+
spec, multi = refer_to_impl(ctx, stype.parameters[0])
|
|
203
|
+
return f"readonly ({spec})[]" if multi else f"readonly {spec}[]", False
|
|
204
|
+
if stype.defn_type.name == builder.BaseTypeName.s_union:
|
|
205
|
+
return (
|
|
206
|
+
f"({' | '.join([refer_to(ctx, p) for p in stype.parameters])})",
|
|
207
|
+
False,
|
|
208
|
+
)
|
|
209
|
+
if stype.defn_type.name == builder.BaseTypeName.s_literal:
|
|
210
|
+
parts = []
|
|
211
|
+
for parameter in stype.parameters:
|
|
212
|
+
assert isinstance(parameter, builder.SpecTypeLiteralWrapper)
|
|
213
|
+
parts.append(refer_to(ctx, parameter))
|
|
214
|
+
return f"({' | '.join(parts)})", False
|
|
215
|
+
if stype.defn_type.name == builder.BaseTypeName.s_optional:
|
|
216
|
+
return f"{refer_to(ctx, stype.parameters[0])} | null", True
|
|
217
|
+
if stype.defn_type.name == builder.BaseTypeName.s_tuple:
|
|
218
|
+
return f"[{', '.join([refer_to(ctx, p) for p in stype.parameters])}]", False
|
|
219
|
+
params = ", ".join([refer_to(ctx, p) for p in stype.parameters])
|
|
220
|
+
return f"{refer_to(ctx, stype.defn_type)}<{params}>", False
|
|
221
|
+
|
|
222
|
+
if isinstance(stype, builder.SpecTypeLiteralWrapper):
|
|
223
|
+
return emit_value_ts(ctx, stype.value_type, stype.value), False
|
|
224
|
+
|
|
225
|
+
if isinstance(stype, builder.SpecTypeGenericParameter):
|
|
226
|
+
return stype.name, False
|
|
227
|
+
|
|
228
|
+
assert isinstance(stype, builder.SpecTypeDefn)
|
|
229
|
+
if stype.is_base: # assume correct namespace
|
|
230
|
+
if stype.name == builder.BaseTypeName.s_list:
|
|
231
|
+
return "any[]", False # TODO: generic type
|
|
232
|
+
return base_name_map[builder.BaseTypeName(stype.name)], False
|
|
233
|
+
|
|
234
|
+
if stype.namespace == ctx.namespace:
|
|
235
|
+
return stype.name, False
|
|
236
|
+
|
|
237
|
+
ctx.namespaces.add(stype.namespace)
|
|
238
|
+
return f"{resolve_namespace_ref(stype.namespace)}.{stype.name}", False
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def ts_enum_name(name: str, name_case: builder.NameCase) -> str:
|
|
242
|
+
if name_case == builder.NameCase.js_upper:
|
|
243
|
+
return name.upper()
|
|
244
|
+
return ts_name(name, name_case)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def resolve_namespace_name(namespace: builder.SpecNamespace) -> str:
|
|
248
|
+
return namespace.name
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def emit_namespace_imports_ts(
|
|
252
|
+
namespaces: set[builder.SpecNamespace],
|
|
253
|
+
out: io.StringIO,
|
|
254
|
+
current_namespace: builder.SpecNamespace,
|
|
255
|
+
) -> None:
|
|
256
|
+
for ns in sorted(
|
|
257
|
+
namespaces,
|
|
258
|
+
key=lambda name: resolve_namespace_name(name),
|
|
259
|
+
):
|
|
260
|
+
import_as = resolve_namespace_ref(ns)
|
|
261
|
+
import_path = (
|
|
262
|
+
"./"
|
|
263
|
+
if len(current_namespace.path) == 1
|
|
264
|
+
else "../" * (len(current_namespace.path) - 1)
|
|
265
|
+
)
|
|
266
|
+
import_from = f"{import_path}{resolve_namespace_name(ns)}"
|
|
267
|
+
out.write(f'import * as {import_as} from "{import_from}"\n') # noqa: E501
|
pkgs/type_spec/load_types.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import os
|
|
2
|
-
from
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
from io import StringIO
|
|
3
4
|
|
|
4
|
-
import yaml
|
|
5
5
|
from shelljob import fs
|
|
6
6
|
|
|
7
|
+
from pkgs.serialization import yaml
|
|
8
|
+
|
|
7
9
|
from .builder import SpecBuilder
|
|
8
10
|
from .config import Config
|
|
9
11
|
|
|
@@ -12,17 +14,36 @@ ext_map = {
|
|
|
12
14
|
".py": "python",
|
|
13
15
|
}
|
|
14
16
|
|
|
17
|
+
_DOC_FILE_REFEX = ".*/docs/(examples|guides)/.*yaml"
|
|
18
|
+
_EXAMPLE_FILE_REGEX = ".*/docs/examples/.*yaml"
|
|
19
|
+
_GUIDE_FILE_REGEX = ".*/docs/guides/.*md"
|
|
20
|
+
|
|
15
21
|
|
|
16
22
|
def find_and_handle_files(
|
|
17
|
-
|
|
23
|
+
*,
|
|
24
|
+
root_folder: str,
|
|
25
|
+
handler: Callable[[str, str], None],
|
|
26
|
+
name_regex: str | None = None,
|
|
27
|
+
not_name_regex: str | None = None,
|
|
28
|
+
whole_name_regex: str | None = None,
|
|
29
|
+
not_whole_name_regex: str | None = None,
|
|
18
30
|
) -> None:
|
|
19
|
-
for file_name in fs.find(
|
|
31
|
+
for file_name in fs.find(
|
|
32
|
+
root_folder,
|
|
33
|
+
name_regex=name_regex,
|
|
34
|
+
not_name_regex=not_name_regex,
|
|
35
|
+
whole_name_regex=whole_name_regex,
|
|
36
|
+
not_whole_name_regex=not_whole_name_regex,
|
|
37
|
+
relative=True,
|
|
38
|
+
):
|
|
20
39
|
with open(os.path.join(root_folder, file_name), encoding="utf-8") as file:
|
|
21
40
|
handler(file_name, file.read())
|
|
22
41
|
|
|
23
42
|
|
|
24
|
-
def load_types(config: Config) ->
|
|
25
|
-
builder = SpecBuilder(
|
|
43
|
+
def load_types(config: Config) -> SpecBuilder | None:
|
|
44
|
+
builder = SpecBuilder(
|
|
45
|
+
api_endpoints=config.api_endpoint, top_namespace=config.top_namespace
|
|
46
|
+
)
|
|
26
47
|
|
|
27
48
|
def handle_builder_add(
|
|
28
49
|
file_name: str, file_content: str, handler: Callable[[str, str, str], None]
|
|
@@ -31,9 +52,16 @@ def load_types(config: Config) -> Optional[SpecBuilder]:
|
|
|
31
52
|
name, ext = os.path.splitext(by_name)
|
|
32
53
|
handler(ext_map[ext], name, file_content)
|
|
33
54
|
|
|
55
|
+
def handle_builder_example_add(file_name: str, file_content: str) -> None:
|
|
56
|
+
yaml_content = yaml.safe_load(StringIO(file_content))
|
|
57
|
+
builder.add_example_file(yaml_content)
|
|
58
|
+
|
|
59
|
+
def handle_builder_guide_add(file_name: str, file_content: str) -> None:
|
|
60
|
+
builder.add_guide_file(file_content)
|
|
61
|
+
|
|
34
62
|
for folder in config.type_spec_types:
|
|
35
63
|
find_and_handle_files(
|
|
36
|
-
folder,
|
|
64
|
+
root_folder=folder,
|
|
37
65
|
name_regex=".*\\.(ts|py)\\.part",
|
|
38
66
|
handler=lambda file_name, file_content: handle_builder_add(
|
|
39
67
|
file_name, file_content, builder.add_part_file
|
|
@@ -42,7 +70,7 @@ def load_types(config: Config) -> Optional[SpecBuilder]:
|
|
|
42
70
|
|
|
43
71
|
for folder in config.type_spec_types:
|
|
44
72
|
find_and_handle_files(
|
|
45
|
-
folder,
|
|
73
|
+
root_folder=folder,
|
|
46
74
|
name_regex=".*\\.(ts|py)\\.prepart",
|
|
47
75
|
handler=lambda file_name, file_content: handle_builder_add(
|
|
48
76
|
file_name, file_content, builder.add_prepart_file
|
|
@@ -63,11 +91,28 @@ def load_types(config: Config) -> Optional[SpecBuilder]:
|
|
|
63
91
|
|
|
64
92
|
for folder in config.type_spec_types:
|
|
65
93
|
find_and_handle_files(
|
|
66
|
-
folder,
|
|
94
|
+
root_folder=folder,
|
|
95
|
+
name_regex=".*\\.yaml",
|
|
96
|
+
not_whole_name_regex=_DOC_FILE_REFEX,
|
|
97
|
+
handler=builder_prescan_file,
|
|
67
98
|
)
|
|
68
99
|
|
|
100
|
+
if config.open_api is not None:
|
|
101
|
+
for folder in config.type_spec_types:
|
|
102
|
+
find_and_handle_files(
|
|
103
|
+
root_folder=folder,
|
|
104
|
+
whole_name_regex=_EXAMPLE_FILE_REGEX,
|
|
105
|
+
handler=handle_builder_example_add,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
for folder in config.type_spec_types:
|
|
109
|
+
find_and_handle_files(
|
|
110
|
+
root_folder=folder,
|
|
111
|
+
whole_name_regex=_GUIDE_FILE_REGEX,
|
|
112
|
+
handler=handle_builder_guide_add,
|
|
113
|
+
)
|
|
114
|
+
|
|
69
115
|
if not builder.process():
|
|
70
116
|
return None
|
|
71
117
|
|
|
72
118
|
return builder
|
|
73
|
-
return True
|
pkgs/type_spec/open_api_util.py
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
-
from enum import
|
|
3
|
-
from io import UnsupportedOperation
|
|
4
|
-
from typing import Optional
|
|
2
|
+
from enum import StrEnum
|
|
5
3
|
|
|
6
4
|
|
|
7
5
|
class OpenAPIType(ABC):
|
|
@@ -13,10 +11,8 @@ class OpenAPIType(ABC):
|
|
|
13
11
|
self.nullable = nullable
|
|
14
12
|
|
|
15
13
|
@abstractmethod
|
|
16
|
-
def asdict(self) -> dict[str, object]:
|
|
17
|
-
|
|
18
|
-
def asarguments(self) -> list[dict[str, object]]:
|
|
19
|
-
raise UnsupportedOperation
|
|
14
|
+
def asdict(self) -> dict[str, object]:
|
|
15
|
+
pass
|
|
20
16
|
|
|
21
17
|
def add_addl_info(self, emitted: dict[str, object]) -> dict[str, object]:
|
|
22
18
|
if self.description is not None:
|
|
@@ -40,7 +36,7 @@ class OpenAPIRefType(OpenAPIType):
|
|
|
40
36
|
return {"$ref": self.source}
|
|
41
37
|
|
|
42
38
|
|
|
43
|
-
class OpenAPIPrimitive(
|
|
39
|
+
class OpenAPIPrimitive(StrEnum):
|
|
44
40
|
string = "string"
|
|
45
41
|
boolean = "boolean"
|
|
46
42
|
integer = "integer"
|
|
@@ -147,9 +143,6 @@ class OpenAPIFreeFormObjectType(OpenAPIType):
|
|
|
147
143
|
def asdict(self) -> dict[str, object]:
|
|
148
144
|
return self.add_addl_info({"type": "object"})
|
|
149
145
|
|
|
150
|
-
def asarguments(self) -> list[dict[str, object]]:
|
|
151
|
-
return []
|
|
152
|
-
|
|
153
146
|
|
|
154
147
|
class OpenAPIObjectType(OpenAPIType):
|
|
155
148
|
"""
|
|
@@ -164,7 +157,7 @@ class OpenAPIObjectType(OpenAPIType):
|
|
|
164
157
|
description: str | None = None,
|
|
165
158
|
nullable: bool = False,
|
|
166
159
|
*,
|
|
167
|
-
property_desc:
|
|
160
|
+
property_desc: dict[str, str] | None = None,
|
|
168
161
|
) -> None:
|
|
169
162
|
self.properties = properties
|
|
170
163
|
if property_desc is None:
|
|
@@ -173,31 +166,39 @@ class OpenAPIObjectType(OpenAPIType):
|
|
|
173
166
|
self.property_desc = property_desc
|
|
174
167
|
super().__init__(description=description, nullable=nullable)
|
|
175
168
|
|
|
169
|
+
def _emit_property_desc(self, property_name: str) -> dict[str, str]:
|
|
170
|
+
desc = self.property_desc.get(property_name)
|
|
171
|
+
if desc is None or desc.strip() == "":
|
|
172
|
+
return {}
|
|
173
|
+
|
|
174
|
+
return {"description": desc}
|
|
175
|
+
|
|
176
|
+
def _emit_property(
|
|
177
|
+
self, property_name: str, property_type: OpenAPIType
|
|
178
|
+
) -> dict[str, object]:
|
|
179
|
+
property_info = {
|
|
180
|
+
**property_type.asdict(),
|
|
181
|
+
}
|
|
182
|
+
property_description = self._emit_property_desc(property_name)
|
|
183
|
+
if "$ref" in property_info and "description" in property_description:
|
|
184
|
+
return {"allOf": [property_info, property_description]}
|
|
185
|
+
|
|
186
|
+
return property_info | property_description
|
|
187
|
+
|
|
176
188
|
def asdict(self) -> dict[str, object]:
|
|
177
189
|
return self.add_addl_info({
|
|
178
190
|
"type": "object",
|
|
191
|
+
"required": [
|
|
192
|
+
property_name
|
|
193
|
+
for property_name, property_type in self.properties.items()
|
|
194
|
+
if not property_type.nullable
|
|
195
|
+
],
|
|
179
196
|
"properties": {
|
|
180
|
-
property_name:
|
|
181
|
-
**property_type.asdict(),
|
|
182
|
-
"description": self.property_desc.get(property_name),
|
|
183
|
-
}
|
|
197
|
+
property_name: self._emit_property(property_name, property_type)
|
|
184
198
|
for property_name, property_type in self.properties.items()
|
|
185
199
|
},
|
|
186
200
|
})
|
|
187
201
|
|
|
188
|
-
def asarguments(self) -> list[dict[str, object]]:
|
|
189
|
-
argument_types: list[dict[str, object]] = []
|
|
190
|
-
for property_name, property_type in self.properties.items():
|
|
191
|
-
desc = self.property_desc.get(property_name)
|
|
192
|
-
argument_types.append({
|
|
193
|
-
"name": property_name,
|
|
194
|
-
"in": "query",
|
|
195
|
-
"schema": property_type.asdict(),
|
|
196
|
-
"required": not property_type.nullable,
|
|
197
|
-
"description": desc or "",
|
|
198
|
-
})
|
|
199
|
-
return argument_types
|
|
200
|
-
|
|
201
202
|
|
|
202
203
|
class OpenAPIUnionType(OpenAPIType):
|
|
203
204
|
"""
|
|
@@ -219,12 +220,6 @@ class OpenAPIUnionType(OpenAPIType):
|
|
|
219
220
|
# TODO: use parents description and nullable
|
|
220
221
|
return {"oneOf": [base_type.asdict() for base_type in self.base_types]}
|
|
221
222
|
|
|
222
|
-
def asarguments(self) -> list[dict[str, object]]:
|
|
223
|
-
# TODO handle inheritence (allOf and refs); need to inline here...
|
|
224
|
-
# for now skip this endpoint
|
|
225
|
-
|
|
226
|
-
return []
|
|
227
|
-
|
|
228
223
|
|
|
229
224
|
class OpenAPIIntersectionType(OpenAPIType):
|
|
230
225
|
"""
|
|
@@ -245,9 +240,3 @@ class OpenAPIIntersectionType(OpenAPIType):
|
|
|
245
240
|
def asdict(self) -> dict[str, object]:
|
|
246
241
|
# TODO: use parents description and nullable
|
|
247
242
|
return {"allOf": [base_type.asdict() for base_type in self.base_types]}
|
|
248
|
-
|
|
249
|
-
def asarguments(self) -> list[dict[str, object]]:
|
|
250
|
-
# TODO handle inheritence (allOf and refs); need to inline here...
|
|
251
|
-
# for now skip this endpoint
|
|
252
|
-
|
|
253
|
-
return []
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Types that type_spec will use in the emitted files.
|
|
3
3
|
"""
|
|
4
|
-
from typing import Union,
|
|
4
|
+
from typing import Union, Any, TYPE_CHECKING
|
|
5
|
+
from collections.abc import Mapping, Sequence
|
|
5
6
|
|
|
6
7
|
# These two are part of the core output, thus don't duplicate here
|
|
7
8
|
# from decimal import Decimal
|
|
@@ -21,8 +22,8 @@ PureJsonScalar = Union[str, float, bool, None]
|
|
|
21
22
|
# Regular expressions for identifying ref names and IDs. Ref names should be
|
|
22
23
|
# using this regular expression as a constriant in the database.
|
|
23
24
|
REF_NAME_REGEX = r"^[a-zA-Z0-9_/-]+$"
|
|
24
|
-
#
|
|
25
|
-
ID_REGEX = r"[1-9][0-9]{0,20}"
|
|
25
|
+
# Ids matching a strict integer number are converted to integers
|
|
26
|
+
ID_REGEX = r"-?[1-9][0-9]{0,20}"
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
if TYPE_CHECKING:
|