Skip to main content
An async Tensorlake function behaves like a regular Python async function. Calling it returns a coroutine that doesn’t run until it’s awaited or started with asyncio.create_task() or other asyncio module functions.
from tensorlake.applications import application, function

@function()
async def capitalize(text: str) -> str:
    return text.upper()

@application()
@function()
async def greet(name: str) -> str:
    # Calling an async Tensorlake function `capitalize` returns a coroutine.
    # `await` is available inside async Tensorlake functions `greet`.
    # `await` starts the `capitalize` coroutine and waits for it to complete, returning the result.
    capitalized: str = await capitalize(name)
    return f"Hello, {capitalized}!"
coroutines returned by async Tensorlake functions behave almost the same way as Futures used with sync Tensorlake functions.

asyncio.create_task

Use asyncio.create_task() to run a coroutine in the background without blocking on it. This returns an asyncio.Task that can be awaited later to get the result.
import asyncio
from tensorlake.applications import application, function

@function()
async def double(x: int) -> int:
    return x * 2

@application()
@function()
async def my_app(x: int) -> int:
    coroutine = double(x)
    # Starts the coroutine in the background and returns an asyncio.Task.
    task: asyncio.Task = asyncio.create_task(coroutine)
    # Do something else and then await the task to get the result.
    return await task

Running coroutines in parallel with asyncio.gather

Use asyncio.gather() to run multiple coroutines in parallel and collect their results. This is the standard Python way to run async functions concurrently.
import asyncio
from tensorlake.applications import application, function

@function()
async def capitalize(text: str) -> str:
    return text.upper()

@function()
async def make_joke(name: str) -> str:
    return f"Why did {name} cross the road? To get to the other side!"

@application()
@function()
async def greet(name: str) -> str:
    # Start both function calls in parallel.
    capitalized, joke = await asyncio.gather(
        capitalize(name),
        make_joke(name),
    )
    return f"Hello, {capitalized}! {joke}"

Non-blocking map and reduce operations

Calling function.map(...) or function.reduce(...) on an async function returns a coroutine.
from tensorlake.applications import application, function

@function()
async def double(x: int) -> int:
    return x * 2

@function()
async def add(a: int, b: int) -> int:
    return a + b

@application()
@function()
async def process_numbers(numbers: list[int]) -> int:
    # Calling .map() on an async function returns a coroutine.
    # `await` runs the map operation and blocks until all items are processed.
    doubled: list[int] = await double.map(numbers)
    # Calling .reduce() on an async function also returns a coroutine.
    total: int = await add.reduce(doubled)
    return total
The coroutines returned by function.map() or function.reduce() behave exactly the same as coroutines returned by async function(...) calls.

Passing coroutines and asyncio.Tasks as inputs

Coroutines returned from async Tensorlake functions and asyncio.Task objects created with asyncio.create_task() from such coroutines can be passed as arguments to other function calls. Tensorlake automatically runs the coroutines or asyncio.Task objects, waits for them to complete, and uses their results as the argument values. This works exactly like passing Futures as inputs.
from tensorlake.applications import application, function

@function()
async def double(x: int) -> int:
    return x * 2

@function()
async def add(a: int, b: int) -> int:
    return a + b

@application()
@function()
async def my_app(x: int) -> int:
    a = double(x)
    b = double(x + 1)
    # Pass coroutines as function call arguments. Tensorlake runs both in parallel,
    # waits for them to complete, and uses their results as the arguments for `add`.
    return await add(a, b)

Tail calls

Returning a Tensorlake function coroutine or its asyncio.Task makes a tail call. The returning function completes immediately and its function container is freed to process the next request. Tensorlake runs the returned coroutine or task and uses its result as the function’s return value. This works exactly like returning a Future as a tail call.
from tensorlake.applications import application, function

@function()
async def double(x: int) -> int:
    return x * 2

@application()
@function()
async def my_app(x: int) -> int:
    # Returns a coroutine as a tail call. The function completes immediately
    # and Tensorlake runs the coroutine in the background.
    return double(x)
Futures can also be returned as tail calls from async functions.
from tensorlake.applications import application, function

@function()
def double(x: int) -> int:
    return x * 2

@application()
@function()
async def my_app(x: int) -> int:
    return double.future(x)

Calling sync functions from async functions

Sync Tensorlake functions can be called directly from async functions. The call blocks the asyncio event loop until the sync function completes. No other asyncio tasks can run while the asyncio event loop is blocked. Because of this, calling sync Tensorlake functions directly is an anti-pattern and should be avoided. Use function.future() to call sync functions without blocking the event loop. Call future.run() to start the Future in the background. Use await future to wait for the Future to complete and get its result. If this doesn’t fit the use case, use future.coroutine() to convert the Future into a coroutine that can be used the same way as any coroutine returned by an async Tensorlake function.
from tensorlake.applications import application, function, Future

@function()
def sync_double(x: int) -> int:
    return x * 2

@application()
@function()
async def my_app(x: int) -> int:
    # Simple await of the sync function call.
    return await sync_double.future(x)


@application()
@function()
async def my_app_tail_call(x: int) -> int:
    # Tail call.
    return sync_double.future(x)

@application()
@function()
async def my_app_background_task(x: int) -> int:
    double_task: asyncio.Task = asyncio.create_task(sync_double.future(x).coroutine())
    return await double_task

Calling async functions from sync functions

Sync functions cannot await coroutines. To call an async Tensorlake function from a sync function, use function.future() to create a Future and call .result() to block until it completes.
from tensorlake.applications import application, function

@function()
async def async_double(x: int) -> int:
    return x * 2

@function()
async def async_add(a: int, b: int) -> int:
    return a + b

@application()
@function()
def my_app(x: int) -> int:
    doubled: int = async_double.future(x).result()
    return async_add.future(x, doubled).result()

Wrapping coroutines and asyncio.Tasks into Python objects is not allowed

When passing Tensorlake coroutines or asyncio.Task objects create from them as arguments to function calls, or returning them as tail calls, they cannot be wrapped into other Python objects. For example, returning a list with a coroutine inside is not allowed. Tensorlake will not recognize the coroutine wrapped into the list. This is the same restriction as with Futures.
Map and reduce operations expect iterables, so they recognize coroutines and asyncio.Task objects wrapped into lists and process them as expected.

See Also