• Stars
    star
    330
  • Rank 127,657 (Top 3 %)
  • Language
    Python
  • License
    MIT License
  • Created about 8 years ago
  • Updated about 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Keep detailed records of the performance of your Django code.

django-perf-rec

https://img.shields.io/github/actions/workflow/status/adamchainz/django-perf-rec/main.yml?branch=main&style=for-the-badge https://img.shields.io/pypi/v/django-perf-rec.svg?style=for-the-badge https://img.shields.io/badge/code%20style-black-000000.svg?style=for-the-badge pre-commit

"Keep detailed records of the performance of your Django code."

django-perf-rec is like Django's assertNumQueries on steroids. It lets you track the individual queries and cache operations that occur in your code. Use it in your tests like so:

def test_home(self):
    with django_perf_rec.record():
        self.client.get("/")

It then stores a YAML file alongside the test file that tracks the queries and operations, looking something like:

MyTests.test_home:
- cache|get: home_data.user_id.#
- db: 'SELECT ... FROM myapp_table WHERE (myapp_table.id = #)'
- db: 'SELECT ... FROM myapp_table WHERE (myapp_table.id = #)'

When the test is run again, the new record will be compared with the one in the YAML file. If they are different, an assertion failure will be raised, failing the test. Magic!

The queries and keys are 'fingerprinted', replacing information that seems variable with # and .... This is done to avoid spurious failures when e.g. primary keys are different, random data is used, new columns are added to tables, etc.

If you check the YAML file in along with your tests, you'll have unbreakable performance with much better information about any regressions compared to assertNumQueries. If you are fine with the changes from a failing test, just remove the file and rerun the test to regenerate it.

For more information, see our introductory blog post that says a little more about why we made it.

Installation

Use pip:

python -m pip install django-perf-rec

Requirements

Python 3.7 to 3.11 supported.

Django 2.2 to 4.2 supported.


Are your tests slow? Check out my book Speed Up Your Django Tests which covers loads of ways to write faster, more accurate tests.


API

record(record_name: str | None=None, path: str | None=None, capture_traceback: callable[[Operation], bool] | None=None, capture_operation: callable[[Operation], bool] | None=None)

Return a context manager that will be used for a single performance test.

The arguments must be passed as keyword arguments.

path is the path to a directory or file in which to store the record. If it ends with '/', or is left as None, the filename will be automatically determined by looking at the filename the calling code is in and replacing the .py[c] extension with .perf.yml. If it points to a directory that doesn't exist, that directory will be created.

record_name is the name of the record inside the performance file to use. If left as None, the code assumes you are inside a Django TestCase and uses magic stack inspection to find that test case, and uses a name based upon the test case name + the test method name + an optional counter if you invoke record() multiple times inside the same test method.

Whilst open, the context manager tracks all DB queries on all connections, and all cache operations on all defined caches. It names the connection/cache in the tracked operation it uses, except from for the default one.

When the context manager exits, it will use the list of operations it has gathered. If the relevant file specified using path doesn't exist, or doesn't contain data for the specific record_name, it will be created and saved and the test will pass with no assertions. However if the record does exist inside the file, the collected record will be compared with the original one, and if different, an AssertionError will be raised. When running on pytest, this will use its fancy assertion rewriting; in other test runners/uses the full diff will be attached to the message.

Example:

import django_perf_rec

from app.models import Author


class AuthorPerformanceTests(TestCase):
    def test_special_method(self):
        with django_perf_rec.record():
            list(Author.objects.special_method())

capture_traceback, if not None, should be a function that takes one argument, the given DB or cache operation, and returns a bool indicating if a traceback should be captured for the operation (by default, they are not). Capturing tracebacks allows fine-grained debugging of code paths causing the operations. Be aware that records differing only by the presence of tracebacks will not match and cause an AssertionError to be raised, so it's not normally suitable to permanently record the tracebacks.

For example, if you wanted to know what code paths query the table my_table, you could use a capture_traceback function like so:

def debug_sql_query(operation):
    return "my_tables" in operation.query


def test_special_method(self):
    with django_perf_rec.record(capture_traceback=debug_sql_query):
        list(Author.objects.special_method())

