Skip to content

Commit

Permalink
update documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
pgorecki committed Mar 17, 2024
1 parent 566e85f commit 387c780
Show file tree
Hide file tree
Showing 9 changed files with 189 additions and 4 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
99 changes: 99 additions & 0 deletions docs/concurrency.rst
Original file line number Diff line number Diff line change
@@ -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
6 changes: 5 additions & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
.. meta::
:http-equiv=Content-Type: text/html; charset=ISO-8859-1

Lato documentation
==================

Expand Down Expand Up @@ -32,6 +35,7 @@ Contents

tutorial/index
key_concepts/index
concurrency
testing
api

Expand Down Expand Up @@ -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
Expand Down
61 changes: 60 additions & 1 deletion docs/key_concepts/handlers.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,63 @@
Message Handlers
================

...
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.
File renamed without changes.
8 changes: 8 additions & 0 deletions docs/key_concepts/src/example1_app.py
Original file line number Diff line number Diff line change
@@ -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")
8 changes: 8 additions & 0 deletions docs/key_concepts/src/example1_module.py
Original file line number Diff line number Diff line change
@@ -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")
7 changes: 6 additions & 1 deletion docs/tutorial/tutorial_06.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
2 changes: 1 addition & 1 deletion examples/async_example/toy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down

0 comments on commit 387c780

Please sign in to comment.