Skip to content

Commit bc9d34e

Browse files
author
Juan Pablo Manson
committed
Initial commit
1 parent 5095514 commit bc9d34e

File tree

17 files changed

+3032
-0
lines changed

17 files changed

+3032
-0
lines changed

.github/workflows/tests.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Tests
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout
15+
uses: actions/checkout@v4
16+
17+
- name: Set up Python
18+
uses: actions/setup-python@v5
19+
with:
20+
python-version: "3.12"
21+
22+
- name: Install dependencies
23+
run: |
24+
python -m pip install --upgrade pip
25+
pip install -e ".[dev]"
26+
27+
- name: Run test suite
28+
run: pytest -q

.gitignore

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Python-generated files
2+
*.py[oc]
3+
build/
4+
dist/
5+
wheels/
6+
*.egg-info
7+
8+
# Virtual environments
9+
.venv
10+
11+
todo
12+
13+
# Agents
14+
CLAUDE.md
15+
.claude
16+
17+
# Caches
18+
.uv-cache
19+
.pytest_cache
20+
__pycache__/

.python-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.12

README.md

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
# codeforms
2+
3+
A Python library for dynamically creating, validating, and rendering web forms using [Pydantic](https://docs.pydantic.dev/).
4+
5+
## Installation
6+
7+
```bash
8+
pip install codeforms
9+
```
10+
11+
Or with [uv](https://docs.astral.sh/uv/):
12+
13+
```bash
14+
uv add codeforms
15+
```
16+
17+
Requires Python 3.12+.
18+
19+
## Quick Start
20+
21+
### Creating a Form
22+
23+
Everything starts with the `Form` class. A form is defined with a name and a list of fields.
24+
25+
```python
26+
from codeforms import Form, TextField, EmailField, NumberField
27+
28+
form = Form(
29+
name="UserRegistration",
30+
fields=[
31+
TextField(name="full_name", label="Full Name", required=True),
32+
EmailField(name="email", label="Email", required=True),
33+
NumberField(name="age", label="Age"),
34+
]
35+
)
36+
```
37+
38+
### The `Form` Class
39+
40+
The `Form` class is the main container for your form structure.
41+
42+
- `id` — Auto-generated UUID.
43+
- `name` — Form name (used in HTML export and validation).
44+
- `fields` — A list of field objects (e.g. `TextField`, `EmailField`).
45+
- `css_classes` — Optional CSS classes for the `<form>` tag.
46+
- `version` — Form version number.
47+
- `attributes` — Dictionary of additional HTML attributes for the `<form>` tag.
48+
49+
## Field Types
50+
51+
All fields inherit from `FormFieldBase` and share these common attributes:
52+
53+
- `name` — Field name (maps to `name` in HTML).
54+
- `label` — User-visible label.
55+
- `field_type` — Field type (`FieldType` enum).
56+
- `required` — Whether the field is mandatory.
57+
- `placeholder` — Placeholder text inside the field.
58+
- `default_value` — Default value.
59+
- `help_text` — Help text displayed below the field.
60+
- `css_classes` — CSS classes for the field element.
61+
- `readonly` — Whether the field is read-only.
62+
- `attributes` — Additional HTML attributes for the `<input>` tag.
63+
64+
### Available Fields
65+
66+
- **`TextField`** — Generic text input (`<input type="text">`).
67+
- `minlength`, `maxlength`: Min/max text length.
68+
- `pattern`: Regex pattern for validation.
69+
- **`EmailField`** — Email address (`<input type="email">`).
70+
- **`NumberField`** — Numeric value (`<input type="number">`).
71+
- `min_value`, `max_value`: Allowed value range.
72+
- `step`: Increment step.
73+
- **`DateField`** — Date picker (`<input type="date">`).
74+
- `min_date`, `max_date`: Allowed date range.
75+
- **`SelectField`** — Dropdown select (`<select>`).
76+
- `options`: List of `SelectOption(value="...", label="...")`.
77+
- `multiple`: Enables multi-select.
78+
- `min_selected`, `max_selected`: Selection count limits (multi-select only).
79+
- **`RadioField`** — Radio buttons (`<input type="radio">`).
80+
- `options`: List of `SelectOption`.
81+
- `inline`: Display options inline.
82+
- **`CheckboxField`** — Single checkbox (`<input type="checkbox">`).
83+
- **`CheckboxGroupField`** — Group of checkboxes.
84+
- `options`: List of `SelectOption`.
85+
- `inline`: Display options inline.
86+
- **`FileField`** — File upload (`<input type="file">`).
87+
- `accept`: Accepted file types (e.g. `"image/*,.pdf"`).
88+
- `multiple`: Allow multiple file uploads.
89+
- **`HiddenField`** — Hidden field (`<input type="hidden">`).
90+
91+
## Data Validation
92+
93+
codeforms offers multiple ways to validate user-submitted data, leveraging Pydantic's validation engine.
94+
95+
### Recommended: `FormDataValidator`
96+
97+
The most robust approach is `FormDataValidator.create_model`, which dynamically generates a Pydantic model from your form definition. This gives you powerful validations and detailed error messages automatically.
98+
99+
```python
100+
from codeforms import Form, FormDataValidator, TextField, SelectField, SelectOption
101+
from pydantic import ValidationError
102+
103+
# 1. Define your form
104+
form = Form(
105+
name="MyForm",
106+
fields=[
107+
TextField(name="name", label="Name", required=True),
108+
SelectField(
109+
name="country",
110+
label="Country",
111+
options=[
112+
SelectOption(value="us", label="United States"),
113+
SelectOption(value="uk", label="United Kingdom"),
114+
]
115+
)
116+
]
117+
)
118+
119+
# 2. Create the validation model
120+
ValidationModel = FormDataValidator.create_model(form)
121+
122+
# 3. Validate incoming data
123+
user_data = {"name": "John", "country": "us"}
124+
125+
try:
126+
validated = ValidationModel.model_validate(user_data)
127+
print("Valid!", validated)
128+
except ValidationError as e:
129+
print("Validation errors:", e.errors())
130+
```
131+
132+
This approach integrates seamlessly with API backends like FastAPI or Flask, since it produces standard Pydantic models.
133+
134+
### Other Validation Methods
135+
136+
Two simpler alternatives exist, though `FormDataValidator` is preferred:
137+
138+
1. `form.validate_data(data)` — Built-in method on the `Form` class. Less flexible; doesn't produce Pydantic models.
139+
2. `validate_form_data(form, data)` — Standalone function with basic validation logic.
140+
141+
## Exporting Forms
142+
143+
Once your form is defined, you can export it to different formats.
144+
145+
```python
146+
# Export to plain HTML
147+
html_output = form.export('html', submit=True)
148+
print(html_output['output'])
149+
150+
# Export to HTML with Bootstrap 5 classes
151+
bootstrap_output = form.export('html_bootstrap5', submit=True)
152+
print(bootstrap_output['output'])
153+
154+
# Export to JSON
155+
json_output = form.to_json()
156+
print(json_output)
157+
158+
# Export to a Python dictionary
159+
dict_output = form.to_dict()
160+
print(dict_output)
161+
```
162+
163+
### Supported Formats
164+
165+
| Format | Description |
166+
|---|---|
167+
| `html` | Semantic HTML |
168+
| `html_bootstrap4` | HTML with Bootstrap 4 classes |
169+
| `html_bootstrap5` | HTML with Bootstrap 5 classes |
170+
| `json` | JSON representation of the form |
171+
| `dict` | Python dictionary representation |
172+
173+
HTML export can also generate a `<script>` block for basic client-side validation.
174+
175+
## Internationalization (i18n)
176+
177+
All validation and export messages are locale-aware. **English** (`en`) and **Spanish** (`es`) are included out of the box, and you can register any additional language at runtime via `register_locale()`.
178+
179+
### Switching Locales
180+
181+
```python
182+
from codeforms import set_locale, get_locale, get_available_locales
183+
184+
print(get_locale()) # "en"
185+
print(get_available_locales()) # ["en", "es"]
186+
187+
set_locale("es")
188+
# All validation messages will now be in Spanish
189+
```
190+
191+
### Registering a Custom Locale
192+
193+
You can add any locale at runtime. Missing keys automatically fall back to English.
194+
195+
```python
196+
from codeforms import register_locale, set_locale
197+
198+
register_locale("pt", {
199+
"field.required": "Este campo é obrigatório",
200+
"field.required_named": "O campo {name} é obrigatório",
201+
"email.invalid": "E-mail inválido",
202+
"number.min_value": "O valor deve ser maior ou igual a {min}",
203+
"form.validation_success": "Dados validados com sucesso",
204+
"form.data_validation_error": "Erro na validação dos dados",
205+
})
206+
207+
set_locale("pt")
208+
```
209+
210+
### Using the Translation Function
211+
212+
The `t()` function translates a message key, with optional interpolation:
213+
214+
```python
215+
from codeforms import t, set_locale
216+
217+
set_locale("en")
218+
print(t("field.required")) # "This field is required"
219+
print(t("field.required_named", name="email")) # "The field email is required"
220+
221+
set_locale("es")
222+
print(t("field.required")) # "Este campo es requerido"
223+
print(t("text.minlength", min=3)) # "La longitud mínima es 3"
224+
```
225+
226+
### Locale-Aware Validation
227+
228+
All validation functions respect the active locale:
229+
230+
```python
231+
from codeforms import Form, TextField, validate_form_data, set_locale
232+
233+
form = Form(
234+
name="example",
235+
fields=[TextField(name="name", label="Name", required=True)]
236+
)
237+
238+
set_locale("en")
239+
result = validate_form_data(form, {})
240+
print(result["errors"][0]["message"]) # "The field name is required"
241+
242+
set_locale("es")
243+
result = validate_form_data(form, {})
244+
print(result["errors"][0]["message"]) # "El campo name es requerido"
245+
```
246+
247+
See [`examples/i18n_usage.py`](examples/i18n_usage.py) for a full working example.
248+
249+
## License
250+
251+
MIT

0 commit comments

Comments
 (0)