On Jan 12, 2011, at 10:09 PM, Arturo Sevilla wrote: > > What I finally did is kind of black magic (not very proud of it) > though it works for what I needed. Is kind of expanding the idea of > the read-only property but making it writable by injecting what I > called an "updator" which basically transforms my Address class as a > proxy to the User class which is mapped withouth composite columns.
I have comments here. First is, all those "_check_for_xxx()" methods are pretty noisy. I'd recommend a memoized attribute. We use this decorator in literally hundreds of places: class memoized_property(object): """A read-only @property that is only evaluated once.""" def __init__(self, fget, doc=None): self.fget = fget self.__doc__ = doc or fget.__doc__ self.__name__ = fget.__name__ def __get__(self, obj, cls): if obj is None: return self obj.__dict__[self.__name__] = result = self.fget(obj) return result then all of your "create upon access" descriptors are simple: class MyClass(object): @memoized_property def _updators(self): return { 'home':{}, 'billing':{} } @memoized_property def _home_address(self): return Address(...) Also the "updator" callables are a bit heavy handed. You could just as well just transfer attributes in one call based on a 'prefix' like 'home_': def _transfer(source, dest, prefix, keys): for key in keys: setattr(dest, '%s_%s' % (prefix, key), getattr(source, key)) _transfer(some_address, some_user, 'home_', ('street', 'city', 'zip')) Also, composites in 0.7 work almost the way you want anyway - the composite proxies to mapped attributes using events to manage things - the mapper and other ORM internals know nothing about the composite anymore. Ticket 2024 will make a small adjustment so that the composite() can receive any mapped attribute name, not just a column. > > The mapper remains almost the same except for the composite columns: > [...] > '_home_address_street': user.c.HomeAddress_Street, > '_home_address_city': orm.relationship( > City, > uselist=False, > primaryjoin=user.c.HomeAddress_City == city.c.ID > ), > '_home_address_zipcode': user.c.HomeAddress_ZipCode, > [...] > The same for my billing address "property" > > And in my User class: > class User(object): > /* Many other attributes here */ > > def _make_updators(self): > self._updators = {} > def create_updator(obj, field): > def _updator(value): > setattr(obj, field, value) > return _updator > > self._updators['home'] = { > 'street': create_updator(self, '_home_address_street'), > 'city': create_updator(self, '_home_address_city'), > 'zipcode': create_updator(self, '_home_address_zipcode') > } > self._updators['billing'] = { > 'street': create_updator(self, '_billing_address_street'), > 'city': create_updator(self, '_billing_address_city'), > 'zipcode': create_updator(self, '_biling_address_zipcode') > } > > def _run_updators(self, address, type_): > if address is None: > street = city = zipcode = None > else: > street = address.street > city = address.city > zipcode = address.zipcode > > self._updators[type_]['street'](street) > self._updators[type_]['city'](city) > self._updators[type_]['zipcode'](zipcode) > > def _check_for_updators(self): > if not hasattr(self, '_updators'): > self._make_updators() > > def _check_for_property(self, name): > if not hasattr(self, name): > setattr(self, name, None) > > @property > def home_address(self): > # We must check for instances created by queries instead of > # usual __init__ > self._check_for_updators() > self._check_for_property('_home_address') > if self._home_address is None: > self._home_address = Address( > self._home_address_street, > self._home_address_city, > self._home_address_zipcode, > ) > self._home_address._updator = self._updators['home'] > return self._home_address > > @home_address.setter > def home_address(self, value): > self._check_for_updators() > self._check_for_property('_home_address') > backup = self._home_address > if value is self._billing_address: > # shallow clone > value = copy.copy(value) > self._home_address = self._get_attribute('home_address', > value, > type=Address) > # if it gets here then no errors were raised > if backup is not None: > backup._updator = None > self._home_address._updator = self._updators['home'] > self._run_updators(self._home_address, 'home') > > self._get_attribute just validates the input. The _updators array > works as a register for "updator" methods which run when home_address > is set or when an attribute or property in Address is changed. The > Address class in this case has obviously street, city, and zipcode > properties: > > class Address(object): > /* __init__ and other stuff */ > @property > def street(self): > return self._street > > @street.setter > def street(self, value): > self._street = self._get_attribute('street', value, > max_length=300) > if self._updator is not None: > self._updator['street'](self._street) > > By making self._updator optional an Address object is not necessarily > bound to a User object. > >> >>> Thanks! >> >>> On Jan 10, 10:57 am, Michael Bayer <mike...@zzzcomputing.com> wrote: >>>> On Jan 10, 2011, at 1:10 PM, Arturo Sevilla wrote: >> >>>>> Hello, >> >>>>> I'm trying to do the following: >> >>>>> 'home': orm.composite( >>>>> Address, >>>>> user.c.HomeAddress_Street, >>>>> # The following column is a UUID but is a foreign key to a >>>>> mapped >>>>> # table in SQLAlchemy, ideally would be to say >>>>> relationship(City) >>>>> # and specify the column >>>>> user.c.HomeAddress_City, >>>>> user.c.HomeAddress_ZipCode >>>>> ) >> >>>> I don't understand what this means, i think you're missing some context >>>> here that would make this easier for others to understand, are there >>>> *other* columns on "user" that also reference "city" ? I don't >>>> understand what "relationship() and specify the column" means. >>>> relationship() accepts a "primaryjoin" expression for this purpose, its in >>>> the docs. If you have "HomeAddress" and "WorkAddress", you'd make two >>>> relationships. >> >>>> I also have an intuition, since it seems like you want a variant on >>>> relationship(), that composite is definitely not what you want to be >>>> using, it pretty much never is, but a picture of all the relevant columns >>>> here would be helpful. You definitely cannot use a column that's part of >>>> a relationship() in a composite() as well and manipulating foreign keys >>>> through composites is not the intended usage. >> >>>>> As such I tried a simple if isinstance(city, uuid.UUID): city = >>>>> City.load(city), where this method is just: >>>>> @classmethod >>>>> def load(cls, id): >>>>> session = Session() >>>>> obj = session.query(cls).get(id) >>>>> session.close() >>>>> return obj >> >>>>> However during session.commit() it appears that my Session class (made >>>>> with scoped_session(sessionmaker()) ) goes to None (and crashes). >>>>> Is this an expected behavior? >> >>>> no, the scoped_session object always returns either an existing or a new >>>> Session and is never None. >> >>> -- >>> You received this message because you are subscribed to the Google Groups >>> "sqlalchemy" group. >>> To post to this group, send email to sqlalch...@googlegroups.com. >>> To unsubscribe from this group, send email to >>> sqlalchemy+unsubscr...@googlegroups.com. >>> For more options, visit this group >>> athttp://groups.google.com/group/sqlalchemy?hl=en. > > -- > You received this message because you are subscribed to the Google Groups > "sqlalchemy" group. > To post to this group, send email to sqlalch...@googlegroups.com. > To unsubscribe from this group, send email to > sqlalchemy+unsubscr...@googlegroups.com. > For more options, visit this group at > http://groups.google.com/group/sqlalchemy?hl=en. > -- You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To post to this group, send email to sqlalch...@googlegroups.com. To unsubscribe from this group, send email to sqlalchemy+unsubscr...@googlegroups.com. For more options, visit this group at http://groups.google.com/group/sqlalchemy?hl=en.