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 theUIIncrementJSON. 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: theshowcaseview is byte-identical (344/344 lines). 20 golden-JSON tests pass.
What works
-
Forms —
@uiclasses; 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]+ mixinsSearchable/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_itemmethods + route navigation. -
Wizards —
Wizardbase,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; implementLinkSupplieron the view for programmatic links — it wins,Nonefalls 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 JavaLayoutInferencedecision table (same constants/thresholds): heavy unstructured editable forms fold optional fields into a collapsed "More options" accordion panel; heavy@read_onlyviews with ≥ 5 sections become an adaptableTabLayout(id_tabs,groupRelationship="alternative"); enums with ≤ 4 members render as radio buttons (UseRadioButtons()forces radio always); developer-declared tabs always carrygroupRelationship="alternative"and areadaptablewhen the class opted in. Explicit layout markers always win. -
UX-pattern components (
mateu_uidl.components, same wire JSON as the JavaMetricCardDto…GanttDto) —MetricCard(+MetricTrend),Scoreboard,DashboardPanel,DashboardLayout,FoldoutPanel/FoldoutLayout(overview slottedoverview, panel contents slottedpanel-N),HeroSection,EmptyState,Skeleton(+SkeletonVariant),Gantt/GanttTask, plus fluentTextandButton.PlanningBoard(tape chart:PlanningResourcerows × days,PlanningBlocks) also carries the OPERA Room Diary extras —attribute_columns- per-resource
attributes/icon, blockicon/summary(hover text), andresize_action_id/open_action_id(double click) /range_select_action_id(drag across empty cells) next tomove_action_id/select_action_id.Map/MapMarker(street map:position"lat, lon",zoom,markers; a marker click runsmarker_action_idwith_markerId).
- per-resource
-
Declarative archetypes — subclass
Dashboard/Foldout/ItemOverview/Welcomeand declare type-hinted fields holding components;Panel(title, subtitle, col_span, row_span, icon, open)inAnnotated[...]marks titled panels/tabs/tiles (the analogue of Java's@Panel). Or subclassComponentTreeSupplierand return any fluent component tree fromcomponent(). -
Validation —
Required(),Min/Max/Size/Pattern,@validation,ValidationSupplier→ the component's client-sidevalidations(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;RestSourceSupplierfor runtime proxy views) and business components (@business_component,components.yaml,ComponentRef). -
Field types — the app's vocabulary (
OrderStatus,Money…) declared once inspecs/ui/types.yaml(or aFieldTypeCatalogSupplier; the file wins by id) and referenced byfieldType:on a YAMLFormField/GridColumnorAnnotated[..., FieldType("X")]on a listing row field: the type supplies defaults (incl. statustoneson columns), the field's own win. -
Sample data — a source in
sources.yamlmay carrysample:(the response) orsampleFile:(JSON/YAML, relative tospecs/ui). It is used only in sample mode, an explicit opt-in: setMATEU_SOURCES_MOCK=true. Then the app metadata saysmockSources: 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 ownListingExporter), 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(anddisabled_unless) match the caller'sIdentity(roles, groups, scopes, permissions). Mateu does not authenticate: the default provider,framework_identity_provider(), takes the identity your app established for the request — anIdentityyour dependency or middleware put inrequest.state.mateu_identity, else Starlette'sAuthenticationMiddleware(request.user'sroles/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 Keycloakrealm_access/resource_accessroles +roles,groups,scope/scp,permissions). No identity → every gate denies. A customidentity_provider(parameterless, readingmateu_core.request_context.current_request()) replaces the default. - Secrets.
secrets_provider(key) -> str | Noneresolves${secret.KEY}; unset → the environment variableMATEU_SECRET_<KEY>(never an arbitrary one). Only the proxy channel (__restfetch__) ever sees them. - CORS (breaking).
add_mateuused to installallow_origins=["*"]by default. CORS is now off unlesscors_origins=[...]lists the allowed origins (["*"]still works if you mean it);cors=Truewithout 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-Idheader and in the ERROR log line with the traceback, loggermateu.errors).dev=True(orMATEU_DEV=true/MATEU_ERRORS_DETAILED=true) shows the exception instead; raisemateu_uidl.UserFacingException("…", title="…")(aliasUserFacingError) 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 inContextVars, never on the shared handler. - Audience is a projection, not security.
Audience()/@audienceread the client-controlledappState["audience"]; any caller can send any value. Use it to tailor a screen to a persona, andEyesOnly(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.0a413
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| mateu_ui-3.0.0a413.tar.gz | 279.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| mateu_ui-3.0.0a413-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 617.7 kB
Release files / mateu_ui-3.0.0a413.tar.gz
| Download URL | mateu_ui-3.0.0a413.tar.gz |
|---|---|
| Size | 279.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5eca336bcc64fabadaf5678d6737c579327be888602f1ac0b6104aa1eadb6e0d
|
|
BLAKE2b-256 checksum How to use checksums |
b0c76d0b67ff6cd117b0e729858386c648853ef4f098020a418a0556941d603c
|
| 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 logRelease files / mateu_ui-3.0.0a413-py3-none-any.whl
| Download URL | mateu_ui-3.0.0a413-py3-none-any.whl |
|---|---|
| Size | 338.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2d2315613b90b7415f1fa22f9541372a3bc5bda5623eded5476e92f30126fb3f
|
|
BLAKE2b-256 checksum How to use checksums |
a1b0700c5afc74db1717aa594b0d93be3237fe8694dff55b734f34bf6d4b1289
|
| 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