Re: [sqlalchemy] Re: Relationship (Foreign key) inside composite column

2011-01-12 Thread Michael Bayer

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

2011-01-10 Thread Michael Bayer

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.