An open API service indexing awesome lists of open source software.

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

Awesome Lists containing this project

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`.