Changeset 129
- Timestamp:
- 06/15/07 10:27:31 (6 years ago)
- Location:
- elixir/branches/autodelay
- Files:
-
- 1 added
- 3 modified
-
elixir/__init__.py (modified) (3 diffs)
-
elixir/entity.py (modified) (10 diffs)
-
elixir/relationships.py (modified) (19 diffs)
-
tests/test_autodelay.py (added)
Legend:
- Unmodified
- Added
- Removed
-
elixir/branches/autodelay/elixir/__init__.py
r119 r129 40 40 'using_options', 'using_table_options', 'using_mapper_options', 41 41 'options_defaults', 'metadata', 'objectstore', 42 'create_all', 'drop_all', 'setup_all', 'cleanup_all', 43 'delay_setup'] + \ 42 'create_all', 'drop_all', 'setup_all', 'cleanup_all'] + \ 44 43 sqlalchemy.types.__all__ 45 44 46 45 __pudge_all__ = ['create_all', 'drop_all', 'setup_all', 'cleanup_all', 47 'metadata', 'objectstore' , 'delay_setup']46 'metadata', 'objectstore'] 48 47 49 48 # connect … … 80 79 md.drop_all() 81 80 82 delayed_entities = set() 83 delay_setup = False 84 81 _delayed_descriptors = list() 85 82 86 83 def setup_all(): 87 84 '''Setup the table and mapper for all entities which have been delayed. 88 85 89 This should be used in conjunction with setting ``delay_setup`` to ``True``90 before defining your entities.86 This is called automatically when your entity is first accessed, or ... 87 [TODO: complete this] 91 88 ''' 92 for entity in delayed_entities: 93 entity.setup_table() 94 for entity in delayed_entities: 95 entity.setup_mapper() 89 if not _delayed_descriptors: 90 return 91 92 #TODO: define all those operations as methods on the descriptor 93 # for method_name in ('setup_table', 'setup_mapper', 'setup_relkeys', ...): 94 # for desc in _delayed_descriptors: 95 # method = getattr(desc, method_name) 96 # method() 97 98 for desc in _delayed_descriptors: 99 #TODO: I need to restore the original table_iterator on the metadata 100 entity = desc.entity 101 entity._ready = False 102 del sqlalchemy.orm.mapper_registry[entity._class_key] 103 del desc.metadata.tables[entity._table_key] 104 105 for desc in _delayed_descriptors: 106 desc.setup_table() 96 107 97 108 # setup all relationships 98 for entity in delayed_entities:99 for rel in entity.relationships.itervalues():100 rel. setup()109 for desc in _delayed_descriptors: 110 for rel in desc.relationships.itervalues(): 111 rel.create_keys() 101 112 102 delayed_entities.clear() 113 for desc in _delayed_descriptors: 114 for rel in desc.relationships.itervalues(): 115 rel.create_tables() 116 117 for desc in _delayed_descriptors: 118 desc.setup_mapper() 119 120 for desc in _delayed_descriptors: 121 for rel in desc.relationships.itervalues(): 122 rel.create_properties() 123 124 del _delayed_descriptors[:] 103 125 104 126 # issue the "CREATE" SQL statements 105 create_all()127 # create_all() 106 128 107 129 … … 113 135 md.clear() 114 136 metadatas.clear() 115 EntityDescriptor.uninitialized_rels.clear()116 137 117 138 objectstore.clear() -
elixir/branches/autodelay/elixir/entity.py
r119 r129 6 6 from sqlalchemy.ext.assignmapper import assign_mapper 7 7 from sqlalchemy.util import OrderedDict 8 import sqlalchemy 8 9 from elixir.statements import Statement 9 10 from elixir.fields import Field … … 55 56 self.constraints = list() 56 57 57 #CHECKME: this is a workaround for the "current" descriptor/target58 # property ugliness. The problem is that this workaround is ugly too.59 # I'm not sure if this is a safe practice. It works but...?60 # setattr(self.module, entity.__name__, entity)61 62 58 # set default value for options 63 59 self.order_by = None … … 93 89 self.tablename = self.tablename(entity) 94 90 95 def setup(self):96 '''97 Create tables, keys, columns that have been specified so far and98 assign a mapper. Will be called when an instance of the entity is99 created or a mapper is needed to access one or many instances of the100 entity. It will try to initialize the entity's relationships (along101 with any delayed relationship) but some of them might be delayed.102 '''103 if elixir.delay_setup:104 elixir.delayed_entities.add(self)105 return106 107 self.setup_table()108 self.setup_mapper()109 110 # This marks all relations of the entity (or, at least those which111 # have been added so far by statements) as being uninitialized112 EntityDescriptor.uninitialized_rels.update(113 self.relationships.values())114 115 # try to setup all uninitialized relationships116 EntityDescriptor.setup_relationships()117 118 91 def translate_order_by(self, order_by): 119 92 if isinstance(order_by, basestring): … … 161 134 properties[field.column.name] = deferred(field.column, 162 135 group=group) 163 164 136 assign_mapper(session.context, self.entity, self.entity.table, 165 137 properties=properties, **kwargs) … … 191 163 # elif self.inheritance == 'concrete': 192 164 # do not reuse parent table, but copy all fields 193 # the problem is that, at this point s, all "plain" fields165 # the problem is that, at this point, all "plain" fields 194 166 # are known, but not those generated by relations 195 167 # for field in self.fields.itervalues(): … … 217 189 self.entity.table = Table(self.tablename, self.metadata, 218 190 *args, **kwargs) 219 191 220 192 def create_auto_primary_key(self): 221 193 ''' … … 285 257 all_relationships = property(all_relationships) 286 258 287 def setup_relationships(cls): 288 for relationship in list(EntityDescriptor.uninitialized_rels): 289 if relationship.setup(): 290 EntityDescriptor.uninitialized_rels.remove(relationship) 291 setup_relationships = classmethod(setup_relationships) 259 260 class TriggerProxy(object): 261 def __init__(self, class_, attrname, setupfunc): 262 self.class_ = class_ 263 self.attrname = attrname 264 self.setupfunc = setupfunc 265 266 def __getattr__(self, name): 267 self.setupfunc() 268 proxied_attr = getattr(self.class_, self.attrname) 269 return getattr(proxied_attr, name) 270 271 def __repr__(self): 272 proxied_attr = getattr(self.class_, self.attrname) 273 return "PROXY(%s)" % (self.class_.__name__) 292 274 293 275 class EntityMeta(type): … … 297 279 entities (ie you don't want to use the provided 'Entity' class). 298 280 """ 281 _ready = False 282 _entities = {} 299 283 300 284 def __init__(cls, name, bases, dict_): … … 303 287 return 304 288 289 cid = cls._caller = id(sys._getframe(1)) 290 caller_entities = EntityMeta._entities.setdefault(cid, {}) 291 caller_entities[name] = cls 292 305 293 # create the entity descriptor 306 294 desc = cls._descriptor = EntityDescriptor(cls) 307 EntityDescriptor.current = desc 308 309 # process statements 295 296 # process statements. Needed before the proxy for metadata 310 297 Statement.process(cls) 311 298 312 299 # setup misc options here (like tablename etc.) 313 300 desc.setup_options() 314 315 # create table & assign (empty) mapper 316 desc.setup() 317 318 def q(cls): 319 return cls.query() 320 q = property(q) 301 302 # create trigger proxies 303 # TODO: support entity_name... or maybe not. I'm not sure it makes 304 # sense in Elixir. 305 cls.setup_proxy() 306 307 def setup_proxy(cls, entity_name=None): 308 #TODO: move as much as possible of those "_private" values to the 309 # descriptor, so that we don't mess the initial class. 310 cls._class_key = sqlalchemy.orm.ClassKey(cls, entity_name) 311 312 tablename = cls._descriptor.tablename 313 schema = cls._descriptor.table_options.get('schema', None) 314 cls._table_key = sqlalchemy.schema._get_table_key(tablename, schema) 315 316 elixir._delayed_descriptors.append(cls._descriptor) 317 318 mapper_proxy = TriggerProxy(cls, 'mapper', elixir.setup_all) 319 table_proxy = TriggerProxy(cls, 'table', elixir.setup_all) 320 321 sqlalchemy.orm.mapper_registry[cls._class_key] = mapper_proxy 322 md = cls._descriptor.metadata 323 md.tables[cls._table_key] = table_proxy 324 325 # We need to monkeypatch the metadata's table iterator method because 326 # otherwise it doesn't work if the setup is triggered by the 327 # metadata.create_all(). 328 # This is because ManyToMany relationships add tables AFTER the list 329 # of tables that are going to be created is "computed" 330 # (metadata.tables.values()). 331 # see: 332 # - table_iterator method in MetaData class in sqlalchemy/schema.py 333 # - visit_metadata method in sqlalchemy/ansisql.py 334 original_table_iterator = md.table_iterator 335 if not hasattr(original_table_iterator, 336 '_elixir_setup_trigger'): 337 def table_iterator(reverse=True, tables=None): 338 elixir.setup_all() 339 return original_table_iterator(reverse, tables) 340 table_iterator._elixir_setup_trigger = True 341 table_iterator.__doc__ = original_table_iterator.__doc__ 342 md.table_iterator = table_iterator 343 344 cls._ready = True 345 346 def __getattribute__(cls, name): 347 if type.__getattribute__(cls, "_ready"): 348 elixir.setup_all() 349 return type.__getattribute__(cls, name) 350 351 def __call__(cls, *args, **kwargs): 352 elixir.setup_all() 353 return type.__call__(cls, *args, **kwargs) 354 355 321 356 322 357 class Entity(object): … … 346 381 347 382 def __init__(self, **kwargs): 348 for key, value in kwargs.items():349 setattr(self, key, value)350 383 for key, value in kwargs.items(): 384 setattr(self, key, value) 385 -
elixir/branches/autodelay/elixir/relationships.py
r119 r129 194 194 from elixir.statements import Statement 195 195 from elixir.fields import Field 196 from elixir.entity import EntityDescriptor 196 from elixir.entity import EntityDescriptor, EntityMeta 197 197 198 198 import sys … … 219 219 self.args = args 220 220 self.kwargs = kwargs 221 221 #TODO: rename relationships to properties? or use add_property? 222 # answer: NO. 222 223 self.entity._descriptor.relationships[self.name] = self 223 224 … … 236 237 def create_properties(self): 237 238 ''' 238 Subclasses (ie. concrete relationships) may override this method to add239 properties to the involved entities.239 Subclasses (ie. concrete relationships) may override this method to 240 add properties to the involved entities. 240 241 ''' 241 242 … … 244 245 Sets up the relationship, creates foreign keys and secondary tables. 245 246 ''' 247 print "Rel::setup", self.name 248 249 if self.property: 250 print " -> already done" 251 return False 246 252 247 253 if not self.target: 254 print " -> not ready" 248 255 return False 249 256 250 if self.property: 251 return True 257 #CHIOTTE, ca trigger le truc 258 # if not self.target._done: 259 # print " -> not ready (!!)" 260 # return False 252 261 253 262 self.create_keys() … … 261 270 path = self.of_kind.rsplit('.', 1) 262 271 classname = path.pop() 272 module = None 263 273 264 274 if path: 265 275 # do we have a fully qualified entity name? 266 276 module = sys.modules[path.pop()] 267 else: 268 # if not, try the same module as the source 269 module = self.entity._descriptor.module 270 271 self._target = getattr(module, classname, None) 272 if not self._target: 273 # This is ugly but we need it because the class which is 274 # currently being defined (we have to keep in mind we are in 275 # its metaclass code) is not yet available in the module 276 # namespace, so the getattr above fails. And unfortunately, 277 # this doesn't only happen for the owning entity of this 278 # relation since we might be setting up a deferred relation. 279 e = EntityDescriptor.current.entity 280 if classname == e.__name__ or \ 281 self.of_kind == e.__module__ +'.'+ e.__name__: 282 self._target = e 283 else: 284 return None 285 277 self._target = getattr(module, classname, None) 278 else: 279 # If not, try the list of entities of the "caller" of the 280 # source class. Most of the time, this will be the module the 281 # class is defined in. But it could also be a method (inner 282 # classes). 283 caller_entities = EntityMeta._entities[self.entity._caller] 284 self._target = caller_entities[classname] 286 285 return self._target 287 286 target = property(target) … … 310 309 inverse = property(inverse) 311 310 311 #TODO: move this to each rel type 312 312 def match_type_of(self, other): 313 313 t1, t2 = type(self), type(other) … … 361 361 source accordingly. 362 362 ''' 363 print "BT::create_keys", self.name, self.entity.__name__ 364 if self.foreign_key: 365 print " -> already done" 366 return 363 367 364 368 source_desc = self.entity._descriptor 369 #TODO: make this work if target is a pure SA-mapped class 370 # for that, I need: 371 # - the list of primary key columns of the target table 372 # - the name of the target table 365 373 target_desc = self.target._descriptor 366 374 … … 369 377 if self.colname: 370 378 self.primaryjoin_clauses = \ 371 _ build_join_clauses(self.entity.table,372 self.colname, None,373 self.target.table)[0]379 _get_join_clauses(self.entity.table, 380 self.colname, None, 381 self.target.table)[0] 374 382 if not self.primaryjoin_clauses: 375 383 raise Exception( … … 413 421 414 422 # build the list of columns the foreign key will point to 415 if target_desc.entity.table.schema: 416 fk_refcols.append("%s.%s.%s" % ( 417 target_desc.entity.table.schema, 418 target_desc.entity.table.name, 419 pk_col.name)) 420 else: 421 fk_refcols.append("%s.%s" % (target_desc.entity.table.name, 422 pk_col.name)) 423 fk_refcols.append("%s.%s" % (target_desc.entity.table.name, 424 pk_col.name)) 423 425 424 426 # build up the primary join. This is needed when you have … … 436 438 437 439 def create_properties(self): 440 if self.property: 441 print " -> already done" 442 return 443 444 print "BT::create_properties", self.name, self.entity.__name__ 438 445 kwargs = self.kwargs 439 446 … … 457 464 458 465 def create_keys(self): 466 print "HO::create_keys", self.name, self.entity.__name__ 467 459 468 # make sure an inverse relationship exists 460 469 if self.inverse is None: … … 469 478 self.entity.__name__)) 470 479 # make sure it is set up because it creates the foreign key we'll need 471 self.inverse. setup()480 self.inverse.create_keys() 472 481 473 482 def create_properties(self): 483 print "HO::create_properties", self.name, self.entity.__name__ 484 if self.property: 485 print " -> already done" 486 return 487 474 488 kwargs = self.kwargs 475 489 … … 497 511 498 512 def create_properties(self): 513 print "HM::create_properties", self.name, self.entity.__name__ 514 if self.property: 515 print " -> already done" 516 return 517 499 518 if 'order_by' in self.kwargs: 500 519 self.kwargs['order_by'] = \ … … 522 541 523 542 def create_tables(self): 543 print "HABTM::create_table", self.name 544 if self.secondary_table: 545 print " -> already done" 546 return 547 524 548 if self.inverse: 525 549 if self.inverse.secondary_table: … … 527 551 self.primaryjoin_clauses = self.inverse.secondaryjoin_clauses 528 552 self.secondaryjoin_clauses = self.inverse.primaryjoin_clauses 529 530 if not self.secondary_table: 531 e1_desc = self.entity._descriptor 532 e2_desc = self.target._descriptor 533 534 # First, we compute the name of the table. Note that some of the 535 # intermediary variables are reused later for the constraint 536 # names. 553 return 554 555 e1_desc = self.entity._descriptor 556 e2_desc = self.target._descriptor 557 558 # First, we compute the name of the table. Note that some of the 559 # intermediary variables are reused later for the constraint 560 # names. 561 562 # We use the name of the relation for the first entity 563 # (instead of the name of its primary key), so that we can 564 # have two many-to-many relations between the same objects 565 # without having a table name collision. 566 source_part = "%s_%s" % (e1_desc.tablename, self.name) 567 568 # And we use only the name of the table of the second entity 569 # when there is no inverse, so that a many-to-many relation 570 # can be defined without an inverse. 571 if self.inverse: 572 target_part = "%s_%s" % (e2_desc.tablename, self.inverse.name) 573 else: 574 target_part = e2_desc.tablename 575 576 if self.user_tablename: 577 tablename = self.user_tablename 578 else: 579 # We need to keep the table name consistent (independant of 580 # whether this relation or its inverse is setup first). 581 if self.inverse and e1_desc.tablename < e2_desc.tablename: 582 tablename = "%s__%s" % (target_part, source_part) 583 else: 584 tablename = "%s__%s" % (source_part, target_part) 585 586 if e1_desc.autoload: 587 self._reflect_table(tablename) 588 else: 589 # We pre-compute the names of the foreign key constraints 590 # pointing to the source (local) entity's table and to the 591 # target's table 592 593 # In some databases (at lease MySQL) the constraint names need 594 # to be unique for the whole database, instead of per table. 595 source_fk_name = "%s_fk" % source_part 596 if self.inverse: 597 target_fk_name = "%s_fk" % target_part 598 else: 599 target_fk_name = "%s_inverse_fk" % source_part 600 601 columns = list() 602 constraints = list() 603 604 joins = (self.primaryjoin_clauses, self.secondaryjoin_clauses) 605 for num, desc, fk_name in ((0, e1_desc, source_fk_name), 606 (1, e2_desc, target_fk_name)): 607 fk_colnames = list() 608 fk_refcols = list() 537 609 538 # We use the name of the relation for the first entity 539 # (instead of the name of its primary key), so that we can 540 # have two many-to-many relations between the same objects 541 # without having a table name collision. 542 source_part = "%s_%s" % (e1_desc.tablename, self.name) 543 544 # And we use only the name of the table of the second entity 545 # when there is no inverse, so that a many-to-many relation 546 # can be defined without an inverse. 547 if self.inverse: 548 target_part = "%s_%s" % (e2_desc.tablename, self.inverse.name) 549 else: 550 target_part = e2_desc.tablename 610 for key in desc.primary_keys: 611 pk_col = key.column 612 613 colname = '%s_%s' % (desc.tablename, pk_col.name) 614 615 # In case we have a many-to-many self-reference, we 616 # need to tweak the names of the columns so that we 617 # don't end up with twice the same column name. 618 if self.entity is self.target: 619 colname += str(num + 1) 620 621 col = Column(colname, pk_col.type) 622 columns.append(col) 623 624 # Build the list of local columns which will be part 625 # of the foreign key. 626 fk_colnames.append(colname) 627 628 # Build the list of columns the foreign key will point 629 # to. 630 fk_refcols.append(desc.tablename + '.' + pk_col.name) 631 632 # Build join clauses (in case we have a self-ref) 633 if self.entity is self.target: 634 joins[num].append(col == pk_col) 635 636 constraints.append( 637 ForeignKeyConstraint(fk_colnames, fk_refcols, 638 name=fk_name)) 639 640 args = columns + constraints 551 641 552 if self.user_tablename: 553 tablename = self.user_tablename 554 else: 555 # We need to keep the table name consistent (independant of 556 # whether this relation or its inverse is setup first). 557 if self.inverse and e1_desc.tablename < e2_desc.tablename: 558 tablename = "%s__%s" % (target_part, source_part) 559 else: 560 tablename = "%s__%s" % (source_part, target_part) 561 562 if e1_desc.autoload: 563 self._reflect_table(tablename) 564 else: 565 # We pre-compute the names of the foreign key constraints 566 # pointing to the source (local) entity's table and to the 567 # target's table 568 569 # In some databases (at lease MySQL) the constraint names need 570 # to be unique for the whole database, instead of per table. 571 source_fk_name = "%s_fk" % source_part 572 if self.inverse: 573 target_fk_name = "%s_fk" % target_part 574 else: 575 target_fk_name = "%s_inverse_fk" % source_part 576 577 columns = list() 578 constraints = list() 579 580 joins = (self.primaryjoin_clauses, self.secondaryjoin_clauses) 581 for num, desc, fk_name in ((0, e1_desc, source_fk_name), 582 (1, e2_desc, target_fk_name)): 583 fk_colnames = list() 584 fk_refcols = list() 585 586 for key in desc.primary_keys: 587 pk_col = key.column 588 589 colname = '%s_%s' % (desc.tablename, pk_col.name) 590 591 # In case we have a many-to-many self-reference, we 592 # need to tweak the names of the columns so that we 593 # don't end up with twice the same column name. 594 if self.entity is self.target: 595 colname += str(num + 1) 596 597 col = Column(colname, pk_col.type) 598 columns.append(col) 599 600 # Build the list of local columns which will be part 601 # of the foreign key. 602 fk_colnames.append(colname) 603 604 # Build the list of columns the foreign key will point 605 # to. 606 fk_refcols.append(desc.tablename + '.' + pk_col.name) 607 608 # Build join clauses (in case we have a self-ref) 609 if self.entity is self.target: 610 joins[num].append(col == pk_col) 611 612 constraints.append( 613 ForeignKeyConstraint(fk_colnames, fk_refcols, 614 name=fk_name)) 615 616 args = columns + constraints 617 618 self.secondary_table = Table(tablename, e1_desc.metadata, 619 *args) 642 self.secondary_table = Table(tablename, e1_desc.metadata, 643 *args) 644 print " -> created", tablename, e1_desc.metadata 620 645 621 646 def _reflect_table(self, tablename): … … 646 671 647 672 self.primaryjoin_clauses, self.secondaryjoin_clauses = \ 648 _ build_join_clauses(self.secondary_table,649 self.local_side, self.remote_side,650 self.entity.table)673 _get_join_clauses(self.secondary_table, 674 self.local_side, self.remote_side, 675 self.entity.table) 651 676 652 677 def create_properties(self): 678 print "HABTM::create_properties", self.name, self.entity.__name__ 679 if self.property: 680 print " -> already done" 681 return 653 682 kwargs = self.kwargs 654 683 … … 671 700 672 701 673 def _ build_join_clauses(local_table, local_cols1, local_cols2, target_table):702 def _get_join_clauses(local_table, local_cols1, local_cols2, target_table): 674 703 primary_join, secondary_join = [], [] 675 704 cols1 = local_cols1[:] … … 685 714 constraint_map = {} 686 715 for constraint in local_table.constraints: 716 print "constraint", constraint 687 717 if isinstance(constraint, ForeignKeyConstraint): 688 718 use_constraint = False … … 719 749 has_many = Statement(HasMany) 720 750 has_and_belongs_to_many = Statement(HasAndBelongsToMany) 751
