ab-openapi-python-generator 2.2.2__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.2 → ab_openapi_python_generator-2.2.4}/.gitignore +1 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/.vscode/launch.json +8 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/PKG-INFO +1 -1
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/pyproject.toml +1 -1
- {ab_openapi_python_generator-2.2.2 → 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.2 → 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.2 → 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.4/src/ab_openapi_python_generator/language_converters/python/templates/async_client_httpx_pydantic_2.jinja2 +146 -0
- ab_openapi_python_generator-2.2.4/src/ab_openapi_python_generator/language_converters/python/templates/sync_client_httpx_pydantic_2.jinja2 +145 -0
- ab_openapi_python_generator-2.2.2/src/ab_openapi_python_generator/language_converters/python/templates/async_client_httpx_pydantic_2.jinja2 +0 -80
- ab_openapi_python_generator-2.2.2/src/ab_openapi_python_generator/language_converters/python/templates/sync_client_httpx_pydantic_2.jinja2 +0 -80
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/.envrc.example +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/.gitattributes +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/.github/dependabot.yml +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/.github/workflows/ci.yaml +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/.github/workflows/publish.yaml +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/.pre-commit-config.yaml +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/.vscode/tasks.json +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/LICENSE +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/Makefile +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/README.md +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/docs/acknowledgements/index.md +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/docs/css/custom.css +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/docs/css/termynal.css +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/docs/index.md +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/docs/js/custom.js +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/docs/js/termynal.js +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/docs/openapi-definition.md +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/docs/quick_start.md +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/docs/references/index.md +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/docs/references/module_usage.md +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/docs/tutorial/advanced.md +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/docs/tutorial/authentication.md +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/docs/tutorial/index.md +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/logo.png +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/__init__.py +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/__main__.py +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/common.py +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/generate_data.py +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/language_converters/__init__.py +0 -0
- {ab_openapi_python_generator-2.2.2 → 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.2 → 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.2 → 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.2 → 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.2 → 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.2 → 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.2 → 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.2 → 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.2 → 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.2 → 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.2 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/models.py +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/parsers/__init__.py +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/parsers/openapi_30.py +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/parsers/openapi_31.py +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/py.typed +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/src/ab_openapi_python_generator/version_detector.py +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/__init__.py +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/build_test_api/api.py +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/conftest.py +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/test_common_normalize_symbol.py +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/test_data/failing_api.json +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/test_data/gitea_issue_11.json +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/test_data/issue_117.json +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/test_data/issue_120.json +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/test_data/issue_17.json +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/test_data/issue_30_87.json +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/test_data/issue_51.json +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/test_data/issue_55.json +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/test_data/issue_71.json +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/test_data/issue_71_31.json +0 -0
- {ab_openapi_python_generator-2.2.2 → 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.2 → ab_openapi_python_generator-2.2.4}/tests/test_data/issue_keyword_parameter_name.json +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/test_data/openapi_gitea_converted.json +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/test_data/swagger_petstore_3_0_4.yaml +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/test_data/swagger_petstore_3_1.yaml +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/test_data/test_api.json +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/test_data/test_api_31.json +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/test_generate_data.py +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/test_generate_data_negative.py +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/test_generated_code.py +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/test_jinja_no_autoescape.py +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/test_main.py +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/test_model_docstring.py +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/test_model_generator.py +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/test_model_generator_edges.py +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/test_openapi_30.py +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/test_openapi_31.py +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/test_openapi_31_completeness.py +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/test_openapi_31_coverage.py +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/test_openapi_31_schema_features.py +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/test_service_generator.py +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/test_service_generator_edges.py +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/test_swagger_petstore_30.py +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/test_swagger_petstore_31.py +0 -0
- {ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/tests/test_version_detector_edges.py +0 -0
- {ab_openapi_python_generator-2.2.2 → 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)
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
{% if env_token_name is not none %}import os{% endif %}
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import AsyncGenerator
|
|
6
|
+
from typing import Any, Dict, Optional, Union
|
|
7
|
+
import json
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
from pydantic import BaseModel, TypeAdapter
|
|
11
|
+
|
|
12
|
+
from ..models import *
|
|
13
|
+
from ..exceptions import HTTPException
|
|
14
|
+
|
|
15
|
+
{% set _base_url = servers[0].url if servers|length > 0 else "/" %}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AsyncClient(BaseModel):
|
|
19
|
+
model_config = {"validate_assignment": True}
|
|
20
|
+
|
|
21
|
+
base_url: str = "{{ _base_url }}"
|
|
22
|
+
verify: Union[bool, str] = True
|
|
23
|
+
{% if env_token_name is none %}
|
|
24
|
+
access_token: Optional[str] = None
|
|
25
|
+
{% endif %}
|
|
26
|
+
|
|
27
|
+
def get_access_token(self) -> Optional[str]:
|
|
28
|
+
{% if env_token_name is not none %}
|
|
29
|
+
try:
|
|
30
|
+
return os.environ["{{ env_token_name }}"]
|
|
31
|
+
except KeyError:
|
|
32
|
+
return None
|
|
33
|
+
{% else %}
|
|
34
|
+
return self.access_token
|
|
35
|
+
{% endif %}
|
|
36
|
+
|
|
37
|
+
def set_access_token(self, value: str) -> None:
|
|
38
|
+
{% if env_token_name is not none %}
|
|
39
|
+
raise Exception(
|
|
40
|
+
"This client was generated with an environment variable for the access token. "
|
|
41
|
+
"Please set '{{ env_token_name }}'."
|
|
42
|
+
)
|
|
43
|
+
{% else %}
|
|
44
|
+
self.access_token = value
|
|
45
|
+
{% endif %}
|
|
46
|
+
|
|
47
|
+
{% for op in operations %}
|
|
48
|
+
async def {{ op.operation_id }}(self{% if op.params %}, {{ op.params }}{% endif %}) -> {%- if op.is_sse -%}
|
|
49
|
+
AsyncGenerator[str | dict[str, Any], None]
|
|
50
|
+
{%- else -%}
|
|
51
|
+
{%- if op.return_type.type is none or op.return_type.type.converted_type is none -%}None
|
|
52
|
+
{%- elif op.return_type.list_type is not none -%}list[{{ op.return_type.list_type }}]
|
|
53
|
+
{%- else -%}{{ op.return_type.type.converted_type }}
|
|
54
|
+
{%- endif -%}
|
|
55
|
+
{%- endif -%}:
|
|
56
|
+
base_url = self.base_url
|
|
57
|
+
path = f"{{ op.path_name }}"
|
|
58
|
+
|
|
59
|
+
headers = {
|
|
60
|
+
"Content-Type": "application/json",
|
|
61
|
+
{% if op.is_sse %}
|
|
62
|
+
"Accept": "text/event-stream",
|
|
63
|
+
{% else %}
|
|
64
|
+
"Accept": "application/json",
|
|
65
|
+
{% endif %}
|
|
66
|
+
"Authorization": f"Bearer { self.get_access_token() }",
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
query_params: Dict[str, Any] = {
|
|
70
|
+
{% for qp in op.query_params %}
|
|
71
|
+
{{ qp }},
|
|
72
|
+
{% endfor %}
|
|
73
|
+
}
|
|
74
|
+
query_params = {k: v for (k, v) in query_params.items() if v is not None}
|
|
75
|
+
|
|
76
|
+
{% if op.is_sse %}
|
|
77
|
+
async with httpx.AsyncClient(base_url=base_url, verify=self.verify) as client:
|
|
78
|
+
async with client.stream(
|
|
79
|
+
"{{ op.method }}",
|
|
80
|
+
httpx.URL(path),
|
|
81
|
+
headers=headers,
|
|
82
|
+
params=query_params,
|
|
83
|
+
{% if op.body_param %}
|
|
84
|
+
json={{ op.body_param }},
|
|
85
|
+
{% endif %}
|
|
86
|
+
) as response:
|
|
87
|
+
if response.status_code != {{ op.return_type.status_code }}:
|
|
88
|
+
raise HTTPException(
|
|
89
|
+
response.status_code,
|
|
90
|
+
f"{{ op.operation_id }} failed with status code: {response.status_code}",
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
async for line in response.aiter_lines():
|
|
94
|
+
if not line:
|
|
95
|
+
continue
|
|
96
|
+
if line.startswith("data:"):
|
|
97
|
+
payload = line[len("data:"):].strip()
|
|
98
|
+
if not payload:
|
|
99
|
+
continue
|
|
100
|
+
if payload == "[DONE]":
|
|
101
|
+
break
|
|
102
|
+
try:
|
|
103
|
+
obj = json.loads(payload)
|
|
104
|
+
if isinstance(obj, dict):
|
|
105
|
+
yield obj
|
|
106
|
+
else:
|
|
107
|
+
yield payload
|
|
108
|
+
except Exception:
|
|
109
|
+
yield payload
|
|
110
|
+
else:
|
|
111
|
+
# Non-data lines: yield as raw text for debugging/visibility
|
|
112
|
+
yield line
|
|
113
|
+
{% else %}
|
|
114
|
+
async with httpx.AsyncClient(base_url=base_url, verify=self.verify) as client:
|
|
115
|
+
response = await client.request(
|
|
116
|
+
"{{ op.method }}",
|
|
117
|
+
httpx.URL(path),
|
|
118
|
+
headers=headers,
|
|
119
|
+
params=query_params,
|
|
120
|
+
{% if op.body_param %}
|
|
121
|
+
json={{ op.body_param }},
|
|
122
|
+
{% endif %}
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
if response.status_code != {{ op.return_type.status_code }}:
|
|
126
|
+
raise HTTPException(
|
|
127
|
+
response.status_code,
|
|
128
|
+
f"{{ op.operation_id }} failed with status code: {response.status_code}",
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
body = None if {{ op.return_type.status_code }} == 204 else response.json()
|
|
132
|
+
|
|
133
|
+
{% if op.return_type.type is none or op.return_type.type.converted_type is none %}
|
|
134
|
+
return None
|
|
135
|
+
{% elif op.return_type.complex_type %}
|
|
136
|
+
{% if op.return_type.list_type is none %}
|
|
137
|
+
return TypeAdapter({{ op.return_type.type.converted_type }}).validate_python(body)
|
|
138
|
+
{% else %}
|
|
139
|
+
return TypeAdapter(list[{{ op.return_type.list_type }}]).validate_python(body)
|
|
140
|
+
{% endif %}
|
|
141
|
+
{% else %}
|
|
142
|
+
return body
|
|
143
|
+
{% endif %}
|
|
144
|
+
{% endif %}
|
|
145
|
+
|
|
146
|
+
{% endfor %}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
{% if env_token_name is not none %}import os{% endif %}
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Generator
|
|
6
|
+
from typing import Any, Dict, Optional, Union
|
|
7
|
+
import json
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
from pydantic import BaseModel, TypeAdapter
|
|
11
|
+
|
|
12
|
+
from ..models import *
|
|
13
|
+
from ..exceptions import HTTPException
|
|
14
|
+
|
|
15
|
+
{% set _base_url = servers[0].url if servers|length > 0 else "/" %}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SyncClient(BaseModel):
|
|
19
|
+
model_config = {"validate_assignment": True}
|
|
20
|
+
|
|
21
|
+
base_url: str = "{{ _base_url }}"
|
|
22
|
+
verify: Union[bool, str] = True
|
|
23
|
+
{% if env_token_name is none %}
|
|
24
|
+
access_token: Optional[str] = None
|
|
25
|
+
{% endif %}
|
|
26
|
+
|
|
27
|
+
def get_access_token(self) -> Optional[str]:
|
|
28
|
+
{% if env_token_name is not none %}
|
|
29
|
+
try:
|
|
30
|
+
return os.environ["{{ env_token_name }}"]
|
|
31
|
+
except KeyError:
|
|
32
|
+
return None
|
|
33
|
+
{% else %}
|
|
34
|
+
return self.access_token
|
|
35
|
+
{% endif %}
|
|
36
|
+
|
|
37
|
+
def set_access_token(self, value: str) -> None:
|
|
38
|
+
{% if env_token_name is not none %}
|
|
39
|
+
raise Exception(
|
|
40
|
+
"This client was generated with an environment variable for the access token. "
|
|
41
|
+
"Please set '{{ env_token_name }}'."
|
|
42
|
+
)
|
|
43
|
+
{% else %}
|
|
44
|
+
self.access_token = value
|
|
45
|
+
{% endif %}
|
|
46
|
+
|
|
47
|
+
{% for op in operations %}
|
|
48
|
+
def {{ op.operation_id }}(self{% if op.params %}, {{ op.params }}{% endif %}) -> {%- if op.is_sse -%}
|
|
49
|
+
Generator[str | dict[str, Any], None, None]
|
|
50
|
+
{%- else -%}
|
|
51
|
+
{%- if op.return_type.type is none or op.return_type.type.converted_type is none -%}None
|
|
52
|
+
{%- elif op.return_type.list_type is not none -%}list[{{ op.return_type.list_type }}]
|
|
53
|
+
{%- else -%}{{ op.return_type.type.converted_type }}
|
|
54
|
+
{%- endif -%}
|
|
55
|
+
{%- endif -%}:
|
|
56
|
+
base_url = self.base_url
|
|
57
|
+
path = f"{{ op.path_name }}"
|
|
58
|
+
|
|
59
|
+
headers = {
|
|
60
|
+
"Content-Type": "application/json",
|
|
61
|
+
{% if op.is_sse %}
|
|
62
|
+
"Accept": "text/event-stream",
|
|
63
|
+
{% else %}
|
|
64
|
+
"Accept": "application/json",
|
|
65
|
+
{% endif %}
|
|
66
|
+
"Authorization": f"Bearer { self.get_access_token() }",
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
query_params: Dict[str, Any] = {
|
|
70
|
+
{% for qp in op.query_params %}
|
|
71
|
+
{{ qp }},
|
|
72
|
+
{% endfor %}
|
|
73
|
+
}
|
|
74
|
+
query_params = {k: v for (k, v) in query_params.items() if v is not None}
|
|
75
|
+
|
|
76
|
+
{% if op.is_sse %}
|
|
77
|
+
with httpx.Client(base_url=base_url, verify=self.verify) as client:
|
|
78
|
+
with client.stream(
|
|
79
|
+
"{{ op.method }}",
|
|
80
|
+
httpx.URL(path),
|
|
81
|
+
headers=headers,
|
|
82
|
+
params=query_params,
|
|
83
|
+
{% if op.body_param %}
|
|
84
|
+
json={{ op.body_param }},
|
|
85
|
+
{% endif %}
|
|
86
|
+
) as response:
|
|
87
|
+
if response.status_code != {{ op.return_type.status_code }}:
|
|
88
|
+
raise HTTPException(
|
|
89
|
+
response.status_code,
|
|
90
|
+
f"{{ op.operation_id }} failed with status code: {response.status_code}",
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
for line in response.iter_lines():
|
|
94
|
+
if not line:
|
|
95
|
+
continue
|
|
96
|
+
if line.startswith("data:"):
|
|
97
|
+
payload = line[len("data:"):].strip()
|
|
98
|
+
if not payload:
|
|
99
|
+
continue
|
|
100
|
+
if payload == "[DONE]":
|
|
101
|
+
break
|
|
102
|
+
try:
|
|
103
|
+
obj = json.loads(payload)
|
|
104
|
+
if isinstance(obj, dict):
|
|
105
|
+
yield obj
|
|
106
|
+
else:
|
|
107
|
+
yield payload
|
|
108
|
+
except Exception:
|
|
109
|
+
yield payload
|
|
110
|
+
else:
|
|
111
|
+
yield line
|
|
112
|
+
{% else %}
|
|
113
|
+
with httpx.Client(base_url=base_url, verify=self.verify) as client:
|
|
114
|
+
response = client.request(
|
|
115
|
+
"{{ op.method }}",
|
|
116
|
+
httpx.URL(path),
|
|
117
|
+
headers=headers,
|
|
118
|
+
params=query_params,
|
|
119
|
+
{% if op.body_param %}
|
|
120
|
+
json={{ op.body_param }},
|
|
121
|
+
{% endif %}
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
if response.status_code != {{ op.return_type.status_code }}:
|
|
125
|
+
raise HTTPException(
|
|
126
|
+
response.status_code,
|
|
127
|
+
f"{{ op.operation_id }} failed with status code: {response.status_code}",
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
body = None if {{ op.return_type.status_code }} == 204 else response.json()
|
|
131
|
+
|
|
132
|
+
{% if op.return_type.type is none or op.return_type.type.converted_type is none %}
|
|
133
|
+
return None
|
|
134
|
+
{% elif op.return_type.complex_type %}
|
|
135
|
+
{% if op.return_type.list_type is none %}
|
|
136
|
+
return TypeAdapter({{ op.return_type.type.converted_type }}).validate_python(body)
|
|
137
|
+
{% else %}
|
|
138
|
+
return TypeAdapter(list[{{ op.return_type.list_type }}]).validate_python(body)
|
|
139
|
+
{% endif %}
|
|
140
|
+
{% else %}
|
|
141
|
+
return body
|
|
142
|
+
{% endif %}
|
|
143
|
+
{% endif %}
|
|
144
|
+
|
|
145
|
+
{% endfor %}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
{% if env_token_name is not none %}import os{% endif %}
|
|
2
|
-
|
|
3
|
-
from typing import Any, Dict, Optional, Union
|
|
4
|
-
|
|
5
|
-
import httpx
|
|
6
|
-
from pydantic import BaseModel, HttpUrl
|
|
7
|
-
|
|
8
|
-
from ..models import *
|
|
9
|
-
{% set _base_url = servers[0].url if servers|length > 0 else "/" %}
|
|
10
|
-
from ..exceptions import HTTPException
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class AsyncClient(BaseModel):
|
|
14
|
-
model_config = {"validate_assignment": True}
|
|
15
|
-
|
|
16
|
-
base_url: str = "{{ _base_url }}"
|
|
17
|
-
verify: Union[bool, str] = True
|
|
18
|
-
{% if env_token_name is none %}
|
|
19
|
-
access_token: Optional[str] = None
|
|
20
|
-
{% endif %}
|
|
21
|
-
|
|
22
|
-
def get_access_token(self) -> Optional[str]:
|
|
23
|
-
{% if env_token_name is not none %}
|
|
24
|
-
try:
|
|
25
|
-
return os.environ["{{ env_token_name }}"]
|
|
26
|
-
except KeyError:
|
|
27
|
-
return None
|
|
28
|
-
{% else %}
|
|
29
|
-
return self.access_token
|
|
30
|
-
{% endif %}
|
|
31
|
-
|
|
32
|
-
def set_access_token(self, value: str) -> None:
|
|
33
|
-
{% if env_token_name is not none %}
|
|
34
|
-
raise Exception(
|
|
35
|
-
"This client was generated with an environment variable for the access token. "
|
|
36
|
-
"Please set '{{ env_token_name }}'."
|
|
37
|
-
)
|
|
38
|
-
{% else %}
|
|
39
|
-
self.access_token = value
|
|
40
|
-
{% endif %}
|
|
41
|
-
|
|
42
|
-
{% for op in operations %}
|
|
43
|
-
async def {{ op.operation_id }}(self{% if op.params %}, {{ op.params }}{% endif %}) -> Any:
|
|
44
|
-
base_url = self.base_url
|
|
45
|
-
path = f"{{ op.path_name }}"
|
|
46
|
-
|
|
47
|
-
headers = {
|
|
48
|
-
"Content-Type": "application/json",
|
|
49
|
-
"Accept": "application/json",
|
|
50
|
-
"Authorization": f"Bearer { self.get_access_token() }",
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
query_params: Dict[str, Any] = {
|
|
54
|
-
{% for qp in op.query_params %}
|
|
55
|
-
{{ qp }},
|
|
56
|
-
{% endfor %}
|
|
57
|
-
}
|
|
58
|
-
query_params = {k: v for (k, v) in query_params.items() if v is not None}
|
|
59
|
-
|
|
60
|
-
async with httpx.AsyncClient(base_url=base_url, verify=self.verify) as client:
|
|
61
|
-
response = await client.request(
|
|
62
|
-
"{{ op.method }}",
|
|
63
|
-
httpx.URL(path),
|
|
64
|
-
headers=headers,
|
|
65
|
-
params=query_params,
|
|
66
|
-
{% if op.body_param %}
|
|
67
|
-
json={{ op.body_param }},
|
|
68
|
-
{% endif %}
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
if response.status_code != {{ op.return_type.status_code }}:
|
|
72
|
-
raise HTTPException(
|
|
73
|
-
response.status_code,
|
|
74
|
-
f"{{ op.operation_id }} failed with status code: {response.status_code}",
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
body = None if {{ op.return_type.status_code }} == 204 else response.json()
|
|
78
|
-
return body
|
|
79
|
-
|
|
80
|
-
{% endfor %}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
{% if env_token_name is not none %}import os{% endif %}
|
|
2
|
-
|
|
3
|
-
from typing import Any, Dict, Optional, Union
|
|
4
|
-
|
|
5
|
-
import httpx
|
|
6
|
-
from pydantic import BaseModel, HttpUrl
|
|
7
|
-
|
|
8
|
-
from ..models import *
|
|
9
|
-
{% set _base_url = servers[0].url if servers|length > 0 else "/" %}
|
|
10
|
-
from ..exceptions import HTTPException
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class SyncClient(BaseModel):
|
|
14
|
-
model_config = {"validate_assignment": True}
|
|
15
|
-
|
|
16
|
-
base_url: str = "{{ _base_url }}"
|
|
17
|
-
verify: Union[bool, str] = True
|
|
18
|
-
{% if env_token_name is none %}
|
|
19
|
-
access_token: Optional[str] = None
|
|
20
|
-
{% endif %}
|
|
21
|
-
|
|
22
|
-
def get_access_token(self) -> Optional[str]:
|
|
23
|
-
{% if env_token_name is not none %}
|
|
24
|
-
try:
|
|
25
|
-
return os.environ["{{ env_token_name }}"]
|
|
26
|
-
except KeyError:
|
|
27
|
-
return None
|
|
28
|
-
{% else %}
|
|
29
|
-
return self.access_token
|
|
30
|
-
{% endif %}
|
|
31
|
-
|
|
32
|
-
def set_access_token(self, value: str) -> None:
|
|
33
|
-
{% if env_token_name is not none %}
|
|
34
|
-
raise Exception(
|
|
35
|
-
"This client was generated with an environment variable for the access token. "
|
|
36
|
-
"Please set '{{ env_token_name }}'."
|
|
37
|
-
)
|
|
38
|
-
{% else %}
|
|
39
|
-
self.access_token = value
|
|
40
|
-
{% endif %}
|
|
41
|
-
|
|
42
|
-
{% for op in operations %}
|
|
43
|
-
def {{ op.operation_id }}(self{% if op.params %}, {{ op.params }}{% endif %}) -> Any:
|
|
44
|
-
base_url = self.base_url
|
|
45
|
-
path = f"{{ op.path_name }}"
|
|
46
|
-
|
|
47
|
-
headers = {
|
|
48
|
-
"Content-Type": "application/json",
|
|
49
|
-
"Accept": "application/json",
|
|
50
|
-
"Authorization": f"Bearer { self.get_access_token() }",
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
query_params: Dict[str, Any] = {
|
|
54
|
-
{% for qp in op.query_params %}
|
|
55
|
-
{{ qp }},
|
|
56
|
-
{% endfor %}
|
|
57
|
-
}
|
|
58
|
-
query_params = {k: v for (k, v) in query_params.items() if v is not None}
|
|
59
|
-
|
|
60
|
-
with httpx.Client(base_url=base_url, verify=self.verify) as client:
|
|
61
|
-
response = client.request(
|
|
62
|
-
"{{ op.method }}",
|
|
63
|
-
httpx.URL(path),
|
|
64
|
-
headers=headers,
|
|
65
|
-
params=query_params,
|
|
66
|
-
{% if op.body_param %}
|
|
67
|
-
json={{ op.body_param }},
|
|
68
|
-
{% endif %}
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
if response.status_code != {{ op.return_type.status_code }}:
|
|
72
|
-
raise HTTPException(
|
|
73
|
-
response.status_code,
|
|
74
|
-
f"{{ op.operation_id }} failed with status code: {response.status_code}",
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
body = None if {{ op.return_type.status_code }} == 204 else response.json()
|
|
78
|
-
return body
|
|
79
|
-
|
|
80
|
-
{% endfor %}
|
|
File without changes
|
|
File without changes
|
{ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/.github/dependabot.yml
RENAMED
|
File without changes
|
{ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/.github/workflows/ci.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{ab_openapi_python_generator-2.2.2 → 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.2 → 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.2 → ab_openapi_python_generator-2.2.4}/docs/openapi-definition.md
RENAMED
|
File without changes
|
|
File without changes
|
{ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/docs/references/index.md
RENAMED
|
File without changes
|
|
File without changes
|
{ab_openapi_python_generator-2.2.2 → ab_openapi_python_generator-2.2.4}/docs/tutorial/advanced.md
RENAMED
|
File without changes
|
|
File without changes
|
{ab_openapi_python_generator-2.2.2 → 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.2 → 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.2 → 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.2 → 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.2 → ab_openapi_python_generator-2.2.4}/tests/test_openapi_30.py
RENAMED
|
File without changes
|
{ab_openapi_python_generator-2.2.2 → 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
|