summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/ext/automap.py
blob: 614afcfc4aba33096d5d0e177d8ee2eef38c0cc2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# ext/automap.py
# Copyright (C) 2005-2013 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

"""Define an extension to the :mod:`sqlalchemy.ext.declarative` system
which automatically generates mapped classes and attributes from a database
schema, typically one which is reflected.

.. versionadded:: 0.9.1 Added :mod:`sqlalchemy.ext.automap`.

.. note::

    The :mod:`sqlalchemy.ext.automap` extension should be considered
    **experimental** as of 0.9.1.   Featureset and API stability is
    not guaranteed at this time.

Features:

* The given :class:`.MetaData` structure may or may not be reflected.
  :mod:`.automap` isn't dependent on this.

* Classes which are known to be present in the :mod:`.automap` structure
  can be pre-declared with known attributes and settings.

* The system integrates with the featureset of :mod:`.declarative`, including
  support of mixins, abstract bases, interoperability with non-automapped
  classes.

* The system can build out classes for an entire :class:`.MetaData` structure
  or for individual :class:`.Table` objects.

* Relationships between classes are generated based on foreign keys, including
  that simple many-to-many relationships are also detectable.

* Hooks are provided for many key points, including:

    * A function which converts the name of table into a mapped class

    * A function which receives a :class:`.Column` object to be mapped and
      produces the element to be part of the mapping.

    * A function which receives two classes which should generate a
      :func:`.relationship` and produces the actual :func:`.relationship`.

    * Functions which produce attribute names; given a scalar column,
      or a class name for a scalar or collection reference, produce an attribute
      name.

"""
from sqlalchemy.ext.declarative import declarative_base, DeferredReflection
from sqlalchemy.ext.declarative.base import _DeferredMapperConfig
from sqlalchemy.schema import ForeignKeyConstraint
from sqlalchemy.orm import relationship, backref
from sqlalchemy import util

def _classname_for_table(table):
    return str(table.name)

def automap_base(**kw):
    Base = declarative_base(**kw)

    class BaseThing(DeferredReflection, Base):
        __abstract__ = True

        registry = util.Properties({})

        @classmethod
        def prepare(cls, engine):
            cls.metadata.reflect(
                        engine,
                        extend_existing=True,
                        autoload_replace=False
                    )

            table_to_map_config = dict(
                                    (m.local_table, m)
                                    for m in _DeferredMapperConfig.classes_for_base(cls)
                                )

            for table in cls.metadata.tables.values():
                if table not in table_to_map_config:
                    mapped_cls = type(
                        _classname_for_table(table),
                        (BaseThing, ),
                        {"__table__": table}
                    )
                    map_config = _DeferredMapperConfig.config_for_cls(mapped_cls)
                    table_to_map_config[table] = map_config

            for map_config in table_to_map_config.values():
                _relationships_for_fks(map_config, table_to_map_config)
                cls.registry[map_config.cls.__name__] = map_config.cls
            super(BaseThing, cls).prepare(engine)


        @classmethod
        def _sa_decl_prepare(cls, local_table, engine):
            pass

    return BaseThing

def _relationships_for_fks(map_config, table_to_map_config):
    local_table = map_config.local_table
    local_cls = map_config.cls
    for constraint in local_table.constraints:
        if isinstance(constraint, ForeignKeyConstraint):
            fks = constraint.elements
            referred_table = fks[0].column.table
            referred_cls = table_to_map_config[referred_table].cls

            map_config.properties[referred_cls.__name__.lower()] = \
                relationship(referred_cls,
                        foreign_keys=[fk.parent for fk in constraint.elements],
                        backref=backref(
                                local_cls.__name__.lower() + "_collection",
                            )
                        )