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
pkgs/type_spec/emit_open_api.py
CHANGED
|
@@ -10,7 +10,7 @@ import re
|
|
|
10
10
|
from typing import Collection, cast
|
|
11
11
|
|
|
12
12
|
from pkgs.serialization import yaml
|
|
13
|
-
from pkgs.serialization_util
|
|
13
|
+
from pkgs.serialization_util import serialize_for_api
|
|
14
14
|
|
|
15
15
|
from . import builder, util
|
|
16
16
|
from .builder import EndpointGuideKey, RootGuideKey
|
|
@@ -563,7 +563,7 @@ def _emit_endpoint(
|
|
|
563
563
|
ep = namespace.endpoint
|
|
564
564
|
gctx.paths.append(
|
|
565
565
|
EmitOpenAPIPath(
|
|
566
|
-
path=f"/{ep.
|
|
566
|
+
path=f"/{ep.resolved_path}",
|
|
567
567
|
ref=ref_path,
|
|
568
568
|
)
|
|
569
569
|
)
|
|
@@ -7,7 +7,7 @@ WORK-IN-PROGRESS, DON'T USE!
|
|
|
7
7
|
from collections import defaultdict
|
|
8
8
|
from dataclasses import dataclass, field
|
|
9
9
|
|
|
10
|
-
from pkgs.serialization_util
|
|
10
|
+
from pkgs.serialization_util import JsonValue
|
|
11
11
|
|
|
12
12
|
from . import builder
|
|
13
13
|
from .open_api_util import OpenAPIType
|
pkgs/type_spec/emit_python.py
CHANGED
|
@@ -2,19 +2,17 @@ import dataclasses
|
|
|
2
2
|
import io
|
|
3
3
|
import os
|
|
4
4
|
from decimal import Decimal
|
|
5
|
-
from typing import Any
|
|
5
|
+
from typing import Any
|
|
6
6
|
|
|
7
7
|
from . import builder, util
|
|
8
|
-
from .builder import EndpointEmitType
|
|
8
|
+
from .builder import EndpointEmitType, EndpointSpecificPath
|
|
9
9
|
from .config import PythonConfig
|
|
10
10
|
|
|
11
11
|
INDENT = " "
|
|
12
12
|
LINE_BREAK = "\n"
|
|
13
13
|
MODIFY_NOTICE = "# DO NOT MODIFY -- This file is generated by type_spec\n"
|
|
14
14
|
# Turn excess line length warning and turn off ruff formatting
|
|
15
|
-
LINT_HEADER =
|
|
16
|
-
"# flake8: noqa: F821\n# ruff: noqa: E402 Q003\n# fmt: off\n# isort: skip_file\n"
|
|
17
|
-
)
|
|
15
|
+
LINT_HEADER = "# ruff: noqa: E402 Q003\n# fmt: off\n# isort: skip_file\n"
|
|
18
16
|
LINT_FOOTER = "# fmt: on\n"
|
|
19
17
|
ROUTE_NOTICE = """# Routes are generated from $endpoint specifications in the
|
|
20
18
|
# type_spec API YAML files. Refer to the section on endpoints in the type_spec/README"""
|
|
@@ -38,7 +36,7 @@ QUEUED_BATCH_REQUEST_STYPE = builder.SpecTypeDefnObject(
|
|
|
38
36
|
|
|
39
37
|
@dataclasses.dataclass(kw_only=True)
|
|
40
38
|
class TrackingContext:
|
|
41
|
-
namespace:
|
|
39
|
+
namespace: builder.SpecNamespace | None = None
|
|
42
40
|
namespaces: set[builder.SpecNamespace] = dataclasses.field(default_factory=set)
|
|
43
41
|
names: set[str] = dataclasses.field(default_factory=set)
|
|
44
42
|
|
|
@@ -221,7 +219,7 @@ def _emit_types_imports(*, out: io.StringIO, ctx: Context) -> None:
|
|
|
221
219
|
out.write("import datetime # noqa: F401\n")
|
|
222
220
|
out.write("from decimal import Decimal # noqa: F401\n")
|
|
223
221
|
if ctx.use_enum:
|
|
224
|
-
out.write("from
|
|
222
|
+
out.write("from enum import StrEnum\n")
|
|
225
223
|
if ctx.use_dataclass:
|
|
226
224
|
out.write("import dataclasses\n")
|
|
227
225
|
out.write("from pkgs.serialization import serial_class\n")
|
|
@@ -344,6 +342,13 @@ def _emit_types(*, builder: builder.SpecBuilder, config: PythonConfig) -> None:
|
|
|
344
342
|
|
|
345
343
|
ENDPOINT_METHOD = "ENDPOINT_METHOD"
|
|
346
344
|
ENDPOINT_PATH = "ENDPOINT_PATH"
|
|
345
|
+
# will be removed in Q1 2025 when ENDPOINT_PATH is made api_endpoint-agnostic
|
|
346
|
+
# is used when the API call has multiple endpoints for the one endpoint that isn't equal to the top_namespace
|
|
347
|
+
ENDPOINT_PATH_ALTERNATE = "ENDPOINT_PATH_ALTERNATE"
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def _get_epf_root(endpoint_specific_path: EndpointSpecificPath) -> str:
|
|
351
|
+
return endpoint_specific_path.root
|
|
347
352
|
|
|
348
353
|
|
|
349
354
|
def _emit_namespace(ctx: Context, namespace: builder.SpecNamespace) -> None:
|
|
@@ -351,9 +356,20 @@ def _emit_namespace(ctx: Context, namespace: builder.SpecNamespace) -> None:
|
|
|
351
356
|
if endpoint is not None:
|
|
352
357
|
ctx.out.write("\n")
|
|
353
358
|
ctx.out.write(f'{ENDPOINT_METHOD} = "{endpoint.method.upper()}"\n')
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
)
|
|
359
|
+
for endpoint_specific_path in sorted(
|
|
360
|
+
endpoint.path_per_api_endpoint.values(), key=_get_epf_root
|
|
361
|
+
):
|
|
362
|
+
endpoint_path_name = ENDPOINT_PATH
|
|
363
|
+
|
|
364
|
+
if (
|
|
365
|
+
len(endpoint.path_per_api_endpoint.keys()) > 1
|
|
366
|
+
and endpoint_specific_path.root != ctx.builder.top_namespace
|
|
367
|
+
):
|
|
368
|
+
endpoint_path_name = ENDPOINT_PATH_ALTERNATE
|
|
369
|
+
ctx.names.add(ENDPOINT_PATH_ALTERNATE)
|
|
370
|
+
ctx.out.write(
|
|
371
|
+
f'{endpoint_path_name} = "{endpoint_specific_path.path_root}/{endpoint_specific_path.path_dirname}/{endpoint_specific_path.path_basename}"\n'
|
|
372
|
+
)
|
|
357
373
|
|
|
358
374
|
ctx.names.add(ENDPOINT_METHOD)
|
|
359
375
|
ctx.names.add(ENDPOINT_PATH)
|
|
@@ -443,8 +459,10 @@ def _emit_endpoint_invocation_function_signature(
|
|
|
443
459
|
else []
|
|
444
460
|
) + (extra_params if extra_params is not None else [])
|
|
445
461
|
|
|
446
|
-
|
|
447
|
-
|
|
462
|
+
# All endpoints share a function name
|
|
463
|
+
function = endpoint.path_per_api_endpoint[endpoint.default_endpoint_key].function
|
|
464
|
+
assert function is not None
|
|
465
|
+
function_name = function.split(".")[-1]
|
|
448
466
|
ctx.out.write(
|
|
449
467
|
f"""
|
|
450
468
|
def {function_name}(
|
|
@@ -702,39 +720,43 @@ def _emit_properties(
|
|
|
702
720
|
if len(properties) > 0:
|
|
703
721
|
|
|
704
722
|
def write_field(prop: builder.SpecProperty) -> None:
|
|
723
|
+
stype = prop.spec_type
|
|
705
724
|
if prop.name_case == builder.NameCase.preserve:
|
|
706
725
|
unconverted_keys.add(prop.name)
|
|
707
726
|
py_name = python_field_name(prop.name, prop.name_case)
|
|
708
727
|
|
|
709
728
|
if prop.convert_value == builder.PropertyConvertValue.no_convert:
|
|
710
729
|
unconverted_values.add(py_name)
|
|
711
|
-
elif not
|
|
730
|
+
elif not stype.is_value_converted():
|
|
712
731
|
assert prop.convert_value == builder.PropertyConvertValue.auto
|
|
713
732
|
unconverted_values.add(py_name)
|
|
714
|
-
if
|
|
733
|
+
if stype.is_value_to_string():
|
|
715
734
|
to_string_values.add(py_name)
|
|
716
735
|
|
|
717
736
|
if prop.parse_require:
|
|
718
737
|
parse_require.add(py_name)
|
|
719
738
|
|
|
720
|
-
ref_type = refer_to(ctx,
|
|
739
|
+
ref_type = refer_to(ctx, stype)
|
|
721
740
|
default = None
|
|
722
741
|
if prop.extant == builder.PropertyExtant.missing:
|
|
723
742
|
ref_type = f"MissingType[{ref_type}]"
|
|
724
743
|
default = "MISSING_SENTRY"
|
|
725
744
|
ctx.use_missing = True
|
|
726
745
|
elif prop.extant == builder.PropertyExtant.optional:
|
|
727
|
-
|
|
746
|
+
if isinstance(
|
|
747
|
+
stype, builder.SpecTypeInstance
|
|
748
|
+
) and stype.defn_type.is_base_type(builder.BaseTypeName.s_optional):
|
|
749
|
+
pass # base type already adds the None union
|
|
750
|
+
elif ref_type == "None":
|
|
751
|
+
pass # no need to add a None union to a none type
|
|
752
|
+
else:
|
|
753
|
+
ref_type = f"{ref_type} | None"
|
|
728
754
|
default = "None"
|
|
729
755
|
elif prop.has_default:
|
|
730
|
-
default = _emit_value(ctx,
|
|
756
|
+
default = _emit_value(ctx, stype, prop.default)
|
|
731
757
|
if (
|
|
732
|
-
isinstance(
|
|
733
|
-
and (
|
|
734
|
-
prop.spec_type.defn_type.is_base_type(
|
|
735
|
-
builder.BaseTypeName.s_list
|
|
736
|
-
)
|
|
737
|
-
)
|
|
758
|
+
isinstance(stype, builder.SpecTypeInstance)
|
|
759
|
+
and (stype.defn_type.is_base_type(builder.BaseTypeName.s_list))
|
|
738
760
|
and default == "[]"
|
|
739
761
|
):
|
|
740
762
|
default = "dataclasses.field(default_factory=list)"
|
|
@@ -924,13 +946,22 @@ base_name_map = {
|
|
|
924
946
|
|
|
925
947
|
def refer_to(ctx: TrackingContext, stype: builder.SpecType) -> str:
|
|
926
948
|
if isinstance(stype, builder.SpecTypeInstance):
|
|
927
|
-
params =
|
|
949
|
+
params = [refer_to(ctx, p) for p in stype.parameters]
|
|
950
|
+
|
|
951
|
+
if stype.defn_type.is_base_type(builder.BaseTypeName.s_union):
|
|
952
|
+
if len(stype.parameters) == 1:
|
|
953
|
+
return f"typing.Union[{params[0]}]"
|
|
954
|
+
return " | ".join(params)
|
|
928
955
|
|
|
929
956
|
if stype.defn_type.is_base_type(builder.BaseTypeName.s_readonly_array):
|
|
930
|
-
assert len(
|
|
931
|
-
|
|
957
|
+
assert len(params) == 1, "Read Only Array takes one parameter"
|
|
958
|
+
return f"tuple[{params[0]}, ...]"
|
|
959
|
+
|
|
960
|
+
if stype.defn_type.is_base_type(builder.BaseTypeName.s_optional):
|
|
961
|
+
assert len(params) == 1, "Optional only takes one parameter"
|
|
962
|
+
return f"{params[0]} | None"
|
|
932
963
|
|
|
933
|
-
return f"{refer_to(ctx, stype.defn_type)}[{params}]"
|
|
964
|
+
return f"{refer_to(ctx, stype.defn_type)}[{', '.join(params)}]"
|
|
934
965
|
|
|
935
966
|
if isinstance(stype, builder.SpecTypeLiteralWrapper):
|
|
936
967
|
return _emit_value(ctx, stype.value_type, stype.value)
|
|
@@ -956,23 +987,21 @@ def refer_to(ctx: TrackingContext, stype: builder.SpecType) -> str:
|
|
|
956
987
|
SpecEndpoint = builder.SpecEndpoint
|
|
957
988
|
|
|
958
989
|
|
|
959
|
-
def _route_identifier(endpoint: builder.SpecEndpoint) -> tuple[str, str, str]:
|
|
960
|
-
return (endpoint.path_dirname, endpoint.path_basename, endpoint.method)
|
|
961
|
-
|
|
962
|
-
|
|
963
990
|
def _emit_routes(*, builder: builder.SpecBuilder, config: PythonConfig) -> None:
|
|
964
991
|
for endpoint_root in builder.api_endpoints:
|
|
965
992
|
endpoints: list[SpecEndpoint] = []
|
|
966
993
|
output = config.routes_output.get(endpoint_root)
|
|
967
994
|
if output is None:
|
|
968
995
|
continue
|
|
996
|
+
last_endpoint: SpecEndpoint | None = None
|
|
969
997
|
for namespace in builder.namespaces.values():
|
|
970
998
|
endpoint = namespace.endpoint
|
|
999
|
+
last_endpoint = endpoint
|
|
971
1000
|
if endpoint is None:
|
|
972
1001
|
continue
|
|
973
|
-
if endpoint.
|
|
1002
|
+
if endpoint_root not in endpoint.path_per_api_endpoint:
|
|
974
1003
|
continue
|
|
975
|
-
if endpoint.function is None:
|
|
1004
|
+
if endpoint.path_per_api_endpoint[endpoint_root].function is None:
|
|
976
1005
|
continue
|
|
977
1006
|
|
|
978
1007
|
endpoints.append(endpoint)
|
|
@@ -985,6 +1014,15 @@ def _emit_routes(*, builder: builder.SpecBuilder, config: PythonConfig) -> None:
|
|
|
985
1014
|
from main.site.framework.types import StaticRouteType
|
|
986
1015
|
"""
|
|
987
1016
|
)
|
|
1017
|
+
|
|
1018
|
+
def _route_identifier(endpoint: SpecEndpoint) -> tuple[str, str, str]:
|
|
1019
|
+
endpoint_specific_path = endpoint.path_per_api_endpoint[endpoint_root]
|
|
1020
|
+
return (
|
|
1021
|
+
endpoint_specific_path.path_dirname,
|
|
1022
|
+
endpoint_specific_path.path_basename,
|
|
1023
|
+
endpoint.method,
|
|
1024
|
+
)
|
|
1025
|
+
|
|
988
1026
|
sorted_endpoints = sorted(endpoints, key=_route_identifier)
|
|
989
1027
|
|
|
990
1028
|
assert len(endpoints) == len(set(map(_route_identifier, endpoints))), (
|
|
@@ -993,17 +1031,21 @@ from main.site.framework.types import StaticRouteType
|
|
|
993
1031
|
|
|
994
1032
|
path_set = set()
|
|
995
1033
|
for endpoint in sorted_endpoints:
|
|
996
|
-
|
|
997
|
-
|
|
1034
|
+
last_endpoint = endpoint
|
|
1035
|
+
endpoint_function_path = endpoint.path_per_api_endpoint[endpoint_root]
|
|
1036
|
+
assert endpoint_function_path.function
|
|
1037
|
+
func_bits = endpoint_function_path.function.split(".")
|
|
998
1038
|
path = ".".join(func_bits[:-1])
|
|
999
1039
|
if path in path_set:
|
|
1000
1040
|
continue
|
|
1001
1041
|
path_set.add(path)
|
|
1002
1042
|
static_out.write(f"import {path}\n")
|
|
1003
1043
|
|
|
1044
|
+
assert last_endpoint is not None
|
|
1045
|
+
|
|
1004
1046
|
static_out.write(
|
|
1005
1047
|
f"""
|
|
1006
|
-
ROUTE_PREFIX = "/{
|
|
1048
|
+
ROUTE_PREFIX = "/{last_endpoint.path_per_api_endpoint[endpoint_root].path_root}"
|
|
1007
1049
|
|
|
1008
1050
|
ROUTES: list[StaticRouteType] = [
|
|
1009
1051
|
"""
|
|
@@ -1017,20 +1059,21 @@ ROUTES: list[StaticRouteType] = [
|
|
|
1017
1059
|
|
|
1018
1060
|
from main.site.framework.types import DynamicRouteType
|
|
1019
1061
|
|
|
1020
|
-
ROUTE_PREFIX = "/{
|
|
1062
|
+
ROUTE_PREFIX = "/{last_endpoint.path_per_api_endpoint[endpoint_root].path_root}"
|
|
1021
1063
|
|
|
1022
1064
|
ROUTES: list[DynamicRouteType] = [
|
|
1023
1065
|
"""
|
|
1024
1066
|
)
|
|
1025
1067
|
|
|
1026
1068
|
for endpoint in sorted_endpoints:
|
|
1069
|
+
endpoint_function_path = endpoint.path_per_api_endpoint[endpoint_root]
|
|
1027
1070
|
dynamic_out.write(
|
|
1028
|
-
f'{INDENT}("{
|
|
1071
|
+
f'{INDENT}("{endpoint_function_path.path_dirname}/{endpoint_function_path.path_basename}", "{endpoint_function_path.function}", ["{endpoint.method.upper()}"]),\n'
|
|
1029
1072
|
)
|
|
1030
1073
|
|
|
1031
|
-
assert
|
|
1074
|
+
assert endpoint_function_path.function
|
|
1032
1075
|
static_out.write(
|
|
1033
|
-
f'{INDENT}("{
|
|
1076
|
+
f'{INDENT}("{endpoint_function_path.path_dirname}/{endpoint_function_path.path_basename}", {endpoint_function_path.function}, ["{endpoint.method.upper()}"]),\n'
|
|
1034
1077
|
)
|
|
1035
1078
|
|
|
1036
1079
|
dynamic_out.write(f"{MODIFY_NOTICE}]\n")
|
|
@@ -1046,7 +1089,7 @@ def _emit_namespace_imports(
|
|
|
1046
1089
|
*,
|
|
1047
1090
|
out: io.StringIO,
|
|
1048
1091
|
namespaces: set[builder.SpecNamespace],
|
|
1049
|
-
from_namespace:
|
|
1092
|
+
from_namespace: builder.SpecNamespace | None,
|
|
1050
1093
|
config: PythonConfig,
|
|
1051
1094
|
skip_non_sdk: bool = False,
|
|
1052
1095
|
) -> None:
|
|
@@ -1084,8 +1127,8 @@ def _emit_id_source(*, builder: builder.SpecBuilder, config: PythonConfig) -> No
|
|
|
1084
1127
|
return None
|
|
1085
1128
|
enum_out = io.StringIO()
|
|
1086
1129
|
enum_out.write(f"{LINT_HEADER}{MODIFY_NOTICE}\n")
|
|
1087
|
-
enum_out.write("
|
|
1088
|
-
enum_out.write("from
|
|
1130
|
+
enum_out.write("import typing\n")
|
|
1131
|
+
enum_out.write("from enum import StrEnum\n")
|
|
1089
1132
|
|
|
1090
1133
|
ctx = TrackingContext()
|
|
1091
1134
|
# In this context the propername
|
|
@@ -1101,11 +1144,11 @@ def _emit_id_source(*, builder: builder.SpecBuilder, config: PythonConfig) -> No
|
|
|
1101
1144
|
known_keys = []
|
|
1102
1145
|
enum_out.write("\nENUM_MAP: dict[str, type[StrEnum]] = {\n")
|
|
1103
1146
|
for key in sorted(named_enums.keys()):
|
|
1104
|
-
enum_out.write(f'"{key}": {named_enums[key]},\n')
|
|
1105
|
-
known_keys.append(f'
|
|
1147
|
+
enum_out.write(f'{INDENT}"{key}": {named_enums[key]},\n')
|
|
1148
|
+
known_keys.append(f'"{key}"')
|
|
1106
1149
|
enum_out.write(f"}}\n{MODIFY_NOTICE}\n")
|
|
1107
1150
|
|
|
1108
|
-
enum_out.write(f"\nKnownEnumsType =
|
|
1151
|
+
enum_out.write(f"\nKnownEnumsType = typing.Literal[\n{INDENT}")
|
|
1109
1152
|
enum_out.write(f",\n{INDENT}".join(known_keys))
|
|
1110
1153
|
enum_out.write(f"\n]\n{MODIFY_NOTICE}\n")
|
|
1111
1154
|
|
|
@@ -1122,21 +1165,36 @@ def _emit_api_stubs(*, builder: builder.SpecBuilder, config: PythonConfig) -> No
|
|
|
1122
1165
|
|
|
1123
1166
|
if endpoint is None:
|
|
1124
1167
|
continue
|
|
1125
|
-
if endpoint.
|
|
1168
|
+
if endpoint_root not in endpoint.path_per_api_endpoint:
|
|
1126
1169
|
continue
|
|
1127
|
-
|
|
1170
|
+
|
|
1171
|
+
endpoint_function = endpoint.path_per_api_endpoint[endpoint_root].function
|
|
1172
|
+
if endpoint_function is None:
|
|
1128
1173
|
continue
|
|
1129
1174
|
|
|
1130
|
-
module_dir, file_name, func_name =
|
|
1175
|
+
module_dir, file_name, func_name = endpoint_function.rsplit(".", 2)
|
|
1131
1176
|
module_path = os.path.abspath(module_dir.replace(".", "/"))
|
|
1132
1177
|
api_stub_file = f"{module_path}/{file_name}.py"
|
|
1133
1178
|
if os.path.isfile(api_stub_file):
|
|
1134
1179
|
continue
|
|
1135
|
-
_create_api_stub(
|
|
1180
|
+
_create_api_stub(
|
|
1181
|
+
api_stub_file=api_stub_file,
|
|
1182
|
+
file_name=file_name,
|
|
1183
|
+
endpoint=endpoint,
|
|
1184
|
+
config=config,
|
|
1185
|
+
endpoint_root=endpoint_root,
|
|
1186
|
+
top_namespace=builder.top_namespace,
|
|
1187
|
+
)
|
|
1136
1188
|
|
|
1137
1189
|
|
|
1138
1190
|
def _create_api_stub(
|
|
1139
|
-
|
|
1191
|
+
*,
|
|
1192
|
+
api_stub_file: str,
|
|
1193
|
+
file_name: str,
|
|
1194
|
+
endpoint: SpecEndpoint,
|
|
1195
|
+
config: PythonConfig,
|
|
1196
|
+
endpoint_root: str,
|
|
1197
|
+
top_namespace: str,
|
|
1140
1198
|
) -> None:
|
|
1141
1199
|
assert (
|
|
1142
1200
|
endpoint.method == builder.RouteMethod.post
|
|
@@ -1144,7 +1202,13 @@ def _create_api_stub(
|
|
|
1144
1202
|
or endpoint.method == builder.RouteMethod.delete
|
|
1145
1203
|
or endpoint.method == builder.RouteMethod.patch
|
|
1146
1204
|
)
|
|
1147
|
-
api_out = _create_api_function(
|
|
1205
|
+
api_out = _create_api_function(
|
|
1206
|
+
file_name=file_name,
|
|
1207
|
+
endpoint=endpoint,
|
|
1208
|
+
config=config,
|
|
1209
|
+
endpoint_root=endpoint_root,
|
|
1210
|
+
top_namespace=top_namespace,
|
|
1211
|
+
)
|
|
1148
1212
|
util.rewrite_file(api_stub_file, api_out.getvalue())
|
|
1149
1213
|
|
|
1150
1214
|
|
|
@@ -1153,15 +1217,22 @@ WRAP_ARGS_END = "\n"
|
|
|
1153
1217
|
|
|
1154
1218
|
|
|
1155
1219
|
def _create_api_function(
|
|
1156
|
-
|
|
1220
|
+
*,
|
|
1221
|
+
file_name: str,
|
|
1222
|
+
endpoint: SpecEndpoint,
|
|
1223
|
+
config: PythonConfig,
|
|
1224
|
+
endpoint_root: str,
|
|
1225
|
+
top_namespace: str,
|
|
1157
1226
|
) -> io.StringIO:
|
|
1227
|
+
endpoint_specific_path = endpoint.path_per_api_endpoint[endpoint_root]
|
|
1228
|
+
assert endpoint_specific_path is not None
|
|
1158
1229
|
api_out = io.StringIO()
|
|
1159
1230
|
python_api_type_root = f"{config.types_package}.api"
|
|
1160
|
-
dot_dirname =
|
|
1231
|
+
dot_dirname = endpoint_specific_path.path_dirname.replace("/", ".")
|
|
1161
1232
|
api_import = (
|
|
1162
1233
|
f"{python_api_type_root}.{dot_dirname}.{file_name}"
|
|
1163
1234
|
if dot_dirname != ""
|
|
1164
|
-
else f"{python_api_type_root}.{
|
|
1235
|
+
else f"{python_api_type_root}.{endpoint_specific_path.path_basename}"
|
|
1165
1236
|
)
|
|
1166
1237
|
|
|
1167
1238
|
if endpoint.method == builder.RouteMethod.post:
|
|
@@ -1173,9 +1244,20 @@ def _create_api_function(
|
|
|
1173
1244
|
elif endpoint.method == builder.RouteMethod.patch:
|
|
1174
1245
|
validated_method = "validated_patch"
|
|
1175
1246
|
|
|
1176
|
-
ruff_requires_wrap = len(
|
|
1247
|
+
ruff_requires_wrap = len(endpoint_specific_path.path_basename) > 14
|
|
1248
|
+
|
|
1249
|
+
account_type = (
|
|
1250
|
+
endpoint_specific_path.root
|
|
1251
|
+
if endpoint_specific_path.root not in ["external", "portal"]
|
|
1252
|
+
else "materials"
|
|
1253
|
+
)
|
|
1177
1254
|
|
|
1178
|
-
|
|
1255
|
+
endpoint_path_name = (
|
|
1256
|
+
ENDPOINT_PATH_ALTERNATE
|
|
1257
|
+
if len(endpoint.path_per_api_endpoint.keys()) > 1
|
|
1258
|
+
and endpoint_specific_path.root != top_namespace
|
|
1259
|
+
else ENDPOINT_PATH
|
|
1260
|
+
)
|
|
1179
1261
|
|
|
1180
1262
|
api_out.write(
|
|
1181
1263
|
f"""import {api_import} as api
|
|
@@ -1183,8 +1265,8 @@ from main.db.session import Session, SessionMaker
|
|
|
1183
1265
|
from main.site.decorators import APIError, APIResponse, {validated_method}
|
|
1184
1266
|
|
|
1185
1267
|
|
|
1186
|
-
@{validated_method}(api.
|
|
1187
|
-
def {
|
|
1268
|
+
@{validated_method}(api.{endpoint_path_name}, "{account_type}", api.Arguments)
|
|
1269
|
+
def {endpoint_specific_path.path_basename}({WRAP_ARGS_START if ruff_requires_wrap else ""}args: api.Arguments, client_sm: SessionMaker{WRAP_ARGS_END if ruff_requires_wrap else ""}) -> APIResponse[api.Data]:
|
|
1188
1270
|
with Session(client_sm) as session:
|
|
1189
1271
|
# return APIResponse(data=api.Data())
|
|
1190
1272
|
pass
|
|
@@ -1216,9 +1298,9 @@ def _emit_api_argument_lookup(
|
|
|
1216
1298
|
|
|
1217
1299
|
if endpoint is None:
|
|
1218
1300
|
continue
|
|
1219
|
-
if endpoint.
|
|
1301
|
+
if endpoint_root not in endpoint.path_per_api_endpoint:
|
|
1220
1302
|
continue
|
|
1221
|
-
if endpoint.function is None:
|
|
1303
|
+
if endpoint.path_per_api_endpoint[endpoint_root].function is None:
|
|
1222
1304
|
continue
|
|
1223
1305
|
if "Arguments" not in namespace.types or "Data" not in namespace.types:
|
|
1224
1306
|
continue
|
|
@@ -1244,7 +1326,7 @@ def _emit_api_argument_lookup(
|
|
|
1244
1326
|
mapping += f"{INDENT}{INDENT}route_group={route_group},\n"
|
|
1245
1327
|
mapping += f"{INDENT}{INDENT}account_type={account_type},\n"
|
|
1246
1328
|
mapping += f"{INDENT}{INDENT}route={import_alias}.ENDPOINT_PATH,\n"
|
|
1247
|
-
mapping += f'{INDENT}{INDENT}handler="{endpoint.function}",\n'
|
|
1329
|
+
mapping += f'{INDENT}{INDENT}handler="{endpoint.path_per_api_endpoint[endpoint_root].function}",\n'
|
|
1248
1330
|
mapping += f"{INDENT}{INDENT}method={import_alias}.ENDPOINT_METHOD,\n"
|
|
1249
1331
|
mapping += f"{INDENT})"
|
|
1250
1332
|
mappings.append(mapping)
|
|
@@ -1299,7 +1381,7 @@ ASYNC_BATCH_PROCESSOR_FILENAME = "async_batch_processor"
|
|
|
1299
1381
|
ASYNC_BATCH_PROCESSOR_IMPORTS = [
|
|
1300
1382
|
"import uuid",
|
|
1301
1383
|
"from abc import ABC, abstractmethod",
|
|
1302
|
-
"from pkgs.serialization_util
|
|
1384
|
+
"from pkgs.serialization_util import serialize_for_api",
|
|
1303
1385
|
]
|
|
1304
1386
|
|
|
1305
1387
|
|
|
@@ -2,6 +2,7 @@ import io
|
|
|
2
2
|
import os
|
|
3
3
|
|
|
4
4
|
from . import builder, util
|
|
5
|
+
from .builder import EndpointKey, EndpointSpecificPath
|
|
5
6
|
from .config import TypeScriptConfig
|
|
6
7
|
from .emit_io_ts import emit_type_io_ts
|
|
7
8
|
from .emit_typescript_util import (
|
|
@@ -46,7 +47,10 @@ def _emit_types(builder: builder.SpecBuilder, config: TypeScriptConfig) -> None:
|
|
|
46
47
|
and len(namespace.constants) == 0
|
|
47
48
|
):
|
|
48
49
|
# Try to capture some common incompleteness errors
|
|
49
|
-
if namespace.endpoint is None or
|
|
50
|
+
if namespace.endpoint is None or any(
|
|
51
|
+
endpoint_specific_path.function is None
|
|
52
|
+
for endpoint_specific_path in namespace.endpoint.path_per_api_endpoint.values()
|
|
53
|
+
):
|
|
50
54
|
raise Exception(
|
|
51
55
|
f"Namespace {'/'.join(namespace.path)} is incomplete. It should have an endpoint with function, types, and/or constants"
|
|
52
56
|
)
|
|
@@ -123,6 +127,7 @@ def _emit_endpoint(
|
|
|
123
127
|
has_data = "Data" in namespace.types
|
|
124
128
|
has_deprecated_result = "DeprecatedResult" in namespace.types
|
|
125
129
|
is_binary = endpoint.result_type == builder.ResultType.binary
|
|
130
|
+
has_multiple_endpoints = len(endpoint.path_per_api_endpoint) > 1
|
|
126
131
|
|
|
127
132
|
result_type_count = sum([has_data, has_deprecated_result, is_binary])
|
|
128
133
|
assert result_type_count < 2
|
|
@@ -157,42 +162,84 @@ def _emit_endpoint(
|
|
|
157
162
|
wrap_call = (
|
|
158
163
|
f"{wrap_name}<Arguments>" if is_binary else f"{wrap_name}<Arguments, Response>"
|
|
159
164
|
)
|
|
165
|
+
|
|
166
|
+
unc_base_api_imports = (
|
|
167
|
+
f"appSpecificApiPath, {wrap_name}" if has_multiple_endpoints else wrap_name
|
|
168
|
+
)
|
|
169
|
+
unc_types_imports = (
|
|
170
|
+
'import { ApplicationT } from "unc_types"\n' if has_multiple_endpoints else ""
|
|
171
|
+
)
|
|
172
|
+
|
|
160
173
|
type_path = f"unc_types/{'/'.join(namespace.path)}"
|
|
161
174
|
|
|
162
175
|
if is_binary:
|
|
163
|
-
tsx_response_part = f"""import {{ {
|
|
176
|
+
tsx_response_part = f"""import {{ {unc_base_api_imports} }} from "unc_base/api"
|
|
164
177
|
import type {{ Arguments }} from "{type_path}"
|
|
165
|
-
|
|
178
|
+
{unc_types_imports}
|
|
166
179
|
export type {{ Arguments }}
|
|
167
180
|
"""
|
|
168
181
|
elif has_data and endpoint.has_attachment:
|
|
169
|
-
tsx_response_part = f"""import {{ {
|
|
182
|
+
tsx_response_part = f"""import {{ {unc_base_api_imports}, type AttachmentResponse }} from "unc_base/api"
|
|
170
183
|
import type {{ Arguments, Data }} from "{type_path}"
|
|
171
|
-
|
|
184
|
+
{unc_types_imports}
|
|
172
185
|
export type {{ Arguments, Data }}
|
|
173
186
|
export type Response = AttachmentResponse<Data>
|
|
174
187
|
"""
|
|
175
188
|
elif has_data:
|
|
176
|
-
tsx_response_part = f"""import {{ {
|
|
189
|
+
tsx_response_part = f"""import {{ {unc_base_api_imports}, type JsonResponse }} from "unc_base/api"
|
|
177
190
|
import type {{ Arguments, Data }} from "{type_path}"
|
|
178
|
-
|
|
191
|
+
{unc_types_imports}
|
|
179
192
|
export type {{ Arguments, Data }}
|
|
180
193
|
export type Response = JsonResponse<Data>
|
|
181
194
|
"""
|
|
182
195
|
|
|
183
196
|
else:
|
|
184
197
|
assert has_deprecated_result
|
|
185
|
-
tsx_response_part = f"""import {{ {
|
|
198
|
+
tsx_response_part = f"""import {{ {unc_base_api_imports} }} from "unc_base/api"
|
|
186
199
|
import type {{ Arguments, DeprecatedResult }} from "{type_path}"
|
|
187
|
-
|
|
200
|
+
{unc_types_imports}
|
|
188
201
|
export type {{ Arguments }}
|
|
189
202
|
export type Response = DeprecatedResult
|
|
190
203
|
"""
|
|
191
204
|
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
export const apiCall = buildWrappedGetCall<Arguments, Response>(
|
|
208
|
+
appSpecificApiPath({
|
|
209
|
+
[ApplicationT.FrontendApplication.materials]: "api/materials/common/list_id_source",
|
|
210
|
+
}),
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
if not has_multiple_endpoints:
|
|
217
|
+
default_endpoint_path = endpoint.path_per_api_endpoint[
|
|
218
|
+
endpoint.default_endpoint_key
|
|
219
|
+
]
|
|
220
|
+
endpoint_path_part = f'"{default_endpoint_path.path_root}/{default_endpoint_path.path_dirname}/{default_endpoint_path.path_basename}",'
|
|
221
|
+
else:
|
|
222
|
+
path_lookup_map = ""
|
|
223
|
+
api_endpoint_key: EndpointKey
|
|
224
|
+
endpoint_specific_path: EndpointSpecificPath
|
|
225
|
+
for (
|
|
226
|
+
api_endpoint_key,
|
|
227
|
+
endpoint_specific_path,
|
|
228
|
+
) in endpoint.path_per_api_endpoint.items():
|
|
229
|
+
full_path = f"{endpoint_specific_path.path_root}/{endpoint_specific_path.path_dirname}/{endpoint_specific_path.path_basename}"
|
|
230
|
+
frontend_app_value = config.endpoint_to_frontend_app_type[api_endpoint_key]
|
|
231
|
+
|
|
232
|
+
path_lookup_map += (
|
|
233
|
+
f'\n [ApplicationT.{frontend_app_value}]: "{full_path}",'
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
endpoint_path_part = f"""appSpecificApiPath({{{path_lookup_map}
|
|
237
|
+
}}),"""
|
|
238
|
+
|
|
192
239
|
tsx_api = f"""{MODIFY_NOTICE}
|
|
193
240
|
{data_loader_head}{tsx_response_part}
|
|
194
241
|
export const apiCall = {wrap_call}(
|
|
195
|
-
|
|
242
|
+
{endpoint_path_part}
|
|
196
243
|
)
|
|
197
244
|
{data_loader_body}"""
|
|
198
245
|
|
pkgs/type_spec/load_types.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from collections.abc import Callable
|
|
3
3
|
from io import StringIO
|
|
4
|
-
from typing import Optional
|
|
5
4
|
|
|
6
5
|
from shelljob import fs
|
|
7
6
|
|
|
@@ -41,7 +40,7 @@ def find_and_handle_files(
|
|
|
41
40
|
handler(file_name, file.read())
|
|
42
41
|
|
|
43
42
|
|
|
44
|
-
def load_types(config: Config) ->
|
|
43
|
+
def load_types(config: Config) -> SpecBuilder | None:
|
|
45
44
|
builder = SpecBuilder(
|
|
46
45
|
api_endpoints=config.api_endpoint, top_namespace=config.top_namespace
|
|
47
46
|
)
|
pkgs/type_spec/open_api_util.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
2
|
from enum import StrEnum
|
|
3
|
-
from typing import Optional
|
|
4
3
|
|
|
5
4
|
|
|
6
5
|
class OpenAPIType(ABC):
|
|
@@ -158,7 +157,7 @@ class OpenAPIObjectType(OpenAPIType):
|
|
|
158
157
|
description: str | None = None,
|
|
159
158
|
nullable: bool = False,
|
|
160
159
|
*,
|
|
161
|
-
property_desc:
|
|
160
|
+
property_desc: dict[str, str] | None = None,
|
|
162
161
|
) -> None:
|
|
163
162
|
self.properties = properties
|
|
164
163
|
if property_desc is None:
|
|
@@ -22,6 +22,8 @@ PureJsonScalar = Union[str, float, bool, None]
|
|
|
22
22
|
# Regular expressions for identifying ref names and IDs. Ref names should be
|
|
23
23
|
# using this regular expression as a constriant in the database.
|
|
24
24
|
REF_NAME_REGEX = r"^[a-zA-Z0-9_/-]+$"
|
|
25
|
+
REF_NAME_STRICT_REGEX_STRING = "^[a-zA-Z_][a-zA-Z0-9_]*$"
|
|
26
|
+
REF_NAME_STRICT_REGEX = rf"{REF_NAME_STRICT_REGEX_STRING}"
|
|
25
27
|
# Ids matching a strict integer number are converted to integers
|
|
26
28
|
ID_REGEX = r"-?[1-9][0-9]{0,20}"
|
|
27
29
|
|