aiopg-0.13.2/0000775000372000037200000000000013223237542013541 5ustar travistravis00000000000000aiopg-0.13.2/LICENSE.txt0000664000372000037200000000242313223236761015367 0ustar travistravis00000000000000Copyright (c) 2014, 2015, Andrew Svetlov All rights reserved. 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. 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. aiopg-0.13.2/README.rst0000664000372000037200000000511513223236761015234 0ustar travistravis00000000000000aiopg ===== .. image:: https://travis-ci.org/aio-libs/aiopg.svg?branch=master :target: https://travis-ci.org/aio-libs/aiopg .. image:: https://codecov.io/gh/aio-libs/aiopg/branch/master/graph/badge.svg :target: https://codecov.io/gh/aio-libs/aiopg .. image:: https://badges.gitter.im/Join%20Chat.svg :target: https://gitter.im/aio-libs/Lobby :alt: Chat on Gitter **aiopg** is a library for accessing a PostgreSQL_ database from the asyncio_ (PEP-3156/tulip) framework. It wraps asynchronous features of the Psycopg database driver. Example ------- .. code:: python import asyncio import aiopg dsn = 'dbname=aiopg user=aiopg password=passwd host=127.0.0.1' async def go(): pool = await aiopg.create_pool(dsn) async with pool.acquire() as conn: async with conn.cursor() as cur: await cur.execute("SELECT 1") ret = [] async for row in cur: ret.append(row) assert ret == [(1,)] loop = asyncio.get_event_loop() loop.run_until_complete(go()) Example of SQLAlchemy optional integration ------------------------------------------ .. code:: python import asyncio from aiopg.sa import create_engine import sqlalchemy as sa metadata = sa.MetaData() tbl = sa.Table('tbl', metadata, sa.Column('id', sa.Integer, primary_key=True), sa.Column('val', sa.String(255))) async def create_table(engine): async with engine.acquire() as conn: await conn.execute('DROP TABLE IF EXISTS tbl') await conn.execute('''CREATE TABLE tbl ( id serial PRIMARY KEY, val varchar(255))''') async def go(): async with create_engine(user='aiopg', database='aiopg', host='127.0.0.1', password='passwd') as engine: async with engine.acquire() as conn: await conn.execute(tbl.insert().values(val='abc')) async for row in conn.execute(tbl.select()): print(row.id, row.val) loop = asyncio.get_event_loop() loop.run_until_complete(go()) For ``yield from`` based code, see the ``./examples`` folder, files with ``old_style`` part in their names. .. _PostgreSQL: http://www.postgresql.org/ .. _asyncio: http://docs.python.org/3.4/library/asyncio.html Please use:: $ make test for executing the project's unittests. See CONTRIBUTING.rst for details on how to set up your environment to run the tests. aiopg-0.13.2/setup.py0000664000372000037200000000355213223236761015262 0ustar travistravis00000000000000import os import re import sys from setuptools import setup install_requires = ['psycopg2>=2.5.2'] PY_VER = sys.version_info if PY_VER < (3, 4): raise RuntimeError("aiopg doesn't suppport Python earlier than 3.4") def read(f): return open(os.path.join(os.path.dirname(__file__), f)).read().strip() extras_require = {'sa': ['sqlalchemy>=1.1'], } def read_version(): regexp = re.compile(r"^__version__\W*=\W*'([\d.abrc]+)'") init_py = os.path.join(os.path.dirname(__file__), 'aiopg', '__init__.py') with open(init_py) as f: for line in f: match = regexp.match(line) if match is not None: return match.group(1) else: raise RuntimeError('Cannot find version in aiopg/__init__.py') classifiers = [ 'License :: OSI Approved :: BSD License', 'Intended Audience :: Developers', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Operating System :: POSIX', 'Operating System :: MacOS :: MacOS X', 'Operating System :: Microsoft :: Windows', 'Environment :: Web Environment', 'Development Status :: 4 - Beta', 'Topic :: Database', 'Topic :: Database :: Front-Ends', 'Framework :: AsyncIO', ] setup(name='aiopg', version=read_version(), description=('Postgres integration with asyncio.'), long_description='\n\n'.join((read('README.rst'), read('CHANGES.txt'))), classifiers=classifiers, platforms=['POSIX'], author='Andrew Svetlov', author_email='andrew.svetlov@gmail.com', url='https://aiopg.readthedocs.io', download_url='https://pypi.python.org/pypi/aiopg', license='BSD', packages=['aiopg', 'aiopg.sa'], install_requires=install_requires, extras_require=extras_require, include_package_data=True) aiopg-0.13.2/aiopg.egg-info/0000775000372000037200000000000013223237542016332 5ustar travistravis00000000000000aiopg-0.13.2/aiopg.egg-info/top_level.txt0000664000372000037200000000000613223237542021060 0ustar travistravis00000000000000aiopg aiopg-0.13.2/aiopg.egg-info/requires.txt0000664000372000037200000000004613223237542020732 0ustar travistravis00000000000000psycopg2>=2.5.2 [sa] sqlalchemy>=1.1 aiopg-0.13.2/aiopg.egg-info/PKG-INFO0000664000372000037200000002354513223237542017440 0ustar travistravis00000000000000Metadata-Version: 1.1 Name: aiopg Version: 0.13.2 Summary: Postgres integration with asyncio. Home-page: https://aiopg.readthedocs.io Author: Andrew Svetlov Author-email: andrew.svetlov@gmail.com License: BSD Download-URL: https://pypi.python.org/pypi/aiopg Description-Content-Type: UNKNOWN Description: aiopg ===== .. image:: https://travis-ci.org/aio-libs/aiopg.svg?branch=master :target: https://travis-ci.org/aio-libs/aiopg .. image:: https://codecov.io/gh/aio-libs/aiopg/branch/master/graph/badge.svg :target: https://codecov.io/gh/aio-libs/aiopg .. image:: https://badges.gitter.im/Join%20Chat.svg :target: https://gitter.im/aio-libs/Lobby :alt: Chat on Gitter **aiopg** is a library for accessing a PostgreSQL_ database from the asyncio_ (PEP-3156/tulip) framework. It wraps asynchronous features of the Psycopg database driver. Example ------- .. code:: python import asyncio import aiopg dsn = 'dbname=aiopg user=aiopg password=passwd host=127.0.0.1' async def go(): pool = await aiopg.create_pool(dsn) async with pool.acquire() as conn: async with conn.cursor() as cur: await cur.execute("SELECT 1") ret = [] async for row in cur: ret.append(row) assert ret == [(1,)] loop = asyncio.get_event_loop() loop.run_until_complete(go()) Example of SQLAlchemy optional integration ------------------------------------------ .. code:: python import asyncio from aiopg.sa import create_engine import sqlalchemy as sa metadata = sa.MetaData() tbl = sa.Table('tbl', metadata, sa.Column('id', sa.Integer, primary_key=True), sa.Column('val', sa.String(255))) async def create_table(engine): async with engine.acquire() as conn: await conn.execute('DROP TABLE IF EXISTS tbl') await conn.execute('''CREATE TABLE tbl ( id serial PRIMARY KEY, val varchar(255))''') async def go(): async with create_engine(user='aiopg', database='aiopg', host='127.0.0.1', password='passwd') as engine: async with engine.acquire() as conn: await conn.execute(tbl.insert().values(val='abc')) async for row in conn.execute(tbl.select()): print(row.id, row.val) loop = asyncio.get_event_loop() loop.run_until_complete(go()) For ``yield from`` based code, see the ``./examples`` folder, files with ``old_style`` part in their names. .. _PostgreSQL: http://www.postgresql.org/ .. _asyncio: http://docs.python.org/3.4/library/asyncio.html Please use:: $ make test for executing the project's unittests. See CONTRIBUTING.rst for details on how to set up your environment to run the tests. CHANGES ------- 0.13.2 (2018-01-03) ^^^^^^^^^^^^^^^^^^^ * Fixed compatibility with SQLAlchemy 1.2.0 #412 * Added support for transaction isolation levels #219 0.13.1 (2017-09-10) ^^^^^^^^^^^^^^^^^^^ * Added connection poll recycling logic #373 0.13.0 (2016-12-02) ^^^^^^^^^^^^^^^^^^^ * Add `async with` support to `.begin_nested()` #208 * Fix connection.cancel() #212 #223 * Raise informative error on unexpected connection closing #191 * Added support for python types columns issues #217 * Added support for default values in SA table issues #206 0.12.0 (2016-10-09) ^^^^^^^^^^^^^^^^^^^ * Add an on_connect callback parameter to pool #141 * Fixed connection to work under both windows and posix based systems #142 0.11.0 (2016-09-12) ^^^^^^^^^^^^^^^^^^^ * Immediately remove callbacks from a closed file descriptor #139 * Drop Python 3.3 support 0.10.0 (2016-07-16) ^^^^^^^^^^^^^^^^^^^ * Refactor tests to use dockerized Postgres server #107 * Reduce default pool minsize to 1 #106 * Explicitly enumerate packages in setup.py #85 * Remove expired connections from pool on acquire #116 * Don't crash when Connection is GC'ed #124 * Use loop.create_future() if available 0.9.2 (2016-01-31) ^^^^^^^^^^^^^^^^^^ * Make pool.release return asyncio.Future, so we can wait on it in `__aexit__` #102 * Add support for uuid type #103 0.9.1 (2016-01-17) ^^^^^^^^^^^^^^^^^^ * Documentation update #101 0.9.0 (2016-01-14) ^^^^^^^^^^^^^^^^^^ * Add async context managers for transactions #91 * Support async iterator in ResultProxy #92 * Add async with for engine #90 0.8.0 (2015-12-31) ^^^^^^^^^^^^^^^^^^ * Add PostgreSQL notification support #58 * Support pools with unlimited size #59 * Cancel current DB operation on asyncio timeout #66 * Add async with support for Pool, Connection, Cursor #88 0.7.0 (2015-04-22) ^^^^^^^^^^^^^^^^^^ * Get rid of resource leak on connection failure. * Report ResourceWarning on non-closed connections. * Deprecate iteration protocol support in cursor and ResultProxy. * Release sa connection to pool on `connection.close()`. 0.6.0 (2015-02-03) ^^^^^^^^^^^^^^^^^^ * Accept dict, list, tuple, named and positional parameters in `SAConnection.execute()` 0.5.2 (2014-12-08) ^^^^^^^^^^^^^^^^^^ * Minor release, fixes a bug that leaves connection in broken state after `cursor.execute()` failure. 0.5.1 (2014-10-31) ^^^^^^^^^^^^^^^^^^ * Fix a bug for processing transactions in line. 0.5.0 (2014-10-31) ^^^^^^^^^^^^^^^^^^ * Add .terminate() to Pool and Engine * Reimplement connection pool (now pool size cannot be greater than pool.maxsize) * Add .close() and .wait_closed() to Pool and Engine * Add minsize, maxsize, size and freesize properties to sa.Engine * Support *echo* parameter for logging executed SQL commands * Connection.close() is not a coroutine (but we keep backward compatibility). 0.4.1 (2014-10-02) ^^^^^^^^^^^^^^^^^^ * make cursor iterable * update docs 0.4.0 (2014-10-02) ^^^^^^^^^^^^^^^^^^ * add timeouts for database operations. * Autoregister psycopg2 support for json data type. * Support JSON in aiopg.sa * Support ARRAY in aiopg.sa * Autoregister hstore support if present in connected DB * Support HSTORE in aiopg.sa 0.3.2 (2014-07-07) ^^^^^^^^^^^^^^^^^^ * change signature to cursor.execute(operation, parameters=None) to follow psycopg2 convention. 0.3.1 (2014-07-04) ^^^^^^^^^^^^^^^^^^ * Forward arguments to cursor constructor for pooled connections. 0.3.0 (2014-06-22) ^^^^^^^^^^^^^^^^^^ * Allow executing SQLAlchemy DDL statements. * Fix bug with race conditions on acquiring/releasing connections from pool. 0.2.3 (2014-06-12) ^^^^^^^^^^^^^^^^^^ * Fix bug in connection pool. 0.2.2 (2014-06-07) ^^^^^^^^^^^^^^^^^^ * Fix bug with passing parameters into SAConnection.execute when executing raw SQL expression. 0.2.1 (2014-05-08) ^^^^^^^^^^^^^^^^^^ * Close connection with invalid transaction status on returning to pool. 0.2.0 (2014-05-04) ^^^^^^^^^^^^^^^^^^ * Implemented optional support for sqlalchemy functional sql layer. 0.1.0 (2014-04-06) ^^^^^^^^^^^^^^^^^^ * Implemented plain connections: connect, Connection, Cursor. * Implemented database pools: create_pool and Pool. Platform: POSIX Classifier: License :: OSI Approved :: BSD License Classifier: Intended Audience :: Developers Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Operating System :: POSIX Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: Microsoft :: Windows Classifier: Environment :: Web Environment Classifier: Development Status :: 4 - Beta Classifier: Topic :: Database Classifier: Topic :: Database :: Front-Ends Classifier: Framework :: AsyncIO aiopg-0.13.2/aiopg.egg-info/dependency_links.txt0000664000372000037200000000000113223237542022400 0ustar travistravis00000000000000 aiopg-0.13.2/aiopg.egg-info/SOURCES.txt0000664000372000037200000000070013223237542020213 0ustar travistravis00000000000000CHANGES.txt LICENSE.txt MANIFEST.in README.rst setup.cfg setup.py aiopg/__init__.py aiopg/connection.py aiopg/cursor.py aiopg/log.py aiopg/pool.py aiopg/transaction.py aiopg/utils.py aiopg.egg-info/PKG-INFO aiopg.egg-info/SOURCES.txt aiopg.egg-info/dependency_links.txt aiopg.egg-info/requires.txt aiopg.egg-info/top_level.txt aiopg/sa/__init__.py aiopg/sa/connection.py aiopg/sa/engine.py aiopg/sa/exc.py aiopg/sa/result.py aiopg/sa/transaction.pyaiopg-0.13.2/setup.cfg0000664000372000037200000000016613223237542015365 0ustar travistravis00000000000000[tool:pytest] timeout = 300 [coverage:run] branch = true source = aiopg,tests [egg_info] tag_build = tag_date = 0 aiopg-0.13.2/MANIFEST.in0000664000372000037200000000015513223236761015302 0ustar travistravis00000000000000include LICENSE.txt include CHANGES.txt include README.rst graft aiopg global-exclude *.pyc exclude tests/** aiopg-0.13.2/PKG-INFO0000664000372000037200000002354513223237542014647 0ustar travistravis00000000000000Metadata-Version: 1.1 Name: aiopg Version: 0.13.2 Summary: Postgres integration with asyncio. Home-page: https://aiopg.readthedocs.io Author: Andrew Svetlov Author-email: andrew.svetlov@gmail.com License: BSD Download-URL: https://pypi.python.org/pypi/aiopg Description-Content-Type: UNKNOWN Description: aiopg ===== .. image:: https://travis-ci.org/aio-libs/aiopg.svg?branch=master :target: https://travis-ci.org/aio-libs/aiopg .. image:: https://codecov.io/gh/aio-libs/aiopg/branch/master/graph/badge.svg :target: https://codecov.io/gh/aio-libs/aiopg .. image:: https://badges.gitter.im/Join%20Chat.svg :target: https://gitter.im/aio-libs/Lobby :alt: Chat on Gitter **aiopg** is a library for accessing a PostgreSQL_ database from the asyncio_ (PEP-3156/tulip) framework. It wraps asynchronous features of the Psycopg database driver. Example ------- .. code:: python import asyncio import aiopg dsn = 'dbname=aiopg user=aiopg password=passwd host=127.0.0.1' async def go(): pool = await aiopg.create_pool(dsn) async with pool.acquire() as conn: async with conn.cursor() as cur: await cur.execute("SELECT 1") ret = [] async for row in cur: ret.append(row) assert ret == [(1,)] loop = asyncio.get_event_loop() loop.run_until_complete(go()) Example of SQLAlchemy optional integration ------------------------------------------ .. code:: python import asyncio from aiopg.sa import create_engine import sqlalchemy as sa metadata = sa.MetaData() tbl = sa.Table('tbl', metadata, sa.Column('id', sa.Integer, primary_key=True), sa.Column('val', sa.String(255))) async def create_table(engine): async with engine.acquire() as conn: await conn.execute('DROP TABLE IF EXISTS tbl') await conn.execute('''CREATE TABLE tbl ( id serial PRIMARY KEY, val varchar(255))''') async def go(): async with create_engine(user='aiopg', database='aiopg', host='127.0.0.1', password='passwd') as engine: async with engine.acquire() as conn: await conn.execute(tbl.insert().values(val='abc')) async for row in conn.execute(tbl.select()): print(row.id, row.val) loop = asyncio.get_event_loop() loop.run_until_complete(go()) For ``yield from`` based code, see the ``./examples`` folder, files with ``old_style`` part in their names. .. _PostgreSQL: http://www.postgresql.org/ .. _asyncio: http://docs.python.org/3.4/library/asyncio.html Please use:: $ make test for executing the project's unittests. See CONTRIBUTING.rst for details on how to set up your environment to run the tests. CHANGES ------- 0.13.2 (2018-01-03) ^^^^^^^^^^^^^^^^^^^ * Fixed compatibility with SQLAlchemy 1.2.0 #412 * Added support for transaction isolation levels #219 0.13.1 (2017-09-10) ^^^^^^^^^^^^^^^^^^^ * Added connection poll recycling logic #373 0.13.0 (2016-12-02) ^^^^^^^^^^^^^^^^^^^ * Add `async with` support to `.begin_nested()` #208 * Fix connection.cancel() #212 #223 * Raise informative error on unexpected connection closing #191 * Added support for python types columns issues #217 * Added support for default values in SA table issues #206 0.12.0 (2016-10-09) ^^^^^^^^^^^^^^^^^^^ * Add an on_connect callback parameter to pool #141 * Fixed connection to work under both windows and posix based systems #142 0.11.0 (2016-09-12) ^^^^^^^^^^^^^^^^^^^ * Immediately remove callbacks from a closed file descriptor #139 * Drop Python 3.3 support 0.10.0 (2016-07-16) ^^^^^^^^^^^^^^^^^^^ * Refactor tests to use dockerized Postgres server #107 * Reduce default pool minsize to 1 #106 * Explicitly enumerate packages in setup.py #85 * Remove expired connections from pool on acquire #116 * Don't crash when Connection is GC'ed #124 * Use loop.create_future() if available 0.9.2 (2016-01-31) ^^^^^^^^^^^^^^^^^^ * Make pool.release return asyncio.Future, so we can wait on it in `__aexit__` #102 * Add support for uuid type #103 0.9.1 (2016-01-17) ^^^^^^^^^^^^^^^^^^ * Documentation update #101 0.9.0 (2016-01-14) ^^^^^^^^^^^^^^^^^^ * Add async context managers for transactions #91 * Support async iterator in ResultProxy #92 * Add async with for engine #90 0.8.0 (2015-12-31) ^^^^^^^^^^^^^^^^^^ * Add PostgreSQL notification support #58 * Support pools with unlimited size #59 * Cancel current DB operation on asyncio timeout #66 * Add async with support for Pool, Connection, Cursor #88 0.7.0 (2015-04-22) ^^^^^^^^^^^^^^^^^^ * Get rid of resource leak on connection failure. * Report ResourceWarning on non-closed connections. * Deprecate iteration protocol support in cursor and ResultProxy. * Release sa connection to pool on `connection.close()`. 0.6.0 (2015-02-03) ^^^^^^^^^^^^^^^^^^ * Accept dict, list, tuple, named and positional parameters in `SAConnection.execute()` 0.5.2 (2014-12-08) ^^^^^^^^^^^^^^^^^^ * Minor release, fixes a bug that leaves connection in broken state after `cursor.execute()` failure. 0.5.1 (2014-10-31) ^^^^^^^^^^^^^^^^^^ * Fix a bug for processing transactions in line. 0.5.0 (2014-10-31) ^^^^^^^^^^^^^^^^^^ * Add .terminate() to Pool and Engine * Reimplement connection pool (now pool size cannot be greater than pool.maxsize) * Add .close() and .wait_closed() to Pool and Engine * Add minsize, maxsize, size and freesize properties to sa.Engine * Support *echo* parameter for logging executed SQL commands * Connection.close() is not a coroutine (but we keep backward compatibility). 0.4.1 (2014-10-02) ^^^^^^^^^^^^^^^^^^ * make cursor iterable * update docs 0.4.0 (2014-10-02) ^^^^^^^^^^^^^^^^^^ * add timeouts for database operations. * Autoregister psycopg2 support for json data type. * Support JSON in aiopg.sa * Support ARRAY in aiopg.sa * Autoregister hstore support if present in connected DB * Support HSTORE in aiopg.sa 0.3.2 (2014-07-07) ^^^^^^^^^^^^^^^^^^ * change signature to cursor.execute(operation, parameters=None) to follow psycopg2 convention. 0.3.1 (2014-07-04) ^^^^^^^^^^^^^^^^^^ * Forward arguments to cursor constructor for pooled connections. 0.3.0 (2014-06-22) ^^^^^^^^^^^^^^^^^^ * Allow executing SQLAlchemy DDL statements. * Fix bug with race conditions on acquiring/releasing connections from pool. 0.2.3 (2014-06-12) ^^^^^^^^^^^^^^^^^^ * Fix bug in connection pool. 0.2.2 (2014-06-07) ^^^^^^^^^^^^^^^^^^ * Fix bug with passing parameters into SAConnection.execute when executing raw SQL expression. 0.2.1 (2014-05-08) ^^^^^^^^^^^^^^^^^^ * Close connection with invalid transaction status on returning to pool. 0.2.0 (2014-05-04) ^^^^^^^^^^^^^^^^^^ * Implemented optional support for sqlalchemy functional sql layer. 0.1.0 (2014-04-06) ^^^^^^^^^^^^^^^^^^ * Implemented plain connections: connect, Connection, Cursor. * Implemented database pools: create_pool and Pool. Platform: POSIX Classifier: License :: OSI Approved :: BSD License Classifier: Intended Audience :: Developers Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Operating System :: POSIX Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: Microsoft :: Windows Classifier: Environment :: Web Environment Classifier: Development Status :: 4 - Beta Classifier: Topic :: Database Classifier: Topic :: Database :: Front-Ends Classifier: Framework :: AsyncIO aiopg-0.13.2/CHANGES.txt0000664000372000037200000001020613223236761015353 0ustar travistravis00000000000000CHANGES ------- 0.13.2 (2018-01-03) ^^^^^^^^^^^^^^^^^^^ * Fixed compatibility with SQLAlchemy 1.2.0 #412 * Added support for transaction isolation levels #219 0.13.1 (2017-09-10) ^^^^^^^^^^^^^^^^^^^ * Added connection poll recycling logic #373 0.13.0 (2016-12-02) ^^^^^^^^^^^^^^^^^^^ * Add `async with` support to `.begin_nested()` #208 * Fix connection.cancel() #212 #223 * Raise informative error on unexpected connection closing #191 * Added support for python types columns issues #217 * Added support for default values in SA table issues #206 0.12.0 (2016-10-09) ^^^^^^^^^^^^^^^^^^^ * Add an on_connect callback parameter to pool #141 * Fixed connection to work under both windows and posix based systems #142 0.11.0 (2016-09-12) ^^^^^^^^^^^^^^^^^^^ * Immediately remove callbacks from a closed file descriptor #139 * Drop Python 3.3 support 0.10.0 (2016-07-16) ^^^^^^^^^^^^^^^^^^^ * Refactor tests to use dockerized Postgres server #107 * Reduce default pool minsize to 1 #106 * Explicitly enumerate packages in setup.py #85 * Remove expired connections from pool on acquire #116 * Don't crash when Connection is GC'ed #124 * Use loop.create_future() if available 0.9.2 (2016-01-31) ^^^^^^^^^^^^^^^^^^ * Make pool.release return asyncio.Future, so we can wait on it in `__aexit__` #102 * Add support for uuid type #103 0.9.1 (2016-01-17) ^^^^^^^^^^^^^^^^^^ * Documentation update #101 0.9.0 (2016-01-14) ^^^^^^^^^^^^^^^^^^ * Add async context managers for transactions #91 * Support async iterator in ResultProxy #92 * Add async with for engine #90 0.8.0 (2015-12-31) ^^^^^^^^^^^^^^^^^^ * Add PostgreSQL notification support #58 * Support pools with unlimited size #59 * Cancel current DB operation on asyncio timeout #66 * Add async with support for Pool, Connection, Cursor #88 0.7.0 (2015-04-22) ^^^^^^^^^^^^^^^^^^ * Get rid of resource leak on connection failure. * Report ResourceWarning on non-closed connections. * Deprecate iteration protocol support in cursor and ResultProxy. * Release sa connection to pool on `connection.close()`. 0.6.0 (2015-02-03) ^^^^^^^^^^^^^^^^^^ * Accept dict, list, tuple, named and positional parameters in `SAConnection.execute()` 0.5.2 (2014-12-08) ^^^^^^^^^^^^^^^^^^ * Minor release, fixes a bug that leaves connection in broken state after `cursor.execute()` failure. 0.5.1 (2014-10-31) ^^^^^^^^^^^^^^^^^^ * Fix a bug for processing transactions in line. 0.5.0 (2014-10-31) ^^^^^^^^^^^^^^^^^^ * Add .terminate() to Pool and Engine * Reimplement connection pool (now pool size cannot be greater than pool.maxsize) * Add .close() and .wait_closed() to Pool and Engine * Add minsize, maxsize, size and freesize properties to sa.Engine * Support *echo* parameter for logging executed SQL commands * Connection.close() is not a coroutine (but we keep backward compatibility). 0.4.1 (2014-10-02) ^^^^^^^^^^^^^^^^^^ * make cursor iterable * update docs 0.4.0 (2014-10-02) ^^^^^^^^^^^^^^^^^^ * add timeouts for database operations. * Autoregister psycopg2 support for json data type. * Support JSON in aiopg.sa * Support ARRAY in aiopg.sa * Autoregister hstore support if present in connected DB * Support HSTORE in aiopg.sa 0.3.2 (2014-07-07) ^^^^^^^^^^^^^^^^^^ * change signature to cursor.execute(operation, parameters=None) to follow psycopg2 convention. 0.3.1 (2014-07-04) ^^^^^^^^^^^^^^^^^^ * Forward arguments to cursor constructor for pooled connections. 0.3.0 (2014-06-22) ^^^^^^^^^^^^^^^^^^ * Allow executing SQLAlchemy DDL statements. * Fix bug with race conditions on acquiring/releasing connections from pool. 0.2.3 (2014-06-12) ^^^^^^^^^^^^^^^^^^ * Fix bug in connection pool. 0.2.2 (2014-06-07) ^^^^^^^^^^^^^^^^^^ * Fix bug with passing parameters into SAConnection.execute when executing raw SQL expression. 0.2.1 (2014-05-08) ^^^^^^^^^^^^^^^^^^ * Close connection with invalid transaction status on returning to pool. 0.2.0 (2014-05-04) ^^^^^^^^^^^^^^^^^^ * Implemented optional support for sqlalchemy functional sql layer. 0.1.0 (2014-04-06) ^^^^^^^^^^^^^^^^^^ * Implemented plain connections: connect, Connection, Cursor. * Implemented database pools: create_pool and Pool. aiopg-0.13.2/aiopg/0000775000372000037200000000000013223237542014640 5ustar travistravis00000000000000aiopg-0.13.2/aiopg/pool.py0000664000372000037200000002462413223236761016175 0ustar travistravis00000000000000import asyncio import collections import sys import warnings from psycopg2.extensions import TRANSACTION_STATUS_IDLE from .connection import connect, TIMEOUT from .log import logger from .utils import (PY_35, _PoolContextManager, _PoolConnectionContextManager, _PoolCursorContextManager, _PoolAcquireContextManager, ensure_future, create_future) PY_341 = sys.version_info >= (3, 4, 1) def create_pool(dsn=None, *, minsize=1, maxsize=10, loop=None, timeout=TIMEOUT, pool_recycle=-1, enable_json=True, enable_hstore=True, enable_uuid=True, echo=False, on_connect=None, **kwargs): coro = _create_pool(dsn=dsn, minsize=minsize, maxsize=maxsize, loop=loop, timeout=timeout, pool_recycle=pool_recycle, enable_json=enable_json, enable_hstore=enable_hstore, enable_uuid=enable_uuid, echo=echo, on_connect=on_connect, **kwargs) return _PoolContextManager(coro) @asyncio.coroutine def _create_pool(dsn=None, *, minsize=1, maxsize=10, loop=None, timeout=TIMEOUT, pool_recycle=-1, enable_json=True, enable_hstore=True, enable_uuid=True, echo=False, on_connect=None, **kwargs): if loop is None: loop = asyncio.get_event_loop() pool = Pool(dsn, minsize, maxsize, loop, timeout, enable_json=enable_json, enable_hstore=enable_hstore, enable_uuid=enable_uuid, echo=echo, on_connect=on_connect, pool_recycle=pool_recycle, **kwargs) if minsize > 0: with (yield from pool._cond): yield from pool._fill_free_pool(False) return pool class Pool(asyncio.AbstractServer): """Connection pool""" def __init__(self, dsn, minsize, maxsize, loop, timeout, *, enable_json, enable_hstore, enable_uuid, echo, on_connect, pool_recycle, **kwargs): if minsize < 0: raise ValueError("minsize should be zero or greater") if maxsize < minsize and maxsize != 0: raise ValueError("maxsize should be not less than minsize") self._dsn = dsn self._minsize = minsize self._loop = loop self._timeout = timeout self._recycle = pool_recycle self._enable_json = enable_json self._enable_hstore = enable_hstore self._enable_uuid = enable_uuid self._echo = echo self._on_connect = on_connect self._conn_kwargs = kwargs self._acquiring = 0 self._free = collections.deque(maxlen=maxsize or None) self._cond = asyncio.Condition(loop=loop) self._used = set() self._terminated = set() self._closing = False self._closed = False @property def echo(self): return self._echo @property def minsize(self): return self._minsize @property def maxsize(self): return self._free.maxlen @property def size(self): return self.freesize + len(self._used) + self._acquiring @property def freesize(self): return len(self._free) @property def timeout(self): return self._timeout @asyncio.coroutine def clear(self): """Close all free connections in pool.""" with (yield from self._cond): while self._free: conn = self._free.popleft() yield from conn.close() self._cond.notify() @property def closed(self): return self._closed def close(self): """Close pool. Mark all pool connections to be closed on getting back to pool. Closed pool doesn't allow to acquire new connections. """ if self._closed: return self._closing = True def terminate(self): """Terminate pool. Close pool with instantly closing all acquired connections also. """ self.close() for conn in list(self._used): conn.close() self._terminated.add(conn) self._used.clear() @asyncio.coroutine def wait_closed(self): """Wait for closing all pool's connections.""" if self._closed: return if not self._closing: raise RuntimeError(".wait_closed() should be called " "after .close()") while self._free: conn = self._free.popleft() conn.close() with (yield from self._cond): while self.size > self.freesize: yield from self._cond.wait() self._closed = True def acquire(self): """Acquire free connection from the pool.""" coro = self._acquire() return _PoolAcquireContextManager(coro, self) @asyncio.coroutine def _acquire(self): if self._closing: raise RuntimeError("Cannot acquire connection after closing pool") with (yield from self._cond): while True: yield from self._fill_free_pool(True) if self._free: conn = self._free.popleft() assert not conn.closed, conn assert conn not in self._used, (conn, self._used) self._used.add(conn) if self._on_connect is not None: yield from self._on_connect(conn) return conn else: yield from self._cond.wait() @asyncio.coroutine def _fill_free_pool(self, override_min): # iterate over free connections and remove timeouted ones n, free = 0, len(self._free) while n < free: conn = self._free[-1] if conn.closed: self._free.pop() elif self._recycle > -1 \ and self._loop.time() - conn.last_usage > self._recycle: conn.close() self._free.pop() else: self._free.rotate() n += 1 while self.size < self.minsize: self._acquiring += 1 try: conn = yield from connect( self._dsn, loop=self._loop, timeout=self._timeout, enable_json=self._enable_json, enable_hstore=self._enable_hstore, enable_uuid=self._enable_uuid, echo=self._echo, **self._conn_kwargs) # raise exception if pool is closing self._free.append(conn) self._cond.notify() finally: self._acquiring -= 1 if self._free: return if override_min and self.size < self.maxsize: self._acquiring += 1 try: conn = yield from connect( self._dsn, loop=self._loop, timeout=self._timeout, enable_json=self._enable_json, enable_hstore=self._enable_hstore, enable_uuid=self._enable_uuid, echo=self._echo, **self._conn_kwargs) # raise exception if pool is closing self._free.append(conn) self._cond.notify() finally: self._acquiring -= 1 @asyncio.coroutine def _wakeup(self): with (yield from self._cond): self._cond.notify() def release(self, conn): """Release free connection back to the connection pool. """ fut = create_future(self._loop) fut.set_result(None) if conn in self._terminated: assert conn.closed, conn self._terminated.remove(conn) return fut assert conn in self._used, (conn, self._used) self._used.remove(conn) if not conn.closed: tran_status = conn._conn.get_transaction_status() if tran_status != TRANSACTION_STATUS_IDLE: logger.warning( "Invalid transaction status on released connection: %d", tran_status) conn.close() return fut if self._closing: conn.close() else: self._free.append(conn) fut = ensure_future(self._wakeup(), loop=self._loop) return fut @asyncio.coroutine def cursor(self, name=None, cursor_factory=None, scrollable=None, withhold=False, *, timeout=None): """XXX""" conn = yield from self.acquire() cur = yield from conn.cursor(name=name, cursor_factory=cursor_factory, scrollable=scrollable, withhold=withhold, timeout=timeout) return _PoolCursorContextManager(self, conn, cur) def __enter__(self): raise RuntimeError( '"yield from" should be used as context manager expression') def __exit__(self, *args): # This must exist because __enter__ exists, even though that # always raises; that's how the with-statement works. pass # pragma: nocover def __iter__(self): # This is not a coroutine. It is meant to enable the idiom: # # with (yield from pool) as conn: # # # as an alternative to: # # conn = yield from pool.acquire() # try: # # finally: # conn.release() conn = yield from self.acquire() return _PoolConnectionContextManager(self, conn) if PY_35: # pragma: no branch @asyncio.coroutine def __aenter__(self): return self @asyncio.coroutine def __aexit__(self, exc_type, exc_val, exc_tb): self.close() yield from self.wait_closed() if PY_341: # pragma: no branch def __del__(self): try: self._free except AttributeError: return # frame has been cleared, __dict__ is empty if self._free: left = 0 while self._free: conn = self._free.popleft() conn.close() left += 1 warnings.warn( "Unclosed {} connections in {!r}".format(left, self), ResourceWarning) aiopg-0.13.2/aiopg/cursor.py0000664000372000037200000003123313223236761016533 0ustar travistravis00000000000000import asyncio import warnings import psycopg2 from .log import logger from .transaction import Transaction, IsolationLevel from .utils import PY_35, PY_352, _TransactionBeginContextManager class Cursor: def __init__(self, conn, impl, timeout, echo): self._conn = conn self._impl = impl self._timeout = timeout self._echo = echo self._transaction = Transaction(self, IsolationLevel.repeatable_read) @property def echo(self): """Return echo mode status.""" return self._echo @property def description(self): """This read-only attribute is a sequence of 7-item sequences. Each of these sequences is a collections.namedtuple containing information describing one result column: 0. name: the name of the column returned. 1. type_code: the PostgreSQL OID of the column. 2. display_size: the actual length of the column in bytes. 3. internal_size: the size in bytes of the column associated to this column on the server. 4. precision: total number of significant digits in columns of type NUMERIC. None for other types. 5. scale: count of decimal digits in the fractional part in columns of type NUMERIC. None for other types. 6. null_ok: always None as not easy to retrieve from the libpq. This attribute will be None for operations that do not return rows or if the cursor has not had an operation invoked via the execute() method yet. """ return self._impl.description def close(self): """Close the cursor now.""" self._impl.close() @property def closed(self): """Read-only boolean attribute: specifies if the cursor is closed.""" return self._impl.closed @property def connection(self): """Read-only attribute returning a reference to the `Connection`.""" return self._conn @property def raw(self): """Underlying psycopg cursor object, readonly""" return self._impl @property def name(self): # Not supported return self._impl.name @property def scrollable(self): # Not supported return self._impl.scrollable @scrollable.setter def scrollable(self, val): # Not supported self._impl.scrollable = val @property def withhold(self): # Not supported return self._impl.withhold @withhold.setter def withhold(self, val): # Not supported self._impl.withhold = val @asyncio.coroutine def execute(self, operation, parameters=None, *, timeout=None): """Prepare and execute a database operation (query or command). Parameters may be provided as sequence or mapping and will be bound to variables in the operation. Variables are specified either with positional %s or named %({name})s placeholders. """ if timeout is None: timeout = self._timeout waiter = self._conn._create_waiter('cursor.execute') if self._echo: logger.info(operation) logger.info("%r", parameters) try: self._impl.execute(operation, parameters) except: self._conn._waiter = None raise try: yield from self._conn._poll(waiter, timeout) except asyncio.TimeoutError: self._impl.close() raise @asyncio.coroutine def executemany(self, operation, seq_of_parameters): # Not supported raise psycopg2.ProgrammingError( "executemany cannot be used in asynchronous mode") @asyncio.coroutine def callproc(self, procname, parameters=None, *, timeout=None): """Call a stored database procedure with the given name. The sequence of parameters must contain one entry for each argument that the procedure expects. The result of the call is returned as modified copy of the input sequence. Input parameters are left untouched, output and input/output parameters replaced with possibly new values. """ if timeout is None: timeout = self._timeout waiter = self._conn._create_waiter('cursor.callproc') if self._echo: logger.info("CALL %s", procname) logger.info("%r", parameters) try: self._impl.callproc(procname, parameters) except: self._conn._waiter = None raise else: yield from self._conn._poll(waiter, timeout) def begin(self): return _TransactionBeginContextManager(self._transaction.begin()) def begin_nested(self): if not self._transaction.is_begin: return _TransactionBeginContextManager( self._transaction.begin()) else: return self._transaction.point() @asyncio.coroutine def mogrify(self, operation, parameters=None): """Return a query string after arguments binding. The string returned is exactly the one that would be sent to the database running the .execute() method or similar. """ ret = self._impl.mogrify(operation, parameters) assert not self._conn._isexecuting(), ("Don't support server side " "mogrify") return ret @asyncio.coroutine def setinputsizes(self, sizes): """This method is exposed in compliance with the DBAPI. It currently does nothing but it is safe to call it. """ self._impl.setinputsizes(sizes) @asyncio.coroutine def fetchone(self): """Fetch the next row of a query result set. Returns a single tuple, or None when no more data is available. """ ret = self._impl.fetchone() assert not self._conn._isexecuting(), ("Don't support server side " "cursors yet") return ret @asyncio.coroutine def fetchmany(self, size=None): """Fetch the next set of rows of a query result. Returns a list of tuples. An empty list is returned when no more rows are available. The number of rows to fetch per call is specified by the parameter. If it is not given, the cursor's .arraysize determines the number of rows to be fetched. The method should try to fetch as many rows as indicated by the size parameter. If this is not possible due to the specified number of rows not being available, fewer rows may be returned. """ if size is None: size = self._impl.arraysize ret = self._impl.fetchmany(size) assert not self._conn._isexecuting(), ("Don't support server side " "cursors yet") return ret @asyncio.coroutine def fetchall(self): """Fetch all (remaining) rows of a query result. Returns them as a list of tuples. An empty list is returned if there is no more record to fetch. """ ret = self._impl.fetchall() assert not self._conn._isexecuting(), ("Don't support server side " "cursors yet") return ret @asyncio.coroutine def scroll(self, value, mode="relative"): """Scroll to a new position according to mode. If mode is relative (default), value is taken as offset to the current position in the result set, if set to absolute, value states an absolute target position. """ ret = self._impl.scroll(value, mode) assert not self._conn._isexecuting(), ("Don't support server side " "cursors yet") return ret @property def arraysize(self): """How many rows will be returned by fetchmany() call. This read/write attribute specifies the number of rows to fetch at a time with fetchmany(). It defaults to 1 meaning to fetch a single row at a time. """ return self._impl.arraysize @arraysize.setter def arraysize(self, val): """How many rows will be returned by fetchmany() call. This read/write attribute specifies the number of rows to fetch at a time with fetchmany(). It defaults to 1 meaning to fetch a single row at a time. """ self._impl.arraysize = val @property def itersize(self): # Not supported return self._impl.itersize @itersize.setter def itersize(self, val): # Not supported self._impl.itersize = val @property def rowcount(self): """Returns the number of rows that has been produced of affected. This read-only attribute specifies the number of rows that the last :meth:`execute` produced (for Data Query Language statements like SELECT) or affected (for Data Manipulation Language statements like UPDATE or INSERT). The attribute is -1 in case no .execute() has been performed on the cursor or the row count of the last operation if it can't be determined by the interface. """ return self._impl.rowcount @property def rownumber(self): """Row index. This read-only attribute provides the current 0-based index of the cursor in the result set or ``None`` if the index cannot be determined.""" return self._impl.rownumber @property def lastrowid(self): """OID of the last inserted row. This read-only attribute provides the OID of the last row inserted by the cursor. If the table wasn't created with OID support or the last operation is not a single record insert, the attribute is set to None. """ return self._impl.lastrowid @property def query(self): """The last executed query string. Read-only attribute containing the body of the last query sent to the backend (including bound arguments) as bytes string. None if no query has been executed yet. """ return self._impl.query @property def statusmessage(self): """the message returned by the last command.""" return self._impl.statusmessage # @asyncio.coroutine # def cast(self, old, s): # ... @property def tzinfo_factory(self): """The time zone factory used to handle data types such as `TIMESTAMP WITH TIME ZONE`. """ return self._impl.tzinfo_factory @tzinfo_factory.setter def tzinfo_factory(self, val): """The time zone factory used to handle data types such as `TIMESTAMP WITH TIME ZONE`. """ self._impl.tzinfo_factory = val @asyncio.coroutine def nextset(self): # Not supported self._impl.nextset() # raises psycopg2.NotSupportedError @asyncio.coroutine def setoutputsize(self, size, column=None): # Does nothing self._impl.setoutputsize(size, column) @asyncio.coroutine def copy_from(self, file, table, sep='\t', null='\\N', size=8192, columns=None): raise psycopg2.ProgrammingError( "copy_from cannot be used in asynchronous mode") @asyncio.coroutine def copy_to(self, file, table, sep='\t', null='\\N', columns=None): raise psycopg2.ProgrammingError( "copy_to cannot be used in asynchronous mode") @asyncio.coroutine def copy_expert(self, sql, file, size=8192): raise psycopg2.ProgrammingError( "copy_expert cannot be used in asynchronous mode") @property def timeout(self): """Return default timeout for cursor operations.""" return self._timeout def __iter__(self): warnings.warn("Iteration over cursor is deprecated", DeprecationWarning, stacklevel=2) while True: row = yield from self.fetchone() if row is None: raise StopIteration else: yield row if PY_35: # pragma: no branch def __aiter__(self): return self if not PY_352: __aiter__ = asyncio.coroutine(__aiter__) @asyncio.coroutine def __anext__(self): ret = yield from self.fetchone() if ret is not None: return ret else: raise StopAsyncIteration # noqa @asyncio.coroutine def __aenter__(self): return self @asyncio.coroutine def __aexit__(self, exc_type, exc_val, exc_tb): self.close() return aiopg-0.13.2/aiopg/log.py0000664000372000037200000000017413223236761015777 0ustar travistravis00000000000000"""Logging configuration.""" import logging # Name the logger after the package. logger = logging.getLogger(__package__) aiopg-0.13.2/aiopg/connection.py0000775000372000037200000004257213223236761017370 0ustar travistravis00000000000000import asyncio import contextlib import errno import select import sys import traceback import warnings import weakref import platform import psycopg2 from psycopg2.extensions import ( POLL_OK, POLL_READ, POLL_WRITE, POLL_ERROR) from psycopg2 import extras from .cursor import Cursor from .utils import _ContextManager, PY_35, create_future __all__ = ('connect',) TIMEOUT = 60.0 PY_341 = sys.version_info >= (3, 4, 1) # Windows specific error code, not in errno for some reason, and doesnt map # to OSError.errno EBADF WSAENOTSOCK = 10038 @asyncio.coroutine def _enable_hstore(conn): cur = yield from conn.cursor() yield from cur.execute("""\ SELECT t.oid, typarray FROM pg_type t JOIN pg_namespace ns ON typnamespace = ns.oid WHERE typname = 'hstore'; """) rv0, rv1 = [], [] for oids in (yield from cur.fetchall()): rv0.append(oids[0]) rv1.append(oids[1]) cur.close() return tuple(rv0), tuple(rv1) def connect(dsn=None, *, timeout=TIMEOUT, loop=None, enable_json=True, enable_hstore=True, enable_uuid=True, echo=False, **kwargs): """A factory for connecting to PostgreSQL. The coroutine accepts all parameters that psycopg2.connect() does plus optional keyword-only `loop` and `timeout` parameters. Returns instantiated Connection object. """ coro = _connect(dsn=dsn, timeout=timeout, loop=loop, enable_json=enable_json, enable_hstore=enable_hstore, enable_uuid=enable_uuid, echo=echo, **kwargs) return _ContextManager(coro) @asyncio.coroutine def _connect(dsn=None, *, timeout=TIMEOUT, loop=None, enable_json=True, enable_hstore=True, enable_uuid=True, echo=False, **kwargs): if loop is None: loop = asyncio.get_event_loop() waiter = create_future(loop) conn = Connection(dsn, loop, timeout, waiter, bool(echo), **kwargs) try: yield from conn._poll(waiter, timeout) except Exception: conn.close() raise if enable_json: extras.register_default_json(conn._conn) if enable_uuid: extras.register_uuid(conn_or_curs=conn._conn) if enable_hstore: oids = yield from _enable_hstore(conn) if oids is not None: oid, array_oid = oids extras.register_hstore(conn._conn, oid=oid, array_oid=array_oid) return conn def _is_bad_descriptor_error(os_error): if platform.system() == 'Windows': # pragma: no cover return os_error.winerror == WSAENOTSOCK else: return os_error.errno == errno.EBADF class Connection: """Low-level asynchronous interface for wrapped psycopg2 connection. The Connection instance encapsulates a database session. Provides support for creating asynchronous cursors. """ _source_traceback = None def __init__(self, dsn, loop, timeout, waiter, echo, **kwargs): self._loop = loop self._conn = psycopg2.connect(dsn, async=True, **kwargs) self._dsn = self._conn.dsn assert self._conn.isexecuting(), "Is conn an async at all???" self._fileno = self._conn.fileno() self._timeout = timeout self._last_usage = self._loop.time() self._waiter = waiter self._writing = False self._cancelling = False self._cancellation_waiter = None self._echo = echo self._notifies = asyncio.Queue(loop=loop) self._weakref = weakref.ref(self) self._loop.add_reader(self._fileno, self._ready, self._weakref) if loop.get_debug(): self._source_traceback = traceback.extract_stack(sys._getframe(1)) @staticmethod def _ready(weak_self): self = weak_self() if self is None: return waiter = self._waiter try: state = self._conn.poll() while self._conn.notifies: notify = self._conn.notifies.pop(0) self._notifies.put_nowait(notify) except (psycopg2.Warning, psycopg2.Error) as exc: if self._fileno is not None: try: select.select([self._fileno], [], [], 0) except OSError as os_exc: if _is_bad_descriptor_error(os_exc): with contextlib.suppress(OSError): self._loop.remove_reader(self._fileno) # forget a bad file descriptor, don't try to # touch it self._fileno = None try: if self._writing: self._writing = False if self._fileno is not None: self._loop.remove_writer(self._fileno) except OSError as exc2: if exc2.errno != errno.EBADF: # EBADF is ok for closed file descriptor # chain exception otherwise exc2.__cause__ = exc exc = exc2 if waiter is not None and not waiter.done(): waiter.set_exception(exc) else: if self._fileno is None: # connection closed if waiter is not None and not waiter.done(): waiter.set_exception( psycopg2.OperationalError("Connection closed")) if state == POLL_OK: if self._writing: self._loop.remove_writer(self._fileno) self._writing = False if waiter is not None and not waiter.done(): waiter.set_result(None) elif state == POLL_READ: if self._writing: self._loop.remove_writer(self._fileno) self._writing = False elif state == POLL_WRITE: if not self._writing: self._loop.add_writer(self._fileno, self._ready, weak_self) self._writing = True elif state == POLL_ERROR: self._fatal_error("Fatal error on aiopg connection: " "POLL_ERROR from underlying .poll() call") else: self._fatal_error("Fatal error on aiopg connection: " "unknown answer {} from underlying " ".poll() call" .format(state)) def _fatal_error(self, message): # Should be called from exception handler only. self._loop.call_exception_handler({ 'message': message, 'connection': self, }) self.close() if self._waiter and not self._waiter.done(): self._waiter.set_exception(psycopg2.OperationalError(message)) def _create_waiter(self, func_name): if self._waiter is not None: if self._cancelling: if not self._waiter.done(): raise RuntimeError('%s() called while connection is ' 'being cancelled' % func_name) else: raise RuntimeError('%s() called while another coroutine is ' 'already waiting for incoming ' 'data' % func_name) self._waiter = create_future(self._loop) return self._waiter @asyncio.coroutine def _poll(self, waiter, timeout): assert waiter is self._waiter, (waiter, self._waiter) self._ready(self._weakref) @asyncio.coroutine def cancel(): self._waiter = create_future(self._loop) self._cancelling = True self._cancellation_waiter = self._waiter self._conn.cancel() if not self._conn.isexecuting(): return try: yield from asyncio.wait_for(self._waiter, timeout, loop=self._loop) except psycopg2.extensions.QueryCanceledError: pass except asyncio.TimeoutError: self._close() try: yield from asyncio.wait_for(self._waiter, timeout, loop=self._loop) except (asyncio.CancelledError, asyncio.TimeoutError) as exc: yield from asyncio.shield(cancel(), loop=self._loop) raise exc except psycopg2.extensions.QueryCanceledError: raise asyncio.CancelledError finally: if self._cancelling: self._cancelling = False if self._waiter is self._cancellation_waiter: self._waiter = None self._cancellation_waiter = None else: self._waiter = None def _isexecuting(self): return self._conn.isexecuting() def cursor(self, name=None, cursor_factory=None, scrollable=None, withhold=False, timeout=None): """A coroutine that returns a new cursor object using the connection. *cursor_factory* argument can be used to create non-standard cursors. The argument must be suclass of `psycopg2.extensions.cursor`. *name*, *scrollable* and *withhold* parameters are not supported by psycopg in asynchronous mode. """ self._last_usage = self._loop.time() coro = self._cursor(name=name, cursor_factory=cursor_factory, scrollable=scrollable, withhold=withhold, timeout=timeout) return _ContextManager(coro) @asyncio.coroutine def _cursor(self, name=None, cursor_factory=None, scrollable=None, withhold=False, timeout=None): if timeout is None: timeout = self._timeout impl = yield from self._cursor_impl(name=name, cursor_factory=cursor_factory, scrollable=scrollable, withhold=withhold) return Cursor(self, impl, timeout, self._echo) @asyncio.coroutine def _cursor_impl(self, name=None, cursor_factory=None, scrollable=None, withhold=False): if cursor_factory is None: impl = self._conn.cursor(name=name, scrollable=scrollable, withhold=withhold) else: impl = self._conn.cursor(name=name, cursor_factory=cursor_factory, scrollable=scrollable, withhold=withhold) return impl def _close(self): """Remove the connection from the event_loop and close it.""" # N.B. If connection contains uncommitted transaction the # transaction will be discarded if self._fileno is not None: self._loop.remove_reader(self._fileno) if self._writing: self._writing = False self._loop.remove_writer(self._fileno) self._conn.close() if self._waiter is not None and not self._waiter.done(): self._waiter.set_exception( psycopg2.OperationalError("Connection closed")) def close(self): self._close() ret = create_future(self._loop) ret.set_result(None) return ret @property def closed(self): """Connection status. Read-only attribute reporting whether the database connection is open (False) or closed (True). """ return self._conn.closed @property def raw(self): """Underlying psycopg connection object, readonly""" return self._conn @asyncio.coroutine def commit(self): raise psycopg2.ProgrammingError( "commit cannot be used in asynchronous mode") @asyncio.coroutine def rollback(self): raise psycopg2.ProgrammingError( "rollback cannot be used in asynchronous mode") # TPC @asyncio.coroutine def xid(self, format_id, gtrid, bqual): return self._conn.xid(format_id, gtrid, bqual) @asyncio.coroutine def tpc_begin(self, xid=None): raise psycopg2.ProgrammingError( "tpc_begin cannot be used in asynchronous mode") @asyncio.coroutine def tpc_prepare(self): raise psycopg2.ProgrammingError( "tpc_prepare cannot be used in asynchronous mode") @asyncio.coroutine def tpc_commit(self, xid=None): raise psycopg2.ProgrammingError( "tpc_commit cannot be used in asynchronous mode") @asyncio.coroutine def tpc_rollback(self, xid=None): raise psycopg2.ProgrammingError( "tpc_rollback cannot be used in asynchronous mode") @asyncio.coroutine def tpc_recover(self): raise psycopg2.ProgrammingError( "tpc_recover cannot be used in asynchronous mode") @asyncio.coroutine def cancel(self): """Cancel the current database operation.""" if self._waiter is None: return @asyncio.coroutine def cancel(): self._conn.cancel() try: yield from self._waiter except psycopg2.extensions.QueryCanceledError: pass yield from asyncio.shield(cancel(), loop=self._loop) @asyncio.coroutine def reset(self): raise psycopg2.ProgrammingError( "reset cannot be used in asynchronous mode") @property def dsn(self): """DSN connection string. Read-only attribute representing dsn connection string used for connectint to PostgreSQL server. """ return self._dsn @asyncio.coroutine def set_session(self, *, isolation_level=None, readonly=None, deferrable=None, autocommit=None): raise psycopg2.ProgrammingError( "set_session cannot be used in asynchronous mode") @property def autocommit(self): """Autocommit status""" return self._conn.autocommit @autocommit.setter def autocommit(self, val): """Autocommit status""" self._conn.autocommit = val @property def isolation_level(self): """Transaction isolation level. The only allowed value is ISOLATION_LEVEL_READ_COMMITTED. """ return self._conn.isolation_level @asyncio.coroutine def set_isolation_level(self, val): """Transaction isolation level. The only allowed value is ISOLATION_LEVEL_READ_COMMITTED. """ self._conn.set_isolation_level(val) @property def encoding(self): """Client encoding for SQL operations.""" return self._conn.encoding @asyncio.coroutine def set_client_encoding(self, val): self._conn.set_client_encoding(val) @property def notices(self): """A list of all db messages sent to the client during the session.""" return self._conn.notices @property def cursor_factory(self): """The default cursor factory used by .cursor().""" return self._conn.cursor_factory @asyncio.coroutine def get_backend_pid(self): """Returns the PID of the backend server process.""" return self._conn.get_backend_pid() @asyncio.coroutine def get_parameter_status(self, parameter): """Look up a current parameter setting of the server.""" return self._conn.get_parameter_status(parameter) @asyncio.coroutine def get_transaction_status(self): """Return the current session transaction status as an integer.""" return self._conn.get_transaction_status() @property def protocol_version(self): """A read-only integer representing protocol being used.""" return self._conn.protocol_version @property def server_version(self): """A read-only integer representing the backend version.""" return self._conn.server_version @property def status(self): """A read-only integer representing the status of the connection.""" return self._conn.status @asyncio.coroutine def lobject(self, *args, **kwargs): raise psycopg2.ProgrammingError( "lobject cannot be used in asynchronous mode") @property def timeout(self): """Return default timeout for connection operations.""" return self._timeout @property def last_usage(self): """Return time() when connection was used.""" return self._last_usage @property def echo(self): """Return echo mode status.""" return self._echo if PY_341: # pragma: no branch def __del__(self): try: _conn = self._conn except AttributeError: return if _conn is not None and not _conn.closed: self.close() warnings.warn("Unclosed connection {!r}".format(self), ResourceWarning) context = {'connection': self, 'message': 'Unclosed connection'} if self._source_traceback is not None: context['source_traceback'] = self._source_traceback self._loop.call_exception_handler(context) @property def notifies(self): """Return notification queue.""" return self._notifies if PY_35: # pragma: no branch @asyncio.coroutine def __aenter__(self): return self @asyncio.coroutine def __aexit__(self, exc_type, exc_val, exc_tb): self.close() aiopg-0.13.2/aiopg/__init__.py0000664000372000037200000000273513223236761016762 0ustar travistravis00000000000000import re import sys from collections import namedtuple from .connection import connect, Connection, TIMEOUT as DEFAULT_TIMEOUT from .cursor import Cursor from .pool import create_pool, Pool from .transaction import IsolationLevel, Transaction __all__ = ('connect', 'create_pool', 'Connection', 'Cursor', 'Pool', 'version', 'version_info', 'DEFAULT_TIMEOUT', 'IsolationLevel', 'Transaction') __version__ = '0.13.2' version = __version__ + ' , Python ' + sys.version VersionInfo = namedtuple('VersionInfo', 'major minor micro releaselevel serial') def _parse_version(ver): RE = (r'^(?P\d+)\.(?P\d+)\.' '(?P\d+)((?P[a-z]+)(?P\d+)?)?$') match = re.match(RE, ver) try: major = int(match.group('major')) minor = int(match.group('minor')) micro = int(match.group('micro')) levels = {'c': 'candidate', 'a': 'alpha', 'b': 'beta', None: 'final'} releaselevel = levels[match.group('releaselevel')] serial = int(match.group('serial')) if match.group('serial') else 0 return VersionInfo(major, minor, micro, releaselevel, serial) except Exception: raise ImportError("Invalid package version {}".format(ver)) version_info = _parse_version(__version__) # make pyflakes happy (connect, create_pool, Connection, Cursor, Pool, DEFAULT_TIMEOUT, IsolationLevel, Transaction) aiopg-0.13.2/aiopg/sa/0000775000372000037200000000000013223237542015243 5ustar travistravis00000000000000aiopg-0.13.2/aiopg/sa/connection.py0000664000372000037200000003214113223236761017757 0ustar travistravis00000000000000import asyncio from sqlalchemy.sql import ClauseElement from sqlalchemy.sql.ddl import DDLElement from sqlalchemy.sql.dml import UpdateBase from . import exc from .result import ResultProxy from .transaction import (RootTransaction, Transaction, NestedTransaction, TwoPhaseTransaction) from ..utils import _SAConnectionContextManager, _TransactionContextManager class SAConnection: def __init__(self, connection, engine): self._connection = connection self._transaction = None self._savepoint_seq = 0 self._engine = engine self._dialect = engine.dialect def execute(self, query, *multiparams, **params): """Executes a SQL query with optional parameters. query - a SQL query string or any sqlalchemy expression. *multiparams/**params - represent bound parameter values to be used in the execution. Typically, the format is a dictionary passed to *multiparams: yield from conn.execute( table.insert(), {"id":1, "value":"v1"}, ) ...or individual key/values interpreted by **params:: yield from conn.execute( table.insert(), id=1, value="v1" ) In the case that a plain SQL string is passed, a tuple or individual values in \*multiparams may be passed:: yield from conn.execute( "INSERT INTO table (id, value) VALUES (%d, %s)", (1, "v1") ) yield from conn.execute( "INSERT INTO table (id, value) VALUES (%s, %s)", 1, "v1" ) Returns ResultProxy instance with results of SQL query execution. """ coro = self._execute(query, *multiparams, **params) return _SAConnectionContextManager(coro) @asyncio.coroutine def _execute(self, query, *multiparams, **params): cursor = yield from self._connection.cursor() dp = _distill_params(multiparams, params) if len(dp) > 1: raise exc.ArgumentError("aiopg doesn't support executemany") elif dp: dp = dp[0] result_map = None if isinstance(query, str): yield from cursor.execute(query, dp) elif isinstance(query, ClauseElement): compiled = query.compile(dialect=self._dialect) # parameters = compiled.params if not isinstance(query, DDLElement): if dp and isinstance(dp, (list, tuple)): if isinstance(query, UpdateBase): dp = {c.key: pval for c, pval in zip(query.table.c, dp)} else: raise exc.ArgumentError("Don't mix sqlalchemy SELECT " "clause with positional " "parameters") compiled_parameters = [compiled.construct_params(dp)] processed_parameters = [] processors = compiled._bind_processors for compiled_params in compiled_parameters: params = {key: (processors[key](compiled_params[key]) if key in processors else compiled_params[key]) for key in compiled_params} processed_parameters.append(params) post_processed_params = self._dialect.execute_sequence_format( processed_parameters) # _result_columns is a private API of Compiled, # but I couldn't find any public API exposing this data. result_map = compiled._result_columns else: if dp: raise exc.ArgumentError("Don't mix sqlalchemy DDL clause " "and execution with parameters") post_processed_params = [compiled.construct_params()] result_map = None yield from cursor.execute(str(compiled), post_processed_params[0]) else: raise exc.ArgumentError("sql statement should be str or " "SQLAlchemy data " "selection/modification clause") return ResultProxy(self, cursor, self._dialect, result_map) @asyncio.coroutine def scalar(self, query, *multiparams, **params): """Executes a SQL query and returns a scalar value.""" res = yield from self.execute(query, *multiparams, **params) return (yield from res.scalar()) @property def closed(self): """The readonly property that returns True if connections is closed.""" return self._connection is None or self._connection.closed @property def info(self): return self._connection.info @property def connection(self): return self._connection def begin(self): """Begin a transaction and return a transaction handle. The returned object is an instance of Transaction. This object represents the "scope" of the transaction, which completes when either the .rollback or .commit method is called. Nested calls to .begin on the same SAConnection instance will return new Transaction objects that represent an emulated transaction within the scope of the enclosing transaction, that is:: trans = yield from conn.begin() # outermost transaction trans2 = yield from conn.begin() # "nested" yield from trans2.commit() # does nothing yield from trans.commit() # actually commits Calls to .commit only have an effect when invoked via the outermost Transaction object, though the .rollback method of any of the Transaction objects will roll back the transaction. See also: .begin_nested - use a SAVEPOINT .begin_twophase - use a two phase/XA transaction """ coro = self._begin() return _TransactionContextManager(coro) @asyncio.coroutine def _begin(self): if self._transaction is None: self._transaction = RootTransaction(self) yield from self._begin_impl() return self._transaction else: return Transaction(self, self._transaction) @asyncio.coroutine def _begin_impl(self): cur = yield from self._connection.cursor() try: yield from cur.execute('BEGIN') finally: cur.close() @asyncio.coroutine def _commit_impl(self): cur = yield from self._connection.cursor() try: yield from cur.execute('COMMIT') finally: cur.close() self._transaction = None @asyncio.coroutine def _rollback_impl(self): cur = yield from self._connection.cursor() try: yield from cur.execute('ROLLBACK') finally: cur.close() self._transaction = None def begin_nested(self): """Begin a nested transaction and return a transaction handle. The returned object is an instance of :class:`.NestedTransaction`. Nested transactions require SAVEPOINT support in the underlying database. Any transaction in the hierarchy may .commit() and .rollback(), however the outermost transaction still controls the overall .commit() or .rollback() of the transaction of a whole. """ coro = self._begin_nested() return _TransactionContextManager(coro) @asyncio.coroutine def _begin_nested(self): if self._transaction is None: self._transaction = RootTransaction(self) yield from self._begin_impl() else: self._transaction = NestedTransaction(self, self._transaction) self._transaction._savepoint = yield from self._savepoint_impl() return self._transaction @asyncio.coroutine def _savepoint_impl(self, name=None): self._savepoint_seq += 1 name = 'aiopg_sa_savepoint_%s' % self._savepoint_seq cur = yield from self._connection.cursor() try: yield from cur.execute('SAVEPOINT ' + name) return name finally: cur.close() @asyncio.coroutine def _rollback_to_savepoint_impl(self, name, parent): cur = yield from self._connection.cursor() try: yield from cur.execute('ROLLBACK TO SAVEPOINT ' + name) finally: cur.close() self._transaction = parent @asyncio.coroutine def _release_savepoint_impl(self, name, parent): cur = yield from self._connection.cursor() try: yield from cur.execute('RELEASE SAVEPOINT ' + name) finally: cur.close() self._transaction = parent @asyncio.coroutine def begin_twophase(self, xid=None): """Begin a two-phase or XA transaction and return a transaction handle. The returned object is an instance of TwoPhaseTransaction, which in addition to the methods provided by Transaction, also provides a TwoPhaseTransaction.prepare() method. xid - the two phase transaction id. If not supplied, a random id will be generated. """ if self._transaction is not None: raise exc.InvalidRequestError( "Cannot start a two phase transaction when a transaction " "is already in progress.") if xid is None: xid = self._dialect.create_xid() self._transaction = TwoPhaseTransaction(self, xid) yield from self._begin_impl() return self._transaction @asyncio.coroutine def _prepare_twophase_impl(self, xid): yield from self.execute("PREPARE TRANSACTION '%s'" % xid) @asyncio.coroutine def recover_twophase(self): """Return a list of prepared twophase transaction ids.""" result = yield from self.execute("SELECT gid FROM pg_prepared_xacts") return [row[0] for row in result] @asyncio.coroutine def rollback_prepared(self, xid, *, is_prepared=True): """Rollback prepared twophase transaction.""" if is_prepared: yield from self.execute("ROLLBACK PREPARED '%s'" % xid) else: yield from self._rollback_impl() @asyncio.coroutine def commit_prepared(self, xid, *, is_prepared=True): """Commit prepared twophase transaction.""" if is_prepared: yield from self.execute("COMMIT PREPARED '%s'" % xid) else: yield from self._commit_impl() @property def in_transaction(self): """Return True if a transaction is in progress.""" return self._transaction is not None and self._transaction.is_active @asyncio.coroutine def close(self): """Close this SAConnection. This results in a release of the underlying database resources, that is, the underlying connection referenced internally. The underlying connection is typically restored back to the connection-holding Pool referenced by the Engine that produced this SAConnection. Any transactional state present on the underlying connection is also unconditionally released via calling Transaction.rollback() method. After .close() is called, the SAConnection is permanently in a closed state, and will allow no further operations. """ if self._connection is None: return if self._transaction is not None: yield from self._transaction.rollback() self._transaction = None # don't close underlying connection, it can be reused by pool # conn.close() self._engine.release(self) self._connection = None self._engine = None def _distill_params(multiparams, params): """Given arguments from the calling form *multiparams, **params, return a list of bind parameter structures, usually a list of dictionaries. In the case of 'raw' execution which accepts positional parameters, it may be a list of tuples or lists. """ if not multiparams: if params: return [params] else: return [] elif len(multiparams) == 1: zero = multiparams[0] if isinstance(zero, (list, tuple)): if not zero or hasattr(zero[0], '__iter__') and \ not hasattr(zero[0], 'strip'): # execute(stmt, [{}, {}, {}, ...]) # execute(stmt, [(), (), (), ...]) return zero else: # execute(stmt, ("value", "value")) return [zero] elif hasattr(zero, 'keys'): # execute(stmt, {"key":"value"}) return [zero] else: # execute(stmt, "value") return [[zero]] else: if (hasattr(multiparams[0], '__iter__') and not hasattr(multiparams[0], 'strip')): return multiparams else: return [multiparams] aiopg-0.13.2/aiopg/sa/__init__.py0000664000372000037200000000106113223236761017354 0ustar travistravis00000000000000"""Optional support for sqlalchemy.sql dynamic query generation.""" from .connection import SAConnection from .engine import create_engine, Engine from .exc import (Error, ArgumentError, InvalidRequestError, NoSuchColumnError, ResourceClosedError) __all__ = ('create_engine', 'SAConnection', 'Error', 'ArgumentError', 'InvalidRequestError', 'NoSuchColumnError', 'ResourceClosedError', 'Engine') (SAConnection, Error, ArgumentError, InvalidRequestError, NoSuchColumnError, ResourceClosedError, create_engine, Engine) aiopg-0.13.2/aiopg/sa/exc.py0000664000372000037200000000127113223236761016377 0ustar travistravis00000000000000class Error(Exception): """Generic error class.""" class ArgumentError(Error): """Raised when an invalid or conflicting function argument is supplied. This error generally corresponds to construction time state errors. """ class InvalidRequestError(ArgumentError): """aiopg.sa was asked to do something it can't do. This error generally corresponds to runtime state errors. """ class NoSuchColumnError(KeyError, InvalidRequestError): """A nonexistent column is requested from a ``RowProxy``.""" class ResourceClosedError(InvalidRequestError): """An operation was requested from a connection, cursor, or other object that's in a closed state.""" aiopg-0.13.2/aiopg/sa/result.py0000664000372000037200000003455413223236761017150 0ustar travistravis00000000000000import asyncio import warnings import weakref from collections.abc import Mapping, Sequence from sqlalchemy.sql import expression, sqltypes from ..utils import PY_35, PY_352 from . import exc class RowProxy(Mapping): __slots__ = ('_result_proxy', '_row', '_processors', '_keymap') def __init__(self, result_proxy, row, processors, keymap): """RowProxy objects are constructed by ResultProxy objects.""" self._result_proxy = result_proxy self._row = row self._processors = processors self._keymap = keymap def __iter__(self): return iter(self._result_proxy.keys) def __len__(self): return len(self._row) def __getitem__(self, key): try: processor, obj, index = self._keymap[key] except KeyError: processor, obj, index = self._result_proxy._key_fallback(key) # Do we need slicing at all? RowProxy now is Mapping not Sequence # except TypeError: # if isinstance(key, slice): # l = [] # for processor, value in zip(self._processors[key], # self._row[key]): # if processor is None: # l.append(value) # else: # l.append(processor(value)) # return tuple(l) # else: # raise if index is None: raise exc.InvalidRequestError( "Ambiguous column name '%s' in result set! " "try 'use_labels' option on select statement." % key) if processor is not None: return processor(self._row[index]) else: return self._row[index] def __getattr__(self, name): try: return self[name] except KeyError as e: raise AttributeError(e.args[0]) def __contains__(self, key): return self._result_proxy._has_key(self._row, key) __hash__ = None def __eq__(self, other): if isinstance(other, RowProxy): return self.as_tuple() == other.as_tuple() elif isinstance(other, Sequence): return self.as_tuple() == other else: return NotImplemented def __ne__(self, other): return not self == other def as_tuple(self): return tuple(self[k] for k in self) def __repr__(self): return repr(self.as_tuple()) class ResultMetaData(object): """Handle cursor.description, applying additional info from an execution context.""" def __init__(self, result_proxy, metadata): self._processors = processors = [] result_map = {} if result_proxy._result_map: result_map = {elem[0]: elem[3] for elem in result_proxy._result_map} # We do not strictly need to store the processor in the key mapping, # though it is faster in the Python version (probably because of the # saved attribute lookup self._processors) self._keymap = keymap = {} self.keys = [] dialect = result_proxy.dialect # `dbapi_type_map` property removed in SQLAlchemy 1.2+. # Usage of `getattr` only needed for backward compatibility with # older versions of SQLAlchemy. typemap = getattr(dialect, 'dbapi_type_map', {}) assert dialect.case_sensitive, \ "Doesn't support case insensitive database connection" # high precedence key values. primary_keymap = {} assert not dialect.description_encoding, \ "psycopg in py3k should not use this" for i, rec in enumerate(metadata): colname = rec[0] coltype = rec[1] # PostgreSQL doesn't require this. # if dialect.requires_name_normalize: # colname = dialect.normalize_name(colname) name, obj, type_ = ( colname, None, result_map.get( colname, typemap.get(coltype, sqltypes.NULLTYPE)) ) processor = type_._cached_result_processor(dialect, coltype) processors.append(processor) rec = (processor, obj, i) # indexes as keys. This is only needed for the Python version of # RowProxy (the C version uses a faster path for integer indexes). primary_keymap[i] = rec # populate primary keymap, looking for conflicts. if primary_keymap.setdefault(name, rec) is not rec: # place a record that doesn't have the "index" - this # is interpreted later as an AmbiguousColumnError, # but only when actually accessed. Columns # colliding by name is not a problem if those names # aren't used; integer access is always # unambiguous. primary_keymap[name] = rec = (None, obj, None) self.keys.append(colname) if obj: for o in obj: keymap[o] = rec # technically we should be doing this but we # are saving on callcounts by not doing so. # if keymap.setdefault(o, rec) is not rec: # keymap[o] = (None, obj, None) # overwrite keymap values with those of the # high precedence keymap. keymap.update(primary_keymap) def _key_fallback(self, key, raiseerr=True): map = self._keymap result = None if isinstance(key, str): result = map.get(key) # fallback for targeting a ColumnElement to a textual expression # this is a rare use case which only occurs when matching text() # or colummn('name') constructs to ColumnElements, or after a # pickle/unpickle roundtrip elif isinstance(key, expression.ColumnElement): if (key._label and key._label in map): result = map[key._label] elif (hasattr(key, 'name') and key.name in map): # match is only on name. result = map[key.name] # search extra hard to make sure this # isn't a column/label name overlap. # this check isn't currently available if the row # was unpickled. if (result is not None and result[1] is not None): for obj in result[1]: if key._compare_name_for_result(obj): break else: result = None if result is None: if raiseerr: raise exc.NoSuchColumnError( "Could not locate column in row for column '%s'" % expression._string_or_unprintable(key)) else: return None else: map[key] = result return result def _has_key(self, row, key): if key in self._keymap: return True else: return self._key_fallback(key, False) is not None class ResultProxy: """Wraps a DB-API cursor object to provide easier access to row columns. Individual columns may be accessed by their integer position, case-insensitive column name, or by sqlalchemy schema.Column object. e.g.: row = fetchone() col1 = row[0] # access via integer position col2 = row['col2'] # access via name col3 = row[mytable.c.mycol] # access via Column object. ResultProxy also handles post-processing of result column data using sqlalchemy TypeEngine objects, which are referenced from the originating SQL statement that produced this result set. """ def __init__(self, connection, cursor, dialect, result_map=None): self._dialect = dialect self._closed = False self._result_map = result_map self._cursor = cursor self._connection = connection self._rowcount = cursor.rowcount if cursor.description is not None: self._metadata = ResultMetaData(self, cursor.description) self._weak = weakref.ref(self, lambda wr: cursor.close()) else: self._metadata = None self.close() self._weak = None @property def dialect(self): """SQLAlchemy dialect.""" return self._dialect @property def cursor(self): return self._cursor def keys(self): """Return the current set of string keys for rows.""" if self._metadata: return tuple(self._metadata.keys) else: return () @property def rowcount(self): """Return the 'rowcount' for this result. The 'rowcount' reports the number of rows *matched* by the WHERE criterion of an UPDATE or DELETE statement. .. note:: Notes regarding .rowcount: * This attribute returns the number of rows *matched*, which is not necessarily the same as the number of rows that were actually *modified* - an UPDATE statement, for example, may have no net change on a given row if the SET values given are the same as those present in the row already. Such a row would be matched but not modified. * .rowcount is *only* useful in conjunction with an UPDATE or DELETE statement. Contrary to what the Python DBAPI says, it does *not* return the number of rows available from the results of a SELECT statement as DBAPIs cannot support this functionality when rows are unbuffered. * Statements that use RETURNING may not return a correct rowcount. """ return self._rowcount @property def returns_rows(self): """True if this ResultProxy returns rows. I.e. if it is legal to call the methods .fetchone(), .fetchmany() and .fetchall()`. """ return self._metadata is not None @property def closed(self): return self._closed def close(self): """Close this ResultProxy. Closes the underlying DBAPI cursor corresponding to the execution. Note that any data cached within this ResultProxy is still available. For some types of results, this may include buffered rows. If this ResultProxy was generated from an implicit execution, the underlying Connection will also be closed (returns the underlying DBAPI connection to the connection pool.) This method is called automatically when: * all result rows are exhausted using the fetchXXX() methods. * cursor.description is None. """ if not self._closed: self._closed = True self._cursor.close() # allow consistent errors self._cursor = None self._weak = None def __iter__(self): warnings.warn("Iteration over ResultProxy is deprecated", DeprecationWarning, stacklevel=2) while True: row = yield from self.fetchone() if row is None: raise StopIteration else: yield row if PY_35: # pragma: no branch def __aiter__(self): return self if not PY_352: __aiter__ = asyncio.coroutine(__aiter__) @asyncio.coroutine def __anext__(self): ret = yield from self.fetchone() if ret is not None: return ret else: raise StopAsyncIteration # noqa def _non_result(self): if self._metadata is None: raise exc.ResourceClosedError( "This result object does not return rows. " "It has been closed automatically.") else: raise exc.ResourceClosedError("This result object is closed.") def _process_rows(self, rows): process_row = RowProxy metadata = self._metadata keymap = metadata._keymap processors = metadata._processors return [process_row(metadata, row, processors, keymap) for row in rows] @asyncio.coroutine def fetchall(self): """Fetch all rows, just like DB-API cursor.fetchall().""" try: rows = yield from self._cursor.fetchall() except AttributeError: self._non_result() else: l = self._process_rows(rows) self.close() return l @asyncio.coroutine def fetchone(self): """Fetch one row, just like DB-API cursor.fetchone(). If a row is present, the cursor remains open after this is called. Else the cursor is automatically closed and None is returned. """ try: row = yield from self._cursor.fetchone() except AttributeError: self._non_result() else: if row is not None: return self._process_rows([row])[0] else: self.close() return None @asyncio.coroutine def fetchmany(self, size=None): """Fetch many rows, just like DB-API cursor.fetchmany(size=cursor.arraysize). If rows are present, the cursor remains open after this is called. Else the cursor is automatically closed and an empty list is returned. """ try: if size is None: rows = yield from self._cursor.fetchmany() else: rows = yield from self._cursor.fetchmany(size) except AttributeError: self._non_result() else: l = self._process_rows(rows) if len(l) == 0: self.close() return l @asyncio.coroutine def first(self): """Fetch the first row and then close the result set unconditionally. Returns None if no row is present. """ if self._metadata is None: self._non_result() try: return (yield from self.fetchone()) finally: self.close() @asyncio.coroutine def scalar(self): """Fetch the first column of the first row, and close the result set. Returns None if no row is present. """ row = yield from self.first() if row is not None: return row[0] else: return None aiopg-0.13.2/aiopg/sa/transaction.py0000664000372000037200000001225013223236761020144 0ustar travistravis00000000000000import asyncio from . import exc from ..utils import PY_35 class Transaction(object): """Represent a database transaction in progress. The Transaction object is procured by calling the SAConnection.begin() method of SAConnection: with (yield from engine) as conn: trans = yield from conn.begin() try: yield from conn.execute("insert into x (a, b) values (1, 2)") except Exception: yield from trans.rollback() else: yield from trans.commit() The object provides .rollback() and .commit() methods in order to control transaction boundaries. See also: SAConnection.begin(), SAConnection.begin_twophase(), SAConnection.begin_nested(). """ def __init__(self, connection, parent): self._connection = connection self._parent = parent or self self._is_active = True @property def is_active(self): """Return ``True`` if a transaction is active.""" return self._is_active @property def connection(self): """Return transaction's connection (SAConnection instance).""" return self._connection @asyncio.coroutine def close(self): """Close this transaction. If this transaction is the base transaction in a begin/commit nesting, the transaction will rollback(). Otherwise, the method returns. This is used to cancel a Transaction without affecting the scope of an enclosing transaction. """ if not self._parent._is_active: return if self._parent is self: yield from self.rollback() else: self._is_active = False @asyncio.coroutine def rollback(self): """Roll back this transaction.""" if not self._parent._is_active: return yield from self._do_rollback() self._is_active = False @asyncio.coroutine def _do_rollback(self): yield from self._parent.rollback() @asyncio.coroutine def commit(self): """Commit this transaction.""" if not self._parent._is_active: raise exc.InvalidRequestError("This transaction is inactive") yield from self._do_commit() self._is_active = False @asyncio.coroutine def _do_commit(self): pass if PY_35: # pragma: no branch @asyncio.coroutine def __aenter__(self): return self @asyncio.coroutine def __aexit__(self, exc_type, exc_val, exc_tb): if exc_type: yield from self.rollback() else: if self._is_active: yield from self.commit() class RootTransaction(Transaction): def __init__(self, connection): super().__init__(connection, None) @asyncio.coroutine def _do_rollback(self): yield from self._connection._rollback_impl() @asyncio.coroutine def _do_commit(self): yield from self._connection._commit_impl() class NestedTransaction(Transaction): """Represent a 'nested', or SAVEPOINT transaction. A new NestedTransaction object may be procured using the SAConnection.begin_nested() method. The interface is the same as that of Transaction class. """ _savepoint = None def __init__(self, connection, parent): super(NestedTransaction, self).__init__(connection, parent) @asyncio.coroutine def _do_rollback(self): assert self._savepoint is not None, "Broken transaction logic" if self._is_active: yield from self._connection._rollback_to_savepoint_impl( self._savepoint, self._parent) @asyncio.coroutine def _do_commit(self): assert self._savepoint is not None, "Broken transaction logic" if self._is_active: yield from self._connection._release_savepoint_impl( self._savepoint, self._parent) class TwoPhaseTransaction(Transaction): """Represent a two-phase transaction. A new TwoPhaseTransaction object may be procured using the SAConnection.begin_twophase() method. The interface is the same as that of Transaction class with the addition of the .prepare() method. """ def __init__(self, connection, xid): super().__init__(connection, None) self._is_prepared = False self._xid = xid @property def xid(self): """Returns twophase transaction id.""" return self._xid @asyncio.coroutine def prepare(self): """Prepare this TwoPhaseTransaction. After a PREPARE, the transaction can be committed. """ if not self._parent.is_active: raise exc.InvalidRequestError("This transaction is inactive") yield from self._connection._prepare_twophase_impl(self._xid) self._is_prepared = True @asyncio.coroutine def _do_rollback(self): yield from self._connection._rollback_twophase_impl( self._xid, is_prepared=self._is_prepared) @asyncio.coroutine def _do_commit(self): yield from self._connection._commit_twophase_impl( self._xid, is_prepared=self._is_prepared) aiopg-0.13.2/aiopg/sa/engine.py0000664000372000037200000001540413223236761017070 0ustar travistravis00000000000000import asyncio import json import aiopg from .connection import SAConnection from .exc import InvalidRequestError from ..connection import TIMEOUT from ..utils import PY_35, _PoolContextManager, _PoolAcquireContextManager try: from sqlalchemy.dialects.postgresql.psycopg2 import PGDialect_psycopg2 from sqlalchemy.dialects.postgresql.psycopg2 import PGCompiler_psycopg2 except ImportError: # pragma: no cover raise ImportError('aiopg.sa requires sqlalchemy') class APGCompiler_psycopg2(PGCompiler_psycopg2): def construct_params(self, params=None, _group_number=None, _check=True): pd = super().construct_params(params, _group_number, _check) for column in self.prefetch: pd[column.key] = self._exec_default(column.default) return pd def _exec_default(self, default): if default.is_callable: return default.arg(self.dialect) else: return default.arg _dialect = PGDialect_psycopg2(json_serializer=json.dumps, json_deserializer=lambda x: x) _dialect.statement_compiler = APGCompiler_psycopg2 _dialect.implicit_returning = True _dialect.supports_native_enum = True _dialect.supports_smallserial = True # 9.2+ _dialect._backslash_escapes = False _dialect.supports_sane_multi_rowcount = True # psycopg 2.0.9+ _dialect._has_native_hstore = True def create_engine(dsn=None, *, minsize=1, maxsize=10, loop=None, dialect=_dialect, timeout=TIMEOUT, pool_recycle=-1, **kwargs): """A coroutine for Engine creation. Returns Engine instance with embedded connection pool. The pool has *minsize* opened connections to PostgreSQL server. """ coro = _create_engine(dsn=dsn, minsize=minsize, maxsize=maxsize, loop=loop, dialect=dialect, timeout=timeout, pool_recycle=pool_recycle, **kwargs) return _EngineContextManager(coro) @asyncio.coroutine def _create_engine(dsn=None, *, minsize=1, maxsize=10, loop=None, dialect=_dialect, timeout=TIMEOUT, pool_recycle=-1, **kwargs): if loop is None: loop = asyncio.get_event_loop() pool = yield from aiopg.create_pool(dsn, minsize=minsize, maxsize=maxsize, loop=loop, timeout=timeout, pool_recycle=pool_recycle, **kwargs) conn = yield from pool.acquire() try: real_dsn = conn.dsn return Engine(dialect, pool, real_dsn) finally: yield from pool.release(conn) class Engine: """Connects a aiopg.Pool and sqlalchemy.engine.interfaces.Dialect together to provide a source of database connectivity and behavior. An Engine object is instantiated publicly using the create_engine coroutine. """ def __init__(self, dialect, pool, dsn): self._dialect = dialect self._pool = pool self._dsn = dsn @property def dialect(self): """An dialect for engine.""" return self._dialect @property def name(self): """A name of the dialect.""" return self._dialect.name @property def driver(self): """A driver of the dialect.""" return self._dialect.driver @property def dsn(self): """DSN connection info""" return self._dsn @property def timeout(self): return self._pool.timeout @property def minsize(self): return self._pool.minsize @property def maxsize(self): return self._pool.maxsize @property def size(self): return self._pool.size @property def freesize(self): return self._pool.freesize @property def closed(self): return self._pool.closed def close(self): """Close engine. Mark all engine connections to be closed on getting back to pool. Closed engine doesn't allow to acquire new connections. """ self._pool.close() def terminate(self): """Terminate engine. Terminate engine pool with instantly closing all acquired connections also. """ self._pool.terminate() @asyncio.coroutine def wait_closed(self): """Wait for closing all engine's connections.""" yield from self._pool.wait_closed() def acquire(self): """Get a connection from pool.""" coro = self._acquire() return _EngineAcquireContextManager(coro, self) @asyncio.coroutine def _acquire(self): raw = yield from self._pool.acquire() conn = SAConnection(raw, self) return conn def release(self, conn): """Revert back connection to pool.""" if conn.in_transaction: raise InvalidRequestError("Cannot release a connection with " "not finished transaction") raw = conn.connection fut = self._pool.release(raw) return fut def __enter__(self): raise RuntimeError( '"yield from" should be used as context manager expression') def __exit__(self, *args): # This must exist because __enter__ exists, even though that # always raises; that's how the with-statement works. pass # pragma: nocover def __iter__(self): # This is not a coroutine. It is meant to enable the idiom: # # with (yield from engine) as conn: # # # as an alternative to: # # conn = yield from engine.acquire() # try: # # finally: # engine.release(conn) conn = yield from self.acquire() return _ConnectionContextManager(self, conn) if PY_35: # pragma: no branch @asyncio.coroutine def __aenter__(self): return self @asyncio.coroutine def __aexit__(self, exc_type, exc_val, exc_tb): self.close() yield from self.wait_closed() _EngineContextManager = _PoolContextManager _EngineAcquireContextManager = _PoolAcquireContextManager class _ConnectionContextManager: """Context manager. This enables the following idiom for acquiring and releasing a connection around a block: with (yield from engine) as conn: cur = yield from conn.cursor() while failing loudly when accidentally using: with engine: """ __slots__ = ('_engine', '_conn') def __init__(self, engine, conn): self._engine = engine self._conn = conn def __enter__(self): return self._conn def __exit__(self, *args): try: self._engine.release(self._conn) finally: self._engine = None self._conn = None aiopg-0.13.2/aiopg/utils.py0000664000372000037200000001355413223236761016364 0ustar travistravis00000000000000import asyncio import sys PY_35 = sys.version_info >= (3, 5) PY_352 = sys.version_info >= (3, 5, 2) if PY_35: from collections.abc import Coroutine base = Coroutine else: base = object try: ensure_future = asyncio.ensure_future except AttributeError: ensure_future = asyncio.async def create_future(loop): try: return loop.create_future() except AttributeError: return asyncio.Future(loop=loop) class _ContextManager(base): __slots__ = ('_coro', '_obj') def __init__(self, coro): self._coro = coro self._obj = None def send(self, value): return self._coro.send(value) def throw(self, typ, val=None, tb=None): if val is None: return self._coro.throw(typ) elif tb is None: return self._coro.throw(typ, val) else: return self._coro.throw(typ, val, tb) def close(self): return self._coro.close() @property def gi_frame(self): return self._coro.gi_frame @property def gi_running(self): return self._coro.gi_running @property def gi_code(self): return self._coro.gi_code def __next__(self): return self.send(None) @asyncio.coroutine def __iter__(self): resp = yield from self._coro return resp if PY_35: def __await__(self): resp = yield from self._coro return resp @asyncio.coroutine def __aenter__(self): self._obj = yield from self._coro return self._obj @asyncio.coroutine def __aexit__(self, exc_type, exc, tb): self._obj.close() self._obj = None class _SAConnectionContextManager(_ContextManager): if PY_35: # pragma: no branch if PY_352: def __aiter__(self): return self._coro else: @asyncio.coroutine def __aiter__(self): result = yield from self._coro return result class _PoolContextManager(_ContextManager): if PY_35: @asyncio.coroutine def __aexit__(self, exc_type, exc, tb): self._obj.close() yield from self._obj.wait_closed() self._obj = None class _TransactionPointContextManager(_ContextManager): if PY_35: @asyncio.coroutine def __aexit__(self, exc_type, exc_val, exc_tb): if exc_type is not None: yield from self._obj.rollback_savepoint() else: yield from self._obj.release_savepoint() self._obj = None class _TransactionBeginContextManager(_ContextManager): if PY_35: @asyncio.coroutine def __aexit__(self, exc_type, exc_val, exc_tb): if exc_type is not None: yield from self._obj.rollback() else: yield from self._obj.commit() self._obj = None class _TransactionContextManager(_ContextManager): if PY_35: @asyncio.coroutine def __aexit__(self, exc_type, exc, tb): if exc_type: yield from self._obj.rollback() else: if self._obj.is_active: yield from self._obj.commit() self._obj = None class _PoolAcquireContextManager(_ContextManager): __slots__ = ('_coro', '_conn', '_pool') def __init__(self, coro, pool): self._coro = coro self._conn = None self._pool = pool if PY_35: @asyncio.coroutine def __aenter__(self): self._conn = yield from self._coro return self._conn @asyncio.coroutine def __aexit__(self, exc_type, exc, tb): yield from self._pool.release(self._conn) self._pool = None self._conn = None class _PoolConnectionContextManager: """Context manager. This enables the following idiom for acquiring and releasing a connection around a block: with (yield from pool) as conn: cur = yield from conn.cursor() while failing loudly when accidentally using: with pool: """ __slots__ = ('_pool', '_conn') def __init__(self, pool, conn): self._pool = pool self._conn = conn def __enter__(self): assert self._conn return self._conn def __exit__(self, exc_type, exc_val, exc_tb): try: self._pool.release(self._conn) finally: self._pool = None self._conn = None if PY_35: @asyncio.coroutine def __aenter__(self): assert not self._conn self._conn = yield from self._pool.acquire() return self._conn @asyncio.coroutine def __aexit__(self, exc_type, exc_val, exc_tb): try: yield from self._pool.release(self._conn) finally: self._pool = None self._conn = None class _PoolCursorContextManager: """Context manager. This enables the following idiom for acquiring and releasing a cursor around a block: with (yield from pool.cursor()) as cur: yield from cur.execute("SELECT 1") while failing loudly when accidentally using: with pool: """ __slots__ = ('_pool', '_conn', '_cur') def __init__(self, pool, conn, cur): self._pool = pool self._conn = conn self._cur = cur def __enter__(self): return self._cur def __exit__(self, *args): try: self._cur.close() self._pool.release(self._conn) finally: self._pool = None self._conn = None self._cur = None if not PY_35: try: from asyncio import coroutines coroutines._COROUTINE_TYPES += (_ContextManager,) except: pass aiopg-0.13.2/aiopg/transaction.py0000664000372000037200000001236213223236761017545 0ustar travistravis00000000000000import asyncio import enum import uuid import warnings from abc import ABC, abstractmethod import psycopg2 from aiopg.utils import PY_35, _TransactionPointContextManager __all__ = ('IsolationLevel', 'Transaction') class IsolationCompiler(ABC): name = '' __slots__ = ('_readonly', '_deferrable') def __init__(self, readonly, deferrable): self._readonly = readonly self._deferrable = deferrable self._check_readonly_deferrable() def _check_readonly_deferrable(self): available = self._readonly or self._deferrable if not isinstance(self, SerializableCompiler) and available: raise ValueError('Is only available for serializable transactions') def savepoint(self, unique_id): return 'SAVEPOINT {}'.format(unique_id) def release_savepoint(self, unique_id): return 'RELEASE SAVEPOINT {}'.format(unique_id) def rollback_savepoint(self, unique_id): return 'ROLLBACK TO SAVEPOINT {}'.format(unique_id) def commit(self): return 'COMMIT' def rollback(self): return 'ROLLBACK' @abstractmethod def begin(self): raise NotImplementedError("Please Implement this method") def __repr__(self): return self.name class ReadCommittedCompiler(IsolationCompiler): name = 'Read committed' def begin(self): return 'BEGIN' class RepeatableReadCompiler(IsolationCompiler): name = 'Repeatable read' def begin(self): return 'BEGIN ISOLATION LEVEL REPEATABLE READ' class SerializableCompiler(IsolationCompiler): name = 'Serializable' def begin(self): query = 'BEGIN ISOLATION LEVEL SERIALIZABLE' if self._readonly: query += ' READ ONLY' if self._deferrable: query += ' DEFERRABLE' return query class IsolationLevel(enum.Enum): serializable = SerializableCompiler repeatable_read = RepeatableReadCompiler read_committed = ReadCommittedCompiler def __call__(self, readonly, deferrable): return self.value(readonly, deferrable) class Transaction: __slots__ = ('_cur', '_is_begin', '_isolation', '_unique_id') def __init__(self, cur, isolation_level, readonly=False, deferrable=False): self._cur = cur self._is_begin = False self._unique_id = None self._isolation = isolation_level(readonly, deferrable) @property def is_begin(self): return self._is_begin @asyncio.coroutine def begin(self): if self._is_begin: raise psycopg2.ProgrammingError( 'You are trying to open a new transaction, use the save point') self._is_begin = True yield from self._cur.execute(self._isolation.begin()) return self @asyncio.coroutine def commit(self): self._check_commit_rollback() yield from self._cur.execute(self._isolation.commit()) self._is_begin = False @asyncio.coroutine def rollback(self): self._check_commit_rollback() yield from self._cur.execute(self._isolation.rollback()) self._is_begin = False @asyncio.coroutine def rollback_savepoint(self): self._check_release_rollback() yield from self._cur.execute( self._isolation.rollback_savepoint(self._unique_id)) self._unique_id = None @asyncio.coroutine def release_savepoint(self): self._check_release_rollback() yield from self._cur.execute( self._isolation.release_savepoint(self._unique_id)) self._unique_id = None @asyncio.coroutine def savepoint(self): self._check_commit_rollback() if self._unique_id is not None: raise psycopg2.ProgrammingError('You do not shut down savepoint') self._unique_id = 's{}'.format(uuid.uuid1().hex) yield from self._cur.execute( self._isolation.savepoint(self._unique_id)) return self def point(self): return _TransactionPointContextManager(self.savepoint()) def _check_commit_rollback(self): if not self._is_begin: raise psycopg2.ProgrammingError('You are trying to commit ' 'the transaction does not open') def _check_release_rollback(self): self._check_commit_rollback() if self._unique_id is None: raise psycopg2.ProgrammingError('You do not start savepoint') def __repr__(self): return "<{} transaction={} id={:#x}>".format( self.__class__.__name__, self._isolation, id(self) ) def __del__(self): if self._is_begin: warnings.warn( "You have not closed transaction {!r}".format(self), ResourceWarning) if self._unique_id is not None: warnings.warn( "You have not closed savepoint {!r}".format(self), ResourceWarning) if PY_35: @asyncio.coroutine def __aenter__(self): return (yield from self.begin()) @asyncio.coroutine def __aexit__(self, exc_type, exc, tb): if exc_type is not None: yield from self.rollback() else: yield from self.commit()