ab-openapi-python-generator 2.2.3__tar.gz → 2.2.4__tar.gz
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.
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/.gitignore +1 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/.vscode/launch.json +8 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/PKG-INFO +1 -1
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/pyproject.toml +1 -1
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/language_converters/python/client_generator.py +38 -1
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/language_converters/python/jinja_config.py +2 -2
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/language_converters/python/model_generator.py +187 -6
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/language_converters/python/templates/async_client_httpx_pydantic_2.jinja2 +3 -3
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/language_converters/python/templates/sync_client_httpx_pydantic_2.jinja2 +3 -3
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/.envrc.example +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/.gitattributes +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/.github/dependabot.yml +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/.github/workflows/ci.yaml +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/.github/workflows/publish.yaml +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/.pre-commit-config.yaml +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/.vscode/tasks.json +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/LICENSE +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/Makefile +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/README.md +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/docs/acknowledgements/index.md +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/docs/css/custom.css +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/docs/css/termynal.css +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/docs/index.md +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/docs/js/custom.js +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/docs/js/termynal.js +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/docs/openapi-definition.md +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/docs/quick_start.md +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/docs/references/index.md +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/docs/references/module_usage.md +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/docs/tutorial/advanced.md +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/docs/tutorial/authentication.md +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/docs/tutorial/index.md +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/logo.png +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/__init__.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/__main__.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/common.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/generate_data.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/language_converters/__init__.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/language_converters/python/__init__.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/language_converters/python/common.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/language_converters/python/exception_generator.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/language_converters/python/generator.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/language_converters/python/templates/alias_union.jinja2 +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/language_converters/python/templates/discriminator_enum.jinja2 +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/language_converters/python/templates/enum.jinja2 +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/language_converters/python/templates/http_exception.jinja2 +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/language_converters/python/templates/models.jinja2 +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/language_converters/python/templates/models_pydantic_2.jinja2 +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/models.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/parsers/__init__.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/parsers/openapi_30.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/parsers/openapi_31.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/py.typed +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/version_detector.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/__init__.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/build_test_api/api.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/conftest.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_common_normalize_symbol.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_data/failing_api.json +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_data/gitea_issue_11.json +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_data/issue_117.json +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_data/issue_120.json +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_data/issue_17.json +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_data/issue_30_87.json +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_data/issue_51.json +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_data/issue_55.json +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_data/issue_71.json +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_data/issue_71_31.json +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_data/issue_illegal_character_in_operation_id.json +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_data/issue_keyword_parameter_name.json +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_data/openapi_gitea_converted.json +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_data/swagger_petstore_3_0_4.yaml +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_data/swagger_petstore_3_1.yaml +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_data/test_api.json +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_data/test_api_31.json +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_generate_data.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_generate_data_negative.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_generated_code.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_jinja_no_autoescape.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_main.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_model_docstring.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_model_generator.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_model_generator_edges.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_openapi_30.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_openapi_31.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_openapi_31_completeness.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_openapi_31_coverage.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_openapi_31_schema_features.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_service_generator.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_service_generator_edges.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_swagger_petstore_30.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_swagger_petstore_31.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_version_detector_edges.py +0 -0
- {ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tox.ini +0 -0
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "0.2.0",
|
|
3
3
|
"configurations": [
|
|
4
|
+
{
|
|
5
|
+
"name": "Python Debugger: Current File with Arguments",
|
|
6
|
+
"type": "debugpy",
|
|
7
|
+
"request": "launch",
|
|
8
|
+
"program": "${file}",
|
|
9
|
+
"console": "integratedTerminal",
|
|
10
|
+
"args": "${command:pickArgs}"
|
|
11
|
+
},
|
|
4
12
|
{
|
|
5
13
|
"name": "pytest",
|
|
6
14
|
"type": "python",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ab-openapi-python-generator
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.4
|
|
4
4
|
Summary: Openapi Python Generator
|
|
5
5
|
Project-URL: Homepage, https://github.com/auth-broker/openapi-python-generator
|
|
6
6
|
Project-URL: Repository, https://github.com/auth-broker/openapi-python-generator
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
import re
|
|
2
3
|
from typing import Any, Dict, List, Literal, Optional, Tuple, Union
|
|
3
4
|
|
|
@@ -91,6 +92,24 @@ def is_schema_type(obj: Any) -> bool:
|
|
|
91
92
|
return isinstance(obj, (Schema30, Schema31))
|
|
92
93
|
|
|
93
94
|
|
|
95
|
+
def _common_suffix(a: str, b: str) -> str:
|
|
96
|
+
i = 1
|
|
97
|
+
while i <= min(len(a), len(b)) and a[-i] == b[-i]:
|
|
98
|
+
i += 1
|
|
99
|
+
return a[-(i - 1) :] if i > 1 else ""
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _common_suffix_many(names: List[str]) -> str:
|
|
103
|
+
if not names:
|
|
104
|
+
return ""
|
|
105
|
+
suf = names[0]
|
|
106
|
+
for n in names[1:]:
|
|
107
|
+
suf = _common_suffix(suf, n)
|
|
108
|
+
if not suf:
|
|
109
|
+
break
|
|
110
|
+
return suf
|
|
111
|
+
|
|
112
|
+
|
|
94
113
|
def operation_is_sse(op: Operation) -> bool:
|
|
95
114
|
"""Detect if an Operation advertises Server-Sent-Events (text/event-stream) in any 2xx response."""
|
|
96
115
|
if not getattr(op, "responses", None):
|
|
@@ -100,7 +119,9 @@ def operation_is_sse(op: Operation) -> bool:
|
|
|
100
119
|
try:
|
|
101
120
|
if not str(status_code).startswith("2"):
|
|
102
121
|
continue
|
|
103
|
-
except Exception:
|
|
122
|
+
except Exception as e:
|
|
123
|
+
logger = logging.getLogger(__name__)
|
|
124
|
+
logger.debug("Skipping response status key; conversion failed", exc_info=e)
|
|
104
125
|
continue
|
|
105
126
|
|
|
106
127
|
# Concrete Response object
|
|
@@ -309,6 +330,22 @@ def generate_return_type(operation: Operation) -> OpReturnType:
|
|
|
309
330
|
complex_type=True,
|
|
310
331
|
)
|
|
311
332
|
elif is_schema_type(inner_schema):
|
|
333
|
+
# NEW: if this is a discriminated response union of refs, prefer a named alias
|
|
334
|
+
disc = getattr(inner_schema, "discriminator", None)
|
|
335
|
+
used = getattr(inner_schema, "oneOf", None) or getattr(inner_schema, "anyOf", None)
|
|
336
|
+
disc_key = getattr(disc, "propertyName", None) if disc is not None else None
|
|
337
|
+
|
|
338
|
+
if disc_key and used and all(is_reference_type(s) for s in used):
|
|
339
|
+
member_models = [common.normalize_symbol(s.ref.split("/")[-1]) for s in used] # type: ignore
|
|
340
|
+
alias_name = common.normalize_symbol(_common_suffix_many(member_models)) or "Response"
|
|
341
|
+
|
|
342
|
+
type_conv = TypeConversion(
|
|
343
|
+
original_type="discriminated_union",
|
|
344
|
+
converted_type=alias_name,
|
|
345
|
+
import_types=None,
|
|
346
|
+
)
|
|
347
|
+
return OpReturnType(type=type_conv, status_code=good_responses[0][0], complex_type=True)
|
|
348
|
+
|
|
312
349
|
converted_result = type_converter(inner_schema, True) # type: ignore
|
|
313
350
|
if "array" in converted_result.original_type and isinstance(converted_result.import_types, list):
|
|
314
351
|
matched = re.findall(r"List\[(.+)\]", converted_result.converted_type)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
|
-
from jinja2 import ChoiceLoader, Environment, FileSystemLoader
|
|
3
|
+
from jinja2 import ChoiceLoader, Environment, FileSystemLoader, select_autoescape
|
|
4
4
|
|
|
5
5
|
from . import common
|
|
6
6
|
|
|
@@ -28,7 +28,7 @@ def create_jinja_env():
|
|
|
28
28
|
if custom_template_path is not None
|
|
29
29
|
else FileSystemLoader(TEMPLATE_PATH)
|
|
30
30
|
),
|
|
31
|
-
autoescape=False,
|
|
31
|
+
autoescape=select_autoescape(default_for_string=False),
|
|
32
32
|
trim_blocks=True,
|
|
33
33
|
lstrip_blocks=True,
|
|
34
34
|
)
|
|
@@ -6,12 +6,14 @@ from dataclasses import dataclass
|
|
|
6
6
|
from typing import Dict, List, Optional, Set, Tuple, Union
|
|
7
7
|
|
|
8
8
|
import click
|
|
9
|
+
from openapi_pydantic.v3 import Operation, PathItem
|
|
9
10
|
from openapi_pydantic.v3.v3_0 import (
|
|
10
11
|
Components as Components30,
|
|
11
12
|
)
|
|
12
13
|
from openapi_pydantic.v3.v3_0 import (
|
|
13
14
|
Reference as Reference30,
|
|
14
15
|
)
|
|
16
|
+
from openapi_pydantic.v3.v3_0 import Response as Response30
|
|
15
17
|
from openapi_pydantic.v3.v3_0 import (
|
|
16
18
|
Schema as Schema30,
|
|
17
19
|
)
|
|
@@ -21,6 +23,7 @@ from openapi_pydantic.v3.v3_1 import (
|
|
|
21
23
|
from openapi_pydantic.v3.v3_1 import (
|
|
22
24
|
Reference as Reference31,
|
|
23
25
|
)
|
|
26
|
+
from openapi_pydantic.v3.v3_1 import Response as Response31
|
|
24
27
|
from openapi_pydantic.v3.v3_1 import (
|
|
25
28
|
Schema as Schema31,
|
|
26
29
|
)
|
|
@@ -330,6 +333,117 @@ def _build_discriminator_bindings(components: Components) -> Dict[str, Discrimin
|
|
|
330
333
|
return bindings
|
|
331
334
|
|
|
332
335
|
|
|
336
|
+
def _common_suffix(a: str, b: str) -> str:
|
|
337
|
+
# longest common suffix
|
|
338
|
+
i = 1
|
|
339
|
+
while i <= min(len(a), len(b)) and a[-i] == b[-i]:
|
|
340
|
+
i += 1
|
|
341
|
+
return a[-(i - 1) :] if i > 1 else ""
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def _common_suffix_many(names: List[str]) -> str:
|
|
345
|
+
if not names:
|
|
346
|
+
return ""
|
|
347
|
+
suf = names[0]
|
|
348
|
+
for n in names[1:]:
|
|
349
|
+
suf = _common_suffix(suf, n)
|
|
350
|
+
if not suf:
|
|
351
|
+
break
|
|
352
|
+
return suf
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _alias_name_for_response_union(member_models: List[str], fallback: str) -> str:
|
|
356
|
+
suf = _common_suffix_many(member_models)
|
|
357
|
+
suf = common.normalize_symbol(suf)
|
|
358
|
+
if len(suf) >= 4:
|
|
359
|
+
return suf
|
|
360
|
+
return common.normalize_symbol(fallback)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def generate_response_union_alias_models(
|
|
364
|
+
paths: Dict[str, PathItem],
|
|
365
|
+
pydantic_version: PydanticVersion = PydanticVersion.V2,
|
|
366
|
+
) -> List[Model]:
|
|
367
|
+
"""
|
|
368
|
+
Finds response schemas like:
|
|
369
|
+
content.application/json.schema.oneOf + discriminator
|
|
370
|
+
and emits a named alias module via alias_union.jinja2.
|
|
371
|
+
"""
|
|
372
|
+
jinja_env = create_jinja_env()
|
|
373
|
+
out: Dict[str, Model] = {}
|
|
374
|
+
|
|
375
|
+
for _path_name, path in paths.items():
|
|
376
|
+
for http_method in ["get", "post", "put", "delete", "patch", "head", "options", "trace"]:
|
|
377
|
+
op: Optional[Operation] = getattr(path, http_method, None)
|
|
378
|
+
if op is None or op.responses is None:
|
|
379
|
+
continue
|
|
380
|
+
|
|
381
|
+
for status_code, resp in op.responses.items():
|
|
382
|
+
if not str(status_code).startswith("2"):
|
|
383
|
+
continue
|
|
384
|
+
if not isinstance(resp, (Response30, Response31)):
|
|
385
|
+
continue
|
|
386
|
+
|
|
387
|
+
content = getattr(resp, "content", None)
|
|
388
|
+
if not isinstance(content, dict):
|
|
389
|
+
continue
|
|
390
|
+
|
|
391
|
+
mt = content.get("application/json")
|
|
392
|
+
if mt is None:
|
|
393
|
+
continue
|
|
394
|
+
|
|
395
|
+
schema = getattr(mt, "media_type_schema", None)
|
|
396
|
+
if not isinstance(schema, (Schema30, Schema31)):
|
|
397
|
+
continue
|
|
398
|
+
|
|
399
|
+
disc_key = _get_discriminator_key(schema)
|
|
400
|
+
used = schema.oneOf if schema.oneOf is not None else schema.anyOf
|
|
401
|
+
if not disc_key or not used:
|
|
402
|
+
continue
|
|
403
|
+
|
|
404
|
+
# Only support ref-only unions (your case)
|
|
405
|
+
member_models: List[str] = []
|
|
406
|
+
for sub in used:
|
|
407
|
+
if not isinstance(sub, (Reference30, Reference31)):
|
|
408
|
+
member_models = []
|
|
409
|
+
break
|
|
410
|
+
member_models.append(common.normalize_symbol(sub.ref.split("/")[-1]))
|
|
411
|
+
if len(member_models) < 2:
|
|
412
|
+
continue
|
|
413
|
+
|
|
414
|
+
alias_name = _alias_name_for_response_union(
|
|
415
|
+
member_models,
|
|
416
|
+
fallback=f"{common.normalize_symbol(op.operationId or 'Response')}Response",
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
# de-dupe
|
|
420
|
+
if alias_name in out:
|
|
421
|
+
continue
|
|
422
|
+
|
|
423
|
+
union_type = "Union[" + ", ".join(member_models) + "]"
|
|
424
|
+
member_imports = [f"from .{m} import {m}" for m in member_models]
|
|
425
|
+
|
|
426
|
+
alias_content = _render_union_alias_module(
|
|
427
|
+
jinja_env=jinja_env,
|
|
428
|
+
alias_name=alias_name,
|
|
429
|
+
union_type=union_type,
|
|
430
|
+
discriminator_key=disc_key,
|
|
431
|
+
member_imports=member_imports,
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
# placeholder schema for Model.openapi_object requirement
|
|
435
|
+
placeholder_schema = Schema31() if isinstance(schema, Schema31) else Schema30()
|
|
436
|
+
|
|
437
|
+
out[alias_name] = Model(
|
|
438
|
+
file_name=alias_name,
|
|
439
|
+
content=alias_content,
|
|
440
|
+
openapi_object=placeholder_schema,
|
|
441
|
+
properties=[],
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
return list(out.values())
|
|
445
|
+
|
|
446
|
+
|
|
333
447
|
def type_converter( # noqa: C901
|
|
334
448
|
schema: Union[Schema, Reference],
|
|
335
449
|
required: bool = False,
|
|
@@ -413,14 +527,33 @@ def type_converter( # noqa: C901
|
|
|
413
527
|
converted_type = "Tuple[" + ",".join([i.converted_type for i in conversions]) + "]"
|
|
414
528
|
|
|
415
529
|
converted_type = pre_type + converted_type + post_type
|
|
416
|
-
# Collect
|
|
417
|
-
import_types =
|
|
418
|
-
i.import_types
|
|
419
|
-
|
|
530
|
+
# Collect *all* imports from sub-schemas (not just the first), then dedupe
|
|
531
|
+
import_types = (
|
|
532
|
+
_dedupe_imports(list(itertools.chain.from_iterable(i.import_types for i in conversions if i.import_types)))
|
|
533
|
+
or None
|
|
534
|
+
)
|
|
420
535
|
|
|
421
536
|
elif schema.oneOf is not None or schema.anyOf is not None:
|
|
422
537
|
used = schema.oneOf if schema.oneOf is not None else schema.anyOf
|
|
423
538
|
used = used if used is not None else []
|
|
539
|
+
# Special-case inline nullable wrapper: (ref | null) => Optional[Ref]
|
|
540
|
+
if len(used) == 2:
|
|
541
|
+
ref = next((v for v in used if isinstance(v, (Reference30, Reference31))), None)
|
|
542
|
+
nul = next((v for v in used if isinstance(v, (Schema30, Schema31)) and _is_null_schema(v)), None)
|
|
543
|
+
if ref is not None and nul is not None:
|
|
544
|
+
import_type = common.normalize_symbol(ref.ref.split("/")[-1])
|
|
545
|
+
override = _REFERENCE_TYPE_OVERRIDES.get(import_type)
|
|
546
|
+
if override is not None:
|
|
547
|
+
return TypeConversion(
|
|
548
|
+
original_type=f"union<{ref.ref},null>",
|
|
549
|
+
converted_type=override.converted_type,
|
|
550
|
+
import_types=override.import_types,
|
|
551
|
+
)
|
|
552
|
+
return TypeConversion(
|
|
553
|
+
original_type=f"union<{ref.ref},null>",
|
|
554
|
+
converted_type=f"Optional[{import_type}]",
|
|
555
|
+
import_types=([f"from .{import_type} import {import_type}"] if import_type != model_name else None),
|
|
556
|
+
)
|
|
424
557
|
conversions = []
|
|
425
558
|
for sub_schema in used:
|
|
426
559
|
if isinstance(sub_schema, Schema30) or isinstance(sub_schema, Schema31):
|
|
@@ -474,6 +607,7 @@ def type_converter( # noqa: C901
|
|
|
474
607
|
converted_type = pre_type + "bool" + post_type
|
|
475
608
|
elif schema.type == "array" or str(schema.type) == "DataType.ARRAY":
|
|
476
609
|
retVal = pre_type + "List["
|
|
610
|
+
item_imports: List[str] = []
|
|
477
611
|
if isinstance(schema.items, Reference30) or isinstance(schema.items, Reference31):
|
|
478
612
|
converted_reference = _generate_property_from_reference(
|
|
479
613
|
model_name or "", "", schema.items, schema, required
|
|
@@ -488,14 +622,35 @@ def type_converter( # noqa: C901
|
|
|
488
622
|
else:
|
|
489
623
|
type_value = str(type_str) if type_str is not None else "unknown"
|
|
490
624
|
original_type = "array<" + type_value + ">"
|
|
491
|
-
|
|
625
|
+
# IMPORTANT: propagate imports from the nested schema (e.g. Union[$ref...])
|
|
626
|
+
item_conv = type_converter(schema.items, True, model_name=model_name)
|
|
627
|
+
retVal += item_conv.converted_type
|
|
628
|
+
if item_conv.import_types:
|
|
629
|
+
item_imports.extend(item_conv.import_types)
|
|
492
630
|
else:
|
|
493
631
|
original_type = "array<unknown>"
|
|
494
632
|
retVal += "Any"
|
|
495
633
|
|
|
496
634
|
converted_type = retVal + "]" + post_type
|
|
635
|
+
|
|
636
|
+
# Merge imports from the items schema (when items is a Schema, not a Reference)
|
|
637
|
+
if item_imports:
|
|
638
|
+
import_types = _dedupe_imports((import_types or []) + item_imports)
|
|
497
639
|
elif schema.type == "object" or str(schema.type) == "DataType.OBJECT":
|
|
498
|
-
|
|
640
|
+
# Support "map" objects: type=object + additionalProperties schema/ref
|
|
641
|
+
addl = getattr(schema, "additionalProperties", None)
|
|
642
|
+
if isinstance(addl, (Reference30, Reference31)):
|
|
643
|
+
# Dict[str, <ref>]
|
|
644
|
+
v = type_converter(addl, required=True, model_name=model_name)
|
|
645
|
+
converted_type = f"{pre_type}Dict[str, {v.converted_type}]{post_type}"
|
|
646
|
+
import_types = _dedupe_imports((import_types or []) + (v.import_types or [])) or None
|
|
647
|
+
elif isinstance(addl, (Schema30, Schema31)):
|
|
648
|
+
# Dict[str, <schema>], including Union[...] etc.
|
|
649
|
+
v = type_converter(addl, required=True, model_name=model_name)
|
|
650
|
+
converted_type = f"{pre_type}Dict[str, {v.converted_type}]{post_type}"
|
|
651
|
+
import_types = _dedupe_imports((import_types or []) + (v.import_types or [])) or None
|
|
652
|
+
else:
|
|
653
|
+
converted_type = pre_type + "Dict[str, Any]" + post_type
|
|
499
654
|
elif schema.type == "null" or str(schema.type) == "DataType.NULL":
|
|
500
655
|
converted_type = pre_type + "None" + post_type
|
|
501
656
|
elif schema.type is None:
|
|
@@ -724,6 +879,32 @@ def generate_models(components: Components, pydantic_version: PydanticVersion =
|
|
|
724
879
|
# Schema property
|
|
725
880
|
conv_property = _generate_property_from_schema(name, prop_name, prop_schema, schema_or_reference)
|
|
726
881
|
|
|
882
|
+
# --------------------------
|
|
883
|
+
# NEW: const / single-value enum -> Literal[...] with default
|
|
884
|
+
# --------------------------
|
|
885
|
+
if isinstance(prop_schema, (Schema30, Schema31)):
|
|
886
|
+
const_val = getattr(prop_schema, "const", None)
|
|
887
|
+
enum_vals = getattr(prop_schema, "enum", None)
|
|
888
|
+
|
|
889
|
+
literal_val = None
|
|
890
|
+
if const_val is not None:
|
|
891
|
+
literal_val = const_val
|
|
892
|
+
elif isinstance(enum_vals, list) and len(enum_vals) == 1:
|
|
893
|
+
literal_val = enum_vals[0]
|
|
894
|
+
|
|
895
|
+
if literal_val is not None:
|
|
896
|
+
# make sure discriminator is present for unions: required + default
|
|
897
|
+
conv_property.required = True
|
|
898
|
+
conv_property.default = repr(literal_val)
|
|
899
|
+
|
|
900
|
+
conv_property.type = TypeConversion(
|
|
901
|
+
original_type=conv_property.type.original_type,
|
|
902
|
+
converted_type=f"Literal[{repr(literal_val)}]",
|
|
903
|
+
import_types=_dedupe_imports(
|
|
904
|
+
(conv_property.type.import_types or []) + ["from typing import Literal"]
|
|
905
|
+
),
|
|
906
|
+
)
|
|
907
|
+
|
|
727
908
|
# If this model is a discriminated union member, and this property
|
|
728
909
|
# is the discriminator key, make it a Literal[...] with a default
|
|
729
910
|
binding = discriminator_bindings.get(name)
|
|
@@ -7,7 +7,7 @@ from typing import Any, Dict, Optional, Union
|
|
|
7
7
|
import json
|
|
8
8
|
|
|
9
9
|
import httpx
|
|
10
|
-
from pydantic import BaseModel
|
|
10
|
+
from pydantic import BaseModel, TypeAdapter
|
|
11
11
|
|
|
12
12
|
from ..models import *
|
|
13
13
|
from ..exceptions import HTTPException
|
|
@@ -134,9 +134,9 @@ AsyncGenerator[str | dict[str, Any], None]
|
|
|
134
134
|
return None
|
|
135
135
|
{% elif op.return_type.complex_type %}
|
|
136
136
|
{% if op.return_type.list_type is none %}
|
|
137
|
-
return {{ op.return_type.type.converted_type }}.
|
|
137
|
+
return TypeAdapter({{ op.return_type.type.converted_type }}).validate_python(body)
|
|
138
138
|
{% else %}
|
|
139
|
-
return [{{ op.return_type.list_type }}.
|
|
139
|
+
return TypeAdapter(list[{{ op.return_type.list_type }}]).validate_python(body)
|
|
140
140
|
{% endif %}
|
|
141
141
|
{% else %}
|
|
142
142
|
return body
|
|
@@ -7,7 +7,7 @@ from typing import Any, Dict, Optional, Union
|
|
|
7
7
|
import json
|
|
8
8
|
|
|
9
9
|
import httpx
|
|
10
|
-
from pydantic import BaseModel
|
|
10
|
+
from pydantic import BaseModel, TypeAdapter
|
|
11
11
|
|
|
12
12
|
from ..models import *
|
|
13
13
|
from ..exceptions import HTTPException
|
|
@@ -133,9 +133,9 @@ Generator[str | dict[str, Any], None, None]
|
|
|
133
133
|
return None
|
|
134
134
|
{% elif op.return_type.complex_type %}
|
|
135
135
|
{% if op.return_type.list_type is none %}
|
|
136
|
-
return {{ op.return_type.type.converted_type }}.
|
|
136
|
+
return TypeAdapter({{ op.return_type.type.converted_type }}).validate_python(body)
|
|
137
137
|
{% else %}
|
|
138
|
-
return [{{ op.return_type.list_type }}.
|
|
138
|
+
return TypeAdapter(list[{{ op.return_type.list_type }}]).validate_python(body)
|
|
139
139
|
{% endif %}
|
|
140
140
|
{% else %}
|
|
141
141
|
return body
|
|
File without changes
|
|
File without changes
|
{ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/.github/dependabot.yml
RENAMED
|
File without changes
|
{ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/.github/workflows/ci.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/.pre-commit-config.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/docs/css/termynal.css
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/docs/openapi-definition.md
RENAMED
|
File without changes
|
|
File without changes
|
{ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/docs/references/index.md
RENAMED
|
File without changes
|
|
File without changes
|
{ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/docs/tutorial/advanced.md
RENAMED
|
File without changes
|
|
File without changes
|
{ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/docs/tutorial/index.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/build_test_api/api.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_generate_data.py
RENAMED
|
File without changes
|
|
File without changes
|
{ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_generated_code.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_openapi_30.py
RENAMED
|
File without changes
|
{ab_openapi_python_generator-2.2.3 → ab_openapi_python_generator-2.2.4}/tests/test_openapi_31.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|