django-assert-queries


Namedjango-assert-queries JSON
Version 1.0.1 PyPI version JSON
download
home_pageNone
SummaryAssert and instrument executed database queries using Django.
upload_time2024-09-29 04:24:09
maintainerNone
docs_urlNone
authorNone
requires_python>=3.8
licenseMIT
keywords database django pytest sql unit tests
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Django-assert-queries: Keep your Django queries from exploding

ORMs. Love 'em or hate 'em, they're often part of the job, and a core part of
writing [Django](https://djangoproject.com) webapps. They can make it easy
to write queries that work across databases, but the trade-off is your queries
might explode in number and in complexity.

Who hasn't made this mistake?

```python
for book in Book.objects.all():
    print(f'Author: {book.author.name}')
```

Spot the error? We're fetching the associated author once for every book. For
100 books, that's 101 queries!

And that's just a really basic query.

These mistakes happen all the time, and they're not always easy to catch in
unit tests.

That's what the clear-but-unimaginatively-named
[django-assert-queries](https://pypi.org/project/django-assert-queries) is
here to solve. With proper use, this can save companies from costly mistakes.
[We've](https://www.reviewboard.org) found it to be an invaluable tool in our
arsenal.

We'll explore how it does that, but first, let's get things installed.


## Installation and Usage

```shell
$ pip install django-assert-queries
```

``django-assert-queries`` follows [semantic versioning](https://semver.org/),
meaning no surprises when you upgrade.

[Documentation](https://django-assert-queries.readthedocs.io) is available,
covering the whole codebase.


## Let's see it in action

We're going to catch the bug above.

```python
from django_assert_queries import assert_queries


def test():
    expected_queries = [
        {
            'model': Book,
        },
    ]

    with assert_queries(expected_queries):
        for book in Book.objects.all():
            print(f'Book {book.name} by {book.author.name}')
```

When we run that, we get:

```
E AssertionError: Expected 1 queries, but got 101
E
E 100 queries failed to meet expectations.
E
E Query 2:
E   model: <class 'django_assert_queries.tests.models.Author'> != None
E   tables: {'tests_author'} != {}
E   where: Q(id=1) != Q()
E   SQL: SELECT "tests_author"."id", "tests_author"."name" FROM "tests_author" WHERE "tests_author"."id" = 1 LIMIT 21
E
E Query 3:
E   model: <class 'django_assert_queries.tests.models.Author'> != None
E   tables: {'tests_author'} != {}
E   where: Q(id=2) != Q()
E   SQL: SELECT "tests_author"."id", "tests_author"."name" FROM "tests_author" WHERE "tests_author"."id" = 2 LIMIT 21
E
E Query 4:
E   model: <class 'django_assert_queries.tests.models.Author'> != None
E   tables: {'tests_author'} != {}
E   where: Q(id=3) != Q()
E   SQL: SELECT "tests_author"."id", "tests_author"."name" FROM "tests_author" WHERE "tests_author"."id" = 3 LIMIT 21
E

[...]
```

That problem just became a lot more clear. Let's fix this.


```python
from django_assert_queries import assert_queries


def test():
    # We'll select-related the authors.
    expected_queries = [
        {
            'model': Book,
            'select_related' ('author',),
        },
    ]

    with assert_queries(expected_queries):
        for book in Book.objects.select_related('author'):
            print(f'Book {book.name} by {book.author.name}')
```

These can be a lot more thorough:

```python
def test_complex_query():
    expected_queries = [
        # Initial query for the books.
        {
            'model': Book,
            'limit': 2,
            'only_fields': {'author', 'name'},
            'select_related': {'author'},
        },

        # Initial query for the authors.
        {
            'model': Author,
            'annotations': {
                'book_count': Count('books'),
            },
            'group_by': True,
            'num_joins': 1,
            'tables': {
                'tests_author',
                'tests_book',
            },
        },

        # The prefetch-related for all the authors' books.
        {
            'model': Book,
            'where': Q(author__in=list(Author.objects.all())),
        },
    ]

    books_queryset = (
        Book.objects
        .filter(name__in=['Book 1', 'Book 9'])
        .only('author', 'name')
        .select_related('author')
        [:2]
    )

    authors_queryset = (
        Author.objects
        .annotate(book_count=Count('books'))
        .prefetch_related('books')
    )

    with assert_queries(expected_queries):
        for book in books_queryset:
            print(f'Book {book.name} by {book.author.name}')

        for author in authors_queryset:
            print(f'Author {author.name} published {author.book_count} books:')

            for book in author.books.all():
                print(f'    {book.name}')
```

Here we've got filtering, annotations, field limiting, filtering, joins,
and prefetch-related.

These cover just about everything that Django queries can do, and when used
correctly your unit tests can account for just about every query your
application makes.


## Brought to you by Beanbag

At [Beanbag](https://www.beanbaginc.com), we're all about building better
software development tools.

Our flagship product is [Review Board](https://www.reviewboard.org), one of
the first-ever code review products on the market, and originator for most
now-standard code review features.

We also build these lovely Python packages:

* [beanbag-docutils](https://github.com/beanbaginc/beanbag-docutils/) -
  Multi-DPI images, enhanced syntax, and many more add-ons for Sphinx
  documentation writers.

* [Djblets](https://github.com/djblets/djblets/) -
  Our pack of Django utilities for datagrids, API, privacy, extensions, and
  more. Used by Review Board.

* [Grumble](https://github.com/beanbaginc/grumble/) -
  For Python print debuggers drowning in print statements.

* [Housekeeping](https://github.com/beanbaginc/housekeeping/) -
  Deprecation management for Python codebases of all sizes.

* [kgb](https://github.com/beanbaginc/kgb/) -
  Function spies for Python unit tests, a major upgrade from mocks.

* [registries](https://github.com/beanbaginc/registries/) -
  Registration management and lookup of objects, for extensible Python
  applications.

* [typelets](https://github.com/beanbaginc/typelets/) -
  Python typing additions, including comprehensive JSON and JSON-compatible
  data structures, symbols, and Django add-ons.

You can see more on [github.com/beanbaginc](https://github.com/beanbaginc) and
[github.com/reviewboard](https://github.com/reviewboard).

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "django-assert-queries",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": null,
    "keywords": "database, django, pytest, sql, unit tests",
    "author": null,
    "author_email": "\"Beanbag, Inc.\" <questions@beanbaginc.com>",
    "download_url": "https://files.pythonhosted.org/packages/97/82/18b3772daec45f073d63ef35b5a1e459540ac54c2b092fa8e9414271203d/django_assert_queries-1.0.1.tar.gz",
    "platform": null,
    "description": "# Django-assert-queries: Keep your Django queries from exploding\n\nORMs. Love 'em or hate 'em, they're often part of the job, and a core part of\nwriting [Django](https://djangoproject.com) webapps. They can make it easy\nto write queries that work across databases, but the trade-off is your queries\nmight explode in number and in complexity.\n\nWho hasn't made this mistake?\n\n```python\nfor book in Book.objects.all():\n    print(f'Author: {book.author.name}')\n```\n\nSpot the error? We're fetching the associated author once for every book. For\n100 books, that's 101 queries!\n\nAnd that's just a really basic query.\n\nThese mistakes happen all the time, and they're not always easy to catch in\nunit tests.\n\nThat's what the clear-but-unimaginatively-named\n[django-assert-queries](https://pypi.org/project/django-assert-queries) is\nhere to solve. With proper use, this can save companies from costly mistakes.\n[We've](https://www.reviewboard.org) found it to be an invaluable tool in our\narsenal.\n\nWe'll explore how it does that, but first, let's get things installed.\n\n\n## Installation and Usage\n\n```shell\n$ pip install django-assert-queries\n```\n\n``django-assert-queries`` follows [semantic versioning](https://semver.org/),\nmeaning no surprises when you upgrade.\n\n[Documentation](https://django-assert-queries.readthedocs.io) is available,\ncovering the whole codebase.\n\n\n## Let's see it in action\n\nWe're going to catch the bug above.\n\n```python\nfrom django_assert_queries import assert_queries\n\n\ndef test():\n    expected_queries = [\n        {\n            'model': Book,\n        },\n    ]\n\n    with assert_queries(expected_queries):\n        for book in Book.objects.all():\n            print(f'Book {book.name} by {book.author.name}')\n```\n\nWhen we run that, we get:\n\n```\nE AssertionError: Expected 1 queries, but got 101\nE\nE 100 queries failed to meet expectations.\nE\nE Query 2:\nE   model: <class 'django_assert_queries.tests.models.Author'> != None\nE   tables: {'tests_author'} != {}\nE   where: Q(id=1) != Q()\nE   SQL: SELECT \"tests_author\".\"id\", \"tests_author\".\"name\" FROM \"tests_author\" WHERE \"tests_author\".\"id\" = 1 LIMIT 21\nE\nE Query 3:\nE   model: <class 'django_assert_queries.tests.models.Author'> != None\nE   tables: {'tests_author'} != {}\nE   where: Q(id=2) != Q()\nE   SQL: SELECT \"tests_author\".\"id\", \"tests_author\".\"name\" FROM \"tests_author\" WHERE \"tests_author\".\"id\" = 2 LIMIT 21\nE\nE Query 4:\nE   model: <class 'django_assert_queries.tests.models.Author'> != None\nE   tables: {'tests_author'} != {}\nE   where: Q(id=3) != Q()\nE   SQL: SELECT \"tests_author\".\"id\", \"tests_author\".\"name\" FROM \"tests_author\" WHERE \"tests_author\".\"id\" = 3 LIMIT 21\nE\n\n[...]\n```\n\nThat problem just became a lot more clear. Let's fix this.\n\n\n```python\nfrom django_assert_queries import assert_queries\n\n\ndef test():\n    # We'll select-related the authors.\n    expected_queries = [\n        {\n            'model': Book,\n            'select_related' ('author',),\n        },\n    ]\n\n    with assert_queries(expected_queries):\n        for book in Book.objects.select_related('author'):\n            print(f'Book {book.name} by {book.author.name}')\n```\n\nThese can be a lot more thorough:\n\n```python\ndef test_complex_query():\n    expected_queries = [\n        # Initial query for the books.\n        {\n            'model': Book,\n            'limit': 2,\n            'only_fields': {'author', 'name'},\n            'select_related': {'author'},\n        },\n\n        # Initial query for the authors.\n        {\n            'model': Author,\n            'annotations': {\n                'book_count': Count('books'),\n            },\n            'group_by': True,\n            'num_joins': 1,\n            'tables': {\n                'tests_author',\n                'tests_book',\n            },\n        },\n\n        # The prefetch-related for all the authors' books.\n        {\n            'model': Book,\n            'where': Q(author__in=list(Author.objects.all())),\n        },\n    ]\n\n    books_queryset = (\n        Book.objects\n        .filter(name__in=['Book 1', 'Book 9'])\n        .only('author', 'name')\n        .select_related('author')\n        [:2]\n    )\n\n    authors_queryset = (\n        Author.objects\n        .annotate(book_count=Count('books'))\n        .prefetch_related('books')\n    )\n\n    with assert_queries(expected_queries):\n        for book in books_queryset:\n            print(f'Book {book.name} by {book.author.name}')\n\n        for author in authors_queryset:\n            print(f'Author {author.name} published {author.book_count} books:')\n\n            for book in author.books.all():\n                print(f'    {book.name}')\n```\n\nHere we've got filtering, annotations, field limiting, filtering, joins,\nand prefetch-related.\n\nThese cover just about everything that Django queries can do, and when used\ncorrectly your unit tests can account for just about every query your\napplication makes.\n\n\n## Brought to you by Beanbag\n\nAt [Beanbag](https://www.beanbaginc.com), we're all about building better\nsoftware development tools.\n\nOur flagship product is [Review Board](https://www.reviewboard.org), one of\nthe first-ever code review products on the market, and originator for most\nnow-standard code review features.\n\nWe also build these lovely Python packages:\n\n* [beanbag-docutils](https://github.com/beanbaginc/beanbag-docutils/) -\n  Multi-DPI images, enhanced syntax, and many more add-ons for Sphinx\n  documentation writers.\n\n* [Djblets](https://github.com/djblets/djblets/) -\n  Our pack of Django utilities for datagrids, API, privacy, extensions, and\n  more. Used by Review Board.\n\n* [Grumble](https://github.com/beanbaginc/grumble/) -\n  For Python print debuggers drowning in print statements.\n\n* [Housekeeping](https://github.com/beanbaginc/housekeeping/) -\n  Deprecation management for Python codebases of all sizes.\n\n* [kgb](https://github.com/beanbaginc/kgb/) -\n  Function spies for Python unit tests, a major upgrade from mocks.\n\n* [registries](https://github.com/beanbaginc/registries/) -\n  Registration management and lookup of objects, for extensible Python\n  applications.\n\n* [typelets](https://github.com/beanbaginc/typelets/) -\n  Python typing additions, including comprehensive JSON and JSON-compatible\n  data structures, symbols, and Django add-ons.\n\nYou can see more on [github.com/beanbaginc](https://github.com/beanbaginc) and\n[github.com/reviewboard](https://github.com/reviewboard).\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Assert and instrument executed database queries using Django.",
    "version": "1.0.1",
    "project_urls": {
        "Documentation": "https://github.com/beanbaginc/django-assert-queries",
        "Homepage": "https://github.com/beanbaginc/django-assert-queries",
        "Repository": "https://github.com/beanbaginc/django-assert-queries"
    },
    "split_keywords": [
        "database",
        " django",
        " pytest",
        " sql",
        " unit tests"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "28c5628880b9b222b6116634037b1d75ef2b13d630d97f1efc3134cfa3e7fa28",
                "md5": "b9d4db86454720713b37ce333340603d",
                "sha256": "da653570342515c02a8a5acbb6426b5b7a8325130a287cb4b1dcde73f2ddd5b6"
            },
            "downloads": -1,
            "filename": "django_assert_queries-1.0.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "b9d4db86454720713b37ce333340603d",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 29033,
            "upload_time": "2024-09-29T04:24:07",
            "upload_time_iso_8601": "2024-09-29T04:24:07.548987Z",
            "url": "https://files.pythonhosted.org/packages/28/c5/628880b9b222b6116634037b1d75ef2b13d630d97f1efc3134cfa3e7fa28/django_assert_queries-1.0.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "978218b3772daec45f073d63ef35b5a1e459540ac54c2b092fa8e9414271203d",
                "md5": "caa5535ab984892da4e838e4f7d613e3",
                "sha256": "62d1887e612ccbac51df4749a5f0570f40d503328fcc8020168a29d63afe5be3"
            },
            "downloads": -1,
            "filename": "django_assert_queries-1.0.1.tar.gz",
            "has_sig": false,
            "md5_digest": "caa5535ab984892da4e838e4f7d613e3",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 29771,
            "upload_time": "2024-09-29T04:24:09",
            "upload_time_iso_8601": "2024-09-29T04:24:09.166183Z",
            "url": "https://files.pythonhosted.org/packages/97/82/18b3772daec45f073d63ef35b5a1e459540ac54c2b092fa8e9414271203d/django_assert_queries-1.0.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-09-29 04:24:09",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "beanbaginc",
    "github_project": "django-assert-queries",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "tox": true,
    "lcname": "django-assert-queries"
}
        
Elapsed time: 1.24999s