Skip to content

Commit

Permalink
refactor: rename to zealot
Browse files Browse the repository at this point in the history
  • Loading branch information
taobojlen committed Jul 3, 2024
1 parent 28c75a4 commit e4ee3a9
Show file tree
Hide file tree
Showing 14 changed files with 69 additions and 70 deletions.
32 changes: 16 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# queryspy
# zealot

This library catches N+1s in your Django project.

Expand All @@ -18,22 +18,22 @@ It's not exactly a fork, but not far from it.

## Installation

To install `queryspy`, add it to your `INSTALLED_APPS` and `MIDDLEWARE`. You probably
To install `zealot`, add it to your `INSTALLED_APPS` and `MIDDLEWARE`. You probably
don't want to run it in production: I haven't profiled it but it will have a performance
impact.

```python
if DEBUG:
INSTALLED_APPS.append("queryspy")
MIDDLEWARE.append("queryspy.middleware.queryspy_middleware")
INSTALLED_APPS.append("zealot")
MIDDLEWARE.append("zealot.middleware.zealot_middleware")
```

This will detect N+1s that happen in web requests. If you also want to find N+1s in other
places like background tasks or management commands, you can use the `setup` and
`teardown` functions, or the `queryspy_context` context manager:
`teardown` functions, or the `zealot_context` context manager:

```python
from queryspy import setup, teardown, queryspy_context
from zealot import setup, teardown, zealot_context


def foo():
Expand All @@ -44,36 +44,36 @@ def foo():
teardown()


@queryspy_context()
@zealot_context()
def bar():
# ...


def baz():
with queryspy_context():
with zealot_context():
# ...
```

