pydantic-openapi-generator 2.2.8__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.
- pydantic_openapi_generator/__init__.py +17 -0
- pydantic_openapi_generator/__main__.py +85 -0
- pydantic_openapi_generator/common.py +58 -0
- pydantic_openapi_generator/generate_data.py +235 -0
- pydantic_openapi_generator/language_converters/__init__.py +0 -0
- pydantic_openapi_generator/language_converters/python/__init__.py +0 -0
- pydantic_openapi_generator/language_converters/python/client_generator.py +495 -0
- pydantic_openapi_generator/language_converters/python/common.py +58 -0
- pydantic_openapi_generator/language_converters/python/exception_generator.py +23 -0
- pydantic_openapi_generator/language_converters/python/generator.py +61 -0
- pydantic_openapi_generator/language_converters/python/jinja_config.py +38 -0
- pydantic_openapi_generator/language_converters/python/model_generator.py +1024 -0
- pydantic_openapi_generator/language_converters/python/templates/alias_union.jinja2 +17 -0
- pydantic_openapi_generator/language_converters/python/templates/async_client_httpx_pydantic_2.jinja2 +151 -0
- pydantic_openapi_generator/language_converters/python/templates/discriminator_enum.jinja2 +7 -0
- pydantic_openapi_generator/language_converters/python/templates/enum.jinja2 +11 -0
- pydantic_openapi_generator/language_converters/python/templates/http_exception.jinja2 +8 -0
- pydantic_openapi_generator/language_converters/python/templates/models.jinja2 +24 -0
- pydantic_openapi_generator/language_converters/python/templates/models_pydantic_2.jinja2 +28 -0
- pydantic_openapi_generator/language_converters/python/templates/sync_client_httpx_pydantic_2.jinja2 +150 -0
- pydantic_openapi_generator/models.py +86 -0
- pydantic_openapi_generator/parsers/__init__.py +13 -0
- pydantic_openapi_generator/parsers/openapi_30.py +65 -0
- pydantic_openapi_generator/parsers/openapi_31.py +65 -0
- pydantic_openapi_generator/py.typed +0 -0
- pydantic_openapi_generator/version_detector.py +67 -0
- pydantic_openapi_generator-2.2.8.dist-info/METADATA +141 -0
- pydantic_openapi_generator-2.2.8.dist-info/RECORD +31 -0
- pydantic_openapi_generator-2.2.8.dist-info/WHEEL +4 -0
- pydantic_openapi_generator-2.2.8.dist-info/entry_points.txt +2 -0
- pydantic_openapi_generator-2.2.8.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{# Renders ONE standalone python module that defines a named Union/Annotated alias #}
|
|
2
|
+
{% if discriminator_key -%}
|
|
3
|
+
from typing import Annotated, Union
|
|
4
|
+
from pydantic import Field
|
|
5
|
+
{% else -%}
|
|
6
|
+
from typing import Union
|
|
7
|
+
{% endif %}
|
|
8
|
+
|
|
9
|
+
{% for imp in member_imports -%}
|
|
10
|
+
{{ imp }}
|
|
11
|
+
{% endfor %}
|
|
12
|
+
|
|
13
|
+
{% if discriminator_key -%}
|
|
14
|
+
{{ alias_name }} = Annotated[{{ union_type }}, Field(discriminator="{{ discriminator_key }}")]
|
|
15
|
+
{% else -%}
|
|
16
|
+
{{ alias_name }} = {{ union_type }}
|
|
17
|
+
{% endif -%}
|
pydantic_openapi_generator/language_converters/python/templates/async_client_httpx_pydantic_2.jinja2
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
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
|
+
async 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
|
+
async 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
|
+
{% if op.body_param %}
|
|
61
|
+
"Content-Type": "application/json",
|
|
62
|
+
{% endif %}
|
|
63
|
+
{% if op.is_sse %}
|
|
64
|
+
"Accept": "text/event-stream",
|
|
65
|
+
{% else %}
|
|
66
|
+
"Accept": "application/json",
|
|
67
|
+
{% endif %}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
_token = await self.get_access_token()
|
|
71
|
+
if _token:
|
|
72
|
+
headers["Authorization"] = f"Bearer {_token}"
|
|
73
|
+
|
|
74
|
+
query_params: Dict[str, Any] = {
|
|
75
|
+
{% for qp in op.query_params %}
|
|
76
|
+
{{ qp }},
|
|
77
|
+
{% endfor %}
|
|
78
|
+
}
|
|
79
|
+
query_params = {k: v for (k, v) in query_params.items() if v is not None}
|
|
80
|
+
|
|
81
|
+
{% if op.is_sse %}
|
|
82
|
+
async with httpx.AsyncClient(base_url=base_url, verify=self.verify) as client:
|
|
83
|
+
async with client.stream(
|
|
84
|
+
"{{ op.method }}",
|
|
85
|
+
httpx.URL(path),
|
|
86
|
+
headers=headers,
|
|
87
|
+
params=query_params,
|
|
88
|
+
{% if op.body_param %}
|
|
89
|
+
json={{ op.body_param }},
|
|
90
|
+
{% endif %}
|
|
91
|
+
) as response:
|
|
92
|
+
if response.status_code != {{ op.return_type.status_code }}:
|
|
93
|
+
raise HTTPException(
|
|
94
|
+
response.status_code,
|
|
95
|
+
f"{{ op.operation_id }} failed with status code: {response.status_code}",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
async for line in response.aiter_lines():
|
|
99
|
+
if not line:
|
|
100
|
+
continue
|
|
101
|
+
if line.startswith("data:"):
|
|
102
|
+
payload = line[len("data:"):].strip()
|
|
103
|
+
if not payload:
|
|
104
|
+
continue
|
|
105
|
+
if payload == "[DONE]":
|
|
106
|
+
break
|
|
107
|
+
try:
|
|
108
|
+
obj = json.loads(payload)
|
|
109
|
+
if isinstance(obj, dict):
|
|
110
|
+
yield obj
|
|
111
|
+
else:
|
|
112
|
+
yield payload
|
|
113
|
+
except Exception:
|
|
114
|
+
yield payload
|
|
115
|
+
else:
|
|
116
|
+
# Non-data lines: yield as raw text for debugging/visibility
|
|
117
|
+
yield line
|
|
118
|
+
{% else %}
|
|
119
|
+
async with httpx.AsyncClient(base_url=base_url, verify=self.verify) as client:
|
|
120
|
+
response = await client.request(
|
|
121
|
+
"{{ op.method }}",
|
|
122
|
+
httpx.URL(path),
|
|
123
|
+
headers=headers,
|
|
124
|
+
params=query_params,
|
|
125
|
+
{% if op.body_param %}
|
|
126
|
+
json={{ op.body_param }},
|
|
127
|
+
{% endif %}
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
if response.status_code != {{ op.return_type.status_code }}:
|
|
131
|
+
raise HTTPException(
|
|
132
|
+
response.status_code,
|
|
133
|
+
f"{{ op.operation_id }} failed with status code: {response.status_code}",
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
body = None if {{ op.return_type.status_code }} == 204 else response.json()
|
|
137
|
+
|
|
138
|
+
{% if op.return_type.type is none or op.return_type.type.converted_type is none %}
|
|
139
|
+
return None
|
|
140
|
+
{% elif op.return_type.complex_type %}
|
|
141
|
+
{% if op.return_type.list_type is none %}
|
|
142
|
+
return TypeAdapter({{ op.return_type.type.converted_type }}).validate_python(body)
|
|
143
|
+
{% else %}
|
|
144
|
+
return TypeAdapter(list[{{ op.return_type.list_type }}]).validate_python(body)
|
|
145
|
+
{% endif %}
|
|
146
|
+
{% else %}
|
|
147
|
+
return body
|
|
148
|
+
{% endif %}
|
|
149
|
+
{% endif %}
|
|
150
|
+
|
|
151
|
+
{% endfor %}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
class HTTPException(Exception):
|
|
2
|
+
def __init__(self, status_code: int, message: str):
|
|
3
|
+
self.status_code = status_code
|
|
4
|
+
self.message = message
|
|
5
|
+
super().__init__(f"{status_code} {message}")
|
|
6
|
+
|
|
7
|
+
def __str__(self) -> str:
|
|
8
|
+
return f"{self.status_code} {self.message}"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from typing import *
|
|
2
|
+
from pydantic import BaseModel, Field
|
|
3
|
+
{% for property in properties %}
|
|
4
|
+
{% if property.type.import_types is not none %}
|
|
5
|
+
{% for import_type in property.type.import_types %}
|
|
6
|
+
{{ import_type }}
|
|
7
|
+
{% endfor %}
|
|
8
|
+
{% endif %}
|
|
9
|
+
{% endfor %}
|
|
10
|
+
|
|
11
|
+
class {{ schema_name }}(BaseModel):
|
|
12
|
+
"""
|
|
13
|
+
{% if schema.title %}{{ schema.title }}{% else %}{{ schema_name }}{% endif %} model
|
|
14
|
+
{% if schema.description %}
|
|
15
|
+
{{ schema.description }}
|
|
16
|
+
{% endif %}
|
|
17
|
+
{% if parent_comment %}
|
|
18
|
+
{{ parent_comment }}
|
|
19
|
+
{% endif %}
|
|
20
|
+
"""
|
|
21
|
+
{% for property in properties %}
|
|
22
|
+
|
|
23
|
+
{{ property.name | normalize_symbol }} : {{ property.type.converted_type | safe }} = Field(alias="{{ property.name }}"{% if property.default is not none %}, default = {{ property.default }}{% endif %})
|
|
24
|
+
{% endfor %}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from typing import *
|
|
2
|
+
from pydantic import BaseModel, Field
|
|
3
|
+
{% for property in properties %}
|
|
4
|
+
{% if property.type.import_types is not none %}
|
|
5
|
+
{% for import_type in property.type.import_types %}
|
|
6
|
+
{{ import_type }}
|
|
7
|
+
{% endfor %}
|
|
8
|
+
{% endif %}
|
|
9
|
+
{% endfor %}
|
|
10
|
+
|
|
11
|
+
class {{ schema_name }}(BaseModel):
|
|
12
|
+
"""
|
|
13
|
+
{% if schema.title %}{{ schema.title }}{% else %}{{ schema_name }}{% endif %} model
|
|
14
|
+
{% if schema.description %}
|
|
15
|
+
{{ schema.description }}
|
|
16
|
+
{% endif %}
|
|
17
|
+
{% if parent_comment %}
|
|
18
|
+
{{ parent_comment }}
|
|
19
|
+
{% endif %}
|
|
20
|
+
"""
|
|
21
|
+
model_config = {
|
|
22
|
+
"populate_by_name": True,
|
|
23
|
+
"validate_assignment": True
|
|
24
|
+
}
|
|
25
|
+
{% for property in properties %}
|
|
26
|
+
|
|
27
|
+
{{ property.name | normalize_symbol }} : {{ property.type.converted_type | safe }} = Field(validation_alias="{{ property.name }}"{% if property.default is not none %}, default = {{ property.default }}{% endif %})
|
|
28
|
+
{% endfor %}
|
pydantic_openapi_generator/language_converters/python/templates/sync_client_httpx_pydantic_2.jinja2
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
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
|
+
{% if op.body_param %}
|
|
61
|
+
"Content-Type": "application/json",
|
|
62
|
+
{% endif %}
|
|
63
|
+
{% if op.is_sse %}
|
|
64
|
+
"Accept": "text/event-stream",
|
|
65
|
+
{% else %}
|
|
66
|
+
"Accept": "application/json",
|
|
67
|
+
{% endif %}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
_token = self.get_access_token()
|
|
71
|
+
if _token:
|
|
72
|
+
headers["Authorization"] = f"Bearer {_token}"
|
|
73
|
+
|
|
74
|
+
query_params: Dict[str, Any] = {
|
|
75
|
+
{% for qp in op.query_params %}
|
|
76
|
+
{{ qp }},
|
|
77
|
+
{% endfor %}
|
|
78
|
+
}
|
|
79
|
+
query_params = {k: v for (k, v) in query_params.items() if v is not None}
|
|
80
|
+
|
|
81
|
+
{% if op.is_sse %}
|
|
82
|
+
with httpx.Client(base_url=base_url, verify=self.verify) as client:
|
|
83
|
+
with client.stream(
|
|
84
|
+
"{{ op.method }}",
|
|
85
|
+
httpx.URL(path),
|
|
86
|
+
headers=headers,
|
|
87
|
+
params=query_params,
|
|
88
|
+
{% if op.body_param %}
|
|
89
|
+
json={{ op.body_param }},
|
|
90
|
+
{% endif %}
|
|
91
|
+
) as response:
|
|
92
|
+
if response.status_code != {{ op.return_type.status_code }}:
|
|
93
|
+
raise HTTPException(
|
|
94
|
+
response.status_code,
|
|
95
|
+
f"{{ op.operation_id }} failed with status code: {response.status_code}",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
for line in response.iter_lines():
|
|
99
|
+
if not line:
|
|
100
|
+
continue
|
|
101
|
+
if line.startswith("data:"):
|
|
102
|
+
payload = line[len("data:"):].strip()
|
|
103
|
+
if not payload:
|
|
104
|
+
continue
|
|
105
|
+
if payload == "[DONE]":
|
|
106
|
+
break
|
|
107
|
+
try:
|
|
108
|
+
obj = json.loads(payload)
|
|
109
|
+
if isinstance(obj, dict):
|
|
110
|
+
yield obj
|
|
111
|
+
else:
|
|
112
|
+
yield payload
|
|
113
|
+
except Exception:
|
|
114
|
+
yield payload
|
|
115
|
+
else:
|
|
116
|
+
yield line
|
|
117
|
+
{% else %}
|
|
118
|
+
with httpx.Client(base_url=base_url, verify=self.verify) as client:
|
|
119
|
+
response = client.request(
|
|
120
|
+
"{{ op.method }}",
|
|
121
|
+
httpx.URL(path),
|
|
122
|
+
headers=headers,
|
|
123
|
+
params=query_params,
|
|
124
|
+
{% if op.body_param %}
|
|
125
|
+
json={{ op.body_param }},
|
|
126
|
+
{% endif %}
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
if response.status_code != {{ op.return_type.status_code }}:
|
|
130
|
+
raise HTTPException(
|
|
131
|
+
response.status_code,
|
|
132
|
+
f"{{ op.operation_id }} failed with status code: {response.status_code}",
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
body = None if {{ op.return_type.status_code }} == 204 else response.json()
|
|
136
|
+
|
|
137
|
+
{% if op.return_type.type is none or op.return_type.type.converted_type is none %}
|
|
138
|
+
return None
|
|
139
|
+
{% elif op.return_type.complex_type %}
|
|
140
|
+
{% if op.return_type.list_type is none %}
|
|
141
|
+
return TypeAdapter({{ op.return_type.type.converted_type }}).validate_python(body)
|
|
142
|
+
{% else %}
|
|
143
|
+
return TypeAdapter(list[{{ op.return_type.list_type }}]).validate_python(body)
|
|
144
|
+
{% endif %}
|
|
145
|
+
{% else %}
|
|
146
|
+
return body
|
|
147
|
+
{% endif %}
|
|
148
|
+
{% endif %}
|
|
149
|
+
|
|
150
|
+
{% endfor %}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from typing import List, Optional, Union
|
|
2
|
+
|
|
3
|
+
from openapi_pydantic.v3.v3_0 import (
|
|
4
|
+
Operation as Operation30,
|
|
5
|
+
)
|
|
6
|
+
from openapi_pydantic.v3.v3_0 import (
|
|
7
|
+
PathItem as PathItem30,
|
|
8
|
+
)
|
|
9
|
+
from openapi_pydantic.v3.v3_0 import (
|
|
10
|
+
Schema as Schema30,
|
|
11
|
+
)
|
|
12
|
+
from openapi_pydantic.v3.v3_1 import (
|
|
13
|
+
Operation as Operation31,
|
|
14
|
+
)
|
|
15
|
+
from openapi_pydantic.v3.v3_1 import (
|
|
16
|
+
PathItem as PathItem31,
|
|
17
|
+
)
|
|
18
|
+
from openapi_pydantic.v3.v3_1 import (
|
|
19
|
+
Schema as Schema31,
|
|
20
|
+
)
|
|
21
|
+
from pydantic import BaseModel, Field
|
|
22
|
+
|
|
23
|
+
# Type unions for compatibility with both OpenAPI 3.0 and 3.1
|
|
24
|
+
Operation = Union[Operation30, Operation31]
|
|
25
|
+
PathItem = Union[PathItem30, PathItem31]
|
|
26
|
+
Schema = Union[Schema30, Schema31]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class LibraryConfig(BaseModel):
|
|
30
|
+
name: str
|
|
31
|
+
library_name: str
|
|
32
|
+
template_name: str
|
|
33
|
+
include_async: bool
|
|
34
|
+
include_sync: bool
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class TypeConversion(BaseModel):
|
|
38
|
+
original_type: str
|
|
39
|
+
converted_type: str
|
|
40
|
+
import_types: Optional[List[str]] = None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class OpReturnType(BaseModel):
|
|
44
|
+
type: Optional[TypeConversion] = None
|
|
45
|
+
status_code: int
|
|
46
|
+
complex_type: bool = False
|
|
47
|
+
list_type: Optional[str] = None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ServiceOperation(BaseModel):
|
|
51
|
+
params: str
|
|
52
|
+
operation_id: str
|
|
53
|
+
query_params: List[str]
|
|
54
|
+
header_params: List[str]
|
|
55
|
+
return_type: OpReturnType
|
|
56
|
+
operation: Operation
|
|
57
|
+
pathItem: PathItem
|
|
58
|
+
content: str
|
|
59
|
+
async_client: Optional[bool] = False
|
|
60
|
+
tag: Optional[str] = None
|
|
61
|
+
path_name: str
|
|
62
|
+
body_param: Optional[str] = None
|
|
63
|
+
method: str
|
|
64
|
+
is_sse: bool = False
|
|
65
|
+
use_orjson: bool = False
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class Property(BaseModel):
|
|
69
|
+
name: str
|
|
70
|
+
type: TypeConversion
|
|
71
|
+
required: bool
|
|
72
|
+
default: Optional[str]
|
|
73
|
+
import_type: Optional[List[str]] = None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class Model(BaseModel):
|
|
77
|
+
file_name: str
|
|
78
|
+
content: str
|
|
79
|
+
openapi_object: Optional[Schema] = None
|
|
80
|
+
properties: List[Property] = []
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class ConversionResult(BaseModel):
|
|
84
|
+
models: List[Model] = Field(default_factory=list)
|
|
85
|
+
clients: List[Model] = Field(default_factory=list)
|
|
86
|
+
exceptions: List[Model] = Field(default_factory=list)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OpenAPI parsers for different specification versions.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .openapi_30 import generate_code_3_0, parse_openapi_3_0
|
|
6
|
+
from .openapi_31 import generate_code_3_1, parse_openapi_3_1
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"parse_openapi_3_0",
|
|
10
|
+
"generate_code_3_0",
|
|
11
|
+
"parse_openapi_3_1",
|
|
12
|
+
"generate_code_3_1",
|
|
13
|
+
]
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OpenAPI 3.0 specific parsing and generation.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from openapi_pydantic.v3.v3_0 import OpenAPI
|
|
8
|
+
|
|
9
|
+
from pydantic_openapi_generator.common import HTTPLibrary, PydanticVersion
|
|
10
|
+
from pydantic_openapi_generator.language_converters.python.generator import (
|
|
11
|
+
generator as base_generator,
|
|
12
|
+
)
|
|
13
|
+
from pydantic_openapi_generator.models import ConversionResult
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def parse_openapi_3_0(spec_data: dict) -> OpenAPI:
|
|
17
|
+
"""
|
|
18
|
+
Parse OpenAPI 3.0 specification data.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
spec_data: Dictionary containing OpenAPI 3.0 specification
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
OpenAPI: Parsed OpenAPI 3.0 specification object
|
|
25
|
+
|
|
26
|
+
Raises:
|
|
27
|
+
ValidationError: If the specification is invalid
|
|
28
|
+
"""
|
|
29
|
+
return OpenAPI(**spec_data) # type: ignore - pydantic issue with extra fields
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def generate_code_3_0(
|
|
33
|
+
data: OpenAPI,
|
|
34
|
+
library: HTTPLibrary = HTTPLibrary.httpx,
|
|
35
|
+
env_token_name: Optional[str] = None,
|
|
36
|
+
use_orjson: bool = False,
|
|
37
|
+
custom_template_path: Optional[str] = None,
|
|
38
|
+
pydantic_version: PydanticVersion = PydanticVersion.V2,
|
|
39
|
+
) -> ConversionResult:
|
|
40
|
+
"""
|
|
41
|
+
Generate Python code from OpenAPI 3.0 specification.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
data: OpenAPI 3.0 specification object
|
|
45
|
+
library: HTTP library to use
|
|
46
|
+
env_token_name: Environment variable name for token
|
|
47
|
+
use_orjson: Whether to use orjson for serialization
|
|
48
|
+
custom_template_path: Custom template path
|
|
49
|
+
pydantic_version: Pydantic version to use
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
ConversionResult: Generated code and metadata
|
|
53
|
+
"""
|
|
54
|
+
from pydantic_openapi_generator.common import library_config_dict
|
|
55
|
+
|
|
56
|
+
library_config = library_config_dict[library]
|
|
57
|
+
|
|
58
|
+
return base_generator(
|
|
59
|
+
data=data,
|
|
60
|
+
library_config=library_config,
|
|
61
|
+
env_token_name=env_token_name,
|
|
62
|
+
use_orjson=use_orjson,
|
|
63
|
+
custom_template_path=custom_template_path,
|
|
64
|
+
pydantic_version=pydantic_version,
|
|
65
|
+
)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OpenAPI 3.1 specific parsing and generation.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from openapi_pydantic.v3.v3_1 import OpenAPI
|
|
8
|
+
|
|
9
|
+
from pydantic_openapi_generator.common import HTTPLibrary, PydanticVersion
|
|
10
|
+
from pydantic_openapi_generator.language_converters.python.generator import (
|
|
11
|
+
generator as base_generator,
|
|
12
|
+
)
|
|
13
|
+
from pydantic_openapi_generator.models import ConversionResult
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def parse_openapi_3_1(spec_data: dict) -> OpenAPI:
|
|
17
|
+
"""
|
|
18
|
+
Parse OpenAPI 3.1 specification data.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
spec_data: Dictionary containing OpenAPI 3.1 specification
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
OpenAPI: Parsed OpenAPI 3.1 specification object
|
|
25
|
+
|
|
26
|
+
Raises:
|
|
27
|
+
ValidationError: If the specification is invalid
|
|
28
|
+
"""
|
|
29
|
+
return OpenAPI(**spec_data) # type: ignore - pydantic issue with extra fields
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def generate_code_3_1(
|
|
33
|
+
data: OpenAPI,
|
|
34
|
+
library: HTTPLibrary = HTTPLibrary.httpx,
|
|
35
|
+
env_token_name: Optional[str] = None,
|
|
36
|
+
use_orjson: bool = False,
|
|
37
|
+
custom_template_path: Optional[str] = None,
|
|
38
|
+
pydantic_version: PydanticVersion = PydanticVersion.V2,
|
|
39
|
+
) -> ConversionResult:
|
|
40
|
+
"""
|
|
41
|
+
Generate Python code from OpenAPI 3.1 specification.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
data: OpenAPI 3.1 specification object
|
|
45
|
+
library: HTTP library to use
|
|
46
|
+
env_token_name: Environment variable name for token
|
|
47
|
+
use_orjson: Whether to use orjson for serialization
|
|
48
|
+
custom_template_path: Custom template path
|
|
49
|
+
pydantic_version: Pydantic version to use
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
ConversionResult: Generated code and metadata
|
|
53
|
+
"""
|
|
54
|
+
from pydantic_openapi_generator.common import library_config_dict
|
|
55
|
+
|
|
56
|
+
library_config = library_config_dict[library]
|
|
57
|
+
|
|
58
|
+
return base_generator(
|
|
59
|
+
data=data,
|
|
60
|
+
library_config=library_config,
|
|
61
|
+
env_token_name=env_token_name,
|
|
62
|
+
use_orjson=use_orjson,
|
|
63
|
+
custom_template_path=custom_template_path,
|
|
64
|
+
pydantic_version=pydantic_version,
|
|
65
|
+
)
|
|
File without changes
|