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.
Files changed (41) hide show
  1. {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/PKG-INFO +2 -1
  2. {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/pyproject.toml +2 -1
  3. {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/__main__.py +8 -0
  4. pydantic_openapi_generator-2.4.0/src/pydantic_openapi_generator/config/__init__.py +5 -0
  5. pydantic_openapi_generator-2.4.0/src/pydantic_openapi_generator/config/base_parameter.py +73 -0
  6. pydantic_openapi_generator-2.4.0/src/pydantic_openapi_generator/config/class_var_parameter.py +60 -0
  7. pydantic_openapi_generator-2.4.0/src/pydantic_openapi_generator/config/function_parameter.py +47 -0
  8. pydantic_openapi_generator-2.4.0/src/pydantic_openapi_generator/config/generator_config.py +33 -0
  9. pydantic_openapi_generator-2.4.0/src/pydantic_openapi_generator/config/kwarg_parameter.py +56 -0
  10. pydantic_openapi_generator-2.4.0/src/pydantic_openapi_generator/config/loader.py +17 -0
  11. pydantic_openapi_generator-2.4.0/src/pydantic_openapi_generator/config/parameter_source.py +7 -0
  12. pydantic_openapi_generator-2.4.0/src/pydantic_openapi_generator/config/parameter_union.py +16 -0
  13. pydantic_openapi_generator-2.4.0/src/pydantic_openapi_generator/config/utils.py +56 -0
  14. {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/generate_data.py +7 -0
  15. {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/language_converters/python/client_generator.py +158 -6
  16. {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/language_converters/python/generator.py +5 -1
  17. {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
  18. {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
  19. {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/models.py +22 -2
  20. {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/parsers/openapi_30.py +5 -0
  21. {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/parsers/openapi_31.py +5 -0
  22. {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/.gitignore +0 -0
  23. {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/LICENSE +0 -0
  24. {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/README.md +0 -0
  25. {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/__init__.py +0 -0
  26. {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/common.py +0 -0
  27. {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/language_converters/__init__.py +0 -0
  28. {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/language_converters/python/__init__.py +0 -0
  29. {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/language_converters/python/common.py +0 -0
  30. {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/language_converters/python/exception_generator.py +0 -0
  31. {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/language_converters/python/jinja_config.py +0 -0
  32. {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/language_converters/python/model_generator.py +0 -0
  33. {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
  34. {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
  35. {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/language_converters/python/templates/enum.jinja2 +0 -0
  36. {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
  37. {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/language_converters/python/templates/models.jinja2 +0 -0
  38. {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
  39. {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/parsers/__init__.py +0 -0
  40. {pydantic_openapi_generator-2.3.0 → pydantic_openapi_generator-2.4.0}/src/pydantic_openapi_generator/py.typed +0 -0
  41. {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.0
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.0"
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,5 @@
1
+ from pydantic_openapi_generator.config.parameter_source import ParameterSource
2
+
3
+ __all__ = [
4
+ "ParameterSource",
5
+ ]
@@ -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,7 @@
1
+ from enum import StrEnum
2
+
3
+
4
+ class ParameterSource(StrEnum):
5
+ KWARG = "Kwarg"
6
+ CLASS_VAR = "ClassVar"
7
+ FUNCTION = "Function"
@@ -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
- params = generate_params(op)
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.split(":")[0].strip() for p in params.split(",") if ":" in 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
- query_params: List[str]
73
- header_params: List[str]
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
  )