Skip to content

pyvelm.types

types

Static-typing helpers for manifest and view authoring.

These exist purely so IDE-side tools (Pylance/Pyright, mypy) can flag typos, missing required keys, and shape mismatches at edit time. They have no runtime effect — the loader still does duck-typed reads of each module's globals. Apps that don't use a type checker can ignore this module entirely.

Recommended usage in a data file:

from pyvelm.types import ListView, FormView

VIEWS: list[View] = [
    ListView(
        name="partner.list",
        model="res.partner",
        view_type="list",
        arch={"fields": ["name", "code"]},
    ),
]

Or, for maximum ergonomics, use the builder helpers in pyvelm.builders:

from pyvelm.builders import list_view, form_view, section, notebook, page

VIEWS = [
    list_view("partner.list", "res.partner", fields=["name", "code"]),
    form_view("partner.form", "res.partner", sections=[
        section("identity", "Identity", ["name", "code"]),
        notebook("extra", pages=[
            page("tags", "Tags", ["tag_ids"]),
        ]),
    ]),
]

In a manifest, annotate each global individually:

from pyvelm.types import Manifest

NAME: str = "partners"
VERSION: tuple[int, ...] = (0, 2, 0)
DEPENDS: list[str] = ["base"]
DATA: list[str] = ["views/partner.py"]

(There is no module-level "Manifest" assignment because the loader reads individual attributes, not a single dict. The Manifest TypedDict below exists for the convenience of tools that want to validate a manifest as a whole — e.g. a future pyvelm lint.)

FieldRef

Bases: _FieldRefRequired

One field entry inside an arch list (list.fields, form.sections[*].fields, kanban.card.fields, kanban.card.badges).

Authoring sugar: a bare string "name" is equivalent to {"name": "name"}. The normalizer rewrites strings to dicts on storage so inheritance has stable addresses.

name is the only required key. Everything else is optional and surface only when the matching renderer / form-control honors it (widget → widget registry, required → red * + the server-side validator, etc.). App-specific attributes that aren't in this list are still accepted by the loader at runtime; the type checker just won't autocomplete them.

One2many on parent forms (see docs/one2many-forms.md):

  • widgetdialog (default) or inline / table; also the default when edit_toggle is true
  • edit_toggle — show Dialog / Inline grid switch (needs columns or list_view for both modes)
  • columns — inline sub-grid columns without a registered list view
  • list_view — name of a comodel list view (columns + sequence)
  • form_view — comodel form for row links and dialog create
Source code in pyvelm/types.py
class FieldRef(_FieldRefRequired, total=False):
    """One field entry inside an arch list (list.fields,
    form.sections[*].fields, kanban.card.fields, kanban.card.badges).

    Authoring sugar: a bare string ``"name"`` is equivalent to
    ``{"name": "name"}``. The normalizer rewrites strings to dicts on
    storage so inheritance has stable addresses.

    ``name`` is the only required key. Everything else is optional
    and surface only when the matching renderer / form-control honors
    it (``widget`` → widget registry, ``required`` → red ``*`` + the
    server-side validator, etc.). App-specific attributes that aren't
    in this list are still accepted by the loader at runtime; the
    type checker just won't autocomplete them.

  **One2many on parent forms** (see ``docs/one2many-forms.md``):

  - ``widget`` — ``dialog`` (default) or ``inline`` / ``table``; also the
    default when ``edit_toggle`` is true
  - ``edit_toggle`` — show **Dialog / Inline grid** switch (needs ``columns``
    or ``list_view`` for both modes)
  - ``columns`` — inline sub-grid columns without a registered list view
  - ``list_view`` — name of a comodel list view (columns + ``sequence``)
  - ``form_view`` — comodel form for row links and dialog create
    """

    edit_toggle: bool

    widget: WidgetHint
    label: str
    readonly: bool | object
    readonly_when: list
    required: bool | object
    required_when: list
    visible: bool | object
    visible_when: list
    hidden: bool | object
    visible_js: str
    live: bool | int | str
    depends_on: list[str]
    options_domain: list | object
    default: object
    # Form-grid only: how many columns of the surrounding section this
    # field's cell spans. ``"full"`` (or an integer >= the section's
    # ``cols``) makes the cell occupy the full row. Ignored on list /
    # kanban / graph / pivot.
    colspan: Union[int, Literal["full"]]
    # One2many / Many2many on parent forms: which comodel list view powers
    # the embedded table (default: lowest ``ir.ui.view`` id for that model).
    list_view: Union[str, tuple[str, str]]
    # Which comodel form view opens for row links / dialog create (default:
    # lowest-id form view on the comodel).
    form_view: Union[str, tuple[str, str]]
    # One2many only: table columns without registering a list view. Same
    # entries as a list arch ``fields`` list (strings or ``field(...)`` dicts).
    # Takes precedence over ``list_view`` for column layout.
    columns: list[FieldRefLike]

