Integrations๐Ÿ”—

In this section, we'll cover the integrations to other libraries that Undine includes.

channels๐Ÿ”—

pip install undine[channels]

Undine provides support for the GraphQL over WebSocket protocol by integrating with the channels library. Using the channels integration requires turning on Undine's Async Support.

Additionally, you need to configure Django in your project's asgi.py file so that websocket requests are sent to Undine's channels consumer.

import os

from django.core.asgi import get_asgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "...")
django_application = get_asgi_application()

# Needs be imported after 'django_application' is created!
from undine.integrations.channels import get_websocket_enabled_app

application = get_websocket_enabled_app(django_application)

This will add a new route to the Django application that will handle the WebSocket requests. The path for this route is defined using the WEBSOCKET_PATH setting.

django-debug-toolbar๐Ÿ”—

pip install undine[debug]

Undine integrates with django-debug-toolbar by modifying the toolbar HTML so that it integrates with GraphiQL. After installing django-debug-toolbar, Undine should automatically patch it without any additional configuration.

django-modeltranslation๐Ÿ”—

Undine integrates with django-modeltranslation by allowing you to modify how autogenerated Fields, Inputs, Filters and Orders are created. Specifically, this happens using two settings: MODELTRANSLATION_INCLUDE_TRANSLATABLE and MODELTRANSLATION_INCLUDE_TRANSLATIONS.

Let's say you the following Model (with MODELTRANSLATION_LANGUAGES = ("en", "fi"))

1
2
3
4
5
6
7
8
9
from django.db import models


class Task(models.Model):
    name = models.CharField(max_length=255)

    # Created by modeltranslation
    name_en: str | None
    name_fi: str | None

...and the following translation options

1
2
3
4
5
6
7
8
9
from modeltranslation.decorators import register
from modeltranslation.translator import TranslationOptions

from .models import Task


@register(Task)
class TaskTranslationOptions(TranslationOptions):
    fields = ["name"]

Based on the Model's translation options, django-modeltranslation adds additional fields for each language defined by the MODELTRANSLATION_LANGUAGES setting. Let's call the added fields the "translatable" fields, and the fields they are based on the "translation" fields.

Using the MODELTRANSLATION_INCLUDE_TRANSLATABLE and MODELTRANSLATION_INCLUDE_TRANSLATIONS settings, you can control which of these fields undine will add to your schema when using autogeneration. By default, only the translation fields are added. You can of course always add the translatable fields manually.

Note that due to the way that django-modeltranslation works, the translation fields are always nullable, even for the default language.

pytest๐Ÿ”—

Undine comes with a pytest plugin that includes a testing client and few fixtures to help you write tests for your GraphQL APIs.

The GraphQLClient class is wrapper around Django's test client that makes testing your GraphQL API easier. It can be added to a test using the graphql fixture. Here is a simple example:

1
2
3
4
5
6
def test_example(graphql) -> None:
    query = "query { test }"

    response = graphql(query)

    assert response.data == {"hello": "Hello, World!"}

GraphQL requests can be made by calling the client as shown above. This makes a request to the GraphQL endpoint set by the GRAPHQL_PATH setting.

GraphQL variables can be passed using the variables argument. If these variables include any files, the client will automatically create a GraphQL multipart request instead of a normal GraphQL request.

1
2
3
4
5
6
7
def test_example(graphql) -> None:
    mutation = "mutation($input: TestInput!) { test(input: $input) }"
    data = {"name": "World"}

    response = graphql(mutation, variables={"input": data})

    assert response.data == {"hello": "Hello, World!"}

The client returns a custom response object GraphQLClientResponse, which has a number of useful properties for introspecting the response. The response object also has details on the database queries that were executed during the request, which can be useful for debugging the performance of your GraphQL API.

def test_example(graphql) -> None:
    query = "query { test { edges { node { id } } } }"

    response = graphql(query, count_queries=True)

    # The whole response
    assert response.json == {
        "data": {"test": {"edges": [{"node": {"id": "1"}}]}},
        "errors": [{"message": "Error message", "path": ["test"]}],
    }

    # Error properties
    assert response.has_errors is True
    assert response.errors == [{"message": "Error message", "path": ["test"]}]
    assert response.error_message(0) == "Error message"

    # Data properties
    assert response.data == {"test": {"edges": [{"node": {"id": "1"}}]}}
    assert response.results == {"edges": [{"node": {"id": "1"}}]}

    # Connection specific properties
    assert response.edges == [{"node": {"id": "1"}}]
    assert response.node(0) == {"id": "1"}

    # Check queries (requires `count_queries=True`)
    assert response.query_count == 1
    assert response.queries == ["SELECT 1;"]
    response.assert_query_count(1)

An async version of the client is also available, which can be accessed from the graphql_async fixture.

import pytest


@pytest.mark.asyncio  # Requires the `pytest-asyncio` plugin
@pytest.mark.django_db(transaction=True)  # For sessions
async def test_example(graphql_async) -> None:
    query = "query { test }"

    response = await graphql_async(query)

    assert response.data == {"hello": "Hello, World!"}

The plugin also includes a undine_settings fixture that allows modifying Undine's settings during testing more easily.

def test_example(undine_settings) -> None:
    undine_settings.NO_ERROR_LOCATION = True

If the channels integration is installed, the test client can also send GraphQL over WebSocket requests using the over_websocket method.

1
2
3
4
5
6
7
8
9
import pytest


@pytest.mark.asyncio  # Requires the `pytest-asyncio` plugin
@pytest.mark.django_db(transaction=True)  # For sessions
async def test_graphql(graphql) -> None:
    query = "query { test }"
    async for response in graphql.over_websocket(query):
        assert response.data == {"test": "Hello, World!"}