diff --git a/requirements/py310-django32.txt b/requirements/py310-django32.txt index 7b5e23f1..25b021ae 100644 --- a/requirements/py310-django32.txt +++ b/requirements/py310-django32.txt @@ -164,3 +164,7 @@ tomli==2.0.1 \ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f # via pytest +typing-extensions==4.3.0 \ + --hash=sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02 \ + --hash=sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6 + # via -r requirements.in diff --git a/requirements/py310-django40.txt b/requirements/py310-django40.txt index b202d540..18b32d92 100644 --- a/requirements/py310-django40.txt +++ b/requirements/py310-django40.txt @@ -160,3 +160,7 @@ tomli==2.0.1 \ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f # via pytest +typing-extensions==4.3.0 \ + --hash=sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02 \ + --hash=sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6 + # via -r requirements.in diff --git a/requirements/py310-django41.txt b/requirements/py310-django41.txt index 0270658e..4fd9e765 100644 --- a/requirements/py310-django41.txt +++ b/requirements/py310-django41.txt @@ -160,3 +160,7 @@ tomli==2.0.1 \ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f # via pytest +typing-extensions==4.3.0 \ + --hash=sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02 \ + --hash=sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6 + # via -r requirements.in diff --git a/requirements/py311-django41.txt b/requirements/py311-django41.txt index 4d84d64b..6e479748 100644 --- a/requirements/py311-django41.txt +++ b/requirements/py311-django41.txt @@ -160,3 +160,7 @@ tomli==2.0.1 \ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f # via pytest +typing-extensions==4.3.0 \ + --hash=sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02 \ + --hash=sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6 + # via -r requirements.in diff --git a/requirements/py37-django32.txt b/requirements/py37-django32.txt index 36ddba86..02b00f1b 100644 --- a/requirements/py37-django32.txt +++ b/requirements/py37-django32.txt @@ -176,6 +176,7 @@ typing-extensions==4.3.0 \ --hash=sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02 \ --hash=sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6 # via + # -r requirements.in # asgiref # importlib-metadata zipp==3.8.1 \ diff --git a/requirements/py38-django32.txt b/requirements/py38-django32.txt index a74aeb4b..7c04cc3c 100644 --- a/requirements/py38-django32.txt +++ b/requirements/py38-django32.txt @@ -168,6 +168,10 @@ tomli==2.0.1 \ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f # via pytest +typing-extensions==4.3.0 \ + --hash=sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02 \ + --hash=sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6 + # via -r requirements.in zipp==3.8.1 \ --hash=sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2 \ --hash=sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009 diff --git a/requirements/py38-django40.txt b/requirements/py38-django40.txt index 51e1446c..5f1aafca 100644 --- a/requirements/py38-django40.txt +++ b/requirements/py38-django40.txt @@ -182,6 +182,10 @@ tomli==2.0.1 \ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f # via pytest +typing-extensions==4.3.0 \ + --hash=sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02 \ + --hash=sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6 + # via -r requirements.in zipp==3.8.1 \ --hash=sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2 \ --hash=sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009 diff --git a/requirements/py38-django41.txt b/requirements/py38-django41.txt index 99abb042..44c6a6a9 100644 --- a/requirements/py38-django41.txt +++ b/requirements/py38-django41.txt @@ -182,6 +182,10 @@ tomli==2.0.1 \ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f # via pytest +typing-extensions==4.3.0 \ + --hash=sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02 \ + --hash=sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6 + # via -r requirements.in zipp==3.8.1 \ --hash=sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2 \ --hash=sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009 diff --git a/requirements/py39-django32.txt b/requirements/py39-django32.txt index 29ecf420..33dad005 100644 --- a/requirements/py39-django32.txt +++ b/requirements/py39-django32.txt @@ -168,6 +168,10 @@ tomli==2.0.1 \ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f # via pytest +typing-extensions==4.3.0 \ + --hash=sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02 \ + --hash=sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6 + # via -r requirements.in zipp==3.8.1 \ --hash=sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2 \ --hash=sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009 diff --git a/requirements/py39-django40.txt b/requirements/py39-django40.txt index 1bc0d967..ebfc5ff7 100644 --- a/requirements/py39-django40.txt +++ b/requirements/py39-django40.txt @@ -164,6 +164,10 @@ tomli==2.0.1 \ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f # via pytest +typing-extensions==4.3.0 \ + --hash=sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02 \ + --hash=sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6 + # via -r requirements.in zipp==3.8.1 \ --hash=sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2 \ --hash=sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009 diff --git a/requirements/py39-django41.txt b/requirements/py39-django41.txt index b01e1529..55907500 100644 --- a/requirements/py39-django41.txt +++ b/requirements/py39-django41.txt @@ -164,6 +164,10 @@ tomli==2.0.1 \ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f # via pytest +typing-extensions==4.3.0 \ + --hash=sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02 \ + --hash=sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6 + # via -r requirements.in zipp==3.8.1 \ --hash=sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2 \ --hash=sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009 diff --git a/requirements/requirements.in b/requirements/requirements.in index 3017cfba..f7dad6f5 100644 --- a/requirements/requirements.in +++ b/requirements/requirements.in @@ -9,3 +9,4 @@ pytest pytest-django pytest-flake8-path pytest-randomly +typing-extensions diff --git a/tests/testapp/models.py b/tests/testapp/models.py index 2abac8ad..dc7a7380 100644 --- a/tests/testapp/models.py +++ b/tests/testapp/models.py @@ -34,6 +34,7 @@ SizedBinaryField, SizedTextField, ) +from tests.testapp.utils import conn_is_mysql class EnumModel(Model): @@ -122,7 +123,7 @@ class DynamicModel(Model): @classmethod def check(cls, **kwargs: Any) -> list[checks.CheckMessage]: # Disable the checks on MySQL so that checks tests don't fail - if not connection.mysql_is_mariadb: + if conn_is_mysql(connection) and not connection.mysql_is_mariadb: return [] return super().check(**kwargs) @@ -136,7 +137,7 @@ class SpeclessDynamicModel(Model): @classmethod def check(cls, **kwargs): # Disable the checks on MySQL so that checks tests don't fail - if not connection.mysql_is_mariadb: + if conn_is_mysql(connection) and not connection.mysql_is_mariadb: return [] return super().check(**kwargs) diff --git a/tests/testapp/test_listcharfield.py b/tests/testapp/test_listcharfield.py index 150b4e8d..9570320d 100644 --- a/tests/testapp/test_listcharfield.py +++ b/tests/testapp/test_listcharfield.py @@ -78,7 +78,7 @@ def test_char_lookup_contains(self): def test_char_lookup_icontains(self): self.check_char_lookup("icontains") - def check_char_lookup(self, lookup): + def check_char_lookup(self, lookup: str) -> None: lname = "field__" + lookup mymodel = CharListModel.objects.create(field=["mouldy", "rotten"]) diff --git a/tests/testapp/test_operations.py b/tests/testapp/test_operations.py index 12bfe9ab..8c39a92a 100644 --- a/tests/testapp/test_operations.py +++ b/tests/testapp/test_operations.py @@ -11,6 +11,7 @@ from django_mysql.operations import AlterStorageEngine, InstallPlugin, InstallSOName from django_mysql.test.utils import override_mysql_variables +from tests.testapp.utils import conn_is_mysql def plugin_exists(plugin_name: str) -> bool: @@ -40,9 +41,9 @@ class PluginOperationTests(TransactionTestCase): databases = {"default", "other"} @classmethod - def setUpClass(cls): + def setUpClass(cls) -> None: super().setUpClass() - if not connection.mysql_is_mariadb: + if not conn_is_mysql(connection) or not connection.mysql_is_mariadb: raise SkipTest("The metadata_lock_info plugin is required") def test_install_plugin_describe(self): diff --git a/tests/testapp/test_rewrite_query.py b/tests/testapp/test_rewrite_query.py index 46b74d91..0faf5f6b 100644 --- a/tests/testapp/test_rewrite_query.py +++ b/tests/testapp/test_rewrite_query.py @@ -38,7 +38,7 @@ def test_non_select_update_deletes_ignored(self): == "SHOW TABLES " ) - def check_identity(self, query): + def check_identity(self, query: str) -> None: assert rewrite_query(query) == query def test_straight_join(self): diff --git a/tests/testapp/test_test_utils.py b/tests/testapp/test_test_utils.py index 4531d124..e98856b8 100644 --- a/tests/testapp/test_test_utils.py +++ b/tests/testapp/test_test_utils.py @@ -12,10 +12,10 @@ class OverrideVarsMethodTest(TestCase): def test_method_decorator(self): self.check_timestamp(123) - def check_timestamp(self, expected, using="default"): + def check_timestamp(self, expected: int, using: str = "default") -> None: with connections[using].cursor() as cursor: cursor.execute("SELECT @@TIMESTAMP") - mode = cursor.fetchone()[0] + mode: int = cursor.fetchone()[0] assert mode == expected diff --git a/tests/testapp/utils.py b/tests/testapp/utils.py index a621ae35..c6b7b263 100644 --- a/tests/testapp/utils.py +++ b/tests/testapp/utils.py @@ -1,22 +1,37 @@ from __future__ import annotations +import sys from contextlib import contextmanager -from typing import Any +from types import TracebackType +from typing import Any, Generator import pytest from django.db import DEFAULT_DB_ALIAS, connection, connections +from django.db.backends.base.base import BaseDatabaseWrapper +from django.db.backends.mysql.base import DatabaseWrapper as MySQLDatabaseWrapper from django.db.backends.utils import CursorWrapper from django.test.utils import CaptureQueriesContext +if sys.version_info >= (3, 10): + from typing import TypeGuard +else: + from typing_extensions import TypeGuard + + +def conn_is_mysql(connection: BaseDatabaseWrapper) -> TypeGuard[MySQLDatabaseWrapper]: + return connection.vendor == "mysql" + @contextmanager -def skip_if_mysql_8_plus(): - if not connection.mysql_is_mariadb and connection.mysql_version >= (8,): +def skip_if_mysql_8_plus() -> Generator[None, None, None]: + if not conn_is_mysql(connection) or ( + not connection.mysql_is_mariadb and connection.mysql_version >= (8,) + ): pytest.skip("Requires MySQL<8 or MariaDB") yield -def column_type(table_name, column_name): +def column_type(table_name: str, column_name: str) -> str: with connection.cursor() as cursor: cursor.execute( """SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS @@ -24,46 +39,57 @@ def column_type(table_name, column_name): COLUMN_NAME = %s""", (table_name, column_name), ) - return cursor.fetchone()[0] + type_: str = cursor.fetchone()[0] + return type_ class CaptureLastQuery: - def __init__(self, conn=None): + def __init__(self, conn: BaseDatabaseWrapper | None = None) -> None: if conn is None: # pragma: no branch conn = connection - self.conn = conn + self.conn: BaseDatabaseWrapper = conn - def __enter__(self): + def __enter__(self) -> CaptureLastQuery: self.capturer = CaptureQueriesContext(self.conn) self.capturer.__enter__() return self - def __exit__(self, a, b, c): - self.capturer.__exit__(a, b, c) + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.capturer.__exit__(exc_type, exc_val, exc_tb) @property - def query(self): + def query(self) -> str: return self.capturer.captured_queries[-1]["sql"] class print_all_queries: - def __init__(self, conn=None): + def __init__(self, conn: BaseDatabaseWrapper | None = None) -> None: if conn is None: # pragma: no branch conn = connection - self.conn = conn + self.conn: BaseDatabaseWrapper = conn - def __enter__(self): + def __enter__(self) -> print_all_queries: self.capturer = CaptureQueriesContext(self.conn) self.capturer.__enter__() return self - def __exit__(self, a, b, c): - self.capturer.__exit__(a, b, c) + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.capturer.__exit__(exc_type, exc_val, exc_tb) for q in self.capturer.captured_queries: print(q["sql"]) -def used_indexes(query, using=DEFAULT_DB_ALIAS): +def used_indexes(query: str, using: str = DEFAULT_DB_ALIAS) -> set[str]: """ Given SQL 'query', run EXPLAIN and return the names of the indexes used """