add Linux_i686
This commit is contained in:
parent
75f9a2fcbc
commit
95cd9b11f2
1644 changed files with 564260 additions and 0 deletions
|
|
@ -0,0 +1,885 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
flaskext.sqlalchemy
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Adds basic SQLAlchemy support to your application.
|
||||
|
||||
:copyright: (c) 2012 by Armin Ronacher, Daniel Neuhäuser.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from __future__ import with_statement, absolute_import
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import functools
|
||||
import sqlalchemy
|
||||
from math import ceil
|
||||
from functools import partial
|
||||
from flask import _request_ctx_stack, abort
|
||||
from flask.signals import Namespace
|
||||
from operator import itemgetter
|
||||
from threading import Lock
|
||||
from sqlalchemy import orm, event
|
||||
from sqlalchemy.orm.exc import UnmappedClassError
|
||||
from sqlalchemy.orm.session import Session
|
||||
from sqlalchemy.event import listen
|
||||
from sqlalchemy.engine.url import make_url
|
||||
from sqlalchemy.ext.declarative import declarative_base, DeclarativeMeta
|
||||
from flask.ext.sqlalchemy._compat import iteritems, itervalues, xrange, \
|
||||
string_types
|
||||
|
||||
# the best timer function for the platform
|
||||
if sys.platform == 'win32':
|
||||
_timer = time.clock
|
||||
else:
|
||||
_timer = time.time
|
||||
|
||||
try:
|
||||
from flask import _app_ctx_stack
|
||||
except ImportError:
|
||||
_app_ctx_stack = None
|
||||
|
||||
|
||||
# Which stack should we use? _app_ctx_stack is new in 0.9
|
||||
connection_stack = _app_ctx_stack or _request_ctx_stack
|
||||
|
||||
|
||||
_camelcase_re = re.compile(r'([A-Z]+)(?=[a-z0-9])')
|
||||
_signals = Namespace()
|
||||
|
||||
|
||||
models_committed = _signals.signal('models-committed')
|
||||
before_models_committed = _signals.signal('before-models-committed')
|
||||
|
||||
|
||||
def _make_table(db):
|
||||
def _make_table(*args, **kwargs):
|
||||
if len(args) > 1 and isinstance(args[1], db.Column):
|
||||
args = (args[0], db.metadata) + args[1:]
|
||||
info = kwargs.pop('info', None) or {}
|
||||
info.setdefault('bind_key', None)
|
||||
kwargs['info'] = info
|
||||
return sqlalchemy.Table(*args, **kwargs)
|
||||
return _make_table
|
||||
|
||||
|
||||
def _set_default_query_class(d):
|
||||
if 'query_class' not in d:
|
||||
d['query_class'] = BaseQuery
|
||||
|
||||
|
||||
def _wrap_with_default_query_class(fn):
|
||||
@functools.wraps(fn)
|
||||
def newfn(*args, **kwargs):
|
||||
_set_default_query_class(kwargs)
|
||||
if "backref" in kwargs:
|
||||
backref = kwargs['backref']
|
||||
if isinstance(backref, string_types):
|
||||
backref = (backref, {})
|
||||
_set_default_query_class(backref[1])
|
||||
return fn(*args, **kwargs)
|
||||
return newfn
|
||||
|
||||
|
||||
def _include_sqlalchemy(obj):
|
||||
for module in sqlalchemy, sqlalchemy.orm:
|
||||
for key in module.__all__:
|
||||
if not hasattr(obj, key):
|
||||
setattr(obj, key, getattr(module, key))
|
||||
# Note: obj.Table does not attempt to be a SQLAlchemy Table class.
|
||||
obj.Table = _make_table(obj)
|
||||
obj.relationship = _wrap_with_default_query_class(obj.relationship)
|
||||
obj.relation = _wrap_with_default_query_class(obj.relation)
|
||||
obj.dynamic_loader = _wrap_with_default_query_class(obj.dynamic_loader)
|
||||
obj.event = event
|
||||
|
||||
|
||||
class _DebugQueryTuple(tuple):
|
||||
statement = property(itemgetter(0))
|
||||
parameters = property(itemgetter(1))
|
||||
start_time = property(itemgetter(2))
|
||||
end_time = property(itemgetter(3))
|
||||
context = property(itemgetter(4))
|
||||
|
||||
@property
|
||||
def duration(self):
|
||||
return self.end_time - self.start_time
|
||||
|
||||
def __repr__(self):
|
||||
return '<query statement="%s" parameters=%r duration=%.03f>' % (
|
||||
self.statement,
|
||||
self.parameters,
|
||||
self.duration
|
||||
)
|
||||
|
||||
|
||||
def _calling_context(app_path):
|
||||
frm = sys._getframe(1)
|
||||
while frm.f_back is not None:
|
||||
name = frm.f_globals.get('__name__')
|
||||
if name and (name == app_path or name.startswith(app_path + '.')):
|
||||
funcname = frm.f_code.co_name
|
||||
return '%s:%s (%s)' % (
|
||||
frm.f_code.co_filename,
|
||||
frm.f_lineno,
|
||||
funcname
|
||||
)
|
||||
frm = frm.f_back
|
||||
return '<unknown>'
|
||||
|
||||
|
||||
class _SignallingSession(Session):
|
||||
|
||||
def __init__(self, db, autocommit=False, autoflush=False, **options):
|
||||
self.app = db.get_app()
|
||||
self._model_changes = {}
|
||||
Session.__init__(self, autocommit=autocommit, autoflush=autoflush,
|
||||
bind=db.engine,
|
||||
binds=db.get_binds(self.app), **options)
|
||||
|
||||
def get_bind(self, mapper, clause=None):
|
||||
# mapper is None if someone tries to just get a connection
|
||||
if mapper is not None:
|
||||
info = getattr(mapper.mapped_table, 'info', {})
|
||||
bind_key = info.get('bind_key')
|
||||
if bind_key is not None:
|
||||
state = get_state(self.app)
|
||||
return state.db.get_engine(self.app, bind=bind_key)
|
||||
return Session.get_bind(self, mapper, clause)
|
||||
|
||||
|
||||
class _SessionSignalEvents(object):
|
||||
|
||||
def register(self):
|
||||
listen(Session, 'before_commit', self.session_signal_before_commit)
|
||||
listen(Session, 'after_commit', self.session_signal_after_commit)
|
||||
listen(Session, 'after_rollback', self.session_signal_after_rollback)
|
||||
|
||||
@staticmethod
|
||||
def session_signal_before_commit(session):
|
||||
d = session._model_changes
|
||||
if d:
|
||||
before_models_committed.send(session.app, changes=d.values())
|
||||
|
||||
@staticmethod
|
||||
def session_signal_after_commit(session):
|
||||
d = session._model_changes
|
||||
if d:
|
||||
models_committed.send(session.app, changes=d.values())
|
||||
d.clear()
|
||||
|
||||
@staticmethod
|
||||
def session_signal_after_rollback(session):
|
||||
session._model_changes.clear()
|
||||
|
||||
|
||||
class _MapperSignalEvents(object):
|
||||
|
||||
def __init__(self, mapper):
|
||||
self.mapper = mapper
|
||||
|
||||
def register(self):
|
||||
listen(self.mapper, 'after_delete', self.mapper_signal_after_delete)
|
||||
listen(self.mapper, 'after_insert', self.mapper_signal_after_insert)
|
||||
listen(self.mapper, 'after_update', self.mapper_signal_after_update)
|
||||
|
||||
def mapper_signal_after_delete(self, mapper, connection, target):
|
||||
self._record(mapper, target, 'delete')
|
||||
|
||||
def mapper_signal_after_insert(self, mapper, connection, target):
|
||||
self._record(mapper, target, 'insert')
|
||||
|
||||
def mapper_signal_after_update(self, mapper, connection, target):
|
||||
self._record(mapper, target, 'update')
|
||||
|
||||
@staticmethod
|
||||
def _record(mapper, target, operation):
|
||||
s = orm.object_session(target)
|
||||
if isinstance(s, _SignallingSession):
|
||||
pk = tuple(mapper.primary_key_from_instance(target))
|
||||
s._model_changes[pk] = (target, operation)
|
||||
|
||||
|
||||
|
||||
class _EngineDebuggingSignalEvents(object):
|
||||
"""Sets up handlers for two events that let us track the execution time of queries."""
|
||||
|
||||
def __init__(self, engine, import_name):
|
||||
self.engine = engine
|
||||
self.app_package = import_name
|
||||
|
||||
def register(self):
|
||||
listen(self.engine, 'before_cursor_execute', self.before_cursor_execute)
|
||||
listen(self.engine, 'after_cursor_execute', self.after_cursor_execute)
|
||||
|
||||
def before_cursor_execute(self, conn, cursor, statement,
|
||||
parameters, context, executemany):
|
||||
if connection_stack.top is not None:
|
||||
context._query_start_time = _timer()
|
||||
|
||||
def after_cursor_execute(self, conn, cursor, statement,
|
||||
parameters, context, executemany):
|
||||
ctx = connection_stack.top
|
||||
if ctx is not None:
|
||||
queries = getattr(ctx, 'sqlalchemy_queries', None)
|
||||
if queries is None:
|
||||
queries = []
|
||||
setattr(ctx, 'sqlalchemy_queries', queries)
|
||||
queries.append( _DebugQueryTuple( (
|
||||
statement, parameters, context._query_start_time, _timer(),
|
||||
_calling_context(self.app_package) ) ) )
|
||||
|
||||
|
||||
def get_debug_queries():
|
||||
"""In debug mode Flask-SQLAlchemy will log all the SQL queries sent
|
||||
to the database. This information is available until the end of request
|
||||
which makes it possible to easily ensure that the SQL generated is the
|
||||
one expected on errors or in unittesting. If you don't want to enable
|
||||
the DEBUG mode for your unittests you can also enable the query
|
||||
recording by setting the ``'SQLALCHEMY_RECORD_QUERIES'`` config variable
|
||||
to `True`. This is automatically enabled if Flask is in testing mode.
|
||||
|
||||
The value returned will be a list of named tuples with the following
|
||||
attributes:
|
||||
|
||||
`statement`
|
||||
The SQL statement issued
|
||||
|
||||
`parameters`
|
||||
The parameters for the SQL statement
|
||||
|
||||
`start_time` / `end_time`
|
||||
Time the query started / the results arrived. Please keep in mind
|
||||
that the timer function used depends on your platform. These
|
||||
values are only useful for sorting or comparing. They do not
|
||||
necessarily represent an absolute timestamp.
|
||||
|
||||
`duration`
|
||||
Time the query took in seconds
|
||||
|
||||
`context`
|
||||
A string giving a rough estimation of where in your application
|
||||
query was issued. The exact format is undefined so don't try
|
||||
to reconstruct filename or function name.
|
||||
"""
|
||||
return getattr(connection_stack.top, 'sqlalchemy_queries', [])
|
||||
|
||||
|
||||
class Pagination(object):
|
||||
"""Internal helper class returned by :meth:`BaseQuery.paginate`. You
|
||||
can also construct it from any other SQLAlchemy query object if you are
|
||||
working with other libraries. Additionally it is possible to pass `None`
|
||||
as query object in which case the :meth:`prev` and :meth:`next` will
|
||||
no longer work.
|
||||
"""
|
||||
|
||||
def __init__(self, query, page, per_page, total, items):
|
||||
#: the unlimited query object that was used to create this
|
||||
#: pagination object.
|
||||
self.query = query
|
||||
#: the current page number (1 indexed)
|
||||
self.page = page
|
||||
#: the number of items to be displayed on a page.
|
||||
self.per_page = per_page
|
||||
#: the total number of items matching the query
|
||||
self.total = total
|
||||
#: the items for the current page
|
||||
self.items = items
|
||||
|
||||
@property
|
||||
def pages(self):
|
||||
"""The total number of pages"""
|
||||
if self.per_page == 0:
|
||||
pages = 0
|
||||
else:
|
||||
pages = int(ceil(self.total / float(self.per_page)))
|
||||
return pages
|
||||
|
||||
def prev(self, error_out=False):
|
||||
"""Returns a :class:`Pagination` object for the previous page."""
|
||||
assert self.query is not None, 'a query object is required ' \
|
||||
'for this method to work'
|
||||
return self.query.paginate(self.page - 1, self.per_page, error_out)
|
||||
|
||||
@property
|
||||
def prev_num(self):
|
||||
"""Number of the previous page."""
|
||||
return self.page - 1
|
||||
|
||||
@property
|
||||
def has_prev(self):
|
||||
"""True if a previous page exists"""
|
||||
return self.page > 1
|
||||
|
||||
def next(self, error_out=False):
|
||||
"""Returns a :class:`Pagination` object for the next page."""
|
||||
assert self.query is not None, 'a query object is required ' \
|
||||
'for this method to work'
|
||||
return self.query.paginate(self.page + 1, self.per_page, error_out)
|
||||
|
||||
@property
|
||||
def has_next(self):
|
||||
"""True if a next page exists."""
|
||||
return self.page < self.pages
|
||||
|
||||
@property
|
||||
def next_num(self):
|
||||
"""Number of the next page"""
|
||||
return self.page + 1
|
||||
|
||||
def iter_pages(self, left_edge=2, left_current=2,
|
||||
right_current=5, right_edge=2):
|
||||
"""Iterates over the page numbers in the pagination. The four
|
||||
parameters control the thresholds how many numbers should be produced
|
||||
from the sides. Skipped page numbers are represented as `None`.
|
||||
This is how you could render such a pagination in the templates:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
{% macro render_pagination(pagination, endpoint) %}
|
||||
<div class=pagination>
|
||||
{%- for page in pagination.iter_pages() %}
|
||||
{% if page %}
|
||||
{% if page != pagination.page %}
|
||||
<a href="{{ url_for(endpoint, page=page) }}">{{ page }}</a>
|
||||
{% else %}
|
||||
<strong>{{ page }}</strong>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class=ellipsis>…</span>
|
||||
{% endif %}
|
||||
{%- endfor %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
"""
|
||||
last = 0
|
||||
for num in xrange(1, self.pages + 1):
|
||||
if num <= left_edge or \
|
||||
(num > self.page - left_current - 1 and \
|
||||
num < self.page + right_current) or \
|
||||
num > self.pages - right_edge:
|
||||
if last + 1 != num:
|
||||
yield None
|
||||
yield num
|
||||
last = num
|
||||
|
||||
|
||||
class BaseQuery(orm.Query):
|
||||
"""The default query object used for models, and exposed as
|
||||
:attr:`~SQLAlchemy.Query`. This can be subclassed and
|
||||
replaced for individual models by setting the :attr:`~Model.query_class`
|
||||
attribute. This is a subclass of a standard SQLAlchemy
|
||||
:class:`~sqlalchemy.orm.query.Query` class and has all the methods of a
|
||||
standard query as well.
|
||||
"""
|
||||
|
||||
def get_or_404(self, ident):
|
||||
"""Like :meth:`get` but aborts with 404 if not found instead of
|
||||
returning `None`.
|
||||
"""
|
||||
rv = self.get(ident)
|
||||
if rv is None:
|
||||
abort(404)
|
||||
return rv
|
||||
|
||||
def first_or_404(self):
|
||||
"""Like :meth:`first` but aborts with 404 if not found instead of
|
||||
returning `None`.
|
||||
"""
|
||||
rv = self.first()
|
||||
if rv is None:
|
||||
abort(404)
|
||||
return rv
|
||||
|
||||
def paginate(self, page, per_page=20, error_out=True):
|
||||
"""Returns `per_page` items from page `page`. By default it will
|
||||
abort with 404 if no items were found and the page was larger than
|
||||
1. This behavor can be disabled by setting `error_out` to `False`.
|
||||
|
||||
Returns an :class:`Pagination` object.
|
||||
"""
|
||||
if error_out and page < 1:
|
||||
abort(404)
|
||||
items = self.limit(per_page).offset((page - 1) * per_page).all()
|
||||
if not items and page != 1 and error_out:
|
||||
abort(404)
|
||||
|
||||
# No need to count if we're on the first page and there are fewer
|
||||
# items than we expected.
|
||||
if page == 1 and len(items) < per_page:
|
||||
total = len(items)
|
||||
else:
|
||||
total = self.order_by(None).count()
|
||||
|
||||
return Pagination(self, page, per_page, total, items)
|
||||
|
||||
|
||||
class _QueryProperty(object):
|
||||
|
||||
def __init__(self, sa):
|
||||
self.sa = sa
|
||||
|
||||
def __get__(self, obj, type):
|
||||
try:
|
||||
mapper = orm.class_mapper(type)
|
||||
if mapper:
|
||||
return type.query_class(mapper, session=self.sa.session())
|
||||
except UnmappedClassError:
|
||||
return None
|
||||
|
||||
|
||||
def _record_queries(app):
|
||||
if app.debug:
|
||||
return True
|
||||
rq = app.config['SQLALCHEMY_RECORD_QUERIES']
|
||||
if rq is not None:
|
||||
return rq
|
||||
return bool(app.config.get('TESTING'))
|
||||
|
||||
|
||||
class _EngineConnector(object):
|
||||
|
||||
def __init__(self, sa, app, bind=None):
|
||||
self._sa = sa
|
||||
self._app = app
|
||||
self._engine = None
|
||||
self._connected_for = None
|
||||
self._bind = bind
|
||||
self._lock = Lock()
|
||||
|
||||
def get_uri(self):
|
||||
if self._bind is None:
|
||||
return self._app.config['SQLALCHEMY_DATABASE_URI']
|
||||
binds = self._app.config.get('SQLALCHEMY_BINDS') or ()
|
||||
assert self._bind in binds, \
|
||||
'Bind %r is not specified. Set it in the SQLALCHEMY_BINDS ' \
|
||||
'configuration variable' % self._bind
|
||||
return binds[self._bind]
|
||||
|
||||
def get_engine(self):
|
||||
with self._lock:
|
||||
uri = self.get_uri()
|
||||
echo = self._app.config['SQLALCHEMY_ECHO']
|
||||
if (uri, echo) == self._connected_for:
|
||||
return self._engine
|
||||
info = make_url(uri)
|
||||
options = {'convert_unicode': True}
|
||||
self._sa.apply_pool_defaults(self._app, options)
|
||||
self._sa.apply_driver_hacks(self._app, info, options)
|
||||
if echo:
|
||||
options['echo'] = True
|
||||
self._engine = rv = sqlalchemy.create_engine(info, **options)
|
||||
if _record_queries(self._app):
|
||||
_EngineDebuggingSignalEvents(self._engine,
|
||||
self._app.import_name).register()
|
||||
self._connected_for = (uri, echo)
|
||||
return rv
|
||||
|
||||
|
||||
def _defines_primary_key(d):
|
||||
"""Figures out if the given dictonary defines a primary key column."""
|
||||
return any(v.primary_key for k, v in iteritems(d)
|
||||
if isinstance(v, sqlalchemy.Column))
|
||||
|
||||
|
||||
class _BoundDeclarativeMeta(DeclarativeMeta):
|
||||
|
||||
def __new__(cls, name, bases, d):
|
||||
tablename = d.get('__tablename__')
|
||||
|
||||
# generate a table name automatically if it's missing and the
|
||||
# class dictionary declares a primary key. We cannot always
|
||||
# attach a primary key to support model inheritance that does
|
||||
# not use joins. We also don't want a table name if a whole
|
||||
# table is defined
|
||||
if not tablename and d.get('__table__') is None and \
|
||||
_defines_primary_key(d):
|
||||
def _join(match):
|
||||
word = match.group()
|
||||
if len(word) > 1:
|
||||
return ('_%s_%s' % (word[:-1], word[-1])).lower()
|
||||
return '_' + word.lower()
|
||||
d['__tablename__'] = _camelcase_re.sub(_join, name).lstrip('_')
|
||||
|
||||
return DeclarativeMeta.__new__(cls, name, bases, d)
|
||||
|
||||
def __init__(self, name, bases, d):
|
||||
bind_key = d.pop('__bind_key__', None)
|
||||
DeclarativeMeta.__init__(self, name, bases, d)
|
||||
if bind_key is not None:
|
||||
self.__table__.info['bind_key'] = bind_key
|
||||
|
||||
|
||||
def get_state(app):
|
||||
"""Gets the state for the application"""
|
||||
assert 'sqlalchemy' in app.extensions, \
|
||||
'The sqlalchemy extension was not registered to the current ' \
|
||||
'application. Please make sure to call init_app() first.'
|
||||
return app.extensions['sqlalchemy']
|
||||
|
||||
|
||||
class _SQLAlchemyState(object):
|
||||
"""Remembers configuration for the (db, app) tuple."""
|
||||
|
||||
def __init__(self, db, app):
|
||||
self.db = db
|
||||
self.app = app
|
||||
self.connectors = {}
|
||||
|
||||
|
||||
class Model(object):
|
||||
"""Baseclass for custom user models."""
|
||||
|
||||
#: the query class used. The :attr:`query` attribute is an instance
|
||||
#: of this class. By default a :class:`BaseQuery` is used.
|
||||
query_class = BaseQuery
|
||||
|
||||
#: an instance of :attr:`query_class`. Can be used to query the
|
||||
#: database for instances of this model.
|
||||
query = None
|
||||
|
||||
|
||||
class SQLAlchemy(object):
|
||||
"""This class is used to control the SQLAlchemy integration to one
|
||||
or more Flask applications. Depending on how you initialize the
|
||||
object it is usable right away or will attach as needed to a
|
||||
Flask application.
|
||||
|
||||
There are two usage modes which work very similar. One is binding
|
||||
the instance to a very specific Flask application::
|
||||
|
||||
app = Flask(__name__)
|
||||
db = SQLAlchemy(app)
|
||||
|
||||
The second possibility is to create the object once and configure the
|
||||
application later to support it::
|
||||
|
||||
db = SQLAlchemy()
|
||||
|
||||
def create_app():
|
||||
app = Flask(__name__)
|
||||
db.init_app(app)
|
||||
return app
|
||||
|
||||
The difference between the two is that in the first case methods like
|
||||
:meth:`create_all` and :meth:`drop_all` will work all the time but in
|
||||
the second case a :meth:`flask.Flask.request_context` has to exist.
|
||||
|
||||
By default Flask-SQLAlchemy will apply some backend-specific settings
|
||||
to improve your experience with them. As of SQLAlchemy 0.6 SQLAlchemy
|
||||
will probe the library for native unicode support. If it detects
|
||||
unicode it will let the library handle that, otherwise do that itself.
|
||||
Sometimes this detection can fail in which case you might want to set
|
||||
`use_native_unicode` (or the ``SQLALCHEMY_NATIVE_UNICODE`` configuration
|
||||
key) to `False`. Note that the configuration key overrides the
|
||||
value you pass to the constructor.
|
||||
|
||||
This class also provides access to all the SQLAlchemy functions and classes
|
||||
from the :mod:`sqlalchemy` and :mod:`sqlalchemy.orm` modules. So you can
|
||||
declare models like this::
|
||||
|
||||
class User(db.Model):
|
||||
username = db.Column(db.String(80), unique=True)
|
||||
pw_hash = db.Column(db.String(80))
|
||||
|
||||
You can still use :mod:`sqlalchemy` and :mod:`sqlalchemy.orm` directly, but
|
||||
note that Flask-SQLAlchemy customizations are available only through an
|
||||
instance of this :class:`SQLAlchemy` class. Query classes default to
|
||||
:class:`BaseQuery` for `db.Query`, `db.Model.query_class`, and the default
|
||||
query_class for `db.relationship` and `db.backref`. If you use these
|
||||
interfaces through :mod:`sqlalchemy` and :mod:`sqlalchemy.orm` directly,
|
||||
the default query class will be that of :mod:`sqlalchemy`.
|
||||
|
||||
.. admonition:: Check types carefully
|
||||
|
||||
Don't perform type or `isinstance` checks against `db.Table`, which
|
||||
emulates `Table` behavior but is not a class. `db.Table` exposes the
|
||||
`Table` interface, but is a function which allows omission of metadata.
|
||||
|
||||
You may also define your own SessionExtension instances as well when
|
||||
defining your SQLAlchemy class instance. You may pass your custom instances
|
||||
to the `session_extensions` keyword. This can be either a single
|
||||
SessionExtension instance, or a list of SessionExtension instances. In the
|
||||
following use case we use the VersionedListener from the SQLAlchemy
|
||||
versioning examples.::
|
||||
|
||||
from history_meta import VersionedMeta, VersionedListener
|
||||
|
||||
app = Flask(__name__)
|
||||
db = SQLAlchemy(app, session_extensions=[VersionedListener()])
|
||||
|
||||
class User(db.Model):
|
||||
__metaclass__ = VersionedMeta
|
||||
username = db.Column(db.String(80), unique=True)
|
||||
pw_hash = db.Column(db.String(80))
|
||||
|
||||
The `session_options` parameter can be used to override session
|
||||
options. If provided it's a dict of parameters passed to the
|
||||
session's constructor.
|
||||
|
||||
.. versionadded:: 0.10
|
||||
The `session_options` parameter was added.
|
||||
|
||||
.. versionadded:: 0.16
|
||||
`scopefunc` is now accepted on `session_options`. It allows specifying
|
||||
a custom function which will define the SQLAlchemy session's scoping.
|
||||
"""
|
||||
|
||||
def __init__(self, app=None,
|
||||
use_native_unicode=True,
|
||||
session_options=None):
|
||||
self.use_native_unicode = use_native_unicode
|
||||
|
||||
if session_options is None:
|
||||
session_options = {}
|
||||
|
||||
session_options.setdefault(
|
||||
'scopefunc', connection_stack.__ident_func__
|
||||
)
|
||||
|
||||
self.session = self.create_scoped_session(session_options)
|
||||
self.Model = self.make_declarative_base()
|
||||
self._engine_lock = Lock()
|
||||
|
||||
if app is not None:
|
||||
self.app = app
|
||||
self.init_app(app)
|
||||
else:
|
||||
self.app = None
|
||||
|
||||
_include_sqlalchemy(self)
|
||||
_MapperSignalEvents(self.mapper).register()
|
||||
_SessionSignalEvents().register()
|
||||
self.Query = BaseQuery
|
||||
|
||||
@property
|
||||
def metadata(self):
|
||||
"""Returns the metadata"""
|
||||
return self.Model.metadata
|
||||
|
||||
def create_scoped_session(self, options=None):
|
||||
"""Helper factory method that creates a scoped session."""
|
||||
if options is None:
|
||||
options = {}
|
||||
scopefunc=options.pop('scopefunc', None)
|
||||
return orm.scoped_session(
|
||||
partial(_SignallingSession, self, **options), scopefunc=scopefunc
|
||||
)
|
||||
|
||||
def make_declarative_base(self):
|
||||
"""Creates the declarative base."""
|
||||
base = declarative_base(cls=Model, name='Model',
|
||||
metaclass=_BoundDeclarativeMeta)
|
||||
base.query = _QueryProperty(self)
|
||||
return base
|
||||
|
||||
def init_app(self, app):
|
||||
"""This callback can be used to initialize an application for the
|
||||
use with this database setup. Never use a database in the context
|
||||
of an application not initialized that way or connections will
|
||||
leak.
|
||||
"""
|
||||
app.config.setdefault('SQLALCHEMY_DATABASE_URI', 'sqlite://')
|
||||
app.config.setdefault('SQLALCHEMY_BINDS', None)
|
||||
app.config.setdefault('SQLALCHEMY_NATIVE_UNICODE', None)
|
||||
app.config.setdefault('SQLALCHEMY_ECHO', False)
|
||||
app.config.setdefault('SQLALCHEMY_RECORD_QUERIES', None)
|
||||
app.config.setdefault('SQLALCHEMY_POOL_SIZE', None)
|
||||
app.config.setdefault('SQLALCHEMY_POOL_TIMEOUT', None)
|
||||
app.config.setdefault('SQLALCHEMY_POOL_RECYCLE', None)
|
||||
app.config.setdefault('SQLALCHEMY_MAX_OVERFLOW', None)
|
||||
app.config.setdefault('SQLALCHEMY_COMMIT_ON_TEARDOWN', False)
|
||||
|
||||
if not hasattr(app, 'extensions'):
|
||||
app.extensions = {}
|
||||
app.extensions['sqlalchemy'] = _SQLAlchemyState(self, app)
|
||||
|
||||
# 0.9 and later
|
||||
if hasattr(app, 'teardown_appcontext'):
|
||||
teardown = app.teardown_appcontext
|
||||
# 0.7 to 0.8
|
||||
elif hasattr(app, 'teardown_request'):
|
||||
teardown = app.teardown_request
|
||||
# Older Flask versions
|
||||
else:
|
||||
if app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN']:
|
||||
raise RuntimeError("Commit on teardown requires Flask >= 0.7")
|
||||
teardown = app.after_request
|
||||
|
||||
@teardown
|
||||
def shutdown_session(response_or_exc):
|
||||
if app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN']:
|
||||
if response_or_exc is None:
|
||||
self.session.commit()
|
||||
self.session.remove()
|
||||
return response_or_exc
|
||||
|
||||
def apply_pool_defaults(self, app, options):
|
||||
def _setdefault(optionkey, configkey):
|
||||
value = app.config[configkey]
|
||||
if value is not None:
|
||||
options[optionkey] = value
|
||||
_setdefault('pool_size', 'SQLALCHEMY_POOL_SIZE')
|
||||
_setdefault('pool_timeout', 'SQLALCHEMY_POOL_TIMEOUT')
|
||||
_setdefault('pool_recycle', 'SQLALCHEMY_POOL_RECYCLE')
|
||||
_setdefault('max_overflow', 'SQLALCHEMY_MAX_OVERFLOW')
|
||||
|
||||
def apply_driver_hacks(self, app, info, options):
|
||||
"""This method is called before engine creation and used to inject
|
||||
driver specific hacks into the options. The `options` parameter is
|
||||
a dictionary of keyword arguments that will then be used to call
|
||||
the :func:`sqlalchemy.create_engine` function.
|
||||
|
||||
The default implementation provides some saner defaults for things
|
||||
like pool sizes for MySQL and sqlite. Also it injects the setting of
|
||||
`SQLALCHEMY_NATIVE_UNICODE`.
|
||||
"""
|
||||
if info.drivername.startswith('mysql'):
|
||||
info.query.setdefault('charset', 'utf8')
|
||||
if info.drivername != 'mysql+gaerdbms':
|
||||
options.setdefault('pool_size', 10)
|
||||
options.setdefault('pool_recycle', 7200)
|
||||
elif info.drivername == 'sqlite':
|
||||
pool_size = options.get('pool_size')
|
||||
detected_in_memory = False
|
||||
# we go to memory and the pool size was explicitly set to 0
|
||||
# which is fail. Let the user know that
|
||||
if info.database in (None, '', ':memory:'):
|
||||
detected_in_memory = True
|
||||
if pool_size == 0:
|
||||
raise RuntimeError('SQLite in memory database with an '
|
||||
'empty queue not possible due to data '
|
||||
'loss.')
|
||||
# if pool size is None or explicitly set to 0 we assume the
|
||||
# user did not want a queue for this sqlite connection and
|
||||
# hook in the null pool.
|
||||
elif not pool_size:
|
||||
from sqlalchemy.pool import NullPool
|
||||
options['poolclass'] = NullPool
|
||||
|
||||
# if it's not an in memory database we make the path absolute.
|
||||
if not detected_in_memory:
|
||||
info.database = os.path.join(app.root_path, info.database)
|
||||
|
||||
unu = app.config['SQLALCHEMY_NATIVE_UNICODE']
|
||||
if unu is None:
|
||||
unu = self.use_native_unicode
|
||||
if not unu:
|
||||
options['use_native_unicode'] = False
|
||||
|
||||
@property
|
||||
def engine(self):
|
||||
"""Gives access to the engine. If the database configuration is bound
|
||||
to a specific application (initialized with an application) this will
|
||||
always return a database connection. If however the current application
|
||||
is used this might raise a :exc:`RuntimeError` if no application is
|
||||
active at the moment.
|
||||
"""
|
||||
return self.get_engine(self.get_app())
|
||||
|
||||
def make_connector(self, app, bind=None):
|
||||
"""Creates the connector for a given state and bind."""
|
||||
return _EngineConnector(self, app, bind)
|
||||
|
||||
def get_engine(self, app, bind=None):
|
||||
"""Returns a specific engine.
|
||||
|
||||
.. versionadded:: 0.12
|
||||
"""
|
||||
with self._engine_lock:
|
||||
state = get_state(app)
|
||||
connector = state.connectors.get(bind)
|
||||
if connector is None:
|
||||
connector = self.make_connector(app, bind)
|
||||
state.connectors[bind] = connector
|
||||
return connector.get_engine()
|
||||
|
||||
def get_app(self, reference_app=None):
|
||||
"""Helper method that implements the logic to look up an application.
|
||||
"""
|
||||
if reference_app is not None:
|
||||
return reference_app
|
||||
if self.app is not None:
|
||||
return self.app
|
||||
ctx = connection_stack.top
|
||||
if ctx is not None:
|
||||
return ctx.app
|
||||
raise RuntimeError('application not registered on db '
|
||||
'instance and no application bound '
|
||||
'to current context')
|
||||
|
||||
def get_tables_for_bind(self, bind=None):
|
||||
"""Returns a list of all tables relevant for a bind."""
|
||||
result = []
|
||||
for table in itervalues(self.Model.metadata.tables):
|
||||
if table.info.get('bind_key') == bind:
|
||||
result.append(table)
|
||||
return result
|
||||
|
||||
def get_binds(self, app=None):
|
||||
"""Returns a dictionary with a table->engine mapping.
|
||||
|
||||
This is suitable for use of sessionmaker(binds=db.get_binds(app)).
|
||||
"""
|
||||
app = self.get_app(app)
|
||||
binds = [None] + list(app.config.get('SQLALCHEMY_BINDS') or ())
|
||||
retval = {}
|
||||
for bind in binds:
|
||||
engine = self.get_engine(app, bind)
|
||||
tables = self.get_tables_for_bind(bind)
|
||||
retval.update(dict((table, engine) for table in tables))
|
||||
return retval
|
||||
|
||||
def _execute_for_all_tables(self, app, bind, operation):
|
||||
app = self.get_app(app)
|
||||
|
||||
if bind == '__all__':
|
||||
binds = [None] + list(app.config.get('SQLALCHEMY_BINDS') or ())
|
||||
elif isinstance(bind, basestring) or bind is None:
|
||||
binds = [bind]
|
||||
else:
|
||||
binds = bind
|
||||
|
||||
for bind in binds:
|
||||
tables = self.get_tables_for_bind(bind)
|
||||
op = getattr(self.Model.metadata, operation)
|
||||
op(bind=self.get_engine(app, bind), tables=tables)
|
||||
|
||||
def create_all(self, bind='__all__', app=None):
|
||||
"""Creates all tables.
|
||||
|
||||
.. versionchanged:: 0.12
|
||||
Parameters were added
|
||||
"""
|
||||
self._execute_for_all_tables(app, bind, 'create_all')
|
||||
|
||||
def drop_all(self, bind='__all__', app=None):
|
||||
"""Drops all tables.
|
||||
|
||||
.. versionchanged:: 0.12
|
||||
Parameters were added
|
||||
"""
|
||||
self._execute_for_all_tables(app, bind, 'drop_all')
|
||||
|
||||
def reflect(self, bind='__all__', app=None):
|
||||
"""Reflects tables from the database.
|
||||
|
||||
.. versionchanged:: 0.12
|
||||
Parameters were added
|
||||
"""
|
||||
self._execute_for_all_tables(app, bind, 'reflect')
|
||||
|
||||
def __repr__(self):
|
||||
app = None
|
||||
if self.app is not None:
|
||||
app = self.app
|
||||
else:
|
||||
ctx = connection_stack.top
|
||||
if ctx is not None:
|
||||
app = ctx.app
|
||||
return '<%s engine=%r>' % (
|
||||
self.__class__.__name__,
|
||||
app and app.config['SQLALCHEMY_DATABASE_URI'] or None
|
||||
)
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
flaskext.sqlalchemy._compat
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Internal Python 2.x/3.x compatibility layer.
|
||||
|
||||
:copyright: (c) 2013 by Daniel Neuhäuser
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import sys
|
||||
|
||||
|
||||
PY2 = sys.version_info[0] == 2
|
||||
|
||||
|
||||
if PY2:
|
||||
def iteritems(d):
|
||||
return d.iteritems()
|
||||
|
||||
def itervalues(d):
|
||||
return d.itervalues()
|
||||
|
||||
xrange = xrange
|
||||
|
||||
string_types = (unicode, bytes)
|
||||
|
||||
else:
|
||||
def iteritems(d):
|
||||
return iter(d.items())
|
||||
|
||||
def itervalues(d):
|
||||
return iter(d.values())
|
||||
|
||||
xrange = range
|
||||
|
||||
string_types = (str, )
|
||||
Loading…
Add table
Add a link
Reference in a new issue