Re: [sqlalchemy] Re: Relationship (Foreign key) inside composite column
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,
Re: [sqlalchemy] Re: Relationship (Foreign key) inside composite column
On Jan 10, 2011, at 2:21 PM, Arturo Sevilla wrote: Hello, Thanks again for the quick reply! I tried to isolate all the mapper columns to try to make it less confusing, now I know that was not a good idea. orm.mapper(User, user, properties={ 'id': user.c.ID, '_first_name': user.c.FirstName, '_middle_name': user.c.MiddleName, '_last_name': user.c.LastName, '_salutation': user.c.Salutation, '_username': user.c.Username, '_password': user.c.Password, '_personal_email': user.c.PersonalEmail, '_bussiness_email': user.c.BussinessEmail, '_birth_date': user.c.BirthDate, '_profession': user.c.Profession, '_contact': orm.composite(ContactInformation, user.c.HomePage, user.c.Telephone, user.c.Fax, user.c.Nextel), '_home_address': orm.composite( Address, user.c.HomeAddress_Street, user.c.HomeAddress_City, user.c.HomeAddress_ZipCode ), '_billing_address': orm.composite( Address, user.c.BillingAddress_Street, user.c.BillingAddress_City, user.c.BillingAddress_ZipCode ), 'active': user.c.Active }) I made the Address class because in some places it makes it easy to encapsulate this information. I don't have any relationship() as you can see in the mapping, that's what I would want but within the composite object. There is no address table in the schema as my intention was to avoid a join (city in this case would have been lazy loaded). In order to get this behavior, do I must then create an address table and join it through a primaryjoin in relationship()? having a separate address table would certainly be preferable, sure. the composite approach would be viewonly: @property def home_address(self): return Address(self.home_street, self.home_city, self.home_zip) in 0.7 it works pretty much in this way. 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 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.