diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2012-04-23 22:17:25 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2012-04-23 22:17:25 -0400 |
| commit | cb3913a186a01d9425e0ba97de89aa6d7d64ab96 (patch) | |
| tree | 7616eade5c09cb0b6e833d8e881f795f4ea965f5 /lib/sqlalchemy/orm/util.py | |
| parent | 841ea194bd7cf239323ee21320210fd6dc5c551d (diff) | |
| download | sqlalchemy-cb3913a186a01d9425e0ba97de89aa6d7d64ab96.tar.gz | |
- [feature] New standalone function with_polymorphic()
provides the functionality of query.with_polymorphic()
in a standalone form. It can be applied to any
entity within a query, including as the target
of a join in place of the "of_type()" modifier.
[ticket:2333]
- redo a large portion of the inheritance docs in terms
of declarative, new with_polymorphic() function
- upgrade examples/inheritance/polymorph, rename to "joined"
Diffstat (limited to 'lib/sqlalchemy/orm/util.py')
| -rw-r--r-- | lib/sqlalchemy/orm/util.py | 142 |
1 files changed, 119 insertions, 23 deletions
diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index 3cbe3f84a..5fcb15a9a 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -294,16 +294,28 @@ class AliasedClass(object): ``adapt_on_names`` is new in 0.7.3. """ - def __init__(self, cls, alias=None, name=None, adapt_on_names=False): + def __init__(self, cls, alias=None, + name=None, + adapt_on_names=False, + with_polymorphic_mappers=(), + with_polymorphic_discriminator=None): self.__mapper = _class_to_mapper(cls) self.__target = self.__mapper.class_ self.__adapt_on_names = adapt_on_names if alias is None: - alias = self.__mapper._with_polymorphic_selectable.alias(name=name) + alias = self.__mapper._with_polymorphic_selectable.alias( + name=name) self.__adapter = sql_util.ClauseAdapter(alias, - equivalents=self.__mapper._equivalent_columns, - adapt_on_names=self.__adapt_on_names) + equivalents=self.__mapper._equivalent_columns, + adapt_on_names=self.__adapt_on_names) self.__alias = alias + self.__with_polymorphic_mappers = with_polymorphic_mappers + self.__with_polymorphic_discriminator = \ + with_polymorphic_discriminator + for poly in with_polymorphic_mappers: + setattr(self, poly.class_.__name__, + AliasedClass(poly.class_, alias)) + # used to assign a name to the RowTuple object # returned by Query. self._sa_label_name = name @@ -315,6 +327,10 @@ class AliasedClass(object): 'alias':self.__alias, 'name':self._sa_label_name, 'adapt_on_names':self.__adapt_on_names, + 'with_polymorphic_mappers': + self.__with_polymorphic_mappers, + 'with_polymorphic_discriminator': + self.__with_polymorphic_discriminator } def __setstate__(self, state): @@ -323,9 +339,13 @@ class AliasedClass(object): self.__adapt_on_names = state['adapt_on_names'] alias = state['alias'] self.__adapter = sql_util.ClauseAdapter(alias, - equivalents=self.__mapper._equivalent_columns, - adapt_on_names=self.__adapt_on_names) + equivalents=self.__mapper._equivalent_columns, + adapt_on_names=self.__adapt_on_names) self.__alias = alias + self.__with_polymorphic_mappers = \ + state.get('with_polymorphic_mappers') + self.__with_polymorphic_discriminator = \ + state.get('with_polymorphic_discriminator') name = state['name'] self._sa_label_name = name self.__name__ = 'AliasedClass_' + str(self.__target) @@ -379,10 +399,75 @@ class AliasedClass(object): def aliased(element, alias=None, name=None, adapt_on_names=False): if isinstance(element, expression.FromClause): if adapt_on_names: - raise sa_exc.ArgumentError("adapt_on_names only applies to ORM elements") + raise sa_exc.ArgumentError( + "adapt_on_names only applies to ORM elements" + ) return element.alias(name) else: - return AliasedClass(element, alias=alias, name=name, adapt_on_names=adapt_on_names) + return AliasedClass(element, alias=alias, + name=name, adapt_on_names=adapt_on_names) + +def with_polymorphic(base, classes, selectable=False, + polymorphic_on=None, aliased=False): + """Produce an :class:`.AliasedClass` construct which specifies + columns for descendant mappers of the given base. + + .. note:: + + :func:`.orm.with_polymorphic` is new in version 0.8. + It is in addition to the existing :class:`.Query` method + :meth:`.Query.with_polymorphic`, which has the same purpose + but is not as flexible in its usage. + + Using this method will ensure that each descendant mapper's + tables are included in the FROM clause, and will allow filter() + criterion to be used against those tables. The resulting + instances will also have those columns already loaded so that + no "post fetch" of those columns will be required. + + See the examples at :ref:`with_polymorphic`. + + :param base: Base class to be aliased. + + :param cls_or_mappers: a single class or mapper, or list of + class/mappers, which inherit from the base class. + Alternatively, it may also be the string ``'*'``, in which case + all descending mapped classes will be added to the FROM clause. + + :param aliased: when True, the selectable will be wrapped in an + alias, that is ``(SELECT * FROM <fromclauses>) AS anon_1``. + This can be important when using the with_polymorphic() + to create the target of a JOIN on a backend that does not + support parenthesized joins, such as SQLite and older + versions of MySQL. + + :param selectable: a table or select() statement that will + be used in place of the generated FROM clause. This argument is + required if any of the desired classes use concrete table + inheritance, since SQLAlchemy currently cannot generate UNIONs + among tables automatically. If used, the ``selectable`` argument + must represent the full set of tables and columns mapped by every + mapped class. Otherwise, the unaccounted mapped columns will + result in their table being appended directly to the FROM clause + which will usually lead to incorrect results. + + :param polymorphic_on: a column to be used as the "discriminator" + column for the given selectable. If not given, the polymorphic_on + attribute of the base classes' mapper will be used, if any. This + is useful for mappings that don't have polymorphic loading + behavior by default. + + """ + primary_mapper = class_mapper(base) + mappers, selectable = primary_mapper.\ + _with_polymorphic_args(classes, selectable) + if aliased: + selectable = selectable.alias() + return AliasedClass(base, + selectable, + with_polymorphic_mappers=mappers, + with_polymorphic_discriminator=polymorphic_on) + def _orm_annotate(element, exclude=None): """Deep copy the given ClauseElement, annotating each element with the @@ -560,19 +645,13 @@ def with_parent(instance, prop): value_is_parent=True) -def _entity_info(entity, compile=True): - """Return mapping information given a class, mapper, or AliasedClass. - - Returns 3-tuple of: mapper, mapped selectable, boolean indicating if this - is an aliased() construct. - - If the given entity is not a mapper, mapped class, or aliased construct, - returns None, the entity, False. This is typically used to allow - unmapped selectables through. - - """ +def _extended_entity_info(entity, compile=True): if isinstance(entity, AliasedClass): - return entity._AliasedClass__mapper, entity._AliasedClass__alias, True + return entity._AliasedClass__mapper, \ + entity._AliasedClass__alias, \ + True, \ + entity._AliasedClass__with_polymorphic_mappers, \ + entity._AliasedClass__with_polymorphic_discriminator if isinstance(entity, mapperlib.Mapper): mapper = entity @@ -581,15 +660,32 @@ def _entity_info(entity, compile=True): class_manager = attributes.manager_of_class(entity) if class_manager is None: - return None, entity, False + return None, entity, False, [], None mapper = class_manager.mapper else: - return None, entity, False + return None, entity, False, [], None if compile and mapperlib.module._new_mappers: mapperlib.configure_mappers() - return mapper, mapper._with_polymorphic_selectable, False + return mapper, \ + mapper._with_polymorphic_selectable, \ + False, \ + mapper._with_polymorphic_mappers, \ + mapper.polymorphic_on + +def _entity_info(entity, compile=True): + """Return mapping information given a class, mapper, or AliasedClass. + + Returns 3-tuple of: mapper, mapped selectable, boolean indicating if this + is an aliased() construct. + + If the given entity is not a mapper, mapped class, or aliased construct, + returns None, the entity, False. This is typically used to allow + unmapped selectables through. + + """ + return _extended_entity_info(entity, compile)[0:3] def _entity_descriptor(entity, key): """Return a class attribute given an entity and string name. |
