├── benchmark ├── __init__.py ├── bench_credis.py ├── bench_redis_py.py └── patch_socket.py ├── MANIFEST.in ├── .gitignore ├── credis ├── __init__.py ├── geventpool.pyx └── base.pyx ├── tea.yaml ├── pyproject.toml ├── README.rst ├── LICENSE ├── test ├── simple_test.py └── pool_test.py └── poetry.lock /benchmark/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | /build 3 | *.html 4 | *.so 5 | *.pyc 6 | *.c 7 | credis.egg-info/ 8 | dist/ 9 | -------------------------------------------------------------------------------- /credis/__init__.py: -------------------------------------------------------------------------------- 1 | from .base import Connection, RedisProtocolError, RedisReplyError, ConnectionError, AuthenticationError 2 | -------------------------------------------------------------------------------- /tea.yaml: -------------------------------------------------------------------------------- 1 | # https://tea.xyz/what-is-this-file 2 | --- 3 | version: 1.0.0 4 | codeOwners: 5 | - '0xb5343fdb2b26337AB1275681646FE20dbe55e802' 6 | quorum: 1 7 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "credis" 3 | version = "2.0.2" 4 | description = "high performance redis client implemented with cython" 5 | authors = ["yihuang "] 6 | license = "MIT" 7 | readme = 'README.rst' 8 | repository = "https://github.com/yihuang/credis" 9 | classifiers=[ 10 | "Development Status :: 6 - Mature", 11 | "Intended Audience :: Developers", 12 | "Programming Language :: Python", 13 | "Programming Language :: Cython", 14 | "Topic :: Database" 15 | ] 16 | 17 | [tool.poetry.build] 18 | script = "build.py" 19 | generate-setup-file = true 20 | 21 | [tool.poetry.dependencies] 22 | python = "^3.7" 23 | hiredis = "^2.2.3" 24 | 25 | [tool.poetry.dev-dependencies] 26 | pytest = "^7.4.0" 27 | 28 | [build-system] 29 | requires = ["poetry-core>=1.0.0", "cython", "setuptools>=50.3"] 30 | build-backend = "poetry.core.masonry.api" 31 | -------------------------------------------------------------------------------- /benchmark/bench_credis.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | import timeit 4 | import cProfile 5 | import patch_socket 6 | 7 | import credis 8 | 9 | rds = credis.Connection() 10 | rds.connect() 11 | 12 | 13 | def bench_simple(): 14 | """the operation for benchmark""" 15 | rds.execute("SET", "test", 100) 16 | 17 | 18 | def bench_pipeline(): 19 | rds.execute_pipeline( 20 | ("SET", 1, 1), ("INCR", 1), ("INCRBY", 1, 1), ("GET", 1), 21 | ) 22 | 23 | 24 | bench = bench_pipeline 25 | 26 | # record once 27 | patch_socket.run_with_recording(rds._sock, bench) 28 | 29 | timeit.main( 30 | [ 31 | "-s", 32 | "from __main__ import patch_socket, rds, bench", 33 | "-n", 34 | "10000", 35 | "patch_socket.run_with_replay(rds._sock, bench)", 36 | ] 37 | ) 38 | 39 | if sys.version_info[0] >= 3: 40 | xrange = range 41 | 42 | cProfile.run( 43 | "for i in xrange(10000):patch_socket.run_with_replay(rds._sock, bench)", sort="time" 44 | ) 45 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | minimal redis client written in cython, 5X faster than redis-py. 2 | 3 | Tutorial 4 | ======== 5 | 6 | execute command 7 | --------------- 8 | 9 | .. code-block:: python 10 | 11 | >>> from credis import Connection 12 | >>> conn = Connection(host='127.0.0.1', port=6379) 13 | >>> conn.execute('set', 'test', 1) 14 | 'OK' 15 | >>> conn.execute('get', 'test') 16 | '1' 17 | 18 | execute pipelined commands 19 | -------------------------- 20 | 21 | .. code-block:: python 22 | 23 | >>> commands = [('set', 'test%d'%i, i) for i in range(3)] 24 | >>> conn.execute_pipeline(*commands) 25 | ('OK', 'OK', 'OK') 26 | 27 | connection pool for gevent 28 | -------------------------- 29 | 30 | .. code-block:: python 31 | 32 | >>> from credis.geventpool import ResourcePool 33 | >>> pool = ResourcePool(32, Connection, host='127.0.0.1', port=6379) 34 | >>> with pool.ctx() as conn: 35 | ... conn.execute('get', 'test') 36 | '1' 37 | >>> pool.execute('get', 'test') 38 | '1' 39 | >>> commands = [('get', 'test%d'%i) for i in range(3)] 40 | >>> pool.execute_pipeline(*commands) 41 | ('1', '2', '3') 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is the MIT license: http://www.opensource.org/licenses/mit-license.php 2 | 3 | Copyright (c) 2015 yihuang. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | software and associated documentation files (the "Software"), to deal in the Software 7 | without restriction, including without limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons 9 | to whom the Software is furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or 12 | substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 16 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 18 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /test/simple_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from credis import Connection 3 | import unittest 4 | 5 | 6 | class TestSimple(unittest.TestCase): 7 | def test_simple(self): 8 | conn = Connection() 9 | 10 | assert conn.execute("SET", 1, 1) == b"OK" 11 | assert conn.execute("GET", 1) == b"1" 12 | 13 | # pipeline 14 | assert conn.execute_pipeline(("SET", 1, 2), ("GET", 1),) == (b"OK", b"2") 15 | 16 | assert conn.execute_pipeline( 17 | ("SET", 1, 1), ("INCR", 1), ("INCRBY", 1, 1), ("GET", 1), 18 | ) == (b"OK", 2, 3, b"3") 19 | 20 | # Connection with explicit db selection. 21 | conn_with_explicit_db = Connection(db=7) 22 | 23 | assert conn_with_explicit_db.execute("SET", 1, 1) == b"OK" 24 | assert conn_with_explicit_db.execute("GET", 1) == b"1" 25 | 26 | # pipeline 27 | assert conn_with_explicit_db.execute_pipeline(("SET", 1, 2), ("GET", 1),) == ( 28 | b"OK", 29 | b"2", 30 | ) 31 | 32 | assert conn_with_explicit_db.execute_pipeline( 33 | ("SET", 1, 1), ("INCR", 1), ("INCRBY", 1, 1), ("GET", 1), 34 | ) == (b"OK", 2, 3, b"3") 35 | -------------------------------------------------------------------------------- /benchmark/bench_redis_py.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | import patch_socket 4 | import timeit 5 | import cProfile 6 | import redis 7 | from redis.connection import Connection 8 | 9 | 10 | def run_with_recording(sock, func): 11 | sock.start_record() 12 | func() 13 | 14 | 15 | def run_with_replay(sock, func): 16 | sock.start_replay() 17 | func() 18 | 19 | 20 | class DummyConnectionPool(object): 21 | def __init__(self): 22 | self.conn = Connection() 23 | 24 | def get_connection(self, *args, **kwargs): 25 | return self.conn 26 | 27 | def release(self, conn): 28 | pass 29 | 30 | def disconnect(self): 31 | pass 32 | 33 | def reinstantiate(self): 34 | pass 35 | 36 | 37 | pool = DummyConnectionPool() 38 | pool.conn.connect() 39 | rds = redis.StrictRedis(connection_pool=pool) 40 | 41 | 42 | def bench_simple(): 43 | """the operation for benchmark""" 44 | rds.set("test", 100) 45 | 46 | 47 | def bench_pipeline(): 48 | pipe = rds.pipeline() 49 | pipe.set(1, 1) 50 | pipe.incr(1) 51 | pipe.incrby(1, 1) 52 | pipe.get(1) 53 | pipe.execute() 54 | 55 | 56 | bench = bench_pipeline 57 | 58 | # record once 59 | run_with_recording(pool.conn._sock, bench) 60 | 61 | timeit.main( 62 | [ 63 | "-s", 64 | "from __main__ import run_with_replay, pool, bench", 65 | "-n", 66 | "10000", 67 | "run_with_replay(pool.conn._sock, bench)", 68 | ] 69 | ) 70 | 71 | if sys.version_info[0] >= 3: 72 | xrange = range 73 | 74 | cProfile.run( 75 | "for i in xrange(10000):run_with_replay(pool.conn._sock, bench)", sort="time" 76 | ) 77 | -------------------------------------------------------------------------------- /test/pool_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import gevent 3 | import random 4 | from credis.geventpool import ResourcePool 5 | import unittest 6 | 7 | 8 | class CRedisException(Exception): 9 | pass 10 | 11 | 12 | class TestPool(unittest.TestCase): 13 | def setUp(self): 14 | self.counter = 0 15 | 16 | def test_pool(self): 17 | pool = ResourcePool(10, self.get_resource) 18 | threads = [gevent.spawn(self.random_worker(), pool) for _ in range(1000)] 19 | gevent.joinall(threads) 20 | assert pool.alloc_count <= pool.max_count 21 | assert pool.used_count == 0 22 | assert self.counter == 10 23 | 24 | def random_worker(self): 25 | return random.choice( 26 | [ 27 | self.worker_normal, 28 | self.worker_exception, 29 | self.worker_using_with, 30 | self.worker_using_with2, 31 | ] 32 | ) 33 | 34 | def worker_normal(self, pool): 35 | res = pool.acquire() 36 | gevent.sleep(0.05) 37 | pool.release(res) 38 | 39 | def worker_exception(self, pool): 40 | res = pool.acquire() 41 | try: 42 | gevent.sleep(0.05) 43 | raise CRedisException("bad worker") 44 | except CRedisException: 45 | pass 46 | finally: 47 | pool.release(res) 48 | 49 | def worker_using_with(self, pool): 50 | try: 51 | with pool.ctx() as res: 52 | gevent.sleep(0.05) 53 | raise CRedisException("bad worker1") 54 | except CRedisException: 55 | pass 56 | 57 | def worker_using_with2(self, pool): 58 | try: 59 | with pool.ctx() as res: 60 | gevent.sleep(0.05) 61 | raise CRedisException("bad worker2") 62 | except CRedisException: 63 | pass 64 | 65 | def get_resource(self): 66 | self.counter += 1 67 | return self.counter 68 | -------------------------------------------------------------------------------- /benchmark/patch_socket.py: -------------------------------------------------------------------------------- 1 | from socket import socket as _socket 2 | 3 | 4 | class socket(object): 5 | """patch socket for testing""" 6 | 7 | def __init__(self, *args, **kwargs): 8 | self._ss = _socket(*args, **kwargs) 9 | self.records = [] 10 | self.recording = False 11 | self.replaying = False 12 | 13 | def start_record(self): 14 | self.recording = True 15 | self.replaying = False 16 | self.records = [] 17 | 18 | def start_replay(self): 19 | self.recording = False 20 | self.replaying = True 21 | self.replay_records = self.records[:] 22 | 23 | def settimeout(self, n): 24 | return self._ss.settimeout(n) 25 | 26 | def connect(self, *args, **kwargs): 27 | return self._ss.connect(*args, **kwargs) 28 | 29 | def setsockopt(self, *args): 30 | if not self.replaying: 31 | return self._ss.setsockopt(*args) 32 | 33 | def shutdown(self, *args): 34 | if not self.replaying: 35 | return self._ss.shutdown(*args) 36 | 37 | def send(self, buf): 38 | if self.replaying: 39 | return len(buf) 40 | else: 41 | return self._ss.send(buf) 42 | 43 | def sendall(self, buf): 44 | if self.replaying: 45 | return len(buf) 46 | else: 47 | return self._ss.sendall(buf) 48 | 49 | def recv(self, size): 50 | if self.replaying: 51 | return self.replay_records.pop() 52 | buf = self._ss.recv(size) 53 | if self.recording: 54 | self.records.append(buf) 55 | return buf 56 | 57 | def recv_into(self, buf): 58 | if self.replaying: 59 | s = self.replay_records.pop() 60 | buf[: len(s)] = s 61 | return len(s) 62 | ret = self._ss.recv_into(buf) 63 | if self.recording: 64 | self.records.append(buf[:ret]) 65 | return ret 66 | 67 | def close(self): 68 | return self._ss.close() 69 | 70 | 71 | import socket as socketmodule # pylint: disable-E305 72 | 73 | socketmodule.socket = socket 74 | 75 | 76 | def run_with_recording(sock, func): 77 | sock.start_record() 78 | func() 79 | 80 | 81 | def run_with_replay(sock, func): 82 | sock.start_replay() 83 | func() 84 | -------------------------------------------------------------------------------- /credis/geventpool.pyx: -------------------------------------------------------------------------------- 1 | from gevent.event import AsyncResult 2 | from contextlib import contextmanager 3 | 4 | cdef class ResourcePool: 5 | """ 6 | simple pool, used for gevent, there is not true concurrency. 7 | """ 8 | cdef public list _pool 9 | cdef public object ctor 10 | cdef public tuple args 11 | cdef public dict kwargs 12 | cdef public str name 13 | 14 | cdef public int max_count 15 | cdef public int alloc_count 16 | cdef public int used_count 17 | 18 | cdef list _waiters 19 | 20 | def __cinit__(self, max_count, ctor, *args, **kwargs): 21 | self.ctor = ctor 22 | self.args = args 23 | self.name = kwargs.pop('name', None) 24 | self.kwargs = kwargs 25 | 26 | self.max_count = max_count 27 | self.alloc_count = 0 28 | self.used_count = 0 29 | 30 | self._pool = [] 31 | self._waiters = [] 32 | 33 | cpdef acquire(self): 34 | try: 35 | res = self._pool.pop() 36 | except IndexError: 37 | if self.alloc_count >= self.max_count: 38 | evt = AsyncResult() 39 | self._waiters.append(evt) 40 | return evt.get() 41 | 42 | # allocate new resource 43 | res = self.ctor(*self.args, **self.kwargs) 44 | self.alloc_count += 1 45 | self.used_count += 1 46 | else: 47 | self.used_count += 1 48 | assert self.alloc_count - self.used_count == len(self._pool), 'impossible[1]' 49 | assert self.used_count <= self.alloc_count, 'impossible[2]' 50 | assert res is not None, 'impossible[4]' 51 | return res 52 | 53 | cpdef release(self, item): 54 | assert item is not None, 'invalid item' 55 | if len(self._waiters) > 0: 56 | self._waiters.pop().set(item) 57 | else: 58 | self.used_count -= 1 59 | self._pool.append(item) 60 | assert self.used_count >= 0, 'impossible[3]' 61 | 62 | def ctx(self): 63 | return pool_context(self) 64 | 65 | def execute(self, *args): 66 | with self.ctx() as conn: 67 | return conn.execute(*args) 68 | 69 | def execute_pipeline(self, *args): 70 | with self.ctx() as conn: 71 | return conn.execute_pipeline(*args) 72 | 73 | @contextmanager 74 | def pool_context(pool): 75 | res = pool.acquire() 76 | try: 77 | yield res 78 | finally: 79 | pool.release(res) 80 | -------------------------------------------------------------------------------- /credis/base.pyx: -------------------------------------------------------------------------------- 1 | cdef bytes SYM_STAR = b'*' 2 | cdef bytes SYM_DOLLAR = b'$' 3 | cdef bytes SYM_CRLF = b'\r\n' 4 | cdef bytes SYM_LF = b'\n' 5 | 6 | from cpython.tuple cimport PyTuple_New, PyTuple_SetItem 7 | from cpython.ref cimport Py_INCREF 8 | from cpython.long cimport PyLong_AsLong 9 | from cpython.exc cimport PyErr_ExceptionMatches, PyErr_Occurred, PyErr_Clear 10 | 11 | # portable with python2.6 12 | cdef long PyLong_AsLongAndOverflow(object o, int *overflow) except? -1: 13 | cdef long ret 14 | try: 15 | ret = PyLong_AsLong(o) 16 | except OverflowError: 17 | overflow[0] = 1 18 | return -1 19 | 20 | overflow[0] = 0 21 | return ret 22 | 23 | 24 | DEF CHAR_BIT = 8 25 | 26 | cdef object int_to_decimal_string(long n): 27 | # sizeof(long)*CHAR_BIT/3+6 28 | cdef char buf[32] 29 | cdef char *p 30 | cdef char *bufend 31 | cdef unsigned long absn 32 | cdef char c = '0' 33 | p = bufend = buf + sizeof(buf) 34 | if n < 0: 35 | absn = 0UL - n 36 | else: 37 | absn = n 38 | while True: 39 | p -= 1 40 | p[0] = c + (absn % 10) 41 | absn /= 10 42 | if absn == 0: 43 | break 44 | if n < 0: 45 | p -= 1 46 | p[0] = '-' 47 | return p[:(bufend-p)] 48 | 49 | cdef bytes simple_bytes(s): 50 | if isinstance(s, unicode): 51 | return (s).encode('latin-1') 52 | elif isinstance(s, bytes): 53 | return s 54 | else: 55 | s = str(s) 56 | if isinstance(s, unicode): 57 | return (s).encode('latin-1') 58 | else: 59 | return s 60 | 61 | import socket 62 | import hiredis 63 | 64 | class RedisProtocolError(Exception): 65 | pass 66 | 67 | class RedisReplyError(Exception): 68 | pass 69 | 70 | class ConnectionError(Exception): 71 | pass 72 | 73 | class AuthenticationError(Exception): 74 | pass 75 | 76 | cdef class Connection(object): 77 | """Manages TCP communication to and from a Redis server""" 78 | 79 | cdef object host 80 | cdef object port 81 | cdef object db 82 | cdef object password 83 | cdef object socket_timeout 84 | cdef object encoding 85 | cdef object encoding_errors 86 | cdef bint decode_responses 87 | cdef object path 88 | 89 | cdef public object _sock 90 | cdef public object _reader 91 | 92 | def __init__(self, host='localhost', port=6379, db=None, password=None, 93 | socket_timeout=None, encoding='utf-8', path=None, 94 | encoding_errors='strict', decode_responses=False, 95 | ): 96 | self.host = host 97 | self.port = port 98 | self.db = db 99 | self.password = password 100 | self.socket_timeout = socket_timeout 101 | if encoding != 'utf-8': 102 | self.encoding = encoding 103 | else: 104 | self.encoding = None # default to use utf-8 encoding 105 | self.path = path 106 | if encoding_errors != 'strict': 107 | self.encoding_errors = encoding_errors 108 | else: 109 | self.encoding_errors = None # default to strict 110 | self.decode_responses = decode_responses 111 | 112 | self._sock = None 113 | self._reader = None 114 | 115 | def __del__(self): 116 | try: 117 | self.disconnect() 118 | except: 119 | pass 120 | 121 | def connect(self): 122 | """Connects to the Redis server if not already connected""" 123 | if self._sock: 124 | return 125 | 126 | try: 127 | sock = self._connect() 128 | except socket.error as e: 129 | raise ConnectionError(self._error_message(e)) 130 | 131 | self._sock = sock 132 | kwargs = { 133 | 'protocolError': RedisProtocolError, 134 | 'replyError': RedisReplyError, 135 | } 136 | if self.decode_responses: 137 | kwargs['encoding'] = self.encoding or 'utf-8' 138 | self._reader = hiredis.Reader(**kwargs) 139 | 140 | self._init_connection() 141 | 142 | cdef _connect(self): 143 | """Create a TCP/UNIX socket connection""" 144 | if self.path is not None: 145 | sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 146 | sock.settimeout(self.socket_timeout) 147 | sock.connect(self.path) 148 | else: 149 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 150 | sock.settimeout(self.socket_timeout) 151 | sock.connect((self.host, self.port)) 152 | return sock 153 | 154 | cdef _error_message(self, exception): 155 | # args for socket.error can either be (errno, "message") 156 | # or just "message" 157 | address = self.path is None and '(%s:%s)'%(self.host, self.port) \ 158 | or self.path 159 | return "Error connecting to %s. %s." % \ 160 | (address, exception.args) 161 | 162 | cdef _init_connection(self): 163 | """Initialize the connection, authenticate and select a database""" 164 | 165 | # if a password is specified, authenticate 166 | if self.password is not None: 167 | self.send_command(('AUTH', self.password)) 168 | reply = self.read_response() 169 | if isinstance(reply, Exception): 170 | self.disconnect() 171 | raise reply 172 | if reply.decode() != 'OK': 173 | self.disconnect() 174 | raise AuthenticationError('Invalid Password') 175 | 176 | # if a database is specified, switch to it 177 | if self.db is not None: 178 | self.send_command(('SELECT', self.db)) 179 | reply = self.read_response() 180 | if isinstance(reply, Exception): 181 | self.disconnect() 182 | raise reply 183 | if reply.decode() != 'OK': 184 | self.disconnect() 185 | raise ConnectionError('Invalid Database') 186 | 187 | cpdef disconnect(self): 188 | """Disconnects from the Redis server""" 189 | self._reader = None 190 | if self._sock is None: 191 | return 192 | try: 193 | self._sock.close() 194 | except socket.error: 195 | pass 196 | self._sock = None 197 | 198 | cpdef send_packed_command(self, commands): 199 | """Send an already packed command to the Redis server""" 200 | if not self._sock: 201 | self.connect() 202 | cdef object sendall = self._sock.sendall 203 | try: 204 | for command in commands: 205 | sendall(command) 206 | except socket.error as e: 207 | self.disconnect() 208 | raise ConnectionError("Error while writing to socket. %s." % 209 | (e.args,)) 210 | except: 211 | self.disconnect() 212 | raise 213 | 214 | cpdef send_command(self, args): 215 | """Pack and send a command to the Redis server""" 216 | self.send_packed_command(self._pack_command(args)) 217 | 218 | cdef _read_response(self): 219 | response = self._reader.gets() 220 | 221 | cdef object recv = self._sock.recv 222 | while response is False: 223 | try: 224 | buffer = recv(4096) 225 | except (socket.error, socket.timeout) as e: 226 | raise ConnectionError("Error while reading from socket: %s" % 227 | (e.args,)) 228 | if not buffer: 229 | raise ConnectionError("Socket closed on remote end") 230 | self._reader.feed(buffer) 231 | # proactively, but not conclusively, check if more data is in the 232 | # buffer. if the data received doesn't end with \n, there's more. 233 | if not buffer.endswith(SYM_LF): 234 | continue 235 | response = self._reader.gets() 236 | return response 237 | 238 | cpdef read_response(self): 239 | """Read the response from a previously sent command""" 240 | try: 241 | return self._read_response() 242 | except: 243 | self.disconnect() 244 | raise 245 | 246 | cpdef read_n_response(self, int n): 247 | cdef result = PyTuple_New(n) 248 | cdef int i 249 | cdef object o 250 | for i in range(n): 251 | o = self.read_response() 252 | Py_INCREF(o) 253 | PyTuple_SetItem(result, i, o) 254 | return result 255 | 256 | cpdef bytes _encode(self, value): 257 | """Return a bytestring representation of the value""" 258 | cdef int overflow = 0 259 | cdef long n = 0 260 | 261 | if isinstance(value, bytes): 262 | return value 263 | 264 | if isinstance(value, int): 265 | n = PyLong_AsLongAndOverflow(value, &overflow) 266 | if overflow == 0: 267 | return int_to_decimal_string(n) 268 | 269 | if isinstance(value, float): 270 | return simple_bytes(repr(value)) 271 | 272 | if not isinstance(value, unicode): 273 | value = str(value) 274 | 275 | if isinstance(value, unicode): 276 | if self.encoding is None and self.encoding_errors is None: 277 | return (value).encode('utf-8') 278 | else: 279 | return (value).encode(self.encoding is not None or 'utf-8', 280 | self.encoding_errors is not None or 'strict') 281 | return value 282 | 283 | cdef _pack_command_list(self, args): 284 | cdef bytes enc_value 285 | cdef list chunks = [] 286 | cdef list chunk = [SYM_STAR, int_to_decimal_string(len(args)), SYM_CRLF] 287 | cdef int chunk_size = 0 288 | for s in chunk: 289 | chunk_size += len(s) 290 | 291 | for value in args: 292 | enc_value = self._encode(value) 293 | 294 | if chunk_size > 6000 or len(enc_value) > 6000: 295 | chunks.append(b''.join(chunk)) 296 | chunk = [] 297 | chunk_size = 0 298 | 299 | chunk.append(SYM_DOLLAR) 300 | chunk_size += len(SYM_DOLLAR) 301 | 302 | s = int_to_decimal_string(len(enc_value)) 303 | chunk.append(s) 304 | chunk_size += len(s) 305 | 306 | chunk.append(SYM_CRLF) 307 | chunk_size += len(SYM_CRLF) 308 | 309 | chunk.append(enc_value) 310 | chunk_size += len(enc_value) 311 | 312 | chunk.append(SYM_CRLF) 313 | chunk_size += len(SYM_CRLF) 314 | 315 | if chunk: 316 | chunks.append(b''.join(chunk)) 317 | 318 | return chunks 319 | 320 | cdef _pack_command(self, args): 321 | """Pack a series of arguments into a value Redis command""" 322 | return self._pack_command_list(args) 323 | 324 | cdef _pack_pipeline_command(self, cmds): 325 | """Pack a series of arguments into a value Redis command""" 326 | cdef list chunks = [] 327 | cdef list chunk = [] 328 | cdef int chunk_size = 0 329 | cdef object args 330 | 331 | for args in cmds: 332 | for item in self._pack_command_list(args): 333 | chunk.append(item) 334 | chunk_size += len(item) 335 | 336 | if chunk_size > 6000: 337 | chunks.append(b''.join(chunk)) 338 | chunk = [] 339 | chunk_size = 0 340 | 341 | if chunk_size > 0: 342 | chunks.append(b''.join(chunk)) 343 | 344 | return chunks 345 | 346 | cpdef send_pipeline(self, cmds): 347 | self.send_packed_command(self._pack_pipeline_command(cmds)) 348 | 349 | def execute(self, *args): 350 | self.send_command(args) 351 | reply = self.read_response() 352 | if isinstance(reply, Exception): 353 | raise reply 354 | return reply 355 | 356 | def execute_pipeline(self, *cmds): 357 | self.send_pipeline(cmds) 358 | return self.read_n_response(len(cmds)) 359 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "colorama" 5 | version = "0.4.6" 6 | description = "Cross-platform colored terminal text." 7 | category = "dev" 8 | optional = false 9 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 10 | files = [ 11 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 12 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 13 | ] 14 | 15 | [[package]] 16 | name = "exceptiongroup" 17 | version = "1.1.3" 18 | description = "Backport of PEP 654 (exception groups)" 19 | category = "dev" 20 | optional = false 21 | python-versions = ">=3.7" 22 | files = [ 23 | {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, 24 | {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, 25 | ] 26 | 27 | [package.extras] 28 | test = ["pytest (>=6)"] 29 | 30 | [[package]] 31 | name = "hiredis" 32 | version = "2.2.3" 33 | description = "Python wrapper for hiredis" 34 | category = "main" 35 | optional = false 36 | python-versions = ">=3.7" 37 | files = [ 38 | {file = "hiredis-2.2.3-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:9a1a80a8fa767f2fdc3870316a54b84fe9fc09fa6ab6a2686783de6a228a4604"}, 39 | {file = "hiredis-2.2.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3f006c28c885deb99b670a5a66f367a175ab8955b0374029bad7111f5357dcd4"}, 40 | {file = "hiredis-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ffaf841546905d90ff189de7397aa56413b1ce5e54547f17a98f0ebf3a3b0a3b"}, 41 | {file = "hiredis-2.2.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cadb0ac7ba3babfd804e425946bec9717b320564a1390f163a54af9365a720a"}, 42 | {file = "hiredis-2.2.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:33bc4721632ef9708fa44e5df0066053fccc8e65410a2c48573192517a533b48"}, 43 | {file = "hiredis-2.2.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:227c5b4bcb60f89008c275d596e4a7b6625a6b3c827b8a66ae582eace7051f71"}, 44 | {file = "hiredis-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61995eb826009d99ed8590747bc0da683a5f4fbb4faa8788166bf3810845cd5c"}, 45 | {file = "hiredis-2.2.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f969edc851efe23010e0f53a64269f2629a9364135e9ec81c842e8b2277d0c1"}, 46 | {file = "hiredis-2.2.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d27e560eefb57914d742a837f1da98d3b29cb22eff013c8023b7cf52ae6e051d"}, 47 | {file = "hiredis-2.2.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3759f4789ae1913b7df278dfc9e8749205b7a106f888cd2903d19461e24a7697"}, 48 | {file = "hiredis-2.2.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c6cb613148422c523945cdb8b6bed617856f2602fd8750e33773ede2616e55d5"}, 49 | {file = "hiredis-2.2.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:1d274d5c511dfc03f83f997d3238eaa9b6ee3f982640979f509373cced891e98"}, 50 | {file = "hiredis-2.2.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3b7fe075e91b9d9cff40eba4fb6a8eff74964d3979a39be9a9ef58b1b4cb3604"}, 51 | {file = "hiredis-2.2.3-cp310-cp310-win32.whl", hash = "sha256:77924b0d32fd1f493d3df15d9609ddf9d94c31a364022a6bf6b525ce9da75bea"}, 52 | {file = "hiredis-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:dcb0569dd5bfe6004658cd0f229efa699a3169dcb4f77bd72e188adda302063d"}, 53 | {file = "hiredis-2.2.3-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:d115790f18daa99b5c11a506e48923b630ef712e9e4b40482af942c3d40638b8"}, 54 | {file = "hiredis-2.2.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c3b8be557e08b234774925622e196f0ee36fe4eab66cd19df934d3efd8f3743"}, 55 | {file = "hiredis-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3f5446068197b35a11ccc697720c41879c8657e2e761aaa8311783aac84cef20"}, 56 | {file = "hiredis-2.2.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa17a3b22b3726d54d7af20394f65d4a1735a842a4e0f557dc67a90f6965c4bc"}, 57 | {file = "hiredis-2.2.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7df645b6b7800e8b748c217fbd6a4ca8361bcb9a1ae6206cc02377833ec8a1aa"}, 58 | {file = "hiredis-2.2.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2fb9300959a0048138791f3d68359d61a788574ec9556bddf1fec07f2dbc5320"}, 59 | {file = "hiredis-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d7e459fe7313925f395148d36d9b7f4f8dac65be06e45d7af356b187cef65fc"}, 60 | {file = "hiredis-2.2.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8eceffca3941775b646cd585cd19b275d382de43cc3327d22f7c75d7b003d481"}, 61 | {file = "hiredis-2.2.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b17baf702c6e5b4bb66e1281a3efbb1d749c9d06cdb92b665ad81e03118f78fc"}, 62 | {file = "hiredis-2.2.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e43e2b5acaad09cf48c032f7e4926392bb3a3f01854416cf6d82ebff94d5467"}, 63 | {file = "hiredis-2.2.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:a7205497d7276a81fe92951a29616ef96562ed2f91a02066f72b6f93cb34b40e"}, 64 | {file = "hiredis-2.2.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:126623b03c31cb6ac3e0d138feb6fcc36dd43dd34fc7da7b7a0c38b5d75bc896"}, 65 | {file = "hiredis-2.2.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:071c5814b850574036506a8118034f97c3cbf2fe9947ff45a27b07a48da56240"}, 66 | {file = "hiredis-2.2.3-cp311-cp311-win32.whl", hash = "sha256:d1be9e30e675f5bc1cb534633324578f6f0944a1bcffe53242cf632f554f83b6"}, 67 | {file = "hiredis-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:b9a7c987e161e3c58f992c63b7e26fea7fe0777f3b975799d23d65bbb8cb5899"}, 68 | {file = "hiredis-2.2.3-cp37-cp37m-macosx_10_12_x86_64.whl", hash = "sha256:f2dcb8389fa3d453927b1299f46bdb38473c293c8269d5c777d33ea0e526b610"}, 69 | {file = "hiredis-2.2.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2df98f5e071320c7d84e8bd07c0542acdd0a7519307fc31774d60e4b842ec4f"}, 70 | {file = "hiredis-2.2.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61a72e4a523cdfc521762137559c08dfa360a3caef63620be58c699d1717dac1"}, 71 | {file = "hiredis-2.2.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9b9e5bde7030cae83aa900b5bd660decc65afd2db8c400f3c568c815a47ca2a"}, 72 | {file = "hiredis-2.2.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd2614f17e261f72efc2f19f5e5ff2ee19e2296570c0dcf33409e22be30710de"}, 73 | {file = "hiredis-2.2.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46525fbd84523cac75af5bf524bc74aaac848beaf31b142d2df8a787d9b4bbc4"}, 74 | {file = "hiredis-2.2.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d1a4ce40ba11da9382c14da31f4f9e88c18f7d294f523decd0fadfb81f51ad18"}, 75 | {file = "hiredis-2.2.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:5cda592405bbd29d53942e0389dc3fa77b49c362640210d7e94a10c14a677d4d"}, 76 | {file = "hiredis-2.2.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:5e6674a017629284ef373b50496d9fb1a89b85a20a7fa100ecd109484ec748e5"}, 77 | {file = "hiredis-2.2.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:e62ec131816c6120eff40dffe43424e140264a15fa4ab88c301bd6a595913af3"}, 78 | {file = "hiredis-2.2.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:17e938d9d3ee92e1adbff361706f1c36cc60eeb3e3eeca7a3a353eae344f4c91"}, 79 | {file = "hiredis-2.2.3-cp37-cp37m-win32.whl", hash = "sha256:95d2305fd2a7b179cacb48b10f618872fc565c175f9f62b854e8d1acac3e8a9e"}, 80 | {file = "hiredis-2.2.3-cp37-cp37m-win_amd64.whl", hash = "sha256:8f9dbe12f011a9b784f58faecc171d22465bb532c310bd588d769ba79a59ef5a"}, 81 | {file = "hiredis-2.2.3-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:5a4bcef114fc071d5f52c386c47f35aae0a5b43673197b9288a15b584da8fa3a"}, 82 | {file = "hiredis-2.2.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:232d0a70519865741ba56e1dfefd160a580ae78c30a1517bad47b3cf95a3bc7d"}, 83 | {file = "hiredis-2.2.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9076ce8429785c85f824650735791738de7143f61f43ae9ed83e163c0ca0fa44"}, 84 | {file = "hiredis-2.2.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec58fb7c2062f835595c12f0f02dcda76d0eb0831423cc191d1e18c9276648de"}, 85 | {file = "hiredis-2.2.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f2b34a6444b8f9c1e9f84bd2c639388e5d14f128afd14a869dfb3d9af893aa2"}, 86 | {file = "hiredis-2.2.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:818dfd310aa1020a13cd08ee48e116dd8c3bb2e23b8161f8ac4df587dd5093d7"}, 87 | {file = "hiredis-2.2.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96d9ea6c8d4cbdeee2e0d43379ce2881e4af0454b00570677c59f33f2531cd38"}, 88 | {file = "hiredis-2.2.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1eadbcd3de55ac42310ff82550d3302cb4efcd4e17d76646a17b6e7004bb42b"}, 89 | {file = "hiredis-2.2.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:477c34c4489666dc73cb5e89dafe2617c3e13da1298917f73d55aac4696bd793"}, 90 | {file = "hiredis-2.2.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:14824e457e4f5cda685c3345d125da13949bcf3bb1c88eb5d248c8d2c3dee08f"}, 91 | {file = "hiredis-2.2.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:9cd32326dfa6ce87edf754153b0105aca64486bebe93b9600ccff74fa0b224df"}, 92 | {file = "hiredis-2.2.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:51341e70b467004dcbec3a6ce8c478d2d6241e0f6b01e4c56764afd5022e1e9d"}, 93 | {file = "hiredis-2.2.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2443659c76b226267e2a04dbbb21bc2a3f91aa53bdc0c22964632753ae43a247"}, 94 | {file = "hiredis-2.2.3-cp38-cp38-win32.whl", hash = "sha256:4e3e3e31423f888d396b1fc1f936936e52af868ac1ec17dd15e3eeba9dd4de24"}, 95 | {file = "hiredis-2.2.3-cp38-cp38-win_amd64.whl", hash = "sha256:20f509e3a1a20d6e5f5794fc37ceb21f70f409101fcfe7a8bde783894d51b369"}, 96 | {file = "hiredis-2.2.3-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:d20891e3f33803b26d54c77fd5745878497091e33f4bbbdd454cf6e71aee8890"}, 97 | {file = "hiredis-2.2.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:50171f985e17970f87d5a29e16603d1e5b03bdbf5c2691a37e6c912942a6b657"}, 98 | {file = "hiredis-2.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9944a2cac25ffe049a7e89f306e11b900640837d1ef38d9be0eaa4a4e2b73a52"}, 99 | {file = "hiredis-2.2.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a5c8019ff94988d56eb49b15de76fe83f6b42536d76edeb6565dbf7fe14b973"}, 100 | {file = "hiredis-2.2.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a286ded34eb16501002e3713b3130c987366eee2ba0d58c33c72f27778e31676"}, 101 | {file = "hiredis-2.2.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b3e974ad15eb32b1f537730dea70b93a4c3db7b026de3ad2b59da49c6f7454d"}, 102 | {file = "hiredis-2.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08415ea74c1c29b9d6a4ca3dd0e810dc1af343c1d1d442e15ba133b11ab5be6a"}, 103 | {file = "hiredis-2.2.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e17d04ea58ab8cf3f2dc52e875db16077c6357846006780086fff3189fb199d"}, 104 | {file = "hiredis-2.2.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6ccdcb635dae85b006592f78e32d97f4bc7541cb27829d505f9c7fefcef48298"}, 105 | {file = "hiredis-2.2.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:69536b821dd1bc78058a6e7541743f8d82bf2d981b91280b14c4daa6cdc7faba"}, 106 | {file = "hiredis-2.2.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:3753df5f873d473f055e1f8837bfad0bd3b277c86f3c9bf058c58f14204cd901"}, 107 | {file = "hiredis-2.2.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6f88cafe46612b6fa68e6dea49e25bebf160598bba00101caa51cc8c1f18d597"}, 108 | {file = "hiredis-2.2.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:33ee3ea5cad3a8cb339352cd230b411eb437a2e75d7736c4899acab32056ccdb"}, 109 | {file = "hiredis-2.2.3-cp39-cp39-win32.whl", hash = "sha256:b4f3d06dc16671b88a13ae85d8ca92534c0b637d59e49f0558d040a691246422"}, 110 | {file = "hiredis-2.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:4f674e309cd055ee7a48304ceb8cf43265d859faf4d7d01d270ce45e976ae9d3"}, 111 | {file = "hiredis-2.2.3-pp37-pypy37_pp73-macosx_10_12_x86_64.whl", hash = "sha256:8f280ab4e043b089777b43b4227bdc2035f88da5072ab36588e0ccf77d45d058"}, 112 | {file = "hiredis-2.2.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15c2a551f3b8a26f7940d6ee10b837810201754b8d7e6f6b1391655370882c5a"}, 113 | {file = "hiredis-2.2.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60c4e3c258eafaab21b174b17270a0cc093718d61cdbde8c03f85ec4bf835343"}, 114 | {file = "hiredis-2.2.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc36a9dded458d4e37492fe3e619c6c83caae794d26ad925adbce61d592f8428"}, 115 | {file = "hiredis-2.2.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:4ed68a3b1ccb4313d2a42546fd7e7439ad4745918a48b6c9bcaa61e1e3e42634"}, 116 | {file = "hiredis-2.2.3-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3bf4b5bae472630c229518e4a814b1b68f10a3d9b00aeaec45f1a330f03a0251"}, 117 | {file = "hiredis-2.2.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33a94d264e6e12a79d9bb8af333b01dc286b9f39c99072ab5fef94ce1f018e17"}, 118 | {file = "hiredis-2.2.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fa6811a618653164f918b891a0fa07052bd71a799defa5c44d167cac5557b26"}, 119 | {file = "hiredis-2.2.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af33f370be90b48bbaf0dab32decbdcc522b1fa95d109020a963282086518a8e"}, 120 | {file = "hiredis-2.2.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b9953d87418ac228f508d93898ab572775e4d3b0eeb886a1a7734553bcdaf291"}, 121 | {file = "hiredis-2.2.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5e7bb4dd524f50b71c20ef5a12bd61da9b463f8894b18a06130942fe31509881"}, 122 | {file = "hiredis-2.2.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89a258424158eb8b3ed9f65548d68998da334ef155d09488c5637723eb1cd697"}, 123 | {file = "hiredis-2.2.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f4a65276f6ecdebe75f2a53f578fbc40e8d2860658420d5e0611c56bbf5054c"}, 124 | {file = "hiredis-2.2.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:334f2738700b20faa04a0d813366fb16ed17287430a6b50584161d5ad31ca6d7"}, 125 | {file = "hiredis-2.2.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d194decd9608f11c777946f596f31d5aacad13972a0a87829ae1e6f2d26c1885"}, 126 | {file = "hiredis-2.2.3.tar.gz", hash = "sha256:e75163773a309e56a9b58165cf5a50e0f84b755f6ff863b2c01a38918fe92daa"}, 127 | ] 128 | 129 | [[package]] 130 | name = "importlib-metadata" 131 | version = "6.7.0" 132 | description = "Read metadata from Python packages" 133 | category = "dev" 134 | optional = false 135 | python-versions = ">=3.7" 136 | files = [ 137 | {file = "importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5"}, 138 | {file = "importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4"}, 139 | ] 140 | 141 | [package.dependencies] 142 | typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} 143 | zipp = ">=0.5" 144 | 145 | [package.extras] 146 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] 147 | perf = ["ipython"] 148 | testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] 149 | 150 | [[package]] 151 | name = "iniconfig" 152 | version = "2.0.0" 153 | description = "brain-dead simple config-ini parsing" 154 | category = "dev" 155 | optional = false 156 | python-versions = ">=3.7" 157 | files = [ 158 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 159 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 160 | ] 161 | 162 | [[package]] 163 | name = "packaging" 164 | version = "23.1" 165 | description = "Core utilities for Python packages" 166 | category = "dev" 167 | optional = false 168 | python-versions = ">=3.7" 169 | files = [ 170 | {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, 171 | {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, 172 | ] 173 | 174 | [[package]] 175 | name = "pluggy" 176 | version = "1.2.0" 177 | description = "plugin and hook calling mechanisms for python" 178 | category = "dev" 179 | optional = false 180 | python-versions = ">=3.7" 181 | files = [ 182 | {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, 183 | {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, 184 | ] 185 | 186 | [package.dependencies] 187 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 188 | 189 | [package.extras] 190 | dev = ["pre-commit", "tox"] 191 | testing = ["pytest", "pytest-benchmark"] 192 | 193 | [[package]] 194 | name = "pytest" 195 | version = "7.4.0" 196 | description = "pytest: simple powerful testing with Python" 197 | category = "dev" 198 | optional = false 199 | python-versions = ">=3.7" 200 | files = [ 201 | {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, 202 | {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, 203 | ] 204 | 205 | [package.dependencies] 206 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 207 | exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} 208 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 209 | iniconfig = "*" 210 | packaging = "*" 211 | pluggy = ">=0.12,<2.0" 212 | tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} 213 | 214 | [package.extras] 215 | testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] 216 | 217 | [[package]] 218 | name = "tomli" 219 | version = "2.0.1" 220 | description = "A lil' TOML parser" 221 | category = "dev" 222 | optional = false 223 | python-versions = ">=3.7" 224 | files = [ 225 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 226 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 227 | ] 228 | 229 | [[package]] 230 | name = "typing-extensions" 231 | version = "4.7.1" 232 | description = "Backported and Experimental Type Hints for Python 3.7+" 233 | category = "dev" 234 | optional = false 235 | python-versions = ">=3.7" 236 | files = [ 237 | {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, 238 | {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, 239 | ] 240 | 241 | [[package]] 242 | name = "zipp" 243 | version = "3.15.0" 244 | description = "Backport of pathlib-compatible object wrapper for zip files" 245 | category = "dev" 246 | optional = false 247 | python-versions = ">=3.7" 248 | files = [ 249 | {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, 250 | {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, 251 | ] 252 | 253 | [package.extras] 254 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] 255 | testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] 256 | 257 | [metadata] 258 | lock-version = "2.0" 259 | python-versions = "^3.7" 260 | content-hash = "4e5c643a593c407449e3d27d877c941255b5444465c685a37ae61f35806d7382" 261 | --------------------------------------------------------------------------------