{"id":51529067,"url":"https://github.com/o0Adrian/flet-mvc","last_synced_at":"2026-07-27T23:00:33.977Z","repository":{"id":65201161,"uuid":"587166953","full_name":"o0Adrian/flet-mvc","owner":"o0Adrian","description":"[DEPRECATED PROJECT]","archived":false,"fork":false,"pushed_at":"2025-02-26T22:25:57.000Z","size":197,"stargazers_count":59,"open_issues_count":2,"forks_count":7,"subscribers_count":7,"default_branch":"master","last_synced_at":"2026-02-14T04:27:13.566Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/o0Adrian.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-01-10T05:36:50.000Z","updated_at":"2025-07-17T03:57:17.000Z","dependencies_parsed_at":"2024-01-11T01:37:36.488Z","dependency_job_id":"a0f5d2fb-7fa5-4c4a-8e64-aa7453d943f7","html_url":"https://github.com/o0Adrian/flet-mvc","commit_stats":{"total_commits":6,"total_committers":2,"mean_commits":3.0,"dds":"0.33333333333333337","last_synced_commit":"49995182cd026c2b6efd4f3ae54995826c50bd9b"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/o0Adrian/flet-mvc","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/o0Adrian%2Fflet-mvc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/o0Adrian%2Fflet-mvc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/o0Adrian%2Fflet-mvc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/o0Adrian%2Fflet-mvc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/o0Adrian","download_url":"https://codeload.github.com/o0Adrian/flet-mvc/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/o0Adrian%2Fflet-mvc/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35968098,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"online","status_checked_at":"2026-07-27T02:00:06.776Z","response_time":101,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-09T01:00:30.566Z","updated_at":"2026-07-27T23:00:33.919Z","avatar_url":"https://github.com/o0Adrian.png","language":"Python","funding_links":[],"categories":["Libraries"],"sub_categories":["Community Extensions"],"readme":"# flet-mvc\n\nAdvanced reactive domain models for Flet, with an optional MVC project\nstructure.\n\n[English](https://github.com/o0Adrian/flet-mvc/blob/master/README.md) ·\n[Español](https://github.com/o0Adrian/flet-mvc/blob/master/README.es.md)\n\n\u003e The upcoming flet-mvc 1.0 targets Flet `\u003e=0.86.3,\u003c0.87` and Python 3.10 or\n\u003e newer. The upper bound is intentional: a new Flet minor release is audited\n\u003e before the range is widened.\n\nflet-mvc gives a domain model a little more structure than a plain observable:\n\n- `@data` declares mutable state;\n- `@computed` declares read-only derived state;\n- dependencies form a deterministic graph;\n- batches, snapshots, subscriptions, and control bindings are built in;\n- `FletModel` is a native `ft.Observable`, so it works with `@ft.component`.\n\nMVC is available when separating models, views, and controllers makes a\nproject easier to maintain. It is not mandatory. Flet remains responsible for\ncontrols, `ft.View`, `ft.Router`, dialogs, navigation, rendering, and\napplication lifecycle.\n\n## Choose a path\n\n- To build something now, follow [Quick start](#quick-start).\n- To learn the state API, read [Model state](#model-state).\n- To use class-oriented MVC, open [Optional MVC structure](#optional-mvc-structure).\n- To migrate old code, use the\n  [migration guide](https://github.com/o0Adrian/flet-mvc/blob/master/MIGRATION.md).\n- To bind a control, check the\n  [control matrix](https://github.com/o0Adrian/flet-mvc/blob/master/docs/CONTROL_COMPATIBILITY.md).\n- To review the Flet upgrade, read the\n  [release audit](https://github.com/o0Adrian/flet-mvc/blob/master/docs/FLET_RELEASE_AUDIT.md).\n\n## Install\n\nCreate and activate a virtual environment:\n\n```bash\npython -m venv .venv\n```\n\nOn macOS or Linux:\n\n```bash\nsource .venv/bin/activate\n```\n\nOn Windows PowerShell:\n\n```powershell\n.venv\\Scripts\\Activate.ps1\n```\n\nInstall flet-mvc:\n\n```bash\npython -m pip install --upgrade pip\npython -m pip install \"flet-mvc\u003e=1.0,\u003c2\"\n```\n\nThe package installs its tested Flet range automatically.\n\n| flet-mvc | Flet | Python | Status |\n|---|---|---|---|\n| 1.0.x | `\u003e=0.86.3,\u003c0.87` | 3.10–3.14 | Current target |\n| 0.1.5 | 0.9.0 in practice | Historical | Legacy |\n\n## Quick start\n\nThis first application uses a flet-mvc model directly from Flet's declarative\nUI. There is no controller or custom view class to understand yet.\n\nCreate `app.py`:\n\n```python\nimport flet as ft\n\nfrom flet_mvc import FletModel, computed, data\n\n\nclass CounterModel(FletModel):\n    @data\n    def count(self) -\u003e int:\n        return 0\n\n    @computed\n    def message(self) -\u003e str:\n        return f\"Count: {self.count.value}\"\n\n\n@ft.component\ndef Counter(model: CounterModel) -\u003e ft.Control:\n    return ft.Column(\n        controls=[\n            ft.Text(model.message.value, size=28),\n            ft.Button(\n                content=\"Add one\",\n                icon=ft.Icons.ADD,\n                on_click=lambda: model.count.update(lambda value: value + 1),\n            ),\n        ]\n    )\n\n\ndef main(page: ft.Page) -\u003e None:\n    page.title = \"Reactive counter\"\n    page.render(Counter, CounterModel())\n\n\nif __name__ == \"__main__\":\n    ft.run(main)\n```\n\nRun it:\n\n```bash\npython app.py\n```\n\nEach click follows one predictable flow:\n\n```text\nButton event\n  → count.update(...)\n  → count changes\n  → message is recomputed\n  → Flet observes the model\n  → Counter renders the new values\n```\n\nNo `page.update()` is needed in this declarative example.\n\n## Model state\n\n### Mutable state with `@data`\n\nThe decorated method supplies the default value. Every model instance owns\nindependent datapoints.\n\n```python\nfrom flet_mvc import FletModel, data\n\n\nclass OrderModel(FletModel):\n    @data\n    def quantity(self) -\u003e int:\n        return 1\n\n    @data\n    def unit_price(self) -\u003e float:\n        return 9.50\n\n\nfirst = OrderModel()\nsecond = OrderModel(quantity=3)\n\nassert first.quantity.value == 1\nassert second.quantity.value == 3\n\nfirst.quantity.set(2)\nfirst.quantity.update(lambda quantity: quantity + 1)\nassert first.quantity.value == 3\n\nfirst.quantity.reset()\nassert first.quantity.value == 1\n```\n\nUse:\n\n- `point.value` to read;\n- `point.set(value)` to replace a value;\n- `point.update(transform)` to derive the next value from the current one;\n- `point.reset()` to evaluate its `@data` method again.\n\nDo not assign `model.quantity = 2`; the descriptor rejects direct assignment\nso changes cannot bypass propagation.\n\nThe descriptors are generic. A method returning `int` becomes a\n`Datapoint[int]`, so type checkers validate `.value`, `set()`, `update()`, and\n`use_datapoint()` without extra annotations.\n\n### Derived state with `@computed`\n\nA computed datapoint is read-only. flet-mvc records which datapoints it reads\nand recomputes it after any dependency changes.\n\n```python\nfrom flet_mvc import FletModel, computed, data\n\n\nclass OrderModel(FletModel):\n    @data\n    def quantity(self) -\u003e int:\n        return 1\n\n    @data\n    def unit_price(self) -\u003e float:\n        return 9.50\n\n    @computed\n    def total(self) -\u003e float:\n        return self.quantity.value * self.unit_price.value\n\n\nmodel = OrderModel()\nassert model.total.value == 9.50\n\nmodel.quantity.set(4)\nassert model.total.value == 38.0\n```\n\nCalling `set()`, `update()`, or `reset()` on `total` raises `TypeError`.\nChange its source data instead.\n\nDependencies can change at runtime. For example, a computed property may read\n`personal_price` in one branch and `business_price` in another. The graph\nremoves stale edges when the active branch changes.\n\nIf a dependency is hidden behind code that does not read the datapoint during\nevaluation, declare it:\n\n```python\nclass SearchModel(FletModel):\n    @data\n    def query(self) -\u003e str:\n        return \"\"\n\n    @computed.depends_on(\"query\")\n    def normalized_query(self) -\u003e str:\n        return normalize_external_query(self.query.value)\n```\n\n`normalize_external_query` is an application function in this example.\nUnknown dependencies and cycles fail with a specific dependency error.\n\n### Replace collections instead of mutating them silently\n\nUse `update()` to return a new collection:\n\n```python\nfrom flet_mvc import FletModel, data\n\n\nclass WorkspaceModel(FletModel):\n    @data\n    def items(self) -\u003e list[dict]:\n        return []\n\n    @data\n    def filters(self) -\u003e dict:\n        return {}\n\n\nmodel = WorkspaceModel()\nnew_item = {\"id\": 3, \"name\": \"Document\"}\nmodel.items.update(lambda items: [*items, new_item])\nmodel.filters.update(lambda filters: {**filters, \"active\": True})\n```\n\nAn in-place mutation such as `model.items.value.append(new_item)` changes the\nPython list without telling the dependency graph. Prefer immutable-style\nupdates like the example above.\n\n### Subscribe to changes\n\nSubscribers receive an immutable `DatapointChange`. The returned function\nunsubscribes safely, even when called more than once.\n\n```python\ndef report(change) -\u003e None:\n    print(change.name, change.old_value, change.new_value, change.source)\n\n\nunsubscribe = model.total.subscribe(report, fire_immediately=True)\nmodel.quantity.set(2)\nunsubscribe()\n```\n\nSynchronous and asynchronous subscribers are supported. Notifications run\nafter the dependency graph has reached a consistent state.\n\n### Batch related changes\n\n`batch()` delays propagation until the outermost block exits. Each affected\ncomputed datapoint is evaluated once with the final source values.\n\n```python\nwith model.batch():\n    model.quantity.set(2)\n    model.unit_price.set(20.0)\n\nassert model.total.value == 40.0\n```\n\nNested batches join the same outer transaction. The outermost `batch()` is an\natomic commit boundary: if an exception escapes its body, or a computed value\nor control binding fails while the graph is stabilizing, flet-mvc restores the\nprevious mutable and computed values, bound control properties, dependency\nedges, pending queues, and `model.revision`. Datapoint subscribers and the\nnative model observable are not notified about the failed commit.\n\nThis rollback protects the in-memory model graph; it cannot undo database,\nfile, network, or other side effects performed by application code.\n\n### Save and restore model state\n\n```python\nsaved = model.snapshot()\n\nmodel.quantity.set(99)\nmodel.restore(saved)\n\nassert model.quantity.value == saved[\"quantity\"]\n```\n\nSnapshots are shallow dictionaries containing mutable `@data` values.\nComputed values are omitted because restoring their dependencies recreates\nthem. `restore(values, strict=False)` ignores unknown keys, which is useful\nwhen loading older saved data. Snapshots are useful for persistence and manual\nstate capture; `batch()` does not require one to roll back a failed commit.\n\n### Inspect the dependency graph\n\n```python\nprint(model.dependency_graph)\nprint(model.dependency_dot())\n```\n\n`dependency_graph` maps each datapoint to the names it depends on.\n`dependency_dot()` returns Graphviz DOT for debugging or visualization.\n\n## Use the model with native Flet\n\n### Declarative components\n\n`FletModel` inherits `ft.Observable`. Pass a model to `@ft.component`, read\nits datapoint values while rendering, and let Flet schedule the rerender.\nThis is the recommended starting point for new declarative applications.\n\n```python\n@ft.component\ndef OrderSummary(model: OrderModel) -\u003e ft.Control:\n    return ft.Column(\n        controls=[\n            ft.Text(f\"Quantity: {model.quantity.value}\"),\n            ft.Text(f\"Total: ${model.total.value:.2f}\"),\n            ft.Button(\n                content=\"Add item\",\n                on_click=lambda: model.quantity.update(lambda value: value + 1),\n            ),\n        ]\n    )\n```\n\nUse Flet hooks, contexts, and routers normally. flet-mvc does not introduce a\nsecond rendering system.\n\nPassing the whole model makes the component observe model commits. For a\nsmaller rerender boundary, `use_datapoint(point)` subscribes only to that\ndatapoint and disposes the subscription when the component unmounts:\n\n```python\nfrom flet_mvc import use_datapoint\n\n\n@ft.component\ndef QuantityValue(point) -\u003e ft.Control:\n    quantity = use_datapoint(point)\n    return ft.Text(f\"Quantity: {quantity}\")\n```\n\nLike every Flet hook, call `use_datapoint()` unconditionally and only inside\nan `@ft.component` body.\n\nThere are two intentionally different subscription contracts:\n\n| API | Callback receives | Use |\n|---|---|---|\n| `point.subscribe(...)` | one `DatapointChange` | Observe a specific value |\n| `model.subscribe(...)` | `(sender, field)` | Native `ft.Observable` contract |\n\nA stable datapoint commit notifies the model once with `field=None`.\n`model.revision` increments once for each such commit, including one increment\nfor a whole outer `batch()`; no-op assignments do not increment it. Regular\npublic attributes on the model retain normal Flet Observable notifications\nwith their field name.\n\n### Imperative control bindings\n\nBindings are useful in an imperative screen or when a specific control should\nreceive updates without rebuilding a larger component.\n\n```python\nclass ProfileModel(FletModel):\n    @data\n    def name(self) -\u003e str:\n        return \"Ada\"\n\n\nmodel = ProfileModel()\nfield = ft.TextField(ref=model.name, label=\"Name\")\npreview = ft.Text(ref=model.name)\n\nmodel.name.set(\"Grace\")\nassert field.value == \"Grace\"\nassert preview.value == \"Grace\"\n```\n\nFor a registered input control, flet-mvc also wraps its current change event.\nTyping into `field` pulls `field.value` into the model, preserves the original\nevent handler, and propagates the result.\n\nAutomatic inference is deliberately limited to 21 controls with one stable\nstate field. Structural properties such as `controls`, `content`, `actions`,\n`options`, and `rows` are never guessed. Bind those explicitly:\n\n```python\nclass BoardModel(FletModel):\n    @data\n    def cards(self) -\u003e list[ft.Control]:\n        return []\n\n\nmodel = BoardModel()\nboard = ft.Column()\nmodel.cards.bind(board, \"controls\")\n\nmodel.cards.set([ft.Text(\"First\"), ft.Text(\"Second\")])\nmodel.cards.sync()\nmodel.cards.pull(board)\nmodel.cards.unbind(board)\n```\n\nThe complete fields and events are in\n[`docs/CONTROL_COMPATIBILITY.md`](https://github.com/o0Adrian/flet-mvc/blob/master/docs/CONTROL_COMPATIBILITY.md).\n\nFor a custom control class with one stable state field, register the contract\nonce:\n\n```python\nregister_control_binding(\n    MyControl,\n    \"selection\",\n    expected_type=str,\n    event=\"on_change\",\n)\n```\n\nFor only one instance, prefer\n`point.bind(control, \"selection\")`. An unsafe or unknown implicit binding\nraises `UnsupportedControlError` with instructions instead of guessing.\n\n### Control references\n\nUse Flet's own `ft.Ref` when you need the control object rather than a reactive\nvalue:\n\n```python\nname_ref = ft.Ref[ft.TextField]()\n\n\nasync def focus_name() -\u003e None:\n    if name_ref.current is not None:\n        await name_ref.current.focus()\n\n\nfield = ft.TextField(ref=name_ref, label=\"Name\")\n```\n\nKeep this reference beside the UI that owns the control.\n\n## Model-aware composite controls\n\n`FletComponent` is a native `@ft.control` subclass of `ft.Column`. Use it when\na reusable control needs lifecycle-managed communication with a model.\n\n`@send(Model.field)` publishes the method's return value.\n`@receive(Model.field)` subscribes on mount and unsubscribes on unmount.\n\n```python\nimport flet as ft\n\nfrom flet_mvc import FletComponent, FletModel, data, receive, send\n\n\nclass CounterModel(FletModel):\n    @data\n    def count(self) -\u003e int:\n        return 0\n\n\n@ft.control\nclass CounterPanel(FletComponent):\n    def init(self) -\u003e None:\n        super().init()\n        self.count_text = ft.Text(size=32)\n        self.controls = [\n            self.count_text,\n            ft.Button(content=\"Add one\", on_click=self.increment),\n        ]\n\n    @receive(CounterModel.count)\n    def show_count(self, value: int) -\u003e None:\n        self.count_text.value = str(value)\n\n    @send(CounterModel.count)\n    def increment(self, _event=None) -\u003e int:\n        return self.model.count.value + 1\n\n\npanel = CounterPanel(model=CounterModel())\n```\n\nBoth decorators accept synchronous and asynchronous methods.\n`@receive` fires immediately by default; pass `fire_immediately=False` when\nonly later changes matter. Return `NO_CHANGE` from a `@send` method when an\nevent should leave the target untouched. Returning `None` deliberately sets\nthe datapoint to `None`.\n\nAn asynchronous `@send` method must be declared with `async def`. A regular\n`def` that returns an awaitable raises `TypeError`, matching Flet's event\ncontract instead of silently dropping the coroutine.\n\nAfter a receiver changes child controls, `FletComponent` updates only its own\nsubtree. Declarative components and controls bound elsewhere keep their own\nFlet update lifecycle; a receiver does not force a whole-page update.\n\nFor ordinary UI composition, prefer Flet's lighter `@ft.component`. Use\n`FletComponent` specifically when send/receive and mount cleanup are useful.\n\n## Optional MVC structure\n\nMVC is a directory and responsibility boundary, not a required runtime layer:\n\n```text\nmy_app/\n├── app.py\n├── controllers/\n│   └── todo.py\n├── models/\n│   └── todo.py\n└── views/\n    └── todo.py\n```\n\n- The **Model** owns domain state and rules.\n- The **View** returns native Flet controls, an `ft.View`, or a component.\n- The **Controller** translates UI actions into model operations.\n- `app.py` creates fresh session objects and connects them.\n\n`models/todo.py`:\n\n```python\nfrom flet_mvc import FletModel, data\n\n\nclass TodoModel(FletModel):\n    @data\n    def title(self) -\u003e str:\n        return \"Learn Flet MVC\"\n```\n\n`controllers/todo.py`:\n\n```python\nfrom dataclasses import dataclass\n\nfrom models.todo import TodoModel\n\n\n@dataclass\nclass TodoController:\n    model: TodoModel\n\n    def rename(self, _event=None) -\u003e None:\n        self.model.title.set(\"Build the application\")\n```\n\n`views/todo.py`:\n\n```python\nimport flet as ft\n\nfrom controllers.todo import TodoController\n\n\ndef build_view(controller: TodoController) -\u003e ft.Control:\n    return ft.Column(\n        controls=[\n            ft.Text(ref=controller.model.title, size=28),\n            ft.Button(content=\"Rename\", on_click=controller.rename),\n        ]\n    )\n```\n\n`app.py`:\n\n```python\nimport flet as ft\n\nfrom controllers.todo import TodoController\nfrom models.todo import TodoModel\nfrom views.todo import build_view\n\n\ndef main(page: ft.Page) -\u003e None:\n    model = TodoModel()\n    controller = TodoController(model)\n    page.add(build_view(controller))\n\n\nif __name__ == \"__main__\":\n    ft.run(main)\n```\n\nCreate the model inside `main()`. Flet invokes `main()` for each session, so\nmodule-level models would accidentally share state between users.\n\nThe controller is an ordinary application class; `@dataclass` only removes\nboilerplate. A function or service object works just as well. flet-mvc exports\nno controller base. Pass `page` into a controller only when that controller\nactually navigates or opens a Flet service. Dialogs, dropdown options, updates,\nand navigation use Flet directly.\n\n## Native routing and views\n\nUse the official `ft.Router`, `ft.Route`, `ft.View`, and `@ft.component`.\nflet-mvc does not maintain a parallel route handler.\n\n```python\nimport flet as ft\n\n\n@ft.component\ndef Home() -\u003e ft.Control:\n    return ft.Text(\"Home\", size=28)\n\n\n@ft.component\ndef About() -\u003e ft.Control:\n    return ft.Text(\"About\", size=28)\n\n\n@ft.component\ndef App() -\u003e ft.Control:\n    page = ft.context.page\n    return ft.Column(\n        controls=[\n            ft.Row(\n                controls=[\n                    ft.Button(content=\"Home\", on_click=lambda: page.navigate(\"/\")),\n                    ft.Button(\n                        content=\"About\",\n                        on_click=lambda: page.navigate(\"/about\"),\n                    ),\n                ]\n            ),\n            ft.Router(\n                routes=[\n                    ft.Route(index=True, component=Home),\n                    ft.Route(path=\"about\", component=About),\n                ]\n            ),\n        ]\n    )\n\n\ndef main(page: ft.Page) -\u003e None:\n    page.render(App)\n\n\nif __name__ == \"__main__\":\n    ft.run(main)\n```\n\nFor mobile back gestures and a real view stack, set\n`ft.Router(..., manage_views=True)`, return `ft.View` objects from route\ncomponents, and call `page.render_views(App)`. See Flet's official\n[Router guide](https://flet.dev/docs/controls/router/).\n\n## Data tables\n\n`DataTableAdapter` builds and exports a native `ft.DataTable`. The Flet control\nis always available through `adapter.control`.\n\n### Load records\n\n```python\nimport flet as ft\n\nfrom flet_mvc import DataTableAdapter\n\n\nadapter = DataTableAdapter().load_records(\n    [\n        {\"name\": \"Ada\", \"score\": 98},\n        {\"name\": \"Grace\", \"score\": 95},\n    ]\n)\n\ntable_control = adapter.control\nassert isinstance(table_control, ft.DataTable)\nprint(adapter.to_records())\nprint(adapter.to_csv())\n```\n\nKeys are collected in first-seen order. Missing values become `None`.\n\n### Set a schema and rows\n\n```python\nadapter = DataTableAdapter()\nadapter.set_columns([\"name\", \"score\"], numeric=\"score\")\nadapter.set_rows(\n    [\n        [\"Ada\", 98],\n        {\"name\": \"Grace\", \"score\": 95},\n    ]\n)\n```\n\nRows may be sequences, mappings, or existing `ft.DataRow` objects. Column and\nvisible-cell counts are validated before the table is updated.\n\n### DataFrames and CSV files\n\n```python\nadapter.load_dataframe(frame)\nframe_copy = adapter.to_dataframe()\n\n\nasync def export_table() -\u003e None:\n    await adapter.save_csv(file_name=\"scores.csv\")\n```\n\n`load_dataframe()` accepts pandas-style `to_dict(orient=\"records\")` and\nPolars-style `to_dicts()` objects without importing either library.\n`to_dataframe()` requires pandas:\n\n```bash\npython -m pip install \"flet-mvc[dataframe]\u003e=1.0,\u003c2\"\n```\n\n`save_csv()` uses Flet's current asynchronous `FilePicker.save_file()` and\nsends CSV bytes for desktop or web download.\n\nThe canonical adapter API is:\n\n| Purpose | API |\n|---|---|\n| Native table | `control` |\n| Define content | `set_columns()`, `set_rows()` |\n| Import | `load_records()`, `load_dataframe()` |\n| Export in memory | `to_records()`, `to_csv()`, `to_dataframe()` |\n| Save or download | `save_csv()` |\n\n## Project templates\n\nThe CLI provides four runnable starting points:\n\n| Command | What it demonstrates |\n|---|---|\n| `flet-mvc start -d my_app` | Small class-oriented MVC project |\n| `flet-mvc reactive -d my_app` | Computed state and `FletComponent` |\n| `flet-mvc routes -d my_app` | Official Flet Router and native views |\n| `flet-mvc tabs -d my_app` | Current `Tabs`, `TabBar`, and `TabBarView` |\n\nPreview every file without writing:\n\n```bash\nflet-mvc reactive --destination my_app --dry-run\n```\n\nExisting files are not overwritten by default. After reviewing the\ndestination, `--force` replaces template files:\n\n```bash\nflet-mvc reactive --destination my_app --force\n```\n\nEach template includes a short local README and its own `pyproject.toml`.\n\n## Public API at a glance\n\n| Area | Public names |\n|---|---|\n| Model | `FletModel`, `Datapoint`, `DatapointChange`, `data`, `computed` |\n| Declarative hook | `use_datapoint` |\n| Binding | `register_control_binding` and binding exceptions |\n| Components | `FletComponent`, `send`, `receive`, `NO_CHANGE` |\n| Tables | `DataTableAdapter` |\n\nThe main errors are:\n\n- `DatapointError`: base reactive-state error;\n- `DatapointBindingError`: binding or atomic synchronization failed;\n- `UnsupportedControlError`: no safe default field is registered;\n- `DatapointDependencyError`: dependency declaration is invalid;\n- `DependencyCycleError`: the graph contains a cycle.\n\nPre-1.0 compatibility aliases and duplicate Flet layers were removed before\nthe stable API was published. The exact replacements are in\n[`MIGRATION.md`](https://github.com/o0Adrian/flet-mvc/blob/master/MIGRATION.md).\n\n## Development\n\nInstall the repository with development dependencies:\n\n```bash\ngit clone https://github.com/o0Adrian/flet-mvc.git\ncd flet-mvc\npython -m pip install -e \".[dev]\"\n```\n\nRun the fast checks:\n\n```bash\npython -m pytest\npython -m ruff check .\npython -m ruff format --check .\npython -m mypy\n```\n\nThe repository also contains real Flet browser, Pyodide, generated-template,\nand packaged integration checks. See\n[`CONTRIBUTING.md`](https://github.com/o0Adrian/flet-mvc/blob/master/CONTRIBUTING.md)\nfor the complete workflow.\n\n## License\n\nMIT. See [LICENSE](https://github.com/o0Adrian/flet-mvc/blob/master/LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fo0Adrian%2Fflet-mvc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fo0Adrian%2Fflet-mvc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fo0Adrian%2Fflet-mvc/lists"}