././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1616094138.032466 Flask-SQLAlchemy-2.5.1/0000755000175000017500000000000000000000000013460 5ustar00daviddavid././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616094087.0 Flask-SQLAlchemy-2.5.1/CHANGES.rst0000644000175000017500000001645700000000000015277 0ustar00daviddavidVersion 2.5.1 ------------- Released 2021-03-18 - Fix compatibility with Python 2.7. Version 2.5.0 ------------- Released 2021-03-18 - Update to support SQLAlchemy 1.4. - SQLAlchemy ``URL`` objects are immutable. Some internal methods have changed to return a new URL instead of ``None``. :issue:`885` Version 2.4.4 ------------- Released 2020-07-14 - Change base class of meta mixins to ``type``. This fixes an issue caused by a regression in CPython 3.8.4. :issue:`852` Version 2.4.3 ------------- Released 2020-05-26 - Deprecate ``SQLALCHEMY_COMMIT_ON_TEARDOWN`` as it can cause various design issues that are difficult to debug. Call ``db.session.commit()`` directly instead. :issue:`216` Version 2.4.2 ------------- Released 2020-05-25 - Fix bad pagination when records are de-duped. :pr:`812` Version 2.4.1 ------------- Released 2019-09-24 - Fix ``AttributeError`` when using multiple binds with polymorphic models. :pr:`651` Version 2.4.0 ------------- Released 2019-04-24 - Make engine configuration more flexible. (:pr:`684`) - Address SQLAlchemy 1.3 deprecations. (:pr:`684`) - ``get_or_404()`` and ``first_or_404()`` now accept a ``description`` parameter to control the 404 message. (:issue:`636`) - Use ``time.perf_counter`` for Python 3 on Windows. (:issue:`638`) - Drop support for Python 2.6 and 3.3. (:pr:`687`) - Add an example of Flask's tutorial project, Flaskr, adapted for Flask-SQLAlchemy. (:pr:`720`) Version 2.3.2 ------------- Released 2017-10-11 - Don't mask the parent table for single-table inheritance models. (:pr:`561`) Version 2.3.1 ------------- Released 2017-10-05 - If a model has a table name that matches an existing table in the metadata, use that table. Fixes a regression where reflected tables were not picked up by models. (:issue:`551`) - Raise the correct error when a model has a table name but no primary key. (:pr:`556`) - Fix ``repr`` on models that don't have an identity because they have not been flushed yet. (:issue:`555`) - Allow specifying a ``max_per_page`` limit for pagination, to avoid users specifying high values in the request args. (:pr:`542`) - For ``paginate`` with ``error_out=False``, the minimum value for ``page`` is 1 and ``per_page`` is 0. (:issue:`558`) Version 2.3.0 ------------- Released 2017-09-28 - Multiple bugs with ``__tablename__`` generation are fixed. Names will be generated for models that define a primary key, but not for single-table inheritance subclasses. Names will not override a ``declared_attr``. ``PrimaryKeyConstraint`` is detected. (:pr:`541`) - Passing an existing ``declarative_base()`` as ``model_class`` to ``SQLAlchemy.__init__`` will use this as the base class instead of creating one. This allows customizing the metaclass used to construct the base. (:issue:`546`) - The undocumented ``DeclarativeMeta`` internals that the extension uses for binds and table name generation have been refactored to work as mixins. Documentation is added about how to create a custom metaclass that does not do table name generation. (:issue:`546`) - Model and metaclass code has been moved to a new ``models`` module. ``_BoundDeclarativeMeta`` is renamed to ``DefaultMeta``; the old name will be removed in 3.0. (:issue:`546`) - Models have a default ``repr`` that shows the model name and primary key. (:pr:`530`) - Fixed a bug where using ``init_app`` would cause connectors to always use the ``current_app`` rather than the app they were created for. This caused issues when multiple apps were registered with the extension. (:pr:`547`) Version 2.2 ----------- Released 2017-02-27, codename Dubnium - 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. (:pr:`328`) - Allow listening to SQLAlchemy events on ``db.session``. (:pr:`364`) - Allow ``__bind_key__`` on abstract models. (:pr:`373`) - Allow ``SQLALCHEMY_ECHO`` to be a string. (:issue:`409`) - Warn when ``SQLALCHEMY_DATABASE_URI`` is not set. (:pr:`443`) - Don't let pagination generate invalid page numbers. (:issue:`460`) - Drop support of Flask < 0.10. This means the db session is always tied to the app context and its teardown event. (:issue:`461`) - Tablename generation logic no longer accesses class properties unless they are ``declared_attr``. (:issue:`467`) Version 2.1 ----------- Released 2015-10-23, codename Caesium - Table names are automatically generated in more cases, including subclassing mixins and abstract models. - Allow using a custom MetaData object. - Add support for binds parameter to session. Version 2.0 ----------- Released 2014-08-29, codename Bohrium - Changed how the builtin signals are subscribed to skip non-Flask-SQLAlchemy sessions. This will also fix the attribute error about model changes not existing. - Added a way to control how signals for model modifications are tracked. - Made the ``SignallingSession`` a public interface and added a hook for customizing session creation. - If the ``bind`` parameter is given to the signalling session it will no longer cause an error that a parameter is given twice. - Added working table reflection support. - Enabled autoflush by default. - Consider ``SQLALCHEMY_COMMIT_ON_TEARDOWN`` harmful and remove from docs. Version 1.0 ----------- Released 2013-07-20, codename Aurum - Added Python 3.3 support. - Dropped 2.5 compatibility. - Various bugfixes - Changed versioning format to do major releases for each update now. Version 0.16 ------------ - New distribution format (flask_sqlalchemy) - Added support for Flask 0.9 specifics. Version 0.15 ------------ - Added session support for multiple databases. Version 0.14 ------------ - Make relative sqlite paths relative to the application root. Version 0.13 ------------ - Fixed an issue with Flask-SQLAlchemy not selecting the correct binds. Version 0.12 ------------ - Added support for multiple databases. - Expose ``BaseQuery`` as ``db.Query``. - Set default ``query_class`` for ``db.relation``, ``db.relationship``, and ``db.dynamic_loader`` to ``BaseQuery``. - Improved compatibility with Flask 0.7. Version 0.11 ------------ - Fixed a bug introduced in 0.10 with alternative table constructors. Version 0.10 ------------ - Added support for signals. - Table names are now automatically set from the class name unless overridden. - ``Model.query`` now always works for applications directly passed to the ``SQLAlchemy`` constructor. Furthermore the property now raises a ``RuntimeError`` instead of being ``None``. - Added session options to constructor. - Fixed a broken ``__repr__``. - ``db.Table`` is now a factory function that creates table objects. This makes it possible to omit the metadata. Version 0.9 ----------- - Applied changes to pass the Flask extension approval process. Version 0.8 ----------- - Added a few configuration keys for creating connections. - Automatically activate connection recycling for MySQL connections. - Added support for the Flask testing mode. Version 0.7 ----------- - Initial public release ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1616094138.0291326 Flask-SQLAlchemy-2.5.1/Flask_SQLAlchemy.egg-info/0000755000175000017500000000000000000000000020234 5ustar00daviddavid././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616094138.0 Flask-SQLAlchemy-2.5.1/Flask_SQLAlchemy.egg-info/PKG-INFO0000644000175000017500000000671100000000000021336 0ustar00daviddavidMetadata-Version: 1.2 Name: Flask-SQLAlchemy Version: 2.5.1 Summary: Adds SQLAlchemy support to your Flask application. Home-page: https://github.com/pallets/flask-sqlalchemy Author: Armin Ronacher Author-email: armin.ronacher@active-4.com Maintainer: Pallets Maintainer-email: contact@palletsprojects.com License: BSD-3-Clause Project-URL: Documentation, https://flask-sqlalchemy.palletsprojects.com/ Project-URL: Code, https://github.com/pallets/flask-sqlalchemy Project-URL: Issue tracker, https://github.com/pallets/flask-sqlalchemy/issues Description: Flask-SQLAlchemy ================ Flask-SQLAlchemy is an extension for `Flask`_ that adds support for `SQLAlchemy`_ to your application. It aims to simplify using SQLAlchemy with Flask by providing useful defaults and extra helpers that make it easier to accomplish common tasks. Installing ---------- Install and update using `pip`_: .. code-block:: text $ pip install -U Flask-SQLAlchemy A Simple Example ---------------- .. code-block:: python from flask import Flask from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///example.sqlite" db = SQLAlchemy(app) class User(db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String, unique=True, nullable=False) email = db.Column(db.String, unique=True, nullable=False) db.session.add(User(name="Flask", email="example@example.com")) db.session.commit() users = User.query.all() Links ----- - Documentation: https://flask-sqlalchemy.palletsprojects.com/ - Releases: https://pypi.org/project/Flask-SQLAlchemy/ - Code: https://github.com/pallets/flask-sqlalchemy - Issue tracker: https://github.com/pallets/flask-sqlalchemy/issues - Test status: https://travis-ci.org/pallets/flask-sqlalchemy - Test coverage: https://codecov.io/gh/pallets/flask-sqlalchemy .. _Flask: https://palletsprojects.com/p/flask/ .. _SQLAlchemy: https://www.sqlalchemy.org .. _pip: https://pip.pypa.io/en/stable/quickstart/ Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content Classifier: Topic :: Software Development :: Libraries :: Python Modules Requires-Python: >= 2.7, != 3.0.*, != 3.1.*, != 3.2.*, != 3.3.* ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616094138.0 Flask-SQLAlchemy-2.5.1/Flask_SQLAlchemy.egg-info/SOURCES.txt0000644000175000017500000000424100000000000022121 0ustar00daviddavidCHANGES.rst LICENSE.rst MANIFEST.in README.rst setup.cfg setup.py tox.ini Flask_SQLAlchemy.egg-info/PKG-INFO Flask_SQLAlchemy.egg-info/SOURCES.txt Flask_SQLAlchemy.egg-info/dependency_links.txt Flask_SQLAlchemy.egg-info/requires.txt Flask_SQLAlchemy.egg-info/top_level.txt artwork/flask-sqlalchemy.svg docs/Makefile docs/api.rst docs/binds.rst docs/changelog.rst docs/conf.py docs/config.rst docs/contexts.rst docs/customizing.rst docs/index.rst docs/license.rst docs/make.bat docs/models.rst docs/queries.rst docs/quickstart.rst docs/requirements.txt docs/signals.rst docs/_static/flask-sqlalchemy-logo.png docs/_static/flask-sqlalchemy-title.png examples/flaskr/.gitignore examples/flaskr/LICENSE.rst examples/flaskr/MANIFEST.in examples/flaskr/README.rst examples/flaskr/setup.cfg examples/flaskr/setup.py examples/flaskr/flaskr/__init__.py examples/flaskr/flaskr/auth/__init__.py examples/flaskr/flaskr/auth/models.py examples/flaskr/flaskr/auth/views.py examples/flaskr/flaskr/blog/__init__.py examples/flaskr/flaskr/blog/models.py examples/flaskr/flaskr/blog/views.py examples/flaskr/flaskr/static/style.css examples/flaskr/flaskr/templates/base.html examples/flaskr/flaskr/templates/auth/login.html examples/flaskr/flaskr/templates/auth/register.html examples/flaskr/flaskr/templates/blog/create.html examples/flaskr/flaskr/templates/blog/index.html examples/flaskr/flaskr/templates/blog/update.html examples/flaskr/tests/conftest.py examples/flaskr/tests/test_auth.py examples/flaskr/tests/test_blog.py examples/flaskr/tests/test_init.py examples/hello/hello.cfg examples/hello/hello.py examples/hello/templates/layout.html examples/hello/templates/new.html examples/hello/templates/show_all.html flask_sqlalchemy/__init__.py flask_sqlalchemy/_compat.py flask_sqlalchemy/model.py flask_sqlalchemy/utils.py tests/conftest.py tests/test_basic_app.py tests/test_binds.py tests/test_commit_on_teardown.py tests/test_config.py tests/test_meta_data.py tests/test_model_class.py tests/test_pagination.py tests/test_query_class.py tests/test_query_property.py tests/test_regressions.py tests/test_sessions.py tests/test_signals.py tests/test_sqlalchemy_includes.py tests/test_table_name.py tests/test_utils.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616094138.0 Flask-SQLAlchemy-2.5.1/Flask_SQLAlchemy.egg-info/dependency_links.txt0000644000175000017500000000000100000000000024302 0ustar00daviddavid ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616094138.0 Flask-SQLAlchemy-2.5.1/Flask_SQLAlchemy.egg-info/requires.txt0000644000175000017500000000003600000000000022633 0ustar00daviddavidFlask>=0.10 SQLAlchemy>=0.8.0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616094138.0 Flask-SQLAlchemy-2.5.1/Flask_SQLAlchemy.egg-info/top_level.txt0000644000175000017500000000002100000000000022757 0ustar00daviddavidflask_sqlalchemy ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1590591119.0 Flask-SQLAlchemy-2.5.1/LICENSE.rst0000644000175000017500000000270300000000000015276 0ustar00daviddavidCopyright 2010 Pallets Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. 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. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE 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 HOLDER 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 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/MANIFEST.in0000644000175000017500000000017700000000000015223 0ustar00daviddavidinclude CHANGES.rst include tox.ini graft artwork graft docs prune docs/_build graft examples graft tests global-exclude *.pyc ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1616094138.032466 Flask-SQLAlchemy-2.5.1/PKG-INFO0000644000175000017500000000671100000000000014562 0ustar00daviddavidMetadata-Version: 1.2 Name: Flask-SQLAlchemy Version: 2.5.1 Summary: Adds SQLAlchemy support to your Flask application. Home-page: https://github.com/pallets/flask-sqlalchemy Author: Armin Ronacher Author-email: armin.ronacher@active-4.com Maintainer: Pallets Maintainer-email: contact@palletsprojects.com License: BSD-3-Clause Project-URL: Documentation, https://flask-sqlalchemy.palletsprojects.com/ Project-URL: Code, https://github.com/pallets/flask-sqlalchemy Project-URL: Issue tracker, https://github.com/pallets/flask-sqlalchemy/issues Description: Flask-SQLAlchemy ================ Flask-SQLAlchemy is an extension for `Flask`_ that adds support for `SQLAlchemy`_ to your application. It aims to simplify using SQLAlchemy with Flask by providing useful defaults and extra helpers that make it easier to accomplish common tasks. Installing ---------- Install and update using `pip`_: .. code-block:: text $ pip install -U Flask-SQLAlchemy A Simple Example ---------------- .. code-block:: python from flask import Flask from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///example.sqlite" db = SQLAlchemy(app) class User(db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String, unique=True, nullable=False) email = db.Column(db.String, unique=True, nullable=False) db.session.add(User(name="Flask", email="example@example.com")) db.session.commit() users = User.query.all() Links ----- - Documentation: https://flask-sqlalchemy.palletsprojects.com/ - Releases: https://pypi.org/project/Flask-SQLAlchemy/ - Code: https://github.com/pallets/flask-sqlalchemy - Issue tracker: https://github.com/pallets/flask-sqlalchemy/issues - Test status: https://travis-ci.org/pallets/flask-sqlalchemy - Test coverage: https://codecov.io/gh/pallets/flask-sqlalchemy .. _Flask: https://palletsprojects.com/p/flask/ .. _SQLAlchemy: https://www.sqlalchemy.org .. _pip: https://pip.pypa.io/en/stable/quickstart/ Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content Classifier: Topic :: Software Development :: Libraries :: Python Modules Requires-Python: >= 2.7, != 3.0.*, != 3.1.*, != 3.2.*, != 3.3.* ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/README.rst0000644000175000017500000000277000000000000015155 0ustar00daviddavidFlask-SQLAlchemy ================ Flask-SQLAlchemy is an extension for `Flask`_ that adds support for `SQLAlchemy`_ to your application. It aims to simplify using SQLAlchemy with Flask by providing useful defaults and extra helpers that make it easier to accomplish common tasks. Installing ---------- Install and update using `pip`_: .. code-block:: text $ pip install -U Flask-SQLAlchemy A Simple Example ---------------- .. code-block:: python from flask import Flask from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///example.sqlite" db = SQLAlchemy(app) class User(db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String, unique=True, nullable=False) email = db.Column(db.String, unique=True, nullable=False) db.session.add(User(name="Flask", email="example@example.com")) db.session.commit() users = User.query.all() Links ----- - Documentation: https://flask-sqlalchemy.palletsprojects.com/ - Releases: https://pypi.org/project/Flask-SQLAlchemy/ - Code: https://github.com/pallets/flask-sqlalchemy - Issue tracker: https://github.com/pallets/flask-sqlalchemy/issues - Test status: https://travis-ci.org/pallets/flask-sqlalchemy - Test coverage: https://codecov.io/gh/pallets/flask-sqlalchemy .. _Flask: https://palletsprojects.com/p/flask/ .. _SQLAlchemy: https://www.sqlalchemy.org .. _pip: https://pip.pypa.io/en/stable/quickstart/ ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1616094138.0291326 Flask-SQLAlchemy-2.5.1/artwork/0000755000175000017500000000000000000000000015151 5ustar00daviddavid././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1567863345.0 Flask-SQLAlchemy-2.5.1/artwork/flask-sqlalchemy.svg0000755000175000017500000037501000000000000021143 0ustar00daviddavid image/svg+xml Flask SQLAlchemy ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1616094138.0291326 Flask-SQLAlchemy-2.5.1/docs/0000755000175000017500000000000000000000000014410 5ustar00daviddavid././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1590591119.0 Flask-SQLAlchemy-2.5.1/docs/Makefile0000644000175000017500000000110500000000000016045 0ustar00daviddavid# Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1616094138.0291326 Flask-SQLAlchemy-2.5.1/docs/_static/0000755000175000017500000000000000000000000016036 5ustar00daviddavid././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1567863345.0 Flask-SQLAlchemy-2.5.1/docs/_static/flask-sqlalchemy-logo.png0000644000175000017500000004001500000000000022742 0ustar00daviddavidPNG  IHDR_sBIT|d pHYs&x&xz_\tEXtSoftwarewww.inkscape.org< IDATxw^EǿlBB:II@@z/J ( UD"REA@P"J$HH{?~gy>lv>}iwgN`fTB-MNfnTe jap`'W=U x8nDB 9^ֱBalO-}SE@ ! nD[}LOl~ C-3eIe5p igfTijfq[R:B.O`f6 lBϸ-})S G@: 8nwnS3SA#np x\hf3!}0k~;=3H 0۷ܘ pӎIpx+06 l|?ǁG>>eKIXZ`[`:hm-7&"lpB88!~fvK%D~RwԖڒt'!-Kz3?^o" !0N. nK_Ԏ$+̦=;d[ FGb3!{[?jԶT,/{!5¤ł¾ȁ[!s6B1-5: `U3BxؼPo0a> of#BC*jҗ #aTB;SH*ҳ lk3`f0^6mKMJ,DM-;O_4aS1G@=hfK:ZB_>f69-- 3)o# mf !e, ߖ,Iy+3o\u۫  !| "BNk֧hK_?C_C(6*MZ,a~(/Y03h2mOV+і2hρ/ .DTEu~ ]k[nR8g&M/ w)ǔ6C FLV,?닁0 XrShˏij^@Z4ͻ&[1`#ڀ-76 x2į 9] Oʑ}=\l^E;"yC:\܍L:>lTN2?8+ kB130"I.T+ s=7{ H]bH\Y!M[hTgjȾmf~ |F/ 33xѵys̬fRy@2-fr\?=f*ܥN9rB8a͐unf1!`VoDJ켦?V|6Lej> i>-yOm1քjwA$C? R7 x OH<i!qQ#VBF֫l5C!WIdfWCdfq7BԻC@Y_>@~`"7"0~n -bBѦ?t 2 %Cr琳s+f6E'BAX`3{+p&#'u>aVcMX4n:'C2=]ksZܻLYdk-8"?X]#:E,u)T]!&6h~Va;͵.aᝁ!eޓ]F_9ƨ16rE_3wCW!L /d=<(>'I .Ć=_F~@ޮgޝ.z!솅X/E !\E@\$O!>X P IkخCL`w?0®~<D}beBV^7T#u<|s_fv B=-18}=DK̠vng"'˘?ƚYδ-n$2/we2D\ hM,4+6@׋D0H*pיlfxC| 8Xއ,zY(Ffr+v o0f4cy9۲ B>"uG@媹sZ0MDvltgBbs+ <>I~} p#>0^HJ0TY)"]&3=}HT}81ER%1]m,I`'"9.qտC/nG%'X't3Qs|o}l0c(?,2C:]Z5_>'Hg wDcM&Gc14])%#.3xx%4G:FW]r/ ++/_U !3xݡEuU^ݼ^=shl\A,s1 tmmn }vՔ<WGTU̳#2#gJmZ QCtB莴R! mu3{*"B4O=O5axC5)ibb"wK.^ WDB9]s |$YX Iibہg̬&$!q j}n3%ї~_a^g#ϬX`嬏-כ܃>WS-ѿ"2?a p~mWG FmƤvHQv1 EyR yEH`͒:h(<\؏ =Rzo"VTGP*o(ؓ: 1P"b~aOd1{",|GMG Sވgkygd}_ ^g#ah{77^S]asg}C˺V&s_ x7ֳV9=S \?4>n!ձs?HqWx(xݯ;@쇶Dzz Y_EZX?q;-;1 D#Re'=z65;vS-ZkoDI_jFYo("Cr΀>N׳ԍRVE[( A]{0ӜL,]c\BZA \xkHOSJVG9DF||a1;HZ|ӲINB)Wx7q#D?@n8a䕲<.S[PWDz#Ht5җgkR nP ȑF "% !Q hs$keK.=@'{Y;=^E]Yp?TuiC BL"h^Ky+InݱO0sa>{E9)ifmC$5{1f(+a=scRqra?эiZD:jM.vE"LVQ* HjO$m%9zedS%Im|ھ'pG u_DA(56bnz[7(L|zBZ Crց8% Je|!h6#g LzKyHwȗ")Ȫ~2B?aʧHܸ!&ZxHfxnhϫb~{+F--G^@Y΅j|jѶՋ$d'#fEE/v큌Db9#~1lgA[~kIav?:C1vsWϴWx5{ ~8sV+_@ѳ iz"KBa(|SkZ Db2 @# 8"LY|ޏNwNcl*~/cEs`3F~*v+ƒn@6s~ .XGړב~}}#vCѬﻑzp'du9aCMŚ$@Ң aʇ|Q2 ZH?R}зTd?X1lj9N6Z fvWm:"]I$@3 TnYtW au18)DdOq,B*8+u "b N4}=_Bv,Lg>Im{UiBmn/z-J5m'mTʹ!UVOR*iwEϩްe6-C$ڦY^PmRwê Z>oGt%!h%HmfO-D=p䞽*TJ|H`5nː|k_6C\zFķpEw#ZPEb<B]zh1WE*Xib^Hkɠ Q5艻.Z~h 3E4e}DJ:!XKۓoԒ6Z$?^iq{"m֚V^]LSG$久yGxV~`;RuϮ鯷Cy_EMr #jvkcOY3xqнE]QBYX/@bLgSh !tB)K[ *^dR; cLEG G#mX&Z3E="hCӔ2vG.C[I被;MrpH1$ЯtB'HK"BGg_N!(5&6&MCjO~HŒ㭅8 _GNx Fo!'[ Ђ>ෑ A3˟D<׽xri}x;xP>e13q4*n|wBq6 CҟCBk!&diumKcdzCC}QDº"iτ"9/Io=/ɥ/[#5Swst?py`{ !lBV%a, s;/Q_4OFEۉ-7[ї 2dBJgmW~H؞&= u8y) $/ETCjA+^~nEf{ı<3[LCf ! 4攏 5LRLEJ Pټַ♩E_ fvBmqqAc'L*iF ThSSSӄIL*Hl7Th:afFzXݑnr3I\&wɆLE&iI{Ma):xRoEIjp7싃C{#EBTM' G{'z"zC>xϦ܉Ӫ0͒HדH*y-qQGq/R0(7-zTi"8@=Oc+MD7̦\SԗI_G1V?SRDI> pro n3I9~P H/dMEN꧑ϴܨ5KJF8rvnw whvRh30ɽEyԓoxY2_Ҿe;`u@,CJ㜛a^ދғc~ ? ϴ;)sJ X-ޢLs9{/qMWڴG}_߁M kxaoe=ͭ9#F4Ly䷿9+lF&ٔj !le2=Lmu{ICCCb /9zёi;dzw݁ y l^#/J@i5UDe@|L'Ž(LH!4TL#o8_RV@Lc-'E{Hh_ JCR:Ϗ)ԵW^[HOX=I~ œQrp\koξs$'^rg3 m-"ut&[ <跽_1<AHg]\c|5RȡS" WB<߆#eKJ1)"v@l )ks(cQҨH ÆYP5\OiqHo!,g3pwhA5:ٻyxы :qIKCّDnQ̩wܮ`?jPKV`h+RU db)$D6U[k{gH2*羇ygdv7`Gb <69YP6ok,٤į̀ FޓB߷C+9%I$AwC)>|,LRDm(vA~?a& ~2f . bZ2zlS(s;$!UirM|~ezK'XB[S{X> '"H9d;^"p@? )Oy|Za&u/?د"qF:rmV"<({ZŃP,|ۓюE4Z/~_uOF Ow6|sbi]B;d( mC%*âOŔd#B&O:AړHhGOR***դ_>v@qFi)#O.l)m#h}Dk3J(BF}ゥKXo^^j-@dmj2@C7 Ưxľшw&s d$!CtH$"/VDSHG"Pڝ @VhwNjo}FxB2EY˾vȗ mֳAVTpGvZ=迣Jz|:c- #+ $'5XPzh)^ҁPIj)-ڜv7pҹsPfx%̛y &~B \du!²3tI~ [402shu _Mw^ˎh#lBtU~m~7 ?e$6$b0onzVDp r Ja4BQfP4n}DDj\Bh㻓o{d"пI]x y1YM%y^6HըMl*+V뇲1F$@G##Zf_!ufv ?żolqGGpjݺ2Dy_h%9#cki6@$ ⼸̯B4,JձO894CzJ OI63)eӛaC\v~Jώ ߈>7vI^$)Kw JgmڑL)/X5?>O^m 7'q1oLrqC:6Ƴ~JgGUFY1]ߝ=Cik-[RzU=9ugn'sSmQhw$ám0RCͳ9n_6f}D&qyeH*Ј)"Mk`~/G4$J[1}$_dQD=k6$,o"y eQ.!ZNɾS]B#Dyx7{;x$",{䫹:v,bnT;DP:l<~ a}x]Xa@t-$}M wB6FA; QYx7/>u7BR,)UFko(AqJ\?1p(弣6ĜZ"+[(%Dײ3ڍ z\A #Q,\JSKu!Ŕ=QYxm>m;o=m$u~XR r/ߘޜS+ qpe//{ޯ7.?_Vb27!<_3X] _ݐ}WA7 AC $W0yShnѷL>׹@ hz¶;EM Qkq/8e7B%ObȄEKGY~~BeIrY*^~_68#696;։2هxh?P,\ha]̯,I&ü:'ʒXj2>њhbVz9e!ԁR3F} )+;ׇ-e%ʸ_SzN@d֡dDS)Ș\;dtҨ$#u[(΂l 1 #dY^g{.4 h ’?c/R'E1MI,tHF*"| yy7 0\w.lccw;J /$/E[rg=#_e!}EHKD? "dᡵE.jvkISX$H:D s_? }mm&aؗRlxm) 9 #A !f]\o;eՑ qV]Db$1^kuR^~ʁymG>9=md Tke܅G[ hm 3Nn1-Y<2EF6b-sr JH/YW|1@c|,Ɇ ,n5H$t*iun#Dgs6ݼ,x|3 УpK$!Aw?%x̸Q%PzlHƭ{?d4GRyp޲:$.I=!ߋXw&B]K2F 2o5EǼe=?C_LRxM<.* U.ڙ1z{~_^Odt%HIHp5yqk x,> |*Z}4üU?g-3Ppf¸7vk`{6$f[Ο姤H({"m< Y$T?p~4 G}"k%D1l'xycG_lKUO>1Yso/3NóIuo"c40")K }G9[[ D~_8DO&a.JNȗ#EyZ3&"ؕj$ (=+55_6sںH|Ÿ]/0h]r&M%i~XsG7_V@mOizj:"fcZ["~g Tpk|8sO~}WVHދ3v3_QgGOЏ2>Op;z}3`ٽ[ UUHf<Ձ /?WUNBwT#{OCrǝ;LB!YK!lHP{Drd*ޏC~nE1c{}Z1mG $p Y&93.e~'(DCXK kukvt?˷!aD$&YKo8mS:ƨ/FPٙRi R> *{ٽө Q6盳+ƉѻkIaV&1vV7`Fb)p~{Jv7,.FҚٯ$K%("b֨m_#d$ח!L/L(wܵ/)?$(](Q6Tb&'YaÆȍϷ=V&.TGJ E a<Ʃ|G-Hp90d (7aw126iHV?΅u8!4gNXq/f1~LS+3pD* $D0 ZX=={xXŚٗ= 9᧤;0$דrڞR͐Bx$ڎG6G )VhlDS5idAd\+Jٝݏf-˜ ˞!p2v> p 1 u-eB#ˤ!½T.j6B= FJ%ExduSإSlJūc}G⒎*3^NBsz-犙z%2C%( "=(|!>̞ ! DBz8]\FL-](FOVG~ ܈DϓȻ`l ч7Y+A!f2 !>LQiF.t2uʶB yh&Gg!=_E(TS"vc\lVaΡB_D8Pe<;+*ckZ3AC呢W' |R'!hy6ng:uG/lj{ U$3=Yxê|{юtMu$:!7On"rd@C_.F"ukVa{l|bvC:Uz5 sEfU11"Z^7Y^HᰫY7!un`S/)XCi?^ۅ omQC+1e^,sN{5D;r {sZ z9.+ciTHRa.pN~$+7>ov%Y>u~Ap_4f"oSWT;=рcR`j3;B;;<6Ҥ!Hyz+!ߨ삼 p2sDcc݇Ի Us>r1-7  0#&%f&λ#@`fb")(׈ tqw$j@L:/- B#\R~lf=vAX?ڞc@n3슞;#vͨ0DsHlBG;DGl( 8_ʮj fHUC#t®OY&}*6GmGDqYwA $8A_ D$ҿG@ۋGWKijsYFMYE$B(愈. ٘TaԘ|K.hYsL30 劜ۈ ^T0S;d{*z mӐ^˜1` j; <gx?D.YGf6K֟:HMH$ > !hNM":xAzYEtHqs?OSR0$X/ϑxFrFMC4rV1jJZj5(ޔr#1ҁ*AM"m,pe*YFʭAD`b̆R1xDVDLAF%apZ;GC)9f!Ef6'Y&}i5&ֻ!9gcHNY]kfiMJ!H*$\ . ?R|M_:@SatRaE}ib>GHU9O/fk@B膤 =r/@Ki 5^/ٴ&bK JVuμqzܴdǯ$MZȾ[<j)2(xLKxujԅLn*ڂ{{^cCp*ry>Ic5,s3 qce_hЖ_x IENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1567863345.0 Flask-SQLAlchemy-2.5.1/docs/_static/flask-sqlalchemy-title.png0000644000175000017500000002554500000000000023136 0ustar00daviddavidPNG  IHDRb7bKGD pHYsa(atIME'09} IDATxw?"("*6T 5,h؈hBlI4`I,(v T%"+ ةJ[{03gι.\ywꞙwWUp88Ép8'.p8p8p8N\pr8Ép8'.p8q9p8p8pr8Ép8N\p8q9p8'.p8pr8p8N\p%*CD!"Rۻ" ЎuD9ʿײ E>ȵ}k:j@ "w$c`jl"40 B+/꬀`V@TvCLѻc%׻|{nY,`$;R5DPX,ٿC6ͭo-j@3`.*.ݳM&&-_aF xt}zk_4|U}GD.UE˱-;WUu4{jqH+? P`џدV(yG&b5"0r ?%6 4J;NlT][Q7lQY˩-k5DU8F\&6ADkHĶ,8o~ {y[IJD[[71m3:Ƌ0ܷSkxzfhbVgvndxTU  \#bw.\u8@k(k qz S#5٭iۯH*n2PA"2ؕ`vbIMz:ZD:$[Y'lN6m\%"!:1rÁ'/`{kM#q=p~zĄ6~~ֱ{5;No!pp~dB+"2@UV>GꂄCj//\ -fGM".UU`HXl;d6G^,6/垓EdE`5śz">y^N+k'S *#3v/  ܴEG`@F #:3'e_G`O`J5|T#kEd0p"0Yx" ĵ<קdj5vG ָ "Aq-99LGU֣:ZV!'"TRi ZQƮW B8o+pFsc'i&^ëvӨ.)XL:ȶpV$SaV{VMN\ΚqoT{?=JժUȠpa)%"35U}ĶR=bY{/"mi"8 y3b.!er E1S*B;^z3Jk 'ekB k8Nף'Uu7TsL7"R &Lek\"`nfrq%"[U"dcckc9Lw`*Z["r &#B?^uTDnEd*G36v "R[D40tEd.'{5WF;oVooUUF@^%x]rg_Hp![Fu=oPi! UU2G:$.QD5TճT8Uݖ'7*HWpn^m,]?B I7.,T8UU U6&diH@UGD-U1WqȁsU}Ǟ9WD65sh]L~".TH5a!&USy4.9`J=EU!:DI:ih붥|] ާwǿ۾f%\ p?_ 9'k$U}6m0Yw$ܣ :\$p,V[L} UDG\OFdKDGSs?R愒( .vع[NJRaYPhIbgXB9iG ׌^KKwblk/ y"o=^%p?,&l8IyOڿmP4yɣ#nQI

