/SQLAlchemy-0.7.8/lib/sqlalchemy/ext/declarative.py
Python | 1768 lines | 1720 code | 20 blank | 28 comment | 40 complexity | 1c4bc993b19160cf31f0442b2de59253 MD5 | raw file
Large files files are truncated, but you can click here to view the full file
1# ext/declarative.py
2# Copyright (C) 2005-2012 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
7"""
8Synopsis
9========
10
11SQLAlchemy object-relational configuration involves the
12combination of :class:`.Table`, :func:`.mapper`, and class
13objects to define a mapped class.
14:mod:`~sqlalchemy.ext.declarative` allows all three to be
15expressed at once within the class declaration. As much as
16possible, regular SQLAlchemy schema and ORM constructs are
17used directly, so that configuration between "classical" ORM
18usage and declarative remain highly similar.
19
20As a simple example::
21
22 from sqlalchemy.ext.declarative import declarative_base
23
24 Base = declarative_base()
25
26 class SomeClass(Base):
27 __tablename__ = 'some_table'
28 id = Column(Integer, primary_key=True)
29 name = Column(String(50))
30
31Above, the :func:`declarative_base` callable returns a new base class from
32which all mapped classes should inherit. When the class definition is
33completed, a new :class:`.Table` and
34:func:`.mapper` will have been generated.
35
36The resulting table and mapper are accessible via
37``__table__`` and ``__mapper__`` attributes on the
38``SomeClass`` class::
39
40 # access the mapped Table
41 SomeClass.__table__
42
43 # access the Mapper
44 SomeClass.__mapper__
45
46Defining Attributes
47===================
48
49In the previous example, the :class:`.Column` objects are
50automatically named with the name of the attribute to which they are
51assigned.
52
53To name columns explicitly with a name distinct from their mapped attribute,
54just give the column a name. Below, column "some_table_id" is mapped to the
55"id" attribute of `SomeClass`, but in SQL will be represented as "some_table_id"::
56
57 class SomeClass(Base):
58 __tablename__ = 'some_table'
59 id = Column("some_table_id", Integer, primary_key=True)
60
61Attributes may be added to the class after its construction, and they will be
62added to the underlying :class:`.Table` and
63:func:`.mapper()` definitions as appropriate::
64
65 SomeClass.data = Column('data', Unicode)
66 SomeClass.related = relationship(RelatedInfo)
67
68Classes which are constructed using declarative can interact freely
69with classes that are mapped explicitly with :func:`mapper`.
70
71It is recommended, though not required, that all tables
72share the same underlying :class:`~sqlalchemy.schema.MetaData` object,
73so that string-configured :class:`~sqlalchemy.schema.ForeignKey`
74references can be resolved without issue.
75
76Accessing the MetaData
77=======================
78
79The :func:`declarative_base` base class contains a
80:class:`.MetaData` object where newly defined
81:class:`.Table` objects are collected. This object is
82intended to be accessed directly for
83:class:`.MetaData`-specific operations. Such as, to issue
84CREATE statements for all tables::
85
86 engine = create_engine('sqlite://')
87 Base.metadata.create_all(engine)
88
89The usual techniques of associating :class:`.MetaData:` with :class:`.Engine`
90apply, such as assigning to the ``bind`` attribute::
91
92 Base.metadata.bind = create_engine('sqlite://')
93
94To associate the engine with the :func:`declarative_base` at time
95of construction, the ``bind`` argument is accepted::
96
97 Base = declarative_base(bind=create_engine('sqlite://'))
98
99:func:`declarative_base` can also receive a pre-existing
100:class:`.MetaData` object, which allows a
101declarative setup to be associated with an already
102existing traditional collection of :class:`~sqlalchemy.schema.Table`
103objects::
104
105 mymetadata = MetaData()
106 Base = declarative_base(metadata=mymetadata)
107
108Configuring Relationships
109=========================
110
111Relationships to other classes are done in the usual way, with the added
112feature that the class specified to :func:`~sqlalchemy.orm.relationship`
113may be a string name. The "class registry" associated with ``Base``
114is used at mapper compilation time to resolve the name into the actual
115class object, which is expected to have been defined once the mapper
116configuration is used::
117
118 class User(Base):
119 __tablename__ = 'users'
120
121 id = Column(Integer, primary_key=True)
122 name = Column(String(50))
123 addresses = relationship("Address", backref="user")
124
125 class Address(Base):
126 __tablename__ = 'addresses'
127
128 id = Column(Integer, primary_key=True)
129 email = Column(String(50))
130 user_id = Column(Integer, ForeignKey('users.id'))
131
132Column constructs, since they are just that, are immediately usable,
133as below where we define a primary join condition on the ``Address``
134class using them::
135
136 class Address(Base):
137 __tablename__ = 'addresses'
138
139 id = Column(Integer, primary_key=True)
140 email = Column(String(50))
141 user_id = Column(Integer, ForeignKey('users.id'))
142 user = relationship(User, primaryjoin=user_id == User.id)
143
144In addition to the main argument for :func:`~sqlalchemy.orm.relationship`,
145other arguments which depend upon the columns present on an as-yet
146undefined class may also be specified as strings. These strings are
147evaluated as Python expressions. The full namespace available within
148this evaluation includes all classes mapped for this declarative base,
149as well as the contents of the ``sqlalchemy`` package, including
150expression functions like :func:`~sqlalchemy.sql.expression.desc` and
151:attr:`~sqlalchemy.sql.expression.func`::
152
153 class User(Base):
154 # ....
155 addresses = relationship("Address",
156 order_by="desc(Address.email)",
157 primaryjoin="Address.user_id==User.id")
158
159As an alternative to string-based attributes, attributes may also be
160defined after all classes have been created. Just add them to the target
161class after the fact::
162
163 User.addresses = relationship(Address,
164 primaryjoin=Address.user_id==User.id)
165
166Configuring Many-to-Many Relationships
167======================================
168
169Many-to-many relationships are also declared in the same way
170with declarative as with traditional mappings. The
171``secondary`` argument to
172:func:`.relationship` is as usual passed a
173:class:`.Table` object, which is typically declared in the
174traditional way. The :class:`.Table` usually shares
175the :class:`.MetaData` object used by the declarative base::
176
177 keywords = Table(
178 'keywords', Base.metadata,
179 Column('author_id', Integer, ForeignKey('authors.id')),
180 Column('keyword_id', Integer, ForeignKey('keywords.id'))
181 )
182
183 class Author(Base):
184 __tablename__ = 'authors'
185 id = Column(Integer, primary_key=True)
186 keywords = relationship("Keyword", secondary=keywords)
187
188Like other :func:`.relationship` arguments, a string is accepted as well,
189passing the string name of the table as defined in the ``Base.metadata.tables``
190collection::
191
192 class Author(Base):
193 __tablename__ = 'authors'
194 id = Column(Integer, primary_key=True)
195 keywords = relationship("Keyword", secondary="keywords")
196
197As with traditional mapping, its generally not a good idea to use
198a :class:`.Table` as the "secondary" argument which is also mapped to
199a class, unless the :class:`.relationship` is declared with ``viewonly=True``.
200Otherwise, the unit-of-work system may attempt duplicate INSERT and
201DELETE statements against the underlying table.
202
203.. _declarative_sql_expressions:
204
205Defining SQL Expressions
206========================
207
208See :ref:`mapper_sql_expressions` for examples on declaratively
209mapping attributes to SQL expressions.
210
211.. _declarative_table_args:
212
213Table Configuration
214===================
215
216Table arguments other than the name, metadata, and mapped Column
217arguments are specified using the ``__table_args__`` class attribute.
218This attribute accommodates both positional as well as keyword
219arguments that are normally sent to the
220:class:`~sqlalchemy.schema.Table` constructor.
221The attribute can be specified in one of two forms. One is as a
222dictionary::
223
224 class MyClass(Base):
225 __tablename__ = 'sometable'
226 __table_args__ = {'mysql_engine':'InnoDB'}
227
228The other, a tuple, where each argument is positional
229(usually constraints)::
230
231 class MyClass(Base):
232 __tablename__ = 'sometable'
233 __table_args__ = (
234 ForeignKeyConstraint(['id'], ['remote_table.id']),
235 UniqueConstraint('foo'),
236 )
237
238Keyword arguments can be specified with the above form by
239specifying the last argument as a dictionary::
240
241 class MyClass(Base):
242 __tablename__ = 'sometable'
243 __table_args__ = (
244 ForeignKeyConstraint(['id'], ['remote_table.id']),
245 UniqueConstraint('foo'),
246 {'autoload':True}
247 )
248
249Using a Hybrid Approach with __table__
250=======================================
251
252As an alternative to ``__tablename__``, a direct
253:class:`~sqlalchemy.schema.Table` construct may be used. The
254:class:`~sqlalchemy.schema.Column` objects, which in this case require
255their names, will be added to the mapping just like a regular mapping
256to a table::
257
258 class MyClass(Base):
259 __table__ = Table('my_table', Base.metadata,
260 Column('id', Integer, primary_key=True),
261 Column('name', String(50))
262 )
263
264``__table__`` provides a more focused point of control for establishing
265table metadata, while still getting most of the benefits of using declarative.
266An application that uses reflection might want to load table metadata elsewhere
267and pass it to declarative classes::
268
269 from sqlalchemy.ext.declarative import declarative_base
270
271 Base = declarative_base()
272 Base.metadata.reflect(some_engine)
273
274 class User(Base):
275 __table__ = metadata.tables['user']
276
277 class Address(Base):
278 __table__ = metadata.tables['address']
279
280Some configuration schemes may find it more appropriate to use ``__table__``,
281such as those which already take advantage of the data-driven nature of
282:class:`.Table` to customize and/or automate schema definition.
283
284Note that when the ``__table__`` approach is used, the object is immediately
285usable as a plain :class:`.Table` within the class declaration body itself,
286as a Python class is only another syntactical block. Below this is illustrated
287by using the ``id`` column in the ``primaryjoin`` condition of a :func:`.relationship`::
288
289 class MyClass(Base):
290 __table__ = Table('my_table', Base.metadata,
291 Column('id', Integer, primary_key=True),
292 Column('name', String(50))
293 )
294
295 widgets = relationship(Widget,
296 primaryjoin=Widget.myclass_id==__table__.c.id)
297
298Similarly, mapped attributes which refer to ``__table__`` can be placed inline,
299as below where we assign the ``name`` column to the attribute ``_name``, generating
300a synonym for ``name``::
301
302 from sqlalchemy.ext.declarative import synonym_for
303
304 class MyClass(Base):
305 __table__ = Table('my_table', Base.metadata,
306 Column('id', Integer, primary_key=True),
307 Column('name', String(50))
308 )
309
310 _name = __table__.c.name
311
312 @synonym_for("_name")
313 def name(self):
314 return "Name: %s" % _name
315
316Using Reflection with Declarative
317=================================
318
319It's easy to set up a :class:`.Table` that uses ``autoload=True``
320in conjunction with a mapped class::
321
322 class MyClass(Base):
323 __table__ = Table('mytable', Base.metadata,
324 autoload=True, autoload_with=some_engine)
325
326However, one improvement that can be made here is to not
327require the :class:`.Engine` to be available when classes are
328being first declared. To achieve this, use the example
329described at :ref:`examples_declarative_reflection` to build a
330declarative base that sets up mappings only after a special
331``prepare(engine)`` step is called::
332
333 Base = declarative_base(cls=DeclarativeReflectedBase)
334
335 class Foo(Base):
336 __tablename__ = 'foo'
337 bars = relationship("Bar")
338
339 class Bar(Base):
340 __tablename__ = 'bar'
341
342 # illustrate overriding of "bar.foo_id" to have
343 # a foreign key constraint otherwise not
344 # reflected, such as when using MySQL
345 foo_id = Column(Integer, ForeignKey('foo.id'))
346
347 Base.prepare(e)
348
349
350Mapper Configuration
351====================
352
353Declarative makes use of the :func:`~.orm.mapper` function internally
354when it creates the mapping to the declared table. The options
355for :func:`~.orm.mapper` are passed directly through via the ``__mapper_args__``
356class attribute. As always, arguments which reference locally
357mapped columns can reference them directly from within the
358class declaration::
359
360 from datetime import datetime
361
362 class Widget(Base):
363 __tablename__ = 'widgets'
364
365 id = Column(Integer, primary_key=True)
366 timestamp = Column(DateTime, nullable=False)
367
368 __mapper_args__ = {
369 'version_id_col': timestamp,
370 'version_id_generator': lambda v:datetime.now()
371 }
372
373.. _declarative_inheritance:
374
375Inheritance Configuration
376=========================
377
378Declarative supports all three forms of inheritance as intuitively
379as possible. The ``inherits`` mapper keyword argument is not needed
380as declarative will determine this from the class itself. The various
381"polymorphic" keyword arguments are specified using ``__mapper_args__``.
382
383Joined Table Inheritance
384~~~~~~~~~~~~~~~~~~~~~~~~
385
386Joined table inheritance is defined as a subclass that defines its own
387table::
388
389 class Person(Base):
390 __tablename__ = 'people'
391 id = Column(Integer, primary_key=True)
392 discriminator = Column('type', String(50))
393 __mapper_args__ = {'polymorphic_on': discriminator}
394
395 class Engineer(Person):
396 __tablename__ = 'engineers'
397 __mapper_args__ = {'polymorphic_identity': 'engineer'}
398 id = Column(Integer, ForeignKey('people.id'), primary_key=True)
399 primary_language = Column(String(50))
400
401Note that above, the ``Engineer.id`` attribute, since it shares the
402same attribute name as the ``Person.id`` attribute, will in fact
403represent the ``people.id`` and ``engineers.id`` columns together, and
404will render inside a query as ``"people.id"``.
405To provide the ``Engineer`` class with an attribute that represents
406only the ``engineers.id`` column, give it a different attribute name::
407
408 class Engineer(Person):
409 __tablename__ = 'engineers'
410 __mapper_args__ = {'polymorphic_identity': 'engineer'}
411 engineer_id = Column('id', Integer, ForeignKey('people.id'),
412 primary_key=True)
413 primary_language = Column(String(50))
414
415Single Table Inheritance
416~~~~~~~~~~~~~~~~~~~~~~~~
417
418Single table inheritance is defined as a subclass that does not have
419its own table; you just leave out the ``__table__`` and ``__tablename__``
420attributes::
421
422 class Person(Base):
423 __tablename__ = 'people'
424 id = Column(Integer, primary_key=True)
425 discriminator = Column('type', String(50))
426 __mapper_args__ = {'polymorphic_on': discriminator}
427
428 class Engineer(Person):
429 __mapper_args__ = {'polymorphic_identity': 'engineer'}
430 primary_language = Column(String(50))
431
432When the above mappers are configured, the ``Person`` class is mapped
433to the ``people`` table *before* the ``primary_language`` column is
434defined, and this column will not be included in its own mapping.
435When ``Engineer`` then defines the ``primary_language`` column, the
436column is added to the ``people`` table so that it is included in the
437mapping for ``Engineer`` and is also part of the table's full set of
438columns. Columns which are not mapped to ``Person`` are also excluded
439from any other single or joined inheriting classes using the
440``exclude_properties`` mapper argument. Below, ``Manager`` will have
441all the attributes of ``Person`` and ``Manager`` but *not* the
442``primary_language`` attribute of ``Engineer``::
443
444 class Manager(Person):
445 __mapper_args__ = {'polymorphic_identity': 'manager'}
446 golf_swing = Column(String(50))
447
448The attribute exclusion logic is provided by the
449``exclude_properties`` mapper argument, and declarative's default
450behavior can be disabled by passing an explicit ``exclude_properties``
451collection (empty or otherwise) to the ``__mapper_args__``.
452
453Concrete Table Inheritance
454~~~~~~~~~~~~~~~~~~~~~~~~~~
455
456Concrete is defined as a subclass which has its own table and sets the
457``concrete`` keyword argument to ``True``::
458
459 class Person(Base):
460 __tablename__ = 'people'
461 id = Column(Integer, primary_key=True)
462 name = Column(String(50))
463
464 class Engineer(Person):
465 __tablename__ = 'engineers'
466 __mapper_args__ = {'concrete':True}
467 id = Column(Integer, primary_key=True)
468 primary_language = Column(String(50))
469 name = Column(String(50))
470
471Usage of an abstract base class is a little less straightforward as it
472requires usage of :func:`~sqlalchemy.orm.util.polymorphic_union`,
473which needs to be created with the :class:`.Table` objects
474before the class is built::
475
476 engineers = Table('engineers', Base.metadata,
477 Column('id', Integer, primary_key=True),
478 Column('name', String(50)),
479 Column('primary_language', String(50))
480 )
481 managers = Table('managers', Base.metadata,
482 Column('id', Integer, primary_key=True),
483 Column('name', String(50)),
484 Column('golf_swing', String(50))
485 )
486
487 punion = polymorphic_union({
488 'engineer':engineers,
489 'manager':managers
490 }, 'type', 'punion')
491
492 class Person(Base):
493 __table__ = punion
494 __mapper_args__ = {'polymorphic_on':punion.c.type}
495
496 class Engineer(Person):
497 __table__ = engineers
498 __mapper_args__ = {'polymorphic_identity':'engineer', 'concrete':True}
499
500 class Manager(Person):
501 __table__ = managers
502 __mapper_args__ = {'polymorphic_identity':'manager', 'concrete':True}
503
504.. _declarative_concrete_helpers:
505
506Using the Concrete Helpers
507^^^^^^^^^^^^^^^^^^^^^^^^^^^
508
509Helper classes provides a simpler pattern for concrete inheritance.
510With these objects, the ``__declare_last__`` helper is used to configure the "polymorphic"
511loader for the mapper after all subclasses have been declared.
512
513.. versionadded:: 0.7.3
514
515An abstract base can be declared using the :class:`.AbstractConcreteBase` class::
516
517 from sqlalchemy.ext.declarative import AbstractConcreteBase
518
519 class Employee(AbstractConcreteBase, Base):
520 pass
521
522To have a concrete ``employee`` table, use :class:`.ConcreteBase` instead::
523
524 from sqlalchemy.ext.declarative import ConcreteBase
525
526 class Employee(ConcreteBase, Base):
527 __tablename__ = 'employee'
528 employee_id = Column(Integer, primary_key=True)
529 name = Column(String(50))
530 __mapper_args__ = {
531 'polymorphic_identity':'employee',
532 'concrete':True}
533
534
535Either ``Employee`` base can be used in the normal fashion::
536
537 class Manager(Employee):
538 __tablename__ = 'manager'
539 employee_id = Column(Integer, primary_key=True)
540 name = Column(String(50))
541 manager_data = Column(String(40))
542 __mapper_args__ = {
543 'polymorphic_identity':'manager',
544 'concrete':True}
545
546 class Engineer(Employee):
547 __tablename__ = 'engineer'
548 employee_id = Column(Integer, primary_key=True)
549 name = Column(String(50))
550 engineer_info = Column(String(40))
551 __mapper_args__ = {'polymorphic_identity':'engineer',
552 'concrete':True}
553
554
555.. _declarative_mixins:
556
557Mixin and Custom Base Classes
558==============================
559
560A common need when using :mod:`~sqlalchemy.ext.declarative` is to
561share some functionality, such as a set of common columns, some common
562table options, or other mapped properties, across many
563classes. The standard Python idioms for this is to have the classes
564inherit from a base which includes these common features.
565
566When using :mod:`~sqlalchemy.ext.declarative`, this idiom is allowed
567via the usage of a custom declarative base class, as well as a "mixin" class
568which is inherited from in addition to the primary base. Declarative
569includes several helper features to make this work in terms of how
570mappings are declared. An example of some commonly mixed-in
571idioms is below::
572
573 from sqlalchemy.ext.declarative import declared_attr
574
575 class MyMixin(object):
576
577 @declared_attr
578 def __tablename__(cls):
579 return cls.__name__.lower()
580
581 __table_args__ = {'mysql_engine': 'InnoDB'}
582 __mapper_args__= {'always_refresh': True}
583
584 id = Column(Integer, primary_key=True)
585
586 class MyModel(MyMixin, Base):
587 name = Column(String(1000))
588
589Where above, the class ``MyModel`` will contain an "id" column
590as the primary key, a ``__tablename__`` attribute that derives
591from the name of the class itself, as well as ``__table_args__``
592and ``__mapper_args__`` defined by the ``MyMixin`` mixin class.
593
594There's no fixed convention over whether ``MyMixin`` precedes
595``Base`` or not. Normal Python method resolution rules apply, and
596the above example would work just as well with::
597
598 class MyModel(Base, MyMixin):
599 name = Column(String(1000))
600
601This works because ``Base`` here doesn't define any of the
602variables that ``MyMixin`` defines, i.e. ``__tablename__``,
603``__table_args__``, ``id``, etc. If the ``Base`` did define
604an attribute of the same name, the class placed first in the
605inherits list would determine which attribute is used on the
606newly defined class.
607
608Augmenting the Base
609~~~~~~~~~~~~~~~~~~~
610
611In addition to using a pure mixin, most of the techniques in this
612section can also be applied to the base class itself, for patterns that
613should apply to all classes derived from a particular base. This
614is achieved using the ``cls`` argument of the :func:`.declarative_base` function::
615
616 from sqlalchemy.ext.declarative import declared_attr
617
618 class Base(object):
619 @declared_attr
620 def __tablename__(cls):
621 return cls.__name__.lower()
622
623 __table_args__ = {'mysql_engine': 'InnoDB'}
624
625 id = Column(Integer, primary_key=True)
626
627 from sqlalchemy.ext.declarative import declarative_base
628
629 Base = declarative_base(cls=Base)
630
631 class MyModel(Base):
632 name = Column(String(1000))
633
634Where above, ``MyModel`` and all other classes that derive from ``Base`` will have
635a table name derived from the class name, an ``id`` primary key column, as well as
636the "InnoDB" engine for MySQL.
637
638Mixing in Columns
639~~~~~~~~~~~~~~~~~
640
641The most basic way to specify a column on a mixin is by simple
642declaration::
643
644 class TimestampMixin(object):
645 created_at = Column(DateTime, default=func.now())
646
647 class MyModel(TimestampMixin, Base):
648 __tablename__ = 'test'
649
650 id = Column(Integer, primary_key=True)
651 name = Column(String(1000))
652
653Where above, all declarative classes that include ``TimestampMixin``
654will also have a column ``created_at`` that applies a timestamp to
655all row insertions.
656
657Those familiar with the SQLAlchemy expression language know that
658the object identity of clause elements defines their role in a schema.
659Two ``Table`` objects ``a`` and ``b`` may both have a column called
660``id``, but the way these are differentiated is that ``a.c.id``
661and ``b.c.id`` are two distinct Python objects, referencing their
662parent tables ``a`` and ``b`` respectively.
663
664In the case of the mixin column, it seems that only one
665:class:`.Column` object is explicitly created, yet the ultimate
666``created_at`` column above must exist as a distinct Python object
667for each separate destination class. To accomplish this, the declarative
668extension creates a **copy** of each :class:`.Column` object encountered on
669a class that is detected as a mixin.
670
671This copy mechanism is limited to simple columns that have no foreign
672keys, as a :class:`.ForeignKey` itself contains references to columns
673which can't be properly recreated at this level. For columns that
674have foreign keys, as well as for the variety of mapper-level constructs
675that require destination-explicit context, the
676:func:`~.declared_attr` decorator is provided so that
677patterns common to many classes can be defined as callables::
678
679 from sqlalchemy.ext.declarative import declared_attr
680
681 class ReferenceAddressMixin(object):
682 @declared_attr
683 def address_id(cls):
684 return Column(Integer, ForeignKey('address.id'))
685
686 class User(ReferenceAddressMixin, Base):
687 __tablename__ = 'user'
688 id = Column(Integer, primary_key=True)
689
690Where above, the ``address_id`` class-level callable is executed at the
691point at which the ``User`` class is constructed, and the declarative
692extension can use the resulting :class:`.Column` object as returned by
693the method without the need to copy it.
694
695.. versionchanged:: > 0.6.5
696 Rename 0.6.5 ``sqlalchemy.util.classproperty`` into :func:`~.declared_attr`.
697
698Columns generated by :func:`~.declared_attr` can also be
699referenced by ``__mapper_args__`` to a limited degree, currently
700by ``polymorphic_on`` and ``version_id_col``, by specifying the
701classdecorator itself into the dictionary - the declarative extension
702will resolve them at class construction time::
703
704 class MyMixin:
705 @declared_attr
706 def type_(cls):
707 return Column(String(50))
708
709 __mapper_args__= {'polymorphic_on':type_}
710
711 class MyModel(MyMixin, Base):
712 __tablename__='test'
713 id = Column(Integer, primary_key=True)
714
715Mixing in Relationships
716~~~~~~~~~~~~~~~~~~~~~~~
717
718Relationships created by :func:`~sqlalchemy.orm.relationship` are provided
719with declarative mixin classes exclusively using the
720:func:`.declared_attr` approach, eliminating any ambiguity
721which could arise when copying a relationship and its possibly column-bound
722contents. Below is an example which combines a foreign key column and a
723relationship so that two classes ``Foo`` and ``Bar`` can both be configured to
724reference a common target class via many-to-one::
725
726 class RefTargetMixin(object):
727 @declared_attr
728 def target_id(cls):
729 return Column('target_id', ForeignKey('target.id'))
730
731 @declared_attr
732 def target(cls):
733 return relationship("Target")
734
735 class Foo(RefTargetMixin, Base):
736 __tablename__ = 'foo'
737 id = Column(Integer, primary_key=True)
738
739 class Bar(RefTargetMixin, Base):
740 __tablename__ = 'bar'
741 id = Column(Integer, primary_key=True)
742
743 class Target(Base):
744 __tablename__ = 'target'
745 id = Column(Integer, primary_key=True)
746
747:func:`~sqlalchemy.orm.relationship` definitions which require explicit
748primaryjoin, order_by etc. expressions should use the string forms
749for these arguments, so that they are evaluated as late as possible.
750To reference the mixin class in these expressions, use the given ``cls``
751to get it's name::
752
753 class RefTargetMixin(object):
754 @declared_attr
755 def target_id(cls):
756 return Column('target_id', ForeignKey('target.id'))
757
758 @declared_attr
759 def target(cls):
760 return relationship("Target",
761 primaryjoin="Target.id==%s.target_id" % cls.__name__
762 )
763
764Mixing in deferred(), column_property(), etc.
765~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
766
767Like :func:`~sqlalchemy.orm.relationship`, all
768:class:`~sqlalchemy.orm.interfaces.MapperProperty` subclasses such as
769:func:`~sqlalchemy.orm.deferred`, :func:`~sqlalchemy.orm.column_property`,
770etc. ultimately involve references to columns, and therefore, when
771used with declarative mixins, have the :func:`.declared_attr`
772requirement so that no reliance on copying is needed::
773
774 class SomethingMixin(object):
775
776 @declared_attr
777 def dprop(cls):
778 return deferred(Column(Integer))
779
780 class Something(SomethingMixin, Base):
781 __tablename__ = "something"
782
783
784Controlling table inheritance with mixins
785~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
786
787The ``__tablename__`` attribute in conjunction with the hierarchy of
788classes involved in a declarative mixin scenario controls what type of
789table inheritance, if any,
790is configured by the declarative extension.
791
792If the ``__tablename__`` is computed by a mixin, you may need to
793control which classes get the computed attribute in order to get the
794type of table inheritance you require.
795
796For example, if you had a mixin that computes ``__tablename__`` but
797where you wanted to use that mixin in a single table inheritance
798hierarchy, you can explicitly specify ``__tablename__`` as ``None`` to
799indicate that the class should not have a table mapped::
800
801 from sqlalchemy.ext.declarative import declared_attr
802
803 class Tablename:
804 @declared_attr
805 def __tablename__(cls):
806 return cls.__name__.lower()
807
808 class Person(Tablename, Base):
809 id = Column(Integer, primary_key=True)
810 discriminator = Column('type', String(50))
811 __mapper_args__ = {'polymorphic_on': discriminator}
812
813 class Engineer(Person):
814 __tablename__ = None
815 __mapper_args__ = {'polymorphic_identity': 'engineer'}
816 primary_language = Column(String(50))
817
818Alternatively, you can make the mixin intelligent enough to only
819return a ``__tablename__`` in the event that no table is already
820mapped in the inheritance hierarchy. To help with this, a
821:func:`~sqlalchemy.ext.declarative.has_inherited_table` helper
822function is provided that returns ``True`` if a parent class already
823has a mapped table.
824
825As an example, here's a mixin that will only allow single table
826inheritance::
827
828 from sqlalchemy.ext.declarative import declared_attr
829 from sqlalchemy.ext.declarative import has_inherited_table
830
831 class Tablename(object):
832 @declared_attr
833 def __tablename__(cls):
834 if has_inherited_table(cls):
835 return None
836 return cls.__name__.lower()
837
838 class Person(Tablename, Base):
839 id = Column(Integer, primary_key=True)
840 discriminator = Column('type', String(50))
841 __mapper_args__ = {'polymorphic_on': discriminator}
842
843 class Engineer(Person):
844 primary_language = Column(String(50))
845 __mapper_args__ = {'polymorphic_identity': 'engineer'}
846
847If you want to use a similar pattern with a mix of single and joined
848table inheritance, you would need a slightly different mixin and use
849it on any joined table child classes in addition to their parent
850classes::
851
852 from sqlalchemy.ext.declarative import declared_attr
853 from sqlalchemy.ext.declarative import has_inherited_table
854
855 class Tablename(object):
856 @declared_attr
857 def __tablename__(cls):
858 if (has_inherited_table(cls) and
859 Tablename not in cls.__bases__):
860 return None
861 return cls.__name__.lower()
862
863 class Person(Tablename, Base):
864 id = Column(Integer, primary_key=True)
865 discriminator = Column('type', String(50))
866 __mapper_args__ = {'polymorphic_on': discriminator}
867
868 # This is single table inheritance
869 class Engineer(Person):
870 primary_language = Column(String(50))
871 __mapper_args__ = {'polymorphic_identity': 'engineer'}
872
873 # This is joined table inheritance
874 class Manager(Tablename, Person):
875 id = Column(Integer, ForeignKey('person.id'), primary_key=True)
876 preferred_recreation = Column(String(50))
877 __mapper_args__ = {'polymorphic_identity': 'engineer'}
878
879Combining Table/Mapper Arguments from Multiple Mixins
880~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
881
882In the case of ``__table_args__`` or ``__mapper_args__``
883specified with declarative mixins, you may want to combine
884some parameters from several mixins with those you wish to
885define on the class iteself. The
886:func:`.declared_attr` decorator can be used
887here to create user-defined collation routines that pull
888from multiple collections::
889
890 from sqlalchemy.ext.declarative import declared_attr
891
892 class MySQLSettings(object):
893 __table_args__ = {'mysql_engine':'InnoDB'}
894
895 class MyOtherMixin(object):
896 __table_args__ = {'info':'foo'}
897
898 class MyModel(MySQLSettings, MyOtherMixin, Base):
899 __tablename__='my_model'
900
901 @declared_attr
902 def __table_args__(cls):
903 args = dict()
904 args.update(MySQLSettings.__table_args__)
905 args.update(MyOtherMixin.__table_args__)
906 return args
907
908 id = Column(Integer, primary_key=True)
909
910Creating Indexes with Mixins
911~~~~~~~~~~~~~~~~~~~~~~~~~~~~
912
913To define a named, potentially multicolumn :class:`.Index` that applies to all
914tables derived from a mixin, use the "inline" form of :class:`.Index` and establish
915it as part of ``__table_args__``::
916
917 class MyMixin(object):
918 a = Column(Integer)
919 b = Column(Integer)
920
921 @declared_attr
922 def __table_args__(cls):
923 return (Index('test_idx_%s' % cls.__tablename__, 'a', 'b'),)
924
925 class MyModel(MyMixin, Base):
926 __tablename__ = 'atable'
927 c = Column(Integer,primary_key=True)
928
929Special Directives
930==================
931
932``__declare_last__()``
933~~~~~~~~~~~~~~~~~~~~~~
934
935The ``__declare_last__()`` hook allows definition of
936a class level function that is automatically called by the :meth:`.MapperEvents.after_configured`
937event, which occurs after mappings are assumed to be completed and the 'configure' step
938has finished::
939
940 class MyClass(Base):
941 @classmethod
942 def __declare_last__(cls):
943 ""
944 # do something with mappings
945
946.. versionadded:: 0.7.3
947
948.. _declarative_abstract:
949
950``__abstract__``
951~~~~~~~~~~~~~~~~~~~
952
953``__abstract__`` causes declarative to skip the production
954of a table or mapper for the class entirely. A class can be added within a hierarchy
955in the same way as mixin (see :ref:`declarative_mixins`), allowing subclasses to extend
956just from the special class::
957
958 class SomeAbstractBase(Base):
959 __abstract__ = True
960
961 def some_helpful_method(self):
962 ""
963
964 @declared_attr
965 def __mapper_args__(cls):
966 return {"helpful mapper arguments":True}
967
968 class MyMappedClass(SomeAbstractBase):
969 ""
970
971One possible use of ``__abstract__`` is to use a distinct :class:`.MetaData` for different
972bases::
973
974 Base = declarative_base()
975
976 class DefaultBase(Base):
977 __abstract__ = True
978 metadata = MetaData()
979
980 class OtherBase(Base):
981 __abstract__ = True
982 metadata = MetaData()
983
984Above, classes which inherit from ``DefaultBase`` will use one :class:`.MetaData` as the
985registry of tables, and those which inherit from ``OtherBase`` will use a different one.
986The tables themselves can then be created perhaps within distinct databases::
987
988 DefaultBase.metadata.create_all(some_engine)
989 OtherBase.metadata_create_all(some_other_engine)
990
991.. versionadded:: 0.7.3
992
993Class Constructor
994=================
995
996As a convenience feature, the :func:`declarative_base` sets a default
997constructor on classes which takes keyword arguments, and assigns them
998to the named attributes::
999
1000 e = Engineer(primary_language='python')
1001
1002Sessions
1003========
1004
1005Note that ``declarative`` does nothing special with sessions, and is
1006only intended as an easier way to configure mappers and
1007:class:`~sqlalchemy.schema.Table` objects. A typical application
1008setup using :func:`~sqlalchemy.orm.scoped_session` might look like::
1009
1010 engine = create_engine('postgresql://scott:tiger@localhost/test')
1011 Session = scoped_session(sessionmaker(autocommit=False,
1012 autoflush=False,
1013 bind=engine))
1014 Base = declarative_base()
1015
1016Mapped instances then make usage of
1017:class:`~sqlalchemy.orm.session.Session` in the usual way.
1018
1019"""
1020
1021from sqlalchemy.schema import Table, Column, MetaData, _get_table_key
1022from sqlalchemy.orm import synonym as _orm_synonym, mapper,\
1023 comparable_property, class_mapper
1024from sqlalchemy.orm.interfaces import MapperProperty
1025from sqlalchemy.orm.properties import RelationshipProperty, ColumnProperty, CompositeProperty
1026from sqlalchemy.orm.util import _is_mapped_class
1027from sqlalchemy import util, exc
1028from sqlalchemy.sql import util as sql_util, expression
1029from sqlalchemy import event
1030from sqlalchemy.orm.util import polymorphic_union, _mapper_or_none
1031
1032
1033__all__ = 'declarative_base', 'synonym_for', \
1034 'comparable_using', 'instrument_declarative'
1035
1036def instrument_declarative(cls, registry, metadata):
1037 """Given a class, configure the class declaratively,
1038 using the given registry, which can be any dictionary, and
1039 MetaData object.
1040
1041 """
1042 if '_decl_class_registry' in cls.__dict__:
1043 raise exc.InvalidRequestError(
1044 "Class %r already has been "
1045 "instrumented declaratively" % cls)
1046 cls._decl_class_registry = registry
1047 cls.metadata = metadata
1048 _as_declarative(cls, cls.__name__, cls.__dict__)
1049
1050def has_inherited_table(cls):
1051 """Given a class, return True if any of the classes it inherits from has a
1052 mapped table, otherwise return False.
1053 """
1054 for class_ in cls.__mro__:
1055 if getattr(class_,'__table__',None) is not None:
1056 return True
1057 return False
1058
1059def _as_declarative(cls, classname, dict_):
1060
1061 # dict_ will be a dictproxy, which we can't write to, and we need to!
1062 dict_ = dict(dict_)
1063
1064 column_copies = {}
1065 potential_columns = {}
1066
1067 mapper_args = {}
1068 table_args = inherited_table_args = None
1069 tablename = None
1070 parent_columns = ()
1071
1072 declarative_props = (declared_attr, util.classproperty)
1073
1074 for base in cls.__mro__:
1075 _is_declarative_inherits = hasattr(base, '_decl_class_registry')
1076
1077 if '__declare_last__' in base.__dict__:
1078 @event.listens_for(mapper, "after_configured")
1079 def go():
1080 cls.__declare_last__()
1081 if '__abstract__' in base.__dict__:
1082 if (base is cls or
1083 (base in cls.__bases__ and not _is_declarative_inherits)
1084 ):
1085 return
1086
1087 class_mapped = _is_mapped_class(base)
1088 if class_mapped:
1089 parent_columns = base.__table__.c.keys()
1090
1091 for name,obj in vars(base).items():
1092 if name == '__mapper_args__':
1093 if not mapper_args and (
1094 not class_mapped or
1095 isinstance(obj, declarative_props)
1096 ):
1097 mapper_args = cls.__mapper_args__
1098 elif name == '__tablename__':
1099 if not tablename and (
1100 not class_mapped or
1101 isinstance(obj, declarative_props)
1102 ):
1103 tablename = cls.__tablename__
1104 elif name == '__table_args__':
1105 if not table_args and (
1106 not class_mapped or
1107 isinstance(obj, declarative_props)
1108 ):
1109 table_args = cls.__table_args__
1110 if not isinstance(table_args, (tuple, dict, type(None))):
1111 raise exc.ArgumentError(
1112 "__table_args__ value must be a tuple, "
1113 "dict, or None")
1114 if base is not cls:
1115 inherited_table_args = True
1116 elif class_mapped:
1117 if isinstance(obj, declarative_props):
1118 util.warn("Regular (i.e. not __special__) "
1119 "attribute '%s.%s' uses @declared_attr, "
1120 "but owning class %s is mapped - "
1121 "not applying to subclass %s."
1122 % (base.__name__, name, base, cls))
1123 continue
1124 elif base is not cls:
1125 # we're a mixin.
1126 if isinstance(obj, Column):
1127 if obj.foreign_keys:
1128 raise exc.InvalidRequestError(
1129 "Columns with foreign keys to other columns "
1130 "must be declared as @declared_attr callables "
1131 "on declarative mixin classes. ")
1132 if name not in dict_ and not (
1133 '__table__' in dict_ and
1134 (obj.name or name) in dict_['__table__'].c
1135 ) and name not in potential_columns:
1136 potential_columns[name] = \
1137 column_copies[obj] = \
1138 obj.copy()
1139 column_copies[obj]._creation_order = \
1140 obj._creation_order
1141 elif isinstance(obj, MapperProperty):
1142 raise exc.InvalidRequestError(
1143 "Mapper properties (i.e. deferred,"
1144 "column_property(), relationship(), etc.) must "
1145 "be declared as @declared_attr callables "
1146 "on declarative mixin classes.")
1147 elif isinstance(obj, declarative_props):
1148 dict_[name] = ret = \
1149 column_copies[obj] = getattr(cls, name)
1150 if isinstance(ret, (Column, MapperProperty)) and \
1151 ret.doc is None:
1152 ret.doc = obj.__doc__
1153
1154 # apply inherited columns as we should
1155 for k, v in potential_columns.items():
1156 if tablename or (v.name or k) not in parent_columns:
1157 dict_[k] = v
1158
1159 if inherited_table_args and not tablename:
1160 table_args = None
1161
1162 # make sure that column copies are used rather
1163 # than the original columns from any mixins
1164 for k in ('version_id_col', 'polymorphic_on',):
1165 if k in mapper_args:
1166 v = mapper_args[k]
1167 mapper_args[k] = column_copies.get(v,v)
1168
1169 if classname in cls._decl_class_registry:
1170 util.warn("The classname %r is already in the registry of this"
1171 " declarative base, mapped to %r" % (
1172 classname,
1173 cls._decl_class_registry[classname]
1174 ))
1175 cls._decl_class_registry[classname] = cls
1176 our_stuff = util.OrderedDict()
1177
1178 for k in dict_:
1179 value = dict_[k]
1180 if isinstance(value, declarative_props):
1181 value = getattr(cls, k)
1182
1183 if (isinstance(value, tuple) and len(value) == 1 and
1184 isinstance(value[0], (Column, MapperProperty))):
1185 util.warn("Ignoring declarative-like tuple value of attribute "
1186 "%s: possibly a copy-and-paste error with a comma "
1187 "left at the end of the line?" % k)
1188 continue
1189 if not isinstance(value, (Column, MapperProperty)):
1190 continue
1191 if k == 'metadata':
1192 raise exc.InvalidRequestError(
1193 "Attribute name 'metadata' is reserved "
1194 "for the MetaData instance when using a "
1195 "declarative base class."
1196 )
1197 prop = _deferred_relationship(cls, value)
1198 our_stuff[k] = prop
1199
1200 # set up attributes in the order they were created
1201 our_stuff.sort(key=lambda key: our_stuff[key]._creation_order)
1202
1203 # extract columns from the class dict
1204 cols = set()
1205 for key, c in our_stuff.iteritems():
1206 if isinstance(c, (ColumnProperty, CompositeProperty)):
1207 for col in c.columns:
1208 if isinstance(col, Column) and \
1209 col.table is None:
1210 _undefer_column_name(key, col)
1211 cols.add(col)
1212 elif isinstance(c, Column):
1213 _undefer_column_name(key, c)
1214 cols.add(c)
1215 # if the column is the same name as the key,
1216 # remove it from the explicit properties dict.
1217 # the normal rules for assigning column-based properties
1218 # will take over, including precedence of columns
1219 # in multi-column ColumnProperties.
1220 if key == c.key:
1221 del our_stuff[key]
1222 cols = sorted(cols, key=lambda c:c._creation_order)
1223 table = None
1224
1225 if hasattr(cls, '__table_cls__'):
1226 table_cls = util.unbound_method_to_callable(cls.__table_cls__)
1227 else:
1228 table_cls = Table
1229
1230 if '__table__' not in dict_:
1231 if tablename is not None:
1232
1233 args, table_kw = (), {}
1234 if table_args:
1235 if isinstance(table_args, dict):
1236 table_kw = table_args
1237 elif isinstance(table_args, tuple):
1238 if isinstance(table_args[-1], dict):
1239 args, table_kw = table_args[0:-1], table_args[-1]
1240 else:
1241 args = table_args
1242
1243 autoload = dict_.get('__autoload__')
1244 if autoload:
1245 table_kw['autoload'] = True
1246
1247 cls.__table__ = table = table_cls(tablename, cls.metadata,
1248 *(tuple(cols) + tuple(args)),
1249 **table_kw)
1250 else:
1251 table = cls.__table__
1252 if cols:
1253 for c in cols:
1254 if not table.c.contains_column(c):
1255 raise exc.ArgumentError(
1256 "Can't add additional column %r when "
1257 "specifying __table__" % c.key
1258 )
1259
1260 if 'inherits' not in mapper_args:
1261 for c in cls.__bases__:
1262 if _is_mapped_class(c):
1263 mapper_args['inherits'] = c
1264 break
1265
1266 if hasattr(cls, '__mapper_cls__'):
1267 mapper_cls = util.unbound_method_to_callable(cls.__mapper_cls__)
1268 else:
1269 mapper_cls = mapper
1270
1271 if table is None and 'inherits' not in mapper_args:
1272 raise exc.InvalidRequestError(
1273 "Class %r does not have a __table__ or __tablename__ "
1274 "specified and does not inherit from an existing "
1275 "table-mapped class." % cls
1276 )
1277
1278 elif 'inherits' in mapper_args and not mapper_args.get('concrete', False):
1279 inherited_mapper = class_mapper(mapper_args['inherits'],
1280 compile=False)
1281 inherited_table = inherited_mapper.local_table
1282
1283 if table is None:
1284 # single table inheritance.
1285 # ensure no table args
1286 if table_args:
1287 raise exc.ArgumentError(
1288 "Can't place __table_args__ on an inherited class "
1289 "with no table."
1290 )
1291
1292 # add any columns declared here to the inherited table.
1293 for c in cols:
1294 if c.primary_key:
1295 raise exc.ArgumentError(
1296 "Can't place primary key columns on an inherited "
1297 "class with no table."
1298 )
1299 if c.name in inherited_table.c:
1300 raise exc.ArgumentError(
1301 "Column '%s' on class %s conflicts with "
1302 "existing column '%s'" %
1303 (c, cls, inherited_table.c[c.name])
1304 )
1305 inherited_table.append_column(c)
1306
1307 # single or joined inheritance
1308 # exclude any cols on the inherited table which are not mapped on the
1309 # parent class, to avoid
1310 # mapping columns specific to sibling/nephew classes
1311 inherited_mapper = class_mapper(mapper_args['inherits'],
1312 compile=False)
1313 inherited_table = inherited_mapper.local_table
1314
1315 if 'exclude_properties' not in mapper_args:
1316 mapper_args['exclude_properties'] = exclude_properties = \
1317 set([c.key for c in inherited_table.c
1318 if c not in inherited_mapper._columntoproperty])
1319 exclude_properties.difference_update([c.key for c in cols])
1320
1321 # look through columns in the current mapper that
1322 # are keyed to a propname different than the colname
1323 # (if names were the same, we'd have popped it out above,
1324 # in which case the mapper makes this combination).
1325 # See if the superclass has a similar column property.
1326 # If so, join them together.
1327 for k, col in our_stuff.items():
1328 if not isinstance(col, expression.ColumnElement):
1329 continue
1330 if k in inherited_mapper._props:
1331 p = inherited_mapper._props[k]
1332 if isinstance(p, ColumnProperty):
1333 # note here we place the subclass column
1334 # first. See [ticket:1892] for background.
1335 our_stuff[k] = [col] + p.columns
1336
1337
1338 cls.__mapper__ = mapper_cls(cls,
1339 table,
1340 properties=our_stuff,
1341 **mapper_args)
1342
1343class DeclarativeMeta(type):
1344 def __init__(cls, classname, bases, dict_):
1345 if '_decl_class_registry' in cls.__dict__:
1346 return type.__init__(cls, classname, bases, dict_)
1347 else:
1348 _as_declarative(cls, classname, cls.__dict__)
1349 return type.__init__(cls, classname, bases, dict_)
1350
1351 def __setattr__(cls, key, value):
1352 if '__mapper__' in cls.__dict__:
1353 if isinstance(value, C…
Large files files are truncated, but you can click here to view the full file