For example, if you use Celery, you can configure this using [signals](https://docs.celeryq.dev/en/stable/userguide/signals.html):

```python
from celery.signals import task_prerun, task_postrun
from queryspy import setup, teardown
from zealot import setup, teardown
from django.conf import settings

if settings.DEBUG:
@task_prerun.connect()
def setup_queryspy(*args, **kwargs):
def setup_zealot(*args, **kwargs):
setup()

@task_postrun.connect()
def teardown_queryspy(*args, **kwargs):
def teardown_zealot(*args, **kwargs):
teardown()
```

## Configuration

By default, any issues detected by `queryspy` will raise a `QuerySpyError`. If you'd
By default, any issues detected by `zealot` will raise a `ZealotError`. If you'd
rather log any detected N+1s, you can set:

```
Expand All @@ -87,14 +87,14 @@ threshold, set the following in your Django settings.
QUERYSPY_NPLUSONE_THRESHOLD = 3
```

To handle false positives, you can temporarily disable `queryspy` in parts of your code
To handle false positives, you can temporarily disable `zealot` in parts of your code
using a context manager:

```python
from queryspy import queryspy_ignore
from zealot import zealot_ignore

with queryspy_ignore():
# code in this block will not log/raise queryspy errors
with zealot_ignore():
# code in this block will not log/raise zealot errors
```

Finally, if you want to ignore N+1 alerts from a specific model/field globally, you can
Expand Down
11 changes: 0 additions & 11 deletions src/queryspy/__init__.py

This file was deleted.

6 changes: 0 additions & 6 deletions src/queryspy/errors.py

This file was deleted.

11 changes: 11 additions & 0 deletions src/zealot/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from .errors import NPlusOneError, ZealotError
from .listeners import setup, teardown, zealot_context, zealot_ignore

__all__ = [
"ZealotError",
"NPlusOneError",
"setup",
"teardown",
"zealot_context",
"zealot_ignore",
]
4 changes: 2 additions & 2 deletions src/queryspy/apps.py → src/zealot/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
from .patch import patch


class QuerySpyConfig(AppConfig):
name = "queryspy"
class ZealotConfig(AppConfig):
name = "zealot"

def ready(self):
patch()
6 changes: 6 additions & 0 deletions src/zealot/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class ZealotError(Exception):
pass


class NPlusOneError(ZealotError):
pass
12 changes: 6 additions & 6 deletions src/queryspy/listeners.py → src/zealot/listeners.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
from django.conf import settings
from django.db import models

from queryspy.util import get_caller
from zealot.util import get_caller

from .errors import NPlusOneError, QuerySpyError
from .errors import NPlusOneError, ZealotError

ModelAndField = tuple[Type[models.Model], str]

_is_in_context = ContextVar("in_context", default=False)

logger = logging.getLogger("queryspy")
logger = logging.getLogger("zealot")


class AllowListEntry(TypedDict):
Expand All @@ -34,7 +34,7 @@ def reset(self): ...

@property
@abstractmethod
def error_class(self) -> type[QuerySpyError]: ...
def error_class(self) -> type[ZealotError]: ...

@property
def _allowlist(self) -> list[AllowListEntry]:
Expand Down Expand Up @@ -115,7 +115,7 @@ def teardown():


@contextmanager
def queryspy_context():
def zealot_context():
token = _is_in_context.set(True)
try:
yield
Expand All @@ -125,7 +125,7 @@ def queryspy_context():


@contextmanager
def queryspy_ignore():
def zealot_ignore():
token = _is_in_context.set(False)
try:
yield
Expand Down
6 changes: 3 additions & 3 deletions src/queryspy/middleware.py → src/zealot/middleware.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from .listeners import queryspy_context
from .listeners import zealot_context


def queryspy_middleware(get_response):
def zealot_middleware(get_response):
def middleware(request):
with queryspy_context():
with zealot_context():
response = get_response(request)
return response

Expand Down
5 changes: 2 additions & 3 deletions src/queryspy/patch.py → src/zealot/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,7 @@ def wrapper(*args, **kwargs):

# don't patch the same queryset more than once
if (
hasattr(queryset, "__queryspy_patched")
and queryset.__queryspy_patched # type: ignore
hasattr(queryset, "__zealot_patched") and queryset.__zealot_patched # type: ignore
):
return queryset
context["args"] = context.get("args", args)
Expand All @@ -71,7 +70,7 @@ def wrapper(*args, **kwargs):
queryset._fetch_all = patch_queryset_fetch_all(
queryset, parser, context
)
queryset.__queryspy_patched = True # type: ignore
queryset.__zealot_patched = True # type: ignore
return queryset

return wrapper
Expand Down
4 changes: 2 additions & 2 deletions src/queryspy/util.py → src/zealot/util.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import inspect

PATTERNS = ["site-packages", "queryspy/listeners.py", "queryspy/patch.py"]
PATTERNS = ["site-packages", "zealot/listeners.py", "zealot/patch.py"]


def get_caller() -> inspect.FrameInfo:
"""
Returns the filename and line number of the current caller,
excluding any code in site-packages or queryspy.
excluding any code in site-packages or zealot.
"""
return next(
frame
Expand Down
4 changes: 2 additions & 2 deletions tests/djangoproject/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
"django.contrib.messages",
"django.contrib.staticfiles",
"djangoproject.social",
"queryspy",
"zealot",
]

MIDDLEWARE = ["queryspy.middleware.queryspy_middleware"]
MIDDLEWARE = ["zealot.middleware.zealot_middleware"]

ROOT_URLCONF = "djangoproject.urls"

Expand Down
14 changes: 7 additions & 7 deletions tests/test_listeners.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@

import pytest
from djangoproject.social.models import Post, User
from queryspy.errors import NPlusOneError
from queryspy.listeners import queryspy_context
from zealot.errors import NPlusOneError
from zealot.listeners import zealot_context

from .factories import PostFactory, UserFactory

pytestmark = pytest.mark.django_db


@queryspy_context()
@zealot_context()
def test_can_log_errors(settings, caplog):
settings.QUERYSPY_RAISE = False

Expand All @@ -30,7 +30,7 @@ def test_can_log_errors(settings, caplog):
), f"{caplog.text} does not match regex"


@queryspy_context()
@zealot_context()
def test_errors_include_caller():
[user_1, user_2] = UserFactory.create_batch(2)
PostFactory.create(author=user_1)
Expand All @@ -43,7 +43,7 @@ def test_errors_include_caller():
_ = list(user.posts.all())


@queryspy_context()
@zealot_context()
def test_can_exclude_with_allowlist(settings):
settings.QUERYSPY_ALLOWLIST = [{"model": "social.User", "field": "posts"}]

