Changeset 356
- Timestamp:
- 07/08/08 11:07:14 (4 years ago)
- Location:
- elixir/trunk
- Files:
-
- 6 modified
-
CHANGES (modified) (1 diff)
-
TODO (modified) (1 diff)
-
elixir/__init__.py (modified) (2 diffs)
-
elixir/entity.py (modified) (8 diffs)
-
elixir/relationships.py (modified) (8 diffs)
-
tests/test_o2m.py (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
elixir/trunk/CHANGES
r355 r356 23 23 - Added on_reconstitute event/method decorator. Only works with SA 0.5. 24 24 - Added support for viewonly relationships (OneToMany and OneToOne). 25 - Added support for filtered OneToMany relationships. Produce viewonly 26 relations. See source:elixir/trunk/tests/test_o2m.py for an example. 25 27 26 28 Changes: -
elixir/trunk/TODO
r347 r356 84 84 instead of the acts_as_taggable Jonathan demonstrated 85 85 86 - implement something like: 87 88 class A(Entity): 89 has_many('b', of_kind='B') 90 has_many('b_filtered', of_kind='B', filter=lambda c: c.extra < 10) 91 class B(Entity): 92 has_field('extra', Integer) 93 belongs_to('a', of_kind='A') 94 95 this is the more or less the same as what is described at: 86 - support custom selectable on relationships, as is done at: 96 87 97 88 http://spyced.blogspot.com/2007/01/why-sqlalchemy-impresses-me.html -
elixir/trunk/elixir/__init__.py
r349 r356 165 165 Optionally drops the tables. 166 166 ''' 167 session.rollback() 168 session.clear() 169 session.close() 170 171 cleanup_entities(entities) 172 173 sqlalchemy.orm.clear_mappers() 174 entities._entities = {} 175 del entities[:] 176 167 177 if drop_tables: 168 178 drop_all(*args, **kwargs) 169 170 cleanup_entities(entities)171 179 172 180 for md in metadatas: … … 174 182 metadatas.clear() 175 183 176 session.close()177 184 178 sqlalchemy.orm.clear_mappers()179 del entities[:]180 -
elixir/trunk/elixir/entity.py
r350 r356 22 22 from elixir.properties import Property 23 23 24 DEBUG = False 24 25 25 26 __doc_all__ = ['Entity', 'EntityMeta'] 26 27 27 28 28 … … 454 454 (col.key, table.name)) 455 455 table.append_column(col) 456 if DEBUG: 457 print "table.append_column(%s)" % col 456 458 457 459 def add_constraint(self, constraint): … … 478 480 if mapper: 479 481 mapper.add_property(name, property) 482 if DEBUG: 483 print "mapper.add_property('%s', %s)" % (name, repr(property)) 480 484 481 485 def add_mapper_extension(self, extension): … … 497 501 return None 498 502 499 def get_inverse_relation(self, rel, reverse=False):503 def get_inverse_relation(self, rel, check_reverse=True): 500 504 ''' 501 505 Return the inverse relation of rel, if any, None otherwise. … … 504 508 matching_rel = None 505 509 for other_rel in self.relationships: 506 if other_rel.is_inverse(rel):510 if rel.is_inverse(other_rel): 507 511 if matching_rel is None: 508 512 matching_rel = other_rel … … 518 522 # of the method though. But we do need to be careful not to start an 519 523 # infinite recursive loop. 520 if matching_rel and notreverse:521 rel.entity._descriptor.get_inverse_relation(matching_rel, True)524 if matching_rel and check_reverse: 525 rel.entity._descriptor.get_inverse_relation(matching_rel, False) 522 526 523 527 return matching_rel … … 921 925 922 926 # session methods 923 def commit(self, *args, **kwargs):924 return object_session(self). commit([self], *args, **kwargs)927 def flush(self, *args, **kwargs): 928 return object_session(self).flush([self], *args, **kwargs) 925 929 926 930 def delete(self, *args, **kwargs): … … 951 955 return self._global_session.update(self, *args, **kwargs) 952 956 957 # only exist in SA < 0.5 958 # IMO, the replacement (session.add) doesn't sound good enough to be added 959 # here. For example: "o = Order(); o.add()" is not very telling. It's 960 # better to leave it as "session.add(o)" 953 961 def save_or_update(self, *args, **kwargs): 954 962 return self._global_session.save_or_update(self, *args, **kwargs) -
elixir/trunk/elixir/relationships.py
r355 r356 356 356 357 357 self._target = None 358 self._inverse = None359 358 360 359 self.property = None # sqlalchemy property … … 395 394 return 396 395 397 kwargs = {} 398 if self.inverse: 396 kwargs = self.get_prop_kwargs() 397 398 # viewonly relationships need to create "standalone" relations (ie 399 # shouldn't be a backref of another relation). 400 if self.inverse and not kwargs.get('viewonly', False): 399 401 # check if the inverse was already processed (and thus has already 400 402 # defined a backref we can use) 401 403 if self.inverse.backref: 402 kwargs['backref'] = self.inverse.backref 404 # let the user override the backref argument 405 if 'backref' not in kwargs: 406 kwargs['backref'] = self.inverse.backref 403 407 else: 404 kwargs = self.get_prop_kwargs()405 406 408 # SQLAlchemy doesn't like when 'secondary' is both defined on 407 409 # the relation and the backref … … 411 413 self.backref = backref(self.name, **kwargs) 412 414 return 413 414 kwargs.update(self.get_prop_kwargs())415 415 416 416 self.property = relation(self.target, **kwargs) … … 428 428 429 429 def inverse(self): 430 if not self._inverse:430 if not hasattr(self, '_inverse'): 431 431 if self.inverse_name: 432 432 desc = self.target._descriptor … … 439 439 assert self.match_type_of(inverse) 440 440 else: 441 inverse = self.target._descriptor.get_inverse_relation(self) 442 443 if inverse: 444 self._inverse = inverse 441 check_reverse = not self.kwargs.get('viewonly', False) 442 inverse = self.target._descriptor.get_inverse_relation(self, 443 check_reverse=check_reverse) 444 445 self._inverse = inverse 446 if inverse and not self.kwargs.get('viewonly', False): 445 447 inverse._inverse = self 446 448 … … 452 454 453 455 def is_inverse(self, other): 454 # viewonly relationships shouldn't match as inverse of anything (so 455 # that no backref is created -- which doesn't make sense in that case) 456 viewonly = self.kwargs.get('viewonly', False) or \ 457 other.kwargs.get('viewonly', False) 458 return not viewonly and \ 456 # viewonly relationships are not symmetrical: a viewonly relationship 457 # should have exactly one inverse (a ManyToOne relationship), but that 458 # inverse shouldn't have the viewonly relationship as its inverse. 459 return not other.kwargs.get('viewonly', False) and \ 459 460 other is not self and \ 460 461 self.match_type_of(other) and \ … … 614 615 uselist = False 615 616 617 def __init__(self, *args, **kwargs): 618 self.filter = kwargs.pop('filter', None) 619 if self.filter is not None: 620 kwargs['viewonly'] = True 621 super(OneToOne, self).__init__(*args, **kwargs) 622 616 623 def match_type_of(self, other): 617 624 return isinstance(other, ManyToOne) 618 625 619 626 def create_keys(self, pk): 620 # When using a viewonly relationship, you are on your own: Elixir621 # doesn't check that a corresponding ManyToOne relationship exists.622 if self.kwargs.get('viewonly', False):623 return624 625 627 # make sure an inverse relationship exists 626 628 if self.inverse is None: … … 648 650 kwargs['remote_side'] = self.inverse.foreign_key 649 651 650 # viewonly relationships do not have any inverse (and they provide651 # their primaryjoin argument manually anyway).652 if not self.kwargs.get('viewonly', False):653 if self.inverse.primaryjoin_clauses:654 kwargs['primaryjoin'] = and_(*self.inverse.primaryjoin_clauses)652 joinclauses = self.inverse.primaryjoin_clauses 653 if self.filter: 654 joinclauses.append(self.filter(self.target.table.c)) 655 if joinclauses: 656 kwargs['primaryjoin'] = and_(*joinclauses) 655 657 656 658 kwargs.update(self.kwargs) -
elixir/trunk/tests/test_o2m.py
r355 r356 108 108 109 109 def test_viewonly(self): 110 # the callable primaryjoin seem to be unstable... sometime it works, 111 # sometime it doesn't... for no apparent reason. I think it's a bug in 112 # the current revision of SQLAlchemy I'm using (4900). 110 113 class User(Entity): 111 114 two_blurbs = OneToMany('Blurb', primaryjoin=lambda: … … 113 116 viewonly=True 114 117 ) 118 blurbs = OneToMany('Blurb', 119 collection_class=ordering_list('position'), 120 order_by='position') 121 122 class Blurb(Entity): 123 user = ManyToOne('User') 124 position = Field(Integer) 125 blurb = Field(Unicode(255)) 126 127 def __init__(self, blurb, **kwargs): 128 super(Blurb, self).__init__(blurb=blurb, **kwargs) 129 130 def __repr__(self): 131 return 'Blurb(%r, %r)' % (self.position, self.blurb) 132 133 setup_all(True) 134 135 user = User(blurbs=[Blurb(u'zero'), Blurb(u'one'), Blurb(u'two')]) 136 137 session.commit() 138 session.clear() 139 140 user = User.get(1) 141 assert len(user.two_blurbs) == 2 142 assert user.two_blurbs[0].blurb == 'zero' 143 assert user.two_blurbs[1].blurb == 'one' 144 145 def test_filtered(self): 146 class User(Entity): 147 two_blurbs = OneToMany('Blurb', filter=lambda c: c.position < 2) 115 148 blurbs = OneToMany('Blurb', 116 149 collection_class=ordering_list('position'),
