-
-
Notifications
You must be signed in to change notification settings - Fork 374
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conditional initialization of attributes #354
Comments
Aren’t you asking for a magic default value? Uninitialized attributes are basically time bombs. |
@roganov can you state the actual use case instead of the proposed solution? |
I this the described behavior would have no attribute @RonnyPfannschmidt is right though; without explaining the use case, it's hard to know whether this is an oversight of some sort. |
My use case has to do with SQLAlchemy ORM, specifically with relationships. If, for example, relationship Please see following test case to understand the issue. import attr
import sqlalchemy as sa
import typing as t
from sqlalchemy import create_engine
from sqlalchemy.orm import mapper, relationship, sessionmaker
@attr.attrs(cmp=False, auto_attribs=True)
class Parent:
id: int
@attr.attrs(cmp=False, auto_attribs=True)
class Child:
id: int
parent_id: t.Optional[int] = None
parent: t.Optional[Parent] = None
metadata = sa.MetaData()
parents = sa.Table(
'parents',
metadata,
sa.Column('id', sa.Integer, primary_key=True),
)
mapper(Parent, parents)
children = sa.Table(
'children',
metadata,
sa.Column('id', sa.Integer, primary_key=True),
sa.Column('parent_id', sa.Integer, sa.ForeignKey('parents.id'), nullable=False),
)
mapper(Child, children, properties={
'parent': relationship(Parent, uselist=False),
})
engine = create_engine('sqlite://', echo=True)
metadata.create_all(engine)
session = sessionmaker(bind=engine)()
parent = Parent(id=1)
session.add(parent)
session.flush()
# FK constraint violation here
# because `parent` attribute is set to `None` in constructor.
child = Child(id=1, parent_id=1)
session.add(child)
session.flush() |
@hynek It is already possible to create a 'time bomb': @attr.s
class A:
a = attr.ib(init=False)
a = A()
a.a # AttributeError Or is it a bug? |
I believe what @hynek was trying to express was that if you have an uninitialized attribute after you initialize your instance, that this is bad juju. So I'd say your example above is a bug in the program; I do think it's generally unsafe to have |
Whoops wrong button! |
so the basic gist is that you have pairs of attributes that are managed by a 3rd party library, aka a attrs_sqlalchemy addon |
@hynek : i think this use case also directly shows a weakness of the codegen approach, at mapper declaration time the class declaration is done and one is unable to fix the generated code by then |
You call it a weakness, I call it the biggest strength. :) And yes, this calls for an add-on that wraps That said: I don’t know the ORM bits of SQLAlchemy well enough, but I’m gonna claim that it would be cleaner/nicer to be able to write: UNSET = object()
@attr.attrs(cmp=False, auto_attribs=True)
class Child:
id: int
parent_id: t.Optional[int] = UNSET
parent: t.Optional[Parent] = UNSET Than doing voodoo with getattr/hasattr. That gives you much better introspection. Are you sure it’s impossible? |
@hynek correct mapper handling needs integration with real descriptors for stuff like lazy loading of dependencies or deferred loading of properties - i dare to claim that attrs biggest "strength" makes it fundamentally incompatible with reasonable orm's |
@hynek |
While trying to use attrs with sqlalchemy, I came across an issue that in some cases it's needed to initialize an attribute only if it was passed to constructor.
Basically, this code
should translate to this
The text was updated successfully, but these errors were encountered: