From 7f4c1239faff33a6f80e3a576bb63ecaadc15480 Mon Sep 17 00:00:00 2001 From: "Gavin M. Roy" Date: Fri, 29 Aug 2014 11:56:12 -0400 Subject: [PATCH] Initial version of the handler mixins --- .travis.yml | 26 ++++ LICENSE | 45 ++++--- MANIFEST.in | 7 ++ README.md | 2 - README.rst | 97 +++++++++++++++ dev-requirements.txt | 6 + docs/Makefile | 153 ++++++++++++++++++++++++ docs/api.rst | 2 + docs/conf.py | 32 +++++ docs/examples.rst | 57 +++++++++ docs/history.rst | 5 + docs/index.rst | 66 ++++++++++ requirements.txt | 2 + setup.cfg | 12 ++ setup.py | 68 +++++++++++ sprockets/__init__.py | 1 + sprockets/mixins/__init__.py | 1 + sprockets/mixins/postgresql/__init__.py | 109 +++++++++++++++++ test-requirements.txt | 3 + tests.py | 59 +++++++++ 20 files changed, 727 insertions(+), 26 deletions(-) create mode 100644 .travis.yml create mode 100644 MANIFEST.in delete mode 100644 README.md create mode 100644 README.rst create mode 100644 dev-requirements.txt create mode 100644 docs/Makefile create mode 100644 docs/api.rst create mode 100644 docs/conf.py create mode 100644 docs/examples.rst create mode 100644 docs/history.rst create mode 100644 docs/index.rst create mode 100644 requirements.txt create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 sprockets/__init__.py create mode 100644 sprockets/mixins/__init__.py create mode 100644 sprockets/mixins/postgresql/__init__.py create mode 100644 test-requirements.txt create mode 100644 tests.py diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..e6eb607 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,26 @@ +%YAML 1.1 +--- +language: python +python: + - 2.6 + - 2.7 + - pypy + - 3.2 + - 3.3 + - 3.4 +install: + - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install unittest2; fi + - pip install -e . + - pip install -r test-requirements.txt +script: nosetests +after_success: + - coveralls +deploy: + provider: pypi + user: sprockets + on: + python: 2.7 + tags: true + all_branches: true + password: + secure: JRmF7wfSuD3Aes2exf7x5mu9tqgkn5gmj1THacfcqqWntWW9odhVcdDfDzLltVsTpA8AKWkcSVBpUczZHWzq4J2lGfrkC5vzi9CHxepxWAduZ7YjmOi9UXxY5A0xjviZGObJjzpnnkeQD/M9dF0KcD/mm6ks94BLoeQeuHlUnPg= diff --git a/LICENSE b/LICENSE index 99b8e75..630f26e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,28 +1,25 @@ -Copyright (c) 2014, Sprockets +Copyright (c) 2014 AWeber Communications All rights reserved. -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of the {organization} 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. + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of Sprockets 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. \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..2979b93 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,7 @@ +include LICENSE +include README.rst +include *requirements.txt +graft docs +graft tests.py +global-exclude __pycache__ +global-exclude *.pyc \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 07d6210..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -sprockets.mixins.postgresql -=========================== diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..33698f1 --- /dev/null +++ b/README.rst @@ -0,0 +1,97 @@ +sprockets.mixins.postgresql +=========================== +Handler mixins that automatically connect a PostgreSQL client session upon initialization. + +|Version| |Downloads| |Status| |Coverage| |License| + +Installation +------------ +sprockets.mixins.postgresql is available on the +`Python Package Index `_ +and can be installed via ``pip`` or ``easy_install``: + +.. code:: bash + + pip install sprockets.mixins.postgresql + +Documentation +------------- +https://sprocketsmixinspostgresql.readthedocs.org + +Requirements +------------ +- `sprockets `_ +- `sprockets.clients.postgresql `_ + +Example +------- +The following example demonstrates using the ``HandlerMixin`` with a +synchronous Tornado ``RequestHandler `` for a +database named ``postgres``: + +.. code:: python + + import os + + from sprockets.mixins import postgresql + from tornado import web + + os.environ['POSTGRES_HOST'] = 'localhost' + os.environ['POSTGRES_USER'] = 'postgres' + os.environ['POSTGRES_PORT'] = 5432 + os.environ['POSTGRES_DBNAME'] = 'postgres' + + class PostgresRequestHandler(postgresql.HandlerMixin, + web.RequestHandler): + + DBNAME = 'postgres' + + def get(self, *args, **kwargs): + result = self.foo_session.query('SELECT * FROM bar') + self.finish({'data': result.items()}) + +The second example demonstrates using the ``AsyncHandlerMixin`` with an +asynchronous Tornado ``RequestHandler`` for a database named ``foo``: + +.. code:: python + + import os + + from sprockets.mixins import postgresql + from tornado import web + + os.environ['FOO_HOST'] = 'localhost' + os.environ['FOO_USER'] = 'postgres' + os.environ['FOO_PORT'] = 5432 + os.environ['FOO_DBNAME'] = 'foo' + os.environ['FOO_PASSWORD'] = 'bar' + + class FooRequestHandler(postgresql.HandlerMixin, + web.RequestHandler): + + DBNAME = 'foo' + + @web.asynchronous + def get(self, *args, **kwargs): + result = yield self.foo_session.query('SELECT * FROM baz') + self.finish({'data': result.items()}) + result.free() + +Version History +--------------- +Available at https://sprocketsmixinspostgresql.readthedocs.org/en/latest/history.html + +.. |Version| image:: https://badge.fury.io/py/sprockets.mixins.postgresql.svg? + :target: http://badge.fury.io/py/sprockets.mixins.postgresql + +.. |Status| image:: https://travis-ci.org/sprockets/sprockets.mixins.postgresql.svg?branch=master + :target: https://travis-ci.org/sprockets/sprockets.mixins.postgresql + +.. |Coverage| image:: https://img.shields.io/coveralls/sprockets/sprockets.mixins.postgresql.svg? + :target: https://coveralls.io/r/sprockets/sprockets.mixins.postgresql + +.. |Downloads| image:: https://pypip.in/d/sprockets.mixins.postgresql/badge.svg? + :target: https://pypi.python.org/pypi/sprockets.mixins.postgresql + +.. |License| image:: https://pypip.in/license/sprockets.mixins.postgresql/badge.svg? + :target: https://sprocketsmixinspostgresql.readthedocs.org \ No newline at end of file diff --git a/dev-requirements.txt b/dev-requirements.txt new file mode 100644 index 0000000..6a259fc --- /dev/null +++ b/dev-requirements.txt @@ -0,0 +1,6 @@ +-r requirements.txt +-r test-requirements.txt +flake8>=2.1,<3 +sphinx>=1.2,<2 +sphinx-rtd-theme>=0.1,<1.0 +sphinxcontrib-httpdomain>=1.2,<2 diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..54c18ff --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,153 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/rabbitpy.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/rabbitpy.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/rabbitpy" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/rabbitpy" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." \ No newline at end of file diff --git a/docs/api.rst b/docs/api.rst new file mode 100644 index 0000000..4f2ba7d --- /dev/null +++ b/docs/api.rst @@ -0,0 +1,2 @@ +.. automodule:: sprockets.mixins.postgresql + :members: \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..a260b25 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python +import sphinx_rtd_theme + +from sprockets.mixins.postgresql import version_info, __version__ + +needs_sphinx = '1.0' +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', + 'sphinxcontrib.httpdomain', +] +templates_path = [] +source_suffix = '.rst' +master_doc = 'index' +project = 'sprockets.mixins.postgresql' +copyright = '2014, AWeber Communications' +version = '.'.join(__version__.split('.')[0:1]) +release = __version__ +if len(version_info) > 3: + release += '-{0}'.format(str(v) for v in version_info[3:]) +exclude_patterns = [] +pygments_style = 'sphinx' +html_theme = 'sphinx_rtd_theme' +html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] +intersphinx_mapping = { + 'python': ('https://docs.python.org/', None), + 'sprockets': ('https://sprockets.readthedocs.org/en/latest/', None), + 'sprockets.clients.postgresql': ( + 'https://sprocketsclientspostgresql.readthedocs.org/en/latest/', + None), + 'tornado': ('http://tornadoweb.org/en/stable/', None) +} \ No newline at end of file diff --git a/docs/examples.rst b/docs/examples.rst new file mode 100644 index 0000000..ab4b495 --- /dev/null +++ b/docs/examples.rst @@ -0,0 +1,57 @@ +Examples +======== +The following example demonstrates using the +:py:class:`HandlerMixin ` with a +synchronous Tornado :py:class:`RequestHandler ` for a database +named ``postgres``: + +.. code:: python + + import os + + from sprockets.mixins import postgresql + from tornado import web + + os.environ['POSTGRES_HOST'] = 'localhost' + os.environ['POSTGRES_USER'] = 'postgres' + os.environ['POSTGRES_PORT'] = 5432 + os.environ['POSTGRES_DBNAME'] = 'postgres' + + class PostgresRequestHandler(postgresql.HandlerMixin, + web.RequestHandler): + + DBNAME = 'postgres' + + def get(self, *args, **kwargs): + result = self.foo_session.query('SELECT * FROM bar') + self.finish({'data': result.items()}) + +The second example demonstrates using the +:py:class:`AsyncHandlerMixin ` +with an asynchronous Tornado :py:class:`RequestHandler ` +for a database named ``foo``: + +.. code:: python + + import os + + from sprockets.mixins import postgresql + from tornado import web + + os.environ['FOO_HOST'] = 'localhost' + os.environ['FOO_USER'] = 'postgres' + os.environ['FOO_PORT'] = 5432 + os.environ['FOO_DBNAME'] = 'foo' + os.environ['FOO_PASSWORD'] = 'bar' + + class FooRequestHandler(postgresql.HandlerMixin, + web.RequestHandler): + + DBNAME = 'foo' + + @web.asynchronous + def get(self, *args, **kwargs): + result = yield self.foo_session.query('SELECT * FROM baz') + self.finish({'data': result.items()}) + result.free() + diff --git a/docs/history.rst b/docs/history.rst new file mode 100644 index 0000000..9c6846c --- /dev/null +++ b/docs/history.rst @@ -0,0 +1,5 @@ +Version History +--------------- +- 0.0.0 [YYYY-MM-DD] + - Change 1 + - Change 2 \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..997b5ac --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,66 @@ +sprockets.mixins.postgresql +=========================== +Handler mixins that automatically connect a PostgreSQL client session upon initialization. + +|Version| |Downloads| |Status| |Coverage| |License| + +Installation +------------ +``sprockets.mixins.postgresql`` is available on the +`Python Package Index `_ +and can be installed via ``pip`` or ``easy_install``: + +.. code:: bash + + pip install sprockets.mixins.postgresql + +Requirements +------------ +- `sprockets `_ +- `sprockets.clients.postgresql `_ + +API Documentation +----------------- +.. toctree:: + :maxdepth: 2 + + api + examples + +Version History +--------------- +See :doc:`history` + +Issues +------ +Please report any issues to the Github project at `https://github.com/sprockets/sprockets.mixins.postgresql/issues `_ + +Source +------ +``sprockets.mixins.postgresql`` source is available on Github at `https://github.com/sprockets/sprockets.mixins.postgresql `_ + +License +------- +``sprockets.mixins.postgresql`` is released under the `3-Clause BSD license `_. + +Indices and tables +------------------ + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + +.. |Version| image:: https://badge.fury.io/py/sprockets.mixins.postgresql.svg? + :target: http://badge.fury.io/py/sprockets.mixins.postgresql + +.. |Status| image:: https://travis-ci.org/sprockets/sprockets.mixins.postgresql.svg?branch=master + :target: https://travis-ci.org/sprockets/sprockets.mixins.postgresql + +.. |Coverage| image:: https://img.shields.io/coveralls/sprockets/sprockets.mixins.postgresql.svg? + :target: https://coveralls.io/r/sprockets/sprockets.mixins.postgresql + +.. |Downloads| image:: https://pypip.in/d/sprockets.mixins.postgresql/badge.svg? + :target: https://pypi.python.org/pypi/sprockets.mixins.postgresql + +.. |License| image:: https://pypip.in/license/sprockets.mixins.postgresql/badge.svg? + :target: https://sprocketsmixinspostgresql.readthedocs.org \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..174e1d6 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +sprockets<2 +sprockets.clients.postgresql>=1.0.0 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..79421df --- /dev/null +++ b/setup.cfg @@ -0,0 +1,12 @@ +[build_sphinx] +all-files = 1 +source-dir = docs +build-dir = build/docs + +[nosetests] +with-coverage = 1 +cover-package = sprockets.mixins.postgresql +verbose = 1 + +[flake8] +exclude = build,dist,docs,env \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..9ec424b --- /dev/null +++ b/setup.py @@ -0,0 +1,68 @@ +import codecs +import sys + +import setuptools + + +def read_requirements_file(req_name): + requirements = [] + try: + with codecs.open(req_name, encoding='utf-8') as req_file: + for req_line in req_file: + if '#' in req_line: + req_line = req_line[0:req_line.find('#')].strip() + if req_line: + requirements.append(req_line.strip()) + except IOError: + pass + return requirements + + +install_requires = read_requirements_file('requirements.txt') +setup_requires = read_requirements_file('setup-requirements.txt') +tests_require = read_requirements_file('test-requirements.txt') + +if sys.version_info < (2, 7): + tests_require.append('unittest2') +if sys.version_info < (3, 0): + tests_require.append('mock') + +setuptools.setup( + name='sprockets.mixins.postgresql', + version='0.0.0', + description='A sprockets mixin that automatically connects to PostgreSQL', + long_description=codecs.open('README.rst', encoding='utf-8').read(), + url='https://github.com/sprockets/sprockets.mixins.postgresql.git', + author='AWeber Communications', + author_email='api@aweber.com', + license=codecs.open('LICENSE', encoding='utf-8').read(), + classifiers=[ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Natural Language :: English', + 'Operating System :: OS Independent', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.2', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: Implementation :: CPython', + 'Programming Language :: Python :: Implementation :: PyPy', + 'Topic :: Software Development :: Libraries', + 'Topic :: Software Development :: Libraries :: Python Modules' + ], + packages=['sprockets', + 'sprockets.mixins', + 'sprockets.mixins.postgresql'], + package_data={'': ['LICENSE', 'README.md']}, + include_package_data=True, + namespace_packages=['sprockets', + 'sprockets.mixins'], + install_requires=install_requires, + setup_requires=setup_requires, + tests_require=tests_require, + test_suite='nose.collector', + zip_safe=False) \ No newline at end of file diff --git a/sprockets/__init__.py b/sprockets/__init__.py new file mode 100644 index 0000000..b0d6433 --- /dev/null +++ b/sprockets/__init__.py @@ -0,0 +1 @@ +__import__('pkg_resources').declare_namespace(__name__) \ No newline at end of file diff --git a/sprockets/mixins/__init__.py b/sprockets/mixins/__init__.py new file mode 100644 index 0000000..b0d6433 --- /dev/null +++ b/sprockets/mixins/__init__.py @@ -0,0 +1 @@ +__import__('pkg_resources').declare_namespace(__name__) \ No newline at end of file diff --git a/sprockets/mixins/postgresql/__init__.py b/sprockets/mixins/postgresql/__init__.py new file mode 100644 index 0000000..b73eceb --- /dev/null +++ b/sprockets/mixins/postgresql/__init__.py @@ -0,0 +1,109 @@ +""" +PostgreSQL Client Mixins +======================== + +Sprockets mixins that automatically connects to PostgreSQL using +`sprockets.clients.postgresql `_. + +Handlers implementing one of the mixins should set an attribute called +``DBNAME`` that specifies the database name to connect to. The value of +``DBNAME`` will be passed into the creation of the +:py:class:`Session ` or +:py:class:`TornadoSession ` +object. + +The Session classes wrap the Queries :py:class:`Session ` or +:py:class:`TornadoSession ` providing +environment variable based configuration. + +The environment variables should be set using the ``DBNAME_[VARIABLE]`` format +where ``[VARIABLE]`` is one of ``HOST``, ``PORT``, ``DBNAME``, ``USER``, and +``PASSWORD``. + +""" +version_info = (1, 0, 0) +__version__ = '.'.join(str(v) for v in version_info) + +from sprockets.clients import postgresql + + +class HandlerMixin(object): + """A handler mixin for connecting to PostgreSQL. The mixin automatically + creates the database session using the DBNAME attribute of the class as + the database name for the + :py:class:`Session ` object + creation. + + Using the mixin, the name of the session attribute will be + ``_session``, automatically created when initializing the object. + + Example: + + .. code:: python + + from sprockets.mixins import postgresql + from tornado import web + + + class FooRequestHandler(postgresql.HandlerMixin, + web.RequestHandler,): + + DBNAME = 'foo' + + def get(self, *args, **kwargs): + result = self.foo_session.query('SELECT * FROM bar') + self.finish({'data': result.items()}) + + """ + DBNAME = 'postgres' + + def initialize(self): + setattr(self, + '%s_session' % self.DBNAME, + postgresql.Session(self.DBNAME)) + try: + super(HandlerMixin, self).initialize() + except AttributeError: + pass + + +class AsyncHandlerMixin(object): + """A asynchronous Tornado handler mixin for connecting to PostgreSQL. The + mixin automatically creates the database session using the DBNAME attribute + of the class as the database name for the + :py:class:`TornadoSession ` + object creation. + + Using the mixin, the name of the session attribute will be + ``_session``, automatically created when initializing the object. + + Example: + + .. code:: python + + from sprockets.mixins import postgresql + from tornado import web + + + class FooRequestHandler(postgresql.AsyncHandlerMixin, + web.RequestHandler): + + DBNAME = 'foo' + + @web.asynchronous + def get(self, *args, **kwargs): + result = yield self.foo_session.query('SELECT * FROM bar') + self.finish({'data': result.items()}) + result.free() + + """ + DBNAME = 'postgres' + + def initialize(self): + setattr(self, + '%s_session' % self.DBNAME, + postgresql.TornadoSession(self.DBNAME)) + try: + super(AsyncHandlerMixin, self).initialize() + except AttributeError: + pass diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000..20b57db --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,3 @@ +coverage>=3.7,<4 +coveralls>=0.4,<1 +nose>=1.3,<2 diff --git a/tests.py b/tests.py new file mode 100644 index 0000000..33b86bc --- /dev/null +++ b/tests.py @@ -0,0 +1,59 @@ +""" +Tests for the sprockets.mixins.postgresql package + +""" +import mock +import os +try: + import unittest2 as unittest +except ImportError: + import unittest + +from sprockets.clients import postgresql as _postgresql +from sprockets.mixins import postgresql + + +class MixinRequestHandler(postgresql.HandlerMixin): + DBNAME = 'bar' + + +class AsyncMixinRequestHandler(postgresql.AsyncHandlerMixin): + DBNAME = 'baz' + + +class HandlerMixinTest(unittest.TestCase): + + @mock.patch('queries.session.Session.__init__') + def setUp(self, mock_init): + self.mock_init = mock_init + + os.environ['BAR_HOST'] = 'db1' + os.environ['BAR_PORT'] = '5433' + os.environ['BAR_DBNAME'] = 'bar' + os.environ['BAR_USER'] = 'foo' + os.environ['BAR_PASSWORD'] = 'baz' + self.mixin = MixinRequestHandler() + self.mixin.initialize() + + def test_session_get_uri_value(self): + self.assertIsInstance(getattr(self.mixin, 'bar_session'), + _postgresql.Session) + + +class AsyncHandlerMixinTest(unittest.TestCase): + + @mock.patch('queries.tornado_session.TornadoSession.__init__') + def setUp(self, mock_init): + self.mock_init = mock_init + + os.environ['BAZ_HOST'] = 'db2' + os.environ['BAZ_PORT'] = '5434' + os.environ['BAZ_DBNAME'] = 'baz' + os.environ['BAZ_USER'] = 'qux' + os.environ['BAZ_PASSWORD'] = 'corgie' + self.mixin = AsyncMixinRequestHandler() + self.mixin.initialize() + + def test_session_get_uri_value(self): + self.assertIsInstance(getattr(self.mixin, 'baz_session'), + _postgresql.TornadoSession)