# -*- coding: utf-8 -*-
from setuptools import setup

packages = \
['fletched', 'fletched.mvp_utils', 'fletched.routed_app']

package_data = \
{'': ['*']}

install_requires = \
['flet>=0.4.0,<0.5.0', 'repath>=0.9.0,<0.10.0']

setup_kwargs = {
    'name': 'fletched',
    'version': '0.4.0',
    'description': 'Tools to build flet apps with multiple views/routes',
    'long_description': '# fletched\n\n- [fletched](#fletched)\n  - [routed-app](#routed-app)\n    - [When will I need this?](#when-will-i-need-this)\n    - [How do I use this?](#how-do-i-use-this)\n      - [ViewBuilder class](#viewbuilder-class)\n      - [Route assignment](#route-assignment)\n      - [Route protection](#route-protection)\n      - [Aggregating ViewBuilder classes](#aggregating-viewbuilder-classes)\n      - [RoutedApp usage](#routedapp-usage)\n      - [App state](#app-state)\n  - [mvp-utils](#mvp-utils)\n    - [Architecture / API](#architecture--api)\n    - [Usage](#usage)\n      - [View](#view)\n      - [Presenter](#presenter)\n      - [DataSource](#datasource)\n      - [Model](#model)\n      - [Validation / error handling](#validation--error-handling)\n\n## routed-app\n\n### When will I need this?\n\nSay you want to design an app for a government agency with multiple,\nlet\'s say 20+ pages (MPA = Multi Page Application).\nSince Flet is technically SPA (Single Page Application) only,\nyou\'ll use views and some routing to simulate the MPA behaviour.\n\nNot every person in the agency should be able to access every page/view.\nAlso, they shouldn\'t be able to see anything\nbut the login page until they\'re logged in.\nThe roles defined in the OAuth token the app receives upon login\nwill determine what pages/views a user has access to.\n\nYou\'ll probably want to design your app in a way that bundles every page/view\ninto its own module.\nIf you used an architecture design pattern\n(which you definitely should at this scale),\nobtaining the view requires building its model\nand presenter or controller as well\nand thus you need some function or method to obtain the view.\n\nThe way flet routing works ATM,\na view will have to be recreated after a route change,\nso you\'ll want to match each route of your app\nto the function or method that creates the appropriate view for it.\nYou\'ll also want the function/method to return a different view\nor raise an exception if the user is not authorized to access it.\nThis can create a lot of boilerplate code\nif you don\'t have the help of a library.\n\nAnother library that might interest you\nif you are designing an app of that scale is\n[flet-mvp-utils](https://github.com/iron3oxide/flet-mvp-utils).\nIt is designed to work well with flet-routed-app\n(e.g. the conveniently included MvpViewBuilder assumes usage of flet-mvp-utils),\nbut neither of them requires usage of the other.\n\n### How do I use this?\n\n#### ViewBuilder class\n\nIn the module of your page/view,\ncreate a file called (something like) `build.py`.\nIn it, create a class called (something like) `{page_name}ViewBuilder`.\nThis class should inherit from the `ViewBuilder` class of this library\nand at minimum define a method with the signature\n\n```python\ndef build_view(self, route_parameters: dict[str, str]) -> flet.View\n```\n\nThis library also contains convenience ViewBuilder subclasses\nthat provide a shortcut for common architecture design patterns.\nThe MvpViewBuilder for example only requires you to define three class variables:\n\n```python\nfrom flet_routed_app import MvpViewBuilder\n\nfrom my_package.views.counter import CounterDataSource, CounterPresenter, CounterView\n\n\nclass CounterViewBuilder(MvpViewBuilder):\n    data_source_class = CounterDataSource\n    presenter_class = CounterPresenter\n    view_class = CounterView\n```\n\n#### Route assignment\n\n```python\nfrom flet_routed_app import MvpViewBuilder, route\n\nfrom my_package.views.counter import CounterDataSource, CounterPresenter, CounterView\n\n@route("/counter")\nclass CounterViewBuilder(MvpViewBuilder):\n    data_source_class = CounterDataSource\n    presenter_class = CounterPresenter\n    view_class = CounterView\n```\n\n#### Route protection\n\n```python\nfrom flet_routed_app import MvpViewBuilder, login_required, route\n\nfrom my_package.views.counter import CounterDataSource, CounterPresenter, CounterView\n\n@login_required\n@route("/counter")\nclass CounterViewBuilder(MvpViewBuilder):\n    data_source_class = CounterDataSource\n    presenter_class = CounterPresenter\n    view_class = CounterView\n```\n\n```python\nfrom flet_routed_app import MvpViewBuilder, group_required, route\n\nfrom my_package.views.counter import CounterDataSource, CounterPresenter, CounterView\n\n@group_required("demo")\n@route("/counter")\nclass CounterViewBuilder(MvpViewBuilder):\n    data_source_class = CounterDataSource\n    presenter_class = CounterPresenter\n    view_class = CounterView\n```\n\nYou can also easily write your own auth decorator,\nall it has to do is define a function that returns a bool\nand set the `auth_func` attribute\nof the ViewBuilder class it wraps to that function.\n\n#### Aggregating ViewBuilder classes\n\nSomewhere in your project, you will have to import all ViewBuilder classes\nand aggregate them in a list.\nThe recommended approach is to do this in the `__init__.py`\nof the module that contains all your view modules.\n\nIt is also possible to create multiple lists\nof different ViewBuilders in different places in your project\nand to then add these lists to the app one after another.\n\n#### RoutedApp usage\n\nIn your main() function,\ncreate an instance of RoutedApp\nand add the previously imported list of ViewBuilder classes to the instance.\n\n```python\nimport flet as ft\n\nfrom flet_routed_app import RoutedApp\n\nfrom mypackage import views\n\ndef main(page: ft.Page):\n    app = RoutedApp(page)\n    app.add_view_builders(views.view_builders)\n\nft.app(target=main)\n```\n\n#### App state\n\nYou can share data between different pages/views\nby storing it in the `state` dictionary of the app instance\nand retrieving it from there.\n\n`state` is a defaultdict;\nif a key does not exist,\nit will return the string Literal "not set".\n\nEach ViewBuilder will be passed the app instance\nwhen it is added to that very instance.\n\nIf you know exactly which variables you will need to pass at runtime\nand you want to have autocomplete in your editor,\nyou can create custom app and state classes in an `app.py` file like this:\n\n```python\nfrom flet_routed_app import CustomAppState, RoutedApp\n\n\nclass AppState(CustomAppState):\n    test: int = 0\n    demo: str = ""\n\n\nclass App(RoutedApp):\n    state: AppState = AppState()\n```\n\nPlease remember to use those throughout your project when typehinting,\notherwise you won\'t reap the autocomplete benefits.\n\n`CustomAppState` is an empty dataclass which saves you the trouble\nof having to import dataclasses and decorate your class\nand ensures better type safety for the library.\n\nYou will also need to pass the `custom_state=True` flag\nwhen creating the app instance,\nso the constructor  of `RoutedApp` knows not to set the `state` class variable\nto an empty defaultdict.\n\n## mvp-utils\n\nThis library provides tools that make it a bit easier\nto follow architecture patterns in your flet app\nthat leverage on immutable models and unidirectional control flow.\nThose are mostly based on the Model-View-Presenter/MVP pattern,\nhence the name of the library.\nAt this stage,\nit can be used to ease working with any model-based architecture pattern though.\n\n### Architecture / API\n\n```mermaid\nflowchart TB\n    View-- "intent methods call handlers"-->Presenter\n    Presenter-- pass new model to render-->View\n    Presenter-- handle intent concretely -->DataSource\n    DataSource-- notify of new model -->Presenter\n    DataSource-- create from intended/resulting changes -->Model\n    DataSource<-- query/modify according to intent -->DB/API/etc\n```\n\nOne of the main goals of this library\nis to reduce the amount of boilerplate code that is needed\nin order to implement an MVP-based architecture for flet apps.\nThis should however not come at the cost of typechecking and autocomplete.\nThat is why for each class you will interact with,\nthe general way of doing things stays the same:\n\n```python\nclass MyClass(LibraryClass):\n    variable_needed_by_library_class_and_this_class: MyOtherClass\n\n    def some_method(self):\n        ...\n```\n\nThis approach solves the following problem:\nIn order to do their helpful work behind the scenes,\nthe library classes need to access objects\nthat the concrete subclasses receive or create,\ne.g. the DataSource in MvpPresenter implementations.\nMvpPresenter can only know that the DataSource is an instance of MvpDataSource,\nso a subclass accessing a `self.data_source` variable set in the parent class\n(how it is set is more or less irrelevant) would not know anymore than that\nand thus your IDE can\'t properly autocomplete for you anymore.\n\nThere is a bit of magic\n(namely abstract class properties and a bit of dataclass wizardry)\ngoing on behind the scenes that makes this work,\nbut it should save you from ever having to write an `__init__()` method\nwhile still getting helpful autocomplete in `MyClass` for\n`variable_needed_by_library_class_and_this_class`,\nwhich itself will be autocompleted for you when defining `MyClass`.\nIt also makes the approach more declarative rather than imperative,\nwhich some developers might prefer (or so I\'ve heard).\n\nIf you feel you need this library to properly design your flet app,\nit is probably complex enough to need routing\nand maybe even an app state solution as well.\nLucky for you, [flet-routed-app](https://github.com/iron3oxide/flet-routed-app)\nand flet-mvp-utils are designed to compliment each other\nwhile none of both strictly requires usage of the other in order to work.\n\n### Usage\n\nSay you have a form and want to validate the TextFields in it\nwhen a submit button is clicked.\n\n#### View\n\nYour view uses [refs](https://flet.dev/docs/guides/python/control-refs/).\nThe actual UI code may be located somewhere else\nand simply receive the refs and/or callbacks\nand return a component that is connected to the ref.\nWhen creating the view class, you inherit from `MvpView`\nand create a class variable named `ref_map`,\ncontaining a dictionary that maps the attribute names\nof your model to the respective ref\nof the control that should be tied to it.\nAny variable intended for the `flet.View` constructor will be accepted\nand passed on by the default `__init__()` method,\nso you don\'t need to define your own in most cases.\n\n```python\nimport flet as ft\n\nfrom flet_mvp_utils import MvpView\n\n\nclass FormView(MvpView):\n    ref_map = {\n            "last_name": ft.Ref[ft.TextField](),\n            "first_name": ft.Ref[ft.TextField](),\n            "age": ft.Ref[ft.TextField](),\n        }\n\n    def some_intent_method(self, e) -> None:\n        ...\n```\n\n`MvpView` has a `render(model)` method that takes a model\nand updates any refs\' current value to the model value if they aren\'t the same.\nThis method is supposed to be called in the callback\nyou register with the DataSource,\nso that a changed model is immediately reflected in the view.\nAs you will learn in the next section,\nthis doesn\'t have to concern you as it can be done automatically.\n\n#### Presenter\n\nAny class that inherits from `MvpPresenter` updates the view automatically\nonce it is notified of a model update.\n`MvpPresenter` is a dataclass\nand so should its subclasses be.\nThis helps to reduce the amount of boilerplate code\n(specifically `__init__()` methods) you have to write\nand keeps the general API of this library consistent.\nSince both the DataSource and the View are known to it\n(because the subclass fields override the fields of the same name in the superclass),\n`MvpPresenter` will automatically register a method as a callback with the DataSource\nthat renders the new model in the given view in its `__post_init__()` hook.\n\n```python\nfrom dataclasses import dataclass\nfrom flet_mvp_utils import MvpPresenter\n\nfrom my_package.views.form import FormDataSource, FormViewProtocol\n\n\n@dataclass\nclass FormPresenter(MvpPresenter):\n    data_source: FormDataSource\n    view: FormViewProtocol\n\n    def some_intent_handling_method(self) -> None:\n        ...\n```\n\n`MvpPresenter` also provides a generic `build()` method\nthat simply calls the `build(presenter)` method of the view\nwith itself as the sole argument.\nIf you need a custom build method for your presenter,\njust override it with your own.\n\n#### DataSource\n\nThe DataSource class, inheriting from `MvpDataSource`,\nis where the business logic of your component/virtual page will live.\nSince the latter inherits from `Observable`,\nbrokers of any kind (presenter classes in MVP-based architectures)\ncan register callback functions with your DataSource class\nthat will be executed when you call `self.notify_observers()` in it.\nAs mentioned above, subclasses of `MvpPresenter` do this for you automatically\nafter you initialized them properly.\n\nThese callbacks are meant to be used to inform a presenter that a new,\nupdated model has been created.\nSince creating new models to replace the current one is a rather repetitive\nand uniform task,\n`MvpDataSource` will do it for you.\nAll you have to do is pass your model class to its constructor\nand call `self.update_model_partial(changes: dict)`\nor `self.update_model_complete(new_model: dict)` depending on your use case.\n\n```python\nfrom flet_mvp_utils import MvpDataSource\n\n\nclass FormDataSource(MvpDataSource):\n    current_model = FormModel()\n\n    def some_method(self) -> None:\n        ...\n```\n\n#### Model\n\nThe model is supposed to act as the state of your view.\nIt should contain everything the view needs to know\nin order to render/update itself.\nThis can be data from a database, an API,\na config file or even just another component.\n\nYour model inherits from `MvpModel`,\nwhich is an immutable pydantic BaseModel.\nThis means you can write custom validators for each attribute\nand validate all your data whenever a new instance of the model is created.\n\nThe model is immutable to force good habits upon the ones using it.\nGood habits in this context means not to modify your current model anywhere\nbut in your DataSource class as that should be the single source of truth.\nOf course immutability is never enforced 100% in python,\nbut this should suffice.\n\n```python\nfrom flet_mvp_utils import MvpModel\n\n\nclass FormModel(MvpModel):\n    last_name: str = ""\n    first_name: str = ""\n    age: int = 0\n```\n\n#### Validation / error handling\n\nNotice that `age` is an `int`,\neven though the ref we assigned to it earlier points to a TextField?\nThat\'s no problem at all,\nyou\'ll have to do no manual conversion.\nPydantic will parse the text input into an `int`\nand raise an error if that fails.\nWe probably want to inform our user though that they have input invalid data.\nTo do this, we\'ll simply typehint `age` differently.\n\n```python\nfrom flet_mvp_utils import ErrorMessage, MvpModel\n\n\nclass FormModel(MvpModel):\n    last_name: str = ""\n    first_name: str = ""\n    age: ErrorMessage | int = 0\n```\n\n> It\'s important to specify the narrower type (ErrorMessage) first,\n> otherwise every error message would just say\n> that the field is not an instance of ErrorMessage.\n\nThis is where the magic of the update_model methods of `MvpDataSource` comes to light.\nIf the creation of a new model fails,\ne.g. because a user put "old" into the age TextField instead of a number,\nour DataSource will now catch this error,\nwrap its message in an `ErrorMessage` object\nand assign it to the age field\nof a new model that contains all changes,\nboth the valid inputs and the error mesages.\nMultiple errors at once are no problem at all,\neach ErrorMessage will be assigned to the field that caused it.\n\nSince we probably don\'t want to make any calls to a database, API etc. in that case,\nthe update_model methods will return a bool\nto let you know if there was an error.\n\nThe subscribed observers will be notified either way\nand the model will thus be rendered.\n`MvpView.render()` will try to assign fields that are instances of `ErrorMessage`\nto the `error_text` property of the control that the associated ref points to.\nThis means that you should only use this technique for model fields\nthat are associated with controls that actually have that property,\nlike TextField or Dropdown.\n',
    'author': 'iron3oxide',
    'author_email': 'jason.hottelet@tuta.io',
    'maintainer': 'None',
    'maintainer_email': 'None',
    'url': 'https://github.com/iron3oxide/flet-routed-app',
    'packages': packages,
    'package_data': package_data,
    'install_requires': install_requires,
    'python_requires': '>=3.10,<4.0',
}


setup(**setup_kwargs)
