5

Not sure, that this is possible, but still

I have default async FastAPI application, where I have sync function

@classmethod
def make_values(
        cls,
        records: list,
        symbol: str = None,
) -> list:
    values = []
    for record in records:
        new_row = SomePydanticModel(
            timestamp=record._mapping.get("time"),
            close=record._mapping.get("close"),
            value=record._mapping.get("value"),
        )
        if symbol in EXTRA_SYMBOLS:
            new_row = split_row(new_row, symbol)
        values.append(new_row)

    return values

The problem is that split_row is async function, that calls other async functions

async def split_row(
        row,
        symbol,
):
    adjusted_datetime = datetime.timestamp(datetime.strptime("01/01/19", "%m/%d/%y"))
    splits = await get_splits(symbol)
    # some big part with business logic with other async calls
    return row

Currently I get coroutine in new_row variable. Is there anyway to get the result of split_row?

3
  • I would suggest having a look at this answer, in order to better understand the concept of async/await and how FastAPI/Starlette works under the hood.
    – Chris
    Commented Jan 26, 2024 at 15:38
  • One way to do this is to use loop.run_in_executor() and run the async function using asyncio.run() in a separate Thread, as shown in this answer. Still, I would highly suggest thoroughly reading the linked answer above.
    – Chris
    Commented Jan 26, 2024 at 15:45
  • As your code stands, values is a list of coroutines. Instead of declaring make_values async def, you could pass values up the call stack until you reach an async function. Unless make_values is a callback, there must be an async function somewhere up the stack (since you have a running event loop). You could then await the list of values at that point. No matter what you do, somewhere you have to await each element of values. Because split_row is async, it cannot return a meaningful result without yielding to the event loop at least once. Commented Jan 27, 2024 at 5:08

2 Answers 2

5

One possible solution might be using Asyncer (which is on top of Anyio) introduced by Sebastian Ramirez the creator of the FastAPI. In the documentation, there's a section "Call Async Code from Sync Code".

Here's the use case:

import time
import anyio
from asyncer import asyncify, syncify

async def do_async_work(name: str):
    await anyio.sleep(1)
    return f"Hello, {name}"

def do_sync_work(name: str):
    time.sleep(1)
    message = syncify(do_async_work)(name=name)
    return message

async def main():
    message = await asyncify(do_sync_work)(name="World")
    print(message)

anyio.run(main) 

[NOTE]:

  • As an advantage, it's also possible to use asyncio.run(main()) instead of anyio.run(main) at the end.
-1

You can use an event loop to run the asynchronous function.

import asyncio

@classmethod
def make_values(cls, records: list, symbol: str = None) -> list:
    values = []
    for record in records:
        new_row = SomePydanticModel(
            timestamp=record._mapping.get("time"),
            close=record._mapping.get("close"),
            value=record._mapping.get("value"),
        )
        if symbol in EXTRA_SYMBOLS:
            loop = asyncio.get_event_loop()
            new_row = loop.run_until_complete(split_row(new_row, symbol))
        values.append(new_row)

    return values
3
  • Nope. RuntimeError: this event loop is already running. The code is already running in async FastAPI application
    – Headmaster
    Commented Jan 26, 2024 at 13:57
  • Oh, Yes. Because fast API is already managing the event loop. Then maybe you can try to make the make_values method asynchronous. Commented Jan 26, 2024 at 14:01
  • yeah, seems like it's the only working idea:( I'm just asking, maybe there is any other way
    – Headmaster
    Commented Jan 26, 2024 at 14:02

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.