diff --git a/.changeset/fix_ruff_formatting_for_metanone.md b/.changeset/fix_ruff_formatting_for_metanone.md new file mode 100644 index 000000000..4ce3b7a3b --- /dev/null +++ b/.changeset/fix_ruff_formatting_for_metanone.md @@ -0,0 +1,14 @@ +--- +default: patch +--- + +# Fix Ruff formatting for `--meta=none` + +PR #940 fixes issue #939. Thanks @satwell! + +Due to the lack of `pyproject.toml`, Ruff was not getting configured properly when `--meta=none`. +As a result, it didn't clean up common generation issues like duplicate imports, which would then cause errors from +linters. + +This is now fixed by changing the default `post_hook` to `ruff check . --fix --extend-select=I` when `--meta=none`. +Using `generate --meta=none` should now be almost identical to the code generated by `update`. diff --git a/.changeset/include_the_up_rule_for_generated_ruff_config.md b/.changeset/include_the_up_rule_for_generated_ruff_config.md new file mode 100644 index 000000000..d9bc5c3a4 --- /dev/null +++ b/.changeset/include_the_up_rule_for_generated_ruff_config.md @@ -0,0 +1,8 @@ +--- +default: minor +--- + +# Include the `UP` rule for generated Ruff config + +This enables [pyupgrade-like improvements](https://docs.astral.sh/ruff/rules/#pyupgrade-up) which should replace some +`.format()` calls with f-strings. diff --git a/.github/check_for_changes.py b/.github/check_for_changes.py deleted file mode 100644 index 03b61358d..000000000 --- a/.github/check_for_changes.py +++ /dev/null @@ -1,11 +0,0 @@ -import subprocess -import sys - -output = subprocess.run(["git", "status", "--porcelain"], capture_output=True, check=True).stdout - -if output == b"": - # No changes - sys.exit(0) - -print(output) -sys.exit(1) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index a0b22cd6e..f99882974 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -149,11 +149,6 @@ jobs: pip install pdm python -m venv .venv pdm install - - name: Regenerate Integration Client - run: | - pdm run openapi-python-client update --url http://localhost:3000/openapi.json --config integration-tests-config.yaml --meta pdm - - name: Check for any file changes - run: python .github/check_for_changes.py - name: Cache Generated Client Dependencies uses: actions/cache@v3 with: diff --git a/end_to_end_tests/custom_post_hooks.config.yml b/end_to_end_tests/custom_post_hooks.config.yml new file mode 100644 index 000000000..956672905 --- /dev/null +++ b/end_to_end_tests/custom_post_hooks.config.yml @@ -0,0 +1,2 @@ +post_hooks: + - echo "this should fail" && exit 1 \ No newline at end of file diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/parameter_references/get_parameter_references_path_param.py b/end_to_end_tests/golden-record/my_test_api_client/api/parameter_references/get_parameter_references_path_param.py index 861a50749..4b034ffe4 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/parameter_references/get_parameter_references_path_param.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/parameter_references/get_parameter_references_path_param.py @@ -34,9 +34,7 @@ def _get_kwargs( _kwargs: Dict[str, Any] = { "method": "get", - "url": "/parameter-references/{path_param}".format( - path_param=path_param, - ), + "url": f"/parameter-references/{path_param}", "params": params, "cookies": cookies, } diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/parameters/delete_common_parameters_overriding_param.py b/end_to_end_tests/golden-record/my_test_api_client/api/parameters/delete_common_parameters_overriding_param.py index 693044930..8203fa750 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/parameters/delete_common_parameters_overriding_param.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/parameters/delete_common_parameters_overriding_param.py @@ -21,9 +21,7 @@ def _get_kwargs( _kwargs: Dict[str, Any] = { "method": "delete", - "url": "/common_parameters_overriding/{param}".format( - param=param_path, - ), + "url": f"/common_parameters_overriding/{param_path}", "params": params, } diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_common_parameters_overriding_param.py b/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_common_parameters_overriding_param.py index 6f29152a2..985e92c20 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_common_parameters_overriding_param.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_common_parameters_overriding_param.py @@ -21,9 +21,7 @@ def _get_kwargs( _kwargs: Dict[str, Any] = { "method": "get", - "url": "/common_parameters_overriding/{param}".format( - param=param_path, - ), + "url": f"/common_parameters_overriding/{param_path}", "params": params, } diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_same_name_multiple_locations_param.py b/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_same_name_multiple_locations_param.py index e4453277a..43f3b8993 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_same_name_multiple_locations_param.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_same_name_multiple_locations_param.py @@ -31,9 +31,7 @@ def _get_kwargs( _kwargs: Dict[str, Any] = { "method": "get", - "url": "/same-name-multiple-locations/{param}".format( - param=param_path, - ), + "url": f"/same-name-multiple-locations/{param_path}", "params": params, "cookies": cookies, } diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/parameters/multiple_path_parameters.py b/end_to_end_tests/golden-record/my_test_api_client/api/parameters/multiple_path_parameters.py index 6ffacceb7..2785fa56f 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/parameters/multiple_path_parameters.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/parameters/multiple_path_parameters.py @@ -16,12 +16,7 @@ def _get_kwargs( ) -> Dict[str, Any]: _kwargs: Dict[str, Any] = { "method": "get", - "url": "/multiple-path-parameters/{param4}/something/{param2}/{param1}/{param3}".format( - param4=param4, - param2=param2, - param1=param1, - param3=param3, - ), + "url": f"/multiple-path-parameters/{param4}/something/{param2}/{param1}/{param3}", } return _kwargs diff --git a/end_to_end_tests/golden-record/pyproject.toml b/end_to_end_tests/golden-record/pyproject.toml index a82e01809..2eed9cffe 100644 --- a/end_to_end_tests/golden-record/pyproject.toml +++ b/end_to_end_tests/golden-record/pyproject.toml @@ -21,5 +21,5 @@ requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" [tool.ruff] -select = ["F", "I"] +select = ["F", "I", "UP"] line-length = 120 diff --git a/end_to_end_tests/metadata_snapshots/pdm.pyproject.toml b/end_to_end_tests/metadata_snapshots/pdm.pyproject.toml index 31f84cb5b..0cc547a17 100644 --- a/end_to_end_tests/metadata_snapshots/pdm.pyproject.toml +++ b/end_to_end_tests/metadata_snapshots/pdm.pyproject.toml @@ -19,5 +19,5 @@ requires = ["pdm-backend"] build-backend = "pdm.backend" [tool.ruff] -select = ["F", "I"] +select = ["F", "I", "UP"] line-length = 120 diff --git a/end_to_end_tests/metadata_snapshots/poetry.pyproject.toml b/end_to_end_tests/metadata_snapshots/poetry.pyproject.toml index 4e409e422..1db67cd2b 100644 --- a/end_to_end_tests/metadata_snapshots/poetry.pyproject.toml +++ b/end_to_end_tests/metadata_snapshots/poetry.pyproject.toml @@ -21,5 +21,5 @@ requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" [tool.ruff] -select = ["F", "I"] +select = ["F", "I", "UP"] line-length = 120 diff --git a/end_to_end_tests/regen_golden_record.py b/end_to_end_tests/regen_golden_record.py index 641c183ae..0bffe132a 100644 --- a/end_to_end_tests/regen_golden_record.py +++ b/end_to_end_tests/regen_golden_record.py @@ -79,12 +79,11 @@ def regen_custom_template_golden_record(): gr_path = Path(__file__).parent / "golden-record" tpl_gr_path = Path(__file__).parent / "custom-templates-golden-record" - output_path = Path(tempfile.mkdtemp()) + output_path = Path.cwd() / "my-test-api-client" config_path = Path(__file__).parent / "config.yml" shutil.rmtree(tpl_gr_path, ignore_errors=True) - os.chdir(str(output_path.absolute())) result = runner.invoke( app, [ @@ -96,9 +95,8 @@ def regen_custom_template_golden_record(): ) if result.stdout: - generated_output_path = output_path / "my-test-api-client" - for f in generated_output_path.glob("**/*"): # nb: works for Windows and Unix - relative_to_generated = f.relative_to(generated_output_path) + for f in output_path.glob("**/*"): # nb: works for Windows and Unix + relative_to_generated = f.relative_to(output_path) gr_file = gr_path / relative_to_generated if not gr_file.exists(): print(f"{gr_file} does not exist, ignoring") diff --git a/end_to_end_tests/test-3-1-golden-record/pyproject.toml b/end_to_end_tests/test-3-1-golden-record/pyproject.toml index 4e409e422..1db67cd2b 100644 --- a/end_to_end_tests/test-3-1-golden-record/pyproject.toml +++ b/end_to_end_tests/test-3-1-golden-record/pyproject.toml @@ -21,5 +21,5 @@ requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" [tool.ruff] -select = ["F", "I"] +select = ["F", "I", "UP"] line-length = 120 diff --git a/end_to_end_tests/test-3-1-golden-record/test_3_1_features_client/api/const/post_const_path.py b/end_to_end_tests/test-3-1-golden-record/test_3_1_features_client/api/const/post_const_path.py index 2805064b2..3f864b3dc 100644 --- a/end_to_end_tests/test-3-1-golden-record/test_3_1_features_client/api/const/post_const_path.py +++ b/end_to_end_tests/test-3-1-golden-record/test_3_1_features_client/api/const/post_const_path.py @@ -28,9 +28,7 @@ def _get_kwargs( _kwargs: Dict[str, Any] = { "method": "post", - "url": "/const/{path}".format( - path=path, - ), + "url": f"/const/{path}", "params": params, } diff --git a/end_to_end_tests/test_end_to_end.py b/end_to_end_tests/test_end_to_end.py index 8b61f8d8d..9087beca3 100644 --- a/end_to_end_tests/test_end_to_end.py +++ b/end_to_end_tests/test_end_to_end.py @@ -1,9 +1,10 @@ import shutil from filecmp import cmpfiles, dircmp from pathlib import Path -from typing import Dict, List, Optional, Tuple +from typing import Dict, List, Optional, Set import pytest +from click.testing import Result from typer.testing import CliRunner from openapi_python_client.cli import app @@ -13,6 +14,7 @@ def _compare_directories( record: Path, test_subject: Path, expected_differences: Dict[Path, str], + expected_missing: Optional[Set[str]] = None, ignore: List[str] = None, depth=0, ): @@ -28,7 +30,7 @@ def _compare_directories( first_printable = record.relative_to(Path.cwd()) second_printable = test_subject.relative_to(Path.cwd()) dc = dircmp(record, test_subject, ignore=[".ruff_cache", "__pycache__"] + (ignore or [])) - missing_files = dc.left_only + dc.right_only + missing_files = set(dc.left_only + dc.right_only) - (expected_missing or set()) if missing_files: pytest.fail( f"{first_printable} or {second_printable} was missing: {missing_files}", @@ -77,21 +79,23 @@ def _compare_directories( def run_e2e_test( openapi_document: str, extra_args: List[str], - expected_differences: Dict[Path, str], + expected_differences: Optional[Dict[Path, str]] = None, golden_record_path: str = "golden-record", output_path: str = "my-test-api-client", -): + expected_missing: Optional[Set[str]] = None, +) -> Result: output_path = Path.cwd() / output_path shutil.rmtree(output_path, ignore_errors=True) - generate(extra_args, openapi_document) + result = generate(extra_args, openapi_document) gr_path = Path(__file__).parent / golden_record_path + expected_differences = expected_differences or {} # Use absolute paths for expected differences for easier comparisons expected_differences = { output_path.joinpath(key): value for key, value in expected_differences.items() } _compare_directories( - gr_path, output_path, expected_differences=expected_differences + gr_path, output_path, expected_differences=expected_differences, expected_missing=expected_missing ) import mypy.api @@ -100,19 +104,31 @@ def run_e2e_test( assert status == 0, f"Type checking client failed: {out}" shutil.rmtree(output_path) + return result -def generate(extra_args: Optional[List[str]], openapi_document: str): +def generate(extra_args: Optional[List[str]], openapi_document: str) -> Result: + """Generate a client from an OpenAPI document and return the path to the generated code""" + _run_command("generate", extra_args, openapi_document) + + +def _run_command(command: str, extra_args: Optional[List[str]] = None, openapi_document: Optional[str] = None, url: Optional[str] = None, config_path: Optional[Path] = None) -> Result: """Generate a client from an OpenAPI document and return the path to the generated code""" runner = CliRunner() - openapi_path = Path(__file__).parent / openapi_document - config_path = Path(__file__).parent / "config.yml" - args = ["generate", f"--config={config_path}", f"--path={openapi_path}"] + if openapi_document is not None: + openapi_path = Path(__file__).parent / openapi_document + source_arg = f"--path={openapi_path}" + else: + source_arg = f"--url={url}" + config_path = config_path or (Path(__file__).parent / "config.yml") + args = [command, f"--config={config_path}", source_arg] if extra_args: args.extend(extra_args) result = runner.invoke(app, args) if result.exit_code != 0: - raise result.exception + raise Exception(result.stdout) + return result + def test_baseline_end_to_end_3_0(): run_e2e_test("baseline_openapi_3.0.json", [], {}) @@ -147,19 +163,22 @@ def test_meta(meta: str, generated_file: Optional[str], expected_file: Optional[ if generated_file and expected_file: assert (output_path / generated_file).exists() - assert (output_path / generated_file).read_text() == (Path(__file__).parent / "metadata_snapshots" / expected_file).read_text() + assert ( + (output_path / generated_file).read_text() == + (Path(__file__).parent / "metadata_snapshots" / expected_file).read_text() + ) shutil.rmtree(output_path) -def test_no_meta(): - output_path = Path.cwd() / "test_3_1_features_client" - shutil.rmtree(output_path, ignore_errors=True) - generate([f"--meta=none"], "3.1_specific.openapi.yaml") - - assert output_path.exists() # Has a different name than with-meta generation - assert (output_path / "__init__.py").exists() - shutil.rmtree(output_path) +def test_none_meta(): + run_e2e_test( + "3.1_specific.openapi.yaml", + ["--meta=none"], + golden_record_path="test-3-1-golden-record/test_3_1_features_client", + output_path="test_3_1_features_client", + expected_missing={"py.typed"}, + ) def test_custom_templates(): @@ -194,3 +213,76 @@ def test_custom_templates(): extra_args=["--custom-template-path=end_to_end_tests/test_custom_templates/"], expected_differences=expected_differences, ) + + +@pytest.mark.parametrize( + "command", ("generate", "update") +) +def test_bad_url(command: str): + runner = CliRunner() + result = runner.invoke(app, [command, "--url=not_a_url"]) + assert result.exit_code == 1 + assert "Could not get OpenAPI document from provided URL" in result.stdout + + +def test_custom_post_hooks(): + shutil.rmtree(Path.cwd() / "my-test-api-client", ignore_errors=True) + runner = CliRunner() + openapi_document = Path(__file__).parent / "baseline_openapi_3.0.json" + config_path = Path(__file__).parent / "custom_post_hooks.config.yml" + result = runner.invoke(app, ["generate", f"--path={openapi_document}", f"--config={config_path}"]) + assert result.exit_code == 1 + assert "this should fail" in result.stdout + shutil.rmtree(Path.cwd() / "my-test-api-client", ignore_errors=True) + + +def test_generate_dir_already_exists(): + project_dir = Path.cwd() / "my-test-api-client" + if not project_dir.exists(): + project_dir.mkdir() + runner = CliRunner() + openapi_document = Path(__file__).parent / "baseline_openapi_3.0.json" + result = runner.invoke(app, ["generate", f"--path={openapi_document}"]) + assert result.exit_code == 1 + assert "Directory already exists" in result.stdout + shutil.rmtree(Path.cwd() / "my-test-api-client", ignore_errors=True) + + +def test_update_dir_not_found(): + project_dir = Path.cwd() / "my-test-api-client" + shutil.rmtree(project_dir, ignore_errors=True) + runner = CliRunner() + openapi_document = Path(__file__).parent / "baseline_openapi_3.0.json" + result = runner.invoke(app, ["update", f"--path={openapi_document}"]) + assert result.exit_code == 1 + assert str(project_dir) in result.stdout + + +@pytest.mark.parametrize( + ("file_name", "content", "expected_error"), + ( + ("invalid_openapi.yaml", "not a valid openapi document", "Failed to parse OpenAPI document"), + ("invalid_json.json", "Invalid JSON", "Invalid JSON"), + ("invalid_yaml.yaml", "{", "Invalid YAML"), + ), + ids=("invalid_openapi", "invalid_json", "invalid_yaml") +) +def test_invalid_openapi_document(file_name, content, expected_error): + runner = CliRunner() + openapi_document = Path.cwd() / file_name + openapi_document.write_text(content) + result = runner.invoke(app, ["generate", f"--path={openapi_document}"]) + assert result.exit_code == 1 + assert expected_error in result.stdout + openapi_document.unlink() + + +def test_update_integration_tests(): + url = "https://raw.githubusercontent.com/openapi-generators/openapi-test-server/main/openapi.json" + source_path = Path(__file__).parent.parent / "integration-tests" + project_path = Path.cwd() / "integration-tests" + if source_path != project_path: # Just in case someone runs this from root dir + shutil.copytree(source_path, project_path) + config_path = project_path / "config.yaml" + _run_command("update", url=url, config_path=config_path) + _compare_directories(source_path, project_path, expected_differences={}) diff --git a/integration-tests-config.yaml b/integration-tests-config.yaml deleted file mode 100644 index bda7890c5..000000000 --- a/integration-tests-config.yaml +++ /dev/null @@ -1 +0,0 @@ -project_name_override: integration-tests \ No newline at end of file diff --git a/integration-tests/config.yaml b/integration-tests/config.yaml new file mode 100644 index 000000000..80153f799 --- /dev/null +++ b/integration-tests/config.yaml @@ -0,0 +1,5 @@ +project_name_override: integration-tests +post_hooks: + - ruff check . --fix + - ruff format . + - mypy . --strict \ No newline at end of file diff --git a/openapi_python_client/__init__.py b/openapi_python_client/__init__.py index cc4290692..39bc18c2f 100644 --- a/openapi_python_client/__init__.py +++ b/openapi_python_client/__init__.py @@ -4,7 +4,6 @@ import mimetypes import shutil import subprocess -from enum import Enum from importlib.metadata import version from pathlib import Path from subprocess import CalledProcessError @@ -17,22 +16,13 @@ from openapi_python_client import utils -from .config import Config +from .config import Config, MetaType from .parser import GeneratorData, import_string_from_class from .parser.errors import ErrorLevel, GeneratorError __version__ = version(__package__) -class MetaType(str, Enum): - """The types of metadata supported for project generation.""" - - NONE = "none" - POETRY = "poetry" - SETUP = "setup" - PDM = "pdm" - - TEMPLATE_FILTERS = { "snakecase": utils.snake_case, "kebabcase": utils.kebab_case, @@ -48,14 +38,10 @@ def __init__( self, *, openapi: GeneratorData, - meta: MetaType, config: Config, custom_template_path: Optional[Path] = None, - file_encoding: str = "utf-8", ) -> None: self.openapi: GeneratorData = openapi - self.meta: MetaType = meta - self.file_encoding = file_encoding self.config = config package_loader = PackageLoader(__package__) @@ -79,7 +65,7 @@ def __init__( self.project_name: str = config.project_name_override or f"{utils.kebab_case(openapi.title).lower()}-client" self.project_dir: Path = Path.cwd() - if meta != MetaType.NONE: + if config.meta_type != MetaType.NONE: self.project_dir /= self.project_name self.package_name: str = config.package_name_override or self.project_name.replace("-", "_") @@ -108,7 +94,7 @@ def __init__( def build(self) -> Sequence[GeneratorError]: """Create the project from templates""" - if self.meta == MetaType.NONE: + if self.config.meta_type == MetaType.NONE: print(f"Generating {self.package_name}") else: print(f"Generating {self.project_name}") @@ -151,7 +137,7 @@ def _run_command(self, cmd: str) -> None: ) return try: - cwd = self.package_dir if self.meta == MetaType.NONE else self.project_dir + cwd = self.package_dir if self.config.meta_type == MetaType.NONE else self.project_dir subprocess.run(cmd, cwd=cwd, shell=True, capture_output=True, check=True) except CalledProcessError as err: self.errors.append( @@ -176,44 +162,44 @@ def _create_package(self) -> None: package_init = self.package_dir / "__init__.py" package_init_template = self.env.get_template("package_init.py.jinja") - package_init.write_text(package_init_template.render(), encoding=self.file_encoding) + package_init.write_text(package_init_template.render(), encoding=self.config.file_encoding) - if self.meta != MetaType.NONE: + if self.config.meta_type != MetaType.NONE: pytyped = self.package_dir / "py.typed" - pytyped.write_text("# Marker file for PEP 561", encoding=self.file_encoding) + pytyped.write_text("# Marker file for PEP 561", encoding=self.config.file_encoding) types_template = self.env.get_template("types.py.jinja") types_path = self.package_dir / "types.py" - types_path.write_text(types_template.render(), encoding=self.file_encoding) + types_path.write_text(types_template.render(), encoding=self.config.file_encoding) def _build_metadata(self) -> None: - if self.meta == MetaType.NONE: + if self.config.meta_type == MetaType.NONE: return self._build_pyproject_toml() - if self.meta == MetaType.SETUP: + if self.config.meta_type == MetaType.SETUP: self._build_setup_py() # README.md readme = self.project_dir / "README.md" readme_template = self.env.get_template("README.md.jinja") readme.write_text( - readme_template.render(poetry=self.meta == MetaType.POETRY), - encoding=self.file_encoding, + readme_template.render(poetry=self.config.meta_type == MetaType.POETRY), + encoding=self.config.file_encoding, ) # .gitignore git_ignore_path = self.project_dir / ".gitignore" git_ignore_template = self.env.get_template(".gitignore.jinja") - git_ignore_path.write_text(git_ignore_template.render(), encoding=self.file_encoding) + git_ignore_path.write_text(git_ignore_template.render(), encoding=self.config.file_encoding) def _build_pyproject_toml(self) -> None: template = "pyproject.toml.jinja" pyproject_template = self.env.get_template(template) pyproject_path = self.project_dir / "pyproject.toml" pyproject_path.write_text( - pyproject_template.render(meta=self.meta), - encoding=self.file_encoding, + pyproject_template.render(meta=self.config.meta_type), + encoding=self.config.file_encoding, ) def _build_setup_py(self) -> None: @@ -221,7 +207,7 @@ def _build_setup_py(self) -> None: path = self.project_dir / "setup.py" path.write_text( template.render(), - encoding=self.file_encoding, + encoding=self.config.file_encoding, ) def _build_models(self) -> None: @@ -235,7 +221,7 @@ def _build_models(self) -> None: model_template = self.env.get_template("model.py.jinja") for model in self.openapi.models: module_path = models_dir / f"{model.class_info.module_name}.py" - module_path.write_text(model_template.render(model=model), encoding=self.file_encoding) + module_path.write_text(model_template.render(model=model), encoding=self.config.file_encoding) imports.append(import_string_from_class(model.class_info)) alls.append(model.class_info.name) @@ -245,32 +231,34 @@ def _build_models(self) -> None: for enum in self.openapi.enums: module_path = models_dir / f"{enum.class_info.module_name}.py" if enum.value_type is int: - module_path.write_text(int_enum_template.render(enum=enum), encoding=self.file_encoding) + module_path.write_text(int_enum_template.render(enum=enum), encoding=self.config.file_encoding) else: - module_path.write_text(str_enum_template.render(enum=enum), encoding=self.file_encoding) + module_path.write_text(str_enum_template.render(enum=enum), encoding=self.config.file_encoding) imports.append(import_string_from_class(enum.class_info)) alls.append(enum.class_info.name) models_init_template = self.env.get_template("models_init.py.jinja") - models_init.write_text(models_init_template.render(imports=imports, alls=alls), encoding=self.file_encoding) + models_init.write_text( + models_init_template.render(imports=imports, alls=alls), encoding=self.config.file_encoding + ) def _build_api(self) -> None: # Generate Client client_path = self.package_dir / "client.py" client_template = self.env.get_template("client.py.jinja") - client_path.write_text(client_template.render(), encoding=self.file_encoding) + client_path.write_text(client_template.render(), encoding=self.config.file_encoding) # Generate included Errors errors_path = self.package_dir / "errors.py" errors_template = self.env.get_template("errors.py.jinja") - errors_path.write_text(errors_template.render(), encoding=self.file_encoding) + errors_path.write_text(errors_template.render(), encoding=self.config.file_encoding) # Generate endpoints api_dir = self.package_dir / "api" api_dir.mkdir() api_init_path = api_dir / "__init__.py" api_init_template = self.env.get_template("api_init.py.jinja") - api_init_path.write_text(api_init_template.render(), encoding=self.file_encoding) + api_init_path.write_text(api_init_template.render(), encoding=self.config.file_encoding) endpoint_collections_by_tag = self.openapi.endpoint_collections_by_tag endpoint_template = self.env.get_template( @@ -284,7 +272,7 @@ def _build_api(self) -> None: endpoint_init_template = self.env.get_template("endpoint_init.py.jinja") endpoint_init_path.write_text( endpoint_init_template.render(endpoint_collection=collection), - encoding=self.file_encoding, + encoding=self.config.file_encoding, ) for endpoint in collection.endpoints: @@ -293,19 +281,15 @@ def _build_api(self) -> None: endpoint_template.render( endpoint=endpoint, ), - encoding=self.file_encoding, + encoding=self.config.file_encoding, ) def _get_project_for_url_or_path( - url: Optional[str], - path: Optional[Path], - meta: MetaType, config: Config, custom_template_path: Optional[Path] = None, - file_encoding: str = "utf-8", ) -> Union[Project, GeneratorError]: - data_dict = _get_document(url=url, path=path, timeout=config.http_timeout) + data_dict = _get_document(source=config.document_source, timeout=config.http_timeout) if isinstance(data_dict, GeneratorError): return data_dict openapi = GeneratorData.from_dict(data_dict, config=config) @@ -314,20 +298,14 @@ def _get_project_for_url_or_path( return Project( openapi=openapi, custom_template_path=custom_template_path, - meta=meta, - file_encoding=file_encoding, config=config, ) def create_new_client( *, - url: Optional[str], - path: Optional[Path], - meta: MetaType, config: Config, custom_template_path: Optional[Path] = None, - file_encoding: str = "utf-8", ) -> Sequence[GeneratorError]: """ Generate the client library @@ -336,11 +314,7 @@ def create_new_client( A list containing any errors encountered when generating. """ project = _get_project_for_url_or_path( - url=url, - path=path, custom_template_path=custom_template_path, - meta=meta, - file_encoding=file_encoding, config=config, ) if isinstance(project, GeneratorError): @@ -350,12 +324,8 @@ def create_new_client( def update_existing_client( *, - url: Optional[str], - path: Optional[Path], - meta: MetaType, config: Config, custom_template_path: Optional[Path] = None, - file_encoding: str = "utf-8", ) -> Sequence[GeneratorError]: """ Update an existing client library @@ -364,11 +334,7 @@ def update_existing_client( A list containing any errors encountered when generating. """ project = _get_project_for_url_or_path( - url=url, - path=path, custom_template_path=custom_template_path, - meta=meta, - file_encoding=file_encoding, config=config, ) if isinstance(project, GeneratorError): @@ -389,27 +355,22 @@ def _load_yaml_or_json(data: bytes, content_type: Optional[str]) -> Union[Dict[s return GeneratorError(header=f"Invalid YAML from provided source: {err}") -def _get_document(*, url: Optional[str], path: Optional[Path], timeout: int) -> Union[Dict[str, Any], GeneratorError]: +def _get_document(*, source: Union[str, Path], timeout: int) -> Union[Dict[str, Any], GeneratorError]: yaml_bytes: bytes content_type: Optional[str] - if url is not None and path is not None: - return GeneratorError(header="Provide URL or Path, not both.") - if url is not None: + if isinstance(source, str): try: - response = httpx.get(url, timeout=timeout) + response = httpx.get(source, timeout=timeout) yaml_bytes = response.content if "content-type" in response.headers: content_type = response.headers["content-type"].split(";")[0] - else: - content_type = mimetypes.guess_type(url, strict=True)[0] + else: # pragma: no cover + content_type = mimetypes.guess_type(source, strict=True)[0] except (httpx.HTTPError, httpcore.NetworkError): return GeneratorError(header="Could not get OpenAPI document from provided URL") - elif path is not None: - yaml_bytes = path.read_bytes() - content_type = mimetypes.guess_type(path.absolute().as_uri(), strict=True)[0] - else: - return GeneratorError(header="No URL or Path provided") + yaml_bytes = source.read_bytes() + content_type = mimetypes.guess_type(source.absolute().as_uri(), strict=True)[0] return _load_yaml_or_json(yaml_bytes, content_type) diff --git a/openapi_python_client/cli.py b/openapi_python_client/cli.py index 94c8e0bab..22478ab04 100644 --- a/openapi_python_client/cli.py +++ b/openapi_python_client/cli.py @@ -1,12 +1,12 @@ import codecs -import pathlib +from pathlib import Path from pprint import pformat -from typing import Optional, Sequence +from typing import Optional, Sequence, Union import typer from openapi_python_client import MetaType -from openapi_python_client.config import Config +from openapi_python_client.config import Config, ConfigFile from openapi_python_client.parser.errors import ErrorLevel, GeneratorError, ParseError app = typer.Typer() @@ -20,14 +20,36 @@ def _version_callback(value: bool) -> None: raise typer.Exit() -def _process_config(path: Optional[pathlib.Path]) -> Config: - if not path: - return Config() +def _process_config( + *, url: Optional[str], path: Optional[Path], config_path: Optional[Path], meta_type: MetaType, file_encoding: str +) -> Config: + source: Union[Path, str] + if url and not path: + source = url + elif path and not url: + source = path + elif url and path: + typer.secho("Provide either --url or --path, not both", fg=typer.colors.RED) + raise typer.Exit(code=1) + else: + typer.secho("You must either provide --url or --path", fg=typer.colors.RED) + raise typer.Exit(code=1) try: - return Config.load_from_path(path=path) - except Exception as err: - raise typer.BadParameter("Unable to parse config") from err + codecs.getencoder(file_encoding) + except LookupError as err: + typer.secho(f"Unknown encoding : {file_encoding}", fg=typer.colors.RED) + raise typer.Exit(code=1) from err + + if not config_path: + config_file = ConfigFile() + else: + try: + config_file = ConfigFile.load_from_path(path=config_path) + except Exception as err: + raise typer.BadParameter("Unable to parse config") from err + + return Config.from_sources(config_file, meta_type, source, file_encoding) # noinspection PyUnusedLocal @@ -114,36 +136,19 @@ def handle_errors(errors: Sequence[GeneratorError], fail_on_warning: bool = Fals @app.command() def generate( url: Optional[str] = typer.Option(None, help="A URL to read the JSON from"), - path: Optional[pathlib.Path] = typer.Option(None, help="A path to the JSON file"), - custom_template_path: Optional[pathlib.Path] = typer.Option(None, **custom_template_path_options), # type: ignore + path: Optional[Path] = typer.Option(None, help="A path to the JSON file"), + custom_template_path: Optional[Path] = typer.Option(None, **custom_template_path_options), # type: ignore meta: MetaType = _meta_option, file_encoding: str = typer.Option("utf-8", help="Encoding used when writing generated"), - config_path: Optional[pathlib.Path] = CONFIG_OPTION, + config_path: Optional[Path] = CONFIG_OPTION, fail_on_warning: bool = False, ) -> None: """Generate a new OpenAPI Client library""" from . import create_new_client - if not url and not path: - typer.secho("You must either provide --url or --path", fg=typer.colors.RED) - raise typer.Exit(code=1) - if url and path: - typer.secho("Provide either --url or --path, not both", fg=typer.colors.RED) - raise typer.Exit(code=1) - - try: - codecs.getencoder(file_encoding) - except LookupError as err: - typer.secho(f"Unknown encoding : {file_encoding}", fg=typer.colors.RED) - raise typer.Exit(code=1) from err - - config = _process_config(config_path) + config = _process_config(url=url, path=path, config_path=config_path, meta_type=meta, file_encoding=file_encoding) errors = create_new_client( - url=url, - path=path, - meta=meta, custom_template_path=custom_template_path, - file_encoding=file_encoding, config=config, ) handle_errors(errors, fail_on_warning) @@ -152,11 +157,11 @@ def generate( @app.command() def update( url: Optional[str] = typer.Option(None, help="A URL to read the JSON from"), - path: Optional[pathlib.Path] = typer.Option(None, help="A path to the JSON file"), - custom_template_path: Optional[pathlib.Path] = typer.Option(None, **custom_template_path_options), # type: ignore + path: Optional[Path] = typer.Option(None, help="A path to the JSON file"), + custom_template_path: Optional[Path] = typer.Option(None, **custom_template_path_options), # type: ignore meta: MetaType = _meta_option, file_encoding: str = typer.Option("utf-8", help="Encoding used when writing generated"), - config_path: Optional[pathlib.Path] = CONFIG_OPTION, + config_path: Optional[Path] = CONFIG_OPTION, fail_on_warning: bool = False, ) -> None: """Update an existing OpenAPI Client library @@ -166,26 +171,9 @@ def update( """ from . import update_existing_client - if not url and not path: - typer.secho("You must either provide --url or --path", fg=typer.colors.RED) - raise typer.Exit(code=1) - if url and path: - typer.secho("Provide either --url or --path, not both", fg=typer.colors.RED) - raise typer.Exit(code=1) - - try: - codecs.getencoder(file_encoding) - except LookupError as err: - typer.secho(f"Unknown encoding : {file_encoding}", fg=typer.colors.RED) - raise typer.Exit(code=1) from err - - config = _process_config(config_path) + config = _process_config(config_path=config_path, meta_type=meta, url=url, path=path, file_encoding=file_encoding) errors = update_existing_client( - url=url, - path=path, - meta=meta, custom_template_path=custom_template_path, - file_encoding=file_encoding, config=config, ) handle_errors(errors, fail_on_warning) diff --git a/openapi_python_client/config.py b/openapi_python_client/config.py index 2b2190f90..73aac11a7 100644 --- a/openapi_python_client/config.py +++ b/openapi_python_client/config.py @@ -1,9 +1,11 @@ import json import mimetypes +from enum import Enum from pathlib import Path -from typing import Dict, List, Optional +from typing import Dict, List, Optional, Union import yaml +from attr import define from pydantic import BaseModel @@ -17,31 +19,86 @@ class ClassOverride(BaseModel): module_name: Optional[str] = None -class Config(BaseModel): - """Contains any configurable values passed by the user. +class MetaType(str, Enum): + """The types of metadata supported for project generation.""" + + NONE = "none" + POETRY = "poetry" + SETUP = "setup" + PDM = "pdm" + + +class ConfigFile(BaseModel): + """Contains any configurable values passed via a config file. See https://github.com/openapi-generators/openapi-python-client#configuration """ - class_overrides: Dict[str, ClassOverride] = {} + class_overrides: Optional[Dict[str, ClassOverride]] = None project_name_override: Optional[str] = None package_name_override: Optional[str] = None package_version_override: Optional[str] = None use_path_prefixes_for_title_model_names: bool = True - post_hooks: List[str] = [ - "ruff check --fix .", - "ruff format .", - ] + post_hooks: Optional[List[str]] = None field_prefix: str = "field_" http_timeout: int = 5 @staticmethod - def load_from_path(path: Path) -> "Config": + def load_from_path(path: Path) -> "ConfigFile": """Creates a Config from provided JSON or YAML file and sets a bunch of globals from it""" mime = mimetypes.guess_type(path.absolute().as_uri(), strict=True)[0] if mime == "application/json": config_data = json.loads(path.read_text()) else: config_data = yaml.safe_load(path.read_text()) - config = Config(**config_data) + config = ConfigFile(**config_data) + return config + + +@define +class Config: + """Contains all the config values for the generator, from files, defaults, and CLI arguments.""" + + meta_type: MetaType + class_overrides: Dict[str, ClassOverride] + project_name_override: Optional[str] + package_name_override: Optional[str] + package_version_override: Optional[str] + use_path_prefixes_for_title_model_names: bool + post_hooks: List[str] + field_prefix: str + http_timeout: int + document_source: Union[Path, str] + file_encoding: str + + @staticmethod + def from_sources( + config_file: ConfigFile, meta_type: MetaType, document_source: Union[Path, str], file_encoding: str + ) -> "Config": + if config_file.post_hooks is not None: + post_hooks = config_file.post_hooks + elif meta_type == MetaType.NONE: + post_hooks = [ + "ruff check . --fix --extend-select=I", + "ruff format .", + ] + else: + post_hooks = [ + "ruff check --fix .", + "ruff format .", + ] + + config = Config( + meta_type=meta_type, + class_overrides=config_file.class_overrides or {}, + project_name_override=config_file.project_name_override, + package_name_override=config_file.package_name_override, + package_version_override=config_file.package_version_override, + use_path_prefixes_for_title_model_names=config_file.use_path_prefixes_for_title_model_names, + post_hooks=post_hooks, + field_prefix=config_file.field_prefix, + http_timeout=config_file.http_timeout, + document_source=document_source, + file_encoding=file_encoding, + ) return config diff --git a/openapi_python_client/templates/pyproject_ruff.toml.jinja b/openapi_python_client/templates/pyproject_ruff.toml.jinja index 8f3b0a5d8..c65287746 100644 --- a/openapi_python_client/templates/pyproject_ruff.toml.jinja +++ b/openapi_python_client/templates/pyproject_ruff.toml.jinja @@ -1,3 +1,3 @@ [tool.ruff] -select = ["F", "I"] +select = ["F", "I", "UP"] line-length = 120 diff --git a/pyproject.toml b/pyproject.toml index 3bb26e6c4..8224fdf10 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -128,8 +128,7 @@ composite = ["test --cov openapi_python_client tests --cov-report=term-missing"] [tool.pdm.scripts.regen_integration] shell = """ -openapi-python-client update --url https://raw.githubusercontent.com/openapi-generators/openapi-test-server/main/openapi.json --config integration-tests-config.yaml --meta pdm \ -&& mypy integration-tests --strict +openapi-python-client update --url https://raw.githubusercontent.com/openapi-generators/openapi-test-server/main/openapi.json --config integration-tests/config.yaml --meta pdm \ """ [build-system] diff --git a/tests/conftest.py b/tests/conftest.py index a8a45cf0e..44fbe717c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,8 +1,11 @@ +from pathlib import Path from typing import Any, Callable, Dict import pytest +from openapi_python_client import Config, MetaType from openapi_python_client import schema as oai +from openapi_python_client.config import ConfigFile from openapi_python_client.parser.properties import ( AnyProperty, BooleanProperty, @@ -21,6 +24,14 @@ from openapi_python_client.schema.parameter_location import ParameterLocation +@pytest.fixture(scope="session") +def config() -> Config: + """Create a default config for when it doesn't matter""" + return Config.from_sources( + ConfigFile(), MetaType.POETRY, document_source=Path("openapi.yaml"), file_encoding="utf-8" + ) + + @pytest.fixture def model_property_factory() -> Callable[..., ModelProperty]: """ diff --git a/tests/test___init__.py b/tests/test___init__.py index e1e701127..c419fdf32 100644 --- a/tests/test___init__.py +++ b/tests/test___init__.py @@ -1,289 +1,23 @@ -import pathlib - -import httpcore -import jinja2 import pytest -from pytest_mock import MockFixture - -from openapi_python_client import Config, ErrorLevel, GeneratorError, Project - -default_http_timeout = Config.model_json_schema()["properties"]["http_timeout"]["default"] - - -def test__get_project_for_url_or_path(mocker): - data_dict = mocker.MagicMock() - _get_document = mocker.patch("openapi_python_client._get_document", return_value=data_dict) - openapi = mocker.MagicMock() - from_dict = mocker.patch("openapi_python_client.parser.GeneratorData.from_dict", return_value=openapi) - _Project = mocker.patch("openapi_python_client.Project") - url = mocker.MagicMock() - path = mocker.MagicMock() - config = mocker.MagicMock() - config.http_timeout = default_http_timeout - - from openapi_python_client import MetaType, _get_project_for_url_or_path - - project = _get_project_for_url_or_path(url=url, path=path, meta=MetaType.POETRY, config=config) - - _get_document.assert_called_once_with(url=url, path=path, timeout=default_http_timeout) - from_dict.assert_called_once_with(data_dict, config=config) - _Project.assert_called_once_with( - openapi=openapi, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8", config=config - ) - assert project == _Project.return_value - - -def test__get_project_for_url_or_path_generator_error(mocker): - data_dict = mocker.MagicMock() - _get_document = mocker.patch("openapi_python_client._get_document", return_value=data_dict) - error = GeneratorError() - from_dict = mocker.patch("openapi_python_client.parser.GeneratorData.from_dict", return_value=error) - _Project = mocker.patch("openapi_python_client.Project") - url = mocker.MagicMock() - path = mocker.MagicMock() - config = mocker.MagicMock() - config.http_timeout = default_http_timeout - - from openapi_python_client import MetaType, _get_project_for_url_or_path - - project = _get_project_for_url_or_path(url=url, path=path, meta=MetaType.POETRY, config=config) - - _get_document.assert_called_once_with(url=url, path=path, timeout=default_http_timeout) - from_dict.assert_called_once_with(data_dict, config=config) - _Project.assert_not_called() - assert project == error - - -def test__get_project_for_url_or_path_document_error(mocker): - error = GeneratorError() - _get_document = mocker.patch("openapi_python_client._get_document", return_value=error) - - from_dict = mocker.patch("openapi_python_client.parser.GeneratorData.from_dict") - url = mocker.MagicMock() - path = mocker.MagicMock() - - from openapi_python_client import MetaType, _get_project_for_url_or_path - - project = _get_project_for_url_or_path(url=url, path=path, meta=MetaType.POETRY, config=Config()) - - _get_document.assert_called_once_with(url=url, path=path, timeout=default_http_timeout) - from_dict.assert_not_called() - assert project == error - - -def test_create_new_client(mocker): - project = mocker.MagicMock() - _get_project_for_url_or_path = mocker.patch( - "openapi_python_client._get_project_for_url_or_path", return_value=project - ) - url = mocker.MagicMock() - path = mocker.MagicMock() - config = mocker.MagicMock() - - from openapi_python_client import MetaType, create_new_client - - result = create_new_client(url=url, path=path, meta=MetaType.POETRY, config=config) - - _get_project_for_url_or_path.assert_called_once_with( - url=url, path=path, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8", config=config - ) - project.build.assert_called_once() - assert result == project.build.return_value - - -def test_create_new_client_project_error(mocker): - error = GeneratorError() - _get_project_for_url_or_path = mocker.patch( - "openapi_python_client._get_project_for_url_or_path", return_value=error - ) - url = mocker.MagicMock() - path = mocker.MagicMock() - config = mocker.MagicMock() - - from openapi_python_client import MetaType, create_new_client - - result = create_new_client(url=url, path=path, meta=MetaType.POETRY, config=config) - - _get_project_for_url_or_path.assert_called_once_with( - url=url, path=path, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8", config=config - ) - assert result == [error] - - -def test_update_existing_client(mocker): - project = mocker.MagicMock() - _get_project_for_url_or_path = mocker.patch( - "openapi_python_client._get_project_for_url_or_path", return_value=project - ) - url = mocker.MagicMock() - path = mocker.MagicMock() - config = mocker.MagicMock() - - from openapi_python_client import MetaType, update_existing_client - - result = update_existing_client(url=url, path=path, meta=MetaType.POETRY, config=config) - - _get_project_for_url_or_path.assert_called_once_with( - url=url, path=path, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8", config=config - ) - project.update.assert_called_once() - assert result == project.update.return_value - - -def test_update_existing_client_project_error(mocker): - error = GeneratorError() - _get_project_for_url_or_path = mocker.patch( - "openapi_python_client._get_project_for_url_or_path", return_value=error - ) - url = mocker.MagicMock() - path = mocker.MagicMock() - config = mocker.MagicMock() - - from openapi_python_client import MetaType, update_existing_client - - result = update_existing_client(url=url, path=path, meta=MetaType.POETRY, config=config) - - _get_project_for_url_or_path.assert_called_once_with( - url=url, path=path, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8", config=config - ) - assert result == [error] - - -class TestGetJson: - def test__get_document_no_url_or_path(self, mocker): - get = mocker.patch("httpx.get") - _Path = mocker.patch("openapi_python_client.Path") - loads = mocker.patch("yaml.safe_load") - - from openapi_python_client import _get_document - - result = _get_document(url=None, path=None, timeout=default_http_timeout) - - assert result == GeneratorError(header="No URL or Path provided") - get.assert_not_called() - _Path.assert_not_called() - loads.assert_not_called() - - def test__get_document_url_and_path(self, mocker): - get = mocker.patch("httpx.get") - _Path = mocker.patch("openapi_python_client.Path") - loads = mocker.patch("yaml.safe_load") - - from openapi_python_client import _get_document - - result = _get_document(url=mocker.MagicMock(), path=mocker.MagicMock(), timeout=default_http_timeout) - - assert result == GeneratorError(header="Provide URL or Path, not both.") - get.assert_not_called() - _Path.assert_not_called() - loads.assert_not_called() - - def test__get_document_bad_url(self, mocker): - get = mocker.patch("httpx.get", side_effect=httpcore.NetworkError) - _Path = mocker.patch("openapi_python_client.Path") - loads = mocker.patch("yaml.safe_load") - - from openapi_python_client import _get_document - - url = mocker.MagicMock() - result = _get_document(url=url, path=None, timeout=default_http_timeout) - - assert result == GeneratorError(header="Could not get OpenAPI document from provided URL") - get.assert_called_once_with(url, timeout=default_http_timeout) - _Path.assert_not_called() - loads.assert_not_called() - - def test__get_document_url_no_path(self, mocker): - get = mocker.patch("httpx.get") - _Path = mocker.patch("openapi_python_client.Path") - loads = mocker.patch("yaml.safe_load") - - from openapi_python_client import _get_document - - url = "test" - _get_document(url=url, path=None, timeout=default_http_timeout) - get.assert_called_once_with(url, timeout=default_http_timeout) - _Path.assert_not_called() - loads.assert_called_once_with(get().content) +from openapi_python_client import Config, ErrorLevel, Project +from openapi_python_client.config import ConfigFile - def test__get_document_path_no_url(self, tmp_path, mocker): - get = mocker.patch("httpx.get") - loads = mocker.patch("yaml.safe_load") - path = tmp_path / "test.yaml" - path.write_text("some test data") +default_http_timeout = ConfigFile.model_json_schema()["properties"]["http_timeout"]["default"] - from openapi_python_client import _get_document - _get_document(url=None, path=path, timeout=default_http_timeout) - - get.assert_not_called() - loads.assert_called_once_with(b"some test data") - - def test__get_document_bad_yaml(self, mocker, tmp_path): - get = mocker.patch("httpx.get") - from openapi_python_client import _get_document - - path = tmp_path / "test.yaml" - path.write_text("'") - result = _get_document(url=None, path=path, timeout=default_http_timeout) - - get.assert_not_called() - assert isinstance(result, GeneratorError) - assert "Invalid YAML" in result.header - - def test__get_document_json(self, mocker): - class FakeResponse: - content = b'{\n\t"foo": "bar"}' - headers = {"content-type": "application/json; encoding=utf8"} # noqa: RUF012 - - get = mocker.patch("httpx.get", return_value=FakeResponse()) - yaml_loads = mocker.patch("yaml.safe_load") - json_result = mocker.MagicMock() - json_loads = mocker.patch("json.loads", return_value=json_result) - - from openapi_python_client import _get_document - - url = mocker.MagicMock() - result = _get_document(url=url, path=None, timeout=default_http_timeout) - - get.assert_called_once() - json_loads.assert_called_once_with(FakeResponse.content.decode()) - yaml_loads.assert_not_called() - assert result == json_result - - def test__get_document_bad_json(self, mocker): - class FakeResponse: - content = b'{"foo"}' - headers = {"content-type": "application/json; encoding=utf8"} # noqa: RUF012 - - get = mocker.patch("httpx.get", return_value=FakeResponse()) - - from openapi_python_client import _get_document - - url = mocker.MagicMock() - result = _get_document(url=url, path=None, timeout=default_http_timeout) - - get.assert_called_once() - assert result == GeneratorError( - header="Invalid JSON from provided source: " "Expecting ':' delimiter: line 1 column 7 (char 6)" - ) - - -def make_project(**kwargs): +def make_project(config: Config) -> Project: from unittest.mock import MagicMock - from openapi_python_client import MetaType, Project + from openapi_python_client import Project - kwargs = {"openapi": MagicMock(title="My Test API"), "meta": MetaType.POETRY, "config": Config(), **kwargs} - - return Project(**kwargs) + return Project(openapi=MagicMock(title="My Test API"), config=config) @pytest.fixture -def project_with_dir() -> Project: +def project_with_dir(config) -> Project: """Return a Project with the project dir pre-made (needed for cwd of commands). Unlinks after the test completes""" - project = make_project() + project = make_project(config) project.project_dir.mkdir() yield project @@ -292,144 +26,7 @@ def project_with_dir() -> Project: class TestProject: - def test___init__(self, mocker): - openapi = mocker.MagicMock(title="My Test API") - - from openapi_python_client import MetaType, Project - - project = Project(openapi=openapi, meta=MetaType.POETRY, config=Config()) - - assert project.openapi == openapi - assert project.project_name == "my-test-api-client" - assert project.package_name == "my_test_api_client" - assert project.package_description == "A client library for accessing My Test API" - assert project.meta == MetaType.POETRY - assert project.project_dir == pathlib.Path.cwd() / project.project_name - assert project.package_dir == pathlib.Path.cwd() / project.project_name / project.package_name - - def test___init___no_meta(self, mocker): - openapi = mocker.MagicMock(title="My Test API") - - from openapi_python_client import MetaType, Project - - project = Project(openapi=openapi, meta=MetaType.NONE, config=Config()) - - assert project.openapi == openapi - assert project.package_description == "A client library for accessing My Test API" - assert project.meta == MetaType.NONE - assert project.project_dir == pathlib.Path.cwd() - assert project.package_dir == pathlib.Path.cwd() / project.package_name - - @pytest.mark.parametrize( - "project_override, package_override, expected_project_name, expected_package_name", - ( - (None, None, "my-test-api-client", "my_test_api_client"), - ("custom-project", None, "custom-project", "custom_project"), - ("custom-project", "custom_package", "custom-project", "custom_package"), - (None, "custom_package", "my-test-api-client", "custom_package"), - ), - ) - def test_project_and_package_names( - self, mocker, project_override, package_override, expected_project_name, expected_package_name - ): - openapi = mocker.MagicMock(title="My Test API") - - from openapi_python_client import MetaType, Project - - project = Project( - openapi=openapi, - meta=MetaType.POETRY, - config=Config(project_name_override=project_override, package_name_override=package_override), - ) - - assert project.project_name == expected_project_name - assert project.package_name == expected_package_name - - def test_build(self, mocker): - project = make_project() - project.project_dir = mocker.MagicMock() - project.package_dir = mocker.MagicMock() - project._build_metadata = mocker.MagicMock() - project._build_models = mocker.MagicMock() - project._build_api = mocker.MagicMock() - project._create_package = mocker.MagicMock() - project._run_post_hooks = mocker.MagicMock() - project._get_errors = mocker.MagicMock() - - result = project.build() - - project.project_dir.mkdir.assert_called_once() - project._create_package.assert_called_once() - project._build_metadata.assert_called_once() - project._build_models.assert_called_once() - project._build_api.assert_called_once() - project._run_post_hooks.assert_called_once() - project._get_errors.assert_called_once() - assert result == project._get_errors.return_value - - def test_build_no_meta(self, mocker): - from openapi_python_client import MetaType - - project = make_project(meta=MetaType.NONE) - project.project_dir = mocker.MagicMock() - project.package_dir = mocker.MagicMock() - project._build_metadata = mocker.MagicMock() - project._build_models = mocker.MagicMock() - project._build_api = mocker.MagicMock() - project._create_package = mocker.MagicMock() - project._run_post_hooks = mocker.MagicMock() - project._get_errors = mocker.MagicMock() - - project.build() - - project.project_dir.mkdir.assert_not_called() - - def test_build_file_exists(self, mocker): - project = make_project() - project.project_dir = mocker.MagicMock() - project.project_dir.mkdir.side_effect = FileExistsError - result = project.build() - - project.project_dir.mkdir.assert_called_once() - - assert result == [GeneratorError(detail="Directory already exists. Delete it or use the update command.")] - - def test_update(self, mocker): - from openapi_python_client import shutil - - rmtree = mocker.patch.object(shutil, "rmtree") - project = make_project() - project.package_dir = mocker.MagicMock() - project._build_metadata = mocker.MagicMock() - project._build_models = mocker.MagicMock() - project._build_api = mocker.MagicMock() - project._create_package = mocker.MagicMock() - project._run_post_hooks = mocker.MagicMock() - project._get_errors = mocker.MagicMock() - - result = project.update() - - rmtree.assert_called_once_with(project.package_dir) - project._create_package.assert_called_once() - project._build_models.assert_called_once() - project._build_api.assert_called_once() - project._run_post_hooks.assert_called_once() - project._get_errors.assert_called_once() - assert result == project._get_errors.return_value - - def test_update_missing_dir(self, mocker: MockFixture): - project = make_project() - mocker.patch.object(project, "package_dir") - project.package_dir.is_dir.return_value = False - mocker.patch.object(project, "_build_models") - - errs = project.update() - - assert len(errs) == 1 - project.package_dir.is_dir.assert_called_once() - project._build_models.assert_not_called() - - def test__run_post_hooks_reports_missing_commands(self, project_with_dir): + def test__run_post_hooks_reports_missing_commands(self, project_with_dir: Project) -> None: fake_command_name = "blahblahdoesntexist" project_with_dir.config.post_hooks = [fake_command_name] need_to_make_cwd = not project_with_dir.project_dir.exists() @@ -465,44 +62,3 @@ def test__run_post_hooks_reports_stderr_of_commands_that_error(self, project_wit assert error.level == ErrorLevel.ERROR assert error.header == "python failed" assert "some exception" in error.detail - - -def test__get_errors(mocker): - from openapi_python_client import GeneratorData, MetaType, Project - from openapi_python_client.parser.openapi import EndpointCollection - - openapi = mocker.MagicMock( - autospec=GeneratorData, - title="My Test API", - endpoint_collections_by_tag={ - "default": mocker.MagicMock(autospec=EndpointCollection, parse_errors=[1]), - "other": mocker.MagicMock(autospec=EndpointCollection, parse_errors=[2]), - }, - errors=[3], - ) - project = Project(openapi=openapi, meta=MetaType.POETRY, config=Config()) - - assert project._get_errors() == [1, 2, 3] - - -def test_custom_templates(mocker): - from openapi_python_client import GeneratorData, MetaType, Project - - openapi = mocker.MagicMock( - autospec=GeneratorData, - title="My Test API", - ) - - project = Project(openapi=openapi, meta=MetaType.POETRY, config=Config()) - assert isinstance(project.env.loader, jinja2.PackageLoader) - - project = Project( - openapi=openapi, - custom_template_path="../end_to_end_tests/test_custom_templates", - meta=MetaType.POETRY, - config=Config(), - ) - assert isinstance(project.env.loader, jinja2.ChoiceLoader) - assert len(project.env.loader.loaders) == 2 # noqa: PLR2004 - assert isinstance(project.env.loader.loaders[0], jinja2.FileSystemLoader) - assert isinstance(project.env.loader.loaders[1], jinja2.PackageLoader) diff --git a/tests/test_cli.py b/tests/test_cli.py index d13042e87..1b3c21501 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,4 +1,3 @@ -from pathlib import Path from unittest.mock import MagicMock import pytest @@ -25,36 +24,7 @@ def _create_new_client(mocker) -> MagicMock: return mocker.patch("openapi_python_client.create_new_client", return_value=[]) -def test_config_arg(mocker, _create_new_client): - load_config = mocker.patch("openapi_python_client.config.Config.load_from_path") - from openapi_python_client.cli import MetaType, app - - config_path = "config/path" - path = "cool/path" - file_encoding = "utf-8" - - result = runner.invoke( - app, - ["generate", f"--config={config_path}", f"--path={path}", f"--file-encoding={file_encoding}"], - catch_exceptions=False, - ) - - assert result.exit_code == 0 - load_config.assert_called_once_with(path=Path(config_path)) - _create_new_client.assert_called_once_with( - url=None, - path=Path(path), - custom_template_path=None, - meta=MetaType.POETRY, - file_encoding="utf-8", - config=load_config.return_value, - ) - - -def test_bad_config(mocker, _create_new_client): - load_config = mocker.patch( - "openapi_python_client.config.Config.load_from_path", side_effect=ValueError("Bad Config") - ) +def test_bad_config(_create_new_client): from openapi_python_client.cli import app config_path = "config/path" @@ -64,8 +34,6 @@ def test_bad_config(mocker, _create_new_client): assert result.exit_code == 2 # noqa: PLR2004 assert "Unable to parse config" in result.stdout - load_config.assert_called_once_with(path=Path(config_path)) - _create_new_client.assert_not_called() class TestGenerate: @@ -85,66 +53,6 @@ def test_generate_url_and_path(self, _create_new_client): assert result.exit_code == 1 _create_new_client.assert_not_called() - def test_generate_url(self, _create_new_client): - url = "cool.url" - from openapi_python_client.cli import Config, MetaType, app - - result = runner.invoke(app, ["generate", f"--url={url}"]) - - assert result.exit_code == 0 - _create_new_client.assert_called_once_with( - url=url, path=None, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8", config=Config() - ) - - def test_generate_path(self, _create_new_client): - path = "cool/path" - from openapi_python_client.cli import Config, MetaType, app - - result = runner.invoke(app, ["generate", f"--path={path}"]) - - assert result.exit_code == 0 - _create_new_client.assert_called_once_with( - url=None, - path=Path(path), - custom_template_path=None, - meta=MetaType.POETRY, - file_encoding="utf-8", - config=Config(), - ) - - def test_generate_meta(self, _create_new_client): - path = "cool/path" - from openapi_python_client.cli import Config, MetaType, app - - result = runner.invoke(app, ["generate", f"--path={path}", "--meta=none"]) - - assert result.exit_code == 0 - _create_new_client.assert_called_once_with( - url=None, - path=Path(path), - custom_template_path=None, - meta=MetaType.NONE, - file_encoding="utf-8", - config=Config(), - ) - - def test_generate_encoding(self, _create_new_client): - path = "cool/path" - file_encoding = "utf-8" - from openapi_python_client.cli import Config, MetaType, app - - result = runner.invoke(app, ["generate", f"--path={path}", f"--file-encoding={file_encoding}"]) - - assert result.exit_code == 0 - _create_new_client.assert_called_once_with( - url=None, - path=Path(path), - custom_template_path=None, - meta=MetaType.POETRY, - file_encoding="utf-8", - config=Config(), - ) - def test_generate_encoding_errors(self, _create_new_client): path = "cool/path" file_encoding = "error-file-encoding" @@ -238,50 +146,6 @@ def test_update_url_and_path(self, _update_existing_client): assert result.exit_code == 1 _update_existing_client.assert_not_called() - def test_update_url(self, _update_existing_client): - url = "cool.url" - from openapi_python_client.cli import Config, MetaType, app - - result = runner.invoke(app, ["update", f"--url={url}"]) - - assert result.exit_code == 0 - _update_existing_client.assert_called_once_with( - url=url, path=None, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8", config=Config() - ) - - def test_update_path(self, _update_existing_client): - path = "cool/path" - from openapi_python_client.cli import Config, MetaType, app - - result = runner.invoke(app, ["update", f"--path={path}"]) - - assert result.exit_code == 0 - _update_existing_client.assert_called_once_with( - url=None, - path=Path(path), - custom_template_path=None, - meta=MetaType.POETRY, - file_encoding="utf-8", - config=Config(), - ) - - def test_update_encoding(self, _update_existing_client): - path = "cool/path" - file_encoding = "utf-8" - from openapi_python_client.cli import Config, MetaType, app - - result = runner.invoke(app, ["update", f"--path={path}", f"--file-encoding={file_encoding}"]) - - assert result.exit_code == 0 - _update_existing_client.assert_called_once_with( - url=None, - path=Path(path), - custom_template_path=None, - meta=MetaType.POETRY, - file_encoding="utf-8", - config=Config(), - ) - def test_update_encoding_errors(self, _update_existing_client): path = "cool/path" file_encoding = "error-file-encoding" diff --git a/tests/test_config.py b/tests/test_config.py index d4bf39a10..ea03dda47 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -5,7 +5,7 @@ import pytest import yaml -from openapi_python_client.config import Config +from openapi_python_client.config import ConfigFile def json_with_tabs(d): @@ -40,7 +40,7 @@ def test_load_from_path(tmp_path: Path, filename, dump, relative): } yml_file.write_text(dump(data)) - config = Config.load_from_path(yml_file) + config = ConfigFile.load_from_path(yml_file) assert config.field_prefix == "blah" assert config.class_overrides["Class1"].model_dump() == override1 assert config.class_overrides["Class2"].model_dump() == override2 diff --git a/tests/test_parser/test_bodies.py b/tests/test_parser/test_bodies.py index 6641905f8..699ed00cf 100644 --- a/tests/test_parser/test_bodies.py +++ b/tests/test_parser/test_bodies.py @@ -1,11 +1,10 @@ -from openapi_python_client import Config from openapi_python_client import schema as oai from openapi_python_client.parser.bodies import body_from_data from openapi_python_client.parser.errors import ParseError from openapi_python_client.parser.properties import Schemas -def test_errors(): +def test_errors(config): operation = oai.Operation( requestBody=oai.RequestBody( content={ @@ -33,7 +32,7 @@ def test_errors(): responses={}, ) - errs, _ = body_from_data(data=operation, schemas=Schemas(), config=Config(), endpoint_name="this will not succeed") + errs, _ = body_from_data(data=operation, schemas=Schemas(), config=config, endpoint_name="this will not succeed") assert len(errs) == len(operation.request_body.content) assert all(isinstance(err, ParseError) for err in errs) diff --git a/tests/test_parser/test_openapi.py b/tests/test_parser/test_openapi.py index d81c57556..0bb8df491 100644 --- a/tests/test_parser/test_openapi.py +++ b/tests/test_parser/test_openapi.py @@ -4,7 +4,7 @@ import pytest import openapi_python_client.schema as oai -from openapi_python_client import Config, GeneratorError +from openapi_python_client import GeneratorError from openapi_python_client.parser.errors import ParseError from openapi_python_client.parser.openapi import Endpoint, EndpointCollection from openapi_python_client.parser.properties import IntProperty, Parameters, Schemas @@ -245,7 +245,7 @@ def test_add_parameters_parse_error(self, mocker): (oai.DataType.OBJECT, False), ], ) - def test_add_parameters_header_types(self, data_type, allowed): + def test_add_parameters_header_types(self, data_type, allowed, config): from openapi_python_client.parser.openapi import Endpoint endpoint = self.make_endpoint() @@ -254,7 +254,6 @@ def test_add_parameters_header_types(self, data_type, allowed): param = oai.Parameter.model_construct( name="test", required=True, param_schema=oai.Schema(type=data_type), param_in=oai.ParameterLocation.HEADER ) - config = Config() result = Endpoint.add_parameters( endpoint=endpoint, @@ -268,7 +267,7 @@ def test_add_parameters_header_types(self, data_type, allowed): else: assert isinstance(result[0], ParseError) - def test__add_parameters_parse_error_on_non_required_path_param(self): + def test__add_parameters_parse_error_on_non_required_path_param(self, config): endpoint = self.make_endpoint() param = oai.Parameter.model_construct( name="test", @@ -284,7 +283,7 @@ def test__add_parameters_parse_error_on_non_required_path_param(self): data=oai.Operation.model_construct(parameters=[param]), parameters=parameters, schemas=schemas, - config=Config(), + config=config, ) assert result == (ParseError(data=param, detail="Path parameter must be required"), schemas, parameters) @@ -392,7 +391,7 @@ def test__add_parameters_with_location_postfix_conflict2(self, mocker, any_prope assert isinstance(result, ParseError) assert result.detail == "Parameters with same Python identifier `prop_name_path` detected" - def test__add_parameters_handles_invalid_references(self): + def test__add_parameters_handles_invalid_references(self, config): """References are not supported as direct params yet""" endpoint = self.make_endpoint() data = oai.Operation.model_construct( @@ -403,13 +402,13 @@ def test__add_parameters_handles_invalid_references(self): parameters = Parameters() (error, _, return_parameters) = endpoint.add_parameters( - endpoint=endpoint, data=data, schemas=Schemas(), parameters=parameters, config=Config() + endpoint=endpoint, data=data, schemas=Schemas(), parameters=parameters, config=config ) assert isinstance(error, ParseError) assert parameters == return_parameters - def test__add_parameters_resolves_references(self, mocker, param_factory): + def test__add_parameters_resolves_references(self, mocker, param_factory, config): """References are not supported as direct params yet""" endpoint = self.make_endpoint() data = oai.Operation.model_construct( @@ -426,13 +425,13 @@ def test__add_parameters_resolves_references(self, mocker, param_factory): parameters.classes_by_reference = {"components/parameters/blah": new_param} (endpoint, _, return_parameters) = endpoint.add_parameters( - endpoint=endpoint, data=data, schemas=Schemas(), parameters=parameters, config=Config() + endpoint=endpoint, data=data, schemas=Schemas(), parameters=parameters, config=config ) assert isinstance(endpoint, Endpoint) assert parameters == return_parameters - def test__add_parameters_skips_params_without_schemas(self): + def test__add_parameters_skips_params_without_schemas(self, config): """Params without schemas are allowed per spec, but the any type doesn't make sense as a parameter""" endpoint = self.make_endpoint() data = oai.Operation.model_construct( @@ -445,13 +444,13 @@ def test__add_parameters_skips_params_without_schemas(self): ) (endpoint, _, _) = endpoint.add_parameters( - endpoint=endpoint, data=data, schemas=Schemas(), parameters=Parameters(), config=Config() + endpoint=endpoint, data=data, schemas=Schemas(), parameters=Parameters(), config=config ) assert isinstance(endpoint, Endpoint) assert len(endpoint.path_parameters) == 0 - def test__add_parameters_same_identifier_conflict(self): + def test__add_parameters_same_identifier_conflict(self, config): endpoint = self.make_endpoint() data = oai.Operation.model_construct( parameters=[ @@ -476,13 +475,13 @@ def test__add_parameters_same_identifier_conflict(self): ) (err, _, _) = endpoint.add_parameters( - endpoint=endpoint, data=data, schemas=Schemas(), parameters=Parameters(), config=Config() + endpoint=endpoint, data=data, schemas=Schemas(), parameters=Parameters(), config=config ) assert isinstance(err, ParseError) assert "param_path" in err.detail - def test__add_parameters_query_optionality(self): + def test__add_parameters_query_optionality(self, config): endpoint = self.make_endpoint() data = oai.Operation.model_construct( parameters=[ @@ -502,7 +501,7 @@ def test__add_parameters_query_optionality(self): ) (endpoint, _, _) = endpoint.add_parameters( - endpoint=endpoint, data=data, schemas=Schemas(), parameters=Parameters(), config=Config() + endpoint=endpoint, data=data, schemas=Schemas(), parameters=Parameters(), config=config ) assert len(endpoint.query_parameters) == 2, "Not all query params were added" # noqa: PLR2004 @@ -512,7 +511,7 @@ def test__add_parameters_query_optionality(self): else: assert not param.required - def test_add_parameters_duplicate_properties(self): + def test_add_parameters_duplicate_properties(self, config): from openapi_python_client.parser.openapi import Endpoint, Schemas endpoint = self.make_endpoint() @@ -522,7 +521,6 @@ def test_add_parameters_duplicate_properties(self): data = oai.Operation.model_construct(parameters=[param, param]) schemas = Schemas() parameters = Parameters() - config = MagicMock() result = Endpoint.add_parameters( endpoint=endpoint, data=data, schemas=schemas, parameters=parameters, config=config @@ -538,7 +536,7 @@ def test_add_parameters_duplicate_properties(self): parameters, ) - def test_add_parameters_duplicate_properties_different_location(self): + def test_add_parameters_duplicate_properties_different_location(self, config): from openapi_python_client.parser.openapi import Endpoint, Schemas endpoint = self.make_endpoint() @@ -550,7 +548,6 @@ def test_add_parameters_duplicate_properties_different_location(self): ) schemas = Schemas() parameters = Parameters() - config = MagicMock() result = Endpoint.add_parameters( endpoint=endpoint, @@ -607,7 +604,7 @@ def test_sort_parameters_extra_param(self, string_property_factory): assert "Incorrect path templating" in result.detail assert endpoint.path in result.detail - def test_from_data_bad_params(self, mocker): + def test_from_data_bad_params(self, mocker, config): from openapi_python_client.parser.openapi import Endpoint path = mocker.MagicMock() @@ -624,7 +621,6 @@ def test_from_data_bad_params(self, mocker): ) initial_schemas = mocker.MagicMock() parameters = Parameters() - config = MagicMock() result = Endpoint.from_data( data=data, @@ -638,7 +634,7 @@ def test_from_data_bad_params(self, mocker): assert result == (parse_error, return_schemas, return_parameters) - def test_from_data_bad_responses(self, mocker): + def test_from_data_bad_responses(self, mocker, config): from openapi_python_client.parser.openapi import Endpoint path = mocker.MagicMock() @@ -659,7 +655,6 @@ def test_from_data_bad_responses(self, mocker): ) initial_schemas = mocker.MagicMock() initial_parameters = mocker.MagicMock() - config = MagicMock() result = Endpoint.from_data( data=data, @@ -673,7 +668,7 @@ def test_from_data_bad_responses(self, mocker): assert result == (parse_error, response_schemas, return_parameters) - def test_from_data_standard(self, mocker): + def test_from_data_standard(self, mocker, config): from openapi_python_client.parser.openapi import Endpoint path = mocker.MagicMock() @@ -697,7 +692,6 @@ def test_from_data_standard(self, mocker): ) initial_schemas = mocker.MagicMock() initial_parameters = mocker.MagicMock() - config = MagicMock() mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=data.description) @@ -730,7 +724,7 @@ def test_from_data_standard(self, mocker): endpoint=param_endpoint, data=data.responses, schemas=param_schemas, config=config ) - def test_from_data_no_operation_id(self, mocker): + def test_from_data_no_operation_id(self, mocker, config): from openapi_python_client.parser.openapi import Endpoint path = "/path/with/{param}/" @@ -749,7 +743,6 @@ def test_from_data_no_operation_id(self, mocker): ) schemas = mocker.MagicMock() mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=data.description) - config = MagicMock() parameters = mocker.MagicMock() endpoint, return_schemas, return_params = Endpoint.from_data( @@ -778,7 +771,7 @@ def test_from_data_no_operation_id(self, mocker): config=config, ) - def test_from_data_no_security(self, mocker): + def test_from_data_no_security(self, mocker, config): from openapi_python_client.parser.openapi import Endpoint data = oai.Operation.model_construct( @@ -798,7 +791,6 @@ def test_from_data_no_security(self, mocker): mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=data.description) schemas = mocker.MagicMock() parameters = mocker.MagicMock() - config = MagicMock() Endpoint.from_data( data=data, path=path, method=method, tag="a", schemas=schemas, parameters=parameters, config=config @@ -826,7 +818,7 @@ def test_from_data_no_security(self, mocker): config=config, ) - def test_from_data_some_bad_bodies(self): + def test_from_data_some_bad_bodies(self, config): endpoint, _, _ = Endpoint.from_data( data=oai.Operation( responses={}, @@ -838,7 +830,7 @@ def test_from_data_some_bad_bodies(self): ), ), schemas=Schemas(), - config=Config(), + config=config, parameters=Parameters(), tag="tag", path="/", @@ -849,7 +841,7 @@ def test_from_data_some_bad_bodies(self): assert len(endpoint.bodies) == 1 assert len(endpoint.errors) == 1 - def test_from_data_all_bodies_bad(self): + def test_from_data_all_bodies_bad(self, config): endpoint, _, _ = Endpoint.from_data( data=oai.Operation( responses={}, @@ -860,7 +852,7 @@ def test_from_data_all_bodies_bad(self): ), ), schemas=Schemas(), - config=Config(), + config=config, parameters=Parameters(), tag="tag", path="/", @@ -905,7 +897,7 @@ def test_import_string_from_reference_with_prefix(self, mocker): class TestEndpointCollection: - def test_from_data(self, mocker): + def test_from_data(self, mocker, config): from openapi_python_client.parser.openapi import Endpoint, EndpointCollection path_1_put = oai.Operation.model_construct() @@ -935,7 +927,6 @@ def test_from_data(self, mocker): ) schemas = mocker.MagicMock() parameters = mocker.MagicMock() - config = MagicMock() result = EndpointCollection.from_data(data=data, schemas=schemas, parameters=parameters, config=config) @@ -979,7 +970,7 @@ def test_from_data(self, mocker): parameters_3, ) - def test_from_data_overrides_path_item_params_with_operation_params(self): + def test_from_data_overrides_path_item_params_with_operation_params(self, config): data = { "/": oai.PathItem.model_construct( parameters=[ @@ -1002,12 +993,12 @@ def test_from_data_overrides_path_item_params_with_operation_params(self): data=data, schemas=Schemas(), parameters=Parameters(), - config=Config(), + config=config, ) collection: EndpointCollection = collections["default"] assert isinstance(collection.endpoints[0].query_parameters["param"], IntProperty) - def test_from_data_errors(self, mocker): + def test_from_data_errors(self, mocker, config): from openapi_python_client.parser.openapi import ParseError path_1_put = oai.Operation.model_construct() @@ -1034,7 +1025,6 @@ def test_from_data_errors(self, mocker): ) schemas = mocker.MagicMock() parameters = mocker.MagicMock() - config = MagicMock() result, result_schemas, result_parameters = EndpointCollection.from_data( data=data, schemas=schemas, config=config, parameters=parameters @@ -1076,7 +1066,7 @@ def test_from_data_errors(self, mocker): assert result["tag_2"].parse_errors[0].data == "2" assert result_schemas == schemas_3 - def test_from_data_tags_snake_case_sanitizer(self, mocker): + def test_from_data_tags_snake_case_sanitizer(self, mocker, config): from openapi_python_client.parser.openapi import Endpoint, EndpointCollection path_1_put = oai.Operation.model_construct() @@ -1108,7 +1098,6 @@ def test_from_data_tags_snake_case_sanitizer(self, mocker): ) schemas = mocker.MagicMock() parameters = mocker.MagicMock() - config = MagicMock() result = EndpointCollection.from_data(data=data, schemas=schemas, parameters=parameters, config=config) diff --git a/tests/test_parser/test_properties/test_enum_property.py b/tests/test_parser/test_properties/test_enum_property.py index 7fe5c8855..704f48b3b 100644 --- a/tests/test_parser/test_properties/test_enum_property.py +++ b/tests/test_parser/test_properties/test_enum_property.py @@ -1,14 +1,13 @@ import openapi_python_client.schema as oai -from openapi_python_client import Config from openapi_python_client.parser.errors import PropertyError from openapi_python_client.parser.properties import EnumProperty, Schemas -def test_conflict(): +def test_conflict(config): schemas = Schemas() _, schemas = EnumProperty.build( - data=oai.Schema(enum=["a"]), name="Existing", required=True, schemas=schemas, parent_name="", config=Config() + data=oai.Schema(enum=["a"]), name="Existing", required=True, schemas=schemas, parent_name="", config=config ) err, new_schemas = EnumProperty.build( data=oai.Schema(enum=["a", "b"]), @@ -16,54 +15,54 @@ def test_conflict(): required=True, schemas=schemas, parent_name="", - config=Config(), + config=config, ) assert schemas == new_schemas assert err.detail == "Found conflicting enums named Existing with incompatible values." -def test_bad_default_value(): +def test_bad_default_value(config): data = oai.Schema(default="B", enum=["A"]) schemas = Schemas() err, new_schemas = EnumProperty.build( - data=data, name="Existing", required=True, schemas=schemas, parent_name="parent", config=Config() + data=data, name="Existing", required=True, schemas=schemas, parent_name="parent", config=config ) assert schemas == new_schemas assert err == PropertyError(detail="Value B is not valid for enum Existing", data=data) -def test_bad_default_type(): +def test_bad_default_type(config): data = oai.Schema(default=123, enum=["A"]) schemas = Schemas() err, new_schemas = EnumProperty.build( - data=data, name="Existing", required=True, schemas=schemas, parent_name="parent", config=Config() + data=data, name="Existing", required=True, schemas=schemas, parent_name="parent", config=config ) assert schemas == new_schemas assert isinstance(err, PropertyError) -def test_mixed_types(): +def test_mixed_types(config): data = oai.Schema(enum=["A", 1]) schemas = Schemas() err, _ = EnumProperty.build( - data=data, name="Enum", required=True, schemas=schemas, parent_name="parent", config=Config() + data=data, name="Enum", required=True, schemas=schemas, parent_name="parent", config=config ) assert isinstance(err, PropertyError) -def test_unsupported_type(): +def test_unsupported_type(config): data = oai.Schema(enum=[1.4, 1.5]) schemas = Schemas() err, _ = EnumProperty.build( - data=data, name="Enum", required=True, schemas=schemas, parent_name="parent", config=Config() + data=data, name="Enum", required=True, schemas=schemas, parent_name="parent", config=config ) assert isinstance(err, PropertyError) diff --git a/tests/test_parser/test_properties/test_init.py b/tests/test_parser/test_properties/test_init.py index 50f3559e0..3290dcd39 100644 --- a/tests/test_parser/test_properties/test_init.py +++ b/tests/test_parser/test_properties/test_init.py @@ -4,7 +4,6 @@ import pytest import openapi_python_client.schema as oai -from openapi_python_client import Config from openapi_python_client.parser.errors import ParameterError, PropertyError from openapi_python_client.parser.properties import ( ListProperty, @@ -12,7 +11,9 @@ StringProperty, UnionProperty, ) +from openapi_python_client.parser.properties.protocol import ModelProperty from openapi_python_client.schema import DataType +from openapi_python_client.utils import ClassName, PythonIdentifier MODULE_NAME = "openapi_python_client.parser.properties" @@ -373,7 +374,7 @@ def test_values_from_list_duplicate(self): class TestPropertyFromData: - def test_property_from_data_str_enum(self, enum_property_factory): + def test_property_from_data_str_enum(self, enum_property_factory, config): from openapi_python_client.parser.properties import Class, Schemas, property_from_data from openapi_python_client.schema import Schema @@ -385,7 +386,7 @@ def test_property_from_data_str_enum(self, enum_property_factory): schemas = Schemas(classes_by_name={"AnEnum": existing}) prop, new_schemas = property_from_data( - name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=Config() + name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=config ) assert prop == enum_property_factory( @@ -403,7 +404,7 @@ def test_property_from_data_str_enum(self, enum_property_factory): } def test_property_from_data_str_enum_with_null( - self, enum_property_factory, union_property_factory, none_property_factory + self, enum_property_factory, union_property_factory, none_property_factory, config ): from openapi_python_client.parser.properties import Class, Schemas, property_from_data from openapi_python_client.schema import Schema @@ -416,7 +417,7 @@ def test_property_from_data_str_enum_with_null( schemas = Schemas(classes_by_name={"AnEnum": existing}) prop, new_schemas = property_from_data( - name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=Config() + name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=config ) # None / null is removed from enum, and property is now nullable @@ -439,7 +440,7 @@ def test_property_from_data_str_enum_with_null( "ParentAnEnum": enum_prop, } - def test_property_from_data_null_enum(self, enum_property_factory, none_property_factory): + def test_property_from_data_null_enum(self, enum_property_factory, none_property_factory, config): from openapi_python_client.parser.properties import Schemas, property_from_data from openapi_python_client.schema import Schema @@ -450,12 +451,12 @@ def test_property_from_data_null_enum(self, enum_property_factory, none_property schemas = Schemas() prop, new_schemas = property_from_data( - name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=Config() + name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=config ) assert prop == none_property_factory(name="my_enum", required=required, default="None") - def test_property_from_data_int_enum(self, enum_property_factory): + def test_property_from_data_int_enum(self, enum_property_factory, config): from openapi_python_client.parser.properties import Class, Schemas, property_from_data from openapi_python_client.schema import Schema @@ -467,7 +468,7 @@ def test_property_from_data_int_enum(self, enum_property_factory): schemas = Schemas(classes_by_name={"AnEnum": existing}) prop, new_schemas = property_from_data( - name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=Config() + name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=config ) assert prop == enum_property_factory( @@ -484,7 +485,7 @@ def test_property_from_data_int_enum(self, enum_property_factory): "ParentAnEnum": prop, } - def test_property_from_data_ref_enum(self, enum_property_factory): + def test_property_from_data_ref_enum(self, enum_property_factory, config): from openapi_python_client.parser.properties import Class, Schemas, property_from_data name = "some_enum" @@ -498,7 +499,7 @@ def test_property_from_data_ref_enum(self, enum_property_factory): schemas = Schemas(classes_by_reference={"/components/schemas/MyEnum": existing_enum}) prop, new_schemas = property_from_data( - name=name, required=False, data=data, schemas=schemas, parent_name="", config=Config() + name=name, required=False, data=data, schemas=schemas, parent_name="", config=config ) assert prop == enum_property_factory( @@ -509,7 +510,7 @@ def test_property_from_data_ref_enum(self, enum_property_factory): ) assert schemas == new_schemas - def test_property_from_data_ref_enum_with_overridden_default(self, enum_property_factory): + def test_property_from_data_ref_enum_with_overridden_default(self, enum_property_factory, config): from openapi_python_client.parser.properties import Class, Schemas, property_from_data name = "some_enum" @@ -527,7 +528,7 @@ def test_property_from_data_ref_enum_with_overridden_default(self, enum_property schemas = Schemas(classes_by_reference={"/components/schemas/MyEnum": existing_enum}) prop, new_schemas = property_from_data( - name=name, required=required, data=data, schemas=schemas, parent_name="", config=Config() + name=name, required=required, data=data, schemas=schemas, parent_name="", config=config ) assert prop == enum_property_factory( @@ -539,7 +540,7 @@ def test_property_from_data_ref_enum_with_overridden_default(self, enum_property ) assert schemas == new_schemas - def test_property_from_data_ref_enum_with_invalid_default(self, enum_property_factory): + def test_property_from_data_ref_enum_with_invalid_default(self, enum_property_factory, config): from openapi_python_client.parser.properties import Class, Schemas, property_from_data name = "some_enum" @@ -556,13 +557,13 @@ def test_property_from_data_ref_enum_with_invalid_default(self, enum_property_fa schemas = Schemas(classes_by_reference={"/components/schemas/MyEnum": existing_enum}) prop, new_schemas = property_from_data( - name=name, required=False, data=data, schemas=schemas, parent_name="", config=Config() + name=name, required=False, data=data, schemas=schemas, parent_name="", config=config ) assert schemas == new_schemas assert prop == PropertyError(data=data, detail="Value x is not valid for enum an_enum") - def test_property_from_data_ref_model(self, model_property_factory): + def test_property_from_data_ref_model(self, model_property_factory, config): from openapi_python_client.parser.properties import Class, Schemas, property_from_data name = "new_name" @@ -578,7 +579,7 @@ def test_property_from_data_ref_model(self, model_property_factory): schemas = Schemas(classes_by_reference={f"/components/schemas/{class_name}": existing_model}) prop, new_schemas = property_from_data( - name=name, required=required, data=data, schemas=schemas, parent_name="", config=Config() + name=name, required=required, data=data, schemas=schemas, parent_name="", config=config ) assert prop == model_property_factory( @@ -605,7 +606,7 @@ def test_property_from_data_ref_not_found(self, mocker): assert schemas.dependencies == {} @pytest.mark.parametrize("references_exist", (True, False)) - def test_property_from_data_ref(self, any_property_factory, references_exist): + def test_property_from_data_ref(self, any_property_factory, references_exist, config): from openapi_python_client.parser.properties import Schemas, property_from_data name = "new_name" @@ -619,7 +620,7 @@ def test_property_from_data_ref(self, any_property_factory, references_exist): schemas = Schemas(classes_by_reference={ref_path: existing_property}, dependencies=references) prop, new_schemas = property_from_data( - name=name, required=required, data=data, schemas=schemas, parent_name="", config=Config(), roots=roots + name=name, required=required, data=data, schemas=schemas, parent_name="", config=config, roots=roots ) assert prop == any_property_factory(name=name, required=required) @@ -645,7 +646,7 @@ def test_property_from_data_invalid_ref(self, mocker): assert prop == PropertyError(data=data, detail="bad stuff") assert schemas == new_schemas - def test_property_from_data_array(self): + def test_property_from_data_array(self, config): from openapi_python_client.parser.properties import Schemas, property_from_data name = "a_list_prop" @@ -655,7 +656,6 @@ def test_property_from_data_array(self): items=oai.Schema(type=DataType.STRING), ) schemas = Schemas() - config = Config() response = property_from_data( name=name, @@ -669,7 +669,7 @@ def test_property_from_data_array(self): assert isinstance(response, ListProperty) assert isinstance(response.inner_property, StringProperty) - def test_property_from_data_union(self): + def test_property_from_data_union(self, config): from openapi_python_client.parser.properties import Schemas, property_from_data name = "union_prop" @@ -681,7 +681,6 @@ def test_property_from_data_union(self): ], ) schemas = Schemas() - config = Config() response = property_from_data( name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=config @@ -690,7 +689,7 @@ def test_property_from_data_union(self): assert isinstance(response, UnionProperty) assert len(response.inner_properties) == 2 # noqa: PLR2004 - def test_property_from_data_list_of_types(self): + def test_property_from_data_list_of_types(self, config): from openapi_python_client.parser.properties import Schemas, property_from_data name = "union_prop" @@ -699,7 +698,6 @@ def test_property_from_data_list_of_types(self): type=[DataType.NUMBER, DataType.NULL], ) schemas = Schemas() - config = Config() response = property_from_data( name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=config @@ -708,13 +706,13 @@ def test_property_from_data_list_of_types(self): assert isinstance(response, UnionProperty) assert len(response.inner_properties) == 2 # noqa: PLR2004 - def test_property_from_data_union_of_one_element(self, model_property_factory): + def test_property_from_data_union_of_one_element(self, model_property_factory, config): from openapi_python_client.parser.properties import Schemas, property_from_data name = "new_name" required = False class_name = "MyModel" - existing_model = model_property_factory() + existing_model: ModelProperty = model_property_factory() schemas = Schemas(classes_by_reference={f"/{class_name}": existing_model}) data = oai.Schema.model_construct( @@ -722,10 +720,10 @@ def test_property_from_data_union_of_one_element(self, model_property_factory): ) prop, schemas = property_from_data( - name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=Config() + name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=config ) - assert prop == attr.evolve(existing_model, name=name, required=required, python_name=name) + assert prop == attr.evolve(existing_model, name=name, required=required, python_name=PythonIdentifier(name, "")) def test_property_from_data_no_valid_props_in_data(self, any_property_factory): from openapi_python_client.parser.properties import Schemas, property_from_data @@ -744,21 +742,21 @@ def test_property_from_data_no_valid_props_in_data(self, any_property_factory): class TestStringBasedProperty: @pytest.mark.parametrize("required", (True, False)) - def test_no_format(self, string_property_factory, required): + def test_no_format(self, string_property_factory, required, config): from openapi_python_client.parser.properties import property_from_data name = "some_prop" data = oai.Schema.model_construct(type="string", default='"hello world"', pattern="abcdef") p, _ = property_from_data( - name=name, required=required, data=data, parent_name=None, config=Config(), schemas=Schemas() + name=name, required=required, data=data, parent_name=None, config=config, schemas=Schemas() ) assert p == string_property_factory( name=name, required=required, default="'\\\\\"hello world\\\\\"'", pattern=data.pattern ) - def test_datetime_format(self, date_time_property_factory): + def test_datetime_format(self, date_time_property_factory, config): from openapi_python_client.parser.properties import property_from_data name = "datetime_prop" @@ -766,12 +764,12 @@ def test_datetime_format(self, date_time_property_factory): data = oai.Schema.model_construct(type="string", schema_format="date-time", default="2020-11-06T12:00:00") p, _ = property_from_data( - name=name, required=required, data=data, schemas=Schemas(), config=Config(), parent_name=None + name=name, required=required, data=data, schemas=Schemas(), config=config, parent_name="" ) assert p == date_time_property_factory(name=name, required=required, default=f"isoparse('{data.default}')") - def test_datetime_bad_default(self): + def test_datetime_bad_default(self, config): from openapi_python_client.parser.properties import property_from_data name = "datetime_prop" @@ -779,13 +777,13 @@ def test_datetime_bad_default(self): data = oai.Schema.model_construct(type="string", schema_format="date-time", default="a") result, _ = property_from_data( - name=name, required=required, data=data, schemas=Schemas(), config=Config(), parent_name=None + name=name, required=required, data=data, schemas=Schemas(), config=config, parent_name="" ) assert isinstance(result, PropertyError) assert result.detail.startswith("Invalid datetime") - def test_date_format(self, date_property_factory): + def test_date_format(self, date_property_factory, config): from openapi_python_client.parser.properties import property_from_data name = "date_prop" @@ -794,12 +792,12 @@ def test_date_format(self, date_property_factory): data = oai.Schema.model_construct(type="string", schema_format="date", default="2020-11-06") p, _ = property_from_data( - name=name, required=required, data=data, schemas=Schemas(), config=Config(), parent_name=None + name=name, required=required, data=data, schemas=Schemas(), config=config, parent_name="" ) assert p == date_property_factory(name=name, required=required, default=f"isoparse('{data.default}').date()") - def test_date_format_bad_default(self): + def test_date_format_bad_default(self, config): from openapi_python_client.parser.properties import property_from_data name = "date_prop" @@ -808,13 +806,13 @@ def test_date_format_bad_default(self): data = oai.Schema.model_construct(type="string", schema_format="date", default="a") p, _ = property_from_data( - name=name, required=required, data=data, schemas=Schemas(), config=Config(), parent_name=None + name=name, required=required, data=data, schemas=Schemas(), config=config, parent_name="" ) assert isinstance(p, PropertyError) assert p.detail.startswith("Invalid date") - def test__string_based_property_binary_format(self, file_property_factory): + def test__string_based_property_binary_format(self, file_property_factory, config): from openapi_python_client.parser.properties import property_from_data name = "file_prop" @@ -822,11 +820,11 @@ def test__string_based_property_binary_format(self, file_property_factory): data = oai.Schema.model_construct(type="string", schema_format="binary", default="a") p, _ = property_from_data( - name=name, required=required, data=data, schemas=Schemas(), config=Config(), parent_name=None + name=name, required=required, data=data, schemas=Schemas(), config=config, parent_name="" ) assert p == file_property_factory(name=name, required=required) - def test__string_based_property_unsupported_format(self, string_property_factory): + def test__string_based_property_unsupported_format(self, string_property_factory, config): from openapi_python_client.parser.properties import property_from_data name = "unknown" @@ -834,21 +832,20 @@ def test__string_based_property_unsupported_format(self, string_property_factory data = oai.Schema.model_construct(type="string", schema_format="blah") p, _ = property_from_data( - name=name, required=required, data=data, schemas=Schemas, config=Config(), parent_name=None + name=name, required=required, data=data, schemas=Schemas(), config=config, parent_name="" ) assert p == string_property_factory(name=name, required=required) class TestCreateSchemas: - def test_skips_references_and_keeps_going(self, mocker): + def test_skips_references_and_keeps_going(self, mocker, config): from openapi_python_client.parser.properties import Schemas, _create_schemas from openapi_python_client.schema import Reference, Schema components = {"a_ref": Reference.model_construct(), "a_schema": Schema.model_construct()} update_schemas_with_data = mocker.patch(f"{MODULE_NAME}.update_schemas_with_data") parse_reference_path = mocker.patch(f"{MODULE_NAME}.parse_reference_path") - config = Config() schemas = Schemas() result = _create_schemas(components=components, schemas=schemas, config=config) @@ -864,7 +861,7 @@ def test_skips_references_and_keeps_going(self, mocker): ) assert result == update_schemas_with_data.return_value - def test_records_bad_uris_and_keeps_going(self, mocker): + def test_records_bad_uris_and_keeps_going(self, mocker, config): from openapi_python_client.parser.properties import Schemas, _create_schemas from openapi_python_client.schema import Schema @@ -873,7 +870,6 @@ def test_records_bad_uris_and_keeps_going(self, mocker): parse_reference_path = mocker.patch( f"{MODULE_NAME}.parse_reference_path", side_effect=[PropertyError(detail="some details"), "a_path"] ) - config = Config() schemas = Schemas() result = _create_schemas(components=components, schemas=schemas, config=config) @@ -891,7 +887,7 @@ def test_records_bad_uris_and_keeps_going(self, mocker): ) assert result == update_schemas_with_data.return_value - def test_retries_failing_properties_while_making_progress(self, mocker): + def test_retries_failing_properties_while_making_progress(self, mocker, config): from openapi_python_client.parser.properties import Schemas, _create_schemas from openapi_python_client.schema import Schema @@ -900,7 +896,6 @@ def test_retries_failing_properties_while_making_progress(self, mocker): f"{MODULE_NAME}.update_schemas_with_data", side_effect=[PropertyError(), Schemas(), PropertyError()] ) parse_reference_path = mocker.patch(f"{MODULE_NAME}.parse_reference_path") - config = Config() schemas = Schemas() result = _create_schemas(components=components, schemas=schemas, config=config) @@ -916,41 +911,45 @@ def test_retries_failing_properties_while_making_progress(self, mocker): class TestProcessModels: - def test_retries_failing_models_while_making_progress(self, mocker, model_property_factory, any_property_factory): + def test_retries_failing_models_while_making_progress( + self, mocker, model_property_factory, any_property_factory, config + ): from openapi_python_client.parser.properties import _process_models first_model = model_property_factory() + second_class_name = ClassName("second", "") schemas = Schemas( classes_by_name={ - "first": first_model, - "second": model_property_factory(), - "non-model": any_property_factory(), + ClassName("first", ""): first_model, + second_class_name: model_property_factory(), + ClassName("non-model", ""): any_property_factory(), } ) process_model = mocker.patch( f"{MODULE_NAME}.process_model", side_effect=[PropertyError(), Schemas(), PropertyError()] ) process_model_errors = mocker.patch(f"{MODULE_NAME}._process_model_errors", return_value=["error"]) - config = Config() result = _process_models(schemas=schemas, config=config) process_model.assert_has_calls( [ call(first_model, schemas=schemas, config=config), - call(schemas.classes_by_name["second"], schemas=schemas, config=config), + call(schemas.classes_by_name[second_class_name], schemas=schemas, config=config), call(first_model, schemas=result, config=config), ] ) assert process_model_errors.was_called_once_with([(first_model, PropertyError())]) assert all(error in result.errors for error in process_model_errors.return_value) - def test_detect_recursive_allof_reference_no_retry(self, mocker, model_property_factory): + def test_detect_recursive_allof_reference_no_retry(self, mocker, model_property_factory, config): from openapi_python_client.parser.properties import Class, _process_models from openapi_python_client.schema import Reference - class_name = "class_name" - recursive_model = model_property_factory(class_info=Class(name=class_name, module_name="module_name")) + class_name = ClassName("class_name", "") + recursive_model = model_property_factory( + class_info=Class(name=class_name, module_name=PythonIdentifier("module_name", "")) + ) schemas = Schemas( classes_by_name={ "recursive": recursive_model, @@ -960,7 +959,6 @@ def test_detect_recursive_allof_reference_no_retry(self, mocker, model_property_ recursion_error = PropertyError(data=Reference.model_construct(ref=f"#/{class_name}")) process_model = mocker.patch(f"{MODULE_NAME}.process_model", side_effect=[recursion_error, Schemas()]) process_model_errors = mocker.patch(f"{MODULE_NAME}._process_model_errors", return_value=["error"]) - config = Config() result = _process_models(schemas=schemas, config=config) @@ -1074,7 +1072,7 @@ def test_process_model_errors(mocker, model_property_factory): class TestBuildParameters: - def test_skips_references_and_keeps_going(self, mocker): + def test_skips_references_and_keeps_going(self, mocker, config): from openapi_python_client.parser.properties import Parameters, build_parameters from openapi_python_client.schema import Parameter, Reference @@ -1093,8 +1091,7 @@ def test_skips_references_and_keeps_going(self, mocker): update_parameters_with_data = mocker.patch(f"{MODULE_NAME}.update_parameters_with_data") parse_reference_path = mocker.patch(f"{MODULE_NAME}.parse_reference_path") - config = Config() - result = build_parameters(components=parameters, parameters=Parameters(), config=Config()) + result = build_parameters(components=parameters, parameters=Parameters(), config=config) # Should not even try to parse a path for the Reference parse_reference_path.assert_called_once_with("#/components/parameters/defined") update_parameters_with_data.assert_called_once_with( @@ -1107,7 +1104,7 @@ def test_skips_references_and_keeps_going(self, mocker): ) assert result == update_parameters_with_data.return_value - def test_records_bad_uris_and_keeps_going(self, mocker): + def test_records_bad_uris_and_keeps_going(self, mocker, config): from openapi_python_client.parser.properties import Parameters, build_parameters from openapi_python_client.schema import Parameter @@ -1117,7 +1114,6 @@ def test_records_bad_uris_and_keeps_going(self, mocker): f"{MODULE_NAME}.parse_reference_path", side_effect=[ParameterError(detail="some details"), "a_path"] ) - config = Config() result = build_parameters(components=parameters, parameters=Parameters(), config=config) parse_reference_path.assert_has_calls( [ @@ -1133,7 +1129,7 @@ def test_records_bad_uris_and_keeps_going(self, mocker): ) assert result == update_parameters_with_data.return_value - def test_retries_failing_parameters_while_making_progress(self, mocker): + def test_retries_failing_parameters_while_making_progress(self, mocker, config): from openapi_python_client.parser.properties import Parameters, build_parameters from openapi_python_client.schema import Parameter @@ -1143,7 +1139,6 @@ def test_retries_failing_parameters_while_making_progress(self, mocker): ) parse_reference_path = mocker.patch(f"{MODULE_NAME}.parse_reference_path") - config = Config() result = build_parameters(components=parameters, parameters=Parameters(), config=config) parse_reference_path.assert_has_calls( [ @@ -1156,7 +1151,7 @@ def test_retries_failing_parameters_while_making_progress(self, mocker): assert result.errors == [ParameterError()] -def test_build_schemas(mocker): +def test_build_schemas(mocker, config): from openapi_python_client.parser.properties import Schemas, build_schemas from openapi_python_client.schema import Reference, Schema @@ -1165,7 +1160,6 @@ def test_build_schemas(mocker): components = {"a_ref": Reference.model_construct(), "a_schema": Schema.model_construct()} schemas = Schemas() - config = Config() result = build_schemas(components=components, schemas=schemas, config=config) diff --git a/tests/test_parser/test_properties/test_list_property.py b/tests/test_parser/test_properties/test_list_property.py index cbed9dfd2..bac87e669 100644 --- a/tests/test_parser/test_properties/test_list_property.py +++ b/tests/test_parser/test_properties/test_list_property.py @@ -1,13 +1,12 @@ import attr import openapi_python_client.schema as oai -from openapi_python_client import Config from openapi_python_client.parser.errors import PropertyError from openapi_python_client.parser.properties import ListProperty from openapi_python_client.schema import DataType -def test_build_list_property_no_items(): +def test_build_list_property_no_items(config): from openapi_python_client.parser import properties name = "list_prop" @@ -21,7 +20,7 @@ def test_build_list_property_no_items(): data=data, schemas=schemas, parent_name="parent", - config=Config(), + config=config, process_properties=True, roots={"root"}, ) @@ -30,7 +29,7 @@ def test_build_list_property_no_items(): assert new_schemas == schemas -def test_build_list_property_invalid_items(): +def test_build_list_property_invalid_items(config): from openapi_python_client.parser import properties name = "name" @@ -40,7 +39,6 @@ def test_build_list_property_invalid_items(): items=oai.Reference(ref="doesnt exist"), ) schemas = properties.Schemas(errors=["error"]) - config = Config() process_properties = False roots = {"root"} @@ -61,7 +59,7 @@ def test_build_list_property_invalid_items(): assert new_schemas == schemas -def test_build_list_property(any_property_factory): +def test_build_list_property(any_property_factory, config): from openapi_python_client.parser import properties name = "prop" @@ -70,7 +68,6 @@ def test_build_list_property(any_property_factory): items=oai.Schema(), ) schemas = properties.Schemas(errors=["error"]) - config = Config() p, new_schemas = ListProperty.build( name=name, diff --git a/tests/test_parser/test_properties/test_model_property.py b/tests/test_parser/test_properties/test_model_property.py index f92a94de3..60629b40f 100644 --- a/tests/test_parser/test_properties/test_model_property.py +++ b/tests/test_parser/test_properties/test_model_property.py @@ -2,9 +2,9 @@ from unittest.mock import MagicMock import pytest +from attr._funcs import evolve import openapi_python_client.schema as oai -from openapi_python_client import Config from openapi_python_client.parser.errors import PropertyError from openapi_python_client.parser.properties import StringProperty @@ -87,7 +87,7 @@ class TestBuild: ), ], ) - def test_additional_schemas(self, additional_properties_schema, expected_additional_properties): + def test_additional_schemas(self, additional_properties_schema, expected_additional_properties, config): from openapi_python_client.parser.properties import ModelProperty, Schemas data = oai.Schema.model_construct( @@ -100,14 +100,14 @@ def test_additional_schemas(self, additional_properties_schema, expected_additio schemas=Schemas(), required=True, parent_name="parent", - config=Config(), + config=config, roots={"root"}, process_properties=True, ) assert model.additional_properties == expected_additional_properties - def test_happy_path(self, model_property_factory, string_property_factory, date_time_property_factory): + def test_happy_path(self, model_property_factory, string_property_factory, date_time_property_factory, config): from openapi_python_client.parser.properties import Class, ModelProperty, Schemas name = "prop" @@ -132,7 +132,7 @@ def test_happy_path(self, model_property_factory, string_property_factory, date_ schemas=schemas, required=required, parent_name="parent", - config=Config(), + config=config, roots=roots, process_properties=True, ) @@ -166,7 +166,7 @@ def test_happy_path(self, model_property_factory, string_property_factory, date_ additional_properties=True, ) - def test_model_name_conflict(self): + def test_model_name_conflict(self, config): from openapi_python_client.parser.properties import ModelProperty, Schemas data = oai.Schema.model_construct() @@ -178,7 +178,7 @@ def test_model_name_conflict(self): schemas=schemas, required=True, parent_name=None, - config=Config(), + config=config, roots={"root"}, process_properties=True, ) @@ -206,7 +206,13 @@ def test_model_name_conflict(self): ), ) def test_model_naming( - self, name: str, title: Optional[str], parent_name: Optional[str], use_title_prefixing: bool, expected: str + self, + name: str, + title: Optional[str], + parent_name: Optional[str], + use_title_prefixing: bool, + expected: str, + config, ): from openapi_python_client.parser.properties import ModelProperty, Schemas @@ -214,19 +220,20 @@ def test_model_naming( title=title, properties={}, ) + config = evolve(config, use_path_prefixes_for_title_model_names=use_title_prefixing) result = ModelProperty.build( data=data, name=name, schemas=Schemas(), required=True, parent_name=parent_name, - config=Config(use_path_prefixes_for_title_model_names=use_title_prefixing), + config=config, roots={"root"}, process_properties=True, )[0] assert result.class_info.name == expected - def test_model_bad_properties(self): + def test_model_bad_properties(self, config): from openapi_python_client.parser.properties import ModelProperty, Schemas data = oai.Schema( @@ -240,13 +247,13 @@ def test_model_bad_properties(self): schemas=Schemas(), required=True, parent_name="parent", - config=Config(), + config=config, roots={"root"}, process_properties=True, )[0] assert isinstance(result, PropertyError) - def test_model_bad_additional_properties(self): + def test_model_bad_additional_properties(self, config): from openapi_python_client.parser.properties import ModelProperty, Schemas additional_properties = oai.Schema( @@ -262,13 +269,13 @@ def test_model_bad_additional_properties(self): schemas=Schemas(), required=True, parent_name="parent", - config=Config(), + config=config, roots={"root"}, process_properties=True, )[0] assert isinstance(result, PropertyError) - def test_process_properties_false(self, model_property_factory): + def test_process_properties_false(self, model_property_factory, config): from openapi_python_client.parser.properties import Class, ModelProperty, Schemas name = "prop" @@ -293,7 +300,7 @@ def test_process_properties_false(self, model_property_factory): schemas=schemas, required=required, parent_name="parent", - config=Config(), + config=config, roots=roots, process_properties=False, ) @@ -318,7 +325,7 @@ def test_process_properties_false(self, model_property_factory): class TestProcessProperties: def test_conflicting_properties_different_types( - self, model_property_factory, string_property_factory, date_time_property_factory + self, model_property_factory, string_property_factory, date_time_property_factory, config ): from openapi_python_client.parser.properties import Schemas from openapi_python_client.parser.properties.model_property import _process_properties @@ -337,11 +344,11 @@ def test_conflicting_properties_different_types( } ) - result = _process_properties(data=data, schemas=schemas, class_name="", config=Config(), roots={"root"}) + result = _process_properties(data=data, schemas=schemas, class_name="", config=config, roots={"root"}) assert isinstance(result, PropertyError) - def test_process_properties_reference_not_exist(self): + def test_process_properties_reference_not_exist(self, config): from openapi_python_client.parser.properties import Schemas from openapi_python_client.parser.properties.model_property import _process_properties @@ -351,43 +358,43 @@ def test_process_properties_reference_not_exist(self): }, ) - result = _process_properties(data=data, class_name="", schemas=Schemas(), config=Config(), roots={"root"}) + result = _process_properties(data=data, class_name="", schemas=Schemas(), config=config, roots={"root"}) assert isinstance(result, PropertyError) - def test_process_properties_all_of_reference_not_exist(self): + def test_process_properties_all_of_reference_not_exist(self, config): from openapi_python_client.parser.properties import Schemas from openapi_python_client.parser.properties.model_property import _process_properties data = oai.Schema.model_construct(allOf=[oai.Reference.model_construct(ref="#/components/schema/NotExist")]) - result = _process_properties(data=data, class_name="", schemas=Schemas(), config=Config(), roots={"root"}) + result = _process_properties(data=data, class_name="", schemas=Schemas(), config=config, roots={"root"}) assert isinstance(result, PropertyError) - def test_process_properties_model_property_roots(self, model_property_factory): + def test_process_properties_model_property_roots(self, model_property_factory, config): from openapi_python_client.parser.properties import Schemas from openapi_python_client.parser.properties.model_property import _process_properties roots = {"root"} data = oai.Schema(properties={"test_model_property": oai.Schema.model_construct(type="object")}) - result = _process_properties(data=data, class_name="", schemas=Schemas(), config=Config(), roots=roots) + result = _process_properties(data=data, class_name="", schemas=Schemas(), config=config, roots=roots) assert all(root in result.optional_props[0].roots for root in roots) - def test_invalid_reference(self): + def test_invalid_reference(self, config): from openapi_python_client.parser.properties import Schemas from openapi_python_client.parser.properties.model_property import _process_properties data = oai.Schema.model_construct(allOf=[oai.Reference.model_construct(ref="ThisIsNotGood")]) schemas = Schemas() - result = _process_properties(data=data, schemas=schemas, class_name="", config=Config(), roots={"root"}) + result = _process_properties(data=data, schemas=schemas, class_name="", config=config, roots={"root"}) assert isinstance(result, PropertyError) - def test_non_model_reference(self, enum_property_factory): + def test_non_model_reference(self, enum_property_factory, config): from openapi_python_client.parser.properties import Schemas from openapi_python_client.parser.properties.model_property import _process_properties @@ -398,11 +405,11 @@ def test_non_model_reference(self, enum_property_factory): } ) - result = _process_properties(data=data, schemas=schemas, class_name="", config=Config(), roots={"root"}) + result = _process_properties(data=data, schemas=schemas, class_name="", config=config, roots={"root"}) assert isinstance(result, PropertyError) - def test_reference_not_processed(self, model_property_factory): + def test_reference_not_processed(self, model_property_factory, config): from openapi_python_client.parser.properties import Schemas from openapi_python_client.parser.properties.model_property import _process_properties @@ -413,11 +420,11 @@ def test_reference_not_processed(self, model_property_factory): } ) - result = _process_properties(data=data, schemas=schemas, class_name="", config=Config(), roots={"root"}) + result = _process_properties(data=data, schemas=schemas, class_name="", config=config, roots={"root"}) assert isinstance(result, PropertyError) - def test_conflicting_properties_same_types(self, model_property_factory, string_property_factory): + def test_conflicting_properties_same_types(self, model_property_factory, string_property_factory, config): from openapi_python_client.parser.properties import Schemas from openapi_python_client.parser.properties.model_property import _process_properties @@ -435,11 +442,13 @@ def test_conflicting_properties_same_types(self, model_property_factory, string_ } ) - result = _process_properties(data=data, schemas=schemas, class_name="", config=Config(), roots={"root"}) + result = _process_properties(data=data, schemas=schemas, class_name="", config=config, roots={"root"}) assert isinstance(result, PropertyError) - def test_allof_string_and_string_enum(self, model_property_factory, enum_property_factory, string_property_factory): + def test_allof_string_and_string_enum( + self, model_property_factory, enum_property_factory, string_property_factory, config + ): from openapi_python_client.parser.properties import Schemas from openapi_python_client.parser.properties.model_property import _process_properties @@ -459,10 +468,12 @@ def test_allof_string_and_string_enum(self, model_property_factory, enum_propert } ) - result = _process_properties(data=data, schemas=schemas, class_name="", config=Config(), roots={"root"}) + result = _process_properties(data=data, schemas=schemas, class_name="", config=config, roots={"root"}) assert result.required_props[0] == enum_property - def test_allof_string_enum_and_string(self, model_property_factory, enum_property_factory, string_property_factory): + def test_allof_string_enum_and_string( + self, model_property_factory, enum_property_factory, string_property_factory, config + ): from openapi_python_client.parser.properties import Schemas from openapi_python_client.parser.properties.model_property import _process_properties @@ -483,10 +494,10 @@ def test_allof_string_enum_and_string(self, model_property_factory, enum_propert } ) - result = _process_properties(data=data, schemas=schemas, class_name="", config=Config(), roots={"root"}) + result = _process_properties(data=data, schemas=schemas, class_name="", config=config, roots={"root"}) assert result.optional_props[0] == enum_property - def test_allof_int_and_int_enum(self, model_property_factory, enum_property_factory, int_property_factory): + def test_allof_int_and_int_enum(self, model_property_factory, enum_property_factory, int_property_factory, config): from openapi_python_client.parser.properties import Schemas from openapi_python_client.parser.properties.model_property import _process_properties @@ -504,10 +515,12 @@ def test_allof_int_and_int_enum(self, model_property_factory, enum_property_fact } ) - result = _process_properties(data=data, schemas=schemas, class_name="", config=Config(), roots={"root"}) + result = _process_properties(data=data, schemas=schemas, class_name="", config=config, roots={"root"}) assert result.required_props[0] == enum_property - def test_allof_enum_incompatible_type(self, model_property_factory, enum_property_factory, int_property_factory): + def test_allof_enum_incompatible_type( + self, model_property_factory, enum_property_factory, int_property_factory, config + ): from openapi_python_client.parser.properties import Schemas from openapi_python_client.parser.properties.model_property import _process_properties @@ -525,10 +538,10 @@ def test_allof_enum_incompatible_type(self, model_property_factory, enum_propert } ) - result = _process_properties(data=data, schemas=schemas, class_name="", config=Config(), roots={"root"}) + result = _process_properties(data=data, schemas=schemas, class_name="", config=config, roots={"root"}) assert isinstance(result, PropertyError) - def test_allof_string_enums(self, model_property_factory, enum_property_factory): + def test_allof_string_enums(self, model_property_factory, enum_property_factory, config): from openapi_python_client.parser.properties import Schemas from openapi_python_client.parser.properties.model_property import _process_properties @@ -552,10 +565,10 @@ def test_allof_string_enums(self, model_property_factory, enum_property_factory) } ) - result = _process_properties(data=data, schemas=schemas, class_name="", config=Config(), roots={"root"}) + result = _process_properties(data=data, schemas=schemas, class_name="", config=config, roots={"root"}) assert result.required_props[0] == enum_property1 - def test_allof_int_enums(self, model_property_factory, enum_property_factory): + def test_allof_int_enums(self, model_property_factory, enum_property_factory, config): from openapi_python_client.parser.properties import Schemas from openapi_python_client.parser.properties.model_property import _process_properties @@ -579,10 +592,10 @@ def test_allof_int_enums(self, model_property_factory, enum_property_factory): } ) - result = _process_properties(data=data, schemas=schemas, class_name="", config=Config(), roots={"root"}) + result = _process_properties(data=data, schemas=schemas, class_name="", config=config, roots={"root"}) assert result.required_props[0] == enum_property2 - def test_allof_enums_are_not_subsets(self, model_property_factory, enum_property_factory): + def test_allof_enums_are_not_subsets(self, model_property_factory, enum_property_factory, config): from openapi_python_client.parser.properties import Schemas from openapi_python_client.parser.properties.model_property import _process_properties @@ -606,10 +619,10 @@ def test_allof_enums_are_not_subsets(self, model_property_factory, enum_property } ) - result = _process_properties(data=data, schemas=schemas, class_name="", config=Config(), roots={"root"}) + result = _process_properties(data=data, schemas=schemas, class_name="", config=config, roots={"root"}) assert isinstance(result, PropertyError) - def test_duplicate_properties(self, model_property_factory, string_property_factory): + def test_duplicate_properties(self, model_property_factory, string_property_factory, config): from openapi_python_client.parser.properties import Schemas from openapi_python_client.parser.properties.model_property import _process_properties @@ -624,7 +637,7 @@ def test_duplicate_properties(self, model_property_factory, string_property_fact } ) - result = _process_properties(data=data, schemas=schemas, class_name="", config=Config(), roots={"root"}) + result = _process_properties(data=data, schemas=schemas, class_name="", config=config, roots={"root"}) assert result.optional_props == [prop], "There should only be one copy of duplicate properties" @@ -694,7 +707,7 @@ def test_direct_properties_non_ref(self, string_property_factory): class TestProcessModel: - def test_process_model_error(self, mocker, model_property_factory): + def test_process_model_error(self, mocker, model_property_factory, config): from openapi_python_client.parser.properties import Schemas from openapi_python_client.parser.properties.model_property import process_model @@ -703,7 +716,7 @@ def test_process_model_error(self, mocker, model_property_factory): process_property_data = mocker.patch(f"{MODULE_NAME}._process_property_data") process_property_data.return_value = (PropertyError(), schemas) - result = process_model(model_prop=model_prop, schemas=schemas, config=Config()) + result = process_model(model_prop=model_prop, schemas=schemas, config=config) assert result == PropertyError() assert model_prop.required_properties is None @@ -711,7 +724,7 @@ def test_process_model_error(self, mocker, model_property_factory): assert model_prop.relative_imports is None assert model_prop.additional_properties is None - def test_process_model(self, mocker, model_property_factory): + def test_process_model(self, mocker, model_property_factory, config): from openapi_python_client.parser.properties import Schemas from openapi_python_client.parser.properties.model_property import _PropertyData, process_model @@ -728,7 +741,7 @@ def test_process_model(self, mocker, model_property_factory): process_property_data = mocker.patch(f"{MODULE_NAME}._process_property_data") process_property_data.return_value = ((property_data, additional_properties), schemas) - result = process_model(model_prop=model_prop, schemas=schemas, config=Config()) + result = process_model(model_prop=model_prop, schemas=schemas, config=config) assert result == schemas assert model_prop.required_properties == property_data.required_props diff --git a/tests/test_parser/test_properties/test_schemas.py b/tests/test_parser/test_properties/test_schemas.py index 1872e2c8f..5560795cf 100644 --- a/tests/test_parser/test_properties/test_schemas.py +++ b/tests/test_parser/test_properties/test_schemas.py @@ -1,6 +1,6 @@ import pytest +from attr import evolve -from openapi_python_client import Config from openapi_python_client.parser.errors import ParameterError from openapi_python_client.parser.properties import Class, Parameters from openapi_python_client.parser.properties.schemas import parameter_from_reference @@ -10,11 +10,10 @@ MODULE_NAME = "openapi_python_client.parser.properties.schemas" -def test_class_from_string_default_config(): - from openapi_python_client import Config +def test_class_from_string_default_config(config): from openapi_python_client.parser.properties import Class - class_ = Class.from_string(string="#/components/schemas/PingResponse", config=Config()) + class_ = Class.from_string(string="#/components/schemas/PingResponse", config=config) assert class_.name == "PingResponse" assert class_.module_name == "ping_response" @@ -29,13 +28,13 @@ def test_class_from_string_default_config(): (None, "some_module", "MyResponse", "some_module"), ), ) -def test_class_from_string(class_override, module_override, expected_class, expected_module): - from openapi_python_client.config import ClassOverride, Config +def test_class_from_string(class_override, module_override, expected_class, expected_module, config): + from openapi_python_client.config import ClassOverride from openapi_python_client.parser.properties import Class ref = "#/components/schemas/MyResponse" - config = Config( - class_overrides={"MyResponse": ClassOverride(class_name=class_override, module_name=module_override)} + config = evolve( + config, class_overrides={"MyResponse": ClassOverride(class_name=class_override, module_name=module_override)} ) result = Class.from_string(string=ref, config=config) @@ -44,34 +43,32 @@ def test_class_from_string(class_override, module_override, expected_class, expe class TestParameterFromData: - def test_cannot_parse_parameters_by_reference(self): + def test_cannot_parse_parameters_by_reference(self, config): from openapi_python_client.parser.properties import Parameters from openapi_python_client.parser.properties.schemas import parameter_from_data ref = Reference.model_construct(ref="#/components/parameters/a_param") parameters = Parameters() - config = Config() param_or_error, new_parameters = parameter_from_data( name="a_param", data=ref, parameters=parameters, config=config ) assert param_or_error == ParameterError("Unable to resolve another reference") assert new_parameters == parameters - def test_parameters_without_schema_are_ignored(self): + def test_parameters_without_schema_are_ignored(self, config): from openapi_python_client.parser.properties import Parameters from openapi_python_client.parser.properties.schemas import parameter_from_data from openapi_python_client.schema import ParameterLocation param = Parameter(name="a_schemaless_param", param_in=ParameterLocation.QUERY) parameters = Parameters() - config = Config() param_or_error, new_parameters = parameter_from_data( name=param.name, data=param, parameters=parameters, config=config ) assert param_or_error == ParameterError("Parameter has no schema") assert new_parameters == parameters - def test_registers_new_parameters(self): + def test_registers_new_parameters(self, config): from openapi_python_client.parser.properties import Parameters from openapi_python_client.parser.properties.schemas import parameter_from_data from openapi_python_client.schema import ParameterLocation, Schema @@ -80,7 +77,6 @@ def test_registers_new_parameters(self): name="a_param", param_in=ParameterLocation.QUERY, param_schema=Schema.model_construct() ) parameters = Parameters() - config = Config() param_or_error, new_parameters = parameter_from_data( name=param.name, data=param, parameters=parameters, config=config ) @@ -122,7 +118,7 @@ def test_returns_reference_from_registry(self): class TestUpdateParametersFromData: - def test_reports_parameters_with_errors(self, mocker): + def test_reports_parameters_with_errors(self, mocker, config): from openapi_python_client.parser.properties.schemas import update_parameters_with_data from openapi_python_client.schema import ParameterLocation, Schema @@ -134,7 +130,6 @@ def test_reports_parameters_with_errors(self, mocker): f"{MODULE_NAME}.parameter_from_data", side_effect=[(ParameterError(), parameters)] ) ref_path = Reference.model_construct(ref="#/components/parameters/a_param") - config = Config() new_parameters_or_error = update_parameters_with_data( ref_path=ref_path.ref, data=param, parameters=parameters, config=config ) @@ -145,7 +140,7 @@ def test_reports_parameters_with_errors(self, mocker): header="Unable to parse parameter #/components/parameters/a_param", ) - def test_records_references_to_parameters(self, mocker): + def test_records_references_to_parameters(self, mocker, config): from openapi_python_client.parser.properties.schemas import update_parameters_with_data from openapi_python_client.schema import ParameterLocation, Schema @@ -155,7 +150,6 @@ def test_records_references_to_parameters(self, mocker): ) parameter_from_data = mocker.patch(f"{MODULE_NAME}.parameter_from_data", side_effect=[(param, parameters)]) ref_path = "#/components/parameters/a_param" - config = Config() new_parameters = update_parameters_with_data( ref_path=ref_path, data=param, parameters=parameters, config=config ) diff --git a/tests/test_parser/test_properties/test_union.py b/tests/test_parser/test_properties/test_union.py index 621921f0c..d8a5d762c 100644 --- a/tests/test_parser/test_properties/test_union.py +++ b/tests/test_parser/test_properties/test_union.py @@ -1,11 +1,10 @@ import openapi_python_client.schema as oai -from openapi_python_client import Config from openapi_python_client.parser.errors import ParseError, PropertyError from openapi_python_client.parser.properties import Schemas, UnionProperty from openapi_python_client.schema import DataType, ParameterLocation -def test_property_from_data_union(union_property_factory, date_time_property_factory, string_property_factory): +def test_property_from_data_union(union_property_factory, date_time_property_factory, string_property_factory, config): from openapi_python_client.parser.properties import Schemas, property_from_data name = "union_prop" @@ -26,73 +25,58 @@ def test_property_from_data_union(union_property_factory, date_time_property_fac ) p, s = property_from_data( - name=name, required=required, data=data, schemas=Schemas(), parent_name="parent", config=Config() + name=name, required=required, data=data, schemas=Schemas(), parent_name="parent", config=config ) assert p == expected assert s == Schemas() -def test_build_union_property_invalid_property(): +def test_build_union_property_invalid_property(config): name = "bad_union" required = True reference = oai.Reference.model_construct(ref="#/components/schema/NotExist") data = oai.Schema(anyOf=[reference]) p, s = UnionProperty.build( - name=name, required=required, data=data, schemas=Schemas(), parent_name="parent", config=Config() + name=name, required=required, data=data, schemas=Schemas(), parent_name="parent", config=config ) assert p == PropertyError(detail=f"Invalid property in union {name}", data=reference) -def test_invalid_default(): +def test_invalid_default(config): data = oai.Schema( type=[DataType.NUMBER, DataType.INTEGER], default="a", ) err, _ = UnionProperty.build( - data=data, - required=True, - schemas=Schemas(), - parent_name="parent", - name="name", - config=Config(), + data=data, required=True, schemas=Schemas(), parent_name="parent", name="name", config=config ) assert isinstance(err, PropertyError) -def test_invalid_location(): +def test_invalid_location(config): data = oai.Schema( type=[DataType.NUMBER, DataType.NULL], ) prop, _ = UnionProperty.build( - data=data, - required=True, - schemas=Schemas(), - parent_name="parent", - name="name", - config=Config(), + data=data, required=True, schemas=Schemas(), parent_name="parent", name="name", config=config ) err = prop.validate_location(ParameterLocation.PATH) assert isinstance(err, ParseError) -def test_not_required_in_path(): +def test_not_required_in_path(config): data = oai.Schema( oneOf=[oai.Schema(type=DataType.NUMBER), oai.Schema(type=DataType.INTEGER)], ) prop, _ = UnionProperty.build( - data=data, - required=False, - schemas=Schemas(), - parent_name="parent", - name="name", - config=Config(), + data=data, required=False, schemas=Schemas(), parent_name="parent", name="name", config=config ) err = prop.validate_location(ParameterLocation.PATH)