Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Python 3.8; drop Python <3.6 #135

Merged
merged 21 commits into from
Jan 22, 2020
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
language: python

python:
- "3.3"
- "3.4"
- "3.5"
- "3.6"
- "3.7"
- "3.8"

before_install:
- sudo apt-get install redis-server
- sudo service redis-server start
- redis-server --version
- which redis-server
- /bin/sleep 10

install:
- "pip install ."
Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
CHANGELOG
=========

0.16.0: unreleased
------------------
- Added support for Python 3.8
- Dropped support for Python <3.6
- Deprecated 'loop' parameter in Connection.create and Pool.create
- Added support for zpopmin and zrangebylex methods
- Fix DeprecationWarning for inspect.formatargspec()
- Fix missing object asyncio.streams.IncompleteReadError
- Intersphinx: all classes that can be imported from the top-level module must now be
referenced from the top-level module in the docstrings too.
E.g. :class:`asyncio_redis.exceptions.Error` has changed to
crusaderky marked this conversation as resolved.
Show resolved Hide resolved
:class:`asyncio_redis.Error`.
crusaderky marked this conversation as resolved.
Show resolved Hide resolved


0.15.1: 2018-07-30
------------------

Expand Down
7 changes: 3 additions & 4 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,9 @@ Redis client for the `PEP 3156`_ Python event loop.

.. _PEP 3156: http://legacy.python.org/dev/peps/pep-3156/

This Redis library is a completely asynchronous, non-blocking client for a
Redis server. It depends on asyncio (PEP 3156) and therefor it requires Python
3.3 or greater. If you're new to asyncio, it can be helpful to check out
`the asyncio documentation`_ first.
This Redis library is a completely asynchronous, non-blocking client for a Redis server.
It depends on asyncio (PEP 3156) and requires Python 3.6 or greater. If you're new to
asyncio, it can be helpful to check out `the asyncio documentation`_ first.

.. _the asyncio documentation: http://docs.python.org/dev/library/asyncio.html