The performance record here would include a standard Python traceback attached to each SQL query containing "my_table".

capture_operation, if not None, should be a function that takes one argument, the given DB or cache operation, and returns a bool indicating if the operation should be recorded at all (by default, all operations are recorded). Not capturing some operations allows for hiding some code paths to be ignored in your tests, such as for ignoring database queries that would be replaced by an external service in production.

For example, if you knew that in testing all queries to some table would be replaced in production with something else you could use a capture_operation function like so:

def hide_my_tables(operation):
    return "my_tables" in operation.query


def test_special_function(self):
    with django_perf_rec.record(capture_operation=hide_my_tables):
        list(Author.objects.all())

TestCaseMixin

A mixin class to be added to your custom TestCase subclass so you can use django-perf-rec across your codebase without needing to import it in each individual test file. It adds one method, record_performance(), whose signature is the same as record() above.

Example:

# yplan/test.py
from django.test import TestCase as OrigTestCase
from django_perf_rec import TestCaseMixin


class TestCase(TestCaseMixin, OrigTestCase):
    pass


# app/tests/models/test_author.py
from app.models import Author
from yplan.test import TestCase


class AuthorPerformanceTests(TestCase):
    def test_special_method(self):
        with self.record_performance():
            list(Author.objects.special_method())

get_perf_path(file_path)

Encapsulates the logic used in record() to form path from the path of the file containing the currently running test, mostly swapping '.py' or '.pyc' for '.perf.yml'. You might want to use this when calling record() from somewhere other than inside a test (which causes the automatic inspection to fail), to match the same filename.

get_record_name(test_name, class_name=None)

Encapsulates the logic used in record() to form a record_name from details of the currently running test. You might want to use this when calling record() from somewhere other than inside a test (which causes the automatic inspection to fail), to match the same record_name.

Settings

Behaviour can be customized with a dictionary called PERF_REC in your Django settings, for example:

PERF_REC = {
    "MODE": "once",
}

The possible keys to this dictionary are explained below.

HIDE_COLUMNS

The HIDE_COLUMNS setting may be used to change the way django-perf-rec simplifies SQL in the recording files it makes. It takes a boolean:

  • True (default) causes column lists in queries to be collapsed, e.g. SELECT a, b, c FROM t becomes SELECT ... FROM t. This is useful because selected columns often don't affect query time in typical Django applications, it makes the records easier to read, and they then don't need updating every time model fields are changed.
  • False stops the collapsing behaviour, causing all the columns to be output in the files.

MODE

The MODE setting may be used to change the way django-perf-rec behaves when a performance record does not exist during a test run.

  • 'once' (default) creates missing records silently.
  • 'none' raises AssertionError when a record does not exist. You probably want to use this mode in CI, to ensure new tests fail if their corresponding performance records were not committed.
  • 'all' creates missing records and then raises AssertionError.
  • 'overwrite' creates or updates records silently.

Usage in Pytest

If you're using Pytest, you might want to call record() from within a Pytest fixture and have it automatically apply to all your tests. We have an example of this, see the file test_pytest_fixture_usage.py in the test suite.

More Repositories

1

django-cors-headers

Django app for handling the server headers required for Cross-Origin Resource Sharing (CORS)
Python
5,087
star
2

django-htmx

Extensions for using Django with htmx.
JavaScript
866
star
3

django-upgrade

Automatically upgrade your Django projects.
Python
641
star
4

django-mysql

๐Ÿฌ ๐Ÿด Extensions to Django for use with MySQL/MariaDB
Python
535
star
5

blacken-docs

Run `black` on python code blocks in documentation files
Python
513
star
6

time-machine

Travel through time in your tests.
Python
447
star
7

flake8-comprehensions

โ„๏ธ A flake8 plugin to help you write better list/set/dict comprehensions.
Python
446
star
8

django-browser-reload

Automatically reload your browser in development.
Python
296
star
9

mac-ansible

๐Ÿ„ Configuring my mac with Ansible
Shell
170
star
10

patchy

โš“ Patch the inner source of python functions at runtime.
Python
163
star
11

apig-wsgi

