~ubuntu-branches/debian/jessie/sqlalchemy/jessie

« back to all changes in this revision

Viewing changes to lib/sqlalchemy/ext/declarative/api.py

  • Committer: Package Import Robot
  • Author(s): Piotr Ożarowski, Jakub Wilk, Piotr Ożarowski
  • Date: 2013-07-06 20:53:52 UTC
  • mfrom: (1.4.23) (16.1.17 experimental)
  • Revision ID: package-import@ubuntu.com-20130706205352-ryppl1eto3illd79
Tags: 0.8.2-1
[ Jakub Wilk ]
* Use canonical URIs for Vcs-* fields.

[ Piotr Ożarowski ]
* New upstream release
* Upload to unstable
* Build depend on python3-all instead of -dev, extensions are not built for
  Python 3.X 

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# ext/declarative/api.py
 
2
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
 
3
#
 
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."""
 
7
 
 
8
 
 
9
from ...schema import Table, MetaData
 
10
from ...orm import synonym as _orm_synonym, mapper,\
 
11
                                comparable_property,\
 
12
                                interfaces
 
13
from ...orm.util import polymorphic_union, _mapper_or_none
 
14
from ... import exc
 
15
import weakref
 
16
 
 
17
from .base import _as_declarative, \
 
18
                _declarative_constructor,\
 
19
                _MapperConfig, _add_attribute
 
20
 
 
21
 
 
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
 
25
    MetaData object.
 
26
 
 
27
    """
 
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__)
 
35
 
 
36
 
 
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.
 
40
    """
 
41
    for class_ in cls.__mro__[1:]:
 
42
        if getattr(class_, '__table__', None) is not None:
 
43
            return True
 
44
    return False
 
45
 
 
46
 
 
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_)
 
52
 
 
53
    def __setattr__(cls, key, value):
 
54
        _add_attribute(cls, key, value)
 
55
 
 
56
 
 
57
def synonym_for(name, map_column=False):
 
58
    """Decorator, make a Python @property a query synonym for a column.
 
59
 
 
60
    A decorator version of :func:`~sqlalchemy.orm.synonym`. The function being
 
61
    decorated is the 'descriptor', otherwise passes its arguments through to
 
62
    synonym()::
 
63
 
 
64
      @synonym_for('col')
 
65
      @property
 
66
      def prop(self):
 
67
          return 'special sauce'
 
68
 
 
69
    The regular ``synonym()`` is also usable directly in a declarative setting
 
70
    and may be convenient for read/write properties::
 
71
 
 
72
      prop = synonym('col', descriptor=property(_read_prop, _write_prop))
 
73
 
 
74
    """
 
75
    def decorate(fn):
 
76
        return _orm_synonym(name, map_column=map_column, descriptor=fn)
 
77
    return decorate
 
78
 
 
79
 
 
80
def comparable_using(comparator_factory):
 
81
    """Decorator, allow a Python @property to be used in query criteria.
 
82
 
 
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::
 
86
 
 
87
      @comparable_using(MyComparatorType)
 
88
      @property
 
89
      def prop(self):
 
90
          return 'special sauce'
 
91
 
 
92
    The regular ``comparable_property()`` is also usable directly in a
 
93
    declarative setting and may be convenient for read/write properties::
 
94
 
 
95
      prop = comparable_property(MyComparatorType)
 
96
 
 
97
    """
 
98
    def decorate(fn):
 
99
        return comparable_property(comparator_factory, fn)
 
100
    return decorate
 
101
 
 
102
 
 
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.
 
106
 
 
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.
 
114
 
 
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::
 
118
 
 
119
        class ProvidesUser(object):
 
120
            "A mixin that adds a 'user' relationship to classes."
 
121
 
 
122
            @declared_attr
 
123
            def user(self):
 
124
                return relationship("User")
 
125
 
 
126
    It also can be applied to mapped classes, such as to provide
 
127
    a "polymorphic" scheme for inheritance::
 
128
 
 
129
        class Employee(Base):
 
130
            id = Column(Integer, primary_key=True)
 
131
            type = Column(String(50), nullable=False)
 
132
 
 
133
            @declared_attr
 
134
            def __tablename__(cls):
 
135
                return cls.__name__.lower()
 
136
 
 
137
            @declared_attr
 
138
            def __mapper_args__(cls):
 
139
                if cls.__name__ == 'Employee':
 
140
                    return {
 
141
                            "polymorphic_on":cls.type,
 
142
                            "polymorphic_identity":"Employee"
 
143
                    }
 
144
                else:
 
145
                    return {"polymorphic_identity":cls.__name__}
 
146
 
 
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.
 
151
 
 
152
 
 
153
    """
 
