Skip to main content

django-apiary

Typed API views in Django.

API Views

Apiary's APIView extends Django's generic View, with a few main differences:

  1. All parameters must be type-annotated.
  2. They automatically return JSON objects unless you return your own HttpResponse.
  3. Parameter values are assigned from the URL path parameters if applicable, then from the querystring for GET requests, and either POST or a JSON body for non-GET requests.
class AuthAPI(APIView):
    class Response(TypedDict):
        authenticated: bool
        token: NotRequired[str]

    def get(self, request: HttpRequest) -> Response:
        return self.Response(authenticated=False)

    def post(self, request: HttpRequest, username: str, password: str) -> Response:
        return self.Response(authenticated=True, token="authtoken")

The post method of this view could be called using form- or JSON-encoded data:

% curl -d 'username=admin&password=letmein' http://localhost:8000/api/v1/auth/

{"authenticated":true,"username":"admin","token":"authtoken"}

% curl -H 'Content-Type: application/json' -d '{"username":"admin","password":"letmein"}' \
    http://localhost:8000/api/v1/auth/

{"authenticated":true,"username":"admin","token":"authtoken"}

Typed Headers

An APIView may define it's own inner Headers class as either a msgspec.Struct, dataclass, or TypedDict. Accessing self.headers will attempt to construct this type from request.headers, changing dashes to underscores in header names. Using msgspec.Struct allows you to specify the header name distinct from the attribute name in Headers.

class HeaderView(APIView):
    class Headers(msgspec.Struct):
        x_count: int
        enabled: bool = msgspec.field(name="X-Enabled")
        request_id: uuid.UUID = msgspec.field(
            name="X-Request-ID", default_factory=uuid.uuid4
        )
        x_optional: int | None = None

    # This ensures the type-checker knows about our Headers and not the default APIView.Headers
    headers: Headers

    def get(self, request: HttpRequest):
        return self.headers

Routing and middleware

Since APIView is a regular Django View subclass, you can use path or re_path like normal. But Apiary also comes with a lightweight middleware system that allows you to be a bit more declarative when defining your URL scheme. For instance:

from apiary import Route
from apiary.middleware import RequireAuth, Throttle

api_root = Route("api/v1/")(
    # Throttles requests for all children to 120 per minute.
    Throttle("120/min")(
        # Children may be API endpoints.
        Route("auth/", views.AuthAPI),
        # Or middleware. RequireAuth checks `request.user.is_authenticated`.
        RequireAuth(
            Route("issue/<int:issue_id>/", views.IssueAPI, name="api-v1-issue"),
            Route("search/", views.SearchAPI)(
                Route("<int:search_id>/", views.SavedSearchAPI),
            ),
        ),
        # RequireAuth can also take one or more permissions to check.
        RequireAuth("myapp.admin_perm")(
            Route("admin/", views.AdminAPI),
        ),
    ),
)

You can also define your own APIMiddleware (or Route, which is just a middleware that also yields URL patterns):

from apiary import exceptions
from .models import AccessToken


class AuthenticatedAPI(Route):
    def on_request(self, request: HttpRequest) -> HttpResponseBase | None:
        if not AccessToken.check_access(request):
            raise exceptions.AuthenticationFailed()

    def on_response(
        self, request: HttpRequest, response: HttpResponseBase
    ) -> HttpResponseBase | None:
        pass


api_root = AuthenticatedAPI("/api")(...)

RequireAuth

RequireAuth checks request.user.is_authenticated and raises AuthenticationFailed if the user is not authenticated. It may also accept a list of permissions to check; if the user does not have all the specified permissions, PermissionDenied is raised.

Throttle

By default, throttles with equivalent rate windows and bucket settings share usage per IP. Use scope to give identical policies independent quotas, for example Throttle("120/min", scope="search") and Throttle("120/min", scope="login"). Different policies always use separate cache entries, even with the same scope.

Regular (function) views

Apiary also provides an api_path function that can be dropped in for Django's path, and calls your function-based views with the same data sourcing and argument sematics as APIView:

def search_view(request: HttpRequest, query: str = "", limit: int | None = None): ...


from apiary import api_path

urlpatterns = [
    api_path("search/", search_view, name="search"),
]

OpenAPI

Apiary comes with an OpenAPIView that can render an OpenAPI specification for your API:

from apiary import OpenAPIView, api_endpoint

api_root = Route("api/v1/")(...)

urlpatterns = [
    api_root.to_urlpattern(),
    api_endpoint("openapi.json", OpenAPIView, route=api_root, title="Test API"),
]

Metadata

Release files for django-apiary 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-apiary 0.1.0
File Size Uploaded
django_apiary-0.1.0.tar.gz 11.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-apiary 0.1.0
File Interpreter ABI Platform
django_apiary-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 26.5 kB

Release files / django_apiary-0.1.0.tar.gz

Download URL django_apiary-0.1.0.tar.gz
Size 11.7 kB
Tags Source
SHA-256 checksum
How to use checksums
00b888a5097c1bd203078eb9eb96b735e3eb23a8a4c00205d5865db58b329081
BLAKE2b-256 checksum
How to use checksums
47762caff8a7d2dcda1b399b2ef938b28abac9434e1458d088eeb91371d250b9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.19 {"installer":{"name":"uv","version":"0.12.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / django_apiary-0.1.0-py3-none-any.whl

Download URL django_apiary-0.1.0-py3-none-any.whl
Size 14.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e727f82f5f76e642b93b8b55b9b918a76bb4b3b74d1fc12389effe64a0decbf7
BLAKE2b-256 checksum
How to use checksums
7235ae68394f3317f9be810968a7429e55ac957ae7721199dc6faa1122c5bb6f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.19 {"installer":{"name":"uv","version":"0.12.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page