From 2f2048fa77652472a5fd6fbde951a7e2af749976 Mon Sep 17 00:00:00 2001 From: Matthew Messinger Date: Tue, 7 May 2024 16:40:20 -0400 Subject: [PATCH 1/9] feat: Enable configuration via pyproject.toml or env vars --- pyproject.toml | 1 + src/fastapi_cli/cli.py | 13 +++-- src/fastapi_cli/config.py | 99 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 5 deletions(-) create mode 100644 src/fastapi_cli/config.py diff --git a/pyproject.toml b/pyproject.toml index 5fb2684..bf7ebd1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,7 @@ classifiers = [ ] dependencies = [ "typer >= 0.12.3", + "tomli >= 2.0.1; python_version < '3.11'", ] [project.optional-dependencies] diff --git a/src/fastapi_cli/cli.py b/src/fastapi_cli/cli.py index 2aea254..3512397 100644 --- a/src/fastapi_cli/cli.py +++ b/src/fastapi_cli/cli.py @@ -13,6 +13,7 @@ from fastapi_cli.exceptions import FastAPICLIException from . import __version__ +from .config import CommandWithProjectConfig from .logging import setup_logging app = typer.Typer(rich_markup_mode="rich") @@ -92,12 +93,13 @@ def _run( ) -@app.command() +@app.command(cls=CommandWithProjectConfig) def dev( path: Annotated[ Union[Path, None], typer.Argument( - help="A path to a Python file or package directory (with [blue]__init__.py[/blue] files) containing a [bold]FastAPI[/bold] app. If not provided, a default set of paths will be tried." + help="A path to a Python file or package directory (with [blue]__init__.py[/blue] files) containing a [bold]FastAPI[/bold] app. If not provided, a default set of paths will be tried.", + envvar=["FASTAPI_DEV_PATH", "FASTAPI_PATH"], ), ] = None, *, @@ -175,12 +177,13 @@ def dev( ) -@app.command() +@app.command(cls=CommandWithProjectConfig) def run( path: Annotated[ Union[Path, None], typer.Argument( - help="A path to a Python file or package directory (with [blue]__init__.py[/blue] files) containing a [bold]FastAPI[/bold] app. If not provided, a default set of paths will be tried." + help="A path to a Python file or package directory (with [blue]__init__.py[/blue] files) containing a [bold]FastAPI[/bold] app. If not provided, a default set of paths will be tried.", + envvar=["FASTAPI_RUN_PATH", "FASTAPI_PATH"], ), ] = None, *, @@ -266,4 +269,4 @@ def run( def main() -> None: - app() + app(auto_envvar_prefix="FASTAPI") diff --git a/src/fastapi_cli/config.py b/src/fastapi_cli/config.py new file mode 100644 index 0000000..0a08aaa --- /dev/null +++ b/src/fastapi_cli/config.py @@ -0,0 +1,99 @@ +from __future__ import annotations + +import logging +import sys +from pathlib import Path +from typing import Any, Sequence + +from click import BadParameter, Context +from typer.core import TyperCommand + +if sys.version_info >= (3, 11): + import tomllib +else: + import tomli as tomllib + +logger = logging.getLogger(__name__) + + +def get_toml_key(config: dict[str, Any], keys: Sequence[str]) -> dict[str, Any]: + for key in keys: + config = config.get(key, {}) + return config + + +def read_pyproject_file(keys: Sequence[str]) -> dict[str, Any] | None: + path = Path("pyproject.toml") + if not path.exists(): + return None + + with path.open("rb") as f: + data = tomllib.load(f) + + config = get_toml_key(data, keys) + return config or None + + +class CommandWithProjectConfig(TyperCommand): + """Command class which loads parameters from a pyproject.toml file. + + It will search the current directory and all parent directories for + a `pyproject.toml` file, then change directories to that file so all paths + defined in it remain relative to it. + + The table `tool.fastapi.cli` will be used. An additional subtable for the + running command will also be used. e.g. `tool.fastapi.cli.dev`. Options + on subcommand tables will override options from the cli table. + + Example: + + ```toml + [tool.fastapi.cli] + path = "asgi.py" + app = "application" + + [tool.fastapi.cli.dev] + host = "0.0.0.0" + port = 5000 + + [tool.fastapi.cli.run] + reload = true + ``` + """ + + toml_keys = ("tool", "fastapi", "cli") + + def load_config_table( + self, + ctx: Context, + config: dict[str, Any], + config_path: str | None = None, + ) -> None: + if config_path is not None: + config = config.get(config_path, {}) + if not config: + return + for param in ctx.command.params: + param_name = param.name or "" + if param_name in config: + try: + value = param.type_cast_value(ctx, config[param_name]) + except (TypeError, BadParameter) as e: + keys: list[str] = list(self.toml_keys) + if config_path is not None: + keys.append(config_path) + keys.append(param_name) + full_path = ".".join(keys) + ctx.fail(f"Error parsing pyproject.toml: key '{full_path}': {e}") + else: + ctx.params[param_name] = value + + def invoke(self, ctx: Context) -> Any: + config = read_pyproject_file(self.toml_keys) + if config is not None: + logger.info("Loading configuration from pyproject.toml") + command_name = ctx.command.name or "" + self.load_config_table(ctx, config) + self.load_config_table(ctx, config, command_name) + + return super().invoke(ctx) From 56ced6b97d9095bbb6a1773d947d4727a3697802 Mon Sep 17 00:00:00 2001 From: Matthew Messinger Date: Tue, 7 May 2024 19:27:44 -0400 Subject: [PATCH 2/9] Add tests for project configs --- .../projects/configured_app/pyproject.toml | 3 ++ .../assets/projects/configured_app/server.py | 8 ++++ .../projects/configured_app_subtable/app.py | 8 ++++ .../configured_app_subtable/pyproject.toml | 11 +++++ tests/test_cli.py | 42 +++++++++++++++++++ 5 files changed, 72 insertions(+) create mode 100644 tests/assets/projects/configured_app/pyproject.toml create mode 100644 tests/assets/projects/configured_app/server.py create mode 100644 tests/assets/projects/configured_app_subtable/app.py create mode 100644 tests/assets/projects/configured_app_subtable/pyproject.toml diff --git a/tests/assets/projects/configured_app/pyproject.toml b/tests/assets/projects/configured_app/pyproject.toml new file mode 100644 index 0000000..5f1f4f0 --- /dev/null +++ b/tests/assets/projects/configured_app/pyproject.toml @@ -0,0 +1,3 @@ +[tool.fastapi.cli] +path = "server.py" +app = "application" diff --git a/tests/assets/projects/configured_app/server.py b/tests/assets/projects/configured_app/server.py new file mode 100644 index 0000000..2364e73 --- /dev/null +++ b/tests/assets/projects/configured_app/server.py @@ -0,0 +1,8 @@ +from fastapi import FastAPI + +application = FastAPI() + + +@application.get("/") +def app_root(): + return {"message": "configured app"} diff --git a/tests/assets/projects/configured_app_subtable/app.py b/tests/assets/projects/configured_app_subtable/app.py new file mode 100644 index 0000000..b5b10be --- /dev/null +++ b/tests/assets/projects/configured_app_subtable/app.py @@ -0,0 +1,8 @@ +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def app_root(): + return {"message": "configured app with subcommand config"} diff --git a/tests/assets/projects/configured_app_subtable/pyproject.toml b/tests/assets/projects/configured_app_subtable/pyproject.toml new file mode 100644 index 0000000..ea56896 --- /dev/null +++ b/tests/assets/projects/configured_app_subtable/pyproject.toml @@ -0,0 +1,11 @@ +[tool.fastapi.cli] +# global option +port = 8001 + +[tool.fastapi.cli.run] +reload = true +workers = 4 + +[tool.fastapi.cli.dev] +# overrides global option +port = 8002 diff --git a/tests/test_cli.py b/tests/test_cli.py index 44c14d2..f619503 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -3,6 +3,7 @@ from pathlib import Path from unittest.mock import patch +import pytest import uvicorn from fastapi_cli.cli import app from typer.testing import CliRunner @@ -82,6 +83,47 @@ def test_dev_args() -> None: assert "│ fastapi run" in result.output +def test_project_run() -> None: + with changing_dir(assets_path / "projects/configured_app"): + with patch.object(uvicorn, "run") as mock_run: + result = runner.invoke(app, ["run"]) + assert result.exit_code == 0, result.output + assert mock_run.called + assert mock_run.call_args + assert mock_run.call_args.kwargs == { + "app": "server:application", + "host": "0.0.0.0", + "port": 8000, + "reload": False, + "workers": None, + "root_path": "", + "proxy_headers": True, + } + + +@pytest.mark.parametrize( + ("command", "kwargs"), + [ + ("run", {"host": "0.0.0.0", "port": 8001, "workers": 4}), + ("dev", {"host": "127.0.0.1", "port": 8002, "workers": None}), + ], +) +def test_project_run_subconfigure(command: str, kwargs: dict) -> None: + with changing_dir(assets_path / "projects/configured_app_subtable"): + with patch.object(uvicorn, "run") as mock_run: + result = runner.invoke(app, [command]) + assert result.exit_code == 0, result.output + assert mock_run.called + assert mock_run.call_args + assert mock_run.call_args.kwargs == { + "app": "app:app", + "reload": True, + "root_path": "", + "proxy_headers": True, + **kwargs, + } + + def test_run() -> None: with changing_dir(assets_path): with patch.object(uvicorn, "run") as mock_run: From dad27fccad36b40b4d842a7406caedd83a3de824 Mon Sep 17 00:00:00 2001 From: Matthew Messinger Date: Tue, 7 May 2024 19:47:46 -0400 Subject: [PATCH 3/9] Remove outdated docstring on project config class --- src/fastapi_cli/config.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/fastapi_cli/config.py b/src/fastapi_cli/config.py index 0a08aaa..5a2ec8f 100644 --- a/src/fastapi_cli/config.py +++ b/src/fastapi_cli/config.py @@ -37,10 +37,6 @@ def read_pyproject_file(keys: Sequence[str]) -> dict[str, Any] | None: class CommandWithProjectConfig(TyperCommand): """Command class which loads parameters from a pyproject.toml file. - It will search the current directory and all parent directories for - a `pyproject.toml` file, then change directories to that file so all paths - defined in it remain relative to it. - The table `tool.fastapi.cli` will be used. An additional subtable for the running command will also be used. e.g. `tool.fastapi.cli.dev`. Options on subcommand tables will override options from the cli table. From 764d8e8ca9c09707180d21090cc0bad9137bef59 Mon Sep 17 00:00:00 2001 From: Matthew Messinger Date: Tue, 7 May 2024 20:08:29 -0400 Subject: [PATCH 4/9] Fix typing error in test --- tests/test_cli.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index f619503..31443af 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,6 +1,9 @@ +from __future__ import annotations + import subprocess import sys from pathlib import Path +from typing import Any from unittest.mock import patch import pytest @@ -108,7 +111,7 @@ def test_project_run() -> None: ("dev", {"host": "127.0.0.1", "port": 8002, "workers": None}), ], ) -def test_project_run_subconfigure(command: str, kwargs: dict) -> None: +def test_project_run_subconfigure(command: str, kwargs: dict[str, Any]) -> None: with changing_dir(assets_path / "projects/configured_app_subtable"): with patch.object(uvicorn, "run") as mock_run: result = runner.invoke(app, [command]) From 260242eb477c3431a0952096be4ede9c04859e94 Mon Sep 17 00:00:00 2001 From: Matthew Messinger Date: Tue, 7 May 2024 20:59:32 -0400 Subject: [PATCH 5/9] Fix tests by removing app imports via fixture --- tests/test_cli.py | 10 ++++++++-- tests/test_utils_default_dir.py | 9 ++++++++- tests/test_utils_default_file.py | 9 ++++++++- tests/test_utils_package.py | 17 ++++++++++++++++- tests/test_utils_single_file.py | 9 ++++++++- tests/utils.py | 23 +++++++++++++++++++++++ 6 files changed, 71 insertions(+), 6 deletions(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index 31443af..1d4472b 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -3,7 +3,7 @@ import subprocess import sys from pathlib import Path -from typing import Any +from typing import Any, Generator from unittest.mock import patch import pytest @@ -11,13 +11,19 @@ from fastapi_cli.cli import app from typer.testing import CliRunner -from tests.utils import changing_dir +from tests.utils import changing_dir, importing runner = CliRunner() assets_path = Path(__file__).parent / "assets" +@pytest.fixture(autouse=True) +def single_file_app_fixture() -> Generator[None, None, None]: + with importing(["single_file_app", "server", "app"]): + yield + + def test_dev() -> None: with changing_dir(assets_path): with patch.object(uvicorn, "run") as mock_run: diff --git a/tests/test_utils_default_dir.py b/tests/test_utils_default_dir.py index 2665203..4a6c643 100644 --- a/tests/test_utils_default_dir.py +++ b/tests/test_utils_default_dir.py @@ -1,15 +1,22 @@ from pathlib import Path +from typing import Generator import pytest from fastapi_cli.discover import get_import_string from fastapi_cli.exceptions import FastAPICLIException from pytest import CaptureFixture -from .utils import changing_dir +from .utils import changing_dir, importing assets_path = Path(__file__).parent / "assets" +@pytest.fixture(autouse=True) +def single_file_app_fixture() -> Generator[None, None, None]: + with importing(["app", "app.main", "app.app", "app.api"]): + yield + + def test_app_dir_main(capsys: CaptureFixture[str]) -> None: with changing_dir(assets_path / "default_files" / "default_app_dir_main"): import_string = get_import_string() diff --git a/tests/test_utils_default_file.py b/tests/test_utils_default_file.py index f5c87c8..f2dd0e2 100644 --- a/tests/test_utils_default_file.py +++ b/tests/test_utils_default_file.py @@ -1,17 +1,24 @@ import importlib import sys from pathlib import Path +from typing import Generator import pytest from fastapi_cli.discover import get_import_string from fastapi_cli.exceptions import FastAPICLIException from pytest import CaptureFixture -from .utils import changing_dir +from .utils import changing_dir, importing assets_path = Path(__file__).parent / "assets" +@pytest.fixture(autouse=True) +def single_file_app_fixture() -> Generator[None, None, None]: + with importing(["app", "api", "main"]): + yield + + def test_single_file_main(capsys: CaptureFixture[str]) -> None: root_path = assets_path / "default_files" / "default_main" old_sys_path = sys.path.copy() diff --git a/tests/test_utils_package.py b/tests/test_utils_package.py index d5573db..96315a5 100644 --- a/tests/test_utils_package.py +++ b/tests/test_utils_package.py @@ -1,15 +1,30 @@ from pathlib import Path +from typing import Generator import pytest from fastapi_cli.discover import get_import_string from fastapi_cli.exceptions import FastAPICLIException from pytest import CaptureFixture -from tests.utils import changing_dir +from tests.utils import changing_dir, importing assets_path = Path(__file__).parent / "assets" +@pytest.fixture(autouse=True) +def single_file_app_fixture() -> Generator[None, None, None]: + with importing( + [ + "package", + "package.mod", + "package.mod.app", + "package.mod.api", + "package.mod.other", + ] + ): + yield + + def test_package_app_root(capsys: CaptureFixture[str]) -> None: with changing_dir(assets_path): import_string = get_import_string(path=Path("package/mod/app.py")) diff --git a/tests/test_utils_single_file.py b/tests/test_utils_single_file.py index 6395b32..5864c35 100644 --- a/tests/test_utils_single_file.py +++ b/tests/test_utils_single_file.py @@ -1,15 +1,22 @@ from pathlib import Path +from typing import Generator import pytest from fastapi_cli.discover import get_import_string from fastapi_cli.exceptions import FastAPICLIException from pytest import CaptureFixture -from .utils import changing_dir +from .utils import changing_dir, importing assets_path = Path(__file__).parent / "assets" +@pytest.fixture(autouse=True) +def single_file_app_fixture() -> Generator[None, None, None]: + with importing(["single_file_app", "single_file_api", "single_file_other"]): + yield + + def test_single_file_app(capsys: CaptureFixture[str]) -> None: with changing_dir(assets_path): import_string = get_import_string(path=Path("single_file_app.py")) diff --git a/tests/utils.py b/tests/utils.py index 804454c..32c41c6 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,4 +1,8 @@ +from __future__ import annotations + import os +import sys +import warnings from contextlib import contextmanager from pathlib import Path from typing import Generator, Union @@ -12,3 +16,22 @@ def changing_dir(directory: Union[str, Path]) -> Generator[None, None, None]: yield finally: os.chdir(initial_dir) + + +@contextmanager +def importing(names: Union[str, list[str]]) -> Generator[None, None, None]: + for name in names: + if name in sys.modules: + warnings.warn( + f"{name} is already imported", + category=UserWarning, + stacklevel=1, + ) + try: + yield + finally: + if isinstance(names, str): + names = [names] + for name in names: + if name in sys.modules: + del sys.modules[name] From 083016128d0d79618cabb47386673d8e1833e477 Mon Sep 17 00:00:00 2001 From: Matthew Messinger Date: Tue, 7 May 2024 21:21:21 -0400 Subject: [PATCH 6/9] Improve coverage --- tests/assets/projects/bad_configured_app/app.py | 8 ++++++++ .../assets/projects/bad_configured_app/pyproject.toml | 2 ++ tests/test_cli.py | 10 ++++++++++ tests/utils.py | 6 ++---- 4 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 tests/assets/projects/bad_configured_app/app.py create mode 100644 tests/assets/projects/bad_configured_app/pyproject.toml diff --git a/tests/assets/projects/bad_configured_app/app.py b/tests/assets/projects/bad_configured_app/app.py new file mode 100644 index 0000000..6cdf1de --- /dev/null +++ b/tests/assets/projects/bad_configured_app/app.py @@ -0,0 +1,8 @@ +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def app_root(): + return {"message": "badly configured app"} diff --git a/tests/assets/projects/bad_configured_app/pyproject.toml b/tests/assets/projects/bad_configured_app/pyproject.toml new file mode 100644 index 0000000..ab3f03a --- /dev/null +++ b/tests/assets/projects/bad_configured_app/pyproject.toml @@ -0,0 +1,2 @@ +[tool.fastapi.cli.run] +port = "http" \ No newline at end of file diff --git a/tests/test_cli.py b/tests/test_cli.py index 1d4472b..c34d287 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -210,6 +210,16 @@ def test_run_error() -> None: assert "Path does not exist non_existing_file.py" in result.output +def test_project_config_error() -> None: + with changing_dir(assets_path / "projects/bad_configured_app"): + result = runner.invoke(app, ["run"]) + assert result.exit_code == 2, result.output + assert ( + "Error parsing pyproject.toml: key 'tool.fastapi.cli.run.port'" + in result.output + ) + + def test_dev_help() -> None: result = runner.invoke(app, ["dev", "--help"]) assert result.exit_code == 0, result.output diff --git a/tests/utils.py b/tests/utils.py index 32c41c6..fd3e8ef 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -19,9 +19,9 @@ def changing_dir(directory: Union[str, Path]) -> Generator[None, None, None]: @contextmanager -def importing(names: Union[str, list[str]]) -> Generator[None, None, None]: +def importing(names: list[str]) -> Generator[None, None, None]: for name in names: - if name in sys.modules: + if name in sys.modules: # pragma: no cover warnings.warn( f"{name} is already imported", category=UserWarning, @@ -30,8 +30,6 @@ def importing(names: Union[str, list[str]]) -> Generator[None, None, None]: try: yield finally: - if isinstance(names, str): - names = [names] for name in names: if name in sys.modules: del sys.modules[name] From 2b56256d84d25887efe3aba064151d3a2b0d3b34 Mon Sep 17 00:00:00 2001 From: Matthew Messinger Date: Thu, 16 May 2024 13:05:26 -0400 Subject: [PATCH 7/9] Simplify test import cleaner --- tests/conftest.py | 24 ++++++++++++++++++++++++ tests/test_cli.py | 10 ++-------- tests/test_utils_default_dir.py | 9 +-------- tests/test_utils_default_file.py | 9 +-------- tests/test_utils_package.py | 17 +---------------- tests/test_utils_single_file.py | 9 +-------- tests/utils.py | 21 --------------------- 7 files changed, 30 insertions(+), 69 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 955fd22..7f07aaa 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,10 +1,14 @@ +import inspect import sys +from pathlib import Path from typing import Generator import pytest from fastapi_cli.logging import setup_logging from typer import rich_utils +assets_path = Path(__file__).parent / "assets" + @pytest.fixture(autouse=True) def reset_syspath() -> Generator[None, None, None]: @@ -21,3 +25,23 @@ def setup_terminal() -> None: rich_utils.FORCE_TERMINAL = False setup_logging(terminal_width=3000) return + + +@pytest.fixture(autouse=True) +def asset_import_cleaner() -> Generator[None, None, None]: + existing_imports = set(sys.modules.keys()) + try: + yield + finally: + # clean up imports + new_imports = set(sys.modules.keys()) ^ existing_imports + for name in new_imports: + try: + mod_file = inspect.getfile(sys.modules[name]) + except TypeError: + # builtin, ignore + pass + else: + # only clean up imports from the test directory + if mod_file.startswith(str(assets_path)): + del sys.modules[name] diff --git a/tests/test_cli.py b/tests/test_cli.py index c34d287..2caac63 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -3,7 +3,7 @@ import subprocess import sys from pathlib import Path -from typing import Any, Generator +from typing import Any from unittest.mock import patch import pytest @@ -11,19 +11,13 @@ from fastapi_cli.cli import app from typer.testing import CliRunner -from tests.utils import changing_dir, importing +from tests.utils import changing_dir runner = CliRunner() assets_path = Path(__file__).parent / "assets" -@pytest.fixture(autouse=True) -def single_file_app_fixture() -> Generator[None, None, None]: - with importing(["single_file_app", "server", "app"]): - yield - - def test_dev() -> None: with changing_dir(assets_path): with patch.object(uvicorn, "run") as mock_run: diff --git a/tests/test_utils_default_dir.py b/tests/test_utils_default_dir.py index 4a6c643..2665203 100644 --- a/tests/test_utils_default_dir.py +++ b/tests/test_utils_default_dir.py @@ -1,22 +1,15 @@ from pathlib import Path -from typing import Generator import pytest from fastapi_cli.discover import get_import_string from fastapi_cli.exceptions import FastAPICLIException from pytest import CaptureFixture -from .utils import changing_dir, importing +from .utils import changing_dir assets_path = Path(__file__).parent / "assets" -@pytest.fixture(autouse=True) -def single_file_app_fixture() -> Generator[None, None, None]: - with importing(["app", "app.main", "app.app", "app.api"]): - yield - - def test_app_dir_main(capsys: CaptureFixture[str]) -> None: with changing_dir(assets_path / "default_files" / "default_app_dir_main"): import_string = get_import_string() diff --git a/tests/test_utils_default_file.py b/tests/test_utils_default_file.py index f2dd0e2..f5c87c8 100644 --- a/tests/test_utils_default_file.py +++ b/tests/test_utils_default_file.py @@ -1,24 +1,17 @@ import importlib import sys from pathlib import Path -from typing import Generator import pytest from fastapi_cli.discover import get_import_string from fastapi_cli.exceptions import FastAPICLIException from pytest import CaptureFixture -from .utils import changing_dir, importing +from .utils import changing_dir assets_path = Path(__file__).parent / "assets" -@pytest.fixture(autouse=True) -def single_file_app_fixture() -> Generator[None, None, None]: - with importing(["app", "api", "main"]): - yield - - def test_single_file_main(capsys: CaptureFixture[str]) -> None: root_path = assets_path / "default_files" / "default_main" old_sys_path = sys.path.copy() diff --git a/tests/test_utils_package.py b/tests/test_utils_package.py index 96315a5..d5573db 100644 --- a/tests/test_utils_package.py +++ b/tests/test_utils_package.py @@ -1,30 +1,15 @@ from pathlib import Path -from typing import Generator import pytest from fastapi_cli.discover import get_import_string from fastapi_cli.exceptions import FastAPICLIException from pytest import CaptureFixture -from tests.utils import changing_dir, importing +from tests.utils import changing_dir assets_path = Path(__file__).parent / "assets" -@pytest.fixture(autouse=True) -def single_file_app_fixture() -> Generator[None, None, None]: - with importing( - [ - "package", - "package.mod", - "package.mod.app", - "package.mod.api", - "package.mod.other", - ] - ): - yield - - def test_package_app_root(capsys: CaptureFixture[str]) -> None: with changing_dir(assets_path): import_string = get_import_string(path=Path("package/mod/app.py")) diff --git a/tests/test_utils_single_file.py b/tests/test_utils_single_file.py index 5864c35..6395b32 100644 --- a/tests/test_utils_single_file.py +++ b/tests/test_utils_single_file.py @@ -1,22 +1,15 @@ from pathlib import Path -from typing import Generator import pytest from fastapi_cli.discover import get_import_string from fastapi_cli.exceptions import FastAPICLIException from pytest import CaptureFixture -from .utils import changing_dir, importing +from .utils import changing_dir assets_path = Path(__file__).parent / "assets" -@pytest.fixture(autouse=True) -def single_file_app_fixture() -> Generator[None, None, None]: - with importing(["single_file_app", "single_file_api", "single_file_other"]): - yield - - def test_single_file_app(capsys: CaptureFixture[str]) -> None: with changing_dir(assets_path): import_string = get_import_string(path=Path("single_file_app.py")) diff --git a/tests/utils.py b/tests/utils.py index fd3e8ef..804454c 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,8 +1,4 @@ -from __future__ import annotations - import os -import sys -import warnings from contextlib import contextmanager from pathlib import Path from typing import Generator, Union @@ -16,20 +12,3 @@ def changing_dir(directory: Union[str, Path]) -> Generator[None, None, None]: yield finally: os.chdir(initial_dir) - - -@contextmanager -def importing(names: list[str]) -> Generator[None, None, None]: - for name in names: - if name in sys.modules: # pragma: no cover - warnings.warn( - f"{name} is already imported", - category=UserWarning, - stacklevel=1, - ) - try: - yield - finally: - for name in names: - if name in sys.modules: - del sys.modules[name] From d17538d89b9b0d5dedbd6da06526b7336edf7f9e Mon Sep 17 00:00:00 2001 From: Matthew Messinger Date: Wed, 22 May 2024 17:23:35 -0400 Subject: [PATCH 8/9] Add pragma no cover to asset_import_cleaner fixture --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 7f07aaa..6ca44a4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -38,7 +38,7 @@ def asset_import_cleaner() -> Generator[None, None, None]: for name in new_imports: try: mod_file = inspect.getfile(sys.modules[name]) - except TypeError: + except TypeError: # pragma: no cover # builtin, ignore pass else: From 07c143cc7566820200830ae3957c435f601a1ed5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 22 May 2024 21:24:24 +0000 Subject: [PATCH 9/9] =?UTF-8?q?=F0=9F=8E=A8=20[pre-commit.ci]=20Auto=20for?= =?UTF-8?q?mat=20from=20pre-commit.com=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/assets/projects/bad_configured_app/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/assets/projects/bad_configured_app/pyproject.toml b/tests/assets/projects/bad_configured_app/pyproject.toml index ab3f03a..75cbbbf 100644 --- a/tests/assets/projects/bad_configured_app/pyproject.toml +++ b/tests/assets/projects/bad_configured_app/pyproject.toml @@ -1,2 +1,2 @@ [tool.fastapi.cli.run] -port = "http" \ No newline at end of file +port = "http"