hK cf{p;EHV#ݕI\ gR{qwC>f*m!> M%LK4W3r4='WNs~x AH[DI0E a`׹9e@\)">'4HkgN4/Jn V IFla";*뼵oٶS %ui%˶FGpSlҿfymL\+7+׀Fn]r`*!DعuwlۅBmnmkKgiOL;W)*@\<`BN7{aOOx?9gI{ybC!mU&,LNhb{IԸKr>MP-2Hu7- X9wp3Yq݀KR \U6M\ Em 8 mK#ȱ wnL9##DmgEu2ѡ6M(2h6t#&b!~2~loٶ P/ #흷MZEoHwߤ<>,a&E>U8 0-ȶ[گxXۼ'GhK3[{B#TBvBF[D8xHU^63UȮWiMU'"ly4U=PopRap+͡;^̸p,@3*klEI}vY*a[c/If eʱƮq;$Ql!q;@ϲ/JӬ8~Gژd8´A6$IS3=rN k+cFF%ʔ$dV FEi seX jP>>1`BNIF mg&4{}cyiZxLS]Ѥf&Zx,p^vpvڻ+boc{2sz4amidvyc}5&ݏK˲4Kp/[+}ٞp~rx<6z=;I±GܛT$oZ$[+!" !B]JH7U=?u6V(릴!BMOүŕ4Ÿq-PG^_LGYN"NDEd's3H$,ӗu6Y=6gx?$uIqNX>LЀ9ַW:'sLRqD.s]?MUX_;XDƙԑzU=? |N(z++Hb ={D(%cʜXSoBKb+ͫR\Pi:$G Y:F\\gL!܇eMa>lBxBffx3: #$c0UD:?`X܃왊!-ilJ&̚ KOӈ6S[wB׶`4*clJ\`) ȥepJ|d1j~}ʾ)x$agfƽIU0sAFXf_6OdoKxWsl=/-3!62 sc)nEӖlcτgljm@8T[w"{ж&E,"Ǘ,pO~]lm3=]$"UC.5a5)h2rKDa<HJ5tpD"PU@B>bH˽hyz%~\wIהq֩NJPbI̲UE`ۦ:3ܯl|%!p׋FqL,߽]#Y 8停eTޥWCz⁺Iĥ$"#cH~>כ!zr5z+^0Q52)(x7"x4D\pBL6nlj6\Mi\T6vw/"{%equ3^P/)ROQN5:?6$[}e_Lj5"ה-WI\LNK65 DZ[v$A\dh\qNJL!?4!$**ў3flܜ}٥ff|>eU ZBǡf9X¨"c^WDz &T9 h{nFS$)h2'AjX!-6lE䄄HIS48`7zMjic! bp.SV\cdvkC/fU=N\BZG&8O [it f4y eVAN7>.lY qYxh%H/ w%-"[6AW%v \F' %8yⲏDUBp7b,0_gfNƅNae8w߇fF:I~CJkt_;KptYVZյP3^^5&^6;b NL }bqṲM-4 F۪: ϒE\mB86$ḍqA>?cfjBٶ6a21-g1)2%؆<vQL⼑Z0ѿRLb VMpxм7XJj]se ڻLM _jF%kc%x3, ᴂj2m;I!M qeL 8g39_Z%sղR<0<VZr}}52LE1B* SMrW"T&2FVaئp&q%歌G |mߠ!~TK FZGsBzTKU=̐%t$~ Ql <$"^/wߢkmgcdI,2)"gXI<&LLi I+hHǘvRܒa8&#*cgv8 *0!.͸oseh1D`3& e:}fh?f.M]~ E{M?m܎ְm_ѾXeM aiO)aWQKDvE4 }I|LZL3L(LW}jŪi?#EA9&<:_o$t|eH;=Bnʾmh7" U}wS{qo5WvRg qlig}i暐iMU0R,ƍ˦Qq:Zô_$n"Ԩ޶DXEo4tA0;}!([?CKHD'=vUҰFq3bZ"LX .Q\LxMZFc(Kլ[x'lp&15iK*$8,TbpQtVUǫ6o "rVD,FTflg[LX8ynV D_ٚ][k_)Hb Nzu^X $Re=9+S2rwTyGgNB`""rkk~2^'f!U/e _EV/7t"Nj"D9%>Q(2ӈ=3q%.C)?흼OpSD!cJ1J7`١eqjq/"-LmSm3>|F[dio4aKYc%J[JyUB#ބxm"$ci\#]U2CleUu7U\7piIRa:w}X%9'Vam XFi,c22V15 Q$~fڑ Ii/"Ϸ'6?$v#G͌ ~FNhGdj&;}pBd2#UԎe;X2K9!9UƖgSM qn B,.M8@"9zˤ̷Lٿ-6eɩW$/K~Z IDATffnC,edzTQ _$O)zRFHX:ovNXӗR/g"R^%tଜՉ⥱j+e YUI7O\!T`] g 6 $PÔf۹"fe<_;Kq`em{_'I,1e,|fX'!A2D)%26e'qPɵM#q^3 _eE @F^7!0q'D&!prW"}_k+k6EWYjf7mL0<eqbu$K|iY@YBqE*MI?ؽ9.zJmpzp5dv!a$^ }9jmYm-I{ }y9Vdtc#şK W]iA soٓΨH~ C`5/Bj%hLI#?y=2Z9!XF STw5I ˼_k[{c[f^"ɹ  ǶH:%g6͹^^"RCHݭI ?o>8m%\c8k124l6n.qMl Ltm ޜ=c]#U&&P*cli_aĿZjӴر{&i_OZ9?wxr6OfB_!dU}X^3b#}Xeނ5v$$w]ڿpI7[=fzC~/v܍{I #`!,BfKRFcvֵg,럭ac׹:zm{g"geU=!xxO3],0li3llbFm}j;,r3kЪ5L;z5[H.WwE? $o`s[+:\abgigYS[s΄}$kg~fMrPH0339L {BZɟLC<1eNUf`۽1aJZ#r0-OgKFRcYZl N]Vߦ=ڑ?oj5lgFu)/1{o#I7(*L32nw$K )lB"r!g ҞoBT}45Xb=v3ܮ5"1lkd|A^<{oI#i~Fz|سQd>S6»]y\?1-]̞V_P8ꘐ._1Iii.q0p$`k0>2v-3ñ- QEj7%fu˱ ꢌ]DVTVgu nZñ*ɽSm޶!)j_CšߏiI'.4@ ,J͵SYXHN[4$ѮɘSwt8V1ٕmy~c~O7!~#oDnn6#&XHSB,1=҆G7!H^x3۞wW^'uv#dY|7IQdoB̮Z}U(AU ȡa'.2H. g5$Ƅx^@Ҳ[ىnvwdOp6d-*0/!dUc YBce(/l+eɲF뻝1?dO2KS[;a1z^."/Q%Xn`ꮺ DDN!VXYi,^7B\-K>oA%+TY+y_Qa!VzYޫ!%X3cUx~N!<4ؚFJ(MpԴ訪dӀ*6ikӀaV^= ܐu?Bm:hX/s8V13P⛈0;!]W'#!_ٿ4bb!M,BHμ~p,=] [_"*! q?W3-:VqxFUw 5J\N;%2vz~N\ǯOB-:wFd?٣!~>> db.create_all() >>> db.create_all(bind=['users']) >>> db.create_all(bind='appmeta') >>> db.drop_all(bind=None) Referring to Binds ------------------ If you declare a model you can specify the bind to use with the :attr:`~Model.__bind_key__` attribute:: class User(db.Model): __bind_key__ = 'users' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(80), unique=True) Internally the bind key is stored in the table's `info` dictionary as ``'bind_key'``. This is important to know because when you want to create a table object directly you will have to put it in there:: user_favorites = db.Table('user_favorites', db.Column('user_id', db.Integer, db.ForeignKey('user.id')), db.Column('message_id', db.Integer, db.ForeignKey('message.id')), info={'bind_key': 'users'} ) If you specified the `__bind_key__` on your models you can use them exactly the way you are used to. The model connects to the specified database connection itself. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/docs/changelog.rst0000644000175000017500000000005500000000000017071 0ustar00daviddavidChanges ======= .. include:: ../CHANGES.rst ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/docs/conf.py0000644000175000017500000000375200000000000015716 0ustar00daviddavidfrom pallets_sphinx_themes import get_version from pallets_sphinx_themes import ProjectLink # Project -------------------------------------------------------------- project = "Flask-SQLAlchemy" copyright = "2010 Pallets" author = "Pallets" release, version = get_version("Flask-SQLAlchemy", version_length=1) # General -------------------------------------------------------------- master_doc = "index" extensions = [ "sphinx.ext.autodoc", "sphinx.ext.intersphinx", "pallets_sphinx_themes", "sphinx_issues", ] intersphinx_mapping = { "python": ("https://docs.python.org/3/", None), "flask": ("http://flask.pocoo.org/docs/", None), "sqlalchemy": ("https://docs.sqlalchemy.org/en/latest/", None), } issues_github_path = "pallets/flask-sqlalchemy" # HTML ----------------------------------------------------------------- html_theme = "flask" html_context = { "project_links": [ ProjectLink("Donate to Pallets", "https://palletsprojects.com/donate"), ProjectLink("Website", "https://palletsprojects.com/"), ProjectLink("PyPI releases", "https://pypi.org/project/Flask-SQLAlchemy/"), ProjectLink("Source Code", "https://github.com/pallets/flask-sqlalchemy/"), ProjectLink( "Issue Tracker", "https://github.com/pallets/flask-sqlalchemy/issues/" ), ] } html_sidebars = { "index": ["project.html", "localtoc.html", "searchbox.html"], "**": ["localtoc.html", "relations.html", "searchbox.html"], } singlehtml_sidebars = {"index": ["project.html", "localtoc.html"]} html_static_path = ["_static"] html_favicon = "_static/flask-sqlalchemy-logo.png" html_logo = "_static/flask-sqlalchemy-logo.png" html_title = "Flask-SQLAlchemy Documentation ({})".format(version) html_show_sourcelink = False # LaTeX ---------------------------------------------------------------- latex_documents = [ ( master_doc, "Flask-SQLAlchemy-{}.tex".format(version), html_title, author, "manual", ) ] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/docs/config.rst0000644000175000017500000002104500000000000016411 0ustar00daviddavid.. currentmodule:: flask_sqlalchemy Configuration ============= The following configuration values exist for Flask-SQLAlchemy. Flask-SQLAlchemy loads these values from your main Flask config which can be populated in various ways. Note that some of those cannot be modified after the engine was created so make sure to configure as early as possible and to not modify them at runtime. Configuration Keys ------------------ A list of configuration keys currently understood by the extension: .. tabularcolumns:: |p{6.5cm}|p{8.5cm}| ================================== ========================================= ``SQLALCHEMY_DATABASE_URI`` The database URI that should be used for the connection. Examples: - ``sqlite:////tmp/test.db`` - ``mysql://username:password@server/db`` ``SQLALCHEMY_BINDS`` A dictionary that maps bind keys to SQLAlchemy connection URIs. For more information about binds see :ref:`binds`. ``SQLALCHEMY_ECHO`` If set to `True` SQLAlchemy will log all the statements issued to stderr which can be useful for debugging. ``SQLALCHEMY_RECORD_QUERIES`` Can be used to explicitly disable or enable query recording. Query recording automatically happens in debug or testing mode. See :func:`get_debug_queries` for more information. ``SQLALCHEMY_NATIVE_UNICODE`` Can be used to explicitly disable native unicode support. This is required for some database adapters (like PostgreSQL on some Ubuntu versions) when used with improper database defaults that specify encoding-less databases. **Deprecated** as of v2.4 and will be removed in v3.0. ``SQLALCHEMY_POOL_SIZE`` The size of the database pool. Defaults to the engine's default (usually 5). **Deprecated** as of v2.4 and will be removed in v3.0. ``SQLALCHEMY_POOL_TIMEOUT`` Specifies the connection timeout in seconds for the pool. **Deprecated** as of v2.4 and will be removed in v3.0. ``SQLALCHEMY_POOL_RECYCLE`` Number of seconds after which a connection is automatically recycled. This is required for MySQL, which removes 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`. **Deprecated** as of v2.4 and will be removed in v3.0. ``SQLALCHEMY_MAX_OVERFLOW`` Controls the number of connections that can be created after the pool reached its maximum size. When those additional connections are returned to the pool, they are disconnected and discarded. **Deprecated** as of v2.4 and will be removed in v3.0. ``SQLALCHEMY_TRACK_MODIFICATIONS`` If set to ``True``, Flask-SQLAlchemy will track modifications of objects and emit signals. The default is ``None``, which enables tracking but issues a warning that it will be disabled by default in the future. This requires extra memory and should be disabled if not needed. ``SQLALCHEMY_ENGINE_OPTIONS`` A dictionary of keyword args to send to :func:`~sqlalchemy.create_engine`. See also ``engine_options`` to :class:`SQLAlchemy`. ================================== ========================================= .. versionadded:: 0.8 The ``SQLALCHEMY_NATIVE_UNICODE``, ``SQLALCHEMY_POOL_SIZE``, ``SQLALCHEMY_POOL_TIMEOUT`` and ``SQLALCHEMY_POOL_RECYCLE`` configuration keys were added. .. versionadded:: 0.12 The ``SQLALCHEMY_BINDS`` configuration key was added. .. versionadded:: 0.17 The ``SQLALCHEMY_MAX_OVERFLOW`` configuration key was added. .. versionadded:: 2.0 The ``SQLALCHEMY_TRACK_MODIFICATIONS`` configuration key was added. .. versionchanged:: 2.1 ``SQLALCHEMY_TRACK_MODIFICATIONS`` will warn if unset. .. versionchanged:: 2.4 * ``SQLALCHEMY_ENGINE_OPTIONS`` configuration key was added. * Deprecated keys * ``SQLALCHEMY_NATIVE_UNICODE`` * ``SQLALCHEMY_POOL_SIZE`` * ``SQLALCHEMY_POOL_TIMEOUT`` * ``SQLALCHEMY_POOL_RECYCLE`` * ``SQLALCHEMY_MAX_OVERFLOW`` .. versionchanged:: 2.4.3 Deprecated ``SQLALCHEMY_COMMIT_ON_TEARDOWN``. Connection URI Format --------------------- For a complete list of connection URIs head over to the SQLAlchemy documentation under (`Supported Databases `_). This here shows some common connection strings. SQLAlchemy indicates the source of an Engine as a URI combined with optional keyword arguments to specify options for the Engine. The form of the URI is:: dialect+driver://username:password@host:port/database Many of the parts in the string are optional. If no driver is specified the default one is selected (make sure to *not* include the ``+`` in that case). Postgres:: postgresql://scott:tiger@localhost/mydatabase MySQL:: mysql://scott:tiger@localhost/mydatabase Oracle:: oracle://scott:tiger@127.0.0.1:1521/sidname SQLite (note that platform path conventions apply):: #Unix/Mac (note the four leading slashes) sqlite:////absolute/path/to/foo.db #Windows (note 3 leading forward slashes and backslash escapes) sqlite:///C:\\absolute\\path\\to\\foo.db #Windows (alternative using raw string) r'sqlite:///C:\absolute\path\to\foo.db' Using custom MetaData and naming conventions -------------------------------------------- You can optionally construct the :class:`SQLAlchemy` object with a custom :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:: from sqlalchemy import MetaData from flask import Flask from flask_sqlalchemy import SQLAlchemy 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(app, metadata=metadata) 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. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/docs/contexts.rst0000644000175000017500000000421700000000000017015 0ustar00daviddavid.. _contexts: .. currentmodule:: flask_sqlalchemy Introduction into Contexts ========================== If you are planning on using only one application you can largely skip this chapter. Just pass your application to the :class:`SQLAlchemy` constructor and you're usually set. However if you want to use more than one application or create the application dynamically in a function you want to read on. If you define your application in a function, but the :class:`SQLAlchemy` object globally, how does the latter learn about the former? The answer is the :meth:`~SQLAlchemy.init_app` function:: from flask import Flask from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy() def create_app(): app = Flask(__name__) db.init_app(app) return app What it does is prepare the application to work with :class:`SQLAlchemy`. However that does not now bind the :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? You will have to setup an application context. If you are working inside a Flask view function or a CLI command, that automatically happens. However, if you are working inside the interactive shell, you will have to do that yourself (see `Creating an Application Context `_). If you try to perform database operations outside an application context, you will see the following error: No application found. Either work inside a view function or push an application context. In a nutshell, do something like this: >>> from yourapp import create_app >>> app = create_app() >>> app.app_context().push() Alternatively, use the with-statement to take care of setup and teardown:: def my_function(): with app.app_context(): user = db.User(...) db.session.add(user) db.session.commit() Some functions inside Flask-SQLAlchemy also accept optionally the application to operate on: >>> from yourapp import db, create_app >>> db.create_all(app=create_app()) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/docs/customizing.rst0000644000175000017500000001420600000000000017520 0ustar00daviddavid.. _customizing: .. currentmodule:: flask_sqlalchemy Customizing =========== Flask-SQLAlchemy defines sensible defaults. However, sometimes customization is needed. There are various ways to customize how the models are defined and interacted with. These customizations are applied at the creation of the :class:`SQLAlchemy` object and extend to all models derived from its ``Model`` class. Model Class ----------- SQLAlchemy models all inherit from a declarative base class. This is exposed as ``db.Model`` in Flask-SQLAlchemy, which all models extend. This can be customized by subclassing the default and passing the custom class to ``model_class``. The following example gives every model an integer primary key, or a foreign key for joined-table inheritance. .. note:: Integer primary keys for everything is not necessarily the best database design (that's up to your project's requirements), this is only an example. :: from flask_sqlalchemy import Model, SQLAlchemy import sqlalchemy as sa from sqlalchemy.ext.declarative import declared_attr, has_inherited_table class IdModel(Model): @declared_attr def id(cls): for base in cls.__mro__[1:-1]: if getattr(base, '__table__', None) is not None: type = sa.ForeignKey(base.id) break else: type = sa.Integer return sa.Column(type, primary_key=True) db = SQLAlchemy(model_class=IdModel) class User(db.Model): name = db.Column(db.String) class Employee(User): title = db.Column(db.String) Model Mixins ------------ If behavior is only needed on some models rather than all models, use mixin classes to customize only those models. For example, if some models should track when they are created or updated:: from datetime import datetime class TimestampMixin(object): created = db.Column( db.DateTime, nullable=False, default=datetime.utcnow) updated = db.Column(db.DateTime, onupdate=datetime.utcnow) class Author(db.Model): ... class Post(TimestampMixin, db.Model): ... Query Class ----------- It is also possible to customize what is available 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) # get a user by id, or return an anonymous user instance user = User.query.get_or(user_id, anonymous_user) 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.orm.relationship`) will also be provided with this functionality. 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``. Model Metaclass --------------- .. warning:: Metaclasses are an advanced topic, and you probably don't need to customize them to achieve what you want. It is mainly documented here to show how to disable table name generation. The model metaclass is responsible for setting up the SQLAlchemy internals when defining model subclasses. Flask-SQLAlchemy adds some extra behaviors through mixins; its default metaclass, :class:`~model.DefaultMeta`, inherits them all. * :class:`~model.BindMetaMixin`: ``__bind_key__`` is extracted from the class and applied to the table. See :ref:`binds`. * :class:`~model.NameMetaMixin`: If the model does not specify a ``__tablename__`` but does specify a primary key, a name is automatically generated. You can add your own behaviors by defining your own metaclass and creating the declarative base yourself. Be sure to still inherit from the mixins you want (or just inherit from the default metaclass). Passing a declarative base class instead of a simple model base class, as shown above, to ``base_class`` will cause Flask-SQLAlchemy to use this base instead of constructing one with the default metaclass. :: from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy.model import DefaultMeta, Model class CustomMeta(DefaultMeta): def __init__(cls, name, bases, d): # custom class setup could go here # be sure to call super super(CustomMeta, cls).__init__(name, bases, d) # custom class-only methods could go here db = SQLAlchemy(model_class=declarative_base( cls=Model, metaclass=CustomMeta, name='Model')) You can also pass whatever other arguments you want to :func:`~sqlalchemy.ext.declarative.declarative_base` to customize the base class as needed. Disabling Table Name Generation ``````````````````````````````` Some projects prefer to set each model's ``__tablename__`` manually rather than relying on Flask-SQLAlchemy's detection and generation. The table name generation can be disabled by defining a custom metaclass. :: from flask_sqlalchemy.model import BindMetaMixin, Model from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base class NoNameMeta(BindMetaMixin, DeclarativeMeta): pass db = SQLAlchemy(model_class=declarative_base( cls=Model, metaclass=NoNameMeta, name='Model')) This creates a base that still supports the ``__bind_key__`` feature but does not generate table names. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/docs/index.rst0000644000175000017500000000236000000000000016252 0ustar00daviddavid.. rst-class:: hide-header Flask-SQLAlchemy ================ .. image:: _static/flask-sqlalchemy-title.png :align: center Flask-SQLAlchemy is an extension for `Flask`_ that adds support for `SQLAlchemy`_ to your application. It aims to simplify using SQLAlchemy with Flask by providing useful defaults and extra helpers that make it easier to accomplish common tasks. .. _SQLAlchemy: https://www.sqlalchemy.org/ .. _Flask: http://flask.pocoo.org/ 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: https://docs.sqlalchemy.org/en/latest/ Requirements ------------ .. csv-table:: :header: "Our Version", "Python", "Flask", "SQLAlchemy" "2.x", "2.7, 3.4+", 0.12+, "0.8+ or 1.0.10+ w/ Python 3.7" "3.0+ (in dev)", "2.7, 3.5+", 1.0+, 1.0+ User Guide ---------- .. toctree:: :maxdepth: 2 quickstart contexts config models queries binds signals customizing API Reference ------------- .. toctree:: :maxdepth: 2 api Additional Information ---------------------- .. toctree:: :maxdepth: 2 license changelog ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/docs/license.rst0000644000175000017500000000005500000000000016564 0ustar00daviddavidLicense ======= .. include:: ../LICENSE.rst ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1590591119.0 Flask-SQLAlchemy-2.5.1/docs/make.bat0000644000175000017500000000136000000000000016015 0ustar00daviddavid@ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=. set BUILDDIR=_build if "%1" == "" goto help %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% :end popd ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/docs/models.rst0000644000175000017500000002005100000000000016423 0ustar00daviddavid.. _models: .. currentmodule:: flask_sqlalchemy Declaring Models ================ Generally Flask-SQLAlchemy behaves like a properly configured declarative base from the :mod:`~sqlalchemy.ext.declarative` extension. As such we recommend reading the SQLAlchemy docs for a full reference. However the most common use cases are also documented here. Things to keep in mind: - 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. Simple Example -------------- A very simple example:: 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) def __repr__(self): return '' % self.username Use :class:`~sqlalchemy.schema.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 ``primary_key=True``. Multiple keys can be marked as primary keys in 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 ================================================ ===================================== 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). 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 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) 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 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. - ``'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. How do you define the lazy status for backrefs? By using the :func:`~sqlalchemy.orm.backref` function:: class Person(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')) Many-to-Many Relationships -------------------------- If you want to use many-to-many relationships you will need to define a helper table that is used for the relationship. For this helper table it 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) ) 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)) 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. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/docs/queries.rst0000644000175000017500000001040300000000000016615 0ustar00daviddavid.. currentmodule:: flask_sqlalchemy Select, Insert, Delete ====================== Now that you have :ref:`declared models ` it's time to query the data from the database. We will be using the model definitions from the :ref:`quickstart` chapter. Inserting Records ----------------- Before we can query something we will have to insert some data. All your models should have a constructor, so make sure to add one if you forgot. Constructors are only used by you, not by SQLAlchemy internally so it's entirely up to you how you define them. Inserting data into the database is a three step process: 1. Create the Python object 2. Add it to the session 3. Commit the session The session here is not the Flask session, but the Flask-SQLAlchemy one. It is essentially a beefed up version of a database transaction. This is how it works: >>> from yourapp import User >>> me = User('admin', 'admin@example.com') >>> db.session.add(me) >>> db.session.commit() Alright, that was not hard. What happens at what point? Before you add the object to the session, SQLAlchemy basically does not plan on adding it to the transaction. That is good because you can still discard the changes. For example think about creating the post at a page but you only want to pass the post to the template for preview rendering instead of storing it in the database. The :func:`~sqlalchemy.orm.session.Session.add` function call then adds the object. It will issue an `INSERT` statement for the database but because the transaction is still not committed you won't get an ID back immediately. If you do the commit, your user will have an ID: >>> me.id 1 Deleting Records ---------------- Deleting records is very similar, instead of :func:`~sqlalchemy.orm.session.Session.add` use :func:`~sqlalchemy.orm.session.Session.delete`: >>> db.session.delete(me) >>> db.session.commit() Querying Records ---------------- So how do we get data back out of our database? For this purpose Flask-SQLAlchemy provides a :attr:`~Model.query` attribute on your :class:`Model` class. When you access it you will get back a new query object over all records. You can then use methods like :func:`~sqlalchemy.orm.query.Query.filter` to filter the records before you fire the select with :func:`~sqlalchemy.orm.query.Query.all` or :func:`~sqlalchemy.orm.query.Query.first`. If you want to go by primary key you can also use :func:`~sqlalchemy.orm.query.Query.get`. The following queries assume following entries in the database: =========== =========== ===================== `id` `username` `email` 1 admin admin@example.com 2 peter peter@example.org 3 guest guest@example.com =========== =========== ===================== Retrieve a user by username: >>> peter = User.query.filter_by(username='peter').first() >>> peter.id 2 >>> peter.email u'peter@example.org' Same as above but for a non existing username gives `None`: >>> missing = User.query.filter_by(username='missing').first() >>> missing is None True Selecting a bunch of users by a more complex expression: >>> User.query.filter(User.email.endswith('@example.com')).all() [, ] Ordering users by something: >>> User.query.order_by(User.username).all() [, , ] Limiting users: >>> User.query.limit(1).all() [] Getting user by primary key: >>> User.query.get(1) Queries in Views ---------------- If you write a Flask view function it's often very handy to return a 404 error for missing entries. Because this is a very common idiom, Flask-SQLAlchemy provides a helper for this exact purpose. Instead of :meth:`~sqlalchemy.orm.query.Query.get` one can use :meth:`~Query.get_or_404` and instead of :meth:`~sqlalchemy.orm.query.Query.first` :meth:`~Query.first_or_404`. This will raise 404 errors instead of returning `None`:: @app.route('/user/') def show_user(username): user = User.query.filter_by(username=username).first_or_404() return render_template('show_user.html', user=user) Also, if you want to add a description with abort(), you can use it as argument as well. >>> User.query.filter_by(username=username).first_or_404(description='There is no data with {}'.format(username))././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/docs/quickstart.rst0000644000175000017500000001473500000000000017346 0ustar00daviddavid.. _quickstart: Quickstart ========== .. currentmodule:: flask_sqlalchemy Flask-SQLAlchemy is fun to use, incredibly easy for basic applications, and readily extends for larger applications. For the complete guide, checkout the API documentation on the :class:`SQLAlchemy` class. A Minimal Application --------------------- For the common case of having one Flask application all you have to do is to create your Flask application, load the configuration of choice and then create the :class:`SQLAlchemy` object by passing it the application. 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 used to declare models:: from flask import Flask from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db' db = SQLAlchemy(app) 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) def __repr__(self): return '' % self.username 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:: >>> from yourapplication import db >>> db.create_all() Boom, and there is your database. Now to create some users:: >>> 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__(self, **kwargs): super(Foo, self).__init__(**kwargs) # do custom stuff Simple Relationships -------------------- SQLAlchemy connects to relational databases and what relational databases 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) category_id = db.Column(db.Integer, db.ForeignKey('category.id'), nullable=False) category = db.relationship('Category', backref=db.backref('posts', lazy=True)) def __repr__(self): return '' % self.title class Category(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(50), nullable=False) def __repr__(self): return '' % self.name First let's create some objects:: >>> 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:: >>> Post.query.with_parent(py).filter(Post.title != 'Snakes').all() [] Road to Enlightenment --------------------- The only things you need to know compared to plain SQLAlchemy are: 1. :class:`SQLAlchemy` gives you access to the following things: - all the functions and classes from :mod:`sqlalchemy` and :mod:`sqlalchemy.orm` - a preconfigured scoped session called ``session`` - the :attr:`~SQLAlchemy.metadata` - the :attr:`~SQLAlchemy.engine` - a :meth:`SQLAlchemy.create_all` and :meth:`SQLAlchemy.drop_all` methods to create and drop tables according to the models. - 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 query the model. (:class:`Model` and :class:`BaseQuery`) 3. You have to commit the session, but you don't have to remove it at the end of the request, Flask-SQLAlchemy does that for you. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/docs/requirements.txt0000644000175000017500000000010000000000000017663 0ustar00daviddavidSphinx~=2.0.1 Pallets-Sphinx-Themes~=1.1.4 sphinx-issues~=1.2.0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/docs/signals.rst0000644000175000017500000000162500000000000016606 0ustar00daviddavidSignalling Support ================== Connect to the following signals to get notified before and after changes are committed to the database. These changes are only tracked if ``SQLALCHEMY_TRACK_MODIFICATIONS`` is enabled in the config. .. versionadded:: 0.10 .. versionchanged:: 2.1 ``before_models_committed`` is triggered correctly. .. deprecated:: 2.1 This will be disabled by default in a future version. .. data:: models_committed This signal is sent when changed models were committed to the database. The sender is the application that emitted the changes. The receiver is passed the ``changes`` parameter with a list of tuples in the form ``(model instance, operation)``. The operation is one of ``'insert'``, ``'update'``, and ``'delete'``. .. data:: before_models_committed This signal works exactly like :data:`models_committed` but is emitted before the commit takes place. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1616094138.0291326 Flask-SQLAlchemy-2.5.1/examples/0000755000175000017500000000000000000000000015276 5ustar00daviddavid././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1616094138.0291326 Flask-SQLAlchemy-2.5.1/examples/flaskr/0000755000175000017500000000000000000000000016560 5ustar00daviddavid././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1590591119.0 Flask-SQLAlchemy-2.5.1/examples/flaskr/.gitignore0000644000175000017500000000013000000000000020542 0ustar00daviddavidvenv/ *.pyc instance/ .pytest_cache/ .coverage htmlcov/ dist/ build/ *.egg-info/ .idea/ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1590591119.0 Flask-SQLAlchemy-2.5.1/examples/flaskr/LICENSE.rst0000644000175000017500000000270300000000000020376 0ustar00daviddavidCopyright 2010 Pallets Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. 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. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE 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 HOLDER 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 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1590591119.0 Flask-SQLAlchemy-2.5.1/examples/flaskr/MANIFEST.in0000644000175000017500000000011400000000000020312 0ustar00daviddavidgraft flaskr/static graft flaskr/templates graft tests global-exclude *.pyc ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/examples/flaskr/README.rst0000644000175000017500000000311400000000000020246 0ustar00daviddavidFlaskr ====== The basic blog app built in the Flask `tutorial`_, modified to use Flask-SQLAlchemy instead of plain SQL. .. _tutorial: http://flask.pocoo.org/docs/tutorial/ Install ------- **Be sure to use the same version of the code as the version of the docs you're reading.** You probably want the latest tagged version, but the default Git version is the master branch. .. code-block:: text # clone the repository $ git clone https://github.com/pallets/flask-sqlalchemy $ cd flask-sqlalchemy/examples/flaskr # checkout the correct version $ git checkout correct-version-tag Create a virtualenv and activate it: .. code-block:: text $ python3 -m venv venv $ . venv/bin/activate Or on Windows cmd: .. code-block:: text $ py -3 -m venv venv $ venv\Scripts\activate.bat Install Flaskr: .. code-block:: text $ pip install -e . Or if you are using the master branch, install Flask-SQLAlchemy from source before installing Flaskr: .. code-block:: text $ pip install -e ../.. $ pip install -e . Run --- .. code-block:: text $ export FLASK_APP=flaskr $ export FLASK_ENV=development $ flask init-db $ flask run Or on Windows cmd: .. code-block:: text > set FLASK_APP=flaskr > set FLASK_ENV=development > flask init-db > flask run Open http://127.0.0.1:5000 in a browser. Test ---- .. code-block:: text $ pip install -e '.[test]' $ pytest Run with coverage report: .. code-block:: text $ coverage run -m pytest $ coverage report $ coverage html # open htmlcov/index.html in a browser ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1616094138.0291326 Flask-SQLAlchemy-2.5.1/examples/flaskr/flaskr/0000755000175000017500000000000000000000000020042 5ustar00daviddavid././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/examples/flaskr/flaskr/__init__.py0000644000175000017500000000345300000000000022160 0ustar00daviddavidimport os import click from flask import Flask from flask.cli import with_appcontext from flask_sqlalchemy import SQLAlchemy __version__ = (1, 0, 0, "dev") db = SQLAlchemy() def create_app(test_config=None): """Create and configure an instance of the Flask application.""" app = Flask(__name__, instance_relative_config=True) # some deploy systems set the database url in the environ db_url = os.environ.get("DATABASE_URL") if db_url is None: # default to a sqlite database in the instance folder db_url = "sqlite:///" + os.path.join(app.instance_path, "flaskr.sqlite") # ensure the instance folder exists os.makedirs(app.instance_path, exist_ok=True) app.config.from_mapping( # default secret that should be overridden in environ or config SECRET_KEY=os.environ.get("SECRET_KEY", "dev"), SQLALCHEMY_DATABASE_URI=db_url, SQLALCHEMY_TRACK_MODIFICATIONS=False, ) if test_config is None: # load the instance config, if it exists, when not testing app.config.from_pyfile("config.py", silent=True) else: # load the test config if passed in app.config.update(test_config) # initialize Flask-SQLAlchemy and the init-db command db.init_app(app) app.cli.add_command(init_db_command) # apply the blueprints to the app from flaskr import auth, blog app.register_blueprint(auth.bp) app.register_blueprint(blog.bp) # make "index" point at "/", which is handled by "blog.index" app.add_url_rule("/", endpoint="index") return app def init_db(): db.drop_all() db.create_all() @click.command("init-db") @with_appcontext def init_db_command(): """Clear existing data and create new tables.""" init_db() click.echo("Initialized the database.") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1616094138.0291326 Flask-SQLAlchemy-2.5.1/examples/flaskr/flaskr/auth/0000755000175000017500000000000000000000000021003 5ustar00daviddavid././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/examples/flaskr/flaskr/auth/__init__.py0000644000175000017500000000002600000000000023112 0ustar00daviddavidfrom .views import bp ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1590591119.0 Flask-SQLAlchemy-2.5.1/examples/flaskr/flaskr/auth/models.py0000644000175000017500000000131300000000000022636 0ustar00daviddavidfrom sqlalchemy.ext.hybrid import hybrid_property from werkzeug.security import check_password_hash from werkzeug.security import generate_password_hash from flaskr import db class User(db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String, unique=True, nullable=False) _password = db.Column("password", db.String, nullable=False) @hybrid_property def password(self): return self._password @password.setter def password(self, value): """Store the password as a hash for security.""" self._password = generate_password_hash(value) def check_password(self, value): return check_password_hash(self.password, value) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1590591119.0 Flask-SQLAlchemy-2.5.1/examples/flaskr/flaskr/auth/views.py0000644000175000017500000000542000000000000022513 0ustar00daviddavidimport functools from flask import Blueprint from flask import flash from flask import g from flask import redirect from flask import render_template from flask import request from flask import session from flask import url_for from flaskr import db from flaskr.auth.models import User bp = Blueprint("auth", __name__, url_prefix="/auth") def login_required(view): """View decorator that redirects anonymous users to the login page.""" @functools.wraps(view) def wrapped_view(**kwargs): if g.user is None: return redirect(url_for("auth.login")) return view(**kwargs) return wrapped_view @bp.before_app_request def load_logged_in_user(): """If a user id is stored in the session, load the user object from the database into ``g.user``.""" user_id = session.get("user_id") g.user = User.query.get(user_id) if user_id is not None else None @bp.route("/register", methods=("GET", "POST")) def register(): """Register a new user. Validates that the username is not already taken. Hashes the password for security. """ if request.method == "POST": username = request.form["username"] password = request.form["password"] error = None if not username: error = "Username is required." elif not password: error = "Password is required." elif db.session.query( User.query.filter_by(username=username).exists() ).scalar(): error = f"User {username} is already registered." if error is None: # the name is available, create the user and go to the login page db.session.add(User(username=username, password=password)) db.session.commit() return redirect(url_for("auth.login")) flash(error) return render_template("auth/register.html") @bp.route("/login", methods=("GET", "POST")) def login(): """Log in a registered user by adding the user id to the session.""" if request.method == "POST": username = request.form["username"] password = request.form["password"] error = None user = User.query.filter_by(username=username).first() if user is None: error = "Incorrect username." elif not user.check_password(password): error = "Incorrect password." if error is None: # store the user id in a new session and return to the index session.clear() session["user_id"] = user.id return redirect(url_for("index")) flash(error) return render_template("auth/login.html") @bp.route("/logout") def logout(): """Clear the current session, including the stored user id.""" session.clear() return redirect(url_for("index")) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1616094138.0291326 Flask-SQLAlchemy-2.5.1/examples/flaskr/flaskr/blog/0000755000175000017500000000000000000000000020765 5ustar00daviddavid././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/examples/flaskr/flaskr/blog/__init__.py0000644000175000017500000000002600000000000023074 0ustar00daviddavidfrom .views import bp ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1590591119.0 Flask-SQLAlchemy-2.5.1/examples/flaskr/flaskr/blog/models.py0000644000175000017500000000143200000000000022622 0ustar00daviddavidfrom flask import url_for from flaskr import db from flaskr.auth.models import User class Post(db.Model): id = db.Column(db.Integer, primary_key=True) author_id = db.Column(db.ForeignKey(User.id), nullable=False) created = db.Column( db.DateTime, nullable=False, server_default=db.func.current_timestamp() ) title = db.Column(db.String, nullable=False) body = db.Column(db.String, nullable=False) # User object backed by author_id # lazy="joined" means the user is returned with the post in one query author = db.relationship(User, lazy="joined", backref="posts") @property def update_url(self): return url_for("blog.update", id=self.id) @property def delete_url(self): return url_for("blog.delete", id=self.id) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1590591119.0 Flask-SQLAlchemy-2.5.1/examples/flaskr/flaskr/blog/views.py0000644000175000017500000000521200000000000022474 0ustar00daviddavidfrom flask import Blueprint from flask import flash from flask import g from flask import redirect from flask import render_template from flask import request from flask import url_for from werkzeug.exceptions import abort from flaskr import db from flaskr.auth.views import login_required from flaskr.blog.models import Post bp = Blueprint("blog", __name__) @bp.route("/") def index(): """Show all the posts, most recent first.""" posts = Post.query.order_by(Post.created.desc()).all() return render_template("blog/index.html", posts=posts) def get_post(id, check_author=True): """Get a post and its author by id. Checks that the id exists and optionally that the current user is the author. :param id: id of post to get :param check_author: require the current user to be the author :return: the post with author information :raise 404: if a post with the given id doesn't exist :raise 403: if the current user isn't the author """ post = Post.query.get_or_404(id, f"Post id {id} doesn't exist.") if check_author and post.author != g.user: abort(403) return post @bp.route("/create", methods=("GET", "POST")) @login_required def create(): """Create a new post for the current user.""" if request.method == "POST": title = request.form["title"] body = request.form["body"] error = None if not title: error = "Title is required." if error is not None: flash(error) else: db.session.add(Post(title=title, body=body, author=g.user)) db.session.commit() return redirect(url_for("blog.index")) return render_template("blog/create.html") @bp.route("//update", methods=("GET", "POST")) @login_required def update(id): """Update a post if the current user is the author.""" post = get_post(id) if request.method == "POST": title = request.form["title"] body = request.form["body"] error = None if not title: error = "Title is required." if error is not None: flash(error) else: post.title = title post.body = body db.session.commit() return redirect(url_for("blog.index")) return render_template("blog/update.html", post=post) @bp.route("//delete", methods=("POST",)) @login_required def delete(id): """Delete a post. Ensures that the post exists and that the logged in user is the author of the post. """ post = get_post(id) db.session.delete(post) db.session.commit() return redirect(url_for("blog.index")) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1616094138.0291326 Flask-SQLAlchemy-2.5.1/examples/flaskr/flaskr/static/0000755000175000017500000000000000000000000021331 5ustar00daviddavid././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1590591119.0 Flask-SQLAlchemy-2.5.1/examples/flaskr/flaskr/static/style.css0000644000175000017500000000324000000000000023202 0ustar00daviddavidhtml { font-family: sans-serif; background: #eee; padding: 1rem; } body { max-width: 960px; margin: 0 auto; background: white; } h1, h2, h3, h4, h5, h6 { font-family: serif; color: #377ba8; margin: 1rem 0; } a { color: #377ba8; } hr { border: none; border-top: 1px solid lightgray; } nav { background: lightgray; display: flex; align-items: center; padding: 0 0.5rem; } nav h1 { flex: auto; margin: 0; } nav h1 a { text-decoration: none; padding: 0.25rem 0.5rem; } nav ul { display: flex; list-style: none; margin: 0; padding: 0; } nav ul li a, nav ul li span, header .action { display: block; padding: 0.5rem; } .content { padding: 0 1rem 1rem; } .content > header { border-bottom: 1px solid lightgray; display: flex; align-items: flex-end; } .content > header h1 { flex: auto; margin: 1rem 0 0.25rem 0; } .flash { margin: 1em 0; padding: 1em; background: #cae6f6; border: 1px solid #377ba8; } .post > header { display: flex; align-items: flex-end; font-size: 0.85em; } .post > header > div:first-of-type { flex: auto; } .post > header h1 { font-size: 1.5em; margin-bottom: 0; } .post .about { color: slategray; font-style: italic; } .post .body { white-space: pre-line; } .content:last-child { margin-bottom: 0; } .content form { margin: 1em 0; display: flex; flex-direction: column; } .content label { font-weight: bold; margin-bottom: 0.5em; } .content input, .content textarea { margin-bottom: 1em; } .content textarea { min-height: 12em; resize: vertical; } input.danger { color: #cc2f2e; } input[type=submit] { align-self: start; min-width: 10em; } ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1616094138.0291326 Flask-SQLAlchemy-2.5.1/examples/flaskr/flaskr/templates/0000755000175000017500000000000000000000000022040 5ustar00daviddavid././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1616094138.032466 Flask-SQLAlchemy-2.5.1/examples/flaskr/flaskr/templates/auth/0000755000175000017500000000000000000000000023001 5ustar00daviddavid././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1590591119.0 Flask-SQLAlchemy-2.5.1/examples/flaskr/flaskr/templates/auth/login.html0000644000175000017500000000065000000000000025000 0ustar00daviddavid{% extends 'base.html' %} {% block header %}

{% block title %}Log In{% endblock %}

{% endblock %} {% block content %}
{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1590591119.0 Flask-SQLAlchemy-2.5.1/examples/flaskr/flaskr/templates/auth/register.html0000644000175000017500000000065400000000000025520 0ustar00daviddavid{% extends 'base.html' %} {% block header %}

{% block title %}Register{% endblock %}

{% endblock %} {% block content %}
{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1590591119.0 Flask-SQLAlchemy-2.5.1/examples/flaskr/flaskr/templates/base.html0000644000175000017500000000136000000000000023640 0ustar00daviddavid {% block title %}{% endblock %} - Flaskr
{% block header %}{% endblock %}
{% for message in get_flashed_messages() %}
{{ message }}
{% endfor %} {% block content %}{% endblock %}
././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1616094138.032466 Flask-SQLAlchemy-2.5.1/examples/flaskr/flaskr/templates/blog/0000755000175000017500000000000000000000000022763 5ustar00daviddavid././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1590591119.0 Flask-SQLAlchemy-2.5.1/examples/flaskr/flaskr/templates/blog/create.html0000644000175000017500000000067700000000000025126 0ustar00daviddavid{% extends 'base.html' %} {% block header %}

{% block title %}New Post{% endblock %}

{% endblock %} {% block content %}
{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1590591119.0 Flask-SQLAlchemy-2.5.1/examples/flaskr/flaskr/templates/blog/index.html0000644000175000017500000000140100000000000024754 0ustar00daviddavid{% extends 'base.html' %} {% block header %}

{% block title %}Posts{% endblock %}

{% if g.user %} New {% endif %} {% endblock %} {% block content %} {% for post in posts %}

{{ post['title'] }}

by {{ post.author.username }} on {{ post.created.strftime('%Y-%m-%d') }}
{% if g.user['id'] == post['author_id'] %} Edit {% endif %}

{{ post['body'] }}

{% if not loop.last %}
{% endif %} {% endfor %} {% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1590591119.0 Flask-SQLAlchemy-2.5.1/examples/flaskr/flaskr/templates/blog/update.html0000644000175000017500000000123400000000000025133 0ustar00daviddavid{% extends 'base.html' %} {% block header %}

{% block title %}Edit "{{ post['title'] }}"{% endblock %}

{% endblock %} {% block content %}

{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1590591119.0 Flask-SQLAlchemy-2.5.1/examples/flaskr/setup.cfg0000644000175000017500000000111100000000000020373 0ustar00daviddavid[metadata] name = flaskr version = attr: flaskr.__version__ url = https://flask-sqlalchemy.palletsprojects.com/ author = Pallets author_email = contact@palletsprojects.com license = BSD-3-Clause license_file = LICENSE.rst description = A basic blog app built with Flask-SQLAlchemy. long_description = file: README.rst [options] packages = find: include_package_data = true python_requires = >= 3.6 install_requires = Flask Flask-SQLAlchemy [options.extras_require] test = pytest coverage [tool:pytest] testpaths = tests [coverage:run] branch = True source = flaskr ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1590591119.0 Flask-SQLAlchemy-2.5.1/examples/flaskr/setup.py0000644000175000017500000000004600000000000020272 0ustar00daviddavidfrom setuptools import setup setup() ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1616094138.032466 Flask-SQLAlchemy-2.5.1/examples/flaskr/tests/0000755000175000017500000000000000000000000017722 5ustar00daviddavid././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/examples/flaskr/tests/conftest.py0000644000175000017500000000344500000000000022127 0ustar00daviddavidfrom datetime import datetime import pytest from werkzeug.security import generate_password_hash from flaskr import create_app from flaskr import db from flaskr import init_db from flaskr.auth.models import User from flaskr.blog.models import Post _user1_pass = generate_password_hash("test") _user2_pass = generate_password_hash("other") @pytest.fixture def app(): """Create and configure a new app instance for each test.""" # create the app with common test config app = create_app({"TESTING": True, "SQLALCHEMY_DATABASE_URI": "sqlite:///:memory:"}) # create the database and load test data # set _password to pre-generated hashes, since hashing for each test is slow with app.app_context(): init_db() user = User(username="test", _password=_user1_pass) db.session.add_all( ( user, User(username="other", _password=_user2_pass), Post( title="test title", body="test\nbody", author=user, created=datetime(2018, 1, 1), ), ) ) db.session.commit() yield app @pytest.fixture def client(app): """A test client for the app.""" return app.test_client() @pytest.fixture def runner(app): """A test runner for the app's Click commands.""" return app.test_cli_runner() class AuthActions(object): def __init__(self, client): self._client = client def login(self, username="test", password="test"): return self._client.post( "/auth/login", data={"username": username, "password": password} ) def logout(self): return self._client.get("/auth/logout") @pytest.fixture def auth(client): return AuthActions(client) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1590591119.0 Flask-SQLAlchemy-2.5.1/examples/flaskr/tests/test_auth.py0000644000175000017500000000421100000000000022272 0ustar00daviddavidimport pytest from flask import g from flask import session from flaskr.auth.models import User def test_register(client, app): # test that viewing the page renders without template errors assert client.get("/auth/register").status_code == 200 # test that successful registration redirects to the login page response = client.post("/auth/register", data={"username": "a", "password": "a"}) assert "http://localhost/auth/login" == response.headers["Location"] # test that the user was inserted into the database with app.app_context(): assert User.query.filter_by(username="a").first() is not None def test_user_password(app): user = User(username="a", password="a") assert user.password != "a" assert user.check_password("a") @pytest.mark.parametrize( ("username", "password", "message"), ( ("", "", b"Username is required."), ("a", "", b"Password is required."), ("test", "test", b"already registered"), ), ) def test_register_validate_input(client, username, password, message): response = client.post( "/auth/register", data={"username": username, "password": password} ) assert message in response.data def test_login(client, auth): # test that viewing the page renders without template errors assert client.get("/auth/login").status_code == 200 # test that successful login redirects to the index page response = auth.login() assert response.headers["Location"] == "http://localhost/" # login request set the user_id in the session # check that the user is loaded from the session with client: client.get("/") assert session["user_id"] == 1 assert g.user.username == "test" @pytest.mark.parametrize( ("username", "password", "message"), (("a", "test", b"Incorrect username."), ("test", "a", b"Incorrect password.")), ) def test_login_validate_input(auth, username, password, message): response = auth.login(username, password) assert message in response.data def test_logout(client, auth): auth.login() with client: auth.logout() assert "user_id" not in session ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1590591119.0 Flask-SQLAlchemy-2.5.1/examples/flaskr/tests/test_blog.py0000644000175000017500000000443100000000000022260 0ustar00daviddavidimport pytest from flaskr import db from flaskr.auth.models import User from flaskr.blog.models import Post def test_index(client, auth): response = client.get("/") assert b"Log In" in response.data assert b"Register" in response.data auth.login() response = client.get("/") assert b"test title" in response.data assert b"by test on 2018-01-01" in response.data assert b"test\nbody" in response.data assert b'href="/1/update"' in response.data @pytest.mark.parametrize("path", ("/create", "/1/update", "/1/delete")) def test_login_required(client, path): response = client.post(path) assert response.headers["Location"] == "http://localhost/auth/login" def test_author_required(app, client, auth): # change the post author to another user with app.app_context(): Post.query.get(1).author = User.query.get(2) db.session.commit() auth.login() # current user can't modify other user's post assert client.post("/1/update").status_code == 403 assert client.post("/1/delete").status_code == 403 # current user doesn't see edit link assert b'href="/1/update"' not in client.get("/").data @pytest.mark.parametrize("path", ("/2/update", "/2/delete")) def test_exists_required(client, auth, path): auth.login() assert client.post(path).status_code == 404 def test_create(client, auth, app): auth.login() assert client.get("/create").status_code == 200 client.post("/create", data={"title": "created", "body": ""}) with app.app_context(): assert Post.query.count() == 2 def test_update(client, auth, app): auth.login() assert client.get("/1/update").status_code == 200 client.post("/1/update", data={"title": "updated", "body": ""}) with app.app_context(): assert Post.query.get(1).title == "updated" @pytest.mark.parametrize("path", ("/create", "/1/update")) def test_create_update_validate(client, auth, path): auth.login() response = client.post(path, data={"title": "", "body": ""}) assert b"Title is required." in response.data def test_delete(client, auth, app): auth.login() response = client.post("/1/delete") assert response.headers["Location"] == "http://localhost/" with app.app_context(): assert Post.query.get(1) is None ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/examples/flaskr/tests/test_init.py0000644000175000017500000000137300000000000022302 0ustar00daviddavidfrom flaskr import create_app def test_config(): """Test create_app without passing test config.""" assert not create_app().testing assert create_app({"TESTING": True}).testing def test_db_url_environ(monkeypatch): """Test DATABASE_URL environment variable.""" monkeypatch.setenv("DATABASE_URL", "sqlite:///environ") app = create_app() assert app.config["SQLALCHEMY_DATABASE_URI"] == "sqlite:///environ" def test_init_db_command(runner, monkeypatch): class Recorder(object): called = False def fake_init_db(): Recorder.called = True monkeypatch.setattr("flaskr.init_db", fake_init_db) result = runner.invoke(args=["init-db"]) assert "Initialized" in result.output assert Recorder.called ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1616094138.032466 Flask-SQLAlchemy-2.5.1/examples/hello/0000755000175000017500000000000000000000000016401 5ustar00daviddavid././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1590591119.0 Flask-SQLAlchemy-2.5.1/examples/hello/hello.cfg0000644000175000017500000000024300000000000020164 0ustar00daviddavidSQLALCHEMY_DATABASE_URI = 'sqlite:////tmp/test.db' SQLALCHEMY_ECHO = False SECRET_KEY = '\xfb\x12\xdf\xa1@i\xd6>V\xc0\xbb\x8fp\x16#Z\x0b\x81\xeb\x16' DEBUG = True ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/examples/hello/hello.py0000644000175000017500000000315100000000000020056 0ustar00daviddavidfrom datetime import datetime from flask import Flask, request, flash, url_for, redirect, \ render_template, abort from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) app.config.from_pyfile('hello.cfg') db = SQLAlchemy(app) class Todo(db.Model): __tablename__ = 'todos' id = db.Column('todo_id', db.Integer, primary_key=True) title = db.Column(db.String(60)) text = db.Column(db.String) done = db.Column(db.Boolean) pub_date = db.Column(db.DateTime) def __init__(self, title, text): self.title = title self.text = text self.done = False self.pub_date = datetime.utcnow() @app.route('/') def show_all(): return render_template('show_all.html', todos=Todo.query.order_by(Todo.pub_date.desc()).all() ) @app.route('/new', methods=['GET', 'POST']) def new(): if request.method == 'POST': if not request.form['title']: flash('Title is required', 'error') elif not request.form['text']: flash('Text is required', 'error') else: todo = Todo(request.form['title'], request.form['text']) db.session.add(todo) db.session.commit() flash(u'Todo item was successfully created') return redirect(url_for('show_all')) return render_template('new.html') @app.route('/update', methods=['POST']) def update_done(): for todo in Todo.query.all(): todo.done = ('done.%d' % todo.id) in request.form flash('Updated status') db.session.commit() return redirect(url_for('show_all')) if __name__ == '__main__': app.run() ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1616094138.032466 Flask-SQLAlchemy-2.5.1/examples/hello/templates/0000755000175000017500000000000000000000000020377 5ustar00daviddavid././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1590591119.0 Flask-SQLAlchemy-2.5.1/examples/hello/templates/layout.html0000644000175000017500000000055200000000000022604 0ustar00daviddavid SQLAlchemy Todo

SQLAlchemy Todo

{%- for category, message in get_flashed_messages(with_categories=true) %}

{{ "Error: " if category == 'error' }}{{ message }}

{%- endfor %} {% block body %}{% endblock %}
SQLAlchemy and Flask powered todo application
././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1590591119.0 Flask-SQLAlchemy-2.5.1/examples/hello/templates/new.html0000644000175000017500000000064400000000000022062 0ustar00daviddavid{% extends "layout.html" %} {% block body %}

New Todo Item

Title:

Text:

Back to list

{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1590591119.0 Flask-SQLAlchemy-2.5.1/examples/hello/templates/show_all.html0000644000175000017500000000130000000000000023067 0ustar00daviddavid{% extends "layout.html" %} {% block body %}

All Items

{%- endfor %}
# Title Date Done? {%- for todo in todos %}
{{ todo.id }} {{ todo.title }} {{ todo.pub_date.strftime('%Y-%m-%d %H:%M') }}
{{ todo.text }}

New item

{% endblock %} ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1616094138.032466 Flask-SQLAlchemy-2.5.1/flask_sqlalchemy/0000755000175000017500000000000000000000000017002 5ustar00daviddavid././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616094064.0 Flask-SQLAlchemy-2.5.1/flask_sqlalchemy/__init__.py0000644000175000017500000011744200000000000021124 0ustar00daviddavid# -*- coding: utf-8 -*- from __future__ import absolute_import import functools import os import sys import time import warnings from math import ceil from operator import itemgetter from threading import Lock import sqlalchemy from flask import _app_ctx_stack, abort, current_app, request from flask.signals import Namespace from sqlalchemy import event, inspect, orm from sqlalchemy.engine.url import make_url from sqlalchemy.orm.exc import UnmappedClassError from sqlalchemy.orm.session import Session as SessionBase from ._compat import itervalues, string_types, xrange from .model import DefaultMeta from .model import Model from . import utils try: from sqlalchemy.orm import declarative_base from sqlalchemy.orm import DeclarativeMeta except ImportError: # SQLAlchemy <= 1.3 from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import DeclarativeMeta # Scope the session to the current greenlet if greenlet is available, # otherwise fall back to the current thread. try: from greenlet import getcurrent as _ident_func except ImportError: try: from threading import get_ident as _ident_func except ImportError: # Python 2.7 from thread import get_ident as _ident_func __version__ = "2.5.1" # the best timer function for the platform if sys.platform == 'win32': if sys.version_info >= (3, 3): _timer = time.perf_counter else: _timer = time.clock else: _timer = time.time _signals = Namespace() models_committed = _signals.signal('models-committed') before_models_committed = _signals.signal('before-models-committed') def _sa_url_set(url, **kwargs): try: url = url.set(**kwargs) except AttributeError: # SQLAlchemy <= 1.3 for key, value in kwargs.items(): setattr(url, key, value) return url def _sa_url_query_setdefault(url, **kwargs): query = dict(url.query) for key, value in kwargs.items(): query.setdefault(key, value) return _sa_url_set(url, query=query) def _make_table(db): def _make_table(*args, **kwargs): if len(args) > 1 and isinstance(args[1], db.Column): args = (args[0], db.metadata) + args[1:] info = kwargs.pop('info', None) or {} info.setdefault('bind_key', None) kwargs['info'] = info return sqlalchemy.Table(*args, **kwargs) return _make_table def _set_default_query_class(d, cls): if 'query_class' not in d: d['query_class'] = cls def _wrap_with_default_query_class(fn, cls): @functools.wraps(fn) def newfn(*args, **kwargs): _set_default_query_class(kwargs, cls) if "backref" in kwargs: backref = kwargs['backref'] if isinstance(backref, string_types): backref = (backref, {}) _set_default_query_class(backref[1], cls) return fn(*args, **kwargs) return newfn def _include_sqlalchemy(obj, cls): 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.event = event class _DebugQueryTuple(tuple): statement = property(itemgetter(0)) parameters = property(itemgetter(1)) start_time = property(itemgetter(2)) end_time = property(itemgetter(3)) context = property(itemgetter(4)) @property def duration(self): return self.end_time - self.start_time def __repr__(self): return '' % ( self.statement, self.parameters, self.duration ) def _calling_context(app_path): frm = sys._getframe(1) while frm.f_back is not None: name = frm.f_globals.get('__name__') if name and (name == app_path or name.startswith(app_path + '.')): funcname = frm.f_code.co_name return '%s:%s (%s)' % ( frm.f_code.co_filename, frm.f_lineno, funcname ) frm = frm.f_back return '' class SignallingSession(SessionBase): """The signalling session is the default session that Flask-SQLAlchemy uses. It extends the default session system with bind selection and modification tracking. If you want to use a different session you can override the :meth:`SQLAlchemy.create_session` function. .. versionadded:: 2.0 .. versionadded:: 2.1 The `binds` option was added, which allows a session to be joined to an external transaction. """ def __init__(self, db, autocommit=False, autoflush=True, **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)) if track_modifications is None or track_modifications: _SessionSignalEvents.register(self) SessionBase.__init__( self, autocommit=autocommit, autoflush=autoflush, bind=bind, binds=binds, **options ) def get_bind(self, mapper=None, clause=None): """Return the engine or connection for a given model or table, using the ``__bind_key__`` if it is set. """ # mapper is None if someone tries to just get a connection if mapper is not None: try: # SA >= 1.3 persist_selectable = mapper.persist_selectable except AttributeError: # SA < 1.3 persist_selectable = mapper.mapped_table info = getattr(persist_selectable, 'info', {}) bind_key = info.get('bind_key') if bind_key is not None: state = get_state(self.app) return state.db.get_engine(self.app, bind=bind_key) return SessionBase.get_bind(self, mapper, clause) class _SessionSignalEvents(object): @classmethod def register(cls, session): if not hasattr(session, '_model_changes'): session._model_changes = {} event.listen(session, 'before_flush', cls.record_ops) event.listen(session, 'before_commit', cls.record_ops) event.listen(session, 'before_commit', cls.before_commit) event.listen(session, 'after_commit', cls.after_commit) event.listen(session, 'after_rollback', cls.after_rollback) @classmethod def unregister(cls, session): if hasattr(session, '_model_changes'): del session._model_changes event.remove(session, 'before_flush', cls.record_ops) event.remove(session, 'before_commit', cls.record_ops) event.remove(session, 'before_commit', cls.before_commit) event.remove(session, 'after_commit', cls.after_commit) event.remove(session, 'after_rollback', cls.after_rollback) @staticmethod def record_ops(session, flush_context=None, instances=None): try: d = session._model_changes except AttributeError: return for targets, operation in ((session.new, 'insert'), (session.dirty, 'update'), (session.deleted, 'delete')): for target in targets: state = inspect(target) key = state.identity_key if state.has_identity else id(target) d[key] = (target, operation) @staticmethod def before_commit(session): try: d = session._model_changes except AttributeError: return if d: before_models_committed.send(session.app, changes=list(d.values())) @staticmethod def after_commit(session): try: d = session._model_changes except AttributeError: return if d: models_committed.send(session.app, changes=list(d.values())) d.clear() @staticmethod def after_rollback(session): try: d = session._model_changes except AttributeError: return d.clear() class _EngineDebuggingSignalEvents(object): """Sets up handlers for two events that let us track the execution time of queries.""" def __init__(self, engine, import_name): self.engine = engine self.app_package = import_name def register(self): 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: context._query_start_time = _timer() def after_cursor_execute( self, conn, cursor, statement, parameters, context, executemany ): if current_app: try: queries = _app_ctx_stack.top.sqlalchemy_queries except AttributeError: queries = _app_ctx_stack.top.sqlalchemy_queries = [] queries.append(_DebugQueryTuple(( statement, parameters, context._query_start_time, _timer(), _calling_context(self.app_package) ))) def get_debug_queries(): """In debug mode Flask-SQLAlchemy will log all the SQL queries sent to the database. This information is available until the end of request which makes it possible to easily ensure that the SQL generated is the one expected on errors or in unittesting. If you don't want to enable the DEBUG mode for your unittests you can also enable the query recording by setting the ``'SQLALCHEMY_RECORD_QUERIES'`` config variable to `True`. This is automatically enabled if Flask is in testing mode. The value returned will be a list of named tuples with the following attributes: `statement` The SQL statement issued `parameters` The parameters for the SQL statement `start_time` / `end_time` Time the query started / the results arrived. Please keep in mind that the timer function used depends on your platform. These values are only useful for sorting or comparing. They do not necessarily represent an absolute timestamp. `duration` Time the query took in seconds `context` A string giving a rough estimation of where in your application query was issued. The exact format is undefined so don't try to reconstruct filename or function name. """ return getattr(_app_ctx_stack.top, 'sqlalchemy_queries', []) class Pagination(object): """Internal helper class returned by :meth:`BaseQuery.paginate`. You can also construct it from any other SQLAlchemy query object if you are working with other libraries. Additionally it is possible to pass `None` as query object in which case the :meth:`prev` and :meth:`next` will no longer work. """ def __init__(self, query, page, per_page, total, items): #: the unlimited query object that was used to create this #: pagination object. self.query = query #: the current page number (1 indexed) self.page = page #: the number of items to be displayed on a page. self.per_page = per_page #: the total number of items matching the query self.total = total #: the items for the current page self.items = items @property def pages(self): """The total number of pages""" if self.per_page == 0: pages = 0 else: pages = int(ceil(self.total / float(self.per_page))) return pages def prev(self, error_out=False): """Returns a :class:`Pagination` object for the previous page.""" assert self.query is not None, 'a query object is required ' \ 'for this method to work' return self.query.paginate(self.page - 1, self.per_page, error_out) @property def prev_num(self): """Number of the previous page.""" if not self.has_prev: return None return self.page - 1 @property def has_prev(self): """True if a previous page exists""" return self.page > 1 def next(self, error_out=False): """Returns a :class:`Pagination` object for the next page.""" assert self.query is not None, 'a query object is required ' \ 'for this method to work' return self.query.paginate(self.page + 1, self.per_page, error_out) @property def has_next(self): """True if a next page exists.""" return self.page < self.pages @property def next_num(self): """Number of the next page""" if not self.has_next: return None return self.page + 1 def iter_pages(self, left_edge=2, left_current=2, right_current=5, right_edge=2): """Iterates over the page numbers in the pagination. The four parameters control the thresholds how many numbers should be produced from the sides. Skipped page numbers are represented as `None`. This is how you could render such a pagination in the templates: .. sourcecode:: html+jinja {% macro render_pagination(pagination, endpoint) %} {% endmacro %} """ last = 0 for num in xrange(1, self.pages + 1): if num <= left_edge or \ (num > self.page - left_current - 1 and num < self.page + right_current) or \ num > self.pages - right_edge: if last + 1 != num: yield None yield num last = num class BaseQuery(orm.Query): """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`. """ def get_or_404(self, ident, description=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, description=description) return rv def first_or_404(self, description=None): """Like :meth:`first` but aborts with 404 if not found instead of returning ``None``.""" rv = self.first() if rv is None: abort(404, description=description) return rv def paginate(self, page=None, per_page=None, error_out=True, max_per_page=None): """Returns ``per_page`` items from page ``page``. If ``page`` or ``per_page`` are ``None``, they will be retrieved from the request query. If ``max_per_page`` is specified, ``per_page`` will be limited to that value. If there is no request or they aren't in the query, they default to 1 and 20 respectively. When ``error_out`` is ``True`` (default), the following rules will cause a 404 response: * No items are found and ``page`` is not 1. * ``page`` is less than 1, or ``per_page`` is negative. * ``page`` or ``per_page`` are not ints. When ``error_out`` is ``False``, ``page`` and ``per_page`` default to 1 and 20 respectively. Returns a :class:`Pagination` object. """ if request: if page is None: try: page = int(request.args.get('page', 1)) except (TypeError, ValueError): if error_out: abort(404) page = 1 if per_page is None: try: per_page = int(request.args.get('per_page', 20)) except (TypeError, ValueError): if error_out: abort(404) per_page = 20 else: if page is None: page = 1 if per_page is None: per_page = 20 if max_per_page is not None: per_page = min(per_page, max_per_page) if page < 1: if error_out: abort(404) else: page = 1 if per_page < 0: if error_out: abort(404) else: per_page = 20 items = self.limit(per_page).offset((page - 1) * per_page).all() if not items and page != 1 and error_out: abort(404) total = self.order_by(None).count() return Pagination(self, page, per_page, total, items) class _QueryProperty(object): def __init__(self, sa): self.sa = sa def __get__(self, obj, type): try: mapper = orm.class_mapper(type) if mapper: return type.query_class(mapper, session=self.sa.session()) except UnmappedClassError: return None def _record_queries(app): if app.debug: return True rq = app.config['SQLALCHEMY_RECORD_QUERIES'] if rq is not None: return rq return bool(app.config.get('TESTING')) class _EngineConnector(object): def __init__(self, sa, app, bind=None): self._sa = sa self._app = app self._engine = None self._connected_for = None self._bind = bind self._lock = Lock() def get_uri(self): if self._bind is None: return self._app.config['SQLALCHEMY_DATABASE_URI'] binds = self._app.config.get('SQLALCHEMY_BINDS') or () assert self._bind in binds, \ 'Bind %r is not specified. Set it in the SQLALCHEMY_BINDS ' \ 'configuration variable' % self._bind return binds[self._bind] def get_engine(self): with self._lock: uri = self.get_uri() echo = self._app.config['SQLALCHEMY_ECHO'] if (uri, echo) == self._connected_for: return self._engine sa_url = make_url(uri) sa_url, options = self.get_options(sa_url, echo) self._engine = rv = self._sa.create_engine(sa_url, options) if _record_queries(self._app): _EngineDebuggingSignalEvents(self._engine, self._app.import_name).register() self._connected_for = (uri, echo) return rv def get_options(self, sa_url, echo): options = {} options = self._sa.apply_pool_defaults(self._app, options) sa_url, options = self._sa.apply_driver_hacks(self._app, sa_url, options) if echo: options['echo'] = echo # Give the config options set by a developer explicitly priority # over decisions FSA makes. options.update(self._app.config['SQLALCHEMY_ENGINE_OPTIONS']) # Give options set in SQLAlchemy.__init__() ultimate priority options.update(self._sa._engine_options) return sa_url, options def get_state(app): """Gets the state for the application""" assert 'sqlalchemy' in app.extensions, \ 'The sqlalchemy extension was not registered to the current ' \ 'application. Please make sure to call init_app() first.' return app.extensions['sqlalchemy'] class _SQLAlchemyState(object): """Remembers configuration for the (db, app) tuple.""" def __init__(self, db): self.db = db self.connectors = {} class SQLAlchemy(object): """This class is used to control the SQLAlchemy integration to one or more Flask applications. Depending on how you initialize the object it is usable right away or will attach as needed to a Flask application. There are two usage modes which work very similarly. One is binding the instance to a very specific Flask application:: app = Flask(__name__) db = SQLAlchemy(app) The second possibility is to create the object once and configure the application later to support it:: db = SQLAlchemy() def create_app(): app = Flask(__name__) db.init_app(app) return app The difference between the two is that in the first case methods like :meth:`create_all` and :meth:`drop_all` will work all the time but in the second case a :meth:`flask.Flask.app_context` has to exist. By default Flask-SQLAlchemy will apply some backend-specific settings to improve your experience with them. As of SQLAlchemy 0.6 SQLAlchemy will probe the library for native unicode support. If it detects unicode it will let the library handle that, otherwise do that itself. Sometimes this detection can fail in which case you might want to set ``use_native_unicode`` (or the ``SQLALCHEMY_NATIVE_UNICODE`` configuration key) to ``False``. Note that the configuration key overrides the value you pass to the constructor. Direct support for ``use_native_unicode`` and SQLALCHEMY_NATIVE_UNICODE are deprecated as of v2.4 and will be removed in v3.0. ``engine_options`` and ``SQLALCHEMY_ENGINE_OPTIONS`` may be used instead. This class also provides access to all the SQLAlchemy functions and classes from the :mod:`sqlalchemy` and :mod:`sqlalchemy.orm` modules. So you can declare models like this:: class User(db.Model): username = db.Column(db.String(80), unique=True) pw_hash = db.Column(db.String(80)) You can still use :mod:`sqlalchemy` and :mod:`sqlalchemy.orm` directly, but note that Flask-SQLAlchemy customizations are available only through an instance of this :class:`SQLAlchemy` class. Query classes default to :class:`BaseQuery` for `db.Query`, `db.Model.query_class`, and the default query_class for `db.relationship` and `db.backref`. If you use these interfaces through :mod:`sqlalchemy` and :mod:`sqlalchemy.orm` directly, the default query class will be that of :mod:`sqlalchemy`. .. admonition:: Check types carefully Don't perform type or `isinstance` checks against `db.Table`, which emulates `Table` behavior but is not a class. `db.Table` exposes the `Table` interface, but is a function which allows omission of metadata. 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. The ``engine_options`` parameter, if provided, is a dict of parameters to be passed to create engine. See :func:`~sqlalchemy.create_engine` for the standard options. The values given here will be merged with and override anything set in the ``'SQLALCHEMY_ENGINE_OPTIONS'`` config variable or othewise set by this library. .. versionadded:: 0.10 The `session_options` parameter was added. .. versionadded:: 0.16 `scopefunc` is now accepted on `session_options`. It allows specifying a custom function which will define the SQLAlchemy session's scoping. .. versionadded:: 2.1 The `metadata` parameter was added. This allows for setting custom naming conventions among other, non-trivial things. 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:: 2.1 Utilise the same query class across `session`, `Model.query` and `Query`. .. versionadded:: 2.4 The `engine_options` parameter was added. .. versionchanged:: 2.4 The `use_native_unicode` parameter was deprecated. .. versionchanged:: 2.4.3 ``COMMIT_ON_TEARDOWN`` is deprecated and will be removed in version 3.1. Call ``db.session.commit()`` directly instead. """ #: 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, query_class=BaseQuery, model_class=Model, engine_options=None): 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._engine_lock = Lock() self.app = app self._engine_options = engine_options or {} _include_sqlalchemy(self, query_class) if app is not None: self.init_app(app) @property def metadata(self): """The metadata associated with ``db.Model``.""" 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`` """ if options is None: options = {} scopefunc = options.pop('scopefunc', _ident_func) options.setdefault('query_cls', self.Query) return orm.scoped_session( 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`. The default implementation creates a ``sessionmaker`` for :class:`SignallingSession`. :param options: dict of keyword arguments passed to session class """ return orm.sessionmaker(class_=SignallingSession, db=self, **options) def make_declarative_base(self, model, metadata=None): """Creates the declarative base that all models will inherit from. :param model: base model class (or a tuple of base classes) to pass to :func:`~sqlalchemy.ext.declarative.declarative_base`. Or a class returned from ``declarative_base``, in which case a new base class is not created. :param metadata: :class:`~sqlalchemy.MetaData` instance to use, or none to use SQLAlchemy's default. .. versionchanged 2.3.0:: ``model`` can be an existing declarative base in order to support complex customization such as changing the metaclass. """ if not isinstance(model, DeclarativeMeta): model = declarative_base( cls=model, name='Model', metadata=metadata, metaclass=DefaultMeta ) # if user passed in a declarative base and a metaclass for some reason, # make sure the base uses the metaclass if metadata is not None and model.metadata is not metadata: model.metadata = metadata if not getattr(model, 'query_class', None): model.query_class = self.Query model.query = _QueryProperty(self) return model def init_app(self, app): """This callback can be used to initialize an application for the use with this database setup. Never use a database in the context of an application not initialized that way or connections will leak. """ if ( 'SQLALCHEMY_DATABASE_URI' not in app.config and 'SQLALCHEMY_BINDS' not in app.config ): warnings.warn( 'Neither SQLALCHEMY_DATABASE_URI nor SQLALCHEMY_BINDS is set. ' 'Defaulting SQLALCHEMY_DATABASE_URI to "sqlite:///:memory:".' ) app.config.setdefault('SQLALCHEMY_DATABASE_URI', 'sqlite:///:memory:') app.config.setdefault('SQLALCHEMY_BINDS', None) app.config.setdefault('SQLALCHEMY_NATIVE_UNICODE', None) app.config.setdefault('SQLALCHEMY_ECHO', False) app.config.setdefault('SQLALCHEMY_RECORD_QUERIES', None) app.config.setdefault('SQLALCHEMY_POOL_SIZE', None) app.config.setdefault('SQLALCHEMY_POOL_TIMEOUT', None) app.config.setdefault('SQLALCHEMY_POOL_RECYCLE', None) app.config.setdefault('SQLALCHEMY_MAX_OVERFLOW', None) app.config.setdefault('SQLALCHEMY_COMMIT_ON_TEARDOWN', False) track_modifications = app.config.setdefault( 'SQLALCHEMY_TRACK_MODIFICATIONS', None ) app.config.setdefault('SQLALCHEMY_ENGINE_OPTIONS', {}) 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.' )) # Deprecation warnings for config keys that should be replaced by SQLALCHEMY_ENGINE_OPTIONS. utils.engine_config_warning(app.config, '3.0', 'SQLALCHEMY_POOL_SIZE', 'pool_size') utils.engine_config_warning(app.config, '3.0', 'SQLALCHEMY_POOL_TIMEOUT', 'pool_timeout') utils.engine_config_warning(app.config, '3.0', 'SQLALCHEMY_POOL_RECYCLE', 'pool_recycle') utils.engine_config_warning(app.config, '3.0', 'SQLALCHEMY_MAX_OVERFLOW', 'max_overflow') app.extensions['sqlalchemy'] = _SQLAlchemyState(self) @app.teardown_appcontext def shutdown_session(response_or_exc): if app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN']: warnings.warn( "'COMMIT_ON_TEARDOWN' is deprecated and will be" " removed in version 3.1. Call" " 'db.session.commit()'` directly instead.", DeprecationWarning, ) if response_or_exc is None: self.session.commit() self.session.remove() return response_or_exc def apply_pool_defaults(self, app, options): """ .. versionchanged:: 2.5 Returns the ``options`` dict, for consistency with :meth:`apply_driver_hacks`. """ def _setdefault(optionkey, configkey): value = app.config[configkey] if value is not None: options[optionkey] = value _setdefault('pool_size', 'SQLALCHEMY_POOL_SIZE') _setdefault('pool_timeout', 'SQLALCHEMY_POOL_TIMEOUT') _setdefault('pool_recycle', 'SQLALCHEMY_POOL_RECYCLE') _setdefault('max_overflow', 'SQLALCHEMY_MAX_OVERFLOW') return options def apply_driver_hacks(self, app, sa_url, options): """This method is called before engine creation and used to inject driver specific hacks into the options. The `options` parameter is a dictionary of keyword arguments that will then be used to call the :func:`sqlalchemy.create_engine` function. The default implementation provides some saner defaults for things like pool sizes for MySQL and sqlite. Also it injects the setting of `SQLALCHEMY_NATIVE_UNICODE`. .. versionchanged:: 2.5 Returns ``(sa_url, options)``. SQLAlchemy 1.4 made the URL immutable, so any changes to it must now be passed back up to the original caller. """ if sa_url.drivername.startswith('mysql'): sa_url = _sa_url_query_setdefault(sa_url, charset="utf8") if sa_url.drivername != 'mysql+gaerdbms': options.setdefault('pool_size', 10) options.setdefault('pool_recycle', 7200) elif sa_url.drivername == 'sqlite': pool_size = options.get('pool_size') detected_in_memory = False if sa_url.database in (None, '', ':memory:'): detected_in_memory = True from sqlalchemy.pool import StaticPool options['poolclass'] = StaticPool if 'connect_args' not in options: 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 ' 'loss.') # if pool size is None or explicitly set to 0 we assume the # user did not want a queue for this sqlite connection and # hook in the null pool. elif not pool_size: from sqlalchemy.pool import NullPool options['poolclass'] = NullPool # if it's not an in memory database we make the path absolute. if not detected_in_memory: sa_url = _sa_url_set( sa_url, database=os.path.join(app.root_path, sa_url.database) ) unu = app.config['SQLALCHEMY_NATIVE_UNICODE'] if unu is None: unu = self.use_native_unicode if not unu: options['use_native_unicode'] = False if app.config['SQLALCHEMY_NATIVE_UNICODE'] is not None: warnings.warn( "The 'SQLALCHEMY_NATIVE_UNICODE' config option is deprecated and will be removed in" " v3.0. Use 'SQLALCHEMY_ENGINE_OPTIONS' instead.", DeprecationWarning ) if not self.use_native_unicode: warnings.warn( "'use_native_unicode' is deprecated and will be removed in v3.0." " Use the 'engine_options' parameter instead.", DeprecationWarning ) return sa_url, options @property def engine(self): """Gives access to the engine. If the database configuration is bound to a specific application (initialized with an application) this will always return a database connection. If however the current application is used this might raise a :exc:`RuntimeError` if no application is active at the moment. """ return self.get_engine() def make_connector(self, app=None, 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.""" app = self.get_app(app) state = get_state(app) with self._engine_lock: connector = state.connectors.get(bind) if connector is None: connector = self.make_connector(app, bind) state.connectors[bind] = connector return connector.get_engine() def create_engine(self, sa_url, engine_opts): """ Override this method to have final say over how the SQLAlchemy engine is created. In most cases, you will want to use ``'SQLALCHEMY_ENGINE_OPTIONS'`` config variable or set ``engine_options`` for :func:`SQLAlchemy`. """ return sqlalchemy.create_engine(sa_url, **engine_opts) def get_app(self, reference_app=None): """Helper method that implements the logic to look up an application.""" if reference_app is not None: return reference_app if current_app: return current_app._get_current_object() if self.app is not None: return self.app raise RuntimeError( 'No application found. Either work inside a view function or push' ' an application context. See' ' http://flask-sqlalchemy.pocoo.org/contexts/.' ) def get_tables_for_bind(self, bind=None): """Returns a list of all tables relevant for a bind.""" result = [] for table in itervalues(self.Model.metadata.tables): if table.info.get('bind_key') == bind: result.append(table) return result def get_binds(self, app=None): """Returns a dictionary with a table->engine mapping. This is suitable for use of sessionmaker(binds=db.get_binds(app)). """ app = self.get_app(app) binds = [None] + list(app.config.get('SQLALCHEMY_BINDS') or ()) retval = {} for bind in binds: engine = self.get_engine(app, bind) tables = self.get_tables_for_bind(bind) retval.update(dict((table, engine) for table in tables)) return retval def _execute_for_all_tables(self, app, bind, operation, skip_tables=False): app = self.get_app(app) if bind == '__all__': binds = [None] + list(app.config.get('SQLALCHEMY_BINDS') or ()) elif isinstance(bind, string_types) or bind is None: binds = [bind] else: binds = bind for bind in binds: extra = {} if not skip_tables: tables = self.get_tables_for_bind(bind) extra['tables'] = tables op = getattr(self.Model.metadata, operation) op(bind=self.get_engine(app, bind), **extra) def create_all(self, bind='__all__', app=None): """Creates all tables. .. versionchanged:: 0.12 Parameters were added """ self._execute_for_all_tables(app, bind, 'create_all') def drop_all(self, bind='__all__', app=None): """Drops all tables. .. versionchanged:: 0.12 Parameters were added """ self._execute_for_all_tables(app, bind, 'drop_all') def reflect(self, bind='__all__', app=None): """Reflects tables from the database. .. versionchanged:: 0.12 Parameters were added """ self._execute_for_all_tables(app, bind, 'reflect', skip_tables=True) def __repr__(self): return '<%s engine=%r>' % ( self.__class__.__name__, self.engine.url if self.app or current_app else None ) class _BoundDeclarativeMeta(DefaultMeta): def __init__(cls, name, bases, d): warnings.warn(FSADeprecationWarning( '"_BoundDeclarativeMeta" has been renamed to "DefaultMeta". The' ' old name will be removed in 3.0.' ), stacklevel=3) super(_BoundDeclarativeMeta, cls).__init__(name, bases, d) class FSADeprecationWarning(DeprecationWarning): pass warnings.simplefilter('always', FSADeprecationWarning) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/flask_sqlalchemy/_compat.py0000644000175000017500000000146500000000000021004 0ustar00daviddavidimport sys PY2 = sys.version_info[0] == 2 if PY2: def iteritems(d): return d.iteritems() def itervalues(d): return d.itervalues() xrange = xrange string_types = (unicode, bytes) def to_str(x, charset='utf8', errors='strict'): if x is None or isinstance(x, str): return x if isinstance(x, unicode): return x.encode(charset, errors) return str(x) else: def iteritems(d): return iter(d.items()) def itervalues(d): return iter(d.values()) xrange = range string_types = (str,) def to_str(x, charset='utf8', errors='strict'): if x is None or isinstance(x, str): return x if isinstance(x, bytes): return x.decode(charset, errors) return str(x) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/flask_sqlalchemy/model.py0000644000175000017500000001157400000000000020464 0ustar00daviddavidimport re import sqlalchemy as sa from sqlalchemy import inspect from sqlalchemy.ext.declarative import DeclarativeMeta, declared_attr from sqlalchemy.schema import _get_table_key from ._compat import to_str def should_set_tablename(cls): """Determine whether ``__tablename__`` should be automatically generated for a model. * If no class in the MRO sets a name, one should be generated. * If a declared attr is found, it should be used instead. * If a name is found, it should be used if the class is a mixin, otherwise one should be generated. * Abstract models should not have one generated. Later, :meth:`._BoundDeclarativeMeta.__table_cls__` will determine if the model looks like single or joined-table inheritance. If no primary key is found, the name will be unset. """ if ( cls.__dict__.get('__abstract__', False) or not any(isinstance(b, DeclarativeMeta) for b in cls.__mro__[1:]) ): return False for base in cls.__mro__: if '__tablename__' not in base.__dict__: continue if isinstance(base.__dict__['__tablename__'], declared_attr): return False return not ( base is cls or base.__dict__.get('__abstract__', False) or not isinstance(base, DeclarativeMeta) ) return True camelcase_re = re.compile(r'([A-Z]+)(?=[a-z0-9])') 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 NameMetaMixin(type): def __init__(cls, name, bases, d): if should_set_tablename(cls): cls.__tablename__ = camel_to_snake_case(cls.__name__) super(NameMetaMixin, cls).__init__(name, bases, d) # __table_cls__ has run at this point # if no table was created, use the parent table if ( '__tablename__' not in cls.__dict__ and '__table__' in cls.__dict__ and cls.__dict__['__table__'] is None ): del cls.__table__ def __table_cls__(cls, *args, **kwargs): """This is called by SQLAlchemy during mapper setup. It determines the final table object that the model will use. If no primary key is found, that indicates single-table inheritance, so no table will be created and ``__tablename__`` will be unset. """ # check if a table with this name already exists # allows reflected tables to be applied to model by name key = _get_table_key(args[0], kwargs.get('schema')) if key in cls.metadata.tables: return sa.Table(*args, **kwargs) # if a primary key or constraint is found, create a table for # joined-table inheritance for arg in args: if ( (isinstance(arg, sa.Column) and arg.primary_key) or isinstance(arg, sa.PrimaryKeyConstraint) ): return sa.Table(*args, **kwargs) # if no base classes define a table, return one # ensures the correct error shows up when missing a primary key for base in cls.__mro__[1:-1]: if '__table__' in base.__dict__: break else: return sa.Table(*args, **kwargs) # single-table inheritance, use the parent tablename if '__tablename__' in cls.__dict__: del cls.__tablename__ class BindMetaMixin(type): def __init__(cls, name, bases, d): bind_key = ( d.pop('__bind_key__', None) or getattr(cls, '__bind_key__', None) ) super(BindMetaMixin, cls).__init__(name, bases, d) if bind_key is not None and getattr(cls, '__table__', None) is not None: cls.__table__.info['bind_key'] = bind_key class DefaultMeta(NameMetaMixin, BindMetaMixin, DeclarativeMeta): pass class Model(object): """Base class for SQLAlchemy declarative base model. To define models, subclass :attr:`db.Model `, not this class. To customize ``db.Model``, subclass this and pass it as ``model_class`` to :class:`SQLAlchemy`. """ #: Query class used by :attr:`query`. Defaults to # :class:`SQLAlchemy.Query`, which defaults to :class:`BaseQuery`. query_class = None #: 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. query = None def __repr__(self): identity = inspect(self).identity if identity is None: pk = "(transient {0})".format(id(self)) else: pk = ', '.join(to_str(value) for value in identity) return '<{0} {1}>'.format(type(self).__name__, pk) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/flask_sqlalchemy/utils.py0000644000175000017500000000255600000000000020524 0ustar00daviddavidimport warnings import sqlalchemy def parse_version(v): """ Take a string version and conver it to a tuple (for easier comparison), e.g.: "1.2.3" --> (1, 2, 3) "1.2" --> (1, 2, 0) "1" --> (1, 0, 0) """ parts = v.split(".") # Pad the list to make sure there is three elements so that we get major, minor, point # comparisons that default to "0" if not given. I.e. "1.2" --> (1, 2, 0) parts = (parts + 3 * ['0'])[:3] return tuple(int(x) for x in parts) def sqlalchemy_version(op, val): sa_ver = parse_version(sqlalchemy.__version__) target_ver = parse_version(val) assert op in ('<', '>', '<=', '>=', '=='), 'op {} not supported'.format(op) if op == '<': return sa_ver < target_ver if op == '>': return sa_ver > target_ver if op == '<=': return sa_ver <= target_ver if op == '>=': return sa_ver >= target_ver return sa_ver == target_ver def engine_config_warning(config, version, deprecated_config_key, engine_option): if config[deprecated_config_key] is not None: warnings.warn( 'The `{}` config option is deprecated and will be removed in' ' v{}. Use `SQLALCHEMY_ENGINE_OPTIONS[\'{}\']` instead.' .format(deprecated_config_key, version, engine_option), DeprecationWarning ) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1616094138.032466 Flask-SQLAlchemy-2.5.1/setup.cfg0000644000175000017500000000052700000000000015305 0ustar00daviddavid[metadata] license_file = LICENSE.rst [bdist_wheel] universal = true [tool:pytest] testpaths = tests [coverage:run] branch = True source = flask_sqlalchemy tests [coverage:paths] source = flask_sqlalchemy .tox/*/lib/python*/site-packages/flask_sqlalchemy .tox/*/site-packages/flask_sqlalchemy [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/setup.py0000644000175000017500000000375200000000000015201 0ustar00daviddavidimport io import re from setuptools import setup with io.open("README.rst", "rt", encoding="utf8") as f: readme = f.read() with io.open("flask_sqlalchemy/__init__.py", "rt", encoding="utf8") as f: version = re.search(r'__version__ = "(.*?)"', f.read(), re.M).group(1) setup( name="Flask-SQLAlchemy", version=version, url="https://github.com/pallets/flask-sqlalchemy", project_urls={ "Documentation": "https://flask-sqlalchemy.palletsprojects.com/", "Code": "https://github.com/pallets/flask-sqlalchemy", "Issue tracker": "https://github.com/pallets/flask-sqlalchemy/issues", }, license="BSD-3-Clause", author="Armin Ronacher", author_email="armin.ronacher@active-4.com", maintainer="Pallets", maintainer_email="contact@palletsprojects.com", description="Adds SQLAlchemy support to your Flask application.", long_description=readme, packages=["flask_sqlalchemy"], include_package_data=True, python_requires=">= 2.7, != 3.0.*, != 3.1.*, != 3.2.*, != 3.3.*", install_requires=["Flask>=0.10", "SQLAlchemy>=0.8.0"], classifiers=[ "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Software Development :: Libraries :: Python Modules", ], ) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1616094138.032466 Flask-SQLAlchemy-2.5.1/tests/0000755000175000017500000000000000000000000014622 5ustar00daviddavid././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/tests/conftest.py0000644000175000017500000000165000000000000017023 0ustar00daviddavidfrom datetime import datetime import flask import pytest import flask_sqlalchemy as fsa @pytest.fixture def app(request): app = flask.Flask(request.module.__name__) app.testing = True app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False return app @pytest.fixture def db(app): return fsa.SQLAlchemy(app) @pytest.fixture def Todo(db): class Todo(db.Model): __tablename__ = 'todos' id = db.Column('todo_id', db.Integer, primary_key=True) title = db.Column(db.String(60)) text = db.Column(db.String) done = db.Column(db.Boolean) pub_date = db.Column(db.DateTime) def __init__(self, title, text): self.title = title self.text = text self.done = False self.pub_date = datetime.utcnow() db.create_all() yield Todo db.drop_all() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/tests/test_basic_app.py0000644000175000017500000000333100000000000020154 0ustar00daviddavidimport flask import flask_sqlalchemy as fsa def test_basic_insert(app, db, Todo): @app.route('/') def index(): return '\n'.join(x.title for x in Todo.query.all()) @app.route('/add', methods=['POST']) def add(): form = flask.request.form todo = Todo(form['title'], form['text']) db.session.add(todo) db.session.commit() return 'added' c = app.test_client() c.post('/add', data=dict(title='First Item', text='The text')) c.post('/add', data=dict(title='2nd Item', text='The text')) rv = c.get('/') assert rv.data == b'First Item\n2nd Item' def test_query_recording(app, db, Todo): with app.test_request_context(): todo = Todo('Test 1', 'test') db.session.add(todo) db.session.flush() todo.done = True db.session.commit() queries = fsa.get_debug_queries() assert len(queries) == 2 query = queries[0] assert 'insert into' in query.statement.lower() assert query.parameters[0] == 'Test 1' assert query.parameters[1] == 'test' assert 'test_basic_app.py' in query.context assert 'test_query_recording' in query.context query = queries[1] assert 'update' in query.statement.lower() assert query.parameters[0] == 1 assert query.parameters[1] == 1 def test_helper_api(db): assert db.metadata == db.Model.metadata def test_persist_selectable(app, db, Todo, recwarn): """ In SA 1.3, mapper.mapped_table should be replaced with mapper.persist_selectable """ with app.test_request_context(): todo = Todo('Test 1', 'test') db.session.add(todo) db.session.commit() assert len(recwarn) == 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/tests/test_binds.py0000644000175000017500000000674500000000000017346 0ustar00daviddavidimport flask_sqlalchemy as fsa from sqlalchemy.ext.declarative import declared_attr def test_basic_binds(app, db): app.config['SQLALCHEMY_BINDS'] = { 'foo': 'sqlite://', 'bar': 'sqlite://' } class Foo(db.Model): __bind_key__ = 'foo' __table_args__ = {"info": {"bind_key": "foo"}} id = db.Column(db.Integer, primary_key=True) class Bar(db.Model): __bind_key__ = 'bar' id = db.Column(db.Integer, primary_key=True) class Baz(db.Model): id = db.Column(db.Integer, primary_key=True) db.create_all() # simple way to check if the engines are looked up properly assert db.get_engine(app, None) == db.engine for key in 'foo', 'bar': engine = db.get_engine(app, key) connector = app.extensions['sqlalchemy'].connectors[key] assert engine == connector.get_engine() assert str(engine.url) == app.config['SQLALCHEMY_BINDS'][key] # do the models have the correct engines? assert db.metadata.tables['foo'].info['bind_key'] == 'foo' assert db.metadata.tables['bar'].info['bind_key'] == 'bar' assert db.metadata.tables['baz'].info.get('bind_key') is None # see the tables created in an engine metadata = db.MetaData() metadata.reflect(bind=db.get_engine(app, 'foo')) assert len(metadata.tables) == 1 assert 'foo' in metadata.tables metadata = db.MetaData() metadata.reflect(bind=db.get_engine(app, 'bar')) assert len(metadata.tables) == 1 assert 'bar' in metadata.tables metadata = db.MetaData() metadata.reflect(bind=db.get_engine(app)) assert len(metadata.tables) == 1 assert 'baz' in metadata.tables # do the session have the right binds set? assert db.get_binds(app) == { Foo.__table__: db.get_engine(app, 'foo'), Bar.__table__: db.get_engine(app, 'bar'), Baz.__table__: db.get_engine(app, None) } def test_abstract_binds(app, db): app.config['SQLALCHEMY_BINDS'] = { 'foo': 'sqlite://' } 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? assert 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')) assert len(metadata.tables) == 1 assert 'foo_bound_model' in metadata.tables def test_connector_cache(app): db = fsa.SQLAlchemy() db.init_app(app) with app.app_context(): db.get_engine() connector = fsa.get_state(app).connectors[None] assert connector._app is app def test_polymorphic_bind(app, db): bind_key = 'polymorphic_bind_key' app.config['SQLALCHEMY_BINDS'] = { bind_key: 'sqlite:///:memory', } class Base(db.Model): __bind_key__ = bind_key __tablename__ = 'base' id = db.Column(db.Integer, primary_key=True) p_type = db.Column(db.String(50)) __mapper_args__ = { 'polymorphic_identity': 'base', 'polymorphic_on': p_type } class Child1(Base): child_1_data = db.Column(db.String(50)) __mapper_args__ = { 'polymorphic_identity': 'child_1', } assert Base.__table__.info['bind_key'] == bind_key assert Child1.__table__.info['bind_key'] == bind_key ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/tests/test_commit_on_teardown.py0000644000175000017500000000173100000000000022124 0ustar00daviddavidimport flask import pytest @pytest.fixture def client(app, db, Todo): app.testing = False app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True @app.route('/') def index(): return '\n'.join(x.title for x in Todo.query.all()) @app.route('/create', methods=['POST']) def create(): db.session.add(Todo('Test one', 'test')) if flask.request.form.get('fail'): raise RuntimeError("Failing as requested") return 'ok' return app.test_client() def test_commit_on_success(client): with pytest.warns(DeprecationWarning, match="COMMIT_ON_TEARDOWN"): resp = client.post('/create') assert resp.status_code == 200 assert client.get('/').data == b'Test one' def test_roll_back_on_failure(client): with pytest.warns(DeprecationWarning, match="COMMIT_ON_TEARDOWN"): resp = client.post('/create', data={'fail': 'on'}) assert resp.status_code == 500 assert client.get('/').data == b'' ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/tests/test_config.py0000644000175000017500000002204500000000000017503 0ustar00daviddavidimport os import mock import pytest from sqlalchemy.pool import NullPool import flask_sqlalchemy as fsa from flask_sqlalchemy import _compat, utils @pytest.fixture def app_nr(app): """ Signal/event registration with record queries breaks when sqlalchemy.create_engine() is mocked out. """ app.config['SQLALCHEMY_RECORD_QUERIES'] = False return app class TestConfigKeys: def test_defaults(self, app): """ Test all documented config values in the order they appear in our documentation: http://flask-sqlalchemy.pocoo.org/dev/config/ """ # Our pytest fixture for creating the app sets # SQLALCHEMY_TRACK_MODIFICATIONS, so we undo that here so that we # can inspect what FSA does below: del app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] with pytest.warns(fsa.FSADeprecationWarning) as records: fsa.SQLAlchemy(app) # Only expecting one warning for the track modifications deprecation. assert len(records) == 1 assert app.config['SQLALCHEMY_DATABASE_URI'] == 'sqlite:///:memory:' assert app.config['SQLALCHEMY_BINDS'] is None assert app.config['SQLALCHEMY_ECHO'] is False assert app.config['SQLALCHEMY_RECORD_QUERIES'] is None assert app.config['SQLALCHEMY_NATIVE_UNICODE'] is None assert app.config['SQLALCHEMY_POOL_SIZE'] is None assert app.config['SQLALCHEMY_POOL_TIMEOUT'] is None assert app.config['SQLALCHEMY_POOL_RECYCLE'] is None assert app.config['SQLALCHEMY_MAX_OVERFLOW'] is None assert app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] is None assert app.config['SQLALCHEMY_ENGINE_OPTIONS'] == {} def test_track_modifications_warning(self, app, recwarn): # pytest fixuture sets SQLALCHEMY_TRACK_MODIFICATIONS = False fsa.SQLAlchemy(app) # So we shouldn't have any warnings assert len(recwarn) == 0 # Let's trigger the warning app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = None fsa.SQLAlchemy(app) # and verify it showed up as expected assert len(recwarn) == 1 expect = '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.' assert recwarn[0].message.args[0] == expect def test_uri_binds_warning(self, app, recwarn): # Let's trigger the warning del app.config['SQLALCHEMY_DATABASE_URI'] fsa.SQLAlchemy(app) # and verify it showed up as expected assert len(recwarn) == 1 expect = 'Neither SQLALCHEMY_DATABASE_URI nor SQLALCHEMY_BINDS' \ ' is set. Defaulting SQLALCHEMY_DATABASE_URI to'\ ' "sqlite:///:memory:".' assert recwarn[0].message.args[0] == expect def test_engine_creation_ok(self, app, recwarn): """ create_engine() isn't called until needed. Let's make sure we can do that without errors or warnings. """ assert fsa.SQLAlchemy(app).get_engine() if utils.sqlalchemy_version('==', '0.8.0') and not _compat.PY2: # In CI, we test Python 3.6 and SA 0.8.0, which produces a warning for # inspect.getargspec() expected_warnings = 1 else: expected_warnings = 0 assert len(recwarn) == expected_warnings @mock.patch.object(fsa.sqlalchemy, 'create_engine', autospec=True, spec_set=True) def test_native_unicode_deprecation_config_opt(self, m_create_engine, app_nr, recwarn): app_nr.config['SQLALCHEMY_NATIVE_UNICODE'] = False assert fsa.SQLAlchemy(app_nr).get_engine() assert len(recwarn) == 1 warning_msg = recwarn[0].message.args[0] assert 'SQLALCHEMY_NATIVE_UNICODE' in warning_msg assert 'deprecated and will be removed in v3.0' in warning_msg @mock.patch.object(fsa.sqlalchemy, 'create_engine', autospec=True, spec_set=True) def test_native_unicode_deprecation_init_opt(self, m_create_engine, app_nr, recwarn): assert fsa.SQLAlchemy(app_nr, use_native_unicode=False).get_engine() assert len(recwarn) == 1 warning_msg = recwarn[0].message.args[0] assert 'use_native_unicode' in warning_msg assert 'deprecated and will be removed in v3.0' in warning_msg @mock.patch.object(fsa.sqlalchemy, 'create_engine', autospec=True, spec_set=True) def test_deprecation_config_opt_pool_size(self, m_create_engine, app_nr, recwarn): app_nr.config['SQLALCHEMY_POOL_SIZE'] = 5 assert fsa.SQLAlchemy(app_nr).get_engine() assert len(recwarn) == 1 warning_msg = recwarn[0].message.args[0] assert 'SQLALCHEMY_POOL_SIZE' in warning_msg assert 'deprecated and will be removed in v3.0.' in warning_msg assert 'pool_size' in warning_msg @mock.patch.object(fsa.sqlalchemy, 'create_engine', autospec=True, spec_set=True) def test_deprecation_config_opt_pool_timeout(self, m_create_engine, app_nr, recwarn): app_nr.config['SQLALCHEMY_POOL_TIMEOUT'] = 5 assert fsa.SQLAlchemy(app_nr).get_engine() assert len(recwarn) == 1 warning_msg = recwarn[0].message.args[0] assert 'SQLALCHEMY_POOL_TIMEOUT' in warning_msg assert 'deprecated and will be removed in v3.0.' in warning_msg assert 'pool_timeout' in warning_msg @mock.patch.object(fsa.sqlalchemy, 'create_engine', autospec=True, spec_set=True) def test_deprecation_config_opt_pool_recycle(self, m_create_engine, app_nr, recwarn): app_nr.config['SQLALCHEMY_POOL_RECYCLE'] = 5 assert fsa.SQLAlchemy(app_nr).get_engine() assert len(recwarn) == 1 warning_msg = recwarn[0].message.args[0] assert 'SQLALCHEMY_POOL_RECYCLE' in warning_msg assert 'deprecated and will be removed in v3.0.' in warning_msg assert 'pool_recycle' in warning_msg @mock.patch.object(fsa.sqlalchemy, 'create_engine', autospec=True, spec_set=True) def test_deprecation_config_opt_max_overflow(self, m_create_engine, app_nr, recwarn): app_nr.config['SQLALCHEMY_MAX_OVERFLOW'] = 5 assert fsa.SQLAlchemy(app_nr).get_engine() assert len(recwarn) == 1 warning_msg = recwarn[0].message.args[0] assert 'SQLALCHEMY_MAX_OVERFLOW' in warning_msg assert 'deprecated and will be removed in v3.0.' in warning_msg assert 'max_overflow' in warning_msg @mock.patch.object(fsa.sqlalchemy, 'create_engine', autospec=True, spec_set=True) class TestCreateEngine: """ Tests for _EngineConnector and SQLAlchemy methods inolved in setting up the SQLAlchemy engine. """ def test_engine_echo_default(self, m_create_engine, app_nr): fsa.SQLAlchemy(app_nr).get_engine() args, options = m_create_engine.call_args assert 'echo' not in options def test_engine_echo_true(self, m_create_engine, app_nr): app_nr.config['SQLALCHEMY_ECHO'] = True fsa.SQLAlchemy(app_nr).get_engine() args, options = m_create_engine.call_args assert options['echo'] is True @mock.patch.object(fsa.utils, 'sqlalchemy') def test_convert_unicode_default_sa_13(self, m_sqlalchemy, m_create_engine, app_nr): m_sqlalchemy.__version__ = '1.3' fsa.SQLAlchemy(app_nr).get_engine() args, options = m_create_engine.call_args assert 'convert_unicode' not in options def test_config_from_engine_options(self, m_create_engine, app_nr): app_nr.config['SQLALCHEMY_ENGINE_OPTIONS'] = {'foo': 'bar'} fsa.SQLAlchemy(app_nr).get_engine() args, options = m_create_engine.call_args assert options['foo'] == 'bar' def test_config_from_init(self, m_create_engine, app_nr): fsa.SQLAlchemy(app_nr, engine_options={'bar': 'baz'}).get_engine() args, options = m_create_engine.call_args assert options['bar'] == 'baz' def test_pool_class_default(self, m_create_engine, app_nr): fsa.SQLAlchemy(app_nr).get_engine() args, options = m_create_engine.call_args assert options['poolclass'].__name__ == 'StaticPool' def test_pool_class_with_pool_size_zero(self, m_create_engine, app_nr, recwarn): app_nr.config['SQLALCHEMY_POOL_SIZE'] = 0 with pytest.raises(RuntimeError) as exc_info: fsa.SQLAlchemy(app_nr).get_engine() expected = 'SQLite in memory database with an empty queue not possible due to data loss.' assert exc_info.value.args[0] == expected def test_pool_class_nullpool(self, m_create_engine, app_nr): engine_options = {'poolclass': NullPool} fsa.SQLAlchemy(app_nr, engine_options=engine_options).get_engine() args, options = m_create_engine.call_args assert options['poolclass'].__name__ == 'NullPool' assert 'pool_size' not in options def test_sqlite_relative_to_app_root(app): app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///test.db" db = fsa.SQLAlchemy(app) assert db.engine.url.database == os.path.join(app.root_path, "test.db") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/tests/test_meta_data.py0000644000175000017500000000303400000000000020152 0ustar00daviddavidimport sqlalchemy as sa import flask_sqlalchemy as fsa def test_default_metadata(app): db = fsa.SQLAlchemy(app, metadata=None) class One(db.Model): id = db.Column(db.Integer, primary_key=True) myindex = db.Column(db.Integer, index=True) class Two(db.Model): id = db.Column(db.Integer, primary_key=True) one_id = db.Column(db.Integer, db.ForeignKey(One.id)) myunique = db.Column(db.Integer, unique=True) assert One.metadata.__class__ is sa.MetaData assert Two.metadata.__class__ is sa.MetaData assert One.__table__.schema is None assert Two.__table__.schema is None def test_custom_metadata(app): class CustomMetaData(sa.MetaData): pass custom_metadata = CustomMetaData(schema="test_schema") db = fsa.SQLAlchemy(app, metadata=custom_metadata) class One(db.Model): id = db.Column(db.Integer, primary_key=True) myindex = db.Column(db.Integer, index=True) class Two(db.Model): id = db.Column(db.Integer, primary_key=True) one_id = db.Column(db.Integer, db.ForeignKey(One.id)) myunique = db.Column(db.Integer, unique=True) assert One.metadata is custom_metadata assert Two.metadata is custom_metadata assert One.metadata.__class__ is not sa.MetaData assert One.metadata.__class__ is CustomMetaData assert Two.metadata.__class__ is not sa.MetaData assert Two.metadata.__class__ is CustomMetaData assert One.__table__.schema == "test_schema" assert Two.__table__.schema == "test_schema" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/tests/test_model_class.py0000644000175000017500000000347000000000000020524 0ustar00daviddavid# coding=utf8 import pytest from sqlalchemy.exc import InvalidRequestError from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base import flask_sqlalchemy as fsa from flask_sqlalchemy._compat import to_str from flask_sqlalchemy.model import BindMetaMixin def test_custom_model_class(): class CustomModelClass(fsa.Model): pass db = fsa.SQLAlchemy(model_class=CustomModelClass) class SomeModel(db.Model): id = db.Column(db.Integer, primary_key=True) assert isinstance(SomeModel(), CustomModelClass) def test_no_table_name(): class NoNameMeta(BindMetaMixin, DeclarativeMeta): pass db = fsa.SQLAlchemy(model_class=declarative_base( cls=fsa.Model, metaclass=NoNameMeta, name='Model')) with pytest.raises(InvalidRequestError): class User(db.Model): pass def test_repr(db): class User(db.Model): name = db.Column(db.String, primary_key=True) class Report(db.Model): id = db.Column(db.Integer, primary_key=True, autoincrement=False) user_name = db.Column(db.ForeignKey(User.name), primary_key=True) db.create_all() u = User(name='test') assert repr(u).startswith("' assert repr(u) == str(u) u2 = User(name=u'🐍') db.session.add(u2) db.session.flush() assert repr(u2) == to_str(u'') assert repr(u2) == str(u2) r = Report(id=2, user_name=u.name) db.session.add(r) db.session.flush() assert repr(r) == '' assert repr(u) == str(u) def test_deprecated_meta(): class OldMeta(fsa._BoundDeclarativeMeta): pass with pytest.warns(fsa.FSADeprecationWarning): declarative_base(cls=fsa.Model, metaclass=OldMeta) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/tests/test_pagination.py0000644000175000017500000000371300000000000020370 0ustar00daviddavidimport pytest from werkzeug.exceptions import NotFound import flask_sqlalchemy as fsa def test_basic_pagination(): p = fsa.Pagination(None, 1, 20, 500, []) assert p.page == 1 assert not p.has_prev assert p.has_next assert p.total == 500 assert p.pages == 25 assert p.next_num == 2 assert list(p.iter_pages()) == [1, 2, 3, 4, 5, None, 24, 25] p.page = 10 assert list(p.iter_pages()) == [1, 2, None, 8, 9, 10, 11, 12, 13, 14, None, 24, 25] def test_pagination_pages_when_0_items_per_page(): p = fsa.Pagination(None, 1, 0, 500, []) assert p.pages == 0 def test_query_paginate(app, db, Todo): with app.app_context(): db.session.add_all([Todo('', '') for _ in range(100)]) db.session.commit() @app.route('/') def index(): p = Todo.query.paginate() return '{0} items retrieved'.format(len(p.items)) c = app.test_client() # request default r = c.get('/') assert r.status_code == 200 # request args r = c.get('/?per_page=10') assert r.data.decode('utf8') == '10 items retrieved' with app.app_context(): # query default p = Todo.query.paginate() assert p.total == 100 def test_query_paginate_more_than_20(app, db, Todo): with app.app_context(): db.session.add_all(Todo('', '') for _ in range(20)) db.session.commit() assert len(Todo.query.paginate(max_per_page=10).items) == 10 def test_paginate_min(app, db, Todo): with app.app_context(): db.session.add_all(Todo(str(x), '') for x in range(20)) db.session.commit() assert Todo.query.paginate(error_out=False, page=-1).items[0].title == '0' assert len(Todo.query.paginate(error_out=False, per_page=0).items) == 0 assert len(Todo.query.paginate(error_out=False, per_page=-1).items) == 20 with pytest.raises(NotFound): Todo.query.paginate(page=0) with pytest.raises(NotFound): Todo.query.paginate(per_page=-1) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/tests/test_query_class.py0000644000175000017500000000337700000000000020577 0ustar00daviddavidimport flask_sqlalchemy as fsa def test_default_query_class(db): 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 assert type(Parent.query) == fsa.BaseQuery assert type(Child.query) == fsa.BaseQuery assert isinstance(p.children, fsa.BaseQuery) assert isinstance(db.session.query(Parent), fsa.BaseQuery) def test_custom_query_class(app): class CustomQueryClass(fsa.BaseQuery): pass db = fsa.SQLAlchemy(app, query_class=CustomQueryClass) 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 assert type(Parent.query) == CustomQueryClass assert type(Child.query) == CustomQueryClass assert isinstance(p.children, CustomQueryClass) assert db.Query == CustomQueryClass assert db.Model.query_class == CustomQueryClass assert isinstance(db.session.query(Parent), CustomQueryClass) def test_dont_override_model_default(app): class CustomQueryClass(fsa.BaseQuery): pass db = fsa.SQLAlchemy(app, query_class=CustomQueryClass) class SomeModel(db.Model): id = db.Column(db.Integer, primary_key=True) query_class = fsa.BaseQuery assert type(SomeModel.query) == fsa.BaseQuery ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/tests/test_query_property.py0000644000175000017500000000257600000000000021356 0ustar00daviddavidimport pytest from werkzeug.exceptions import NotFound import flask_sqlalchemy as fsa def test_no_app_bound(app): db = fsa.SQLAlchemy() db.init_app(app) class Foo(db.Model): id = db.Column(db.Integer, primary_key=True) # If no app is bound to the SQLAlchemy instance, a # request context is required to access Model.query. pytest.raises(RuntimeError, getattr, Foo, 'query') with app.test_request_context(): db.create_all() foo = Foo() db.session.add(foo) db.session.commit() assert len(Foo.query.all()) == 1 def test_app_bound(db, Todo): # If an app was passed to the SQLAlchemy constructor, # the query property is always available. todo = Todo('Test', 'test') db.session.add(todo) db.session.commit() assert len(Todo.query.all()) == 1 def test_get_or_404(Todo): with pytest.raises(NotFound): Todo.query.get_or_404(1) expected = 'Expected message' with pytest.raises(NotFound) as e_info: Todo.query.get_or_404(1, description=expected) assert e_info.value.description == expected def test_first_or_404(Todo): with pytest.raises(NotFound): Todo.query.first_or_404() expected = 'Expected message' with pytest.raises(NotFound) as e_info: Todo.query.first_or_404(description=expected) assert e_info.value.description == expected ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/tests/test_regressions.py0000644000175000017500000000360400000000000020601 0ustar00daviddavidimport pytest @pytest.fixture def db(app, db): app.testing = False return db def test_joined_inheritance(db): class Base(db.Model): id = db.Column(db.Integer, primary_key=True) type = db.Column(db.Unicode(20)) __mapper_args__ = {'polymorphic_on': type} class SubBase(Base): id = db.Column(db.Integer, db.ForeignKey('base.id'), primary_key=True) __mapper_args__ = {'polymorphic_identity': 'sub'} assert Base.__tablename__ == 'base' assert SubBase.__tablename__ == 'sub_base' db.create_all() def test_single_table_inheritance(db): class Base(db.Model): id = db.Column(db.Integer, primary_key=True) type = db.Column(db.Unicode(20)) __mapper_args__ = {'polymorphic_on': type} class SubBase(Base): __mapper_args__ = {'polymorphic_identity': 'sub'} assert Base.__tablename__ == 'base' assert SubBase.__tablename__ == 'base' db.create_all() def test_joined_inheritance_relation(db): class Relation(db.Model): id = db.Column(db.Integer, primary_key=True) base_id = db.Column(db.Integer, db.ForeignKey('base.id')) name = db.Column(db.Unicode(20)) def __init__(self, name): self.name = name class Base(db.Model): id = db.Column(db.Integer, primary_key=True) type = db.Column(db.Unicode(20)) __mapper_args__ = {'polymorphic_on': type} class SubBase(Base): id = db.Column(db.Integer, db.ForeignKey('base.id'), primary_key=True) __mapper_args__ = {'polymorphic_identity': u'sub'} relations = db.relationship(Relation) db.create_all() base = SubBase() base.relations = [Relation(name=u'foo')] db.session.add(base) db.session.commit() base = base.query.one() def test_connection_binds(db): assert db.session.connection() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/tests/test_sessions.py0000644000175000017500000000343200000000000020103 0ustar00daviddavidimport sqlalchemy as sa from sqlalchemy.orm import sessionmaker import flask_sqlalchemy as fsa def test_default_session_scoping(app, db): class FOOBar(db.Model): id = db.Column(db.Integer, primary_key=True) db.create_all() with app.test_request_context(): fb = FOOBar() db.session.add(fb) assert fb in db.session def test_session_scoping_changing(app): def scopefunc(): return id(dict()) db = fsa.SQLAlchemy(app, session_options=dict(scopefunc=scopefunc)) class FOOBar(db.Model): id = db.Column(db.Integer, primary_key=True) db.create_all() with app.test_request_context(): fb = FOOBar() db.session.add(fb) assert fb not in db.session # because a new scope is generated on each call def test_insert_update_delete(db): # Ensure _SignalTrackingMapperExtension doesn't croak when # faced with a vanilla SQLAlchemy session. # # Verifies that "AttributeError: 'SessionMaker' object has no attribute '_model_changes'" # is not thrown. Session = sessionmaker(bind=db.engine) class QazWsx(db.Model): id = db.Column(db.Integer, primary_key=True) x = db.Column(db.String, default='') db.create_all() session = Session() session.add(QazWsx()) 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.expunge_all() qaz_wsx = session.query(QazWsx).first() assert qaz_wsx.x == 'test' session.delete(qaz_wsx) # issues a DELETE. assert session.query(QazWsx).first() is None def test_listen_to_session_event(db): sa.event.listen(db.session, 'after_commit', lambda session: None) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/tests/test_signals.py0000644000175000017500000000276600000000000017706 0ustar00daviddavidimport flask import pytest import flask_sqlalchemy as fsa pytestmark = pytest.mark.skipif( not flask.signals_available, reason='Signals require the blinker library.' ) @pytest.fixture() def app(app): app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True return app def test_before_committed(app, db, Todo): class Namespace(object): is_received = False def before_committed(sender, changes): Namespace.is_received = True fsa.before_models_committed.connect(before_committed) todo = Todo('Awesome', 'the text') db.session.add(todo) db.session.commit() assert Namespace.is_received fsa.before_models_committed.disconnect(before_committed) def test_model_signals(db, Todo): recorded = [] def committed(sender, changes): assert isinstance(changes, list) recorded.extend(changes) fsa.models_committed.connect(committed) todo = Todo('Awesome', 'the text') db.session.add(todo) assert len(recorded) == 0 db.session.commit() assert len(recorded) == 1 assert recorded[0][0] == todo assert recorded[0][1] == 'insert' del recorded[:] todo.text = 'aha' db.session.commit() assert len(recorded) == 1 assert recorded[0][0] == todo assert recorded[0][1] == 'update' del recorded[:] db.session.delete(todo) db.session.commit() assert len(recorded) == 1 assert recorded[0][0] == todo assert recorded[0][1] == 'delete' fsa.models_committed.disconnect(committed) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/tests/test_sqlalchemy_includes.py0000644000175000017500000000047400000000000022270 0ustar00daviddavidimport sqlalchemy as sa import flask_sqlalchemy as fsa def test_sqlalchemy_includes(): """Various SQLAlchemy objects are exposed as attributes.""" db = fsa.SQLAlchemy() assert db.Column == sa.Column # The Query object we expose is actually our own subclass. assert db.Query == fsa.BaseQuery ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/tests/test_table_name.py0000644000175000017500000001345600000000000020333 0ustar00daviddavidimport inspect import pytest from sqlalchemy.exc import ArgumentError from sqlalchemy.ext.declarative import declared_attr def test_name(db): class FOOBar(db.Model): id = db.Column(db.Integer, primary_key=True) class BazBar(db.Model): id = db.Column(db.Integer, primary_key=True) class Ham(db.Model): __tablename__ = 'spam' id = db.Column(db.Integer, primary_key=True) assert FOOBar.__tablename__ == 'foo_bar' assert BazBar.__tablename__ == 'baz_bar' assert Ham.__tablename__ == 'spam' def test_single_name(db): """Single table inheritance should not set a new name.""" class Duck(db.Model): id = db.Column(db.Integer, primary_key=True) class Mallard(Duck): pass assert '__tablename__' not in Mallard.__dict__ assert Mallard.__tablename__ == 'duck' def test_joined_name(db): """Model has a separate primary key; it should set a new name.""" class Duck(db.Model): id = db.Column(db.Integer, primary_key=True) class Donald(Duck): id = db.Column(db.Integer, db.ForeignKey(Duck.id), primary_key=True) assert Donald.__tablename__ == 'donald' def test_mixin_id(db): """Primary key provided by mixin should still allow model to set tablename. """ class Base(object): id = db.Column(db.Integer, primary_key=True) class Duck(Base, db.Model): pass assert not hasattr(Base, '__tablename__') assert Duck.__tablename__ == 'duck' def test_mixin_attr(db): """A declared attr tablename will be used down multiple levels of inheritance. """ class Mixin(object): @declared_attr def __tablename__(cls): return cls.__name__.upper() class Bird(Mixin, db.Model): id = db.Column(db.Integer, primary_key=True) class Duck(Bird): # object reference id = db.Column(db.ForeignKey(Bird.id), primary_key=True) class Mallard(Duck): # string reference id = db.Column(db.ForeignKey('DUCK.id'), primary_key=True) assert Bird.__tablename__ == 'BIRD' assert Duck.__tablename__ == 'DUCK' assert Mallard.__tablename__ == 'MALLARD' def test_abstract_name(db): """Abstract model should not set a name. Subclass should set a name.""" class Base(db.Model): __abstract__ = True id = db.Column(db.Integer, primary_key=True) class Duck(Base): pass assert '__tablename__' not in Base.__dict__ assert Duck.__tablename__ == 'duck' def test_complex_inheritance(db): """Joined table inheritance, but the new primary key is provided by a mixin, not directly on the class. """ class Duck(db.Model): id = db.Column(db.Integer, primary_key=True) class IdMixin(object): @declared_attr def id(cls): return db.Column( db.Integer, db.ForeignKey(Duck.id), primary_key=True ) class RubberDuck(IdMixin, Duck): pass assert RubberDuck.__tablename__ == 'rubber_duck' def test_manual_name(db): """Setting a manual name prevents generation for the immediate model. A name is generated for joined but not single-table inheritance. """ class Duck(db.Model): __tablename__ = 'DUCK' id = db.Column(db.Integer, primary_key=True) type = db.Column(db.String) __mapper_args__ = { 'polymorphic_on': type } class Daffy(Duck): id = db.Column(db.Integer, db.ForeignKey(Duck.id), primary_key=True) __mapper_args__ = { 'polymorphic_identity': 'Warner' } class Donald(Duck): __mapper_args__ = { 'polymorphic_identity': 'Disney' } assert Duck.__tablename__ == 'DUCK' assert Daffy.__tablename__ == 'daffy' assert '__tablename__' not in Donald.__dict__ assert Donald.__tablename__ == 'DUCK' # polymorphic condition for single-table query assert 'WHERE "DUCK".type' in str(Donald.query) def test_primary_constraint(db): """Primary key will be picked up from table args.""" class Duck(db.Model): id = db.Column(db.Integer) __table_args__ = ( db.PrimaryKeyConstraint(id), ) assert Duck.__table__ is not None assert Duck.__tablename__ == 'duck' def test_no_access_to_class_property(db): """Ensure the implementation doesn't access class properties or declared attrs while inspecting the unmapped model. """ 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 class Witch(Duck): @declared_attr def is_duck(self): # declared attrs will be accessed during mapper configuration, # but make sure they're not accessed before that info = inspect.getouterframes(inspect.currentframe())[2] assert info[3] != '_should_set_tablename' ns.accessed = True @class_property def floats(self): assert False assert ns.accessed def test_metadata_has_table(db): user = db.Table( 'user', db.Column('id', db.Integer, primary_key=True), ) class User(db.Model): pass assert User.__table__ is user def test_correct_error_for_no_primary_key(db): with pytest.raises(ArgumentError) as info: class User(db.Model): pass assert 'could not assemble any primary key' in str(info.value) def test_single_has_parent_table(db): class Duck(db.Model): id = db.Column(db.Integer, primary_key=True) class Call(Duck): pass assert Call.__table__ is Duck.__table__ assert '__table__' not in Call.__dict__ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/tests/test_utils.py0000644000175000017500000000176200000000000017401 0ustar00daviddavidimport mock from flask_sqlalchemy import utils class TestSQLAlchemyVersion: def test_parse_version(self): assert utils.parse_version('1.2.3') == (1, 2, 3) assert utils.parse_version('1.2') == (1, 2, 0) assert utils.parse_version('1') == (1, 0, 0) @mock.patch.object(utils, 'sqlalchemy') def test_sqlalchemy_version(self, m_sqlalchemy): m_sqlalchemy.__version__ = '1.3' assert not utils.sqlalchemy_version('<', '1.3') assert not utils.sqlalchemy_version('>', '1.3') assert utils.sqlalchemy_version('<=', '1.3') assert utils.sqlalchemy_version('==', '1.3') assert utils.sqlalchemy_version('>=', '1.3') m_sqlalchemy.__version__ = '1.2.99' assert utils.sqlalchemy_version('<', '1.3') assert not utils.sqlalchemy_version('>', '1.3') assert utils.sqlalchemy_version('<=', '1.3') assert not utils.sqlalchemy_version('==', '1.3') assert not utils.sqlalchemy_version('>=', '1.3') ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616093781.0 Flask-SQLAlchemy-2.5.1/tox.ini0000644000175000017500000000231500000000000014774 0ustar00daviddavid[tox] envlist = # All supported Python versions. If you update this, please update docs/index.rst. py{37,36,35,34,27,py} # Don't need to test all Python versions against lowest requirements. py{37,36,27}-lowest docs coverage-report # This will only apply on a dev machine. We set this false for CI. skip_missing_interpreters = true [testenv] deps = pytest coverage blinker mock py27-lowest,py36-lowest: flask==0.12 py27-lowest,py36-lowest: sqlalchemy==0.8 # This is the first version that works w/ Python 3.7 py37-lowest: sqlalchemy==1.0.10 commands = coverage run -p -m pytest --tb=short --basetemp={envtmpdir} {posargs} [testenv:docs] deps = Sphinx Pallets-Sphinx-Themes sphinx-issues setuptools twine commands = sphinx-build -W -b html -d {envtmpdir}/doctrees docs docs/_build/html # readme python setup.py sdist twine check dist/* [testenv:coverage-report] deps = coverage skip_install = true commands = coverage combine coverage html coverage report [testenv:codecov] passenv = CI TRAVIS TRAVIS_* APPVEYOR APPVEYOR_* deps = codecov skip_install = true commands = coverage combine codecov coverage report