Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

Mateu server-side for Python

A Python implementation of Mateu's server side: annotate plain Python classes and serve the /mateu/v3/sync API, so the existing renderers (web, React Native, IntelliJ plugin) render a Python backend with zero client changes. See DESIGN.md for the plan and the C# port for the sibling implementation.

The leverage: the renderers are backend-agnostic — they POST /mateu/v3/sync/{route} and consume the UIIncrement JSON. So this is not a port of the ~865-file Java framework; it just emits the same JSON. Verified by diffing the live output against the C# reference: the showcase view is byte-identical (344/344 lines). 20 golden-JSON tests pass.

What works

  • Forms — @ui classes; public type-hinted fields become fields (type inferred: str, int→integer, float/Decimal→number, bool→boolean, date/datetime→date, Enum→dropdown); Required() → required (enforced server-side); Section("…") → cards; @button → action + toast.

  • CRUD — Crud[T]: searchable listing (table on desktop, cards on mobile) plus full detail / edit / new (routes /x, /x/{id}, /x/{id}/edit, /x/new), save with validation, delete, back-to-list navigation.

  • Capability listings — Listing[Row] + mixins Searchable/Filterable[F]/Navigable/ Editable/Creatable/Deletable: a listing that grows by declaring capabilities as base classes (mirror of the Java model; Crud[T] = all of them at once).

  • App shell — @app + a menu from @menu_item methods + route navigation.

  • Wizards — Wizard base, Step(n) fields, progress + Back/Next, state round-trip.

  • Decorations — @subtitle, @banner, HeaderBadge.

  • Tail features — Tab → tab strip (Tab("Name", open=True) makes that tab the one selected on first render instead of the first-declared one); field stereotypes (Multiline, Password, Money, PlainText, Stereotype); LinkTo(href, icon, title, target) → a nav-link icon on the field (href/title travel as raw ${...} templates, interpolated client-side; implement LinkSupplier on the view for programmatic links — it wins, None falls back to the marker); @kpi → KPI cards; @fab → floating action buttons; @shortcut; @compact; @confirm_on_navigation_if_dirty.

  • i18n / events / security — Translator; @emits / @subscribe_to; @secured.

  • Layout inference — @auto_layout (opt-in, @auto_layout(False) opts out) ports the Java LayoutInference decision table (same constants/thresholds): heavy unstructured editable forms fold optional fields into a collapsed "More options" accordion panel; heavy @read_only views with ≥ 5 sections become an adaptable TabLayout (id _tabs, groupRelationship="alternative"); enums with ≤ 4 members render as radio buttons (UseRadioButtons() forces radio always); developer-declared tabs always carry groupRelationship="alternative" and are adaptable when the class opted in. Explicit layout markers always win.

  • UX-pattern components (mateu_uidl.components, same wire JSON as the Java MetricCardDto…GanttDto) — MetricCard (+ MetricTrend), Scoreboard, DashboardPanel, DashboardLayout, FoldoutPanel/FoldoutLayout (overview slotted overview, panel contents slotted panel-N), HeroSection, EmptyState, Skeleton (+ SkeletonVariant), Gantt / GanttTask, plus fluent Text and Button. PlanningBoard (tape chart: PlanningResource rows × days, PlanningBlocks) also carries the OPERA Room Diary extras — attribute_columns

    • per-resource attributes/icon, block icon/summary (hover text), and resize_action_id / open_action_id (double click) / range_select_action_id (drag across empty cells) next to move_action_id/select_action_id. Map / MapMarker (street map: position "lat, lon", zoom, markers; a marker click runs marker_action_id with _markerId).
  • Declarative archetypes — subclass Dashboard / Foldout / ItemOverview / Welcome and declare type-hinted fields holding components; Panel(title, subtitle, col_span, row_span, icon, open) in Annotated[...] marks titled panels/tabs/tiles (the analogue of Java's @Panel). Or subclass ComponentTreeSupplier and return any fluent component tree from component().

  • Validation — Required(), Min/Max/Size/Pattern, @validation, ValidationSupplier → the component's client-side validations (Java's conditions and messages), re-checked on save.

  • Grid fields — a list[Row] field is a grid with a row editor (add / edit / save / prev / next / remove / move), InlineEditing() for in-place cells; wide fields span the row.

  • Catalogues — named REST sources (@rest_source, sources.yaml, source= on every surface; RestSourceSupplier for runtime proxy views) and business components (@business_component, components.yaml, ComponentRef).

  • Field types — the app's vocabulary (OrderStatus, Money…) declared once in specs/ui/types.yaml (or a FieldTypeCatalogSupplier; the file wins by id) and referenced by fieldType: on a YAML FormField/GridColumn or Annotated[..., FieldType("X")] on a listing row field: the type supplies defaults (incl. status tones on columns), the field's own win.

  • Sample data — a source in sources.yaml may carry sample: (the response) or sampleFile: (JSON/YAML, relative to specs/ui). It is used only in sample mode, an explicit opt-in: set MATEU_SOURCES_MOCK=true. Then the app metadata says mockSources: true (the browser answers its direct fetches from the samples), the samples travel in the catalogue, and the proxied __restfetch__ answers reads with the sample and writes with {} without calling the endpoint. Without it, samples never reach the wire and endpoints are called for real.

  • Component adapters (ComponentAdapter + AdaptedView), embedded islands (a field holding a routed view), layoutDelta: pages, group actions (@group_action), wizard completion actions, listing exports (CSV built in; Excel/PDF through your own ListingExporter), class/method-level @eyes_only.

