1
# ext/declarative/api.py
2
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
4
# This module is part of SQLAlchemy and is released under
5
# the MIT License: http://www.opensource.org/licenses/mit-license.php
6
"""Public API functions and helpers for declarative."""
9
from ...schema import Table, MetaData
10
from ...orm import synonym as _orm_synonym, mapper,\
13
from ...orm.util import polymorphic_union, _mapper_or_none
17
from .base import _as_declarative, \
18
_declarative_constructor,\
19
_MapperConfig, _add_attribute
22
def instrument_declarative(cls, registry, metadata):
23
"""Given a class, configure the class declaratively,
24
using the given registry, which can be any dictionary, and
28
if '_decl_class_registry' in cls.__dict__:
29
raise exc.InvalidRequestError(
30
"Class %r already has been "
31
"instrumented declaratively" % cls)
32
cls._decl_class_registry = registry
33
cls.metadata = metadata
34
_as_declarative(cls, cls.__name__, cls.__dict__)
37
def has_inherited_table(cls):
38
"""Given a class, return True if any of the classes it inherits from has a
39
mapped table, otherwise return False.
41
for class_ in cls.__mro__[1:]:
42
if getattr(class_, '__table__', None) is not None:
47
class DeclarativeMeta(type):
48
def __init__(cls, classname, bases, dict_):
49
if '_decl_class_registry' not in cls.__dict__:
50
_as_declarative(cls, classname, cls.__dict__)
51
type.__init__(cls, classname, bases, dict_)
53
def __setattr__(cls, key, value):
54
_add_attribute(cls, key, value)
57
def synonym_for(name, map_column=False):
58
"""Decorator, make a Python @property a query synonym for a column.
60
A decorator version of :func:`~sqlalchemy.orm.synonym`. The function being
61
decorated is the 'descriptor', otherwise passes its arguments through to
67
return 'special sauce'
69
The regular ``synonym()`` is also usable directly in a declarative setting
70
and may be convenient for read/write properties::
72
prop = synonym('col', descriptor=property(_read_prop, _write_prop))
76
return _orm_synonym(name, map_column=map_column, descriptor=fn)
80
def comparable_using(comparator_factory):
81
"""Decorator, allow a Python @property to be used in query criteria.
83
This is a decorator front end to
84
:func:`~sqlalchemy.orm.comparable_property` that passes
85
through the comparator_factory and the function being decorated::
87
@comparable_using(MyComparatorType)
90
return 'special sauce'
92
The regular ``comparable_property()`` is also usable directly in a
93
declarative setting and may be convenient for read/write properties::
95
prop = comparable_property(MyComparatorType)
99
return comparable_property(comparator_factory, fn)
103
class declared_attr(interfaces._MappedAttribute, property):
104
"""Mark a class-level method as representing the definition of
105
a mapped property or special declarative member name.
107
@declared_attr turns the attribute into a scalar-like
108
property that can be invoked from the uninstantiated class.
109
Declarative treats attributes specifically marked with
110
@declared_attr as returning a construct that is specific
111
to mapping or declarative table configuration. The name
112
of the attribute is that of what the non-dynamic version
113
of the attribute would be.
115
@declared_attr is more often than not applicable to mixins,
116
to define relationships that are to be applied to different
117
implementors of the class::
119
class ProvidesUser(object):
120
"A mixin that adds a 'user' relationship to classes."
124
return relationship("User")
126
It also can be applied to mapped classes, such as to provide
127
a "polymorphic" scheme for inheritance::
129
class Employee(Base):
130
id = Column(Integer, primary_key=True)
131
type = Column(String(50), nullable=False)
134
def __tablename__(cls):
135
return cls.__name__.lower()
138
def __mapper_args__(cls):
139
if cls.__name__ == 'Employee':
141
"polymorphic_on":cls.type,
142
"polymorphic_identity":"Employee"
145
return {"polymorphic_identity":cls.__name__}
147
.. versionchanged:: 0.8 :class:`.declared_attr` can be used with
148
non-ORM or extension attributes, such as user-defined attributes
149
or :func:`.association_proxy` objects, which will be assigned
150
to the class at class construction time.
155
def __init__(self, fget, *arg, **kw):
156
super(declared_attr, self).__init__(fget, *arg, **kw)
157
self.__doc__ = fget.__doc__
159
def __get__(desc, self, cls):
160
return desc.fget(cls)
163
def declarative_base(bind=None, metadata=None, mapper=None, cls=object,
164
name='Base', constructor=_declarative_constructor,
166
metaclass=DeclarativeMeta):
167
"""Construct a base class for declarative class definitions.
169
The new base class will be given a metaclass that produces
170
appropriate :class:`~sqlalchemy.schema.Table` objects and makes
171
the appropriate :func:`~sqlalchemy.orm.mapper` calls based on the
172
information provided declaratively in the class and any subclasses
175
:param bind: An optional
176
:class:`~sqlalchemy.engine.base.Connectable`, will be assigned
177
the ``bind`` attribute on the :class:`~sqlalchemy.MetaData`
181
An optional :class:`~sqlalchemy.MetaData` instance. All
182
:class:`~sqlalchemy.schema.Table` objects implicitly declared by
183
subclasses of the base will share this MetaData. A MetaData instance
184
will be created if none is provided. The
185
:class:`~sqlalchemy.MetaData` instance will be available via the
186
`metadata` attribute of the generated declarative base class.
189
An optional callable, defaults to :func:`~sqlalchemy.orm.mapper`. Will
190
be used to map subclasses to their Tables.
193
Defaults to :class:`object`. A type to use as the base for the generated
194
declarative base class. May be a class or tuple of classes.
197
Defaults to ``Base``. The display name for the generated
198
class. Customizing this is not required, but can improve clarity in
199
tracebacks and debugging.
203
:func:`~sqlalchemy.ext.declarative._declarative_constructor`, an
204
__init__ implementation that assigns \**kwargs for declared
205
fields and relationships to an instance. If ``None`` is supplied,
206
no __init__ will be provided and construction will fall back to
207
cls.__init__ by way of the normal Python semantics.
209
:param class_registry: optional dictionary that will serve as the
210
registry of class names-> mapped classes when string names
211
are used to identify classes inside of :func:`.relationship`
212
and others. Allows two or more declarative base classes
213
to share the same registry of class names for simplified
214
inter-base relationships.
217
Defaults to :class:`.DeclarativeMeta`. A metaclass or __metaclass__
218
compatible callable to use as the meta type of the generated
219
declarative base class.
222
lcl_metadata = metadata or MetaData()
224
lcl_metadata.bind = bind
226
if class_registry is None:
227
class_registry = weakref.WeakValueDictionary()
229
bases = not isinstance(cls, tuple) and (cls,) or cls
230
class_dict = dict(_decl_class_registry=class_registry,
231
metadata=lcl_metadata)
234
class_dict['__init__'] = constructor
236
class_dict['__mapper_cls__'] = mapper
238
return metaclass(name, bases, class_dict)
241
class ConcreteBase(object):
242
"""A helper class for 'concrete' declarative mappings.
244
:class:`.ConcreteBase` will use the :func:`.polymorphic_union`
245
function automatically, against all tables mapped as a subclass
246
to this class. The function is called via the
247
``__declare_last__()`` function, which is essentially
248
a hook for the :func:`.MapperEvents.after_configured` event.
250
:class:`.ConcreteBase` produces a mapped
251
table for the class itself. Compare to :class:`.AbstractConcreteBase`,
256
from sqlalchemy.ext.declarative import ConcreteBase
258
class Employee(ConcreteBase, Base):
259
__tablename__ = 'employee'
260
employee_id = Column(Integer, primary_key=True)
261
name = Column(String(50))
263
'polymorphic_identity':'employee',
266
class Manager(Employee):
267
__tablename__ = 'manager'
268
employee_id = Column(Integer, primary_key=True)
269
name = Column(String(50))
270
manager_data = Column(String(40))
272
'polymorphic_identity':'manager',
278
def _create_polymorphic_union(cls, mappers):
279
return polymorphic_union(dict(
280
(mp.polymorphic_identity, mp.local_table)
285
def __declare_last__(cls):
287
if m.with_polymorphic:
290
mappers = list(m.self_and_descendants)
291
pjoin = cls._create_polymorphic_union(mappers)
292
m._set_with_polymorphic(("*", pjoin))
293
m._set_polymorphic_on(pjoin.c.type)
296
class AbstractConcreteBase(ConcreteBase):
297
"""A helper class for 'concrete' declarative mappings.
299
:class:`.AbstractConcreteBase` will use the :func:`.polymorphic_union`
300
function automatically, against all tables mapped as a subclass
301
to this class. The function is called via the
302
``__declare_last__()`` function, which is essentially
303
a hook for the :func:`.MapperEvents.after_configured` event.
305
:class:`.AbstractConcreteBase` does not produce a mapped
306
table for the class itself. Compare to :class:`.ConcreteBase`,
311
from sqlalchemy.ext.declarative import AbstractConcreteBase
313
class Employee(AbstractConcreteBase, Base):
316
class Manager(Employee):
317
__tablename__ = 'manager'
318
employee_id = Column(Integer, primary_key=True)
319
name = Column(String(50))
320
manager_data = Column(String(40))
322
'polymorphic_identity':'manager',
330
def __declare_last__(cls):
331
if hasattr(cls, '__mapper__'):
334
# can't rely on 'self_and_descendants' here
335
# since technically an immediate subclass
336
# might not be mapped, but a subclass
339
stack = list(cls.__subclasses__())
342
stack.extend(klass.__subclasses__())
343
mn = _mapper_or_none(klass)
346
pjoin = cls._create_polymorphic_union(mappers)
347
cls.__mapper__ = m = mapper(cls, pjoin, polymorphic_on=pjoin.c.type)
349
for scls in cls.__subclasses__():
350
sm = _mapper_or_none(scls)
351
if sm.concrete and cls in scls.__bases__:
352
sm._set_concrete_base(m)
355
class DeferredReflection(object):
356
"""A helper class for construction of mappings based on
357
a deferred reflection step.
359
Normally, declarative can be used with reflection by
360
setting a :class:`.Table` object using autoload=True
361
as the ``__table__`` attribute on a declarative class.
362
The caveat is that the :class:`.Table` must be fully
363
reflected, or at the very least have a primary key column,
364
at the point at which a normal declarative mapping is
365
constructed, meaning the :class:`.Engine` must be available
366
at class declaration time.
368
The :class:`.DeferredReflection` mixin moves the construction
369
of mappers to be at a later point, after a specific
370
method is called which first reflects all :class:`.Table`
371
objects created so far. Classes can define it as such::
373
from sqlalchemy.ext.declarative import declarative_base
374
from sqlalchemy.ext.declarative import DeferredReflection
375
Base = declarative_base()
377
class MyClass(DeferredReflection, Base):
378
__tablename__ = 'mytable'
380
Above, ``MyClass`` is not yet mapped. After a series of
381
classes have been defined in the above fashion, all tables
382
can be reflected and mappings created using
383
:meth:`.DeferredReflection.prepare`::
385
engine = create_engine("someengine://...")
386
DeferredReflection.prepare(engine)
388
The :class:`.DeferredReflection` mixin can be applied to individual
389
classes, used as the base for the declarative base itself,
390
or used in a custom abstract class. Using an abstract base
391
allows that only a subset of classes to be prepared for a
392
particular prepare step, which is necessary for applications
393
that use more than one engine. For example, if an application
394
has two engines, you might use two bases, and prepare each
397
class ReflectedOne(DeferredReflection, Base):
400
class ReflectedTwo(DeferredReflection, Base):
403
class MyClass(ReflectedOne):
404
__tablename__ = 'mytable'
406
class MyOtherClass(ReflectedOne):
407
__tablename__ = 'myothertable'
409
class YetAnotherClass(ReflectedTwo):
410
__tablename__ = 'yetanothertable'
414
Above, the class hierarchies for ``ReflectedOne`` and
415
``ReflectedTwo`` can be configured separately::
417
ReflectedOne.prepare(engine_one)
418
ReflectedTwo.prepare(engine_two)
420
.. versionadded:: 0.8
424
def prepare(cls, engine):
425
"""Reflect all :class:`.Table` objects for all current
426
:class:`.DeferredReflection` subclasses"""
427
to_map = [m for m in _MapperConfig.configs.values()
428
if issubclass(m.cls, cls)]
429
for thingy in to_map:
430
cls._sa_decl_prepare(thingy.local_table, engine)
434
def _sa_decl_prepare(cls, local_table, engine):
435
# autoload Table, which is already
436
# present in the metadata. This
437
# will fill in db-loaded columns
438
# into the existing Table object.
439
if local_table is not None:
440
Table(local_table.name,
441
local_table.metadata,
442
extend_existing=True,
443
autoload_replace=False,
445
autoload_with=engine,
446
schema=local_table.schema)