api.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696
  1. # ext/declarative/api.py
  2. # Copyright (C) 2005-2017 the SQLAlchemy authors and contributors
  3. # <see AUTHORS file>
  4. #
  5. # This module is part of SQLAlchemy and is released under
  6. # the MIT License: http://www.opensource.org/licenses/mit-license.php
  7. """Public API functions and helpers for declarative."""
  8. from ...schema import Table, MetaData, Column
  9. from ...orm import synonym as _orm_synonym, \
  10. comparable_property,\
  11. interfaces, properties, attributes
  12. from ...orm.util import polymorphic_union
  13. from ...orm.base import _mapper_or_none
  14. from ...util import OrderedDict, hybridmethod, hybridproperty
  15. from ... import util
  16. from ... import exc
  17. import weakref
  18. from .base import _as_declarative, \
  19. _declarative_constructor,\
  20. _DeferredMapperConfig, _add_attribute
  21. from .clsregistry import _class_resolver
  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. if '_decl_class_registry' in cls.__dict__:
  28. raise exc.InvalidRequestError(
  29. "Class %r already has been "
  30. "instrumented declaratively" % cls)
  31. cls._decl_class_registry = registry
  32. cls.metadata = metadata
  33. _as_declarative(cls, cls.__name__, cls.__dict__)
  34. def has_inherited_table(cls):
  35. """Given a class, return True if any of the classes it inherits from has a
  36. mapped table, otherwise return False.
  37. This is used in declarative mixins to build attributes that behave
  38. differently for the base class vs. a subclass in an inheritance
  39. hierarchy.
  40. .. seealso::
  41. :ref:`decl_mixin_inheritance`
  42. """
  43. for class_ in cls.__mro__[1:]:
  44. if getattr(class_, '__table__', None) is not None:
  45. return True
  46. return False
  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. def __setattr__(cls, key, value):
  53. _add_attribute(cls, key, value)
  54. def synonym_for(name, map_column=False):
  55. """Decorator, make a Python @property a query synonym for a column.
  56. A decorator version of :func:`~sqlalchemy.orm.synonym`. The function being
  57. decorated is the 'descriptor', otherwise passes its arguments through to
  58. synonym()::
  59. @synonym_for('col')
  60. @property
  61. def prop(self):
  62. return 'special sauce'
  63. The regular ``synonym()`` is also usable directly in a declarative setting
  64. and may be convenient for read/write properties::
  65. prop = synonym('col', descriptor=property(_read_prop, _write_prop))
  66. """
  67. def decorate(fn):
  68. return _orm_synonym(name, map_column=map_column, descriptor=fn)
  69. return decorate
  70. def comparable_using(comparator_factory):
  71. """Decorator, allow a Python @property to be used in query criteria.
  72. This is a decorator front end to
  73. :func:`~sqlalchemy.orm.comparable_property` that passes
  74. through the comparator_factory and the function being decorated::
  75. @comparable_using(MyComparatorType)
  76. @property
  77. def prop(self):
  78. return 'special sauce'
  79. The regular ``comparable_property()`` is also usable directly in a
  80. declarative setting and may be convenient for read/write properties::
  81. prop = comparable_property(MyComparatorType)
  82. """
  83. def decorate(fn):
  84. return comparable_property(comparator_factory, fn)
  85. return decorate
  86. class declared_attr(interfaces._MappedAttribute, property):
  87. """Mark a class-level method as representing the definition of
  88. a mapped property or special declarative member name.
  89. @declared_attr turns the attribute into a scalar-like
  90. property that can be invoked from the uninstantiated class.
  91. Declarative treats attributes specifically marked with
  92. @declared_attr as returning a construct that is specific
  93. to mapping or declarative table configuration. The name
  94. of the attribute is that of what the non-dynamic version
  95. of the attribute would be.
  96. @declared_attr is more often than not applicable to mixins,
  97. to define relationships that are to be applied to different
  98. implementors of the class::
  99. class ProvidesUser(object):
  100. "A mixin that adds a 'user' relationship to classes."
  101. @declared_attr
  102. def user(self):
  103. return relationship("User")
  104. It also can be applied to mapped classes, such as to provide
  105. a "polymorphic" scheme for inheritance::
  106. class Employee(Base):
  107. id = Column(Integer, primary_key=True)
  108. type = Column(String(50), nullable=False)
  109. @declared_attr
  110. def __tablename__(cls):
  111. return cls.__name__.lower()
  112. @declared_attr
  113. def __mapper_args__(cls):
  114. if cls.__name__ == 'Employee':
  115. return {
  116. "polymorphic_on":cls.type,
  117. "polymorphic_identity":"Employee"
  118. }
  119. else:
  120. return {"polymorphic_identity":cls.__name__}
  121. .. versionchanged:: 0.8 :class:`.declared_attr` can be used with
  122. non-ORM or extension attributes, such as user-defined attributes
  123. or :func:`.association_proxy` objects, which will be assigned
  124. to the class at class construction time.
  125. """
  126. def __init__(self, fget, cascading=False):
  127. super(declared_attr, self).__init__(fget)
  128. self.__doc__ = fget.__doc__
  129. self._cascading = cascading
  130. def __get__(desc, self, cls):
  131. reg = cls.__dict__.get('_sa_declared_attr_reg', None)
  132. if reg is None:
  133. manager = attributes.manager_of_class(cls)
  134. if manager is None:
  135. util.warn(
  136. "Unmanaged access of declarative attribute %s from "
  137. "non-mapped class %s" %
  138. (desc.fget.__name__, cls.__name__))
  139. return desc.fget(cls)
  140. elif desc in reg:
  141. return reg[desc]
  142. else:
  143. reg[desc] = obj = desc.fget(cls)
  144. return obj
  145. @hybridmethod
  146. def _stateful(cls, **kw):
  147. return _stateful_declared_attr(**kw)
  148. @hybridproperty
  149. def cascading(cls):
  150. """Mark a :class:`.declared_attr` as cascading.
  151. This is a special-use modifier which indicates that a column
  152. or MapperProperty-based declared attribute should be configured
  153. distinctly per mapped subclass, within a mapped-inheritance scenario.
  154. Below, both MyClass as well as MySubClass will have a distinct
  155. ``id`` Column object established::
  156. class HasIdMixin(object):
  157. @declared_attr.cascading
  158. def id(cls):
  159. if has_inherited_table(cls):
  160. return Column(ForeignKey('myclass.id'), primary_key=True)
  161. else:
  162. return Column(Integer, primary_key=True)
  163. class MyClass(HasIdMixin, Base):
  164. __tablename__ = 'myclass'
  165. # ...
  166. class MySubClass(MyClass):
  167. ""
  168. # ...
  169. The behavior of the above configuration is that ``MySubClass``
  170. will refer to both its own ``id`` column as well as that of
  171. ``MyClass`` underneath the attribute named ``some_id``.
  172. .. seealso::
  173. :ref:`declarative_inheritance`
  174. :ref:`mixin_inheritance_columns`
  175. """
  176. return cls._stateful(cascading=True)
  177. class _stateful_declared_attr(declared_attr):
  178. def __init__(self, **kw):
  179. self.kw = kw
  180. def _stateful(self, **kw):
  181. new_kw = self.kw.copy()
  182. new_kw.update(kw)
  183. return _stateful_declared_attr(**new_kw)
  184. def __call__(self, fn):
  185. return declared_attr(fn, **self.kw)
  186. def declarative_base(bind=None, metadata=None, mapper=None, cls=object,
  187. name='Base', constructor=_declarative_constructor,
  188. class_registry=None,
  189. metaclass=DeclarativeMeta):
  190. r"""Construct a base class for declarative class definitions.
  191. The new base class will be given a metaclass that produces
  192. appropriate :class:`~sqlalchemy.schema.Table` objects and makes
  193. the appropriate :func:`~sqlalchemy.orm.mapper` calls based on the
  194. information provided declaratively in the class and any subclasses
  195. of the class.
  196. :param bind: An optional
  197. :class:`~sqlalchemy.engine.Connectable`, will be assigned
  198. the ``bind`` attribute on the :class:`~sqlalchemy.schema.MetaData`
  199. instance.
  200. :param metadata:
  201. An optional :class:`~sqlalchemy.schema.MetaData` instance. All
  202. :class:`~sqlalchemy.schema.Table` objects implicitly declared by
  203. subclasses of the base will share this MetaData. A MetaData instance
  204. will be created if none is provided. The
  205. :class:`~sqlalchemy.schema.MetaData` instance will be available via the
  206. `metadata` attribute of the generated declarative base class.
  207. :param mapper:
  208. An optional callable, defaults to :func:`~sqlalchemy.orm.mapper`. Will
  209. be used to map subclasses to their Tables.
  210. :param cls:
  211. Defaults to :class:`object`. A type to use as the base for the generated
  212. declarative base class. May be a class or tuple of classes.
  213. :param name:
  214. Defaults to ``Base``. The display name for the generated
  215. class. Customizing this is not required, but can improve clarity in
  216. tracebacks and debugging.
  217. :param constructor:
  218. Defaults to
  219. :func:`~sqlalchemy.ext.declarative.base._declarative_constructor`, an
  220. __init__ implementation that assigns \**kwargs for declared
  221. fields and relationships to an instance. If ``None`` is supplied,
  222. no __init__ will be provided and construction will fall back to
  223. cls.__init__ by way of the normal Python semantics.
  224. :param class_registry: optional dictionary that will serve as the
  225. registry of class names-> mapped classes when string names
  226. are used to identify classes inside of :func:`.relationship`
  227. and others. Allows two or more declarative base classes
  228. to share the same registry of class names for simplified
  229. inter-base relationships.
  230. :param metaclass:
  231. Defaults to :class:`.DeclarativeMeta`. A metaclass or __metaclass__
  232. compatible callable to use as the meta type of the generated
  233. declarative base class.
  234. .. versionchanged:: 1.1 if :paramref:`.declarative_base.cls` is a single class (rather
  235. than a tuple), the constructed base class will inherit its docstring.
  236. .. seealso::
  237. :func:`.as_declarative`
  238. """
  239. lcl_metadata = metadata or MetaData()
  240. if bind:
  241. lcl_metadata.bind = bind
  242. if class_registry is None:
  243. class_registry = weakref.WeakValueDictionary()
  244. bases = not isinstance(cls, tuple) and (cls,) or cls
  245. class_dict = dict(_decl_class_registry=class_registry,
  246. metadata=lcl_metadata)
  247. if isinstance(cls, type):
  248. class_dict['__doc__'] = cls.__doc__
  249. if constructor:
  250. class_dict['__init__'] = constructor
  251. if mapper:
  252. class_dict['__mapper_cls__'] = mapper
  253. return metaclass(name, bases, class_dict)
  254. def as_declarative(**kw):
  255. """
  256. Class decorator for :func:`.declarative_base`.
  257. Provides a syntactical shortcut to the ``cls`` argument
  258. sent to :func:`.declarative_base`, allowing the base class
  259. to be converted in-place to a "declarative" base::
  260. from sqlalchemy.ext.declarative import as_declarative
  261. @as_declarative()
  262. class Base(object):
  263. @declared_attr
  264. def __tablename__(cls):
  265. return cls.__name__.lower()
  266. id = Column(Integer, primary_key=True)
  267. class MyMappedClass(Base):
  268. # ...
  269. All keyword arguments passed to :func:`.as_declarative` are passed
  270. along to :func:`.declarative_base`.
  271. .. versionadded:: 0.8.3
  272. .. seealso::
  273. :func:`.declarative_base`
  274. """
  275. def decorate(cls):
  276. kw['cls'] = cls
  277. kw['name'] = cls.__name__
  278. return declarative_base(**kw)
  279. return decorate
  280. class ConcreteBase(object):
  281. """A helper class for 'concrete' declarative mappings.
  282. :class:`.ConcreteBase` will use the :func:`.polymorphic_union`
  283. function automatically, against all tables mapped as a subclass
  284. to this class. The function is called via the
  285. ``__declare_last__()`` function, which is essentially
  286. a hook for the :meth:`.after_configured` event.
  287. :class:`.ConcreteBase` produces a mapped
  288. table for the class itself. Compare to :class:`.AbstractConcreteBase`,
  289. which does not.
  290. Example::
  291. from sqlalchemy.ext.declarative import ConcreteBase
  292. class Employee(ConcreteBase, Base):
  293. __tablename__ = 'employee'
  294. employee_id = Column(Integer, primary_key=True)
  295. name = Column(String(50))
  296. __mapper_args__ = {
  297. 'polymorphic_identity':'employee',
  298. 'concrete':True}
  299. class Manager(Employee):
  300. __tablename__ = 'manager'
  301. employee_id = Column(Integer, primary_key=True)
  302. name = Column(String(50))
  303. manager_data = Column(String(40))
  304. __mapper_args__ = {
  305. 'polymorphic_identity':'manager',
  306. 'concrete':True}
  307. .. seealso::
  308. :class:`.AbstractConcreteBase`
  309. :ref:`concrete_inheritance`
  310. :ref:`inheritance_concrete_helpers`
  311. """
  312. @classmethod
  313. def _create_polymorphic_union(cls, mappers):
  314. return polymorphic_union(OrderedDict(
  315. (mp.polymorphic_identity, mp.local_table)
  316. for mp in mappers
  317. ), 'type', 'pjoin')
  318. @classmethod
  319. def __declare_first__(cls):
  320. m = cls.__mapper__
  321. if m.with_polymorphic:
  322. return
  323. mappers = list(m.self_and_descendants)
  324. pjoin = cls._create_polymorphic_union(mappers)
  325. m._set_with_polymorphic(("*", pjoin))
  326. m._set_polymorphic_on(pjoin.c.type)
  327. class AbstractConcreteBase(ConcreteBase):
  328. """A helper class for 'concrete' declarative mappings.
  329. :class:`.AbstractConcreteBase` will use the :func:`.polymorphic_union`
  330. function automatically, against all tables mapped as a subclass
  331. to this class. The function is called via the
  332. ``__declare_last__()`` function, which is essentially
  333. a hook for the :meth:`.after_configured` event.
  334. :class:`.AbstractConcreteBase` does produce a mapped class
  335. for the base class, however it is not persisted to any table; it
  336. is instead mapped directly to the "polymorphic" selectable directly
  337. and is only used for selecting. Compare to :class:`.ConcreteBase`,
  338. which does create a persisted table for the base class.
  339. Example::
  340. from sqlalchemy.ext.declarative import AbstractConcreteBase
  341. class Employee(AbstractConcreteBase, Base):
  342. pass
  343. class Manager(Employee):
  344. __tablename__ = 'manager'
  345. employee_id = Column(Integer, primary_key=True)
  346. name = Column(String(50))
  347. manager_data = Column(String(40))
  348. __mapper_args__ = {
  349. 'polymorphic_identity':'manager',
  350. 'concrete':True}
  351. The abstract base class is handled by declarative in a special way;
  352. at class configuration time, it behaves like a declarative mixin
  353. or an ``__abstract__`` base class. Once classes are configured
  354. and mappings are produced, it then gets mapped itself, but
  355. after all of its decscendants. This is a very unique system of mapping
  356. not found in any other SQLAlchemy system.
  357. Using this approach, we can specify columns and properties
  358. that will take place on mapped subclasses, in the way that
  359. we normally do as in :ref:`declarative_mixins`::
  360. class Company(Base):
  361. __tablename__ = 'company'
  362. id = Column(Integer, primary_key=True)
  363. class Employee(AbstractConcreteBase, Base):
  364. employee_id = Column(Integer, primary_key=True)
  365. @declared_attr
  366. def company_id(cls):
  367. return Column(ForeignKey('company.id'))
  368. @declared_attr
  369. def company(cls):
  370. return relationship("Company")
  371. class Manager(Employee):
  372. __tablename__ = 'manager'
  373. name = Column(String(50))
  374. manager_data = Column(String(40))
  375. __mapper_args__ = {
  376. 'polymorphic_identity':'manager',
  377. 'concrete':True}
  378. When we make use of our mappings however, both ``Manager`` and
  379. ``Employee`` will have an independently usable ``.company`` attribute::
  380. session.query(Employee).filter(Employee.company.has(id=5))
  381. .. versionchanged:: 1.0.0 - The mechanics of :class:`.AbstractConcreteBase`
  382. have been reworked to support relationships established directly
  383. on the abstract base, without any special configurational steps.
  384. .. seealso::
  385. :class:`.ConcreteBase`
  386. :ref:`concrete_inheritance`
  387. :ref:`inheritance_concrete_helpers`
  388. """
  389. __no_table__ = True
  390. @classmethod
  391. def __declare_first__(cls):
  392. cls._sa_decl_prepare_nocascade()
  393. @classmethod
  394. def _sa_decl_prepare_nocascade(cls):
  395. if getattr(cls, '__mapper__', None):
  396. return
  397. to_map = _DeferredMapperConfig.config_for_cls(cls)
  398. # can't rely on 'self_and_descendants' here
  399. # since technically an immediate subclass
  400. # might not be mapped, but a subclass
  401. # may be.
  402. mappers = []
  403. stack = list(cls.__subclasses__())
  404. while stack:
  405. klass = stack.pop()
  406. stack.extend(klass.__subclasses__())
  407. mn = _mapper_or_none(klass)
  408. if mn is not None:
  409. mappers.append(mn)
  410. pjoin = cls._create_polymorphic_union(mappers)
  411. # For columns that were declared on the class, these
  412. # are normally ignored with the "__no_table__" mapping,
  413. # unless they have a different attribute key vs. col name
  414. # and are in the properties argument.
  415. # In that case, ensure we update the properties entry
  416. # to the correct column from the pjoin target table.
  417. declared_cols = set(to_map.declared_columns)
  418. for k, v in list(to_map.properties.items()):
  419. if v in declared_cols:
  420. to_map.properties[k] = pjoin.c[v.key]
  421. to_map.local_table = pjoin
  422. m_args = to_map.mapper_args_fn or dict
  423. def mapper_args():
  424. args = m_args()
  425. args['polymorphic_on'] = pjoin.c.type
  426. return args
  427. to_map.mapper_args_fn = mapper_args
  428. m = to_map.map()
  429. for scls in cls.__subclasses__():
  430. sm = _mapper_or_none(scls)
  431. if sm and sm.concrete and cls in scls.__bases__:
  432. sm._set_concrete_base(m)
  433. class DeferredReflection(object):
  434. """A helper class for construction of mappings based on
  435. a deferred reflection step.
  436. Normally, declarative can be used with reflection by
  437. setting a :class:`.Table` object using autoload=True
  438. as the ``__table__`` attribute on a declarative class.
  439. The caveat is that the :class:`.Table` must be fully
  440. reflected, or at the very least have a primary key column,
  441. at the point at which a normal declarative mapping is
  442. constructed, meaning the :class:`.Engine` must be available
  443. at class declaration time.
  444. The :class:`.DeferredReflection` mixin moves the construction
  445. of mappers to be at a later point, after a specific
  446. method is called which first reflects all :class:`.Table`
  447. objects created so far. Classes can define it as such::
  448. from sqlalchemy.ext.declarative import declarative_base
  449. from sqlalchemy.ext.declarative import DeferredReflection
  450. Base = declarative_base()
  451. class MyClass(DeferredReflection, Base):
  452. __tablename__ = 'mytable'
  453. Above, ``MyClass`` is not yet mapped. After a series of
  454. classes have been defined in the above fashion, all tables
  455. can be reflected and mappings created using
  456. :meth:`.prepare`::
  457. engine = create_engine("someengine://...")
  458. DeferredReflection.prepare(engine)
  459. The :class:`.DeferredReflection` mixin can be applied to individual
  460. classes, used as the base for the declarative base itself,
  461. or used in a custom abstract class. Using an abstract base
  462. allows that only a subset of classes to be prepared for a
  463. particular prepare step, which is necessary for applications
  464. that use more than one engine. For example, if an application
  465. has two engines, you might use two bases, and prepare each
  466. separately, e.g.::
  467. class ReflectedOne(DeferredReflection, Base):
  468. __abstract__ = True
  469. class ReflectedTwo(DeferredReflection, Base):
  470. __abstract__ = True
  471. class MyClass(ReflectedOne):
  472. __tablename__ = 'mytable'
  473. class MyOtherClass(ReflectedOne):
  474. __tablename__ = 'myothertable'
  475. class YetAnotherClass(ReflectedTwo):
  476. __tablename__ = 'yetanothertable'
  477. # ... etc.
  478. Above, the class hierarchies for ``ReflectedOne`` and
  479. ``ReflectedTwo`` can be configured separately::
  480. ReflectedOne.prepare(engine_one)
  481. ReflectedTwo.prepare(engine_two)
  482. .. versionadded:: 0.8
  483. """
  484. @classmethod
  485. def prepare(cls, engine):
  486. """Reflect all :class:`.Table` objects for all current
  487. :class:`.DeferredReflection` subclasses"""
  488. to_map = _DeferredMapperConfig.classes_for_base(cls)
  489. for thingy in to_map:
  490. cls._sa_decl_prepare(thingy.local_table, engine)
  491. thingy.map()
  492. mapper = thingy.cls.__mapper__
  493. metadata = mapper.class_.metadata
  494. for rel in mapper._props.values():
  495. if isinstance(rel, properties.RelationshipProperty) and \
  496. rel.secondary is not None:
  497. if isinstance(rel.secondary, Table):
  498. cls._reflect_table(rel.secondary, engine)
  499. elif isinstance(rel.secondary, _class_resolver):
  500. rel.secondary._resolvers += (
  501. cls._sa_deferred_table_resolver(engine, metadata),
  502. )
  503. @classmethod
  504. def _sa_deferred_table_resolver(cls, engine, metadata):
  505. def _resolve(key):
  506. t1 = Table(key, metadata)
  507. cls._reflect_table(t1, engine)
  508. return t1
  509. return _resolve
  510. @classmethod
  511. def _sa_decl_prepare(cls, local_table, engine):
  512. # autoload Table, which is already
  513. # present in the metadata. This
  514. # will fill in db-loaded columns
  515. # into the existing Table object.
  516. if local_table is not None:
  517. cls._reflect_table(local_table, engine)
  518. @classmethod
  519. def _reflect_table(cls, table, engine):
  520. Table(table.name,
  521. table.metadata,
  522. extend_existing=True,
  523. autoload_replace=False,
  524. autoload=True,
  525. autoload_with=engine,
  526. schema=table.schema)