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 <[email protected]> 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 [email protected].
>>> To unsubscribe from this group, send email to
>>> [email protected].
>>> 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 [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.
>
--
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.