summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--[-rwxr-xr-x]lib/sqlalchemy/ext/declarative/__init__.py (renamed from lib/sqlalchemy/ext/declarative.py)953
-rw-r--r--lib/sqlalchemy/ext/declarative/api.py436
-rw-r--r--lib/sqlalchemy/ext/declarative/base.py418
-rw-r--r--lib/sqlalchemy/ext/declarative/clsregistry.py244
4 files changed, 1137 insertions, 914 deletions
diff --git a/lib/sqlalchemy/ext/declarative.py b/lib/sqlalchemy/ext/declarative/__init__.py
index 974881a82..e6d6e388b 100755..100644
--- a/lib/sqlalchemy/ext/declarative.py
+++ b/lib/sqlalchemy/ext/declarative/__init__.py
@@ -1,4 +1,4 @@
-# ext/declarative.py
+# ext/declarative/__init__.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
@@ -156,13 +156,40 @@ expression functions like :func:`~sqlalchemy.sql.expression.desc` and
order_by="desc(Address.email)",
primaryjoin="Address.user_id==User.id")
-As an alternative to string-based attributes, attributes may also be
-defined after all classes have been created. Just add them to the target
-class after the fact::
+For the case where more than one module contains a class of the same name,
+string class names can also be specified as fully module-qualified paths
+within any of these string expressions::
+
+ class User(Base):
+ # ....
+ addresses = relationship("myapp.model.address.Address",
+ order_by="desc(myapp.model.address.Address.email)",
+ primaryjoin="myapp.model.address.Address.user_id=="
+ "myapp.model.user.User.id")
+
+.. versionadded:: 0.8
+ Fully module-qualified paths can be used when specifying string arguments
+ with Declarative.
+
+Two alternatives also exist to using string-based attributes. A lambda
+can also be used, which will be evaluated after all mappers have been
+configured::
+
+ class User(Base):
+ # ...
+ addresses = relationship(lambda: Address,
+ order_by=lambda: desc(Address.email),
+ primaryjoin=lambda: Address.user_id==User.id)
+
+Or, the relationship can be added to the class explicitly after the classes
+are available::
User.addresses = relationship(Address,
primaryjoin=Address.user_id==User.id)
+
+
+
Configuring Many-to-Many Relationships
======================================
@@ -1026,915 +1053,13 @@ Mapped instances then make usage of
"""
-from ..schema import Table, Column, MetaData, _get_table_key
-from ..orm import synonym as _orm_synonym, mapper,\
- comparable_property, class_mapper
-from ..orm.interfaces import MapperProperty
-from ..orm.properties import RelationshipProperty, ColumnProperty, CompositeProperty
-from ..orm.util import _is_mapped_class
-from .. import util, exc
-from ..sql import expression
-from .. import event
-from ..orm.util import polymorphic_union, _mapper_or_none
-import weakref
-
-__all__ = 'declarative_base', 'synonym_for', \
- 'comparable_using', 'instrument_declarative'
-
-def _declared_mapping_info(cls):
- # deferred mapping
- if cls in _MapperConfig.configs:
- return _MapperConfig.configs[cls]
- # regular mapping
- elif _is_mapped_class(cls):
- return class_mapper(cls, configure=False)
- else:
- return None
-
-def instrument_declarative(cls, registry, metadata):
- """Given a class, configure the class declaratively,
- using the given registry, which can be any dictionary, and
- MetaData object.
-
- """
- if '_decl_class_registry' in cls.__dict__:
- raise exc.InvalidRequestError(
- "Class %r already has been "
- "instrumented declaratively" % cls)
- cls._decl_class_registry = registry
- cls.metadata = metadata
- _as_declarative(cls, cls.__name__, cls.__dict__)
-
-def has_inherited_table(cls):
- """Given a class, return True if any of the classes it inherits from has a
- mapped table, otherwise return False.
- """
- for class_ in cls.__mro__:
- if getattr(class_, '__table__', None) is not None:
- return True
- return False
-
-def _as_declarative(cls, classname, dict_):
-
- # dict_ will be a dictproxy, which we can't write to, and we need to!
- dict_ = dict(dict_)
-
- column_copies = {}
- potential_columns = {}
-
- mapper_args_fn = None
- table_args = inherited_table_args = None
- tablename = None
- parent_columns = ()
-
- declarative_props = (declared_attr, util.classproperty)
-
- for base in cls.__mro__:
- _is_declarative_inherits = hasattr(base, '_decl_class_registry')
-
- if '__declare_last__' in base.__dict__:
- @event.listens_for(mapper, "after_configured")
- def go():
- cls.__declare_last__()
- if '__abstract__' in base.__dict__:
- if (base is cls or
- (base in cls.__bases__ and not _is_declarative_inherits)
- ):
- return
-
- class_mapped = _declared_mapping_info(base) is not None
- if class_mapped:
- parent_columns = base.__table__.c.keys()
-
- for name, obj in vars(base).items():
- if name == '__mapper_args__':
- if not mapper_args_fn and (
- not class_mapped or
- isinstance(obj, declarative_props)
- ):
- # don't even invoke __mapper_args__ until
- # after we've determined everything about the
- # mapped table.
- mapper_args_fn = lambda: cls.__mapper_args__
- elif name == '__tablename__':
- if not tablename and (
- not class_mapped or
- isinstance(obj, declarative_props)
- ):
- tablename = cls.__tablename__
- elif name == '__table_args__':
- if not table_args and (
- not class_mapped or
- isinstance(obj, declarative_props)
- ):
- table_args = cls.__table_args__
- if not isinstance(table_args, (tuple, dict, type(None))):
- raise exc.ArgumentError(
- "__table_args__ value must be a tuple, "
- "dict, or None")
- if base is not cls:
- inherited_table_args = True
- elif class_mapped:
- if isinstance(obj, declarative_props):
- util.warn("Regular (i.e. not __special__) "
- "attribute '%s.%s' uses @declared_attr, "
- "but owning class %s is mapped - "
- "not applying to subclass %s."
- % (base.__name__, name, base, cls))
- continue
- elif base is not cls:
- # we're a mixin.
- if isinstance(obj, Column):
- if obj.foreign_keys:
- raise exc.InvalidRequestError(
- "Columns with foreign keys to other columns "
- "must be declared as @declared_attr callables "
- "on declarative mixin classes. ")
- if name not in dict_ and not (
- '__table__' in dict_ and
- (obj.name or name) in dict_['__table__'].c
- ) and name not in potential_columns:
- potential_columns[name] = \
- column_copies[obj] = \
- obj.copy()
- column_copies[obj]._creation_order = \
- obj._creation_order
- elif isinstance(obj, MapperProperty):
- raise exc.InvalidRequestError(
- "Mapper properties (i.e. deferred,"
- "column_property(), relationship(), etc.) must "
- "be declared as @declared_attr callables "
- "on declarative mixin classes.")
- elif isinstance(obj, declarative_props):
- dict_[name] = ret = \
- column_copies[obj] = getattr(cls, name)
- if isinstance(ret, (Column, MapperProperty)) and \
- ret.doc is None:
- ret.doc = obj.__doc__
-
- # apply inherited columns as we should
- for k, v in potential_columns.items():
- if tablename or (v.name or k) not in parent_columns:
- dict_[k] = v
-
- if inherited_table_args and not tablename:
- table_args = None
-
- if classname in cls._decl_class_registry:
- util.warn("The classname %r is already in the registry of this"
- " declarative base, mapped to %r" % (
- classname,
- cls._decl_class_registry[classname]
- ))
- cls._decl_class_registry[classname] = cls
- our_stuff = util.OrderedDict()
-
- for k in dict_:
-
- # TODO: improve this ? all dunders ?
- if k in ('__table__', '__tablename__', '__mapper_args__'):
- continue
-
- value = dict_[k]
- if isinstance(value, declarative_props):
- value = getattr(cls, k)
-
- if (isinstance(value, tuple) and len(value) == 1 and
- isinstance(value[0], (Column, MapperProperty))):
- util.warn("Ignoring declarative-like tuple value of attribute "
- "%s: possibly a copy-and-paste error with a comma "
- "left at the end of the line?" % k)
- continue
- if not isinstance(value, (Column, MapperProperty)):
- continue
- if k == 'metadata':
- raise exc.InvalidRequestError(
- "Attribute name 'metadata' is reserved "
- "for the MetaData instance when using a "
- "declarative base class."
- )
- prop = _deferred_relationship(cls, value)
- our_stuff[k] = prop
-
- # set up attributes in the order they were created
- our_stuff.sort(key=lambda key: our_stuff[key]._creation_order)
-
- # extract columns from the class dict
- declared_columns = set()
- for key, c in our_stuff.iteritems():
- if isinstance(c, (ColumnProperty, CompositeProperty)):
- for col in c.columns:
- if isinstance(col, Column) and \
- col.table is None:
- _undefer_column_name(key, col)
- declared_columns.add(col)
- elif isinstance(c, Column):
- _undefer_column_name(key, c)
- declared_columns.add(c)
- # if the column is the same name as the key,
- # remove it from the explicit properties dict.
- # the normal rules for assigning column-based properties
- # will take over, including precedence of columns
- # in multi-column ColumnProperties.
- if key == c.key:
- del our_stuff[key]
- declared_columns = sorted(declared_columns, key=lambda c: c._creation_order)
- table = None
-
- if hasattr(cls, '__table_cls__'):
- table_cls = util.unbound_method_to_callable(cls.__table_cls__)
- else:
- table_cls = Table
-
- if '__table__' not in dict_:
- if tablename is not None:
-
- args, table_kw = (), {}
- if table_args:
- if isinstance(table_args, dict):
- table_kw = table_args
- elif isinstance(table_args, tuple):
- if isinstance(table_args[-1], dict):
- args, table_kw = table_args[0:-1], table_args[-1]
- else:
- args = table_args
-
- autoload = dict_.get('__autoload__')
- if autoload:
- table_kw['autoload'] = True
-
- cls.__table__ = table = table_cls(tablename, cls.metadata,
- *(tuple(declared_columns) + tuple(args)),
- **table_kw)
- else:
- table = cls.__table__
- if declared_columns:
- for c in declared_columns:
- if not table.c.contains_column(c):
- raise exc.ArgumentError(
- "Can't add additional column %r when "
- "specifying __table__" % c.key
- )
-
- if hasattr(cls, '__mapper_cls__'):
- mapper_cls = util.unbound_method_to_callable(cls.__mapper_cls__)
- else:
- mapper_cls = mapper
-
- for c in cls.__bases__:
- if _declared_mapping_info(c) is not None:
- inherits = c
- break
- else:
- inherits = None
-
- if table is None and inherits is None:
- raise exc.InvalidRequestError(
- "Class %r does not have a __table__ or __tablename__ "
- "specified and does not inherit from an existing "
- "table-mapped class." % cls
- )
- elif inherits:
- inherited_mapper = _declared_mapping_info(inherits)
- inherited_table = inherited_mapper.local_table
-
- if table is None:
- # single table inheritance.
- # ensure no table args
- if table_args:
- raise exc.ArgumentError(
- "Can't place __table_args__ on an inherited class "
- "with no table."
- )
-
- # add any columns declared here to the inherited table.
- for c in declared_columns:
- if c.primary_key:
- raise exc.ArgumentError(
- "Can't place primary key columns on an inherited "
- "class with no table."
- )
- if c.name in inherited_table.c:
- raise exc.ArgumentError(
- "Column '%s' on class %s conflicts with "
- "existing column '%s'" %
- (c, cls, inherited_table.c[c.name])
- )
- inherited_table.append_column(c)
-
- mt = _MapperConfig(mapper_cls,
- cls, table,
- inherits,
- declared_columns,
- column_copies,
- our_stuff,
- mapper_args_fn)
- if not hasattr(cls, '_sa_decl_prepare'):
- mt.map()
-
-class _MapperConfig(object):
- configs = util.OrderedDict()
-
- def __init__(self, mapper_cls,
- cls,
- table,
- inherits,
- declared_columns,
- column_copies,
- properties, mapper_args_fn):
- self.mapper_cls = mapper_cls
- self.cls = cls
- self.local_table = table
- self.inherits = inherits
- self.properties = properties
- self.mapper_args_fn = mapper_args_fn
- self.declared_columns = declared_columns
- self.column_copies = column_copies
- self.configs[cls] = self
-
- def _prepare_mapper_arguments(self):
- properties = self.properties
- if self.mapper_args_fn:
- mapper_args = self.mapper_args_fn()
- else:
- mapper_args = {}
-
- # make sure that column copies are used rather
- # than the original columns from any mixins
- for k in ('version_id_col', 'polymorphic_on',):
- if k in mapper_args:
- v = mapper_args[k]
- mapper_args[k] = self.column_copies.get(v, v)
-
- assert 'inherits' not in mapper_args, \
- "Can't specify 'inherits' explicitly with declarative mappings"
-
- if self.inherits:
- mapper_args['inherits'] = self.inherits
-
- if self.inherits and not mapper_args.get('concrete', False):
- # single or joined inheritance
- # exclude any cols on the inherited table which are
- # not mapped on the parent class, to avoid
- # mapping columns specific to sibling/nephew classes
- inherited_mapper = _declared_mapping_info(self.inherits)
- inherited_table = inherited_mapper.local_table
-
- if 'exclude_properties' not in mapper_args:
- mapper_args['exclude_properties'] = exclude_properties = \
- set([c.key for c in inherited_table.c
- if c not in inherited_mapper._columntoproperty])
- exclude_properties.difference_update(
- [c.key for c in self.declared_columns])
-
- # look through columns in the current mapper that
- # are keyed to a propname different than the colname
- # (if names were the same, we'd have popped it out above,
- # in which case the mapper makes this combination).
- # See if the superclass has a similar column property.
- # If so, join them together.
- for k, col in properties.items():
- if not isinstance(col, expression.ColumnElement):
- continue
- if k in inherited_mapper._props:
- p = inherited_mapper._props[k]
- if isinstance(p, ColumnProperty):
- # note here we place the subclass column
- # first. See [ticket:1892] for background.
- properties[k] = [col] + p.columns
-
- result_mapper_args = mapper_args.copy()
- result_mapper_args['properties'] = properties
- return result_mapper_args
-
- def map(self):
- self.configs.pop(self.cls, None)
- mapper_args = self._prepare_mapper_arguments()
- self.cls.__mapper__ = self.mapper_cls(
- self.cls,
- self.local_table,
- **mapper_args
- )
-
-class DeclarativeMeta(type):
- def __init__(cls, classname, bases, dict_):
- if '_decl_class_registry' in cls.__dict__:
- return type.__init__(cls, classname, bases, dict_)
- else:
- _as_declarative(cls, classname, cls.__dict__)
- return type.__init__(cls, classname, bases, dict_)
-
- def __setattr__(cls, key, value):
- if '__mapper__' in cls.__dict__:
- if isinstance(value, Column):
- _undefer_column_name(key, value)
- cls.__table__.append_column(value)
- cls.__mapper__.add_property(key, value)
- elif isinstance(value, ColumnProperty):
- for col in value.columns:
- if isinstance(col, Column) and col.table is None:
- _undefer_column_name(key, col)
- cls.__table__.append_column(col)
- cls.__mapper__.add_property(key, value)
- elif isinstance(value, MapperProperty):
- cls.__mapper__.add_property(
- key,
- _deferred_relationship(cls, value)
- )
- else:
- type.__setattr__(cls, key, value)
- else:
- type.__setattr__(cls, key, value)
-
-
-class _GetColumns(object):
- def __init__(self, cls):
- self.cls = cls
-
- def __getattr__(self, key):
- mapper = class_mapper(self.cls, configure=False)
- if mapper:
- if not mapper.has_property(key):
- raise exc.InvalidRequestError(
- "Class %r does not have a mapped column named %r"
- % (self.cls, key))
-
- prop = mapper.get_property(key)
- if not isinstance(prop, ColumnProperty):
- raise exc.InvalidRequestError(
- "Property %r is not an instance of"
- " ColumnProperty (i.e. does not correspond"
- " directly to a Column)." % key)
- return getattr(self.cls, key)
-
-class _GetTable(object):
- def __init__(self, key, metadata):
- self.key = key
- self.metadata = metadata
-
- def __getattr__(self, key):
- return self.metadata.tables[
- _get_table_key(key, self.key)
- ]
-
-def _deferred_relationship(cls, prop):
- def resolve_arg(arg):
- import sqlalchemy
- from sqlalchemy.orm import foreign, remote
-
- fallback = sqlalchemy.__dict__.copy()
- fallback.update({'foreign': foreign, 'remote': remote})
-
- def access_cls(key):
- if key in cls._decl_class_registry:
- return _GetColumns(cls._decl_class_registry[key])
- elif key in cls.metadata.tables:
- return cls.metadata.tables[key]
- elif key in cls.metadata._schemas:
- return _GetTable(key, cls.metadata)
- else:
- return fallback[key]
-
- d = util.PopulateDict(access_cls)
- def return_cls():
- try:
- x = eval(arg, globals(), d)
-
- if isinstance(x, _GetColumns):
- return x.cls
- else:
- return x
- except NameError, n:
- raise exc.InvalidRequestError(
- "When initializing mapper %s, expression %r failed to "
- "locate a name (%r). If this is a class name, consider "
- "adding this relationship() to the %r class after "
- "both dependent classes have been defined." %
- (prop.parent, arg, n.args[0], cls)
- )
- return return_cls
+from .api import declarative_base, synonym_for, comparable_using, \
+ instrument_declarative, ConcreteBase, AbstractConcreteBase, \
+ DeclarativeMeta, DeferredReflection, has_inherited_table,\
+ declared_attr
- if isinstance(prop, RelationshipProperty):
- for attr in ('argument', 'order_by', 'primaryjoin', 'secondaryjoin',
- 'secondary', '_user_defined_foreign_keys', 'remote_side'):
- v = getattr(prop, attr)
- if isinstance(v, basestring):
- setattr(prop, attr, resolve_arg(v))
-
- if prop.backref and isinstance(prop.backref, tuple):
- key, kwargs = prop.backref
- for attr in ('primaryjoin', 'secondaryjoin', 'secondary',
- 'foreign_keys', 'remote_side', 'order_by'):
- if attr in kwargs and isinstance(kwargs[attr], basestring):
- kwargs[attr] = resolve_arg(kwargs[attr])
-
-
- return prop
-
-def synonym_for(name, map_column=False):
- """Decorator, make a Python @property a query synonym for a column.
-
- A decorator version of :func:`~sqlalchemy.orm.synonym`. The function being
- decorated is the 'descriptor', otherwise passes its arguments through to
- synonym()::
-
- @synonym_for('col')
- @property
- def prop(self):
- return 'special sauce'
-
- The regular ``synonym()`` is also usable directly in a declarative setting
- and may be convenient for read/write properties::
-
- prop = synonym('col', descriptor=property(_read_prop, _write_prop))
-
- """
- def decorate(fn):
- return _orm_synonym(name, map_column=map_column, descriptor=fn)
- return decorate
-
-def comparable_using(comparator_factory):
- """Decorator, allow a Python @property to be used in query criteria.
-
- This is a decorator front end to
- :func:`~sqlalchemy.orm.comparable_property` that passes
- through the comparator_factory and the function being decorated::
-
- @comparable_using(MyComparatorType)
- @property
- def prop(self):
- return 'special sauce'
-
- The regular ``comparable_property()`` is also usable directly in a
- declarative setting and may be convenient for read/write properties::
-
- prop = comparable_property(MyComparatorType)
-
- """
- def decorate(fn):
- return comparable_property(comparator_factory, fn)
- return decorate
-
-class declared_attr(property):
- """Mark a class-level method as representing the definition of
- a mapped property or special declarative member name.
-
- .. versionchanged:: 0.6.{2,3,4}
- ``@declared_attr`` is available as
- ``sqlalchemy.util.classproperty`` for SQLAlchemy versions
- 0.6.2, 0.6.3, 0.6.4.
-
- @declared_attr turns the attribute into a scalar-like
- property that can be invoked from the uninstantiated class.
- Declarative treats attributes specifically marked with
- @declared_attr as returning a construct that is specific
- to mapping or declarative table configuration. The name
- of the attribute is that of what the non-dynamic version
- of the attribute would be.
-
- @declared_attr is more often than not applicable to mixins,
- to define relationships that are to be applied to different
- implementors of the class::
-
- class ProvidesUser(object):
- "A mixin that adds a 'user' relationship to classes."
-
- @declared_attr
- def user(self):
- return relationship("User")
-
- It also can be applied to mapped classes, such as to provide
- a "polymorphic" scheme for inheritance::
-
- class Employee(Base):
- id = Column(Integer, primary_key=True)
- type = Column(String(50), nullable=False)
-
- @declared_attr
- def __tablename__(cls):
- return cls.__name__.lower()
- @declared_attr
- def __mapper_args__(cls):
- if cls.__name__ == 'Employee':
- return {
- "polymorphic_on":cls.type,
- "polymorphic_identity":"Employee"
- }
- else:
- return {"polymorphic_identity":cls.__name__}
-
- """
-
- def __init__(self, fget, *arg, **kw):
- super(declared_attr, self).__init__(fget, *arg, **kw)
- self.__doc__ = fget.__doc__
-
- def __get__(desc, self, cls):
- return desc.fget(cls)
-
-def _declarative_constructor(self, **kwargs):
- """A simple constructor that allows initialization from kwargs.
-
- Sets attributes on the constructed instance using the names and
- values in ``kwargs``.
-
- Only keys that are present as
- attributes of the instance's class are allowed. These could be,
- for example, any mapped columns or relationships.
- """
- cls_ = type(self)
- for k in kwargs:
- if not hasattr(cls_, k):
- raise TypeError(
- "%r is an invalid keyword argument for %s" %
- (k, cls_.__name__))
- setattr(self, k, kwargs[k])
-_declarative_constructor.__name__ = '__init__'
-
-def declarative_base(bind=None, metadata=None, mapper=None, cls=object,
- name='Base', constructor=_declarative_constructor,
- class_registry=None,
- metaclass=DeclarativeMeta):
- """Construct a base class for declarative class definitions.
-
- The new base class will be given a metaclass that produces
- appropriate :class:`~sqlalchemy.schema.Table` objects and makes
- the appropriate :func:`~sqlalchemy.orm.mapper` calls based on the
- information provided declaratively in the class and any subclasses
- of the class.
-
- :param bind: An optional
- :class:`~sqlalchemy.engine.base.Connectable`, will be assigned
- the ``bind`` attribute on the :class:`~sqlalchemy.MetaData`
- instance.
-
- :param metadata:
- An optional :class:`~sqlalchemy.MetaData` instance. All
- :class:`~sqlalchemy.schema.Table` objects implicitly declared by
- subclasses of the base will share this MetaData. A MetaData instance
- will be created if none is provided. The
- :class:`~sqlalchemy.MetaData` instance will be available via the
- `metadata` attribute of the generated declarative base class.
-
- :param mapper:
- An optional callable, defaults to :func:`~sqlalchemy.orm.mapper`. Will
- be used to map subclasses to their Tables.
-
- :param cls:
- Defaults to :class:`object`. A type to use as the base for the generated
- declarative base class. May be a class or tuple of classes.
-
- :param name:
- Defaults to ``Base``. The display name for the generated
- class. Customizing this is not required, but can improve clarity in
- tracebacks and debugging.
-
- :param constructor:
- Defaults to
- :func:`~sqlalchemy.ext.declarative._declarative_constructor`, an
- __init__ implementation that assigns \**kwargs for declared
- fields and relationships to an instance. If ``None`` is supplied,
- no __init__ will be provided and construction will fall back to
- cls.__init__ by way of the normal Python semantics.
-
- :param class_registry: optional dictionary that will serve as the
- registry of class names-> mapped classes when string names
- are used to identify classes inside of :func:`.relationship`
- and others. Allows two or more declarative base classes
- to share the same registry of class names for simplified
- inter-base relationships.
-
- :param metaclass:
- Defaults to :class:`.DeclarativeMeta`. A metaclass or __metaclass__
- compatible callable to use as the meta type of the generated
- declarative base class.
-
- """
- lcl_metadata = metadata or MetaData()
- if bind:
- lcl_metadata.bind = bind
-
- if class_registry is None:
- class_registry = weakref.WeakValueDictionary()
-
- bases = not isinstance(cls, tuple) and (cls,) or cls
- class_dict = dict(_decl_class_registry=class_registry,
- metadata=lcl_metadata)
-
- if constructor:
- class_dict['__init__'] = constructor
- if mapper:
- class_dict['__mapper_cls__'] = mapper
-
- return metaclass(name, bases, class_dict)
-
-def _undefer_column_name(key, column):
- if column.key is None:
- column.key = key
- if column.name is None:
- column.name = key
-
-class ConcreteBase(object):
- """A helper class for 'concrete' declarative mappings.
-
- :class:`.ConcreteBase` will use the :func:`.polymorphic_union`
- function automatically, against all tables mapped as a subclass
- to this class. The function is called via the
- ``__declare_last__()`` function, which is essentially
- a hook for the :func:`.MapperEvents.after_configured` event.
-
- :class:`.ConcreteBase` produces a mapped
- table for the class itself. Compare to :class:`.AbstractConcreteBase`,
- which does not.
-
- Example::
-
- from sqlalchemy.ext.declarative import ConcreteBase
-
- class Employee(ConcreteBase, Base):
- __tablename__ = 'employee'
- employee_id = Column(Integer, primary_key=True)
- name = Column(String(50))
- __mapper_args__ = {
- 'polymorphic_identity':'employee',
- 'concrete':True}
-
- class Manager(Employee):
- __tablename__ = 'manager'
- employee_id = Column(Integer, primary_key=True)
- name = Column(String(50))
- manager_data = Column(String(40))
- __mapper_args__ = {
- 'polymorphic_identity':'manager',
- 'concrete':True}
-
- """
-
- @classmethod
- def _create_polymorphic_union(cls, mappers):
- return polymorphic_union(dict(
- (mapper.polymorphic_identity, mapper.local_table)
- for mapper in mappers
- ), 'type', 'pjoin')
-
- @classmethod
- def __declare_last__(cls):
- m = cls.__mapper__
- if m.with_polymorphic:
- return
-
- mappers = list(m.self_and_descendants)
- pjoin = cls._create_polymorphic_union(mappers)
- m._set_with_polymorphic(("*", pjoin))
- m._set_polymorphic_on(pjoin.c.type)
-
-class AbstractConcreteBase(ConcreteBase):
- """A helper class for 'concrete' declarative mappings.
-
- :class:`.AbstractConcreteBase` will use the :func:`.polymorphic_union`
- function automatically, against all tables mapped as a subclass
- to this class. The function is called via the
- ``__declare_last__()`` function, which is essentially
- a hook for the :func:`.MapperEvents.after_configured` event.
-
- :class:`.AbstractConcreteBase` does not produce a mapped
- table for the class itself. Compare to :class:`.ConcreteBase`,
- which does.
-
- Example::
-
- from sqlalchemy.ext.declarative import ConcreteBase
-
- class Employee(AbstractConcreteBase, Base):
- pass
-
- class Manager(Employee):
- __tablename__ = 'manager'
- employee_id = Column(Integer, primary_key=True)
- name = Column(String(50))
- manager_data = Column(String(40))
- __mapper_args__ = {
- 'polymorphic_identity':'manager',
- 'concrete':True}
-
- """
-
- __abstract__ = True
-
- @classmethod
- def __declare_last__(cls):
- if hasattr(cls, '__mapper__'):
- return
-
- # can't rely on 'self_and_descendants' here
- # since technically an immediate subclass
- # might not be mapped, but a subclass
- # may be.
- mappers = []
- stack = list(cls.__subclasses__())
- while stack:
- klass = stack.pop()
- stack.extend(klass.__subclasses__())
- mn = _mapper_or_none(klass)
- if mn is not None:
- mappers.append(mn)
- pjoin = cls._create_polymorphic_union(mappers)
- cls.__mapper__ = m = mapper(cls, pjoin, polymorphic_on=pjoin.c.type)
-
- for scls in cls.__subclasses__():
- sm = _mapper_or_none(scls)
- if sm.concrete and cls in scls.__bases__:
- sm._set_concrete_base(m)
-
-
-class DeferredReflection(object):
- """A helper class for construction of mappings based on
- a deferred reflection step.
-
- Normally, declarative can be used with reflection by
- setting a :class:`.Table` object using autoload=True
- as the ``__table__`` attribute on a declarative class.
- The caveat is that the :class:`.Table` must be fully
- reflected, or at the very least have a primary key column,
- at the point at which a normal declarative mapping is
- constructed, meaning the :class:`.Engine` must be available
- at class declaration time.
-
- The :class:`.DeferredReflection` mixin moves the construction
- of mappers to be at a later point, after a specific
- method is called which first reflects all :class:`.Table`
- objects created so far. Classes can define it as such::
-
- from sqlalchemy.ext.declarative import declarative_base, DeferredReflection
- Base = declarative_base()
-
- class MyClass(DeferredReflection, Base):
- __tablename__ = 'mytable'
-
- Above, ``MyClass`` is not yet mapped. After a series of
- classes have been defined in the above fashion, all tables
- can be reflected and mappings created using :meth:`.DeferredReflection.prepare`::
-
- engine = create_engine("someengine://...")
- DeferredReflection.prepare(engine)
-
- The :class:`.DeferredReflection` mixin can be applied to individual
- classes, used as the base for the declarative base itself,
- or used in a custom abstract class. Using an abstract base
- allows that only a subset of classes to be prepared for a
- particular prepare step, which is necessary for applications
- that use more than one engine. For example, if an application
- has two engines, you might use two bases, and prepare each
- separately, e.g.::
-
- class ReflectedOne(DeferredReflection, Base):
- __abstract__ = True
-
- class ReflectedTwo(DeferredReflection, Base):
- __abstract__ = True
-
- class MyClass(ReflectedOne):
- __tablename__ = 'mytable'
-
- class MyOtherClass(ReflectedOne):
- __tablename__ = 'myothertable'
-
- class YetAnotherClass(ReflectedTwo):
- __tablename__ = 'yetanothertable'
-
- # ... etc.
-
- Above, the class hierarchies for ``ReflectedOne`` and
- ``ReflectedTwo`` can be configured separately::
-
- ReflectedOne.prepare(engine_one)
- ReflectedTwo.prepare(engine_two)
-
- .. versionadded:: 0.8
-
- """
- @classmethod
- def prepare(cls, engine):
- """Reflect all :class:`.Table` objects for all current
- :class:`.DeferredReflection` subclasses"""
- to_map = [m for m in _MapperConfig.configs.values()
- if issubclass(m.cls, cls)]
- for thingy in to_map:
- cls._sa_decl_prepare(thingy.local_table, engine)
- thingy.map()
-
- @classmethod
- def _sa_decl_prepare(cls, local_table, engine):
- # autoload Table, which is already
- # present in the metadata. This
- # will fill in db-loaded columns
- # into the existing Table object.
- if local_table is not None:
- Table(local_table.name,
- local_table.metadata,
- extend_existing=True,
- autoload_replace=False,
- autoload=True,
- autoload_with=engine,
- schema=local_table.schema)
+__all__ = ['declarative_base', 'synonym_for', 'has_inherited_table',
+ 'comparable_using', 'instrument_declarative', 'declared_attr',
+ 'ConcreteBase', 'AbstractConcreteBase', 'DeclarativeMeta',
+ 'DeferredReflection']
diff --git a/lib/sqlalchemy/ext/declarative/api.py b/lib/sqlalchemy/ext/declarative/api.py
new file mode 100644
index 000000000..80934c194
--- /dev/null
+++ b/lib/sqlalchemy/ext/declarative/api.py
@@ -0,0 +1,436 @@
+# ext/declarative/api.py
+# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+"""Public API functions and helpers for declarative."""
+
+
+from ...schema import Table, MetaData
+from ...orm import synonym as _orm_synonym, mapper,\
+ comparable_property
+from ...orm.util import polymorphic_union, _mapper_or_none
+from ... import exc
+import weakref
+
+from .base import _as_declarative, \
+ _declarative_constructor,\
+ _MapperConfig, _add_attribute
+
+
+def instrument_declarative(cls, registry, metadata):
+ """Given a class, configure the class declaratively,
+ using the given registry, which can be any dictionary, and
+ MetaData object.
+
+ """
+ if '_decl_class_registry' in cls.__dict__:
+ raise exc.InvalidRequestError(
+ "Class %r already has been "
+ "instrumented declaratively" % cls)
+ cls._decl_class_registry = registry
+ cls.metadata = metadata
+ _as_declarative(cls, cls.__name__, cls.__dict__)
+
+def has_inherited_table(cls):
+ """Given a class, return True if any of the classes it inherits from has a
+ mapped table, otherwise return False.
+ """
+ for class_ in cls.__mro__:
+ if getattr(class_, '__table__', None) is not None:
+ return True
+ return False
+
+class DeclarativeMeta(type):
+ def __init__(cls, classname, bases, dict_):
+ if '_decl_class_registry' in cls.__dict__:
+ return type.__init__(cls, classname, bases, dict_)
+ else:
+ _as_declarative(cls, classname, cls.__dict__)
+ return type.__init__(cls, classname, bases, dict_)
+
+ def __setattr__(cls, key, value):
+ _add_attribute(cls, key, value)
+
+def synonym_for(name, map_column=False):
+ """Decorator, make a Python @property a query synonym for a column.
+
+ A decorator version of :func:`~sqlalchemy.orm.synonym`. The function being
+ decorated is the 'descriptor', otherwise passes its arguments through to
+ synonym()::
+
+ @synonym_for('col')
+ @property
+ def prop(self):
+ return 'special sauce'
+
+ The regular ``synonym()`` is also usable directly in a declarative setting
+ and may be convenient for read/write properties::
+
+ prop = synonym('col', descriptor=property(_read_prop, _write_prop))
+
+ """
+ def decorate(fn):
+ return _orm_synonym(name, map_column=map_column, descriptor=fn)
+ return decorate
+
+def comparable_using(comparator_factory):
+ """Decorator, allow a Python @property to be used in query criteria.
+
+ This is a decorator front end to
+ :func:`~sqlalchemy.orm.comparable_property` that passes
+ through the comparator_factory and the function being decorated::
+
+ @comparable_using(MyComparatorType)
+ @property
+ def prop(self):
+ return 'special sauce'
+
+ The regular ``comparable_property()`` is also usable directly in a
+ declarative setting and may be convenient for read/write properties::
+
+ prop = comparable_property(MyComparatorType)
+
+ """
+ def decorate(fn):
+ return comparable_property(comparator_factory, fn)
+ return decorate
+
+class declared_attr(property):
+ """Mark a class-level method as representing the definition of
+ a mapped property or special declarative member name.
+
+ .. versionchanged:: 0.6.{2,3,4}
+ ``@declared_attr`` is available as
+ ``sqlalchemy.util.classproperty`` for SQLAlchemy versions
+ 0.6.2, 0.6.3, 0.6.4.
+
+ @declared_attr turns the attribute into a scalar-like
+ property that can be invoked from the uninstantiated class.
+ Declarative treats attributes specifically marked with
+ @declared_attr as returning a construct that is specific
+ to mapping or declarative table configuration. The name
+ of the attribute is that of what the non-dynamic version
+ of the attribute would be.
+
+ @declared_attr is more often than not applicable to mixins,
+ to define relationships that are to be applied to different
+ implementors of the class::
+
+ class ProvidesUser(object):
+ "A mixin that adds a 'user' relationship to classes."
+
+ @declared_attr
+ def user(self):
+ return relationship("User")
+
+ It also can be applied to mapped classes, such as to provide
+ a "polymorphic" scheme for inheritance::
+
+ class Employee(Base):
+ id = Column(Integer, primary_key=True)
+ type = Column(String(50), nullable=False)
+
+ @declared_attr
+ def __tablename__(cls):
+ return cls.__name__.lower()
+
+ @declared_attr
+ def __mapper_args__(cls):
+ if cls.__name__ == 'Employee':
+ return {
+ "polymorphic_on":cls.type,
+ "polymorphic_identity":"Employee"
+ }
+ else:
+ return {"polymorphic_identity":cls.__name__}
+
+ """
+
+ def __init__(self, fget, *arg, **kw):
+ super(declared_attr, self).__init__(fget, *arg, **kw)
+ self.__doc__ = fget.__doc__
+
+ def __get__(desc, self, cls):
+ return desc.fget(cls)
+
+def declarative_base(bind=None, metadata=None, mapper=None, cls=object,
+ name='Base', constructor=_declarative_constructor,
+ class_registry=None,
+ metaclass=DeclarativeMeta):
+ """Construct a base class for declarative class definitions.
+
+ The new base class will be given a metaclass that produces
+ appropriate :class:`~sqlalchemy.schema.Table` objects and makes
+ the appropriate :func:`~sqlalchemy.orm.mapper` calls based on the
+ information provided declaratively in the class and any subclasses
+ of the class.
+
+ :param bind: An optional
+ :class:`~sqlalchemy.engine.base.Connectable`, will be assigned
+ the ``bind`` attribute on the :class:`~sqlalchemy.MetaData`
+ instance.
+
+ :param metadata:
+ An optional :class:`~sqlalchemy.MetaData` instance. All
+ :class:`~sqlalchemy.schema.Table` objects implicitly declared by
+ subclasses of the base will share this MetaData. A MetaData instance
+ will be created if none is provided. The
+ :class:`~sqlalchemy.MetaData` instance will be available via the
+ `metadata` attribute of the generated declarative base class.
+
+ :param mapper:
+ An optional callable, defaults to :func:`~sqlalchemy.orm.mapper`. Will
+ be used to map subclasses to their Tables.
+
+ :param cls:
+ Defaults to :class:`object`. A type to use as the base for the generated
+ declarative base class. May be a class or tuple of classes.
+
+ :param name:
+ Defaults to ``Base``. The display name for the generated
+ class. Customizing this is not required, but can improve clarity in
+ tracebacks and debugging.
+
+ :param constructor:
+ Defaults to
+ :func:`~sqlalchemy.ext.declarative._declarative_constructor`, an
+ __init__ implementation that assigns \**kwargs for declared
+ fields and relationships to an instance. If ``None`` is supplied,
+ no __init__ will be provided and construction will fall back to
+ cls.__init__ by way of the normal Python semantics.
+
+ :param class_registry: optional dictionary that will serve as the
+ registry of class names-> mapped classes when string names
+ are used to identify classes inside of :func:`.relationship`
+ and others. Allows two or more declarative base classes
+ to share the same registry of class names for simplified
+ inter-base relationships.
+
+ :param metaclass:
+ Defaults to :class:`.DeclarativeMeta`. A metaclass or __metaclass__
+ compatible callable to use as the meta type of the generated
+ declarative base class.
+
+ """
+ lcl_metadata = metadata or MetaData()
+ if bind:
+ lcl_metadata.bind = bind
+
+ if class_registry is None:
+ class_registry = weakref.WeakValueDictionary()
+
+ bases = not isinstance(cls, tuple) and (cls,) or cls
+ class_dict = dict(_decl_class_registry=class_registry,
+ metadata=lcl_metadata)
+
+ if constructor:
+ class_dict['__init__'] = constructor
+ if mapper:
+ class_dict['__mapper_cls__'] = mapper
+
+ return metaclass(name, bases, class_dict)
+
+class ConcreteBase(object):
+ """A helper class for 'concrete' declarative mappings.
+
+ :class:`.ConcreteBase` will use the :func:`.polymorphic_union`
+ function automatically, against all tables mapped as a subclass
+ to this class. The function is called via the
+ ``__declare_last__()`` function, which is essentially
+ a hook for the :func:`.MapperEvents.after_configured` event.
+
+ :class:`.ConcreteBase` produces a mapped
+ table for the class itself. Compare to :class:`.AbstractConcreteBase`,
+ which does not.
+
+ Example::
+
+ from sqlalchemy.ext.declarative import ConcreteBase
+
+ class Employee(ConcreteBase, Base):
+ __tablename__ = 'employee'
+ employee_id = Column(Integer, primary_key=True)
+ name = Column(String(50))
+ __mapper_args__ = {
+ 'polymorphic_identity':'employee',
+ 'concrete':True}
+
+ class Manager(Employee):
+ __tablename__ = 'manager'
+ employee_id = Column(Integer, primary_key=True)
+ name = Column(String(50))
+ manager_data = Column(String(40))
+ __mapper_args__ = {
+ 'polymorphic_identity':'manager',
+ 'concrete':True}
+
+ """
+
+ @classmethod
+ def _create_polymorphic_union(cls, mappers):
+ return polymorphic_union(dict(
+ (mp.polymorphic_identity, mp.local_table)
+ for mp in mappers
+ ), 'type', 'pjoin')
+
+ @classmethod
+ def __declare_last__(cls):
+ m = cls.__mapper__
+ if m.with_polymorphic:
+ return
+
+ mappers = list(m.self_and_descendants)
+ pjoin = cls._create_polymorphic_union(mappers)
+ m._set_with_polymorphic(("*", pjoin))
+ m._set_polymorphic_on(pjoin.c.type)
+
+class AbstractConcreteBase(ConcreteBase):
+ """A helper class for 'concrete' declarative mappings.
+
+ :class:`.AbstractConcreteBase` will use the :func:`.polymorphic_union`
+ function automatically, against all tables mapped as a subclass
+ to this class. The function is called via the
+ ``__declare_last__()`` function, which is essentially
+ a hook for the :func:`.MapperEvents.after_configured` event.
+
+ :class:`.AbstractConcreteBase` does not produce a mapped
+ table for the class itself. Compare to :class:`.ConcreteBase`,
+ which does.
+
+ Example::
+
+ from sqlalchemy.ext.declarative import ConcreteBase
+
+ class Employee(AbstractConcreteBase, Base):
+ pass
+
+ class Manager(Employee):
+ __tablename__ = 'manager'
+ employee_id = Column(Integer, primary_key=True)
+ name = Column(String(50))
+ manager_data = Column(String(40))
+ __mapper_args__ = {
+ 'polymorphic_identity':'manager',
+ 'concrete':True}
+
+ """
+
+ __abstract__ = True
+
+ @classmethod
+ def __declare_last__(cls):
+ if hasattr(cls, '__mapper__'):
+ return
+
+ # can't rely on 'self_and_descendants' here
+ # since technically an immediate subclass
+ # might not be mapped, but a subclass
+ # may be.
+ mappers = []
+ stack = list(cls.__subclasses__())
+ while stack:
+ klass = stack.pop()
+ stack.extend(klass.__subclasses__())
+ mn = _mapper_or_none(klass)
+ if mn is not None:
+ mappers.append(mn)
+ pjoin = cls._create_polymorphic_union(mappers)
+ cls.__mapper__ = m = mapper(cls, pjoin, polymorphic_on=pjoin.c.type)
+
+ for scls in cls.__subclasses__():
+ sm = _mapper_or_none(scls)
+ if sm.concrete and cls in scls.__bases__:
+ sm._set_concrete_base(m)
+
+
+class DeferredReflection(object):
+ """A helper class for construction of mappings based on
+ a deferred reflection step.
+
+ Normally, declarative can be used with reflection by
+ setting a :class:`.Table` object using autoload=True
+ as the ``__table__`` attribute on a declarative class.
+ The caveat is that the :class:`.Table` must be fully
+ reflected, or at the very least have a primary key column,
+ at the point at which a normal declarative mapping is
+ constructed, meaning the :class:`.Engine` must be available
+ at class declaration time.
+
+ The :class:`.DeferredReflection` mixin moves the construction
+ of mappers to be at a later point, after a specific
+ method is called which first reflects all :class:`.Table`
+ objects created so far. Classes can define it as such::
+
+ from sqlalchemy.ext.declarative import declarative_base, DeferredReflection
+ Base = declarative_base()
+
+ class MyClass(DeferredReflection, Base):
+ __tablename__ = 'mytable'
+
+ Above, ``MyClass`` is not yet mapped. After a series of
+ classes have been defined in the above fashion, all tables
+ can be reflected and mappings created using :meth:`.DeferredReflection.prepare`::
+
+ engine = create_engine("someengine://...")
+ DeferredReflection.prepare(engine)
+
+ The :class:`.DeferredReflection` mixin can be applied to individual
+ classes, used as the base for the declarative base itself,
+ or used in a custom abstract class. Using an abstract base
+ allows that only a subset of classes to be prepared for a
+ particular prepare step, which is necessary for applications
+ that use more than one engine. For example, if an application
+ has two engines, you might use two bases, and prepare each
+ separately, e.g.::
+
+ class ReflectedOne(DeferredReflection, Base):
+ __abstract__ = True
+
+ class ReflectedTwo(DeferredReflection, Base):
+ __abstract__ = True
+
+ class MyClass(ReflectedOne):
+ __tablename__ = 'mytable'
+
+ class MyOtherClass(ReflectedOne):
+ __tablename__ = 'myothertable'
+
+ class YetAnotherClass(ReflectedTwo):
+ __tablename__ = 'yetanothertable'
+
+ # ... etc.
+
+ Above, the class hierarchies for ``ReflectedOne`` and
+ ``ReflectedTwo`` can be configured separately::
+
+ ReflectedOne.prepare(engine_one)
+ ReflectedTwo.prepare(engine_two)
+
+ .. versionadded:: 0.8
+
+ """
+ @classmethod
+ def prepare(cls, engine):
+ """Reflect all :class:`.Table` objects for all current
+ :class:`.DeferredReflection` subclasses"""
+ to_map = [m for m in _MapperConfig.configs.values()
+ if issubclass(m.cls, cls)]
+ for thingy in to_map:
+ cls._sa_decl_prepare(thingy.local_table, engine)
+ thingy.map()
+
+ @classmethod
+ def _sa_decl_prepare(cls, local_table, engine):
+ # autoload Table, which is already
+ # present in the metadata. This
+ # will fill in db-loaded columns
+ # into the existing Table object.
+ if local_table is not None:
+ Table(local_table.name,
+ local_table.metadata,
+ extend_existing=True,
+ autoload_replace=False,
+ autoload=True,
+ autoload_with=engine,
+ schema=local_table.schema)
diff --git a/lib/sqlalchemy/ext/declarative/base.py b/lib/sqlalchemy/ext/declarative/base.py
new file mode 100644
index 000000000..100a68678
--- /dev/null
+++ b/lib/sqlalchemy/ext/declarative/base.py
@@ -0,0 +1,418 @@
+# ext/declarative/base.py
+# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+"""Internal implementation for declarative."""
+
+from ...schema import Table, Column
+from ...orm import mapper, class_mapper
+from ...orm.interfaces import MapperProperty
+from ...orm.properties import ColumnProperty, CompositeProperty
+from ...orm.util import _is_mapped_class
+from ... import util, exc
+from ...sql import expression
+from ... import event
+from . import clsregistry
+
+def _declared_mapping_info(cls):
+ # deferred mapping
+ if cls in _MapperConfig.configs:
+ return _MapperConfig.configs[cls]
+ # regular mapping
+ elif _is_mapped_class(cls):
+ return class_mapper(cls, configure=False)
+ else:
+ return None
+
+
+def _as_declarative(cls, classname, dict_):
+ from .api import declared_attr
+
+ # dict_ will be a dictproxy, which we can't write to, and we need to!
+ dict_ = dict(dict_)
+
+ column_copies = {}
+ potential_columns = {}
+
+ mapper_args_fn = None
+ table_args = inherited_table_args = None
+ tablename = None
+ parent_columns = ()
+
+ declarative_props = (declared_attr, util.classproperty)
+
+ for base in cls.__mro__:
+ _is_declarative_inherits = hasattr(base, '_decl_class_registry')
+
+ if '__declare_last__' in base.__dict__:
+ @event.listens_for(mapper, "after_configured")
+ def go():
+ cls.__declare_last__()
+ if '__abstract__' in base.__dict__:
+ if (base is cls or
+ (base in cls.__bases__ and not _is_declarative_inherits)
+ ):
+ return
+
+ class_mapped = _declared_mapping_info(base) is not None
+ if class_mapped:
+ parent_columns = base.__table__.c.keys()
+
+ for name, obj in vars(base).items():
+ if name == '__mapper_args__':
+ if not mapper_args_fn and (
+ not class_mapped or
+ isinstance(obj, declarative_props)
+ ):
+ # don't even invoke __mapper_args__ until
+ # after we've determined everything about the
+ # mapped table.
+ mapper_args_fn = lambda: cls.__mapper_args__
+ elif name == '__tablename__':
+ if not tablename and (
+ not class_mapped or
+ isinstance(obj, declarative_props)
+ ):
+ tablename = cls.__tablename__
+ elif name == '__table_args__':
+ if not table_args and (
+ not class_mapped or
+ isinstance(obj, declarative_props)
+ ):
+ table_args = cls.__table_args__
+ if not isinstance(table_args, (tuple, dict, type(None))):
+ raise exc.ArgumentError(
+ "__table_args__ value must be a tuple, "
+ "dict, or None")
+ if base is not cls:
+ inherited_table_args = True
+ elif class_mapped:
+ if isinstance(obj, declarative_props):
+ util.warn("Regular (i.e. not __special__) "
+ "attribute '%s.%s' uses @declared_attr, "
+ "but owning class %s is mapped - "
+ "not applying to subclass %s."
+ % (base.__name__, name, base, cls))
+ continue
+ elif base is not cls:
+ # we're a mixin.
+ if isinstance(obj, Column):
+ if obj.foreign_keys:
+ raise exc.InvalidRequestError(
+ "Columns with foreign keys to other columns "
+ "must be declared as @declared_attr callables "
+ "on declarative mixin classes. ")
+ if name not in dict_ and not (
+ '__table__' in dict_ and
+ (obj.name or name) in dict_['__table__'].c
+ ) and name not in potential_columns:
+ potential_columns[name] = \
+ column_copies[obj] = \
+ obj.copy()
+ column_copies[obj]._creation_order = \
+ obj._creation_order
+ elif isinstance(obj, MapperProperty):
+ raise exc.InvalidRequestError(
+ "Mapper properties (i.e. deferred,"
+ "column_property(), relationship(), etc.) must "
+ "be declared as @declared_attr callables "
+ "on declarative mixin classes.")
+ elif isinstance(obj, declarative_props):
+ dict_[name] = ret = \
+ column_copies[obj] = getattr(cls, name)
+ if isinstance(ret, (Column, MapperProperty)) and \
+ ret.doc is None:
+ ret.doc = obj.__doc__
+
+ # apply inherited columns as we should
+ for k, v in potential_columns.items():
+ if tablename or (v.name or k) not in parent_columns:
+ dict_[k] = v
+
+ if inherited_table_args and not tablename:
+ table_args = None
+
+ clsregistry.add_class(classname, cls)
+ our_stuff = util.OrderedDict()
+
+ for k in dict_:
+
+ # TODO: improve this ? all dunders ?
+ if k in ('__table__', '__tablename__', '__mapper_args__'):
+ continue
+
+ value = dict_[k]
+ if isinstance(value, declarative_props):
+ value = getattr(cls, k)
+
+ if (isinstance(value, tuple) and len(value) == 1 and
+ isinstance(value[0], (Column, MapperProperty))):
+ util.warn("Ignoring declarative-like tuple value of attribute "
+ "%s: possibly a copy-and-paste error with a comma "
+ "left at the end of the line?" % k)
+ continue
+ if not isinstance(value, (Column, MapperProperty)):
+ continue
+ if k == 'metadata':
+ raise exc.InvalidRequestError(
+ "Attribute name 'metadata' is reserved "
+ "for the MetaData instance when using a "
+ "declarative base class."
+ )
+ prop = clsregistry._deferred_relationship(cls, value)
+ our_stuff[k] = prop
+
+ # set up attributes in the order they were created
+ our_stuff.sort(key=lambda key: our_stuff[key]._creation_order)
+
+ # extract columns from the class dict
+ declared_columns = set()
+ for key, c in our_stuff.iteritems():
+ if isinstance(c, (ColumnProperty, CompositeProperty)):
+ for col in c.columns:
+ if isinstance(col, Column) and \
+ col.table is None:
+ _undefer_column_name(key, col)
+ declared_columns.add(col)
+ elif isinstance(c, Column):
+ _undefer_column_name(key, c)
+ declared_columns.add(c)
+ # if the column is the same name as the key,
+ # remove it from the explicit properties dict.
+ # the normal rules for assigning column-based properties
+ # will take over, including precedence of columns
+ # in multi-column ColumnProperties.
+ if key == c.key:
+ del our_stuff[key]
+ declared_columns = sorted(declared_columns, key=lambda c: c._creation_order)
+ table = None
+
+ if hasattr(cls, '__table_cls__'):
+ table_cls = util.unbound_method_to_callable(cls.__table_cls__)
+ else:
+ table_cls = Table
+
+ if '__table__' not in dict_:
+ if tablename is not None:
+
+ args, table_kw = (), {}
+ if table_args:
+ if isinstance(table_args, dict):
+ table_kw = table_args
+ elif isinstance(table_args, tuple):
+ if isinstance(table_args[-1], dict):
+ args, table_kw = table_args[0:-1], table_args[-1]
+ else:
+ args = table_args
+
+ autoload = dict_.get('__autoload__')
+ if autoload:
+ table_kw['autoload'] = True
+
+ cls.__table__ = table = table_cls(tablename, cls.metadata,
+ *(tuple(declared_columns) + tuple(args)),
+ **table_kw)
+ else:
+ table = cls.__table__
+ if declared_columns:
+ for c in declared_columns:
+ if not table.c.contains_column(c):
+ raise exc.ArgumentError(
+ "Can't add additional column %r when "
+ "specifying __table__" % c.key
+ )
+
+ if hasattr(cls, '__mapper_cls__'):
+ mapper_cls = util.unbound_method_to_callable(cls.__mapper_cls__)
+ else:
+ mapper_cls = mapper
+
+ for c in cls.__bases__:
+ if _declared_mapping_info(c) is not None:
+ inherits = c
+ break
+ else:
+ inherits = None
+
+ if table is None and inherits is None:
+ raise exc.InvalidRequestError(
+ "Class %r does not have a __table__ or __tablename__ "
+ "specified and does not inherit from an existing "
+ "table-mapped class." % cls
+ )
+ elif inherits:
+ inherited_mapper = _declared_mapping_info(inherits)
+ inherited_table = inherited_mapper.local_table
+
+ if table is None:
+ # single table inheritance.
+ # ensure no table args
+ if table_args:
+ raise exc.ArgumentError(
+ "Can't place __table_args__ on an inherited class "
+ "with no table."
+ )
+
+ # add any columns declared here to the inherited table.
+ for c in declared_columns:
+ if c.primary_key:
+ raise exc.ArgumentError(
+ "Can't place primary key columns on an inherited "
+ "class with no table."
+ )
+ if c.name in inherited_table.c:
+ raise exc.ArgumentError(
+ "Column '%s' on class %s conflicts with "
+ "existing column '%s'" %
+ (c, cls, inherited_table.c[c.name])
+ )
+ inherited_table.append_column(c)
+
+ mt = _MapperConfig(mapper_cls,
+ cls, table,
+ inherits,
+ declared_columns,
+ column_copies,
+ our_stuff,
+ mapper_args_fn)
+ if not hasattr(cls, '_sa_decl_prepare'):
+ mt.map()
+
+class _MapperConfig(object):
+ configs = util.OrderedDict()
+
+ def __init__(self, mapper_cls,
+ cls,
+ table,
+ inherits,
+ declared_columns,
+ column_copies,
+ properties, mapper_args_fn):
+ self.mapper_cls = mapper_cls
+ self.cls = cls
+ self.local_table = table
+ self.inherits = inherits
+ self.properties = properties
+ self.mapper_args_fn = mapper_args_fn
+ self.declared_columns = declared_columns
+ self.column_copies = column_copies
+ self.configs[cls] = self
+
+ def _prepare_mapper_arguments(self):
+ properties = self.properties
+ if self.mapper_args_fn:
+ mapper_args = self.mapper_args_fn()
+ else:
+ mapper_args = {}
+
+ # make sure that column copies are used rather
+ # than the original columns from any mixins
+ for k in ('version_id_col', 'polymorphic_on',):
+ if k in mapper_args:
+ v = mapper_args[k]
+ mapper_args[k] = self.column_copies.get(v, v)
+
+ assert 'inherits' not in mapper_args, \
+ "Can't specify 'inherits' explicitly with declarative mappings"
+
+ if self.inherits:
+ mapper_args['inherits'] = self.inherits
+
+ if self.inherits and not mapper_args.get('concrete', False):
+ # single or joined inheritance
+ # exclude any cols on the inherited table which are
+ # not mapped on the parent class, to avoid
+ # mapping columns specific to sibling/nephew classes
+ inherited_mapper = _declared_mapping_info(self.inherits)
+ inherited_table = inherited_mapper.local_table
+
+ if 'exclude_properties' not in mapper_args:
+ mapper_args['exclude_properties'] = exclude_properties = \
+ set([c.key for c in inherited_table.c
+ if c not in inherited_mapper._columntoproperty])
+ exclude_properties.difference_update(
+ [c.key for c in self.declared_columns])
+
+ # look through columns in the current mapper that
+ # are keyed to a propname different than the colname
+ # (if names were the same, we'd have popped it out above,
+ # in which case the mapper makes this combination).
+ # See if the superclass has a similar column property.
+ # If so, join them together.
+ for k, col in properties.items():
+ if not isinstance(col, expression.ColumnElement):
+ continue
+ if k in inherited_mapper._props:
+ p = inherited_mapper._props[k]
+ if isinstance(p, ColumnProperty):
+ # note here we place the subclass column
+ # first. See [ticket:1892] for background.
+ properties[k] = [col] + p.columns
+
+ result_mapper_args = mapper_args.copy()
+ result_mapper_args['properties'] = properties
+ return result_mapper_args
+
+ def map(self):
+ self.configs.pop(self.cls, None)
+ mapper_args = self._prepare_mapper_arguments()
+ self.cls.__mapper__ = self.mapper_cls(
+ self.cls,
+ self.local_table,
+ **mapper_args
+ )
+
+def _add_attribute(cls, key, value):
+ """add an attribute to an existing declarative class.
+
+ This runs through the logic to determine MapperProperty,
+ adds it to the Mapper, adds a column to the mapped Table, etc.
+
+ """
+ if '__mapper__' in cls.__dict__:
+ if isinstance(value, Column):
+ _undefer_column_name(key, value)
+ cls.__table__.append_column(value)
+ cls.__mapper__.add_property(key, value)
+ elif isinstance(value, ColumnProperty):
+ for col in value.columns:
+ if isinstance(col, Column) and col.table is None:
+ _undefer_column_name(key, col)
+ cls.__table__.append_column(col)
+ cls.__mapper__.add_property(key, value)
+ elif isinstance(value, MapperProperty):
+ cls.__mapper__.add_property(
+ key,
+ clsregistry._deferred_relationship(cls, value)
+ )
+ else:
+ type.__setattr__(cls, key, value)
+ else:
+ type.__setattr__(cls, key, value)
+
+def _declarative_constructor(self, **kwargs):
+ """A simple constructor that allows initialization from kwargs.
+
+ Sets attributes on the constructed instance using the names and
+ values in ``kwargs``.
+
+ Only keys that are present as
+ attributes of the instance's class are allowed. These could be,
+ for example, any mapped columns or relationships.
+ """
+ cls_ = type(self)
+ for k in kwargs:
+ if not hasattr(cls_, k):
+ raise TypeError(
+ "%r is an invalid keyword argument for %s" %
+ (k, cls_.__name__))
+ setattr(self, k, kwargs[k])
+_declarative_constructor.__name__ = '__init__'
+
+
+def _undefer_column_name(key, column):
+ if column.key is None:
+ column.key = key
+ if column.name is None:
+ column.name = key
diff --git a/lib/sqlalchemy/ext/declarative/clsregistry.py b/lib/sqlalchemy/ext/declarative/clsregistry.py
new file mode 100644
index 000000000..08b487db3
--- /dev/null
+++ b/lib/sqlalchemy/ext/declarative/clsregistry.py
@@ -0,0 +1,244 @@
+# ext/declarative/clsregistry.py
+# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+"""Routines to handle the string class registry used by declarative.
+
+This system allows specification of classes and expressions used in
+:func:`.relationship` using strings.
+
+"""
+from ...orm.properties import ColumnProperty, RelationshipProperty
+from ...schema import _get_table_key
+from ...orm import class_mapper
+from ... import util
+from ... import exc
+import weakref
+
+# strong references to registries which we place in
+# the _decl_class_registry, which is usually weak referencing.
+# the internal registries here link to classes with weakrefs and remove
+# themselves when all references to contained classes are removed.
+_registries = set()
+
+def add_class(classname, cls):
+ """Add a class to the _decl_class_registry associated with the
+ given declarative class.
+
+ """
+ if classname in cls._decl_class_registry:
+ # class already exists.
+ existing = cls._decl_class_registry[classname]
+ if not isinstance(existing, _MultipleClassMarker):
+ existing = \
+ cls._decl_class_registry[classname] = \
+ _MultipleClassMarker([cls, existing])
+ else:
+ cls._decl_class_registry[classname] = cls
+
+ try:
+ module = cls._decl_class_registry['_sa_module_registry']
+ except KeyError:
+ cls._decl_class_registry['_sa_module_registry'] = \
+ module = _ModuleMarker('_sa_module_registry', None)
+ for token in cls.__module__.split("."):
+ module = module.get_module(token)
+
+ module.add_class(classname, cls)
+
+class _MultipleClassMarker(object):
+ """refers to multiple classes of the same name
+ within _decl_class_registry.
+
+ """
+
+ def __init__(self, classes):
+ self.contents = set([
+ weakref.ref(item, self._remove_item) for item in classes])
+ _registries.add(self)
+
+ def __iter__(self):
+ return (ref() for ref in self.contents)
+
+ def attempt_get(self, key):
+ if len(self.contents) > 1:
+ raise exc.InvalidRequestError(
+ "Multiple classes with the classname "
+ "%r are in the registry of this declarative "
+ "base. Please use a fully module-qualified path." % key)
+ else:
+ ref = list(self.contents)[0]
+ cls = ref()
+ if cls is None:
+ raise NameError(key)
+ return cls
+
+ def _remove_item(self, ref):
+ self.contents.remove(ref)
+ if not self.contents:
+ _registries.discard(self)
+
+ def add_item(self, item, base):
+ self.contents.add(weakref.ref(item, self._remove_item))
+
+class _ModuleMarker(object):
+ """"refers to a module name within
+ _decl_class_registry.
+
+ """
+ def __init__(self, name, parent):
+ self.parent = parent
+ self.name = name
+ self.contents = {}
+ self.mod_ns = _ModNS(self)
+ _registries.add(self)
+
+ def __contains__(self, name):
+ return name in self.contents
+
+ def __getitem__(self, name):
+ return self.contents[name]()
+
+ def _remove_item(self, name):
+ self.contents.pop(name, None)
+ if not self.contents and self.parent is not None:
+ self.parent._remove_item(self.name)
+ _registries.discard(self)
+
+ def resolve_attr(self, key):
+ return getattr(self.mod_ns, key)
+
+ def get_module(self, name):
+ if name not in self.contents:
+ marker = _ModuleMarker(name, self)
+ self.contents[name] = lambda: marker
+ else:
+ marker = self.contents[name]()
+ return marker
+
+ def add_class(self, name, cls):
+ if name in self.contents:
+ util.warn(
+ "This declarative base already contains a class with the "
+ "same class name and module name as %r, and will be replaced "
+ "in the string-lookup table." % cls)
+
+ self.contents[name] = weakref.ref(cls,
+ lambda ref: self._remove_item(name))
+
+
+class _ModNS(object):
+ def __init__(self, parent):
+ self.__parent = parent
+
+ def __getattr__(self, key):
+ try:
+ value = self.__parent.contents[key]
+ except KeyError:
+ pass
+ else:
+ value = value()
+ if value is not None:
+ if isinstance(value, _ModuleMarker):
+ return value.mod_ns
+ else:
+ return value
+ raise AttributeError("Module %r has no mapped classes "
+ "registered under the name %r" % (self.__parent.name, key))
+
+class _GetColumns(object):
+ def __init__(self, cls):
+ self.cls = cls
+
+ def __getattr__(self, key):
+ mp = class_mapper(self.cls, configure=False)
+ if mp:
+ if not mp.has_property(key):
+ raise exc.InvalidRequestError(
+ "Class %r does not have a mapped column named %r"
+ % (self.cls, key))
+
+ prop = mp.get_property(key)
+ if not isinstance(prop, ColumnProperty):
+ raise exc.InvalidRequestError(
+ "Property %r is not an instance of"
+ " ColumnProperty (i.e. does not correspond"
+ " directly to a Column)." % key)
+ return getattr(self.cls, key)
+
+class _GetTable(object):
+ def __init__(self, key, metadata):
+ self.key = key
+ self.metadata = metadata
+
+ def __getattr__(self, key):
+ return self.metadata.tables[
+ _get_table_key(key, self.key)
+ ]
+
+def _determine_container(key, value):
+ if isinstance(value, _MultipleClassMarker):
+ value = value.attempt_get(key)
+ return _GetColumns(value)
+
+def _resolver(cls, prop):
+ def resolve_arg(arg):
+ import sqlalchemy
+ from sqlalchemy.orm import foreign, remote
+
+ fallback = sqlalchemy.__dict__.copy()
+ fallback.update({'foreign': foreign, 'remote': remote})
+
+ def access_cls(key):
+ if key in cls._decl_class_registry:
+ return _determine_container(key, cls._decl_class_registry[key])
+ elif key in cls.metadata.tables:
+ return cls.metadata.tables[key]
+ elif key in cls.metadata._schemas:
+ return _GetTable(key, cls.metadata)
+ elif '_sa_module_registry' in cls._decl_class_registry and \
+ key in cls._decl_class_registry['_sa_module_registry']:
+ return cls._decl_class_registry['_sa_module_registry'].resolve_attr(key)
+ else:
+ return fallback[key]
+
+ d = util.PopulateDict(access_cls)
+ def return_cls():
+ try:
+ x = eval(arg, globals(), d)
+
+ if isinstance(x, _GetColumns):
+ return x.cls
+ else:
+ return x
+ except NameError, n:
+ raise exc.InvalidRequestError(
+ "When initializing mapper %s, expression %r failed to "
+ "locate a name (%r). If this is a class name, consider "
+ "adding this relationship() to the %r class after "
+ "both dependent classes have been defined." %
+ (prop.parent, arg, n.args[0], cls)
+ )
+ return return_cls
+ return resolve_arg
+
+def _deferred_relationship(cls, prop):
+
+ if isinstance(prop, RelationshipProperty):
+ resolve_arg = _resolver(cls, prop)
+
+ for attr in ('argument', 'order_by', 'primaryjoin', 'secondaryjoin',
+ 'secondary', '_user_defined_foreign_keys', 'remote_side'):
+ v = getattr(prop, attr)
+ if isinstance(v, basestring):
+ setattr(prop, attr, resolve_arg(v))
+
+ if prop.backref and isinstance(prop.backref, tuple):
+ key, kwargs = prop.backref
+ for attr in ('primaryjoin', 'secondaryjoin', 'secondary',
+ 'foreign_keys', 'remote_side', 'order_by'):
+ if attr in kwargs and isinstance(kwargs[attr], basestring):
+ kwargs[attr] = resolve_arg(kwargs[attr])
+
+ return prop