pydantic-openapi-generator 2.3.0__tar.gz → 2.4.0__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.
- {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/PKG-INFO +2 -1
- {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/pyproject.toml +2 -1
- {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/__main__.py +8 -0
- pydantic_openapi_generator-2.4.0/src/pydantic_openapi_generator/config/__init__.py +5 -0
- pydantic_openapi_generator-2.4.0/src/pydantic_openapi_generator/config/base_parameter.py +73 -0
- pydantic_openapi_generator-2.4.0/src/pydantic_openapi_generator/config/class_var_parameter.py +60 -0
- pydantic_openapi_generator-2.4.0/src/pydantic_openapi_generator/config/function_parameter.py +47 -0
- pydantic_openapi_generator-2.4.0/src/pydantic_openapi_generator/config/generator_config.py +33 -0
- pydantic_openapi_generator-2.4.0/src/pydantic_openapi_generator/config/kwarg_parameter.py +56 -0
- pydantic_openapi_generator-2.4.0/src/pydantic_openapi_generator/config/loader.py +17 -0
- pydantic_openapi_generator-2.4.0/src/pydantic_openapi_generator/config/parameter_source.py +7 -0
- pydantic_openapi_generator-2.4.0/src/pydantic_openapi_generator/config/parameter_union.py +16 -0
- pydantic_openapi_generator-2.4.0/src/pydantic_openapi_generator/config/utils.py +56 -0
- {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/generate_data.py +7 -0
- {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/language_converters/python/client_generator.py +158 -6
- {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/language_converters/python/generator.py +5 -1
- {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/language_converters/python/templates/async_client_httpx_pydantic_2.jinja2 +22 -3
- {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/language_converters/python/templates/sync_client_httpx_pydantic_2.jinja2 +22 -3
- {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/models.py +22 -2
- {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/parsers/openapi_30.py +5 -0
- {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/parsers/openapi_31.py +5 -0
- {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/.gitignore +0 -0
- {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/LICENSE +0 -0
- {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/README.md +0 -0
- {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/__init__.py +0 -0
- {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/common.py +0 -0
- {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/language_converters/__init__.py +0 -0
- {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/language_converters/python/__init__.py +0 -0
- {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/language_converters/python/common.py +0 -0
- {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/language_converters/python/exception_generator.py +0 -0
- {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/language_converters/python/jinja_config.py +0 -0
- {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/language_converters/python/model_generator.py +0 -0
- {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/language_converters/python/templates/alias_union.jinja2 +0 -0
- {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/language_converters/python/templates/discriminator_enum.jinja2 +0 -0
- {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/language_converters/python/templates/enum.jinja2 +0 -0
- {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/language_converters/python/templates/http_exception.jinja2 +0 -0
- {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/language_converters/python/templates/models.jinja2 +0 -0
- {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/language_converters/python/templates/models_pydantic_2.jinja2 +0 -0
- {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/parsers/__init__.py +0 -0
- {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/py.typed +0 -0
- {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/version_detector.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pydantic-openapi-generator
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: Openapi Python Generator
|
|
5
5
|
Project-URL: Homepage, https://github.com/mattcoulter7/pydantic-openapi-generator
|
|
6
6
|
Project-URL: Repository, https://github.com/mattcoulter7/pydantic-openapi-generator
|
|
@@ -20,6 +20,7 @@ Requires-Dist: isort>=5.10.1
|
|
|
20
20
|
Requires-Dist: jinja2<4,>=3.1.2
|
|
21
21
|
Requires-Dist: openapi-pydantic<0.6,>=0.5.1
|
|
22
22
|
Requires-Dist: orjson<4,>=3.9.15
|
|
23
|
+
Requires-Dist: pydantic-di>=0.3.1
|
|
23
24
|
Requires-Dist: pydantic<3,>=2.10.2
|
|
24
25
|
Requires-Dist: pyyaml<7,>=6.0.2
|
|
25
26
|
Description-Content-Type: text/markdown
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pydantic-openapi-generator"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.4.0"
|
|
4
4
|
description = "Openapi Python Generator"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Marco Müllner", email = "muellnermarco@gmail.com" },
|
|
@@ -27,6 +27,7 @@ dependencies = [
|
|
|
27
27
|
"openapi-pydantic>=0.5.1,<0.6",
|
|
28
28
|
"pyyaml>=6.0.2,<7",
|
|
29
29
|
"importlib-metadata>=8.7.0,<9",
|
|
30
|
+
"pydantic-di>=0.3.1",
|
|
30
31
|
]
|
|
31
32
|
|
|
32
33
|
[project.urls]
|
|
@@ -52,6 +52,12 @@ from pydantic_openapi_generator.generate_data import generate_data
|
|
|
52
52
|
show_default=True,
|
|
53
53
|
help="Option to choose which auto formatter is applied.",
|
|
54
54
|
)
|
|
55
|
+
@click.option(
|
|
56
|
+
"--config-path",
|
|
57
|
+
type=click.Path(exists=True, dir_okay=False),
|
|
58
|
+
default=None,
|
|
59
|
+
help="Optional YAML configuration for generated client parameters.",
|
|
60
|
+
)
|
|
55
61
|
@click.version_option(version=__version__)
|
|
56
62
|
def main(
|
|
57
63
|
source: str,
|
|
@@ -62,6 +68,7 @@ def main(
|
|
|
62
68
|
custom_template_path: Optional[str] = None,
|
|
63
69
|
pydantic_version: PydanticVersion = PydanticVersion.V2,
|
|
64
70
|
formatter: Formatter = Formatter.BLACK,
|
|
71
|
+
config_path: Optional[str] = None,
|
|
65
72
|
) -> None:
|
|
66
73
|
"""
|
|
67
74
|
Generate Python code from an OpenAPI 3.0+ specification.
|
|
@@ -78,6 +85,7 @@ def main(
|
|
|
78
85
|
custom_template_path,
|
|
79
86
|
pydantic_version,
|
|
80
87
|
formatter,
|
|
88
|
+
config_path,
|
|
81
89
|
)
|
|
82
90
|
|
|
83
91
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, ConfigDict
|
|
5
|
+
|
|
6
|
+
from pydantic_openapi_generator.config.utils import Parameter, parameter_base_type_hint
|
|
7
|
+
from pydantic_openapi_generator.language_converters.python import common
|
|
8
|
+
from pydantic_openapi_generator.models import GeneratedParameter
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BaseArgumentConfiguration(BaseModel, ABC):
|
|
12
|
+
model_config = ConfigDict(extra="forbid")
|
|
13
|
+
|
|
14
|
+
name: str
|
|
15
|
+
code_name: Optional[str] = None
|
|
16
|
+
|
|
17
|
+
def resolve_parameter(self, param: Parameter) -> GeneratedParameter:
|
|
18
|
+
code_name = self.resolved_code_name(param)
|
|
19
|
+
type_hint, default = self.method_type_and_default(param)
|
|
20
|
+
|
|
21
|
+
return GeneratedParameter(
|
|
22
|
+
wire_name=param.name,
|
|
23
|
+
code_name=code_name,
|
|
24
|
+
location=param.param_in, # type: ignore[arg-type]
|
|
25
|
+
type_hint=type_hint,
|
|
26
|
+
base_type_hint=parameter_base_type_hint(param),
|
|
27
|
+
required=param.required,
|
|
28
|
+
default=default,
|
|
29
|
+
source=self.type, # type: ignore[attr-defined]
|
|
30
|
+
is_secret=self.is_secret_parameter(),
|
|
31
|
+
field_type_hint=self.field_type_hint(param),
|
|
32
|
+
field_default=self.field_default(param),
|
|
33
|
+
getter_name=self.getter_name(code_name),
|
|
34
|
+
local_name=self.local_name(code_name),
|
|
35
|
+
value_expression=self.value_expression(code_name),
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
def resolved_code_name(self, param: Parameter) -> str:
|
|
39
|
+
if self.code_name:
|
|
40
|
+
return common.normalize_symbol(self.code_name)
|
|
41
|
+
return self.default_code_name(param.name)
|
|
42
|
+
|
|
43
|
+
@abstractmethod
|
|
44
|
+
def default_code_name(self, param_name: str) -> str:
|
|
45
|
+
raise NotImplementedError
|
|
46
|
+
|
|
47
|
+
@abstractmethod
|
|
48
|
+
def method_type_and_default(self, param: Parameter) -> tuple[str, Optional[str]]:
|
|
49
|
+
raise NotImplementedError
|
|
50
|
+
|
|
51
|
+
@abstractmethod
|
|
52
|
+
def field_type_hint(self, param: Parameter) -> Optional[str]:
|
|
53
|
+
raise NotImplementedError
|
|
54
|
+
|
|
55
|
+
@abstractmethod
|
|
56
|
+
def field_default(self, param: Parameter) -> Optional[str]:
|
|
57
|
+
raise NotImplementedError
|
|
58
|
+
|
|
59
|
+
@abstractmethod
|
|
60
|
+
def getter_name(self, code_name: str) -> Optional[str]:
|
|
61
|
+
raise NotImplementedError
|
|
62
|
+
|
|
63
|
+
@abstractmethod
|
|
64
|
+
def local_name(self, code_name: str) -> Optional[str]:
|
|
65
|
+
raise NotImplementedError
|
|
66
|
+
|
|
67
|
+
@abstractmethod
|
|
68
|
+
def value_expression(self, code_name: str) -> str:
|
|
69
|
+
raise NotImplementedError
|
|
70
|
+
|
|
71
|
+
@abstractmethod
|
|
72
|
+
def is_secret_parameter(self) -> bool:
|
|
73
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from typing import Literal, Optional, override
|
|
2
|
+
|
|
3
|
+
from pydantic_openapi_generator.config.base_parameter import (
|
|
4
|
+
BaseArgumentConfiguration,
|
|
5
|
+
)
|
|
6
|
+
from pydantic_openapi_generator.config.parameter_source import ParameterSource
|
|
7
|
+
from pydantic_openapi_generator.config.utils import (
|
|
8
|
+
Parameter,
|
|
9
|
+
lower_code_name,
|
|
10
|
+
optional_override_type_and_default,
|
|
11
|
+
optional_type,
|
|
12
|
+
parameter_base_type_hint,
|
|
13
|
+
parameter_default,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ClassVarArgumentConfiguration(BaseArgumentConfiguration):
|
|
18
|
+
type: Literal[ParameterSource.CLASS_VAR] = ParameterSource.CLASS_VAR
|
|
19
|
+
is_secret: bool = False
|
|
20
|
+
|
|
21
|
+
@override
|
|
22
|
+
def default_code_name(self, param_name: str) -> str:
|
|
23
|
+
return lower_code_name(param_name)
|
|
24
|
+
|
|
25
|
+
@override
|
|
26
|
+
def method_type_and_default(self, param: Parameter) -> tuple[str, Optional[str]]:
|
|
27
|
+
return optional_override_type_and_default(param)
|
|
28
|
+
|
|
29
|
+
@override
|
|
30
|
+
def field_type_hint(self, param: Parameter) -> str:
|
|
31
|
+
default = parameter_default(param)
|
|
32
|
+
base_type = "SecretStr" if self.is_secret else parameter_base_type_hint(param)
|
|
33
|
+
|
|
34
|
+
if param.required or default is not None:
|
|
35
|
+
return base_type
|
|
36
|
+
|
|
37
|
+
return optional_type(base_type)
|
|
38
|
+
|
|
39
|
+
@override
|
|
40
|
+
def field_default(self, param: Parameter) -> Optional[str]:
|
|
41
|
+
default = parameter_default(param)
|
|
42
|
+
if param.required:
|
|
43
|
+
return None
|
|
44
|
+
return default if default is not None else "None"
|
|
45
|
+
|
|
46
|
+
@override
|
|
47
|
+
def getter_name(self, code_name: str) -> Optional[str]:
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
@override
|
|
51
|
+
def local_name(self, code_name: str) -> str:
|
|
52
|
+
return f"_{code_name}"
|
|
53
|
+
|
|
54
|
+
@override
|
|
55
|
+
def value_expression(self, code_name: str) -> str:
|
|
56
|
+
return self.local_name(code_name)
|
|
57
|
+
|
|
58
|
+
@override
|
|
59
|
+
def is_secret_parameter(self) -> bool:
|
|
60
|
+
return self.is_secret
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from typing import Literal, Optional, override
|
|
2
|
+
|
|
3
|
+
from pydantic_openapi_generator.config.base_parameter import (
|
|
4
|
+
BaseArgumentConfiguration,
|
|
5
|
+
)
|
|
6
|
+
from pydantic_openapi_generator.config.parameter_source import ParameterSource
|
|
7
|
+
from pydantic_openapi_generator.config.utils import (
|
|
8
|
+
Parameter,
|
|
9
|
+
lower_code_name,
|
|
10
|
+
optional_override_type_and_default,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class FunctionArgumentConfiguration(BaseArgumentConfiguration):
|
|
15
|
+
type: Literal[ParameterSource.FUNCTION] = ParameterSource.FUNCTION
|
|
16
|
+
|
|
17
|
+
@override
|
|
18
|
+
def default_code_name(self, param_name: str) -> str:
|
|
19
|
+
return lower_code_name(param_name)
|
|
20
|
+
|
|
21
|
+
@override
|
|
22
|
+
def method_type_and_default(self, param: Parameter) -> tuple[str, Optional[str]]:
|
|
23
|
+
return optional_override_type_and_default(param)
|
|
24
|
+
|
|
25
|
+
@override
|
|
26
|
+
def field_type_hint(self, param: Parameter) -> Optional[str]:
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
@override
|
|
30
|
+
def field_default(self, param: Parameter) -> Optional[str]:
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
@override
|
|
34
|
+
def getter_name(self, code_name: str) -> str:
|
|
35
|
+
return f"get_{code_name}"
|
|
36
|
+
|
|
37
|
+
@override
|
|
38
|
+
def local_name(self, code_name: str) -> str:
|
|
39
|
+
return f"_{code_name}"
|
|
40
|
+
|
|
41
|
+
@override
|
|
42
|
+
def value_expression(self, code_name: str) -> str:
|
|
43
|
+
return self.local_name(code_name)
|
|
44
|
+
|
|
45
|
+
@override
|
|
46
|
+
def is_secret_parameter(self) -> bool:
|
|
47
|
+
return False
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
2
|
+
|
|
3
|
+
from pydantic_openapi_generator.config.base_parameter import BaseArgumentConfiguration
|
|
4
|
+
from pydantic_openapi_generator.config.kwarg_parameter import KwargArgumentConfiguration
|
|
5
|
+
from pydantic_openapi_generator.config.parameter_union import ArgumentConfiguration
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PydanticOpenAPIGeneratorConfig(BaseModel):
|
|
9
|
+
model_config = ConfigDict(extra="forbid")
|
|
10
|
+
|
|
11
|
+
parameters: list[ArgumentConfiguration] = Field(default_factory=list)
|
|
12
|
+
|
|
13
|
+
def parameter_configuration_for(self, parameter_name: str) -> BaseArgumentConfiguration:
|
|
14
|
+
configured = self.parameters_by_name().get(parameter_name)
|
|
15
|
+
return configured if configured is not None else KwargArgumentConfiguration(name=parameter_name)
|
|
16
|
+
|
|
17
|
+
def parameters_by_name(self) -> dict[str, ArgumentConfiguration]:
|
|
18
|
+
return {parameter.name: parameter for parameter in self.parameters}
|
|
19
|
+
|
|
20
|
+
@model_validator(mode="after")
|
|
21
|
+
def validate_unique_parameter_names(self) -> "PydanticOpenAPIGeneratorConfig":
|
|
22
|
+
names: set[str] = set()
|
|
23
|
+
duplicates: set[str] = set()
|
|
24
|
+
for parameter in self.parameters:
|
|
25
|
+
if parameter.name in names:
|
|
26
|
+
duplicates.add(parameter.name)
|
|
27
|
+
names.add(parameter.name)
|
|
28
|
+
|
|
29
|
+
if duplicates:
|
|
30
|
+
duplicate_list = ", ".join(sorted(duplicates))
|
|
31
|
+
raise ValueError(f"Duplicate parameter configuration for: {duplicate_list}")
|
|
32
|
+
|
|
33
|
+
return self
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from typing import Literal, Optional, override
|
|
2
|
+
|
|
3
|
+
from pydantic_openapi_generator.config.base_parameter import BaseArgumentConfiguration
|
|
4
|
+
from pydantic_openapi_generator.config.parameter_source import ParameterSource
|
|
5
|
+
from pydantic_openapi_generator.config.utils import (
|
|
6
|
+
Parameter,
|
|
7
|
+
optional_type,
|
|
8
|
+
parameter_base_type_hint,
|
|
9
|
+
parameter_default,
|
|
10
|
+
)
|
|
11
|
+
from pydantic_openapi_generator.language_converters.python import common
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class KwargArgumentConfiguration(BaseArgumentConfiguration):
|
|
15
|
+
type: Literal[ParameterSource.KWARG] = ParameterSource.KWARG
|
|
16
|
+
|
|
17
|
+
@override
|
|
18
|
+
def default_code_name(self, param_name: str) -> str:
|
|
19
|
+
return common.normalize_symbol(param_name)
|
|
20
|
+
|
|
21
|
+
@override
|
|
22
|
+
def method_type_and_default(self, param: Parameter) -> tuple[str, Optional[str]]:
|
|
23
|
+
base_type = parameter_base_type_hint(param)
|
|
24
|
+
default = parameter_default(param)
|
|
25
|
+
|
|
26
|
+
if param.required:
|
|
27
|
+
return base_type, None
|
|
28
|
+
|
|
29
|
+
if default is not None:
|
|
30
|
+
return base_type, default
|
|
31
|
+
|
|
32
|
+
return optional_type(base_type), "None"
|
|
33
|
+
|
|
34
|
+
@override
|
|
35
|
+
def field_type_hint(self, param: Parameter) -> Optional[str]:
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
@override
|
|
39
|
+
def field_default(self, param: Parameter) -> Optional[str]:
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
@override
|
|
43
|
+
def getter_name(self, code_name: str) -> Optional[str]:
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
@override
|
|
47
|
+
def local_name(self, code_name: str) -> Optional[str]:
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
@override
|
|
51
|
+
def value_expression(self, code_name: str) -> str:
|
|
52
|
+
return code_name
|
|
53
|
+
|
|
54
|
+
@override
|
|
55
|
+
def is_secret_parameter(self) -> bool:
|
|
56
|
+
return False
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from pydantic_di.loaders import ObjectLoaderYaml
|
|
5
|
+
|
|
6
|
+
from pydantic_openapi_generator.config.generator_config import (
|
|
7
|
+
PydanticOpenAPIGeneratorConfig,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def load_config(config_path: Optional[str | Path]) -> PydanticOpenAPIGeneratorConfig:
|
|
12
|
+
if config_path is None:
|
|
13
|
+
return PydanticOpenAPIGeneratorConfig()
|
|
14
|
+
|
|
15
|
+
return ObjectLoaderYaml[PydanticOpenAPIGeneratorConfig](
|
|
16
|
+
path=Path(config_path),
|
|
17
|
+
).load()
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from typing import Annotated
|
|
2
|
+
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
|
|
5
|
+
from pydantic_openapi_generator.config.class_var_parameter import (
|
|
6
|
+
ClassVarArgumentConfiguration,
|
|
7
|
+
)
|
|
8
|
+
from pydantic_openapi_generator.config.function_parameter import (
|
|
9
|
+
FunctionArgumentConfiguration,
|
|
10
|
+
)
|
|
11
|
+
from pydantic_openapi_generator.config.kwarg_parameter import KwargArgumentConfiguration
|
|
12
|
+
|
|
13
|
+
ArgumentConfiguration = Annotated[
|
|
14
|
+
KwargArgumentConfiguration | ClassVarArgumentConfiguration | FunctionArgumentConfiguration,
|
|
15
|
+
Field(discriminator="type"),
|
|
16
|
+
]
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Any, Optional, Union
|
|
3
|
+
|
|
4
|
+
from openapi_pydantic.v3.v3_0 import Reference as Reference30
|
|
5
|
+
from openapi_pydantic.v3.v3_0 import Schema as Schema30
|
|
6
|
+
from openapi_pydantic.v3.v3_0.parameter import Parameter as Parameter30
|
|
7
|
+
from openapi_pydantic.v3.v3_1 import Reference as Reference31
|
|
8
|
+
from openapi_pydantic.v3.v3_1 import Schema as Schema31
|
|
9
|
+
from openapi_pydantic.v3.v3_1.parameter import Parameter as Parameter31
|
|
10
|
+
|
|
11
|
+
from pydantic_openapi_generator.language_converters.python import common
|
|
12
|
+
from pydantic_openapi_generator.language_converters.python.model_generator import (
|
|
13
|
+
type_converter,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
Parameter = Union[Parameter30, Parameter31]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def lower_code_name(value: str) -> str:
|
|
20
|
+
normalized = common.normalize_symbol(value)
|
|
21
|
+
parts = re.findall(r"[A-Z]+(?=[A-Z][a-z0-9]|$)|[A-Z]?[a-z0-9]+", normalized)
|
|
22
|
+
return "_".join(part.lower() for part in parts) or normalized.lower()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def literal_default(schema: Any) -> Optional[str]:
|
|
26
|
+
default = getattr(schema, "default", None)
|
|
27
|
+
return f"{default!r}" if default is not None else None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def optional_type(type_hint: str) -> str:
|
|
31
|
+
if type_hint.startswith("Optional[") and type_hint.endswith("]"):
|
|
32
|
+
return type_hint
|
|
33
|
+
return f"Optional[{type_hint}]"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def parameter_type_hint(param: Parameter, required: bool) -> str:
|
|
37
|
+
if isinstance(param.param_schema, (Schema30, Schema31)):
|
|
38
|
+
return type_converter(param.param_schema, required).converted_type
|
|
39
|
+
if isinstance(param.param_schema, (Reference30, Reference31)):
|
|
40
|
+
model_name = common.normalize_symbol(param.param_schema.ref.split("/")[-1])
|
|
41
|
+
return model_name if required else f"Optional[{model_name}]"
|
|
42
|
+
return "Any"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def parameter_base_type_hint(param: Parameter) -> str:
|
|
46
|
+
return parameter_type_hint(param, True)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def parameter_default(param: Parameter) -> Optional[str]:
|
|
50
|
+
if isinstance(param.param_schema, (Schema30, Schema31)):
|
|
51
|
+
return literal_default(param.param_schema)
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def optional_override_type_and_default(param: Parameter) -> tuple[str, Optional[str]]:
|
|
56
|
+
return optional_type(parameter_base_type_hint(param)), "None"
|
|
@@ -12,6 +12,8 @@ from httpx import ConnectError, ConnectTimeout
|
|
|
12
12
|
from pydantic import ValidationError
|
|
13
13
|
|
|
14
14
|
from .common import FormatOptions, Formatter, HTTPLibrary, PydanticVersion
|
|
15
|
+
from .config.generator_config import PydanticOpenAPIGeneratorConfig
|
|
16
|
+
from .config.loader import load_config
|
|
15
17
|
from .models import ConversionResult
|
|
16
18
|
from .parsers import (
|
|
17
19
|
generate_code_3_0,
|
|
@@ -203,11 +205,14 @@ def generate_data(
|
|
|
203
205
|
custom_template_path: Optional[str] = None,
|
|
204
206
|
pydantic_version: PydanticVersion = PydanticVersion.V2,
|
|
205
207
|
formatter: Formatter = Formatter.BLACK,
|
|
208
|
+
config_path: Optional[Union[str, Path]] = None,
|
|
209
|
+
config: Optional[PydanticOpenAPIGeneratorConfig] = None,
|
|
206
210
|
) -> None:
|
|
207
211
|
"""
|
|
208
212
|
Generate Python code from an OpenAPI 3.0+ specification.
|
|
209
213
|
"""
|
|
210
214
|
openapi_obj, version = get_open_api(source)
|
|
215
|
+
loaded_config = config if config is not None else load_config(config_path)
|
|
211
216
|
click.echo(f"Generating data from {source} (OpenAPI {version})")
|
|
212
217
|
|
|
213
218
|
# Use version-specific generator
|
|
@@ -219,6 +224,7 @@ def generate_data(
|
|
|
219
224
|
use_orjson,
|
|
220
225
|
custom_template_path,
|
|
221
226
|
pydantic_version,
|
|
227
|
+
loaded_config,
|
|
222
228
|
)
|
|
223
229
|
elif version == "3.1":
|
|
224
230
|
result = generate_code_3_1(
|
|
@@ -228,6 +234,7 @@ def generate_data(
|
|
|
228
234
|
use_orjson,
|
|
229
235
|
custom_template_path,
|
|
230
236
|
pydantic_version,
|
|
237
|
+
loaded_config,
|
|
231
238
|
)
|
|
232
239
|
else:
|
|
233
240
|
raise ValueError(f"Unsupported OpenAPI version: {version}")
|
|
@@ -39,6 +39,10 @@ from openapi_pydantic.v3.v3_1 import (
|
|
|
39
39
|
from openapi_pydantic.v3.v3_1.parameter import Parameter as Parameter31
|
|
40
40
|
|
|
41
41
|
from pydantic_openapi_generator.common import PydanticVersion
|
|
42
|
+
from pydantic_openapi_generator.config.generator_config import (
|
|
43
|
+
PydanticOpenAPIGeneratorConfig,
|
|
44
|
+
)
|
|
45
|
+
from pydantic_openapi_generator.config.parameter_source import ParameterSource
|
|
42
46
|
from pydantic_openapi_generator.language_converters.python import common
|
|
43
47
|
from pydantic_openapi_generator.language_converters.python.jinja_config import (
|
|
44
48
|
ASYNC_CLIENT_HTTPX_TEMPLATE_PYDANTIC_V2,
|
|
@@ -49,6 +53,7 @@ from pydantic_openapi_generator.language_converters.python.model_generator impor
|
|
|
49
53
|
type_converter,
|
|
50
54
|
)
|
|
51
55
|
from pydantic_openapi_generator.models import (
|
|
56
|
+
GeneratedParameter,
|
|
52
57
|
LibraryConfig,
|
|
53
58
|
Model,
|
|
54
59
|
OpReturnType,
|
|
@@ -58,6 +63,15 @@ from pydantic_openapi_generator.models import (
|
|
|
58
63
|
TypeConversion,
|
|
59
64
|
)
|
|
60
65
|
|
|
66
|
+
RESERVED_CLIENT_MEMBER_NAMES = {
|
|
67
|
+
"base_url",
|
|
68
|
+
"verify",
|
|
69
|
+
"access_token",
|
|
70
|
+
"get_access_token",
|
|
71
|
+
"set_access_token",
|
|
72
|
+
"model_config",
|
|
73
|
+
}
|
|
74
|
+
|
|
61
75
|
|
|
62
76
|
# Helper functions for isinstance checks across OpenAPI versions
|
|
63
77
|
def is_response_type(obj) -> bool:
|
|
@@ -222,6 +236,112 @@ def generate_body_param(operation: Operation) -> Union[str, None]:
|
|
|
222
236
|
return None if request_body is None else request_body.expression
|
|
223
237
|
|
|
224
238
|
|
|
239
|
+
def _resolve_parameter(
|
|
240
|
+
param: Union[Parameter30, Parameter31],
|
|
241
|
+
config: PydanticOpenAPIGeneratorConfig,
|
|
242
|
+
) -> GeneratedParameter:
|
|
243
|
+
return config.parameter_configuration_for(param.name).resolve_parameter(param)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _method_signature(parameters: List[GeneratedParameter], body_param: Optional[str]) -> str:
|
|
247
|
+
required_params: List[str] = []
|
|
248
|
+
default_params: List[str] = []
|
|
249
|
+
|
|
250
|
+
for parameter in parameters:
|
|
251
|
+
rendered = f"{parameter.code_name}: {parameter.type_hint}"
|
|
252
|
+
if parameter.default is None:
|
|
253
|
+
required_params.append(rendered)
|
|
254
|
+
else:
|
|
255
|
+
default_params.append(f"{rendered} = {parameter.default}")
|
|
256
|
+
|
|
257
|
+
if body_param is not None:
|
|
258
|
+
required_params.append(body_param)
|
|
259
|
+
|
|
260
|
+
all_params = required_params + default_params
|
|
261
|
+
return ", ".join(all_params) + (", " if all_params else "")
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _body_signature_param(operation: Operation) -> Optional[str]:
|
|
265
|
+
if operation.requestBody is None or is_reference_type(operation.requestBody):
|
|
266
|
+
if isinstance(operation.requestBody, (Reference, Reference30, Reference31)):
|
|
267
|
+
return f"data : {operation.requestBody.ref.split('/')[-1]}" # type: ignore
|
|
268
|
+
return None
|
|
269
|
+
|
|
270
|
+
rb_content = getattr(operation.requestBody, "content", None)
|
|
271
|
+
operation_request_body_types = [
|
|
272
|
+
"application/json",
|
|
273
|
+
"text/plain",
|
|
274
|
+
"multipart/form-data",
|
|
275
|
+
"application/x-www-form-urlencoded",
|
|
276
|
+
"application/octet-stream",
|
|
277
|
+
]
|
|
278
|
+
if not isinstance(rb_content, dict):
|
|
279
|
+
return None
|
|
280
|
+
content_type = next((i for i in operation_request_body_types if rb_content.get(i)), None)
|
|
281
|
+
if content_type is None:
|
|
282
|
+
return None
|
|
283
|
+
content = rb_content.get(content_type)
|
|
284
|
+
if content is None or not hasattr(content, "media_type_schema"):
|
|
285
|
+
return None
|
|
286
|
+
mts = getattr(content, "media_type_schema", None)
|
|
287
|
+
if isinstance(mts, (Reference, Reference30, Reference31)):
|
|
288
|
+
return f"data : {mts.ref.split('/')[-1]}" # type: ignore
|
|
289
|
+
if isinstance(mts, (Schema, Schema30, Schema31)):
|
|
290
|
+
return f"data : {type_converter(mts, True).converted_type}" # type: ignore
|
|
291
|
+
return None
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _parameter_dict_items(parameters: List[GeneratedParameter]) -> List[str]:
|
|
295
|
+
return [f"{p.wire_name!r}: {p.value_expression}" for p in parameters]
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _resolved_path_name(path_name: str, path_params: List[GeneratedParameter]) -> str:
|
|
299
|
+
resolved = path_name
|
|
300
|
+
for parameter in path_params:
|
|
301
|
+
resolved = re.sub(
|
|
302
|
+
r"\{" + re.escape(parameter.wire_name) + r"\}",
|
|
303
|
+
"{" + (parameter.value_expression or parameter.code_name) + "}",
|
|
304
|
+
resolved,
|
|
305
|
+
)
|
|
306
|
+
return clean_up_path_name(resolved)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def _collect_client_parameters(
|
|
310
|
+
operations: List[ServiceOperation],
|
|
311
|
+
source: ParameterSource,
|
|
312
|
+
) -> List[GeneratedParameter]:
|
|
313
|
+
collected: Dict[str, GeneratedParameter] = {}
|
|
314
|
+
wire_by_code_name: Dict[str, str] = {}
|
|
315
|
+
|
|
316
|
+
for operation in operations:
|
|
317
|
+
for parameter in operation.path_params + operation.query_params + operation.header_params:
|
|
318
|
+
if parameter.source != source:
|
|
319
|
+
continue
|
|
320
|
+
|
|
321
|
+
if parameter.code_name in RESERVED_CLIENT_MEMBER_NAMES:
|
|
322
|
+
raise ValueError(f"Configured parameter code_name {parameter.code_name!r} is reserved")
|
|
323
|
+
|
|
324
|
+
existing_wire_name = wire_by_code_name.get(parameter.code_name)
|
|
325
|
+
if existing_wire_name is not None and existing_wire_name != parameter.wire_name:
|
|
326
|
+
raise ValueError(
|
|
327
|
+
f"Configured parameter code_name {parameter.code_name!r} is used by both "
|
|
328
|
+
f"{existing_wire_name!r} and {parameter.wire_name!r}"
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
wire_by_code_name[parameter.code_name] = parameter.wire_name
|
|
332
|
+
existing = collected.get(parameter.code_name)
|
|
333
|
+
if existing is None:
|
|
334
|
+
collected[parameter.code_name] = parameter
|
|
335
|
+
continue
|
|
336
|
+
|
|
337
|
+
if not existing.required and parameter.required:
|
|
338
|
+
existing.required = True
|
|
339
|
+
existing.field_type_hint = parameter.field_type_hint
|
|
340
|
+
existing.field_default = parameter.field_default
|
|
341
|
+
|
|
342
|
+
return list(collected.values())
|
|
343
|
+
|
|
344
|
+
|
|
225
345
|
def generate_params(operation: Operation) -> str:
|
|
226
346
|
def _schema_default(schema: Any) -> Any:
|
|
227
347
|
default = getattr(schema, "default", None)
|
|
@@ -339,6 +459,25 @@ def generate_header_params(operation: Operation) -> List[str]:
|
|
|
339
459
|
return _generate_params(operation, "header")
|
|
340
460
|
|
|
341
461
|
|
|
462
|
+
def generate_operation_parameters(
|
|
463
|
+
operation: Operation,
|
|
464
|
+
config: PydanticOpenAPIGeneratorConfig,
|
|
465
|
+
param_in: Optional[Literal["path", "query", "header", "cookie"]] = None,
|
|
466
|
+
) -> List[GeneratedParameter]:
|
|
467
|
+
if operation.parameters is None:
|
|
468
|
+
return []
|
|
469
|
+
|
|
470
|
+
parameters: List[GeneratedParameter] = []
|
|
471
|
+
for param in operation.parameters:
|
|
472
|
+
if not isinstance(param, (Parameter30, Parameter31)):
|
|
473
|
+
continue
|
|
474
|
+
if param_in is not None and param.param_in != param_in:
|
|
475
|
+
continue
|
|
476
|
+
parameters.append(_resolve_parameter(param, config))
|
|
477
|
+
|
|
478
|
+
return parameters
|
|
479
|
+
|
|
480
|
+
|
|
342
481
|
def _is_binary_schema(schema: Any) -> bool:
|
|
343
482
|
schema_format = getattr(schema, "schema_format", None)
|
|
344
483
|
schema_type = getattr(schema, "type", None)
|
|
@@ -524,6 +663,7 @@ def generate_clients(
|
|
|
524
663
|
library_config: LibraryConfig,
|
|
525
664
|
env_token_name: Optional[str],
|
|
526
665
|
pydantic_version: PydanticVersion,
|
|
666
|
+
config: Optional[PydanticOpenAPIGeneratorConfig] = None,
|
|
527
667
|
) -> List[Model]:
|
|
528
668
|
"""
|
|
529
669
|
Generate two client modules:
|
|
@@ -533,6 +673,7 @@ def generate_clients(
|
|
|
533
673
|
jinja_env = create_jinja_env()
|
|
534
674
|
|
|
535
675
|
service_ops: List[ServiceOperation] = []
|
|
676
|
+
generator_config = config or PydanticOpenAPIGeneratorConfig()
|
|
536
677
|
|
|
537
678
|
def _generate_service_operation(
|
|
538
679
|
op: Operation,
|
|
@@ -556,24 +697,29 @@ def generate_clients(
|
|
|
556
697
|
op.parameters = [] # type: ignore
|
|
557
698
|
op.parameters.append(p) # type: ignore
|
|
558
699
|
|
|
559
|
-
|
|
700
|
+
request_body = generate_request_body(op)
|
|
701
|
+
body_param = None if request_body is None else request_body.expression
|
|
702
|
+
path_params = generate_operation_parameters(op, generator_config, "path")
|
|
703
|
+
query_params = generate_operation_parameters(op, generator_config, "query")
|
|
704
|
+
header_params = generate_operation_parameters(op, generator_config, "header")
|
|
705
|
+
all_params = path_params + query_params + header_params
|
|
706
|
+
params = _method_signature(all_params, _body_signature_param(op) if body_param is not None else None)
|
|
707
|
+
path_name = _resolved_path_name(path_name, path_params)
|
|
708
|
+
|
|
560
709
|
placeholder_names = [m.group(1) for m in re.finditer(r"\{([^}/]+)\}", path_name)]
|
|
561
|
-
existing_param_names = {p.
|
|
710
|
+
existing_param_names = {p.code_name for p in all_params}
|
|
562
711
|
for ph in placeholder_names:
|
|
563
712
|
norm_ph = common.normalize_symbol(ph)
|
|
564
713
|
if norm_ph not in existing_param_names and norm_ph:
|
|
565
714
|
params = f"{norm_ph}: Any, " + params
|
|
566
715
|
|
|
567
716
|
operation_id = generate_operation_id(op, http_operation, path_name)
|
|
568
|
-
query_params = generate_query_params(op)
|
|
569
|
-
header_params = generate_header_params(op)
|
|
570
717
|
return_type = generate_return_type(op)
|
|
571
|
-
request_body = generate_request_body(op)
|
|
572
|
-
body_param = None if request_body is None else request_body.expression
|
|
573
718
|
|
|
574
719
|
so = ServiceOperation(
|
|
575
720
|
params=params,
|
|
576
721
|
operation_id=operation_id,
|
|
722
|
+
path_params=path_params,
|
|
577
723
|
query_params=query_params,
|
|
578
724
|
header_params=header_params,
|
|
579
725
|
return_type=return_type,
|
|
@@ -605,6 +751,8 @@ def generate_clients(
|
|
|
605
751
|
|
|
606
752
|
sync_ops = [so for so in service_ops if not so.async_client]
|
|
607
753
|
async_ops = [so for so in service_ops if so.async_client]
|
|
754
|
+
client_fields = _collect_client_parameters(service_ops, ParameterSource.CLASS_VAR)
|
|
755
|
+
client_functions = _collect_client_parameters(service_ops, ParameterSource.FUNCTION)
|
|
608
756
|
|
|
609
757
|
openapi_dump = openapi.model_dump() if hasattr(openapi, "model_dump") else {}
|
|
610
758
|
|
|
@@ -612,11 +760,15 @@ def generate_clients(
|
|
|
612
760
|
**openapi_dump,
|
|
613
761
|
env_token_name=env_token_name,
|
|
614
762
|
operations=[so.model_dump() for so in sync_ops],
|
|
763
|
+
client_fields=[p.model_dump() for p in client_fields],
|
|
764
|
+
client_functions=[p.model_dump() for p in client_functions],
|
|
615
765
|
)
|
|
616
766
|
async_content = jinja_env.get_template(ASYNC_CLIENT_HTTPX_TEMPLATE_PYDANTIC_V2).render(
|
|
617
767
|
**openapi_dump,
|
|
618
768
|
env_token_name=env_token_name,
|
|
619
769
|
operations=[so.model_dump() for so in async_ops],
|
|
770
|
+
client_fields=[p.model_dump() for p in client_fields],
|
|
771
|
+
client_functions=[p.model_dump() for p in client_functions],
|
|
620
772
|
)
|
|
621
773
|
|
|
622
774
|
compile(sync_content, "<string>", "exec")
|
|
@@ -4,6 +4,9 @@ from openapi_pydantic.v3.v3_0 import OpenAPI as OpenAPI30
|
|
|
4
4
|
from openapi_pydantic.v3.v3_1 import OpenAPI as OpenAPI31
|
|
5
5
|
|
|
6
6
|
from pydantic_openapi_generator.common import PydanticVersion
|
|
7
|
+
from pydantic_openapi_generator.config.generator_config import (
|
|
8
|
+
PydanticOpenAPIGeneratorConfig,
|
|
9
|
+
)
|
|
7
10
|
from pydantic_openapi_generator.language_converters.python import common
|
|
8
11
|
from pydantic_openapi_generator.language_converters.python.client_generator import (
|
|
9
12
|
generate_clients,
|
|
@@ -28,6 +31,7 @@ def generator(
|
|
|
28
31
|
use_orjson: bool = False,
|
|
29
32
|
custom_template_path: Optional[str] = None,
|
|
30
33
|
pydantic_version: PydanticVersion = PydanticVersion.V2,
|
|
34
|
+
config: Optional[PydanticOpenAPIGeneratorConfig] = None,
|
|
31
35
|
) -> ConversionResult:
|
|
32
36
|
"""
|
|
33
37
|
Generate Python code from an OpenAPI 3.0+ specification.
|
|
@@ -50,7 +54,7 @@ def generator(
|
|
|
50
54
|
models.extend(resp_alias_models)
|
|
51
55
|
|
|
52
56
|
if data.paths is not None:
|
|
53
|
-
clients = generate_clients(data, data.paths, library_config, env_token_name, pydantic_version)
|
|
57
|
+
clients = generate_clients(data, data.paths, library_config, env_token_name, pydantic_version, config)
|
|
54
58
|
else:
|
|
55
59
|
clients = []
|
|
56
60
|
|
|
@@ -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, TypeAdapter
|
|
10
|
+
from pydantic import BaseModel, SecretStr, TypeAdapter
|
|
11
11
|
|
|
12
12
|
from ..models import *
|
|
13
13
|
from ..exceptions import HTTPException
|
|
@@ -23,6 +23,9 @@ class AsyncClient(BaseModel):
|
|
|
23
23
|
{% if env_token_name is none %}
|
|
24
24
|
access_token: Optional[str] = None
|
|
25
25
|
{% endif %}
|
|
26
|
+
{% for field in client_fields %}
|
|
27
|
+
{{ field.code_name }}: {{ field.field_type_hint }}{% if field.field_default is not none %} = {{ field.field_default }}{% endif %}{{ "\n" }}
|
|
28
|
+
{% endfor %}
|
|
26
29
|
|
|
27
30
|
async def get_access_token(self) -> Optional[str]:
|
|
28
31
|
{% if env_token_name is not none %}
|
|
@@ -44,6 +47,11 @@ class AsyncClient(BaseModel):
|
|
|
44
47
|
self.access_token = value
|
|
45
48
|
{% endif %}
|
|
46
49
|
|
|
50
|
+
{% for function in client_functions %}
|
|
51
|
+
async def {{ function.getter_name }}(self) -> {{ function.base_type_hint }}:
|
|
52
|
+
raise NotImplementedError
|
|
53
|
+
|
|
54
|
+
{% endfor %}
|
|
47
55
|
{% for op in operations %}
|
|
48
56
|
async def {{ op.operation_id }}(self{% if op.params %}, {{ op.params }}{% endif %}) -> {%- if op.is_sse -%}
|
|
49
57
|
AsyncGenerator[str | dict[str, Any], None]
|
|
@@ -51,6 +59,17 @@ AsyncGenerator[str | dict[str, Any], None]
|
|
|
51
59
|
{{ op.return_type.return_type_hint or "None" }}
|
|
52
60
|
{%- endif -%}:
|
|
53
61
|
base_url = self.base_url
|
|
62
|
+
{% for param in op.path_params + op.query_params + op.header_params %}
|
|
63
|
+
{% if param.source == "ClassVar" %}
|
|
64
|
+
{% if param.is_secret %}
|
|
65
|
+
{{ param.local_name }} = {{ param.code_name }} if {{ param.code_name }} is not None else (self.{{ param.code_name }}.get_secret_value() if self.{{ param.code_name }} is not None else None)
|
|
66
|
+
{% else %}
|
|
67
|
+
{{ param.local_name }} = {{ param.code_name }} if {{ param.code_name }} is not None else self.{{ param.code_name }}
|
|
68
|
+
{% endif %}
|
|
69
|
+
{% elif param.source == "Function" %}
|
|
70
|
+
{{ param.local_name }} = {{ param.code_name }} if {{ param.code_name }} is not None else await self.{{ param.getter_name }}()
|
|
71
|
+
{% endif %}
|
|
72
|
+
{% endfor %}
|
|
54
73
|
path = f"{{ op.path_name }}"
|
|
55
74
|
|
|
56
75
|
headers = {
|
|
@@ -65,7 +84,7 @@ AsyncGenerator[str | dict[str, Any], None]
|
|
|
65
84
|
"Accept": "{{ op.return_type.accept_content_types | join(', ') or 'application/json' }}",
|
|
66
85
|
{% endif %}
|
|
67
86
|
{% for hp in op.header_params %}
|
|
68
|
-
{{ hp }},
|
|
87
|
+
"{{ hp.wire_name }}": {{ hp.value_expression }},
|
|
69
88
|
{% endfor %}
|
|
70
89
|
}
|
|
71
90
|
headers = {k: v for (k, v) in headers.items() if v is not None}
|
|
@@ -76,7 +95,7 @@ AsyncGenerator[str | dict[str, Any], None]
|
|
|
76
95
|
|
|
77
96
|
query_params: Dict[str, Any] = {
|
|
78
97
|
{% for qp in op.query_params %}
|
|
79
|
-
{{ qp }},
|
|
98
|
+
"{{ qp.wire_name }}": {{ qp.value_expression }},
|
|
80
99
|
{% endfor %}
|
|
81
100
|
}
|
|
82
101
|
query_params = {k: v for (k, v) in query_params.items() if v is not None}
|
|
@@ -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, TypeAdapter
|
|
10
|
+
from pydantic import BaseModel, SecretStr, TypeAdapter
|
|
11
11
|
|
|
12
12
|
from ..models import *
|
|
13
13
|
from ..exceptions import HTTPException
|
|
@@ -23,6 +23,9 @@ class SyncClient(BaseModel):
|
|
|
23
23
|
{% if env_token_name is none %}
|
|
24
24
|
access_token: Optional[str] = None
|
|
25
25
|
{% endif %}
|
|
26
|
+
{% for field in client_fields %}
|
|
27
|
+
{{ field.code_name }}: {{ field.field_type_hint }}{% if field.field_default is not none %} = {{ field.field_default }}{% endif %}{{ "\n" }}
|
|
28
|
+
{% endfor %}
|
|
26
29
|
|
|
27
30
|
def get_access_token(self) -> Optional[str]:
|
|
28
31
|
{% if env_token_name is not none %}
|
|
@@ -44,6 +47,11 @@ class SyncClient(BaseModel):
|
|
|
44
47
|
self.access_token = value
|
|
45
48
|
{% endif %}
|
|
46
49
|
|
|
50
|
+
{% for function in client_functions %}
|
|
51
|
+
def {{ function.getter_name }}(self) -> {{ function.base_type_hint }}:
|
|
52
|
+
raise NotImplementedError
|
|
53
|
+
|
|
54
|
+
{% endfor %}
|
|
47
55
|
{% for op in operations %}
|
|
48
56
|
def {{ op.operation_id }}(self{% if op.params %}, {{ op.params }}{% endif %}) -> {%- if op.is_sse -%}
|
|
49
57
|
Generator[str | dict[str, Any], None, None]
|
|
@@ -51,6 +59,17 @@ Generator[str | dict[str, Any], None, None]
|
|
|
51
59
|
{{ op.return_type.return_type_hint or "None" }}
|
|
52
60
|
{%- endif -%}:
|
|
53
61
|
base_url = self.base_url
|
|
62
|
+
{% for param in op.path_params + op.query_params + op.header_params %}
|
|
63
|
+
{% if param.source == "ClassVar" %}
|
|
64
|
+
{% if param.is_secret %}
|
|
65
|
+
{{ param.local_name }} = {{ param.code_name }} if {{ param.code_name }} is not None else (self.{{ param.code_name }}.get_secret_value() if self.{{ param.code_name }} is not None else None)
|
|
66
|
+
{% else %}
|
|
67
|
+
{{ param.local_name }} = {{ param.code_name }} if {{ param.code_name }} is not None else self.{{ param.code_name }}
|
|
68
|
+
{% endif %}
|
|
69
|
+
{% elif param.source == "Function" %}
|
|
70
|
+
{{ param.local_name }} = {{ param.code_name }} if {{ param.code_name }} is not None else self.{{ param.getter_name }}()
|
|
71
|
+
{% endif %}
|
|
72
|
+
{% endfor %}
|
|
54
73
|
path = f"{{ op.path_name }}"
|
|
55
74
|
|
|
56
75
|
headers = {
|
|
@@ -65,7 +84,7 @@ Generator[str | dict[str, Any], None, None]
|
|
|
65
84
|
"Accept": "{{ op.return_type.accept_content_types | join(', ') or 'application/json' }}",
|
|
66
85
|
{% endif %}
|
|
67
86
|
{% for hp in op.header_params %}
|
|
68
|
-
{{ hp }},
|
|
87
|
+
"{{ hp.wire_name }}": {{ hp.value_expression }},
|
|
69
88
|
{% endfor %}
|
|
70
89
|
}
|
|
71
90
|
headers = {k: v for (k, v) in headers.items() if v is not None}
|
|
@@ -76,7 +95,7 @@ Generator[str | dict[str, Any], None, None]
|
|
|
76
95
|
|
|
77
96
|
query_params: Dict[str, Any] = {
|
|
78
97
|
{% for qp in op.query_params %}
|
|
79
|
-
{{ qp }},
|
|
98
|
+
"{{ qp.wire_name }}": {{ qp.value_expression }},
|
|
80
99
|
{% endfor %}
|
|
81
100
|
}
|
|
82
101
|
query_params = {k: v for (k, v) in query_params.items() if v is not None}
|
|
@@ -20,6 +20,8 @@ from openapi_pydantic.v3.v3_1 import (
|
|
|
20
20
|
)
|
|
21
21
|
from pydantic import BaseModel, Field
|
|
22
22
|
|
|
23
|
+
from pydantic_openapi_generator.config.parameter_source import ParameterSource
|
|
24
|
+
|
|
23
25
|
# Type unions for compatibility with both OpenAPI 3.0 and 3.1
|
|
24
26
|
Operation = Union[Operation30, Operation31]
|
|
25
27
|
PathItem = Union[PathItem30, PathItem31]
|
|
@@ -66,11 +68,29 @@ class RequestBodyDefinition(BaseModel):
|
|
|
66
68
|
expression: str
|
|
67
69
|
|
|
68
70
|
|
|
71
|
+
class GeneratedParameter(BaseModel):
|
|
72
|
+
wire_name: str
|
|
73
|
+
code_name: str
|
|
74
|
+
location: Literal["path", "query", "header", "cookie"]
|
|
75
|
+
type_hint: str
|
|
76
|
+
base_type_hint: str
|
|
77
|
+
required: bool
|
|
78
|
+
default: Optional[str] = None
|
|
79
|
+
source: ParameterSource = ParameterSource.KWARG
|
|
80
|
+
is_secret: bool = False
|
|
81
|
+
field_type_hint: Optional[str] = None
|
|
82
|
+
field_default: Optional[str] = None
|
|
83
|
+
getter_name: Optional[str] = None
|
|
84
|
+
local_name: Optional[str] = None
|
|
85
|
+
value_expression: Optional[str] = None
|
|
86
|
+
|
|
87
|
+
|
|
69
88
|
class ServiceOperation(BaseModel):
|
|
70
89
|
params: str
|
|
71
90
|
operation_id: str
|
|
72
|
-
|
|
73
|
-
|
|
91
|
+
path_params: List[GeneratedParameter] = Field(default_factory=list)
|
|
92
|
+
query_params: List[GeneratedParameter]
|
|
93
|
+
header_params: List[GeneratedParameter]
|
|
74
94
|
return_type: OpReturnType
|
|
75
95
|
operation: Operation
|
|
76
96
|
pathItem: PathItem
|
|
@@ -7,6 +7,9 @@ from typing import Optional
|
|
|
7
7
|
from openapi_pydantic.v3.v3_0 import OpenAPI
|
|
8
8
|
|
|
9
9
|
from pydantic_openapi_generator.common import HTTPLibrary, PydanticVersion
|
|
10
|
+
from pydantic_openapi_generator.config.generator_config import (
|
|
11
|
+
PydanticOpenAPIGeneratorConfig,
|
|
12
|
+
)
|
|
10
13
|
from pydantic_openapi_generator.language_converters.python.generator import (
|
|
11
14
|
generator as base_generator,
|
|
12
15
|
)
|
|
@@ -36,6 +39,7 @@ def generate_code_3_0(
|
|
|
36
39
|
use_orjson: bool = False,
|
|
37
40
|
custom_template_path: Optional[str] = None,
|
|
38
41
|
pydantic_version: PydanticVersion = PydanticVersion.V2,
|
|
42
|
+
config: Optional[PydanticOpenAPIGeneratorConfig] = None,
|
|
39
43
|
) -> ConversionResult:
|
|
40
44
|
"""
|
|
41
45
|
Generate Python code from OpenAPI 3.0 specification.
|
|
@@ -62,4 +66,5 @@ def generate_code_3_0(
|
|
|
62
66
|
use_orjson=use_orjson,
|
|
63
67
|
custom_template_path=custom_template_path,
|
|
64
68
|
pydantic_version=pydantic_version,
|
|
69
|
+
config=config,
|
|
65
70
|
)
|
|
@@ -7,6 +7,9 @@ from typing import Optional
|
|
|
7
7
|
from openapi_pydantic.v3.v3_1 import OpenAPI
|
|
8
8
|
|
|
9
9
|
from pydantic_openapi_generator.common import HTTPLibrary, PydanticVersion
|
|
10
|
+
from pydantic_openapi_generator.config.generator_config import (
|
|
11
|
+
PydanticOpenAPIGeneratorConfig,
|
|
12
|
+
)
|
|
10
13
|
from pydantic_openapi_generator.language_converters.python.generator import (
|
|
11
14
|
generator as base_generator,
|
|
12
15
|
)
|
|
@@ -36,6 +39,7 @@ def generate_code_3_1(
|
|
|
36
39
|
use_orjson: bool = False,
|
|
37
40
|
custom_template_path: Optional[str] = None,
|
|
38
41
|
pydantic_version: PydanticVersion = PydanticVersion.V2,
|
|
42
|
+
config: Optional[PydanticOpenAPIGeneratorConfig] = None,
|
|
39
43
|
) -> ConversionResult:
|
|
40
44
|
"""
|
|
41
45
|
Generate Python code from OpenAPI 3.1 specification.
|
|
@@ -62,4 +66,5 @@ def generate_code_3_1(
|
|
|
62
66
|
use_orjson=use_orjson,
|
|
63
67
|
custom_template_path=custom_template_path,
|
|
64
68
|
pydantic_version=pydantic_version,
|
|
69
|
+
config=config,
|
|
65
70
|
)
|
|
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
|