Skip to content

Commit

Permalink
Add Django 4.1-4.2 and Python 3.11 support (#229)
Browse files Browse the repository at this point in the history
* Add Django 4.1-4.2 and Python 3.11 support (Fixes #228)
* Increment version to 4.5.0

* Remove Python 2 and Django 2.2 code

* Fix CI supported versions

* Correct problems with tests (#234)

* Fix simple error

* Andrew wang/dj4.2 Improve assertNumQueries by filtering out transaction-related queries (#238)

* Andrew wang/dj4.2 (#239)

* Added a FilteredTransactionTestCase, updated tests.

* Added more filtered tests

* Removed Python <3.10 from the tox envlist for Django main.

* Enhanced error message to include current database vendor of the connection for better easier troubleshooting.

* Use python3.10 for GH workflows, fix postgres range issues in tests.

* Check if psycopg3 is in use, if so, add psycopg3 types to CACHABLE_PARAM_TYPES.

---------

Co-authored-by: Benedikt Willi <[email protected]>

---------

Co-authored-by: Jack Linke <[email protected]>
Co-authored-by: Jack Linke <[email protected]>
Co-authored-by: Benedikt Willi <[email protected]>
Co-authored-by: Benedikt Willi <[email protected]>
  • Loading branch information
5 people authored Jul 11, 2023
1 parent de63580 commit 2697986
Show file tree
Hide file tree
Showing 24 changed files with 215 additions and 183 deletions.
20 changes: 13 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,27 @@ on:
pull_request:
branches: [ master ]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ['3.7', '3.8', '3.9', '3.10']
django-version: ['2.2', '3.2', '4.0', '4.1']
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11']
django-version: ['3.2', '4.1', '4.2']
exclude:
- python-version: '3.10'
django-version: '2.2'
- python-version: '3.7'
django-version: '4.0'
- python-version: '3.11'
django-version: '3.2'
- python-version: '3.11'
django-version: '4.1'
- python-version: '3.7'
django-version: '4.1'
- python-version: '3.7'
django-version: '4.2'
services:
redis:
image: redis:6
Expand Down Expand Up @@ -71,7 +77,7 @@ jobs:
${{ matrix.python-version }}-v1-
- name: Install dependencies
run: |
sudo apt-get install -y libmemcached-dev zlib1g-dev
sudo apt-get install -y libmemcached-dev zlib1g-dev libpq-dev
python -m pip install --upgrade pip wheel
python -m pip install tox tox-gh-actions coveralls
- name: Tox Test
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/main-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ['3.9']
python-version: ['3.10']

services:
redis:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ coverage.xml
# Django stuff:
*.log
local_settings.py
*.sqlite3
db.sqlite3
db.sqlite3-journal

Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
What’s new in django-cachalot?
==============================

2.6.0
-----

- Dropped Django 2.2 and 4.0 support
- Added Django 4.2 and Python 3.11 support
- Added psycopg support (#229)
- Updated tests to account for the `BEGIN` and `COMMIT` query changes in Django 4.2
- Standardized django version comparisons in tests

2.5.3
-----

- Verify get_meta isn't none before requesting db_table (#225 #226)

2.5.2
-----

- Added Django 4.1 support (#217)

2.5.1
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Table of Contents:
Quickstart
----------

Cachalot officially supports Python 3.7-3.10 and Django 2.2, 3.2, and 4.0-4.1 with the databases PostgreSQL, SQLite, and MySQL.
Cachalot officially supports Python 3.7-3.11 and Django 3.2, 4.1, 4.2 with the databases PostgreSQL, SQLite, and MySQL.

Note: an upper limit on Django version is set for your safety. Please do not ignore it.

Expand Down
10 changes: 2 additions & 8 deletions cachalot/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
VERSION = (2, 5, 3)
VERSION = (2, 6, 0)
__version__ = ".".join(map(str, VERSION))

try:
from django import VERSION as DJANGO_VERSION

if DJANGO_VERSION < (3, 2):
default_app_config = "cachalot.apps.CachalotConfig"
except ImportError: # pragma: no cover
default_app_config = "cachalot.apps.CachalotConfig"
default_app_config = "cachalot.apps.CachalotConfig"
36 changes: 12 additions & 24 deletions cachalot/api.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from contextlib import contextmanager
from typing import Any, Optional, Tuple, Union

from django.apps import apps
from django.conf import settings
Expand Down Expand Up @@ -45,7 +46,11 @@ def _get_tables(tables_or_models):
else table_or_model._meta.db_table)


def invalidate(*tables_or_models, **kwargs):
def invalidate(
*tables_or_models: Tuple[Union[str, Any], ...],
cache_alias: Optional[str] = None,
db_alias: Optional[str] = None,
) -> None:
"""
Clears what was cached by django-cachalot implying one or more SQL tables
or models from ``tables_or_models``.
Expand All @@ -62,19 +67,9 @@ def invalidate(*tables_or_models, **kwargs):
(or a combination)
:type tables_or_models: tuple of strings or models
:arg cache_alias: Alias from the Django ``CACHES`` setting
:type cache_alias: string or NoneType
:arg db_alias: Alias from the Django ``DATABASES`` setting
:type db_alias: string or NoneType
:returns: Nothing
:rtype: NoneType
"""
# TODO: Replace with positional arguments when we drop Python 2 support.
cache_alias = kwargs.pop('cache_alias', None)
db_alias = kwargs.pop('db_alias', None)
for k in kwargs:
raise TypeError(
"invalidate() got an unexpected keyword argument '%s'" % k)

send_signal = False
invalidated = set()
for cache_alias, db_alias, tables in _cache_db_tables_iterator(
Expand All @@ -90,7 +85,11 @@ def invalidate(*tables_or_models, **kwargs):
post_invalidation.send(table, db_alias=db_alias)


def get_last_invalidation(*tables_or_models, **kwargs):
def get_last_invalidation(
*tables_or_models: Tuple[Union[str, Any], ...],
cache_alias: Optional[str] = None,
db_alias: Optional[str] = None,
) -> float:
"""
Returns the timestamp of the most recent invalidation of the given
``tables_or_models``. If ``tables_or_models`` is not specified,
Expand All @@ -106,19 +105,9 @@ def get_last_invalidation(*tables_or_models, **kwargs):
(or a combination)
:type tables_or_models: tuple of strings or models
:arg cache_alias: Alias from the Django ``CACHES`` setting
:type cache_alias: string or NoneType
:arg db_alias: Alias from the Django ``DATABASES`` setting
:type db_alias: string or NoneType
:returns: The timestamp of the most recent invalidation
:rtype: float
"""
# TODO: Replace with positional arguments when we drop Python 2 support.
cache_alias = kwargs.pop('cache_alias', None)
db_alias = kwargs.pop('db_alias', None)
for k in kwargs:
raise TypeError("get_last_invalidation() got an unexpected "
"keyword argument '%s'" % k)

last_invalidation = 0.0
for cache_alias, db_alias, tables in _cache_db_tables_iterator(
list(_get_tables(tables_or_models)), cache_alias, db_alias):
Expand All @@ -134,7 +123,7 @@ def get_last_invalidation(*tables_or_models, **kwargs):


@contextmanager
def cachalot_disabled(all_queries=False):
def cachalot_disabled(all_queries: bool = False):
"""
Context manager for temporarily disabling cachalot.
If you evaluate the same queryset a second time,
Expand All @@ -158,7 +147,6 @@ def cachalot_disabled(all_queries=False):
the original and duplicate query.
:arg all_queries: Any query, including already evaluated queries, are re-evaluated.
:type all_queries: bool
"""
was_enabled = getattr(LOCAL_STORAGE, "cachalot_enabled", cachalot_settings.CACHALOT_ENABLED)
LOCAL_STORAGE.cachalot_enabled = False
Expand Down
4 changes: 0 additions & 4 deletions cachalot/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
'django.db.backends.sqlite3',
'django.db.backends.postgresql',
'django.db.backends.mysql',
# TODO: Remove when we drop Django 2.x support.
'django.db.backends.postgresql_psycopg2',

# GeoDjango
'django.contrib.gis.db.backends.spatialite',
Expand All @@ -20,8 +18,6 @@
# django-transaction-hooks
'transaction_hooks.backends.sqlite3',
'transaction_hooks.backends.postgis',
# TODO: Remove when we drop Django 2.x support.
'transaction_hooks.backends.postgresql_psycopg2',
'transaction_hooks.backends.mysql',

# django-prometheus wrapped engines
Expand Down
16 changes: 2 additions & 14 deletions cachalot/tests/migrations/0001_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from django.conf import settings
from django.contrib.postgres.fields import (
ArrayField, HStoreField, IntegerRangeField,
DateRangeField, DateTimeRangeField)
DateRangeField, DateTimeRangeField, DecimalRangeField)
from django.contrib.postgres.operations import (
HStoreExtension, UnaccentExtension)
from django.db import models, migrations
Expand All @@ -21,19 +21,6 @@ def extra_regular_available_fields():

def extra_postgres_available_fields():
fields = []
try:
# TODO Remove when Dj31 support is dropped
from django.contrib.postgres.fields import FloatRangeField
fields.append(('float_range', FloatRangeField(null=True, blank=True)))
except ImportError:
pass

try:
# TODO Add to module import when Dj31 is dropped
from django.contrib.postgres.fields import DecimalRangeField
fields.append(('decimal_range', DecimalRangeField(null=True, blank=True)))
except ImportError:
pass

# Future proofing with Django 40 deprecation
if DJANGO_VERSION[0] < 4:
Expand Down Expand Up @@ -103,6 +90,7 @@ class Migration(migrations.Migration):
('int_range', IntegerRangeField(null=True, blank=True)),
('date_range', DateRangeField(null=True, blank=True)),
('datetime_range', DateTimeRangeField(null=True, blank=True)),
('decimal_range', DecimalRangeField(null=True, blank=True))
] + extra_postgres_available_fields(),
),
migrations.RunSQL('CREATE TABLE cachalot_unmanagedmodel '
Expand Down
2 changes: 1 addition & 1 deletion cachalot/tests/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class PostgresModel(Model):
null=True, blank=True)

hstore = HStoreField(null=True, blank=True)
if DJANGO_VERSION[0] < 4:
if DJANGO_VERSION < (4, 0):
from django.contrib.postgres.fields import JSONField
json = JSONField(null=True, blank=True)

Expand Down
25 changes: 2 additions & 23 deletions cachalot/tests/multi_db.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from unittest import skipIf

from django import VERSION as DJANGO_VERSION
from django.conf import settings
from django.db import DEFAULT_DB_ALIAS, connections, transaction
from django.test import TransactionTestCase
Expand All @@ -27,24 +26,6 @@ def setUp(self):
# will execute an extra SQL request below.
connection2.cursor()

def is_django_21_below_and_sqlite2(self):
"""
Note: See test_utils.py with this function name
Checks if Django 2.1 or below and SQLite2
"""
django_version = DJANGO_VERSION
if not self.is_sqlite2:
# Immediately know if SQLite
return False
if django_version[0] < 2:
# Takes Django 0 and 1 out of the picture
return True
else:
if django_version[0] == 2 and django_version[1] < 2:
# Takes Django 2.0-2.1 out
return True
return False

def test_read(self):
with self.assertNumQueries(1):
data1 = list(Test.objects.all())
Expand All @@ -66,8 +47,7 @@ def test_invalidate_other_db(self):
data1 = list(Test.objects.using(self.db_alias2))
self.assertListEqual(data1, [])

with self.assertNumQueries(2 if self.is_django_21_below_and_sqlite2() else 1,
using=self.db_alias2):
with self.assertNumQueries(1, using=self.db_alias2):
t3 = Test.objects.using(self.db_alias2).create(name='test3')

with self.assertNumQueries(1, using=self.db_alias2):
Expand All @@ -82,8 +62,7 @@ def test_invalidation_independence(self):
data1 = list(Test.objects.all())
self.assertListEqual(data1, [self.t1, self.t2])

with self.assertNumQueries(2 if self.is_django_21_below_and_sqlite2() else 1,
using=self.db_alias2):
with self.assertNumQueries(1, using=self.db_alias2):
Test.objects.using(self.db_alias2).create(name='test3')

with self.assertNumQueries(0):
Expand Down
13 changes: 12 additions & 1 deletion cachalot/tests/postgres.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,21 @@
from decimal import Decimal
from unittest import skipUnless

from django import VERSION
from django.contrib.postgres.functions import TransactionNow
from django.db import connection
from django.test import TransactionTestCase, override_settings
from psycopg2.extras import DateRange, DateTimeTZRange, NumericRange

# If we are using Django 4.2 or higher, we need to use:
if VERSION >= (4, 2):
from django.db.backends.postgresql.psycopg_any import (
DateRange,
DateTimeTZRange,
NumericRange,
)
else:
from psycopg2.extras import DateRange, DateTimeTZRange, NumericRange

from pytz import timezone

from ..utils import UncachableQuery
Expand Down
Loading

0 comments on commit 2697986

Please sign in to comment.