The shared wire-conformance corpus is a hard gate here: see tests/test_wire_conformance.py.

Projects

Package Role
mateu_uidl Public API — decorators (@ui, @app, @button, …) + field markers + Message, Crud, Wizard, Translator. One module per concern (markers, security, suppliers, class_decorators, method_decorators, listing, views, archetypes, …), all re-exported from mateu_uidl
mateu_dtos The wire model in Pydantic — UIIncrement, Component + metadata (polymorphism on type); split by concern (app, layout, fields, components, envelope, …), re-exported from mateu_dtos
mateu_core The engine — MateuRegistry, ReflectionMapper (package mapper/: one mixin per concern — app, view, archetypes, components, crud, layout, fields…), SyncHandler (package sync_handler/: dispatch, wizard, listing, crud, search, proxy…)
mateu_fastapi add_mateu(app, …) — the POST /mateu/v3/sync/{route} endpoint
samples/demo A runnable FastAPI app
tests Golden-JSON tests asserting wire compatibility with the Java/C# backends

Install

pip install "mateu-ui[server]"          # the package + uvicorn
pip install "mateu-ui[all]"             # the FastAPI server extras

The distribution is mateu-ui (the PyPI name mateu belongs to an unrelated project); the import names are mateu_uidl, mateu_dtos, mateu_core and mateu_fastapi. Versions move in lockstep with the Java artifacts: the v3.0-alpha.N release publishes 3.0.0aN.

Run

cd backend/python
python3 -m venv .venv && . .venv/bin/activate
pip install -e ".[dev,all]"
uvicorn samples.demo.main:app --host 0.0.0.0 --port 8594   # serves the sync API
pytest                                                     # golden tests

Point any Mateu renderer at it — e.g. set the Compose app's mateu.baseUrl=http://localhost:8594. The server binds to 0.0.0.0, so the iOS simulator (localhost:8594) and Android emulator (10.0.2.2:8594) reach it too.

Serving: add_mateu

from fastapi import FastAPI
from mateu_fastapi import add_mateu
from mateu_core.identity import identity_from_claims

app = FastAPI()
add_mateu(
    app, views,
    cors_origins=["https://app.example.com"],            # CORS is OFF unless you list origins
    secrets_provider=lambda key: vault.read(key),        # ${secret.KEY} in proxied REST sources
)

