Changeset 347
- Timestamp:
- 06/19/08 15:35:21 (5 years ago)
- Location:
- elixir/trunk
- Files:
-
- 37 modified
-
CHANGES (modified) (24 diffs)
-
README (modified) (1 diff)
-
TODO (modified) (5 diffs)
-
elixir/__init__.py (modified) (8 diffs)
-
elixir/entity.py (modified) (42 diffs)
-
elixir/fields.py (modified) (9 diffs)
-
elixir/options.py (modified) (6 diffs)
-
elixir/properties.py (modified) (10 diffs)
-
elixir/py23compat.py (modified) (2 diffs)
-
elixir/relationships.py (modified) (47 diffs)
-
elixir/statements.py (modified) (4 diffs)
-
release.howto (modified) (1 diff)
-
setup.py (modified) (1 diff)
-
tests/a.py (modified) (1 diff)
-
tests/test_acts_as_list.py (modified) (8 diffs)
-
tests/test_associable.py (modified) (2 diffs)
-
tests/test_autoload.py (modified) (9 diffs)
-
tests/test_autoload_mixed.py (modified) (1 diff)
-
tests/test_autosetup.py (modified) (8 diffs)
-
tests/test_class_methods.py (modified) (2 diffs)
-
tests/test_collections.py (modified) (1 diff)
-
tests/test_custombase.py (modified) (3 diffs)
-
tests/test_encryption.py (modified) (3 diffs)
-
tests/test_events.py (modified) (4 diffs)
-
tests/test_fields.py (modified) (4 diffs)
-
tests/test_inherit.py (modified) (7 diffs)
-
tests/test_m2m.py (modified) (10 diffs)
-
tests/test_m2o.py (modified) (11 diffs)
-
tests/test_nestedclass.py (modified) (1 diff)
-
tests/test_o2m.py (modified) (5 diffs)
-
tests/test_options.py (modified) (15 diffs)
-
tests/test_order_by.py (modified) (3 diffs)
-
tests/test_packages.py (modified) (2 diffs)
-
tests/test_properties.py (modified) (9 diffs)
-
tests/test_sa_integration.py (modified) (4 diffs)
-
tests/test_through.py (modified) (1 diff)
-
tests/test_versioning.py (modified) (8 diffs)
Legend:
- Unmodified
- Added
- Removed
-
elixir/trunk/CHANGES
r346 r347 6 6 simple JSON-like dictionary notation (patch from Paul Johnston, 7 7 closes ticket #40). 8 - Added experimental (!) support for concrete table inheritance (both 8 - Added experimental (!) support for concrete table inheritance (both 9 9 polymorphic or not). Concrete polymorphic inheritance requires SQLalchemy 10 10 0.4.5 or later. … … 25 25 - Fixed multi-table inheritance when using a non default schema (closes #38) 26 26 - Fixed ManyToOne relationships using 'key' kwarg in their column_kwargs 27 (patch by Jason R. Coombs) 28 - Fixed inheritance with autoloaded entities: when using autoload, we 27 (patch by Jason R. Coombs) 28 - Fixed inheritance with autoloaded entities: when using autoload, we 29 29 shouldn't try to add columns to the table (closes tickets #41 and #43). 30 30 - Fixed ColumnProperty to work with latest version of SQLAlchemy (O.4.5 and 31 31 later) 32 - Fixed ManyToMany relationships when not using the default schema 32 - Fixed ManyToMany relationships when not using the default schema 33 33 (patch from Diez B. Roggisch, closes ticket #48) 34 34 … … 40 40 41 41 New features: 42 - Added an optional `check_concurrency` keyword argument to the versioning 43 extension, supporting the usage of SQLAlchemy's built-in optimistic 42 - Added an optional `check_concurrency` keyword argument to the versioning 43 extension, supporting the usage of SQLAlchemy's built-in optimistic 44 44 concurrency check. 45 45 46 46 Changes: 47 - Made Elixir python 2.3 compatible again (based on patches from 47 - Made Elixir python 2.3 compatible again (based on patches from 48 48 Jason R. Coombs) 49 49 … … 58 58 New features: 59 59 - Added a new elixir plugin for managing entities as (ordered) lists. 60 - Added a `column_format` keyword argument to `ManyToMany` which can be used 60 - Added a `column_format` keyword argument to `ManyToMany` which can be used 61 61 to specify an alternate format string for column names in the mapping table. 62 62 - Added support for custom base classes which inherit from another class (ie 63 63 not directly from object). 64 64 - Added an alternate (nicer) syntax to define synonym properties. This syntax 65 has a more limited scope, except that it can refer to properties defined in 65 has a more limited scope, except that it can refer to properties defined in 66 66 a parent entity. This is based on a patch from Alexandre da Silva. 67 67 … … 71 71 - The polymorphic_identity kwarg in using_mapper_options is not overriden 72 72 anymore by the one generated by Elixir (patch from Ben Bangert). 73 - Moved the format of the multi-table inheritance column to a constant in 73 - Moved the format of the multi-table inheritance column to a constant in 74 74 options (so that it can be changed globally). 75 75 - The foreign key constraint of the column in a multi-table inheritance is … … 80 80 options) if it doesn't use any statement itself. 81 81 - Made inheritance work for custom base classes (closes #25). 82 - Fixed the inverse relationship matching when the inverse relationship is 82 - Fixed the inverse relationship matching when the inverse relationship is 83 83 defined in a parent Entity (thanks to Alexandre da Silva). 84 84 - Fixed bug in setup_entities (it always used the global entity list and not … … 90 90 0.5.0 - 2007-12-08 91 91 92 Please see http://elixir.ematia.de/trac/wiki/Migrate04to05 for detailed 92 Please see http://elixir.ematia.de/trac/wiki/Migrate04to05 for detailed 93 93 upgrade notes. 94 94 … … 104 104 the class), before the setup phase happens, it won't work. This was done 105 105 because of a change in SQLAlchemy trunk (future SA 0.4.2) which broke that 106 piece of code (and prevented to use autosetup at all). Since that code 107 was a hack in the first place, instead of doing some even uglier hackery, 106 piece of code (and prevented to use autosetup at all). Since that code 107 was a hack in the first place, instead of doing some even uglier hackery, 108 108 I got rid of it altogether. 109 109 - Moved some format strings to constants in options, so that one can change … … 111 111 - Allow overriding primary_key columns on autoloaded entities (closes tickets 112 112 #20 and #22) 113 - Columns created by ManyToOne relationships can now optionally (through 113 - Columns created by ManyToOne relationships can now optionally (through 114 114 column_kwargs) *not* create an index (ie it's not harcoded anymore). 115 115 Suggestion by Jason R. Coombs. 116 116 117 117 Bug fixes: 118 - Fixed a nasty bug which prevented inheritance to work correctly when using 118 - Fixed a nasty bug which prevented inheritance to work correctly when using 119 119 the attribute syntax in many cases. 120 120 - Fixed associable extension to work with SQLAlchemy trunk (future 0.4.2). … … 123 123 which prevented to reuse class properties of one class in other (subsequent) 124 124 classes. 125 - Fixed our tests to work with SA trunk (future 0.4.2) (unicode data + use of 125 - Fixed our tests to work with SA trunk (future 0.4.2) (unicode data + use of 126 126 deprecated attributes) 127 127 128 128 0.4.0 - 2007-10-29 129 129 130 Please see http://elixir.ematia.de/trac/wiki/Migrate03to04 for detailed 130 Please see http://elixir.ematia.de/trac/wiki/Migrate03to04 for detailed 131 131 upgrade notes. 132 132 … … 134 134 - Implemented a new syntax to declare fields and relationships, much closer to 135 135 what is found in other Python ORM's. The with_fields syntax is now 136 deprecated in favor of a that new syntax. The old statement based (has_field 136 deprecated in favor of a that new syntax. The old statement based (has_field 137 137 et al.) syntax is still available though (and will remain so for quite some 138 138 time). This was done with help from a patch by Adam Gomaa. … … 140 140 non-polymorphic multi-table (aka joined table) inheritance. 141 141 - Added ext sub-package for additional Elixir statements. 142 - Added associable extension for generating polymorphic associations with 142 - Added associable extension for generating polymorphic associations with 143 143 Elixir statements. 144 144 - Added versioning extension to keep track to all changes to your entities by … … 152 152 - Added support to add any SQLAlchemy property on your mapper, through the 153 153 GenericProperty class (as well as the has_property statement). These can 154 work even if they rely on the entity columns (an thus need them to be 154 work even if they rely on the entity columns (an thus need them to be 155 155 defined before the property can be declared). See tests/test_properties.py 156 156 for examples. 157 - Added support for "manual session management" (ie you can now define an 157 - Added support for "manual session management" (ie you can now define an 158 158 entity with "using_options(session=None)" and it won't use any 159 159 SessionContext extension, nor receive the "query" attribute. … … 172 172 the Entity class. Now, if people don't like them, they have the option to 173 173 simply provide another base class. 174 - Default objectstore is now a ScopedSession when working on SQLAlchemy 0.4. 175 It means that it's not wrapped in an Objectstore object at all. This means, 176 that depending on the version of SA you are using, you'll get a slightly 174 - Default objectstore is now a ScopedSession when working on SQLAlchemy 0.4. 175 It means that it's not wrapped in an Objectstore object at all. This means, 176 that depending on the version of SA you are using, you'll get a slightly 177 177 different behavior. 178 178 - Relationships to other classes can now also be defined using the classes … … 184 184 - Added "through" and "via" keyword arguments on relationships and has_field 185 185 statement, to proxy values through relationships (uses association_proxy) 186 - Made EntityMeta public, so that people can actually define their own base 186 - Made EntityMeta public, so that people can actually define their own base 187 187 class. 188 188 - Changed the order of relationship kwargs processing so that computed kwargs … … 198 198 enables one "normal" entity (fully defined in Elixir) to refer to an entity 199 199 which is autoloaded. 200 - Added translation (from column name to column object) of the primary_key 200 - Added translation (from column name to column object) of the primary_key 201 201 mapper option so that it can actually be used. This allows to have entities 202 202 without any primary key defined at the table level. … … 208 208 Bug fixes: 209 209 - Reworked/cleaned tests so that they don't leak stuff to other tests (both at 210 the method level and module level) anymore. Uses nosetest's module level 210 the method level and module level) anymore. Uses nosetest's module level 211 211 fixture. 212 212 - Fixed relationships to entities whose primary_key field has been defined … … 215 215 - Fixed relationships to tables using a schema (patch by Neil Blakey-Milner) 216 216 - Made inverse relationships use backrefs. This fixes the "bidirectional 217 coherency" problem some people had before doing a flush. (based on a patch 217 coherency" problem some people had before doing a flush. (based on a patch 218 218 from Remi Jolin). 219 219 … … 227 227 name for your intermediary table than the one generated. You also _have to_ 228 228 specify at least one of either local_side or remote_side argument. 229 - Added support for the "version_id_col" option on entities. This option adds 229 - Added support for the "version_id_col" option on entities. This option adds 230 230 a column to the table which will be used to prevent concurrent modifications 231 231 on any row of the entity's table (i.e. it will raise an error if it happens). … … 238 238 belongs_to relationships. The content of that argument is forwarded to the 239 239 foreign key constraint. 240 - Foreign key names generated by belongs_to relationships use column names 240 - Foreign key names generated by belongs_to relationships use column names 241 241 instead of relation names in case we have a relation with the same name 242 242 defined in several entities inheriting from the same entity using single- … … 265 265 0.1.0 while I had introduced more decorators in the trunk in the mean time). 266 266 267 - Made some PEP8 tweaks in many places. Used the pep8 script provided with 267 - Made some PEP8 tweaks in many places. Used the pep8 script provided with 268 268 Cheesecake. 269 269 - Some cleanup/useless code removal … … 274 274 ensure that we apply statements to the proper class. We now attach the 275 275 statement list to the class itself, rather than attaching it to a global 276 list that is neither threadsafe, nor safe when doing nested class 276 list that is neither threadsafe, nor safe when doing nested class 277 277 definition. Also added a test to validate that this works. 278 278 - implemented singletable non-polymorphic inheritance 279 279 - added support to pass non-keyword arguments to tables. You just pass 280 280 them to the using_table_options statement and they will be forwarded to the 281 table along with the keyword arguments. This can be used to set table 281 table along with the keyword arguments. This can be used to set table 282 282 constraints. 283 283 - added support for deferred columns (use the "deferred" keyword argument on … … 285 285 - added a "required" keyword argument on fields and BelongsTo 286 286 relationships. This is the opposite of the "nullable" SA argument. 287 - added a "column_kwargs" keyword argument to BelongsTo relationships 287 - added a "column_kwargs" keyword argument to BelongsTo relationships 288 288 to forward any keyword argument directly to the SA Column. 289 289 - added support for the use_alter and constraint_kwargs kwargs on BelongsTo … … 293 293 -> removed it from HasAndBelongsToMany relations, since I think a 294 294 circular foreign key dependency can't happen with those relations. 295 - fixed foreign key names on MySQL (and possibly other) databases by 295 - fixed foreign key names on MySQL (and possibly other) databases by 296 296 making sure the generated name is unique for the whole database, and not 297 297 only for the table on which it applies. -
elixir/trunk/README
r254 r347 4 4 5 5 Elixir is a declarative layer on top of the `SQLAlchemy library 6 <http://www.sqlalchemy.org/>`_. It is a fairly thin wrapper, which provides 7 the ability to create simple Python classes that map directly to relational 6 <http://www.sqlalchemy.org/>`_. It is a fairly thin wrapper, which provides 7 the ability to create simple Python classes that map directly to relational 8 8 database tables (this pattern is often referred to as the Active Record design 9 pattern), providing many of the benefits of traditional databases 10 without losing the convenience of Python objects. 9 pattern), providing many of the benefits of traditional databases 10 without losing the convenience of Python objects. 11 11 12 Elixir is intended to replace the ActiveMapper SQLAlchemy extension, and the 12 Elixir is intended to replace the ActiveMapper SQLAlchemy extension, and the 13 13 TurboEntity project but does not intend to replace SQLAlchemy's core features, 14 and instead focuses on providing a simpler syntax for defining model objects 15 when you do not need the full expressiveness of SQLAlchemy's manual mapper 14 and instead focuses on providing a simpler syntax for defining model objects 15 when you do not need the full expressiveness of SQLAlchemy's manual mapper 16 16 definitions. 17 17 -
elixir/trunk/TODO
r271 r347 27 27 ------------ 28 28 29 - Besides, the current system also has another case I don't like: if the user 29 - Besides, the current system also has another case I don't like: if the user 30 30 specifies an inverse (on one or both sides) but also set a table name on one 31 side (or two different table names), it will consider the relation as being 32 different even though the user explicitly told it was the same. This should 33 not happen. The system should rather throw an exception in that case. But 31 side (or two different table names), it will consider the relation as being 32 different even though the user explicitly told it was the same. This should 33 not happen. The system should rather throw an exception in that case. But 34 34 this last part should be easilty fixable, I think (it'a a matter of tweaking 35 35 the is_inverse method of the HasAndBelongsToMany class)... … … 51 51 http://www.sqlalchemy.org/trac/attachment/ticket/547 52 52 * the provided "_find_class_descriptor" seem overly complex though 53 * the (unlikely?) case where a parent who is not inheriting from XFBase (or 53 * the (unlikely?) case where a parent who is not inheriting from XFBase (or 54 54 Entity) defines a property will probably fail because of line 16. 55 55 … … 58 58 - support all mapper arguments which take column arguments in a generic way 59 59 60 - instead of linking the descriptor in the entity (cls._descriptor) we could 61 do it externally, like the mappers in SA. This would solve some of the 60 - instead of linking the descriptor in the entity (cls._descriptor) we could 61 do it externally, like the mappers in SA. This would solve some of the 62 62 ugliness we have in the current implementation (mostly in target). 63 63 … … 81 81 http://www.sqlalchemy.org/trac/browser/sqlalchemy/trunk/examples/poly_assoc 82 82 83 - investigate whether it would be possible to do a generic acts_as(xxx) 83 - investigate whether it would be possible to do a generic acts_as(xxx) 84 84 instead of the acts_as_taggable Jonathan demonstrated 85 85 86 86 - implement something like: 87 87 … … 98 98 ie relations using any "selectable" instead of the normal object table. 99 99 100 mapper(User, users, 100 mapper(User, users, 101 101 properties={ 102 102 'orders': relation(mapper(Order, orders), order_by=orders.c.id), -
elixir/trunk/elixir/__init__.py
r343 r347 7 7 database tables (this pattern is often referred to as the Active Record design 8 8 pattern), providing many of the benefits of traditional databases 9 without losing the convenience of Python objects. 9 without losing the convenience of Python objects. 10 10 11 11 Elixir is intended to replace the ActiveMapper SQLAlchemy extension, and the … … 45 45 46 46 __all__ = ['Entity', 'EntityMeta', 'EntityCollection', 'entities', 47 'Field', 'has_field', 'with_fields', 47 'Field', 'has_field', 'with_fields', 48 48 'has_property', 'GenericProperty', 'ColumnProperty', 'Synonym', 49 49 'belongs_to', 'has_one', 'has_many', 'has_and_belongs_to_many', … … 62 62 63 63 class Objectstore(object): 64 """a wrapper for a SQLAlchemy session-making object, such as 64 """a wrapper for a SQLAlchemy session-making object, such as 65 65 SessionContext or ScopedSession. 66 66 67 67 Uses the ``registry`` attribute present on both objects 68 68 (versions 0.3 and 0.4) in order to return the current 69 69 contextual session. 70 70 """ 71 71 72 72 def __init__(self, ctx): 73 73 self.context = ctx … … 75 75 def __getattr__(self, name): 76 76 return getattr(self.context.registry(), name) 77 77 78 78 session = property(lambda s:s.context.registry()) 79 79 80 80 # default session 81 try: 81 try: 82 82 from sqlalchemy.orm import scoped_session 83 83 session = scoped_session(sqlalchemy.orm.create_session) 84 except ImportError: 84 except ImportError: 85 85 # Not on version 0.4 of sqlalchemy 86 86 from sqlalchemy.ext.sessioncontext import SessionContext … … 98 98 class EntityCollection(list): 99 99 def __init__(self): 100 # _entities is a dict of entities for each frame where there are 100 # _entities is a dict of entities for each frame where there are 101 101 # entities defined. 102 102 self._entities = {} 103 103 # self._entity_map = {} 104 104 list.__init__(self) 105 105 106 106 def map_entity(self, entity): 107 107 self.append(entity) … … 121 121 caller_entities[key] = entity 122 122 123 # Append all entities which are currently visible by the entity. This 124 # will find more entities only if some of them where imported from 123 # Append all entities which are currently visible by the entity. This 124 # will find more entities only if some of them where imported from 125 125 # another module. 126 for ent in [e for e in caller_frame.f_locals.values() 126 for ent in [e for e in caller_frame.f_locals.values() 127 127 if isinstance(e, EntityMeta)]: 128 128 caller_entities[ent.__name__] = ent 129 129 130 130 # self._entity_map[key] = entity 131 131 132 132 def resolve(self, key, entity=None): 133 133 ''' … … 144 144 return getattr(module, classname, None) 145 145 else: 146 # If not, try the list of entities of the "caller" of the 147 # source class. Most of the time, this will be the module 148 # the class is defined in. But it could also be a method 146 # If not, try the list of entities of the "caller" of the 147 # source class. Most of the time, this will be the module 148 # the class is defined in. But it could also be a method 149 149 # (inner classes). 150 150 caller_entities = self._entities[entity._caller] … … 189 189 190 190 def cleanup_all(drop_tables=False, *args, **kwargs): 191 '''Clear all mappers, clear the session, and clear all metadatas. 191 '''Clear all mappers, clear the session, and clear all metadatas. 192 192 Optionally drops the tables. 193 193 ''' -
elixir/trunk/elixir/entity.py
r343 r347 29 29 30 30 31 try: 31 try: 32 32 from sqlalchemy.orm import ScopedSession 33 except ImportError: 33 except ImportError: 34 34 # Not on sqlalchemy version 0.4 35 35 ScopedSession = type(None) … … 57 57 return session.registry().query(cls) 58 58 59 if not 'query' in cls.__dict__: 59 if not 'query' in cls.__dict__: 60 60 cls.query = query() 61 61 … … 70 70 EntityDescriptor describes fields and options needed for table creation. 71 71 ''' 72 72 73 73 def __init__(self, entity): 74 74 entity.table = None … … 90 90 if self.parent: 91 91 raise Exception('%s entity inherits from several entities,' 92 ' and this is not supported.' 92 ' and this is not supported.' 93 93 % self.entity.__name__) 94 94 else: … … 117 117 118 118 for option in ('autosetup', 'inheritance', 'polymorphic', 'identity', 119 'autoload', 'tablename', 'shortnames', 120 'auto_primarykey', 'version_id_col', 119 'autoload', 'tablename', 'shortnames', 120 'auto_primarykey', 'version_id_col', 121 121 'allowcoloverride'): 122 122 setattr(self, option, options.options_defaults[option]) 123 123 124 124 for option_dict in ('mapper_options', 'table_options'): 125 setattr(self, option_dict, 125 setattr(self, option_dict, 126 126 options.options_defaults[option_dict].copy()) 127 127 128 128 def setup_options(self): 129 129 ''' 130 Setup any values that might depend on using_options. For example, the 130 Setup any values that might depend on using_options. For example, the 131 131 tablename or the metadata. 132 132 ''' … … 144 144 elif not hasattr(session, 'registry'): 145 145 # Both SessionContext and ScopedSession have a registry attribute, 146 # but objectstores (whether Elixir's or Activemapper's) don't, so 146 # but objectstores (whether Elixir's or Activemapper's) don't, so 147 147 # if we are here, it means an Objectstore is used for the session. 148 #XXX: still true for activemapper post 0.4? 148 #XXX: still true for activemapper post 0.4? 149 149 objectstore = session 150 150 session = objectstore.context … … 193 193 def create_pk_cols(self): 194 194 """ 195 Create primary_key columns. That is, call the 'create_pk_cols' 196 builders then add a primary key to the table if it hasn't already got 197 one and needs one. 198 199 This method is "semi-recursive" in some cases: it calls the 195 Create primary_key columns. That is, call the 'create_pk_cols' 196 builders then add a primary key to the table if it hasn't already got 197 one and needs one. 198 199 This method is "semi-recursive" in some cases: it calls the 200 200 create_keys method on ManyToOne relationships and those in turn call 201 create_pk_cols on their target. It shouldn't be possible to have an 201 create_pk_cols on their target. It shouldn't be possible to have an 202 202 infinite loop since a loop of primary_keys is not a valid situation. 203 203 """ … … 210 210 if self.parent: 211 211 if self.inheritance == 'multi': 212 # Add columns with foreign keys to the parent's primary 213 # key columns 212 # Add columns with foreign keys to the parent's primary 213 # key columns 214 214 parent_desc = self.parent._descriptor 215 215 schema = parent_desc.table_options.get('schema', None) 216 tablename = parent_desc.tablename 216 tablename = parent_desc.tablename 217 217 if schema is not None: 218 218 tablename = "%s.%s" % (schema, tablename) … … 223 223 224 224 # It seems like SA ForeignKey is not happy being given 225 # a real column object when said column is not yet 225 # a real column object when said column is not yet 226 226 # attached to a table 227 227 pk_col_name = "%s.%s" % (tablename, pk_col.key) … … 242 242 243 243 self.add_column( 244 Column(colname, options.DEFAULT_AUTO_PRIMARYKEY_TYPE, 244 Column(colname, options.DEFAULT_AUTO_PRIMARYKEY_TYPE, 245 245 primary_key=True)) 246 246 self._pk_col_done = True … … 251 251 def before_table(self): 252 252 self.call_builders('before_table') 253 253 254 254 def setup_table(self, only_autoloaded=False): 255 255 ''' 256 Create a SQLAlchemy table-object with all columns that have been 256 Create a SQLAlchemy table-object with all columns that have been 257 257 defined up to this point. 258 258 ''' … … 271 271 if self.inheritance == 'single': 272 272 # we know the parent is setup before the child 273 self.entity.table = self.parent.table 274 275 # re-add the entity columns to the parent entity so that 276 # they are added to the parent's table (whether the 273 self.entity.table = self.parent.table 274 275 # re-add the entity columns to the parent entity so that 276 # they are added to the parent's table (whether the 277 277 # parent's table is already setup or not). 278 278 for col in self.columns: … … 281 281 self.parent._descriptor.add_constraint(constraint) 282 282 return 283 elif self.inheritance == 'concrete': 284 #TODO: we should also copy columns from the parent table 283 elif self.inheritance == 'concrete': 284 #TODO: we should also copy columns from the parent table 285 285 # if the parent is a base (abstract?) entity (whatever the 286 286 # inheritance type -> elif will need to be changed) 287 287 288 # Copy all non-primary key columns from parent table 288 # Copy all non-primary key columns from parent table 289 289 # (primary key columns have already been copied earlier). 290 290 for col in self.parent._descriptor.columns: … … 292 292 self.add_column(col.copy()) 293 293 294 #FIXME: use the public equivalent of _get_colspec when 295 #available 294 #FIXME: use the public equivalent of _get_colspec when 295 #available 296 296 for con in self.parent._descriptor.constraints: 297 297 self.add_constraint( … … 306 306 self.inheritance in ('single', 'multi') and \ 307 307 self.children and not self.parent: 308 self.add_column(Column(self.polymorphic, 308 self.add_column(Column(self.polymorphic, 309 309 options.POLYMORPHIC_COL_TYPE)) 310 310 … … 315 315 316 316 args = self.columns + self.constraints + self.table_args 317 318 self.entity.table = Table(self.tablename, self.metadata, 317 318 self.entity.table = Table(self.tablename, self.metadata, 319 319 *args, **kwargs) 320 320 … … 330 330 for func in methods: 331 331 ret = func(instance) 332 # I couldn't commit myself to force people to 333 # systematicaly return EXT_CONTINUE in all their event 332 # I couldn't commit myself to force people to 333 # systematicaly return EXT_CONTINUE in all their event 334 334 # methods. 335 335 # But not doing that diverge to how SQLAlchemy works. 336 # I should try to convince Mike to do EXT_CONTINUE by 336 # I should try to convince Mike to do EXT_CONTINUE by 337 337 # default, and stop processing as the special case. 338 338 # if ret != EXT_CONTINUE: … … 351 351 if not methods: 352 352 return 353 353 354 354 # transform that list into methods themselves 355 355 for event in methods: 356 356 methods[event] = make_proxy_method(methods[event]) 357 357 358 358 # create a custom mapper extension class, tailored to our entity 359 359 ext = type('EventMapperExtension', (MapperExtension,), methods)() 360 360 361 361 # then, make sure that the entity's mapper has our mapper extension 362 362 self.add_mapper_extension(ext) … … 374 374 if isinstance(order_by, basestring): 375 375 order_by = [order_by] 376 376 377 377 order = list() 378 378 for colname in order_by: … … 397 397 if self.order_by: 398 398 kwargs['order_by'] = self.translate_order_by(self.order_by) 399 399 400 400 if self.version_id_col: 401 401 kwargs['version_id_col'] = self.get_column(self.version_id_col) … … 416 416 if self.inheritance == 'concrete': 417 417 keys = [(self.identity, self.entity.table)] 418 keys.extend([(child._descriptor.identity, child.table) 418 keys.extend([(child._descriptor.identity, child.table) 419 419 for child in self._get_children()]) 420 #XXX: we might need to change the alias name so that 420 #XXX: we might need to change the alias name so that 421 421 # children (which are parent themselves) don't end up 422 422 # with the same alias than their parent? … … 432 432 433 433 #TODO: this is an optimization, and it breaks the multi 434 # table polymorphic inheritance test with a relation. 435 # So I turn it off for now. We might want to provide an 434 # table polymorphic inheritance test with a relation. 435 # So I turn it off for now. We might want to provide an 436 436 # option to turn it on. 437 437 # if self.inheritance == 'multi': … … 486 486 if check_duplicate is None: 487 487 check_duplicate = not self.allowcoloverride 488 488 489 489 if check_duplicate and self.get_column(col.key, False) is not None: 490 raise Exception("Column '%s' already exist in '%s' ! " % 490 raise Exception("Column '%s' already exist in '%s' ! " % 491 491 (col.key, self.entity.__name__)) 492 492 self._columns.append(col) 493 493 494 494 if col.primary_key: 495 495 self.has_pk = True 496 496 497 497 # Autosetup triggers shouldn't be active anymore at this point, so we 498 # can theoretically access the entity's table safely. But the problem 499 # is that if, for some reason, the trigger removal phase didn't 500 # happen, we'll get an infinite loop. So we just make sure we don't 498 # can theoretically access the entity's table safely. But the problem 499 # is that if, for some reason, the trigger removal phase didn't 500 # happen, we'll get an infinite loop. So we just make sure we don't 501 501 # get one in any case. 502 502 table = type.__getattribute__(self.entity, 'table') 503 503 if table: 504 504 if check_duplicate and col.key in table.columns.keys(): 505 raise Exception("Column '%s' already exist in table '%s' ! " % 505 raise Exception("Column '%s' already exist in table '%s' ! " % 506 506 (col.key, table.name)) 507 507 table.append_column(col) … … 509 509 def add_constraint(self, constraint): 510 510 self.constraints.append(constraint) 511 511 512 512 table = self.entity.table 513 513 if table: … … 516 516 def add_property(self, name, property, check_duplicate=True): 517 517 if check_duplicate and name in self.properties: 518 raise Exception("property '%s' already exist in '%s' ! " % 518 raise Exception("property '%s' already exist in '%s' ! " % 519 519 (name, self.entity.__name__)) 520 520 self.properties[name] = property 521 521 522 #FIXME: something like this is needed to propagate the relationships from 523 # parent entities to their children in a concrete inheritance scenario. But 522 #FIXME: something like this is needed to propagate the relationships from 523 # parent entities to their children in a concrete inheritance scenario. But 524 524 # this doesn't work because of the backref matching code. 525 525 # if self.children and self.inheritance == 'concrete': … … 530 530 if mapper: 531 531 mapper.add_property(name, property) 532 532 533 533 def add_mapper_extension(self, extension): 534 534 extensions = self.mapper_options.get('extension', []) … … 585 585 586 586 def columns(self): 587 #FIXME: this would be more correct but it breaks inheritance, so I'll 587 #FIXME: this would be more correct but it breaks inheritance, so I'll 588 588 # use the old test for now. 589 589 # if self.entity.table: 590 if self.autoload: 590 if self.autoload: 591 591 return self.entity.table.columns 592 592 else: 593 #FIXME: depending on the type of inheritance, we should also 594 # return the parent entity's columns (for example for order_by 593 #FIXME: depending on the type of inheritance, we should also 594 # return the parent entity's columns (for example for order_by 595 595 # using a column defined in the parent. 596 596 return self._columns … … 652 652 def is_entity(cls): 653 653 """ 654 Scan the bases classes of `cls` to see if any is an instance of 654 Scan the bases classes of `cls` to see if any is an instance of 655 655 EntityMeta. If we don't find any, it means it is either an unrelated class 656 656 or an entity base class (like the 'Entity' class). … … 663 663 class EntityMeta(type): 664 664 """ 665 Entity meta class. 666 You should only use it directly if you want to define your own base class 665 Entity meta class. 666 You should only use it directly if you want to define your own base class 667 667 for your entities (ie you don't want to use the provided 'Entity' class). 668 668 """ … … 670 670 def __init__(cls, name, bases, dict_): 671 671 # Only process further subclasses of the base classes (Entity et al.), 672 # not the base classes themselves. We don't want the base entities to 673 # be registered in an entity collection, nor to have a table name and 674 # so on. 672 # not the base classes themselves. We don't want the base entities to 673 # be registered in an entity collection, nor to have a table name and 674 # so on. 675 675 if not is_entity(cls): 676 676 return … … 679 679 desc = cls._descriptor = EntityDescriptor(cls) 680 680 681 # Process attributes (using the assignment syntax), looking for 681 # Process attributes (using the assignment syntax), looking for 682 682 # 'Property' instances and attaching them to this entity. 683 properties = [(name, attr) for name, attr in dict_.iteritems() 683 properties = [(name, attr) for name, attr in dict_.iteritems() 684 684 if isinstance(attr, Property)] 685 685 sorted_props = sorted(properties, key=lambda i: i[1]._counter) … … 696 696 697 697 # create trigger proxies 698 # TODO: support entity_name... It makes sense only for autoloaded 698 # TODO: support entity_name... It makes sense only for autoloaded 699 699 # tables for now, and would make more sense if we support "external" 700 700 # tables … … 730 730 md.tables[cls._table_key] = table_proxy 731 731 732 # We need to monkeypatch the metadata's table iterator method because 733 # otherwise it doesn't work if the setup is triggered by the 732 # We need to monkeypatch the metadata's table iterator method because 733 # otherwise it doesn't work if the setup is triggered by the 734 734 # metadata.create_all(). 735 # This is because ManyToMany relationships add tables AFTER the list 736 # of tables that are going to be created is "computed" 735 # This is because ManyToMany relationships add tables AFTER the list 736 # of tables that are going to be created is "computed" 737 737 # (metadata.tables.values()). 738 738 # see: 739 # - table_iterator method in MetaData class in sqlalchemy/schema.py 739 # - table_iterator method in MetaData class in sqlalchemy/schema.py 740 740 # - visit_metadata method in sqlalchemy/ansisql.py 741 741 original_table_iterator = md.table_iterator 742 if not hasattr(original_table_iterator, 742 if not hasattr(original_table_iterator, 743 743 '_non_elixir_patched_iterator'): 744 744 def table_iterator(*args, **kwargs): … … 773 773 md = desc.metadata 774 774 775 # the fake table could have already been removed (namely in a 775 # the fake table could have already been removed (namely in a 776 776 # single table inheritance scenario) 777 777 md.tables.pop(cls._table_key, None) … … 784 784 del cls._has_triggers 785 785 786 786 787 787 def setup_entities(entities): 788 788 '''Setup all entities in the list passed as argument''' … … 808 808 def cleanup_entities(entities): 809 809 """ 810 Try to revert back the list of entities passed as argument to the state 811 they had just before their setup phase. It will not work entirely for 810 Try to revert back the list of entities passed as argument to the state 811 they had just before their setup phase. It will not work entirely for 812 812 autosetup entities as we need to remove the autosetup triggers. 813 813 814 As of now, this function is *not* functional in that it doesn't revert to 815 the exact same state the entities were before setup. For example, the 814 As of now, this function is *not* functional in that it doesn't revert to 815 the exact same state the entities were before setup. For example, the 816 816 properties do not work yet as those would need to be regenerated (since the 817 columns they are based on are regenerated too -- and as such the 818 corresponding joins are not correct) but this doesn't happen because of 819 the way relationship setup is designed to be called only once (especially 817 columns they are based on are regenerated too -- and as such the 818 corresponding joins are not correct) but this doesn't happen because of 819 the way relationship setup is designed to be called only once (especially 820 820 the backref stuff in create_properties). 821 821 """ … … 830 830 entity.table = None 831 831 entity.mapper = None 832 832 833 833 desc._pk_col_done = False 834 834 desc.has_pk = False … … 841 841 ''' 842 842 The base class for all entities 843 843 844 844 All Elixir model objects should inherit from this class. Statements can 845 845 appear within the body of the definition of an entity to define its 846 846 fields, relationships, and other options. 847 847 848 848 Here is an example: 849 849 850 850 .. sourcecode:: python 851 851 852 852 class Person(Entity): 853 853 name = Field(Unicode(128)) 854 854 birthdate = Field(DateTime, default=datetime.now) 855 855 856 856 Please note, that if you don't specify any primary keys, Elixir will 857 857 automatically create one called ``id``. 858 858 859 859 For further information, please refer to the provided examples or 860 860 tutorial. 861 861 ''' 862 862 __metaclass__ = EntityMeta 863 863 864 864 def __init__(self, **kwargs): 865 865 for key, value in kwargs.items(): … … 891 891 # Build a lookup dict: {(pk1, pk2): value} 892 892 lookup = dict([ 893 (tuple([getattr(o, c.name) for c in pkey]), o) 893 (tuple([getattr(o, c.name) for c in pkey]), o) 894 894 for o in dbdata]) 895 895 for row in data[rname]: 896 # If any primary key columns are missing or None, 896 # If any primary key columns are missing or None, 897 897 # create a new object 898 898 if [1 for c in pkey if not row.get(c.name)]: … … 904 904 905 905 # If the row isn't found, we must fail the request 906 # in a web scenario, this could be a parameter 906 # in a web scenario, this could be a parameter 907 907 # tampering attack 908 908 if not subobj: … … 932 932 for col in table.c: 933 933 columns.append(col) 934 934 935 935 data = dict([(col.name, getattr(self, col.name)) 936 936 for col in columns if col.name not in exclude]) … … 961 961 return object_session(self).expunge(self, *args, **kwargs) 962 962 963 # This bunch of session methods, along with all the query methods below 963 # This bunch of session methods, along with all the query methods below 964 964 # only make sense when using a global/scoped/contextual session. 965 965 def _global_session(self): -
elixir/trunk/elixir/fields.py
r320 r347 1 1 ''' 2 This module provides support for defining the fields (columns) of your 2 This module provides support for defining the fields (columns) of your 3 3 entities. Elixir currently supports two syntaxes to do so: the default 4 4 `Attribute-based syntax`_ as well as the has_field_ DSL statement. 5 5 6 Note that the old with_fields_ statement is currently deprecated in favor of 6 Note that the old with_fields_ statement is currently deprecated in favor of 7 7 the `Attribute-based syntax`_. 8 8 … … 28 28 29 29 30 The Field class takes one mandatory argument, which is its type. Please refer 31 to SQLAlchemy documentation for a list of `types supported by SQLAlchemy 30 The Field class takes one mandatory argument, which is its type. Please refer 31 to SQLAlchemy documentation for a list of `types supported by SQLAlchemy 32 32 <http://www.sqlalchemy.org/docs/04/types.html>`_. 33 33 34 Following that first mandatory argument, fields can take any number of 35 optional keyword arguments. Please note that all the **arguments** that are 36 **not specifically processed by Elixir**, as mentioned in the documentation 37 below **are passed on to the SQLAlchemy ``Column`` object**. Please refer to 38 the `SQLAlchemy Column object's documentation 34 Following that first mandatory argument, fields can take any number of 35 optional keyword arguments. Please note that all the **arguments** that are 36 **not specifically processed by Elixir**, as mentioned in the documentation 37 below **are passed on to the SQLAlchemy ``Column`` object**. Please refer to 38 the `SQLAlchemy Column object's documentation 39 39 <http://www.sqlalchemy.org/docs/04/sqlalchemy_schema.html 40 #docstrings_sqlalchemy.schema_Column>`_ for more details about other 40 #docstrings_sqlalchemy.schema_Column>`_ for more details about other 41 41 supported keyword arguments. 42 42 … … 78 78 79 79 The first argument is the name of the field, the second is its type. Following 80 these, any number of keyword arguments can be specified for additional 80 these, any number of keyword arguments can be specified for additional 81 81 behavior. The following arguments are supported: 82 82 … … 111 111 ----------- 112 112 The `with_fields` statement is **deprecated** in favor of the `attribute-based 113 syntax`_. 114 115 It allows you to define all fields of an entity at once. 113 syntax`_. 114 115 It allows you to define all fields of an entity at once. 116 116 Each keyword argument to this statement represents one field, which should 117 be a `Field` object. The first argument to a Field object is its type. 117 be a `Field` object. The first argument to a Field object is its type. 118 118 Following it, any number of keyword arguments can be specified for 119 additional behavior. The `with_fields` statement supports the same keyword 119 additional behavior. The `with_fields` statement supports the same keyword 120 120 arguments than the `has_field` statement. 121 121 … … 145 145 ''' 146 146 Represents the definition of a 'field' on an entity. 147 147 148 148 This class represents a column on the table where the entity is stored. 149 149 This object is only used with the `with_fields` syntax for defining all … … 151 151 require the manual creation of this object. 152 152 ''' 153 153 154 154 def __init__(self, type, *args, **kwargs): 155 155 super(Field, self).__init__() 156 156 157 157 self.colname = kwargs.pop('colname', None) 158 158 self.synonym = kwargs.pop('synonym', None) … … 165 165 self.column = None 166 166 self.property = None 167 167 168 168 self.args = args 169 169 self.kwargs = kwargs … … 205 205 206 206 if self.synonym: 207 self.entity._descriptor.add_property(self.synonym, 207 self.entity._descriptor.add_property(self.synonym, 208 208 synonym(self.name)) 209 209 … … 211 211 def has_field_handler(entity, name, *args, **kwargs): 212 212 if 'through' in kwargs: 213 setattr(entity, name, 214 association_proxy(kwargs.pop('through'), 213 setattr(entity, name, 214 association_proxy(kwargs.pop('through'), 215 215 kwargs.pop('attribute', name), 216 216 **kwargs)) -
elixir/trunk/elixir/options.py
r336 r347 1 1 ''' 2 2 This module provides support for defining several options on your Elixir 3 entities. There are three different kinds of options that can be set 3 entities. There are three different kinds of options that can be set 4 4 up, and for this there are three different statements: using_options_, 5 5 using_table_options_ and using_mapper_options_. 6 6 7 Alternatively, these options can be set on all Elixir entities by modifying 7 Alternatively, these options can be set on all Elixir entities by modifying 8 8 the `options_defaults` dictionary before defining any entity. 9 9 … … 12 12 The 'using_options' DSL statement allows you to set up some additional 13 13 behaviors on your model objects, including table names, ordering, and 14 more. To specify an option, simply supply the option as a keyword 14 more. To specify an option, simply supply the option as a keyword 15 15 argument onto the statement, as follows: 16 16 … … 139 139 `using_table_options` 140 140 --------------------- 141 The 'using_table_options' DSL statement allows you to set up some 142 additional options on your entity table. It is meant only to handle the 141 The 'using_table_options' DSL statement allows you to set up some 142 additional options on your entity table. It is meant only to handle the 143 143 options which are not supported directly by the 'using_options' statement. 144 By opposition to the 'using_options' statement, these options are passed 144 By opposition to the 'using_options' statement, these options are passed 145 145 directly to the underlying SQLAlchemy Table object (both non-keyword arguments 146 146 and keyword arguments) without any processing. … … 150 150 #docstrings_sqlalchemy.schema_Table>`_. 151 151 152 You might also be interested in the section about `constraints 152 You might also be interested in the section about `constraints 153 153 <http://www.sqlalchemy.org/docs/04/metadata.html#metadata_constraints>`_. 154 154 155 155 `using_mapper_options` 156 156 ---------------------- 157 The 'using_mapper_options' DSL statement allows you to set up some 158 additional options on your entity mapper. It is meant only to handle the 157 The 'using_mapper_options' DSL statement allows you to set up some 158 additional options on your entity mapper. It is meant only to handle the 159 159 options which are not supported directly by the 'using_options' statement. 160 By opposition to the 'using_options' statement, these options are passed 161 directly to the underlying SQLAlchemy mapper (as keyword arguments) 160 By opposition to the 'using_options' statement, these options are passed 161 directly to the underlying SQLAlchemy mapper (as keyword arguments) 162 162 without any processing. 163 163 164 For further information, please refer to the `SQLAlchemy mapper 165 function's documentation 164 For further information, please refer to the `SQLAlchemy mapper 165 function's documentation 166 166 <http://www.sqlalchemy.org/docs/04/sqlalchemy_orm.html 167 167 #docstrings_sqlalchemy.orm_modfunc_mapper>`_. … … 187 187 POLYMORPHIC_COL_TYPE = String(POLYMORPHIC_COL_SIZE) 188 188 189 # 189 # 190 190 options_defaults = dict( 191 191 autosetup=False, … … 217 217 setattr(entity._descriptor, kwarg, kwargs[kwarg]) 218 218 else: 219 raise Exception("'%s' is not a valid option for Elixir entities." 219 raise Exception("'%s' is not a valid option for Elixir entities." 220 220 % kwarg) 221 221 -
elixir/trunk/elixir/properties.py
r327 r347 1 1 ''' 2 2 This module provides support for defining properties on your entities. It both 3 provides, the `Property` class which acts as a building block for common 4 properties such as fields and relationships (for those, please consult the 5 corresponding modules), but also provides some more specialized properties, 6 such as `ColumnProperty` and `Synonym`. It also provides the GenericProperty 7 class which allows you to wrap any SQLAlchemy property, and its DSL-syntax 3 provides, the `Property` class which acts as a building block for common 4 properties such as fields and relationships (for those, please consult the 5 corresponding modules), but also provides some more specialized properties, 6 such as `ColumnProperty` and `Synonym`. It also provides the GenericProperty 7 class which allows you to wrap any SQLAlchemy property, and its DSL-syntax 8 8 equivalent: has_property_. 9 9 10 10 `has_property` 11 11 -------------- 12 The ``has_property`` statement allows you to define properties which rely on 12 The ``has_property`` statement allows you to define properties which rely on 13 13 their entity's table (and columns) being defined before they can be declared 14 14 themselves. The `has_property` statement takes two arguments: first the name of … … 16 16 lambda) taking one argument and returning the desired SQLAlchemy property. That 17 17 function will be called whenever the entity table is completely defined, and 18 will be given the .c attribute of the entity as argument (as a way to access 18 will be given the .c attribute of the entity as argument (as a way to access 19 19 the entity columns). 20 20 … … 26 26 has_field('quantity', Float) 27 27 has_field('unit_price', Float) 28 has_property('price', 28 has_property('price', 29 29 lambda c: column_property( 30 30 (c.quantity * c.unit_price).label('price'))) … … 34 34 from sqlalchemy.orm import column_property, synonym 35 35 36 __doc_all__ = ['EntityBuilder', 'Property', 'GenericProperty', 36 __doc_all__ = ['EntityBuilder', 'Property', 'GenericProperty', 37 37 'ColumnProperty'] 38 38 39 39 class EntityBuilder(object): 40 40 ''' 41 Abstract base class for all entity builders. An Entity builder is a class 42 of objects which can be added to an Entity (usually by using special 41 Abstract base class for all entity builders. An Entity builder is a class 42 of objects which can be added to an Entity (usually by using special 43 43 properties or statements) to "build" that entity. Building an entity, 44 44 meaning to add columns to its "main" table, create other tables, add … … 66 66 67 67 def create_properties(self): 68 pass 68 pass 69 69 70 70 def before_mapper(self): 71 pass 71 pass 72 72 73 73 def after_mapper(self): 74 pass 74 pass 75 75 76 76 def finalize(self): 77 pass 77 pass 78 78 79 79 … … 98 98 ''' 99 99 __metaclass__ = CounterMeta 100 100 101 101 def __init__(self, *args, **kwargs): 102 102 self.entity = None … … 135 135 (c.quantity * c.unit_price).label('price'))) 136 136 ''' 137 137 138 138 def __init__(self, prop): 139 139 super(GenericProperty, self).__init__() … … 154 154 class ColumnProperty(GenericProperty): 155 155 ''' 156 A specialized form of the GenericProperty to generate SQLAlchemy 157 ``column_property``'s. 158 159 It takes a single argument, which is a function (often 160 given as an anonymous lambda) taking one argument and returning the 156 A specialized form of the GenericProperty to generate SQLAlchemy 157 ``column_property``'s. 158 159 It takes a single argument, which is a function (often 160 given as an anonymous lambda) taking one argument and returning the 161 161 desired (scalar-returning) SQLAlchemy ClauseElement. That function will be 162 called whenever the entity table is completely defined, and will be given 163 the .c attribute of the entity as argument (as a way to access the entity 162 called whenever the entity table is completely defined, and will be given 163 the .c attribute of the entity as argument (as a way to access the entity 164 164 columns). The ColumnProperty will first wrap your ClauseElement in a label 165 165 with the same name as the property, then wrap that in a column_property. … … 172 172 price = ColumnProperty(lambda c: c.quantity * c.unit_price) 173 173 174 Please look at the `corresponding SQLAlchemy 174 Please look at the `corresponding SQLAlchemy 175 175 documentation <http://www.sqlalchemy.org/docs/04/mappers.html 176 176 #advdatamapping_mapper_expressions>`_ for details. … … 184 184 ''' 185 185 This class represents a synonym property of another property (column, ...) 186 of an entity. As opposed to the `synonym` kwarg to the Field class (which 187 share the same goal), this class can be used to define a synonym of a 186 of an entity. As opposed to the `synonym` kwarg to the Field class (which 187 share the same goal), this class can be used to define a synonym of a 188 188 property defined in a parent class (of the current class). On the other 189 hand, it cannot define a synonym for the purpose of using a standard python 189 hand, it cannot define a synonym for the purpose of using a standard python 190 190 property in queries. See the Field class for details on that usage. 191 191 -
elixir/trunk/elixir/py23compat.py
r313 r347 23 23 if reverse: 24 24 cmp = lambda self, other, cmp=cmp: -cmp(self,other) 25 l.sort(cmp) 25 l.sort(cmp) 26 26 27 27 # sorted … … 63 63 """ 64 64 assert maxsplit >= 0 65 65 66 66 if maxsplit == 0: return [s] 67 67 68 68 # the following lines perform the function, but inefficiently. 69 69 # This may be adequate for compatibility purposes -
elixir/trunk/elixir/relationships.py
r346 r347 1 1 ''' 2 This module provides support for defining relationships between your Elixir 2 This module provides support for defining relationships between your Elixir 3 3 entities. Elixir currently supports two syntaxes to do so: the default 4 4 `Attribute-based syntax`_ which supports the following types of relationships: 5 ManyToOne_, OneToMany_, OneToOne_ and ManyToMany_, as well as a 6 `DSL-based syntax`_ which provides the following statements: belongs_to_, 7 has_many_, has_one_ and has_and_belongs_to_many_. 5 ManyToOne_, OneToMany_, OneToOne_ and ManyToMany_, as well as a 6 `DSL-based syntax`_ which provides the following statements: belongs_to_, 7 has_many_, has_one_ and has_and_belongs_to_many_. 8 8 9 9 ====================== … … 11 11 ====================== 12 12 13 The first argument to all these "normal" relationship classes is the name of 14 the class (entity) you are relating to. 15 16 Following that first mandatory argument, any number of additional keyword 17 arguments can be specified for advanced behavior. See each relationship type 13 The first argument to all these "normal" relationship classes is the name of 14 the class (entity) you are relating to. 15 16 Following that first mandatory argument, any number of additional keyword 17 arguments can be specified for advanced behavior. See each relationship type 18 18 for a list of their specific keyword arguments. At this point, we'll just note 19 that all the arguments that are not specifically processed by Elixir, as 20 mentioned in the documentation below are passed on to the SQLAlchemy 21 ``relation`` function. So, please refer to the `SQLAlchemy relation function's 19 that all the arguments that are not specifically processed by Elixir, as 20 mentioned in the documentation below are passed on to the SQLAlchemy 21 ``relation`` function. So, please refer to the `SQLAlchemy relation function's 22 22 documentation <http://www.sqlalchemy.org/docs/04/sqlalchemy_orm.html 23 #docstrings_sqlalchemy.orm_modfunc_relation>`_ for further detail about which 24 keyword arguments are supported. 25 26 You should keep in mind that the following 27 keyword arguments are automatically generated by Elixir and should not be used 28 unless you want to override the value provided by Elixir: ``uselist``, 23 #docstrings_sqlalchemy.orm_modfunc_relation>`_ for further detail about which 24 keyword arguments are supported. 25 26 You should keep in mind that the following 27 keyword arguments are automatically generated by Elixir and should not be used 28 unless you want to override the value provided by Elixir: ``uselist``, 29 29 ``remote_side``, ``secondary``, ``primaryjoin`` and ``secondaryjoin``. 30 30 31 31 Additionally, if you want a bidirectionnal relationship, you should define the 32 32 inverse relationship on the other entity explicitly (as opposed to how 33 SQLAlchemy's backrefs are defined). In non-ambiguous situations, Elixir will 33 SQLAlchemy's backrefs are defined). In non-ambiguous situations, Elixir will 34 34 match relationships together automatically. If there are several relationships 35 of the same type between two entities, Elixir is not able to determine which 36 relationship is the inverse of which, so you have to disambiguate the 37 situation by giving the name of the inverse relationship in the ``inverse`` 35 of the same type between two entities, Elixir is not able to determine which 36 relationship is the inverse of which, so you have to disambiguate the 37 situation by giving the name of the inverse relationship in the ``inverse`` 38 38 keyword argument. 39 39 … … 43 43 ----------- 44 44 45 Describes the child's side of a parent-child relationship. For example, 45 Describes the child's side of a parent-child relationship. For example, 46 46 a `Pet` object may belong to its owner, who is a `Person`. This could be 47 47 expressed like so: … … 52 52 owner = ManyToOne('Person') 53 53 54 Behind the scene, assuming the primary key of the `Person` entity is 55 an integer column named `id`, the ``ManyToOne`` relationship will 56 automatically add an integer column named `owner_id` to the entity, with a 54 Behind the scene, assuming the primary key of the `Person` entity is 55 an integer column named `id`, the ``ManyToOne`` relationship will 56 automatically add an integer column named `owner_id` to the entity, with a 57 57 foreign key referencing the `id` column of the `Person` entity. 58 58 59 In addition to the keyword arguments inherited from SQLAlchemy's relation 59 In addition to the keyword arguments inherited from SQLAlchemy's relation 60 60 function, ``ManyToOne`` relationships accept the following optional arguments 61 61 which will be directed to the created column: … … 102 102 +----------------------+------------------------------------------------------+ 103 103 104 Additionally, Elixir supports the belongs_to_ statement as an alternative, 104 Additionally, Elixir supports the belongs_to_ statement as an alternative, 105 105 DSL-based, syntax to define ManyToOne_ relationships. 106 106 … … 119 119 children = OneToMany('Person') 120 120 121 Note that a ``OneToMany`` relationship **cannot exist** without a 121 Note that a ``OneToMany`` relationship **cannot exist** without a 122 122 corresponding ``ManyToOne`` relationship in the other way. This is because the 123 ``OneToMany`` relationship needs the foreign key created by the ``ManyToOne`` 123 ``OneToMany`` relationship needs the foreign key created by the ``ManyToOne`` 124 124 relationship. 125 125 126 In addition to keyword arguments inherited from SQLAlchemy, ``OneToMany`` 126 In addition to keyword arguments inherited from SQLAlchemy, ``OneToMany`` 127 127 relationships accept the following optional (keyword) arguments: 128 128 … … 141 141 OneToMany_ relationships, with the has_many_ statement. 142 142 143 Also, as for standard SQLAlchemy relations, the ``order_by`` keyword argument 143 Also, as for standard SQLAlchemy relations, the ``order_by`` keyword argument 144 144 145 145 … … 148 148 149 149 Describes the parent's side of a parent-child relationship when there is only 150 one child. For example, a `Car` object has one gear stick, which is 150 one child. For example, a `Car` object has one gear stick, which is 151 151 represented as a `GearStick` object. This could be expressed like so: 152 152 … … 159 159 car = ManyToOne('Car') 160 160 161 Note that a ``OneToOne`` relationship **cannot exist** without a corresponding 161 Note that a ``OneToOne`` relationship **cannot exist** without a corresponding 162 162 ``ManyToOne`` relationship in the other way. This is because the ``OneToOne`` 163 163 relationship needs the foreign_key created by the ``ManyToOne`` relationship. … … 183 183 articles = ManyToMany('Article') 184 184 185 Behind the scene, the ``ManyToMany`` relationship will 185 Behind the scene, the ``ManyToMany`` relationship will 186 186 automatically create an intermediate table to host its data. 187 187 188 188 Note that you don't necessarily need to define the inverse relationship. In 189 our example, even though we want tags to be usable on several articles, we 189 our example, even though we want tags to be usable on several articles, we 190 190 might not be interested in which articles correspond to a particular tag. In 191 191 that case, we could have omitted the `Tag` side of the relationship. 192 192 193 If the entity containing your ``ManyToMany`` relationship is 193 If the entity containing your ``ManyToMany`` relationship is 194 194 autoloaded, you **must** specify at least one of either the ``remote_side`` or 195 195 ``local_side`` argument. 196 196 197 In addition to keyword arguments inherited from SQLAlchemy, ``ManyToMany`` 197 In addition to keyword arguments inherited from SQLAlchemy, ``ManyToMany`` 198 198 relationships accept the following optional (keyword) arguments: 199 199 … … 238 238 239 239 The following DSL statements provide an alternative way to define relationships 240 between your entities. The first argument to all those statements is the name 241 of the relationship, the second is the 'kind' of object you are relating to 242 (it is usually given using the ``of_kind`` keyword). 240 between your entities. The first argument to all those statements is the name 241 of the relationship, the second is the 'kind' of object you are relating to 242 (it is usually given using the ``of_kind`` keyword). 243 243 244 244 `belongs_to` … … 269 269 has_many('children', of_kind='Person') 270 270 271 There is also an alternate form of the ``has_many`` relationship that takes 272 only two keyword arguments: ``through`` and ``via`` in order to encourage a 273 richer form of many-to-many relationship that is an alternative to the 271 There is also an alternate form of the ``has_many`` relationship that takes 272 only two keyword arguments: ``through`` and ``via`` in order to encourage a 273 richer form of many-to-many relationship that is an alternative to the 274 274 ``has_and_belongs_to_many`` statement. Here is an example: 275 275 … … 313 313 ------------------------- 314 314 315 The ``has_and_belongs_to_many`` statement is the DSL syntax equivalent to the 315 The ``has_and_belongs_to_many`` statement is the DSL syntax equivalent to the 316 316 ManyToMany_ relationship. As such, it supports all the same arguments as 317 317 ManyToMany_ relationships. … … 347 347 Base class for relationships. 348 348 ''' 349 349 350 350 def __init__(self, of_kind, *args, **kwargs): 351 351 super(Relationship, self).__init__() … … 357 357 self._target = None 358 358 self._inverse = None 359 359 360 360 self.property = None # sqlalchemy property 361 361 self.backref = None # sqlalchemy backref … … 368 368 super(Relationship, self).attach(entity, name) 369 369 entity._descriptor.relationships.append(self) 370 370 371 371 def create_pk_cols(self): 372 372 self.create_keys(True) … … 377 377 def create_keys(self, pk): 378 378 ''' 379 Subclasses (ie. concrete relationships) may override this method to 379 Subclasses (ie. concrete relationships) may override this method to 380 380 create foreign keys. 381 381 ''' 382 382 383 383 def create_tables(self): 384 384 ''' 385 Subclasses (ie. concrete relationships) may override this method to 385 Subclasses (ie. concrete relationships) may override this method to 386 386 create secondary tables. 387 387 ''' 388 388 389 389 def create_properties(self): 390 390 ''' … … 425 425 return self._target 426 426 target = property(target) 427 427 428 428 def inverse(self): 429 429 if not self._inverse: … … 434 434 raise Exception( 435 435 "Couldn't find a relationship named '%s' in " 436 "entity '%s' or its parent entities." 436 "entity '%s' or its parent entities." 437 437 % (self.inverse_name, self.target.__name__)) 438 438 assert self.match_type_of(inverse) … … 443 443 self._inverse = inverse 444 444 inverse._inverse = self 445 445 446 446 return self._inverse 447 447 inverse = property(inverse) 448 448 449 449 def match_type_of(self, other): 450 450 return False … … 461 461 class ManyToOne(Relationship): 462 462 ''' 463 463 464 464 ''' 465 465 466 466 def __init__(self, *args, **kwargs): 467 467 self.colname = kwargs.pop('colname', []) … … 480 480 if 'use_alter' in kwargs: 481 481 self.constraint_kwargs['use_alter'] = kwargs.pop('use_alter') 482 482 483 483 if 'ondelete' in kwargs: 484 484 self.constraint_kwargs['ondelete'] = kwargs.pop('ondelete') 485 485 if 'onupdate' in kwargs: 486 486 self.constraint_kwargs['onupdate'] = kwargs.pop('onupdate') 487 487 488 488 self.foreign_key = list() 489 489 self.primaryjoin_clauses = list() 490 490 491 491 super(ManyToOne, self).__init__(*args, **kwargs) 492 492 493 493 def match_type_of(self, other): 494 494 return isinstance(other, (OneToMany, OneToOne)) … … 496 496 def create_keys(self, pk): 497 497 ''' 498 Find all primary keys on the target and create foreign keys on the 498 Find all primary keys on the target and create foreign keys on the 499 499 source accordingly. 500 500 ''' … … 508 508 source_desc = self.entity._descriptor 509 509 #TODO: make this work if target is a pure SA-mapped class 510 # for that, I need: 510 # for that, I need: 511 511 # - the list of primary key columns of the target table (type and name) 512 512 # - the name of the target table … … 520 520 if self.colname: 521 521 self.primaryjoin_clauses = \ 522 _get_join_clauses(self.entity.table, 523 self.colname, None, 522 _get_join_clauses(self.entity.table, 523 self.colname, None, 524 524 self.target.table)[0] 525 525 if not self.primaryjoin_clauses: … … 539 539 "'%s' entity is not the same as the number of columns " 540 540 "of the primary key of '%s'." 541 % (self.name, self.entity.__name__, 541 % (self.name, self.entity.__name__, 542 542 self.target.__name__)) 543 543 … … 546 546 raise Exception("No primary key found in target table ('%s') " 547 547 "for the '%s' relationship of the '%s' entity." 548 % (self.target.tablename, self.name, 548 % (self.target.tablename, self.name, 549 549 self.entity.__name__)) 550 550 … … 554 554 else: 555 555 colname = options.FKCOL_NAMEFORMAT % \ 556 {'relname': self.name, 556 {'relname': self.name, 557 557 'key': pk_col.key} 558 558 … … 569 569 fk_colnames.append(col.key) 570 570 571 # Build the list of column "paths" the foreign key will 571 # Build the list of column "paths" the foreign key will 572 572 # point to 573 573 target_path = "%s.%s" % (target_desc.tablename, pk_col.key) … … 577 577 fk_refcols.append(target_path) 578 578 579 # Build up the primary join. This is needed when you have 579 # Build up the primary join. This is needed when you have 580 580 # several belongs_to relationships between two objects 581 581 self.primaryjoin_clauses.append(col == pk_col) 582 582 583 583 if 'name' not in self.constraint_kwargs: 584 584 # In some databases (at least MySQL) the constraint name needs 585 585 # to be unique for the whole database, instead of per table. 586 586 fk_name = options.CONSTRAINT_NAMEFORMAT % \ 587 {'tablename': source_desc.tablename, 587 {'tablename': source_desc.tablename, 588 588 'colnames': '_'.join(fk_colnames)} 589 589 self.constraint_kwargs['name'] = fk_name 590 590 591 591 source_desc.add_constraint( 592 592 ForeignKeyConstraint(fk_colnames, fk_refcols, … … 595 595 def get_prop_kwargs(self): 596 596 kwargs = {'uselist': False} 597 597 598 598 if self.entity.table is self.target.table: 599 599 kwargs['remote_side'] = \ … … 626 626 % (self.target.__name__, self.name, 627 627 self.entity.__name__)) 628 628 629 629 def get_prop_kwargs(self): 630 630 kwargs = {'uselist': self.uselist} 631 631 632 632 #TODO: for now, we don't break any test if we remove those 2 lines. 633 633 # So, we should either complete the selfref test to prove that they … … 639 639 # autoloaded tables 640 640 kwargs['remote_side'] = self.inverse.foreign_key 641 641 642 642 if self.inverse.primaryjoin_clauses: 643 643 kwargs['primaryjoin'] = and_(*self.inverse.primaryjoin_clauses) … … 650 650 class OneToMany(OneToOne): 651 651 uselist = True 652 652 653 653 def get_prop_kwargs(self): 654 654 kwargs = super(OneToMany, self).get_prop_kwargs() … … 688 688 def create_tables(self): 689 689 # Warning: if the table was specified manually, the join clauses won't 690 # be computed. We might want to autodetect joins based on fk, as for 690 # be computed. We might want to autodetect joins based on fk, as for 691 691 # autoloaded entities 692 692 if self.secondary_table: 693 693 return 694 694 695 695 if self.inverse: 696 696 if self.inverse.secondary_table: … … 707 707 assert e1_schema == e2_schema, \ 708 708 "Schema %r for entity %s differs from schema %r of entity %s" \ 709 % (e1_schema, self.entity.__name__, 709 % (e1_schema, self.entity.__name__, 710 710 e2_schema, self.target.__name__) 711 712 # First, we compute the name of the table. Note that some of the 713 # intermediary variables are reused later for the constraint 711 712 # First, we compute the name of the table. Note that some of the 713 # intermediary variables are reused later for the constraint 714 714 # names. 715 716 # We use the name of the relation for the first entity 717 # (instead of the name of its primary key), so that we can 718 # have two many-to-many relations between the same objects 719 # without having a table name collision. 715 716 # We use the name of the relation for the first entity 717 # (instead of the name of its primary key), so that we can 718 # have two many-to-many relations between the same objects 719 # without having a table name collision. 720 720 source_part = "%s_%s" % (e1_desc.tablename, self.name) 721 721 722 722 # And we use only the name of the table of the second entity 723 # when there is no inverse, so that a many-to-many relation 723 # when there is no inverse, so that a many-to-many relation 724 724 # can be defined without an inverse. 725 725 if self.inverse: … … 727 727 else: 728 728 target_part = e2_desc.tablename 729 729 730 730 if self.user_tablename: 731 731 tablename = self.user_tablename 732 732 else: 733 # We need to keep the table name consistent (independant of 733 # We need to keep the table name consistent (independant of 734 734 # whether this relation or its inverse is setup first). 735 735 if self.inverse and e1_desc.tablename < e2_desc.tablename: … … 741 741 self._reflect_table(tablename) 742 742 else: 743 # We pre-compute the names of the foreign key constraints 744 # pointing to the source (local) entity's table and to the 743 # We pre-compute the names of the foreign key constraints 744 # pointing to the source (local) entity's table and to the 745 745 # target's table 746 746 747 # In some databases (at least MySQL) the constraint names need 747 # In some databases (at least MySQL) the constraint names need 748 748 # to be unique for the whole database, instead of per table. 749 749 source_fk_name = "%s_fk" % source_part … … 758 758 joins = (self.primaryjoin_clauses, self.secondaryjoin_clauses) 759 759 for num, desc, fk_name, m2m in ( 760 (0, e1_desc, source_fk_name, self), 760 (0, e1_desc, source_fk_name, self), 761 761 (1, e2_desc, target_fk_name, self.inverse)): 762 762 fk_colnames = list() 763 763 fk_refcols = list() 764 764 765 765 for pk_col in desc.primary_keys: 766 766 colname = self.column_format % \ … … 768 768 'key': pk_col.key, 769 769 'entity': desc.entity.__name__.lower()} 770 771 # In case we have a many-to-many self-reference, we 772 # need to tweak the names of the columns so that we 770 771 # In case we have a many-to-many self-reference, we 772 # need to tweak the names of the columns so that we 773 773 # don't end up with twice the same column name. 774 774 if self.entity is self.target: 775 775 colname += str(num + 1) 776 776 777 777 col = Column(colname, pk_col.type, primary_key=True) 778 778 columns.append(col) 779 779 780 # Build the list of local columns which will be part 780 # Build the list of local columns which will be part 781 781 # of the foreign key. 782 782 fk_colnames.append(colname) 783 783 784 # Build the list of column "paths" the foreign key will 784 # Build the list of column "paths" the foreign key will 785 785 # point to 786 786 target_path = "%s.%s" % (desc.tablename, pk_col.key) … … 793 793 if self.entity is self.target: 794 794 joins[num].append(col == pk_col) 795 795 796 796 onupdate = m2m and m2m.onupdate 797 797 ondelete = m2m and m2m.ondelete 798 798 799 799 constraints.append( 800 800 ForeignKeyConstraint(fk_colnames, fk_refcols, 801 name=fk_name, onupdate=onupdate, 801 name=fk_name, onupdate=onupdate, 802 802 ondelete=ondelete)) 803 803 804 804 args = columns + constraints 805 806 self.secondary_table = Table(tablename, e1_desc.metadata, 805 806 self.secondary_table = Table(tablename, e1_desc.metadata, 807 807 schema=e1_schema, *args) 808 808 … … 815 815 % (self.entity.__name__, self.name, 816 816 self.target.__name__)) 817 818 self.secondary_table = Table(tablename, 817 818 self.secondary_table = Table(tablename, 819 819 self.entity._descriptor.metadata, 820 820 autoload=True) … … 822 822 # In the case we have a self-reference, we need to build join clauses 823 823 if self.entity is self.target: 824 #CHECKME: maybe we should try even harder by checking if that 824 #CHECKME: maybe we should try even harder by checking if that 825 825 # information was defined on the inverse relationship) 826 826 if not self.local_side and not self.remote_side: … … 834 834 835 835 self.primaryjoin_clauses, self.secondaryjoin_clauses = \ 836 _get_join_clauses(self.secondary_table, 837 self.local_side, self.remote_side, 836 _get_join_clauses(self.secondary_table, 837 self.local_side, self.remote_side, 838 838 self.entity.table) 839 839 840 840 def get_prop_kwargs(self): 841 kwargs = {'secondary': self.secondary_table, 841 kwargs = {'secondary': self.secondary_table, 842 842 'uselist': self.uselist} 843 843 … … 856 856 def is_inverse(self, other): 857 857 return super(ManyToMany, self).is_inverse(other) and \ 858 (self.user_tablename == other.user_tablename or 858 (self.user_tablename == other.user_tablename or 859 859 (not self.user_tablename and not other.user_tablename)) 860 860 … … 895 895 # one of the joins (primary or secondary), or we assume the current 896 896 # columns match because the columns for this join were not given and we 897 # know the other join is either not used (is None) or has an explicit 897 # know the other join is either not used (is None) or has an explicit 898 898 # match. 899 899 900 900 #TODO: rewrite this. Even with the comment, I don't even understand it myself. 901 901 for cols, constraint in constraint_map.iteritems(): 902 if cols == cols1 or (cols != cols2 and 902 if cols == cols1 or (cols != cols2 and 903 903 not cols1 and (cols2 in constraint_map or 904 904 cols2 is None)): … … 916 916 def handler(entity, name, *args, **kwargs): 917 917 if 'through' in kwargs and 'via' in kwargs: 918 setattr(entity, name, 919 association_proxy(kwargs.pop('through'), 918 setattr(entity, name, 919 association_proxy(kwargs.pop('through'), 920 920 kwargs.pop('via'), 921 921 **kwargs)) -
elixir/trunk/elixir/statements.py
r311 r347 3 3 MUTATORS = '__elixir_mutators__' 4 4 5 class ClassMutator(object): 5 class ClassMutator(object): 6 6 ''' 7 7 DSL-style syntax 8 8 9 9 A ``ClassMutator`` object represents a DSL term. 10 10 ''' 11 11 12 12 def __init__(self, handler): 13 13 ''' … … 16 16 ''' 17 17 self.handler = handler 18 18 19 19 # called when a mutator (eg. "has_field(...)") is parsed 20 20 def __call__(self, *args, **kwargs): … … 28 28 def process(self, entity, *args, **kwargs): 29 29 ''' 30 Process one mutator. This version simply calls the handler callable, 30 Process one mutator. This version simply calls the handler callable, 31 31 but another mutator (sub)class could do more processing. 32 32 ''' … … 37 37 def process_mutators(entity): 38 38 ''' 39 Apply all mutators of the given entity. That is, loop over all mutators 39 Apply all mutators of the given entity. That is, loop over all mutators 40 40 in the class's mutator list and process them. 41 41 ''' -
elixir/trunk/release.howto
r326 r347 24 24 25 25 - announce release on those lists: 26 - Elixir <sqlelixir@googlegroups.com> 26 - Elixir <sqlelixir@googlegroups.com> 27 27 - SQLAlchemy <sqlalchemy@googlegroups.com> 28 28 - TurboGears <turbogears@googlegroups.com> -
elixir/trunk/setup.py
r336 r347 14 14 databases without losing the convenience of Python objects. 15 15 16 Elixir is intended to replace the ActiveMapper SQLAlchemy extension, and the 16 Elixir is intended to replace the ActiveMapper SQLAlchemy extension, and the 17 17 TurboEntity project but does not intend to replace SQLAlchemy's core features, 18 and instead focuses on providing a simpler syntax for defining model objects 19 when you do not need the full expressiveness of SQLAlchemy's manual mapper 18 and instead focuses on providing a simpler syntax for defining model objects 19 when you do not need the full expressiveness of SQLAlchemy's manual mapper 20 20 definitions. 21 21 """, -
elixir/trunk/tests/a.py
r234 r347 4 4 name = Field(String(30)) 5 5 b = ManyToOne('tests.b.B') 6 -
elixir/trunk/tests/test_acts_as_list.py
r293 r347 4 4 def setup(): 5 5 global ToDo, Person 6 6 7 7 class ToDo(Entity): 8 8 subject = Field(String(128)) … … 11 11 def qualify(self): 12 12 return ToDo.owner_id == self.owner_id 13 13 14 14 acts_as_list(qualifier=qualify, column_name='position') 15 15 16 16 def __repr__(self): 17 17 return '<%d:%s>' % (self.position, self.subject) 18 18 19 19 class Person(Entity): 20 20 name = Field(String(64)) 21 21 todos = OneToMany('ToDo', order_by='position') 22 23 22 23 24 24 setup_all() 25 25 metadata.bind = 'sqlite:///' … … 33 33 def setup(self): 34 34 create_all() 35 35 36 36 def teardown(self): 37 37 drop_all() 38 38 session.clear() 39 39 40 40 def test_acts_as_list(self): 41 41 # create a person 42 42 # you must create and flush this _before_ you attach todo's to it 43 # because of the way that the plugin is implemented 43 # because of the way that the plugin is implemented 44 44 p = Person(name='Jonathan') 45 45 session.flush(); session.clear() 46 46 47 47 # add three todos, in the reverse order that we want them 48 48 p = Person.get(1) … … 51 51 p.todos.append(ToDo(subject='One')) 52 52 session.flush(); session.clear() 53 53 54 54 # move the first item lower 55 55 p = Person.get(1) 56 p.todos[0].move_lower() 56 p.todos[0].move_lower() 57 57 session.flush(); session.clear() 58 58 59 59 # validate it worked 60 60 p = Person.get(1) … … 62 62 assert p.todos[1].subject == 'Three' 63 63 assert p.todos[2].subject == 'One' 64 64 65 65 # move the last item to the top to put things in correct order 66 66 p.todos[2].move_to_top() 67 67 session.flush(); session.clear() 68 68 69 69 # validate it worked 70 70 p = Person.get(1) … … 72 72 assert p.todos[1].subject == 'Two' 73 73 assert p.todos[2].subject == 'Three' 74 74 75 75 # lets shuffle them again for the sake of testing move_to_bottom 76 76 # and move_to_top 77 77 p.todos[2].move_to_top() 78 78 session.flush(); session.clear() 79 79 80 80 p = Person.get(1) 81 81 assert p.todos[0].subject == 'Three' 82 82 assert p.todos[1].subject == 'One' 83 83 assert p.todos[2].subject == 'Two' 84 84 85 85 p.todos[1].move_to_bottom() 86 86 session.flush(); session.clear() 87 87 88 88 p = Person.get(1) 89 89 assert p.todos[0].subject == 'Three' 90 90 assert p.todos[1].subject == 'Two' 91 91 assert p.todos[2].subject == 'One' 92 92 93 93 # lets move everything back to how it should be 94 94 p = Person.get(1) … … 96 96 p.todos[2].move_to(1) 97 97 session.flush(); session.clear() 98 98 99 99 # validate it worked 100 100 p = Person.get(1) … … 105 105 assert p.todos[2].subject == 'Three' 106 106 assert p.todos[2].position == 3 107 107 108 108 # delete the second todo list item 109 109 p.todos[1].delete() 110 110 session.flush(); session.clear() 111 111 112 112 # validate that the deletion worked, and sequence numebers 113 # were properly managed 113 # were properly managed 114 114 p = Person.get(1) 115 115 assert p.todos[0].subject == 'One' -
elixir/trunk/tests/test_associable.py
r336 r347 15 15 def teardown(self): 16 16 cleanup_all(True) 17 17 18 18 def test_empty(self): 19 19 class Foo(Entity): … … 83 83 assert '243 Hooper st.' in streets 84 84 assert '123 Elm St.' in streets 85 85 86 86 people = Person.select_addresses(and_(Address.street=='132 Elm St', 87 87 Address.city=='Smallville')) -
elixir/trunk/tests/test_autoload.py
r327 r347 56 56 pets = OneToMany('Animal', inverse='owner') 57 57 animals = OneToMany('Animal', inverse='feeder') 58 categories = ManyToMany('Category', 58 categories = ManyToMany('Category', 59 59 tablename='person_category') 60 60 appreciate = ManyToMany('Person', … … 71 71 72 72 class Category(Entity): 73 persons = ManyToMany('Person', 73 persons = ManyToMany('Person', 74 74 tablename='person_category') 75 75 … … 87 87 def setup(self): 88 88 create_all() 89 89 90 90 def teardown(self): 91 91 drop_all() 92 92 session.clear() 93 93 94 94 def test_simple(self): 95 95 snowball = Animal(name="Snowball II") … … 97 97 homer = Person(name="Homer", animals=[snowball, slh], pets=[slh]) 98 98 lisa = Person(name="Lisa", pets=[snowball]) 99 100 session.flush() 101 session.clear() 102 99 100 session.flush() 101 session.clear() 102 103 103 homer = Person.get_by(name="Homer") 104 104 lisa = Person.get_by(name="Lisa") 105 105 slh = Animal.get_by(name="Santa's Little Helper") 106 106 107 107 assert len(homer.animals) == 2 108 108 assert homer == lisa.pets[0].feeder … … 114 114 bart = Person(name="Bart") 115 115 lisa = Person(name="Lisa") 116 116 117 117 grampa.children.append(homer) 118 118 homer.children.append(bart) 119 119 lisa.father = homer 120 121 session.flush() 122 session.clear() 123 120 121 session.flush() 122 session.clear() 123 124 124 p = Person.get_by(name="Homer") 125 125 126 126 assert p in p.father.children 127 127 assert p.father.name == "Abe" … … 138 138 bart = Person(name="Bart") 139 139 lisa = Person(name="Lisa") 140 140 141 141 simpson.persons.extend([bart, lisa]) 142 143 session.flush() 144 session.clear() 145 142 143 session.flush() 144 session.clear() 145 146 146 c = Category.get_by(name="Simpson") 147 147 grampa = Person.get_by(name="Abe") 148 148 149 149 print "Persons in the '%s' category: %s." % ( 150 c.name, 150 c.name, 151 151 ", ".join([p.name for p in c.persons])) 152 152 153 153 assert len(c.persons) == 4 154 154 assert c in grampa.categories … … 160 160 session.flush() 161 161 session.clear() 162 162 163 163 homer = Person.get_by(name="Homer") 164 164 barney = Person.get_by(name="Barney") … … 208 208 Column('id', Integer), 209 209 Column('name', String(32))) 210 210 211 211 local_meta.create_all() 212 212 … … 243 243 using_options(tablename='father') 244 244 245 class Son(Father): 245 class Son(Father): 246 246 pass 247 247 -
elixir/trunk/tests/test_autoload_mixed.py
r272 r347 13 13 (user_id INTEGER PRIMARY KEY AUTOINCREMENT)""") 14 14 conn.close() 15 15 16 16 def test_belongs_to(self): 17 17 class User(Entity): -
elixir/trunk/tests/test_autosetup.py
r275 r347 17 17 def teardown(self): 18 18 cleanup_all() 19 19 20 20 def test_autosetup_manual_setup_all(self): 21 21 class Person(Entity): … … 25 25 # check that we have a fake table installed 26 26 assert 'person' in metadata.tables 27 assert isinstance(metadata.tables['person'], 27 assert isinstance(metadata.tables['person'], 28 28 elixir.entity.TriggerProxy) 29 29 … … 40 40 # check that we have a fake table installed 41 41 assert 'person' in metadata.tables 42 assert isinstance(metadata.tables['person'], 42 assert isinstance(metadata.tables['person'], 43 43 elixir.entity.TriggerProxy) 44 44 … … 46 46 47 47 assert 'person' not in metadata.tables 48 48 49 49 def test_drop_create_drop(self): 50 50 class User(Entity): … … 72 72 # check that accessing the table didn't trigger the setup 73 73 assert 'person' not in metadata.tables 74 74 75 75 setup_all() 76 76 … … 85 85 homer = Person(name="Homer Simpson") 86 86 assert isinstance(metadata.tables['person'], Table) 87 87 88 88 def test_getattr(self): 89 89 class Person(Entity): … … 92 92 93 93 tablename = Person.table.name 94 assert tablename == 'person' 94 assert tablename == 'person' 95 95 assert isinstance(metadata.tables['person'], Table) 96 96 … … 123 123 # def test_mapper(self): 124 124 # we want to hit the mapper directly (without hitting any of the 125 # other triggers first). We do so by getting a query object using a 125 # other triggers first). We do so by getting a query object using a 126 126 # manual session. 127 127 # class Person(Entity): -
elixir/trunk/tests/test_class_methods.py
r271 r347 10 10 def setup(self): 11 11 metadata.bind = 'sqlite:///' 12 12 13 13 def teardown(self): 14 14 cleanup_all() 15 15 16 16 def test_get(self): 17 17 class A(Entity): … … 21 21 22 22 a1 = A(name="a1") 23 23 24 24 session.flush() 25 25 session.clear() 26 26 27 27 assert A.get(1).name == "a1" 28 28 -
elixir/trunk/tests/test_collections.py
r343 r347 17 17 def teardown(self): 18 18 cleanup_all() 19 19 20 20 def test_no_collection(self): 21 21 class Person(Entity): -
elixir/trunk/tests/test_custombase.py
r330 r347 26 26 27 27 setup_all(True) 28 28 29 29 a1 = A(name="a1") 30 30 31 31 session.flush() 32 32 session.clear() 33 33 34 34 a = A.query.filter_by(name="a1").one() 35 35 36 36 assert a.name == 'a1' 37 37 … … 42 42 class B(A): 43 43 data = Field(String(30)) 44 44 45 45 setup_all(True) 46 46 47 47 a1 = A(name="a1") 48 48 b1 = B(name="b1", data="-b1-") 49 49 50 50 session.flush() 51 51 session.clear() 52 52 53 53 b = A.query.filter_by(name="b1").one() 54 54 55 55 assert b.data == '-b1-' 56 56 … … 67 67 68 68 setup_all(True) 69 69 70 70 a1 = A() 71 a1.name = "a1" 72 71 a1.name = "a1" 72 73 73 session.flush() 74 74 session.clear() 75 75 76 76 a = A.query.filter_by(name="a1").one() 77 77 78 78 assert a.name == 'a1' 79 79 assert a.test() == "success" 80 80 81 def test_base_with_fields(self): 82 class FieldBase(object): 83 __metaclass__ = EntityMeta 84 85 common = Field(String(32)) 86 87 def __init__(self, **kwargs): 88 for key, value in kwargs.items(): 89 setattr(self, key, value) 90 91 class A(FieldBase): 92 name = Field(String(32)) 93 94 class B(FieldBase): 95 pass 96 97 setup_all(True) 98 99 assert 'name' in A.table.columns.keys() 100 assert 'common' in A.table.columns.keys() 101 assert 'common' in B.table.columns.keys() -
elixir/trunk/tests/test_encryption.py
r310 r347 1 1 from elixir import * 2 2 from elixir.ext.encrypted import acts_as_encrypted 3 3 4 4 5 5 def setup(): 6 6 global Person, Pet 7 7 8 8 class Person(Entity): 9 9 name = Field(String(50)) … … 19 19 owner = ManyToOne('Person') 20 20 21 21 22 22 metadata.bind = 'sqlite:///' 23 23 … … 30 30 def setup(self): 31 31 setup_all(True) 32 32 33 33 def teardown(self): 34 34 drop_all() 35 35 session.clear() 36 37 def test_encryption(self): 36 37 def test_encryption(self): 38 38 jonathan = Person( 39 name=u'Jonathan LaCour', 40 password=u's3cr3tw0RD', 39 name=u'Jonathan LaCour', 40 password=u's3cr3tw0RD', 41 41 ssn=u'123-45-6789' 42 42 ) -
elixir/trunk/tests/test_events.py
r313 r347 15 15 def setup(): 16 16 global Document 17 17 18 18 class Document(Entity): 19 19 name = Field(String(50)) 20 20 21 21 def pre_insert(self): 22 22 global before_insert_called 23 23 before_insert_called += 1 24 24 pre_insert = before_insert(pre_insert) 25 25 26 26 def post_insert(self): 27 27 global after_insert_called … … 33 33 before_update_called += 1 34 34 pre_update = before_update(pre_update) 35 35 36 36 def post_update(self): 37 37 global after_update_called … … 43 43 before_delete_called += 1 44 44 pre_delete = before_delete(pre_delete) 45 45 46 46 def post_delete(self): 47 47 global after_delete_called 48 48 after_delete_called += 1 49 49 post_delete = after_delete(post_delete) 50 50 51 51 def pre_any(self): 52 52 global before_any_called … … 65 65 def setup(self): 66 66 create_all() 67 67 68 68 def teardown(self): 69 69 drop_all() 70 70 session.clear() 71 71 72 72 def test_events(self): 73 73 d = Document(name='My Document') 74 74 session.flush(); session.clear() 75 75 76 76 d = Document.query.get(1) 77 77 d.name = 'My Document Updated' 78 78 session.flush(); session.clear() 79 79 80 80 d = Document.query.get(1) 81 81 d.delete() 82 82 session.flush(); session.clear() 83 83 84 84 assert before_insert_called == 1 85 85 assert before_update_called == 1 -
elixir/trunk/tests/test_fields.py
r271 r347 7 7 def setup(): 8 8 metadata.bind = 'sqlite:///' 9 9 10 10 class TestFields(object): 11 11 def teardown(self): 12 12 cleanup_all(True) 13 13 14 14 def test_attr_syntax(self): 15 15 class Person(Entity): … … 18 18 19 19 setup_all(True) 20 20 21 21 homer = Person(firstname="Homer", surname="Simpson") 22 22 bart = Person(firstname="Bart", surname="Simpson") 23 23 24 24 session.flush() 25 25 session.clear() 26 26 27 27 p = Person.get_by(firstname="Homer") 28 28 29 29 assert p.surname == 'Simpson' 30 30 … … 35 35 36 36 setup_all(True) 37 37 38 38 homer = Person(firstname="Homer", surname="Simpson") 39 39 bart = Person(firstname="Bart", surname="Simpson") 40 40 41 41 session.flush() 42 42 session.clear() 43 43 44 44 p = Person.get_by(firstname="Homer") 45 45 46 46 assert p.surname == 'Simpson' 47 47 48 48 def test_with_fields(self): 49 49 class Person(Entity): … … 52 52 surname = Field(String(30)) 53 53 ) 54 54 55 55 setup_all(True) 56 56 57 57 homer = Person(firstname="Homer", surname="Simpson") 58 58 bart = Person(firstname="Bart", surname="Simpson") 59 59 60 60 session.flush() 61 61 session.clear() 62 62 63 63 p = Person.get_by(firstname="Homer") 64 64 65 65 assert p.surname == 'Simpson' 66 66 -
elixir/trunk/tests/test_inherit.py
r341 r347 52 52 for class_ in (A, B, C, D, E): 53 53 res[class_.__name__] = class_.query.all() 54 sort_list(res[class_.__name__], key=lambda o: o.__class__.__name__) 54 sort_list(res[class_.__name__], key=lambda o: o.__class__.__name__) 55 55 56 56 for query_class in ('A', 'B', 'C', 'D', 'E'): … … 65 65 cleanup_all(True) 66 66 67 # this is related to SA ticket 866 67 # this is related to SA ticket 866 68 68 # http://www.sqlalchemy.org/trac/ticket/866 69 69 # the problem was caused by the fact that the attribute-based syntax left … … 73 73 pass 74 74 75 class B(A): 75 class B(A): 76 76 name = Field(String(30)) 77 77 other = Field(Text) … … 103 103 # enforcing the foreign key constraint cascade rule). 104 104 # assert not B.table.select().execute().fetchall() 105 105 106 106 def test_inheritance_wh_schema(self): 107 107 # I can only test schema stuff on postgres … … 109 109 print "schema test skipped" 110 110 return 111 111 112 112 class A(Entity): 113 113 using_options(inheritance="multi") … … 175 175 # def test_polymorphic_concrete_inheritance(self): 176 176 # to get this test to work, I need to duplicate parent relationships in 177 # the children. The problem is that the properties are setup post 178 # mapper setup, so I'll need to add some logic into the add_property 177 # the children. The problem is that the properties are setup post 178 # mapper setup, so I'll need to add some logic into the add_property 179 179 # method which I'm reluctant to do. 180 180 # do_tst('concrete', True, { … … 194 194 'E': ('E',), 195 195 }) 196 196 197 197 def test_polymorphic_multitable_inheritance(self): 198 198 do_tst('multi', True, { -
elixir/trunk/tests/test_m2m.py
r297 r347 10 10 def setup(self): 11 11 metadata.bind = 'sqlite:///' 12 12 13 13 def teardown(self): 14 14 cleanup_all(True) 15 15 16 16 def test_simple(self): 17 17 class A(Entity): … … 35 35 assert a in b.as_ 36 36 assert b in a.bs_ 37 37 38 38 def test_column_format(self): 39 39 class A(Entity): … … 59 59 assert a in b.as_ 60 60 assert b in a.bs_ 61 61 62 62 found_a = False 63 63 found_b = False … … 67 67 assert found_a 68 68 assert found_b 69 69 70 70 def test_multi_pk_in_target(self): 71 71 class A(Entity): … … 98 98 rel1 = ManyToMany('B') 99 99 rel2 = ManyToMany('B') 100 100 101 101 class B(Entity): 102 102 name = Field(String(20), primary_key=True) 103 103 104 104 setup_all(True) 105 105 106 106 b1 = B(name='b1') 107 107 a1 = A(name='a1', rel1=[B(name='b2'), b1], … … 110 110 session.flush() 111 111 session.clear() 112 112 113 113 a1 = A.query.one() 114 114 b1 = B.get_by(name='b1') 115 115 b2 = B.get_by(name='b2') 116 116 117 assert b1 in a1.rel1 117 assert b1 in a1.rel1 118 118 assert b1 in a1.rel2 119 119 assert b2 in a1.rel1 … … 122 122 class Person(Entity): 123 123 name = Field(String(30)) 124 124 125 125 friends = ManyToMany('Person') 126 126 … … 133 133 session.flush() 134 134 session.clear() 135 135 136 136 homer = Person.get_by(name="Homer") 137 137 barney = Person.get_by(name="Barney") … … 145 145 146 146 has_and_belongs_to_many('bs', of_kind='B') 147 147 148 148 class B(Entity): 149 149 has_field('name', String(100), primary_key=True) 150 150 151 151 setup_all(True) 152 152 153 153 b1 = B(name='b1') 154 154 a1 = A(name='a1', bs=[B(name='b2'), b1]) … … 158 158 session.flush() 159 159 session.clear() 160 160 161 161 a1 = A.get_by(name='a1') 162 162 a2 = A.get_by(name='a2') -
elixir/trunk/tests/test_m2o.py
r319 r347 11 11 def teardown(self): 12 12 cleanup_all(True) 13 13 14 14 def test_simple(self): 15 15 class A(Entity): … … 40 40 class B(Entity): 41 41 a = ManyToOne('A') 42 42 43 43 setup_all(True) 44 44 45 45 b1 = B(a=A(testx=1)) 46 46 47 47 session.flush() 48 48 session.clear() 49 49 50 50 b = B.query.one() 51 51 … … 55 55 class A(Entity): 56 56 name = Field(String(128), default="foo") 57 57 58 58 class B(Entity): 59 59 # specify a different key for the column so that … … 62 62 a = ManyToOne('A', colname='a', 63 63 column_kwargs=dict(key='a_id')) 64 64 65 65 setup_all(True) 66 66 … … 79 79 class A(Entity): 80 80 name = Field(String(40), primary_key=True) 81 81 82 82 class B(Entity): 83 83 a = ManyToOne('A', primary_key=True) 84 84 85 85 class C(Entity): 86 86 b = ManyToOne('B', primary_key=True) … … 95 95 class A(Entity): 96 96 pass 97 97 98 98 class B(Entity): 99 99 a = ManyToOne('A', primary_key=True) 100 100 101 101 setup_all() 102 102 … … 109 109 key1 = Field(Integer, primary_key=True) 110 110 key2 = Field(String(40), primary_key=True) 111 111 112 112 class B(Entity): 113 113 num = Field(Integer, primary_key=True) 114 114 a = ManyToOne('A', primary_key=True) 115 115 116 116 class C(Entity): 117 117 num = Field(Integer, primary_key=True) … … 135 135 class A(Entity): 136 136 c = ManyToOne('C', use_alter=True) 137 137 138 138 class B(Entity): 139 139 a = ManyToOne('A', primary_key=True) 140 140 141 141 class C(Entity): 142 142 b = ManyToOne('B', primary_key=True) … … 152 152 class A(Entity): 153 153 name = Field(String(32)) 154 154 155 155 class B(Entity): 156 156 name = Field(String(15)) 157 157 158 158 a_rel1 = ManyToOne('A') 159 159 a_rel2 = ManyToOne('A') … … 165 165 b1 = B(name="b1", a_rel1=a1, a_rel2=a2) 166 166 b2 = B(name="b2", a_rel1=a1, a_rel2=a1) 167 167 168 168 session.flush() 169 169 session.clear() 170 170 171 171 a1 = A.get_by(name="a1") 172 172 a2 = A.get_by(name="a2") 173 173 b1 = B.get_by(name="b1") 174 174 b2 = B.get_by(name="b2") 175 175 176 176 assert a1 == b2.a_rel1 177 177 assert a2 == b1.a_rel2 … … 186 186 187 187 setup_all(True) 188 188 189 189 santa = Person(name="Santa Claus") 190 190 rudolph = Animal(name="Rudolph", owner=santa) 191 191 192 192 session.flush() 193 193 session.clear() 194 194 195 195 assert "Claus" in Animal.get_by(name="Rudolph").owner.name -
elixir/trunk/tests/test_nestedclass.py
r245 r347 7 7 name = Field(String(40)) 8 8 type = Field(String(40)) 9 9 10 10 class Stuff(Entity): 11 11 ping = Field(String(32)) 12 12 pong = Field(String(32)) 13 13 14 14 other = Field(String(40)) 15 16 setup_all() 17 15 16 setup_all() 17 18 18 class TestNestedClass(object): 19 19 def test_nestedclass(self): -
elixir/trunk/tests/test_o2m.py
r313 r347 11 11 def teardown(self): 12 12 cleanup_all(True) 13 13 14 14 def test_simple(self): 15 15 class A(Entity): … … 40 40 class Person(Entity): 41 41 name = Field(String(30)) 42 42 43 43 father = ManyToOne('Person', inverse='children') 44 44 children = OneToMany('Person', inverse='father') … … 50 50 bart = Person(name="Bart") 51 51 lisa = Person(name="Lisa") 52 53 grampa.children.append(homer) 52 53 grampa.children.append(homer) 54 54 homer.children.append(bart) 55 55 lisa.father = homer 56 56 57 57 session.flush() 58 58 session.clear() 59 59 60 60 p = Person.get_by(name="Homer") 61 62 print "%s is %s's child." % (p.name, p.father.name) 61 62 print "%s is %s's child." % (p.name, p.father.name) 63 63 print "His children are: %s." % ( 64 64 " and ".join([c.name for c in p.children])) 65 65 66 66 assert p in p.father.children 67 67 assert p.father is Person.get_by(name="Abe") … … 98 98 node.children.append(node2) 99 99 node.children.append(TreeNode(name='node3')) 100 100 101 101 session.flush() 102 102 session.clear() 103 103 104 104 root = TreeNode.get_by(name='rootnode') 105 105 print root … … 115 115 116 116 setup_all(True) 117 117 118 118 santa = Person(name="Santa Claus") 119 119 rudolph = Animal(name="Rudolph", owner=santa) 120 120 121 121 session.flush() 122 122 session.clear() 123 123 124 124 santa = Person.get_by(name="Santa Claus") 125 125 -
elixir/trunk/tests/test_options.py
r341 r347 5 5 from sqlalchemy import UniqueConstraint, create_engine, Column 6 6 from sqlalchemy.orm import create_session 7 from sqlalchemy.exceptions import SQLError, ConcurrentModificationError 7 from sqlalchemy.exceptions import SQLError, ConcurrentModificationError 8 8 from elixir import * 9 9 … … 19 19 class Person(Entity): 20 20 name = Field(String(30)) 21 21 22 22 using_options(version_id_col=True) 23 23 24 24 setup_all() 25 25 Person.table.create() 26 26 27 27 p1 = Person(name='Daniel') 28 28 session.flush() 29 29 session.clear() 30 30 31 31 person = Person.query.first() 32 32 person.name = 'Gaetan' … … 34 34 session.clear() 35 35 assert person.row_version == 2 36 36 37 37 person = Person.query.first() 38 38 person.name = 'Jonathan' … … 40 40 session.clear() 41 41 assert person.row_version == 3 42 42 43 43 # check that a concurrent modification raises exception 44 44 p1 = Person.query.first() … … 77 77 78 78 # Note that this test is bogus as you cannot just change a column this 79 # way since the mapper is already constructed at this point and will 79 # way since the mapper is already constructed at this point and will 80 80 # use the old column!!! This test is only meant as a way to check no 81 81 # exception is raised. … … 121 121 122 122 engine = create_engine('sqlite:///') 123 123 124 124 ctx = SessionContext(lambda: create_session(bind=engine)) 125 125 126 126 class Person(Entity): 127 127 using_options(session=ctx) … … 135 135 bart = Person(firstname="Bart", surname='Simpson') 136 136 ctx.current.flush() 137 137 138 138 assert Person.query.session is ctx.current 139 139 assert Person.query.filter_by(firstname='Homer').one() is homer … … 141 141 def test_manual_session(self): 142 142 engine = create_engine('sqlite:///') 143 143 144 144 class Person(Entity): 145 145 using_options(session=None) … … 158 158 session.save(bart) 159 159 session.flush() 160 160 161 161 bart.delete() 162 162 session.flush() … … 168 168 try: 169 169 from sqlalchemy.orm import scoped_session, sessionmaker 170 #TODO: this test, as-is has no sense on SA 0.4 since activemapper 171 # session uses scoped_session, but we need to provide a new 170 #TODO: this test, as-is has no sense on SA 0.4 since activemapper 171 # session uses scoped_session, but we need to provide a new 172 172 # test for that. 173 173 return … … 179 179 except ImportError: 180 180 return 181 181 182 182 engine = create_engine('sqlite:///') 183 183 … … 206 206 print "Not on version 0.4 or later of sqlalchemy" 207 207 return 208 208 209 209 engine = create_engine('sqlite:///') 210 210 … … 225 225 assert Person.query.session is Session() 226 226 assert Person.query.filter_by(firstname='Homer').one() is homer 227 227 228 228 def test_global_scoped_session(self): 229 229 try: … … 234 234 235 235 global __session__ 236 237 engine = create_engine('sqlite:///') 238 236 237 engine = create_engine('sqlite:///') 238 239 239 session = scoped_session(sessionmaker(bind=engine)) 240 240 __session__ = session 241 242 class Person(Entity): 243 firstname = Field(String(30)) 244 surname = Field(String(30)) 245 246 setup_all() 247 create_all(engine) 248 249 homer = Person(firstname="Homer", surname='Simpson') 250 bart = Person(firstname="Bart", surname='Simpson') 251 session.flush() 252 241 242 class Person(Entity): 243 firstname = Field(String(30)) 244 surname = Field(String(30)) 245 246 setup_all() 247 create_all(engine) 248 249 homer = Person(firstname="Homer", surname='Simpson') 250 bart = Person(firstname="Bart", surname='Simpson') 251 session.flush() 252 253 253 assert Person.query.session is session() 254 254 assert Person.query.filter_by(firstname='Homer').one() is homer 255 255 256 256 del __session__ 257 257 258 258 class TestTableOptions(object): 259 259 def setup(self): … … 264 264 265 265 def test_unique_constraint(self): 266 266 267 267 class Person(Entity): 268 268 firstname = Field(String(30)) -
elixir/trunk/tests/test_order_by.py
r271 r347 8 8 def setup(): 9 9 global Record, Artist, Genre 10 10 11 11 class Record(Entity): 12 12 title = Field(String(100)) … … 43 43 ("Octavarium", 2005), 44 44 # 2005 is a mistake to make the test more interesting 45 ("Six Degrees Of Inner Turbulence", 2005), 45 ("Six Degrees Of Inner Turbulence", 2005), 46 46 ("Train Of Thought", 2003), 47 47 ("When Dream And Day Unite", 1989) 48 48 ) 49 49 50 50 for title, year in titles: 51 51 Record(title=title, artist=artist, year=year, genres=[genre]) … … 62 62 def teardown(self): 63 63 session.clear() 64 64 65 65 def test_mapper_order_by(self): 66 66 records = Record.query.all() -
elixir/trunk/tests/test_packages.py
r344 r347 12 12 def teardown(self): 13 13 cleanup_all(True) 14 14 15 15 def test_packages(self): 16 16 # This is an ugly workaround because when nosetest is run globally (ie … … 18 18 # all modules, including a and b. Then when any other test calls 19 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 20 # calls cleanup_all(), so when we get here, A and B are already dead 21 21 # and reimporting their modules does nothing because they were already 22 22 # imported. -
elixir/trunk/tests/test_properties.py
r338 r347 14 14 def teardown(self): 15 15 cleanup_all(True) 16 16 17 17 def test_generic_property(self): 18 18 class Tag(Entity): … … 20 20 score2 = Field(Float) 21 21 22 score = GenericProperty( 22 score = GenericProperty( 23 23 lambda c: column_property( 24 24 (c.score1 * c.score2).label('score'))) … … 30 30 session.flush() 31 31 session.clear() 32 32 33 33 for tag in Tag.query.all(): 34 34 assert tag.score == tag.score1 * tag.score2 … … 48 48 session.flush() 49 49 session.clear() 50 50 51 51 for tag in Tag.query.all(): 52 52 assert tag.score == tag.score1 * tag.score2 … … 56 56 score1 = Field(Float) 57 57 score2 = Field(Float) 58 58 59 59 user = ManyToOne('User') 60 60 61 61 score = ColumnProperty(lambda c: c.score1 * c.score2) 62 62 63 63 class User(Entity): 64 64 name = Field(String(16)) 65 65 category = ManyToOne('Category') 66 66 tags = OneToMany('Tag', lazy=False) 67 score = ColumnProperty(lambda c: 67 score = ColumnProperty(lambda c: 68 68 select([func.sum(Tag.score)], 69 69 Tag.user_id == c.id).as_scalar()) 70 70 71 71 class Category(Entity): 72 72 name = Field(String(16)) 73 users = OneToMany('User', lazy=False) 74 75 score = ColumnProperty(lambda c: 73 users = OneToMany('User', lazy=False) 74 75 score = ColumnProperty(lambda c: 76 76 select([func.avg(User.score)], 77 77 User.category_id == c.id 78 78 ).as_scalar()) 79 79 setup_all(True) 80 81 u1 = User(name='joe', tags=[Tag(score1=5.0, score2=3.0), 80 81 u1 = User(name='joe', tags=[Tag(score1=5.0, score2=3.0), 82 82 Tag(score1=55.0, score2=1.0)]) 83 84 u2 = User(name='bar', tags=[Tag(score1=5.0, score2=4.0), 83 84 u2 = User(name='bar', tags=[Tag(score1=5.0, score2=4.0), 85 85 Tag(score1=50.0, score2=1.0), 86 86 Tag(score1=15.0, score2=2.0)]) 87 87 88 88 c1 = Category(name='dummy', users=[u1, u2]) 89 89 … … 102 102 has_field('score1', Float) 103 103 has_field('score2', Float) 104 has_property('score', 104 has_property('score', 105 105 lambda c: column_property( 106 106 (c.score1 * c.score2).label('score'))) … … 113 113 session.flush() 114 114 session.clear() 115 115 116 116 for tag in Tag.query.all(): 117 117 assert tag.score == tag.score1 * tag.score2 … … 157 157 session.flush() 158 158 session.clear() 159 159 160 160 p = Person.get_by(email='x@z.com') 161 161 162 162 assert p.name == 'Mr. X' 163 163 … … 213 213 214 214 assert a.name == 'a1' 215 215 -
elixir/trunk/tests/test_sa_integration.py
r336 r347 13 13 def teardown(self): 14 14 cleanup_all(True) 15 15 16 16 def test_sa_to_elixir(self): 17 17 class A(Entity): … … 22 22 setup_all(True) 23 23 24 b_table = Table('b', metadata, 24 b_table = Table('b', metadata, 25 25 Column('id', Integer, primary_key=True), 26 26 Column('name', String(60)), … … 49 49 50 50 # def test_elxir_to_sa(self): 51 # a_table = Table('a', metadata, 51 # a_table = Table('a', metadata, 52 52 # Column('id', Integer, primary_key=True), 53 53 # Column('name', String(60)), … … 63 63 # name = Field(String(60)) 64 64 # a = ManyToOne('A') 65 # 65 # 66 66 # setup_all(True) 67 67 # -
elixir/trunk/tests/test_through.py
r319 r347 13 13 def teardown(self): 14 14 cleanup_all(True) 15 15 16 16 def test_rel_through(self): 17 17 # converted from http://www.sqlalchemy.org/docs/04/plugins.html#plugins_associationproxy -
elixir/trunk/tests/test_versioning.py
r312 r347 49 49 def setup(self): 50 50 create_all() 51 51 52 52 def teardown(self): 53 53 drop_all() 54 54 session.clear() 55 56 def test_versioning(self): 55 56 def test_versioning(self): 57 57 gilliam = Director(name='Terry Gilliam') 58 58 monkeys = Movie(id=1, title='12 Monkeys', description='draft description', director=gilliam) 59 59 bruce = Actor(name='Bruce Willis', movies=[monkeys]) 60 60 session.flush(); session.clear() 61 61 62 62 time.sleep(1) 63 63 after_create = datetime.now() 64 64 time.sleep(1) 65 65 66 66 movie = Movie.get_by(title='12 Monkeys') 67 67 assert movie.version == 1 … … 71 71 movie.description = 'description two' 72 72 session.flush(); session.clear() 73 73 74 74 time.sleep(1) 75 75 after_update_one = datetime.now() 76 76 time.sleep(1) 77 77 78 78 movie = Movie.get_by(title='12 Monkeys') 79 79 movie.description = 'description three' … … 84 84 monkeys.ignoreme = 1 85 85 session.flush(); session.clear() 86 86 87 87 time.sleep(1) 88 88 after_update_two = datetime.now() 89 89 time.sleep(1) 90 90 91 91 movie = Movie.get_by(title='12 Monkeys') 92 92 assert movie.autoupd == 8, movie.autoupd … … 94 94 middle_version = movie.get_as_of(after_update_one) 95 95 latest_version = movie.get_as_of(after_update_two) 96 96 97 97 initial_timestamp = oldest_version.timestamp 98 98 99 99 assert oldest_version.version == 1 100 100 assert oldest_version.description == 'draft description' … … 102 102 assert oldest_version.autoupd is not None 103 103 assert oldest_version.autoupd > 0 104 104 105 105 assert middle_version.version == 2 106 106 assert middle_version.description == 'description two' 107 107 assert middle_version.autoupd > oldest_version.autoupd 108 108 109 109 assert latest_version.version == 3, 'version=%i' % latest_version.version 110 110 assert latest_version.description == 'description three' 111 111 assert latest_version.ignoreme == 1 112 112 assert latest_version.autoupd > middle_version.autoupd 113 113 114 114 differences = latest_version.compare_with(oldest_version) 115 115 assert differences['description'] == ('description three', 'draft description') 116 116 117 117 assert len(movie.versions) == 3 118 118 assert movie.versions[0] == oldest_version … … 124 124 movie.revert_to(2) 125 125 session.flush(); session.clear() 126 126 127 127 movie = Movie.get_by(title='12 Monkeys') 128 128 assert movie.version == 2, "version=%i, should be 2" % movie.version … … 150 150 movie = Movie(id=3, title='Foo', description='1') 151 151 session.commit(); 152 152 153 153 session.begin() 154 154 movie.description = '2' … … 156 156 session.rollback() 157 157 session.clear() 158 158 159 159 session.begin() 160 160 movie = Movie.get_by(title='Foo')
