BaseRenderer#
BaseRenderer is the abstract base class that all TUI Forms renderer backends extend.
It lives in tui_forms.renderer.base.
The class provides the complete rendering pipeline—question ordering, condition evaluation, Jinja2 default rendering, and hidden-field resolution—while delegating all terminal I/O to five abstract methods that each concrete renderer must implement.
Class definition#
class BaseRenderer(ABC):
name: str = "base"
_user_provided: bool = True
Set name to a unique string identifier on your subclass.
This value is used when the renderer is discovered via available_renderers().
Set _user_provided = False on a subclass to prevent answers recorded by
_ask_questions from being added to form._user_answers.
Use this for non-interactive renderers where no real user input takes place.
noinput renderer sets this to False.
Constructor#
def __init__(self, frm: form.Form, config: dict[str, Any] | None = None) -> None
Parameter |
Type |
Description |
|---|---|---|
|
|
The parsed form to render. Produced by |
|
|
Optional Jinja2 environment configuration. See Jinja2 configuration below. |
You do not normally call this directly—use tui_forms.create_renderer() instead.
Public method#
render#
def render(self, initial_answers: dict[str, Any] | None = None) -> dict[str, Any]
Run the form and return the collected answers.
Parameter |
Type |
Description |
|---|---|---|
|
|
Optional pre-populated answers that take priority over schema defaults. Pass the dict exactly as returned by a previous |
Calls the abstract methods in order as the user progresses through the form. After all user-facing questions are answered, resolves hidden fields automatically.
When initial_answers is provided, it is seeded into form.answers before questions
are processed. If a question's key already has a value in form.answers (under
root_key if set) before the abstract method is called, the pipeline passes that
existing value as the default argument instead of evaluating the Jinja2
template. For interactive renderers, this means the pre-populated value appears as
the suggested default when the user is prompted.
Returns: A flat dict mapping each question key to its answer.
When root_key was set on the form, all answers are nested under that key.
Inspecting user-provided answers#
After render() returns, form.user_answers contains only the answers
that were actively provided by the user—either by accepting the suggested
default or by entering a new value.
Hidden computed fields and answers recorded by non-interactive renderers
(such as noinput renderer) are excluded.
from tui_forms import create_form, get_renderer
frm = create_form(schema)
renderer = get_renderer("stdlib")(frm)
answers = renderer.render()
# answers includes all fields (user-provided and computed)
# frm.user_answers includes only what the user actively answered
print(frm.user_answers)
form.user_answers returns a dict[str, Any].
When root_key was set on the form, the root_key nesting is resolved:
the returned dict uses plain field keys, not nested keys.
Abstract methods#
You must implement all five of the following methods in your subclass.
_ask_string#
@abstractmethod
def _ask_string(
self, question: BaseQuestion, default: Any, prefix: str
) -> str
Ask a free-text question.
Called for fields with type: string, type: integer, or type: number.
Parameter |
Type |
Description |
|---|---|---|
|
|
The question to ask. See BaseQuestion attributes. |
|
|
The resolved default value: a pre-populated answer if one was already recorded for this key, otherwise the Jinja2-rendered schema default, or |
|
|
Progress prefix (for example, |
Returns: The user's answer as a str.
_ask_boolean#
@abstractmethod
def _ask_boolean(
self, question: BaseQuestion, default: Any, prefix: str
) -> bool
Ask a yes/no question.
Called for fields with type: boolean.
Parameter |
Type |
Description |
|---|---|---|
|
|
The question to ask. |
|
|
The resolved default: a pre-populated answer if already recorded, otherwise |
|
|
Progress prefix. |
Returns: True or False.
_ask_choice#
@abstractmethod
def _ask_choice(
self, question: BaseQuestion, default: Any, prefix: str
) -> Any
Ask a single-choice question.
Called for fields that have oneOf or anyOf.
The available options are in question.options.
Parameter |
Type |
Description |
|---|---|---|
|
|
The question to ask. |
|
|
The resolved default: a pre-populated answer if already recorded, otherwise the |
|
|
Progress prefix. |
Returns: The const value of the selected option.
_ask_multiple#
@abstractmethod
def _ask_multiple(
self, question: BaseQuestion, default: Any, prefix: str
) -> list
Ask a multiple-choice question.
Called for fields with type: array and oneOf or anyOf on items.
The available options are in question.options.
Parameter |
Type |
Description |
|---|---|---|
|
|
The question to ask. |
|
|
The resolved default: a pre-populated answer if already recorded, otherwise the list of |
|
|
Progress prefix. |
Returns: A list of const values for the selected options.
_validation_error#
@abstractmethod
def _validation_error(self, question: BaseQuestion) -> None
Display an error message when the user's answer fails the field's validator. Called automatically by the rendering pipeline before re-prompting the question.
Parameter |
Type |
Description |
|---|---|---|
|
|
The question whose answer failed validation. |
Overridable method#
_format_prefix#
def _format_prefix(self, current: int, total: int) -> str
Return the progress prefix prepended to each question title.
The default implementation returns "[current/total] " (for example, "[3/9] ").
Override this method to change the format or return "" to suppress the prefix.
Parameter |
Type |
Description |
|---|---|---|
|
|
1-based index of the current question. |
|
|
Total number of user-facing questions. |
Returns: A str prefix, or "" to show no prefix.
BaseQuestion attributes#
The question argument passed to all abstract methods is a BaseQuestion instance.
The following attributes are available to every renderer.
Attribute |
Type |
Description |
|---|---|---|
|
|
The field key, used as the answer dict key. |
|
|
The field type (for example, |
|
|
The human-readable label to display as the prompt. |
|
|
Optional hint text shown below the title. May be an empty string. |
|
|
The raw default from the schema (before Jinja2 rendering and before pre-populated answer lookup). Use the resolved |
|
|
Available choices for |
|
|
A callable |
QuestionOption#
Each entry in question.options is a TypedDict with two keys:
Key |
Type |
Description |
|---|---|---|
|
|
The stored value returned when this option is selected. |
|
|
The human-readable label shown to the user. |
# Example: iterating over options in _ask_choice
for i, opt in enumerate(question.options or [], 1):
print(f" {i}. {opt['title']}") # display label
# opt["const"] is what you return when this option is chosen
Question class hierarchy#
The following class tree shows all question types.
The concrete question argument passed to each abstract method is always an instance of one of the leaf classes.
BaseQuestion
├── Question free-text string, integer, or number input
│ ├── QuestionBoolean yes/no input; default_value() returns bool
│ ├── QuestionChoice single-choice; default_value() normalises list → scalar
│ └── QuestionMultiple multiple-choice; default_value() always returns a list
└── QuestionHidden never shown to the user; resolved after all user-facing questions
├── QuestionConstant returns raw default unchanged—no Jinja2 rendering
└── QuestionComputed renders default as a Jinja2 template (str, list, or dict)
type: object questions are not a separate class—they use BaseQuestion with subquestions set.
The pipeline recurses into subquestions without calling any abstract method on the object itself.
Jinja2 configuration#
The optional config constructor argument lets you customise the Jinja2 environment used to render default templates.
Pass a dict with a jinja2_environment key whose value is forwarded as keyword arguments to jinja2.Environment.
from tui_forms.parser import jsonschema_to_form
from tui_forms.renderer.stdlib import StdlibRenderer
frm = jsonschema_to_form(schema)
renderer = StdlibRenderer(
frm,
config={
"jinja2_environment": {
"variable_start_string": "[[",
"variable_end_string": "]]",
}
},
)
answers = renderer.render()
The autoescape keyword is always forced to True and cannot be overridden through config.
config is only available when constructing a renderer directly.
tui_forms.create_renderer() does not currently expose this parameter.