ArchList

Bases: _ArchListRequired

Arch for view_type="list" views.

Only fields is required. Optional keys:

  • title — human-readable heading shown above the table.
  • form_view"<name>" of a form view to link each row to.
  • detail_view — read-only detail view for row clicks (uses can_read).
  • record_href — URL pattern for row clicks; {id} is substituted.
  • create_href — URL for the list's New button (full navigation).
  • page_actions — toolbar buttons (same shape as form header_actions).
  • bulk_actions — bulk bar actions (default: delete when unlink allowed).
  • row_actions — per-row action buttons in the Actions column.
  • sequence — name of an integer field; when set the renderer adds a drag handle and forces sort by that field.
  • domain — static domain ANDed with toolbar search / filter chips (same semantics as graph/pivot views).
Source code in pyvelm/types.py
class ArchList(_ArchListRequired, total=False):
    """Arch for ``view_type="list"`` views.

    Only ``fields`` is required. Optional keys:

    - ``title``     — human-readable heading shown above the table.
    - ``form_view`` — ``"<name>"`` of a form view to link each row to.
    - ``detail_view`` — read-only detail view for row clicks (uses ``can_read``).
    - ``record_href`` — URL pattern for row clicks; ``{id}`` is substituted.
    - ``create_href`` — URL for the list's New button (full navigation).
    - ``page_actions`` — toolbar buttons (same shape as form ``header_actions``).
    - ``bulk_actions`` — bulk bar actions (default: delete when unlink allowed).
    - ``row_actions`` — per-row action buttons in the Actions column.
    - ``sequence``  — name of an integer field; when set the renderer
                      adds a drag handle and forces sort by that field.
    - ``domain``    — static domain ANDed with toolbar search / filter chips
                      (same semantics as graph/pivot views).
    """

    title: str
    form_view: str
    detail_view: str
    record_href: str
    create_href: str
    page_actions: list[ArchHeaderAction]
    bulk_actions: list[ArchListBulkAction]
    row_actions: list[ArchHeaderAction]
    sequence: str
    domain: list

ArchListBulkAction

Bases: TypedDict

One bulk action on a list view bulk bar.

Built-in action values: "unlink" (delete selected rows). Custom URL actions are not supported yet — use row_actions / page_actions for one-off endpoints.

Source code in pyvelm/types.py
class ArchListBulkAction(TypedDict, total=False):
    """One bulk action on a list view bulk bar.

    Built-in ``action`` values: ``"unlink"`` (delete selected rows).
    Custom URL actions are not supported yet — use ``row_actions`` /
    ``page_actions`` for one-off endpoints.
    """

    label: str
    action: str
    confirm: str
    perm: str

ArchSection

Bases: _ArchSectionRequired

One section of a form view.

