mateu-ui 3.0.0a412__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. mateu_ui-3.0.0a412/.gitignore +6 -0
  2. mateu_ui-3.0.0a412/DESIGN.md +107 -0
  3. mateu_ui-3.0.0a412/PKG-INFO +278 -0
  4. mateu_ui-3.0.0a412/README.md +236 -0
  5. mateu_ui-3.0.0a412/mateu_core/__init__.py +24 -0
  6. mateu_ui-3.0.0a412/mateu_core/_version.py +8 -0
  7. mateu_ui-3.0.0a412/mateu_core/action_guard.py +273 -0
  8. mateu_ui-3.0.0a412/mateu_core/action_registry.py +319 -0
  9. mateu_ui-3.0.0a412/mateu_core/capabilities.py +49 -0
  10. mateu_ui-3.0.0a412/mateu_core/component_registry.py +123 -0
  11. mateu_ui-3.0.0a412/mateu_core/dev_specs.py +223 -0
  12. mateu_ui-3.0.0a412/mateu_core/documents.py +203 -0
  13. mateu_ui-3.0.0a412/mateu_core/environments.py +176 -0
  14. mateu_ui-3.0.0a412/mateu_core/error_boundary.py +77 -0
  15. mateu_ui-3.0.0a412/mateu_core/errors.py +40 -0
  16. mateu_ui-3.0.0a412/mateu_core/export.py +56 -0
  17. mateu_ui-3.0.0a412/mateu_core/field_type_registry.py +223 -0
  18. mateu_ui-3.0.0a412/mateu_core/identity.py +92 -0
  19. mateu_ui-3.0.0a412/mateu_core/importwizard.py +389 -0
  20. mateu_ui-3.0.0a412/mateu_core/inference_switches.py +36 -0
  21. mateu_ui-3.0.0a412/mateu_core/islands.py +93 -0
  22. mateu_ui-3.0.0a412/mateu_core/labels_aside_inference.py +73 -0
  23. mateu_ui-3.0.0a412/mateu_core/layout_delta.py +196 -0
  24. mateu_ui-3.0.0a412/mateu_core/layout_inference.py +77 -0
  25. mateu_ui-3.0.0a412/mateu_core/mapper/__init__.py +368 -0
  26. mateu_ui-3.0.0a412/mateu_core/mapper/_base.py +16 -0
  27. mateu_ui-3.0.0a412/mateu_core/mapper/_common.py +436 -0
  28. mateu_ui-3.0.0a412/mateu_core/mapper/app.py +566 -0
  29. mateu_ui-3.0.0a412/mateu_core/mapper/archetypes.py +233 -0
  30. mateu_ui-3.0.0a412/mateu_core/mapper/components.py +1086 -0
  31. mateu_ui-3.0.0a412/mateu_core/mapper/crud.py +655 -0
  32. mateu_ui-3.0.0a412/mateu_core/mapper/decorations.py +153 -0
  33. mateu_ui-3.0.0a412/mateu_core/mapper/fields.py +714 -0
  34. mateu_ui-3.0.0a412/mateu_core/mapper/layout.py +624 -0
  35. mateu_ui-3.0.0a412/mateu_core/mapper/view.py +270 -0
  36. mateu_ui-3.0.0a412/mateu_core/mapper/wizard.py +262 -0
  37. mateu_ui-3.0.0a412/mateu_core/mcp.py +318 -0
  38. mateu_ui-3.0.0a412/mateu_core/menu_route_conformance.py +55 -0
  39. mateu_ui-3.0.0a412/mateu_core/mobile_manifest.py +94 -0
  40. mateu_ui-3.0.0a412/mateu_core/naming.py +58 -0
  41. mateu_ui-3.0.0a412/mateu_core/page_inference.py +63 -0
  42. mateu_ui-3.0.0a412/mateu_core/page_type_inference.py +72 -0
  43. mateu_ui-3.0.0a412/mateu_core/partial_registry.py +80 -0
  44. mateu_ui-3.0.0a412/mateu_core/py.typed +0 -0
  45. mateu_ui-3.0.0a412/mateu_core/reflection.py +103 -0
  46. mateu_ui-3.0.0a412/mateu_core/registry.py +210 -0
  47. mateu_ui-3.0.0a412/mateu_core/request_context.py +81 -0
  48. mateu_ui-3.0.0a412/mateu_core/rest_source_registry.py +280 -0
  49. mateu_ui-3.0.0a412/mateu_core/route_registry.py +545 -0
  50. mateu_ui-3.0.0a412/mateu_core/sample_sources.py +27 -0
  51. mateu_ui-3.0.0a412/mateu_core/sync_handler/__init__.py +273 -0
  52. mateu_ui-3.0.0a412/mateu_core/sync_handler/_base.py +16 -0
  53. mateu_ui-3.0.0a412/mateu_core/sync_handler/_common.py +161 -0
  54. mateu_ui-3.0.0a412/mateu_core/sync_handler/actions.py +175 -0
  55. mateu_ui-3.0.0a412/mateu_core/sync_handler/app.py +144 -0
  56. mateu_ui-3.0.0a412/mateu_core/sync_handler/contract.py +76 -0
  57. mateu_ui-3.0.0a412/mateu_core/sync_handler/crud.py +547 -0
  58. mateu_ui-3.0.0a412/mateu_core/sync_handler/dispatch.py +362 -0
  59. mateu_ui-3.0.0a412/mateu_core/sync_handler/grid_field.py +307 -0
  60. mateu_ui-3.0.0a412/mateu_core/sync_handler/helpers.py +261 -0
  61. mateu_ui-3.0.0a412/mateu_core/sync_handler/listing.py +270 -0
  62. mateu_ui-3.0.0a412/mateu_core/sync_handler/proxy.py +164 -0
  63. mateu_ui-3.0.0a412/mateu_core/sync_handler/render.py +64 -0
  64. mateu_ui-3.0.0a412/mateu_core/sync_handler/search.py +572 -0
  65. mateu_ui-3.0.0a412/mateu_core/sync_handler/wizard.py +161 -0
  66. mateu_ui-3.0.0a412/mateu_core/translations.py +247 -0
  67. mateu_ui-3.0.0a412/mateu_core/url_template.py +82 -0
  68. mateu_ui-3.0.0a412/mateu_core/validation.py +158 -0
  69. mateu_ui-3.0.0a412/mateu_core/yaml_access.py +210 -0
  70. mateu_ui-3.0.0a412/mateu_core/yaml_preview.py +215 -0
  71. mateu_ui-3.0.0a412/mateu_core/yaml_spec_loader.py +225 -0
  72. mateu_ui-3.0.0a412/mateu_dtos/__init__.py +210 -0
  73. mateu_ui-3.0.0a412/mateu_dtos/app.py +158 -0
  74. mateu_ui-3.0.0a412/mateu_dtos/base.py +20 -0
  75. mateu_ui-3.0.0a412/mateu_dtos/basic.py +129 -0
  76. mateu_ui-3.0.0a412/mateu_dtos/components.py +270 -0
  77. mateu_ui-3.0.0a412/mateu_dtos/dashboard.py +212 -0
  78. mateu_ui-3.0.0a412/mateu_dtos/envelope.py +137 -0
  79. mateu_ui-3.0.0a412/mateu_dtos/fields.py +207 -0
  80. mateu_ui-3.0.0a412/mateu_dtos/front_office.py +390 -0
  81. mateu_ui-3.0.0a412/mateu_dtos/layout.py +135 -0
  82. mateu_ui-3.0.0a412/mateu_dtos/overlays.py +89 -0
  83. mateu_ui-3.0.0a412/mateu_dtos/py.typed +0 -0
  84. mateu_ui-3.0.0a412/mateu_dtos/records.py +236 -0
  85. mateu_ui-3.0.0a412/mateu_dtos/ux.py +328 -0
  86. mateu_ui-3.0.0a412/mateu_fastapi/__init__.py +401 -0
  87. mateu_ui-3.0.0a412/mateu_fastapi/py.typed +0 -0
  88. mateu_ui-3.0.0a412/mateu_uidl/__init__.py +370 -0
  89. mateu_ui-3.0.0a412/mateu_uidl/action_catalog.py +51 -0
  90. mateu_ui-3.0.0a412/mateu_uidl/adapters.py +64 -0
  91. mateu_ui-3.0.0a412/mateu_uidl/archetype_pages.py +600 -0
  92. mateu_ui-3.0.0a412/mateu_uidl/archetypes.py +282 -0
  93. mateu_ui-3.0.0a412/mateu_uidl/business_components.py +46 -0
  94. mateu_ui-3.0.0a412/mateu_uidl/class_decorators.py +673 -0
  95. mateu_ui-3.0.0a412/mateu_uidl/components.py +1843 -0
  96. mateu_ui-3.0.0a412/mateu_uidl/constraints.py +100 -0
  97. mateu_ui-3.0.0a412/mateu_uidl/documents.py +81 -0
  98. mateu_ui-3.0.0a412/mateu_uidl/environments.py +35 -0
  99. mateu_ui-3.0.0a412/mateu_uidl/export.py +130 -0
  100. mateu_ui-3.0.0a412/mateu_uidl/field_types.py +116 -0
  101. mateu_ui-3.0.0a412/mateu_uidl/flow.py +107 -0
  102. mateu_ui-3.0.0a412/mateu_uidl/i18n.py +29 -0
  103. mateu_ui-3.0.0a412/mateu_uidl/listing.py +337 -0
  104. mateu_ui-3.0.0a412/mateu_uidl/live_header_values.py +62 -0
  105. mateu_ui-3.0.0a412/mateu_uidl/markers.py +327 -0
  106. mateu_ui-3.0.0a412/mateu_uidl/messages.py +82 -0
  107. mateu_ui-3.0.0a412/mateu_uidl/method_decorators.py +231 -0
  108. mateu_ui-3.0.0a412/mateu_uidl/patterns.py +208 -0
  109. mateu_ui-3.0.0a412/mateu_uidl/placement.py +107 -0
  110. mateu_ui-3.0.0a412/mateu_uidl/py.typed +0 -0
  111. mateu_ui-3.0.0a412/mateu_uidl/rest_sources.py +207 -0
  112. mateu_ui-3.0.0a412/mateu_uidl/security.py +135 -0
  113. mateu_ui-3.0.0a412/mateu_uidl/suppliers.py +238 -0
  114. mateu_ui-3.0.0a412/mateu_uidl/views.py +131 -0
  115. mateu_ui-3.0.0a412/pyproject.toml +92 -0
