Skip to content

Commit

Permalink
Version 2.2.0 (#146)
Browse files Browse the repository at this point in the history
This PR upgrades cachalot to version 2.2.0 allowing for anyone to install cachalot at any Django package above 1.11. All tests currently function now.

* Added mysql as a service to fix Travis CI

* Removed tox dependency

* Removed upper limit on Django and support 2.2-3.0 CI Test
* Cachalot will only say "officially supports" while removing upper limit to not hinder anyone's progress.
* Added Python 3.8 and Django 2.2 and 3.0 to CI tests

- Max is 200 and we'll get there quickly if we add another Python version. Utilizes #127
- Keep dependencies for six and django.utils.six

* Correctly run Travis test by updating tox.ini
* Originally using tox syntax but changed testenv to travis so that travis env and travis dependencies and stuff are used

* Tox tests should start running command

* testenv without dependencies broke travis. New spot in Tox
* testenv without dependencies broke travis
* I can't figure out where testenv is supposed to be
* Good thing there's squash

* Added missing six in test
* Also added six package to Tox for Django 3.0 coverage.
* The missing six package caused several problems in several tests due to using an incorrect DB

* Resolves #138
* Need to add the panels here in order to satisfy tests

* Adds CachalotPanel to Python 2 backwards compatibility
* Forgot super class took those like save()

* Added databases to certain test cases
* APITestCase, DebugToolbarTestCase, SignalsTestCase required a database set
* Fixes errors like these: https://travis-ci.org/noripyt/django-cachalot/jobs/648691385#L4892

* Dropped OFFICIAL support for Python 3.4 + MySQL
* MySQL and its client on 3.4 is f--king up a lot of the testing and I can't bear with it. We can revisit it later, but, honestly, who uses Python 3.4... and if an organization is, then they will either have no problem with Postgres and SQLite or know that MySQL doesn't work.

* Drop Py3.4 from running. Fixed databases
* Now goes with setting's databases rather than hardcoded since some tests don't have other databases.

* Fix ReadTestCase

Django 2.2+ changed self.queryset at line 1000 with self.query=queryset.query

* Django Dependency Removed from utils.py

* Last commit used Django version. This one used try except

* Fixed assertNumQuery; added allow_failures for 3.4
* Primary: More information in test_utils.py with method is_django_21_below_and_is_sqlite
* The problem with the entire build lie in the self.is_sqlite for the tests. SQLite will make 2 queries when creating or destroying or updating instead of 1.
* In summary, if SQLite is the DB and Django

* Changed build matrix to allow failures for 3.4 instead of taking it out completely so that we can get it to work soon.

* Remove -> From function return
* I love backwards compatibility

* SQLite2 in multi_db.py returned incorrect bool

* Django 2.1 and SQLite checker doesn't work for some tests
* Some tests DO HAVE the BEGIN query that is returned... Not sure why. If the next test show completely different WriteTestCase problems, then we need to find alternative.
* Removed check from atomic

* Adjust Django and is_sqlite errors
* CI testing the Tox errors: https://travis-ci.org/noripyt/django-cachalot?utm_medium=notification&utm_source=github_status

* Adjust Django and is_sqlite errors (590 CI)
* CI testing the Tox errors: https://travis-ci.org/noripyt/django-cachalot?utm_medium=notification&utm_source=github_status
* Adjustment 2

* Make include matrix and Adjust Django and is_sqlite errors (594 CI)
* CI testing the Tox errors: https://travis-ci.org/noripyt/django-cachalot?utm_medium=notification&utm_source=github_status
* Adjustment 3

* Adjust Django and is_sqlite errors (596 CI)
* CI testing the Tox errors: https://travis-ci.org/noripyt/django-cachalot?utm_medium=notification&utm_source=github_status
* Adjustment 4
* Added my intro to ReadTheDocs

* Adjust Django and is_sqlite errors (598 CI)
* CI testing the Tox errors: https://travis-ci.org/noripyt/django-cachalot?utm_medium=notification&utm_source=github_status
* Adjustment 5

* Drop Python 3.4 from tests since PyLibMC not cooperating at that level.

* Bump package version up 1 minor for Django 2.2 and 3.0
  • Loading branch information
Andrew-Chen-Wang committed Feb 12, 2020
1 parent a2980f1 commit 602cdce
Show file tree
Hide file tree
Showing 22 changed files with 271 additions and 493 deletions.
451 changes: 38 additions & 413 deletions .travis.yml

Large diffs are not rendered by default.

12 changes: 11 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ Documentation: http://django-cachalot.readthedocs.io
.. image:: https://img.shields.io/badge/cachalot-Chat%20on%20Slack-green?style=flat&logo=slack
:target: https://join.slack.com/t/cachalotdjango/shared_invite/enQtOTMyNzI0NTQzOTA3LWViYmYwMWY3MmU0OTZkYmNiMjBhN2NjNjc4OWVlZDNiMjMxN2Y3YzljYmNiYTY4ZTRjOGQxZDRiMTM0NWE3NGI

Quickstart
----------

Cachalot officially supports Python 2.7, 3.4-3.8 and Django 1.11, 2.0-2.2, 3.0 with the databases PostgreSQL, SQLite, and MySQL.

Note: Python 3.4 with MySQL fails on tests. If you're MySQL is configured correctly,

Third-Party Cache Comparison
----------------------------

Expand All @@ -46,7 +53,10 @@ Cachalot is good when there are <50 modifications per second on a hot cached tab
which is why we suggest you use cache-machine for hot caches. Cache-machine caches individual objects, taking up more in the memory store but
invalidates those individual objects instead of the entire table like cachalot.

Yes, the bane of our entire existence lies in cache invalidation and naming variables. Why does cachalot suck when stuck with a huge table that's accessed rapidly? Since you've mixed your cold (90% of) with your hot (10% of) records, you're caching and invalidating an entire table. It's like trying to boil 1 ton of noodles inside ONE pot instead of 100 pots boiling 1 ton of noodles. Which is more efficient? The splitting up of them.
Yes, the bane of our entire existence lies in cache invalidation and naming variables. Why does cachalot suck when
stuck with a huge table that's modified rapidly? Since you've mixed your cold (90% of) with your hot (10% of) records,
you're caching and invalidating an entire table. It's like trying to boil 1 ton of noodles inside ONE pot instead of
100 pots boiling 1 ton of noodles. Which is more efficient? The splitting up of them.

Note 1: My personal experience with caches stems from Reddit's: https://redditblog.com/2017/01/17/caching-at-reddit/

Expand Down
2 changes: 1 addition & 1 deletion cachalot/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
VERSION = (2, 1, 0)
VERSION = (2, 2, 0)
__version__ = '.'.join(map(str, VERSION))

default_app_config = 'cachalot.apps.CachalotConfig'
5 changes: 4 additions & 1 deletion cachalot/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
from django.apps import apps
from django.conf import settings
from django.db import connections
from six import string_types
try:
from django.utils.six import string_types
except ImportError:
from six import string_types

from .cache import cachalot_caches
from .settings import cachalot_settings
Expand Down
6 changes: 5 additions & 1 deletion cachalot/monkey_patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@
SQLCompiler, SQLInsertCompiler, SQLUpdateCompiler, SQLDeleteCompiler,
)
from django.db.transaction import Atomic, get_connection
from six import binary_type, wraps

