diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2007-07-27 04:08:53 +0000 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2007-07-27 04:08:53 +0000 |
| commit | ed4fc64bb0ac61c27bc4af32962fb129e74a36bf (patch) | |
| tree | c1cf2fb7b1cafced82a8898e23d2a0bf5ced8526 /lib/sqlalchemy/orm/__init__.py | |
| parent | 3a8e235af64e36b3b711df1f069d32359fe6c967 (diff) | |
| download | sqlalchemy-ed4fc64bb0ac61c27bc4af32962fb129e74a36bf.tar.gz | |
merging 0.4 branch to trunk. see CHANGES for details. 0.3 moves to maintenance branch in branches/rel_0_3.
Diffstat (limited to 'lib/sqlalchemy/orm/__init__.py')
| -rw-r--r-- | lib/sqlalchemy/orm/__init__.py | 486 |
1 files changed, 365 insertions, 121 deletions
diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py index 7ef2da897..1982a94f7 100644 --- a/lib/sqlalchemy/orm/__init__.py +++ b/lib/sqlalchemy/orm/__init__.py @@ -11,58 +11,229 @@ packages and tying operations to class properties and constructors. from sqlalchemy import exceptions from sqlalchemy import util as sautil -from sqlalchemy.orm.mapper import * +from sqlalchemy.orm.mapper import Mapper, object_mapper, class_mapper, mapper_registry +from sqlalchemy.orm.interfaces import SynonymProperty, MapperExtension, EXT_PASS, ExtensionOption, PropComparator +from sqlalchemy.orm.properties import PropertyLoader, ColumnProperty, CompositeProperty, BackRef from sqlalchemy.orm import mapper as mapperlib +from sqlalchemy.orm import collections, strategies from sqlalchemy.orm.query import Query from sqlalchemy.orm.util import polymorphic_union -from sqlalchemy.orm import properties, strategies, interfaces from sqlalchemy.orm.session import Session as create_session from sqlalchemy.orm.session import object_session, attribute_manager -__all__ = ['relation', 'column_property', 'backref', 'eagerload', 'lazyload', 'noload', 'deferred', 'defer', 'undefer', 'extension', - 'mapper', 'clear_mappers', 'compile_mappers', 'clear_mapper', 'class_mapper', 'object_mapper', 'MapperExtension', 'Query', - 'cascade_mappers', 'polymorphic_union', 'create_session', 'synonym', 'contains_alias', 'contains_eager', 'EXT_PASS', 'object_session' - ] +__all__ = ['relation', 'column_property', 'composite', 'backref', 'eagerload', + 'eagerload_all', 'lazyload', 'noload', 'deferred', 'defer', 'undefer', + 'undefer_group', 'extension', 'mapper', 'clear_mappers', + 'compile_mappers', 'class_mapper', 'object_mapper', + 'MapperExtension', 'Query', 'polymorphic_union', 'create_session', + 'synonym', 'contains_alias', 'contains_eager', 'EXT_PASS', + 'object_session', 'PropComparator' + ] -def relation(*args, **kwargs): +def relation(argument, secondary=None, **kwargs): """Provide a relationship of a primary Mapper to a secondary Mapper. - This corresponds to a parent-child or associative table relationship. + This corresponds to a parent-child or associative table relationship. + The constructed class is an instance of [sqlalchemy.orm.properties#PropertyLoader]. + + argument + a class or Mapper instance, representing the target of the relation. + + secondary + for a many-to-many relationship, specifies the intermediary table. The + ``secondary`` keyword argument should generally only be used for a table + that is not otherwise expressed in any class mapping. In particular, + using the Association Object Pattern is + generally mutually exclusive against using the ``secondary`` keyword + argument. + + \**kwargs follow: + + association + Deprecated; as of version 0.3.0 the association keyword is synonomous + with applying the "all, delete-orphan" cascade to a "one-to-many" + relationship. SA can now automatically reconcile a "delete" and + "insert" operation of two objects with the same "identity" in a flush() + operation into a single "update" statement, which is the pattern that + "association" used to indicate. + + backref + indicates the name of a property to be placed on the related mapper's + class that will handle this relationship in the other direction, + including synchronizing the object attributes on both sides of the + relation. Can also point to a ``backref()`` construct for more + configurability. + + cascade + a string list of cascade rules which determines how persistence + operations should be "cascaded" from parent to child. + + collection_class + a class or function that returns a new list-holding object. will be + used in place of a plain list for storing elements. + + foreign_keys + a list of columns which are to be used as "foreign key" columns. + this parameter should be used in conjunction with explicit + ``primaryjoin`` and ``secondaryjoin`` (if needed) arguments, and the + columns within the ``foreign_keys`` list should be present within + those join conditions. Normally, ``relation()`` will inspect the + columns within the join conditions to determine which columns are + the "foreign key" columns, based on information in the ``Table`` + metadata. Use this argument when no ForeignKey's are present in the + join condition, or to override the table-defined foreign keys. + + foreignkey + deprecated. use the ``foreign_keys`` argument for foreign key + specification, or ``remote_side`` for "directional" logic. + + lazy=True + specifies how the related items should be loaded. a value of True + indicates they should be loaded lazily when the property is first + accessed. A value of False indicates they should be loaded by joining + against the parent object query, so parent and child are loaded in one + round trip (i.e. eagerly). A value of None indicates the related items + are not loaded by the mapper in any case; the application will manually + insert items into the list in some other way. In all cases, items added + or removed to the parent object's collection (or scalar attribute) will + cause the appropriate updates and deletes upon flush(), i.e. this + option only affects load operations, not save operations. + + order_by + indicates the ordering that should be applied when loading these items. + + passive_deletes=False + Indicates if lazy-loaders should not be executed during the ``flush()`` + process, which normally occurs in order to locate all existing child + items when a parent item is to be deleted. Setting this flag to True is + appropriate when ``ON DELETE CASCADE`` rules have been set up on the + actual tables so that the database may handle cascading deletes + automatically. This strategy is useful particularly for handling the + deletion of objects that have very large (and/or deep) child-object + collections. + + post_update + this indicates that the relationship should be handled by a second + UPDATE statement after an INSERT or before a DELETE. Currently, it also + will issue an UPDATE after the instance was UPDATEd as well, although + this technically should be improved. This flag is used to handle saving + bi-directional dependencies between two individual rows (i.e. each row + references the other), where it would otherwise be impossible to INSERT + or DELETE both rows fully since one row exists before the other. Use + this flag when a particular mapping arrangement will incur two rows + that are dependent on each other, such as a table that has a + one-to-many relationship to a set of child rows, and also has a column + that references a single child row within that list (i.e. both tables + contain a foreign key to each other). If a ``flush()`` operation returns + an error that a "cyclical dependency" was detected, this is a cue that + you might want to use ``post_update`` to "break" the cycle. + + primaryjoin + a ClauseElement that will be used as the primary join of this child + object against the parent object, or in a many-to-many relationship the + join of the primary object to the association table. By default, this + value is computed based on the foreign key relationships of the parent + and child tables (or association table). + + private=False + deprecated. setting ``private=True`` is the equivalent of setting + ``cascade="all, delete-orphan"``, and indicates the lifecycle of child + objects should be contained within that of the parent. + + remote_side + used for self-referential relationships, indicates the column or list + of columns that form the "remote side" of the relationship. + + secondaryjoin + a ClauseElement that will be used as the join of an association table + to the child object. By default, this value is computed based on the + foreign key relationships of the association and child tables. + + uselist=(True|False) + a boolean that indicates if this property should be loaded as a list or + a scalar. In most cases, this value is determined automatically by + ``relation()``, based on the type and direction of the relationship - one + to many forms a list, many to one forms a scalar, many to many is a + list. If a scalar is desired where normally a list would be present, + such as a bi-directional one-to-one relationship, set uselist to False. + + viewonly=False + when set to True, the relation is used only for loading objects within + the relationship, and has no effect on the unit-of-work flush process. + Relations with viewonly can specify any kind of join conditions to + provide additional views of related objects onto a parent object. Note + that the functionality of a viewonly relationship has its limits - + complicated join conditions may not compile into eager or lazy loaders + properly. If this is the case, use an alternative method. + """ - if len(args) > 1 and isinstance(args[0], type): - raise exceptions.ArgumentError("relation(class, table, **kwargs) is deprecated. Please use relation(class, **kwargs) or relation(mapper, **kwargs).") - return _relation_loader(*args, **kwargs) + return PropertyLoader(argument, secondary=secondary, **kwargs) + +# return _relation_loader(argument, secondary=secondary, **kwargs) + +#def _relation_loader(mapper, secondary=None, primaryjoin=None, secondaryjoin=None, lazy=True, **kwargs): def column_property(*args, **kwargs): """Provide a column-level property for use with a Mapper. + + Column-based properties can normally be applied to the mapper's + ``properties`` dictionary using the ``schema.Column`` element directly. + Use this function when the given column is not directly present within + the mapper's selectable; examples include SQL expressions, functions, + and scalar SELECT queries. + + Columns that arent present in the mapper's selectable won't be persisted + by the mapper and are effectively "read-only" attributes. + + \*cols + list of Column objects to be mapped. - Normally, custom column-level properties that represent columns - directly or indirectly present within the mapped selectable - can just be added to the ``properties`` dictionary directly, - in which case this function's usage is not necessary. - - In the case of a ``ColumnElement`` directly present within the - ``properties`` dictionary, the given column is converted to be the exact column - located within the mapped selectable, in the case that the mapped selectable - is not the exact parent selectable of the given column, but shares a common - base table relationship with that column. + group + a group name for this property when marked as deferred. + + deferred + when True, the column property is "deferred", meaning that + it does not load immediately, and is instead loaded when the + attribute is first accessed on an instance. See also + [sqlalchemy.orm#deferred()]. + + """ - Use this function when the column expression being added does not - correspond to any single column within the mapped selectable, - such as a labeled function or scalar-returning subquery, to force the element - to become a mapped property regardless of it not being present within the - mapped selectable. + return ColumnProperty(*args, **kwargs) + +def composite(class_, *cols, **kwargs): + """Return a composite column-based property for use with a Mapper. + + This is very much like a column-based property except the given class + is used to construct values composed of one or more columns. The class must + implement a constructor with positional arguments matching the order of + columns given, as well as a __colset__() method which returns its attributes + in column order. - Note that persistence of instances is driven from the collection of columns - within the mapped selectable, so column properties attached to a Mapper which have - no direct correspondence to the mapped selectable will effectively be non-persisted - attributes. + class\_ + the "composite type" class. + + \*cols + list of Column objects to be mapped. + + group + a group name for this property when marked as deferred. + + deferred + when True, the column property is "deferred", meaning that + it does not load immediately, and is instead loaded when the + attribute is first accessed on an instance. See also + [sqlalchemy.orm#deferred()]. + + comparator + an optional instance of [sqlalchemy.orm#PropComparator] which + provides SQL expression generation functions for this composite + type. """ - return properties.ColumnProperty(*args, **kwargs) -def _relation_loader(mapper, secondary=None, primaryjoin=None, secondaryjoin=None, lazy=True, **kwargs): - return properties.PropertyLoader(mapper, secondary, primaryjoin, secondaryjoin, lazy=lazy, **kwargs) + return CompositeProperty(class_, *cols, **kwargs) + def backref(name, **kwargs): """Create a BackRef object with explicit arguments, which are the same arguments one @@ -72,7 +243,7 @@ def backref(name, **kwargs): place of a string argument. """ - return properties.BackRef(name, **kwargs) + return BackRef(name, **kwargs) def deferred(*columns, **kwargs): """Return a ``DeferredColumnProperty``, which indicates this @@ -82,15 +253,141 @@ def deferred(*columns, **kwargs): Used with the `properties` dictionary sent to ``mapper()``. """ - return properties.ColumnProperty(deferred=True, *columns, **kwargs) - -def mapper(class_, table=None, *args, **params): - """Return a new ``Mapper`` object. - - See the ``Mapper`` class for a description of arguments. + return ColumnProperty(deferred=True, *columns, **kwargs) + +def mapper(class_, local_table=None, *args, **params): + """Return a new [sqlalchemy.orm#Mapper] object. + + class\_ + The class to be mapped. + + local_table + The table to which the class is mapped, or None if this + mapper inherits from another mapper using concrete table + inheritance. + + entity_name + A name to be associated with the `class`, to allow alternate + mappings for a single class. + + always_refresh + If True, all query operations for this mapped class will + overwrite all data within object instances that already + exist within the session, erasing any in-memory changes with + whatever information was loaded from the database. Usage + of this flag is highly discouraged; as an alternative, + see the method `populate_existing()` on [sqlalchemy.orm.query#Query]. + + allow_column_override + If True, allows the usage of a ``relation()`` which has the + same name as a column in the mapped table. The table column + will no longer be mapped. + + allow_null_pks + Indicates that composite primary keys where one or more (but + not all) columns contain NULL is a valid primary key. + Primary keys which contain NULL values usually indicate that + a result row does not contain an entity and should be + skipped. + + batch + Indicates that save operations of multiple entities can be + batched together for efficiency. setting to False indicates + that an instance will be fully saved before saving the next + instance, which includes inserting/updating all table rows + corresponding to the entity as well as calling all + ``MapperExtension`` methods corresponding to the save + operation. + + column_prefix + A string which will be prepended to the `key` name of all + Columns when creating column-based properties from the given + Table. Does not affect explicitly specified column-based + properties + + concrete + If True, indicates this mapper should use concrete table + inheritance with its parent mapper. + + extension + A [sqlalchemy.orm#MapperExtension] instance or list of + ``MapperExtension`` instances which will be applied to all + operations by this ``Mapper``. + + inherits + Another ``Mapper`` for which this ``Mapper`` will have an + inheritance relationship with. + + inherit_condition + For joined table inheritance, a SQL expression (constructed + ``ClauseElement``) which will define how the two tables are + joined; defaults to a natural join between the two tables. + + order_by + A single ``Column`` or list of ``Columns`` for which + selection operations should use as the default ordering for + entities. Defaults to the OID/ROWID of the table if any, or + the first primary key column of the table. + + non_primary + Construct a ``Mapper`` that will define only the selection + of instances, not their persistence. Any number of non_primary + mappers may be created for a particular class. + + polymorphic_on + Used with mappers in an inheritance relationship, a ``Column`` + which will identify the class/mapper combination to be used + with a particular row. requires the polymorphic_identity + value to be set for all mappers in the inheritance + hierarchy. + + _polymorphic_map + Used internally to propigate the full map of polymorphic + identifiers to surrogate mappers. + + polymorphic_identity + A value which will be stored in the Column denoted by + polymorphic_on, corresponding to the *class identity* of + this mapper. + + polymorphic_fetch + specifies how subclasses mapped through joined-table + inheritance will be fetched. options are 'union', + 'select', and 'deferred'. if the select_table argument + is present, defaults to 'union', otherwise defaults to + 'select'. + + properties + A dictionary mapping the string names of object attributes + to ``MapperProperty`` instances, which define the + persistence behavior of that attribute. Note that the + columns in the mapped table are automatically converted into + ``ColumnProperty`` instances based on the `key` property of + each ``Column`` (although they can be overridden using this + dictionary). + + primary_key + A list of ``Column`` objects which define the *primary key* + to be used against this mapper's selectable unit. This is + normally simply the primary key of the `local_table`, but + can be overridden here. + + select_table + A [sqlalchemy.schema#Table] or any [sqlalchemy.sql#Selectable] + which will be used to select instances of this mapper's class. + usually used to provide polymorphic loading among several + classes in an inheritance hierarchy. + + version_id_col + A ``Column`` which must have an integer type that will be + used to keep a running *version id* of mapped entities in + the database. this is used during save operations to ensure + that no other thread or process has updated the instance + during the lifetime of the entity, else a + ``ConcurrentModificationError`` exception is thrown. """ - return Mapper(class_, table, *args, **params) + return Mapper(class_, local_table, *args, **params) def synonym(name, proxy=False): """Set up `name` as a synonym to another ``MapperProperty``. @@ -98,7 +395,7 @@ def synonym(name, proxy=False): Used with the `properties` dictionary sent to ``mapper()``. """ - return interfaces.SynonymProperty(name, proxy=proxy) + return SynonymProperty(name, proxy=proxy) def compile_mappers(): """Compile all mappers that have been defined. @@ -120,32 +417,13 @@ def clear_mappers(): mapperlib._COMPILE_MUTEX.acquire() try: for mapper in mapper_registry.values(): - attribute_manager.reset_class_managed(mapper.class_) - if hasattr(mapper.class_, 'c'): - del mapper.class_.c + mapper.dispose() mapper_registry.clear() # TODO: either dont use ArgSingleton, or # find a way to clear only ClassKey instances from it sautil.ArgSingleton.instances.clear() finally: mapperlib._COMPILE_MUTEX.release() - -def clear_mapper(m): - """Remove the given mapper from the storage of mappers. - - When a new mapper is created for the previous mapper's class, it - will be used as that classes' new primary mapper. - """ - - mapperlib._COMPILE_MUTEX.acquire() - try: - del mapper_registry[m.class_key] - attribute_manager.reset_class_managed(m.class_) - if hasattr(m.class_, 'c'): - del m.class_.c - m.class_key.dispose() - finally: - mapperlib._COMPILE_MUTEX.release() def extension(ext): """Return a ``MapperOption`` that will insert the given @@ -166,6 +444,22 @@ def eagerload(name): return strategies.EagerLazyOption(name, lazy=False) +def eagerload_all(name): + """Return a ``MapperOption`` that will convert all + properties along the given dot-separated path into an + eager load. + + e.g:: + query.options(eagerload_all('orders.items.keywords'))... + + will set all of 'orders', 'orders.items', and 'orders.items.keywords' + to load in one eager load. + + Used with ``query.options()``. + """ + + return strategies.EagerLazyOption(name, lazy=False, chained=True) + def lazyload(name): """Return a ``MapperOption`` that will convert the property of the given name into a lazy load. @@ -175,6 +469,9 @@ def lazyload(name): return strategies.EagerLazyOption(name, lazy=True) +def fetchmode(name, type): + return strategies.FetchModeOption(name, type) + def noload(name): """Return a ``MapperOption`` that will convert the property of the given name into a non-load. @@ -250,64 +547,11 @@ def undefer(name): return strategies.DeferredOption(name, defer=False) +def undefer_group(name): + """Return a ``MapperOption`` that will convert the given + group of deferred column properties into a non-deferred (regular column) load. -def cascade_mappers(*classes_or_mappers): - """Attempt to create a series of ``relations()`` between mappers - automatically, via introspecting the foreign key relationships of - the underlying tables. - - Given a list of classes and/or mappers, identify the foreign key - relationships between the given mappers or corresponding class - mappers, and create ``relation()`` objects representing those - relationships, including a backreference. Attempt to find the - *secondary* table in a many-to-many relationship as well. - - The names of the relations will be a lowercase version of the - related class. In the case of one-to-many or many-to-many, the - name will be *pluralized*, which currently is based on the English - language (i.e. an 's' or 'es' added to it). - - NOTE: this method usually works poorly, and its usage is generally - not advised. + Used with ``query.options()``. """ - - table_to_mapper = {} - for item in classes_or_mappers: - if isinstance(item, Mapper): - m = item - else: - klass = item - m = class_mapper(klass) - table_to_mapper[m.mapped_table] = m - - def pluralize(name): - # oh crap, do we need locale stuff now - if name[-1] == 's': - return name + "es" - else: - return name + "s" - - for table,mapper in table_to_mapper.iteritems(): - for fk in table.foreign_keys: - if fk.column.table is table: - continue - secondary = None - try: - m2 = table_to_mapper[fk.column.table] - except KeyError: - if len(fk.column.table.primary_key): - continue - for sfk in fk.column.table.foreign_keys: - if sfk.column.table is table: - continue - m2 = table_to_mapper.get(sfk.column.table) - secondary = fk.column.table - if m2 is None: - continue - if secondary: - propname = pluralize(m2.class_.__name__.lower()) - propname2 = pluralize(mapper.class_.__name__.lower()) - else: - propname = m2.class_.__name__.lower() - propname2 = pluralize(mapper.class_.__name__.lower()) - mapper.add_property(propname, relation(m2, secondary=secondary, backref=propname2)) + return strategies.UndeferGroupOption(name) + |
