Python library for creating, validating, and rendering web forms using Pydantic
Project description
codeforms
A Python library for dynamically creating, validating, and rendering web forms using Pydantic.
Installation
pip install codeforms
Or with uv:
uv add codeforms
Requires Python 3.9+.
Quick Start
Creating a Form
Everything starts with the Form class. A form is defined with a name and a list of fields.
from codeforms import Form, TextField, EmailField, NumberField
form = Form(
name="UserRegistration",
fields=[
TextField(name="full_name", label="Full Name", required=True),
EmailField(name="email", label="Email", required=True),
NumberField(name="age", label="Age"),
]
)
The Form Class
The Form class is the main container for your form structure.
id— Auto-generated UUID.name— Form name (used in HTML export and validation).fields— A list of field objects (e.g.TextField,EmailField).css_classes— Optional CSS classes for the<form>tag.version— Form version number.attributes— Dictionary of additional HTML attributes for the<form>tag.
Field Types
All fields inherit from FormFieldBase and share these common attributes:
name— Field name (maps tonamein HTML).label— User-visible label.field_type— Field type (FieldTypeenum).required— Whether the field is mandatory.placeholder— Placeholder text inside the field.default_value— Default value.help_text— Help text displayed below the field.css_classes— CSS classes for the field element.readonly— Whether the field is read-only.attributes— Additional HTML attributes for the<input>tag.
Available Fields
TextField— Generic text input (<input type="text">).minlength,maxlength: Min/max text length.pattern: Regex pattern for validation.
EmailField— Email address (<input type="email">).NumberField— Numeric value (<input type="number">).min_value,max_value: Allowed value range.step: Increment step.
DateField— Date picker (<input type="date">).min_date,max_date: Allowed date range.
SelectField— Dropdown select (<select>).options: List ofSelectOption(value="...", label="...").multiple: Enables multi-select.min_selected,max_selected: Selection count limits (multi-select only).
RadioField— Radio buttons (<input type="radio">).options: List ofSelectOption.inline: Display options inline.
CheckboxField— Single checkbox (<input type="checkbox">).CheckboxGroupField— Group of checkboxes.options: List ofSelectOption.inline: Display options inline.
FileField— File upload (<input type="file">).accept: Accepted file types (e.g."image/*,.pdf").multiple: Allow multiple file uploads.
HiddenField— Hidden field (<input type="hidden">).
Data Validation
codeforms offers multiple ways to validate user-submitted data, leveraging Pydantic's validation engine.
Recommended: FormDataValidator
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.
from codeforms import Form, FormDataValidator, TextField, SelectField, SelectOption
from pydantic import ValidationError
# 1. Define your form
form = Form(
name="MyForm",
fields=[
TextField(name="name", label="Name", required=True),
SelectField(
name="country",
label="Country",
options=[
SelectOption(value="us", label="United States"),
SelectOption(value="uk", label="United Kingdom"),
]
)
]
)
# 2. Create the validation model
ValidationModel = FormDataValidator.create_model(form)
# 3. Validate incoming data
user_data = {"name": "John", "country": "us"}
try:
validated = ValidationModel.model_validate(user_data)
print("Valid!", validated)
except ValidationError as e:
print("Validation errors:", e.errors())
This approach integrates seamlessly with API backends like FastAPI or Flask, since it produces standard Pydantic models.
Other Validation Methods
Two simpler alternatives exist, though FormDataValidator is preferred:
form.validate_data(data)— Built-in method on theFormclass. Less flexible; doesn't produce Pydantic models.validate_form_data(form, data)— Standalone function with basic validation logic.
Exporting Forms
Once your form is defined, you can export it to different formats.
# Export to plain HTML
html_output = form.export('html', submit=True)
print(html_output['output'])
# Export to HTML with Bootstrap 5 classes
bootstrap_output = form.export('html_bootstrap5', submit=True)
print(bootstrap_output['output'])
# Export to JSON
json_output = form.to_json()
print(json_output)
# Export to a Python dictionary
dict_output = form.to_dict()
print(dict_output)
Supported Formats
| Format | Description |
|---|---|
html |
Semantic HTML |
html_bootstrap4 |
HTML with Bootstrap 4 classes |
html_bootstrap5 |
HTML with Bootstrap 5 classes |
json_schema |
JSON Schema (draft-07) |
json |
JSON representation of the form |
dict |
Python dictionary representation |
HTML export can also generate a <script> block for basic client-side validation.
JSON Schema Export
Generate a standard JSON Schema (draft-07) from any form. The resulting schema is compatible with tools like React JSON Schema Form, Angular Formly, and any JSON Schema validator.
import json
from codeforms import (
Form, TextField, EmailField, NumberField, SelectField, SelectOption,
CheckboxField, form_to_json_schema,
)
form = Form(
name="registration",
fields=[
TextField(name="name", label="Full Name", required=True, minlength=2, maxlength=100),
EmailField(name="email", label="Email", required=True),
NumberField(name="age", label="Age", min_value=18, max_value=120),
SelectField(
name="country",
label="Country",
required=True,
options=[
SelectOption(value="us", label="United States"),
SelectOption(value="uk", label="United Kingdom"),
],
),
CheckboxField(name="terms", label="Accept Terms", required=True),
],
)
# Option 1: Direct function call
schema = form_to_json_schema(form)
print(json.dumps(schema, indent=2))
# Option 2: Via form.export()
result = form.export("json_schema")
schema = result["output"]
Output:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"title": "registration",
"properties": {
"name": {
"type": "string",
"minLength": 2,
"maxLength": 100,
"title": "Full Name"
},
"email": {
"type": "string",
"format": "email",
"title": "Email"
},
"age": {
"type": "number",
"minimum": 18,
"maximum": 120,
"title": "Age"
},
"country": {
"type": "string",
"enum": ["us", "uk"],
"title": "Country"
},
"terms": {
"type": "boolean",
"title": "Accept Terms"
}
},
"required": ["name", "email", "country", "terms"],
"additionalProperties": false
}
Field Type Mapping
| codeforms Field | JSON Schema Type | Extra Keywords |
|---|---|---|
TextField |
string |
minLength, maxLength, pattern |
EmailField |
string (format: "email") |
— |
NumberField |
number |
minimum, maximum, multipleOf |
DateField |
string (format: "date") |
— |
SelectField |
string + enum |
— |
SelectField (multiple=True) |
array of enum strings |
minItems, maxItems, uniqueItems |
RadioField |
string + enum |
— |
CheckboxField |
boolean |
— |
CheckboxGroupField |
array of enum strings |
uniqueItems |
FileField |
string (contentEncoding: "base64") |
— |
FileField (multiple=True) |
array of base64 strings |
— |
HiddenField |
string |
— |
UrlField |
string (format: "uri") |
minLength, maxLength |
TextareaField |
string |
minLength, maxLength |
ListField |
array |
minItems, maxItems |
Field annotations like label, help_text, default_value, and readonly map to the JSON Schema keywords title, description, default, and readOnly respectively.
Fields inside FieldGroup and FormStep containers are flattened into the top-level properties automatically.
Internationalization (i18n)
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().
Switching Locales
from codeforms import set_locale, get_locale, get_available_locales
print(get_locale()) # "en"
print(get_available_locales()) # ["en", "es"]
set_locale("es")
# All validation messages will now be in Spanish
Registering a Custom Locale
You can add any locale at runtime. Missing keys automatically fall back to English.
from codeforms import register_locale, set_locale
register_locale("pt", {
"field.required": "Este campo é obrigatório",
"field.required_named": "O campo {name} é obrigatório",
"email.invalid": "E-mail inválido",
"number.min_value": "O valor deve ser maior ou igual a {min}",
"form.validation_success": "Dados validados com sucesso",
"form.data_validation_error": "Erro na validação dos dados",
})
set_locale("pt")
Using the Translation Function
The t() function translates a message key, with optional interpolation:
from codeforms import t, set_locale
set_locale("en")
print(t("field.required")) # "This field is required"
print(t("field.required_named", name="email")) # "The field email is required"
set_locale("es")
print(t("field.required")) # "Este campo es requerido"
print(t("text.minlength", min=3)) # "La longitud mínima es 3"
Locale-Aware Validation
All validation functions respect the active locale:
from codeforms import Form, TextField, validate_form_data, set_locale
form = Form(
name="example",
fields=[TextField(name="name", label="Name", required=True)]
)
set_locale("en")
result = validate_form_data(form, {})
print(result["errors"][0]["message"]) # "The field name is required"
set_locale("es")
result = validate_form_data(form, {})
print(result["errors"][0]["message"]) # "El campo name es requerido"
See examples/i18n_usage.py for a full working example.
Dynamic Forms
Conditional Visibility
Fields can be shown or hidden based on the value of other fields using visible_when. This is metadata that your frontend can use for dynamic UI, and the backend can respect during validation.
from codeforms import Form, TextField, SelectField, SelectOption, VisibilityRule
form = Form(
name="address",
fields=[
SelectField(
name="country",
label="Country",
required=True,
options=[
SelectOption(value="US", label="United States"),
SelectOption(value="AR", label="Argentina"),
],
),
TextField(
name="state",
label="State",
required=True,
visible_when=[
VisibilityRule(field="country", operator="equals", value="US"),
],
),
TextField(
name="province",
label="Province",
required=True,
visible_when=[
VisibilityRule(field="country", operator="equals", value="AR"),
],
),
],
)
Supported operators: equals, not_equals, in, not_in, gt, lt, is_empty, is_not_empty.
Dynamic Validation
Use validate_form_data_dynamic() to validate only the fields that are currently visible:
from codeforms import validate_form_data_dynamic
result = validate_form_data_dynamic(
form,
{"country": "US", "state": "California"},
respect_visibility=True,
)
print(result["success"]) # True — "province" is hidden, so not required
The legacy validate_form_data() function is unchanged and always validates all fields regardless of visibility.
Checking Visible Fields
visible = form.get_visible_fields({"country": "US"})
print([f.name for f in visible]) # ["country", "state"]
See examples/conditional_visibility.py for a full working example.
Dependent Options
Use DependentOptionsConfig to define option sets that change based on another field's value:
from codeforms import SelectField, SelectOption, DependentOptionsConfig
city_field = SelectField(
name="city",
label="City",
options=[ # all possible options (for static HTML rendering)
SelectOption(value="nyc", label="New York City"),
SelectOption(value="bsas", label="Buenos Aires"),
],
dependent_options=DependentOptionsConfig(
depends_on="country",
options_map={
"US": [SelectOption(value="nyc", label="New York City")],
"AR": [SelectOption(value="bsas", label="Buenos Aires")],
},
),
)
The dependent_options metadata serializes to JSON for your frontend to consume. See examples/dependent_options.py.
Multi-Step Wizard Forms
Use FormStep to split a form into multiple steps. Each step contains its own fields and can be validated independently.
from codeforms import Form, FormStep, TextField, EmailField, CheckboxField
form = Form(
name="registration",
content=[
FormStep(
title="Personal Information",
description="Tell us about yourself",
content=[
TextField(name="name", label="Name", required=True),
EmailField(name="email", label="Email", required=True),
],
),
FormStep(
title="Confirmation",
content=[
CheckboxField(name="terms", label="I accept the terms", required=True),
],
validation_mode="on_submit",
),
],
)
Step Validation
# Validate a single step
result = form.validate_step(0, {"name": "John", "email": "john@example.com"})
print(result["success"]) # True
# Validate all steps at once
result = form.validate_all_steps({
"name": "John",
"email": "john@example.com",
"terms": True,
})
print(result["success"]) # True
Wizard Helpers
steps = form.get_steps() # List[FormStep]
fields = form.fields # Flat list of all fields across all steps
HTML Export
Wizard forms export with data-wizard="true" on the <form> tag. Each step renders as a <section data-step="true"> (not <fieldset>), so you can wire up your own step navigation in JavaScript.
See examples/wizard_form.py for a full working example.
Custom Field Types
You can create your own field types by subclassing FormFieldBase and registering them with register_field_type(). Custom fields integrate seamlessly with forms, JSON serialization, validation, and HTML export.
Defining a Custom Field
from codeforms import FormFieldBase, register_field_type
class PhoneField(FormFieldBase):
field_type: str = "phone" # unique string identifier
country_code: str = "+1"
class RatingField(FormFieldBase):
field_type: str = "rating"
min_rating: int = 1
max_rating: int = 5
register_field_type(PhoneField)
register_field_type(RatingField)
Using Custom Fields in Forms
from codeforms import Form, TextField
form = Form(
name="feedback",
fields=[
TextField(name="name", label="Name", required=True),
PhoneField(name="phone", label="Phone", country_code="+54"),
RatingField(name="score", label="Score", max_rating=10),
],
)
JSON Roundtrip
Custom fields serialize and deserialize automatically (as long as the field type is registered before deserialization):
import json
json_str = form.to_json()
restored = Form.loads(json_str)
assert isinstance(restored.fields[1], PhoneField)
assert restored.fields[1].country_code == "+54"
Listing Registered Types
from codeforms import get_registered_field_types
for name, classes in sorted(get_registered_field_types().items()):
print(f"{name}: {[c.__name__ for c in classes]}")
See examples/custom_fields.py for a full working example.
License
MIT
Project details
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
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file codeforms-0.2.1.tar.gz.
File metadata
- Download URL: codeforms-0.2.1.tar.gz
- Upload date:
- Size: 62.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.25
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
539a8319b92179efc9262e5c12e607f5db03700ef41608bc6ab178e3700fd6cb
|
|
| MD5 |
6e5b6266710be12f0b7311e7b2437cec
|
|
| BLAKE2b-256 |
16206c6e4a54efa8c9e044e774e19a1335bbe352bbfc9ca6d0cbd4d8192dff76
|
File details
Details for the file codeforms-0.2.1-py3-none-any.whl.
File metadata
- Download URL: codeforms-0.2.1-py3-none-any.whl
- Upload date:
- Size: 27.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.25
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dfc8b3b22e1a14da74becc5946a047ea662162d16ba32f735c6c7ae505ac6506
|
|
| MD5 |
fc6badb0cd89ede8556cf6a2ca26de27
|
|
| BLAKE2b-256 |
91961b058c3d47e4594b6a3cf6ed84c50e6982f5811b0d2b4a1d6009c871c6b2
|