@@ -0,0 +1,6 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ .pytest_cache/
5
+ .coverage
6
+ .coverage.*
@@ -0,0 +1,107 @@
1
+ # Mateu server-side for Python — design
2
+
3
+ > Status: **implemented & verified**. Forms + sections + field types + validation, `Crud[T]`
4
+ > (list / detail / edit / new / save / delete), the `@app` shell + menu navigation, wizards,
5
+ > page decorations, i18n, events, security scaffolding, the tail features (tabs, stereotypes,
6
+ > KPIs, FABs, shortcuts, compact, unsaved-changes guard), and the UX-pattern components
7
+ > (MetricCard/Scoreboard/DashboardPanel/DashboardLayout, FoldoutLayout, HeroSection, EmptyState,
8
+ > Skeleton, Gantt) with the Dashboard/Foldout/ItemOverview/Welcome declarative archetypes, and
9
+ > `@auto_layout` deterministic layout inference (fold-optionals, sections-as-tabs, small-enum
10
+ > radios — the port of the Java `LayoutInference` decision table; `TabLayout` metadata carries
11
+ > `groupRelationship`/`adaptable` on the wire like `TabLayoutDto`).
12
+ > The golden-JSON tests pass; the live `showcase` view is byte-identical to the C# reference.
13
+ > Since 2026-10-10 the port is a fully supported producer (PyPI `mateu-ui`): the shared wire-
14
+ > conformance corpus is a hard gate, and every server row of the parity matrix is ✅ (see
15
+ > `doc/src/content/docs/reference/parity.md`). The engine is split by concern: `mateu_core/mapper/`
16
+ > and `mateu_core/sync_handler/` are packages of mixins composed into `ReflectionMapper` /
17
+ > `SyncHandler`; `mateu_uidl` and `mateu_dtos` are modules re-exported from their package roots.
18
+
19
+ ## 1. Guiding principle: the renderers are backend-agnostic
20
+
21
+ Every Mateu renderer speaks one protocol:
22
+
23
+ ```
24
+ POST /{baseUrl}/mateu/v3/sync/{route}
25
+ request (RunActionRq): { route, consumedRoute, actionId, serverSideType,
26
+ initiatorComponentId, componentState, appState, parameters }
27
+ response (UIIncrement): { commands[], messages[], fragments[], banners[], appendBanners,
28
+ appData, appState }
29
+ ```
30
+
31
+ So the Python task is **not** "rewrite the framework" — it is **"emit the same JSON"**. We validate
32
+ by pointing a real renderer (or the C# reference server) at the Python server and diffing the JSON for
33
+ the same view.
34
+
35
+ ## 2. Stack (decided)
36
+
37
+ - **FastAPI** (ASP.NET-minimal-API analogue) for the single `sync` route family.
38
+ - **Pydantic v2** for the wire model, using **discriminated unions** (`Field(discriminator="type")`
39
+ + a `Literal["…"]` `type` field per model) for the `type` discriminators — the Python analogue of
40
+ Java's `@JsonTypeInfo` and C#'s `[JsonPolymorphic]`/`[JsonDerivedType]`. camelCase via
41
+ `alias_generator=to_camel`; nulls kept; dump with `by_alias=True`.
42
+ - **Reflection** over user classes: type hints for fields, `Annotated[...]` metadata for field
43
+ modifiers, decorators for class/method features. Read via `inspect.get_annotations` (PEP 649-safe).
44
+
45
+ ## 3. The Python idiom: `Annotated` + decorators
46
+
47
+ C# attributes don't exist on Python attributes, so:
48
+
49
+ - **Field modifiers** ride in `Annotated[T, Marker(...)]`: `Required()`, `Section("…")`, `Tab("…")`,
50
+ `Stereotype("…")`, `Multiline()`, `Password()`, `Money()`, `PlainText()`, `HeaderBadge(...)`,
51
+ `Step(n)`, `Label("…")`.
52
+ - **Class features** are decorators: `@ui`, `@title`, `@subtitle`, `@app`, `@compact`,
53
+ `@confirm_on_navigation_if_dirty`, `@plain_text`, `@emits`, `@subscribe_to`, `@secured`,
54
+ `@menu_group` (a menu folder's look: `display="cards"` opens it as a card panel — the analogue of
55
+ Java's `@Menu(display = cards)` on a group field).
56
+ - **Method features** are decorators: `@button`, `@menu_item`, `@kpi`, `@fab`, `@banner`,
57
+ `@shortcut`. Each stamps a `__mateu_*__` attribute the mapper reads. `@menu_item(group=,
58
+ description=, icon=, image=)` — a `/` in `group` nests folders, and description/icon/image are
59
+ the entry's card look (wire `MenuItem.description/icon/display/image`, mirroring
60
+ `MenuOptionDto`).
61
+
62
+ Validation reuses "a field has `Required()`" (the analogue of `@NotEmpty` / `[Required]`), enforced
63
+ server-side on CRUD save.
64
+
65
+ ## 4. Solution layout
66
+
67
+ ```
68
+ backend/python/
69
+ mateu_uidl/ ← public API: decorators + field markers + Message/Crud/Wizard/Translator
70
+ mateu_dtos/ ← the wire model (Pydantic): UIIncrement, Component + metadata subtypes
71
+ mateu_core/ ← the engine: naming, reflection, MateuRegistry, ReflectionMapper, SyncHandler
72
+ mateu_fastapi/ ← add_mateu(app, *sources): the POST /mateu/v3/sync/{route} endpoint
73
+ samples/demo/ ← a runnable FastAPI app (Person, Reservations, SignupWizard, Showcase, …)
74
+ tests/ ← golden-JSON tests (pytest) — same suite as the C# port
75
+ ```
76
+
77
+ ## 5. Mapping pipeline (mateu_core)
78
+
79
+ ```
80
+ HTTP sync ─▶ MateuRegistry.resolve(serverSideType|route) ─▶ instantiate the view type
81
+ ─▶ (action?) bind componentState → set attributes → invoke the action method
82
+ ─▶ ReflectionMapper.map_view(instance) ─▶ Component tree
83
+ ─▶ wrap in UIFragment(targetComponentId="ux_main"/"crud") ─▶ UIIncrement
84
+ ```
85
+
86
+ - **MateuRegistry** scans the given modules/classes once for `@app`/`@ui` and resolves a request by
87
+ `serverSideType` (a stable `module.qualname`), by route, or — for CRUD sub-routes — by the longest
88
+ route prefix.
89
+ - **ReflectionMapper** is the heart: class → App/Page shell; fields → FormField metadata (type
90
+ inferred from the hint, label from `Label`/humanized name, stereotypes, options from `Enum`);
91
+ methods → buttons / KPIs / FABs / banners; `Section`/`Tab` → cards / tab strips.
92
+ - **SyncHandler** dispatches: app shell, CRUD (list/detail/edit/new + search/create/save/delete),
93
+ wizards (back/next/finish), and plain views (render or run an action → `Message` → toast).
94
+
95
+ ## 6. Validation strategy
96
+
97
+ - **Golden-JSON tests** (`tests/`) — for each sample view, assert the serialized `UIIncrement` has
98
+ the expected wire shape (ignoring volatile bits like the random SSC id). Same assertions as the C#
99
+ suite.
100
+ - **Live diff** — run the demo on `:8594` and diff its JSON against the C# reference on `:8593` for
101
+ the same view. The `showcase` view (tabs + stereotypes + KPIs + FABs + shortcut + compact + dirty
102
+ guard) is byte-identical apart from the (demo-level) `serverSideType` name.
103
+
104
+ ## 7. Beyond the core
105
+
106
+ The remaining Java features (component adapters, federated microfrontends, framework adapters,
107
+ SSE/AI chat) follow the same pattern: add a metadata DTO, extend the mapper, add a golden test.
@@ -0,0 +1,278 @@
1
+ Metadata-Version: 2.5
2
+ Name: mateu-ui
3
+ Version: 3.0.0a412
4
+ Summary: Mateu server-side for Python — annotate Python classes; any Mateu renderer renders them.
5
+ Project-URL: Homepage, https://mateu.io
6
+ Project-URL: Documentation, https://mateu.io
7
+ Project-URL: Repository, https://github.com/miguelperezcolom/mateu
8
+ Project-URL: Issues, https://github.com/miguelperezcolom/mateu/issues
9
+ Project-URL: Source, https://github.com/miguelperezcolom/mateu/tree/master/backend/python
10
+ Author: Miguel Pérez Colom
11
+ License-Expression: Apache-2.0
12
+ Keywords: backend-driven-ui,crud,fastapi,forms,low-code,model-driven,ui
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Framework :: FastAPI
15
+ Classifier: Framework :: Pydantic :: 2
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
24
+ Classifier: Topic :: Software Development :: User Interfaces
25
+ Classifier: Typing :: Typed
26
+ Requires-Python: >=3.11
27
+ Requires-Dist: fastapi>=0.110
28
+ Requires-Dist: pydantic<3,>=2.6
29
+ Requires-Dist: pyyaml>=6
30
+ Provides-Extra: all
31
+ Requires-Dist: uvicorn>=0.27; extra == 'all'
32
+ Provides-Extra: dev
33
+ Requires-Dist: build>=1.2; extra == 'dev'
34
+ Requires-Dist: coverage>=7.2; extra == 'dev'
35
+ Requires-Dist: httpx>=0.27; extra == 'dev'
36
+ Requires-Dist: mypy>=1.10; extra == 'dev'
37
+ Requires-Dist: pytest>=8; extra == 'dev'
38
+ Requires-Dist: types-pyyaml; extra == 'dev'
39
+ Provides-Extra: server
40
+ Requires-Dist: uvicorn>=0.27; extra == 'server'
41
+ Description-Content-Type: text/markdown
42
+
43
+ # Mateu server-side for Python
44
+
45
+ A Python implementation of Mateu's **server side**: annotate plain Python classes and serve the
46
+ `/mateu/v3/sync` API, so the **existing renderers** (web, React Native, IntelliJ plugin) render a Python backend
47
+ with **zero client changes**. See [`DESIGN.md`](DESIGN.md) for the plan and the [C# port](../dotnet)
48
+ for the sibling implementation.
49
+
50
+ > **The leverage:** the renderers are backend-agnostic — they POST `/mateu/v3/sync/{route}` and
51
+ > consume the `UIIncrement` JSON. So this is **not** a port of the ~865-file Java framework; it just
52
+ > **emits the same JSON**. Verified by diffing the live output against the C# reference: the
53
+ > `showcase` view is byte-identical (344/344 lines). 20 golden-JSON tests pass.
54
+
55
+ ## What works
56
+
57
+ - **Forms** — `@ui` classes; public type-hinted fields become fields (type inferred: `str`,
58
+ `int`→integer, `float`/`Decimal`→number, `bool`→boolean, `date`/`datetime`→date, `Enum`→dropdown);
59
+ `Required()` → required (enforced server-side); `Section("…")` → cards; `@button` → action + toast.
60
+ - **CRUD** — `Crud[T]`: searchable listing (table on desktop, cards on mobile) **plus** full
61
+ detail / edit / new (routes `/x`, `/x/{id}`, `/x/{id}/edit`, `/x/new`), save with validation,
62
+ delete, back-to-list navigation.
63
+ - **Capability listings** — `Listing[Row]` + mixins `Searchable`/`Filterable[F]`/`Navigable`/
64
+ `Editable`/`Creatable`/`Deletable`: a listing that grows by declaring capabilities as base
65
+ classes (mirror of the Java model; `Crud[T]` = all of them at once).
66
+ - **App shell** — `@app` + a menu from `@menu_item` methods + route navigation.
67
+ - **Wizards** — `Wizard` base, `Step(n)` fields, progress + Back/Next, state round-trip.
68
+ - **Decorations** — `@subtitle`, `@banner`, `HeaderBadge`.
69
+ - **Tail features** — `Tab` → tab strip (`Tab("Name", open=True)` makes that tab the one selected on
70
+ first render instead of the first-declared one); field stereotypes (`Multiline`, `Password`, `Money`,
71
+ `PlainText`, `Stereotype`); `LinkTo(href, icon, title, target)` → a nav-link icon on the field
72
+ (href/title travel as raw `${...}` templates, interpolated client-side; implement `LinkSupplier`
73
+ on the view for programmatic links — it wins, `None` falls back to the marker); `@kpi` → KPI
74
+ cards; `@fab` → floating action buttons; `@shortcut`; `@compact`;
75
+ `@confirm_on_navigation_if_dirty`.
76
+ - **i18n / events / security** — `Translator`; `@emits` / `@subscribe_to`; `@secured`.
77
+ - **Layout inference** — `@auto_layout` (opt-in, `@auto_layout(False)` opts out) ports the Java
78
+ `LayoutInference` decision table (same constants/thresholds): heavy unstructured editable forms
79
+ fold optional fields into a collapsed "More options" accordion panel; heavy `@read_only` views
80
+ with ≥ 5 sections become an adaptable `TabLayout` (id `_tabs`, `groupRelationship="alternative"`);
81
+ enums with ≤ 4 members render as radio buttons (`UseRadioButtons()` forces radio always);
82
+ developer-declared tabs always carry `groupRelationship="alternative"` and are `adaptable` when
83
+ the class opted in. Explicit layout markers always win.
84
+ - **UX-pattern components** (`mateu_uidl.components`, same wire JSON as the Java
85
+ `MetricCardDto`…`GanttDto`) — `MetricCard` (+ `MetricTrend`), `Scoreboard`, `DashboardPanel`,
86
+ `DashboardLayout`, `FoldoutPanel`/`FoldoutLayout` (overview slotted `overview`, panel contents
87
+ slotted `panel-N`), `HeroSection`, `EmptyState`, `Skeleton` (+ `SkeletonVariant`), `Gantt` /
88
+ `GanttTask`, plus fluent `Text` and `Button`. `PlanningBoard` (tape chart: `PlanningResource`
89
+ rows × days, `PlanningBlock`s) also carries the OPERA Room Diary extras — `attribute_columns`
90
+ + per-resource `attributes`/`icon`, block `icon`/`summary` (hover text), and
91
+ `resize_action_id` / `open_action_id` (double click) / `range_select_action_id` (drag across
92
+ empty cells) next to `move_action_id`/`select_action_id`. `Map` / `MapMarker` (street map: `position`
93
+ "lat, lon", `zoom`, `markers`; a marker click runs `marker_action_id` with `_markerId`).
94
+ - **Declarative archetypes** — subclass `Dashboard` / `Foldout` / `ItemOverview` / `Welcome` and
95
+ declare type-hinted fields holding components; `Panel(title, subtitle, col_span, row_span, icon,
96
+ open)` in `Annotated[...]` marks titled panels/tabs/tiles (the analogue of Java's `@Panel`).
97
+ Or subclass `ComponentTreeSupplier` and return any fluent component tree from `component()`.
98
+
99
+ - **Validation** — `Required()`, `Min`/`Max`/`Size`/`Pattern`, `@validation`, `ValidationSupplier`
100
+ → the component's client-side `validations` (Java's conditions and messages), re-checked on save.
101
+ - **Grid fields** — a `list[Row]` field is a grid with a row editor (add / edit / save / prev /
102
+ next / remove / move), `InlineEditing()` for in-place cells; wide fields span the row.
103
+ - **Catalogues** — named REST sources (`@rest_source`, `sources.yaml`, `source=` on every surface;
104
+ `RestSourceSupplier` for runtime proxy views) and business components (`@business_component`,
105
+ `components.yaml`, `ComponentRef`).
106
+ - **Field types** — the app's vocabulary (`OrderStatus`, `Money`…) declared once in
107
+ `specs/ui/types.yaml` (or a `FieldTypeCatalogSupplier`; the file wins by id) and referenced by
108
+ `fieldType:` on a YAML `FormField`/`GridColumn` or `Annotated[..., FieldType("X")]` on a listing
109
+ row field: the type supplies defaults (incl. status `tones` on columns), the field's own win.
110
+ - **Sample data** — a source in `sources.yaml` may carry `sample:` (the response) or `sampleFile:`
111
+ (JSON/YAML, relative to `specs/ui`). It is used **only in sample mode**, an explicit opt-in:
112
+ set `MATEU_SOURCES_MOCK=true`. Then the app metadata says `mockSources: true` (the browser
113
+ answers its direct fetches from the samples), the samples travel in the catalogue, and the
114
+ proxied `__restfetch__` answers reads with the sample and writes with `{}` without calling the
115
+ endpoint. Without it, samples never reach the wire and endpoints are called for real.
116
+ - **Component adapters** (`ComponentAdapter` + `AdaptedView`), **embedded islands** (a field holding
117
+ a routed view), **`layoutDelta:`** pages, **group actions** (`@group_action`), **wizard
118
+ completion actions**, **listing exports** (CSV built in; Excel/PDF through your own `ListingExporter`), class/method-level **`@eyes_only`**.
119
+
120
+ The shared wire-conformance corpus is a hard gate here: see `tests/test_wire_conformance.py`.
121
+
122
+ ## Projects
123
+
124
+ | Package | Role |
125
+ |---|---|
126
+ | `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` |
127
+ | `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` |
128
+ | `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…) |
129
+ | `mateu_fastapi` | `add_mateu(app, …)` — the `POST /mateu/v3/sync/{route}` endpoint |
130
+ | `samples/demo` | A runnable FastAPI app |
131
+ | `tests` | Golden-JSON tests asserting wire compatibility with the Java/C# backends |
132
+
133
+ ## Install
134
+
135
+ ```bash
136
+ pip install "mateu-ui[server]" # the package + uvicorn
137
+ pip install "mateu-ui[all]" # the FastAPI server extras
138
+ ```
139
+
140
+ The distribution is **`mateu-ui`** (the PyPI name `mateu` belongs to an unrelated project); the
141
+ import names are `mateu_uidl`, `mateu_dtos`, `mateu_core` and `mateu_fastapi`. Versions move in
142
+ lockstep with the Java artifacts: the `v3.0-alpha.N` release publishes `3.0.0aN`.
143
+
144
+ ## Run
145
+
146
+ ```bash
147
+ cd backend/python
148
+ python3 -m venv .venv && . .venv/bin/activate
149
+ pip install -e ".[dev,all]"
150
+ uvicorn samples.demo.main:app --host 0.0.0.0 --port 8594 # serves the sync API
151
+ pytest # golden tests
152
+ ```
153
+
154
+ Point any Mateu renderer at it — e.g. set the Compose app's `mateu.baseUrl=http://localhost:8594`.
155
+ The server binds to `0.0.0.0`, so the iOS simulator (`localhost:8594`) and Android emulator
156
+ (`10.0.2.2:8594`) reach it too.
157
+
158
+ ## Serving: `add_mateu`
159
+
160
+ ```python
161
+ from fastapi import FastAPI
162
+ from mateu_fastapi import add_mateu
163
+ from mateu_core.identity import identity_from_claims
164
+
165
+ app = FastAPI()
166
+ add_mateu(
167
+ app, views,
168
+ cors_origins=["https://app.example.com"], # CORS is OFF unless you list origins
169
+ secrets_provider=lambda key: vault.read(key), # ${secret.KEY} in proxied REST sources
170
+ )
171
+
172
+ @app.middleware("http") # YOUR authentication, not Mateu's
173
+ async def authenticate(request, call_next):
174
+ claims = verify_with_your_idp(request.headers.get("authorization")) # e.g. PyJWT + JWKS
175
+ if claims is not None:
176
+ request.state.mateu_identity = identity_from_claims(claims)
177
+ return await call_next(request)
178
+ ```
179
+
180
+ - **Identity.** `EyesOnly` / `ReadOnlyUnless` / `DisabledUnless` (and `disabled_unless`) match the
181
+ caller's `Identity(roles, groups, scopes, permissions)`. **Mateu does not authenticate**: the
182
+ default provider, `framework_identity_provider()`, takes the identity your app established for
183
+ the request — an `Identity` your dependency or middleware put in `request.state.mateu_identity`,
184
+ else Starlette's `AuthenticationMiddleware` (`request.user`'s `roles`/`groups`/`permissions` +
185
+ `request.auth.scopes`). A Bearer token is never decoded: verify it in your app and hand Mateu the
186
+ result (`identity_from_claims(claims)` maps Keycloak `realm_access`/`resource_access` roles +
187
+ `roles`, `groups`, `scope`/`scp`, `permissions`). No identity → every gate denies. A custom
188
+ `identity_provider` (parameterless, reading `mateu_core.request_context.current_request()`)
189
+ replaces the default.
190
+ - **Secrets.** `secrets_provider(key) -> str | None` resolves `${secret.KEY}`; unset → the
191
+ environment variable `MATEU_SECRET_<KEY>` (never an arbitrary one). Only the proxy channel (`__restfetch__`) ever sees them.
192
+ - **CORS (breaking).** `add_mateu` used to install `allow_origins=["*"]` by default. CORS is now
193
+ off unless `cors_origins=[...]` lists the allowed origins (`["*"]` still works if you mean it);
194
+ `cors=True` without origins raises. A renderer served by the same app needs no CORS.
195
+ - **Errors.** An unhandled exception answers an error toast, never a raw 500 — the same texts as
196
+ Java and .NET: "Something went wrong" / "An unexpected error occurred. Reference: <id>", the
197
+ reference being the request's correlation id (also in the `X-Mateu-Correlation-Id` header and in
198
+ the ERROR log line with the traceback, logger `mateu.errors`). `dev=True` (or `MATEU_DEV=true` /
199
+ `MATEU_ERRORS_DETAILED=true`) shows the exception instead; raise
200
+ `mateu_uidl.UserFacingException("…", title="…")` (alias `UserFacingError`) for a message written
201
+ for the user, always shown; a pydantic validation error shows its messages. A denied action
202
+ still answers 403.
203
+ - **Concurrency.** The handler runs in Starlette's threadpool, so a slow proxied upstream
204
+ (`proxy_timeout_seconds`, default 30) never blocks the event loop; per-request state (the
205
+ request, the matched route seed, the audience) lives in `ContextVar`s, never on the shared
206
+ handler.
207
+ - **Audience is a projection, not security.** `Audience()` / `@audience` read the client-controlled
208
+ `appState["audience"]`; any caller can send any value. Use it to tailor a screen to a persona, and
209
+ `EyesOnly` (identity) for anything that must stay hidden.
210
+
211
+ ## Define views
212
+
213
+ ```python
214
+ from typing import Annotated
215
+ from mateu_uidl import ui, title, section, button, Required, Section, Message, app, menu_item, menu_group, MenuDisplay, Crud
216
+
217
+ @ui("person")
218
+ @title("Person")
219
+ class Person:
220
+ name: Annotated[str | None, Required(), Section("Identity")] = None
221
+ age: int = 0
222
+
223
+ @button()
224
+ def save(self) -> Message:
225
+ return Message(f"Saved {self.name}")
226
+
227
+ @ui("reservations")
228
+ @title("Reservations")
229
+ class Reservations(Crud[Reservation]):
230
+ def fetch(self, search): ...
231
+
232
+ @app("My Python Mateu app")
233
+ class DemoApp:
234
+ @menu_item("Person")
235
+ def person(self) -> Person: return Person()
236
+
237
+ # A card menu (like the product dropdowns of a docs site): @menu_group(display="cards") opens a
238
+ # folder as a panel of cards; @menu_item(description=, icon=, image=) / @menu_group on a nested
239
+ # folder ("Bookings/Reservations") give each card its look, and a nested folder's entries are its
240
+ # actions. Code-authored menus (MenuSupplier / AppSupplier) set MenuItem.display/description/icon/image.
241
+ @app("PMS")
242
+ @menu_group("Bookings", display=MenuDisplay.cards)
243
+ @menu_group("Bookings/Reservations", description="Search, create and modify",
244
+ icon="vaadin:calendar", image="/img/res.png")
245
+ class PmsApp:
246
+ @menu_item("Search", group="Bookings/Reservations")
247
+ def search(self) -> Reservations: return Reservations()
248
+
249
+ @menu_item("Room diary", group="Bookings", description="Rooms by day")
250
+ def diary(self) -> Person: return Person()
251
+ ```
252
+
253
+ Dashboards, foldouts and Gantt charts use the fluent components + archetypes:
254
+
255
+ ```python
256
+ from datetime import date
257
+ from mateu_uidl import ui, title, Dashboard, Panel
258
+ from mateu_uidl.components import MetricCard, MetricTrend, Gantt, GanttTask
259
+
260
+ @ui("dashboard")
261
+ @title("Sales dashboard")
262
+ class SalesDashboard(Dashboard):
263
+ revenue: MetricCard = MetricCard(title="Revenue", value="1.2", unit="M€",
264
+ trend=MetricTrend.up, action_id="openRevenue")
265
+ incidents: MetricCard = MetricCard(title="Incidents", value="3", trend=MetricTrend.down)
266
+ plan: Annotated[Gantt, Panel("Rollout plan", col_span=2)] = Gantt(tasks=(
267
+ GanttTask(id="t1", title="Design", start=date(2026, 7, 1), end=date(2026, 7, 20),
268
+ progress=80.0),
269
+ ))
270
+
271
+ def open_revenue(self): ... # runs when the Revenue tile is clicked
272
+ ```
273
+
274
+ ## Note for Python 3.14+
275
+
276
+ Under [PEP 649](https://peps.python.org/pep-0649/) annotations are evaluated lazily and are no longer
277
+ eagerly stored in `__dict__['__annotations__']`. The reflection layer reads them via
278
+ `inspect.get_annotations(...)`, so the field markers work on 3.11 through 3.14+.
@@ -0,0 +1,236 @@
1
+ # Mateu server-side for Python
2
+
3
+ A Python implementation of Mateu's **server side**: annotate plain Python classes and serve the
4
+ `/mateu/v3/sync` API, so the **existing renderers** (web, React Native, IntelliJ plugin) render a Python backend
5
+ with **zero client changes**. See [`DESIGN.md`](DESIGN.md) for the plan and the [C# port](../dotnet)
6
+ for the sibling implementation.
7
+
8
+ > **The leverage:** the renderers are backend-agnostic — they POST `/mateu/v3/sync/{route}` and
9
+ > consume the `UIIncrement` JSON. So this is **not** a port of the ~865-file Java framework; it just
10
+ > **emits the same JSON**. Verified by diffing the live output against the C# reference: the
11
+ > `showcase` view is byte-identical (344/344 lines). 20 golden-JSON tests pass.
12
+
13
+ ## What works
14
+
15
+ - **Forms** — `@ui` classes; public type-hinted fields become fields (type inferred: `str`,
16
+ `int`→integer, `float`/`Decimal`→number, `bool`→boolean, `date`/`datetime`→date, `Enum`→dropdown);
17
+ `Required()` → required (enforced server-side); `Section("…")` → cards; `@button` → action + toast.
18
+ - **CRUD** — `Crud[T]`: searchable listing (table on desktop, cards on mobile) **plus** full
19
+ detail / edit / new (routes `/x`, `/x/{id}`, `/x/{id}/edit`, `/x/new`), save with validation,
20
+ delete, back-to-list navigation.
21
+ - **Capability listings** — `Listing[Row]` + mixins `Searchable`/`Filterable[F]`/`Navigable`/
22
+ `Editable`/`Creatable`/`Deletable`: a listing that grows by declaring capabilities as base
23
+ classes (mirror of the Java model; `Crud[T]` = all of them at once).
24
+ - **App shell** — `@app` + a menu from `@menu_item` methods + route navigation.
25
+ - **Wizards** — `Wizard` base, `Step(n)` fields, progress + Back/Next, state round-trip.
26
+ - **Decorations** — `@subtitle`, `@banner`, `HeaderBadge`.
27
+ - **Tail features** — `Tab` → tab strip (`Tab("Name", open=True)` makes that tab the one selected on
28
+ first render instead of the first-declared one); field stereotypes (`Multiline`, `Password`, `Money`,
29
+ `PlainText`, `Stereotype`); `LinkTo(href, icon, title, target)` → a nav-link icon on the field
30
+ (href/title travel as raw `${...}` templates, interpolated client-side; implement `LinkSupplier`
31
+ on the view for programmatic links — it wins, `None` falls back to the marker); `@kpi` → KPI
32
+ cards; `@fab` → floating action buttons; `@shortcut`; `@compact`;
33
+ `@confirm_on_navigation_if_dirty`.
34
+ - **i18n / events / security** — `Translator`; `@emits` / `@subscribe_to`; `@secured`.
35
+ - **Layout inference** — `@auto_layout` (opt-in, `@auto_layout(False)` opts out) ports the Java
36
+ `LayoutInference` decision table (same constants/thresholds): heavy unstructured editable forms
37
+ fold optional fields into a collapsed "More options" accordion panel; heavy `@read_only` views
38
+ with ≥ 5 sections become an adaptable `TabLayout` (id `_tabs`, `groupRelationship="alternative"`);
39
+ enums with ≤ 4 members render as radio buttons (`UseRadioButtons()` forces radio always);
40
+ developer-declared tabs always carry `groupRelationship="alternative"` and are `adaptable` when
41
+ the class opted in. Explicit layout markers always win.
42
+ - **UX-pattern components** (`mateu_uidl.components`, same wire JSON as the Java
43
+ `MetricCardDto`…`GanttDto`) — `MetricCard` (+ `MetricTrend`), `Scoreboard`, `DashboardPanel`,
44
+ `DashboardLayout`, `FoldoutPanel`/`FoldoutLayout` (overview slotted `overview`, panel contents
45
+ slotted `panel-N`), `HeroSection`, `EmptyState`, `Skeleton` (+ `SkeletonVariant`), `Gantt` /
46
+ `GanttTask`, plus fluent `Text` and `Button`. `PlanningBoard` (tape chart: `PlanningResource`
47
+ rows × days, `PlanningBlock`s) also carries the OPERA Room Diary extras — `attribute_columns`
48
+ + per-resource `attributes`/`icon`, block `icon`/`summary` (hover text), and
49
+ `resize_action_id` / `open_action_id` (double click) / `range_select_action_id` (drag across
50
+ empty cells) next to `move_action_id`/`select_action_id`. `Map` / `MapMarker` (street map: `position`
51
+ "lat, lon", `zoom`, `markers`; a marker click runs `marker_action_id` with `_markerId`).
52
+ - **Declarative archetypes** — subclass `Dashboard` / `Foldout` / `ItemOverview` / `Welcome` and
53
+ declare type-hinted fields holding components; `Panel(title, subtitle, col_span, row_span, icon,
54
+ open)` in `Annotated[...]` marks titled panels/tabs/tiles (the analogue of Java's `@Panel`).
55
+ Or subclass `ComponentTreeSupplier` and return any fluent component tree from `component()`.
56
+
57
+ - **Validation** — `Required()`, `Min`/`Max`/`Size`/`Pattern`, `@validation`, `ValidationSupplier`
58
+ → the component's client-side `validations` (Java's conditions and messages), re-checked on save.
59
+ - **Grid fields** — a `list[Row]` field is a grid with a row editor (add / edit / save / prev /
60
+ next / remove / move), `InlineEditing()` for in-place cells; wide fields span the row.
61
+ - **Catalogues** — named REST sources (`@rest_source`, `sources.yaml`, `source=` on every surface;
62
+ `RestSourceSupplier` for runtime proxy views) and business components (`@business_component`,
63
+ `components.yaml`, `ComponentRef`).
64
+ - **Field types** — the app's vocabulary (`OrderStatus`, `Money`…) declared once in
65
+ `specs/ui/types.yaml` (or a `FieldTypeCatalogSupplier`; the file wins by id) and referenced by
66
+ `fieldType:` on a YAML `FormField`/`GridColumn` or `Annotated[..., FieldType("X")]` on a listing
67
+ row field: the type supplies defaults (incl. status `tones` on columns), the field's own win.
68
+ - **Sample data** — a source in `sources.yaml` may carry `sample:` (the response) or `sampleFile:`
69
+ (JSON/YAML, relative to `specs/ui`). It is used **only in sample mode**, an explicit opt-in:
70
+ set `MATEU_SOURCES_MOCK=true`. Then the app metadata says `mockSources: true` (the browser
71
+ answers its direct fetches from the samples), the samples travel in the catalogue, and the
72
+ proxied `__restfetch__` answers reads with the sample and writes with `{}` without calling the
73
+ endpoint. Without it, samples never reach the wire and endpoints are called for real.
74
+ - **Component adapters** (`ComponentAdapter` + `AdaptedView`), **embedded islands** (a field holding
75
+ a routed view), **`layoutDelta:`** pages, **group actions** (`@group_action`), **wizard
76
+ completion actions**, **listing exports** (CSV built in; Excel/PDF through your own `ListingExporter`), class/method-level **`@eyes_only`**.
77
+
78
+ The shared wire-conformance corpus is a hard gate here: see `tests/test_wire_conformance.py`.
79
+
80
+ ## Projects
81
+
82
+ | Package | Role |
83
+ |---|---|
84
+ | `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` |
85
+ | `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` |
86
+ | `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…) |
87
+ | `mateu_fastapi` | `add_mateu(app, …)` — the `POST /mateu/v3/sync/{route}` endpoint |
88
+ | `samples/demo` | A runnable FastAPI app |
89
+ | `tests` | Golden-JSON tests asserting wire compatibility with the Java/C# backends |
90
+
91
+ ## Install
92
+
93
+ ```bash
94
+ pip install "mateu-ui[server]" # the package + uvicorn
95
+ pip install "mateu-ui[all]" # the FastAPI server extras
96
+ ```
97
+
98
+ The distribution is **`mateu-ui`** (the PyPI name `mateu` belongs to an unrelated project); the
99
+ import names are `mateu_uidl`, `mateu_dtos`, `mateu_core` and `mateu_fastapi`. Versions move in
100
+ lockstep with the Java artifacts: the `v3.0-alpha.N` release publishes `3.0.0aN`.
101
+
102
+ ## Run
103
+
104
+ ```bash
105
+ cd backend/python
106
+ python3 -m venv .venv && . .venv/bin/activate
107
+ pip install -e ".[dev,all]"
108
+ uvicorn samples.demo.main:app --host 0.0.0.0 --port 8594 # serves the sync API
109
+ pytest # golden tests
110
+ ```
111
+
112
+ Point any Mateu renderer at it — e.g. set the Compose app's `mateu.baseUrl=http://localhost:8594`.
113
+ The server binds to `0.0.0.0`, so the iOS simulator (`localhost:8594`) and Android emulator
114
+ (`10.0.2.2:8594`) reach it too.
115
+
116
+ ## Serving: `add_mateu`
117
+
118
+ ```python
119
+ from fastapi import FastAPI
120
+ from mateu_fastapi import add_mateu
121
+ from mateu_core.identity import identity_from_claims
122
+
123
+ app = FastAPI()
124
+ add_mateu(
125
+ app, views,
126
+ cors_origins=["https://app.example.com"], # CORS is OFF unless you list origins
127
+ secrets_provider=lambda key: vault.read(key), # ${secret.KEY} in proxied REST sources
128
+ )
129
+
130
+ @app.middleware("http") # YOUR authentication, not Mateu's
131
+ async def authenticate(request, call_next):
132
+ claims = verify_with_your_idp(request.headers.get("authorization")) # e.g. PyJWT + JWKS
133
+ if claims is not None:
134
+ request.state.mateu_identity = identity_from_claims(claims)
135
+ return await call_next(request)
136
+ ```
137
+
138
+ - **Identity.** `EyesOnly` / `ReadOnlyUnless` / `DisabledUnless` (and `disabled_unless`) match the
139
+ caller's `Identity(roles, groups, scopes, permissions)`. **Mateu does not authenticate**: the
140
+ default provider, `framework_identity_provider()`, takes the identity your app established for
141
+ the request — an `Identity` your dependency or middleware put in `request.state.mateu_identity`,
142
+ else Starlette's `AuthenticationMiddleware` (`request.user`'s `roles`/`groups`/`permissions` +
143
+ `request.auth.scopes`). A Bearer token is never decoded: verify it in your app and hand Mateu the
144
+ result (`identity_from_claims(claims)` maps Keycloak `realm_access`/`resource_access` roles +
145
+ `roles`, `groups`, `scope`/`scp`, `permissions`). No identity → every gate denies. A custom
146
+ `identity_provider` (parameterless, reading `mateu_core.request_context.current_request()`)
147
+ replaces the default.
148
+ - **Secrets.** `secrets_provider(key) -> str | None` resolves `${secret.KEY}`; unset → the
149
+ environment variable `MATEU_SECRET_<KEY>` (never an arbitrary one). Only the proxy channel (`__restfetch__`) ever sees them.
150
+ - **CORS (breaking).** `add_mateu` used to install `allow_origins=["*"]` by default. CORS is now
151
+ off unless `cors_origins=[...]` lists the allowed origins (`["*"]` still works if you mean it);
152
+ `cors=True` without origins raises. A renderer served by the same app needs no CORS.
153
+ - **Errors.** An unhandled exception answers an error toast, never a raw 500 — the same texts as
154
+ Java and .NET: "Something went wrong" / "An unexpected error occurred. Reference: <id>", the
155
+ reference being the request's correlation id (also in the `X-Mateu-Correlation-Id` header and in
156
+ the ERROR log line with the traceback, logger `mateu.errors`). `dev=True` (or `MATEU_DEV=true` /
157
+ `MATEU_ERRORS_DETAILED=true`) shows the exception instead; raise
158
+ `mateu_uidl.UserFacingException("…", title="…")` (alias `UserFacingError`) for a message written
159
+ for the user, always shown; a pydantic validation error shows its messages. A denied action
160
+ still answers 403.
161
+ - **Concurrency.** The handler runs in Starlette's threadpool, so a slow proxied upstream
162
+ (`proxy_timeout_seconds`, default 30) never blocks the event loop; per-request state (the
163
+ request, the matched route seed, the audience) lives in `ContextVar`s, never on the shared
164
+ handler.
165
+ - **Audience is a projection, not security.** `Audience()` / `@audience` read the client-controlled
166
+ `appState["audience"]`; any caller can send any value. Use it to tailor a screen to a persona, and
167
+ `EyesOnly` (identity) for anything that must stay hidden.
168
+
169
+ ## Define views
170
+
171
+ ```python
172
+ from typing import Annotated
173
+ from mateu_uidl import ui, title, section, button, Required, Section, Message, app, menu_item, menu_group, MenuDisplay, Crud
174
+
175
+ @ui("person")
176
+ @title("Person")
177
+ class Person:
178
+ name: Annotated[str | None, Required(), Section("Identity")] = None
179
+ age: int = 0
180
+
181
+ @button()
182
+ def save(self) -> Message:
183
+ return Message(f"Saved {self.name}")
184
+
185
+ @ui("reservations")
186
+ @title("Reservations")
187
+ class Reservations(Crud[Reservation]):
188
+ def fetch(self, search): ...
189
+
190
+ @app("My Python Mateu app")
191
+ class DemoApp:
192
+ @menu_item("Person")
193
+ def person(self) -> Person: return Person()
194
+
195
+ # A card menu (like the product dropdowns of a docs site): @menu_group(display="cards") opens a
196
+ # folder as a panel of cards; @menu_item(description=, icon=, image=) / @menu_group on a nested
197
+ # folder ("Bookings/Reservations") give each card its look, and a nested folder's entries are its
198
+ # actions. Code-authored menus (MenuSupplier / AppSupplier) set MenuItem.display/description/icon/image.
199
+ @app("PMS")
200
+ @menu_group("Bookings", display=MenuDisplay.cards)
201
+ @menu_group("Bookings/Reservations", description="Search, create and modify",
202
+ icon="vaadin:calendar", image="/img/res.png")
203
+ class PmsApp:
204
+ @menu_item("Search", group="Bookings/Reservations")
205
+ def search(self) -> Reservations: return Reservations()
206
+
207
+ @menu_item("Room diary", group="Bookings", description="Rooms by day")
208
+ def diary(self) -> Person: return Person()
209
+ ```
210
+
211
+ Dashboards, foldouts and Gantt charts use the fluent components + archetypes:
212
+
213
+ ```python
214
+ from datetime import date
215
+ from mateu_uidl import ui, title, Dashboard, Panel
216
+ from mateu_uidl.components import MetricCard, MetricTrend, Gantt, GanttTask
217
+
218
+ @ui("dashboard")
219
+ @title("Sales dashboard")
220
+ class SalesDashboard(Dashboard):
221
+ revenue: MetricCard = MetricCard(title="Revenue", value="1.2", unit="M€",
222
+ trend=MetricTrend.up, action_id="openRevenue")
223
+ incidents: MetricCard = MetricCard(title="Incidents", value="3", trend=MetricTrend.down)
224
+ plan: Annotated[Gantt, Panel("Rollout plan", col_span=2)] = Gantt(tasks=(
225
+ GanttTask(id="t1", title="Design", start=date(2026, 7, 1), end=date(2026, 7, 20),
226
+ progress=80.0),
227
+ ))
228
+
229
+ def open_revenue(self): ... # runs when the Revenue tile is clicked
230
+ ```
231
+
232
+ ## Note for Python 3.14+
233
+
234
+ Under [PEP 649](https://peps.python.org/pep-0649/) annotations are evaluated lazily and are no longer
235
+ eagerly stored in `__dict__['__annotations__']`. The reflection layer reads them via
236
+ `inspect.get_annotations(...)`, so the field markers work on 3.11 through 3.14+.
@@ -0,0 +1,24 @@
1
+ """The Mateu engine: route registry, reflection mapper, and the sync use-case."""
2
+
3
+ from ._version import __version__
4
+ from .action_guard import MateuForbiddenException
5
+ from .importwizard import ColumnMapping, ImportWizard, RowIssue, detect_separator, parse_csv
6
+ from .mapper import ReflectionMapper
7
+ from .registry import MateuRegistry, normalize, type_name
8
+ from .sync_handler import RunActionRq, SyncHandler
9
+
10
+ __all__ = [
11
+ "ColumnMapping",
12
+ "ImportWizard",
13
+ "MateuForbiddenException",
14
+ "MateuRegistry",
15
+ "ReflectionMapper",
16
+ "RowIssue",
17
+ "RunActionRq",
18
+ "SyncHandler",
19
+ "detect_separator",
20
+ "normalize",
21
+ "parse_csv",
22
+ "type_name",
23
+ "__version__",
24
+ ]