A lot of libraries use string for attribute names to do some "dynamic" things.
A typical example are SQLAlchemy
[validators](https://docs.sqlalchemy.org/en/13/orm/mapped_attributes.html#simple-validators):
```python
from sqlalchemy.orm import validates
class EmailAddress(Base):
__tablename__ = 'address'
id = Column(Integer, primary_key=True)
email = Column(String)
@validates('email') # Here
def validate_email(self, key, address):
assert '@' in address
return address
```
In this example of SQLAlchemy documentation, email validator use `"email"`
string in order to associate the validator to the `email` column.
However this dynamic things don't play well with all static tools like linters,
but especially IDE (for usages finding, navigation, refactoring, etc.), and of
course type checkers.
This issue could be solved with a "magic attribute" `__attrs__` (name can be
discussed), used the following way:
```python
@dataclass
class Foo:
bar: int
foo = Foo(0)
assert getattr(foo, Foo.__attrs__.bar) == 0
assert getattr(foo, foo.__attrs__.bar) == 0
```
To make it usable in class declaration, the `__attrs__` symbol should be added
to class declaration namespace:
```python
class Foo:
bar: int
@validator(__attrs__.bar)
def validate(self):
...
```
No check would be done by `__attrs__`, they are let to linters which would
integrate this language feature and check for the presence of the attribute in
the class/instance.
And that becomes more interesting because type checkers could use this feature
to check dynamic attribute retrieving.
A special type `Attribute[Owner, T]` could be defined to be used by type
checkers such as `getattr` signature become:
```
# default parameter omitted for concision
def getattr(obj: Owner, attr: Attribute[Owner, T], /) -> T:
...
```
(of course, `getattr` can still be used with strings, as the relation between
`Attribute` and `str` is explained later)
It could allow to add typing to function like the following:
```python
Key = TypeVar("Key", bound=Hashable)
def dict_by_attr(elts: Collection[T], key_attr: Attribute[T, Key]) ->
Mapping[Key, T]:
return {getattr(elt, key_attr): elt for elt in elts}
```
Concerning the implementation of this feature would be very straightforward,
`__attrs__` being defined as a instance of a class:
```python
class Attrs:
__slots__ = []
def __getattribute__(self, name):
return name
```
Thus, `Foo.__attrs__.bar` would be simply equal to `"bar"`; `Attribute` would
be a special type, but backed by a `str`. hence there is no need to modify
`getattr` implementation or existing code using it. `Attribute` type should
then be a kind of `_SpecialForm`, compatible with string by the "relation"
`str` <=> `Attribute[Any, Any]`
The only modifications in the langage would be to add the `Attrs` class, an
`__attrs__` field to `type` and in class definition namespace when it is
evaluated.
The rest of the work (checking the attribute presence, type checking with
`Attribute[Owner, T]`) should be done by external tools (Pycharm, Mypy, etc.)
to handle the feature.
To sum up, `__attrs__` would be a pseudo-static wrapper to attribute name
retrieving in order to benefit of static tools (refactoring, usages finding,
navigation, type checking), with a straightforward and backward compatible
implementation.
And I don't see that as a "niche" feature, because a lot of libraries could
actually benefit from it (and SQLAlchemy, Pydantic, etc. are not a small
libraries).
Joseph
_______________________________________________
Python-ideas mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at
https://mail.python.org/archives/list/[email protected]/message/UMPALRK3E2BL525V4C6KTTN5QV2NW3JY/
Code of Conduct: http://python.org/psf/codeofconduct/