├── .coveragerc ├── .editorconfig ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE.txt ├── MANIFEST.in ├── README.rst ├── changes.rst ├── docs ├── Makefile ├── changes.rst ├── conf.py ├── index.rst ├── init.rst ├── tests.rst ├── transactions.rst └── usage.rst ├── setup.cfg ├── setup.py ├── src └── pyramid_sqlalchemy │ ├── __init__.py │ ├── events.py │ ├── fixtures.py │ ├── meta.py │ ├── model.py │ └── testing.py └── tests ├── __init__.py ├── test_init.py └── test_testing.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = true 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | trim_trailing_whitespace = true 3 | charset = utf-8 4 | 5 | [**.py] 6 | indent_size = 4 7 | indent_style = space 8 | 9 | [Makefile] 10 | indent_size = 8 11 | indent_style = tab 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw[opq] 2 | *.py[co] 3 | /.cache 4 | /.coverage 5 | /.pytest_cache 6 | /build 7 | /dist 8 | /coverage.xml 9 | /nosetests.xml 10 | /docs/_build 11 | /src/pyramid_sqlalchemy.egg-info 12 | 13 | /.Python 14 | /bin 15 | /include 16 | /lib 17 | 18 | /pyvenv.cfg 19 | /pip-selfcheck.json 20 | 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.5" 5 | - "3.6" 6 | - "3.7" 7 | - "3.8" 8 | - pypy 9 | - pypy3 10 | install: 11 | - pip install -e '.[tests]' 12 | script: py.test 13 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Reporting an issue 2 | ------------------ 3 | 4 | When submitting a ticket please include the following information: 5 | 6 | * the installed version of `pyramid_sqlalchemy` 7 | * the installed version of `pyramid` 8 | * the installed version of `SQLAlchemy` 9 | * if you encounter an exception: the complete Python backtrace 10 | 11 | 12 | Patches and pull requests 13 | ------------------------- 14 | 15 | When submitting a patch or pull request please make sure it applies cleanly to 16 | the current git master branch and all tests are passing on Python 2.7 and 17 | Python 3.4. To easiest way to run the tests is to use pytest in a virtualenv. 18 | For Python 2 use the following: 19 | 20 | ``` 21 | $ virtualenv . 22 | $ bin/pip install -e '.[tests]' 23 | $ bin/pip install pytest 24 | $ bin/py.test src 25 | ``` 26 | 27 | For Python 3.4 use the following: 28 | 29 | ``` 30 | $ pythonn3.4 -m venv . 31 | $ bin/pip install -e '.[tests]' 32 | $ bin/pip install pytest 33 | $ bin/py.test src 34 | ``` 35 | 36 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) Wichert Akkerman 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 3. Neither the name of the University nor the names of its contributors 13 | may be used to endorse or promote products derived from this software 14 | without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.rst 2 | include *.txt 3 | include *.md 4 | include .travis.yml 5 | graft docs 6 | prune docs/_build 7 | 8 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://travis-ci.org/wichert/pyramid_sqlalchemy.svg?branch=master 2 | :target: https://travis-ci.org/wichert/pyramid_sqlalchemy 3 | 4 | `pyramid_sqlalchemy` provides some basic glue to facilitate using 5 | `SQLAlchemy `_ with `Pyramid 6 | `_. 7 | 8 | SQLAlchemy relies on global state for a few things: 9 | 10 | * A ``MetaData`` instance which tracks all known SQL tables. 11 | * A base class for all models using the ORM. 12 | * A session factory. 13 | 14 | Every application using SQLAlchemy must provides its own instance of these. 15 | This makes it hard create add-on packages that also use SQLAlchemy, since they 16 | either need to have their own SQLAlchemy state, which makes it hard to 17 | integrate them into your application, or they need to jump through multiple 18 | complex hoops to allow them share state with your application. 19 | 20 | pyramid_sqlalchemy helps by providing a canonical location for the global 21 | SQLAlchemy state. In addition it provides a convenient way to configure 22 | SQLAlchemy in a Pyramid application. 23 | 24 | :: 25 | 26 | from pyramid.config import Configurator 27 | from pyramid_sqlalchemy import BaseObject 28 | 29 | class MyModel(BaseObject): 30 | __tablename__ = 'my_model' 31 | ... 32 | 33 | def main(): 34 | config = Configurator() 35 | # Configure SQLAlchemy using settings from the .ini file 36 | config.include('pyramid_sqlalchemy') 37 | ... 38 | return config.make_wsgi_app() 39 | -------------------------------------------------------------------------------- /changes.rst: -------------------------------------------------------------------------------- 1 | docs/changes.rst -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = ../bin/sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | 15 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest 16 | 17 | help: 18 | @echo "Please use \`make ' where is one of" 19 | @echo " html to make standalone HTML files" 20 | @echo " dirhtml to make HTML files named index.html in directories" 21 | @echo " singlehtml to make a single large HTML file" 22 | @echo " pickle to make pickle files" 23 | @echo " json to make JSON files" 24 | @echo " htmlhelp to make HTML files and a HTML help project" 25 | @echo " qthelp to make HTML files and a qthelp project" 26 | @echo " devhelp to make HTML files and a Devhelp project" 27 | @echo " epub to make an epub" 28 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 29 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 30 | @echo " text to make text files" 31 | @echo " man to make manual pages" 32 | @echo " changes to make an overview of all changed/added/deprecated items" 33 | @echo " linkcheck to check all external links for integrity" 34 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 35 | 36 | clean: 37 | -rm -rf $(BUILDDIR)/* 38 | 39 | html: 40 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 41 | @echo 42 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 43 | 44 | dirhtml: 45 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 48 | 49 | singlehtml: 50 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 51 | @echo 52 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 53 | 54 | pickle: 55 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 56 | @echo 57 | @echo "Build finished; now you can process the pickle files." 58 | 59 | json: 60 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 61 | @echo 62 | @echo "Build finished; now you can process the JSON files." 63 | 64 | htmlhelp: 65 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 66 | @echo 67 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 68 | ".hhp project file in $(BUILDDIR)/htmlhelp." 69 | 70 | qthelp: 71 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 72 | @echo 73 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 74 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 75 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/s4usqlalchemy.qhcp" 76 | @echo "To view the help file:" 77 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/s4usqlalchemy.qhc" 78 | 79 | devhelp: 80 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 81 | @echo 82 | @echo "Build finished." 83 | @echo "To view the help file:" 84 | @echo "# mkdir -p $$HOME/.local/share/devhelp/s4usqlalchemy" 85 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/s4usqlalchemy" 86 | @echo "# devhelp" 87 | 88 | epub: 89 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 90 | @echo 91 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 92 | 93 | latex: 94 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 95 | @echo 96 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 97 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 98 | "(use \`make latexpdf' here to do that automatically)." 99 | 100 | latexpdf: 101 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 102 | @echo "Running LaTeX files through pdflatex..." 103 | make -C $(BUILDDIR)/latex all-pdf 104 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 105 | 106 | text: 107 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 108 | @echo 109 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 110 | 111 | man: 112 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 113 | @echo 114 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 115 | 116 | changes: 117 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 118 | @echo 119 | @echo "The overview file is in $(BUILDDIR)/changes." 120 | 121 | linkcheck: 122 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 123 | @echo 124 | @echo "Link check complete; look for any errors in the above output " \ 125 | "or in $(BUILDDIR)/linkcheck/output.txt." 126 | 127 | doctest: 128 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 129 | @echo "Testing of doctests in the sources finished, look at the " \ 130 | "results in $(BUILDDIR)/doctest/output.txt." 131 | -------------------------------------------------------------------------------- /docs/changes.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | 1.6 - January 4, 2016 5 | --------------------- 6 | 7 | - Update ``transaction`` pytest fixture to not mock out ``transation.get()`` 8 | completely, but only the ``commit()`` transaction method. This fixes problems 9 | with code trying to write on the current transaction. 10 | 11 | 12 | 1.5 - December 30, 2015 13 | ----------------------- 14 | 15 | - Fix a compatibility error with the DatabaseTestCase class which could break 16 | functional test setup. 17 | 18 | - Code reorganisation: move tests outside the package; there is no point in 19 | installing them. 20 | 21 | 22 | 1.4 - November 25, 2015 23 | ----------------------- 24 | 25 | - Revert naming convention change. This change broke all existing data models 26 | which did not supply a constraint name everywhere. This is especially bad 27 | for types which implicitly create unnamed constraints, such as booleans and 28 | enums on dialects that do not have native support. 29 | 30 | 31 | 1.3 - November 23, 2015 32 | ----------------------- 33 | 34 | - Configure a default naming convention, as `recommended by alembic 35 | `_). 36 | `Pull request 3 `_ 37 | from Marcin Lulek. 38 | 39 | - Fix a broken import in pyramid_sqlalchemy's own test when running on Python 3. 40 | 41 | - Allow overriding the database used for testing with the pytest ``--sql-url`` 42 | option when using the ``DatabaseTestCase`` test class. For non-pytest users 43 | support the ``DB_URI`` environment variable as well. 44 | 45 | 46 | 1.2.2 - September 11, 2014 47 | -------------------------- 48 | 49 | - Add dependency on mock for Python <3.3. This fixes import problems who try to 50 | import pyramid_sqlalchemy.testing in production code. 51 | 52 | 53 | 1.2.1 - September 1, 2014 54 | ------------------------- 55 | 56 | - Move pyramid to a test-only dependency. This makes it simpler to use 57 | pyramid_sqlalchemy in non-pyramid contexts. 58 | 59 | 60 | 1.2 - August 30, 2014 61 | --------------------- 62 | 63 | - Use `unittest.mock` when available. This removes the `mock` dependency on 64 | Python 3. 65 | 66 | - Tests no longer need to mock out pyramid_sqlalchemy.includeme; this is now 67 | handled by ``DatabaseTestCase`` and the py.test fixtures. 68 | 69 | - Automatically make py.test fixtures available externally. This removes the 70 | need to copy & paste them over from the documentation. 71 | 72 | - Fix error on pytest fixture example. 73 | 74 | - Setup `Travis `_ to 75 | automatically run tests on CPython 2.6, CPython 2.7, CPython 3.3, CPython 3.4 76 | and PyPy. 77 | 78 | 79 | 1.1 - July 14, 2014 80 | ------------------- 81 | 82 | - Add missing schema to the Pyramid-URL in the package description. This broke 83 | ReST rendering on the PyPI page. 84 | 85 | - Add a new ``enable_sql_two_phase_commit()`` configuration directive to enable 86 | two-phase commit. 87 | 88 | - Enable foreign key constraint checking for SQLite in DatabaseTestCase. 89 | 90 | - Use SQLAlchemy events instead of ZopeTransactionExtension to handle 91 | integration of zope.sqlalchemy and SQLAlchemy. 92 | 93 | 94 | 1.0 - July 13, 2014 95 | ------------------- 96 | 97 | - First release. 98 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # pyramid_sqlalchemy documentation build configuration file, created by 4 | # sphinx-quickstart on Tue Jul 12 17:42:20 2011. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | 15 | # If extensions (or modules to document with autodoc) are in another directory, 16 | # add these directories to sys.path here. If the directory is relative to the 17 | # documentation root, use os.path.abspath to make it absolute, like shown here. 18 | #sys.path.insert(0, os.path.abspath('.')) 19 | 20 | # -- General configuration ----------------------------------------------------- 21 | 22 | # If your documentation needs a minimal Sphinx version, state it here. 23 | #needs_sphinx = '1.0' 24 | 25 | # Add any Sphinx extension module names here, as strings. They can be extensions 26 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 27 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx'] 28 | 29 | # Add any paths that contain templates here, relative to this directory. 30 | templates_path = ['_templates'] 31 | 32 | # The suffix of source filenames. 33 | source_suffix = '.rst' 34 | 35 | # The encoding of source files. 36 | #source_encoding = 'utf-8-sig' 37 | 38 | # The master toctree document. 39 | master_doc = 'index' 40 | 41 | # General information about the project. 42 | project = u'pyramid_sqlalchemy' 43 | copyright = u'2011-2014, Wichert Akkerman' 44 | 45 | # The version info for the project you're documenting, acts as replacement for 46 | # |version| and |release|, also used in various other places throughout the 47 | # built documents. 48 | # 49 | # The short X.Y version. 50 | version = '1.2' 51 | # The full version, including alpha/beta/rc tags. 52 | release = '1.2' 53 | 54 | # The language for content autogenerated by Sphinx. Refer to documentation 55 | # for a list of supported languages. 56 | #language = None 57 | 58 | # There are two options for replacing |today|: either, you set today to some 59 | # non-false value, then it is used: 60 | #today = '' 61 | # Else, today_fmt is used as the format for a strftime call. 62 | #today_fmt = '%B %d, %Y' 63 | 64 | # List of patterns, relative to source directory, that match files and 65 | # directories to ignore when looking for source files. 66 | exclude_patterns = ['_build'] 67 | 68 | # The reST default role (used for this markup: `text`) to use for all documents. 69 | #default_role = None 70 | 71 | # If true, '()' will be appended to :func: etc. cross-reference text. 72 | #add_function_parentheses = True 73 | 74 | # If true, the current module name will be prepended to all description 75 | # unit titles (such as .. function::). 76 | #add_module_names = True 77 | 78 | # If true, sectionauthor and moduleauthor directives will be shown in the 79 | # output. They are ignored by default. 80 | #show_authors = False 81 | 82 | # The name of the Pygments (syntax highlighting) style to use. 83 | pygments_style = 'sphinx' 84 | 85 | # A list of ignored prefixes for module index sorting. 86 | #modindex_common_prefix = [] 87 | 88 | 89 | # -- Options for HTML output --------------------------------------------------- 90 | 91 | # The theme to use for HTML and HTML Help pages. See the documentation for 92 | # a list of builtin themes. 93 | html_theme = 'haiku' 94 | 95 | # Theme options are theme-specific and customize the look and feel of a theme 96 | # further. For a list of options available for each theme, see the 97 | # documentation. 98 | #html_theme_options = {} 99 | 100 | # Add any paths that contain custom themes here, relative to this directory. 101 | #html_theme_path = [] 102 | 103 | # The name for this set of Sphinx documents. If None, it defaults to 104 | # " v documentation". 105 | html_title = 'pyramid_sqlalchemy 1.0' 106 | 107 | # A shorter title for the navigation bar. Default is the same as html_title. 108 | #html_short_title = None 109 | 110 | # The name of an image file (relative to this directory) to place at the top 111 | # of the sidebar. 112 | #html_logo = None 113 | 114 | # The name of an image file (within the static path) to use as favicon of the 115 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 116 | # pixels large. 117 | #html_favicon = None 118 | 119 | # Add any paths that contain custom static files (such as style sheets) here, 120 | # relative to this directory. They are copied after the builtin static files, 121 | # so a file named "default.css" will overwrite the builtin "default.css". 122 | html_static_path = ['_static'] 123 | 124 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 125 | # using the given strftime format. 126 | #html_last_updated_fmt = '%b %d, %Y' 127 | 128 | # If true, SmartyPants will be used to convert quotes and dashes to 129 | # typographically correct entities. 130 | #html_use_smartypants = True 131 | 132 | # Custom sidebar templates, maps document names to template names. 133 | #html_sidebars = {} 134 | 135 | # Additional templates that should be rendered to pages, maps page names to 136 | # template names. 137 | #html_additional_pages = {} 138 | 139 | # If false, no module index is generated. 140 | #html_domain_indices = True 141 | 142 | # If false, no index is generated. 143 | #html_use_index = True 144 | 145 | # If true, the index is split into individual pages for each letter. 146 | #html_split_index = False 147 | 148 | # If true, links to the reST sources are added to the pages. 149 | #html_show_sourcelink = True 150 | 151 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 152 | #html_show_sphinx = True 153 | 154 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 155 | #html_show_copyright = True 156 | 157 | # If true, an OpenSearch description file will be output, and all pages will 158 | # contain a tag referring to it. The value of this option must be the 159 | # base URL from which the finished HTML is served. 160 | #html_use_opensearch = '' 161 | 162 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 163 | #html_file_suffix = None 164 | 165 | # Output file base name for HTML help builder. 166 | htmlhelp_basename = 'pyramid_sqlalchemydoc' 167 | 168 | 169 | # -- Options for LaTeX output -------------------------------------------------- 170 | 171 | # The paper size ('letter' or 'a4'). 172 | #latex_paper_size = 'letter' 173 | 174 | # The font size ('10pt', '11pt' or '12pt'). 175 | #latex_font_size = '10pt' 176 | 177 | # Grouping the document tree into LaTeX files. List of tuples 178 | # (source start file, target name, title, author, documentclass [howto/manual]). 179 | latex_documents = [ 180 | ('index', 'pyramid_sqlalchemy', u'pyramid_sqlalchemy Documentation', 181 | u'Wichert Akkerman', 'manual'), 182 | ] 183 | 184 | # The name of an image file (relative to this directory) to place at the top of 185 | # the title page. 186 | #latex_logo = None 187 | 188 | # For "manual" documents, if this is true, then toplevel headings are parts, 189 | # not chapters. 190 | #latex_use_parts = False 191 | 192 | # If true, show page references after internal links. 193 | #latex_show_pagerefs = False 194 | 195 | # If true, show URL addresses after external links. 196 | #latex_show_urls = False 197 | 198 | # Additional stuff for the LaTeX preamble. 199 | #latex_preamble = '' 200 | 201 | # Documents to append as an appendix to all manuals. 202 | #latex_appendices = [] 203 | 204 | # If false, no module index is generated. 205 | #latex_domain_indices = True 206 | 207 | 208 | # -- Options for manual page output -------------------------------------------- 209 | 210 | # One entry per manual page. List of tuples 211 | # (source start file, name, description, authors, manual section). 212 | man_pages = [ 213 | ('index', 'pyramid_sqlalchemy', u'pyramid_sqlalchemy Documentation', 214 | [u'Wichert Akkerman'], 1) 215 | ] 216 | 217 | 218 | # Example configuration for intersphinx: refer to the Python standard library. 219 | intersphinx_mapping = { 220 | 'python': ('http://docs.python.org/', None), 221 | 'pyramid': ('http://docs.pylonsproject.org/projects/pyramid/en/latest/', None), 222 | 'sqlalchemy': ('http://www.sqlalchemy.org/docs', None)} 223 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | `pyramid_sqlalchemy` provides some basic glue to facilitate using 2 | `SQLAlchemy `_ with `Pyramid 3 | `_. 4 | 5 | SQLAlchemy relies on global state for a few things: 6 | 7 | * A :ref:`MetaData ` instance which tracks all 8 | known SQL tables. 9 | * A :ref:`ORM base class ` for all models using the ORM. 10 | * A :ref:`session factory `. 11 | 12 | Every application using SQLAlchemy must provides its own instance of these. 13 | This makes it hard create add-on packages that also use SQLAlchemy, since they 14 | either need to have their own SQLAlchemy state, which makes it hard to 15 | integrate them into your application, or they need to jump through multiple 16 | complex hoops to allow them share state with your application. 17 | 18 | pyramid_sqlalchemy helps by providing a canonical location for the global 19 | SQLAlchemy state. In addition it provides a convenient way to configure 20 | SQLAlchemy in a Pyramid application. 21 | 22 | .. code-block:: python 23 | :linenos: 24 | 25 | from pyramid.config import Configurator 26 | from pyramid_sqlalchemy import BaseObject 27 | 28 | class MyModel(BaseObject): 29 | __tablename__ = 'my_model' 30 | ... 31 | 32 | def main(global_config, **settings): 33 | config = Configurator(settings=settings) 34 | # Configure SQLAlchemy using settings from the .ini file 35 | config.include('pyramid_sqlalchemy') 36 | ... 37 | return config.make_wsgi_app() 38 | 39 | 40 | Contents 41 | ======== 42 | 43 | .. toctree:: 44 | :maxdepth: 1 45 | 46 | usage 47 | init 48 | transactions 49 | tests 50 | changes 51 | 52 | Indices and tables 53 | ================== 54 | 55 | * :ref:`genindex` 56 | * :ref:`modindex` 57 | * :ref:`search` 58 | -------------------------------------------------------------------------------- /docs/init.rst: -------------------------------------------------------------------------------- 1 | Initialisation 2 | ============== 3 | 4 | When using pyramid_sqlalchemy in a Pyramid application you can easily configure 5 | it by using the ``includeme`` function of the Pyramid configurator object: 6 | 7 | .. code-block:: python 8 | :linenos: 9 | 10 | from pyramid.config import Configurator 11 | 12 | def main(global_config, **settings): 13 | config = Configurator(settings=settings) 14 | # Configure SQLAlchemy using settings from the .ini file 15 | config.include('pyramid_sqlalchemy') 16 | 17 | This will pick up any ``sqlalchemy.*`` entries from your ``.ini`` file and 18 | use those to configure SQLAlchemy. In particular the ``sqlalchemy.url`` 19 | entry is used, which must contain a :ref:`database URL 20 | `. 21 | 22 | For non-Pyramid applications or special situations you can also use 23 | :py:func:`pyramid_sqlalchemy.init_sqlalchemy` to configure a SQLAlchemy engine 24 | directly: 25 | 26 | .. code-block:: python 27 | :linenos: 28 | 29 | from sqlalchemy import create_engine 30 | from pyramid_sqlalchemy import init_sqlalchemy 31 | 32 | engine = create_engine('sqlite://') 33 | init_sqlachemy(engine) 34 | 35 | 36 | Two-phase transactions 37 | ---------------------- 38 | 39 | If your application uses both SQL and other transaction-aware systems such as 40 | `repoze.filesafe `_, `AcidFS 41 | `_, `pyramid_mailer 42 | `_ or `ZODB `_ 43 | you need to `two-phase commits 44 | `_ to coordinate 45 | transactions. You can enable these using the ``enable_sql_two_phase_commit`` 46 | configuration directive. 47 | 48 | .. code-block:: python 49 | :linenos: 50 | 51 | def main(global_config, **settings): 52 | config = Configurator(settings=settings) 53 | config.include('pyramid_sqlalchemy') 54 | config.enable_sql_two_phase_commit() 55 | 56 | Please note that this is not supported for all SQL servers. PostgreSQL is 57 | the only server where this is guaranteed to work. SQL Server does support 58 | two-phase transactions but the Python driver support for it is unusable. 59 | The cx_oracle `supports them with some caveats 60 | `_. 61 | -------------------------------------------------------------------------------- /docs/tests.rst: -------------------------------------------------------------------------------- 1 | Writing tests 2 | ============= 3 | 4 | unittest test cases 5 | ------------------- 6 | 7 | pyramid_sqlalchemy provides a ``DatabaseTestCase`` class which can be used when 8 | writing unit or integration tests that require a working database. You can use 9 | this as base class for you test classes. This example updates the 10 | :ref:`Pyramid integration test example ` to add 11 | database support. 12 | 13 | 14 | .. code-block:: python 15 | :linenos: 16 | 17 | 18 | from pyramid_sqlalchemy.testing import DatabaseTestCase 19 | from pyramid import testing 20 | 21 | class ViewIntegrationTests(DatabaseTestCase): 22 | def setUp(self): 23 | super(ViewIntegrationTests, self).setUp() 24 | self.config = testing.setUp() 25 | self.config.include('myapp') 26 | 27 | def tearDown(self): 28 | testing.tearDown() 29 | super(ViewIntegrationTests, self).tearDown() 30 | 31 | 32 | .. warning:: 33 | 34 | It is critical that you call the setUp() method of the base classes first. 35 | This is necessary to guarantee everything is configured correctly before you 36 | run any code that might touch pyramid_sqlalchemy. 37 | 38 | Writing functional tests is just as easy. The example below is a modified 39 | version of the :ref:`Pyramid functional test example 40 | `. 41 | 42 | .. code-block:: python 43 | :linenos: 44 | 45 | from pyramid_sqlalchemy.testing import DatabaseTestCase 46 | 47 | class FunctionalTests(DatabaseTestCase): 48 | def setUp(self): 49 | from myapp import main 50 | from webtest import TestApp 51 | super(FunctionalTests, self).setUp() 52 | app = main({}) 53 | self.testapp = TestApp(app) 54 | 55 | 56 | 57 | Normally all tests are run with an in-memory SQLite database. There are several 58 | ways to change this: 59 | 60 | * Set the ``db_uri`` variable in your test class to a different database URI. 61 | * Set the ``DB_URI`` environment variable. 62 | * If you use pytest as test runner you can use the ``--sql-url`` option to 63 | set the database URI. 64 | 65 | 66 | py.test fixtures 67 | ---------------- 68 | 69 | If you use `pytest `_ you can use the test fixtures provided 70 | by pyramid_sqlalchemy. 71 | 72 | For tests that need an active connection, but that do not need to use SQLAlchemy 73 | there is a ``transaction`` fixture available. This fixture creates a new transaction 74 | that will automatically be aborted at the end of the test. In order to prevent the 75 | transaction from being committed accidentally it is marked as `doomed`: this will 76 | turn any call to ``commit()`` into an error. 77 | 78 | .. code-block:: python 79 | :linenos: 80 | 81 | @pytest.mark.usefixtures('transaction') 82 | def test_transaction_integration(): 83 | # Test code that needs a transaction 84 | 85 | The ``sql_session`` fixture must be used to test any code that needs to access 86 | a database. This fixture will setup a SQL backend and create all known tables. 87 | To speed up tests this will only be done once for the py.test session. Each 88 | test itself is running within its own transaction, to guarantee that any 89 | database changes are reverted after the test, and the next test starts with a 90 | clean database. 91 | 92 | .. code-block:: python 93 | :linenos: 94 | 95 | def test_model_sets_id_automatically(sql_session): 96 | obj = Account(login='jane') 97 | sql_session.add(obj) 98 | sql_session.flush() 99 | assert obj.id is not None 100 | 101 | Normally all tests will use an in-memory SQLite database. You can run your tests 102 | with a different backend by using the ``--sql-url=`` commandline option. For 103 | example to run all tests against a local PostgreSQL server using the `pytest` 104 | database:: 105 | 106 | $ bin/py.test --sql-url=postgresql:///pytest 107 | 108 | There is also a ``--sql-echo`` commandline option which will echo all executed SQL 109 | statements to the console. This must be used in combination with pytests' ``-s`` 110 | option to make the console output visisble. 111 | 112 | :: 113 | 114 | $ bin/py.test --sql-echo -s 115 | ======================================= test session starts ======================================== 116 | platform darwin -- Python 2.7.8 -- py-1.4.20 -- pytest-2.5.2 117 | plugins: pyramid-sqlalchemy 118 | collected 36 items / 3 skipped 119 | 120 | tests/ext/test_sql.py 2014-08-30 09:02:38,070 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1 121 | 2014-08-30 09:02:38,070 INFO sqlalchemy.engine.base.Engine () 122 | 2014-08-30 09:02:38,070 INFO sqlalchemy.engine.base.Engine SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1 123 | 124 | Using the provided fixtures you can create a new fixture for functional tests. 125 | This fixture needs add a special key to the request environment to tell the 126 | `pyramid_tm` tween not to to create or commit transactions. 127 | 128 | .. code-block:: python 129 | :linenos: 130 | 131 | import pytest 132 | from webtest import TestApp 133 | from myapp import main 134 | 135 | @pytest.fixture 136 | def app(monkeypatch, sql_session): 137 | # Prevent SQL re-configuration with non-testing setup 138 | monkeypatch.setattr('pyramid_sqlalchemy.includeme', lambda c: None) 139 | app = main({}) 140 | return TestApp(app, extra_environ={'repoze.tm.active': True}) 141 | 142 | -------------------------------------------------------------------------------- /docs/transactions.rst: -------------------------------------------------------------------------------- 1 | Transactions 2 | ============ 3 | 4 | All database operations must use a transaction. Transactions are created 5 | automatically when you perform a SQL operation, but you must provide a way to 6 | commit a transaction. There are two ways to manage transaction: automatically 7 | for each request using pyramid_tm, or manually. 8 | 9 | Using pyramid_tm 10 | ---------------- 11 | 12 | The easiest way to handle transactions is to use the `pyramid_tm 13 | `_ package. 14 | 15 | .. code-block:: python 16 | :linenos: 17 | 18 | def main(): 19 | config = Configurator() 20 | config.include('pyramid_tm') 21 | config.include('pyramid_sqlalchemy') 22 | 23 | pyramid_tm will automatically commit the transaction when processing of a 24 | request has finished. If an exception is raised during request processing the 25 | transaction will be aborted. Note that raising HTTP exceptions such as 26 | HTTPFound will also abort the transaction. This is the main difference in 27 | Pyramid between raising a HTTP exception and returning it. 28 | 29 | 30 | Managing transactions manually 31 | ------------------------------ 32 | 33 | You can also manage transactions manually. To do this you will need to use the 34 | API provided by the `transaction `_ 35 | package. The easiest way to do this is to use its context manager. 36 | 37 | .. code-block:: python 38 | :linenos: 39 | 40 | import transaction 41 | from pyramid_sqlalchemy import Session 42 | from .model import MyModel 43 | 44 | def my_func(): 45 | with transaction.manager: 46 | Session.query(MyModel).update({'active': False}) 47 | 48 | 49 | The transaction manager will automatically commit the transaction, unless an 50 | exception is raised, in which case it will abort the transaction. You can also 51 | abort the transaction manually by calling ``abort()`` on the transaction. 52 | 53 | .. code-block:: python 54 | :linenos: 55 | 56 | import transaction 57 | from pyramid_sqlalchemy import Session 58 | from .model import MyModel 59 | 60 | def my_func(): 61 | with transaction.manager as tx: 62 | Session.query(MyModel).update({'active': False}) 63 | if something_bad_happened: 64 | tx.abort() 65 | 66 | If you prefer not to use the context manager you can also use the underlying 67 | transaction API directly. The methods you can use are: 68 | 69 | * ``transaction.abort()`` aborts the current transaction. 70 | * ``transaction.commit()`` commits the current transaction. 71 | 72 | .. code-block:: python 73 | :linenos: 74 | 75 | import transaction 76 | from pyramid_sqlalchemy import Session 77 | from .model import MyModel 78 | 79 | def my_func(): 80 | Session.query(MyModel).update({'active': False}) 81 | if something_bad_happened: 82 | tx.abort() 83 | else: 84 | tx.commit() 85 | 86 | 87 | Savepoints or nested transactions 88 | --------------------------------- 89 | 90 | .. caution:: 91 | 92 | Not all SQL implementations support savepoints. 93 | 94 | Savepoints, sometimes also known as nested transactions, provide a way to 95 | execute code that might fail, but where a failure should not doom the rest of 96 | the transaction. The transaction system allows you to create savepoints for 97 | this purpose. 98 | 99 | .. code-block:: python 100 | :linenos: 101 | 102 | import transaction 103 | 104 | def my_func(): 105 | ... 106 | savepoint = transaction.savepoint() 107 | try: 108 | dangerous_function() 109 | except: 110 | savepoint.rollback() 111 | ... 112 | 113 | 114 | If you manage transaction manually you should call the savepoint method of the 115 | current transaction instead of the global savepoint function. 116 | 117 | .. code-block:: python 118 | :linenos: 119 | 120 | import transaction 121 | 122 | with transaction.manager as tx: 123 | ... 124 | savepoint = tx.savepoint() 125 | try: 126 | dangerous_function() 127 | except: 128 | savepoint.rollback() 129 | ... 130 | 131 | 132 | Non-ORM modifications 133 | --------------------- 134 | 135 | zope.sqlalchemy, which is used to handle the integration of SQLAlchemy and 136 | the transaction system, can only detect changes made through the ORM. Sometimes 137 | you may need to bypass the ORM and execute SQL statements directly using SQLAlchemy's 138 | core API. 139 | 140 | .. code-block:: python 141 | :linenos: 142 | 143 | from pyramid_sqlalchemy import Session 144 | from myapp.models import MyModel 145 | 146 | # Execute an UPDATE query directly, without using the ORM 147 | Session.query(MyModel).update({'active': False}) 148 | 149 | If you do this zope.sqlalchemy will not detect that you made any changes and 150 | will not correctly commit the transaction. To handle this you must call 151 | ``mark_changed()`` with the current session. 152 | 153 | .. code-block:: python 154 | :linenos: 155 | :emphasize-lines: 2,7 156 | 157 | from pyramid_sqlalchemy import Session 158 | from zope.sqlalchemy import mark_changed 159 | from myapp.models import MyModel 160 | 161 | session = Session() 162 | session.query(MyModel).update({'active': False}) 163 | mark_changed(session) 164 | -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | Usage 2 | ===== 3 | 4 | The ORM base class is available as :py:class:`pyramid_sqlalchemy.meta.BaseObject` 5 | and can be included directly: 6 | 7 | .. code-block:: python 8 | 9 | from pyramid_sqlalchemy import BaseObject 10 | 11 | class Account(BaseObject): 12 | __tablename__ = 'account' 13 | # Define your columns and methods here. 14 | 15 | 16 | When you need to build a query you can use the 17 | :py:obj:`pyramid_sqlalchemy.Session` session factory. 18 | 19 | .. code-block:: python 20 | 21 | from pyramid_sqlalchemy import Session 22 | 23 | account = Session.query(Account).first() 24 | 25 | When writing methods for a model in a you can also use 26 | :py:func:`sqlalchemy.orm.session.object_session` to get the current session for 27 | the object. 28 | 29 | 30 | .. code-block:: python 31 | 32 | from sqlalchemy.orm import object_session 33 | 34 | class Account(BaseObject): 35 | def favourites(self): 36 | """Return all the recent favourite articles.""" 37 | session = object_session(self) 38 | return session.query(Article).all() 39 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal=1 3 | 4 | [tool:pytest] 5 | testpaths = tests 6 | addopts = --tb=short 7 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from setuptools import setup 3 | from setuptools import find_packages 4 | 5 | version = '1.6' 6 | 7 | requires = [ 8 | 'SQLAlchemy >=0.7.0', 9 | 'zope.sqlalchemy >=0.7.4', 10 | ] 11 | 12 | if sys.version_info[:2] < (3, 3): 13 | requires.append('mock') 14 | 15 | tests_require = ['pyramid'] 16 | 17 | try: 18 | import unittest.mock 19 | except ImportError: 20 | tests_require.append('mock') 21 | 22 | setup(name='pyramid_sqlalchemy', 23 | version=version, 24 | description='SQLAlchemy integration for pyramid', 25 | long_description=open('README.rst').read() + '\n' + 26 | open('changes.rst').read(), 27 | classifiers=[ 28 | 'Development Status :: 5 - Production/Stable', 29 | 'Intended Audience :: Developers', 30 | 'License :: OSI Approved :: BSD License', 31 | 'Framework :: Pyramid', 32 | 'Programming Language :: Python :: 2', 33 | 'Programming Language :: Python :: 2.6', 34 | 'Programming Language :: Python :: 2.7', 35 | 'Programming Language :: Python :: 3', 36 | 'Programming Language :: Python :: 3.4', 37 | 'Programming Language :: Python :: 3.5', 38 | 'Topic :: Database', 39 | 'Topic :: Software Development :: Libraries :: Python Modules', 40 | ], 41 | author='Wichert Akkerman', 42 | author_email='wichert@wiggy.net', 43 | url='https://pyramid-sqlalchemy.readthedocs.org', 44 | license='BSD', 45 | packages=find_packages('src'), 46 | package_dir={'': 'src'}, 47 | include_package_data=True, 48 | zip_safe=True, 49 | install_requires=requires, 50 | tests_require=tests_require, 51 | extras_require={ 52 | 'docs': ['sphinx'], 53 | 'tests': tests_require, 54 | }, 55 | entry_points={ 56 | 'pytest11': ['pyramid_sqlalchemy = pyramid_sqlalchemy.fixtures'], 57 | } 58 | ) 59 | -------------------------------------------------------------------------------- /src/pyramid_sqlalchemy/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from sqlalchemy import engine_from_config 3 | from pyramid_sqlalchemy.meta import metadata 4 | from pyramid_sqlalchemy.meta import BaseObject 5 | from pyramid_sqlalchemy.meta import Session 6 | import pyramid_sqlalchemy.events 7 | pyramid_sqlalchemy.events # Keep PyFlakes happy 8 | 9 | 10 | log = logging.getLogger('pyramid_sqlalchemy') 11 | 12 | 13 | def init_sqlalchemy(engine): 14 | """Initialise the SQLAlchemy models. This must be called before using 15 | using any of the SQLAlchemy managed the tables or classes in the model.""" 16 | Session.configure(bind=engine) 17 | metadata.bind = engine 18 | 19 | 20 | def enable_sql_two_phase_commit(config, enable=True): 21 | Session.configure(twophase=enable) 22 | 23 | 24 | def includeme(config): 25 | """'Convenience method to initialise all components of this 26 | :mod:`pyramid_sqlalchemy` package from a pyramid applicaiton. 27 | """ 28 | config.add_directive('enable_sql_two_phase_commit', enable_sql_two_phase_commit) 29 | engine = engine_from_config(config.registry.settings, 'sqlalchemy.') 30 | init_sqlalchemy(engine) 31 | 32 | 33 | __all__ = ['BaseObject', 'Session', 'metadata'] 34 | -------------------------------------------------------------------------------- /src/pyramid_sqlalchemy/events.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import event 2 | from pyramid_sqlalchemy import model 3 | from pyramid_sqlalchemy.meta import BaseObject 4 | 5 | 6 | @event.listens_for(BaseObject, 'class_instrument') 7 | def register_model(cls): 8 | setattr(model, cls.__name__, cls) 9 | 10 | 11 | @event.listens_for(BaseObject, 'class_uninstrument') 12 | def unregister_model(cls): 13 | if hasattr(model, cls.__name__): 14 | delattr(model, cls.__name__) 15 | -------------------------------------------------------------------------------- /src/pyramid_sqlalchemy/fixtures.py: -------------------------------------------------------------------------------- 1 | try: 2 | from unittest import mock 3 | except ImportError: 4 | import mock 5 | import pytest 6 | from sqlalchemy import create_engine 7 | from .testing import DatabaseTestCase 8 | from . import Session 9 | from . import metadata 10 | from . import init_sqlalchemy 11 | 12 | 13 | DEFAULT_URI = 'sqlite:///' 14 | 15 | 16 | def pytest_addoption(parser): 17 | parser.addoption('--sql-url', default=DEFAULT_URI, 18 | help='SQLAlchemy Database URL') 19 | parser.addoption('--sql-echo', default=False, action='store_true', 20 | help='Echo SQL statements to console') 21 | 22 | 23 | def pytest_configure(config): 24 | DatabaseTestCase.db_uri = config.getoption('sql_url') 25 | 26 | 27 | @pytest.yield_fixture(scope='session') 28 | def _sqlalchemy(request): 29 | 30 | engine = create_engine(request.config.option.sql_url, 31 | echo=request.config.option.sql_echo) 32 | 33 | if engine.dialect.name == 'sqlite': 34 | engine.execute('PRAGMA foreign_keys = ON') 35 | # Check if a previous test has kept a session open. This will silently 36 | # make Session.configure do nothing and then break all our tests. 37 | assert not Session.registry.has() 38 | init_sqlalchemy(engine) 39 | metadata.create_all(engine) 40 | 41 | yield Session() 42 | 43 | Session.remove() 44 | metadata.drop_all(engine) 45 | Session.configure(bind=None) 46 | metadata.bind = None 47 | engine.dispose() 48 | 49 | 50 | @pytest.yield_fixture 51 | def transaction(): 52 | """Create a new transaction for a test. The transaction is automatically 53 | marked as doomed to prevent it from being committed accidentily. At the end 54 | of the test it will be rolled back. 55 | """ 56 | import transaction 57 | tx = transaction.begin() 58 | tx.doom() # Make sure a transaction can never be commited. 59 | # Mock out transaction.commit so code can never commit around us. 60 | with mock.patch('transaction.Transaction.commit'): 61 | yield 62 | tx.abort() 63 | 64 | 65 | @pytest.fixture 66 | def sql_session(transaction, _sqlalchemy, monkeypatch): 67 | """Provide a configured SQLAlchemy session running within a transaction. 68 | You can use the --sql-url commandline option to specify the SQL backend to 69 | use. The default configuration will use an in-memory SQLite database. 70 | 71 | You can also use the --sql-echo option to enable logging of all SQL 72 | statements to the console. 73 | """ 74 | # SQL is already configured, so make sure it is not run again which would 75 | # result in a second connection. 76 | monkeypatch.setattr('pyramid_sqlalchemy.includeme', lambda c: None) 77 | return _sqlalchemy 78 | 79 | 80 | __all__ = ['transaction', 'sql_session'] 81 | -------------------------------------------------------------------------------- /src/pyramid_sqlalchemy/meta.py: -------------------------------------------------------------------------------- 1 | """SQLAlchemy Metadata and Session object""" 2 | from sqlalchemy import orm 3 | from sqlalchemy import schema 4 | from sqlalchemy.ext.declarative import declarative_base 5 | from zope.sqlalchemy import register 6 | 7 | #: SQLAlchemy session manager. Updated by 8 | # :py:func:`pyramid_sqlalchemy.init_sqlalchemy`. 9 | Session = orm.scoped_session(orm.sessionmaker()) 10 | register(Session) 11 | 12 | #: Global metadata. If you have multiple databases with overlapping table 13 | #: names, you'll need a metadata for each database 14 | metadata = schema.MetaData() 15 | 16 | #: Base classes for models using declarative syntax 17 | BaseObject = declarative_base(metadata=metadata) 18 | 19 | __all__ = ['Session', 'metadata', 'BaseObject'] 20 | -------------------------------------------------------------------------------- /src/pyramid_sqlalchemy/model.py: -------------------------------------------------------------------------------- 1 | # This module will automatically be populated with all instrumented classes. 2 | -------------------------------------------------------------------------------- /src/pyramid_sqlalchemy/testing.py: -------------------------------------------------------------------------------- 1 | try: 2 | from unittest import mock 3 | except ImportError: 4 | import mock 5 | import os 6 | import unittest 7 | from sqlalchemy import create_engine 8 | from pyramid_sqlalchemy import init_sqlalchemy 9 | from pyramid_sqlalchemy import metadata 10 | from pyramid_sqlalchemy import Session 11 | import transaction 12 | 13 | 14 | class DatabaseTestCase(unittest.TestCase): 15 | """Base class for tests which require a database connection. 16 | 17 | This class provides makes sure a SQL connection to a database is available 18 | for tests. Each test is run in a separate transaction, and all tables are 19 | recreated for each test to guarantee a clean slate. 20 | """ 21 | 22 | #: FLag indicating if SQL tables should be created. This is normally 23 | #: set to `True`, but you may want to disabled table creation when writing 24 | #: tests for migration code. 25 | create_tables = True 26 | 27 | #: :ref:`Database URL ` for the test database. 28 | #: Normally a private in-memory SQLite database is used. You can override 29 | #: this with the pytest --sql-url parameter, or the DB_URI environment 30 | #: environment variable. 31 | db_uri = 'sqlite://' 32 | 33 | def database_url(self): 34 | return os.environ.get('DB_URI', self.db_uri) 35 | 36 | def setUp(self): 37 | self.engine = create_engine(self.database_url()) 38 | if self.engine.dialect.name == 'sqlite': 39 | self.engine.execute('PRAGMA foreign_keys = ON') 40 | init_sqlalchemy(self.engine) 41 | if self.create_tables: 42 | metadata.create_all() 43 | super(DatabaseTestCase, self).setUp() 44 | self._sqlalchemy_patcher = mock.patch('pyramid_sqlalchemy.includeme', lambda c: None) 45 | self._sqlalchemy_patcher.start() 46 | 47 | def tearDown(self): 48 | transaction.abort() 49 | Session.remove() 50 | if self.create_tables: 51 | metadata.drop_all() 52 | Session.configure(bind=None) 53 | metadata.bind = None 54 | self.engine.dispose() 55 | self._sqlalchemy_patcher.stop() 56 | super(DatabaseTestCase, self).tearDown() 57 | 58 | 59 | __all__ = ['DatabaseTestCase'] 60 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wichert/pyramid_sqlalchemy/b316350387ed8cedf49d14de048b716a6842c572/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_init.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from sqlalchemy import create_engine 3 | from pyramid.config import Configurator 4 | from pyramid_sqlalchemy import includeme 5 | from pyramid_sqlalchemy import init_sqlalchemy 6 | from pyramid_sqlalchemy import Session 7 | 8 | 9 | class test_init_sqlalchemy(unittest.TestCase): 10 | def test_basic_sqlite(self): 11 | engine = create_engine('sqlite://') 12 | init_sqlalchemy(engine) 13 | self.assertTrue(Session.session_factory.kw['bind'] is engine) 14 | 15 | 16 | class includeme_tests(unittest.TestCase): 17 | def test_sqlite_config(self): 18 | try: 19 | from unittest import mock 20 | except ImportError: 21 | import mock 22 | 23 | config = Configurator(settings={'sqlalchemy.url': 'sqlite://'}) 24 | 25 | with mock.patch('pyramid_sqlalchemy.init_sqlalchemy') as init_sqlalchemy: 26 | includeme(config) 27 | self.assertTrue(init_sqlalchemy.called) 28 | engine = init_sqlalchemy.mock_calls[0][1][0] 29 | self.assertEqual(str(engine.url), 'sqlite://') 30 | 31 | def test_two_phase_directive(self): 32 | try: 33 | from unittest import mock 34 | except ImportError: 35 | import mock 36 | 37 | config = Configurator() 38 | with mock.patch('pyramid_sqlalchemy.Session.configure') as configure: 39 | with mock.patch('pyramid_sqlalchemy.engine_from_config'): 40 | with mock.patch('pyramid_sqlalchemy.init_sqlalchemy'): 41 | config.include('pyramid_sqlalchemy') 42 | config.enable_sql_two_phase_commit() 43 | configure.assert_called_with(twophase=True) 44 | -------------------------------------------------------------------------------- /tests/test_testing.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import sqlalchemy.schema 3 | import sqlalchemy.types 4 | from pyramid_sqlalchemy.meta import BaseObject 5 | from pyramid_sqlalchemy.meta import Session 6 | 7 | 8 | class DatabaseTestCaseTests(unittest.TestCase): 9 | def DatabaseTestCase(self, *a, **kw): 10 | from pyramid_sqlalchemy.testing import DatabaseTestCase 11 | 12 | class MockCase(DatabaseTestCase): 13 | def runTest(self): # pragma: no cover 14 | pass 15 | 16 | return MockCase(*a, **kw) 17 | 18 | def test_tables_exist(self): 19 | from sqlalchemy.engine.reflection import Inspector 20 | 21 | testcase = self.DatabaseTestCase() 22 | try: 23 | testcase.setUp() 24 | inspector = Inspector.from_engine(Session.bind) 25 | self.assertTrue('dummy' in inspector.get_table_names()) 26 | finally: 27 | testcase.tearDown() 28 | 29 | def test_no_leakage(self): 30 | testcase = self.DatabaseTestCase() 31 | 32 | class Dummy(BaseObject): 33 | __tablename__ = 'dummy' 34 | 35 | id = sqlalchemy.schema.Column(sqlalchemy.types.Integer(), 36 | primary_key=True, autoincrement=True) 37 | 38 | try: 39 | testcase.setUp() 40 | Session.add(Dummy()) 41 | testcase.tearDown() 42 | testcase.setUp() 43 | self.assertEqual(Session.query(Dummy).count(), 0) 44 | finally: 45 | testcase.tearDown() 46 | --------------------------------------------------------------------------------