Changeset 199
- Timestamp:
- 09/06/07 15:55:47 (6 years ago)
- Location:
- elixir/trunk
- Files:
-
- 1 added
- 9 modified
-
CHANGES (modified) (1 diff)
-
docs/tutorial.rst (modified) (5 diffs)
-
elixir/__init__.py (modified) (2 diffs)
-
elixir/entity.py (modified) (16 diffs)
-
elixir/ext/associable.py (modified) (3 diffs)
-
elixir/fields.py (modified) (2 diffs)
-
elixir/relationships.py (modified) (12 diffs)
-
tests/b.py (modified) (1 diff)
-
tests/test_oo_syntax.py (added)
-
tests/test_packages.py (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
elixir/trunk/CHANGES
r198 r199 3 3 non-polymorphic multi-table (aka joined table) inheritance. 4 4 - Made the statement system more powerfull. 5 - Implemented a new syntax to declare fiels and relationships, much closer to 6 what is found in other Python ORM's. The with_fields syntax is now 7 deprecated in favor of a that new syntax. The old statement based (has_field et 8 al.) syntax stays the default for now. This was done with help from a patch 9 by Adam Gomaa. 10 - Relationships to other classes can now also be defined using the classes 11 themselves in addition to the class namees. Obviously, this doesn't work for 12 forward references. 5 13 - Autodelay and init order changed => order_by + belongs_to, belongs_to + pk, 6 14 ... -
elixir/trunk/docs/tutorial.rst
r59 r199 38 38 39 39 class Movie(Entity): 40 with_fields( 41 title = Field(Unicode(30)), 42 year = Field(Integer), 43 description = Field(Unicode) 44 ) 40 title = Field(Unicode(30)) 41 year = Field(Integer) 42 description = Field(Unicode) 45 43 46 44 def __repr__(self): … … 62 60 easily trace what is happening in an interactive python interpreter. 63 61 64 Also, please note that elixir currently provide two different ways to declare65 the fields on your entities. We have not decided yet on which one we like best, 66 o r if we will always keep both. The other way to declare your fields is using67 the ``has_field`` statement, rather than the ``with_fields`` statement. The 68 ``Movie`` example above can be declared using the ``has_field`` statement like69 so:62 Also, please note that elixir currently provide two different ways to 63 declare the fields on your entities. We have not decided yet on which 64 one we like best, or if we will always keep both. The other way to 65 declare your fields is using the ``has_field`` statement, rather than 66 assigning directly to the class attributes. The ``Movie`` example 67 above could be declared using the ``has_field`` statement like so: 70 68 71 69 :: … … 151 149 152 150 class Genre(Entity): 153 with_fields( 154 name = Field(Unicode(15), unique=True) 155 ) 151 name = Field(Unicode(15), unique=True) 152 156 153 157 154 def __repr__(self): … … 169 166 170 167 class Movie(Entity): 171 with_fields( 172 title = Field(Unicode(30)), 173 year = Field(Integer), 174 description = Field(Unicode) 175 ) 168 title = Field(Unicode(30)), 169 year = Field(Integer), 170 description = Field(Unicode) 176 171 177 172 belongs_to('genre', of_kind='Genre') # add this line … … 182 177 183 178 class Genre(Entity): 184 with_fields( 185 name = Field(Unicode(15)) 186 ) 179 name = Field(Unicode(15)) 187 180 188 181 has_many('movies', of_kind='Movie') # and this one -
elixir/trunk/elixir/__init__.py
r196 r199 28 28 from elixir.relationships import belongs_to, has_one, has_many, \ 29 29 has_and_belongs_to_many 30 from elixir.relationships import ManyToOne, OneToOne, OneToMany, ManyToMany 30 31 from elixir.properties import has_property 31 32 from elixir.statements import Statement … … 41 42 'has_property', 42 43 'belongs_to', 'has_one', 'has_many', 'has_and_belongs_to_many', 44 'ManyToOne', 'OneToOne', 'OneToMany', 'ManyToMany', 43 45 'using_options', 'using_table_options', 'using_mapper_options', 44 46 'options_defaults', 'metadata', 'objectstore', -
elixir/trunk/elixir/entity.py
r197 r199 2 2 Entity baseclass, metaclass and descriptor 3 3 ''' 4 5 import sqlalchemy 4 6 5 7 from sqlalchemy import Table, Integer, String, desc,\ … … 9 11 from sqlalchemy.ext.sessioncontext import SessionContext 10 12 from sqlalchemy.util import OrderedDict 11 import sqlalchemy 13 12 14 from elixir.statements import Statement 13 15 from elixir.fields import Field … … 62 64 63 65 self.fields = OrderedDict() 64 self.relationships = OrderedDict()66 self.relationships = list() 65 67 self.delayed_properties = dict() 66 68 self.constraints = list() … … 120 122 since a loop of primary_keys is not a valid situation. 121 123 """ 122 for rel in self.relationships .itervalues():124 for rel in self.relationships: 123 125 rel.create_keys(True) 124 126 … … 153 155 154 156 def setup_relkeys(self): 155 for rel in self.relationships .itervalues():157 for rel in self.relationships: 156 158 rel.create_keys(False) 157 159 … … 217 219 218 220 def setup_reltables(self): 219 for rel in self.relationships .itervalues():221 for rel in self.relationships: 220 222 rel.create_tables() 221 223 … … 367 369 368 370 def setup_properties(self): 369 for rel in self.relationships .itervalues():371 for rel in self.relationships: 370 372 rel.create_properties() 371 373 … … 417 419 418 420 matching_rel = None 419 for other_rel in self.relationships .itervalues():421 for other_rel in self.relationships: 420 422 if other_rel.is_inverse(rel): 421 423 if matching_rel is None: … … 437 439 return matching_rel 438 440 441 def find_relationship(self, name): 442 for rel in self.relationships: 443 if rel.name == name: 444 return rel 445 if self.parent: 446 return self.parent.find_relationship(name) 447 else: 448 return None 449 439 450 def primary_keys(self): 440 451 if self.autoload: … … 448 459 primary_keys = property(primary_keys) 449 460 450 def all_relationships(self):451 if self.parent:452 res = self.parent._descriptor.all_relationships453 else:454 res = dict()455 res.update(self.relationships)456 return res457 all_relationships = property(all_relationships)458 459 461 460 462 class TriggerProxy(object): 463 """A class that serves as a "trigger" ; accessing its attributes runs 464 the function that is set at initialization. 465 466 Primarily used for setup_all(). 467 468 Note that the `setupfunc` parameter is called on each access of 469 the attribute. 470 471 """ 461 472 def __init__(self, class_, attrname, setupfunc): 462 473 self.class_ = class_ … … 472 483 proxied_attr = getattr(self.class_, self.attrname) 473 484 return "<TriggerProxy (%s)>" % (self.class_.__name__) 485 486 def _is_entity(class_): 487 return isinstance(class_, EntityMeta) 474 488 475 489 class EntityMeta(type): … … 495 509 496 510 # Append all entities which are currently visible by the entity. This 497 # will find more entities only if some of them where imported from another498 # module.511 # will find more entities only if some of them where imported from 512 # another module. 499 513 for entity in [e for e in caller_frame.f_locals.values() 500 if e.__class__.__name__ == 'EntityMeta']:514 if _is_entity(e)]: 501 515 caller_entities[entity.__name__] = entity 502 516 … … 506 520 # process statements. Needed before the proxy for metadata 507 521 Statement.process(cls) 522 523 # Process attributes, for the assignment syntax. 524 cls._process_attrs(dict_) 508 525 509 526 # setup misc options here (like tablename etc.) … … 513 530 # TODO: support entity_name... or maybe not. I'm not sure it makes 514 531 # sense in Elixir. 515 cls. setup_proxy()516 517 def setup_proxy(cls, entity_name=None):532 cls._setup_proxy() 533 534 def _setup_proxy(cls, entity_name=None): 518 535 #TODO: move as much as possible of those "_private" values to the 519 536 # descriptor, so that we don't mess the initial class. … … 555 572 cls._ready = True 556 573 574 def _process_attrs(cls, attr_dict): 575 """Process class attributes, looking for Elixir `Field`s or 576 `Relationship`. 577 """ 578 579 for name, attr in attr_dict.iteritems(): 580 # Check if it's Elixir related. 581 if isinstance(attr, Field): 582 # If no colname was defined (through the 'colname' kwarg), set 583 # it to the name of the attr. 584 if attr.colname is None: 585 attr.colname = name 586 cls._descriptor.add_field(attr) 587 elif isinstance(attr, elixir.relationships.Relationship): 588 attr.name = name 589 attr.entity = cls 590 cls._descriptor.relationships.append(attr) 591 else: 592 # Not an Elixir field, let it be. 593 pass 594 return 595 557 596 def __getattribute__(cls, name): 558 597 if type.__getattribute__(cls, "_ready"): … … 593 632 tutorial. 594 633 ''' 595 596 634 __metaclass__ = EntityMeta 597 635 -
elixir/trunk/elixir/ext/associable.py
r195 r199 131 131 self.type = tablename 132 132 133 class Associable( el.relationships.Relationship):133 class Associable(object): 134 134 """An associable Elixir Statement object""" 135 135 def __init__(self, entity, name=None, uselist=True, lazy=True): … … 142 142 else: 143 143 self.name = name 144 self.entity._descriptor.relationships[able_name] = self 145 146 def create_keys(self, pk): 147 if pk: 148 return 144 145 def after_table(self): 149 146 field = el.Field(sa.Integer, sa.ForeignKey('%s.id' % able_name), 150 147 colname='%s_assoc_id' % interface_name) 151 148 self.entity._descriptor.add_field(field) 152 149 153 def create_tables(self):154 150 if not hasattr(assoc_entity, '_assoc_table'): 155 151 association_table = sa.Table("%s" % able_name, assoc_entity._descriptor.metadata, … … 178 174 }) 179 175 180 def create_properties(self):181 176 entity = self.entity 182 177 entity.mapper.add_property( -
elixir/trunk/elixir/fields.py
r196 r199 8 8 This module contains DSL statements which allow you to declare which 9 9 fields (columns) your Elixir entities have. There are currently two 10 different statements that you can use to declare fields: 10 different ways to declare your entities fields: through the has_field_ 11 statement, and by using the `Object-oriented syntax`_. Note that the 12 with_fields_ statement is currently deprecated in favor of the 13 `Object-oriented syntax`_. 11 14 12 15 13 `has_field` 14 --------- --16 has_field 17 --------- 15 18 The `has_field` statement allows you to define fields one at a time. 16 19 … … 65 68 has_field('name', String(50)) 66 69 70 Object-oriented syntax 71 ---------------------- 67 72 68 `with_fields` 69 ------------- 70 The `with_fields` statement allows you to define fields all at once. 73 Here is a quick example of how to use the object-oriented syntax. 71 74 75 :: 76 77 class Person(Entity): 78 id = Field(Integer, primary_key=True) 79 name = Field(String(50)) 80 81 with_fields 82 ----------- 83 The `with_fields` statement is **deprecated** in favor of the `Object-oriented 84 syntax`_. It allows you to define all fields of an entity at once. 72 85 Each keyword argument to this statement represents one field, which should 73 86 be a `Field` object. The first argument to a Field object is its type. -
elixir/trunk/elixir/relationships.py
r198 r199 239 239 ''' 240 240 241 def __init__(self, entity, name, *args, **kwargs):242 self.entity = entity243 self.name = name241 def __init__(self, of_kind, *args, **kwargs): 242 self.entity = None 243 self.name = None 244 244 self.inverse_name = kwargs.pop('inverse', None) 245 245 246 if 'through' in kwargs and 'via' in kwargs: 247 setattr(entity, name, 248 association_proxy(kwargs.pop('through'), kwargs.pop('via'), 249 **kwargs)) 250 return 251 elif 'through' in kwargs or 'via' in kwargs: 252 raise Exception("'through' and 'via' relationship keyword " 253 "arguments should be used in combination.") 254 255 self.of_kind = kwargs.pop('of_kind') 246 self.of_kind = of_kind 256 247 257 248 self._target = None … … 265 256 self.kwargs = kwargs 266 257 267 self.entity._descriptor.relationships[self.name] = self268 258 269 259 def create_keys(self, pk): … … 310 300 def target(self): 311 301 if not self._target: 312 path = self.of_kind.rsplit('.', 1) 313 classname = path.pop() 314 315 if path: 316 # do we have a fully qualified entity name? 317 module = sys.modules[path.pop()] 318 self._target = getattr(module, classname, None) 302 if isinstance(self.of_kind, EntityMeta): 303 self._target = self.of_kind 319 304 else: 320 # If not, try the list of entities of the "caller" of the 321 # source class. Most of the time, this will be the module the 322 # class is defined in. But it could also be a method (inner 323 # classes). 324 caller_entities = EntityMeta._entities[self.entity._caller] 325 self._target = caller_entities[classname] 305 path = self.of_kind.rsplit('.', 1) 306 classname = path.pop() 307 308 if path: 309 # do we have a fully qualified entity name? 310 module = sys.modules[path.pop()] 311 self._target = getattr(module, classname, None) 312 else: 313 # If not, try the list of entities of the "caller" of the 314 # source class. Most of the time, this will be the module 315 # the class is defined in. But it could also be a method 316 # (inner classes). 317 caller_entities = EntityMeta._entities[self.entity._caller] 318 self._target = caller_entities[classname] 326 319 return self._target 327 320 target = property(target) … … 331 324 if self.inverse_name: 332 325 desc = self.target._descriptor 333 # we use all_relationships so that relationships from parent 334 # entities are included too 335 inverse = desc.all_relationships.get(self.inverse_name, None) 326 inverse = desc.find_relationship(self.inverse_name) 336 327 if inverse is None: 337 328 raise Exception( … … 362 353 363 354 364 class BelongsTo(Relationship):355 class ManyToOne(Relationship): 365 356 ''' 366 357 367 358 ''' 368 359 369 def __init__(self, entity, name,*args, **kwargs):360 def __init__(self, *args, **kwargs): 370 361 self.colname = kwargs.pop('colname', []) 371 362 if self.colname and not isinstance(self.colname, list): … … 389 380 self.foreign_key = list() 390 381 self.primaryjoin_clauses = list() 391 super( BelongsTo, self).__init__(entity, name,*args, **kwargs)382 super(ManyToOne, self).__init__(*args, **kwargs) 392 383 393 384 def match_type_of(self, other): 394 return isinstance(other, ( HasMany, HasOne))385 return isinstance(other, (OneToMany, OneToOne)) 395 386 396 387 def create_keys(self, pk): … … 497 488 498 489 499 class HasOne(Relationship):490 class OneToOne(Relationship): 500 491 uselist = False 501 492 502 493 def match_type_of(self, other): 503 return isinstance(other, BelongsTo)494 return isinstance(other, ManyToOne) 504 495 505 496 def create_keys(self, pk): … … 537 528 538 529 539 class HasMany(HasOne):530 class OneToMany(OneToOne): 540 531 uselist = True 541 532 542 533 def get_prop_kwargs(self): 543 kwargs = super( HasMany, self).get_prop_kwargs()534 kwargs = super(OneToMany, self).get_prop_kwargs() 544 535 545 536 if 'order_by' in kwargs: … … 551 542 552 543 553 class HasAndBelongsToMany(Relationship):544 class ManyToMany(Relationship): 554 545 uselist = True 555 546 556 def __init__(self, entity, name,*args, **kwargs):547 def __init__(self, *args, **kwargs): 557 548 self.user_tablename = kwargs.pop('tablename', None) 558 549 self.local_side = kwargs.pop('local_side', []) … … 565 556 self.primaryjoin_clauses = list() 566 557 self.secondaryjoin_clauses = list() 567 super(HasAndBelongsToMany, self).__init__(entity, name, 568 *args, **kwargs) 558 super(ManyToMany, self).__init__(*args, **kwargs) 569 559 570 560 def match_type_of(self, other): 571 return isinstance(other, HasAndBelongsToMany)561 return isinstance(other, ManyToMany) 572 562 573 563 def create_tables(self): … … 718 708 719 709 def is_inverse(self, other): 720 return super( HasAndBelongsToMany, self).is_inverse(other) and \710 return super(ManyToMany, self).is_inverse(other) and \ 721 711 (self.user_tablename == other.user_tablename or 722 712 (not self.user_tablename and not other.user_tablename)) … … 776 766 777 767 778 belongs_to = Statement(BelongsTo) 779 has_one = Statement(HasOne) 780 has_many = Statement(HasMany) 781 has_and_belongs_to_many = Statement(HasAndBelongsToMany) 768 def rel_statement_handler(target): 769 class Handler(object): 770 def __init__(self, entity, name, *args, **kwargs): 771 if 'through' in kwargs and 'via' in kwargs: 772 setattr(entity, name, 773 association_proxy(kwargs.pop('through'), 774 kwargs.pop('via'), 775 **kwargs)) 776 return 777 elif 'through' in kwargs or 'via' in kwargs: 778 raise Exception("'through' and 'via' relationship keyword " 779 "arguments should be used in combination.") 780 rel = target(kwargs.pop('of_kind'), *args, **kwargs) 781 rel.name = name 782 rel.entity = entity 783 entity._descriptor.relationships.append(rel) 784 return Handler 785 786 787 belongs_to = Statement(rel_statement_handler(ManyToOne)) 788 has_one = Statement(rel_statement_handler(OneToOne)) 789 has_many = Statement(rel_statement_handler(OneToMany)) 790 has_and_belongs_to_many = Statement(rel_statement_handler(ManyToMany)) -
elixir/trunk/tests/b.py
r147 r199 3 3 class B(Entity): 4 4 has_field('name', String(30)) 5 has_many('a ', of_kind='tests.a.A')5 has_many('as_', of_kind='tests.a.A') 6 6 -
elixir/trunk/tests/test_packages.py
r178 r199 4 4 5 5 from elixir import * 6 import elixir6 import sys 7 7 8 def teardown():9 cleanup_all()8 def setup(self): 9 metadata.bind = 'sqlite:///' 10 10 11 11 class TestPackages(object): 12 def setup(self):13 metadata.bind = 'sqlite:///'14 15 12 def teardown(self): 16 drop_all() 17 objectstore.clear() 13 cleanup_all(True) 18 14 19 15 def test_packages(self): 20 16 # This is an ugly workaround because when nosetest is run globally (ie 21 17 # either on the tests directory or in the "trunk" directory, it imports 22 # all modules, including a and b and thus their entities are 23 # immediately setup but then the other tests clear all mappers, and 24 # when we get here, this tests doesn't reinit those because the modules 25 # are not reimported. 26 # In short, one more reason to use delay_setup by default. 27 # Note that even if we set delay setup in this particular test, before 28 # the module imports, it'll fail because we'd need to set delay_setup 29 # before the a and b modules are imported by nosetests. 30 import sys 18 # all modules, including a and b. Then when any other test calls 19 # setup_all(), A and B are also setup, but then the other test also 20 # calls cleanup_all(), so when we get here, A and B are already dead and 21 # reimporting their modules does nothing because they were already 22 # imported. 31 23 sys.modules.pop('tests.a', None) 32 24 sys.modules.pop('tests.b', None) … … 34 26 from tests.a import A 35 27 from tests.b import B 36 create_all()37 28 38 a = A(name='a1') 39 b = B(name='b1') 29 setup_all(True) 40 30 41 b .a.append(a)31 b1 = B(name='b1', as_=[A(name='a1')]) 42 32 43 33 objectstore.flush() 34 objectstore.clear() 44 35 36 a = A.query().one() 37 38 assert a in a.b.as_ 39 40 def test_ref_to_imported_entity_using_class(self): 41 sys.modules.pop('tests.a', None) 42 sys.modules.pop('tests.b', None) 43 44 from tests.a import A 45 from tests.b import B 46 47 class C(Entity): 48 has_field('name', String(30)) 49 belongs_to('a', of_kind=A) 50 51 setup_all(True) 52 53 # 'a_id' in ... is not supported before SA 0.4 54 assert C.table.columns.has_key('a_id') 55 56 def test_ref_to_imported_entity_using_name(self): 57 sys.modules.pop('tests.a', None) 58 sys.modules.pop('tests.b', None) 59 60 from tests.a import A 61 from tests.b import B 62 63 class C(Entity): 64 has_field('name', String(30)) 65 belongs_to('a', of_kind='A') 66 67 setup_all(True) 68 69 assert C.table.columns.has_key('a_id') 70
