.. meta:: :http-equiv=Content-Type: text/html; charset=ISO-8859-1 :description: Lato - a Python microframework for building modular applications :google-site-verification: Iliv_95Aua1_V110coworVO8MGv98wf-cSr1OfaXZQA Lato documentation ================== **Lato** is a microframework for building modular applications in Python. It helps you to build applications that are easy to maintain and extend. It is based on such concepts as `modularity `_, `dependency injection `_, and `loose coupling `_. If you are trying to build a modular monolith in Python, **Lato** is the right choice for you. It is framework agnostic, you can use it with any framework of your choice, i.e. `Flask`, `FastAPI`, etc. Lato is Open Source and licensed under the MIT License. Core Features ------------- - **Modularity**: Organize your application into smaller, independent modules for better maintainability. - **Flexibility**: Loosely couple your application components, making them easier to refactor and extend. - **Testability**: Easily test your application components in isolation. - **Minimalistic**: Intuitive and lean API for rapid development without the bloat. - **Concurrency support**: built-in support for coroutines declared with async / await syntax. Contents -------- .. toctree:: :maxdepth: 2 tutorial/index key_concepts/index concurrency testing api Installation ============ **Lato** is available on `PyPI `_ and can be installed with pip:: pip install lato Getting Started =============== The ``Application`` serves as a top-level building block in **Lato**. Let's create a simple greeting application. When you instantiate the application you need to name it, and in addition you can pass as many keyword arguments as you need - they will be used as dependencies. Later on you will see how the dependencies are being used. Let's create the application with one dependency - ``greeting_phrase``: .. testcode:: from lato import Application app = Application(name="Hello World", greeting_phrase="Hello") Resolving parameters -------------------- Now, let's see the core feature of **Lato** - *automatic parameter resolution*. To demonstrate it, let's declare a ``greet_person`` function and invoke it via ``app.call``: .. testcode:: def greet_person(person: str, greeting_phrase: str): return f"{greeting_phrase} {person}" result = app.call(greet_person, "Bob") assert result == "Hello Bob" The ``greet_person`` function requires two arguments: ``person`` and ``greeting phrase``. When invoking the function via ``Application.call()``, we provided only one argument - the value of ``person``. The other argument - ``greeting phrase`` is automatically provided (injected) by the app, as you provided it as a dependency. In general, **Lato** application is capable of injecting missing function arguments, if the function parameter matches the dependency (either by parameter name, or by type). Declaring a handler ------------------- To understand what a handler is, think of it as of named function. This name is an *alias*, which is a string. When invoking a handler, you can pass an alias instead of the function itself. This idea promotes loose coupling. You can create the handler and call it using its alias like so: .. testcode:: @app.handler("greet") def greet_person(person: str, greeting_phrase: str): return f"{greeting_phrase} {person}" result = app.call("greet", "Bob") assert result == "Hello Bob" You could use this behavior in a simple command line tool, like so: .. code-block:: python if __name__ == "__main__": command = sys.argv[1] name = sys.argv[2] print(app.call(command, name)) Using a command handler ----------------------- **Lato** follows the `Command Pattern `_ in its design. It is a behavioral design pattern which is used to implement lose coupling in a request-response model. The idea is to turn a request (command) into a standalone object. This object contains all parameters that are required to handle a request. Such separation allows for queuing of requests, logging of the parameters for better observability, and invocation of the request. Instead of using an alias, you can create a command handler, and then invoke the handler by calling ``App.execute()``: .. testcode:: from lato import Command class Greet(Command): title: str name: str @app.handler(Greet) def greet_person(command: Greet, greeting_phrase: str): return f"{greeting_phrase} {command.title} {command.name}" result = app.execute(Greet(title="Mr", name="Bob")) assert result == "Hello Mr Bob" The ``Application.execute()`` passes the command to a designated handler. It should be noted that the first parameter of a command handler is a command, and the remaining parameters are resolved from application dependencies. Integration with a web framework -------------------------------- **Lato** is web framework agnostic. Let's see how it fits into our framework of choice - FastAPI: .. code-block:: python from fastapi import FastAPI from lato import Application, Command app = Application("Greetings", greeting_phrase="Hello") class Greet(Command): title: str name: str @app.handler(Greet) def greet_person(command: Greet, greeting_phrase: str): return f"{greeting_phrase} {task.title} {task.name}" api = FastAPI() @api.get("/greet") def foo(request, title: str, name: str): command = Greet(title, name) message = app.execute(command) return {'message': message}