name, title and fields are required. cols overrides the form-level column count for the fields rendered inside this section (default: the form's cols, which defaults to 2).

Source code in pyvelm/types.py
class ArchSection(_ArchSectionRequired, total=False):
    """One section of a form view.

    ``name``, ``title`` and ``fields`` are required. ``cols`` overrides
    the form-level column count for the fields rendered inside this
    section (default: the form's ``cols``, which defaults to 2).
    """

    cols: int

ArchPage

Bases: _ArchPageRequired

One tab page inside a form notebook.

Same grid rules as ArchSection; title is the tab label.

Source code in pyvelm/types.py
class ArchPage(_ArchPageRequired, total=False):
    """One tab page inside a form notebook.

    Same grid rules as ``ArchSection``; ``title`` is the tab label.
    """

    cols: int

ArchNotebook

Bases: _ArchNotebookRequired

Tabbed notebook block on a form (Odoo <notebook>).

Use pages instead of fields. Optional title renders as an outer fieldset legend above the tab strip.

Source code in pyvelm/types.py
class ArchNotebook(_ArchNotebookRequired, total=False):
    """Tabbed notebook block on a form (Odoo ``<notebook>``).

    Use ``pages`` instead of ``fields``. Optional ``title`` renders as an
    outer fieldset legend above the tab strip.
    """

    title: str

ArchHeaderAction

Bases: TypedDict

One button rendered in the form's display-mode action toolbar.

Keys:

  • label — button text (required).
  • url — endpoint to hit; {id} is substituted with the current record id at render time.
  • method — HTTP verb, default "POST".
  • confirm — optional confirmation prompt; when set, the button asks before firing.
  • perm — CRUD permission (read / write / create / unlink) required to see this action. When the current user lacks it, the button is hidden instead of rendered-then-denied. Omit for actions any reader may run.
  • model — model the perm check targets; defaults to the view's own model.
Source code in pyvelm/types.py
class ArchHeaderAction(TypedDict, total=False):
    """One button rendered in the form's display-mode action toolbar.

    Keys:

    - ``label``   — button text (required).
    - ``url``     — endpoint to hit; ``{id}`` is substituted with the
                    current record id at render time.
    - ``method``  — HTTP verb, default ``"POST"``.
    - ``confirm`` — optional confirmation prompt; when set, the button
                    asks before firing.
    - ``perm``    — CRUD permission (``read`` / ``write`` / ``create`` /
                    ``unlink``) required to *see* this action. When the
                    current user lacks it, the button is hidden instead
                    of rendered-then-denied. Omit for actions any reader
                    may run.
    - ``model``   — model the ``perm`` check targets; defaults to the
                    view's own model.
    """

    label: str
    url: str
    method: str
    confirm: str
    perm: str
    model: str
    policy: str

ArchForm

Bases: _ArchFormRequired

Arch for view_type="form" views.

Only sections is required. Each entry is either a flat section(...) (fields) or a notebook(...) (pages). Optional keys:

  • title — overrides the auto-generated page heading.
  • header_actions — list of buttons rendered next to Edit / Delete in display mode (e.g. "Run Now" on a cron form).
  • cols — number of columns each section uses (default 2). Each field(...) may set colspan to span multiple cells; a section or notebook page may override cols for its own block.
Source code in pyvelm/types.py
class ArchForm(_ArchFormRequired, total=False):
    """Arch for ``view_type="form"`` views.

    Only ``sections`` is required. Each entry is either a flat
    ``section(...)`` (``fields``) or a ``notebook(...)`` (``pages``).
    Optional keys:

    - ``title`` — overrides the auto-generated page heading.
    - ``header_actions`` — list of buttons rendered next to Edit /
      Delete in display mode (e.g. "Run Now" on a cron form).
    - ``cols`` — number of columns each section uses (default 2). Each
      ``field(...)`` may set ``colspan`` to span multiple cells; a
      section or notebook page may override ``cols`` for its own block.
    """

    title: str
    header_actions: list[ArchHeaderAction]
    cols: int

ArchDetail

Bases: _ArchFormRequired

Arch for view_type="detail" views — read-only record pages.

Same layout keys as :class:ArchForm. Optional form_view names the editable form opened by the Edit toolbar link.

Source code in pyvelm/types.py
class ArchDetail(_ArchFormRequired, total=False):
    """Arch for ``view_type="detail"`` views — read-only record pages.

    Same layout keys as :class:`ArchForm`. Optional ``form_view`` names
    the editable form opened by the **Edit** toolbar link.
    """

    title: str
    header_actions: list[ArchHeaderAction]
    cols: int
    form_view: str

ArchKanban

Bases: TypedDict

Arch for view_type="kanban" views. All keys are optional.

Source code in pyvelm/types.py
class ArchKanban(TypedDict, total=False):
    """Arch for ``view_type="kanban"`` views. All keys are optional."""

    title: str
    card: ArchKanbanCard
    group_by: str
    sequence: str
    form_view: str

ArchGraph

Bases: _ArchGraphRequired

Arch for view_type="graph" views.

Required keys are groupby and measure. Optional:

  • title — page heading (defaults to the model's plural form).
  • chart"bar" | "line" | "pie" (default "bar").
  • stacked — bar charts only: stack measures (no-op until we grow multi-measure bar support).
  • horizontal — bar charts only: render horizontally.
  • domain — extra static domain ANDed with the search filters.
Source code in pyvelm/types.py
class ArchGraph(_ArchGraphRequired, total=False):
    """Arch for ``view_type="graph"`` views.

    Required keys are ``groupby`` and ``measure``. Optional:

    - ``title``     — page heading (defaults to the model's plural form).
    - ``chart``     — ``"bar" | "line" | "pie"`` (default ``"bar"``).
    - ``stacked``   — bar charts only: stack measures (no-op until we
                      grow multi-measure bar support).
    - ``horizontal`` — bar charts only: render horizontally.
    - ``domain``    — extra static domain ANDed with the search filters.
    """

    title: str
    chart: Literal["bar", "line", "pie"]
    stacked: bool
    horizontal: bool
    domain: list

ArchPivot

Bases: _ArchPivotRequired

Arch for view_type="pivot" views.

Required: row_groupby, col_groupby, measures. Optional:

  • title — page heading.
  • domain — extra static domain ANDed with the search filters.
Source code in pyvelm/types.py
class ArchPivot(_ArchPivotRequired, total=False):
    """Arch for ``view_type="pivot"`` views.

    Required: ``row_groupby``, ``col_groupby``, ``measures``. Optional:

    - ``title``  — page heading.
    - ``domain`` — extra static domain ANDed with the search filters.
    """

    title: str
    domain: list

DashboardWidget

Bases: TypedDict

One tile on a view_type="dashboard" page.

Every widget carries type and id (a stable DOM key). Other keys depend on the type — see the builder helpers in pyvelm.builders.

Source code in pyvelm/types.py
class DashboardWidget(TypedDict, total=False):
    """One tile on a ``view_type="dashboard"`` page.

    Every widget carries ``type`` and ``id`` (a stable DOM key). Other
    keys depend on the type — see the builder helpers in
    ``pyvelm.builders``.
    """

    type: DashboardWidgetType
    id: str
    title: str
    colspan: DashboardColspan
    # chart
    model: str
    groupby: str
    measure: str
    chart: Literal["bar", "line", "pie"]
    domain: list
    view: ViewRef
    # table
    fields: list[FieldRefLike]
    columns: list[str]
    limit: int
    order: str
    more_href: str
    # stat
    href: str
    # link
    subtitle: str
    description: str
    url: str

ArchDashboard

Bases: _ArchDashboardRequired

Arch for view_type="dashboard" views.

Source code in pyvelm/types.py
class ArchDashboard(_ArchDashboardRequired, total=False):
    """Arch for ``view_type="dashboard"`` views."""

    title: str
    subtitle: str
    columns: int

ListView

Bases: _ListViewRequired

A view_type="list" view declaration.

Source code in pyvelm/types.py
class ListView(_ListViewRequired, total=False):
    """A ``view_type="list"`` view declaration."""

    priority: int

FormView

Bases: _FormViewRequired

A view_type="form" view declaration.

Source code in pyvelm/types.py
class FormView(_FormViewRequired, total=False):
    """A ``view_type="form"`` view declaration."""

    priority: int

DetailView

Bases: _DetailViewRequired

A view_type="detail" read-only record view declaration.

Source code in pyvelm/types.py
class DetailView(_DetailViewRequired, total=False):
    """A ``view_type="detail"`` read-only record view declaration."""

    priority: int

KanbanView

Bases: _KanbanViewRequired

A view_type="kanban" view declaration.

Source code in pyvelm/types.py
class KanbanView(_KanbanViewRequired, total=False):
    """A ``view_type="kanban"`` view declaration."""

    priority: int

GraphView

Bases: _GraphViewRequired

A view_type="graph" view declaration.

Source code in pyvelm/types.py
class GraphView(_GraphViewRequired, total=False):
    """A ``view_type="graph"`` view declaration."""

    priority: int

PivotView

Bases: _PivotViewRequired

A view_type="pivot" view declaration.

Source code in pyvelm/types.py
class PivotView(_PivotViewRequired, total=False):
    """A ``view_type="pivot"`` view declaration."""

    priority: int

DashboardView

Bases: _DashboardViewRequired

A view_type="dashboard" view declaration.

Source code in pyvelm/types.py
class DashboardView(_DashboardViewRequired, total=False):
    """A ``view_type="dashboard"`` view declaration."""

    priority: int

Operation

Bases: _OperationRequired

One inheritance operation against a parent view's arch.

target is a list of segments. Slice C of Stage 7 widens the accepted segment types: - str – dict-key or list-by-name lookup (shorthand for {"name": "<str>"} on list-of-dicts parents) - int – positional index on a list parent - dict – predicate; first list entry where every key/value in the dict matches the entry's attributes - "**" – wildcard prefix, only valid as the first segment; finds any descendant where the next segment would succeed and anchors the rest of the lookup there

value is required for every op except remove. The type checker can't easily express "required for op != remove" so value stays optional here; apply_operations raises at install time when it's missing.

Source code in pyvelm/types.py
class Operation(_OperationRequired, total=False):
    """One inheritance operation against a parent view's arch.

    ``target`` is a list of segments. Slice C of Stage 7 widens the
    accepted segment types:
      - ``str``  – dict-key or list-by-``name`` lookup (shorthand for
                   ``{"name": "<str>"}`` on list-of-dicts parents)
      - ``int``  – positional index on a list parent
      - ``dict`` – predicate; first list entry where every key/value
                   in the dict matches the entry's attributes
      - ``"**"`` – wildcard prefix, only valid as the first segment;
                   finds any descendant where the next segment would
                   succeed and anchors the rest of the lookup there

    ``value`` is required for every op except ``remove``. The type
    checker can't easily express "required for op != remove" so
    ``value`` stays optional here; ``apply_operations`` raises at
    install time when it's missing.
    """

    value: Any

ViewInherit

Bases: _ViewInheritRequired

An extension view that patches another via operations.

Source code in pyvelm/types.py
class ViewInherit(_ViewInheritRequired, total=False):
    """An extension view that patches another via ``operations``."""

    priority: int

Menu

Bases: _MenuRequired

One entry in a module's MENUS list.

Top-level groups have an icon (Heroicons name like "home" or legacy inline SVG) and no parent or href. Leaf items have a parent ("<module>.<group_name>", e.g. "partners.business" for group business in module partners) and an href (typically /web/views/<module>/<view>).

Prefer :class:~pyvelm.builders.Menus so parent and href are derived from short group names and view names. Nested trees use :class:~pyvelm.builders.MenuBranch (m.group(...).children([...])).

Source code in pyvelm/types.py
class Menu(_MenuRequired, total=False):
    """One entry in a module's ``MENUS`` list.

    Top-level groups have an ``icon`` (Heroicons name like ``"home"`` or
    legacy inline SVG) and no ``parent`` or
    ``href``. Leaf items have a ``parent`` (``"<module>.<group_name>"``,
    e.g. ``"partners.business"`` for group ``business`` in module
    ``partners``) and an ``href`` (typically ``/web/views/<module>/<view>``).

    Prefer :class:`~pyvelm.builders.Menus` so ``parent`` and ``href`` are
    derived from short group names and view names. Nested trees use
    :class:`~pyvelm.builders.MenuBranch` (``m.group(...).children([...])``).
    """

    icon: str
    href: str
    parent: str  # fully qualified: "<module>.<menu_name>"
    sequence: int
    access_model: str  # gate visibility on this model …
    access_perm: str   # … with this perm (default "read")
    access_policy: str  # optional policy method name for recordless gating

Manifest

Bases: _ManifestRequired

Shape of a __pyvelm__.py manifest's module-level globals.

The loader reads these as individual attributes, so a manifest declares them at module scope rather than building a dict named Manifest. This TypedDict exists for tooling that wants to validate a manifest as a single shape.

Only NAME and VERSION are strictly required by the loader; everything else (the install hook, model package, dependencies, catalog metadata) has sensible defaults.

Source code in pyvelm/types.py
class Manifest(_ManifestRequired, total=False):
    """Shape of a ``__pyvelm__.py`` manifest's module-level globals.

    The loader reads these as individual attributes, so a manifest
    declares them at module scope rather than building a dict named
    Manifest. This TypedDict exists for tooling that wants to
    validate a manifest as a single shape.

    Only ``NAME`` and ``VERSION`` are strictly required by the loader;
    everything else (the install hook, model package, dependencies,
    catalog metadata) has sensible defaults.
    """

    DEPENDS: list[str]
    DATA: list[str]
    MODELS_PACKAGE: str
    MIGRATIONS_PACKAGE: str
    INSTALL_HOOK: str            # dotted reference, e.g. "pkg.mod:fn"
    SYNC_HOOK: str               # runs on Apps Sync (installed, same version)
    WEB_ROUTES: str              # dotted ``register_routes(app)`` hook
    COMMANDS: list[str]         # dotted Command classes, e.g. "pkg.cmd:MyCommand"
    # Apps catalog metadata — purely informational, drives /web/apps.
    DISPLAY_NAME: str            # human label; NAME is the technical id
    SUMMARY: str
    DESCRIPTION: str
    CATEGORY: str
    AUTHOR: str
    ICON: str                    # raw inline SVG markup
    # Optional Apps catalog visibility gate (UI only):
    CATALOG_ACCESS_MODEL: str
    CATALOG_ACCESS_PERM: str
    CATALOG_ACCESS_POLICY: str