Skip to content

Commit

Permalink
Added improved documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsuhiko committed Jun 2, 2010
1 parent ab6cd65 commit 15dfb48
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 32 deletions.
Binary file added docs/_static/flask-sqlalchemy.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 8 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
sys.path.append(os.path.abspath('_themes'))

# -- General configuration -----------------------------------------------------
Expand Down Expand Up @@ -96,7 +97,10 @@
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
html_theme_options = {
'index_logo': 'flask-sqlalchemy.png',
'github_fork': 'mitsuhiko/flask-sqlalchemy'
}

# Add any paths that contain custom themes here, relative to this directory.
html_theme_path = ['_themes']
Expand Down Expand Up @@ -217,4 +221,6 @@


# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'http://docs.python.org/': None}
intersphinx_mapping = {'http://docs.python.org/': None,
'http://flask.pocoo.org/docs/': None,
'http://www.sqlalchemy.org/docs/': None}
81 changes: 67 additions & 14 deletions docs/index.rst
Original file line number Diff line number Diff line change
@@ -1,20 +1,73 @@
.. Flask-SQLAlchemy documentation master file, created by
sphinx-quickstart on Tue Jun 1 14:31:57 2010.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Flask-SQLAlchemy
================

Welcome to Flask-SQLAlchemy's documentation!
============================================
.. module:: flaskext.sqlalchemy

Contents:
Flask-SQLAlchemy adds support for `SQLAlchemy`_ to your `Flask`_
application. This module is currently still under development and the
documentation is lacking. If you want to get started, have a look at the
`example sourcecode`_.

.. toctree::
:maxdepth: 2
.. _SQLAlchemy: http://sqlalchemy.org/
.. _Flask: http://flask.pocoo.org/
.. _example sourcecode:
http://github.com/mitsuhiko/flask-sqlalchemy/tree/master/examples/

Indices and tables
==================
API
---

* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
This part of the documentation documents each and every public class or
function from Flask-SQLAlchemy.

Configuration
-------------

.. autoclass:: SQLAlchemy
:members:

Models
------

.. autoclass:: Model
:members:

.. autoclass:: BaseQuery
:members: get, get_or_404, paginate

.. method:: get(ident)

Return an instance of the object based on the given identifier
(primary key), or `None` if not found.

.. method:: all()

Return the results represented by this query as a list. This
results in an execution of the underlying query.

.. method:: order_by(*criteron)

apply one or more ORDER BY criterion to the query and return the
newly resulting query.

.. method:: limit(limit)

Apply a LIMIT to the query and return the newly resulting query.

.. method:: offset(offset)

Apply an OFFSET to the query and return the newly resulting
query.

.. method:: first()

Return the first result of this query or `None` if the result
doesn’t contain any row. This results in an execution of the
underlying query.

.. autoclass:: Pagination
:members:

Debug Helpers
-------------

.. autofunction:: get_debug_queries
2 changes: 1 addition & 1 deletion examples/hello.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
db = SQLAlchemy(app)


class Todo(db.Base):
class Todo(db.Model):
__tablename__ = 'todos'
id = db.Column('todo_id', db.Integer, primary_key=True)
title = db.Column(db.String(60))
Expand Down
156 changes: 141 additions & 15 deletions flaskext/sqlalchemy.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import time
import sqlalchemy
from math import ceil
from types import MethodType
from flask import _request_ctx_stack, abort
from operator import itemgetter
from threading import Lock
Expand Down Expand Up @@ -57,7 +58,7 @@ def __repr__(self):
return '<query statement="%s" parameters=%r duration=%.03f>' % (
self.statement,
self.parameters,
self.duration
shelf.duration
)


Expand All @@ -67,7 +68,6 @@ def _calling_context(app_path):
name = frm.f_globals.get('__name__')
if name and (name == app_path or name.startswith(app_path + '.')):
funcname = frm.f_code.co_name
# TODO: detect methods
return '%s:%s (%s)' % (
frm.f_code.co_filename,
frm.f_lineno,
Expand Down Expand Up @@ -101,54 +101,121 @@ def cursor_execute(self, execute, cursor, statement, parameters,


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`.
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 for your platform might different this
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(_request_ctx_stack.top, 'sqlalchemy_queries', [])


class Pagination(object):
"""Internal helper class returned by :meth:`BaseQuery.paginate`."""

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"""
return int(ceil(self.total / float(self.per_page)))

def prev(self, error_out=False):
"""Returns a :class:`Pagination` object for the previous page."""
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

def have_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."""
return self.query.paginate(self.page + 1, self.per_page, error_out)

def have_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

class BaseQuery(orm.Query):

def get_or_404(self, *args, **kwargs):
rv = self.get(*args, **kwargs)
class BaseQuery(orm.Query):
"""The default query object used for models. 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 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)
total_count = self.count()
items = self.limit(per_page).offset((page - 1) * per_page).all()
if not items and page != 1 and error_out:
abort(404)
return Pagination(self, page, per_page, total, items)
return Pagination(self, page, per_page, self.count(), items)


class QueryProperty(object):
class _QueryProperty(object):

def __init__(self, sa):
self.sa = sa
Expand Down Expand Up @@ -181,7 +248,8 @@ def get_engine(self):
info = make_url(uri)
options = {'convert_unicode': True}
self._sa.apply_driver_hacks(info, options)
if self._app.debug:
if self._app.debug or \
self._app.config['SQLALCHEMY_RECORD_QUERIES']:
options['proxy'] = _ConnectionDebugProxy(self._app.import_name)
if echo:
options['echo'] = True
Expand All @@ -190,15 +258,58 @@ def get_engine(self):
return rv


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 __init__(self, app=None, disable_native_unicode=False):
self.disable_native_unicode = disable_native_unicode
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 section 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` to `False`.
"""

def __init__(self, app=None, use_native_unicode=True):
self.use_native_unicode = use_native_unicode
self.session = _create_scoped_session(self)

self.Base = declarative_base()
self.Base.query_class = BaseQuery
self.Base.query = QueryProperty(self)
self.Model = declarative_base(cls=Model, name='Model')
self.Model.query = _QueryProperty(self)

self._engine_lock = Lock()

Expand All @@ -211,8 +322,15 @@ def __init__(self, app=None, disable_native_unicode=False):
_include_sqlalchemy(self)

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_ECHO', False)
app.config.setdefault('SQLALCHEMY_RECORD_QUERIES', False)

@app.after_request
def shutdown_session(response):
self.session.remove()
Expand All @@ -221,11 +339,17 @@ def shutdown_session(response):
def apply_driver_hacks(self, info, options):
if info.drivername == 'mysql':
info.query.setdefault('charset', 'utf8')
if self.disable_native_unicode:
if not self.use_native_unicode:
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.
"""
with self._engine_lock:
if self.app is not None:
app = self.app
Expand All @@ -244,9 +368,11 @@ def engine(self):
return connector.get_engine()

def create_all(self):
"""Creates all tables."""
self.Base.metadata.create_all(bind=self.engine)

def drop_all(self):
"""Drops all tables."""
self.Base.metadata.drop_all(bind=self.engine)

def __repr__(self):
Expand Down

0 comments on commit 15dfb48

Please sign in to comment.