try:
from django.utils.six import binary_type, wraps
except ImportError:
from six import binary_type, wraps

from .api import invalidate
from .cache import cachalot_caches
Expand Down
3 changes: 2 additions & 1 deletion cachalot/panels.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ def disable_instrumentation(self):
settings.CACHALOT_ENABLED = False
cachalot_settings.reload()

def process_response(self, request, response):
def process_request(self, request):
self.collect_invalidations()
return super(CachalotPanel, self).process_request(request)

def collect_invalidations(self):
models = apps.get_models()
Expand Down
2 changes: 2 additions & 0 deletions cachalot/tests/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@


class APITestCase(TestUtilsMixin, TransactionTestCase):
databases = set(settings.DATABASES.keys())

def setUp(self):
super(APITestCase, self).setUp()
self.t1 = Test.objects.create(name='test1')
Expand Down
3 changes: 3 additions & 0 deletions cachalot/tests/debug_toolbar.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from uuid import UUID
from bs4 import BeautifulSoup
from django.conf import settings
from django.test import LiveServerTestCase, override_settings


@override_settings(DEBUG=True)
class DebugToolbarTestCase(LiveServerTestCase):
databases = set(settings.DATABASES.keys())

def test_rendering(self):
#
# Rendering toolbar
Expand Down
23 changes: 21 additions & 2 deletions cachalot/tests/multi_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from __future__ import unicode_literals
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 @@ -28,6 +29,24 @@ 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 @@ -49,7 +68,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_sqlite2 else 1,
with self.assertNumQueries(2 if self.is_django_21_below_and_sqlite2() else 1,
using=self.db_alias2):
t3 = Test.objects.using(self.db_alias2).create(name='test3')

