diff -Nru flask-sqlalchemy-2.2/CHANGES flask-sqlalchemy-2.1/CHANGES --- flask-sqlalchemy-2.2/CHANGES 2017-02-27 15:20:34.000000000 +0000 +++ flask-sqlalchemy-2.1/CHANGES 2015-10-23 09:49:12.000000000 +0000 @@ -1,32 +1,13 @@ Changelog ========= -Version 2.2 ------------ - -Released on February 27, 2017, codename Dubnium +Here you can see the full list of changes between each Flask-SQLAlchemy +release. -- Minimum SQLAlchemy version is 0.8 due to use of ``sqlalchemy.inspect``. -- Added support for custom ``query_class`` and ``model_class`` as args - to the ``SQLAlchemy`` constructor. (`#328`_) -- Allow listening to SQLAlchemy events on ``db.session``. (`#364`_) -- Allow ``__bind_key__`` on abstract models. (`#373`_) -- Allow ``SQLALCHEMY_ECHO`` to be a string. (`#409`_) -- Warn when ``SQLALCHEMY_DATABASE_URI`` is not set. (`#443`_) -- Don't let pagination generate invalid page numbers. (`#460`_) -- Drop support of Flask < 0.10. This means the db session is always tied to - the app context and its teardown event. (`#461`_) -- Tablename generation logic no longer accesses class properties unless they - are ``declared_attr``. (`#467`_) +Version 3.0 +----------- -.. _#328: https://github.com/mitsuhiko/flask-sqlalchemy/pull/328 -.. _#364: https://github.com/mitsuhiko/flask-sqlalchemy/pull/364 -.. _#373: https://github.com/mitsuhiko/flask-sqlalchemy/pull/373 -.. _#409: https://github.com/mitsuhiko/flask-sqlalchemy/pull/409 -.. _#443: https://github.com/mitsuhiko/flask-sqlalchemy/pull/443 -.. _#460: https://github.com/mitsuhiko/flask-sqlalchemy/pull/460 -.. _#461: https://github.com/mitsuhiko/flask-sqlalchemy/pull/461 -.. _#467: https://github.com/mitsuhiko/flask-sqlalchemy/pull/467 +In development, codename Dubnium Version 2.1 ----------- diff -Nru flask-sqlalchemy-2.2/debian/changelog flask-sqlalchemy-2.1/debian/changelog --- flask-sqlalchemy-2.2/debian/changelog 2017-06-27 15:19:53.000000000 +0000 +++ flask-sqlalchemy-2.1/debian/changelog 2017-06-27 15:57:20.000000000 +0000 @@ -1,11 +1,11 @@ -flask-sqlalchemy (2.2-1xenial1) xenial; urgency=medium +flask-sqlalchemy (2.1-1xenial1) xenial; urgency=medium * built for privacyidea - -- Cornelius Kölbel Tue, 27 Jun 2017 17:19:53 +0200 + -- Cornelius Kölbel Tue, 27 Jun 2017 17:57:20 +0200 -flask-sqlalchemy (2.2-1) unstable; urgency=low +flask-sqlalchemy (2.1-1) unstable; urgency=low * source package automatically created by stdeb 0.8.5 - -- Cornelius Kölbel Tue, 27 Jun 2017 17:19:52 +0200 + -- Cornelius Kölbel Tue, 27 Jun 2017 17:57:18 +0200 diff -Nru flask-sqlalchemy-2.2/debian/compat flask-sqlalchemy-2.1/debian/compat --- flask-sqlalchemy-2.2/debian/compat 2017-06-27 15:19:52.000000000 +0000 +++ flask-sqlalchemy-2.1/debian/compat 2017-06-27 15:57:18.000000000 +0000 @@ -1 +1 @@ -9 +7 diff -Nru flask-sqlalchemy-2.2/debian/control flask-sqlalchemy-2.1/debian/control --- flask-sqlalchemy-2.2/debian/control 2017-06-27 15:19:52.000000000 +0000 +++ flask-sqlalchemy-2.1/debian/control 2017-06-27 15:57:18.000000000 +0000 @@ -2,9 +2,10 @@ Maintainer: Cornelius Kölbel Section: python Priority: optional -Build-Depends: dh-python, python-setuptools (>= 0.6b3), python-all (>= 2.6.6-3), debhelper (>= 9) -Standards-Version: 3.9.6 -Homepage: http://github.com/mitsuhiko/flask-sqlalchemy +Build-Depends: python-setuptools (>= 0.6b3), python-all (>= 2.6.6-3), debhelper (>= 7) +Standards-Version: 3.9.1 + + Package: python-flask-sqlalchemy Architecture: all @@ -25,3 +26,5 @@ . . + + diff -Nru flask-sqlalchemy-2.2/debian/rules flask-sqlalchemy-2.1/debian/rules --- flask-sqlalchemy-2.2/debian/rules 2017-06-27 15:19:52.000000000 +0000 +++ flask-sqlalchemy-2.1/debian/rules 2017-06-27 15:57:18.000000000 +0000 @@ -1,8 +1,31 @@ #!/usr/bin/make -f # This file was automatically generated by stdeb 0.8.5 at -# Tue, 27 Jun 2017 17:19:52 +0200 -export PYBUILD_NAME=flask-sqlalchemy +# Tue, 27 Jun 2017 17:57:18 +0200 + %: - dh $@ --with python2 --buildsystem=pybuild + dh $@ --with python2 --buildsystem=python_distutils + + +override_dh_auto_clean: + python setup.py clean -a + find . -name \*.pyc -exec rm {} \; + + + +override_dh_auto_build: + python setup.py build --force + + + +override_dh_auto_install: + python setup.py install --force --root=debian/python-flask-sqlalchemy --no-compile -O0 --install-layout=deb --prefix=/usr + + + +override_dh_python2: + dh_python2 --no-guessing-versions + + + diff -Nru flask-sqlalchemy-2.2/debian/source/options flask-sqlalchemy-2.1/debian/source/options --- flask-sqlalchemy-2.2/debian/source/options 1970-01-01 00:00:00.000000000 +0000 +++ flask-sqlalchemy-2.1/debian/source/options 2017-06-27 15:57:18.000000000 +0000 @@ -0,0 +1 @@ +extend-diff-ignore="\.egg-info$" \ No newline at end of file diff -Nru flask-sqlalchemy-2.2/debian/watch flask-sqlalchemy-2.1/debian/watch --- flask-sqlalchemy-2.2/debian/watch 2017-06-27 15:19:52.000000000 +0000 +++ flask-sqlalchemy-2.1/debian/watch 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ -# please also check http://pypi.debian.net/Flask-SQLAlchemy/watch -version=3 -opts=uversionmangle=s/(rc|a|b|c)/~$1/ \ -http://pypi.debian.net/Flask-SQLAlchemy/Flask-SQLAlchemy-(.+)\.(?:zip|tgz|tbz|txz|(?:tar\.(?:gz|bz2|xz))) \ No newline at end of file diff -Nru flask-sqlalchemy-2.2/docs/api.rst flask-sqlalchemy-2.1/docs/api.rst --- flask-sqlalchemy-2.2/docs/api.rst 2016-05-31 16:38:29.000000000 +0000 +++ flask-sqlalchemy-2.1/docs/api.rst 2015-07-28 19:15:32.000000000 +0000 @@ -1,7 +1,7 @@ API --- -.. module:: flask_sqlalchemy +.. module:: flask.ext.sqlalchemy This part of the documentation documents all the public classes and functions in Flask-SQLAlchemy. @@ -12,6 +12,10 @@ .. autoclass:: SQLAlchemy :members: + .. attribute:: Query + + The :class:`BaseQuery` class. + Models `````` @@ -31,7 +35,32 @@ explicitly, that will be used instead. .. autoclass:: BaseQuery - :members: + :members: get, get_or_404, paginate, first_or_404 + + .. method:: all() + + Return the results represented by this query as a list. This + results in an execution of the underlying query. + + .. method:: order_by(*criterion) + + 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 rows. This results in an execution of the + underlying query. Sessions ```````` diff -Nru flask-sqlalchemy-2.2/docs/binds.rst flask-sqlalchemy-2.1/docs/binds.rst --- flask-sqlalchemy-2.2/docs/binds.rst 2016-05-31 16:38:29.000000000 +0000 +++ flask-sqlalchemy-2.1/docs/binds.rst 2015-02-26 17:58:08.000000000 +0000 @@ -1,6 +1,6 @@ .. _binds: -.. currentmodule:: flask_sqlalchemy +.. currentmodule:: flask.ext.sqlalchemy Multiple Databases with Binds ============================= diff -Nru flask-sqlalchemy-2.2/docs/config.rst flask-sqlalchemy-2.1/docs/config.rst --- flask-sqlalchemy-2.2/docs/config.rst 2016-10-28 14:21:05.000000000 +0000 +++ flask-sqlalchemy-2.1/docs/config.rst 2015-07-28 19:15:32.000000000 +0000 @@ -47,10 +47,7 @@ connections after 8 hours idle by default. Note that Flask-SQLAlchemy automatically sets this to 2 hours if - MySQL is used. Some backends may use a - different default timeout value. For more - information about timeouts see - :ref:`timeouts`. + MySQL is used. ``SQLALCHEMY_MAX_OVERFLOW`` Controls the number of connections that can be created after the pool reached its maximum size. When those additional @@ -122,16 +119,17 @@ :class:`~sqlalchemy.schema.MetaData` object. This allows you to, among other things, specify a `custom constraint naming convention -`_ -in conjunction with SQLAlchemy 0.9.2 or higher. +`_. Doing so is important for dealing with database migrations (for instance using `alembic `_ as stated -`here `_. Here's an -example, as suggested by the SQLAlchemy docs:: +`here `_. Since SQL +defines no standard naming conventions, there is no guaranteed nor effective +compatibility by default among database implementations. You can define a +custom naming convention like this as suggested by the SQLAlchemy docs:: from sqlalchemy import MetaData from flask import Flask - from flask_sqlalchemy import SQLAlchemy + from flask.ext.sqlalchemy import SQLAlchemy convention = { "ix": 'ix_%(column_0_label)s', @@ -147,21 +145,3 @@ For more info about :class:`~sqlalchemy.schema.MetaData`, `check out the official docs on it `_. - -.. _timeouts: - -Timeouts --------- - -Certain database backends may impose different inactive connection timeouts, -which interferes with Flask-SQLAlchemy's connection pooling. - -By default, MariaDB is configured to have a 600 second timeout. This often -surfaces hard to debug, production environment only exceptions like ``2013: Lost connection to MySQL server during query``. - -If you are using a backend (or a pre-configured database-as-a-service) with a -lower connection timeout, it is recommended that you set -`SQLALCHEMY_POOL_RECYCLE` to a value less than your backend's timeout. - - - diff -Nru flask-sqlalchemy-2.2/docs/conf.py flask-sqlalchemy-2.1/docs/conf.py --- flask-sqlalchemy-2.2/docs/conf.py 2016-06-14 14:39:26.000000000 +0000 +++ flask-sqlalchemy-2.1/docs/conf.py 2015-07-28 19:15:32.000000000 +0000 @@ -11,15 +11,12 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import os -import sys -from datetime import datetime -import pkg_resources +import sys, os # 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 ----------------------------------------------------- @@ -45,23 +42,24 @@ # General information about the project. project = u'Flask-SQLAlchemy' -copyright = u'2010 - {0}, Armin Ronacher'.format(datetime.utcnow().year) +copyright = u'2010-2014, Armin Ronacher' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. +import pkg_resources try: release = pkg_resources.get_distribution('Flask-SQLAlchemy').version except pkg_resources.DistributionNotFound: - print('To build the documentation, the distribution information of') + print('To build the documentation, The distribution information of') print('Flask-SQLAlchemy has to be available. Either install the package') print('into your development environment or run "setup.py develop"') print('to setup the metadata. A virtualenv is recommended!') sys.exit(1) +del pkg_resources if 'dev' in release: - release = ''.join(release.partition('dev')[:2]) - + release = release.split('dev')[0] + 'dev' version = '.'.join(release.split('.')[:2]) # The language for content autogenerated by Sphinx. Refer to documentation @@ -106,7 +104,7 @@ # further. For a list of options available for each theme, see the # documentation. html_theme_options = { - 'index_logo': 'flask-sqlalchemy-title.png' + 'index_logo': 'flask-sqlalchemy.png' } # Add any paths that contain custom themes here, relative to this directory. @@ -143,8 +141,9 @@ # Custom sidebar templates, maps document names to template names. html_sidebars = { - 'index': ['sidebarlogo.html', 'sidebarintro.html', 'sourcelink.html', 'searchbox.html'], - '**': ['sidebarlogo.html', 'localtoc.html', 'relations.html', 'sourcelink.html', 'searchbox.html'] + 'index': ['sidebarintro.html', 'sourcelink.html', 'searchbox.html'], + '**': ['sidebarlogo.html', 'localtoc.html', 'relations.html', + 'sourcelink.html', 'searchbox.html'] } # Additional templates that should be rendered to pages, maps page names to @@ -225,11 +224,9 @@ # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = { - 'python': ('https://docs.python.org/3/', None), - 'flask': ('http://flask.pocoo.org/docs/', None), - 'sqlalchemy': ('http://docs.sqlalchemy.org/en/latest/', None) -} +intersphinx_mapping = {'http://docs.python.org/': None, + 'http://flask.pocoo.org/docs/': None, + 'http://www.sqlalchemy.org/docs/': None} pygments_style = 'flask_theme_support.FlaskyStyle' # fall back if theme is not there diff -Nru flask-sqlalchemy-2.2/docs/contents.rst.inc flask-sqlalchemy-2.1/docs/contents.rst.inc --- flask-sqlalchemy-2.2/docs/contents.rst.inc 2016-10-28 14:21:05.000000000 +0000 +++ flask-sqlalchemy-2.1/docs/contents.rst.inc 2015-02-26 17:58:08.000000000 +0000 @@ -14,7 +14,6 @@ queries binds signals - customizing API Reference ------------- diff -Nru flask-sqlalchemy-2.2/docs/contexts.rst flask-sqlalchemy-2.1/docs/contexts.rst --- flask-sqlalchemy-2.2/docs/contexts.rst 2016-05-31 16:38:29.000000000 +0000 +++ flask-sqlalchemy-2.1/docs/contexts.rst 2015-07-28 19:15:32.000000000 +0000 @@ -1,6 +1,6 @@ .. _contexts: -.. currentmodule:: flask_sqlalchemy +.. currentmodule:: flask.ext.sqlalchemy Introduction into Contexts ========================== @@ -31,7 +31,7 @@ :class:`SQLAlchemy` object to your application. Why doesn't it do that? Because there might be more than one application created. -So how does :class:`SQLAlchemy` come to know about your application? +So how does :class:`SQLAlchemy` now really know about your application? You will have to setup an application context. If you are working inside a Flask view function, that automatically happens. However if you are working inside the interactive shell, you will have to do that yourself @@ -44,7 +44,7 @@ >>> app = create_app() >>> app.app_context().push() -Alternatively, use the with-statement to take care of setup and teardown:: +Inside scripts it makes also sense to use the with-statement:: def my_function(): with app.app_context(): diff -Nru flask-sqlalchemy-2.2/docs/customizing.rst flask-sqlalchemy-2.1/docs/customizing.rst --- flask-sqlalchemy-2.2/docs/customizing.rst 2016-10-28 14:21:05.000000000 +0000 +++ flask-sqlalchemy-2.1/docs/customizing.rst 1970-01-01 00:00:00.000000000 +0000 @@ -1,123 +0,0 @@ -.. _customizing: - -.. currentmodule:: flask_sqlalchemy - -Customizing -=========== - -Flask-SQLAlchemy defines sensible defaults. However, sometimes customization is -needed. Two major pieces to customize are the Model base class and the default -Query class. - -Both of these customizations are applied at the creation of the :class:`SQLAlchemy` -object and extend to all models derived from its ``Model`` class. - -Model Class ------------ - -Flask-SQLAlchemy allows defining a custom declarative base, just like SQLAlchemy, -that all model classes should extend from. For example, if all models should have -a custom ``__repr__`` method:: - - from flask_sqlalchemy import Model # this is the default declarative base - from flask_sqlalchemy import SQLAlchemy - - class ReprBase(Model): - def __repr__(self): - return "<{0} id: {1}>".format(self.__class__.__name__, self.id) - - db = SQLAlchemy(model_class=ReprBase) - - class MyModel(db.Model): - ... - -.. note:: - - While not strictly necessary to inherit from :class:`flask_sqlalchemy.Model` - it is encouraged as future changes may cause incompatibility. - -.. note:: - - If behavior is needed in only some models, not all, a better strategy - is to use a Mixin, as exampled below. - -While this particular example is more useful for debugging, it is possible to -provide many augmentations to models that would otherwise be achieved with -mixins instead. The above example is equivalent to the following:: - - class ReprBase(object): - def __repr__(self): - return "<{0} id: {1}>".format(self.__class__.__name__, self.id) - - db = SQLAlchemy() - - class MyModel(db.Model, ReprBase): - ... - -It also possible to provide default columns and properties to all models as well:: - - from flask_sqlalchemy import Model, SQLAlchemy - from sqlalchemy import Column, DateTime - from datetime import datetime - - class TimestampedModel(Model): - created_at = Column(DateTime, default=datetime.utcnow) - - db = SQLAlchemy(model_class=TimestampedModel) - - class MyModel(db.Model): - ... - -All model classes extending from ``db.Model`` will now inherit a -``created_at`` column. - -Query Class ------------ - -It is also possible to customize what is availble for use on the -special ``query`` property of models. For example, providing a -``get_or`` method:: - - from flask_sqlalchemy import BaseQuery, SQLAlchemy - - class GetOrQuery(BaseQuery): - def get_or(self, ident, default=None): - return self.get(ident) or default - - db = SQLAlchemy(query_class=GetOrQuery) - -And now all queries executed from the special ``query`` property -on Flask-SQLAlchemy models can use the ``get_or`` method as part -of their queries. All relationships defined with -``db.relationship`` (but not :func:`sqlalchemy.relationship`) -will also be provided with this functionality. - -.. warning:: - - Unlike a custom ``Model`` base class, it is required - to either inherit from either :class:`flask_sqlalchemy.BaseQuery` - or :func:`sqlalchemy.orm.Query` in order to define a custom - query class. - -It also possible to define a custom query class for individual -relationships as well, by providing the ``query_class`` keyword -in the definition. This works with both ``db.relationship`` -and ``sqlalchemy.relationship``:: - - class MyModel(db.Model): - cousin = db.relationship('OtherModel', query_class=GetOrQuery) - -.. note:: - - If a query class is defined on a relationship, it will take - precedence over the query class attached to its corresponding - model. - -It is also possible to define a specific query class for individual models -by overriding the ``query_class`` class attribute on the model:: - - class MyModel(db.Model): - query_class = GetOrQuery - -In this case, the ``get_or`` method will be only availble on queries -orginating from ``MyModel.query``. diff -Nru flask-sqlalchemy-2.2/docs/index.rst flask-sqlalchemy-2.1/docs/index.rst --- flask-sqlalchemy-2.2/docs/index.rst 2016-05-31 16:38:29.000000000 +0000 +++ flask-sqlalchemy-2.1/docs/index.rst 2015-02-26 17:58:08.000000000 +0000 @@ -3,10 +3,10 @@ Flask-SQLAlchemy ================ -.. module:: flask_sqlalchemy +.. module:: flask.ext.sqlalchemy Flask-SQLAlchemy is an extension for `Flask`_ that adds support for -`SQLAlchemy`_ to your application. It requires SQLAlchemy 0.8 or +`SQLAlchemy`_ to your application. It requires SQLAlchemy 0.6 or higher. It aims to simplify using SQLAlchemy with Flask by providing useful defaults and extra helpers that make it easier to accomplish common tasks. @@ -16,8 +16,4 @@ .. _example sourcecode: http://github.com/mitsuhiko/flask-sqlalchemy/tree/master/examples/ -See `the SQLAlchemy documentation`_ to learn how to work with the ORM in depth. The following documentation is a brief overview of the most common tasks, as well as the features specific to Flask-SQLAlchemy. - -.. _the SQLAlchemy documentation: http://docs.sqlalchemy.org/ - .. include:: contents.rst.inc diff -Nru flask-sqlalchemy-2.2/docs/models.rst flask-sqlalchemy-2.1/docs/models.rst --- flask-sqlalchemy-2.2/docs/models.rst 2017-01-14 19:57:27.000000000 +0000 +++ flask-sqlalchemy-2.1/docs/models.rst 2015-02-26 17:58:08.000000000 +0000 @@ -1,6 +1,6 @@ .. _models: -.. currentmodule:: flask_sqlalchemy +.. currentmodule:: flask.ext.sqlalchemy Declaring Models ================ @@ -12,14 +12,13 @@ Things to keep in mind: -- The baseclass for all your models is called ``db.Model``. It's stored +- The baseclass for all your models is called `db.Model`. It's stored on the SQLAlchemy instance you have to create. See :ref:`quickstart` for more details. - Some parts that are required in SQLAlchemy are optional in Flask-SQLAlchemy. For instance the table name is automatically set for you unless overridden. It's derived from the class name converted - to lowercase and with “CamelCase” converted to “camel_case”. To override - the table name, set the ``__tablename__`` class attribute. + to lowercase and with “CamelCase” converted to “camel_case”. Simple Example -------------- @@ -28,13 +27,17 @@ class User(db.Model): id = db.Column(db.Integer, primary_key=True) - username = db.Column(db.String(80), unique=True, nullable=False) - email = db.Column(db.String(120), unique=True, nullable=False) + username = db.Column(db.String(80), unique=True) + email = db.Column(db.String(120), unique=True) + + def __init__(self, username, email): + self.username = username + self.email = email def __repr__(self): return '' % self.username -Use :class:`~sqlalchemy.schema.Column` to define a column. The name of the +Use :class:`~sqlalchemy.Column` to define a column. The name of the column is the name you assign it to. If you want to use a different name in the table you can provide an optional first argument which is a string with the desired column name. Primary keys are marked with @@ -42,94 +45,78 @@ which case they become a compound primary key. The types of the column are the first argument to -:class:`~sqlalchemy.schema.Column`. You can either provide them directly -or call them to further specify them (like providing a length). The -following types are the most common: - -================================================ ===================================== -:class:`~sqlalchemy.types.Integer` an integer -:class:`String(size) ` a string with a maximum length - (optional in some databases, e.g. - PostgreSQL) -:class:`~sqlalchemy.types.Text` some longer unicode text -:class:`~sqlalchemy.types.DateTime` date and time expressed as Python - :class:`~datetime.datetime` object. -:class:`~sqlalchemy.types.Float` stores floating point values -:class:`~sqlalchemy.types.Boolean` stores a boolean value -:class:`~sqlalchemy.types.PickleType` stores a pickled Python object -:class:`~sqlalchemy.types.LargeBinary` stores large arbitrary binary data -================================================ ===================================== +:class:`~sqlalchemy.Column`. You can either provide them directly or call +them to further specify them (like providing a length). The following +types are the most common: + +=================== ===================================== +`Integer` an integer +`String` (size) a string with a maximum length +`Text` some longer unicode text +`DateTime` date and time expressed as Python + :mod:`~datetime.datetime` object. +`Float` stores floating point values +`Boolean` stores a boolean value +`PickleType` stores a pickled Python object +`LargeBinary` stores large arbitrary binary data +=================== ===================================== One-to-Many Relationships ------------------------- The most common relationships are one-to-many relationships. Because relationships are declared before they are established you can use strings -to refer to classes that are not created yet (for instance if ``Person`` -defines a relationship to ``Address`` which is declared later in the file). +to refer to classes that are not created yet (for instance if `Person` +defines a relationship to `Article` which is declared later in the file). Relationships are expressed with the :func:`~sqlalchemy.orm.relationship` function. However the foreign key has to be separately declared with the -:class:`~sqlalchemy.schema.ForeignKey` class:: +:class:`sqlalchemy.schema.ForeignKey` class:: class Person(db.Model): id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(50), nullable=False) - addresses = db.relationship('Address', backref='person', lazy=True) + name = db.Column(db.String(50)) + addresses = db.relationship('Address', backref='person', + lazy='dynamic') class Address(db.Model): id = db.Column(db.Integer, primary_key=True) - email = db.Column(db.String(120), nullable=False) - person_id = db.Column(db.Integer, db.ForeignKey('person.id'), - nullable=False) - -What does :func:`db.relationship() ` do? -That function returns a new property that can do multiple things. -In this case we told it to point to the ``Address`` class and load -multiple of those. How does it know that this will return more than -one address? Because SQLAlchemy guesses a useful default from your -declaration. If you would want to have a one-to-one relationship you -can pass ``uselist=False`` to :func:`~sqlalchemy.orm.relationship`. - -Since a person with no name or an email address with no address associated -makes no sense, ``nullable=False`` tells SQLAlchemy to create the column -as ``NOT NULL``. This is implied for primary key columns, but it's a good -idea to specify it for all other columns to make it clear to other people -working on your code that you did actually want a nullable column and did -not just forget to add it. - -So what do ``backref`` and ``lazy`` mean? ``backref`` is a simple way to also -declare a new property on the ``Address`` class. You can then also use -``my_address.person`` to get to the person at that address. ``lazy`` defines + email = db.Column(db.String(50)) + person_id = db.Column(db.Integer, db.ForeignKey('person.id')) + +What does ``db.relationship()`` do? That function returns a new property +that can do multiple things. In this case we told it to point to the +`Address` class and load multiple of those. How does it know that this +will return more than one address? Because SQLAlchemy guesses a useful +default from your declaration. If you would want to have a one-to-one +relationship you can pass ``uselist=False`` to +:func:`~sqlalchemy.orm.relationship`. + +So what do `backref` and `lazy` mean? `backref` is a simple way to also +declare a new property on the `Address` class. You can then also use +``my_address.person`` to get to the person at that address. `lazy` defines when SQLAlchemy will load the data from the database: -- ``'select'`` / ``True`` (which is the default, but explicit is better - than implicit) means that SQLAlchemy will load the data as necessary - in one go using a standard select statement. -- ``'joined'`` / ``False`` tells SQLAlchemy to load the relationship in - the same query as the parent using a ``JOIN`` statement. +- ``'select'`` (which is the default) means that SQLAlchemy will load + the data as necessary in one go using a standard select statement. +- ``'joined'`` tells SQLAlchemy to load the relationship in the same + query as the parent using a `JOIN` statement. - ``'subquery'`` works like ``'joined'`` but instead SQLAlchemy will use a subquery. -- ``'dynamic'`` is special and can be useful if you have many items - and always want to apply additional SQL filters to them. - Instead of loading the items SQLAlchemy will return another query - object which you can further refine before loading the items. - Note that this cannot be turned into a different loading strategy - when querying so it's often a good idea to avoid using this in - favor of ``lazy=True``. A query object equivalent to a dynamic - ``user.addresses`` relationship can be created using - :meth:`Address.query.with_parent(user) ` - while still being able to use - lazy or eager loading on the relationship itself as necessary. +- ``'dynamic'`` is special and useful if you have many items. Instead of + loading the items SQLAlchemy will return another query object which + you can further refine before loading the items. This is usually + what you want if you expect more than a handful of items for this + relationship. How do you define the lazy status for backrefs? By using the :func:`~sqlalchemy.orm.backref` function:: class User(db.Model): id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(50), nullable=False) - addresses = db.relationship('Address', lazy='select', - backref=db.backref('person', lazy='joined')) + name = db.Column(db.String(50)) + addresses = db.relationship('Address', + backref=db.backref('person', lazy='joined'), lazy='dynamic') Many-to-Many Relationships -------------------------- @@ -139,31 +126,19 @@ is strongly recommended to *not* use a model but an actual table:: tags = db.Table('tags', - db.Column('tag_id', db.Integer, db.ForeignKey('tag.id'), primary_key=True), - db.Column('page_id', db.Integer, db.ForeignKey('page.id'), primary_key=True) + db.Column('tag_id', db.Integer, db.ForeignKey('tag.id')), + db.Column('page_id', db.Integer, db.ForeignKey('page.id')) ) class Page(db.Model): id = db.Column(db.Integer, primary_key=True) - tags = db.relationship('Tag', secondary=tags, lazy='subquery', - backref=db.backref('pages', lazy=True)) + tags = db.relationship('Tag', secondary=tags, + backref=db.backref('pages', lazy='dynamic')) class Tag(db.Model): id = db.Column(db.Integer, primary_key=True) -Here we configured ``Page.tags`` to be loaded immediately after loading -a Page, but using a separate query. This always results in two -queries when retrieving a Page, but when querying for multiple pages -you will not get additional queries. - -The list of pages for a tag on the other hand is something that's -rarely needed. For example, you won't need that list when retrieving -the tags for a specific page. Therefore, the backref is set to be -lazy-loaded so that accessing it for the first time will trigger a -query to get the list of pages for that tag. If you need to apply -further query options on that list, you could either switch to the -``'dynamic'`` strategy - with the drawbacks mentioned above - or get -a query object using -:meth:`Page.query.with_parent(some_tag) ` -and then use it exactly as you would with the query object from a dynamic -relationship. +Here we configured `Page.tags` to be a list of tags once loaded because we +don't expect too many tags per page. The list of pages per tag +(`Tag.pages`) however is a dynamic backref. As mentioned above this means +that you will get a query object back you can use to fire a select yourself. diff -Nru flask-sqlalchemy-2.2/docs/queries.rst flask-sqlalchemy-2.1/docs/queries.rst --- flask-sqlalchemy-2.2/docs/queries.rst 2016-05-31 16:38:29.000000000 +0000 +++ flask-sqlalchemy-2.1/docs/queries.rst 2015-02-26 17:58:08.000000000 +0000 @@ -1,4 +1,4 @@ -.. currentmodule:: flask_sqlalchemy +.. currentmodule:: flask.ext.sqlalchemy Select, Insert, Delete ====================== @@ -80,7 +80,7 @@ >>> peter = User.query.filter_by(username='peter').first() >>> peter.id -2 +1 >>> peter.email u'peter@example.org' @@ -97,7 +97,7 @@ Ordering users by something: ->>> User.query.order_by(User.username).all() +>>> User.query.order_by(User.username) [, , ] Limiting users: diff -Nru flask-sqlalchemy-2.2/docs/quickstart.rst flask-sqlalchemy-2.1/docs/quickstart.rst --- flask-sqlalchemy-2.2/docs/quickstart.rst 2017-01-14 19:57:27.000000000 +0000 +++ flask-sqlalchemy-2.1/docs/quickstart.rst 2015-07-28 19:15:32.000000000 +0000 @@ -3,7 +3,7 @@ Quickstart ========== -.. currentmodule:: flask_sqlalchemy +.. currentmodule:: flask.ext.sqlalchemy Flask-SQLAlchemy is fun to use, incredibly easy for basic applications, and readily extends for larger applications. For the complete guide, checkout @@ -18,7 +18,7 @@ Once created, that object then contains all the functions and helpers from both :mod:`sqlalchemy` and :mod:`sqlalchemy.orm`. Furthermore it -provides a class called ``Model`` that is a declarative base which can be +provides a class called `Model` that is a declarative base which can be used to declare models:: from flask import Flask @@ -31,51 +31,42 @@ class User(db.Model): id = db.Column(db.Integer, primary_key=True) - username = db.Column(db.String(80), unique=True, nullable=False) - email = db.Column(db.String(120), unique=True, nullable=False) + username = db.Column(db.String(80), unique=True) + email = db.Column(db.String(120), unique=True) + + def __init__(self, username, email): + self.username = username + self.email = email def __repr__(self): return '' % self.username -To create the initial database, just import the ``db`` object from an +To create the initial database, just import the `db` object from an interactive Python shell and run the :meth:`SQLAlchemy.create_all` method to create the -tables and database:: +tables and database: + +>>> from yourapplication import db +>>> db.create_all() + +Boom, and there is your database. Now to create some users: - >>> from yourapplication import db - >>> db.create_all() +>>> from yourapplication import User +>>> admin = User('admin', 'admin@example.com') +>>> guest = User('guest', 'guest@example.com') -Boom, and there is your database. Now to create some users:: +But they are not yet in the database, so let's make sure they are: - >>> from yourapplication import User - >>> admin = User(username='admin', email='admin@example.com') - >>> guest = User(username='guest', email='guest@example.com') - -But they are not yet in the database, so let's make sure they are:: - - >>> db.session.add(admin) - >>> db.session.add(guest) - >>> db.session.commit() - -Accessing the data in database is easy as a pie:: - - >>> User.query.all() - [, ] - >>> User.query.filter_by(username='admin').first() - - -Note how we never defined a ``__init__`` method on the ``User`` class? -That's because SQLAlchemy adds an implicit constructor to all model -classes which accepts keyword arguments for all its columns and -relationships. If you decide to override the constructor for any -reason, make sure to keep accepting ``**kwargs`` and call the super -constructor with those ``**kwargs`` to preserve this behavior:: - - class Foo(db.Model): - # ... - def __init__(**kwargs): - super(Foo, self).__init__(**kwargs) - # do custom stuff +>>> db.session.add(admin) +>>> db.session.add(guest) +>>> db.session.commit() + +Accessing the data in database is easy as a pie: + +>>> users = User.query.all() +[, ] +>>> admin = User.query.filter_by(username='admin').first() + Simple Relationships -------------------- @@ -84,20 +75,27 @@ are really good at are relations. As such, we shall have an example of an application that uses two tables that have a relationship to each other:: + from datetime import datetime class Post(db.Model): id = db.Column(db.Integer, primary_key=True) - title = db.Column(db.String(80), nullable=False) - body = db.Column(db.Text, nullable=False) - pub_date = db.Column(db.DateTime, nullable=False, - default=datetime.utcnow) + title = db.Column(db.String(80)) + body = db.Column(db.Text) + pub_date = db.Column(db.DateTime) - category_id = db.Column(db.Integer, db.ForeignKey('category.id'), - nullable=False) + category_id = db.Column(db.Integer, db.ForeignKey('category.id')) category = db.relationship('Category', - backref=db.backref('posts', lazy=True)) + backref=db.backref('posts', lazy='dynamic')) + + def __init__(self, title, body, category, pub_date=None): + self.title = title + self.body = body + if pub_date is None: + pub_date = datetime.utcnow() + self.pub_date = pub_date + self.category = category def __repr__(self): return '' % self.title @@ -105,54 +103,32 @@ class Category(db.Model): id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(50), nullable=False) + name = db.Column(db.String(50)) + + def __init__(self, name): + self.name = name def __repr__(self): return '' % self.name -First let's create some objects:: +First let's create some objects: + +>>> py = Category('Python') +>>> p = Post('Hello Python!', 'Python is pretty cool', py) +>>> db.session.add(py) +>>> db.session.add(p) + +Now because we declared `posts` as dynamic relationship in the backref +it shows up as query: + +>>> py.posts + - >>> py = Category(name='Python') - >>> Post(title='Hello Python!', body='Python is pretty cool', category=py) - >>> p = Post(title='Snakes', body='Ssssssss') - >>> py.posts.append(p) - >>> db.session.add(py) - -As you can see, there is no need to add the ``Post`` objects to the -session. Since the ``Category`` is part of the session all objects -associated with it through relationships will be added too. It does -not matter whether :meth:`db.session.add() ` -is called before or after creating these objects. The association can -also be done on either side of the relationship - so a post can be -created with a category or it can be added to the list of posts of -the category. - -Let's look at the posts. Accessing them will load them from the database -since the relationship is lazy-loaded, but you will probably not notice -the difference - loading a list is quite fast:: - - >>> py.posts - [, ] - -While lazy-loading a relationship is fast, it can easily become a major -bottleneck when you end up triggering extra queries in a loop for more -than a few objects. For this case, SQLAlchemy lets you override the -loading strategy on the query level. If you wanted a single query to -load all categories and their posts, you could do it like this:: - - >>> from sqlalchemy.orm import joinedload - >>> query = Category.query.options(joinedload('posts')) - >>> for category in query: - ... print category, category.posts - [, ] - - -If you want to get a query object for that relationship, you can do so -using :meth:`~sqlalchemy.orm.query.Query.with_parent`. Let's exclude -that post about Snakes for example:: +It behaves like a regular query object so we can ask it for all posts that +are associated with our test “Python” category: - >>> Post.query.with_parent(py).filter(Post.title != 'Snakes').all() - [] +>>> py.posts.all() +[] Road to Enlightenment @@ -164,7 +140,7 @@ - all the functions and classes from :mod:`sqlalchemy` and :mod:`sqlalchemy.orm` - - a preconfigured scoped session called ``session`` + - a preconfigured scoped session called `session` - the :attr:`~SQLAlchemy.metadata` - the :attr:`~SQLAlchemy.engine` - a :meth:`SQLAlchemy.create_all` and :meth:`SQLAlchemy.drop_all` @@ -172,7 +148,7 @@ - a :class:`Model` baseclass that is a configured declarative base. 2. The :class:`Model` declarative base class behaves like a regular - Python class but has a ``query`` attribute attached that can be used to + Python class but has a `query` attribute attached that can be used to query the model. (:class:`Model` and :class:`BaseQuery`) 3. You have to commit the session, but you don't have to remove it at Binary files /tmp/tmpAAF4Ri/KAnyR0sCfq/flask-sqlalchemy-2.2/docs/_static/flask-sqlalchemy-logo.png and /tmp/tmpAAF4Ri/VZbze0IV0W/flask-sqlalchemy-2.1/docs/_static/flask-sqlalchemy-logo.png differ Binary files /tmp/tmpAAF4Ri/KAnyR0sCfq/flask-sqlalchemy-2.2/docs/_static/flask-sqlalchemy.png and /tmp/tmpAAF4Ri/VZbze0IV0W/flask-sqlalchemy-2.1/docs/_static/flask-sqlalchemy.png differ Binary files /tmp/tmpAAF4Ri/KAnyR0sCfq/flask-sqlalchemy-2.2/docs/_static/flask-sqlalchemy-small.png and /tmp/tmpAAF4Ri/VZbze0IV0W/flask-sqlalchemy-2.1/docs/_static/flask-sqlalchemy-small.png differ Binary files /tmp/tmpAAF4Ri/KAnyR0sCfq/flask-sqlalchemy-2.2/docs/_static/flask-sqlalchemy-title.png and /tmp/tmpAAF4Ri/VZbze0IV0W/flask-sqlalchemy-2.1/docs/_static/flask-sqlalchemy-title.png differ diff -Nru flask-sqlalchemy-2.2/docs/_templates/sidebarintro.html flask-sqlalchemy-2.1/docs/_templates/sidebarintro.html --- flask-sqlalchemy-2.2/docs/_templates/sidebarintro.html 2016-06-14 14:30:02.000000000 +0000 +++ flask-sqlalchemy-2.1/docs/_templates/sidebarintro.html 2015-02-26 17:58:08.000000000 +0000 @@ -1,16 +1,27 @@ +

About

+

+ Flask-SQLAlchemy provides a SQLAlchemy extension to Flask. + Flask is a micro webdevelopment framework for Python. You are currently + looking at the documentation of the development version. Things are + not stable yet, but if you have some feedback, + let me know. +

+

Useful Links

-

diff -Nru flask-sqlalchemy-2.2/docs/_themes/flask/layout.html flask-sqlalchemy-2.1/docs/_themes/flask/layout.html --- flask-sqlalchemy-2.2/docs/_themes/flask/layout.html 2016-06-14 14:03:37.000000000 +0000 +++ flask-sqlalchemy-2.1/docs/_themes/flask/layout.html 1970-01-01 00:00:00.000000000 +0000 @@ -1,25 +0,0 @@ -{%- extends "basic/layout.html" %} -{%- block extrahead %} - {{ super() }} - {% if theme_touch_icon %} - - {% endif %} - -{% endblock %} -{%- block relbar2 %}{% endblock %} -{% block header %} - {{ super() }} - {% if pagename == 'index' %} -
- {% endif %} -{% endblock %} -{%- block footer %} - - {% if pagename == 'index' %} -
- {% endif %} -{%- endblock %} diff -Nru flask-sqlalchemy-2.2/docs/_themes/flask/relations.html flask-sqlalchemy-2.1/docs/_themes/flask/relations.html --- flask-sqlalchemy-2.2/docs/_themes/flask/relations.html 2016-06-14 14:03:37.000000000 +0000 +++ flask-sqlalchemy-2.1/docs/_themes/flask/relations.html 1970-01-01 00:00:00.000000000 +0000 @@ -1,19 +0,0 @@ -

Related Topics

- diff -Nru flask-sqlalchemy-2.2/docs/_themes/flask/static/flasky.css_t flask-sqlalchemy-2.1/docs/_themes/flask/static/flasky.css_t --- flask-sqlalchemy-2.2/docs/_themes/flask/static/flasky.css_t 2016-06-14 14:03:37.000000000 +0000 +++ flask-sqlalchemy-2.1/docs/_themes/flask/static/flasky.css_t 1970-01-01 00:00:00.000000000 +0000 @@ -1,395 +0,0 @@ -/* - * flasky.css_t - * ~~~~~~~~~~~~ - * - * :copyright: Copyright 2010 by Armin Ronacher. - * :license: Flask Design License, see LICENSE for details. - */ - -{% set page_width = '940px' %} -{% set sidebar_width = '220px' %} - -@import url("basic.css"); - -/* -- page layout ----------------------------------------------------------- */ - -body { - font-family: 'Georgia', serif; - font-size: 17px; - background-color: white; - color: #000; - margin: 0; - padding: 0; -} - -div.document { - width: {{ page_width }}; - margin: 30px auto 0 auto; -} - -div.documentwrapper { - float: left; - width: 100%; -} - -div.bodywrapper { - margin: 0 0 0 {{ sidebar_width }}; -} - -div.sphinxsidebar { - width: {{ sidebar_width }}; -} - -hr { - border: 1px solid #B1B4B6; -} - -div.body { - background-color: #ffffff; - color: #3E4349; - padding: 0 30px 0 30px; -} - -img.floatingflask { - padding: 0 0 10px 10px; - float: right; -} - -div.footer { - width: {{ page_width }}; - margin: 20px auto 30px auto; - font-size: 14px; - color: #888; - text-align: right; -} - -div.footer a { - color: #888; -} - -div.related { - display: none; -} - -div.sphinxsidebar a { - color: #444; - text-decoration: none; - border-bottom: 1px dotted #999; -} - -div.sphinxsidebar a:hover { - border-bottom: 1px solid #999; -} - -div.sphinxsidebar { - font-size: 14px; - line-height: 1.5; -} - -div.sphinxsidebarwrapper { - padding: 18px 10px; -} - -div.sphinxsidebarwrapper p.logo { - padding: 0 0 20px 0; - margin: 0; - text-align: center; -} - -div.sphinxsidebar h3, -div.sphinxsidebar h4 { - font-family: 'Garamond', 'Georgia', serif; - color: #444; - font-size: 24px; - font-weight: normal; - margin: 0 0 5px 0; - padding: 0; -} - -div.sphinxsidebar h4 { - font-size: 20px; -} - -div.sphinxsidebar h3 a { - color: #444; -} - -div.sphinxsidebar p.logo a, -div.sphinxsidebar h3 a, -div.sphinxsidebar p.logo a:hover, -div.sphinxsidebar h3 a:hover { - border: none; -} - -div.sphinxsidebar p { - color: #555; - margin: 10px 0; -} - -div.sphinxsidebar ul { - margin: 10px 0; - padding: 0; - color: #000; -} - -div.sphinxsidebar input { - border: 1px solid #ccc; - font-family: 'Georgia', serif; - font-size: 1em; -} - -/* -- body styles ----------------------------------------------------------- */ - -a { - color: #004B6B; - text-decoration: underline; -} - -a:hover { - color: #6D4100; - text-decoration: underline; -} - -div.body h1, -div.body h2, -div.body h3, -div.body h4, -div.body h5, -div.body h6 { - font-family: 'Garamond', 'Georgia', serif; - font-weight: normal; - margin: 30px 0px 10px 0px; - padding: 0; -} - -{% if theme_index_logo %} -div.indexwrapper h1 { - text-indent: -999999px; - background: url({{ theme_index_logo }}) no-repeat center center; - height: {{ theme_index_logo_height }}; -} -{% endif %} - -div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } -div.body h2 { font-size: 180%; } -div.body h3 { font-size: 150%; } -div.body h4 { font-size: 130%; } -div.body h5 { font-size: 100%; } -div.body h6 { font-size: 100%; } - -a.headerlink { - color: #ddd; - padding: 0 4px; - text-decoration: none; -} - -a.headerlink:hover { - color: #444; - background: #eaeaea; -} - -div.body p, div.body dd, div.body li { - line-height: 1.4em; -} - -div.admonition { - background: #fafafa; - margin: 20px -30px; - padding: 10px 30px; - border-top: 1px solid #ccc; - border-bottom: 1px solid #ccc; -} - -div.admonition tt.xref, div.admonition a tt { - border-bottom: 1px solid #fafafa; -} - -dd div.admonition { - margin-left: -60px; - padding-left: 60px; -} - -div.admonition p.admonition-title { - font-family: 'Garamond', 'Georgia', serif; - font-weight: normal; - font-size: 24px; - margin: 0 0 10px 0; - padding: 0; - line-height: 1; -} - -div.admonition p.last { - margin-bottom: 0; -} - -div.highlight { - background-color: white; -} - -dt:target, .highlight { - background: #FAF3E8; -} - -div.note { - background-color: #eee; - border: 1px solid #ccc; -} - -div.seealso { - background-color: #ffc; - border: 1px solid #ff6; -} - -div.topic { - background-color: #eee; -} - -p.admonition-title { - display: inline; -} - -p.admonition-title:after { - content: ":"; -} - -pre, tt { - font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; - font-size: 0.9em; -} - -img.screenshot { -} - -tt.descname, tt.descclassname { - font-size: 0.95em; -} - -tt.descname { - padding-right: 0.08em; -} - -img.screenshot { - -moz-box-shadow: 2px 2px 4px #eee; - -webkit-box-shadow: 2px 2px 4px #eee; - box-shadow: 2px 2px 4px #eee; -} - -table.docutils { - border: 1px solid #888; - -moz-box-shadow: 2px 2px 4px #eee; - -webkit-box-shadow: 2px 2px 4px #eee; - box-shadow: 2px 2px 4px #eee; -} - -table.docutils td, table.docutils th { - border: 1px solid #888; - padding: 0.25em 0.7em; -} - -table.field-list, table.footnote { - border: none; - -moz-box-shadow: none; - -webkit-box-shadow: none; - box-shadow: none; -} - -table.footnote { - margin: 15px 0; - width: 100%; - border: 1px solid #eee; - background: #fdfdfd; - font-size: 0.9em; -} - -table.footnote + table.footnote { - margin-top: -15px; - border-top: none; -} - -table.field-list th { - padding: 0 0.8em 0 0; -} - -table.field-list td { - padding: 0; -} - -table.footnote td.label { - width: 0px; - padding: 0.3em 0 0.3em 0.5em; -} - -table.footnote td { - padding: 0.3em 0.5em; -} - -dl { - margin: 0; - padding: 0; -} - -dl dd { - margin-left: 30px; -} - -blockquote { - margin: 0 0 0 30px; - padding: 0; -} - -ul, ol { - margin: 10px 0 10px 30px; - padding: 0; -} - -pre { - background: #eee; - padding: 7px 30px; - margin: 15px -30px; - line-height: 1.3em; -} - -dl pre, blockquote pre, li pre { - margin-left: -60px; - padding-left: 60px; -} - -dl dl pre { - margin-left: -90px; - padding-left: 90px; -} - -tt { - background-color: #ecf0f3; - color: #222; - /* padding: 1px 2px; */ -} - -tt.xref, a tt { - background-color: #FBFBFB; - border-bottom: 1px solid white; -} - -a.reference { - text-decoration: none; - border-bottom: 1px dotted #004B6B; -} - -a.reference:hover { - border-bottom: 1px solid #6D4100; -} - -a.footnote-reference { - text-decoration: none; - font-size: 0.7em; - vertical-align: top; - border-bottom: 1px dotted #004B6B; -} - -a.footnote-reference:hover { - border-bottom: 1px solid #6D4100; -} - -a:hover tt { - background: #EEE; -} diff -Nru flask-sqlalchemy-2.2/docs/_themes/flask/static/small_flask.css flask-sqlalchemy-2.1/docs/_themes/flask/static/small_flask.css --- flask-sqlalchemy-2.2/docs/_themes/flask/static/small_flask.css 2016-06-14 14:03:37.000000000 +0000 +++ flask-sqlalchemy-2.1/docs/_themes/flask/static/small_flask.css 1970-01-01 00:00:00.000000000 +0000 @@ -1,70 +0,0 @@ -/* - * small_flask.css_t - * ~~~~~~~~~~~~~~~~~ - * - * :copyright: Copyright 2010 by Armin Ronacher. - * :license: Flask Design License, see LICENSE for details. - */ - -body { - margin: 0; - padding: 20px 30px; -} - -div.documentwrapper { - float: none; - background: white; -} - -div.sphinxsidebar { - display: block; - float: none; - width: 102.5%; - margin: 50px -30px -20px -30px; - padding: 10px 20px; - background: #333; - color: white; -} - -div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, -div.sphinxsidebar h3 a { - color: white; -} - -div.sphinxsidebar a { - color: #aaa; -} - -div.sphinxsidebar p.logo { - display: none; -} - -div.document { - width: 100%; - margin: 0; -} - -div.related { - display: block; - margin: 0; - padding: 10px 0 20px 0; -} - -div.related ul, -div.related ul li { - margin: 0; - padding: 0; -} - -div.footer { - display: none; -} - -div.bodywrapper { - margin: 0; -} - -div.body { - min-height: 0; - padding: 0; -} diff -Nru flask-sqlalchemy-2.2/docs/_themes/flask/theme.conf flask-sqlalchemy-2.1/docs/_themes/flask/theme.conf --- flask-sqlalchemy-2.2/docs/_themes/flask/theme.conf 2016-06-14 14:03:37.000000000 +0000 +++ flask-sqlalchemy-2.1/docs/_themes/flask/theme.conf 1970-01-01 00:00:00.000000000 +0000 @@ -1,9 +0,0 @@ -[theme] -inherit = basic -stylesheet = flasky.css -pygments_style = flask_theme_support.FlaskyStyle - -[options] -index_logo = '' -index_logo_height = 120px -touch_icon = diff -Nru flask-sqlalchemy-2.2/docs/_themes/flask_small/layout.html flask-sqlalchemy-2.1/docs/_themes/flask_small/layout.html --- flask-sqlalchemy-2.2/docs/_themes/flask_small/layout.html 2016-06-14 14:03:37.000000000 +0000 +++ flask-sqlalchemy-2.1/docs/_themes/flask_small/layout.html 1970-01-01 00:00:00.000000000 +0000 @@ -1,22 +0,0 @@ -{% extends "basic/layout.html" %} -{% block header %} - {{ super() }} - {% if pagename == 'index' %} -
- {% endif %} -{% endblock %} -{% block footer %} - {% if pagename == 'index' %} -
- {% endif %} -{% endblock %} -{# do not display relbars #} -{% block relbar1 %}{% endblock %} -{% block relbar2 %} - {% if theme_github_fork %} - Fork me on GitHub - {% endif %} -{% endblock %} -{% block sidebar1 %}{% endblock %} -{% block sidebar2 %}{% endblock %} diff -Nru flask-sqlalchemy-2.2/docs/_themes/flask_small/static/flasky.css_t flask-sqlalchemy-2.1/docs/_themes/flask_small/static/flasky.css_t --- flask-sqlalchemy-2.2/docs/_themes/flask_small/static/flasky.css_t 2016-06-14 14:03:37.000000000 +0000 +++ flask-sqlalchemy-2.1/docs/_themes/flask_small/static/flasky.css_t 1970-01-01 00:00:00.000000000 +0000 @@ -1,287 +0,0 @@ -/* - * flasky.css_t - * ~~~~~~~~~~~~ - * - * Sphinx stylesheet -- flasky theme based on nature theme. - * - * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -@import url("basic.css"); - -/* -- page layout ----------------------------------------------------------- */ - -body { - font-family: 'Georgia', serif; - font-size: 17px; - color: #000; - background: white; - margin: 0; - padding: 0; -} - -div.documentwrapper { - float: left; - width: 100%; -} - -div.bodywrapper { - margin: 40px auto 0 auto; - width: 700px; -} - -hr { - border: 1px solid #B1B4B6; -} - -div.body { - background-color: #ffffff; - color: #3E4349; - padding: 0 30px 30px 30px; -} - -img.floatingflask { - padding: 0 0 10px 10px; - float: right; -} - -div.footer { - text-align: right; - color: #888; - padding: 10px; - font-size: 14px; - width: 650px; - margin: 0 auto 40px auto; -} - -div.footer a { - color: #888; - text-decoration: underline; -} - -div.related { - line-height: 32px; - color: #888; -} - -div.related ul { - padding: 0 0 0 10px; -} - -div.related a { - color: #444; -} - -/* -- body styles ----------------------------------------------------------- */ - -a { - color: #004B6B; - text-decoration: underline; -} - -a:hover { - color: #6D4100; - text-decoration: underline; -} - -div.body { - padding-bottom: 40px; /* saved for footer */ -} - -div.body h1, -div.body h2, -div.body h3, -div.body h4, -div.body h5, -div.body h6 { - font-family: 'Garamond', 'Georgia', serif; - font-weight: normal; - margin: 30px 0px 10px 0px; - padding: 0; -} - -{% if theme_index_logo %} -div.indexwrapper h1 { - text-indent: -999999px; - background: url({{ theme_index_logo }}) no-repeat center center; - height: {{ theme_index_logo_height }}; -} -{% endif %} - -div.body h2 { font-size: 180%; } -div.body h3 { font-size: 150%; } -div.body h4 { font-size: 130%; } -div.body h5 { font-size: 100%; } -div.body h6 { font-size: 100%; } - -a.headerlink { - color: white; - padding: 0 4px; - text-decoration: none; -} - -a.headerlink:hover { - color: #444; - background: #eaeaea; -} - -div.body p, div.body dd, div.body li { - line-height: 1.4em; -} - -div.admonition { - background: #fafafa; - margin: 20px -30px; - padding: 10px 30px; - border-top: 1px solid #ccc; - border-bottom: 1px solid #ccc; -} - -div.admonition p.admonition-title { - font-family: 'Garamond', 'Georgia', serif; - font-weight: normal; - font-size: 24px; - margin: 0 0 10px 0; - padding: 0; - line-height: 1; -} - -div.admonition p.last { - margin-bottom: 0; -} - -div.highlight{ - background-color: white; -} - -dt:target, .highlight { - background: #FAF3E8; -} - -div.note { - background-color: #eee; - border: 1px solid #ccc; -} - -div.seealso { - background-color: #ffc; - border: 1px solid #ff6; -} - -div.topic { - background-color: #eee; -} - -div.warning { - background-color: #ffe4e4; - border: 1px solid #f66; -} - -p.admonition-title { - display: inline; -} - -p.admonition-title:after { - content: ":"; -} - -pre, tt { - font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; - font-size: 0.85em; -} - -img.screenshot { -} - -tt.descname, tt.descclassname { - font-size: 0.95em; -} - -tt.descname { - padding-right: 0.08em; -} - -img.screenshot { - -moz-box-shadow: 2px 2px 4px #eee; - -webkit-box-shadow: 2px 2px 4px #eee; - box-shadow: 2px 2px 4px #eee; -} - -table.docutils { - border: 1px solid #888; - -moz-box-shadow: 2px 2px 4px #eee; - -webkit-box-shadow: 2px 2px 4px #eee; - box-shadow: 2px 2px 4px #eee; -} - -table.docutils td, table.docutils th { - border: 1px solid #888; - padding: 0.25em 0.7em; -} - -table.field-list, table.footnote { - border: none; - -moz-box-shadow: none; - -webkit-box-shadow: none; - box-shadow: none; -} - -table.footnote { - margin: 15px 0; - width: 100%; - border: 1px solid #eee; -} - -table.field-list th { - padding: 0 0.8em 0 0; -} - -table.field-list td { - padding: 0; -} - -table.footnote td { - padding: 0.5em; -} - -dl { - margin: 0; - padding: 0; -} - -dl dd { - margin-left: 30px; -} - -pre { - padding: 0; - margin: 15px -30px; - padding: 8px; - line-height: 1.3em; - padding: 7px 30px; - background: #eee; - border-radius: 2px; - -moz-border-radius: 2px; - -webkit-border-radius: 2px; -} - -dl pre { - margin-left: -60px; - padding-left: 60px; -} - -tt { - background-color: #ecf0f3; - color: #222; - /* padding: 1px 2px; */ -} - -tt.xref, a tt { - background-color: #FBFBFB; -} - -a:hover tt { - background: #EEE; -} diff -Nru flask-sqlalchemy-2.2/docs/_themes/flask_small/theme.conf flask-sqlalchemy-2.1/docs/_themes/flask_small/theme.conf --- flask-sqlalchemy-2.2/docs/_themes/flask_small/theme.conf 2016-06-14 14:03:37.000000000 +0000 +++ flask-sqlalchemy-2.1/docs/_themes/flask_small/theme.conf 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ -[theme] -inherit = basic -stylesheet = flasky.css -nosidebar = true -pygments_style = flask_theme_support.FlaskyStyle - -[options] -index_logo = '' -index_logo_height = 120px -github_fork = '' diff -Nru flask-sqlalchemy-2.2/docs/_themes/flask_theme_support.py flask-sqlalchemy-2.1/docs/_themes/flask_theme_support.py --- flask-sqlalchemy-2.2/docs/_themes/flask_theme_support.py 2016-06-14 14:03:37.000000000 +0000 +++ flask-sqlalchemy-2.1/docs/_themes/flask_theme_support.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,86 +0,0 @@ -# flasky extensions. flasky pygments style based on tango style -from pygments.style import Style -from pygments.token import Keyword, Name, Comment, String, Error, \ - Number, Operator, Generic, Whitespace, Punctuation, Other, Literal - - -class FlaskyStyle(Style): - background_color = "#f8f8f8" - default_style = "" - - styles = { - # No corresponding class for the following: - #Text: "", # class: '' - Whitespace: "underline #f8f8f8", # class: 'w' - Error: "#a40000 border:#ef2929", # class: 'err' - Other: "#000000", # class 'x' - - Comment: "italic #8f5902", # class: 'c' - Comment.Preproc: "noitalic", # class: 'cp' - - Keyword: "bold #004461", # class: 'k' - Keyword.Constant: "bold #004461", # class: 'kc' - Keyword.Declaration: "bold #004461", # class: 'kd' - Keyword.Namespace: "bold #004461", # class: 'kn' - Keyword.Pseudo: "bold #004461", # class: 'kp' - Keyword.Reserved: "bold #004461", # class: 'kr' - Keyword.Type: "bold #004461", # class: 'kt' - - Operator: "#582800", # class: 'o' - Operator.Word: "bold #004461", # class: 'ow' - like keywords - - Punctuation: "bold #000000", # class: 'p' - - # because special names such as Name.Class, Name.Function, etc. - # are not recognized as such later in the parsing, we choose them - # to look the same as ordinary variables. - Name: "#000000", # class: 'n' - Name.Attribute: "#c4a000", # class: 'na' - to be revised - Name.Builtin: "#004461", # class: 'nb' - Name.Builtin.Pseudo: "#3465a4", # class: 'bp' - Name.Class: "#000000", # class: 'nc' - to be revised - Name.Constant: "#000000", # class: 'no' - to be revised - Name.Decorator: "#888", # class: 'nd' - to be revised - Name.Entity: "#ce5c00", # class: 'ni' - Name.Exception: "bold #cc0000", # class: 'ne' - Name.Function: "#000000", # class: 'nf' - Name.Property: "#000000", # class: 'py' - Name.Label: "#f57900", # class: 'nl' - Name.Namespace: "#000000", # class: 'nn' - to be revised - Name.Other: "#000000", # class: 'nx' - Name.Tag: "bold #004461", # class: 'nt' - like a keyword - Name.Variable: "#000000", # class: 'nv' - to be revised - Name.Variable.Class: "#000000", # class: 'vc' - to be revised - Name.Variable.Global: "#000000", # class: 'vg' - to be revised - Name.Variable.Instance: "#000000", # class: 'vi' - to be revised - - Number: "#990000", # class: 'm' - - Literal: "#000000", # class: 'l' - Literal.Date: "#000000", # class: 'ld' - - String: "#4e9a06", # class: 's' - String.Backtick: "#4e9a06", # class: 'sb' - String.Char: "#4e9a06", # class: 'sc' - String.Doc: "italic #8f5902", # class: 'sd' - like a comment - String.Double: "#4e9a06", # class: 's2' - String.Escape: "#4e9a06", # class: 'se' - String.Heredoc: "#4e9a06", # class: 'sh' - String.Interpol: "#4e9a06", # class: 'si' - String.Other: "#4e9a06", # class: 'sx' - String.Regex: "#4e9a06", # class: 'sr' - String.Single: "#4e9a06", # class: 's1' - String.Symbol: "#4e9a06", # class: 'ss' - - Generic: "#000000", # class: 'g' - Generic.Deleted: "#a40000", # class: 'gd' - Generic.Emph: "italic #000000", # class: 'ge' - Generic.Error: "#ef2929", # class: 'gr' - Generic.Heading: "bold #000080", # class: 'gh' - Generic.Inserted: "#00A000", # class: 'gi' - Generic.Output: "#888", # class: 'go' - Generic.Prompt: "#745334", # class: 'gp' - Generic.Strong: "bold #000000", # class: 'gs' - Generic.Subheading: "bold #800080", # class: 'gu' - Generic.Traceback: "bold #a40000", # class: 'gt' - } diff -Nru flask-sqlalchemy-2.2/docs/_themes/.git flask-sqlalchemy-2.1/docs/_themes/.git --- flask-sqlalchemy-2.2/docs/_themes/.git 2016-06-14 14:03:37.000000000 +0000 +++ flask-sqlalchemy-2.1/docs/_themes/.git 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -gitdir: ../../.git/modules/docs/_themes diff -Nru flask-sqlalchemy-2.2/docs/_themes/.gitignore flask-sqlalchemy-2.1/docs/_themes/.gitignore --- flask-sqlalchemy-2.2/docs/_themes/.gitignore 2016-06-14 14:03:37.000000000 +0000 +++ flask-sqlalchemy-2.1/docs/_themes/.gitignore 1970-01-01 00:00:00.000000000 +0000 @@ -1,3 +0,0 @@ -*.pyc -*.pyo -.DS_Store diff -Nru flask-sqlalchemy-2.2/docs/_themes/LICENSE flask-sqlalchemy-2.1/docs/_themes/LICENSE --- flask-sqlalchemy-2.2/docs/_themes/LICENSE 2016-06-14 14:03:37.000000000 +0000 +++ flask-sqlalchemy-2.1/docs/_themes/LICENSE 1970-01-01 00:00:00.000000000 +0000 @@ -1,37 +0,0 @@ -Copyright (c) 2010 by Armin Ronacher. - -Some rights reserved. - -Redistribution and use in source and binary forms of the theme, with or -without modification, are permitted provided that the following conditions -are met: - -* Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - -* The names of the contributors may not be used to endorse or - promote products derived from this software without specific - prior written permission. - -We kindly ask you to only use these themes in an unmodified manner just -for Flask and Flask-related products, not for unrelated projects. If you -like the visual style and want to use it for your own projects, please -consider making some larger changes to the themes (such as changing -font faces, sizes, colors or margins). - -THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff -Nru flask-sqlalchemy-2.2/docs/_themes/README flask-sqlalchemy-2.1/docs/_themes/README --- flask-sqlalchemy-2.2/docs/_themes/README 2016-06-14 14:03:37.000000000 +0000 +++ flask-sqlalchemy-2.1/docs/_themes/README 1970-01-01 00:00:00.000000000 +0000 @@ -1,31 +0,0 @@ -Flask Sphinx Styles -=================== - -This repository contains sphinx styles for Flask and Flask related -projects. To use this style in your Sphinx documentation, follow -this guide: - -1. put this folder as _themes into your docs folder. Alternatively - you can also use git submodules to check out the contents there. -2. add this to your conf.py: - - sys.path.append(os.path.abspath('_themes')) - html_theme_path = ['_themes'] - html_theme = 'flask' - -The following themes exist: - -- 'flask' - the standard flask documentation theme for large - projects -- 'flask_small' - small one-page theme. Intended to be used by - very small addon libraries for flask. - -The following options exist for the flask_small theme: - - [options] - index_logo = '' filename of a picture in _static - to be used as replacement for the - h1 in the index.rst file. - index_logo_height = 120px height of the index logo - github_fork = '' repository name on github for the - "fork me" badge diff -Nru flask-sqlalchemy-2.2/flask_sqlalchemy/__init__.py flask-sqlalchemy-2.1/flask_sqlalchemy/__init__.py --- flask-sqlalchemy-2.2/flask_sqlalchemy/__init__.py 2017-02-27 15:18:28.000000000 +0000 +++ flask-sqlalchemy-2.1/flask_sqlalchemy/__init__.py 2015-10-23 09:49:49.000000000 +0000 @@ -8,31 +8,27 @@ :copyright: (c) 2014 by Armin Ronacher, Daniel Neuhäuser. :license: BSD, see LICENSE for more details. """ -from __future__ import absolute_import - -import functools +from __future__ import with_statement, absolute_import import os import re import sys import time +import functools import warnings +import sqlalchemy from math import ceil +from functools import partial +from flask import _request_ctx_stack, abort, has_request_context, request +from flask.signals import Namespace from operator import itemgetter from threading import Lock - -import sqlalchemy -from flask import _app_ctx_stack, abort, current_app, g, request -from flask.signals import Namespace -from sqlalchemy import event, inspect, orm -from sqlalchemy.engine.url import make_url -from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base, \ - declared_attr +from sqlalchemy import orm, event, inspect from sqlalchemy.orm.exc import UnmappedClassError from sqlalchemy.orm.session import Session as SessionBase - -from ._compat import iteritems, itervalues, string_types, xrange - -__version__ = '2.2' +from sqlalchemy.engine.url import make_url +from sqlalchemy.ext.declarative import declarative_base, DeclarativeMeta +from flask_sqlalchemy._compat import iteritems, itervalues, xrange, \ + string_types # the best timer function for the platform if sys.platform == 'win32': @@ -40,9 +36,23 @@ else: _timer = time.time -_camelcase_re = re.compile(r'([A-Z]+)(?=[a-z0-9])') +try: + from flask import _app_ctx_stack +except ImportError: + _app_ctx_stack = None + + +__version__ = '2.1' + + +# 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') @@ -58,34 +68,34 @@ return _make_table -def _set_default_query_class(d, cls): +def _set_default_query_class(d): if 'query_class' not in d: - d['query_class'] = cls + d['query_class'] = BaseQuery -def _wrap_with_default_query_class(fn, cls): +def _wrap_with_default_query_class(fn): @functools.wraps(fn) def newfn(*args, **kwargs): - _set_default_query_class(kwargs, cls) + _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], cls) + _set_default_query_class(backref[1]) return fn(*args, **kwargs) return newfn -def _include_sqlalchemy(obj, cls): +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, cls) - obj.relation = _wrap_with_default_query_class(obj.relation, cls) - obj.dynamic_loader = _wrap_with_default_query_class(obj.dynamic_loader, cls) + 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 @@ -138,12 +148,12 @@ to an external transaction. """ - def __init__(self, db, autocommit=False, autoflush=True, **options): + def __init__(self, db, autocommit=False, autoflush=True, app=None, **options): #: The application that this session belongs to. self.app = app = db.get_app() track_modifications = app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] bind = options.pop('bind', None) or db.engine - binds = options.pop('binds', db.get_binds(app)) + binds = options.pop('binds', None) or db.get_binds(app) if track_modifications is None or track_modifications: _SessionSignalEvents.register(self) @@ -232,38 +242,32 @@ class _EngineDebuggingSignalEvents(object): - """Sets up handlers for two events that let us track the execution time of - queries.""" + """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): - event.listen( - self.engine, 'before_cursor_execute', self.before_cursor_execute - ) - event.listen( - self.engine, 'after_cursor_execute', self.after_cursor_execute - ) + event.listen(self.engine, 'before_cursor_execute', self.before_cursor_execute) + event.listen(self.engine, 'after_cursor_execute', self.after_cursor_execute) - def before_cursor_execute( - self, conn, cursor, statement, parameters, context, executemany - ): - if current_app: + 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 - ): - if current_app: - if 'sqlalchemy_queries' not in g: - g.sqlalchemy_queries = [] - - g.sqlalchemy_queries.append(_DebugQueryTuple(( + 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) - ))) + _calling_context(self.app_package)))) def get_debug_queries(): @@ -298,7 +302,7 @@ query was issued. The exact format is undefined so don't try to reconstruct filename or function name. """ - return g.get('sqlalchemy_queries', ()) + return getattr(connection_stack.top, 'sqlalchemy_queries', []) class Pagination(object): @@ -340,8 +344,6 @@ @property def prev_num(self): """Number of the previous page.""" - if not self.has_prev: - return None return self.page - 1 @property @@ -363,8 +365,6 @@ @property def next_num(self): """Number of the next page""" - if not self.has_next: - return None return self.page + 1 def iter_pages(self, left_edge=2, left_current=2, @@ -405,46 +405,47 @@ class BaseQuery(orm.Query): - """SQLAlchemy :class:`~sqlalchemy.orm.query.Query` subclass with convenience methods for querying in a web application. - - This is the default :attr:`~Model.query` object used for models, and exposed as :attr:`~SQLAlchemy.Query`. - Override the query class for an individual model by subclassing this and setting :attr:`~Model.query_class`. + """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``.""" - + """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``.""" - + """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=None, per_page=None, error_out=True): - """Returns ``per_page`` items from page ``page``. + """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`. - If no items are found and ``page`` is greater than 1, or if page is - less than 1, it aborts with 404. - This behavior can be disabled by passing ``error_out=False``. - - If ``page`` or ``per_page`` are ``None``, they will be retrieved from - the request query. - If the values are not ints and ``error_out`` is ``True``, it aborts - with 404. - If there is no request or they aren't in the query, they default to 1 - and 20 respectively. + If page or per_page are None, they will be retrieved from the + request query. If the values are not ints and ``error_out`` is + true, it will abort with 404. If there is no request or they + aren't in the query, they default to page 1 and 20 + respectively. - Returns a :class:`Pagination` object. + Returns an :class:`Pagination` object. """ - if request: + if has_request_context(): if page is None: try: page = int(request.args.get('page', 1)) @@ -488,6 +489,7 @@ class _QueryProperty(object): + def __init__(self, sa): self.sa = sa @@ -539,7 +541,7 @@ self._sa.apply_pool_defaults(self._app, options) self._sa.apply_driver_hacks(self._app, info, options) if echo: - options['echo'] = echo + options['echo'] = True self._engine = rv = sqlalchemy.create_engine(info, **options) if _record_queries(self._app): _EngineDebuggingSignalEvents(self._engine, @@ -548,9 +550,18 @@ return rv -def _should_set_tablename(cls): - """Traverse the model's MRO. If a primary key column is found before a - table or tablename, then a new tablename should be generated. +def _should_set_tablename(bases, d): + """Check what values are set by a class and its bases to determine if a + tablename should be automatically generated. + + The class and its bases are checked in order of precedence: the class + itself then each base in the order they were given at class definition. + + Abstract classes do not generate a tablename, although they may have set + or inherited a tablename elsewhere. + + If a class defines a tablename or table, a new one will not be generated. + Otherwise, if the class defines a primary key, a new name will be generated. This supports: @@ -558,50 +569,45 @@ * Single table inheritance. * Inheriting from mixins or abstract models. - :param cls: model to check + :param bases: base classes of new class + :param d: new class dict :return: True if tablename should be set """ - for base in cls.__mro__: - d = base.__dict__ + if '__tablename__' in d or '__table__' in d or '__abstract__' in d: + return False + + if any(v.primary_key for v in itervalues(d) if isinstance(v, sqlalchemy.Column)): + return True - if '__tablename__' in d or '__table__' in d: + for base in bases: + if hasattr(base, '__tablename__') or hasattr(base, '__table__'): return False - for name, obj in iteritems(d): - if isinstance(obj, declared_attr): - obj = getattr(cls, name) + for name in dir(base): + attr = getattr(base, name) - if isinstance(obj, sqlalchemy.Column) and obj.primary_key: + if isinstance(attr, sqlalchemy.Column) and attr.primary_key: return True -def camel_to_snake_case(name): - def _join(match): - word = match.group() - - if len(word) > 1: - return ('_%s_%s' % (word[:-1], word[-1])).lower() - - return '_' + word.lower() - - return _camelcase_re.sub(_join, name).lstrip('_') - - class _BoundDeclarativeMeta(DeclarativeMeta): + def __new__(cls, name, bases, d): - # if tablename is set explicitly, move it to the cache attribute so - # that future subclasses still have auto behavior - if '__tablename__' in d: - d['_cached_tablename'] = d.pop('__tablename__') + if _should_set_tablename(bases, 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) or getattr(self, '__bind_key__', None) + bind_key = d.pop('__bind_key__', None) DeclarativeMeta.__init__(self, name, bases, d) - - if bind_key is not None and hasattr(self, '__table__'): + if bind_key is not None: self.__table__.info['bind_key'] = bind_key @@ -616,38 +622,23 @@ class _SQLAlchemyState(object): """Remembers configuration for the (db, app) tuple.""" - def __init__(self, db): + def __init__(self, db, app): self.db = db + self.app = app self.connectors = {} class Model(object): - """Base class for SQLAlchemy declarative base model. + """Baseclass for custom user models.""" - To define models, subclass :attr:`db.Model `, not this class. - To customize ``db.Model``, subclass this and pass it as ``model_class`` to :func:`SQLAlchemy`. - """ - - #: Query class used by :attr:`query`. - #: Defaults to :class:`SQLAlchemy.Query`, which defaults to :class:`BaseQuery`. - query_class = None + #: the query class used. The :attr:`query` attribute is an instance + #: of this class. By default a :class:`BaseQuery` is used. + query_class = BaseQuery - #: Convenience property to query the database for instances of this model using the current session. - #: Equivalent to ``db.session.query(Model)`` unless :attr:`query_class` has been changed. + #: an instance of :attr:`query_class`. Can be used to query the + #: database for instances of this model. query = None - _cached_tablename = None - - @declared_attr - def __tablename__(cls): - if ( - '_cached_tablename' not in cls.__dict__ and - _should_set_tablename(cls) - ): - cls._cached_tablename = camel_to_snake_case(cls.__name__) - - return cls._cached_tablename - class SQLAlchemy(object): """This class is used to control the SQLAlchemy integration to one @@ -680,8 +671,8 @@ 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 + `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 @@ -706,9 +697,26 @@ emulates `Table` behavior but is not a class. `db.Table` exposes the `Table` interface, but is a function which allows omission of metadata. - The ``session_options`` parameter, if provided, is a dict of parameters - to be passed to the session constructor. See :class:`~sqlalchemy.orm.session.Session` - for the standard options. + 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. @@ -720,91 +728,53 @@ .. versionadded:: 2.1 The `metadata` parameter was added. This allows for setting custom naming conventions among other, non-trivial things. - - .. versionadded:: 3.0 - The `query_class` parameter was added, to allow customisation - of the query class, in place of the default of :class:`BaseQuery`. - - The `model_class` parameter was added, which allows a custom model - class to be used in place of :class:`Model`. - - .. versionchanged:: 3.0 - Utilise the same query class across `session`, `Model.query` and `Query`. """ - #: Default query class used by :attr:`Model.query` and other queries. - #: Customize this by passing ``query_class`` to :func:`SQLAlchemy`. - #: Defaults to :class:`BaseQuery`. - Query = None + def __init__(self, app=None, use_native_unicode=True, session_options=None, metadata=None): - def __init__(self, app=None, use_native_unicode=True, session_options=None, - metadata=None, query_class=BaseQuery, model_class=Model): + if session_options is None: + session_options = {} + session_options.setdefault('scopefunc', connection_stack.__ident_func__) self.use_native_unicode = use_native_unicode - self.Query = query_class self.session = self.create_scoped_session(session_options) - self.Model = self.make_declarative_base(model_class, metadata) + self.Model = self.make_declarative_base(metadata) + self.Query = BaseQuery self._engine_lock = Lock() self.app = app - _include_sqlalchemy(self, query_class) + _include_sqlalchemy(self) if app is not None: self.init_app(app) @property def metadata(self): - """The metadata associated with ``db.Model``.""" - + """Returns the metadata""" return self.Model.metadata def create_scoped_session(self, options=None): - """Create a :class:`~sqlalchemy.orm.scoping.scoped_session` - on the factory from :meth:`create_session`. - - An extra key ``'scopefunc'`` can be set on the ``options`` dict to - specify a custom scope function. If it's not provided, Flask's app - context stack identity is used. This will ensure that sessions are - created and removed with the request/response cycle, and should be fine - in most cases. - - :param options: dict of keyword arguments passed to session class in - ``create_session`` + """Helper factory method that creates a scoped session. It + internally calls :meth:`create_session`. """ - if options is None: options = {} - - scopefunc = options.pop('scopefunc', _app_ctx_stack.__ident_func__) - options.setdefault('query_cls', self.Query) - return orm.scoped_session( - self.create_session(options), scopefunc=scopefunc - ) + scopefunc = options.pop('scopefunc', None) + return orm.scoped_session(partial(self.create_session, options), + scopefunc=scopefunc) def create_session(self, options): - """Create the session factory used by :meth:`create_scoped_session`. - - The factory **must** return an object that SQLAlchemy recognizes as a session, - or registering session events may raise an exception. - - Valid factories include a :class:`~sqlalchemy.orm.session.Session` - class or a :class:`~sqlalchemy.orm.session.sessionmaker`. + """Creates the session. The default implementation returns a + :class:`SignallingSession`. - The default implementation creates a ``sessionmaker`` for :class:`SignallingSession`. - - :param options: dict of keyword arguments passed to session class + .. versionadded:: 2.0 """ + return SignallingSession(self, **options) - return orm.sessionmaker(class_=SignallingSession, db=self, **options) - - def make_declarative_base(self, model, metadata=None): + def make_declarative_base(self, metadata=None): """Creates the declarative base.""" - base = declarative_base(cls=model, name='Model', + base = declarative_base(cls=Model, name='Model', metadata=metadata, metaclass=_BoundDeclarativeMeta) - - if not getattr(base, 'query_class', None): - base.query_class = self.Query - base.query = _QueryProperty(self) return base @@ -814,13 +784,7 @@ of an application not initialized that way or connections will leak. """ - if 'SQLALCHEMY_DATABASE_URI' not in app.config: - warnings.warn( - 'SQLALCHEMY_DATABASE_URI not set. Defaulting to ' - '"sqlite:///:memory:".' - ) - - app.config.setdefault('SQLALCHEMY_DATABASE_URI', 'sqlite:///:memory:') + 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) @@ -830,25 +794,32 @@ app.config.setdefault('SQLALCHEMY_POOL_RECYCLE', None) app.config.setdefault('SQLALCHEMY_MAX_OVERFLOW', None) app.config.setdefault('SQLALCHEMY_COMMIT_ON_TEARDOWN', False) - track_modifications = app.config.setdefault( - 'SQLALCHEMY_TRACK_MODIFICATIONS', None - ) + track_modifications = app.config.setdefault('SQLALCHEMY_TRACK_MODIFICATIONS', None) if track_modifications is None: - warnings.warn(FSADeprecationWarning( - 'SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and ' - 'will be disabled by default in the future. Set it to True ' - 'or False to suppress this warning.' - )) + 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.') - app.extensions['sqlalchemy'] = _SQLAlchemyState(self) + 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 - @app.teardown_appcontext + @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 @@ -880,6 +851,8 @@ 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 from sqlalchemy.pool import StaticPool @@ -888,8 +861,6 @@ options['connect_args'] = {} options['connect_args']['check_same_thread'] = False - # we go to memory and the pool size was explicitly set - # to 0 which is fail. Let the user know that if pool_size == 0: raise RuntimeError('SQLite in memory database with an ' 'empty queue not possible due to data ' @@ -919,44 +890,38 @@ is used this might raise a :exc:`RuntimeError` if no application is active at the moment. """ - return self.get_engine() + return self.get_engine(self.get_app()) - def make_connector(self, app=None, bind=None): + def make_connector(self, app, bind=None): """Creates the connector for a given state and bind.""" - return _EngineConnector(self, self.get_app(app), bind) - - def get_engine(self, app=None, bind=None): - """Returns a specific engine.""" + return _EngineConnector(self, app, bind) - app = self.get_app(app) - state = get_state(app) + 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.""" - + """Helper method that implements the logic to look up an application. + """ if reference_app is not None: return reference_app - - if current_app: - return current_app - if self.app is not None: return self.app - - raise RuntimeError( - 'application not registered on db instance and no application' - 'bound to current context' - ) + 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.""" @@ -1023,14 +988,14 @@ self._execute_for_all_tables(app, bind, 'reflect', skip_tables=True) 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__, - self.engine.url if self.app or current_app else None + app and app.config['SQLALCHEMY_DATABASE_URI'] or None ) - - -class FSADeprecationWarning(DeprecationWarning): - pass - - -warnings.simplefilter('always', FSADeprecationWarning) diff -Nru flask-sqlalchemy-2.2/Flask_SQLAlchemy.egg-info/PKG-INFO flask-sqlalchemy-2.1/Flask_SQLAlchemy.egg-info/PKG-INFO --- flask-sqlalchemy-2.2/Flask_SQLAlchemy.egg-info/PKG-INFO 2017-02-27 15:38:44.000000000 +0000 +++ flask-sqlalchemy-2.1/Flask_SQLAlchemy.egg-info/PKG-INFO 2015-10-23 09:49:49.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: Flask-SQLAlchemy -Version: 2.2 +Version: 2.1 Summary: Adds SQLAlchemy support to your Flask application Home-page: http://github.com/mitsuhiko/flask-sqlalchemy Author: Phil Howell @@ -34,5 +34,3 @@ Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 -Classifier: Programming Language :: Python :: 3.5 -Classifier: Programming Language :: Python :: 3.6 diff -Nru flask-sqlalchemy-2.2/Flask_SQLAlchemy.egg-info/requires.txt flask-sqlalchemy-2.1/Flask_SQLAlchemy.egg-info/requires.txt --- flask-sqlalchemy-2.2/Flask_SQLAlchemy.egg-info/requires.txt 2017-02-27 15:38:44.000000000 +0000 +++ flask-sqlalchemy-2.1/Flask_SQLAlchemy.egg-info/requires.txt 2015-10-23 09:49:49.000000000 +0000 @@ -1,2 +1,2 @@ Flask>=0.10 -SQLAlchemy>=0.8.0 +SQLAlchemy>=0.7 \ No newline at end of file diff -Nru flask-sqlalchemy-2.2/Flask_SQLAlchemy.egg-info/SOURCES.txt flask-sqlalchemy-2.1/Flask_SQLAlchemy.egg-info/SOURCES.txt --- flask-sqlalchemy-2.2/Flask_SQLAlchemy.egg-info/SOURCES.txt 2017-02-27 15:38:44.000000000 +0000 +++ flask-sqlalchemy-2.1/Flask_SQLAlchemy.egg-info/SOURCES.txt 2015-10-23 09:49:49.000000000 +0000 @@ -5,7 +5,6 @@ setup.cfg setup.py test_sqlalchemy.py -tox.ini Flask_SQLAlchemy.egg-info/PKG-INFO Flask_SQLAlchemy.egg-info/SOURCES.txt Flask_SQLAlchemy.egg-info/dependency_links.txt @@ -20,7 +19,6 @@ docs/config.rst docs/contents.rst.inc docs/contexts.rst -docs/customizing.rst docs/flaskstyle.sty docs/index.rst docs/logo.pdf @@ -29,22 +27,9 @@ docs/queries.rst docs/quickstart.rst docs/signals.rst -docs/_static/flask-sqlalchemy-logo.png -docs/_static/flask-sqlalchemy-title.png +docs/_static/flask-sqlalchemy-small.png +docs/_static/flask-sqlalchemy.png docs/_templates/sidebarintro.html docs/_templates/sidebarlogo.html -docs/_themes/.git -docs/_themes/.gitignore -docs/_themes/LICENSE -docs/_themes/README -docs/_themes/flask_theme_support.py -docs/_themes/flask/layout.html -docs/_themes/flask/relations.html -docs/_themes/flask/theme.conf -docs/_themes/flask/static/flasky.css_t -docs/_themes/flask/static/small_flask.css -docs/_themes/flask_small/layout.html -docs/_themes/flask_small/theme.conf -docs/_themes/flask_small/static/flasky.css_t flask_sqlalchemy/__init__.py flask_sqlalchemy/_compat.py \ No newline at end of file diff -Nru flask-sqlalchemy-2.2/MANIFEST.in flask-sqlalchemy-2.1/MANIFEST.in --- flask-sqlalchemy-2.2/MANIFEST.in 2017-02-27 15:37:57.000000000 +0000 +++ flask-sqlalchemy-2.1/MANIFEST.in 2015-02-26 17:58:08.000000000 +0000 @@ -1,6 +1,6 @@ -include LICENSE CHANGES tox.ini test_sqlalchemy.py -graft docs -prune docs/_build -prune docs/_themes/.git +include LICENSE CHANGES *.py +recursive-include docs * recursive-exclude docs *.pyc recursive-exclude docs *.pyo +prune docs/_build +prune docs/_themes/.git diff -Nru flask-sqlalchemy-2.2/PKG-INFO flask-sqlalchemy-2.1/PKG-INFO --- flask-sqlalchemy-2.2/PKG-INFO 2017-02-27 15:38:44.000000000 +0000 +++ flask-sqlalchemy-2.1/PKG-INFO 2015-10-23 09:49:49.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: Flask-SQLAlchemy -Version: 2.2 +Version: 2.1 Summary: Adds SQLAlchemy support to your Flask application Home-page: http://github.com/mitsuhiko/flask-sqlalchemy Author: Phil Howell @@ -34,5 +34,3 @@ Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 -Classifier: Programming Language :: Python :: 3.5 -Classifier: Programming Language :: Python :: 3.6 diff -Nru flask-sqlalchemy-2.2/README flask-sqlalchemy-2.1/README --- flask-sqlalchemy-2.2/README 2016-05-31 16:38:29.000000000 +0000 +++ flask-sqlalchemy-2.1/README 2015-07-28 19:15:32.000000000 +0000 @@ -9,7 +9,7 @@ ~ What's the latest version? - 2.1 is the most recent stable version. + 2.0 is the most recent stable version. 2.1 is slated for release in early February. ~ What do I need? diff -Nru flask-sqlalchemy-2.2/setup.cfg flask-sqlalchemy-2.1/setup.cfg --- flask-sqlalchemy-2.2/setup.cfg 2017-02-27 15:38:44.000000000 +0000 +++ flask-sqlalchemy-2.1/setup.cfg 2015-10-23 09:49:49.000000000 +0000 @@ -1,16 +1,11 @@ [egg_info] -tag_build = tag_date = 0 +tag_build = +tag_svn_revision = 0 [aliases] -release = egg_info -Db '' +release = egg_info -RDb '' -[tool:pytest] +[pytest] norecursedirs = .* _* scripts {args} -[bdist_wheel] -universal = 1 - -[metadata] -license_file = LICENSE - diff -Nru flask-sqlalchemy-2.2/setup.py flask-sqlalchemy-2.1/setup.py --- flask-sqlalchemy-2.2/setup.py 2017-02-27 15:18:28.000000000 +0000 +++ flask-sqlalchemy-2.1/setup.py 2015-10-23 09:49:49.000000000 +0000 @@ -1,4 +1,3 @@ -#!/usr/bin/env python """ Flask-SQLAlchemy ---------------- @@ -15,9 +14,10 @@ """ from setuptools import setup + setup( name='Flask-SQLAlchemy', - version='2.2', + version='2.1', url='http://github.com/mitsuhiko/flask-sqlalchemy', license='BSD', author='Armin Ronacher', @@ -31,7 +31,7 @@ platforms='any', install_requires=[ 'Flask>=0.10', - 'SQLAlchemy>=0.8.0' + 'SQLAlchemy>=0.7' ], test_suite='test_sqlalchemy.suite', classifiers=[ @@ -48,7 +48,5 @@ 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', ] ) diff -Nru flask-sqlalchemy-2.2/test_sqlalchemy.py flask-sqlalchemy-2.1/test_sqlalchemy.py --- flask-sqlalchemy-2.2/test_sqlalchemy.py 2017-02-27 14:33:32.000000000 +0000 +++ flask-sqlalchemy-2.1/test_sqlalchemy.py 2015-07-28 19:15:32.000000000 +0000 @@ -1,16 +1,14 @@ -#!/usr/bin/env python from __future__ import with_statement +import atexit import unittest from datetime import datetime - import flask -import sqlalchemy as sa +import flask_sqlalchemy as sqlalchemy +from sqlalchemy import MetaData from sqlalchemy.ext.declarative import declared_attr from sqlalchemy.orm import sessionmaker -import flask_sqlalchemy as fsa - def make_todo_model(db): class Todo(db.Model): @@ -26,15 +24,16 @@ self.text = text self.done = False self.pub_date = datetime.utcnow() - return Todo class BasicAppTestCase(unittest.TestCase): + def setUp(self): app = flask.Flask(__name__) + app.config['SQLALCHEMY_ENGINE'] = 'sqlite://' app.config['TESTING'] = True - db = fsa.SQLAlchemy(app) + db = sqlalchemy.SQLAlchemy(app) self.Todo = make_todo_model(db) @app.route('/') @@ -70,7 +69,7 @@ self.db.session.add(todo) self.db.session.commit() - queries = fsa.get_debug_queries() + queries = sqlalchemy.get_debug_queries() self.assertEqual(len(queries), 1) query = queries[0] self.assertTrue('insert into' in query.statement.lower()) @@ -83,13 +82,25 @@ self.assertEqual(self.db.metadata, self.db.Model.metadata) -class MetaDataTestCase(unittest.TestCase): +class CustomMetaDataTestCase(unittest.TestCase): + def setUp(self): self.app = flask.Flask(__name__) + self.app.config['SQLALCHEMY_ENGINE'] = 'sqlite://' self.app.config['TESTING'] = True - def test_default_metadata(self): - db = fsa.SQLAlchemy(self.app, metadata=None) + def test_custom_metadata_positive(self): + + convention = { + "ix": 'ix_%(column_0_label)s', + "uq": "uq_%(table_name)s_%(column_0_name)s", + "ck": "ck_%(table_name)s_%(constraint_name)s", + "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s", + "pk": "pk_%(table_name)s" + } + + metadata = MetaData(naming_convention=convention) + db = sqlalchemy.SQLAlchemy(self.app, metadata=metadata) self.db = db class One(db.Model): @@ -101,18 +112,15 @@ one_id = db.Column(db.Integer, db.ForeignKey(One.id)) myunique = db.Column(db.Integer, unique=True) - self.assertTrue(One.metadata.__class__ is sa.MetaData) - self.assertTrue(Two.metadata.__class__ is sa.MetaData) + self.assertEqual(list(One.__table__.constraints)[0].name, 'pk_one') + self.assertEqual(list(One.__table__.indexes)[0].name, 'ix_one_myindex') - self.assertEqual(One.__table__.schema, None) - self.assertEqual(Two.__table__.schema, None) + self.assertTrue('fk_two_one_id_one' in [c.name for c in Two.__table__.constraints]) + self.assertTrue('uq_two_myunique' in [c.name for c in Two.__table__.constraints]) + self.assertTrue('pk_two' in [c.name for c in Two.__table__.constraints]) - def test_custom_metadata(self): - class CustomMetaData(sa.MetaData): - pass - - custom_metadata = CustomMetaData(schema="test_schema") - db = fsa.SQLAlchemy(self.app, metadata=custom_metadata) + def test_custom_metadata_negative(self): + db = sqlalchemy.SQLAlchemy(self.app, metadata=None) self.db = db class One(db.Model): @@ -124,26 +132,22 @@ one_id = db.Column(db.Integer, db.ForeignKey(One.id)) myunique = db.Column(db.Integer, unique=True) - self.assertTrue(One.metadata is custom_metadata) - self.assertTrue(Two.metadata is custom_metadata) - - self.assertFalse(One.metadata.__class__ is sa.MetaData) - self.assertTrue(One.metadata.__class__ is CustomMetaData) - - self.assertFalse(Two.metadata.__class__ is sa.MetaData) - self.assertTrue(Two.metadata.__class__ is CustomMetaData) + self.assertNotEqual(list(One.__table__.constraints)[0].name, 'pk_one') - self.assertEqual(One.__table__.schema, "test_schema") - self.assertEqual(Two.__table__.schema, "test_schema") + self.assertFalse('fk_two_one_id_one' in [c.name for c in Two.__table__.constraints]) + self.assertFalse('uq_two_myunique' in [c.name for c in Two.__table__.constraints]) + self.assertFalse('pk_two' in [c.name for c in Two.__table__.constraints]) class TestQueryProperty(unittest.TestCase): + def setUp(self): self.app = flask.Flask(__name__) + self.app.config['SQLALCHEMY_ENGINE'] = 'sqlite://' self.app.config['TESTING'] = True def test_no_app_bound(self): - db = fsa.SQLAlchemy() + db = sqlalchemy.SQLAlchemy() db.init_app(self.app) Todo = make_todo_model(db) @@ -158,7 +162,7 @@ self.assertEqual(len(Todo.query.all()), 1) def test_app_bound(self): - db = fsa.SQLAlchemy(self.app) + db = sqlalchemy.SQLAlchemy(self.app) Todo = make_todo_model(db) db.create_all() @@ -171,10 +175,12 @@ class SignallingTestCase(unittest.TestCase): + def setUp(self): self.app = app = flask.Flask(__name__) + app.config['SQLALCHEMY_ENGINE'] = 'sqlite://' app.config['TESTING'] = True - self.db = fsa.SQLAlchemy(app) + self.db = sqlalchemy.SQLAlchemy(app) self.Todo = make_todo_model(self.db) self.db.create_all() @@ -188,47 +194,45 @@ def before_committed(sender, changes): Namespace.is_received = True - fsa.before_models_committed.connect(before_committed) - todo = self.Todo('Awesome', 'the text') - self.db.session.add(todo) - self.db.session.commit() - self.assertTrue(Namespace.is_received) - fsa.before_models_committed.disconnect(before_committed) + with sqlalchemy.before_models_committed.connected_to(before_committed, sender=self.app): + todo = self.Todo('Awesome', 'the text') + self.db.session.add(todo) + self.db.session.commit() + self.assertTrue(Namespace.is_received) def test_model_signals(self): recorded = [] - def committed(sender, changes): self.assertTrue(isinstance(changes, list)) recorded.extend(changes) - - fsa.models_committed.connect(committed) - todo = self.Todo('Awesome', 'the text') - self.db.session.add(todo) - self.assertEqual(len(recorded), 0) - self.db.session.commit() - self.assertEqual(len(recorded), 1) - self.assertEqual(recorded[0][0], todo) - self.assertEqual(recorded[0][1], 'insert') - del recorded[:] - todo.text = 'aha' - self.db.session.commit() - self.assertEqual(len(recorded), 1) - self.assertEqual(recorded[0][0], todo) - self.assertEqual(recorded[0][1], 'update') - del recorded[:] - self.db.session.delete(todo) - self.db.session.commit() - self.assertEqual(len(recorded), 1) - self.assertEqual(recorded[0][0], todo) - self.assertEqual(recorded[0][1], 'delete') - fsa.models_committed.disconnect(committed) + with sqlalchemy.models_committed.connected_to(committed, + sender=self.app): + todo = self.Todo('Awesome', 'the text') + self.db.session.add(todo) + self.assertEqual(len(recorded), 0) + self.db.session.commit() + self.assertEqual(len(recorded), 1) + self.assertEqual(recorded[0][0], todo) + self.assertEqual(recorded[0][1], 'insert') + del recorded[:] + todo.text = 'aha' + self.db.session.commit() + self.assertEqual(len(recorded), 1) + self.assertEqual(recorded[0][0], todo) + self.assertEqual(recorded[0][1], 'update') + del recorded[:] + self.db.session.delete(todo) + self.db.session.commit() + self.assertEqual(len(recorded), 1) + self.assertEqual(recorded[0][0], todo) + self.assertEqual(recorded[0][1], 'delete') class TablenameTestCase(unittest.TestCase): def test_name(self): app = flask.Flask(__name__) - db = fsa.SQLAlchemy(app) + app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://' + db = sqlalchemy.SQLAlchemy(app) class FOOBar(db.Model): id = db.Column(db.Integer, primary_key=True) @@ -248,7 +252,8 @@ """Single table inheritance should not set a new name.""" app = flask.Flask(__name__) - db = fsa.SQLAlchemy(app) + app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://' + db = sqlalchemy.SQLAlchemy(app) class Duck(db.Model): id = db.Column(db.Integer, primary_key=True) @@ -262,7 +267,8 @@ """Model has a separate primary key; it should set a new name.""" app = flask.Flask(__name__) - db = fsa.SQLAlchemy(app) + app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://' + db = sqlalchemy.SQLAlchemy(app) class Duck(db.Model): id = db.Column(db.Integer, primary_key=True) @@ -276,7 +282,8 @@ """Primary key provided by mixin should still allow model to set tablename.""" app = flask.Flask(__name__) - db = fsa.SQLAlchemy(app) + app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://' + db = sqlalchemy.SQLAlchemy(app) class Base(object): id = db.Column(db.Integer, primary_key=True) @@ -291,7 +298,8 @@ """Abstract model should not set a name. Subclass should set a name.""" app = flask.Flask(__name__) - db = fsa.SQLAlchemy(app) + app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://' + db = sqlalchemy.SQLAlchemy(app) class Base(db.Model): __abstract__ = True @@ -300,14 +308,15 @@ class Duck(Base): pass - self.assertEqual(Base.__tablename__, 'base') + self.assertFalse(hasattr(Base, '__tablename__')) self.assertEqual(Duck.__tablename__, 'duck') def test_complex_inheritance(self): """Joined table inheritance, but the new primary key is provided by a mixin, not directly on the class.""" app = flask.Flask(__name__) - db = fsa.SQLAlchemy(app) + app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://' + db = sqlalchemy.SQLAlchemy(app) class Duck(db.Model): id = db.Column(db.Integer, primary_key=True) @@ -322,57 +331,11 @@ self.assertEqual(RubberDuck.__tablename__, 'rubber_duck') - def test_manual_name(self): - app = flask.Flask(__name__) - db = fsa.SQLAlchemy(app) - - class Duck(db.Model): - __tablename__ = 'DUCK' - id = db.Column(db.Integer, primary_key=True) - - class Daffy(Duck): - id = db.Column(db.Integer, db.ForeignKey(Duck.id), primary_key=True) - - self.assertEqual(Duck.__tablename__, 'DUCK') - self.assertEqual(Daffy.__tablename__, 'daffy') - - def test_no_access_to_class_property(self): - app = flask.Flask(__name__) - db = fsa.SQLAlchemy(app) - - class class_property(object): - def __init__(self, f): - self.f = f - - def __get__(self, instance, owner): - return self.f(owner) - - class Duck(db.Model): - id = db.Column(db.Integer, primary_key=True) - - class ns(object): - accessed = False - - # Since there's no id provided by the following model, - # _should_set_tablename will scan all attributes. If it's working - # properly, it won't access the class property, but will access the - # declared_attr. - - class Witch(Duck): - @declared_attr - def is_duck(self): - ns.accessed = True - - @class_property - def floats(self): - assert False - - self.assertTrue(ns.accessed) - class PaginationTestCase(unittest.TestCase): + def test_basic_pagination(self): - p = fsa.Pagination(None, 1, 20, 500, []) + p = sqlalchemy.Pagination(None, 1, 20, 500, []) self.assertEqual(p.page, 1) self.assertFalse(p.has_prev) self.assertTrue(p.has_next) @@ -386,12 +349,12 @@ [1, 2, None, 8, 9, 10, 11, 12, 13, 14, None, 24, 25]) def test_pagination_pages_when_0_items_per_page(self): - p = fsa.Pagination(None, 1, 0, 500, []) + p = sqlalchemy.Pagination(None, 1, 0, 500, []) self.assertEqual(p.pages, 0) def test_query_paginate(self): app = flask.Flask(__name__) - db = fsa.SQLAlchemy(app) + db = sqlalchemy.SQLAlchemy(app) Todo = make_todo_model(db) db.create_all() @@ -419,13 +382,28 @@ class BindsTestCase(unittest.TestCase): + def test_basic_binds(self): + import tempfile + _, db1 = tempfile.mkstemp() + _, db2 = tempfile.mkstemp() + + def _remove_files(): + import os + try: + os.remove(db1) + os.remove(db2) + except IOError: + pass + atexit.register(_remove_files) + app = flask.Flask(__name__) + app.config['SQLALCHEMY_ENGINE'] = 'sqlite://' app.config['SQLALCHEMY_BINDS'] = { - 'foo': 'sqlite://', - 'bar': 'sqlite://' + 'foo': 'sqlite:///' + db1, + 'bar': 'sqlite:///' + db2 } - db = fsa.SQLAlchemy(app) + db = sqlalchemy.SQLAlchemy(app) class Foo(db.Model): __bind_key__ = 'foo' @@ -478,121 +456,39 @@ Baz.__table__: db.get_engine(app, None) }) - def test_abstract_binds(self): - app = flask.Flask(__name__) - app.config['SQLALCHEMY_BINDS'] = { - 'foo': 'sqlite://' - } - db = fsa.SQLAlchemy(app) - - class AbstractFooBoundModel(db.Model): - __abstract__ = True - __bind_key__ = 'foo' - - class FooBoundModel(AbstractFooBoundModel): - id = db.Column(db.Integer, primary_key=True) - - db.create_all() - - # does the model have the correct engines? - self.assertEqual(db.metadata.tables['foo_bound_model'].info['bind_key'], 'foo') - - # see the tables created in an engine - metadata = db.MetaData() - metadata.reflect(bind=db.get_engine(app, 'foo')) - self.assertEqual(len(metadata.tables), 1) - self.assertTrue('foo_bound_model' in metadata.tables) - class DefaultQueryClassTestCase(unittest.TestCase): - def test_default_query_class(self): - app = flask.Flask(__name__) - app.config['TESTING'] = True - db = fsa.SQLAlchemy(app) - - class Parent(db.Model): - id = db.Column(db.Integer, primary_key=True) - children = db.relationship("Child", backref="parent", lazy='dynamic') - - class Child(db.Model): - id = db.Column(db.Integer, primary_key=True) - parent_id = db.Column(db.Integer, db.ForeignKey('parent.id')) - - p = Parent() - c = Child() - c.parent = p - - self.assertEqual(type(Parent.query), fsa.BaseQuery) - self.assertEqual(type(Child.query), fsa.BaseQuery) - self.assertTrue(isinstance(p.children, fsa.BaseQuery)) - self.assertTrue(isinstance(db.session.query(Parent), fsa.BaseQuery)) - - -class CustomQueryClassTestCase(unittest.TestCase): - def test_custom_query_class(self): - class CustomQueryClass(fsa.BaseQuery): - pass + def test_default_query_class(self): app = flask.Flask(__name__) + app.config['SQLALCHEMY_ENGINE'] = 'sqlite://' app.config['TESTING'] = True - db = fsa.SQLAlchemy(app, query_class=CustomQueryClass) + db = sqlalchemy.SQLAlchemy(app) class Parent(db.Model): id = db.Column(db.Integer, primary_key=True) - children = db.relationship("Child", backref="parent", lazy='dynamic') - + children = db.relationship("Child", backref = "parents", lazy='dynamic') class Child(db.Model): id = db.Column(db.Integer, primary_key=True) parent_id = db.Column(db.Integer, db.ForeignKey('parent.id')) - p = Parent() c = Child() c.parent = p - - self.assertEqual(type(Parent.query), CustomQueryClass) - self.assertEqual(type(Child.query), CustomQueryClass) - self.assertTrue(isinstance(p.children, CustomQueryClass)) - self.assertEqual(db.Query, CustomQueryClass) - self.assertEqual(db.Model.query_class, CustomQueryClass) - self.assertTrue(isinstance(db.session.query(Parent), CustomQueryClass)) - - def test_dont_override_model_default(self): - class CustomQueryClass(fsa.BaseQuery): - pass - - app = flask.Flask(__name__) - app.config['TESTING'] = True - db = fsa.SQLAlchemy(app, query_class=CustomQueryClass) - - class SomeModel(db.Model): - id = db.Column(db.Integer, primary_key=True) - query_class = fsa.BaseQuery - - self.assertEqual(type(SomeModel.query), fsa.BaseQuery) - - -class CustomModelClassTestCase(unittest.TestCase): - def test_custom_query_class(self): - class CustomModelClass(fsa.Model): - pass - - app = flask.Flask(__name__) - app.config['TESTING'] = True - db = fsa.SQLAlchemy(app, model_class=CustomModelClass) - - class SomeModel(db.Model): - id = db.Column(db.Integer, primary_key=True) - - self.assertTrue(isinstance(SomeModel(), CustomModelClass)) + self.assertEqual(type(Parent.query), sqlalchemy.BaseQuery) + self.assertEqual(type(Child.query), sqlalchemy.BaseQuery) + self.assertTrue(isinstance(p.children, sqlalchemy.BaseQuery)) + #self.assertTrue(isinstance(c.parents, sqlalchemy.BaseQuery)) class SQLAlchemyIncludesTestCase(unittest.TestCase): + def test(self): """Various SQLAlchemy objects are exposed as attributes. """ - db = fsa.SQLAlchemy() + db = sqlalchemy.SQLAlchemy() - self.assertTrue(db.Column == sa.Column) + import sqlalchemy as sqlalchemy_lib + self.assertTrue(db.Column == sqlalchemy_lib.Column) # The Query object we expose is actually our own subclass. from flask_sqlalchemy import BaseQuery @@ -600,9 +496,10 @@ class RegressionTestCase(unittest.TestCase): + def test_joined_inheritance(self): app = flask.Flask(__name__) - db = fsa.SQLAlchemy(app) + db = sqlalchemy.SQLAlchemy(app) class Base(db.Model): id = db.Column(db.Integer, primary_key=True) @@ -620,7 +517,7 @@ def test_single_table_inheritance(self): app = flask.Flask(__name__) - db = fsa.SQLAlchemy(app) + db = sqlalchemy.SQLAlchemy(app) class Base(db.Model): id = db.Column(db.Integer, primary_key=True) @@ -636,7 +533,7 @@ def test_joined_inheritance_relation(self): app = flask.Flask(__name__) - db = fsa.SQLAlchemy(app) + db = sqlalchemy.SQLAlchemy(app) class Relation(db.Model): id = db.Column(db.Integer, primary_key=True) @@ -668,15 +565,16 @@ def test_connection_binds(self): app = flask.Flask(__name__) - db = fsa.SQLAlchemy(app) + db = sqlalchemy.SQLAlchemy(app) assert db.session.connection() - class SessionScopingTestCase(unittest.TestCase): + def test_default_session_scoping(self): app = flask.Flask(__name__) + app.config['SQLALCHEMY_ENGINE'] = 'sqlite://' app.config['TESTING'] = True - db = fsa.SQLAlchemy(app) + db = sqlalchemy.SQLAlchemy(app) class FOOBar(db.Model): id = db.Column(db.Integer, primary_key=True) @@ -690,12 +588,13 @@ def test_session_scoping_changing(self): app = flask.Flask(__name__) + app.config['SQLALCHEMY_ENGINE'] = 'sqlite://' app.config['TESTING'] = True def scopefunc(): return id(dict()) - db = fsa.SQLAlchemy(app, session_options=dict(scopefunc=scopefunc)) + db = sqlalchemy.SQLAlchemy(app, session_options=dict(scopefunc=scopefunc)) class FOOBar(db.Model): id = db.Column(db.Integer, primary_key=True) @@ -708,11 +607,14 @@ assert fb not in db.session # because a new scope is generated on each call + class CommitOnTeardownTestCase(unittest.TestCase): + def setUp(self): app = flask.Flask(__name__) + app.config['SQLALCHEMY_ENGINE'] = 'sqlite://' app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True - db = fsa.SQLAlchemy(app) + db = sqlalchemy.SQLAlchemy(app) Todo = make_todo_model(db) db.create_all() @@ -741,6 +643,7 @@ class StandardSessionTestCase(unittest.TestCase): + def test_insert_update_delete(self): # Ensure _SignalTrackingMapperExtension doesn't croak when # faced with a vanilla SQLAlchemy session. @@ -748,8 +651,9 @@ # Verifies that "AttributeError: 'SessionMaker' object has no attribute '_model_changes'" # is not thrown. app = flask.Flask(__name__) + app.config['SQLALCHEMY_ENGINE'] = 'sqlite://' app.config['TESTING'] = True - db = fsa.SQLAlchemy(app) + db = sqlalchemy.SQLAlchemy(app) Session = sessionmaker(bind=db.engine) class QazWsx(db.Model): @@ -759,46 +663,35 @@ db.create_all() session = Session() session.add(QazWsx()) - session.flush() # issues an INSERT. + session.flush() # issues an INSERT. session.expunge_all() qaz_wsx = session.query(QazWsx).first() assert qaz_wsx.x == '' qaz_wsx.x = 'test' - session.flush() # issues an UPDATE. + session.flush() # issues an UPDATE. session.expunge_all() qaz_wsx = session.query(QazWsx).first() assert qaz_wsx.x == 'test' - session.delete(qaz_wsx) # issues a DELETE. + session.delete(qaz_wsx) # issues a DELETE. assert session.query(QazWsx).first() is None - def test_listen_to_session_event(self): - app = flask.Flask(__name__) - app.config['TESTING'] = True - db = fsa.SQLAlchemy(app) - sa.event.listen(db.session, 'after_commit', lambda session: None) - def suite(): suite = unittest.TestSuite() - - suite.addTest(unittest.makeSuite(RegressionTestCase)) suite.addTest(unittest.makeSuite(BasicAppTestCase)) - suite.addTest(unittest.makeSuite(SQLAlchemyIncludesTestCase)) - suite.addTest(unittest.makeSuite(MetaDataTestCase)) + suite.addTest(unittest.makeSuite(CustomMetaDataTestCase)) suite.addTest(unittest.makeSuite(TestQueryProperty)) - suite.addTest(unittest.makeSuite(DefaultQueryClassTestCase)) - suite.addTest(unittest.makeSuite(CustomQueryClassTestCase)) suite.addTest(unittest.makeSuite(TablenameTestCase)) suite.addTest(unittest.makeSuite(PaginationTestCase)) suite.addTest(unittest.makeSuite(BindsTestCase)) - suite.addTest(unittest.makeSuite(StandardSessionTestCase)) + suite.addTest(unittest.makeSuite(DefaultQueryClassTestCase)) + suite.addTest(unittest.makeSuite(SQLAlchemyIncludesTestCase)) + suite.addTest(unittest.makeSuite(RegressionTestCase)) suite.addTest(unittest.makeSuite(SessionScopingTestCase)) suite.addTest(unittest.makeSuite(CommitOnTeardownTestCase)) - suite.addTest(unittest.makeSuite(CustomModelClassTestCase)) - if flask.signals_available: suite.addTest(unittest.makeSuite(SignallingTestCase)) - + suite.addTest(unittest.makeSuite(StandardSessionTestCase)) return suite diff -Nru flask-sqlalchemy-2.2/tox.ini flask-sqlalchemy-2.1/tox.ini --- flask-sqlalchemy-2.2/tox.ini 2017-01-14 19:57:27.000000000 +0000 +++ flask-sqlalchemy-2.1/tox.ini 1970-01-01 00:00:00.000000000 +0000 @@ -1,16 +0,0 @@ -[tox] -envlist = {py26,py27,pypy,py33,py34,py35,py36}-{lowest,current} - -[testenv] -commands = py.test {posargs} - -deps = - pytest - - lowest: flask==0.10 - lowest: sqlalchemy==0.8 - lowest: blinker==1.0 - - current: flask - current: sqlalchemy - current: blinker