Declarative Django request validation
Project description
Django API Forms
Django Forms approach in paring and validation of request payload (especially for content type like JSON or MessagePack) without HTML front-end.
Motivation
Main idea was to create a simple and declarative way to specify format of expecting request with ability to validate them. Firstly I tried to use Django Forms to validate my API request (I use pure Django in my APIs). I have encountered a problem with nesting my requests without huge boilerplate. Also, the whole HTML thing was pretty useless in my RESTful APIs.
I wanted something to:
- define my requests as object (
Form
) - pass the request to my defined object (
form = Form.create_from_request(request)
) - validate my request
form.is_valid()
- extract data
form.clean_data
property
I wanted to keep:
- friendly declarative Django syntax (DeclarativeFieldsMetaclass is beautiful)
- Django Validators
- ValidationError
So I decided to create simple Python package to cover all my expectations.
Installation
# Using pip
pip install django-api-forms
# Using poetry
peotry add django-api-forms
# Using pipenv
pipenv install django-api-forms
# Using setup.py
python setup.py install
Optional:
# msgpack support (for requests with Content-Type: application/x-msgpack)
peotry add msgpack
# ImageField support
peotry add Pillow
Example
Simple nested JSON request
{
"title": "Unknown Pleasures",
"type": "vinyl",
"artist": {
"name": "Joy Division",
"genres": [
"rock",
"punk"
],
"members": 4
},
"year": 1979,
"songs": [
{
"title": "Disorder",
"duration": "3:29"
},
{
"title": "Day of the Lords",
"duration": "4:48",
"metadata": {
"_section": {
"type": "ID3v2",
"offset": 0,
"byteLength": 2048
},
"header": {
"majorVersion": 3,
"minorRevision": 0,
"flagsOctet": 0,
"unsynchronisationFlag": false,
"extendedHeaderFlag": false,
"experimentalIndicatorFlag": false,
"size": 2038
}
}
}
],
"metadata": {
"created_at": "2019-10-21T18:57:03+0100",
"updated_at": "2019-10-21T18:57:03+0100"
}
}
Django API Forms equivalent + validation
from enum import Enum
from django.core.exceptions import ValidationError
from django.forms import fields
from django_api_forms import FieldList, FormField, FormFieldList, DictionaryField, EnumField, AnyField, Form
class AlbumType(Enum):
CD = 'cd'
VINYL = 'vinyl'
class ArtistForm(Form):
name = fields.CharField(required=True, max_length=100)
genres = FieldList(field=fields.CharField(max_length=30))
members = fields.IntegerField()
class SongForm(Form):
title = fields.CharField(required=True, max_length=100)
duration = fields.DurationField(required=False)
metadata = AnyField(required=False)
class AlbumForm(Form):
title = fields.CharField(max_length=100)
year = fields.IntegerField()
artist = FormField(form=ArtistForm)
songs = FormFieldList(form=SongForm)
type = EnumField(enum=AlbumType, required=True)
metadata = DictionaryField(fields.DateTimeField())
def clean_year(self):
if self.cleaned_data['year'] == 1992:
raise ValidationError("Year 1992 is forbidden!", 'forbidden-value')
return self.cleaned_data['year']
def clean(self):
if (self.cleaned_data['year'] == 1998) and (self.cleaned_data['artist']['name'] == "Nirvana"):
raise ValidationError("Sounds like a bullshit", code='time-traveling')
if not self._request.user.is_authenticated():
raise ValidationError("You can use request in form validation!")
return self.cleaned_data
"""
Django view example
"""
def create_album(request):
form = AlbumForm.create_from_request(request)
if not form.is_valid():
# Process your validation error
print(form.errors)
# Cleaned valid payload
payload = form.cleaned_data
print(payload)
If you want example with whole Django project, check out repository created by pawl django_api_forms_modelchoicefield_example, where he uses library with ModelChoiceField.
Running Tests
# install all dependencies
poetry install
# run the tests
poetry run pytest
Made with ❤️ and ☕️ by Jakub Dubec & BACKBONE s.r.o.
Changelog
0.15.0 : 23.08.2020
- Feature:
FileField
andImageField
introduced - Note: Defined extras in
setup.py
for optionalPillow
andmsgpack
dependencies - Feature: Working
Form::fill()
method for primitive data types. IntroducedIgnoreFillMixin
0.14.0 : 07.08.2020
- Feature:
BaseForm._request
property introduced (now it's possible to use request inclean_
methods)
0.13.0 : 09.07.2020
- Fix: Fixed
Content-Type
handling ifcharset
orboundary
is present
0.12.0 : 11.06.2020
- Fix: Do not call resolvers methods, if property is not required and not present in request
0.11.0 : 10.06.2020
- Change: Non specified non-required fields will no longer be available in the cleaned_data form attribute.
0.10.0 : 01.06.2020
- Change: All package exceptions inherits from
ApiFormException
. - Fix: Specifying encoding while opening files in
setup.py
(failing on Windows OS).
0.9.0 : 11.05.2020
- Change: Moved field error messages to default_error_messages for easier overriding and testing.
- Fix: Fix KeyError when invalid values are sent to FieldList.
- Fix: Removed unnecessary error checking in FieldList.
0.8.0 : 05.05.2020
- Maintenance: Add tests for fields
- Change: Remove DeclarativeFieldsMetaclass and import from Django instead.
- Change: Msgpack dependency is no longer required.
- Change: Empty values passed into a FormField now return {} rather than None.
- Fix: Throw a more user friendly error when passing non-Enums or invalid values to EnumField.
0.7.1 : 13.04.2020
- Change Use poetry instead of pipenv
- Change: Library renamed from
django_api_forms
todjango-api-forms
(cosmetic change without effect)
0.7.0 : 03.03.2020
- Change: Library renamed from
django_request_formatter
todjango_api_forms
- Change: Imports in main module
django_api_forms
0.6.0 : 18.02.2020
- Feature:
BooleanField
introduced
0.5.8 : 07.01.2020
- Fix: Pass
Invalid value
asValidationError
not as astring
0.5.7 : 07.01.2020
- Fix: Introduced generic
Invalid value
error message, if there isAttributeError
,TypeError
,ValueError
0.5.6 : 01.01.2020
- Fix: Fixing issue from version
0.5.5
but this time for real - Change: Renamed version file from
__version__.py
toversion.py
0.5.5 : 01.01.2020
- Fix: Check instance only if there is a value in
FieldList
andFormFieldList
0.5.4 : 24.12.2019
- Fix: Added missing
msgpack`` dependency to
setup.py`
0.5.3 : 20.12.2019
- Feature: Introduced generic
AnyField
0.5.2 : 19.12.2019
- Fix: Skip processing of the
FormField
if value is not required and empty
0.5.1 : 19.12.2019
- Fix: Process
EnumField
even if it's not marked as required
0.5.0 : 16.12.2019
- Change: Use native
django.form.fields
if possible - Change: Removed
kwargs
propagation from release0.3.0
- Change: Changed syntax back to
django.forms
compatible (e.g.form.validate_{key}()
->form.clean_{key}()
) - Change:
FieldList
raisesValidationError
instead ofRuntimeException
if there is a type in validation - Change: Use private properties for internal data in field objects
- Fixed:
FieldList
returns values instead ofNone
- Fix: Fixed validation in
DictionaryField
- Maintenance: Basic unit tests
0.4.3 : 29.11.2019
- Fix: Fixed
Form
has no attributeself._data
0.4.2 : 29.11.2019
- Fix: If payload is empty, create empty dictionary to avoid
NoneType
error
0.4.1 : 14.11.2019
- Feature: Introduced
UUIDField
0.4.0 : 13.11.2019
- Feature: Introduced
DictionaryField
0.3.0 : 11.11.2019
- Feature: Propagate
kwargs
fromForm.is_valid()
toForm.validate()
andForm.validate_{key}()
methods
0.2.1 : 4.11.2019
- Fix: Fixed
to_python()
in FormFieldList
0.2.0 : 31.10.2019
- Change:
Form.validate()
replaced byForm.is_valid()
- Feature:
Form.validate()
is now used as a last step of form validation and it's aimed to be overwritten if needed - Note: Unit tests initialization
0.1.6 : 24.10.2019
- Fix: Non-required EnumField is now working
- Feature: WIP: Initial method for filling objects
Form::fill()
0.1.5 : 23.10.2019
- Fix: Assign errors to form before raising
ValidationError
0.1.4 : 23.10.2019
- Fix: Do not return empty error records in
Form:errors
0.1.3 : 23.10.2019
- Fix: Use custom
DeclarativeFieldsMetaclass
because of customField
class - Fix: Do not return untouched fields in
Form::payload
- Fix: Fix for None
default_validators
inField
0.1.2 : 22:10.2019
- Feature: Support for
validation_{field}
methods inForm
(initial support)
0.1.1 : 22.10.2019
- Feature:
EnumField
0.1.0 : 22.10.2019
- Feature: First version of
Form
class - Feature:
CharField
- Feature:
IntegerField
- Feature:
FloatField
- Feature:
DecimalField
- Feature:
DateField
- Feature:
TimeField
- Feature:
DateTimeField
- Feature:
DurationField
- Feature:
RegexField
- Feature:
EmailField
- Feature:
BooleanField
- Feature:
RegexField
- Feature:
FieldList
- Feature:
FormField
- Feature:
FormFieldList
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for django_api_forms-0.15.0-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | ff38c00345342f1a6b92ce43175c3dfca0a2055cb71b90053152a0d526ee0920 |
|
MD5 | 8713e280372c821789d075816617f7a4 |
|
BLAKE2b-256 | 7140e031a0fa1d0f76bb8d08aadc538f665229f36378c84bbfaf0804db357ce3 |