Async Class Constructors
Ive noticed that this library uses classmethods to mimic an async init magic method, but by using the new magic method in a clever way we can do it directly in the constructor.
Also, this makes it so you can't have a un-initialized class by initializing the class without using the async classmethod.
Here is an example:
class AsyncNeededToInit:
def __new__(cls, foo):
async def init():
self = super(cls, cls).__new__(cls)
self.foo = foo
self.bar = await async_func()
return self
return init()
Which can be used by:
baz = await AsyncNeededToInit(myfoo)
Could this be added? I could make a PR too.
My main concern here is that mypy doesn't allow this behavior, and I'd very much like everything to be rewritten to use type annotations in a mypy-compliant way.
Instead of doing the whole init in __new__, we can just run the async functions in __new__ and if they return anything pass them to __init__, which is fully type compliant:
class AsyncNeededToInit:
def __new__(cls):
async def init():
foo = await async_func()
self = super(cls, cls).__new__(cls)
self.__init__(foo)
return self
return init()
def __init__(self, foo):
self.foo = foo
Could you add type annotations to that example and share the mypy output?
If I try it, it doesn't look compliant.
/tmp/test.py:4: error: Incompatible return type for "__new__" (returns "Awaitable[None]", but must return a subtype of "AsyncNeededToInit")
We can get the class type-checked by adding annotations to the class and type: ignoring the __new__ since mypy expects it to return the instance of the class. Also ive found the simplest way to do this by using classmethods and making __new__ use them:
from __future__ import annotations
import asyncio
from typing import Awaitable
async def async_func() -> int:
return 123
class AsyncNeededToInit:
foo: int
def __new__(cls) -> Awaitable[AsyncNeededToInit]: # type: ignore
return cls.__async_init()
@classmethod
async def __async_init(cls) -> AsyncNeededToInit:
self = super(cls, cls).__new__(cls) # this must be this way or it will start an infinite loop
self.foo = await async_func()
return self
async def main() -> None:
myclass = await AsyncNeededToInit()
print(myclass.foo)
asyncio.run(main())
Also, for type checking i generally recommend pyright since it is better than mypy. With pyright we can do Awaitable[Self] instead of Awaitable[AsyncNeededToInit] becuase mypy doesn't accept typing_extensions.Self a valid type.