Expand All @@ -65,7 +84,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_sqlite2 else 1,
with self.assertNumQueries(2 if self.is_django_21_below_and_sqlite2() else 1,
using=self.db_alias2):
Test.objects.using(self.db_alias2).create(name='test3')

Expand Down
28 changes: 15 additions & 13 deletions cachalot/tests/read.py
Original file line number Diff line number Diff line change
Expand Up @@ -707,7 +707,9 @@ def test_explain(self):
r'Planning time: [\d\.]+ ms\n'
r'Execution time: [\d\.]+ ms$') % (operation_detail,
operation_detail)
with self.assertNumQueries(2 if self.is_mysql else 1):
with self.assertNumQueries(
2 if self.is_mysql and django_version[0] < 3
else 1):
explanation1 = Test.objects.explain(**explain_kwargs)
self.assertRegex(explanation1, expected)
with self.assertNumQueries(0):
Expand Down Expand Up @@ -915,9 +917,9 @@ def test_binary(self):
self.assert_query_cached(qs, after=1 if self.is_sqlite else 0)

def test_float(self):
with self.assertNumQueries(2 if self.is_sqlite else 1):
with self.assertNumQueries(2 if self.is_dj_21_below_and_is_sqlite() else 1):
Test.objects.create(name='test1', a_float=0.123456789)
with self.assertNumQueries(2 if self.is_sqlite else 1):
with self.assertNumQueries(2 if self.is_dj_21_below_and_is_sqlite() else 1):
Test.objects.create(name='test2', a_float=12345.6789)
with self.assertNumQueries(1):
data1 = list(Test.objects.values_list('a_float', flat=True).filter(
Expand All @@ -936,9 +938,9 @@ def test_float(self):
Test.objects.get(a_float=0.123456789)

def test_decimal(self):
with self.assertNumQueries(2 if self.is_sqlite else 1):
with self.assertNumQueries(2 if self.is_dj_21_below_and_is_sqlite() else 1):
Test.objects.create(name='test1', a_decimal=Decimal('123.45'))
with self.assertNumQueries(2 if self.is_sqlite else 1):
with self.assertNumQueries(2 if self.is_dj_21_below_and_is_sqlite() else 1):
Test.objects.create(name='test1', a_decimal=Decimal('12.3'))

qs = Test.objects.values_list('a_decimal', flat=True).filter(
Expand All @@ -952,9 +954,9 @@ def test_decimal(self):
Test.objects.get(a_decimal=Decimal('123.45'))

def test_ipv4_address(self):
with self.assertNumQueries(2 if self.is_sqlite else 1):
with self.assertNumQueries(2 if self.is_dj_21_below_and_is_sqlite() else 1):
Test.objects.create(name='test1', ip='127.0.0.1')
with self.assertNumQueries(2 if self.is_sqlite else 1):
with self.assertNumQueries(2 if self.is_dj_21_below_and_is_sqlite() else 1):
Test.objects.create(name='test2', ip='192.168.0.1')

qs = Test.objects.values_list('ip', flat=True).filter(
Expand All @@ -968,9 +970,9 @@ def test_ipv4_address(self):
Test.objects.get(ip='127.0.0.1')

def test_ipv6_address(self):
with self.assertNumQueries(2 if self.is_sqlite else 1):
with self.assertNumQueries(2 if self.is_dj_21_below_and_is_sqlite() else 1):
Test.objects.create(name='test1', ip='2001:db8:a0b:12f0::1/64')
with self.assertNumQueries(2 if self.is_sqlite else 1):
with self.assertNumQueries(2 if self.is_dj_21_below_and_is_sqlite() else 1):
Test.objects.create(name='test2', ip='2001:db8:0:85a3::ac1f:8001')

qs = Test.objects.values_list('ip', flat=True).filter(
Expand All @@ -985,9 +987,9 @@ def test_ipv6_address(self):
Test.objects.get(ip='2001:db8:0:85a3::ac1f:8001')

def test_duration(self):
with self.assertNumQueries(2 if self.is_sqlite else 1):
with self.assertNumQueries(2 if self.is_dj_21_below_and_is_sqlite() else 1):
Test.objects.create(name='test1', duration=datetime.timedelta(30))
with self.assertNumQueries(2 if self.is_sqlite else 1):
with self.assertNumQueries(2 if self.is_dj_21_below_and_is_sqlite() else 1):
Test.objects.create(name='test2', duration=datetime.timedelta(60))

qs = Test.objects.values_list('duration', flat=True).filter(
Expand All @@ -1002,10 +1004,10 @@ def test_duration(self):
Test.objects.get(duration=datetime.timedelta(30))

def test_uuid(self):
with self.assertNumQueries(2 if self.is_sqlite else 1):
with self.assertNumQueries(2 if self.is_dj_21_below_and_is_sqlite() else 1):
Test.objects.create(name='test1',
uuid='1cc401b7-09f4-4520-b8d0-c267576d196b')
with self.assertNumQueries(2 if self.is_sqlite else 1):
with self.assertNumQueries(2 if self.is_dj_21_below_and_is_sqlite() else 1):
Test.objects.create(name='test2',
uuid='ebb3b6e1-1737-4321-93e3-4c35d61ff491')

Expand Down
2 changes: 1 addition & 1 deletion cachalot/tests/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def test_enabled(self):
list(Test.objects.all())

with self.settings(CACHALOT_ENABLED=False):
with self.assertNumQueries(2 if self.is_sqlite else 1):
with self.assertNumQueries(2 if self.is_dj_21_below_and_is_sqlite() else 1):
t = Test.objects.create(name='test')
with self.assertNumQueries(1):
data = list(Test.objects.all())
Expand Down
2 changes: 2 additions & 0 deletions cachalot/tests/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@


class SignalsTestCase(TransactionTestCase):
databases = set(settings.DATABASES.keys())

def test_table_invalidated(self):
l = []

Expand Down
33 changes: 32 additions & 1 deletion cachalot/tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
from django import VERSION as DJANGO_VERSION
from django.core.management.color import no_style
from django.db import connection, transaction
from django.utils.six import string_types
try:
from django.utils.six import string_types
except ImportError:
from six import string_types

from .models import PostgresModel
from ..utils import _get_tables
Expand Down Expand Up @@ -58,3 +62,30 @@ def assert_query_cached(self, queryset, result=None, result_type=None,
assert_function(data2, data1)
if result is not None:
assert_function(data2, result)

def is_dj_21_below_and_is_sqlite(self):
"""
Checks if Django 2.1 or lower and if SQLite is the DB
Django 2.1 and lower had two queries on SQLite DBs:
After an insertion, e.g. Test.objects.create(name="asdf"),
SQLite returns the queries:
[{'sql': 'INSERT INTO "cachalot_test" ("name") VALUES (\'asd\')', 'time': '0.001'}, {'sql': 'BEGIN', 'time': '0.000'}]
This can be seen with django.db import connection; print(connection.queries)
In Django 2.2 and above, the latter was removed.
:return: bool is Django 2.1 or below and is SQLite the DB
"""
django_version = DJANGO_VERSION
if not self.is_sqlite:
# 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
Loading

0 comments on commit 602cdce

Please sign in to comment.