https://github.com/jpsca/fodantic
Pydantic-based HTTP forms
https://github.com/jpsca/fodantic
Last synced: 5 months ago
JSON representation
Pydantic-based HTTP forms
- Host: GitHub
- URL: https://github.com/jpsca/fodantic
- Owner: jpsca
- License: mit
- Created: 2024-03-28T21:51:21.000Z (about 2 years ago)
- Default Branch: main
- Last Pushed: 2025-06-02T21:56:30.000Z (about 1 year ago)
- Last Synced: 2025-11-17T01:19:29.169Z (7 months ago)
- Language: Python
- Size: 495 KB
- Stars: 17
- Watchers: 2
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: MIT-LICENSE
Awesome Lists containing this project
- awesome-pydantic - fodantic - Pydantic-based HTTP forms (Utilities)
README
# Fodantic

Pydantic-based HTTP forms.
[Pydantic](https://docs.pydantic.dev) is the most widely used data validation library for Python, but it's hard to use it with regular HTTP forms... until now.
**Fodantic** allow you to quickly wrap your Pydantic models and use them as forms: with support for multiple values, checkboxes, error handling, and integration with your favorite ORM.
## A simple example
```py
from fodantic import formable
from pydantic import BaseModel
@formable
class UserModel(BaseModel):
name: str
friends: list[int]
active: bool = True
# This is just an example. Here you would use the
# request POST data of your web framework instead.
# For example, for Flask: `request_data = request.form`
from multidict import MultiDict
request_data = MultiDict([
("name", "John Doe"),
("friends", "2"),
("friends", "3"),
])
# The magic
form = UserModel.as_form(request_data, object=None)
print(form)
#> UserModel.as_form(name='John Doe', friends=[2, 3], active=False)
print(form.fields["name"].value)
#> John Doe
print(form.fields["name"].error)
#> None
print(form.save()) # Can also update the `object` passed as an argument
#> {'name': 'John Doe', 'friends': [2, 3], 'active': False}
```
## Installation
pip install fodantic
### Requirements
- Python > 3.10
- Pydantic 2.*
## Form Fields Parsing with Nested Notation
Fodantic supports parsing nested form fields using bracket (`[]`) notation -- similar to how Ruby on Rails and PHP handle form data.
This allows you to easily create complex nested data structures from flat form submissions.
### Nested Object Notation
You can use brackets to define nested objects in your form fields:
```html
```
This will be parsed into a nested structure:
```python
{
"user": {
"name": "Alice",
"email": "alice@example.com",
"address": {
"city": "New York",
"zip": "10001"
}
}
}
```
### Array Notation
You can create arrays using numeric indexes or empty brackets:
#### Indexed Arrays
```html
```
This will be parsed into:
```python
{
"contacts": [
{"name": "John", "phone": "555-1234"},
{"name": "Jane", "phone": "555-5678"},
]
}
```
#### Array Append (Empty Brackets)
```html
```
This will be parsed into:
```python
{
"tags": ["important", "urgent", "follow-up"]
}
```
#### Mixed Structures
You can combine these notations to create complex data structures:
```html
```
This will be parsed into:
```python
{
"user": {
"name": "Bob",
"skills": ["Python", "JavaScript"],
"projects": [
{"name": "Project A", "status": "active"},
{"name": "Project B", "status": "pending"}
]
}
}
```
### Usage with Pydantic Models
This nested notation works seamlessly with Pydantic models, allowing you to map complex form structures to nested models:
```python
from fodantic import formable
from pydantic import BaseModel
from typing import List
class Address(BaseModel):
city: str
zip: str
class Project(BaseModel):
name: str
status: str
@formable
class UserModel(BaseModel):
name: str
skills: List[str] = []
address: Address
projects: List[Project] = []
# Your form data with nested structure
form = UserModel.as_form(request_data)
```
The parser handles all the complexity of transforming the flat form structure into the nested objects your models expect.
## Booleans fields
Boolean fields are treated special because of how browsers handle checkboxes:
- If not checked: the browser doesn't send the field at all, so the missing field will be interpreted as `False`.
- If checked: It sends the "value" attribute, but this is optional, so it could send an empty string instead. So any value other than None will be interpreted as `True`.