Expand Down
55 changes: 49 additions & 6 deletions asyncio_redis/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,50 @@
"""Redis protocol implementation for asyncio (PEP 3156)
"""
Redis protocol implementation for asyncio (PEP 3156)
"""
from .connection import *
from .exceptions import *
from .pool import *
from .protocol import *
from .connection import Connection
from .exceptions import (
ConnectionLostError,
Error,
ErrorReply,
NoAvailableConnectionsInPoolError,
NoRunningScriptError,
NotConnectedError,
ScriptKilledError,
TimeoutError,
TransactionError,
)
from .pool import Pool
from .protocol import (
RedisProtocol,
HiRedisProtocol,
Transaction,
Subscription,
Script,
ZAggregate,
ZScoreBoundary,
)


__all__ = (
'Connection',
'Pool',

# Protocols
'RedisProtocol',
'HiRedisProtocol',
'Transaction',
'Subscription',
'Script',
'ZAggregate',
'ZScoreBoundary',

# Exceptions
'ConnectionLostError',
'Error',
'ErrorReply',
'NoAvailableConnectionsInPoolError',
'NoRunningScriptError',
'NotConnectedError',
'ScriptKilledError',
'TimeoutError',
'TransactionError',
)
85 changes: 41 additions & 44 deletions asyncio_redis/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,7 @@
from .protocol import RedisProtocol, _all_commands
import asyncio
import logging


__all__ = ('Connection', )


# In Python 3.4.4, `async` was renamed to `ensure_future`.
try:
ensure_future = asyncio.ensure_future
except AttributeError:
ensure_future = getattr(asyncio, "async")
import warnings


class Connection:
Expand All @@ -21,37 +12,40 @@ class Connection:


::

connection = yield from Connection.create(host='localhost', port=6379)
result = yield from connection.set('key', 'value')
connection = await Connection.create(host='localhost', port=6379)
result = await connection.set('key', 'value')
"""
@classmethod
@asyncio.coroutine
def create(cls, host='localhost', port=6379, *, password=None, db=0,
async def create(cls, host='localhost', port=6379, *, password=None, db=0,
encoder=None, auto_reconnect=True, loop=None, protocol_class=RedisProtocol):
"""
:param host: Address, either host or unix domain socket path
:type host: str
:param port: TCP port. If port is 0 then host assumed to be unix socket path
:type port: int
:param password: Redis database password
:type password: bytes
:param db: Redis database
:type db: int
:param encoder: Encoder to use for encoding to or decoding from redis bytes to a native type.
:type encoder: :class:`~asyncio_redis.encoders.BaseEncoder` instance.
:param auto_reconnect: Enable auto reconnect
:type auto_reconnect: bool
:param loop: (optional) asyncio event loop.
:type protocol_class: :class:`~asyncio_redis.RedisProtocol`
:param protocol_class: (optional) redis protocol implementation
:param str host:
Address, either host or unix domain socket path
:param int port:
TCP port. If port is 0 then host assumed to be unix socket path
:param bytes password:
Redis database password
:param int db:
Redis database
:param encoder:
Encoder to use for encoding to or decoding from redis bytes to a native type.
:type encoder:
:class:`~asyncio_redis.encoders.BaseEncoder`
:param bool auto_reconnect:
Enable auto reconnect
:param protocol_class:
(optional) redis protocol implementation
:type protocol_class:
:class:`~asyncio_redis.RedisProtocol`
"""
assert port >= 0, "Unexpected port value: %r" % (port, )
if loop:
warnings.warn("Deprecated parameter: loop", DeprecationWarning)

connection = cls()

connection.host = host
connection.port = port
connection._loop = loop or asyncio.get_event_loop()
connection._retry_interval = .5
connection._closed = False
connection._closing = False
Expand All @@ -61,17 +55,18 @@ def create(cls, host='localhost', port=6379, *, password=None, db=0,
# Create protocol instance
def connection_lost():
if connection._auto_reconnect and not connection._closing:
ensure_future(connection._reconnect(), loop=connection._loop)
loop = asyncio.get_event_loop()
loop.create_task(connection._reconnect())

# Create protocol instance
connection.protocol = protocol_class(password=password, db=db, encoder=encoder,
connection_lost_callback=connection_lost, loop=connection._loop)
connection_lost_callback=connection_lost)

# Connect
if connection._auto_reconnect:
yield from connection._reconnect()
await connection._reconnect()
else:
yield from connection._connect()
await connection._connect()

return connection

Expand All @@ -92,33 +87,35 @@ def _increase_retry_interval(self):
""" When a connection failed. Increase the interval."""
self._retry_interval = min(60, 1.5 * self._retry_interval)

@asyncio.coroutine
def _connect(self):
async def _connect(self):
"""
Set up Redis connection.
"""
loop = asyncio.get_event_loop()
logger.log(logging.INFO, 'Connecting to redis')
if self.port:
yield from self._loop.create_connection(lambda: self.protocol, self.host, self.port)
await loop.create_connection(lambda: self.protocol, self.host, self.port)
else:
yield from self._loop.create_unix_connection(lambda: self.protocol, self.host)
await loop.create_unix_connection(lambda: self.protocol, self.host)

@asyncio.coroutine
def _reconnect(self):
async def _reconnect(self):
"""
Set up Redis re-connection.
"""
while True:
try:
yield from self._connect()
await self._connect()
self._reset_retry_interval()
return
except OSError:
# Sleep and try again
self._increase_retry_interval()
interval = self._get_retry_interval()
logger.log(logging.INFO, 'Connecting to redis failed. Retrying in %i seconds' % interval)
yield from asyncio.sleep(interval, loop=self._loop)
logger.log(
logging.INFO,
f'Connecting to redis failed. Retrying in {interval} seconds',
)
await asyncio.sleep(interval)

def __getattr__(self, name):
# Only proxy commands.
Expand Down
37 changes: 14 additions & 23 deletions asyncio_redis/cursors.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import asyncio
from collections import deque

__all__ = (
Expand Down Expand Up @@ -30,21 +29,17 @@ def __init__(self, name, scanfunc):
def __repr__(self):
return '<%s %s>' % (self.__class__.__name__, self._name)

@asyncio.coroutine
def _fetch_more(self):
async def _fetch_more(self):
""" Get next chunk of keys from Redis """
if not self._done:
chunk = yield from self._scanfunc(self._cursor, self.count)
chunk = await self._scanfunc(self._cursor, self.count)
self._cursor = chunk.new_cursor_pos

if chunk.new_cursor_pos == 0:
self._done = True
self._done = chunk.new_cursor_pos == 0

for i in chunk.items:
self._queue.append(i)

@asyncio.coroutine
def fetchone(self):
async def fetchone(self):
"""
Coroutines that returns the next item.
It returns `None` after the last item.
Expand All @@ -54,19 +49,18 @@ def fetchone(self):
# Redis can return a chunk of zero items, even when we're not yet finished.
# See: https://github.com/jonathanslenders/asyncio-redis/issues/65#issuecomment-127026408
while not self._queue and not self._done:
yield from self._fetch_more()
await self._fetch_more()

# Return the next item.
if self._queue:
return self._queue.popleft()

@asyncio.coroutine
def fetchall(self):
async def fetchall(self):
""" Coroutine that reads all the items in one list. """
results = []

while not self._done:
yield from self._fetch_more()
await self._fetch_more()
results.extend(self._queue)
self._queue.clear()

Expand All @@ -78,9 +72,8 @@ class SetCursor(Cursor):
Cursor for walking through the results of a :func:`sscan
<asyncio_redis.RedisProtocol.sscan>` query.
"""
@asyncio.coroutine
def fetchall(self):
result = yield from super().fetchall()
async def fetchall(self):
result = await super().fetchall()
return set(result)


Expand All @@ -92,26 +85,24 @@ class DictCursor(Cursor):
def _parse(self, key, value):
return key, value

@asyncio.coroutine
def fetchone(self):
async def fetchone(self):
"""
Get next { key: value } tuple
It returns `None` after the last item.
"""
key = yield from super().fetchone()
value = yield from super().fetchone()
key = await super().fetchone()
value = await super().fetchone()

if key is not None:
key, value = self._parse(key, value)
return { key: value }

@asyncio.coroutine
def fetchall(self):
async def fetchall(self):
""" Coroutine that reads all the items in one dictionary. """
results = {}

while True:
i = yield from self.fetchone()
i = await self.fetchone()
if i is None:
break
else:
Expand Down
16 changes: 2 additions & 14 deletions asyncio_redis/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,5 @@
__all__ = (
'ConnectionLostError',
'Error',
'ErrorReply',
'NoAvailableConnectionsInPoolError',
'NoRunningScriptError',
'NotConnectedError',
'ScriptKilledError',
'TimeoutError',
'TransactionError',
)


# See following link for the proper way to create user defined exceptions:
# http://docs.python.org/3.3/tutorial/errors.html#user-defined-exceptions
# http://docs.python.org/3.8/tutorial/errors.html#user-defined-exceptions


class Error(Exception):
Expand Down Expand Up @@ -51,6 +38,7 @@ class NoAvailableConnectionsInPoolError(NotConnectedError):
When the connection pool has no available connections.
"""


class ScriptKilledError(Error):
""" Script was killed during an evalsha call. """

Expand Down
Loading