diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index ce613342..69bc72a7 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -14,7 +14,7 @@ jobs:
- name: Setup Python
uses: actions/setup-python@v1
with:
- python-version: '3.8'
+ python-version: '3.9'
architecture: 'x64'
- uses: actions/cache@v1
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index c6e638f3..aab2cff3 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -20,10 +20,10 @@ jobs:
key: ${{ runner.os }}-pip-${{ hashFiles('**/poetry.lock') }}
restore-keys: |
${{ runner.os }}-pip-
- - name: Set up Python 3.8
+ - name: Set up Python 3.9
uses: actions/setup-python@v1
with:
- python-version: 3.8
+ python-version: 3.9
- name: Build publish
run: |
python -m pip install poetry poetry-dynamic-versioning
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index f643e2f7..cd4f8553 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -10,7 +10,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
+ python-version: ["3.9", "3.10", "3.11", "3.12"]
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
diff --git a/.gitignore b/.gitignore
index 7225e1d1..d75b96db 100644
--- a/.gitignore
+++ b/.gitignore
@@ -176,4 +176,6 @@ fabric.properties
.idea
+.vscode/
+
version.py
diff --git a/README.md b/README.md
index 956d3449..2d4c7d05 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,7 @@ Options:
--specify-tags Use along with --generate-routers to generate specific routers from given list of tags.
-c, --custom-visitors PATH - A custom visitor that adds variables to the template.
-d, --output-model-type Specify a Pydantic base model to use (see [datamodel-code-generator](https://github.com/koxudaxi/datamodel-code-generator); default is `pydantic.BaseModel`).
- -p, --python-version Specify a Python version to target (default is `3.8`).
+ -p, --python-version Specify a Python version to target (default is `3.9`).
--install-completion Install completion for the current shell.
--show-completion Show completion for the current shell, to copy it
or customize the installation.
diff --git a/fastapi_code_generator/__init__.py b/fastapi_code_generator/__init__.py
index e69de29b..c998a3db 100644
--- a/fastapi_code_generator/__init__.py
+++ b/fastapi_code_generator/__init__.py
@@ -0,0 +1,3 @@
+from .patches import patch_parse
+
+patch_parse()
diff --git a/fastapi_code_generator/__main__.py b/fastapi_code_generator/__main__.py
index 196c7f44..4d3e6610 100644
--- a/fastapi_code_generator/__main__.py
+++ b/fastapi_code_generator/__main__.py
@@ -28,7 +28,7 @@
BUILTIN_VISITOR_DIR = Path(__file__).parent / "visitors"
-MODEL_PATH: Path = Path("models.py")
+MODEL_PATH: Path = Path("models")
def dynamic_load_module(module_path: Path) -> Any:
@@ -63,7 +63,7 @@ def main(
DataModelType.PydanticBaseModel.value, "--output-model-type", "-d"
),
python_version: PythonVersion = typer.Option(
- PythonVersion.PY_38.value, "--python-version", "-p"
+ PythonVersion.PY_39.value, "--python-version", "-p"
),
) -> None:
input_name: str = input_file
@@ -72,10 +72,7 @@ def main(
with open(input_file, encoding=encoding) as f:
input_text = f.read()
- if model_file:
- model_path = Path(model_file).with_suffix('.py')
- else:
- model_path = MODEL_PATH
+ model_path = Path(model_file) if model_file else MODEL_PATH # pragma: no cover
return generate_code(
input_name,
@@ -119,7 +116,7 @@ def generate_code(
generate_routers: Optional[bool] = None,
specify_tags: Optional[str] = None,
output_model_type: DataModelType = DataModelType.PydanticBaseModel,
- python_version: PythonVersion = PythonVersion.PY_38,
+ python_version: PythonVersion = PythonVersion.PY_39,
) -> None:
if not model_path:
model_path = MODEL_PATH
@@ -149,14 +146,16 @@ def generate_code(
with chdir(output_dir):
models = parser.parse()
- output = output_dir / model_path
if not models:
# if no models (schemas), just generate an empty model file.
- modules = {output: ("", input_name)}
+ modules = {output_dir / model_path.with_suffix('.py'): ("", input_name)}
elif isinstance(models, str):
- modules = {output: (models, input_name)}
+ modules = {output_dir / model_path.with_suffix('.py'): (models, input_name)}
else:
- raise Exception('Modular references are not supported in this version')
+ modules = {
+ output_dir / model_path / module_name[0]: (model.body, input_name)
+ for module_name, model in models.items()
+ }
environment: Environment = Environment(
loader=FileSystemLoader(
diff --git a/fastapi_code_generator/parser.py b/fastapi_code_generator/parser.py
index 579f6014..f9b06780 100644
--- a/fastapi_code_generator/parser.py
+++ b/fastapi_code_generator/parser.py
@@ -2,6 +2,7 @@
import pathlib
import re
+from functools import cached_property
from typing import (
Any,
Callable,
@@ -42,7 +43,6 @@
ResponseObject,
)
from datamodel_code_generator.types import DataType, DataTypeManager, StrictTypes
-from datamodel_code_generator.util import cached_property
from pydantic import BaseModel, ValidationInfo
RE_APPLICATION_JSON_PATTERN: Pattern[str] = re.compile(r'^application/.*json$')
@@ -93,16 +93,43 @@ class Argument(CachedPropertyModel):
type_hint: UsefulStr
default: Optional[UsefulStr] = None
default_value: Optional[UsefulStr] = None
+ field: Union[DataModelField, list[DataModelField], None] = None
required: bool
def __str__(self) -> str:
return self.argument
- @cached_property
+ @property
def argument(self) -> str:
+ if self.field is None:
+ type_hint = self.type_hint
+ else:
+ type_hint = (
+ UsefulStr(self.field.type_hint)
+ if not isinstance(self.field, list)
+ else UsefulStr(
+ f"Union[{', '.join(field.type_hint for field in self.field)}]"
+ )
+ )
+ if self.default is None and self.required:
+ return f'{self.name}: {type_hint}'
+ return f'{self.name}: {type_hint} = {self.default}'
+
+ @property
+ def snakecase(self) -> str:
+ if self.field is None:
+ type_hint = self.type_hint
+ else:
+ type_hint = (
+ UsefulStr(self.field.type_hint)
+ if not isinstance(self.field, list)
+ else UsefulStr(
+ f"Union[{', '.join(field.type_hint for field in self.field)}]"
+ )
+ )
if self.default is None and self.required:
- return f'{self.name}: {self.type_hint}'
- return f'{self.name}: {self.type_hint} = {self.default}'
+ return f'{stringcase.snakecase(self.name)}: {type_hint}'
+ return f'{stringcase.snakecase(self.name)}: {type_hint} = {self.default}'
class Operation(CachedPropertyModel):
@@ -114,16 +141,39 @@ class Operation(CachedPropertyModel):
parameters: List[Dict[str, Any]] = []
responses: Dict[UsefulStr, Any] = {}
deprecated: bool = False
- imports: List[Import] = []
security: Optional[List[Dict[str, List[str]]]] = None
tags: Optional[List[str]] = []
- arguments: str = ''
- snake_case_arguments: str = ''
request: Optional[Argument] = None
response: str = ''
additional_responses: Dict[Union[str, int], Dict[str, str]] = {}
return_type: str = ''
callbacks: Dict[UsefulStr, List["Operation"]] = {}
+ arguments_list: List[Argument] = []
+
+ @classmethod
+ def merge_arguments_with_union(cls, arguments: List[Argument]) -> List[Argument]:
+ grouped_arguments: DefaultDict[str, List[Argument]] = DefaultDict(list)
+ for argument in arguments:
+ grouped_arguments[argument.name].append(argument)
+
+ merged_arguments = []
+ for argument_list in grouped_arguments.values():
+ if len(argument_list) == 1:
+ merged_arguments.append(argument_list[0])
+ else:
+ argument = argument_list[0]
+ fields = [
+ item
+ for arg in argument_list
+ if arg.field is not None
+ for item in (
+ arg.field if isinstance(arg.field, list) else [arg.field]
+ )
+ if item is not None
+ ]
+ argument.field = fields
+ merged_arguments.append(argument)
+ return merged_arguments
@cached_property
def type(self) -> UsefulStr:
@@ -132,6 +182,27 @@ def type(self) -> UsefulStr:
"""
return self.method
+ @property
+ def arguments(self) -> str:
+ sorted_arguments = Operation.merge_arguments_with_union(self.arguments_list)
+ return ", ".join(argument.argument for argument in sorted_arguments)
+
+ @property
+ def snake_case_arguments(self) -> str:
+ sorted_arguments = Operation.merge_arguments_with_union(self.arguments_list)
+ return ", ".join(argument.snakecase for argument in sorted_arguments)
+
+ @property
+ def imports(self) -> Imports:
+ imports = Imports()
+ for argument in self.arguments_list:
+ if isinstance(argument.field, list):
+ for field in argument.field:
+ imports.append(field.data_type.import_)
+ elif argument.field:
+ imports.append(argument.field.data_type.import_)
+ return imports
+
@cached_property
def root_path(self) -> UsefulStr:
paths = self.path.split("/")
@@ -153,7 +224,7 @@ def function_name(self) -> str:
return stringcase.snakecase(name)
-@snooper_to_methods(max_variable_length=None)
+@snooper_to_methods()
class OpenAPIParser(OpenAPIModelParser):
def __init__(
self,
@@ -166,7 +237,7 @@ def __init__(
base_class: Optional[str] = None,
custom_template_dir: Optional[pathlib.Path] = None,
extra_template_data: Optional[DefaultDict[str, Dict[str, Any]]] = None,
- target_python_version: PythonVersion = PythonVersion.PY_37,
+ target_python_version: PythonVersion = PythonVersion.PY_39,
dump_resolve_reference_action: Optional[Callable[[Iterable[str]], str]] = None,
validation: bool = False,
field_constraints: bool = False,
@@ -314,6 +385,7 @@ def get_parameter_type(
default=default, # type: ignore
default_value=schema.default,
required=field.required,
+ field=field,
)
def get_arguments(self, snake_case: bool, path: List[str]) -> str:
@@ -347,6 +419,10 @@ def get_argument_list(self, snake_case: bool, path: List[str]) -> List[Argument]
or argument.type_hint.startswith('Optional[')
)
+ # check if there are duplicate argument.name
+ argument_names = [argument.name for argument in arguments]
+ if len(argument_names) != len(set(argument_names)):
+ self.imports_for_fastapi.append(Import(from_='typing', import_="Union"))
return arguments
def parse_request_body(
@@ -466,10 +542,7 @@ def parse_operation(
resolved_path = self.model_resolver.resolve_ref(path)
path_name, method = path[-2:]
- self._temporary_operation['arguments'] = self.get_arguments(
- snake_case=False, path=path
- )
- self._temporary_operation['snake_case_arguments'] = self.get_arguments(
+ self._temporary_operation['arguments_list'] = self.get_argument_list(
snake_case=True, path=path
)
main_operation = self._temporary_operation
@@ -499,11 +572,8 @@ def parse_operation(
self._temporary_operation = {'_parameters': []}
cb_path = path + ['callbacks', key, route, method]
super().parse_operation(cb_op, cb_path)
- self._temporary_operation['arguments'] = self.get_arguments(
- snake_case=False, path=cb_path
- )
- self._temporary_operation['snake_case_arguments'] = (
- self.get_arguments(snake_case=True, path=cb_path)
+ self._temporary_operation['arguments_list'] = (
+ self.get_argument_list(snake_case=True, path=cb_path)
)
callbacks[key].append(
@@ -527,13 +597,16 @@ def _collapse_root_model(self, data_type: DataType) -> DataType:
reference = data_type.reference
import functools
- if not (
- reference
- and (
- len(reference.children) == 1
- or functools.reduce(lambda a, b: a == b, reference.children)
- )
- ):
+ try:
+ if not (
+ reference
+ and (
+ len(reference.children) == 0
+ or functools.reduce(lambda a, b: a == b, reference.children)
+ )
+ ):
+ return data_type
+ except RecursionError:
return data_type
source = reference.source
if not isinstance(source, CustomRootType):
diff --git a/fastapi_code_generator/patches.py b/fastapi_code_generator/patches.py
new file mode 100644
index 00000000..f3f4dd37
--- /dev/null
+++ b/fastapi_code_generator/patches.py
@@ -0,0 +1,257 @@
+# todo: remove this file when the following PR is merged into datamodels-code-generator:
+#
+# https://github.com/koxudaxi/datamodel-code-generator/pull/2379
+
+import logging
+from itertools import groupby
+from pathlib import Path
+from typing import NamedTuple, Optional, Union
+
+from datamodel_code_generator.format import CodeFormatter
+from datamodel_code_generator.imports import IMPORT_ANNOTATIONS, Import, Imports
+from datamodel_code_generator.model.base import DataModel
+from datamodel_code_generator.parser import base
+from datamodel_code_generator.reference import ModelResolver
+
+logger = logging.getLogger(__name__)
+
+# Save the original method before patching
+original_parse = base.Parser.parse
+
+
+def patch_parse() -> None: # noqa: C901
+ def __alias_shadowed_imports(
+ self: base.Parser,
+ models: list[DataModel],
+ all_model_field_names: set[str],
+ ) -> None:
+ for model in models:
+ for model_field in model.fields:
+ if model_field.data_type.type in all_model_field_names:
+ alias = model_field.data_type.type + "_aliased"
+ model_field.data_type.type = alias
+ if model_field.data_type.import_:
+ model_field.data_type.import_.alias = alias
+
+ def _parse( # noqa: PLR0912, PLR0914, PLR0915
+ self: base.Parser,
+ with_import: Optional[bool] = True, # noqa: FBT001, FBT002
+ format_: Optional[bool] = True, # noqa: FBT001, FBT002
+ settings_path: Optional[Path] = None,
+ ) -> Union[str, dict[tuple[str, ...], base.Result]]:
+ self.parse_raw()
+
+ if with_import:
+ self.imports.append(IMPORT_ANNOTATIONS)
+
+ if format_:
+ code_formatter: Optional[CodeFormatter] = CodeFormatter(
+ self.target_python_version,
+ settings_path,
+ self.wrap_string_literal,
+ skip_string_normalization=not self.use_double_quotes,
+ known_third_party=self.known_third_party,
+ custom_formatters=self.custom_formatter,
+ custom_formatters_kwargs=self.custom_formatters_kwargs,
+ encoding=self.encoding,
+ formatters=self.formatters,
+ )
+ else:
+ code_formatter = None
+
+ _, sorted_data_models, require_update_action_models = base.sort_data_models(
+ self.results
+ )
+
+ results: dict[tuple[str, ...], base.Result] = {}
+
+ def module_key(data_model: DataModel) -> tuple[str, ...]:
+ return tuple(data_model.module_path)
+
+ def sort_key(data_model: DataModel) -> tuple[int, tuple[str, ...]]:
+ return (len(data_model.module_path), tuple(data_model.module_path))
+
+ # process in reverse order to correctly establish module levels
+ grouped_models = groupby(
+ sorted(sorted_data_models.values(), key=sort_key, reverse=True),
+ key=module_key,
+ )
+
+ module_models: list[tuple[tuple[str, ...], list[DataModel]]] = []
+ unused_models: list[DataModel] = []
+ model_to_module_models: dict[
+ DataModel, tuple[tuple[str, ...], list[DataModel]]
+ ] = {}
+ module_to_import: dict[tuple[str, ...], Imports] = {}
+
+ previous_module: tuple[str, ...] = ()
+ for module, models in ((k, [*v]) for k, v in grouped_models):
+ for model in models:
+ model_to_module_models[model] = module, models
+ self._Parser__delete_duplicate_models(models) # type: ignore[attr-defined]
+ self._Parser__replace_duplicate_name_in_module(models) # type: ignore[attr-defined]
+ if len(previous_module) - len(module) > 1:
+ module_models.extend(
+ (
+ previous_module[:parts],
+ [],
+ )
+ for parts in range(len(previous_module) - 1, len(module), -1)
+ )
+ module_models.append(
+ (
+ module,
+ models,
+ )
+ )
+ previous_module = module
+
+ class Processed(NamedTuple):
+ module: tuple[str, ...]
+ models: list[DataModel]
+ init: bool
+ imports: Imports
+ scoped_model_resolver: ModelResolver
+
+ processed_models: list[Processed] = []
+
+ for module_, models in module_models:
+ imports = module_to_import[module_] = Imports(self.use_exact_imports)
+ init = False
+ if module_:
+ parent = (*module_[:-1], "__init__.py")
+ if parent not in results:
+ results[parent] = base.Result(body="")
+ if (*module_, "__init__.py") in results:
+ module = (*module_, "__init__.py")
+ init = True
+ else:
+ module = tuple(
+ part.replace("-", "_")
+ for part in (*module_[:-1], f"{module_[-1]}.py")
+ )
+ else:
+ module = ("__init__.py",)
+
+ all_module_fields = {
+ field.name
+ for model in models
+ for field in model.fields
+ if field.name is not None
+ }
+ scoped_model_resolver = ModelResolver(exclude_names=all_module_fields)
+
+ self.__alias_shadowed_imports(models, all_module_fields) # type: ignore[attr-defined]
+ self._Parser__override_required_field(models) # type: ignore[attr-defined]
+ self._Parser__replace_unique_list_to_set(models) # type: ignore[attr-defined]
+ self._Parser__change_from_import( # type: ignore[attr-defined]
+ models, imports, scoped_model_resolver, init
+ )
+ self._Parser__extract_inherited_enum(models) # type: ignore[attr-defined]
+ self._Parser__set_reference_default_value_to_field(models) # type: ignore[attr-defined]
+ self._Parser__reuse_model(models, require_update_action_models) # type: ignore[attr-defined]
+ self._Parser__collapse_root_models( # type: ignore[attr-defined]
+ models, unused_models, imports, scoped_model_resolver
+ )
+ self._Parser__set_default_enum_member(models) # type: ignore[attr-defined]
+ self._Parser__sort_models(models, imports) # type: ignore[attr-defined]
+ self._Parser__change_field_name(models) # type: ignore[attr-defined]
+ self._Parser__apply_discriminator_type(models, imports) # type: ignore[attr-defined]
+ self._Parser__set_one_literal_on_default(models) # type: ignore[attr-defined]
+
+ processed_models.append(
+ Processed(module, models, init, imports, scoped_model_resolver)
+ )
+
+ for processed_model in processed_models:
+ for model in processed_model.models:
+ processed_model.imports.append(model.imports)
+
+ for unused_model in unused_models:
+ module, models = model_to_module_models[unused_model]
+ if unused_model in models: # pragma: no cover
+ imports = module_to_import[module]
+ imports.remove(unused_model.imports)
+ models.remove(unused_model)
+
+ for processed_model in processed_models:
+ # postprocess imports to remove unused imports.
+ model_code = str("\n".join([str(m) for m in processed_model.models]))
+ unused_imports = [
+ (from_, import_)
+ for from_, imports_ in processed_model.imports.items()
+ for import_ in imports_
+ if import_ not in model_code
+ ]
+ for from_, import_ in unused_imports:
+ processed_model.imports.remove(Import(from_=from_, import_=import_))
+
+ for (
+ module,
+ models,
+ init,
+ imports,
+ scoped_model_resolver,
+ ) in processed_models: # noqa: B007
+ # process after removing unused models
+ self._Parser__change_imported_model_name( # type: ignore[attr-defined]
+ models, imports, scoped_model_resolver
+ )
+
+ for (
+ module,
+ models,
+ init,
+ imports,
+ scoped_model_resolver,
+ ) in processed_models: # noqa: B007
+ result: list[str] = []
+ if models:
+ if with_import:
+ result += [str(self.imports), str(imports), "\n"]
+
+ code = base.dump_templates(models)
+ result += [code]
+
+ if self.dump_resolve_reference_action is not None:
+ result += [
+ "\n",
+ self.dump_resolve_reference_action(
+ m.reference.short_name
+ for m in models
+ if m.path in require_update_action_models
+ ),
+ ]
+ if not result and not init:
+ continue
+ body = "\n".join(result)
+ if code_formatter:
+ body = code_formatter.format_code(body)
+
+ results[module] = base.Result(
+ body=body, source=models[0].file_path if models else None
+ )
+
+ # retain existing behaviour
+ if [*results] == [("__init__.py",)]:
+ return results["__init__.py",].body
+
+ results = {tuple(i.replace("-", "_") for i in k): v for k, v in results.items()}
+ return (
+ self._Parser__postprocess_result_modules(results) # type: ignore[attr-defined]
+ if self.treat_dot_as_module
+ else {
+ tuple(
+ (
+ part[: part.rfind(".")].replace(".", "_")
+ + part[part.rfind(".") :]
+ )
+ for part in k
+ ): v
+ for k, v in results.items()
+ }
+ )
+
+ base.Parser.parse = _parse # type: ignore[method-assign]
+ base.Parser.__alias_shadowed_imports = __alias_shadowed_imports # type: ignore[attr-defined]
+ logger.info("Patched Parser.parse method.")
diff --git a/fastapi_code_generator/visitors/imports.py b/fastapi_code_generator/visitors/imports.py
index e7ea2225..b1b37be6 100644
--- a/fastapi_code_generator/visitors/imports.py
+++ b/fastapi_code_generator/visitors/imports.py
@@ -32,6 +32,9 @@ def get_imports(parser: OpenAPIParser, model_path: Path) -> Dict[str, object]:
)
for from_, imports_ in parser.imports_for_fastapi.items():
imports[from_].update(imports_)
+ for operation in parser.operations.values():
+ if operation.imports:
+ imports.alias.update(operation.imports.alias)
return {'imports': imports}
diff --git a/poetry.lock b/poetry.lock
index 98e1a4fe..e8b87169 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,4 +1,4 @@
-# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
+# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand.
[[package]]
name = "annotated-types"
@@ -6,20 +6,19 @@ version = "0.7.0"
description = "Reusable constraint types to use with typing.Annotated"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
{file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
]
-[package.dependencies]
-typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""}
-
[[package]]
name = "anyio"
version = "4.4.0"
description = "High level compatibility layer for multiple asynchronous event loop implementations"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"},
{file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"},
@@ -33,7 +32,7 @@ typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""}
[package.extras]
doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
-test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
+test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\""]
trio = ["trio (>=0.23)"]
[[package]]
@@ -42,6 +41,7 @@ version = "3.4.0"
description = "Bash tab completion for argparse"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "argcomplete-3.4.0-py3-none-any.whl", hash = "sha256:69a79e083a716173e5532e0fa3bef45f793f4e61096cf52b5a42c0211c8b8aa5"},
{file = "argcomplete-3.4.0.tar.gz", hash = "sha256:c2abcdfe1be8ace47ba777d4fce319eb13bf8ad9dace8d085dcad6eded88057f"},
@@ -56,6 +56,7 @@ version = "24.4.2"
description = "The uncompromising code formatter."
optional = false
python-versions = ">=3.8"
+groups = ["main", "dev"]
files = [
{file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"},
{file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"},
@@ -92,7 +93,7 @@ typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
[package.extras]
colorama = ["colorama (>=0.4.3)"]
-d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"]
+d = ["aiohttp (>=3.7.4) ; sys_platform != \"win32\" or implementation_name != \"pypy\"", "aiohttp (>=3.7.4,!=3.9.0) ; sys_platform == \"win32\" and implementation_name == \"pypy\""]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
uvloop = ["uvloop (>=0.15.2)"]
@@ -102,6 +103,7 @@ version = "2024.7.4"
description = "Python package for providing Mozilla's CA Bundle."
optional = false
python-versions = ">=3.6"
+groups = ["main"]
files = [
{file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"},
{file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"},
@@ -113,6 +115,7 @@ version = "8.1.7"
description = "Composable command line interface toolkit"
optional = false
python-versions = ">=3.7"
+groups = ["main", "dev"]
files = [
{file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
{file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
@@ -127,10 +130,12 @@ version = "0.4.6"
description = "Cross-platform colored terminal text."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+groups = ["main", "dev"]
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
+markers = {main = "platform_system == \"Windows\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\""}
[[package]]
name = "coverage"
@@ -138,6 +143,7 @@ version = "7.5.3"
description = "Code coverage measurement for Python"
optional = false
python-versions = ">=3.8"
+groups = ["dev"]
files = [
{file = "coverage-7.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a6519d917abb15e12380406d721e37613e2a67d166f9fb7e5a8ce0375744cd45"},
{file = "coverage-7.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aea7da970f1feccf48be7335f8b2ca64baf9b589d79e05b9397a06696ce1a1ec"},
@@ -197,77 +203,40 @@ files = [
tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
[package.extras]
-toml = ["tomli"]
+toml = ["tomli ; python_full_version <= \"3.11.0a6\""]
[[package]]
name = "datamodel-code-generator"
-version = "0.25.6"
+version = "0.28.5"
description = "Datamodel Code Generator"
optional = false
-python-versions = "<4.0,>=3.7"
+python-versions = ">=3.9"
+groups = ["main"]
files = [
- {file = "datamodel_code_generator-0.25.6-py3-none-any.whl", hash = "sha256:adbdb485a713a7035d7260c28d3f280e598b0eb3170d2361cb92431a44c4d154"},
- {file = "datamodel_code_generator-0.25.6.tar.gz", hash = "sha256:466f56de876947b73d4a4cd0c947ce37f63f68f2f6c0ce1477045d1e6e495da5"},
+ {file = "datamodel_code_generator-0.28.5-py3-none-any.whl", hash = "sha256:f899c1da5af04b5d5b6e3edbd718c1bf3a00fc4b2fe8210cef609d93a9983e9e"},
+ {file = "datamodel_code_generator-0.28.5.tar.gz", hash = "sha256:20e8b817d301d2d0bb15f436e81c97b25ad1c2ef922c99249c2444141ae15a6a"},
]
[package.dependencies]
-argcomplete = ">=1.10,<4.0"
+argcomplete = ">=2.10.1,<4"
black = ">=19.10b0"
-genson = ">=1.2.1,<2.0"
-httpx = {version = "*", optional = true, markers = "extra == \"http\""}
-inflect = ">=4.1.0,<6.0"
-isort = ">=4.3.21,<6.0"
-jinja2 = ">=2.10.1,<4.0"
+genson = ">=1.2.1,<2"
+httpx = {version = ">=0.24.1", optional = true, markers = "extra == \"http\""}
+inflect = ">=4.1,<6"
+isort = ">=4.3.21,<7"
+jinja2 = ">=2.10.1,<4"
packaging = "*"
-pydantic = [
- {version = ">=1.10.0,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.4.0 || >2.4.0,<3.0", extras = ["email"], markers = "python_version >= \"3.12\" and python_version < \"4.0\""},
- {version = ">=1.10.0,<2.4.0 || >2.4.0,<3.0", extras = ["email"], markers = "python_version >= \"3.11\" and python_version < \"3.12\""},
- {version = ">=1.5.1,<2.4.0 || >2.4.0,<3.0", extras = ["email"], markers = "python_version < \"3.10\""},
- {version = ">=1.9.0,<2.4.0 || >2.4.0,<3.0", extras = ["email"], markers = "python_version >= \"3.10\" and python_version < \"3.11\""},
-]
+pydantic = ">=1.5"
pyyaml = ">=6.0.1"
-toml = {version = ">=0.10.0,<1.0.0", markers = "python_version < \"3.11\""}
-
-[package.extras]
-debug = ["PySnooper (>=0.4.1,<2.0.0)"]
-graphql = ["graphql-core (>=3.2.3,<4.0.0)"]
-http = ["httpx"]
-validation = ["openapi-spec-validator (>=0.2.8,<0.7.0)", "prance (>=0.18.2)"]
-
-[[package]]
-name = "dnspython"
-version = "2.6.1"
-description = "DNS toolkit"
-optional = false
-python-versions = ">=3.8"
-files = [
- {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"},
- {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"},
-]
+tomli = {version = ">=2.2.1,<3", markers = "python_version <= \"3.11\""}
[package.extras]
-dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "sphinx (>=7.2.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"]
-dnssec = ["cryptography (>=41)"]
-doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"]
-doq = ["aioquic (>=0.9.25)"]
-idna = ["idna (>=3.6)"]
-trio = ["trio (>=0.23)"]
-wmi = ["wmi (>=1.5.1)"]
-
-[[package]]
-name = "email-validator"
-version = "2.1.2"
-description = "A robust email address syntax and deliverability validation library."
-optional = false
-python-versions = ">=3.8"
-files = [
- {file = "email_validator-2.1.2-py3-none-any.whl", hash = "sha256:d89f6324e13b1e39889eab7f9ca2f91dc9aebb6fa50a6d8bd4329ab50f251115"},
- {file = "email_validator-2.1.2.tar.gz", hash = "sha256:14c0f3d343c4beda37400421b39fa411bbe33a75df20825df73ad53e06a9f04c"},
-]
-
-[package.dependencies]
-dnspython = ">=2.0.0"
-idna = ">=2.0.0"
+all = ["graphql-core (>=3.2.3)", "httpx (>=0.24.1)", "openapi-spec-validator (>=0.2.8,<0.7)", "prance (>=0.18.2)", "pysnooper (>=0.4.1,<2)", "ruff (>=0.9.10)"]
+debug = ["pysnooper (>=0.4.1,<2)"]
+graphql = ["graphql-core (>=3.2.3)"]
+http = ["httpx (>=0.24.1)"]
+ruff = ["ruff (>=0.9.10)"]
+validation = ["openapi-spec-validator (>=0.2.8,<0.7)", "prance (>=0.18.2)"]
[[package]]
name = "exceptiongroup"
@@ -275,6 +244,8 @@ version = "1.2.1"
description = "Backport of PEP 654 (exception groups)"
optional = false
python-versions = ">=3.7"
+groups = ["main", "dev"]
+markers = "python_version < \"3.11\""
files = [
{file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"},
{file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"},
@@ -289,6 +260,7 @@ version = "1.5.1"
description = "Let your Python tests travel through time"
optional = false
python-versions = ">=3.7"
+groups = ["dev"]
files = [
{file = "freezegun-1.5.1-py3-none-any.whl", hash = "sha256:bf111d7138a8abe55ab48a71755673dbaa4ab87f4cff5634a4442dfec34c15f1"},
{file = "freezegun-1.5.1.tar.gz", hash = "sha256:b29dedfcda6d5e8e083ce71b2b542753ad48cfec44037b3fc79702e2980a89e9"},
@@ -303,6 +275,7 @@ version = "1.3.0"
description = "GenSON is a powerful, user-friendly JSON Schema generator."
optional = false
python-versions = "*"
+groups = ["main"]
files = [
{file = "genson-1.3.0-py3-none-any.whl", hash = "sha256:468feccd00274cc7e4c09e84b08704270ba8d95232aa280f65b986139cec67f7"},
{file = "genson-1.3.0.tar.gz", hash = "sha256:e02db9ac2e3fd29e65b5286f7135762e2cd8a986537c075b06fc5f1517308e37"},
@@ -314,6 +287,7 @@ version = "0.14.0"
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
optional = false
python-versions = ">=3.7"
+groups = ["main"]
files = [
{file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
@@ -325,6 +299,7 @@ version = "1.0.5"
description = "A minimal low-level HTTP client."
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"},
{file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"},
@@ -346,6 +321,7 @@ version = "0.27.0"
description = "The next generation HTTP client."
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"},
{file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"},
@@ -359,7 +335,7 @@ idna = "*"
sniffio = "*"
[package.extras]
-brotli = ["brotli", "brotlicffi"]
+brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""]
cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
@@ -370,6 +346,7 @@ version = "3.7"
description = "Internationalized Domain Names in Applications (IDNA)"
optional = false
python-versions = ">=3.5"
+groups = ["main"]
files = [
{file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"},
{file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
@@ -381,6 +358,7 @@ version = "5.6.2"
description = "Correctly generate plurals, singular nouns, ordinals, indefinite articles; convert numbers to words"
optional = false
python-versions = ">=3.7"
+groups = ["main"]
files = [
{file = "inflect-5.6.2-py3-none-any.whl", hash = "sha256:b45d91a4a28a4e617ff1821117439b06eaa86e2a4573154af0149e9be6687238"},
{file = "inflect-5.6.2.tar.gz", hash = "sha256:aadc7ed73928f5e014129794bbac03058cca35d0a973a5fc4eb45c7fa26005f9"},
@@ -388,7 +366,7 @@ files = [
[package.extras]
docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"]
-testing = ["pygments", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"]
+testing = ["pygments", "pytest (>=6)", "pytest-black (>=0.3.7) ; platform_python_implementation != \"PyPy\"", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1) ; platform_python_implementation != \"PyPy\""]
[[package]]
name = "iniconfig"
@@ -396,6 +374,7 @@ version = "2.0.0"
description = "brain-dead simple config-ini parsing"
optional = false
python-versions = ">=3.7"
+groups = ["dev"]
files = [
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
@@ -407,6 +386,7 @@ version = "5.13.2"
description = "A Python utility / library to sort Python imports."
optional = false
python-versions = ">=3.8.0"
+groups = ["main", "dev"]
files = [
{file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"},
{file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"},
@@ -421,6 +401,7 @@ version = "3.1.4"
description = "A very fast and expressive template engine."
optional = false
python-versions = ">=3.7"
+groups = ["main"]
files = [
{file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"},
{file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"},
@@ -438,6 +419,7 @@ version = "3.0.0"
description = "Python port of markdown-it. Markdown parsing, done right!"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"},
{file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"},
@@ -462,6 +444,7 @@ version = "2.1.5"
description = "Safely add untrusted strings to HTML/XML markup."
optional = false
python-versions = ">=3.7"
+groups = ["main"]
files = [
{file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"},
{file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"},
@@ -531,6 +514,7 @@ version = "0.1.2"
description = "Markdown URL utilities"
optional = false
python-versions = ">=3.7"
+groups = ["main"]
files = [
{file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
@@ -542,6 +526,7 @@ version = "1.10.0"
description = "Optional static typing for Python"
optional = false
python-versions = ">=3.8"
+groups = ["dev"]
files = [
{file = "mypy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da1cbf08fb3b851ab3b9523a884c232774008267b1f83371ace57f412fe308c2"},
{file = "mypy-1.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:12b6bfc1b1a66095ab413160a6e520e1dc076a28f3e22f7fb25ba3b000b4ef99"},
@@ -589,6 +574,7 @@ version = "1.0.0"
description = "Type system extensions for programs checked with the mypy type checker."
optional = false
python-versions = ">=3.5"
+groups = ["main", "dev"]
files = [
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
@@ -600,6 +586,7 @@ version = "24.1"
description = "Core utilities for Python packages"
optional = false
python-versions = ">=3.8"
+groups = ["main", "dev"]
files = [
{file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"},
{file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
@@ -611,6 +598,7 @@ version = "0.12.1"
description = "Utility library for gitignore style pattern matching of file paths."
optional = false
python-versions = ">=3.8"
+groups = ["main", "dev"]
files = [
{file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
{file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
@@ -622,6 +610,7 @@ version = "4.2.2"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
optional = false
python-versions = ">=3.8"
+groups = ["main", "dev"]
files = [
{file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"},
{file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"},
@@ -638,6 +627,7 @@ version = "1.5.0"
description = "plugin and hook calling mechanisms for python"
optional = false
python-versions = ">=3.8"
+groups = ["dev"]
files = [
{file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
{file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
@@ -653,6 +643,7 @@ version = "2.8.2"
description = "Data validation using Python type hints"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"},
{file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"},
@@ -660,12 +651,8 @@ files = [
[package.dependencies]
annotated-types = ">=0.4.0"
-email-validator = {version = ">=2.0.0", optional = true, markers = "extra == \"email\""}
pydantic-core = "2.20.1"
-typing-extensions = [
- {version = ">=4.12.2", markers = "python_version >= \"3.13\""},
- {version = ">=4.6.1", markers = "python_version < \"3.13\""},
-]
+typing-extensions = {version = ">=4.6.1", markers = "python_version < \"3.13\""}
[package.extras]
email = ["email-validator (>=2.0.0)"]
@@ -676,6 +663,7 @@ version = "2.20.1"
description = "Core functionality for Pydantic validation and serialization"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"},
{file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"},
@@ -777,6 +765,7 @@ version = "2.18.0"
description = "Pygments is a syntax highlighting package written in Python."
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"},
{file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"},
@@ -791,6 +780,7 @@ version = "1.1.1"
description = "A poor man's debugger for Python."
optional = false
python-versions = "*"
+groups = ["main"]
files = [
{file = "PySnooper-1.1.1-py2.py3-none-any.whl", hash = "sha256:378f13d731a3e04d3d0350e5f295bdd0f1b49fc8a8b8bf2067fe1e5290bd20be"},
{file = "PySnooper-1.1.1.tar.gz", hash = "sha256:d17dc91cca1593c10230dce45e46b1d3ff0f8910f0c38e941edf6ba1260b3820"},
@@ -805,6 +795,7 @@ version = "8.2.2"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.8"
+groups = ["dev"]
files = [
{file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"},
{file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"},
@@ -827,6 +818,7 @@ version = "5.0.0"
description = "Pytest plugin for measuring coverage."
optional = false
python-versions = ">=3.8"
+groups = ["dev"]
files = [
{file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"},
{file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"},
@@ -845,6 +837,7 @@ version = "3.14.0"
description = "Thin-wrapper around the mock package for easier use with pytest"
optional = false
python-versions = ">=3.8"
+groups = ["dev"]
files = [
{file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"},
{file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"},
@@ -862,6 +855,7 @@ version = "2.9.0.post0"
description = "Extensions to the standard Python datetime module"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+groups = ["dev"]
files = [
{file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"},
{file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"},
@@ -876,6 +870,7 @@ version = "6.0.1"
description = "YAML parser and emitter for Python"
optional = false
python-versions = ">=3.6"
+groups = ["main"]
files = [
{file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"},
{file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"},
@@ -936,6 +931,7 @@ version = "13.7.1"
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
optional = false
python-versions = ">=3.7.0"
+groups = ["main"]
files = [
{file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"},
{file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"},
@@ -944,7 +940,6 @@ files = [
[package.dependencies]
markdown-it-py = ">=2.2.0"
pygments = ">=2.13.0,<3.0.0"
-typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""}
[package.extras]
jupyter = ["ipywidgets (>=7.5.1,<9)"]
@@ -955,6 +950,7 @@ version = "1.5.4"
description = "Tool to Detect Surrounding Shell"
optional = false
python-versions = ">=3.7"
+groups = ["main"]
files = [
{file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"},
{file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"},
@@ -966,6 +962,7 @@ version = "1.16.0"
description = "Python 2 and 3 compatibility utilities"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+groups = ["dev"]
files = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
@@ -977,6 +974,7 @@ version = "1.3.1"
description = "Sniff out which async library your code is running under"
optional = false
python-versions = ">=3.7"
+groups = ["main"]
files = [
{file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
@@ -988,31 +986,53 @@ version = "1.2.0"
description = "String case converter."
optional = false
python-versions = "*"
+groups = ["main"]
files = [
{file = "stringcase-1.2.0.tar.gz", hash = "sha256:48a06980661908efe8d9d34eab2b6c13aefa2163b3ced26972902e3bdfd87008"},
]
-[[package]]
-name = "toml"
-version = "0.10.2"
-description = "Python Library for Tom's Obvious, Minimal Language"
-optional = false
-python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
-files = [
- {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
- {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
-]
-
[[package]]
name = "tomli"
-version = "2.0.1"
+version = "2.2.1"
description = "A lil' TOML parser"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
+groups = ["main", "dev"]
files = [
- {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
- {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
+ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
+ {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
+ {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"},
+ {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"},
+ {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"},
+ {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"},
+ {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"},
+ {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"},
+ {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"},
+ {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"},
+ {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"},
+ {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"},
+ {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"},
+ {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"},
+ {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"},
+ {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"},
+ {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"},
+ {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"},
+ {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"},
+ {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"},
+ {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"},
+ {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"},
+ {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"},
+ {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"},
+ {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"},
+ {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"},
+ {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"},
+ {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"},
+ {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"},
+ {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"},
+ {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"},
+ {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"},
]
+markers = {main = "python_version <= \"3.11\"", dev = "python_full_version <= \"3.11.0a6\""}
[[package]]
name = "typed-ast"
@@ -1020,6 +1040,7 @@ version = "1.5.5"
description = "a fork of Python 2 and 3 ast modules with type comment support"
optional = false
python-versions = ">=3.6"
+groups = ["main"]
files = [
{file = "typed_ast-1.5.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4bc1efe0ce3ffb74784e06460f01a223ac1f6ab31c6bc0376a21184bf5aabe3b"},
{file = "typed_ast-1.5.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f7a8c46a8b333f71abd61d7ab9255440d4a588f34a21f126bbfc95f6049e686"},
@@ -1070,6 +1091,7 @@ version = "0.12.3"
description = "Typer, build great CLIs. Easy to code. Based on Python type hints."
optional = false
python-versions = ">=3.7"
+groups = ["main"]
files = [
{file = "typer-0.12.3-py3-none-any.whl", hash = "sha256:070d7ca53f785acbccba8e7d28b08dcd88f79f1fbda035ade0aecec71ca5c914"},
{file = "typer-0.12.3.tar.gz", hash = "sha256:49e73131481d804288ef62598d97a1ceef3058905aa536a1134f90891ba35482"},
@@ -1087,12 +1109,13 @@ version = "4.12.2"
description = "Backported and Experimental Type Hints for Python 3.8+"
optional = false
python-versions = ">=3.8"
+groups = ["main", "dev"]
files = [
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
]
[metadata]
-lock-version = "2.0"
-python-versions = "^3.8.0"
-content-hash = "fc9627faa9f8f59f49a6a7e94e5f94576d2df77e39108d7ac39066814fafc933"
+lock-version = "2.1"
+python-versions = ">=3.9,<3.13"
+content-hash = "4c877a8538b055a678c7b0389bbe91cb862a0ecbc41d8fbd843b43be7999b23d"
diff --git a/pyproject.toml b/pyproject.toml
index c6de822b..d3726ecc 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -13,7 +13,6 @@ classifiers = [
"Natural Language :: English",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
- "Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
@@ -30,9 +29,9 @@ pattern = "^(?P\\d+\\.\\d+\\.\\d+)(-?((?P[a-zA-Z]+)\\.?(?P=3.9,<3.13"
typer = {extras = ["all"], version = ">=0.2.1,<0.13.0"}
-datamodel-code-generator = {extras = ["http"], version = "0.25.6"}
+datamodel-code-generator = {extras = ["http"], version = "0.28.5"}
stringcase = "^1.2.0"
PySnooper = ">=0.4.1,<1.2.0"
jinja2 = ">=2.11.2,<4.0.0"
@@ -45,7 +44,7 @@ typed-ast = [
#mkdocs = {version = "1.1.2", optional = true}
#mkdocs-material = {version = "5.2.3", optional = true}
-[tool.poetry.dev-dependencies]
+[tool.poetry.group.dev.dependencies]
pytest = "^8.2"
pytest-cov = "*"
pytest-mock = "*"
@@ -61,7 +60,7 @@ isort = "==5.13.2"
[tool.black]
line-length = 88
skip-string-normalization = true
-target-version = ['py38']
+target-version = ['py39']
exclude = '(tests/data|\.eggs|\.git|\.hg|\.mypy_cache|\.nox|\.tox|\.venv|_build|buck-out|build|dist|.*\/models\.py.*|.*\/models\/.*)'
[tool.isort]
diff --git a/tests/data/expected/openapi/default_template/discriminator_in_root_with_properties/main.py b/tests/data/expected/openapi/default_template/discriminator_in_root_with_properties/main.py
new file mode 100644
index 00000000..ed31e607
--- /dev/null
+++ b/tests/data/expected/openapi/default_template/discriminator_in_root_with_properties/main.py
@@ -0,0 +1,13 @@
+# generated by fastapi-codegen:
+# filename: discriminator_in_root_with_properties.yaml
+# timestamp: 2020-06-19T00:00:00+00:00
+
+from __future__ import annotations
+
+from fastapi import FastAPI
+
+app = FastAPI(
+ title='discriminator_in_root_with_properties',
+ version='1.0.0',
+ servers=[{'url': 'https://your-domain.atlassian.net'}],
+)
diff --git a/tests/data/expected/openapi/default_template/discriminator_in_root_with_properties/models.py b/tests/data/expected/openapi/default_template/discriminator_in_root_with_properties/models.py
new file mode 100644
index 00000000..f1018102
--- /dev/null
+++ b/tests/data/expected/openapi/default_template/discriminator_in_root_with_properties/models.py
@@ -0,0 +1,43 @@
+# generated by fastapi-codegen:
+# filename: discriminator_in_root_with_properties.yaml
+# timestamp: 2020-06-19T00:00:00+00:00
+
+from __future__ import annotations
+
+from typing import Literal, Optional, Union
+
+from pydantic import BaseModel, Extra, Field
+
+
+class IssueContextVariable(BaseModel):
+ id: Optional[int] = Field(None, description='The issue ID.')
+ key: Optional[str] = Field(None, description='The issue key.')
+ type: str = Field(..., description='Type of custom context variable.')
+
+
+class UserContextVariable(BaseModel):
+ accountId: str = Field(..., description='The account ID of the user.')
+ type: str = Field(..., description='Type of custom context variable.')
+
+
+class CustomContextVariable1(UserContextVariable):
+ class Config:
+ extra = Extra.forbid
+
+ type: Literal['user'] = Field(..., description='Type of custom context variable.')
+
+
+class CustomContextVariable2(IssueContextVariable):
+ class Config:
+ extra = Extra.forbid
+
+ type: Literal['issue'] = Field(..., description='Type of custom context variable.')
+
+
+class CustomContextVariable(BaseModel):
+ class Config:
+ extra = Extra.forbid
+
+ __root__: Union[CustomContextVariable1, CustomContextVariable2] = Field(
+ ..., discriminator='type'
+ )
diff --git a/tests/data/expected/openapi/default_template/duplicate_anonymus_parameter/main.py b/tests/data/expected/openapi/default_template/duplicate_anonymus_parameter/main.py
new file mode 100644
index 00000000..69ec27f7
--- /dev/null
+++ b/tests/data/expected/openapi/default_template/duplicate_anonymus_parameter/main.py
@@ -0,0 +1,45 @@
+# generated by fastapi-codegen:
+# filename: duplicate_anonymus_parameter.yaml
+# timestamp: 2020-06-19T00:00:00+00:00
+
+from __future__ import annotations
+
+from typing import Optional, Union
+
+from fastapi import FastAPI
+
+from .models import HTTPValidationError, SpreadsheetId
+
+app = FastAPI(
+ title='test',
+ version='0.1.0',
+ servers=[
+ {'url': 'http://locahost:8000', 'description': 'Local development server'}
+ ],
+)
+
+
+@app.get(
+ '/get-sheet', response_model=str, responses={'422': {'model': HTTPValidationError}}
+)
+def get_sheet_get_sheet_get(
+ spreadsheet_id: Optional[SpreadsheetId] = 'none',
+) -> Union[str, HTTPValidationError]:
+ """
+ Get Sheet
+ """
+ pass
+
+
+@app.post(
+ '/update-sheet',
+ response_model=str,
+ responses={'422': {'model': HTTPValidationError}},
+)
+def update_sheet_update_sheet_post(
+ spreadsheet_id: Optional[SpreadsheetId] = 'none',
+) -> Union[str, HTTPValidationError]:
+ """
+ Update Sheet
+ """
+ pass
diff --git a/tests/data/expected/openapi/default_template/duplicate_anonymus_parameter/models.py b/tests/data/expected/openapi/default_template/duplicate_anonymus_parameter/models.py
new file mode 100644
index 00000000..22d8094e
--- /dev/null
+++ b/tests/data/expected/openapi/default_template/duplicate_anonymus_parameter/models.py
@@ -0,0 +1,25 @@
+# generated by fastapi-codegen:
+# filename: duplicate_anonymus_parameter.yaml
+# timestamp: 2020-06-19T00:00:00+00:00
+
+from __future__ import annotations
+
+from enum import Enum
+from typing import List, Optional, Union
+
+from pydantic import BaseModel, Field
+
+
+class ValidationError(BaseModel):
+ loc: List[Union[str, int]] = Field(..., title='Location')
+ msg: str = Field(..., title='Message')
+ type: str = Field(..., title='Error Type')
+
+
+class SpreadsheetId(Enum):
+ cards = 'cards'
+ none = 'none'
+
+
+class HTTPValidationError(BaseModel):
+ detail: Optional[List[ValidationError]] = Field(None, title='Detail')
diff --git a/tests/data/expected/openapi/default_template/duplicate_param/main.py b/tests/data/expected/openapi/default_template/duplicate_param/main.py
new file mode 100644
index 00000000..a40a5466
--- /dev/null
+++ b/tests/data/expected/openapi/default_template/duplicate_param/main.py
@@ -0,0 +1,24 @@
+# generated by fastapi-codegen:
+# filename: duplicate_param.yaml
+# timestamp: 2020-06-19T00:00:00+00:00
+
+from __future__ import annotations
+
+from typing import Union
+
+from fastapi import FastAPI
+from pydantic import constr
+
+app = FastAPI(
+ title='REST API',
+ version='0.0.1',
+ servers=[{'url': 'https://api.something.com/1'}],
+)
+
+
+@app.delete('/boards/{id}', response_model=None)
+def delete_boards_id(id: Union[str, constr(regex=r'^[0-9a-fA-F]{24}$')]) -> None:
+ """
+ Delete a Board
+ """
+ pass
diff --git a/tests/data/expected/openapi/default_template/duplicate_param/models.py b/tests/data/expected/openapi/default_template/duplicate_param/models.py
new file mode 100644
index 00000000..ecb52027
--- /dev/null
+++ b/tests/data/expected/openapi/default_template/duplicate_param/models.py
@@ -0,0 +1,3 @@
+# generated by fastapi-codegen:
+# filename: duplicate_param.yaml
+# timestamp: 2020-06-19T00:00:00+00:00
diff --git a/tests/data/expected/openapi/default_template/modular_reference/main.py b/tests/data/expected/openapi/default_template/modular_reference/main.py
new file mode 100644
index 00000000..f73fde10
--- /dev/null
+++ b/tests/data/expected/openapi/default_template/modular_reference/main.py
@@ -0,0 +1,24 @@
+# generated by fastapi-codegen:
+# filename: modular_reference.yaml
+# timestamp: 2020-06-19T00:00:00+00:00
+
+from __future__ import annotations
+
+from fastapi import FastAPI, Path
+
+from .models.pets import Pet
+
+app = FastAPI(
+ version='1.0.0',
+ title='modular_reference_template',
+ license={'name': 'MIT'},
+ servers=[{'url': 'http://petstore.swagger.io/v1'}],
+)
+
+
+@app.get('/pets/{petId}', response_model=Pet, tags=['pets'])
+def show_pet_by_id(pet_id: str = Path(..., alias='petId')) -> Pet:
+ """
+ Info for a specific pet
+ """
+ pass
diff --git a/tests/data/expected/openapi/default_template/modular_reference/models/__init__.py b/tests/data/expected/openapi/default_template/modular_reference/models/__init__.py
new file mode 100644
index 00000000..66a71bb0
--- /dev/null
+++ b/tests/data/expected/openapi/default_template/modular_reference/models/__init__.py
@@ -0,0 +1,3 @@
+# generated by fastapi-codegen:
+# filename: modular_reference.yaml
+# timestamp: 2020-06-19T00:00:00+00:00
diff --git a/tests/data/expected/openapi/default_template/modular_reference/models/pets.py b/tests/data/expected/openapi/default_template/modular_reference/models/pets.py
new file mode 100644
index 00000000..9fcfc5a3
--- /dev/null
+++ b/tests/data/expected/openapi/default_template/modular_reference/models/pets.py
@@ -0,0 +1,15 @@
+# generated by fastapi-codegen:
+# filename: modular_reference.yaml
+# timestamp: 2020-06-19T00:00:00+00:00
+
+from __future__ import annotations
+
+from typing import Optional
+
+from pydantic import BaseModel
+
+
+class Pet(BaseModel):
+ id: int
+ name: str
+ tag: Optional[str] = None
diff --git a/tests/data/expected/openapi/default_template/recursion/main.py b/tests/data/expected/openapi/default_template/recursion/main.py
new file mode 100644
index 00000000..a33633bf
--- /dev/null
+++ b/tests/data/expected/openapi/default_template/recursion/main.py
@@ -0,0 +1,20 @@
+# generated by fastapi-codegen:
+# filename: recursion.yaml
+# timestamp: 2020-06-19T00:00:00+00:00
+
+from __future__ import annotations
+
+from fastapi import FastAPI
+
+from .models import ID
+
+app = FastAPI(
+ title='REST API',
+ version='0.0.1',
+ servers=[{'url': 'https://api.something.com/1'}],
+)
+
+
+@app.get('/actions/{id}', response_model=None)
+def get_actions_id(id: ID) -> None:
+ pass
diff --git a/tests/data/expected/openapi/default_template/recursion/models.py b/tests/data/expected/openapi/default_template/recursion/models.py
new file mode 100644
index 00000000..f6005120
--- /dev/null
+++ b/tests/data/expected/openapi/default_template/recursion/models.py
@@ -0,0 +1,32 @@
+# generated by fastapi-codegen:
+# filename: recursion.yaml
+# timestamp: 2020-06-19T00:00:00+00:00
+
+from __future__ import annotations
+
+from typing import Optional
+
+from pydantic import BaseModel, Field, constr
+
+
+class ID(BaseModel):
+ __root__: constr(regex=r'^[0-9a-fA-F]{24}$') = Field(
+ ..., example='5abbe4b7ddc1b351ef961414'
+ )
+
+
+class Card(BaseModel):
+ id: Optional[ID] = None
+
+
+class Board(BaseModel):
+ id: Optional[ID] = None
+
+
+class Data(BaseModel):
+ card: Optional[Card] = None
+ board: Optional[Board] = None
+
+
+class Action(BaseModel):
+ data: Optional[Data] = None
diff --git a/tests/data/expected/openapi/default_template/shadowed_imports/main.py b/tests/data/expected/openapi/default_template/shadowed_imports/main.py
new file mode 100644
index 00000000..fb97c255
--- /dev/null
+++ b/tests/data/expected/openapi/default_template/shadowed_imports/main.py
@@ -0,0 +1,21 @@
+# generated by fastapi-codegen:
+# filename: shadowed_imports.yaml
+# timestamp: 2020-06-19T00:00:00+00:00
+
+from __future__ import annotations
+
+from datetime import date as date_aliased
+from typing import Optional
+
+from fastapi import FastAPI
+
+app = FastAPI(
+ title='REST API',
+ version='0.0.1',
+ servers=[{'url': 'https://api.something.com/1'}],
+)
+
+
+@app.get('/actions/', response_model=None)
+def get_actions_(due: Optional[date_aliased] = None) -> None:
+ pass
diff --git a/tests/data/expected/openapi/default_template/shadowed_imports/models.py b/tests/data/expected/openapi/default_template/shadowed_imports/models.py
new file mode 100644
index 00000000..65108846
--- /dev/null
+++ b/tests/data/expected/openapi/default_template/shadowed_imports/models.py
@@ -0,0 +1,15 @@
+# generated by fastapi-codegen:
+# filename: shadowed_imports.yaml
+# timestamp: 2020-06-19T00:00:00+00:00
+
+from __future__ import annotations
+
+from datetime import date as date_aliased
+from typing import Optional
+
+from pydantic import BaseModel, Field
+
+
+class MarketingOptIn(BaseModel):
+ optedIn: Optional[bool] = Field(None, example=False)
+ date: Optional[date_aliased] = Field(None, example='2018-04-26T17:03:25.155Z')
diff --git a/tests/data/openapi/default_template/discriminator_in_root_with_properties.yaml b/tests/data/openapi/default_template/discriminator_in_root_with_properties.yaml
new file mode 100644
index 00000000..1b9d7b1f
--- /dev/null
+++ b/tests/data/openapi/default_template/discriminator_in_root_with_properties.yaml
@@ -0,0 +1,54 @@
+info:
+ title: discriminator_in_root_with_properties
+ version: 1.0.0
+openapi: 3.0.1
+paths: {}
+servers:
+ - url: https://your-domain.atlassian.net
+tags: []
+components:
+ schemas:
+ CustomContextVariable:
+ additionalProperties: false
+ oneOf:
+ - $ref: '#/components/schemas/UserContextVariable'
+ - $ref: '#/components/schemas/IssueContextVariable'
+ properties:
+ type:
+ description: Type of custom context variable.
+ type: string
+ discriminator:
+ mapping:
+ issue: '#/components/schemas/IssueContextVariable'
+ user: '#/components/schemas/UserContextVariable'
+ propertyName: type
+ required:
+ - type
+ type: object
+ IssueContextVariable:
+ properties:
+ id:
+ description: The issue ID.
+ format: int64
+ type: integer
+ key:
+ description: The issue key.
+ type: string
+ type:
+ description: Type of custom context variable.
+ type: string
+ required:
+ - type
+ type: object
+ UserContextVariable:
+ properties:
+ accountId:
+ description: The account ID of the user.
+ type: string
+ type:
+ description: Type of custom context variable.
+ type: string
+ required:
+ - accountId
+ - type
+ type: object
\ No newline at end of file
diff --git a/tests/data/openapi/default_template/duplicate_anonymus_parameter.yaml b/tests/data/openapi/default_template/duplicate_anonymus_parameter.yaml
new file mode 100644
index 00000000..6ea436c9
--- /dev/null
+++ b/tests/data/openapi/default_template/duplicate_anonymus_parameter.yaml
@@ -0,0 +1,100 @@
+openapi: 3.1.0
+info:
+ title: test
+ version: 0.1.0
+servers:
+ - url: http://locahost:8000
+ description: Local development server
+paths:
+ /get-sheet:
+ get:
+ summary: Get Sheet
+ description: Get data from a Google Sheet
+ operationId: get_sheet_get_sheet_get
+ parameters:
+ - name: spreadsheet_id
+ in: query
+ required: false
+ schema:
+ type: string
+ default: none
+ enum:
+ - cards
+ - none
+ title: Spreadsheet Id
+ responses:
+ '200':
+ description: Successful Response
+ content:
+ application/json:
+ schema:
+ type: string
+ title: Response Get Sheet Get Sheet Get
+ '422':
+ description: Validation Error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/HTTPValidationError'
+ /update-sheet:
+ post:
+ summary: Update Sheet
+ description: Update data in a Google Sheet within the existing spreadsheet
+ operationId: update_sheet_update_sheet_post
+ parameters:
+ - name: spreadsheet_id
+ in: query
+ required: false
+ schema:
+ type: string
+ default: none
+ enum:
+ - cards
+ - none
+ title: Spreadsheet Id
+ responses:
+ '200':
+ description: Successful Response
+ content:
+ application/json:
+ schema:
+ type: string
+ title: Response Update Sheet Update Sheet Post
+ '422':
+ description: Validation Error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/HTTPValidationError'
+components:
+ schemas:
+ HTTPValidationError:
+ properties:
+ detail:
+ items:
+ $ref: '#/components/schemas/ValidationError'
+ type: array
+ title: Detail
+ type: object
+ title: HTTPValidationError
+ ValidationError:
+ properties:
+ loc:
+ items:
+ anyOf:
+ - type: string
+ - type: integer
+ type: array
+ title: Location
+ msg:
+ type: string
+ title: Message
+ type:
+ type: string
+ title: Error Type
+ type: object
+ required:
+ - loc
+ - msg
+ - type
+ title: ValidationError
diff --git a/tests/data/openapi/default_template/duplicate_param.yaml b/tests/data/openapi/default_template/duplicate_param.yaml
new file mode 100644
index 00000000..ae948ec2
--- /dev/null
+++ b/tests/data/openapi/default_template/duplicate_param.yaml
@@ -0,0 +1,39 @@
+openapi: 3.0.0
+info:
+ title: REST API
+ version: 0.0.1
+servers:
+ - url: https://api.something.com/1
+components:
+ securitySchemes: {}
+ schemas:
+ ID:
+ type: string
+ pattern: ^[0-9a-fA-F]{24}$
+ example: 5abbe4b7ddc1b351ef961414
+ responses: {}
+paths:
+ /boards/{id}:
+ parameters:
+ - name: id
+ in: path
+ description: ''
+ required: true
+ schema:
+ $ref: '#/components/schemas/ID'
+ delete:
+ tags: []
+ summary: Delete a Board
+ description: Delete a board.
+ operationId: delete-boards-id
+ parameters:
+ - name: id
+ in: path
+ description: The id of the board to delete
+ required: true
+ schema:
+ type: string
+ deprecated: false
+ responses:
+ '200':
+ description: Success
diff --git a/tests/data/openapi/default_template/modular_reference.yaml b/tests/data/openapi/default_template/modular_reference.yaml
new file mode 100644
index 00000000..61a4ed62
--- /dev/null
+++ b/tests/data/openapi/default_template/modular_reference.yaml
@@ -0,0 +1,44 @@
+openapi: 3.0.0
+info:
+ version: 1.0.0
+ title: modular_reference_template
+ license:
+ name: MIT
+servers:
+ - url: http://petstore.swagger.io/v1
+paths:
+ /pets/{petId}:
+ get:
+ summary: Info for a specific pet
+ operationId: showPetById
+ tags:
+ - pets
+ parameters:
+ - name: petId
+ in: path
+ required: true
+ description: The id of the pet to retrieve
+ schema:
+ type: string
+ responses:
+ '200':
+ description: Expected response to a valid request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/pets.Pet'
+components:
+ schemas:
+ pets.Pet:
+ type: object
+ required:
+ - id
+ - name
+ properties:
+ id:
+ type: integer
+ format: int64
+ name:
+ type: string
+ tag:
+ type: string
diff --git a/tests/data/openapi/default_template/recursion.yaml b/tests/data/openapi/default_template/recursion.yaml
new file mode 100644
index 00000000..be88f92e
--- /dev/null
+++ b/tests/data/openapi/default_template/recursion.yaml
@@ -0,0 +1,43 @@
+openapi: 3.0.0
+info:
+ title: REST API
+ version: 0.0.1
+servers:
+ - url: https://api.something.com/1
+components:
+ schemas:
+ Action:
+ type: object
+ properties:
+ data:
+ type: object
+ properties:
+ card:
+ type: object
+ properties:
+ id:
+ type: string
+ $ref: '#/components/schemas/ID'
+ board:
+ type: object
+ properties:
+ id:
+ type: string
+ $ref: '#/components/schemas/ID'
+ ID:
+ type: string
+ pattern: ^[0-9a-fA-F]{24}$
+ example: 5abbe4b7ddc1b351ef961414
+paths:
+ /actions/{id}:
+ parameters:
+ - name: id
+ in: path
+ description: The ID of the Action
+ required: true
+ schema:
+ $ref: '#/components/schemas/ID'
+ get:
+ responses:
+ '200':
+ description: Success
diff --git a/tests/data/openapi/default_template/shadowed_imports.yaml b/tests/data/openapi/default_template/shadowed_imports.yaml
new file mode 100644
index 00000000..1478f728
--- /dev/null
+++ b/tests/data/openapi/default_template/shadowed_imports.yaml
@@ -0,0 +1,33 @@
+openapi: 3.0.0
+info:
+ title: REST API
+ version: 0.0.1
+servers:
+ - url: https://api.something.com/1
+components:
+ schemas:
+ marketingOptIn:
+ type: object
+ properties:
+ optedIn:
+ type: boolean
+ example: false
+ date:
+ type: string
+ format: date
+ example: '2018-04-26T17:03:25.155Z'
+paths:
+ /actions/:
+ get:
+ parameters:
+ - name: due
+ in: query
+ description: A due date for the card
+ required: false
+ schema:
+ type: string
+ format: date
+ responses:
+ '200':
+ description: Success
+
diff --git a/tests/test_generate.py b/tests/test_generate.py
index c88cac61..0ef13f3b 100644
--- a/tests/test_generate.py
+++ b/tests/test_generate.py
@@ -26,7 +26,11 @@
@pytest.mark.parametrize(
- "oas_file", (DATA_DIR / OPEN_API_DEFAULT_TEMPLATE_DIR_NAME).glob("*.yaml")
+ "oas_file",
+ (DATA_DIR / OPEN_API_DEFAULT_TEMPLATE_DIR_NAME).glob("*.yaml"),
+ ids=[
+ id.stem for id in (DATA_DIR / OPEN_API_DEFAULT_TEMPLATE_DIR_NAME).glob("*.yaml")
+ ],
)
@freeze_time("2020-06-19")
def test_generate_default_template(oas_file):
@@ -40,8 +44,8 @@ def test_generate_default_template(oas_file):
template_dir=None,
)
expected_dir = EXPECTED_DIR / OPEN_API_DEFAULT_TEMPLATE_DIR_NAME / oas_file.stem
- output_files = sorted(list(output_dir.glob('*')))
- expected_files = sorted(list(expected_dir.glob('*')))
+ output_files = sorted(list(output_dir.glob('**/*.py')))
+ expected_files = sorted(list(expected_dir.glob('**/*.py')))
assert [f.name for f in output_files] == [f.name for f in expected_files]
for output_file, expected_file in zip(output_files, expected_files):
assert output_file.read_text() == expected_file.read_text(), oas_file