154
 
 
155
    def __init__(self, fget, *arg, **kw):
 
156
        super(declared_attr, self).__init__(fget, *arg, **kw)
 
157
        self.__doc__ = fget.__doc__
 
158
 
 
159
    def __get__(desc, self, cls):
 
160
        return desc.fget(cls)
 
161
 
 
162
 
 
163
def declarative_base(bind=None, metadata=None, mapper=None, cls=object,
 
164
                     name='Base', constructor=_declarative_constructor,
 
165
                     class_registry=None,
 
166
                     metaclass=DeclarativeMeta):
 
167
    """Construct a base class for declarative class definitions.
 
168
 
 
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
 
173
    of the class.
 
174
 
 
175
    :param bind: An optional
 
176
      :class:`~sqlalchemy.engine.base.Connectable`, will be assigned
 
177
      the ``bind`` attribute on the :class:`~sqlalchemy.MetaData`
 
178
      instance.
 
179
 
 
180
    :param 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.
 
187
 
 
188
    :param mapper:
 
189
      An optional callable, defaults to :func:`~sqlalchemy.orm.mapper`. Will
 
190
      be used to map subclasses to their Tables.
 
191
 
 
192
    :param cls:
 
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.
 
195
 
 
196
    :param name:
 
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.
 
200
 
 
201
    :param constructor:
 
202
      Defaults to
 
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.
 
208
 
 
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.
 
215
 
 
216
    :param metaclass:
 
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.
 
220
 
 
221
    """
 
222
    lcl_metadata = metadata or MetaData()
 
223
    if bind:
 
224
        lcl_metadata.bind = bind
 
225
 
 
226
    if class_registry is None:
 
227
        class_registry = weakref.WeakValueDictionary()
 
228
 
 
229
    bases = not isinstance(cls, tuple) and (cls,) or cls
 
230
    class_dict = dict(_decl_class_registry=class_registry,
 
231
                      metadata=lcl_metadata)
 
232
 
 
233
    if constructor:
 
234
        class_dict['__init__'] = constructor
 
235
    if mapper:
 
236
        class_dict['__mapper_cls__'] = mapper
 
237
 
 
238
    return metaclass(name, bases, class_dict)
 
239
 
 
240
 
 
241
class ConcreteBase(object):
 
242
    """A helper class for 'concrete' declarative mappings.
 
243
 
 
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.
 
249
 
 
250
    :class:`.ConcreteBase` produces a mapped
 
251
    table for the class itself.  Compare to :class:`.AbstractConcreteBase`,
 
252
    which does not.
 
253
 
 
254
    Example::
 
255
 
 
256
        from sqlalchemy.ext.declarative import ConcreteBase
 
257
 
 
258
        class Employee(ConcreteBase, Base):
 
259
            __tablename__ = 'employee'
 
260
            employee_id = Column(Integer, primary_key=True)
 
261
            name = Column(String(50))
 
262
            __mapper_args__ = {
 
263
                            'polymorphic_identity':'employee',
 
264
                            'concrete':True}
 
265
 
 
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))
 
271
            __mapper_args__ = {
 
272
                            'polymorphic_identity':'manager',
 
273
                            'concrete':True}
 
274
 
 
275
    """
 
276
 
 
277
    @classmethod
 
278
    def _create_polymorphic_union(cls, mappers):
 
