Changeset 194
- Timestamp:
- 08/31/07 11:20:27 (6 years ago)
- Location:
- elixir/trunk
- Files:
-
- 1 removed
- 2 modified
-
elixir/ext/associable.py (modified) (4 diffs)
-
tests/test_associable.py (modified) (2 diffs)
-
tests/test_associable_imports.py (deleted)
Legend:
- Unmodified
- Added
- Removed
-
elixir/trunk/elixir/ext/associable.py
r190 r194 114 114 import sqlalchemy as sa 115 115 116 def associable( entity, plural_name=None, lazy=True):116 def associable(assoc_entity, plural_name=None, lazy=True): 117 117 ''' 118 118 Generate an associable Elixir Statement 119 119 ''' 120 interface_name = entity.table.name120 interface_name = assoc_entity._descriptor.tablename 121 121 able_name = interface_name + 'able' 122 122 123 123 if plural_name: 124 124 attr_name = "%s_rel" % plural_name … … 126 126 plural_name = interface_name 127 127 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 153 129 class GenericAssoc(object): 154 def __init__(self, name):155 se tattr(self, '%s_type' % able_name, name)156 130 def __init__(self, tablename): 131 self.type = tablename 132 157 133 class Associable(el.relationships.Relationship): 158 134 """An associable Elixir Statement object""" … … 166 142 else: 167 143 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 }) 194 176 195 177 def create_properties(self): 196 178 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, 198 180 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 200 185 if self.uselist: 201 186 def get(self): … … 220 205 setattr(entity, self.name, property(get, set)) 221 206 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 227 216 return Statement(Associable) -
elixir/trunk/tests/test_associable.py
r190 r194 7 7 from elixir.ext.associable import associable 8 8 9 class Address(Entity):10 has_field('street', String(130))11 has_field('city', String)12 using_options(shortnames=True)13 9 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() 10 def setup(self): 11 # metadata.bind = create_engine('sqlite:///', echo=True) 12 metadata.bind = 'sqlite:///' 50 13 51 14 class 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) 56 17 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 60 34 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 61 65 home = Address(street='123 Elm St.', city='Spooksville') 62 66 work = Address(street='243 Hooper st.', city='Cupertino') … … 74 78 # Queries using the added helpers 75 79 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 79 85 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()
