From 387c780a2a4d22d82a2343b09fbafb40fe6de718 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20G=C3=B3recki?= Date: Mon, 18 Mar 2024 00:25:16 +0100 Subject: [PATCH] update documentation --- .github/workflows/release.yml | 2 + docs/concurrency.rst | 99 +++++++++++++++++++ docs/index.rst | 6 +- docs/key_concepts/handlers.rst | 61 +++++++++++- .../modularity.rst} | 0 docs/key_concepts/src/example1_app.py | 8 ++ docs/key_concepts/src/example1_module.py | 8 ++ docs/tutorial/tutorial_06.rst | 7 +- examples/async_example/toy.py | 2 +- 9 files changed, 189 insertions(+), 4 deletions(-) create mode 100644 docs/concurrency.rst rename docs/{tutorial/__init__.py => key_concepts/modularity.rst} (100%) create mode 100644 docs/key_concepts/src/example1_app.py create mode 100644 docs/key_concepts/src/example1_module.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2e04842..96c860d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,6 +36,8 @@ jobs: - name: Create Release uses: ncipollo/release-action@v1 with: + allowUpdates: true + omitBodyDuringUpdate: true artifacts: "dist/*" token: ${{ secrets.GITHUB_TOKEN }} draft: false diff --git a/docs/concurrency.rst b/docs/concurrency.rst new file mode 100644 index 0000000..6574540 --- /dev/null +++ b/docs/concurrency.rst @@ -0,0 +1,99 @@ +.. _concurrency: + +Concurrency +=========== + +Lato supports ``async / await`` syntax. To make your synchronous code *asynchronous* simply replace: + +- ``app.call()`` with ``app.call_async()`` +- ``app.execute()`` with ``app.execute_async()`` +- ``app.publish()`` with ``app.publish_async()`` + +Below is an example of async application: + +.. testcode:: + + import asyncio + import logging + + from lato import Application, TransactionContext + + logging.basicConfig(level=logging.DEBUG) + root_logger = logging.getLogger("toy") + + app = Application() + + + class Counter: + def __init__(self): + self.value = 0 + + def next(self): + self.value += 1 + return self.value + + + counter = Counter() + + + @app.on_enter_transaction_context + async def on_enter_transaction_context(ctx: TransactionContext): + correlation_id = str(counter.next()) + logger = root_logger.getChild(correlation_id) + ctx.set_dependencies(logger=logger) + logger.info("Connecting to database") + await asyncio.sleep(0.001) + logger.info("Connected") + + + @app.on_exit_transaction_context + async def on_exit_transaction_context(ctx: TransactionContext, exception=None): + logger = ctx["logger"] + logger.info("Disconnecting from database") + await asyncio.sleep(0.001) + logger.info("Disconnected from database") + + + @app.handler("foo") + async def handle_foo(x, logger): + logger.info(f"Starting foo, x={x}") + await asyncio.sleep(0.001) + logger.info("Finished foo") + + + async def main() -> None: + await asyncio.gather( + app.call_async("foo", x=1), + app.call_async("foo", x=2), + app.call_async("foo", x=3), + ) + + + asyncio.run(main()) + + +And the output is: + +.. testoutput:: + + INFO:toy.1:Connecting to database + INFO:toy.2:Connecting to database + INFO:toy.3:Connecting to database + INFO:toy.1:Connected + INFO:toy.1:Starting foo, x=1 + INFO:toy.2:Connected + INFO:toy.2:Starting foo, x=2 + INFO:toy.3:Connected + INFO:toy.3:Starting foo, x=3 + INFO:toy.1:Finished foo + INFO:toy.1:Disconnecting from database + INFO:toy.2:Finished foo + INFO:toy.2:Disconnecting from database + INFO:toy.3:Finished foo + INFO:toy.3:Disconnecting from database + INFO:toy.1:Disconnected from database + INFO:toy.2:Disconnected from database + INFO:toy.3:Disconnected from database + + +Feel free to check other example in the repository: https://github.com/pgorecki/lato/tree/main/examples/async_example \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index 8e16247..2e2f733 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,3 +1,6 @@ +.. meta:: + :http-equiv=Content-Type: text/html; charset=ISO-8859-1 + Lato documentation ================== @@ -32,6 +35,7 @@ Contents tutorial/index key_concepts/index + concurrency testing api @@ -122,7 +126,7 @@ Instead of using an alias, you can create a command handler, and then invoke the .. testcode:: - from lato import Task as Command + from lato import Command class Greet(Command): title: str diff --git a/docs/key_concepts/handlers.rst b/docs/key_concepts/handlers.rst index 81fd078..e32504d 100644 --- a/docs/key_concepts/handlers.rst +++ b/docs/key_concepts/handlers.rst @@ -1,4 +1,63 @@ Message Handlers ================ -... \ No newline at end of file +There are 3 way of interacting with lato application. + +1. Directly invoking a function, +2. Calling a function using an *alias*, +3. Calling the function using a message handler. + +Let's have a closer look at all the possibilities. + +Directly invoking a function +---------------------------- + +In this approach, a function is passed to a :func:`Application.call` as is: + +.. testcode:: + + from lato import Application + + def foo(): + print("called directly") + + app = Application("example") + + app.call(foo) + +And the output is: + +.. testoutput:: + + called + + +Calling a function using an *alias* +----------------------------------- + +In this approach, a function is first decorated with :func:`ApplicationM.handler`, and then called using an alias: + +.. testcode:: + from lato import ApplicationModule + + from lato import Application + app = Application("example") + + @app.handler("alias_of_foo") + def foo(): + print("called via alias") + + app.call("alias_of_foo") + +And the output is: + +.. testoutput:: + + called via alias + + +Calling the function using a message handler +-------------------------------------------- + +In this approach, a message is declared, then a :func:`Application.handler` decorator is used to +associate the message with its handler. \ No newline at end of file diff --git a/docs/tutorial/__init__.py b/docs/key_concepts/modularity.rst similarity index 100% rename from docs/tutorial/__init__.py rename to docs/key_concepts/modularity.rst diff --git a/docs/key_concepts/src/example1_app.py b/docs/key_concepts/src/example1_app.py new file mode 100644 index 0000000..5abc1d5 --- /dev/null +++ b/docs/key_concepts/src/example1_app.py @@ -0,0 +1,8 @@ +from lato import Application +from example1_module import my_module + +app = Application("alias_example") +app.include_submodule(my_module) + + +app.call("alias_of_foo") \ No newline at end of file diff --git a/docs/key_concepts/src/example1_module.py b/docs/key_concepts/src/example1_module.py new file mode 100644 index 0000000..1419087 --- /dev/null +++ b/docs/key_concepts/src/example1_module.py @@ -0,0 +1,8 @@ +from lato import ApplicationModule + +my_module = ApplicationModule("my_module") + + +@my_module.handler("alias_of_foo") +def foo(): + print("called via alias") \ No newline at end of file diff --git a/docs/tutorial/tutorial_06.rst b/docs/tutorial/tutorial_06.rst index e909cd4..b7ebacf 100644 --- a/docs/tutorial/tutorial_06.rst +++ b/docs/tutorial/tutorial_06.rst @@ -25,7 +25,12 @@ For now, let's look at the FastAPI example: @api.get("/") async def root(app: Annotated[Application, Depends(get_application)]): - result = app.execute(GetAllTodos()) + result = await app.execute_async(GetAllTodos()) return {"todos": result} +FastAPI supports ``async / await`` syntax, and we take advantage of it by using ``await app.execute_async(...)`` instead +of ``app.execute(...)`` we used before. + +See :ref:`concurrency` for more details on writing concurrent code in Lato. + This concludes the tutorial. \ No newline at end of file diff --git a/examples/async_example/toy.py b/examples/async_example/toy.py index 4bff33d..75f2555 100644 --- a/examples/async_example/toy.py +++ b/examples/async_example/toy.py @@ -3,7 +3,7 @@ from lato import Application, TransactionContext -logging.basicConfig(level=logging.DEBUG) +logging.basicConfig(level=logging.INFO) root_logger = logging.getLogger("toy") app = Application()