Skip to content
This repository has been archived by the owner on May 24, 2022. It is now read-only.

Redesigning aiodine? #26

Open
florimondmanca opened this issue Sep 11, 2019 · 0 comments
Open

Redesigning aiodine? #26

florimondmanca opened this issue Sep 11, 2019 · 0 comments

Comments

@florimondmanca
Copy link
Member

florimondmanca commented Sep 11, 2019

Coming back at aiodine after a few months off, the initial design and API don't seem as optimal as they could be from a developer experience point of view.

The most serious problems I see are:

  • Lack of editor support, mainly because @consumer modifies the signature of its decorated function (Consumer API and signature modification #23)
  • Complexity: there are a lot of concepts, e.g. "provider", "consumer", "store", "provider freezing", etc. All of them seem overly complicated and potentially leaky abstractions.

The path that FastAPI took for its dependency injection mechanism seems to solve both of these problems. I'm curious at whether we could replicate the design here, although that should probably mean aiodine is spun up as a different library altogether (code-named asyncdeps below).

import asyncio
from asyncdeps import depends

async def get_hello() -> str:
    return "Hello"

async def show(hello: str = depends(get_hello)) -> None:
    print(hello)

asyncio.run(show())

We should make sure depends works well with static type checkers, e.g. by casting its return value to the return value type of its function.

If we go for this style, there's one question to tackle: can we keep scopes with this API?

My intuition is that we can keep the idea of caching dependencies for reuse (which is what the "session" scope is all about), but propose a more straight-forward API.

The default behavior would be to evaluate dependencies on every call (equivalent of the current "function" scope). The behavior of the "session" scope could be obtained via an @cached marker, e.g.:

from asyncdeps import depends, cached

@cached
async def get_hello() -> str:
    print("Evaluating hello…")
    return "Hello"

async def show(hello: str = depends(get_hello)) -> None:
    print(hello)

async def main():
    await show()  # Prints "Evaluating hello…"
    await show()  # Nothing printed (hello already evaluated in this scope)

Besides, we can force-turn on/off caching on callees, e.g. depends(get_hello, cached=False).

Combined with a generator-based API, this would allow us to support the database provisioning use case for async web frameworks, e.g. in Bocadillo:

from bocadillo import App
from asyncdeps import depends, cached
from databases import Database

app = App()

@cached
async def get_db() -> Database:
    async with Database("sqlite://:memory:") as db:
        yield db

@app.route("/")
async def home(req, res, message: Database = depends(get_db)):
    res.text = message

Overall, I think this API would greatly reduce the scope and footprint of this library, as well as result in much simpler usage patterns for users.

I may sketch out a PoC for async-only dependencies (w/o generator support) to see how this could look like.

@florimondmanca florimondmanca changed the title Redesigning aiodine Redesigning aiodine? Sep 11, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant