├── tests ├── __init__.py ├── conftest.py └── test_aiocassandra.py ├── MANIFEST.in ├── docker-compose.yml ├── .pyup.yml ├── .gitignore ├── setup.cfg ├── requirements.txt ├── tox.ini ├── LICENSE ├── example.py ├── setup.py ├── .travis.yml ├── README.rst └── aiocassandra.py /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include LICENSE 3 | recursive-exclude * __pycache__ 4 | recursive-exclude * *.py[co] 5 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | cassandra: 5 | ports: 6 | - 9042:9042 7 | image: cassandra:3.11 8 | -------------------------------------------------------------------------------- /.pyup.yml: -------------------------------------------------------------------------------- 1 | # autogenerated pyup.io config file 2 | # see https://pyup.io/docs/configuration/ for all available options 3 | 4 | schedule: every week 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # python specific 2 | env* 3 | *.pyc 4 | *.so 5 | *.pyd 6 | build/* 7 | dist/* 8 | .eggs/* 9 | MANIFEST 10 | __pycache__/ 11 | *.egg-info/ 12 | .coverage 13 | htmlcov 14 | 15 | # generic files to ignore 16 | *~ 17 | *.lock 18 | *.DS_Store 19 | *.swp 20 | *.out 21 | 22 | .tox/ 23 | deps/ 24 | docs/_build/ 25 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 3 | 4 | [metadata] 5 | license_file = LICENSE 6 | 7 | [coverage:run] 8 | branch = True 9 | omit = site-packages 10 | 11 | [isort] 12 | known_first_party = aiocassandra 13 | known_third_party = async_generator,cassandra,pytest 14 | 15 | [tool:pytest] 16 | addopts = -s --keep-duplicates --cache-clear --verbose --no-cov-on-fail --cov=aiocassandra --cov-report=term --cov-report=html 17 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | appdirs==1.4.3 2 | appnope==0.1.0 3 | async-generator==1.9 4 | attrs==18.1.0 5 | cassandra-driver==3.14.0 6 | coverage==4.5.1 7 | Cython==0.28.4 8 | decorator==4.2.1 9 | flake8==3.5.0 10 | ipdb==0.11 11 | ipython==6.5.0 12 | ipython-genutils==0.2.0 13 | isort==4.3.4 14 | jedi==0.12.1 15 | mccabe==0.6.1 16 | packaging==17.1 17 | parso==0.3.0 18 | pexpect==4.6.0 19 | pickleshare==0.7.4 20 | pluggy==0.7.1 21 | prompt-toolkit==2.0.4 22 | ptyprocess==0.6.0 23 | py==1.5.4 24 | pycodestyle==2.4.0 25 | pyflakes==2.0.0 26 | Pygments==2.2.0 27 | pyparsing==2.2.0 28 | pytest==3.6.4 29 | pytest-asyncio==0.9.0 30 | pytest-cov==2.5.1 31 | simplegeneric==0.8.1 32 | six==1.11.0 33 | tox==3.1.2 34 | traitlets==4.3.2 35 | virtualenv==16.0.0 36 | wcwidth==0.1.7 37 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | py3{5,6} 4 | pypy3 5 | skip_missing_interpreters = True 6 | 7 | [testenv] 8 | deps = 9 | flake8 10 | isort 11 | commands = 12 | flake8 --show-source aiocassandra.py setup.py 13 | isort --check-only aiocassandra.py setup.py 14 | flake8 --show-source tests 15 | isort --check-only -rc tests --diff 16 | 17 | {envpython} setup.py pytest 18 | 19 | [testenv:wait-for-cassandra] 20 | basepython = python2.7 21 | skipsdist = true 22 | skip_install = true 23 | deps = 24 | cqlsh 25 | whitelist_externals = 26 | bash 27 | echo 28 | setenv = 29 | CQL_VERSION=3.4.4 30 | commands = 31 | echo Waiting till cassandra is up... 32 | bash -c " \ 33 | while ! echo exit | cqlsh --cqlversion=$CQL_VERSION; \ 34 | do \ 35 | sleep 1; \ 36 | echo -n .; \ 37 | done \ 38 | " 39 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import gc 3 | import os 4 | 5 | import pytest 6 | from cassandra.cluster import Cluster 7 | 8 | from aiocassandra import aiosession 9 | 10 | asyncio.set_event_loop(None) 11 | 12 | 13 | @pytest.fixture 14 | def cluster(): 15 | cluster = Cluster() 16 | 17 | yield cluster 18 | 19 | cluster.shutdown() 20 | 21 | 22 | @pytest.fixture 23 | def session(cluster): 24 | return cluster.connect() 25 | 26 | 27 | @pytest.fixture 28 | def cassandra(session, loop): 29 | return aiosession(session, loop=loop) 30 | 31 | 32 | @pytest.fixture 33 | def event_loop(request): 34 | loop = asyncio.new_event_loop() 35 | loop.set_debug(bool(os.environ.get('PYTHONASYNCIODEBUG'))) 36 | 37 | yield loop 38 | 39 | loop.run_until_complete(loop.shutdown_asyncgens()) 40 | 41 | loop.call_soon(loop.stop) 42 | loop.run_forever() 43 | loop.close() 44 | 45 | gc.collect() 46 | gc.collect() # for pypy 47 | 48 | 49 | @pytest.fixture 50 | def loop(event_loop, request): 51 | asyncio.set_event_loop(None) 52 | request.addfinalizer(lambda: asyncio.set_event_loop(None)) 53 | 54 | return event_loop 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2018 aio-libs team https://github.com/aio-libs/ 4 | Copyright (c) 2017 Ocean S. A. https://ocean.io/ 5 | Copyright (c) 2016-2017 WikiBusiness Corporation. http://wikibusiness.org/ 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /example.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from aiocassandra import aiosession 4 | from cassandra.cluster import Cluster 5 | from cassandra.query import SimpleStatement 6 | 7 | # connection is blocking call 8 | cluster = Cluster() 9 | # aiocassandra uses executor_threads to talk to cassndra driver 10 | # https://datastax.github.io/python-driver/api/cassandra/cluster.html?highlight=executor_threads 11 | session = cluster.connect() 12 | 13 | 14 | async def main(): 15 | # patches and adds `execute_future`, `execute_futures` and `prepare_future` 16 | # to `cassandra.cluster.Session` 17 | aiosession(session) 18 | 19 | # best way is to use cassandra prepared statements 20 | # https://cassandra-zone.com/prepared-statements/ 21 | # https://datastax.github.io/python-driver/api/cassandra/cluster.html#cassandra.cluster.Session.prepare 22 | # try to create them once on application init 23 | query = session.prepare('SELECT now() FROM system.local;') 24 | 25 | # if non-blocking prepared statements is really needed: 26 | query = await session.prepare_future('SELECT now() FROM system.local;') 27 | 28 | print(await session.execute_future(query)) 29 | 30 | # pagination is also supported 31 | query = 'SELECT * FROM system.size_estimates;' 32 | statement = SimpleStatement(query, fetch_size=100) 33 | 34 | # don't miss *s* (execute_futureS) 35 | async with session.execute_futures(statement) as paginator: 36 | async for row in paginator: 37 | print(row) 38 | 39 | 40 | loop = asyncio.get_event_loop() 41 | loop.run_until_complete(main()) 42 | cluster.shutdown() 43 | loop.close() 44 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import io 2 | import os 3 | import re 4 | import sys 5 | 6 | from setuptools import setup 7 | 8 | needs_pytest = 'pytest' in set(sys.argv) 9 | 10 | 11 | def get_version(): 12 | regex = r"__version__\s=\s\'(?P[\d\.ab]+?)\'" 13 | 14 | path = 'aiocassandra.py' 15 | 16 | return re.search(regex, read(path)).group('version') 17 | 18 | 19 | def read(*parts): 20 | filename = os.path.join(os.path.abspath(os.path.dirname(__file__)), *parts) 21 | 22 | with io.open(filename, encoding='utf-8', mode='rt') as fp: 23 | return fp.read() 24 | 25 | 26 | setup( 27 | name='aiocassandra', 28 | version=get_version(), 29 | author='Victor Kovtun', 30 | author_email='hellysmile@gmail.com', 31 | url='https://github.com/aio-libs/aiocassandra', 32 | description='Simple threaded cassandra wrapper for asyncio', 33 | long_description=read('README.rst'), 34 | install_requires=['cassandra-driver', 'async-generator'], 35 | setup_requires=['pytest-runner'] if needs_pytest else [], 36 | tests_require=['pytest', 'pytest-asyncio', 'pytest-cov'], 37 | python_requires='>=3.4.0', 38 | py_modules=['aiocassandra'], 39 | zip_safe=False, 40 | classifiers=[ 41 | 'Development Status :: 5 - Production/Stable', 42 | 'Intended Audience :: Developers', 43 | 'License :: OSI Approved :: MIT License', 44 | 'Operating System :: POSIX', 45 | 'Operating System :: Microsoft :: Windows', 46 | 'Programming Language :: Python', 47 | 'Programming Language :: Python :: 3', 48 | 'Programming Language :: Python :: 3.5', 49 | 'Programming Language :: Python :: 3.6', 50 | 'Framework :: AsyncIO', 51 | ], 52 | keywords=['cassandra', 'asyncio'], 53 | ) 54 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial 2 | sudo: required 3 | 4 | language: python 5 | python: 6 | - 3.5 7 | - 3.6 8 | - pypy3.5-5.10.0 9 | 10 | cache: pip 11 | 12 | addons: 13 | apt: 14 | sources: 15 | - sourceline: 'ppa:openjdk-r/ppa' 16 | - sourceline: 'deb http://www.apache.org/dist/cassandra/debian 311x main' 17 | key_url: &apache-key https://www.apache.org/dist/cassandra/KEYS 18 | - sourceline: 'deb-src http://www.apache.org/dist/cassandra/debian 311x main' 19 | key_url: *apache-key 20 | packages: 21 | - openjdk-8-jdk 22 | - cassandra 23 | services: 24 | - cassandra 25 | 26 | install: 27 | - pip install tox 28 | 29 | before_script: 30 | - tox -e wait-for-cassandra 31 | script: 32 | - tox -e python 33 | after_success: 34 | - pip install codecov 35 | - codecov 36 | 37 | jobs: 38 | include: 39 | - stage: Push to PYPI (runs only for tagged commits) 40 | if: tag IS present 41 | python: 3.6 42 | addons: {} 43 | services: [] 44 | before_install: [] 45 | install: skip 46 | before_script: [] 47 | script: skip 48 | after_success: [] 49 | deploy: 50 | provider: pypi 51 | user: hellysmile 52 | password: 53 | secure: "EG6ZiN/WbbfObG8+GCFh3e5kzqdcv35gHcP+bJ1GjJL6sw4ohWBYXZCX1mo/ScYNFcVJzR/TxPp1hSKhgBG5m/X/tTm7tvenpq/44B8xTFX4I8G18AW5k7Jj7kR321eXS13txQ/3a43k38icfEzmMKy3U01cOwJE9X9dVq57zZpSpAk3O+eMqAnfvQN620TaxYoBGlQ8jBgwtw8F3IQXStRm1QCIdCpYXttMxTWTPbdDgTnoDiMa76JYuYIaGiOvm544INjrJyXDMQx9bY+jxnV/AcCkSF3FODnoO9DuFU0NdUzQB0gRtGo8ZKeKR1Q1vcufWubnbU3fRvaHUQPXirS3T6yu+GZ5gcwt4s54VPeYShwL55DK0r8h6IQWYr/ZhGUoWVm9adGmnoP1SDycbjsu0Prq8XzQT4BePmto4yNZbTYsgFuLTXFA+AWZ1QL20MPgTM5yqrIbUnG04uGHEWiuZfHnT8kjpJleOFOtCm3WHLJGHyeFHduANLiEPjVwORFqNEXRH/QjFNLkd+Q1EkkusPt/WMCMmz0v2a9lVle/h+3jB3XSP0KN38xNmQDjU4vFR0LURVs4pyKc51EKwhi/3sbMb47SsCwhGyJATg/TgVTTM90/CLq9W3Q7dEy4qPejUI7YD5kixKIBh7w5jB08X26fy98IXYh/KfBmow4=" 54 | distributions: sdist bdist_wheel 55 | on: 56 | tags: true 57 | all_branches: true 58 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | aiocassandra 2 | ============ 3 | 4 | :info: Simple threaded cassandra wrapper for asyncio 5 | 6 | .. image:: https://travis-ci.org/aio-libs/aiocassandra.svg?branch=master 7 | :target: https://travis-ci.org/aio-libs/aiocassandra 8 | 9 | .. image:: https://img.shields.io/pypi/v/aiocassandra.svg 10 | :target: https://pypi.python.org/pypi/aiocassandra 11 | 12 | .. image:: https://codecov.io/gh/aio-libs/aiocassandra/branch/master/graph/badge.svg 13 | :target: https://codecov.io/gh/aio-libs/aiocassandra 14 | 15 | Installation 16 | ------------ 17 | 18 | .. code-block:: shell 19 | 20 | pip install aiocassandra 21 | 22 | Usage 23 | ----- 24 | 25 | .. code-block:: python 26 | 27 | import asyncio 28 | 29 | from aiocassandra import aiosession 30 | from cassandra.cluster import Cluster 31 | from cassandra.query import SimpleStatement 32 | 33 | # connection is blocking call 34 | cluster = Cluster() 35 | # aiocassandra uses executor_threads to talk to cassndra driver 36 | # https://datastax.github.io/python-driver/api/cassandra/cluster.html?highlight=executor_threads 37 | session = cluster.connect() 38 | 39 | 40 | async def main(): 41 | # patches and adds `execute_future`, `execute_futures` and `prepare_future` 42 | # to `cassandra.cluster.Session` 43 | aiosession(session) 44 | 45 | # best way is to use cassandra prepared statements 46 | # https://cassandra-zone.com/prepared-statements/ 47 | # https://datastax.github.io/python-driver/api/cassandra/cluster.html#cassandra.cluster.Session.prepare 48 | # try to create them once on application init 49 | query = session.prepare('SELECT now() FROM system.local;') 50 | 51 | # if non-blocking prepared statements is really needed: 52 | query = await session.prepare_future('SELECT now() FROM system.local;') 53 | 54 | print(await session.execute_future(query)) 55 | 56 | # pagination is also supported 57 | query = 'SELECT * FROM system.size_estimates;' 58 | statement = SimpleStatement(query, fetch_size=100) 59 | 60 | # don't miss *s* (execute_futureS) 61 | async with session.execute_futures(statement) as paginator: 62 | async for row in paginator: 63 | print(row) 64 | 65 | 66 | loop = asyncio.get_event_loop() 67 | loop.run_until_complete(main()) 68 | cluster.shutdown() 69 | loop.close() 70 | 71 | Python 3.5+ is required 72 | 73 | Thanks 74 | ------ 75 | 76 | The library was donated by `Ocean S.A. `_ 77 | 78 | Thanks to the company for contribution. 79 | -------------------------------------------------------------------------------- /aiocassandra.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import logging 3 | import sys 4 | from collections import deque 5 | from concurrent.futures import ThreadPoolExecutor 6 | from functools import partial 7 | from threading import Event 8 | from types import MethodType 9 | 10 | from async_generator import async_generator, yield_ 11 | from cassandra.cluster import Session 12 | 13 | __version__ = '2.0.1' 14 | 15 | PY_352 = sys.version_info >= (3, 5, 2) 16 | 17 | logger = logging.getLogger(__name__) 18 | 19 | 20 | class _Paginator: 21 | 22 | def __init__(self, request, *, executor, loop): 23 | self.cassandra_fut = None 24 | 25 | self._request = request 26 | 27 | self._executor = executor 28 | self._loop = loop 29 | 30 | self._deque = deque() 31 | self._exc = None 32 | self._drain_event = asyncio.Event(loop=loop) 33 | self._finish_event = asyncio.Event(loop=loop) 34 | self._exit_event = Event() 35 | 36 | self.__pages = set() 37 | 38 | def _handle_page(self, rows): 39 | if self._exit_event.is_set(): 40 | _len = len(rows) 41 | logger.debug( 42 | 'Paginator is closed, skipping new %i records', _len) 43 | return 44 | 45 | for row in rows: 46 | self._deque.append(row) 47 | 48 | self._loop.call_soon_threadsafe(self._drain_event.set) 49 | 50 | if self.cassandra_fut.has_more_pages: 51 | _fn = self.cassandra_fut.start_fetching_next_page 52 | fut = self._loop.run_in_executor(self._executor, _fn) 53 | self.__pages.add(fut) 54 | fut.add_done_callback(self.__pages.remove) 55 | return 56 | 57 | self._loop.call_soon_threadsafe(self._finish_event.set) 58 | 59 | def _handle_err(self, exc): 60 | self._exc = exc 61 | 62 | self._loop.call_soon_threadsafe(self._finish_event.set) 63 | 64 | async def __aenter__(self): 65 | self.cassandra_fut = await self._loop.run_in_executor( 66 | self._executor, 67 | self._request 68 | ) 69 | 70 | self.cassandra_fut.add_callbacks( 71 | callback=self._handle_page, 72 | errback=self._handle_err 73 | ) 74 | return self 75 | 76 | async def __aexit__(self, *exc_info): 77 | self._exit_event.set() 78 | _len = len(self._deque) 79 | self._deque.clear() 80 | logger.debug( 81 | 'Paginator is closed, cleared in-memory %i records', _len) 82 | 83 | await asyncio.gather(*self.__pages, loop=self._loop) 84 | 85 | def __aiter__(self): 86 | return self._paginator() 87 | 88 | if not PY_352: # pragma: no cover 89 | __aiter__ = asyncio.coroutine(__aiter__) 90 | 91 | @async_generator 92 | async def _paginator(self): 93 | if self.cassandra_fut is None: 94 | raise RuntimeError( 95 | 'Pagination should be done inside async context manager') 96 | 97 | while ( 98 | self._deque or 99 | not self._finish_event.is_set() or 100 | self._exc is not None 101 | ): 102 | if self._exc is not None: 103 | raise self._exc 104 | 105 | while self._deque: 106 | await yield_(self._deque.popleft()) 107 | 108 | await asyncio.wait( 109 | ( 110 | self._drain_event.wait(), 111 | self._finish_event.wait(), 112 | ), 113 | return_when=asyncio.FIRST_COMPLETED, 114 | loop=self._loop 115 | ) 116 | 117 | 118 | def _asyncio_fut_factory(loop): 119 | try: 120 | return loop.create_future 121 | except AttributeError: # pragma: no cover 122 | return partial(asyncio.Future, loop=loop) 123 | 124 | 125 | def _asyncio_result(self, fut, result): 126 | if fut.cancelled(): 127 | return 128 | 129 | self._asyncio_loop.call_soon_threadsafe(fut.set_result, result) 130 | 131 | 132 | def _asyncio_exception(self, fut, exc): 133 | if fut.cancelled(): 134 | return 135 | 136 | self._asyncio_loop.call_soon_threadsafe(fut.set_exception, exc) 137 | 138 | 139 | async def execute_future(self, *args, **kwargs): 140 | _request = partial(self.execute_async, *args, **kwargs) 141 | cassandra_fut = await self._asyncio_loop.run_in_executor( 142 | self._asyncio_executor, 143 | _request 144 | ) 145 | 146 | asyncio_fut = self._asyncio_fut_factory() 147 | 148 | cassandra_fut.add_callbacks( 149 | callback=partial(self._asyncio_result, asyncio_fut), 150 | errback=partial(self._asyncio_exception, asyncio_fut) 151 | ) 152 | 153 | return await asyncio_fut 154 | 155 | 156 | def execute_futures(self, *args, **kwargs): 157 | _request = partial(self.execute_async, *args, **kwargs) 158 | return _Paginator( 159 | _request, 160 | executor=self._asyncio_executor, 161 | loop=self._asyncio_loop 162 | ) 163 | 164 | 165 | def prepare_future(self, *args, **kwargs): 166 | _fn = partial(self.prepare, *args, **kwargs) 167 | return self._asyncio_loop.run_in_executor(self._asyncio_executor, _fn) 168 | 169 | 170 | def aiosession(session, *, executor=None, loop=None): 171 | if not isinstance(session, Session): 172 | raise RuntimeError( 173 | 'provide cassandra.cluster.Session') 174 | 175 | if hasattr(session, '_asyncio_fut_factory'): 176 | raise RuntimeError( 177 | 'session is already patched by aiosession') 178 | 179 | if executor is not None: 180 | if not isinstance(executor, ThreadPoolExecutor): 181 | raise RuntimeError( 182 | 'executor should be instance of ThreadPoolExecutor') 183 | 184 | if loop is None: 185 | loop = asyncio.get_event_loop() 186 | 187 | session._asyncio_loop = loop 188 | session._asyncio_executor = executor 189 | session._asyncio_fut_factory = _asyncio_fut_factory(loop=loop) 190 | 191 | session._asyncio_result = MethodType(_asyncio_result, session) 192 | session._asyncio_exception = MethodType(_asyncio_exception, session) 193 | session.execute_future = MethodType(execute_future, session) 194 | session.execute_futures = MethodType(execute_futures, session) 195 | session.prepare_future = MethodType(prepare_future, session) 196 | 197 | return session 198 | -------------------------------------------------------------------------------- /tests/test_aiocassandra.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import logging 3 | import uuid 4 | from concurrent.futures import ThreadPoolExecutor 5 | 6 | import pytest 7 | from cassandra.cluster import Cluster 8 | from cassandra.protocol import SyntaxException 9 | from cassandra.query import SimpleStatement 10 | 11 | from aiocassandra import aiosession 12 | 13 | 14 | @pytest.mark.asyncio 15 | async def test_prepare_future(cassandra): 16 | query = 'SELECT now() as now FROM system.local;' 17 | 18 | blocking = cassandra.prepare(query) 19 | 20 | non_blocking = await cassandra.prepare_future(query) 21 | 22 | assert blocking.query_id == non_blocking.query_id 23 | 24 | 25 | @pytest.mark.asyncio 26 | async def test_execute_future_prepare(cassandra): 27 | cql = cassandra.prepare('SELECT now() as now FROM system.local;') 28 | 29 | ret = await cassandra.execute_future(cql) 30 | 31 | assert len(ret) == 1 32 | 33 | assert isinstance(ret[0].now, uuid.UUID) 34 | 35 | 36 | @pytest.mark.asyncio 37 | async def test_execute_future(cassandra): 38 | cql = 'SELECT now() as now FROM system.local;' 39 | 40 | ret = await cassandra.execute_future(cql) 41 | 42 | assert len(ret) == 1 43 | 44 | assert isinstance(ret[0].now, uuid.UUID) 45 | 46 | 47 | @pytest.mark.asyncio 48 | async def test_execute_future_error(cassandra): 49 | cql = 'SELECT 1;' 50 | 51 | with pytest.raises(SyntaxException): 52 | await cassandra.execute_future(cql) 53 | 54 | 55 | @pytest.mark.asyncio 56 | async def test_execute_future_cancel(cassandra, caplog, loop): 57 | cql = 'SELECT now() as now FROM system.local;' 58 | 59 | old_fut_factory = cassandra._asyncio_fut_factory 60 | 61 | def new_patch_factory(): 62 | fut = old_fut_factory() 63 | fut.cancel() 64 | return fut 65 | 66 | cassandra._asyncio_fut_factory = new_patch_factory 67 | 68 | fut = loop.create_task(cassandra.execute_future(cql)) 69 | 70 | with caplog.at_level(logging.ERROR): 71 | await asyncio.sleep(0.1, loop=loop) 72 | 73 | assert len(caplog.records) == 0 74 | 75 | with pytest.raises(asyncio.CancelledError): 76 | await fut 77 | 78 | 79 | @pytest.mark.asyncio 80 | async def test_execute_future_cancel_error(cassandra, caplog, loop): 81 | cql = 'SELECT 1;' 82 | 83 | old_fut_factory = cassandra._asyncio_fut_factory 84 | 85 | def new_patch_factory(): 86 | fut = old_fut_factory() 87 | fut.cancel() 88 | return fut 89 | 90 | cassandra._asyncio_fut_factory = new_patch_factory 91 | 92 | fut = loop.create_task(cassandra.execute_future(cql)) 93 | 94 | with caplog.at_level(logging.ERROR): 95 | await asyncio.sleep(0.1, loop=loop) 96 | 97 | assert len(caplog.records) == 0 98 | 99 | with pytest.raises(asyncio.CancelledError): 100 | await fut 101 | 102 | 103 | @pytest.mark.asyncio 104 | async def test_execute_futures_simple(cassandra): 105 | cql = 'SELECT now() as now FROM system.local;' 106 | 107 | ret = [] 108 | 109 | async with cassandra.execute_futures(cql) as paginator: 110 | async for row in paginator: 111 | ret.append(row) 112 | 113 | assert len(ret) == 1 114 | 115 | assert isinstance(ret[0].now, uuid.UUID) 116 | 117 | 118 | @pytest.mark.asyncio 119 | async def test_execute_futures_simple_statement_empty(cassandra): 120 | cql = 'SELECT * FROM system_schema.types;' 121 | statement = SimpleStatement(cql, fetch_size=1) 122 | 123 | ret = [] 124 | 125 | async with cassandra.execute_futures(statement) as paginator: 126 | async for row in paginator: 127 | ret.append(row) 128 | 129 | assert len(ret) == 0 130 | 131 | 132 | @pytest.mark.asyncio 133 | async def test_execute_futures_simple_statement(cassandra): 134 | cql = 'SELECT * FROM system.size_estimates;' 135 | statement = SimpleStatement(cql, fetch_size=100) 136 | 137 | ret = [] 138 | 139 | async with cassandra.execute_futures(statement) as paginator: 140 | async for row in paginator: 141 | assert isinstance(row, tuple) 142 | ret.append(row) 143 | 144 | assert len(ret) != 0 145 | 146 | 147 | @pytest.mark.asyncio 148 | async def test_execute_futures_break(cassandra): 149 | cql = 'SELECT * FROM system.size_estimates;' 150 | statement = SimpleStatement(cql, fetch_size=100) 151 | 152 | ret = [] 153 | 154 | async with cassandra.execute_futures(statement) as paginator: 155 | async for row in paginator: 156 | assert isinstance(row, tuple) 157 | ret.append(row) 158 | break 159 | 160 | assert len(ret) == 1 161 | 162 | assert len(paginator._deque) == 0 163 | 164 | 165 | @pytest.mark.asyncio 166 | async def test_execute_futures_simple_statement_error(cassandra): 167 | cql = 'SELECT 1;' 168 | statement = SimpleStatement(cql, fetch_size=1) 169 | 170 | ret = [] 171 | 172 | with pytest.raises(SyntaxException): 173 | async with cassandra.execute_futures(statement) as paginator: 174 | async for row in paginator: 175 | ret.append(row) 176 | 177 | assert len(ret) == 0 178 | 179 | 180 | @pytest.mark.asyncio 181 | async def test_execute_futures_runtime_error(cassandra): 182 | cql = 'SELECT * FROM system.size_estimates;' 183 | statement = SimpleStatement(cql, fetch_size=100) 184 | 185 | paginator = cassandra.execute_futures(statement) 186 | 187 | ret = [] 188 | 189 | with pytest.raises(RuntimeError): 190 | async for row in paginator: 191 | ret.append(row) 192 | 193 | assert len(ret) == 0 194 | 195 | 196 | def test_malformed_session(): 197 | with pytest.raises(RuntimeError): 198 | aiosession(None) 199 | 200 | 201 | def test_patched_twice(cassandra, session, loop): 202 | with pytest.raises(RuntimeError): 203 | aiosession(session, loop=loop) 204 | 205 | 206 | def test_main_thread_loop_missing(session): 207 | with pytest.raises(RuntimeError): 208 | aiosession(session) 209 | 210 | 211 | def test_main_thread_loop(loop, session): 212 | asyncio.set_event_loop(loop) 213 | cluster = Cluster() 214 | session = cluster.connect() 215 | 216 | aiosession(session) 217 | 218 | assert loop is session._asyncio_loop 219 | 220 | 221 | def test_explicit_loop(cassandra, loop): 222 | assert loop is cassandra._asyncio_loop 223 | 224 | 225 | def test_explicit_executor(loop, session): 226 | executor = ThreadPoolExecutor(max_workers=1) 227 | 228 | aiosession(session, executor=executor, loop=loop) 229 | 230 | assert executor is session._asyncio_executor 231 | 232 | executor.shutdown(wait=True) 233 | 234 | 235 | def test_wrong_executor(loop, session): 236 | with pytest.raises(RuntimeError): 237 | aiosession(session, executor=1, loop=loop) 238 | 239 | 240 | def test_session_patched(cassandra): 241 | assert getattr(cassandra, 'execute_future', None) is not None 242 | --------------------------------------------------------------------------------