How can I annotate class variables and instance variables with different types?
I want means to annotate class variables and instance variables with distinct types. I'm using the well-known ORM library SQLAlchemy. With entity declaration, class variables are representations of table columns, while instance variables are the actual values of each record. Thus, because of this behaviour, I cannot write fully-typed code. For example, with the class declaration shown below: ``` class Entity(Base): __tablename__ = 'entity' column_1 = Column('column_1', BIGINT(unsigned=True), primary_key=True, autoincrement=True) ``` The actual type of the class variable `Entity.column_1` is `Column`, but on the other hand, the instance variable `Entity().column_1` is `int`. As far as I know, today, we cannot annotate variables like `Entity.column_1` with correct types, but I want to achieve those codes fully typed.
You can annotate the class variable as a class that implements the descriptor protocol (`__get__` and `__set__` methods). That way, it can return a different type when accessed through an instance of `Entity`.
To add to this, if you are using mypy you can use this package: https://github.com/dropbox/sqlalchemy-stubs If you are using pyright or any other static type checker, you probably want to do something similar to what this package ( https://github.com/sbdchd/django-types) does to django. An example for `IntegerField`: https://github.com/sbdchd/django-types/blob/main/django-stubs/db/models/fiel... Note that different from django, which define a different field to each type (i.e. CharField for str, IntegerField for int, etc), sqlalchemy uses a parameter for that. So you probably want to define a single `Column` and an `overload` for its `__init__`/`__new__` methods for each possible column typetype to return a different descriptor to match the configuration, probably also taking `nullable` into consideration to type it as `<type> | None`. On Sun, Mar 5, 2023 at 12:46 PM Eric Traut <eric@traut.com> wrote:
You can annotate the class variable as a class that implements the descriptor protocol (`__get__` and `__set__` methods). That way, it can return a different type when accessed through an instance of `Entity`. _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: hackedbellini@gmail.com
-- Thiago Bellini Ribeiro | https://bellini.dev
I already know those packages, but thanks. Now I know how to use `__get__` overload to annotate class and instance variables with distinct types. Thanks, Eric. ```python from typing import Generic, Self, Type, TypeVar, overload TClassVar = TypeVar('TClassVar') TInstanceVar = TypeVar('TInstanceVar') class VariableMember(Generic[TClassVar, TInstanceVar]): @overload def __get__(self: 'VariableMember', obj: Self, obj_type: Type[Self]) -> TInstanceVar: ... @overload def __get__(self: 'VariableMember', obj: None, obj_type: Type[Self]) -> TClassVar: ... class Foo: a: VariableMember[int, str] = 5 # typechecker OK Foo.a = 10 # typechecker NG Foo.a = '10' # typechecker OK Foo().a = '10' # typechecker NG Foo().a = 10 ```
I tried this way, but I found it cannot make `Class.attribute` assignable. With this solution, in the example below, pyright reports an incompatible type error in line 3. ```python class Entity(Base): __tablename__ = 'entity' column_1: AttrAnnotation[Column, int] = Column('column_1', BIGINT(unsigned=True), primary_key=True, autoincrement=True) # ^ Expression of type "Column[int]" cannot be assigned to declared type "AttrAnnotation[Column[Unknown], int]" # "Column[int]" is incompatible with "AttrAnnotation[Column[Unknown], int]" ``` Does anyone still have any idea?
In django-stubs we use descriptors for this task: https://github.com/typeddjango/django-stubs/blob/5092bdf951edb6ef3ba23b28c7e... Simple repro: https://mypy-play.net/?mypy=latest&python=3.11&gist=019398b283c7000ec842a1f4963cc481 ср, 22 мар. 2023 г. в 10:47, Chihiro Sakai <sakaic2003@gmail.com>:
I tried this way, but I found it cannot make `Class.attribute` assignable. With this solution, in the example below, pyright reports an incompatible type error in line 3.
```python class Entity(Base): __tablename__ = 'entity' column_1: AttrAnnotation[Column, int] = Column('column_1', BIGINT(unsigned=True), primary_key=True, autoincrement=True) # ^ Expression of type "Column[int]" cannot be assigned to declared type "AttrAnnotation[Column[Unknown], int]" # "Column[int]" is incompatible with "AttrAnnotation[Column[Unknown], int]" ```
Does anyone still have any idea? _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: n.a.sobolev@gmail.com
No, this is not a solution I actually want. ```python class Entity(Base): __tablename__ = 'entity' column_1: 'IWantCorrectTypeHere' = Column('column_1', BIGINT(unsigned=True), ...) ``` I want a proper type for `IWantCorrectTypeHere`, while `Field` isn't. The requirement of the type is: 1. No errors shouldn't be reported while initializing the class variable with the `Column` instance. 2. Incorrect type error should be reported while initializing/assigning the class variable with an `int` or any other value, not a `Column` instance. 3. No errors shouldn't be reported while assigning/reading the instance variable with an `int` value. 4. Incorrect type error should be reported while assigning/reading the class variable with a `Column` instance or any other value, not an `int` value. Obviously, descriptors like properties never handle assigning class variables' values themselves, so I believe those descriptors aren't proper for these cases.
I think it is better to move this discussion to https://github.com/python/typing/discussions :) чт, 23 мар. 2023 г. в 14:55, Chihiro Sakai <sakaic2003@gmail.com>:
No, this is not a solution I actually want.
```python class Entity(Base): __tablename__ = 'entity' column_1: 'IWantCorrectTypeHere' = Column('column_1', BIGINT(unsigned=True), ...) ```
I want a proper type for `IWantCorrectTypeHere`, while `Field` isn't. The requirement of the type is:
1. No errors shouldn't be reported while initializing the class variable with the `Column` instance. 2. Incorrect type error should be reported while initializing/assigning the class variable with an `int` or any other value, not a `Column` instance. 3. No errors shouldn't be reported while assigning/reading the instance variable with an `int` value. 4. Incorrect type error should be reported while assigning/reading the class variable with a `Column` instance or any other value, not an `int` value.
Obviously, descriptors like properties never handle assigning class variables' values themselves, so I believe those descriptors aren't proper for these cases. _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: n.a.sobolev@gmail.com
participants (4)
-
Chihiro Sakai
-
Eric Traut
-
Thiago Bellini Ribeiro
-
Никита Соболев