Core Concepts#
YAFOWIL is built on a few core ideas:
Runtime rules over static configuration
Framework-agnostic design
Simple, Pythonic callables instead of class inheritance
No coupling to storage
Chains and trees as structures
Widget#
A YAFOWIL form is a tree of widgets. Everything called a “field” in other libraries is a widget. Widgets can have children - fieldsets and forms are also widgets.
Widget behavior is defined by blueprints and properties. The widget class is generic and never instantiated directly - use the factory instead.
Widgets provide a dict-like API for building compounds:
form = factory('form', name='myform', props={'action': '/submit'})
form['field1'] = factory('text')
form['fieldset'] = factory('fieldset')
form['fieldset']['nested'] = factory('text')
RuntimeData#
During a request-response cycle, widget state is held in RuntimeData. It collects values, request data, errors, and rendered HTML for each widget.
Key attributes:
value- The initial/persisted valueextracted- The value extracted from requesterrors- List of ExtractionErrorsrendered- The rendered HTML outputrequest- Reference to the request object
Access via path:
data.fetch('myform.fieldset.nested').extracted
Controller#
The Controller orchestrates form processing:
controller = Controller(form, request)
It immediately processes the form upon initialization:
controller.rendered- The rendered form HTMLcontroller.data- The RuntimeData tree
Factory#
The factory is a singleton registry that creates widgets from blueprints:
from yafowil.base import factory
widget = factory('text', props={'class': 'input-text'})
For root widgets, provide a name:
form = factory('form', name='myform', props={'action': '/submit'})
Blueprint Chains#
Blueprints can be chained using colon-separated names:
widget = factory('field:label:error:text')
This combines the rendering and extraction logic of all named blueprints.
To address properties for a specific blueprint, prefix with the blueprint name:
widget = factory('field:label:text', props={
'label': 'My Field',
'label.class': 'label-class', # Only affects 'label' blueprint
})
Macros#
Macros are predefined blueprint chains. Use the # prefix:
widget = factory('#field:text') # Expands 'field' macro
Custom Blueprints#
Inject custom behavior using the * prefix:
def my_validator(widget, data):
if data.extracted != 'expected':
raise ExtractionError('Invalid value')
return data.extracted
widget = factory('field:*validate:text', custom={
'validate': {'extractors': [my_validator]},
})
Chains#
Blueprints consist of five chains of callables:
- Extractors
Get, convert, and validate data from the request. Executed right-to-left during extraction. Raise
ExtractionErroron validation failure.- Edit Renderers
Generate HTML for edit mode. Executed left-to-right. Each receives the previous renderer’s output in
data.rendered.- Display Renderers
Generate HTML for display mode. Same execution as edit renderers.
- Preprocessors
Run once before extraction/rendering. Used for framework integration (request wrapping, i18n setup). Rarely needed in custom blueprints.
- Builders
Run once at widget creation time. Used to auto-populate compound widgets with children. Rarely needed in custom blueprints.
Validation#
Extraction and validation happen together. If validation fails, raise
ExtractionError:
from yafowil.base import ExtractionError
def validate_positive(widget, data):
if data.extracted < 0:
raise ExtractionError('Value must be positive')
return data.extracted
The abort parameter controls chain behavior:
raise ExtractionError('message', abort=True) # Stop extraction chain
raise ExtractionError('message', abort=False) # Continue chain
Rendering Modes#
Widgets support three modes via the mode property:
editDefault. Renders form inputs using edit_renderers.
displayRead-only display using display_renderers.
skipRenders empty string.
widget = factory('text', props={'mode': 'display'})
# Or dynamically
def get_mode(widget, data):
return 'display' if user.readonly else 'edit'
widget = factory('text', props={'mode': get_mode})