From 8a499b21f76d8e5c3478d841e2a70e6c6c2c9208 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 30 Oct 2023 09:11:56 +0200 Subject: [PATCH] Replace flake8, isort with ruff --- Makefile | 7 +- docs/conf.py | 5 +- pyproject.toml | 47 +++++-- pytest_django/asserts.py | 56 ++++---- pytest_django/fixtures.py | 44 +++--- pytest_django/lazy_django.py | 8 +- pytest_django/live_server_helper.py | 6 +- pytest_django/plugin.py | 129 +++++++++--------- .../app/migrations/0001_initial.py | 8 +- pytest_django_test/db_helpers.py | 12 +- pytest_django_test/settings_mysql_innodb.py | 2 +- pytest_django_test/settings_mysql_myisam.py | 2 +- pytest_django_test/settings_postgres.py | 2 +- pytest_django_test/settings_sqlite.py | 2 +- pytest_django_test/settings_sqlite_file.py | 2 +- setup.cfg | 6 - tests/conftest.py | 13 +- tests/test_asserts.py | 5 +- tests/test_database.py | 4 +- tests/test_db_setup.py | 21 ++- tests/test_django_settings_module.py | 4 +- tests/test_fixtures.py | 10 +- tests/test_manage_py_scan.py | 4 +- tests/test_unittest.py | 8 +- tox.ini | 7 +- 25 files changed, 231 insertions(+), 183 deletions(-) diff --git a/Makefile b/Makefile index 8e78202d..36a27df9 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: docs test clean isort +.PHONY: docs test clean fix test: tox -e py311-dj42-sqlite_file @@ -6,9 +6,8 @@ test: docs: tox -e docs -# See setup.cfg for configuration. -isort: - isort pytest_django pytest_django_test tests +fix: + ruff check --fix pytest_django pytest_django_test tests clean: rm -rf bin include/ lib/ man/ pytest_django.egg-info/ build/ diff --git a/docs/conf.py b/docs/conf.py index 55a8936a..607a37f4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,6 +1,7 @@ +import datetime import os import sys -import datetime + # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -27,7 +28,7 @@ # General information about the project. project = 'pytest-django' -copyright = f'{datetime.date.today().year}, Andreas Pelme and contributors' +copyright = f'{datetime.datetime.now(tz=datetime.timezone.utc).year}, Andreas Pelme and contributors' exclude_patterns = ['_build'] diff --git a/pyproject.toml b/pyproject.toml index ca497ac6..e99073c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,15 +54,46 @@ exclude_lines = [ "if TYPE_CHECKING:", ] -[tool.isort] -forced_separate = [ +[tool.ruff] +target-version = "py38" +line-length = 99 +extend-exclude = [ + "pytest_django/_version.py", +] + +[tool.ruff.lint] +extend-select = [ + "B", # flake8-bugbear + "BLE", # flake8-blind-except + "DTZ", # flake8-datetimez + "FA", # flake8-future-annotations + "G", # flake8-logging-format + "I", # isort + "PGH", # pygrep-hooks + "PIE", # flake8-pie + "PL", # pylint + "PT", # flake8-pytest-style + "PYI", # flake8-pyi + "RUF", # Ruff-specific rules + "SLOT", # flake8-slots + "T10", # flake8-debugger + "UP", # pyupgrade + "YTT", # flake8-2020 +] +ignore = [ + "PLR0913", # Too many arguments in function definition + "PLR2004", # Magic value used in comparison, consider replacing 3 with a constant variable + "PT001", # Use `@pytest.fixture()` over `@pytest.fixture` + "PT004", # Fixture `fixture_with_db` does not return anything, add leading underscore + "PT023", # Use `@pytest.mark.django_db()` over `@pytest.mark.django_db` +] + +[tool.ruff.isort] +forced-separate = [ "tests", "pytest_django", "pytest_django_test", ] -combine_as_imports = true -include_trailing_comma = true -line_length = 79 -multi_line_output = 5 -lines_after_imports = 2 -extend_skip = ["pytest_django/_version.py"] +combine-as-imports = true +split-on-trailing-comma = false +lines-after-imports = 2 diff --git a/pytest_django/asserts.py b/pytest_django/asserts.py index ddd884c5..36a60e97 100644 --- a/pytest_django/asserts.py +++ b/pytest_django/asserts.py @@ -1,14 +1,12 @@ """ Dynamically load all Django assertion cases and expose them for importing. """ +from __future__ import annotations + from functools import wraps -from typing import ( - TYPE_CHECKING, Any, Callable, Optional, Sequence, Set, Type, Union, -) +from typing import TYPE_CHECKING, Any, Callable, Sequence -from django.test import ( - LiveServerTestCase, SimpleTestCase, TestCase, TransactionTestCase, -) +from django.test import LiveServerTestCase, SimpleTestCase, TestCase, TransactionTestCase test_case = TestCase("run") @@ -25,7 +23,7 @@ def assertion_func(*args, **kwargs): __all__ = [] -assertions_names: Set[str] = set() +assertions_names: set[str] = set() assertions_names.update( {attr for attr in vars(TestCase) if attr.startswith("assert")}, {attr for attr in vars(SimpleTestCase) if attr.startswith("assert")}, @@ -35,7 +33,7 @@ def assertion_func(*args, **kwargs): for assert_func in assertions_names: globals()[assert_func] = _wrapper(assert_func) - __all__.append(assert_func) + __all__.append(assert_func) # noqa: PYI056 if TYPE_CHECKING: @@ -61,7 +59,7 @@ def assertURLEqual( def assertContains( response: HttpResponseBase, text: object, - count: Optional[int] = ..., + count: int | None = ..., status_code: int = ..., msg_prefix: str = ..., html: bool = False, @@ -80,8 +78,8 @@ def assertNotContains( def assertFormError( response: HttpResponseBase, form: str, - field: Optional[str], - errors: Union[str, Sequence[str]], + field: str | None, + errors: str | Sequence[str], msg_prefix: str = ..., ) -> None: ... @@ -89,30 +87,30 @@ def assertFormError( def assertFormsetError( response: HttpResponseBase, formset: str, - form_index: Optional[int], - field: Optional[str], - errors: Union[str, Sequence[str]], + form_index: int | None, + field: str | None, + errors: str | Sequence[str], msg_prefix: str = ..., ) -> None: ... def assertTemplateUsed( - response: Optional[Union[HttpResponseBase, str]] = ..., - template_name: Optional[str] = ..., + response: HttpResponseBase | str | None = ..., + template_name: str | None = ..., msg_prefix: str = ..., - count: Optional[int] = ..., + count: int | None = ..., ): ... def assertTemplateNotUsed( - response: Optional[Union[HttpResponseBase, str]] = ..., - template_name: Optional[str] = ..., + response: HttpResponseBase | str | None = ..., + template_name: str | None = ..., msg_prefix: str = ..., ): ... def assertRaisesMessage( - expected_exception: Type[Exception], + expected_exception: type[Exception], expected_message: str, *args, **kwargs @@ -140,21 +138,21 @@ def assertFieldOutput( def assertHTMLEqual( html1: str, html2: str, - msg: Optional[str] = ..., + msg: str | None = ..., ) -> None: ... def assertHTMLNotEqual( html1: str, html2: str, - msg: Optional[str] = ..., + msg: str | None = ..., ) -> None: ... def assertInHTML( needle: str, haystack: str, - count: Optional[int] = ..., + count: int | None = ..., msg_prefix: str = ..., ) -> None: ... @@ -162,28 +160,28 @@ def assertInHTML( def assertJSONEqual( raw: str, expected_data: Any, - msg: Optional[str] = ..., + msg: str | None = ..., ) -> None: ... def assertJSONNotEqual( raw: str, expected_data: Any, - msg: Optional[str] = ..., + msg: str | None = ..., ) -> None: ... def assertXMLEqual( xml1: str, xml2: str, - msg: Optional[str] = ..., + msg: str | None = ..., ) -> None: ... def assertXMLNotEqual( xml1: str, xml2: str, - msg: Optional[str] = ..., + msg: str | None = ..., ) -> None: ... @@ -193,7 +191,7 @@ def assertQuerysetEqual( values, transform=..., ordered: bool = ..., - msg: Optional[str] = ..., + msg: str | None = ..., ) -> None: ... @@ -202,7 +200,7 @@ def assertQuerySetEqual( values, transform=..., ordered: bool = ..., - msg: Optional[str] = ..., + msg: str | None = ..., ) -> None: ... diff --git a/pytest_django/fixtures.py b/pytest_django/fixtures.py index 901c85b8..73bb5158 100644 --- a/pytest_django/fixtures.py +++ b/pytest_django/fixtures.py @@ -1,9 +1,19 @@ """All pytest-django fixtures""" +from __future__ import annotations + import os from contextlib import contextmanager from functools import partial from typing import ( - TYPE_CHECKING, Any, Generator, Iterable, List, Literal, Optional, Tuple, + TYPE_CHECKING, + Any, + ClassVar, + Generator, + Iterable, + List, + Literal, + Optional, + Tuple, Union, ) @@ -133,7 +143,7 @@ def django_db_setup( with django_db_blocker.unblock(): try: teardown_databases(db_cfg, verbosity=request.config.option.verbose) - except Exception as exc: + except Exception as exc: # noqa: BLE001 request.node.warn( pytest.PytestWarning( f"Error when trying to teardown test databases: {exc!r}" @@ -314,7 +324,7 @@ def _set_suffix_to_test_databases(suffix: str) -> None: # ############### User visible fixtures ################ -@pytest.fixture(scope="function") +@pytest.fixture() def db(_django_db_helper: None) -> None: """Require a django test database. @@ -331,7 +341,7 @@ def db(_django_db_helper: None) -> None: # The `_django_db_helper` fixture checks if `db` is requested. -@pytest.fixture(scope="function") +@pytest.fixture() def transactional_db(_django_db_helper: None) -> None: """Require a django test database with transaction support. @@ -347,7 +357,7 @@ def transactional_db(_django_db_helper: None) -> None: # The `_django_db_helper` fixture checks if `transactional_db` is requested. -@pytest.fixture(scope="function") +@pytest.fixture() def django_db_reset_sequences( _django_db_helper: None, transactional_db: None, @@ -363,7 +373,7 @@ def django_db_reset_sequences( # is requested. -@pytest.fixture(scope="function") +@pytest.fixture() def django_db_serialized_rollback( _django_db_helper: None, db: None, @@ -385,7 +395,7 @@ def django_db_serialized_rollback( @pytest.fixture() -def client() -> "django.test.client.Client": +def client() -> django.test.client.Client: """A Django test client instance.""" skip_if_no_django() @@ -395,7 +405,7 @@ def client() -> "django.test.client.Client": @pytest.fixture() -def async_client() -> "django.test.client.AsyncClient": +def async_client() -> django.test.client.AsyncClient: """A Django test async client instance.""" skip_if_no_django() @@ -454,7 +464,7 @@ def admin_user( def admin_client( db: None, admin_user, -) -> "django.test.client.Client": +) -> django.test.client.Client: """A Django test client logged in as an admin user.""" from django.test.client import Client @@ -464,7 +474,7 @@ def admin_client( @pytest.fixture() -def rf() -> "django.test.client.RequestFactory": +def rf() -> django.test.client.RequestFactory: """RequestFactory instance""" skip_if_no_django() @@ -474,7 +484,7 @@ def rf() -> "django.test.client.RequestFactory": @pytest.fixture() -def async_rf() -> "django.test.client.AsyncRequestFactory": +def async_rf() -> django.test.client.AsyncRequestFactory: """AsyncRequestFactory instance""" skip_if_no_django() @@ -484,7 +494,7 @@ def async_rf() -> "django.test.client.AsyncRequestFactory": class SettingsWrapper: - _to_restore: List[Any] = [] + _to_restore: ClassVar[list[Any]] = [] def __delattr__(self, attr: str) -> None: from django.test import override_settings @@ -557,7 +567,7 @@ def live_server(request: pytest.FixtureRequest): server.stop() -@pytest.fixture(autouse=True, scope="function") +@pytest.fixture(autouse=True) def _live_server_helper(request: pytest.FixtureRequest) -> Generator[None, None, None]: """Helper to make live_server work, internal to pytest-django. @@ -592,7 +602,7 @@ def _assert_num_queries( exact: bool = True, connection=None, info=None, -) -> Generator["django.test.utils.CaptureQueriesContext", None, None]: +) -> Generator[django.test.utils.CaptureQueriesContext, None, None]: from django.test.utils import CaptureQueriesContext if connection is None: @@ -624,17 +634,17 @@ def _assert_num_queries( pytest.fail(msg) -@pytest.fixture(scope="function") +@pytest.fixture() def django_assert_num_queries(pytestconfig): return partial(_assert_num_queries, pytestconfig) -@pytest.fixture(scope="function") +@pytest.fixture() def django_assert_max_num_queries(pytestconfig): return partial(_assert_num_queries, pytestconfig, exact=False) -@pytest.fixture(scope="function") +@pytest.fixture() def django_capture_on_commit_callbacks(): from django.test import TestCase diff --git a/pytest_django/lazy_django.py b/pytest_django/lazy_django.py index df8d4457..e599240b 100644 --- a/pytest_django/lazy_django.py +++ b/pytest_django/lazy_django.py @@ -1,9 +1,11 @@ """ Helpers to load Django lazily when Django settings can't be configured. """ +from __future__ import annotations + import os import sys -from typing import Any, Tuple +from typing import Any import pytest @@ -30,8 +32,8 @@ def django_settings_is_configured() -> bool: return ret -def get_django_version() -> Tuple[int, int, int, str, int]: +def get_django_version() -> tuple[int, int, int, str, int]: import django - version: Tuple[int, int, int, str, int] = django.VERSION + version: tuple[int, int, int, str, int] = django.VERSION return version diff --git a/pytest_django/live_server_helper.py b/pytest_django/live_server_helper.py index 9000c543..9972e990 100644 --- a/pytest_django/live_server_helper.py +++ b/pytest_django/live_server_helper.py @@ -1,4 +1,6 @@ -from typing import Any, Dict +from __future__ import annotations + +from typing import Any class LiveServer: @@ -13,7 +15,7 @@ def __init__(self, addr: str, *, start: bool = True) -> None: from django.test.testcases import LiveServerThread from django.test.utils import modify_settings - liveserver_kwargs: Dict[str, Any] = {} + liveserver_kwargs: dict[str, Any] = {} connections_override = {} for conn in connections.all(): diff --git a/pytest_django/plugin.py b/pytest_django/plugin.py index 8942ac81..9b7b46da 100644 --- a/pytest_django/plugin.py +++ b/pytest_django/plugin.py @@ -3,6 +3,7 @@ This plugin handles creating and destroying the test environment and test database and provides some useful text fixtures. """ +from __future__ import annotations import contextlib import inspect @@ -10,42 +11,41 @@ import pathlib import sys from functools import reduce -from typing import ( - TYPE_CHECKING, ContextManager, Generator, List, NoReturn, Optional, Tuple, - Union, -) +from typing import TYPE_CHECKING, ContextManager, Generator, List, NoReturn import pytest -from .django_compat import is_django_unittest # noqa -from .fixtures import _django_db_helper # noqa -from .fixtures import _live_server_helper # noqa -from .fixtures import admin_client # noqa -from .fixtures import admin_user # noqa -from .fixtures import async_client # noqa -from .fixtures import async_rf # noqa -from .fixtures import client # noqa -from .fixtures import db # noqa -from .fixtures import django_assert_max_num_queries # noqa -from .fixtures import django_assert_num_queries # noqa -from .fixtures import django_capture_on_commit_callbacks # noqa -from .fixtures import django_db_createdb # noqa -from .fixtures import django_db_keepdb # noqa -from .fixtures import django_db_modify_db_settings # noqa -from .fixtures import django_db_modify_db_settings_parallel_suffix # noqa -from .fixtures import django_db_modify_db_settings_tox_suffix # noqa -from .fixtures import django_db_modify_db_settings_xdist_suffix # noqa -from .fixtures import django_db_reset_sequences # noqa -from .fixtures import django_db_serialized_rollback # noqa -from .fixtures import django_db_setup # noqa -from .fixtures import django_db_use_migrations # noqa -from .fixtures import django_user_model # noqa -from .fixtures import django_username_field # noqa -from .fixtures import live_server # noqa -from .fixtures import rf # noqa -from .fixtures import settings # noqa -from .fixtures import transactional_db # noqa -from .fixtures import validate_django_db +from .django_compat import is_django_unittest +from .fixtures import ( + _django_db_helper, # noqa: F401 + _live_server_helper, # noqa: F401 + admin_client, # noqa: F401 + admin_user, # noqa: F401 + async_client, # noqa: F401 + async_rf, # noqa: F401 + client, # noqa: F401 + db, # noqa: F401 + django_assert_max_num_queries, # noqa: F401 + django_assert_num_queries, # noqa: F401 + django_capture_on_commit_callbacks, # noqa: F401 + django_db_createdb, # noqa: F401 + django_db_keepdb, # noqa: F401 + django_db_modify_db_settings, # noqa: F401 + django_db_modify_db_settings_parallel_suffix, # noqa: F401 + django_db_modify_db_settings_tox_suffix, # noqa: F401 + django_db_modify_db_settings_xdist_suffix, # noqa: F401 + django_db_reset_sequences, # noqa: F401 + django_db_serialized_rollback, # noqa: F401 + django_db_setup, # noqa: F401 + django_db_use_migrations, # noqa: F401 + django_user_model, # noqa: F401 + django_username_field, # noqa: F401 + live_server, # noqa: F401 + rf, # noqa: F401 + settings, # noqa: F401 + transactional_db, # noqa: F401 + validate_django_db, +) from .lazy_django import django_settings_is_configured, skip_if_no_django @@ -178,7 +178,7 @@ def _handle_import_error(extra_message: str) -> Generator[None, None, None]: except ImportError as e: django_msg = (e.args[0] + "\n\n") if e.args else "" msg = django_msg + extra_message - raise ImportError(msg) + raise ImportError(msg) from None def _add_django_project_to_path(args) -> str: @@ -193,7 +193,7 @@ def arg_to_path(arg: str) -> pathlib.Path: arg = arg.split("::", 1)[0] return pathlib.Path(arg) - def find_django_path(args) -> Optional[pathlib.Path]: + def find_django_path(args) -> pathlib.Path | None: str_args = (str(arg) for arg in args) path_args = [arg_to_path(x) for x in str_args if not x.startswith("-")] @@ -237,9 +237,9 @@ def _setup_django() -> None: def _get_boolean_value( - x: Union[None, bool, str], + x: None | (bool | str), name: str, - default: Optional[bool] = None, + default: bool | None = None, ) -> bool: if x is None: return bool(default) @@ -252,7 +252,7 @@ def _get_boolean_value( possible = ", ".join(possible_values) raise ValueError( f"{x} is not a valid value for {name}. It must be one of {possible}." - ) + ) from None report_header_key = pytest.StashKey[List[str]]() @@ -262,7 +262,7 @@ def _get_boolean_value( def pytest_load_initial_conftests( early_config: pytest.Config, parser: pytest.Parser, - args: List[str], + args: list[str], ) -> None: # Register the marks early_config.addinivalue_line( @@ -316,9 +316,9 @@ def pytest_load_initial_conftests( os.environ[INVALID_TEMPLATE_VARS_ENV] = "true" def _get_option_with_source( - option: Optional[str], + option: str | None, envname: str, - ) -> Union[Tuple[str, str], Tuple[None, None]]: + ) -> tuple[str, str] | tuple[None, None]: if option: return option, "option" if envname in os.environ: @@ -331,7 +331,7 @@ def _get_option_with_source( ds, ds_source = _get_option_with_source(options.ds, SETTINGS_MODULE_ENV) dc, dc_source = _get_option_with_source(options.dc, CONFIGURATION_ENV) - report_header: List[str] = [] + report_header: list[str] = [] early_config.stash[report_header_key] = report_header if ds: @@ -352,7 +352,7 @@ def _get_option_with_source( from django.conf import settings as dj_settings with _handle_import_error(_django_project_scan_outcome): - dj_settings.DATABASES + dj_settings.DATABASES # noqa: B018 _setup_django() @@ -365,7 +365,7 @@ def pytest_configure() -> None: @pytest.hookimpl() -def pytest_report_header(config: pytest.Config) -> Optional[List[str]]: +def pytest_report_header(config: pytest.Config) -> list[str] | None: report_header = config.stash[report_header_key] if "django" in sys.modules: @@ -420,7 +420,7 @@ def pytest_itemcollected(item: pytest.Item) -> None: @pytest.hookimpl(tryfirst=True) -def pytest_collection_modifyitems(items: List[pytest.Item]) -> None: +def pytest_collection_modifyitems(items: list[pytest.Item]) -> None: # If Django is not configured we don't need to bother if not django_settings_is_configured(): return @@ -476,9 +476,7 @@ def django_test_environment(request: pytest.FixtureRequest) -> Generator[None, N """ if django_settings_is_configured(): _setup_django() - from django.test.utils import ( - setup_test_environment, teardown_test_environment, - ) + from django.test.utils import setup_test_environment, teardown_test_environment debug_ini = request.config.getini("django_debug_mode") if debug_ini == "keep": @@ -495,7 +493,7 @@ def django_test_environment(request: pytest.FixtureRequest) -> Generator[None, N @pytest.fixture(scope="session") -def django_db_blocker() -> "Optional[_DatabaseBlocker]": +def django_db_blocker() -> _DatabaseBlocker | None: """Wrapper around Django's database access. This object can be used to re-enable database access. This fixture is used @@ -525,7 +523,7 @@ def _django_db_marker(request: pytest.FixtureRequest) -> None: @pytest.fixture(autouse=True, scope="class") def _django_setup_unittest( request: pytest.FixtureRequest, - django_db_blocker: "_DatabaseBlocker", + django_db_blocker: _DatabaseBlocker, ) -> Generator[None, None, None]: """Setup a django unittest, internal to pytest-django.""" if not django_settings_is_configured() or not is_django_unittest(request): @@ -552,7 +550,7 @@ def non_debugging_runtest(self) -> None: TestCaseFunction.runtest = original_runtest # type: ignore[method-assign] -@pytest.fixture(scope="function", autouse=True) +@pytest.fixture(autouse=True) def _dj_autoclear_mailbox() -> None: if not django_settings_is_configured(): return @@ -562,11 +560,11 @@ def _dj_autoclear_mailbox() -> None: del mail.outbox[:] -@pytest.fixture(scope="function") +@pytest.fixture() def mailoutbox( django_mail_patch_dns: None, _dj_autoclear_mailbox: None, -) -> "Optional[List[django.core.mail.EmailMessage]]": +) -> list[django.core.mail.EmailMessage] | None: if not django_settings_is_configured(): return None @@ -575,7 +573,7 @@ def mailoutbox( return mail.outbox # type: ignore[no-any-return] -@pytest.fixture(scope="function") +@pytest.fixture() def django_mail_patch_dns( monkeypatch: pytest.MonkeyPatch, django_mail_dnsname: str, @@ -585,12 +583,12 @@ def django_mail_patch_dns( monkeypatch.setattr(mail.message, "DNS_NAME", django_mail_dnsname) -@pytest.fixture(scope="function") +@pytest.fixture() def django_mail_dnsname() -> str: return "fake-tests.example.com" -@pytest.fixture(autouse=True, scope="function") +@pytest.fixture(autouse=True) def _django_set_urlconf(request: pytest.FixtureRequest) -> Generator[None, None, None]: """Apply the @pytest.mark.urls marker, internal to pytest-django.""" marker = request.node.get_closest_marker("urls") @@ -640,7 +638,7 @@ def __contains__(self, key: str) -> bool: return key == "%s" @staticmethod - def _get_origin() -> Optional[str]: + def _get_origin() -> str | None: stack = inspect.stack() # Try to use topmost `self.origin` first (Django 1.9+, and with @@ -649,7 +647,7 @@ def _get_origin() -> Optional[str]: func = f[3] if func == "render": frame = f[0] - origin: Optional[str] + origin: str | None try: origin = frame.f_locals["self"].origin except (AttributeError, KeyError): @@ -723,7 +721,7 @@ def _template_string_if_invalid_marker( ) -@pytest.fixture(autouse=True, scope="function") +@pytest.fixture(autouse=True) def _django_clear_site_cache() -> None: """Clears ``django.contrib.sites.models.SITE_CACHE`` to avoid unexpected behavior with cached site objects. @@ -763,7 +761,7 @@ def __init__(self) -> None: self._real_ensure_connection = None @property - def _dj_db_wrapper(self) -> "django.db.backends.base.base.BaseDatabaseWrapper": + def _dj_db_wrapper(self) -> django.db.backends.base.base.BaseDatabaseWrapper: from django.db.backends.base.base import BaseDatabaseWrapper # The first time the _dj_db_wrapper is accessed, we will save a @@ -776,22 +774,21 @@ def _dj_db_wrapper(self) -> "django.db.backends.base.base.BaseDatabaseWrapper": def _save_active_wrapper(self) -> None: self._history.append(self._dj_db_wrapper.ensure_connection) - def _blocking_wrapper(*args, **kwargs) -> "NoReturn": + def _blocking_wrapper(*args, **kwargs) -> NoReturn: __tracebackhide__ = True - __tracebackhide__ # Silence pyflakes raise RuntimeError( "Database access not allowed, " 'use the "django_db" mark, or the ' '"db" or "transactional_db" fixtures to enable it.' ) - def unblock(self) -> "ContextManager[None]": + def unblock(self) -> ContextManager[None]: """Enable access to the Django database.""" self._save_active_wrapper() self._dj_db_wrapper.ensure_connection = self._real_ensure_connection return _DatabaseBlockerContextManager(self) - def block(self) -> "ContextManager[None]": + def block(self) -> ContextManager[None]: """Disable access to the Django database.""" self._save_active_wrapper() self._dj_db_wrapper.ensure_connection = self._blocking_wrapper @@ -804,14 +801,14 @@ def restore(self) -> None: _blocking_manager = _DatabaseBlocker() -def validate_urls(marker) -> List[str]: +def validate_urls(marker) -> list[str]: """Validate the urls marker. It checks the signature and creates the `urls` attribute on the marker which will have the correct value. """ - def apifun(urls: List[str]) -> List[str]: + def apifun(urls: list[str]) -> list[str]: return urls return apifun(*marker.args, **marker.kwargs) diff --git a/pytest_django_test/app/migrations/0001_initial.py b/pytest_django_test/app/migrations/0001_initial.py index eb795c31..ac2f7ef6 100644 --- a/pytest_django_test/app/migrations/0001_initial.py +++ b/pytest_django_test/app/migrations/0001_initial.py @@ -1,4 +1,6 @@ -from typing import List, Tuple +from __future__ import annotations + +from typing import ClassVar from django.db import migrations, models @@ -7,9 +9,9 @@ class Migration(migrations.Migration): initial = True - dependencies: List[Tuple[str, str]] = [] + dependencies: tuple[tuple[str, str], ...] = () - operations = [ + operations: ClassVar = [ migrations.CreateModel( name="Item", fields=[ diff --git a/pytest_django_test/db_helpers.py b/pytest_django_test/db_helpers.py index a6748e2c..bb26d315 100644 --- a/pytest_django_test/db_helpers.py +++ b/pytest_django_test/db_helpers.py @@ -1,7 +1,9 @@ +from __future__ import annotations + import os import sqlite3 import subprocess -from typing import Mapping, Optional +from typing import Mapping import pytest from django.conf import settings @@ -44,7 +46,7 @@ def __init__(self, status_code: int, std_out: bytes, std_err: bytes) -> None: self.std_err = std_err -def run_cmd(*args: str, env: Optional[Mapping[str, str]] = None) -> CmdResult: +def run_cmd(*args: str, env: Mapping[str, str] | None = None) -> CmdResult: r = subprocess.Popen( args, stdout=subprocess.PIPE, @@ -92,14 +94,14 @@ def skip_if_sqlite_in_memory() -> None: pytest.skip("Do not test db reuse since database does not support it") -def _get_db_name(db_suffix: Optional[str] = None) -> str: +def _get_db_name(db_suffix: str | None = None) -> str: name = TEST_DB_NAME if db_suffix: name = f"{name}_{db_suffix}" return name -def drop_database(db_suffix: Optional[str] = None) -> None: +def drop_database(db_suffix: str | None = None) -> None: name = _get_db_name(db_suffix) db_engine = get_db_engine() @@ -121,7 +123,7 @@ def drop_database(db_suffix: Optional[str] = None) -> None: os.unlink(name) -def db_exists(db_suffix: Optional[str] = None) -> bool: +def db_exists(db_suffix: str | None = None) -> bool: name = _get_db_name(db_suffix) db_engine = get_db_engine() diff --git a/pytest_django_test/settings_mysql_innodb.py b/pytest_django_test/settings_mysql_innodb.py index 062cfac0..e744c9f3 100644 --- a/pytest_django_test/settings_mysql_innodb.py +++ b/pytest_django_test/settings_mysql_innodb.py @@ -1,6 +1,6 @@ from os import environ -from .settings_base import * # noqa: F401 F403 +from .settings_base import * # noqa: F403 DATABASES = { diff --git a/pytest_django_test/settings_mysql_myisam.py b/pytest_django_test/settings_mysql_myisam.py index d939b7cb..54ec8c93 100644 --- a/pytest_django_test/settings_mysql_myisam.py +++ b/pytest_django_test/settings_mysql_myisam.py @@ -1,6 +1,6 @@ from os import environ -from .settings_base import * # noqa: F401 F403 +from .settings_base import * # noqa: F403 DATABASES = { diff --git a/pytest_django_test/settings_postgres.py b/pytest_django_test/settings_postgres.py index 2661fbc5..f1627146 100644 --- a/pytest_django_test/settings_postgres.py +++ b/pytest_django_test/settings_postgres.py @@ -1,6 +1,6 @@ from os import environ -from .settings_base import * # noqa: F401 F403 +from .settings_base import * # noqa: F403 # PyPy compatibility diff --git a/pytest_django_test/settings_sqlite.py b/pytest_django_test/settings_sqlite.py index 057b8344..039e49a9 100644 --- a/pytest_django_test/settings_sqlite.py +++ b/pytest_django_test/settings_sqlite.py @@ -1,4 +1,4 @@ -from .settings_base import * # noqa: F401 F403 +from .settings_base import * # noqa: F403 DATABASES = { diff --git a/pytest_django_test/settings_sqlite_file.py b/pytest_django_test/settings_sqlite_file.py index 206b658a..d6cd36c4 100644 --- a/pytest_django_test/settings_sqlite_file.py +++ b/pytest_django_test/settings_sqlite_file.py @@ -1,6 +1,6 @@ import tempfile -from .settings_base import * # noqa: F401 F403 +from .settings_base import * # noqa: F403 # This is a SQLite configuration, which uses a file based database for diff --git a/setup.cfg b/setup.cfg index f34320bb..7195dead 100644 --- a/setup.cfg +++ b/setup.cfg @@ -52,9 +52,3 @@ testing = [options.package_data] pytest_django = py.typed - -[flake8] -# W503 line break before binary operator -ignore = W503 -max-line-length = 99 -exclude = lib/,src/,docs/,bin/,pytest_django/_version.py diff --git a/tests/conftest.py b/tests/conftest.py index 5b40c7af..84340433 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,9 +1,11 @@ +from __future__ import annotations + import copy import pathlib import shutil from pathlib import Path from textwrap import dedent -from typing import Optional, cast +from typing import cast import pytest from django.conf import settings @@ -25,7 +27,7 @@ def pytest_configure(config: pytest.Config) -> None: def _marker_apifun( extra_settings: str = "", create_manage_py: bool = False, - project_root: Optional[str] = None, + project_root: str | None = None, ): return { "extra_settings": extra_settings, @@ -40,14 +42,17 @@ def pytester(pytester: pytest.Pytester, monkeypatch: pytest.MonkeyPatch) -> pyte return pytester -@pytest.fixture(scope="function") +@pytest.fixture() def django_pytester( request: pytest.FixtureRequest, pytester: pytest.Pytester, monkeypatch: pytest.MonkeyPatch, ) -> DjangoPytester: from pytest_django_test.db_helpers import ( - DB_NAME, SECOND_DB_NAME, SECOND_TEST_DB_NAME, TEST_DB_NAME, + DB_NAME, + SECOND_DB_NAME, + SECOND_TEST_DB_NAME, + TEST_DB_NAME, ) marker = request.node.get_closest_marker("django_project") diff --git a/tests/test_asserts.py b/tests/test_asserts.py index 0434f168..20e62995 100644 --- a/tests/test_asserts.py +++ b/tests/test_asserts.py @@ -1,8 +1,9 @@ """ Tests the dynamic loading of all Django assertion cases. """ +from __future__ import annotations + import inspect -from typing import List import pytest @@ -10,7 +11,7 @@ from pytest_django.asserts import __all__ as asserts_all -def _get_actual_assertions_names() -> List[str]: +def _get_actual_assertions_names() -> list[str]: """ Returns list with names of all assertion helpers in Django. """ diff --git a/tests/test_database.py b/tests/test_database.py index ce8d2ad4..a18cebf4 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Generator import pytest @@ -68,7 +70,7 @@ def all_dbs(self, request: pytest.FixtureRequest) -> None: elif request.param == "django_db_serialized_rollback": request.getfixturevalue("django_db_serialized_rollback") else: - assert False # pragma: no cover + raise AssertionError() # pragma: no cover def test_access(self, all_dbs: None) -> None: Item.objects.create(name="spam") diff --git a/tests/test_db_setup.py b/tests/test_db_setup.py index d58444c4..82be2f69 100644 --- a/tests/test_db_setup.py +++ b/tests/test_db_setup.py @@ -1,9 +1,16 @@ +from __future__ import annotations + +from typing import ClassVar + import pytest from .helpers import DjangoPytester from pytest_django_test.db_helpers import ( - db_exists, drop_database, mark_database, mark_exists, + db_exists, + drop_database, + mark_database, + mark_exists, skip_if_sqlite_in_memory, ) @@ -160,7 +167,7 @@ def test_db_can_be_accessed(): class TestSqlite: - db_settings = { + db_settings: ClassVar = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": "db_name", @@ -268,7 +275,7 @@ def test_d(settings): class TestSqliteWithXdist: - db_settings = { + db_settings: ClassVar = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": "/tmp/should-not-be-used", @@ -300,7 +307,7 @@ def test_a(): class TestSqliteWithMultipleDbsAndXdist: - db_settings = { + db_settings: ClassVar = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": "/tmp/should-not-be-used", @@ -350,7 +357,7 @@ def test_a(): class TestSqliteWithTox: - db_settings = { + db_settings: ClassVar = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": "db_name", @@ -417,7 +424,7 @@ def test_inner(): class TestSqliteWithToxAndXdist: - db_settings = { + db_settings: ClassVar = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": "db_name", @@ -457,7 +464,7 @@ def test_inner(): class TestSqliteInMemoryWithXdist: - db_settings = { + db_settings: ClassVar = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:", diff --git a/tests/test_django_settings_module.py b/tests/test_django_settings_module.py index e3a309ff..9bfc3cdf 100644 --- a/tests/test_django_settings_module.py +++ b/tests/test_django_settings_module.py @@ -322,7 +322,7 @@ def test_debug_is_false(): assert r.ret == 0 -@pytest.mark.parametrize('django_debug_mode', (False, True)) +@pytest.mark.parametrize('django_debug_mode', [False, True]) def test_django_debug_mode_true_false( pytester: pytest.Pytester, monkeypatch: pytest.MonkeyPatch, @@ -358,7 +358,7 @@ def test_debug_is_false(): assert r.ret == 0 -@pytest.mark.parametrize('settings_debug', (False, True)) +@pytest.mark.parametrize('settings_debug', [False, True]) def test_django_debug_mode_keep( pytester: pytest.Pytester, monkeypatch: pytest.MonkeyPatch, diff --git a/tests/test_fixtures.py b/tests/test_fixtures.py index b4afa55a..865eec55 100644 --- a/tests/test_fixtures.py +++ b/tests/test_fixtures.py @@ -13,9 +13,7 @@ from django.conf import settings as real_settings from django.core import mail from django.db import connection, transaction -from django.test.client import ( - AsyncClient, AsyncRequestFactory, Client, RequestFactory, -) +from django.test.client import AsyncClient, AsyncRequestFactory, Client, RequestFactory from django.utils.encoding import force_str from .helpers import DjangoPytester @@ -120,7 +118,7 @@ def test_django_assert_max_num_queries_db( Item.objects.create(name="1-foo") Item.objects.create(name="2-bar") - with pytest.raises(pytest.fail.Exception) as excinfo: + with pytest.raises(pytest.fail.Exception) as excinfo: # noqa: PT012 with django_assert_max_num_queries(2) as captured: Item.objects.create(name="1-foo") Item.objects.create(name="2-bar") @@ -559,7 +557,7 @@ def test_with_live_server(live_server): django_pytester.runpytest_subprocess(f"--liveserver=localhost:{port}") -@pytest.mark.parametrize("username_field", ("email", "identifier")) +@pytest.mark.parametrize("username_field", ["email", "identifier"]) @pytest.mark.django_project( extra_settings=""" AUTH_USER_MODEL = 'app.MyCustomUser' @@ -682,7 +680,7 @@ class Migration(migrations.Migration): bases=None, ), ] - """, # noqa: E501 + """, "migrations/0002_custom_user_model.py", ) diff --git a/tests/test_manage_py_scan.py b/tests/test_manage_py_scan.py index 35ec7dfb..45675708 100644 --- a/tests/test_manage_py_scan.py +++ b/tests/test_manage_py_scan.py @@ -152,12 +152,12 @@ def test_runs_without_error_on_long_args(django_pytester: DjangoPytester) -> Non """ def test_this_is_a_long_message_which_caused_a_bug_when_scanning_for_manage_py_12346712341234123412341234123412341234123412341234123412341234123412341234123412341234123412341234123412341234123412341234123412341234123412341234123412341234112341234112451234123412341234123412341234123412341234123412341234123412341234123412341234123412341234(): assert 1 + 1 == 2 - """ # noqa: E501 + """ ) result = django_pytester.runpytest_subprocess( "-k", - "this_is_a_long_message_which_caused_a_bug_when_scanning_for_manage_py_12346712341234123412341234123412341234123412341234123412341234123412341234123412341234123412341234123412341234123412341234123412341234123412341234123412341234112341234112451234123412341234123412341234123412341234123412341234123412341234123412341234123412341234", # noqa: E501 + "this_is_a_long_message_which_caused_a_bug_when_scanning_for_manage_py_12346712341234123412341234123412341234123412341234123412341234123412341234123412341234123412341234123412341234123412341234123412341234123412341234123412341234112341234112451234123412341234123412341234123412341234123412341234123412341234123412341234123412341234", "django_project_root", ) assert result.ret == 0 diff --git a/tests/test_unittest.py b/tests/test_unittest.py index 0b51d054..521cb4fd 100644 --- a/tests/test_unittest.py +++ b/tests/test_unittest.py @@ -7,7 +7,7 @@ class TestFixtures(TestCase): - fixtures = ["items"] + fixtures = ("items",) def test_fixtures(self) -> None: assert Item.objects.count() == 1 @@ -26,10 +26,10 @@ def setUp(self) -> None: Item.objects.create(name="Some item again") def test_count(self) -> None: - self.assertEqual(Item.objects.count(), 2) + self.assertEqual(Item.objects.count(), 2) # noqa: PT009 assert Item.objects.count() == 2 Item.objects.create(name="Foo") - self.assertEqual(Item.objects.count(), 3) + self.assertEqual(Item.objects.count(), 3) # noqa: PT009 def test_count_again(self) -> None: self.test_count() @@ -40,7 +40,7 @@ def tearDown(self) -> None: class TestFixturesWithSetup(TestCase): - fixtures = ["items"] + fixtures = ("items", ) def setUp(self) -> None: assert Item.objects.count() == 1 diff --git a/tox.ini b/tox.ini index 987357b1..e175d72f 100644 --- a/tox.ini +++ b/tox.ini @@ -52,14 +52,11 @@ commands = [testenv:linting] extras = deps = - flake8 + ruff==0.1.3 mypy==1.6.1 - isort commands = - flake8 --version - flake8 --statistics {posargs:pytest_django pytest_django_test tests} + ruff check --statistics {posargs:pytest_django pytest_django_test tests} mypy {posargs:pytest_django pytest_django_test tests} - isort --check-only --diff pytest_django pytest_django_test tests [testenv:doc8] extras =