PageRenderTime 62ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 1ms

/venv/lib/python2.7/site-packages/flask_sqlalchemy/__init__.py

https://gitlab.com/ismailam/openexport
Python | 1001 lines | 735 code | 114 blank | 152 comment | 109 complexity | b52b5fd9ac5ebf3dd8f03c87c7989139 MD5 | raw file
  1. # -*- coding: utf-8 -*-
  2. """
  3. flaskext.sqlalchemy
  4. ~~~~~~~~~~~~~~~~~~~
  5. Adds basic SQLAlchemy support to your application.
  6. :copyright: (c) 2014 by Armin Ronacher, Daniel Neuhäuser.
  7. :license: BSD, see LICENSE for more details.
  8. """
  9. from __future__ import with_statement, absolute_import
  10. import os
  11. import re
  12. import sys
  13. import time
  14. import functools
  15. import warnings
  16. import sqlalchemy
  17. from math import ceil
  18. from functools import partial
  19. from flask import _request_ctx_stack, abort, has_request_context, request
  20. from flask.signals import Namespace
  21. from operator import itemgetter
  22. from threading import Lock
  23. from sqlalchemy import orm, event, inspect
  24. from sqlalchemy.orm.exc import UnmappedClassError
  25. from sqlalchemy.orm.session import Session as SessionBase
  26. from sqlalchemy.engine.url import make_url
  27. from sqlalchemy.ext.declarative import declarative_base, DeclarativeMeta
  28. from flask_sqlalchemy._compat import iteritems, itervalues, xrange, \
  29. string_types
  30. # the best timer function for the platform
  31. if sys.platform == 'win32':
  32. _timer = time.clock
  33. else:
  34. _timer = time.time
  35. try:
  36. from flask import _app_ctx_stack
  37. except ImportError:
  38. _app_ctx_stack = None
  39. __version__ = '2.1'
  40. # Which stack should we use? _app_ctx_stack is new in 0.9
  41. connection_stack = _app_ctx_stack or _request_ctx_stack
  42. _camelcase_re = re.compile(r'([A-Z]+)(?=[a-z0-9])')
  43. _signals = Namespace()
  44. models_committed = _signals.signal('models-committed')
  45. before_models_committed = _signals.signal('before-models-committed')
  46. def _make_table(db):
  47. def _make_table(*args, **kwargs):
  48. if len(args) > 1 and isinstance(args[1], db.Column):
  49. args = (args[0], db.metadata) + args[1:]
  50. info = kwargs.pop('info', None) or {}
  51. info.setdefault('bind_key', None)
  52. kwargs['info'] = info
  53. return sqlalchemy.Table(*args, **kwargs)
  54. return _make_table
  55. def _set_default_query_class(d):
  56. if 'query_class' not in d:
  57. d['query_class'] = BaseQuery
  58. def _wrap_with_default_query_class(fn):
  59. @functools.wraps(fn)
  60. def newfn(*args, **kwargs):
  61. _set_default_query_class(kwargs)
  62. if "backref" in kwargs:
  63. backref = kwargs['backref']
  64. if isinstance(backref, string_types):
  65. backref = (backref, {})
  66. _set_default_query_class(backref[1])
  67. return fn(*args, **kwargs)
  68. return newfn
  69. def _include_sqlalchemy(obj):
  70. for module in sqlalchemy, sqlalchemy.orm:
  71. for key in module.__all__:
  72. if not hasattr(obj, key):
  73. setattr(obj, key, getattr(module, key))
  74. # Note: obj.Table does not attempt to be a SQLAlchemy Table class.
  75. obj.Table = _make_table(obj)
  76. obj.relationship = _wrap_with_default_query_class(obj.relationship)
  77. obj.relation = _wrap_with_default_query_class(obj.relation)
  78. obj.dynamic_loader = _wrap_with_default_query_class(obj.dynamic_loader)
  79. obj.event = event
  80. class _DebugQueryTuple(tuple):
  81. statement = property(itemgetter(0))
  82. parameters = property(itemgetter(1))
  83. start_time = property(itemgetter(2))
  84. end_time = property(itemgetter(3))
  85. context = property(itemgetter(4))
  86. @property
  87. def duration(self):
  88. return self.end_time - self.start_time
  89. def __repr__(self):
  90. return '<query statement="%s" parameters=%r duration=%.03f>' % (
  91. self.statement,
  92. self.parameters,
  93. self.duration
  94. )
  95. def _calling_context(app_path):
  96. frm = sys._getframe(1)
  97. while frm.f_back is not None:
  98. name = frm.f_globals.get('__name__')
  99. if name and (name == app_path or name.startswith(app_path + '.')):
  100. funcname = frm.f_code.co_name
  101. return '%s:%s (%s)' % (
  102. frm.f_code.co_filename,
  103. frm.f_lineno,
  104. funcname
  105. )
  106. frm = frm.f_back
  107. return '<unknown>'
  108. class SignallingSession(SessionBase):
  109. """The signalling session is the default session that Flask-SQLAlchemy
  110. uses. It extends the default session system with bind selection and
  111. modification tracking.
  112. If you want to use a different session you can override the
  113. :meth:`SQLAlchemy.create_session` function.
  114. .. versionadded:: 2.0
  115. .. versionadded:: 2.1
  116. The `binds` option was added, which allows a session to be joined
  117. to an external transaction.
  118. """
  119. def __init__(self, db, autocommit=False, autoflush=True, app=None, **options):
  120. #: The application that this session belongs to.
  121. self.app = app = db.get_app()
  122. track_modifications = app.config['SQLALCHEMY_TRACK_MODIFICATIONS']
  123. bind = options.pop('bind', None) or db.engine
  124. binds = options.pop('binds', None) or db.get_binds(app)
  125. if track_modifications is None or track_modifications:
  126. _SessionSignalEvents.register(self)
  127. SessionBase.__init__(
  128. self, autocommit=autocommit, autoflush=autoflush,
  129. bind=bind, binds=binds, **options
  130. )
  131. def get_bind(self, mapper=None, clause=None):
  132. # mapper is None if someone tries to just get a connection
  133. if mapper is not None:
  134. info = getattr(mapper.mapped_table, 'info', {})
  135. bind_key = info.get('bind_key')
  136. if bind_key is not None:
  137. state = get_state(self.app)
  138. return state.db.get_engine(self.app, bind=bind_key)
  139. return SessionBase.get_bind(self, mapper, clause)
  140. class _SessionSignalEvents(object):
  141. @classmethod
  142. def register(cls, session):
  143. if not hasattr(session, '_model_changes'):
  144. session._model_changes = {}
  145. event.listen(session, 'before_flush', cls.record_ops)
  146. event.listen(session, 'before_commit', cls.record_ops)
  147. event.listen(session, 'before_commit', cls.before_commit)
  148. event.listen(session, 'after_commit', cls.after_commit)
  149. event.listen(session, 'after_rollback', cls.after_rollback)
  150. @classmethod
  151. def unregister(cls, session):
  152. if hasattr(session, '_model_changes'):
  153. del session._model_changes
  154. event.remove(session, 'before_flush', cls.record_ops)
  155. event.remove(session, 'before_commit', cls.record_ops)
  156. event.remove(session, 'before_commit', cls.before_commit)
  157. event.remove(session, 'after_commit', cls.after_commit)
  158. event.remove(session, 'after_rollback', cls.after_rollback)
  159. @staticmethod
  160. def record_ops(session, flush_context=None, instances=None):
  161. try:
  162. d = session._model_changes
  163. except AttributeError:
  164. return
  165. for targets, operation in ((session.new, 'insert'), (session.dirty, 'update'), (session.deleted, 'delete')):
  166. for target in targets:
  167. state = inspect(target)
  168. key = state.identity_key if state.has_identity else id(target)
  169. d[key] = (target, operation)
  170. @staticmethod
  171. def before_commit(session):
  172. try:
  173. d = session._model_changes
  174. except AttributeError:
  175. return
  176. if d:
  177. before_models_committed.send(session.app, changes=list(d.values()))
  178. @staticmethod
  179. def after_commit(session):
  180. try:
  181. d = session._model_changes
  182. except AttributeError:
  183. return
  184. if d:
  185. models_committed.send(session.app, changes=list(d.values()))
  186. d.clear()
  187. @staticmethod
  188. def after_rollback(session):
  189. try:
  190. d = session._model_changes
  191. except AttributeError:
  192. return
  193. d.clear()
  194. class _EngineDebuggingSignalEvents(object):
  195. """Sets up handlers for two events that let us track the execution time of queries."""
  196. def __init__(self, engine, import_name):
  197. self.engine = engine
  198. self.app_package = import_name
  199. def register(self):
  200. event.listen(self.engine, 'before_cursor_execute', self.before_cursor_execute)
  201. event.listen(self.engine, 'after_cursor_execute', self.after_cursor_execute)
  202. def before_cursor_execute(self, conn, cursor, statement,
  203. parameters, context, executemany):
  204. if connection_stack.top is not None:
  205. context._query_start_time = _timer()
  206. def after_cursor_execute(self, conn, cursor, statement,
  207. parameters, context, executemany):
  208. ctx = connection_stack.top
  209. if ctx is not None:
  210. queries = getattr(ctx, 'sqlalchemy_queries', None)
  211. if queries is None:
  212. queries = []
  213. setattr(ctx, 'sqlalchemy_queries', queries)
  214. queries.append(_DebugQueryTuple((
  215. statement, parameters, context._query_start_time, _timer(),
  216. _calling_context(self.app_package))))
  217. def get_debug_queries():
  218. """In debug mode Flask-SQLAlchemy will log all the SQL queries sent
  219. to the database. This information is available until the end of request
  220. which makes it possible to easily ensure that the SQL generated is the
  221. one expected on errors or in unittesting. If you don't want to enable
  222. the DEBUG mode for your unittests you can also enable the query
  223. recording by setting the ``'SQLALCHEMY_RECORD_QUERIES'`` config variable
  224. to `True`. This is automatically enabled if Flask is in testing mode.
  225. The value returned will be a list of named tuples with the following
  226. attributes:
  227. `statement`
  228. The SQL statement issued
  229. `parameters`
  230. The parameters for the SQL statement
  231. `start_time` / `end_time`
  232. Time the query started / the results arrived. Please keep in mind
  233. that the timer function used depends on your platform. These
  234. values are only useful for sorting or comparing. They do not
  235. necessarily represent an absolute timestamp.
  236. `duration`
  237. Time the query took in seconds
  238. `context`
  239. A string giving a rough estimation of where in your application
  240. query was issued. The exact format is undefined so don't try
  241. to reconstruct filename or function name.
  242. """
  243. return getattr(connection_stack.top, 'sqlalchemy_queries', [])
  244. class Pagination(object):
  245. """Internal helper class returned by :meth:`BaseQuery.paginate`. You
  246. can also construct it from any other SQLAlchemy query object if you are
  247. working with other libraries. Additionally it is possible to pass `None`
  248. as query object in which case the :meth:`prev` and :meth:`next` will
  249. no longer work.
  250. """
  251. def __init__(self, query, page, per_page, total, items):
  252. #: the unlimited query object that was used to create this
  253. #: pagination object.
  254. self.query = query
  255. #: the current page number (1 indexed)
  256. self.page = page
  257. #: the number of items to be displayed on a page.
  258. self.per_page = per_page
  259. #: the total number of items matching the query
  260. self.total = total
  261. #: the items for the current page
  262. self.items = items
  263. @property
  264. def pages(self):
  265. """The total number of pages"""
  266. if self.per_page == 0:
  267. pages = 0
  268. else:
  269. pages = int(ceil(self.total / float(self.per_page)))
  270. return pages
  271. def prev(self, error_out=False):
  272. """Returns a :class:`Pagination` object for the previous page."""
  273. assert self.query is not None, 'a query object is required ' \
  274. 'for this method to work'
  275. return self.query.paginate(self.page - 1, self.per_page, error_out)
  276. @property
  277. def prev_num(self):
  278. """Number of the previous page."""
  279. return self.page - 1
  280. @property
  281. def has_prev(self):
  282. """True if a previous page exists"""
  283. return self.page > 1
  284. def next(self, error_out=False):
  285. """Returns a :class:`Pagination` object for the next page."""
  286. assert self.query is not None, 'a query object is required ' \
  287. 'for this method to work'
  288. return self.query.paginate(self.page + 1, self.per_page, error_out)
  289. @property
  290. def has_next(self):
  291. """True if a next page exists."""
  292. return self.page < self.pages
  293. @property
  294. def next_num(self):
  295. """Number of the next page"""
  296. return self.page + 1
  297. def iter_pages(self, left_edge=2, left_current=2,
  298. right_current=5, right_edge=2):
  299. """Iterates over the page numbers in the pagination. The four
  300. parameters control the thresholds how many numbers should be produced
  301. from the sides. Skipped page numbers are represented as `None`.
  302. This is how you could render such a pagination in the templates:
  303. .. sourcecode:: html+jinja
  304. {% macro render_pagination(pagination, endpoint) %}
  305. <div class=pagination>
  306. {%- for page in pagination.iter_pages() %}
  307. {% if page %}
  308. {% if page != pagination.page %}
  309. <a href="{{ url_for(endpoint, page=page) }}">{{ page }}</a>
  310. {% else %}
  311. <strong>{{ page }}</strong>
  312. {% endif %}
  313. {% else %}
  314. <span class=ellipsis>…</span>
  315. {% endif %}
  316. {%- endfor %}
  317. </div>
  318. {% endmacro %}
  319. """
  320. last = 0
  321. for num in xrange(1, self.pages + 1):
  322. if num <= left_edge or \
  323. (num > self.page - left_current - 1 and \
  324. num < self.page + right_current) or \
  325. num > self.pages - right_edge:
  326. if last + 1 != num:
  327. yield None
  328. yield num
  329. last = num
  330. class BaseQuery(orm.Query):
  331. """The default query object used for models, and exposed as
  332. :attr:`~SQLAlchemy.Query`. This can be subclassed and
  333. replaced for individual models by setting the :attr:`~Model.query_class`
  334. attribute. This is a subclass of a standard SQLAlchemy
  335. :class:`~sqlalchemy.orm.query.Query` class and has all the methods of a
  336. standard query as well.
  337. """
  338. def get_or_404(self, ident):
  339. """Like :meth:`get` but aborts with 404 if not found instead of
  340. returning `None`.
  341. """
  342. rv = self.get(ident)
  343. if rv is None:
  344. abort(404)
  345. return rv
  346. def first_or_404(self):
  347. """Like :meth:`first` but aborts with 404 if not found instead of
  348. returning `None`.
  349. """
  350. rv = self.first()
  351. if rv is None:
  352. abort(404)
  353. return rv
  354. def paginate(self, page=None, per_page=None, error_out=True):
  355. """Returns `per_page` items from page `page`. By default it will
  356. abort with 404 if no items were found and the page was larger than
  357. 1. This behavor can be disabled by setting `error_out` to `False`.
  358. If page or per_page are None, they will be retrieved from the
  359. request query. If the values are not ints and ``error_out`` is
  360. true, it will abort with 404. If there is no request or they
  361. aren't in the query, they default to page 1 and 20
  362. respectively.
  363. Returns an :class:`Pagination` object.
  364. """
  365. if has_request_context():
  366. if page is None:
  367. try:
  368. page = int(request.args.get('page', 1))
  369. except (TypeError, ValueError):
  370. if error_out:
  371. abort(404)
  372. page = 1
  373. if per_page is None:
  374. try:
  375. per_page = int(request.args.get('per_page', 20))
  376. except (TypeError, ValueError):
  377. if error_out:
  378. abort(404)
  379. per_page = 20
  380. else:
  381. if page is None:
  382. page = 1
  383. if per_page is None:
  384. per_page = 20
  385. if error_out and page < 1:
  386. abort(404)
  387. items = self.limit(per_page).offset((page - 1) * per_page).all()
  388. if not items and page != 1 and error_out:
  389. abort(404)
  390. # No need to count if we're on the first page and there are fewer
  391. # items than we expected.
  392. if page == 1 and len(items) < per_page:
  393. total = len(items)
  394. else:
  395. total = self.order_by(None).count()
  396. return Pagination(self, page, per_page, total, items)
  397. class _QueryProperty(object):
  398. def __init__(self, sa):
  399. self.sa = sa
  400. def __get__(self, obj, type):
  401. try:
  402. mapper = orm.class_mapper(type)
  403. if mapper:
  404. return type.query_class(mapper, session=self.sa.session())
  405. except UnmappedClassError:
  406. return None
  407. def _record_queries(app):
  408. if app.debug:
  409. return True
  410. rq = app.config['SQLALCHEMY_RECORD_QUERIES']
  411. if rq is not None:
  412. return rq
  413. return bool(app.config.get('TESTING'))
  414. class _EngineConnector(object):
  415. def __init__(self, sa, app, bind=None):
  416. self._sa = sa
  417. self._app = app
  418. self._engine = None
  419. self._connected_for = None
  420. self._bind = bind
  421. self._lock = Lock()
  422. def get_uri(self):
  423. if self._bind is None:
  424. return self._app.config['SQLALCHEMY_DATABASE_URI']
  425. binds = self._app.config.get('SQLALCHEMY_BINDS') or ()
  426. assert self._bind in binds, \
  427. 'Bind %r is not specified. Set it in the SQLALCHEMY_BINDS ' \
  428. 'configuration variable' % self._bind
  429. return binds[self._bind]
  430. def get_engine(self):
  431. with self._lock:
  432. uri = self.get_uri()
  433. echo = self._app.config['SQLALCHEMY_ECHO']
  434. if (uri, echo) == self._connected_for:
  435. return self._engine
  436. info = make_url(uri)
  437. options = {'convert_unicode': True}
  438. self._sa.apply_pool_defaults(self._app, options)
  439. self._sa.apply_driver_hacks(self._app, info, options)
  440. if echo:
  441. options['echo'] = True
  442. self._engine = rv = sqlalchemy.create_engine(info, **options)
  443. if _record_queries(self._app):
  444. _EngineDebuggingSignalEvents(self._engine,
  445. self._app.import_name).register()
  446. self._connected_for = (uri, echo)
  447. return rv
  448. def _should_set_tablename(bases, d):
  449. """Check what values are set by a class and its bases to determine if a
  450. tablename should be automatically generated.
  451. The class and its bases are checked in order of precedence: the class
  452. itself then each base in the order they were given at class definition.
  453. Abstract classes do not generate a tablename, although they may have set
  454. or inherited a tablename elsewhere.
  455. If a class defines a tablename or table, a new one will not be generated.
  456. Otherwise, if the class defines a primary key, a new name will be generated.
  457. This supports:
  458. * Joined table inheritance without explicitly naming sub-models.
  459. * Single table inheritance.
  460. * Inheriting from mixins or abstract models.
  461. :param bases: base classes of new class
  462. :param d: new class dict
  463. :return: True if tablename should be set
  464. """
  465. if '__tablename__' in d or '__table__' in d or '__abstract__' in d:
  466. return False
  467. if any(v.primary_key for v in itervalues(d) if isinstance(v, sqlalchemy.Column)):
  468. return True
  469. for base in bases:
  470. if hasattr(base, '__tablename__') or hasattr(base, '__table__'):
  471. return False
  472. for name in dir(base):
  473. attr = getattr(base, name)
  474. if isinstance(attr, sqlalchemy.Column) and attr.primary_key:
  475. return True
  476. class _BoundDeclarativeMeta(DeclarativeMeta):
  477. def __new__(cls, name, bases, d):
  478. if _should_set_tablename(bases, d):
  479. def _join(match):
  480. word = match.group()
  481. if len(word) > 1:
  482. return ('_%s_%s' % (word[:-1], word[-1])).lower()
  483. return '_' + word.lower()
  484. d['__tablename__'] = _camelcase_re.sub(_join, name).lstrip('_')
  485. return DeclarativeMeta.__new__(cls, name, bases, d)
  486. def __init__(self, name, bases, d):
  487. bind_key = d.pop('__bind_key__', None)
  488. DeclarativeMeta.__init__(self, name, bases, d)
  489. if bind_key is not None:
  490. self.__table__.info['bind_key'] = bind_key
  491. def get_state(app):
  492. """Gets the state for the application"""
  493. assert 'sqlalchemy' in app.extensions, \
  494. 'The sqlalchemy extension was not registered to the current ' \
  495. 'application. Please make sure to call init_app() first.'
  496. return app.extensions['sqlalchemy']
  497. class _SQLAlchemyState(object):
  498. """Remembers configuration for the (db, app) tuple."""
  499. def __init__(self, db, app):
  500. self.db = db
  501. self.app = app
  502. self.connectors = {}
  503. class Model(object):
  504. """Baseclass for custom user models."""
  505. #: the query class used. The :attr:`query` attribute is an instance
  506. #: of this class. By default a :class:`BaseQuery` is used.
  507. query_class = BaseQuery
  508. #: an instance of :attr:`query_class`. Can be used to query the
  509. #: database for instances of this model.
  510. query = None
  511. class SQLAlchemy(object):
  512. """This class is used to control the SQLAlchemy integration to one
  513. or more Flask applications. Depending on how you initialize the
  514. object it is usable right away or will attach as needed to a
  515. Flask application.
  516. There are two usage modes which work very similarly. One is binding
  517. the instance to a very specific Flask application::
  518. app = Flask(__name__)
  519. db = SQLAlchemy(app)
  520. The second possibility is to create the object once and configure the
  521. application later to support it::
  522. db = SQLAlchemy()
  523. def create_app():
  524. app = Flask(__name__)
  525. db.init_app(app)
  526. return app
  527. The difference between the two is that in the first case methods like
  528. :meth:`create_all` and :meth:`drop_all` will work all the time but in
  529. the second case a :meth:`flask.Flask.app_context` has to exist.
  530. By default Flask-SQLAlchemy will apply some backend-specific settings
  531. to improve your experience with them. As of SQLAlchemy 0.6 SQLAlchemy
  532. will probe the library for native unicode support. If it detects
  533. unicode it will let the library handle that, otherwise do that itself.
  534. Sometimes this detection can fail in which case you might want to set
  535. `use_native_unicode` (or the ``SQLALCHEMY_NATIVE_UNICODE`` configuration
  536. key) to `False`. Note that the configuration key overrides the
  537. value you pass to the constructor.
  538. This class also provides access to all the SQLAlchemy functions and classes
  539. from the :mod:`sqlalchemy` and :mod:`sqlalchemy.orm` modules. So you can
  540. declare models like this::
  541. class User(db.Model):
  542. username = db.Column(db.String(80), unique=True)
  543. pw_hash = db.Column(db.String(80))
  544. You can still use :mod:`sqlalchemy` and :mod:`sqlalchemy.orm` directly, but
  545. note that Flask-SQLAlchemy customizations are available only through an
  546. instance of this :class:`SQLAlchemy` class. Query classes default to
  547. :class:`BaseQuery` for `db.Query`, `db.Model.query_class`, and the default
  548. query_class for `db.relationship` and `db.backref`. If you use these
  549. interfaces through :mod:`sqlalchemy` and :mod:`sqlalchemy.orm` directly,
  550. the default query class will be that of :mod:`sqlalchemy`.
  551. .. admonition:: Check types carefully
  552. Don't perform type or `isinstance` checks against `db.Table`, which
  553. emulates `Table` behavior but is not a class. `db.Table` exposes the
  554. `Table` interface, but is a function which allows omission of metadata.
  555. You may also define your own SessionExtension instances as well when
  556. defining your SQLAlchemy class instance. You may pass your custom instances
  557. to the `session_extensions` keyword. This can be either a single
  558. SessionExtension instance, or a list of SessionExtension instances. In the
  559. following use case we use the VersionedListener from the SQLAlchemy
  560. versioning examples.::
  561. from history_meta import VersionedMeta, VersionedListener
  562. app = Flask(__name__)
  563. db = SQLAlchemy(app, session_extensions=[VersionedListener()])
  564. class User(db.Model):
  565. __metaclass__ = VersionedMeta
  566. username = db.Column(db.String(80), unique=True)
  567. pw_hash = db.Column(db.String(80))
  568. The `session_options` parameter can be used to override session
  569. options. If provided it's a dict of parameters passed to the
  570. session's constructor.
  571. .. versionadded:: 0.10
  572. The `session_options` parameter was added.
  573. .. versionadded:: 0.16
  574. `scopefunc` is now accepted on `session_options`. It allows specifying
  575. a custom function which will define the SQLAlchemy session's scoping.
  576. .. versionadded:: 2.1
  577. The `metadata` parameter was added. This allows for setting custom
  578. naming conventions among other, non-trivial things.
  579. """
  580. def __init__(self, app=None, use_native_unicode=True, session_options=None, metadata=None):
  581. if session_options is None:
  582. session_options = {}
  583. session_options.setdefault('scopefunc', connection_stack.__ident_func__)
  584. self.use_native_unicode = use_native_unicode
  585. self.session = self.create_scoped_session(session_options)
  586. self.Model = self.make_declarative_base(metadata)
  587. self.Query = BaseQuery
  588. self._engine_lock = Lock()
  589. self.app = app
  590. _include_sqlalchemy(self)
  591. if app is not None:
  592. self.init_app(app)
  593. @property
  594. def metadata(self):
  595. """Returns the metadata"""
  596. return self.Model.metadata
  597. def create_scoped_session(self, options=None):
  598. """Helper factory method that creates a scoped session. It
  599. internally calls :meth:`create_session`.
  600. """
  601. if options is None:
  602. options = {}
  603. scopefunc = options.pop('scopefunc', None)
  604. return orm.scoped_session(partial(self.create_session, options),
  605. scopefunc=scopefunc)
  606. def create_session(self, options):
  607. """Creates the session. The default implementation returns a
  608. :class:`SignallingSession`.
  609. .. versionadded:: 2.0
  610. """
  611. return SignallingSession(self, **options)
  612. def make_declarative_base(self, metadata=None):
  613. """Creates the declarative base."""
  614. base = declarative_base(cls=Model, name='Model',
  615. metadata=metadata,
  616. metaclass=_BoundDeclarativeMeta)
  617. base.query = _QueryProperty(self)
  618. return base
  619. def init_app(self, app):
  620. """This callback can be used to initialize an application for the
  621. use with this database setup. Never use a database in the context
  622. of an application not initialized that way or connections will
  623. leak.
  624. """
  625. app.config.setdefault('SQLALCHEMY_DATABASE_URI', 'sqlite://')
  626. app.config.setdefault('SQLALCHEMY_BINDS', None)
  627. app.config.setdefault('SQLALCHEMY_NATIVE_UNICODE', None)
  628. app.config.setdefault('SQLALCHEMY_ECHO', False)
  629. app.config.setdefault('SQLALCHEMY_RECORD_QUERIES', None)
  630. app.config.setdefault('SQLALCHEMY_POOL_SIZE', None)
  631. app.config.setdefault('SQLALCHEMY_POOL_TIMEOUT', None)
  632. app.config.setdefault('SQLALCHEMY_POOL_RECYCLE', None)
  633. app.config.setdefault('SQLALCHEMY_MAX_OVERFLOW', None)
  634. app.config.setdefault('SQLALCHEMY_COMMIT_ON_TEARDOWN', False)
  635. track_modifications = app.config.setdefault('SQLALCHEMY_TRACK_MODIFICATIONS', None)
  636. if track_modifications is None:
  637. warnings.warn('SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future. Set it to True to suppress this warning.')
  638. if not hasattr(app, 'extensions'):
  639. app.extensions = {}
  640. app.extensions['sqlalchemy'] = _SQLAlchemyState(self, app)
  641. # 0.9 and later
  642. if hasattr(app, 'teardown_appcontext'):
  643. teardown = app.teardown_appcontext
  644. # 0.7 to 0.8
  645. elif hasattr(app, 'teardown_request'):
  646. teardown = app.teardown_request
  647. # Older Flask versions
  648. else:
  649. if app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN']:
  650. raise RuntimeError("Commit on teardown requires Flask >= 0.7")
  651. teardown = app.after_request
  652. @teardown
  653. def shutdown_session(response_or_exc):
  654. if app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN']:
  655. if response_or_exc is None:
  656. self.session.commit()
  657. self.session.remove()
  658. return response_or_exc
  659. def apply_pool_defaults(self, app, options):
  660. def _setdefault(optionkey, configkey):
  661. value = app.config[configkey]
  662. if value is not None:
  663. options[optionkey] = value
  664. _setdefault('pool_size', 'SQLALCHEMY_POOL_SIZE')
  665. _setdefault('pool_timeout', 'SQLALCHEMY_POOL_TIMEOUT')
  666. _setdefault('pool_recycle', 'SQLALCHEMY_POOL_RECYCLE')
  667. _setdefault('max_overflow', 'SQLALCHEMY_MAX_OVERFLOW')
  668. def apply_driver_hacks(self, app, info, options):
  669. """This method is called before engine creation and used to inject
  670. driver specific hacks into the options. The `options` parameter is
  671. a dictionary of keyword arguments that will then be used to call
  672. the :func:`sqlalchemy.create_engine` function.
  673. The default implementation provides some saner defaults for things
  674. like pool sizes for MySQL and sqlite. Also it injects the setting of
  675. `SQLALCHEMY_NATIVE_UNICODE`.
  676. """
  677. if info.drivername.startswith('mysql'):
  678. info.query.setdefault('charset', 'utf8')
  679. if info.drivername != 'mysql+gaerdbms':
  680. options.setdefault('pool_size', 10)
  681. options.setdefault('pool_recycle', 7200)
  682. elif info.drivername == 'sqlite':
  683. pool_size = options.get('pool_size')
  684. detected_in_memory = False
  685. # we go to memory and the pool size was explicitly set to 0
  686. # which is fail. Let the user know that
  687. if info.database in (None, '', ':memory:'):
  688. detected_in_memory = True
  689. from sqlalchemy.pool import StaticPool
  690. options['poolclass'] = StaticPool
  691. if 'connect_args' not in options:
  692. options['connect_args'] = {}
  693. options['connect_args']['check_same_thread'] = False
  694. if pool_size == 0:
  695. raise RuntimeError('SQLite in memory database with an '
  696. 'empty queue not possible due to data '
  697. 'loss.')
  698. # if pool size is None or explicitly set to 0 we assume the
  699. # user did not want a queue for this sqlite connection and
  700. # hook in the null pool.
  701. elif not pool_size:
  702. from sqlalchemy.pool import NullPool
  703. options['poolclass'] = NullPool
  704. # if it's not an in memory database we make the path absolute.
  705. if not detected_in_memory:
  706. info.database = os.path.join(app.root_path, info.database)
  707. unu = app.config['SQLALCHEMY_NATIVE_UNICODE']
  708. if unu is None:
  709. unu = self.use_native_unicode
  710. if not unu:
  711. options['use_native_unicode'] = False
  712. @property
  713. def engine(self):
  714. """Gives access to the engine. If the database configuration is bound
  715. to a specific application (initialized with an application) this will
  716. always return a database connection. If however the current application
  717. is used this might raise a :exc:`RuntimeError` if no application is
  718. active at the moment.
  719. """
  720. return self.get_engine(self.get_app())
  721. def make_connector(self, app, bind=None):
  722. """Creates the connector for a given state and bind."""
  723. return _EngineConnector(self, app, bind)
  724. def get_engine(self, app, bind=None):
  725. """Returns a specific engine.
  726. .. versionadded:: 0.12
  727. """
  728. with self._engine_lock:
  729. state = get_state(app)
  730. connector = state.connectors.get(bind)
  731. if connector is None:
  732. connector = self.make_connector(app, bind)
  733. state.connectors[bind] = connector
  734. return connector.get_engine()
  735. def get_app(self, reference_app=None):
  736. """Helper method that implements the logic to look up an application.
  737. """
  738. if reference_app is not None:
  739. return reference_app
  740. if self.app is not None:
  741. return self.app
  742. ctx = connection_stack.top
  743. if ctx is not None:
  744. return ctx.app
  745. raise RuntimeError('application not registered on db '
  746. 'instance and no application bound '
  747. 'to current context')
  748. def get_tables_for_bind(self, bind=None):
  749. """Returns a list of all tables relevant for a bind."""
  750. result = []
  751. for table in itervalues(self.Model.metadata.tables):
  752. if table.info.get('bind_key') == bind:
  753. result.append(table)
  754. return result
  755. def get_binds(self, app=None):
  756. """Returns a dictionary with a table->engine mapping.
  757. This is suitable for use of sessionmaker(binds=db.get_binds(app)).
  758. """
  759. app = self.get_app(app)
  760. binds = [None] + list(app.config.get('SQLALCHEMY_BINDS') or ())
  761. retval = {}
  762. for bind in binds:
  763. engine = self.get_engine(app, bind)
  764. tables = self.get_tables_for_bind(bind)
  765. retval.update(dict((table, engine) for table in tables))
  766. return retval
  767. def _execute_for_all_tables(self, app, bind, operation, skip_tables=False):
  768. app = self.get_app(app)
  769. if bind == '__all__':
  770. binds = [None] + list(app.config.get('SQLALCHEMY_BINDS') or ())
  771. elif isinstance(bind, string_types) or bind is None:
  772. binds = [bind]
  773. else:
  774. binds = bind
  775. for bind in binds:
  776. extra = {}
  777. if not skip_tables:
  778. tables = self.get_tables_for_bind(bind)
  779. extra['tables'] = tables
  780. op = getattr(self.Model.metadata, operation)
  781. op(bind=self.get_engine(app, bind), **extra)
  782. def create_all(self, bind='__all__', app=None):
  783. """Creates all tables.
  784. .. versionchanged:: 0.12
  785. Parameters were added
  786. """
  787. self._execute_for_all_tables(app, bind, 'create_all')
  788. def drop_all(self, bind='__all__', app=None):
  789. """Drops all tables.
  790. .. versionchanged:: 0.12
  791. Parameters were added
  792. """
  793. self._execute_for_all_tables(app, bind, 'drop_all')
  794. def reflect(self, bind='__all__', app=None):
  795. """Reflects tables from the database.
  796. .. versionchanged:: 0.12
  797. Parameters were added
  798. """
  799. self._execute_for_all_tables(app, bind, 'reflect', skip_tables=True)
  800. def __repr__(self):
  801. app = None
  802. if self.app is not None:
  803. app = self.app
  804. else:
  805. ctx = connection_stack.top
  806. if ctx is not None:
  807. app = ctx.app
  808. return '<%s engine=%r>' % (
  809. self.__class__.__name__,
  810. app and app.config['SQLALCHEMY_DATABASE_URI'] or None
  811. )