Faster API development with django-dataclasses
Our new django-dataclasses library allows you to:
- Define your API schema using Python dataclasses
- Use the dataclasses as request and response bodies in Django views
- Generate a JavaScript API client from an OpenAPI export
Why did we build this if there are many great libraries for building APIs in Python already?
Why not…
Use FastAPI?
I like the Django ORM and the overall quality of the Django documentation. FastAPI does not recommend a particular ORM, but I think it is often used with SQLAlchemy which in my opinion is more complicated to use, has less usable documentation, and requires the use of a separate tool (Alembic) to manage database migrations.
Use pydantic?
pydantic does almost exactly the same thing as dataclasses-jsonschema, and when used with FastAPI it is a similar solution to Django + django-dataclasses. However, pydantic does not do schema validation; if you pass it a float it will silently converted to an integer as documented here.
Use Django REST framework?
Django REST framework (DRF) is powerful but complicated if you use the the class-based views and override methods like get_object or filter_query. The REST paradigm adds complexity such as serializers which accept different fields on GET versus POST.
Use gRPC?
I prefer gRPC for API development and I have used it with the Django ORM, but some projects require JSON serialization. The HTTP/2 streaming features, compact protobuf serialization, and code generation features are great if you don’t need JSON.
What it is
django-dataclasses glues together the dataclasses-jsonschema library and Django, allowing you to use Python dataclasses to define the request and response schemas for a Django view. An example illustrates this best:
Most of the value enabled by django-dataclasses is implemented by the dataclass-jsonschema library, so kudos to Simon Knibbs for his work in that package.
The type annotations on the view function tell the django-dataclasses wrapper which schemas to use for the request and response.
The simplicity of procedure calls
The RPC (Remote Procedure Call) paradigm is based on actions as opposed to the object state used in REST (REpresentational State Transfer). The RPC approach is valuable it encourages developers to make API methods that do one thing well, whereas REST encourages all actions on an object to be grouped into a single method. For example, a hypothetical REST endpoint for a user object like /user/1
could support changing the username or the password, and the developer may expect API clients to do one or the other but not both. A bug of this type could be an issue with either approach, but RPC encourages these actions to be implemented as two separate methods.
RPC is also better suited to payloads which contain information about multiple related objects because there is no presumption that the payload represents a single object type. A strict REST approach would require decomposing this into separate endpoints, which is inefficient. Similarly, REST objects are often one-to-one with database models, which makes changing either difficult. RPC has no opinion on the contents of the payload, thereby encourages endpoints tailored to a specific client need and not mapped directly to a single database model. GraphQL is based on a similar learning, but does not try anticipate the client’s needs at all!
Sharing the API definition
Having written the API, why should clients redefine the request and response schemas and associated URLs? django-dataclasses provides an export of your schemas in the OpenAPI format using a management command:
./manage.py openapi_export > api.json
The export can be passed to a tool like openapi-typescript-codegen to generate a JavaScript client library that provides the TypeScript types and methods for calling the API:
To use the client in your code, you write something like this:
Conclusion
API development is constantly improving as new tools remove boilerplate code and support better type checking. I hope you find the django-dataclasses library useful, and contribute by sharing issues or creating merge requests. Thanks!