Changeset 175
- Timestamp:
- 08/06/07 14:16:45 (6 years ago)
- Location:
- elixir/trunk
- Files:
-
- 13 modified
-
elixir/__init__.py (modified) (3 diffs)
-
elixir/entity.py (modified) (21 diffs)
-
elixir/ext/versioned.py (modified) (1 diff)
-
elixir/fields.py (modified) (1 diff)
-
elixir/options.py (modified) (5 diffs)
-
elixir/relationships.py (modified) (12 diffs)
-
elixir/statements.py (modified) (2 diffs)
-
tests/test_autoload.py (modified) (5 diffs)
-
tests/test_autoload_mixed.py (modified) (3 diffs)
-
tests/test_autoload_nopk.py (modified) (1 diff)
-
tests/test_has_property.py (modified) (1 diff)
-
tests/test_inherit.py (modified) (1 diff)
-
tests/test_options.py (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
elixir/trunk/elixir/__init__.py
r148 r175 29 29 has_and_belongs_to_many 30 30 from elixir.properties import has_property 31 from elixir.statements import Statement 31 32 32 33 try: … … 42 43 'using_options', 'using_table_options', 'using_mapper_options', 43 44 'options_defaults', 'metadata', 'objectstore', 44 'create_all', 'drop_all', 'setup_all', 'cleanup_all', 45 'delay_setup'] + \ 45 'create_all', 'drop_all', 'setup_all', 'cleanup_all'] + \ 46 46 sqlalchemy.types.__all__ 47 47 48 48 __pudge_all__ = ['create_all', 'drop_all', 'setup_all', 'cleanup_all', 49 'metadata', 'objectstore' , 'delay_setup']49 'metadata', 'objectstore'] 50 50 51 51 # connect 52 metadata = sqlalchemy. DynamicMetaData(threadlocal=False)52 metadata = sqlalchemy.MetaData() 53 53 54 54 try: … … 83 83 md.drop_all() 84 84 85 delayed_entities = set() 86 delay_setup = False 85 _delayed_descriptors = list() 86 87 def setup_all(create_tables=False): 88 '''Setup the table and mapper for all entities which have been delayed. 89 90 This is called automatically when your entity is first accessed, or ... 91 [TODO: complete this] 92 ''' 93 94 if not _delayed_descriptors: 95 return 96 97 #TODO: define all those operations as methods on the descriptor 98 # for method_name in ('setup_table', 'setup_mapper', 'setup_relkeys', ...): 99 # for desc in _delayed_descriptors: 100 # method = getattr(desc, method_name) 101 # method() 102 try: 103 for desc in _delayed_descriptors: 104 entity = desc.entity 105 entity._ready = False 106 del sqlalchemy.orm.mapper_registry[entity._class_key] 107 108 md = desc.metadata 109 # the table could have already been removed (namely in a single 110 # table inheritance scenario) 111 md.tables.pop(entity._table_key, None) 112 113 # restore original table iterator if not done already 114 if hasattr(md.table_iterator, '_non_elixir_patched_iterator'): 115 md.table_iterator = \ 116 md.table_iterator._non_elixir_patched_iterator 117 118 # Make sure autoloaded tables are available so that we can setup 119 # foreign keys to their columns 120 for desc in _delayed_descriptors: 121 if desc.autoload: 122 desc.setup_table() 123 124 for desc in _delayed_descriptors: 125 desc.create_pk_cols() 126 127 # Create other columns from belongs_to relationships. 128 for desc in _delayed_descriptors: 129 for rel in desc.relationships.itervalues(): 130 rel.create_keys(False) 131 132 for desc in _delayed_descriptors: 133 if not desc.autoload: 134 desc.setup_table() 135 136 for desc in _delayed_descriptors: 137 for rel in desc.relationships.itervalues(): 138 rel.create_tables() 139 140 for desc in _delayed_descriptors: 141 desc.setup_events() 142 143 for desc in _delayed_descriptors: 144 desc.setup_mapper() 145 146 for desc in _delayed_descriptors: 147 for rel in desc.relationships.itervalues(): 148 rel.create_properties() 149 150 #TODO: merge this with the "when" feature of statements 151 for desc in _delayed_descriptors: 152 # allow the statements to do any "finalization" 153 Statement.finalize(desc.entity) 154 155 finally: 156 # make sure that even if we fail to initialize, we don't leave junk for 157 # others 158 del _delayed_descriptors[:] 159 160 # issue the "CREATE" SQL statements 161 if create_tables: 162 create_all() 87 163 88 164 89 def setup_all(): 90 '''Setup the table and mapper for all entities which have been delayed. 91 92 This should be used in conjunction with setting ``delay_setup`` to ``True`` 93 before defining your entities. 94 ''' 95 for entity in delayed_entities: 96 entity.setup_table() 97 for entity in delayed_entities: 98 entity.setup_mapper() 99 100 # setup all relationships 101 for entity in delayed_entities: 102 for rel in entity.relationships.itervalues(): 103 rel.setup() 104 105 delayed_entities.clear() 106 107 # issue the "CREATE" SQL statements 108 create_all() 109 110 111 def cleanup_all(): 165 def cleanup_all(drop_tables=False): 112 166 '''Drop table and clear mapper for all entities, and clear all metadatas. 113 167 ''' 114 drop_all() 168 if drop_tables: 169 drop_all() 170 115 171 for md in metadatas: 116 172 md.clear() 117 173 metadatas.clear() 118 EntityDescriptor.uninitialized_rels.clear()119 174 120 175 objectstore.clear() 121 sqlalchemy. clear_mappers()176 sqlalchemy.orm.clear_mappers() 122 177 -
elixir/trunk/elixir/entity.py
r170 r175 3 3 ''' 4 4 5 from sqlalchemy import Table, Integer, desc 5 from sqlalchemy import Table, Integer, String, desc,\ 6 ForeignKey 6 7 from sqlalchemy.orm import deferred, Query, MapperExtension 7 8 from sqlalchemy.ext.assignmapper import assign_mapper 8 9 from sqlalchemy.util import OrderedDict 10 import sqlalchemy 9 11 from elixir.statements import Statement 10 12 from elixir.fields import Field … … 17 19 18 20 import sys 21 import warnings 22 19 23 import elixir 20 24 import inspect … … 25 29 DEFAULT_AUTO_PRIMARYKEY_TYPE = Integer 26 30 DEFAULT_VERSION_ID_COL = "row_version" 31 DEFAULT_POLYMORPHIC_COL_NAME = "row_type" 32 DEFAULT_POLYMORPHIC_COL_SIZE = 20 33 DEFAULT_POLYMORPHIC_COL_TYPE = String(DEFAULT_POLYMORPHIC_COL_SIZE) 27 34 28 35 class EntityDescriptor(object): … … 44 51 45 52 self.parent = None 53 self.children = [] 54 46 55 for base in entity.__bases__: 47 56 if issubclass(base, Entity) and base is not Entity: … … 52 61 else: 53 62 self.parent = base 63 self.parent._descriptor.children.append(entity) 54 64 55 65 self.fields = OrderedDict() 66 #TODO Ordered 56 67 self.relationships = dict() 57 68 self.delayed_properties = dict() 58 69 self.constraints = list() 59 60 #CHECKME: this is a workaround for the "current" descriptor/target61 # property ugliness. The problem is that this workaround is ugly too.62 # I'm not sure if this is a safe practice. It works but...?63 # setattr(self.module, entity.__name__, entity)64 70 65 71 # set default value for options … … 68 74 self.metadata = getattr(self.module, 'metadata', elixir.metadata) 69 75 70 for option in ('inheritance', 76 for option in ('inheritance', 'polymorphic', 71 77 'autoload', 'tablename', 'shortnames', 72 78 'auto_primarykey', … … 85 91 86 92 entity = self.entity 93 if self.inheritance == 'concrete' and self.polymorphic: 94 raise NotImplementedError("Polymorphic concrete inheritance is " 95 "not yet implemented.") 96 97 if self.parent: 98 if self.inheritance == 'single': 99 self.tablename = self.parent._descriptor.tablename 87 100 88 101 if not self.tablename: … … 96 109 self.tablename = self.tablename(entity) 97 110 98 def setup(self):99 '''100 Create tables, keys, columns that have been specified so far and101 assign a mapper. Will be called when an instance of the entity is102 created or a mapper is needed to access one or many instances of the103 entity. It will try to initialize the entity's relationships (along104 with any delayed relationship) but some of them might be delayed.105 '''106 if elixir.delay_setup:107 elixir.delayed_entities.add(self)108 return109 110 self.setup_events()111 self.setup_table()112 self.setup_mapper()113 114 # This marks all relations of the entity (or, at least those which115 # have been added so far by statements) as being uninitialized116 EntityDescriptor.uninitialized_rels.update(117 self.relationships.values())118 119 # try to setup all uninitialized relationships120 EntityDescriptor.setup_relationships()121 122 # finally, allow the statement to do any "finalization"123 Statement.finalize(self.entity)124 125 111 def setup_events(self): 126 112 # create a list of callbacks for each event … … 179 165 kwargs['version_id_col'] = self.fields[self.version_id_col].column 180 166 181 if self.parent: 182 if self.inheritance == 'single': 183 # at this point, we don't know whether the parent relationships 184 # have already been processed or not. Some of them might be, 185 # some other might not. 186 if not self.parent.mapper: 187 self.parent._descriptor.setup_mapper() 167 if self.inheritance in ('single', 'concrete', 'multi'): 168 if self.parent and \ 169 not (self.inheritance == 'concrete' and not self.polymorphic): 188 170 kwargs['inherits'] = self.parent.mapper 171 172 if self.polymorphic: 173 if self.children and not self.parent: 174 kwargs['polymorphic_on'] = \ 175 self.fields[self.polymorphic].column 176 if self.inheritance == 'multi': 177 children = self._get_children() 178 join = self.entity.table 179 for child in children: 180 join = join.outerjoin(child.table) 181 kwargs['select_table'] = join 182 183 if self.children or self.parent: 184 #TODO: make this customizable (both callable and string) 185 #TODO: include module name 186 kwargs['polymorphic_identity'] = \ 187 self.entity.__name__.lower() 188 189 if self.inheritance == 'concrete': 190 kwargs['concrete'] = True 189 191 190 192 properties = dict() … … 197 199 group=group) 198 200 199 #TODO: make this happen after the rel columns have been added.200 201 for name, prop in self.delayed_properties.iteritems(): 201 202 properties[name] = self.evaluate_property(prop) … … 207 208 colname in kwargs['primary_key']] 208 209 209 assign_mapper(session.context, self.entity, self.entity.table, 210 properties=properties, **kwargs) 210 if self.parent and self.inheritance == 'single': 211 args = [] 212 else: 213 args = [self.entity.table] 214 215 assign_mapper(session.context, self.entity, properties=properties, 216 *args, **kwargs) 217 218 def _get_children(self): 219 children = self.children[:] 220 for child in self.children: 221 children.extend(child._descriptor._get_children()) 222 return children 211 223 212 224 def evaluate_property(self, prop): … … 240 252 if self.parent: 241 253 if self.inheritance == 'single': 242 # reuse the parent's table 243 if not self.parent.table: 244 self.parent._descriptor.setup_table() 245 254 # we know the parent is setup before the child 246 255 self.entity.table = self.parent.table 247 256 … … 253 262 254 263 return 255 # elif self.inheritance == 'concrete': 256 # do not reuse parent table, but copy all fields 257 # the problem is that, at this point, all "plain" fields 258 # are known, but not those generated by relations 259 # for field in self.fields.itervalues(): 260 # self.add_field(field) 264 elif self.inheritance == 'concrete': 265 # copy all fields from parent table 266 for field in self.parent._descriptor.fields.itervalues(): 267 self.add_field(field.copy()) 268 269 if self.polymorphic and self.inheritance in ('single', 'multi') and \ 270 self.children and not self.parent: 271 if not isinstance(self.polymorphic, basestring): 272 self.polymorphic = DEFAULT_POLYMORPHIC_COL_NAME 273 274 self.add_field(Field(DEFAULT_POLYMORPHIC_COL_TYPE, 275 colname=self.polymorphic)) 261 276 262 277 if self.version_id_col: … … 264 279 self.version_id_col = DEFAULT_VERSION_ID_COL 265 280 self.add_field(Field(Integer, colname=self.version_id_col)) 266 267 if not self.autoload:268 if not self.has_pk and self.auto_primarykey:269 self.create_auto_primary_key()270 281 271 282 # create list of columns and constraints … … 283 294 284 295 296 def create_pk_cols(self): 297 """ 298 Create primary_key columns. That is, add columns from belongs_to 299 relationships marked as being a primary_key and then adds a primary 300 key to the table if it hasn't already got one and needs one. 301 302 This method is "semi-recursive" in that it calls the create_keys 303 method on BelongsTo relationships and those in turn call create_pk_cols 304 on their target. It shouldn't be possible to have an infinite loop 305 since a loop of primary_keys is not a valid situation. 306 """ 307 for rel in self.relationships.itervalues(): 308 rel.create_keys(True) 309 310 if not self.autoload: 311 if self.parent and self.inheritance == 'multi': 312 # add foreign keys to the parent's primary key columns 313 parent_desc = self.parent._descriptor 314 for pk_col in parent_desc.primary_keys: 315 colname = "%s_%s" % (self.parent.__name__.lower(), 316 pk_col.name) 317 field = Field(pk_col.type, ForeignKey(pk_col), 318 colname=colname, primary_key=True) 319 self.add_field(field) 320 if not self.has_pk and self.auto_primarykey: 321 self.create_auto_primary_key() 322 323 285 324 def create_auto_primary_key(self): 286 325 ''' 287 326 Creates a primary key 288 327 ''' 289 290 assert not self.has_pk and self.auto_primarykey291 328 292 329 if isinstance(self.auto_primarykey, basestring): … … 304 341 self.has_pk = True 305 342 306 table = self.entity.table 343 # we don't want to trigger setup_all too early 344 table = type.__getattribute__(self.entity, 'table') 307 345 if table: 308 346 table.append_column(field.column) … … 342 380 343 381 def primary_keys(self): 344 return [col for col in self.entity.table.primary_key.columns] 382 if self.autoload: 383 return [col for col in self.entity.table.primary_key.columns] 384 else: 385 return [field.column for field in self.fields.itervalues() if 386 field.primary_key] 345 387 primary_keys = property(primary_keys) 346 388 … … 354 396 all_relationships = property(all_relationships) 355 397 356 def setup_relationships(cls): 357 for relationship in list(EntityDescriptor.uninitialized_rels): 358 if relationship.setup(): 359 EntityDescriptor.uninitialized_rels.remove(relationship) 360 setup_relationships = classmethod(setup_relationships) 398 399 class TriggerProxy(object): 400 def __init__(self, class_, attrname, setupfunc): 401 self.class_ = class_ 402 self.attrname = attrname 403 self.setupfunc = setupfunc 404 405 def __getattr__(self, name): 406 self.setupfunc() 407 proxied_attr = getattr(self.class_, self.attrname) 408 return getattr(proxied_attr, name) 409 410 def __repr__(self): 411 proxied_attr = getattr(self.class_, self.attrname) 412 return "<TriggerProxy (%s)>" % (self.class_.__name__) 361 413 362 414 class EntityMeta(type): … … 366 418 entities (ie you don't want to use the provided 'Entity' class). 367 419 """ 420 _ready = False 421 _entities = {} 368 422 369 423 def __init__(cls, name, bases, dict_): … … 372 426 return 373 427 428 cid = cls._caller = id(sys._getframe(1)) 429 caller_entities = EntityMeta._entities.setdefault(cid, {}) 430 caller_entities[name] = cls 431 374 432 # create the entity descriptor 375 433 desc = cls._descriptor = EntityDescriptor(cls) 376 EntityDescriptor.current = desc 377 378 # process statements 379 Statement.process(cls) 434 435 # process statements. Needed before the proxy for metadata 436 Statement.process(cls, 'init') 380 437 381 438 # setup misc options here (like tablename etc.) 382 439 desc.setup_options() 383 440 384 # create table & assign (empty) mapper 385 desc.setup() 441 # create trigger proxies 442 # TODO: support entity_name... or maybe not. I'm not sure it makes 443 # sense in Elixir. 444 cls.setup_proxy() 445 446 def setup_proxy(cls, entity_name=None): 447 #TODO: move as much as possible of those "_private" values to the 448 # descriptor, so that we don't mess the initial class. 449 cls._class_key = sqlalchemy.orm.mapperlib.ClassKey(cls, entity_name) 450 451 tablename = cls._descriptor.tablename 452 schema = cls._descriptor.table_options.get('schema', None) 453 cls._table_key = sqlalchemy.schema._get_table_key(tablename, schema) 454 455 elixir._delayed_descriptors.append(cls._descriptor) 456 457 mapper_proxy = TriggerProxy(cls, 'mapper', elixir.setup_all) 458 table_proxy = TriggerProxy(cls, 'table', elixir.setup_all) 459 460 sqlalchemy.orm.mapper_registry[cls._class_key] = mapper_proxy 461 md = cls._descriptor.metadata 462 md.tables[cls._table_key] = table_proxy 463 464 # We need to monkeypatch the metadata's table iterator method because 465 # otherwise it doesn't work if the setup is triggered by the 466 # metadata.create_all(). 467 # This is because ManyToMany relationships add tables AFTER the list 468 # of tables that are going to be created is "computed" 469 # (metadata.tables.values()). 470 # see: 471 # - table_iterator method in MetaData class in sqlalchemy/schema.py 472 # - visit_metadata method in sqlalchemy/ansisql.py 473 original_table_iterator = md.table_iterator 474 if not hasattr(original_table_iterator, 475 '_non_elixir_patched_iterator'): 476 def table_iterator(*args, **kwargs): 477 elixir.setup_all() 478 return original_table_iterator(*args, **kwargs) 479 table_iterator.__doc__ = original_table_iterator.__doc__ 480 table_iterator._non_elixir_patched_iterator = \ 481 original_table_iterator 482 md.table_iterator = table_iterator 483 484 cls._ready = True 485 486 def __getattribute__(cls, name): 487 if type.__getattribute__(cls, "_ready"): 488 #TODO: we need to add all assign_mapper methods 489 if name in ('c', 'table', 'mapper'): 490 elixir.setup_all() 491 return type.__getattribute__(cls, name) 492 493 def __call__(cls, *args, **kwargs): 494 elixir.setup_all() 495 return type.__call__(cls, *args, **kwargs) 386 496 387 497 def q(cls): … … 419 529 setattr(self, key, value) 420 530 531 def get_by(cls, *args, **kwargs): 532 # warnings.warn("The get_by method on the class is deprecated." 533 # "You should use cls.query.get_by", DeprecationWarning, 534 # stacklevel=2) 535 return cls.q.get_by(*args, **kwargs) 536 get_by = classmethod(get_by) 537 538 def select(cls, *args, **kwargs): 539 # warnings.warn("The select method on the class is deprecated." 540 # "You should use cls.query.select", DeprecationWarning, 541 # stacklevel=2) 542 return cls.q.select(*args, **kwargs) 543 select = classmethod(select) 544 545 -
elixir/trunk/elixir/ext/versioned.py
r172 r175 43 43 from elixir.statements import Statement 44 44 from elixir.fields import Field 45 from sqlalchemy import Table, Column, and_, desc 46 from sqlalchemy.orm import mapper 45 47 from sqlalchemy.orm.mapper import MapperExtension, EXT_PASS 46 from sqlalchemy import Table, Column, mapper, and_, desc47 48 from datetime import datetime 48 49 -
elixir/trunk/elixir/fields.py
r119 r175 99 99 self.args = args 100 100 self.kwargs = kwargs 101 101 102 def copy(self): 103 ''' 104 Returns a copy of the field 105 ''' 106 107 kwargs = self.kwargs 108 kwargs.update({'colname': self.colname, 'deferred': self.deferred}) 109 if 'nullable' in self.kwargs: 110 kwargs['required'] = not self.kwargs['nullable'] 111 return Field(self.type, *self.args, **kwargs) 112 102 113 def column(self): 103 114 ''' -
elixir/trunk/elixir/options.py
r119 r175 34 34 | ``inheritance`` | Specify the type of inheritance this entity must use. | 35 35 | | It can be one of ``single``, ``concrete`` or | 36 | | ``multi``. | 37 | | **For now, only the single type is implemented.** | 36 | | ``multi``. Defaults to ``single``. | 38 37 +---------------------+-------------------------------------------------------+ 39 | ``metadata`` | Specify a custom MetaData | 38 | ``polymorphic`` | Whether the inheritance should be polymorphic or not. | 39 | | Defaults to ``False``. Note that polymorphic concrete | 40 | | inheritance is currently not implemented. | 41 +---------------------+-------------------------------------------------------+ 42 | ``metadata`` | Specify a custom MetaData. | 40 43 +---------------------+-------------------------------------------------------+ 41 44 | ``autoload`` | Automatically load column definitions from the | 42 45 | | existing database table. | 43 | | Using autoloaded tables implies setting |44 | | ``delay_setup`` to ``True`` before defining your |45 | | entities. |46 46 +---------------------+-------------------------------------------------------+ 47 47 | ``tablename`` | Specify a custom tablename. You can either provide a | … … 55 55 | | underscores ("_"), eg.: "project1_model_myentity" | 56 56 | | for an entity named "MyEntity" in the module | 57 | | "project1.model". If shortnames is True, the|57 | | "project1.model". If shortnames is ``True``, the | 58 58 | | tablename will just be the entity's classname | 59 59 | | lower-cased, ie. "myentity". | … … 116 116 options_defaults = dict( 117 117 inheritance='single', 118 polymorphic=False, 118 119 autoload=None, 119 120 shortnames=False, … … 128 129 valid_options = ( 129 130 'inheritance', 130 ' metadata',131 'polymorphic', 131 132 'autoload', 132 133 'tablename', … … 134 135 'auto_primarykey', 135 136 'version_id_col', 137 'metadata', 136 138 'order_by', 137 139 ) -
elixir/trunk/elixir/relationships.py
r163 r175 199 199 from elixir.statements import Statement 200 200 from elixir.fields import Field 201 from elixir.entity import EntityDescriptor 201 from elixir.entity import EntityDescriptor, EntityMeta 202 202 203 203 import sys … … 228 228 self.entity._descriptor.relationships[self.name] = self 229 229 230 def create_keys(self ):230 def create_keys(self, pk): 231 231 ''' 232 232 Subclasses (ie. concrete relationships) may override this method to … … 250 250 kwargs = {} 251 251 if self.inverse: 252 # check if the inverse was already processed (and th is has already defined253 # a backref)252 # check if the inverse was already processed (and thus has already 253 # defined a backref we can use) 254 254 if self.inverse.backref: 255 255 kwargs['backref'] = self.inverse.backref … … 269 269 self.entity.mapper.add_property(self.name, self.property) 270 270 271 def setup(self):272 '''273 Sets up the relationship, creates foreign keys and secondary tables.274 '''275 276 if not self.target:277 return False278 279 if self.property or self.backref:280 return True281 282 self.create_keys()283 self.create_tables()284 self.create_properties()285 286 return True287 288 271 def target(self): 289 272 if not self._target: … … 293 276 if path: 294 277 # do we have a fully qualified entity name? 295 module = sys.modules.get(path.pop(), None) 296 if module is None: 297 # the module is probably not yet defined 298 #TODO: in a delay_setup scenario, we should raise an 299 #exception 300 return None 278 module = sys.modules[path.pop()] 279 self._target = getattr(module, classname, None) 301 280 else: 302 # if not, try the same module as the source 303 module = self.entity._descriptor.module 304 305 self._target = getattr(module, classname, None) 306 if not self._target: 307 # This is ugly but we need it because the class which is 308 # currently being defined (we have to keep in mind we are in 309 # its metaclass code) is not yet available in the module 310 # namespace, so the getattr above fails. And unfortunately, 311 # this doesn't only happen for the owning entity of this 312 # relation since we might be setting up a deferred relation. 313 e = EntityDescriptor.current.entity 314 if classname == e.__name__ or \ 315 self.of_kind == e.__module__ +'.'+ e.__name__: 316 self._target = e 317 else: 318 return None 319 281 # If not, try the list of entities of the "caller" of the 282 # source class. Most of the time, this will be the module the 283 # class is defined in. But it could also be a method (inner 284 # classes). 285 caller_entities = EntityMeta._entities[self.entity._caller] 286 self._target = caller_entities[classname] 320 287 return self._target 321 288 target = property(target) … … 386 353 return isinstance(other, (HasMany, HasOne)) 387 354 388 def create_keys(self ):355 def create_keys(self, pk): 389 356 ''' 390 357 Find all primary keys on the target and create foreign keys on the … … 394 361 return 395 362 363 if self.column_kwargs.get('primary_key', False) != pk: 364 return 365 396 366 source_desc = self.entity._descriptor 367 #TODO: make this work if target is a pure SA-mapped class 368 # for that, I need: 369 # - the list of primary key columns of the target table 370 # - the name of the target table 397 371 target_desc = self.target._descriptor 372 #make sure the target has all its pk setup up 373 target_desc.create_pk_cols() 398 374 399 375 if source_desc.autoload: … … 431 407 432 408 # we use a Field here instead of using a Column directly 433 # because of add_field409 # because add_field can be used before the table is created 434 410 field = Field(pk_col.type, colname=colname, index=True, 435 411 **self.column_kwargs) … … 443 419 fk_colnames.append(colname) 444 420 445 # build the list of columns the foreign key will point to 446 if target_desc.entity.table.schema: 447 fk_refcols.append("%s.%s.%s" % ( 448 target_desc.entity.table.schema, 449 target_desc.entity.table.name, 450 pk_col.name)) 451 else: 452 fk_refcols.append("%s.%s" % (target_desc.entity.table.name, 453 pk_col.name)) 421 # build the list of column "paths" the foreign key will 422 # point to 423 target_path = "%s.%s" % (target_desc.tablename, pk_col.name) 424 schema = target_desc.table_options.get('schema', None) 425 if schema is not None: 426 target_path = "%s.%s" % (schema, target_path) 427 fk_refcols.append(target_path) 454 428 455 429 # build up the primary join. This is needed when you have … … 459 433 # In some databases (at lease MySQL) the constraint name needs to 460 434 # be unique for the whole database, instead of per table. 461 fk_name = "%s_%s_fk" % (s elf.entity.table.name,435 fk_name = "%s_%s_fk" % (source_desc.tablename, 462 436 '_'.join(fk_colnames)) 463 437 source_desc.add_constraint(ForeignKeyConstraint( … … 487 461 return isinstance(other, BelongsTo) 488 462 489 def create_keys(self ):463 def create_keys(self, pk): 490 464 # make sure an inverse relationship exists 491 465 if self.inverse is None: … … 500 474 self.entity.__name__)) 501 475 # make sure it is set up because it creates the foreign key we'll need 502 self.inverse.create_keys( )476 self.inverse.create_keys(pk) 503 477 504 478 def get_prop_kwargs(self): -
elixir/trunk/elixir/statements.py
r164 r175 12 12 ''' 13 13 14 def __init__(self, target ):14 def __init__(self, target, when='init'): 15 15 ''' 16 16 target is the class which will handle this statement. For example, the … … 18 18 ''' 19 19 self.target = target 20 self.when = when 20 21 21 22 def __call__(self, *args, **kwargs): 22 # jam this statement into the class's statement list23 # jam this statement into one of the class's statement lists 23 24 class_locals = sys._getframe(1).f_locals 24 statements = class_locals.setdefault(STATEMENTS, []) 25 statement_map = class_locals.setdefault(STATEMENTS, {}) 26 statements = statement_map.setdefault(self.when, []) 25 27 statements.append((self, args, kwargs)) 26 28 27 29 @classmethod 28 def finalize(cls, entity): 29 for statement, args, kwargs in getattr(entity, STATEMENTS, []): 30 def finalize(cls, entity, when='init'): 31 statement_map = getattr(entity, STATEMENTS, {}) 32 statements = statement_map.get(when, []) 33 for statement, args, kwargs in statements: 30 34 getattr(statement.target, 'finalize', lambda x: None)(entity) 31 35 32 36 @classmethod 33 def process(cls, entity ):37 def process(cls, entity, when): 34 38 ''' 35 39 Apply all statements to the given entity. 36 40 ''' 37 # loop over all statements in the class's statement list 41 # loop over all statements in the class's statement list named "when" 38 42 # and apply them, i.e. instanciate the corresponding classes 39 for statement, args, kwargs in getattr(entity, STATEMENTS, []): 43 statement_map = getattr(entity, STATEMENTS, {}) 44 statements = statement_map.get(when, []) 45 for statement, args, kwargs in statements: 40 46 statement.target(entity, *args, **kwargs) -
elixir/trunk/tests/test_autoload.py
r142 r175 3 3 """ 4 4 5 from sqlalchemy import Table, Column, ForeignKey, BoundMetaData, create_engine5 from sqlalchemy import Table, Column, ForeignKey, MetaData 6 6 from elixir import * 7 7 import elixir … … 9 9 def setup(): 10 10 # First create the tables (it would be better to use an external db) 11 engine = create_engine('sqlite:///') 12 meta = BoundMetaData(engine) 11 meta = MetaData('sqlite:///') 13 12 14 13 person_table = Table('person', meta, … … 37 36 meta.create_all() 38 37 39 elixir.delay_setup = True40 38 elixir.options_defaults.update(dict(autoload=True, shortnames=True)) 41 39 … … 68 66 tablename='person_category') 69 67 70 elixir.delay_setup = False71 68 elixir.options_defaults.update(dict(autoload=False, shortnames=False)) 72 69 73 metadata.connect( engine)70 metadata.connect(meta.bind) 74 71 setup_all() 75 72 … … 118 115 objectstore.clear() 119 116 120 p = Person.get_by(name="Homer") 121 122 print "%s is %s's child." % (p.name, p.father.name) 123 print "His children are: %s." % ( 124 " and ".join(c.name for c in p.children)) 117 p = Person.q.filter_by(name="Homer").one() 125 118 126 119 assert p in p.father.children 120 assert p.father.name == "Abe" 127 121 assert p.father is Person.get_by(name="Abe") 128 122 assert p is Person.get_by(name="Lisa").father -
elixir/trunk/tests/test_autoload_mixed.py
r145 r175 5 5 6 6 def teardown(): 7 cleanup_all( )7 cleanup_all(True) 8 8 9 9 class TestAutoloadMixed(object): 10 10 def setup(self): 11 conn = metadata. engine.connect()11 conn = metadata.bind.connect() 12 12 conn.execute("""CREATE TABLE user 13 13 (user_id INTEGER PRIMARY KEY AUTOINCREMENT)""") … … 15 15 16 16 def test_belongs_to(self): 17 global User, Item18 19 17 class User(Entity): 20 18 using_options(tablename='user', autoload=True) … … 23 21 belongs_to('owner', of_kind='User') 24 22 25 create_all()23 setup_all(True) 26 24 27 25 assert Item.table.c['owner_user_id'].foreign_key.column.name == 'user_id' -
elixir/trunk/tests/test_autoload_nopk.py
r145 r175 16 16 class TestAutoload(object): 17 17 def test_pk(self): 18 local_meta = MetaData(metadata. engine)18 local_meta = MetaData(metadata.bind) 19 19 20 20 person_table = Table('person', local_meta, -
elixir/trunk/tests/test_has_property.py
r146 r175 3 3 """ 4 4 5 from sqlalchemy import column_property5 from sqlalchemy.orm import column_property 6 6 from elixir import * 7 7 -
elixir/trunk/tests/test_inherit.py
r146 r175 6 6 7 7 def setup(): 8 global Person, PersonExtended9 10 class Person(Entity):11 has_field('firstname', Unicode(30))12 has_field('surname', Unicode(30))13 belongs_to('sister', of_kind='Person')14 15 @property16 def name(self):17 return "%s %s" % (self.firstname, self.surname)18 19 def __str__(self):20 sister = self.sister and self.sister.name or "unknown"21 return "%s [%s]" % (self.name, sister)22 23 class PersonExtended(Person):24 has_field('age', Integer)25 belongs_to('parent', of_kind='PersonExtended')26 27 using_options(inheritance='single')28 29 def __str__(self):30 parent = self.parent and self.parent.name or "unknown"31 return "%s (%s) {%s}" % (super(PersonExtended, self).__str__(),32 self.age, parent)33 34 8 metadata.connect('sqlite:///') 35 9 10 def do_tst(inheritance, polymorphic, expected_res): 11 class A(Entity): 12 has_field('data1', String(20)) 13 using_options(inheritance=inheritance, polymorphic=polymorphic) 36 14 37 def teardown(): 38 cleanup_all() 15 class B(A): 16 has_field('data2', String(20)) 17 using_options(inheritance=inheritance, polymorphic=polymorphic) 18 19 class C(B): 20 has_field('data3', String(20)) 21 using_options(inheritance=inheritance, polymorphic=polymorphic) 22 23 class D(A): 24 has_field('data4', String(20)) 25 using_options(inheritance=inheritance, polymorphic=polymorphic) 26 27 setup_all(True) 28 29 A(data1='a1') 30 B(data1='b1', data2='b2') 31 C(data1='c1', data2='c2', data3='c3') 32 D(data1='d1', data4='d4') 33 34 objectstore.flush() 35 objectstore.clear() 36 37 res = {} 38 for class_ in (A, B, C, D): 39 res[class_.__name__] = class_.q.all() 40 res[class_.__name__].sort(key=lambda o: o.__class__.__name__) 41 42 for query_class in ('A', 'B', 'C', 'D'): 43 assert len(res[query_class]) == len(expected_res[query_class]) 44 for real, expected in zip(res[query_class], expected_res[query_class]): 45 assert real.__class__.__name__ == expected 39 46 40 47 41 48 class TestInheritance(object): 42 def setup(self):43 create_all()44 45 49 def teardown(self): 46 drop_all() 47 objectstore.clear() 50 cleanup_all(True) 48 51 49 52 def test_singletable_inheritance(self): 50 homer = PersonExtended(firstname="Homer", surname="Simpson", age=36) 51 # lisa needs to be a Person object, not a PersonExtended object because 52 # the sister relationship points to a Person, not a PersonExtended, so 53 # bart's sister must be a Person. This is to comply with SQLAlchemy's 54 # policy to prevent loading relationships with unintended types, unless 55 # explicitly enabled (enable_typechecks=False). 56 lisa = Person(firstname="Lisa", surname="Simpson") 57 bart = PersonExtended(firstname="Bart", surname="Simpson", 58 parent=homer, sister=lisa) 53 do_tst('single', False, { 54 'A': ('A', 'A', 'A', 'A'), 55 'B': ('B', 'B', 'B', 'B'), 56 'C': ('C', 'C', 'C', 'C'), 57 'D': ('D', 'D', 'D', 'D'), 58 }) 59 59 60 objectstore.flush() 61 objectstore.clear() 60 def test_polymorphic_singletable_inheritance(self): 61 do_tst('single', True, { 62 'A': ('A', 'B', 'C', 'D'), 63 'B': ('B', 'C'), 64 'C': ('C',), 65 'D': ('D',), 66 }) 62 67 63 p = PersonExtended.get_by(firstname="Bart") 68 def test_concrete_inheritance(self): 69 do_tst('concrete', False, { 70 'A': ('A',), 71 'B': ('B',), 72 'C': ('C',), 73 'D': ('D',), 74 }) 64 75 65 assert p.sister.name == 'Lisa Simpson' 66 assert p.parent.age == 36 76 def test_multitable_inheritance(self): 77 do_tst('multi', False, { 78 'A': ('A', 'A', 'A', 'A'), 79 'B': ('B', 'B'), 80 'C': ('C',), 81 'D': ('D',), 82 }) 67 83 68 for p in Person.select(): 69 print p 84 def test_polymorphic_multitable_inheritance(self): 85 do_tst('multi', True, { 86 'A': ('A', 'B', 'C', 'D'), 87 'B': ('B', 'C'), 88 'C': ('C',), 89 'D': ('D',), 90 }) 70 91 71 for p in PersonExtended.select():72 print p73 74 if __name__ == '__main__':75 setup()76 test = TestInheritance()77 test.setup()78 test.test_singletable_inheritance()79 test.teardown()80 teardown() -
elixir/trunk/tests/test_options.py
r146 r175 3 3 """ 4 4 5 from sqlalchemy import create_session, UniqueConstraint 5 from sqlalchemy import UniqueConstraint 6 from sqlalchemy.orm import create_session 6 7 from sqlalchemy.exceptions import SQLError, ConcurrentModificationError 7 8 from elixir import * … … 84 85 cleanup_all() 85 86 86 def test_table_options(self): 87 def test_unique_constraint(self): 88 87 89 class Person(Entity): 88 90 has_field('firstname', Unicode(30)) … … 108 110 assert raised 109 111 112 def test_unique_constraint_belongs_to(self): 113 class Author(Entity): 114 has_field("name", Unicode) 115 116 class Book(Entity): 117 has_field("title", Unicode, required=True) 118 belongs_to("author", of_kind="Author") 119 120 using_table_options(UniqueConstraint("title", "author_id")) 121 122 setup_all(True) 123 124 tolkien = Author(name="J. R. R. Tolkien") 125 lotr = Book(title="The Lord of the Rings", author=tolkien) 126 hobbit = Book(title="The Hobbit", author=tolkien) 127 128 objectstore.flush() 129 130 tolkien2 = Author(name="Tolkien") 131 hobbit2 = Book(title="The Hobbit", author=tolkien2) 132 133 objectstore.flush() 134 135 hobbit3 = Book(title="The Hobbit", author=tolkien) 136 137 raised = False 138 try: 139 objectstore.flush() 140 except SQLError: 141 raised = True 142 143 assert raised 144
