On Feb 29, 2012, at 2:23 PM, Daniel Nouri wrote:
> Hi!
>
> My problem: I have a base class that maps a 'title' attribute. In a
> subclass I want to turn that title into a descriptor (just like the
> 'hybrid_property' examples really).
>
> I think I want to avoid mapping the attribute as '_title' in the base
> class. Not sure I like the added complexity.
It's not totally ideal but this approach allows developers to make descriptors
totally independently of SQLAlchemy's own descriptor and persistence mechanics.
Here is the issue. You have a SubClass. SQLAlchemy wants to populate the
"title" column of the database with the object's value. But you've redefined
"title" to be something else. Where is the original value of "title"? The
contract of a column-mapped attribute is, that attribute is now the canonical
source of information about that column. If you want your domain object to
override that, you need to figure out where the actual persisted value is
maintained. Just having the developer put in in "_title" is definitely the
simplest way to do it - otherwise you need to interact with behavioral
contracts set up by the attribute system.
Right now the attribute system doesn't have the capability to navigate around a
user-defined descriptor on the same name. It does seem to populate __dict__ at
least but persistence doesn't work:
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
Base= declarative_base()
class A(Base):
__tablename__ = "a"
id = Column(Integer, primary_key=True)
title = Column(String)
class B(A):
@property
def title(self):
return "The title is: " + A.title.__get__(self, "title")
@title.setter
def title(self, value):
A.title.__set__(self, value)
e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)
b = B()
b.title = "hi"
assert b.title == "The title is: hi"
s = Session(e)
s.add(b)
s.commit()
# fails
assert s.scalar("select title from a") == "hi"
# crashes
assert b.title == "The title is: hi"
# all fails
b.title = "this is a new title"
assert b.title == "The title is: this is a new title"
s.commit()
assert b.title == "The title is: this is a new title"
The reason we haven't pursued making the above work is because, IMHO it's
uglier, more verbose, requires that the setter be present even if not needed,
difficult to understand, and brittle, than just using a completely vanilla
@property in conjunction with using cls._column as the source of data for the
column itself. That said, with some tinkering around the attributes and
mapper modules the above could probably work but it would be a little involved
as far as getting it going fully plus test coverage and all that. We'd
probably have to introduce yet another decorator like @instruments or something
like that, along with a firm convention of where "title" comes from (like
perhaps __dict__, or passed in to the function that's decorated). Then we've
also diluted @hybrid_property, when do i use @hybrid and when do I use
@instruments? it's a difficult story to tell.
Sometimes, when I think about these things for a few more hours, some concise
and simple approach does come to mind and the problem is solved; but I have a
real backlog of major fixes/features for 0.7 and 0.8 so I can't guarantee
something is coming forth on this one.
> Also, if I were to
> prefix, I might just as well make all my attributes start with '_',
> because I can't possibly anticipate what deriving classes might want
> to turn into a property and what not.
I don't totally buy that since you can just change the superclass as needed,
I'm not sure why that is a big deal. If you're trying to build
my_special_library.MagicBaseClass, where people install my_special_library
somewhere and just extend from MagicBaseClass, I'm a little skeptical about
that approach. I don't necessarily think it's a good idea that persistence
schemes should be imported by third party libraries.
> Using a 'column_prefix' might
> be the right way, but I guess I'd then have to write hybrid properties
> for all my attributes, and they all wouldn't really do anything
> interesting.
I'd avoid trying to do any of that, it basically means building out your own
instrumentation system. There are ways to do that, in fact with the
InstrumentationManager and all that, but again that's all fringe stuff I don't
necessarily think is worth getting into.
>
> Is there a way to do this in my deriving class without the need to
> touch the super class? I was thinking about implementing a custom
> InstrumentedAttribute, but then I I'm not sure where to hook it in.
I don't know that this is feasible right now without adding to SQLAlchemy
instrumentation internals. I tried playing around with the above example and
calling upon various attribute functions to make it behave perhaps like a
synonym(), but didn't get immediate success. I think there's a way to make
it work but then the decision needs to be made how does this descriptor get
access to the underlying data. How do we reconcile it with the hybrid
approach.
--
You received this message because you are subscribed to the Google Groups
"sqlalchemy" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/sqlalchemy?hl=en.