On Jan 10, 12:44 pm, Michael Bayer <mike...@zzzcomputing.com> wrote:
> 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.

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.

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.

Reply via email to