279
        return polymorphic_union(dict(
 
280
            (mp.polymorphic_identity, mp.local_table)
 
281
            for mp in mappers
 
282
         ), 'type', 'pjoin')
 
283
 
 
284
    @classmethod
 
285
    def __declare_last__(cls):
 
286
        m = cls.__mapper__
 
287
        if m.with_polymorphic:
 
288
            return
 
289
 
 
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)
 
294
 
 
295
 
 
296
class AbstractConcreteBase(ConcreteBase):
 
297
    """A helper class for 'concrete' declarative mappings.
 
298
 
 
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.
 
304
 
 
305
    :class:`.AbstractConcreteBase` does not produce a mapped
 
306
    table for the class itself.  Compare to :class:`.ConcreteBase`,
 
307
    which does.
 
308
 
 
309
    Example::
 
310
 
 
311
        from sqlalchemy.ext.declarative import AbstractConcreteBase
 
312
 
 
313
        class Employee(AbstractConcreteBase, Base):
 
314
            pass
 
315
 
 
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))
 
321
            __mapper_args__ = {
 
322
                            'polymorphic_identity':'manager',
 
323
                            'concrete':True}
 
324
 
 
325
    """
 
326
 
 
327
    __abstract__ = True
 
328
 
 
329
    @classmethod
 
330
    def __declare_last__(cls):
 
331
        if hasattr(cls, '__mapper__'):
 
332
            return
 
333
 
 
334
        # can't rely on 'self_and_descendants' here
 
335
        # since technically an immediate subclass
 
336
        # might not be mapped, but a subclass
 
337
        # may be.
 
338
        mappers = []
 
339
        stack = list(cls.__subclasses__())
 
340
        while stack:
 
341
            klass = stack.pop()
 
342
            stack.extend(klass.__subclasses__())
 
343
            mn = _mapper_or_none(klass)
 
344
            if mn is not None:
 
345
                mappers.append(mn)
 
346
        pjoin = cls._create_polymorphic_union(mappers)
 
347
        cls.__mapper__ = m = mapper(cls, pjoin, polymorphic_on=pjoin.c.type)
 
348
 
 
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)
 
353
 
 
354
 
 
355
class DeferredReflection(object):
 
356
    """A helper class for construction of mappings based on
 
357
    a deferred reflection step.
 
358
 
 
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.
 
367
 
 
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::
 
372
 
 
373
        from sqlalchemy.ext.declarative import declarative_base
 
374
        from sqlalchemy.ext.declarative import DeferredReflection
 
375
        Base = declarative_base()
 
376
 
 
377
        class MyClass(DeferredReflection, Base):
 
378
            __tablename__ = 'mytable'
 
379
 
 
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`::
 
384
 
 
385
        engine = create_engine("someengine://...")
 
386
        DeferredReflection.prepare(engine)
 
387
 
 
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
 
395
    separately, e.g.::
 
396
 
 
397
        class ReflectedOne(DeferredReflection, Base):
 
398
            __abstract__ = True
 
399
 
 
400
        class ReflectedTwo(DeferredReflection, Base):
 
401
            __abstract__ = True
 
402
 
 
403
        class MyClass(ReflectedOne):
 
404
            __tablename__ = 'mytable'
 
405
 
 
406
        class MyOtherClass(ReflectedOne):
 
407
            __tablename__ = 'myothertable'
 
408
 
 
409
        class YetAnotherClass(ReflectedTwo):
 
410
            __tablename__ = 'yetanothertable'
 
411
 
 
412
        # ... etc.
 
413
 
 
414
    Above, the class hierarchies for ``ReflectedOne`` and
 
415
    ``ReflectedTwo`` can be configured separately::
 
416
 
 
417
        ReflectedOne.prepare(engine_one)
 
418
        ReflectedTwo.prepare(engine_two)
 
419
 
 
420
    .. versionadded:: 0.8
 
421
 
 
422
    """
 
423
    @classmethod
 
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)
 
431
            thingy.map()
 
432
 
 
433
    @classmethod
 
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,
 
444
                autoload=True,
 
445
                autoload_with=engine,
 
446
                schema=local_table.schema)