[3] | 1 | """A simple declarative layer for SQLAlchemy ORM. |
---|
| 2 | |
---|
| 3 | Synopsis |
---|
| 4 | ======== |
---|
| 5 | |
---|
| 6 | SQLAlchemy object-relational configuration involves the usage of :class:`~sqlalchemy.schema.Table`, |
---|
| 7 | :func:`~sqlalchemy.orm.mapper`, and class objects to define the three areas of configuration. |
---|
| 8 | ``declarative`` moves these three types of configuration underneath the individual |
---|
| 9 | mapped class. Regular SQLAlchemy schema and ORM constructs are used in most |
---|
| 10 | cases:: |
---|
| 11 | |
---|
| 12 | from sqlalchemy.ext.declarative import declarative_base |
---|
| 13 | |
---|
| 14 | Base = declarative_base() |
---|
| 15 | |
---|
| 16 | class SomeClass(Base): |
---|
| 17 | __tablename__ = 'some_table' |
---|
| 18 | id = Column(Integer, primary_key=True) |
---|
| 19 | name = Column(String(50)) |
---|
| 20 | |
---|
| 21 | Above, the :func:`declarative_base` callable produces a new base class from which |
---|
| 22 | all mapped classes inherit from. When the class definition is completed, a |
---|
| 23 | new :class:`~sqlalchemy.schema.Table` and :class:`~sqlalchemy.orm.mapper` have been generated, accessible via the |
---|
| 24 | ``__table__`` and ``__mapper__`` attributes on the ``SomeClass`` class. |
---|
| 25 | |
---|
| 26 | Defining Attributes |
---|
| 27 | =================== |
---|
| 28 | |
---|
| 29 | :class:`~sqlalchemy.schema.Column` objects may be explicitly named, |
---|
| 30 | including using a different name than the attribute in which they are associated. |
---|
| 31 | The column will be assigned to the :class:`~sqlalchemy.schema.Table` using the |
---|
| 32 | given name, and mapped to the class using the attribute name:: |
---|
| 33 | |
---|
| 34 | class SomeClass(Base): |
---|
| 35 | __tablename__ = 'some_table' |
---|
| 36 | id = Column("some_table_id", Integer, primary_key=True) |
---|
| 37 | name = Column("name", String(50)) |
---|
| 38 | |
---|
| 39 | Otherwise, you may omit the names from the Column definitions. |
---|
| 40 | Declarative will set the ``name`` attribute on the column when the class |
---|
| 41 | is initialized:: |
---|
| 42 | |
---|
| 43 | class SomeClass(Base): |
---|
| 44 | __tablename__ = 'some_table' |
---|
| 45 | id = Column(Integer, primary_key=True) |
---|
| 46 | name = Column(String(50)) |
---|
| 47 | |
---|
| 48 | Attributes may be added to the class after its construction, and they will be |
---|
| 49 | added to the underlying :class:`~sqlalchemy.schema.Table` and :func:`~sqlalchemy.orm.mapper()` definitions as |
---|
| 50 | appropriate:: |
---|
| 51 | |
---|
| 52 | SomeClass.data = Column('data', Unicode) |
---|
| 53 | SomeClass.related = relation(RelatedInfo) |
---|
| 54 | |
---|
| 55 | Classes which are mapped explicitly using :func:`~sqlalchemy.orm.mapper()` can interact freely |
---|
| 56 | with declarative classes. It is recommended, though not required, that all tables |
---|
| 57 | share the same underlying :class:`~sqlalchemy.schema.MetaData` object, so that |
---|
| 58 | string-configured :class:`~sqlalchemy.schema.ForeignKey` references can be resolved without issue. |
---|
| 59 | |
---|
| 60 | Association of Metadata and Engine |
---|
| 61 | ================================== |
---|
| 62 | |
---|
| 63 | The :func:`declarative_base` base class contains a :class:`~sqlalchemy.schema.MetaData` object where newly |
---|
| 64 | defined :class:`~sqlalchemy.schema.Table` objects are collected. This is accessed via the |
---|
| 65 | :class:`~sqlalchemy.schema.MetaData` class level accessor, so to create tables we can say:: |
---|
| 66 | |
---|
| 67 | engine = create_engine('sqlite://') |
---|
| 68 | Base.metadata.create_all(engine) |
---|
| 69 | |
---|
| 70 | The :class:`~sqlalchemy.engine.base.Engine` created above may also be directly associated with the |
---|
| 71 | declarative base class using the ``bind`` keyword argument, where it will be |
---|
| 72 | associated with the underlying :class:`~sqlalchemy.schema.MetaData` object and allow SQL operations |
---|
| 73 | involving that metadata and its tables to make use of that engine |
---|
| 74 | automatically:: |
---|
| 75 | |
---|
| 76 | Base = declarative_base(bind=create_engine('sqlite://')) |
---|
| 77 | |
---|
| 78 | Or, as :class:`~sqlalchemy.schema.MetaData` allows, at any time using the ``bind`` attribute:: |
---|
| 79 | |
---|
| 80 | Base.metadata.bind = create_engine('sqlite://') |
---|
| 81 | |
---|
| 82 | The :func:`declarative_base` can also receive a pre-created :class:`~sqlalchemy.schema.MetaData` object, |
---|
| 83 | which allows a declarative setup to be associated with an already existing |
---|
| 84 | traditional collection of :class:`~sqlalchemy.schema.Table` objects:: |
---|
| 85 | |
---|
| 86 | mymetadata = MetaData() |
---|
| 87 | Base = declarative_base(metadata=mymetadata) |
---|
| 88 | |
---|
| 89 | Configuring Relations |
---|
| 90 | ===================== |
---|
| 91 | |
---|
| 92 | Relations to other classes are done in the usual way, with the added feature |
---|
| 93 | that the class specified to :func:`~sqlalchemy.orm.relation()` may be a string name. The "class |
---|
| 94 | registry" associated with ``Base`` is used at mapper compilation time to |
---|
| 95 | resolve the name into the actual class object, which is expected to have been |
---|
| 96 | defined once the mapper configuration is used:: |
---|
| 97 | |
---|
| 98 | class User(Base): |
---|
| 99 | __tablename__ = 'users' |
---|
| 100 | |
---|
| 101 | id = Column(Integer, primary_key=True) |
---|
| 102 | name = Column(String(50)) |
---|
| 103 | addresses = relation("Address", backref="user") |
---|
| 104 | |
---|
| 105 | class Address(Base): |
---|
| 106 | __tablename__ = 'addresses' |
---|
| 107 | |
---|
| 108 | id = Column(Integer, primary_key=True) |
---|
| 109 | email = Column(String(50)) |
---|
| 110 | user_id = Column(Integer, ForeignKey('users.id')) |
---|
| 111 | |
---|
| 112 | Column constructs, since they are just that, are immediately usable, as below |
---|
| 113 | where we define a primary join condition on the ``Address`` class using them:: |
---|
| 114 | |
---|
| 115 | class Address(Base): |
---|
| 116 | __tablename__ = 'addresses' |
---|
| 117 | |
---|
| 118 | id = Column(Integer, primary_key=True) |
---|
| 119 | email = Column(String(50)) |
---|
| 120 | user_id = Column(Integer, ForeignKey('users.id')) |
---|
| 121 | user = relation(User, primaryjoin=user_id == User.id) |
---|
| 122 | |
---|
| 123 | In addition to the main argument for :func:`~sqlalchemy.orm.relation`, other arguments |
---|
| 124 | which depend upon the columns present on an as-yet undefined class |
---|
| 125 | may also be specified as strings. These strings are evaluated as |
---|
| 126 | Python expressions. The full namespace available within this |
---|
| 127 | evaluation includes all classes mapped for this declarative base, |
---|
| 128 | as well as the contents of the ``sqlalchemy`` package, including |
---|
| 129 | expression functions like :func:`~sqlalchemy.sql.expression.desc` and :attr:`~sqlalchemy.sql.expression.func`:: |
---|
| 130 | |
---|
| 131 | class User(Base): |
---|
| 132 | # .... |
---|
| 133 | addresses = relation("Address", order_by="desc(Address.email)", |
---|
| 134 | primaryjoin="Address.user_id==User.id") |
---|
| 135 | |
---|
| 136 | As an alternative to string-based attributes, attributes may also be |
---|
| 137 | defined after all classes have been created. Just add them to the target |
---|
| 138 | class after the fact:: |
---|
| 139 | |
---|
| 140 | User.addresses = relation(Address, primaryjoin=Address.user_id == User.id) |
---|
| 141 | |
---|
| 142 | Configuring Many-to-Many Relations |
---|
| 143 | ================================== |
---|
| 144 | |
---|
| 145 | There's nothing special about many-to-many with declarative. The ``secondary`` |
---|
| 146 | argument to :func:`~sqlalchemy.orm.relation` still requires a :class:`~sqlalchemy.schema.Table` object, not a declarative class. |
---|
| 147 | The :class:`~sqlalchemy.schema.Table` should share the same :class:`~sqlalchemy.schema.MetaData` object used by the declarative base:: |
---|
| 148 | |
---|
| 149 | keywords = Table('keywords', Base.metadata, |
---|
| 150 | Column('author_id', Integer, ForeignKey('authors.id')), |
---|
| 151 | Column('keyword_id', Integer, ForeignKey('keywords.id')) |
---|
| 152 | ) |
---|
| 153 | |
---|
| 154 | class Author(Base): |
---|
| 155 | __tablename__ = 'authors' |
---|
| 156 | id = Column(Integer, primary_key=True) |
---|
| 157 | keywords = relation("Keyword", secondary=keywords) |
---|
| 158 | |
---|
| 159 | You should generally **not** map a class and also specify its table in a many-to-many |
---|
| 160 | relation, since the ORM may issue duplicate INSERT and DELETE statements. |
---|
| 161 | |
---|
| 162 | |
---|
| 163 | Defining Synonyms |
---|
| 164 | ================= |
---|
| 165 | |
---|
| 166 | Synonyms are introduced in :ref:`synonyms`. To define a getter/setter which |
---|
| 167 | proxies to an underlying attribute, use :func:`~sqlalchemy.orm.synonym` with the |
---|
| 168 | ``descriptor`` argument:: |
---|
| 169 | |
---|
| 170 | class MyClass(Base): |
---|
| 171 | __tablename__ = 'sometable' |
---|
| 172 | |
---|
| 173 | _attr = Column('attr', String) |
---|
| 174 | |
---|
| 175 | def _get_attr(self): |
---|
| 176 | return self._some_attr |
---|
| 177 | def _set_attr(self, attr): |
---|
| 178 | self._some_attr = attr |
---|
| 179 | attr = synonym('_attr', descriptor=property(_get_attr, _set_attr)) |
---|
| 180 | |
---|
| 181 | The above synonym is then usable as an instance attribute as well as a |
---|
| 182 | class-level expression construct:: |
---|
| 183 | |
---|
| 184 | x = MyClass() |
---|
| 185 | x.attr = "some value" |
---|
| 186 | session.query(MyClass).filter(MyClass.attr == 'some other value').all() |
---|
| 187 | |
---|
| 188 | For simple getters, the :func:`synonym_for` decorator can be used in conjunction |
---|
| 189 | with ``@property``:: |
---|
| 190 | |
---|
| 191 | class MyClass(Base): |
---|
| 192 | __tablename__ = 'sometable' |
---|
| 193 | |
---|
| 194 | _attr = Column('attr', String) |
---|
| 195 | |
---|
| 196 | @synonym_for('_attr') |
---|
| 197 | @property |
---|
| 198 | def attr(self): |
---|
| 199 | return self._some_attr |
---|
| 200 | |
---|
| 201 | Similarly, :func:`comparable_using` is a front end for the :func:`~sqlalchemy.orm.comparable_property` |
---|
| 202 | ORM function:: |
---|
| 203 | |
---|
| 204 | class MyClass(Base): |
---|
| 205 | __tablename__ = 'sometable' |
---|
| 206 | |
---|
| 207 | name = Column('name', String) |
---|
| 208 | |
---|
| 209 | @comparable_using(MyUpperCaseComparator) |
---|
| 210 | @property |
---|
| 211 | def uc_name(self): |
---|
| 212 | return self.name.upper() |
---|
| 213 | |
---|
| 214 | Table Configuration |
---|
| 215 | =================== |
---|
| 216 | |
---|
| 217 | Table arguments other than the name, metadata, and mapped Column arguments |
---|
| 218 | are specified using the ``__table_args__`` class attribute. This attribute |
---|
| 219 | accommodates both positional as well as keyword arguments that are normally |
---|
| 220 | sent to the :class:`~sqlalchemy.schema.Table` constructor. The attribute can be specified |
---|
| 221 | in one of two forms. One is as a dictionary:: |
---|
| 222 | |
---|
| 223 | class MyClass(Base): |
---|
| 224 | __tablename__ = 'sometable' |
---|
| 225 | __table_args__ = {'mysql_engine':'InnoDB'} |
---|
| 226 | |
---|
| 227 | The other, a tuple of the form ``(arg1, arg2, ..., {kwarg1:value, ...})``, which |
---|
| 228 | allows positional arguments to be specified as well (usually constraints):: |
---|
| 229 | |
---|
| 230 | class MyClass(Base): |
---|
| 231 | __tablename__ = 'sometable' |
---|
| 232 | __table_args__ = ( |
---|
| 233 | ForeignKeyConstraint(['id'], ['remote_table.id']), |
---|
| 234 | UniqueConstraint('foo'), |
---|
| 235 | {'autoload':True} |
---|
| 236 | ) |
---|
| 237 | |
---|
| 238 | Note that the dictionary is required in the tuple form even if empty. |
---|
| 239 | |
---|
| 240 | As an alternative to ``__tablename__``, a direct :class:`~sqlalchemy.schema.Table` |
---|
| 241 | construct may be used. The :class:`~sqlalchemy.schema.Column` objects, which |
---|
| 242 | in this case require their names, will be |
---|
| 243 | added to the mapping just like a regular mapping to a table:: |
---|
| 244 | |
---|
| 245 | class MyClass(Base): |
---|
| 246 | __table__ = Table('my_table', Base.metadata, |
---|
| 247 | Column('id', Integer, primary_key=True), |
---|
| 248 | Column('name', String(50)) |
---|
| 249 | ) |
---|
| 250 | |
---|
| 251 | Mapper Configuration |
---|
| 252 | ==================== |
---|
| 253 | |
---|
| 254 | Mapper arguments are specified using the ``__mapper_args__`` class variable, |
---|
| 255 | which is a dictionary that accepts the same names as the :class:`~sqlalchemy.orm.mapper` |
---|
| 256 | function accepts as keywords:: |
---|
| 257 | |
---|
| 258 | class Widget(Base): |
---|
| 259 | __tablename__ = 'widgets' |
---|
| 260 | id = Column(Integer, primary_key=True) |
---|
| 261 | |
---|
| 262 | __mapper_args__ = {'extension': MyWidgetExtension()} |
---|
| 263 | |
---|
| 264 | Inheritance Configuration |
---|
| 265 | ========================= |
---|
| 266 | |
---|
| 267 | Declarative supports all three forms of inheritance as intuitively |
---|
| 268 | as possible. The ``inherits`` mapper keyword argument is not needed, |
---|
| 269 | as declarative will determine this from the class itself. The various |
---|
| 270 | "polymorphic" keyword arguments are specified using ``__mapper_args__``. |
---|
| 271 | |
---|
| 272 | Joined Table Inheritance |
---|
| 273 | ~~~~~~~~~~~~~~~~~~~~~~~~ |
---|
| 274 | |
---|
| 275 | Joined table inheritance is defined as a subclass that defines its own |
---|
| 276 | table:: |
---|
| 277 | |
---|
| 278 | class Person(Base): |
---|
| 279 | __tablename__ = 'people' |
---|
| 280 | id = Column(Integer, primary_key=True) |
---|
| 281 | discriminator = Column('type', String(50)) |
---|
| 282 | __mapper_args__ = {'polymorphic_on': discriminator} |
---|
| 283 | |
---|
| 284 | class Engineer(Person): |
---|
| 285 | __tablename__ = 'engineers' |
---|
| 286 | __mapper_args__ = {'polymorphic_identity': 'engineer'} |
---|
| 287 | id = Column(Integer, ForeignKey('people.id'), primary_key=True) |
---|
| 288 | primary_language = Column(String(50)) |
---|
| 289 | |
---|
| 290 | Note that above, the ``Engineer.id`` attribute, since it shares the same |
---|
| 291 | attribute name as the ``Person.id`` attribute, will in fact represent the ``people.id`` |
---|
| 292 | and ``engineers.id`` columns together, and will render inside a query as ``"people.id"``. |
---|
| 293 | To provide the ``Engineer`` class with an attribute that represents only the |
---|
| 294 | ``engineers.id`` column, give it a different attribute name:: |
---|
| 295 | |
---|
| 296 | class Engineer(Person): |
---|
| 297 | __tablename__ = 'engineers' |
---|
| 298 | __mapper_args__ = {'polymorphic_identity': 'engineer'} |
---|
| 299 | engineer_id = Column('id', Integer, ForeignKey('people.id'), primary_key=True) |
---|
| 300 | primary_language = Column(String(50)) |
---|
| 301 | |
---|
| 302 | Single Table Inheritance |
---|
| 303 | ~~~~~~~~~~~~~~~~~~~~~~~~ |
---|
| 304 | |
---|
| 305 | Single table inheritance is defined as a subclass that does not have its |
---|
| 306 | own table; you just leave out the ``__table__`` and ``__tablename__`` attributes:: |
---|
| 307 | |
---|
| 308 | class Person(Base): |
---|
| 309 | __tablename__ = 'people' |
---|
| 310 | id = Column(Integer, primary_key=True) |
---|
| 311 | discriminator = Column('type', String(50)) |
---|
| 312 | __mapper_args__ = {'polymorphic_on': discriminator} |
---|
| 313 | |
---|
| 314 | class Engineer(Person): |
---|
| 315 | __mapper_args__ = {'polymorphic_identity': 'engineer'} |
---|
| 316 | primary_language = Column(String(50)) |
---|
| 317 | |
---|
| 318 | When the above mappers are configured, the ``Person`` class is mapped to the ``people`` |
---|
| 319 | table *before* the ``primary_language`` column is defined, and this column will not be included |
---|
| 320 | in its own mapping. When ``Engineer`` then defines the ``primary_language`` |
---|
| 321 | column, the column is added to the ``people`` table so that it is included in the mapping |
---|
| 322 | for ``Engineer`` and is also part of the table's full set of columns. |
---|
| 323 | Columns which are not mapped to ``Person`` are also excluded from any other |
---|
| 324 | single or joined inheriting classes using the ``exclude_properties`` mapper argument. |
---|
| 325 | Below, ``Manager`` will have all the attributes of ``Person`` and ``Manager`` but *not* |
---|
| 326 | the ``primary_language`` attribute of ``Engineer``:: |
---|
| 327 | |
---|
| 328 | class Manager(Person): |
---|
| 329 | __mapper_args__ = {'polymorphic_identity': 'manager'} |
---|
| 330 | golf_swing = Column(String(50)) |
---|
| 331 | |
---|
| 332 | The attribute exclusion logic is provided by the ``exclude_properties`` mapper argument, |
---|
| 333 | and declarative's default behavior can be disabled by passing an explicit |
---|
| 334 | ``exclude_properties`` collection (empty or otherwise) to the ``__mapper_args__``. |
---|
| 335 | |
---|
| 336 | Concrete Table Inheritance |
---|
| 337 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ |
---|
| 338 | |
---|
| 339 | Concrete is defined as a subclass which has its own table and sets the |
---|
| 340 | ``concrete`` keyword argument to ``True``:: |
---|
| 341 | |
---|
| 342 | class Person(Base): |
---|
| 343 | __tablename__ = 'people' |
---|
| 344 | id = Column(Integer, primary_key=True) |
---|
| 345 | name = Column(String(50)) |
---|
| 346 | |
---|
| 347 | class Engineer(Person): |
---|
| 348 | __tablename__ = 'engineers' |
---|
| 349 | __mapper_args__ = {'concrete':True} |
---|
| 350 | id = Column(Integer, primary_key=True) |
---|
| 351 | primary_language = Column(String(50)) |
---|
| 352 | name = Column(String(50)) |
---|
| 353 | |
---|
| 354 | Usage of an abstract base class is a little less straightforward as it requires |
---|
| 355 | usage of :func:`~sqlalchemy.orm.util.polymorphic_union`:: |
---|
| 356 | |
---|
| 357 | engineers = Table('engineers', Base.metadata, |
---|
| 358 | Column('id', Integer, primary_key=True), |
---|
| 359 | Column('name', String(50)), |
---|
| 360 | Column('primary_language', String(50)) |
---|
| 361 | ) |
---|
| 362 | managers = Table('managers', Base.metadata, |
---|
| 363 | Column('id', Integer, primary_key=True), |
---|
| 364 | Column('name', String(50)), |
---|
| 365 | Column('golf_swing', String(50)) |
---|
| 366 | ) |
---|
| 367 | |
---|
| 368 | punion = polymorphic_union({ |
---|
| 369 | 'engineer':engineers, |
---|
| 370 | 'manager':managers |
---|
| 371 | }, 'type', 'punion') |
---|
| 372 | |
---|
| 373 | class Person(Base): |
---|
| 374 | __table__ = punion |
---|
| 375 | __mapper_args__ = {'polymorphic_on':punion.c.type} |
---|
| 376 | |
---|
| 377 | class Engineer(Person): |
---|
| 378 | __table__ = engineers |
---|
| 379 | __mapper_args__ = {'polymorphic_identity':'engineer', 'concrete':True} |
---|
| 380 | |
---|
| 381 | class Manager(Person): |
---|
| 382 | __table__ = managers |
---|
| 383 | __mapper_args__ = {'polymorphic_identity':'manager', 'concrete':True} |
---|
| 384 | |
---|
| 385 | Class Usage |
---|
| 386 | =========== |
---|
| 387 | |
---|
| 388 | As a convenience feature, the :func:`declarative_base` sets a default |
---|
| 389 | constructor on classes which takes keyword arguments, and assigns them to the |
---|
| 390 | named attributes:: |
---|
| 391 | |
---|
| 392 | e = Engineer(primary_language='python') |
---|
| 393 | |
---|
| 394 | Note that ``declarative`` has no integration built in with sessions, and is |
---|
| 395 | only intended as an optional syntax for the regular usage of mappers and Table |
---|
| 396 | objects. A typical application setup using :func:`~sqlalchemy.orm.scoped_session` might look |
---|
| 397 | like:: |
---|
| 398 | |
---|
| 399 | engine = create_engine('postgres://scott:tiger@localhost/test') |
---|
| 400 | Session = scoped_session(sessionmaker(autocommit=False, |
---|
| 401 | autoflush=False, |
---|
| 402 | bind=engine)) |
---|
| 403 | Base = declarative_base() |
---|
| 404 | |
---|
| 405 | Mapped instances then make usage of :class:`~sqlalchemy.orm.session.Session` in the usual way. |
---|
| 406 | |
---|
| 407 | """ |
---|
| 408 | |
---|
| 409 | from sqlalchemy.schema import Table, Column, MetaData |
---|
| 410 | from sqlalchemy.orm import synonym as _orm_synonym, mapper, comparable_property, class_mapper |
---|
| 411 | from sqlalchemy.orm.interfaces import MapperProperty |
---|
| 412 | from sqlalchemy.orm.properties import PropertyLoader, ColumnProperty |
---|
| 413 | from sqlalchemy.orm.util import _is_mapped_class |
---|
| 414 | from sqlalchemy import util, exceptions |
---|
| 415 | from sqlalchemy.sql import util as sql_util |
---|
| 416 | |
---|
| 417 | |
---|
| 418 | __all__ = 'declarative_base', 'synonym_for', 'comparable_using', 'instrument_declarative' |
---|
| 419 | |
---|
| 420 | def instrument_declarative(cls, registry, metadata): |
---|
| 421 | """Given a class, configure the class declaratively, |
---|
| 422 | using the given registry (any dictionary) and MetaData object. |
---|
| 423 | This operation does not assume any kind of class hierarchy. |
---|
| 424 | |
---|
| 425 | """ |
---|
| 426 | if '_decl_class_registry' in cls.__dict__: |
---|
| 427 | raise exceptions.InvalidRequestError("Class %r already has been instrumented declaratively" % cls) |
---|
| 428 | cls._decl_class_registry = registry |
---|
| 429 | cls.metadata = metadata |
---|
| 430 | _as_declarative(cls, cls.__name__, cls.__dict__) |
---|
| 431 | |
---|
| 432 | def _as_declarative(cls, classname, dict_): |
---|
| 433 | cls._decl_class_registry[classname] = cls |
---|
| 434 | our_stuff = util.OrderedDict() |
---|
| 435 | for k in dict_: |
---|
| 436 | value = dict_[k] |
---|
| 437 | if (isinstance(value, tuple) and len(value) == 1 and |
---|
| 438 | isinstance(value[0], (Column, MapperProperty))): |
---|
| 439 | util.warn("Ignoring declarative-like tuple value of attribute " |
---|
| 440 | "%s: possibly a copy-and-paste error with a comma " |
---|
| 441 | "left at the end of the line?" % k) |
---|
| 442 | continue |
---|
| 443 | if not isinstance(value, (Column, MapperProperty)): |
---|
| 444 | continue |
---|
| 445 | prop = _deferred_relation(cls, value) |
---|
| 446 | our_stuff[k] = prop |
---|
| 447 | |
---|
| 448 | # set up attributes in the order they were created |
---|
| 449 | our_stuff.sort(key=lambda key: our_stuff[key]._creation_order) |
---|
| 450 | |
---|
| 451 | # extract columns from the class dict |
---|
| 452 | cols = [] |
---|
| 453 | for key, c in our_stuff.iteritems(): |
---|
| 454 | if isinstance(c, ColumnProperty): |
---|
| 455 | for col in c.columns: |
---|
| 456 | if isinstance(col, Column) and col.table is None: |
---|
| 457 | _undefer_column_name(key, col) |
---|
| 458 | cols.append(col) |
---|
| 459 | elif isinstance(c, Column): |
---|
| 460 | _undefer_column_name(key, c) |
---|
| 461 | cols.append(c) |
---|
| 462 | # if the column is the same name as the key, |
---|
| 463 | # remove it from the explicit properties dict. |
---|
| 464 | # the normal rules for assigning column-based properties |
---|
| 465 | # will take over, including precedence of columns |
---|
| 466 | # in multi-column ColumnProperties. |
---|
| 467 | if key == c.key: |
---|
| 468 | del our_stuff[key] |
---|
| 469 | |
---|
| 470 | table = None |
---|
| 471 | if '__table__' not in cls.__dict__: |
---|
| 472 | if '__tablename__' in cls.__dict__: |
---|
| 473 | tablename = cls.__tablename__ |
---|
| 474 | |
---|
| 475 | table_args = cls.__dict__.get('__table_args__') |
---|
| 476 | if isinstance(table_args, dict): |
---|
| 477 | args, table_kw = (), table_args |
---|
| 478 | elif isinstance(table_args, tuple): |
---|
| 479 | args = table_args[0:-1] |
---|
| 480 | table_kw = table_args[-1] |
---|
| 481 | if len(table_args) < 2 or not isinstance(table_kw, dict): |
---|
| 482 | raise exceptions.ArgumentError( |
---|
| 483 | "Tuple form of __table_args__ is " |
---|
| 484 | "(arg1, arg2, arg3, ..., {'kw1':val1, 'kw2':val2, ...})" |
---|
| 485 | ) |
---|
| 486 | else: |
---|
| 487 | args, table_kw = (), {} |
---|
| 488 | |
---|
| 489 | autoload = cls.__dict__.get('__autoload__') |
---|
| 490 | if autoload: |
---|
| 491 | table_kw['autoload'] = True |
---|
| 492 | |
---|
| 493 | cls.__table__ = table = Table(tablename, cls.metadata, |
---|
| 494 | *(tuple(cols) + tuple(args)), **table_kw) |
---|
| 495 | else: |
---|
| 496 | table = cls.__table__ |
---|
| 497 | if cols: |
---|
| 498 | for c in cols: |
---|
| 499 | if not table.c.contains_column(c): |
---|
| 500 | raise exceptions.ArgumentError("Can't add additional column %r when specifying __table__" % key) |
---|
| 501 | |
---|
| 502 | mapper_args = getattr(cls, '__mapper_args__', {}) |
---|
| 503 | if 'inherits' not in mapper_args: |
---|
| 504 | for c in cls.__bases__: |
---|
| 505 | if _is_mapped_class(c): |
---|
| 506 | mapper_args['inherits'] = cls._decl_class_registry.get(c.__name__, None) |
---|
| 507 | break |
---|
| 508 | |
---|
| 509 | if hasattr(cls, '__mapper_cls__'): |
---|
| 510 | mapper_cls = util.unbound_method_to_callable(cls.__mapper_cls__) |
---|
| 511 | else: |
---|
| 512 | mapper_cls = mapper |
---|
| 513 | |
---|
| 514 | if not table and 'inherits' not in mapper_args: |
---|
| 515 | raise exceptions.InvalidRequestError("Class %r does not have a __table__ or __tablename__ " |
---|
| 516 | "specified and does not inherit from an existing table-mapped class." % cls) |
---|
| 517 | |
---|
| 518 | elif 'inherits' in mapper_args and not mapper_args.get('concrete', False): |
---|
| 519 | inherited_mapper = class_mapper(mapper_args['inherits'], compile=False) |
---|
| 520 | inherited_table = inherited_mapper.local_table |
---|
| 521 | if 'inherit_condition' not in mapper_args and table: |
---|
| 522 | # figure out the inherit condition with relaxed rules |
---|
| 523 | # about nonexistent tables, to allow for ForeignKeys to |
---|
| 524 | # not-yet-defined tables (since we know for sure that our |
---|
| 525 | # parent table is defined within the same MetaData) |
---|
| 526 | mapper_args['inherit_condition'] = sql_util.join_condition( |
---|
| 527 | mapper_args['inherits'].__table__, table, |
---|
| 528 | ignore_nonexistent_tables=True) |
---|
| 529 | |
---|
| 530 | if not table: |
---|
| 531 | # single table inheritance. |
---|
| 532 | # ensure no table args |
---|
| 533 | table_args = cls.__dict__.get('__table_args__') |
---|
| 534 | if table_args is not None: |
---|
| 535 | raise exceptions.ArgumentError("Can't place __table_args__ on an inherited class with no table.") |
---|
| 536 | |
---|
| 537 | # add any columns declared here to the inherited table. |
---|
| 538 | for c in cols: |
---|
| 539 | if c.primary_key: |
---|
| 540 | raise exceptions.ArgumentError("Can't place primary key columns on an inherited class with no table.") |
---|
| 541 | inherited_table.append_column(c) |
---|
| 542 | |
---|
| 543 | # single or joined inheritance |
---|
| 544 | # exclude any cols on the inherited table which are not mapped on the parent class, to avoid |
---|
| 545 | # mapping columns specific to sibling/nephew classes |
---|
| 546 | inherited_mapper = class_mapper(mapper_args['inherits'], compile=False) |
---|
| 547 | inherited_table = inherited_mapper.local_table |
---|
| 548 | |
---|
| 549 | if 'exclude_properties' not in mapper_args: |
---|
| 550 | mapper_args['exclude_properties'] = exclude_properties = \ |
---|
| 551 | set([c.key for c in inherited_table.c if c not in inherited_mapper._columntoproperty]) |
---|
| 552 | exclude_properties.difference_update([c.key for c in cols]) |
---|
| 553 | |
---|
| 554 | cls.__mapper__ = mapper_cls(cls, table, properties=our_stuff, **mapper_args) |
---|
| 555 | |
---|
| 556 | class DeclarativeMeta(type): |
---|
| 557 | def __init__(cls, classname, bases, dict_): |
---|
| 558 | if '_decl_class_registry' in cls.__dict__: |
---|
| 559 | return type.__init__(cls, classname, bases, dict_) |
---|
| 560 | |
---|
| 561 | _as_declarative(cls, classname, dict_) |
---|
| 562 | return type.__init__(cls, classname, bases, dict_) |
---|
| 563 | |
---|
| 564 | def __setattr__(cls, key, value): |
---|
| 565 | if '__mapper__' in cls.__dict__: |
---|
| 566 | if isinstance(value, Column): |
---|
| 567 | _undefer_column_name(key, value) |
---|
| 568 | cls.__table__.append_column(value) |
---|
| 569 | cls.__mapper__.add_property(key, value) |
---|
| 570 | elif isinstance(value, ColumnProperty): |
---|
| 571 | for col in value.columns: |
---|
| 572 | if isinstance(col, Column) and col.table is None: |
---|
| 573 | _undefer_column_name(key, col) |
---|
| 574 | cls.__table__.append_column(col) |
---|
| 575 | cls.__mapper__.add_property(key, value) |
---|
| 576 | elif isinstance(value, MapperProperty): |
---|
| 577 | cls.__mapper__.add_property(key, _deferred_relation(cls, value)) |
---|
| 578 | else: |
---|
| 579 | type.__setattr__(cls, key, value) |
---|
| 580 | else: |
---|
| 581 | type.__setattr__(cls, key, value) |
---|
| 582 | |
---|
| 583 | |
---|
| 584 | class _GetColumns(object): |
---|
| 585 | def __init__(self, cls): |
---|
| 586 | self.cls = cls |
---|
| 587 | def __getattr__(self, key): |
---|
| 588 | mapper = class_mapper(self.cls, compile=False) |
---|
| 589 | if not mapper: |
---|
| 590 | return getattr(self.cls, key) |
---|
| 591 | else: |
---|
| 592 | prop = mapper.get_property(key) |
---|
| 593 | if not isinstance(prop, ColumnProperty): |
---|
| 594 | raise exceptions.InvalidRequestError("Property %r is not an instance of ColumnProperty (i.e. does not correspnd directly to a Column)." % key) |
---|
| 595 | return prop.columns[0] |
---|
| 596 | |
---|
| 597 | |
---|
| 598 | def _deferred_relation(cls, prop): |
---|
| 599 | def resolve_arg(arg): |
---|
| 600 | import sqlalchemy |
---|
| 601 | |
---|
| 602 | def access_cls(key): |
---|
| 603 | if key in cls._decl_class_registry: |
---|
| 604 | return _GetColumns(cls._decl_class_registry[key]) |
---|
| 605 | elif key in cls.metadata.tables: |
---|
| 606 | return cls.metadata.tables[key] |
---|
| 607 | else: |
---|
| 608 | return sqlalchemy.__dict__[key] |
---|
| 609 | |
---|
| 610 | d = util.PopulateDict(access_cls) |
---|
| 611 | def return_cls(): |
---|
| 612 | try: |
---|
| 613 | x = eval(arg, globals(), d) |
---|
| 614 | |
---|
| 615 | if isinstance(x, _GetColumns): |
---|
| 616 | return x.cls |
---|
| 617 | else: |
---|
| 618 | return x |
---|
| 619 | except NameError, n: |
---|
| 620 | raise exceptions.InvalidRequestError( |
---|
| 621 | "When compiling mapper %s, expression %r failed to locate a name (%r). " |
---|
| 622 | "If this is a class name, consider adding this relation() to the %r " |
---|
| 623 | "class after both dependent classes have been defined." % ( |
---|
| 624 | prop.parent, arg, n.args[0], cls)) |
---|
| 625 | return return_cls |
---|
| 626 | |
---|
| 627 | if isinstance(prop, PropertyLoader): |
---|
| 628 | for attr in ('argument', 'order_by', 'primaryjoin', 'secondaryjoin', 'secondary', '_foreign_keys', 'remote_side'): |
---|
| 629 | v = getattr(prop, attr) |
---|
| 630 | if isinstance(v, basestring): |
---|
| 631 | setattr(prop, attr, resolve_arg(v)) |
---|
| 632 | |
---|
| 633 | if prop.backref: |
---|
| 634 | for attr in ('primaryjoin', 'secondaryjoin', 'secondary', 'foreign_keys', 'remote_side', 'order_by'): |
---|
| 635 | if attr in prop.backref.kwargs and isinstance(prop.backref.kwargs[attr], basestring): |
---|
| 636 | prop.backref.kwargs[attr] = resolve_arg(prop.backref.kwargs[attr]) |
---|
| 637 | |
---|
| 638 | |
---|
| 639 | return prop |
---|
| 640 | |
---|
| 641 | def synonym_for(name, map_column=False): |
---|
| 642 | """Decorator, make a Python @property a query synonym for a column. |
---|
| 643 | |
---|
| 644 | A decorator version of :func:`~sqlalchemy.orm.synonym`. The function being |
---|
| 645 | decorated is the 'descriptor', otherwise passes its arguments through |
---|
| 646 | to synonym():: |
---|
| 647 | |
---|
| 648 | @synonym_for('col') |
---|
| 649 | @property |
---|
| 650 | def prop(self): |
---|
| 651 | return 'special sauce' |
---|
| 652 | |
---|
| 653 | The regular ``synonym()`` is also usable directly in a declarative setting |
---|
| 654 | and may be convenient for read/write properties:: |
---|
| 655 | |
---|
| 656 | prop = synonym('col', descriptor=property(_read_prop, _write_prop)) |
---|
| 657 | |
---|
| 658 | """ |
---|
| 659 | def decorate(fn): |
---|
| 660 | return _orm_synonym(name, map_column=map_column, descriptor=fn) |
---|
| 661 | return decorate |
---|
| 662 | |
---|
| 663 | def comparable_using(comparator_factory): |
---|
| 664 | """Decorator, allow a Python @property to be used in query criteria. |
---|
| 665 | |
---|
| 666 | A decorator front end to :func:`~sqlalchemy.orm.comparable_property`, passes |
---|
| 667 | through the comparator_factory and the function being decorated:: |
---|
| 668 | |
---|
| 669 | @comparable_using(MyComparatorType) |
---|
| 670 | @property |
---|
| 671 | def prop(self): |
---|
| 672 | return 'special sauce' |
---|
| 673 | |
---|
| 674 | The regular ``comparable_property()`` is also usable directly in a |
---|
| 675 | declarative setting and may be convenient for read/write properties:: |
---|
| 676 | |
---|
| 677 | prop = comparable_property(MyComparatorType) |
---|
| 678 | |
---|
| 679 | """ |
---|
| 680 | def decorate(fn): |
---|
| 681 | return comparable_property(comparator_factory, fn) |
---|
| 682 | return decorate |
---|
| 683 | |
---|
| 684 | def _declarative_constructor(self, **kwargs): |
---|
| 685 | """A simple constructor that allows initialization from kwargs. |
---|
| 686 | |
---|
| 687 | Sets kwargs on the constructed instance. Only keys that are present as |
---|
| 688 | attributes of type(self) are allowed (for example, any mapped column or |
---|
| 689 | relation). |
---|
| 690 | |
---|
| 691 | """ |
---|
| 692 | for k in kwargs: |
---|
| 693 | if not hasattr(type(self), k): |
---|
| 694 | raise TypeError( |
---|
| 695 | "%r is an invalid keyword argument for %s" % |
---|
| 696 | (k, type(self).__name__)) |
---|
| 697 | setattr(self, k, kwargs[k]) |
---|
| 698 | _declarative_constructor.__name__ = '__init__' |
---|
| 699 | |
---|
| 700 | def declarative_base(bind=None, metadata=None, mapper=None, cls=object, |
---|
| 701 | name='Base', constructor=_declarative_constructor, |
---|
| 702 | metaclass=DeclarativeMeta, engine=None): |
---|
| 703 | """Construct a base class for declarative class definitions. |
---|
| 704 | |
---|
| 705 | The new base class will be given a metaclass that invokes |
---|
| 706 | :func:`instrument_declarative()` upon each subclass definition, and routes |
---|
| 707 | later Column- and Mapper-related attribute assignments made on the class |
---|
| 708 | into Table and Mapper assignments. |
---|
| 709 | |
---|
| 710 | :param bind: An optional :class:`~sqlalchemy.engine.base.Connectable`, will be assigned |
---|
| 711 | the ``bind`` attribute on the :class:`~sqlalchemy.MetaData` instance. |
---|
| 712 | The `engine` keyword argument is a deprecated synonym for `bind`. |
---|
| 713 | |
---|
| 714 | :param metadata: |
---|
| 715 | An optional :class:`~sqlalchemy.MetaData` instance. All :class:`~sqlalchemy.schema.Table` |
---|
| 716 | objects implicitly declared by |
---|
| 717 | subclasses of the base will share this MetaData. A MetaData instance |
---|
| 718 | will be create if none is provided. The MetaData instance will be |
---|
| 719 | available via the `metadata` attribute of the generated declarative |
---|
| 720 | base class. |
---|
| 721 | |
---|
| 722 | :param mapper: |
---|
| 723 | An optional callable, defaults to :func:`~sqlalchemy.orm.mapper`. Will be |
---|
| 724 | used to map subclasses to their Tables. |
---|
| 725 | |
---|
| 726 | :param cls: |
---|
| 727 | Defaults to :class:`object`. A type to use as the base for the generated |
---|
| 728 | declarative base class. May be a type or tuple of types. |
---|
| 729 | |
---|
| 730 | :param name: |
---|
| 731 | Defaults to ``Base``. The display name for the generated |
---|
| 732 | class. Customizing this is not required, but can improve clarity in |
---|
| 733 | tracebacks and debugging. |
---|
| 734 | |
---|
| 735 | :param constructor: |
---|
| 736 | Defaults to declarative._declarative_constructor, an __init__ |
---|
| 737 | implementation that assigns \**kwargs for declared fields and relations |
---|
| 738 | to an instance. If ``None`` is supplied, no __init__ will be installed |
---|
| 739 | and construction will fall back to cls.__init__ with normal Python |
---|
| 740 | semantics. |
---|
| 741 | |
---|
| 742 | :param metaclass: |
---|
| 743 | Defaults to :class:`DeclarativeMeta`. A metaclass or __metaclass__ |
---|
| 744 | compatible callable to use as the meta type of the generated |
---|
| 745 | declarative base class. |
---|
| 746 | |
---|
| 747 | """ |
---|
| 748 | lcl_metadata = metadata or MetaData() |
---|
| 749 | if bind or engine: |
---|
| 750 | lcl_metadata.bind = bind or engine |
---|
| 751 | |
---|
| 752 | bases = not isinstance(cls, tuple) and (cls,) or cls |
---|
| 753 | class_dict = dict(_decl_class_registry=dict(), |
---|
| 754 | metadata=lcl_metadata) |
---|
| 755 | |
---|
| 756 | if constructor: |
---|
| 757 | class_dict['__init__'] = constructor |
---|
| 758 | if mapper: |
---|
| 759 | class_dict['__mapper_cls__'] = mapper |
---|
| 760 | |
---|
| 761 | return metaclass(name, bases, class_dict) |
---|
| 762 | |
---|
| 763 | def _undefer_column_name(key, column): |
---|
| 764 | if column.key is None: |
---|
| 765 | column.key = key |
---|
| 766 | if column.name is None: |
---|
| 767 | column.name = key |
---|