Wrap a WSGI application in an AWS Lambda handler function for running on API Gateway or an ALB.
Python
146
star
12

django-linear-migrations

Ensure your migration history is linear.
Python
136
star
13

treepoem

Barcode rendering for Python supporting QRcode, Aztec, PDF417, I25, Code128, Code39 and many more types.
PostScript
115
star
14

ec2-metadata

An easy interface to query the EC2 metadata API, with caching.
Python
102
star
15

django-rich

Extensions for using Rich with Django.
Python
90
star
16

django-permissions-policy

Set the draft security HTTP header Permissions-Policy (previously Feature-Policy) on your Django app.
Python
81
star
17

django-watchfiles

Use watchfiles in Djangoโ€™s autoreloader.
Python
81
star
18

django-read-only

Disable Django database writes.
Python
75
star
19

django-minify-html

Use minify-html, the extremely fast HTML + JS + CSS minifier, with Django.
Python
73
star
20

flake8-tidy-imports

โ„๏ธ A flake8 plugin that helps you write tidier imports.
Python
60
star
21

heroicons

Use heroicons in your Django and Jinja templates.
Python
58
star
22

SublimeFiglet

Add in ASCII text art from "figlet"
Python
46
star
23

lifelogger

๐Ÿ“… Track your life like a pro on Google Calendar via your terminal.
Python
40
star
24

pip-lock

Check for differences between requirements.txt files and your environment
Python
36
star
25

django-capture-on-commit-callbacks

Capture and make assertions on transaction.on_commit() callbacks.
Python
35
star
26

django-version-checks

System checks for your project's environment.
Python
34
star
27

django-jsonfield

(Maintenance mode only) Cross-database JSON field for Django models.
Python
30
star
28

unittest-parametrize

Parametrize tests within unittest TestCases.
Python
29
star
29

scripts

Useful little scripts that I use on commandline. Work in OS-X + zsh at least.
Shell
27
star
30

multilint

โœ… Run multiple python linters easily
Python
27
star
31

flake8-no-pep420

A flake8 plugin to ban PEP-420 implicit namespace packages.
Python
22
star
32

django-startproject-templates

Python
22
star
33

pytest-is-running

pytest plugin providing a function to check if pytest is running.
Python
21
star
34

SublimeHTMLMustache

โœ๏ธ Adds HTML Mustache as a language to Sublime Text 2/3, with snippets.
19
star
35

pytest-reverse

Pytest plugin to reverse test order.
Python
19
star
36

owela-club

Play the Namibian game of Owela against a terrible AI. Built using Django and htmx.
Python
18
star
37

nose-randomly

๐Ÿ‘ƒ Nose plugin to randomly order tests and control `random.seed`
Python
17
star
38

talk-how-to-hack-a-django-website

JavaScript
14
star
39

dynamodb_utils

A toolchain for Amazon's DynamoDB to make common operations (backup, restore backups) easier.
Python
12
star
40

sound-resynthesis

๐Ÿ”ˆ Sound Resynthesis with a Genetic Algorithm - my final year project from university
Java
12
star
41

mariadb-dyncol

๐Ÿ’พ Python dicts <-> MariaDB Dynamic Column binary format
Python
11
star
42

pre-commit-oxipng

Mirror of oxipng for pre-commit.
Rust
11
star
43

pytest-flake8dir

โ„๏ธ A pytest fixture for testing flake8 plugins.
Python
11
star
44

logentries-cli

๐Ÿ“’ Get your logs from Logentries on the comandline.
Python
10
star
45

pre-commit-dprint

Mirror of dprint for pre-commit.
9
star
46

sublime-rst-improved

Python
8
star
47

h

Python
8
star
48

talk-improve-startup-time

โ€œHow to profile and improve startup timeโ€ talk
JavaScript
8
star
49

sublime_text_settings

โœ๏ธ My settings for sublime text 3 - as in Packages/User
Python
8
star
50

talk-django-and-htmx

JavaScript
7
star
51

django-settings-file

Python
7
star
52

tox-py

Adds the --py flag to tox to run environments matching a given Python interpreter.
Python
6
star
53