Expand All @@ -60,7 +60,7 @@ def test_can_exclude_with_allowlist(settings):
_ = post.author


@queryspy_context()
@zealot_context()
def test_can_use_fnmatch_pattern_in_allowlist_model(settings):
settings.QUERYSPY_ALLOWLIST = [{"model": "social.U*"}]

Expand All @@ -77,7 +77,7 @@ def test_can_use_fnmatch_pattern_in_allowlist_model(settings):
_ = post.author


@queryspy_context()
@zealot_context()
def test_can_use_fnmatch_pattern_in_allowlist_field(settings):
settings.QUERYSPY_ALLOWLIST = [{"model": "social.User", "field": "p*st*"}]

Expand Down
20 changes: 10 additions & 10 deletions tests/test_nplusones.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
from django.db import connection
from django.test.utils import CaptureQueriesContext
from djangoproject.social.models import Post, Profile, User
from queryspy import NPlusOneError, queryspy_context
from zealot import NPlusOneError, zealot_context

from .factories import PostFactory, ProfileFactory, UserFactory

pytestmark = pytest.mark.django_db


@queryspy_context()
@zealot_context()
def test_detects_nplusone_in_forward_many_to_one():
[user_1, user_2] = UserFactory.create_batch(2)
PostFactory.create(author=user_1)
Expand All @@ -26,7 +26,7 @@ def test_detects_nplusone_in_forward_many_to_one():
_ = post.author.username


@queryspy_context()
@zealot_context()
def test_detects_nplusone_in_reverse_many_to_one():
[user_1, user_2] = UserFactory.create_batch(2)
PostFactory.create(author=user_1)
Expand All @@ -45,14 +45,14 @@ def test_no_false_positive_when_calling_reverse_many_to_one_twice():
user = UserFactory.create()
PostFactory.create(author=user)

with queryspy_context(), CaptureQueriesContext(connection) as ctx:
with zealot_context(), CaptureQueriesContext(connection) as ctx:
queryset = user.posts.all()
list(queryset) # evaluate queryset once
list(queryset) # evalute again (cached)
assert len(ctx.captured_queries) == 1


@queryspy_context()
@zealot_context()
def test_detects_nplusone_in_forward_one_to_one():
[user_1, user_2] = UserFactory.create_batch(2)
ProfileFactory.create(user=user_1)
Expand All @@ -67,7 +67,7 @@ def test_detects_nplusone_in_forward_one_to_one():
_ = profile.user.username


@queryspy_context()
@zealot_context()
def test_detects_nplusone_in_reverse_one_to_one():
[user_1, user_2] = UserFactory.create_batch(2)
ProfileFactory.create(user=user_1)
Expand All @@ -82,7 +82,7 @@ def test_detects_nplusone_in_reverse_one_to_one():
_ = user.profile.display_name


@queryspy_context()
@zealot_context()
def test_detects_nplusone_in_forward_many_to_many():
[user_1, user_2] = UserFactory.create_batch(2)
user_1.following.add(user_2)
Expand All @@ -97,7 +97,7 @@ def test_detects_nplusone_in_forward_many_to_many():
_ = list(user.following.all())


@queryspy_context()
@zealot_context()
def test_detects_nplusone_in_reverse_many_to_many():
[user_1, user_2] = UserFactory.create_batch(2)
user_1.following.add(user_2)
Expand All @@ -112,7 +112,7 @@ def test_detects_nplusone_in_reverse_many_to_many():
_ = list(user.followers.all())


@queryspy_context()
@zealot_context()
def test_detects_nplusone_in_reverse_many_to_many_with_no_related_name():
[user_1, user_2] = UserFactory.create_batch(2)
user_1.blocked.add(user_2)
Expand All @@ -127,7 +127,7 @@ def test_detects_nplusone_in_reverse_many_to_many_with_no_related_name():
_ = list(user.user_set.all())


@queryspy_context()
@zealot_context()
def test_detects_nplusone_due_to_deferred_fields():
[user_1, user_2] = UserFactory.create_batch(2)
PostFactory.create(author=user_1)
Expand Down
Loading

0 comments on commit e4ee3a9

Please sign in to comment.