@app.middleware("http")                                  # YOUR authentication, not Mateu's
async def authenticate(request, call_next):
    claims = verify_with_your_idp(request.headers.get("authorization"))   # e.g. PyJWT + JWKS
    if claims is not None:
        request.state.mateu_identity = identity_from_claims(claims)
    return await call_next(request)
  • Identity. EyesOnly / ReadOnlyUnless / DisabledUnless (and disabled_unless) match the caller's Identity(roles, groups, scopes, permissions). Mateu does not authenticate: the default provider, framework_identity_provider(), takes the identity your app established for the request — an Identity your dependency or middleware put in request.state.mateu_identity, else Starlette's AuthenticationMiddleware (request.user's roles/groups/permissions + request.auth.scopes). A Bearer token is never decoded: verify it in your app and hand Mateu the result (identity_from_claims(claims) maps Keycloak realm_access/resource_access roles + roles, groups, scope/scp, permissions). No identity → every gate denies. A custom identity_provider (parameterless, reading mateu_core.request_context.current_request()) replaces the default.
  • Secrets. secrets_provider(key) -> str | None resolves ${secret.KEY}; unset → the environment variable MATEU_SECRET_<KEY> (never an arbitrary one). Only the proxy channel (__restfetch__) ever sees them.
  • CORS (breaking). add_mateu used to install allow_origins=["*"] by default. CORS is now off unless cors_origins=[...] lists the allowed origins (["*"] still works if you mean it); cors=True without origins raises. A renderer served by the same app needs no CORS.
  • Errors. An unhandled exception answers an error toast, never a raw 500 — the same texts as Java and .NET: "Something went wrong" / "An unexpected error occurred. Reference: ", the reference being the request's correlation id (also in the X-Mateu-Correlation-Id header and in the ERROR log line with the traceback, logger mateu.errors). dev=True (or MATEU_DEV=true / MATEU_ERRORS_DETAILED=true) shows the exception instead; raise mateu_uidl.UserFacingException("…", title="…") (alias UserFacingError) for a message written for the user, always shown; a pydantic validation error shows its messages. A denied action still answers 403.
  • Concurrency. The handler runs in Starlette's threadpool, so a slow proxied upstream (proxy_timeout_seconds, default 30) never blocks the event loop; per-request state (the request, the matched route seed, the audience) lives in ContextVars, never on the shared handler.
  • Audience is a projection, not security. Audience() / @audience read the client-controlled appState["audience"]; any caller can send any value. Use it to tailor a screen to a persona, and EyesOnly (identity) for anything that must stay hidden.

Define views

from typing import Annotated
from mateu_uidl import ui, title, section, button, Required, Section, Message, app, menu_item, menu_group, MenuDisplay, Crud

@ui("person")
@title("Person")
class Person:
    name: Annotated[str | None, Required(), Section("Identity")] = None
    age: int = 0

    @button()
    def save(self) -> Message:
        return Message(f"Saved {self.name}")

@ui("reservations")
@title("Reservations")
class Reservations(Crud[Reservation]):
    def fetch(self, search): ...

@app("My Python Mateu app")
class DemoApp:
    @menu_item("Person")
    def person(self) -> Person: return Person()

# A card menu (like the product dropdowns of a docs site): @menu_group(display="cards") opens a
# folder as a panel of cards; @menu_item(description=, icon=, image=) / @menu_group on a nested
# folder ("Bookings/Reservations") give each card its look, and a nested folder's entries are its
# actions. Code-authored menus (MenuSupplier / AppSupplier) set MenuItem.display/description/icon/image.
@app("PMS")
@menu_group("Bookings", display=MenuDisplay.cards)
@menu_group("Bookings/Reservations", description="Search, create and modify",
            icon="vaadin:calendar", image="/img/res.png")
class PmsApp:
    @menu_item("Search", group="Bookings/Reservations")
    def search(self) -> Reservations: return Reservations()

    @menu_item("Room diary", group="Bookings", description="Rooms by day")
    def diary(self) -> Person: return Person()

Dashboards, foldouts and Gantt charts use the fluent components + archetypes:

from datetime import date
from mateu_uidl import ui, title, Dashboard, Panel
from mateu_uidl.components import MetricCard, MetricTrend, Gantt, GanttTask

@ui("dashboard")
@title("Sales dashboard")
class SalesDashboard(Dashboard):
    revenue: MetricCard = MetricCard(title="Revenue", value="1.2", unit="M€",
                                     trend=MetricTrend.up, action_id="openRevenue")
    incidents: MetricCard = MetricCard(title="Incidents", value="3", trend=MetricTrend.down)
    plan: Annotated[Gantt, Panel("Rollout plan", col_span=2)] = Gantt(tasks=(
        GanttTask(id="t1", title="Design", start=date(2026, 7, 1), end=date(2026, 7, 20),
                  progress=80.0),
    ))

    def open_revenue(self): ...   # runs when the Revenue tile is clicked

Note for Python 3.14+

Under PEP 649 annotations are evaluated lazily and are no longer eagerly stored in __dict__['__annotations__']. The reflection layer reads them via inspect.get_annotations(...), so the field markers work on 3.11 through 3.14+.

Metadata

Release files for mateu-ui 3.0.0a412

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mateu-ui 3.0.0a412
File Size Uploaded
mateu_ui-3.0.0a412.tar.gz 271.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for mateu-ui 3.0.0a412
File Interpreter ABI Platform
mateu_ui-3.0.0a412-py3-none-any.whl Python 3 none any Details

Total release size: 602.9 kB

Release files / mateu_ui-3.0.0a412.tar.gz

Download URL mateu_ui-3.0.0a412.tar.gz
Size 271.9 kB
Tags Source
SHA-256 checksum
How to use checksums
400edf400bd44283d0185c36cfa556507e5ef294fc72aabb7f1b2dbaf5231b14
BLAKE2b-256 checksum
How to use checksums
a8204c674a2e8c9dc0039e5a4b704808db2e1a667d9f9b032a5bbb84c3825fe7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release files / mateu_ui-3.0.0a412-py3-none-any.whl

Download URL mateu_ui-3.0.0a412-py3-none-any.whl
Size 331.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8de2c4696a6e5a8fbe5029a2794a89a921126cf03f2bbc01ffcee25826db0bc3
BLAKE2b-256 checksum
How to use checksums
6c928d7d402f74236ae7d7ad35fbdb9cb09f8e8ef80e81f57acd7a454f12ba14
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

3.0.0a412 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page