kwargs-only

A decorator to make a function accept keyword arguments only, on both Python 2 and 3.
Python
6
star
54

pytest-super-check

๐Ÿ”’ Pytest plugin to ensure all your TestCase classes call super() in setUp, tearDown, etc.
Python
6
star
55

django_atomic_celery

Atomic transaction aware Celery tasks for Django
Python
6
star
56

django-coverage-example

Python
5
star
57

django-pymysql-backend

A Django database backend for MySQL using PyMySQL.
Python
5
star
58

pytest-restrict

๐Ÿ”’ Pytest plugin to restrict the test types allowed
Python
5
star
59

talk-data-oriented-django

JavaScript
4
star
60

talk-speed-up-your-tests-with-setuptestdata

JavaScript
4
star
61

talk-django-and-web-security-headers

JavaScript
3
star
62

fluentd.tmLanguage

Syntax highlighting for Fluentd configuration files
3
star
63

django-ticket-33153

https://code.djangoproject.com/ticket/33153
Python
3
star
64

pytest-flake8-path

A pytest fixture for testing flake8 plugins.
Python
3
star
65

django_atomic_signals

Signals for atomic transaction blocks in Django 1.6+
Python
3
star
66

flake8-no-types

A flake8 plugin to ban type hints.
Python
3
star
67

SublimeMoveTabs

โœ๏ธ A short plugin for Sublime Text 2 that allows rearrangement of tabs/'views' with the keyboard.
Python
3
star
68

dynamodb_local_utils

Automatically run DynamoDB Local on Mac OS X
Shell
3
star
69

pygments-git

Pygments lexers for Git output and files
Python
3
star
70

workshop-evenergy-concurrency-and-parallelism

Python
2
star
71

talk-how-complex-systems-fail

Talk for the Papers We Love London meetup
TeX
2
star
72

google_lifelog

Making a lifelog on google calendar.
Python
2
star
73

talk-building-interactive-pages-with-htmx

JavaScript
2
star
74

workshop-idiomatic-python

Python
2
star
75

ansible-talk-custom-template-filters

My talk for the Ansible London Meetup in March 2015
TeX
2
star
76

talk-django-vs-flask

JavaScript
2
star
77

django-talk-factory-boy

Talk for London Django Meetup
TeX
2
star
78

ProgrammingInterview

Solving the problems posted on ProgrammingInterview on YouTube
Python
2
star
79

example-pre-commit-ci-lite

example
2
star
80

django-server-push-demo

Python
2
star
81

talk-django-capture-on-commit-callbacks

JavaScript
2
star
82

techblog

Filled with little coding notes and fixes.
2
star
83

adamchainz

๐Ÿ‘‹
Python
2
star
84

SublimeCowsay

โœ๏ธ๐Ÿฎ A silly little Sublime Text plugin for 2 and 3 to allow you to quickly convert a text selection to a cow speech bubble via the brilliant cowsay utility.
Python
2
star
85

djceu2019-workshop

Python
2
star
86

talk-what-happens-when-you-run-manage.py-test

JavaScript
2
star
87

talk-technologies-that-will-be-around-in-21-years

JavaScript
2
star
88

django-demo-constraint-single-column-not-null

Python
2
star
89

django-harlequin

Launch Harlequin, the SQL IDE for your Terminal, with your Django database configuration.
Python
2
star
90

channels-bug-connection-closed

Reproduction for Channels bug
Python
1
star
91

workshop-concurrency-and-parallelism

Python
1
star
92

django-talk-duth

Django Under The Hood 2015 Summary
TeX
1
star
93

workshop-rest-api-django

Python
1
star
94

workshop-recommended-practices

Python
1
star
95

talk-django-3.2-test-features

JavaScript
1
star
96

workshop-profiling-and-debugging

Python
1
star
97

django-feature-policy-shim

1
star
98

kvkit

high-level python toolkit for ordered key/value stores
Python
1
star
99

phabricator-csv-import

Python
1
star
100

django-blue-green-example

Reproducing the technique from โ€œSmooth Database Changes in Blue-Green Deploymentsโ€ by Mariusz Felisiak.
Python
1
star