Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can a runtime object type be used as a generic type hint parameter?

Introduction

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

The Question

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!)

Edit

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
like image 296
Jarrad Avatar asked Feb 08 '16 00:02

Jarrad


1 Answers

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)

like image 169
michaeldel Avatar answered Oct 26 '22 22:10

michaeldel