Changeset 194

Show
Ignore:
Timestamp:
08/31/07 11:20:27 (6 years ago)
Author:
ged
Message:
  • fixed associable ext for the autodelay mechanism and simplified/cleaned up
    the associable ext in the process. Also made it work with SA 0.4/trunk.
  • fixed associable test so that they don't fail once in a while because the
    order of the has_many element varies.
  • merged the two associable tests.
Location:
elixir/trunk
Files:
1 removed
2 modified

Legend:

Unmodified
Added
Removed
  • elixir/trunk/elixir/ext/associable.py

    r190 r194  
    114114import sqlalchemy as sa 
    115115 
    116 def associable(entity, plural_name=None, lazy=True): 
     116def associable(assoc_entity, plural_name=None, lazy=True): 
    117117    ''' 
    118118    Generate an associable Elixir Statement 
    119119    ''' 
    120     interface_name = entity.table.name 
     120    interface_name = assoc_entity._descriptor.tablename 
    121121    able_name = interface_name + 'able' 
    122      
     122 
    123123    if plural_name: 
    124124        attr_name = "%s_rel" % plural_name 
     
    126126        plural_name = interface_name 
    127127        attr_name = "%s_rel" % interface_name 
    128      
    129     association_table = sa.Table("%s" % able_name, entity._descriptor.metadata, 
    130         sa.Column('%s_id' % able_name, sa.Integer, primary_key=True), 
    131         sa.Column('%s_type' % able_name, sa.String(40), nullable=False), 
    132     ) 
    133      
    134     association_to_table = sa.Table("%s_to_%s" % (able_name, interface_name), entity._descriptor.metadata, 
    135         sa.Column('%s_id' % able_name, sa.Integer, sa.ForeignKey(getattr(association_table.c, '%s_id' % able_name), ondelete="CASCADE"), primary_key=True), 
    136         sa.Column('%s_id' % interface_name, sa.Integer, sa.ForeignKey(entity.table.c.id, ondelete="RESTRICT"), primary_key=True), 
    137     ) 
    138      
    139     entity._assoc_table = association_table 
    140     entity._assoc_to_table = association_to_table 
    141     assoc_entity = entity 
    142     assoc_entity._assoc_relations = [] 
    143      
    144     def finder(key): 
    145         def find_by(cls, value): 
    146             pass 
    147         return find_by 
    148      
    149     for col in entity.table.columns.keys(): 
    150         if col != 'id': 
    151             setattr(entity, 'find_by_%s' % col, finder(col)) 
    152      
     128 
    153129    class GenericAssoc(object): 
    154         def __init__(self, name): 
    155             setattr(self, '%s_type' % able_name, name) 
    156      
     130        def __init__(self, tablename): 
     131            self.type = tablename 
     132    
    157133    class Associable(el.relationships.Relationship): 
    158134        """An associable Elixir Statement object""" 
     
    166142            else: 
    167143                self.name = name 
    168              
    169             assoc_entity._assoc_relations.append(entity) 
    170              
    171             field = type('myfield', (object,), {}) 
    172             field.colname = '%s_assoc_id' % interface_name 
    173             field.deferred = False 
    174             field.primary_key = False 
    175             # CHANGE: I had to change the second argument from None to sa.Integer 
    176             # in order to get associable working with the versioning extension... 
    177             # Ben: was this the right thing to do? 
    178             field.column = sa.Column('%s_assoc_id' % interface_name, sa.Integer,  
    179                                   sa.ForeignKey('%s.%s_id' % (able_name, able_name))) 
    180             entity._descriptor.add_field(field) 
    181             entity._descriptor.relationships[able_name] = self 
    182              
    183             def select_by(cls, **kwargs): 
    184                 return cls.query().join(attr_name).join('targets').filter_by(**kwargs).list() 
    185             setattr(entity, 'select_by_%s' % self.name, classmethod(select_by)) 
    186              
    187             def select(cls, *args, **kwargs): 
    188                 return cls.query().join(attr_name).join('targets').filter(*args, **kwargs).list() 
    189             setattr(entity, 'select_%s' % self.name, classmethod(select)) 
    190          
    191         def setup(self): 
    192             self.create_properties() 
    193             return True 
     144            self.entity._descriptor.relationships[able_name] = self 
     145 
     146        def create_keys(self, pk): 
     147            field = el.Field(sa.Integer, sa.ForeignKey('%s.id' % able_name), 
     148                          colname='%s_assoc_id' % interface_name) 
     149            self.entity._descriptor.add_field(field) 
     150 
     151        def create_tables(self): 
     152            if not hasattr(assoc_entity, '_assoc_table'): 
     153                association_table = sa.Table("%s" % able_name, assoc_entity._descriptor.metadata, 
     154                    sa.Column('id', sa.Integer, primary_key=True), 
     155                    sa.Column('type', sa.String(40), nullable=False), 
     156                ) 
     157                 
     158                association_to_table = sa.Table("%s_to_%s" % (able_name, interface_name), assoc_entity._descriptor.metadata, 
     159                    sa.Column('assoc_id', sa.Integer, sa.ForeignKey(association_table.c.id, ondelete="CASCADE"), primary_key=True), 
     160                    #FIXME: this assumes a single id col 
     161                    sa.Column('%s_id' % interface_name, sa.Integer, sa.ForeignKey(assoc_entity.table.c.id, ondelete="RESTRICT"), primary_key=True), 
     162                ) 
     163 
     164                assoc_entity._assoc_table = association_table 
     165                assoc_entity._assoc_to_table = association_to_table 
     166 
     167        def after_mapper(self): 
     168            if not hasattr(assoc_entity, '_assoc_mapper'): 
     169                assoc_entity._assoc_mapper = sa.orm.mapper(GenericAssoc, 
     170                        assoc_entity._assoc_table, properties={ 
     171                    'targets': sa.orm.relation(assoc_entity, 
     172                        secondary=assoc_entity._assoc_to_table, 
     173                                           lazy=lazy, backref='associations', 
     174                                           order_by=assoc_entity.mapper.order_by) 
     175                }) 
    194176         
    195177        def create_properties(self): 
    196178            entity = self.entity 
    197             entity.mapper.add_property(attr_name, sa.relation(GenericAssoc, lazy=self.lazy, 
     179            entity.mapper.add_property(attr_name, sa.orm.relation(GenericAssoc, lazy=self.lazy, 
    198180                                       backref='_backref_%s' % entity.table.name)) 
    199             entity.mapper.add_property(self.name, sa.synonym(attr_name)) 
     181            # this is strange! self.name is both set via mapper synonym and  
     182            # the python property 
     183            entity.mapper.add_property(self.name, sa.orm.synonym(attr_name)) 
     184 
    200185            if self.uselist: 
    201186                def get(self): 
     
    220205                setattr(entity, self.name, property(get, set)) 
    221206 
    222     sa.mapper(GenericAssoc, association_table, properties={ 
    223         'targets': sa.relation(entity, secondary=association_to_table, 
    224                                lazy=lazy, backref='association', 
    225                                order_by=entity.mapper.order_by) 
    226     }) 
     207            # add helper methods 
     208            def select_by(cls, **kwargs): 
     209                return cls.query().join([attr_name, 'targets']).filter_by(**kwargs).all() 
     210            setattr(entity, 'select_by_%s' % self.name, classmethod(select_by)) 
     211             
     212            def select(cls, *args, **kwargs): 
     213                return cls.query().join([attr_name, 'targets']).filter(*args, **kwargs).all() 
     214            setattr(entity, 'select_%s' % self.name, classmethod(select)) 
     215 
    227216    return Statement(Associable) 
  • elixir/trunk/tests/test_associable.py

    r190 r194  
    77from elixir.ext.associable import associable 
    88 
    9 class Address(Entity): 
    10     has_field('street', String(130)) 
    11     has_field('city', String) 
    12     using_options(shortnames=True) 
    139 
    14  
    15 class Comment(Entity): 
    16     has_field('id', Integer, primary_key=True) 
    17     has_field('name', Unicode) 
    18     has_field('text', String) 
    19  
    20 is_addressable = associable(Address, 'addresses') 
    21 is_commentable = associable(Comment, 'comments') 
    22  
    23 class Person(Entity): 
    24     has_field('id', Integer, primary_key=True) 
    25     has_field('name', Unicode) 
    26     has_many('orders', of_kind='Order') 
    27     using_options(shortnames=True) 
    28     is_addressable() 
    29     is_commentable() 
    30  
    31 class Order(Entity): 
    32     has_field('order_num', Integer, primary_key=True) 
    33     has_field('item_count', Integer) 
    34     belongs_to('person', of_kind='Person') 
    35     using_options(shortnames=True) 
    36     is_addressable('address', uselist=False) 
    37  
    38 class Foo(Entity): 
    39     pass 
    40  
    41 class Bar(Entity): 
    42     pass 
    43  
    44 is_fooable = associable(Foo) 
    45 is_barable = associable(Bar) 
    46  
    47 class Quux(Entity): 
    48     is_fooable() 
    49     is_barable() 
     10def setup(self): 
     11#    metadata.bind = create_engine('sqlite:///', echo=True) 
     12    metadata.bind = 'sqlite:///' 
    5013 
    5114class TestOrders(object): 
    52     def setup(self): 
    53         engine = create_engine('sqlite:///', echo=True) 
    54         metadata.connect(engine) 
    55         create_all() 
     15    def teardown(self): 
     16        cleanup_all(True) 
    5617     
    57     def teardown(self): 
    58         cleanup_all() 
    59      
     18    def test_empty(self): 
     19        class Foo(Entity): 
     20            pass 
     21 
     22        class Bar(Entity): 
     23            pass 
     24 
     25        is_fooable = associable(Foo) 
     26        is_barable = associable(Bar) 
     27 
     28        class Quux(Entity): 
     29            is_fooable() 
     30            is_barable() 
     31 
     32        setup_all(True) 
     33 
    6034    def test_basic(self): 
     35        class Address(Entity): 
     36            has_field('street', String(130)) 
     37            has_field('city', String) 
     38            using_options(shortnames=True) 
     39 
     40        class Comment(Entity): 
     41            has_field('id', Integer, primary_key=True) 
     42            has_field('name', Unicode) 
     43            has_field('text', String) 
     44 
     45        is_addressable = associable(Address, 'addresses') 
     46        is_commentable = associable(Comment, 'comments') 
     47 
     48        class Person(Entity): 
     49            has_field('id', Integer, primary_key=True) 
     50            has_field('name', Unicode) 
     51            has_many('orders', of_kind='Order') 
     52            using_options(shortnames=True) 
     53            is_addressable() 
     54            is_commentable() 
     55 
     56        class Order(Entity): 
     57            has_field('order_num', Integer, primary_key=True) 
     58            has_field('item_count', Integer) 
     59            belongs_to('person', of_kind='Person') 
     60            using_options(shortnames=True) 
     61            is_addressable('address', uselist=False) 
     62 
     63        setup_all(True) 
     64 
    6165        home = Address(street='123 Elm St.', city='Spooksville') 
    6266        work = Address(street='243 Hooper st.', city='Cupertino') 
     
    7478        # Queries using the added helpers 
    7579        people = Person.select_by_addresses(city='Cupertino') 
    76         assert len(people) > 0 
    77         assert people[0].addresses[1].street == '243 Hooper st.' 
    78         assert people[0].addresses[0].street == '123 Elm St.' 
     80        assert len(people) == 1 
     81 
     82        streets = [adr.street for adr in people[0].addresses] 
     83        assert '243 Hooper st.' in streets 
     84        assert '123 Elm St.' in streets 
    7985         
    80         peopl = Person.select_addresses(and_(Address.c.street=='132 Elm St', 
    81                                         Address.c.city=='Smallville')) 
    82         assert len(people) > 0 
    83          
     86        people = Person.select_addresses(and_(Address.c.street=='132 Elm St', 
     87                                              Address.c.city=='Smallville')) 
     88        assert len(people) == 0 
     89 
     90    def test_with_forward_ref(self): 
     91        class Checkout(Entity): 
     92            belongs_to('by', of_kind='Villian', ondelete='cascade') 
     93            has_field('stamp', DateTime) 
     94 
     95        can_checkout = associable(Checkout, 'checked_out') 
     96 
     97        class Article(Entity): 
     98            has_field('title', Unicode) 
     99            has_field('content', Unicode) 
     100            can_checkout('checked_out_by', uselist=False) 
     101            using_options(tablename='article') 
     102 
     103        class Villian(Entity): 
     104            has_field('name', Unicode) 
     105            using_options(tablename='villian') 
     106 
     107        setup_all(True) 
     108 
     109        art = Article(title='Hope Soars') 
     110 
     111        objectstore.flush() 
     112        objectstore.clear()