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.

Reply via email to