With Python/MyPy type hints, one can use .pyi stubs to keep annotations in separate files to implementations. I am using this functionality to give basic hinting of SQLAlchemy's ORM (more specifically, the flask_sqlalchemy
plugin).
Models are defined like:
class MyModel(db.Model):
id = db.Column()
...
...
where db.Model
is included directly from SQLAlchemy.
They can be queried, for example, by:
MyModel.query.filter({options: options}).one_or_none()
where filter()
returns another Query
, and one_or_none()
returns an instance of MyModel
(or None
, obviously).
The follwing .pyi file successfully hints the above construct, though it is incomplete - there is no way to hint the return type of one_or_none()
.
class _SQLAlchemy(sqlalchemy.orm.session.Session):
class Model:
query = ... # type: _Query
class _Query(sqlalchemy.orm.query.Query):
def filter(self, *args) -> query.Query: ...
def one_or_none(self) -> Any: ...
db = ... # type: _SQLAlchemy
How can one fully and generically hint the above, and hint the return type of one_or_none()
?
My first attempt was to use generics, but it looks like I have no access to the the subtype in question (in the given example, MyModel
). To illustrate a nonworking approach:
from typing import Generic, TypeVar
_T = TypeVar('_T')
class _SQLAlchemy(sqlalchemy.orm.session.Session):
class Model:
def __init__(self, *args, **kwargs):
self.query = ... # type: _Query[self.__class__]
class _Query(Generic[_T], sqlalchemy.orm.query.Query):
def filter(self, *args) -> _Query[_T]: ...
def one_or_none(self) -> _T: ...
db = ... # type: _SQLAlchemy
Is there any way to get this working?
Apologies for the specific and example, but I tried for a while to write this concisely with a generic example and it was never as clear as it is currently (which is possibly still not much!)
Another non-working approach (I'm aware this would have the limitation of having to call myModelInstance.query
... instead of the static MyModel.query
, but even this does not work):
from typing import Generic, TypeVar
_T = TypeVar('_T')
class _SQLAlchemy(sqlalchemy.orm.session.Session):
class Model:
@property
def query(self: _T) -> _Query[_T]: ...
class _Query(Generic[_T], sqlalchemy.orm.query.Query):
def filter(self, *args) -> _Query[_T]: ...
def one_or_none(self) -> _T: ...
db = ... # type: _SQLAlchemy
Type annotation stubs are fortunately now available for that specific issue https://github.com/dropbox/sqlalchemy-stubs
Exact implementation for Query
type annotation is available here: https://github.com/dropbox/sqlalchemy-stubs/blob/master/sqlalchemy-stubs/orm/query.pyi (archive)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With