redis 6.1.1__tar.gz → 6.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. {redis-6.1.1 → redis-6.3.0}/PKG-INFO +8 -10
  2. {redis-6.1.1 → redis-6.3.0}/README.md +5 -6
  3. {redis-6.1.1 → redis-6.3.0}/dev_requirements.txt +1 -1
  4. {redis-6.1.1 → redis-6.3.0}/pyproject.toml +3 -4
  5. {redis-6.1.1 → redis-6.3.0}/redis/__init__.py +4 -3
  6. {redis-6.1.1 → redis-6.3.0}/redis/_parsers/__init__.py +8 -1
  7. {redis-6.1.1 → redis-6.3.0}/redis/_parsers/base.py +53 -1
  8. {redis-6.1.1 → redis-6.3.0}/redis/_parsers/helpers.py +2 -1
  9. {redis-6.1.1 → redis-6.3.0}/redis/_parsers/hiredis.py +72 -5
  10. {redis-6.1.1 → redis-6.3.0}/redis/_parsers/resp3.py +10 -35
  11. {redis-6.1.1 → redis-6.3.0}/redis/asyncio/client.py +1 -2
  12. {redis-6.1.1 → redis-6.3.0}/redis/asyncio/cluster.py +738 -60
  13. {redis-6.1.1 → redis-6.3.0}/redis/asyncio/connection.py +15 -11
  14. {redis-6.1.1 → redis-6.3.0}/redis/asyncio/retry.py +14 -35
  15. {redis-6.1.1 → redis-6.3.0}/redis/asyncio/sentinel.py +33 -18
  16. {redis-6.1.1 → redis-6.3.0}/redis/backoff.py +1 -1
  17. {redis-6.1.1 → redis-6.3.0}/redis/client.py +2 -3
  18. {redis-6.1.1 → redis-6.3.0}/redis/cluster.py +13 -7
  19. {redis-6.1.1 → redis-6.3.0}/redis/commands/core.py +74 -12
  20. {redis-6.1.1 → redis-6.3.0}/redis/commands/search/field.py +4 -4
  21. {redis-6.1.1 → redis-6.3.0}/redis/commands/sentinel.py +42 -12
  22. {redis-6.1.1 → redis-6.3.0}/redis/connection.py +18 -9
  23. {redis-6.1.1 → redis-6.3.0}/redis/exceptions.py +7 -1
  24. {redis-6.1.1 → redis-6.3.0}/redis/retry.py +36 -18
  25. {redis-6.1.1 → redis-6.3.0}/redis/sentinel.py +30 -15
  26. {redis-6.1.1 → redis-6.3.0}/redis/typing.py +0 -4
  27. {redis-6.1.1 → redis-6.3.0}/redis/utils.py +12 -5
  28. {redis-6.1.1 → redis-6.3.0}/tests/test_asyncio/compat.py +0 -6
  29. {redis-6.1.1 → redis-6.3.0}/tests/test_asyncio/conftest.py +1 -2
  30. {redis-6.1.1 → redis-6.3.0}/tests/test_asyncio/test_cluster.py +23 -5
  31. redis-6.3.0/tests/test_asyncio/test_cluster_transaction.py +399 -0
  32. {redis-6.1.1 → redis-6.3.0}/tests/test_asyncio/test_commands.py +247 -0
  33. {redis-6.1.1 → redis-6.3.0}/tests/test_asyncio/test_connection.py +1 -1
  34. {redis-6.1.1 → redis-6.3.0}/tests/test_asyncio/test_connection_pool.py +7 -5
  35. {redis-6.1.1 → redis-6.3.0}/tests/test_asyncio/test_pipeline.py +3 -1
  36. {redis-6.1.1 → redis-6.3.0}/tests/test_asyncio/test_pubsub.py +8 -6
  37. {redis-6.1.1 → redis-6.3.0}/tests/test_asyncio/test_search.py +183 -0
  38. {redis-6.1.1 → redis-6.3.0}/tests/test_asyncio/test_sentinel.py +85 -6
  39. {redis-6.1.1 → redis-6.3.0}/tests/test_asyncio/test_sentinel_managed_connection.py +2 -2
  40. {redis-6.1.1 → redis-6.3.0}/tests/test_cache.py +4 -5
  41. {redis-6.1.1 → redis-6.3.0}/tests/test_cluster.py +6 -2
  42. {redis-6.1.1 → redis-6.3.0}/tests/test_cluster_transaction.py +12 -6
  43. {redis-6.1.1 → redis-6.3.0}/tests/test_commands.py +246 -0
  44. {redis-6.1.1 → redis-6.3.0}/tests/test_connection_pool.py +11 -9
  45. redis-6.3.0/tests/test_max_connections_error.py +112 -0
  46. {redis-6.1.1 → redis-6.3.0}/tests/test_pubsub.py +5 -6
  47. {redis-6.1.1 → redis-6.3.0}/tests/test_retry.py +17 -5
  48. {redis-6.1.1 → redis-6.3.0}/tests/test_search.py +846 -3
  49. {redis-6.1.1 → redis-6.3.0}/tests/test_sentinel.py +82 -6
  50. redis-6.3.0/tests/test_sentinel_managed_connection.py +34 -0
  51. {redis-6.1.1 → redis-6.3.0}/.gitignore +0 -0
  52. {redis-6.1.1 → redis-6.3.0}/LICENSE +0 -0
  53. {redis-6.1.1 → redis-6.3.0}/redis/_parsers/commands.py +0 -0
  54. {redis-6.1.1 → redis-6.3.0}/redis/_parsers/encoders.py +0 -0
  55. {redis-6.1.1 → redis-6.3.0}/redis/_parsers/resp2.py +0 -0
  56. {redis-6.1.1 → redis-6.3.0}/redis/_parsers/socket.py +0 -0
  57. {redis-6.1.1 → redis-6.3.0}/redis/asyncio/__init__.py +0 -0
  58. {redis-6.1.1 → redis-6.3.0}/redis/asyncio/lock.py +0 -0
  59. {redis-6.1.1 → redis-6.3.0}/redis/asyncio/utils.py +0 -0
  60. {redis-6.1.1 → redis-6.3.0}/redis/auth/__init__.py +0 -0
  61. {redis-6.1.1 → redis-6.3.0}/redis/auth/err.py +0 -0
  62. {redis-6.1.1 → redis-6.3.0}/redis/auth/idp.py +0 -0
  63. {redis-6.1.1 → redis-6.3.0}/redis/auth/token.py +0 -0
  64. {redis-6.1.1 → redis-6.3.0}/redis/auth/token_manager.py +0 -0
  65. {redis-6.1.1 → redis-6.3.0}/redis/cache.py +0 -0
  66. {redis-6.1.1 → redis-6.3.0}/redis/commands/__init__.py +0 -0
  67. {redis-6.1.1 → redis-6.3.0}/redis/commands/bf/__init__.py +0 -0
  68. {redis-6.1.1 → redis-6.3.0}/redis/commands/bf/commands.py +0 -0
  69. {redis-6.1.1 → redis-6.3.0}/redis/commands/bf/info.py +0 -0
  70. {redis-6.1.1 → redis-6.3.0}/redis/commands/cluster.py +0 -0
  71. {redis-6.1.1 → redis-6.3.0}/redis/commands/helpers.py +0 -0
  72. {redis-6.1.1 → redis-6.3.0}/redis/commands/json/__init__.py +0 -0
  73. {redis-6.1.1 → redis-6.3.0}/redis/commands/json/_util.py +0 -0
  74. {redis-6.1.1 → redis-6.3.0}/redis/commands/json/commands.py +0 -0
  75. {redis-6.1.1 → redis-6.3.0}/redis/commands/json/decoders.py +0 -0
  76. {redis-6.1.1 → redis-6.3.0}/redis/commands/json/path.py +0 -0
  77. {redis-6.1.1 → redis-6.3.0}/redis/commands/redismodules.py +0 -0
  78. {redis-6.1.1 → redis-6.3.0}/redis/commands/search/__init__.py +0 -0
  79. {redis-6.1.1 → redis-6.3.0}/redis/commands/search/_util.py +0 -0
  80. {redis-6.1.1 → redis-6.3.0}/redis/commands/search/aggregation.py +0 -0
  81. {redis-6.1.1 → redis-6.3.0}/redis/commands/search/commands.py +0 -0
  82. {redis-6.1.1 → redis-6.3.0}/redis/commands/search/dialect.py +0 -0
  83. {redis-6.1.1 → redis-6.3.0}/redis/commands/search/document.py +0 -0
  84. {redis-6.1.1 → redis-6.3.0}/redis/commands/search/index_definition.py +0 -0
  85. {redis-6.1.1 → redis-6.3.0}/redis/commands/search/profile_information.py +0 -0
  86. {redis-6.1.1 → redis-6.3.0}/redis/commands/search/query.py +0 -0
  87. {redis-6.1.1 → redis-6.3.0}/redis/commands/search/querystring.py +0 -0
  88. {redis-6.1.1 → redis-6.3.0}/redis/commands/search/reducers.py +0 -0
  89. {redis-6.1.1 → redis-6.3.0}/redis/commands/search/result.py +0 -0
  90. {redis-6.1.1 → redis-6.3.0}/redis/commands/search/suggestion.py +0 -0
  91. {redis-6.1.1 → redis-6.3.0}/redis/commands/timeseries/__init__.py +0 -0
  92. {redis-6.1.1 → redis-6.3.0}/redis/commands/timeseries/commands.py +0 -0
  93. {redis-6.1.1 → redis-6.3.0}/redis/commands/timeseries/info.py +0 -0
  94. {redis-6.1.1 → redis-6.3.0}/redis/commands/timeseries/utils.py +0 -0
  95. {redis-6.1.1 → redis-6.3.0}/redis/commands/vectorset/__init__.py +0 -0
  96. {redis-6.1.1 → redis-6.3.0}/redis/commands/vectorset/commands.py +0 -0
  97. {redis-6.1.1 → redis-6.3.0}/redis/commands/vectorset/utils.py +0 -0
  98. {redis-6.1.1 → redis-6.3.0}/redis/crc.py +0 -0
  99. {redis-6.1.1 → redis-6.3.0}/redis/credentials.py +0 -0
  100. {redis-6.1.1 → redis-6.3.0}/redis/event.py +0 -0
  101. {redis-6.1.1 → redis-6.3.0}/redis/lock.py +0 -0
  102. {redis-6.1.1 → redis-6.3.0}/redis/ocsp.py +0 -0
  103. {redis-6.1.1 → redis-6.3.0}/redis/py.typed +0 -0
  104. {redis-6.1.1 → redis-6.3.0}/tests/__init__.py +0 -0
  105. {redis-6.1.1 → redis-6.3.0}/tests/conftest.py +0 -0
  106. {redis-6.1.1 → redis-6.3.0}/tests/entraid_utils.py +0 -0
  107. {redis-6.1.1 → redis-6.3.0}/tests/mocks.py +0 -0
  108. {redis-6.1.1 → redis-6.3.0}/tests/ssl_utils.py +0 -0
  109. {redis-6.1.1 → redis-6.3.0}/tests/test_asyncio/__init__.py +0 -0
  110. {redis-6.1.1 → redis-6.3.0}/tests/test_asyncio/mocks.py +0 -0
  111. {redis-6.1.1 → redis-6.3.0}/tests/test_asyncio/test_bloom.py +0 -0
  112. {redis-6.1.1 → redis-6.3.0}/tests/test_asyncio/test_connect.py +0 -0
  113. {redis-6.1.1 → redis-6.3.0}/tests/test_asyncio/test_credentials.py +0 -0
  114. {redis-6.1.1 → redis-6.3.0}/tests/test_asyncio/test_cwe_404.py +0 -0
  115. {redis-6.1.1 → redis-6.3.0}/tests/test_asyncio/test_encoding.py +0 -0
  116. {redis-6.1.1 → redis-6.3.0}/tests/test_asyncio/test_hash.py +0 -0
  117. {redis-6.1.1 → redis-6.3.0}/tests/test_asyncio/test_json.py +0 -0
  118. {redis-6.1.1 → redis-6.3.0}/tests/test_asyncio/test_lock.py +0 -0
  119. {redis-6.1.1 → redis-6.3.0}/tests/test_asyncio/test_monitor.py +0 -0
  120. {redis-6.1.1 → redis-6.3.0}/tests/test_asyncio/test_retry.py +0 -0
  121. {redis-6.1.1 → redis-6.3.0}/tests/test_asyncio/test_scripting.py +0 -0
  122. {redis-6.1.1 → redis-6.3.0}/tests/test_asyncio/test_ssl.py +0 -0
  123. {redis-6.1.1 → redis-6.3.0}/tests/test_asyncio/test_timeseries.py +0 -0
  124. {redis-6.1.1 → redis-6.3.0}/tests/test_asyncio/test_utils.py +0 -0
  125. {redis-6.1.1 → redis-6.3.0}/tests/test_asyncio/test_vsets.py +0 -0
  126. {redis-6.1.1 → redis-6.3.0}/tests/test_asyncio/testdata/jsontestdata.py +0 -0
  127. {redis-6.1.1 → redis-6.3.0}/tests/test_asyncio/testdata/titles.csv +0 -0
  128. {redis-6.1.1 → redis-6.3.0}/tests/test_asyncio/testdata/will_play_text.csv.bz2 +0 -0
  129. {redis-6.1.1 → redis-6.3.0}/tests/test_auth/__init__.py +0 -0
  130. {redis-6.1.1 → redis-6.3.0}/tests/test_auth/test_token.py +0 -0
  131. {redis-6.1.1 → redis-6.3.0}/tests/test_auth/test_token_manager.py +0 -0
  132. {redis-6.1.1 → redis-6.3.0}/tests/test_backoff.py +0 -0
  133. {redis-6.1.1 → redis-6.3.0}/tests/test_bloom.py +0 -0
  134. {redis-6.1.1 → redis-6.3.0}/tests/test_command_parser.py +0 -0
  135. {redis-6.1.1 → redis-6.3.0}/tests/test_connect.py +0 -0
  136. {redis-6.1.1 → redis-6.3.0}/tests/test_connection.py +0 -0
  137. {redis-6.1.1 → redis-6.3.0}/tests/test_credentials.py +0 -0
  138. {redis-6.1.1 → redis-6.3.0}/tests/test_encoding.py +0 -0
  139. {redis-6.1.1 → redis-6.3.0}/tests/test_function.py +0 -0
  140. {redis-6.1.1 → redis-6.3.0}/tests/test_hash.py +0 -0
  141. {redis-6.1.1 → redis-6.3.0}/tests/test_helpers.py +0 -0
  142. {redis-6.1.1 → redis-6.3.0}/tests/test_json.py +0 -0
  143. {redis-6.1.1 → redis-6.3.0}/tests/test_lock.py +0 -0
  144. {redis-6.1.1 → redis-6.3.0}/tests/test_monitor.py +0 -0
  145. {redis-6.1.1 → redis-6.3.0}/tests/test_multiprocessing.py +0 -0
  146. {redis-6.1.1 → redis-6.3.0}/tests/test_parsers/test_helpers.py +0 -0
  147. {redis-6.1.1 → redis-6.3.0}/tests/test_pipeline.py +0 -0
  148. {redis-6.1.1 → redis-6.3.0}/tests/test_scripting.py +0 -0
  149. {redis-6.1.1 → redis-6.3.0}/tests/test_ssl.py +0 -0
  150. {redis-6.1.1 → redis-6.3.0}/tests/test_timeseries.py +0 -0
  151. {redis-6.1.1 → redis-6.3.0}/tests/test_utils.py +0 -0
  152. {redis-6.1.1 → redis-6.3.0}/tests/test_vsets.py +0 -0
  153. {redis-6.1.1 → redis-6.3.0}/tests/testdata/jsontestdata.py +0 -0
  154. {redis-6.1.1 → redis-6.3.0}/tests/testdata/titles.csv +0 -0
  155. {redis-6.1.1 → redis-6.3.0}/tests/testdata/will_play_text.csv.bz2 +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: redis
3
- Version: 6.1.1
3
+ Version: 6.3.0
4
4
  Summary: Python client for Redis database and key-value store
5
5
  Project-URL: Changes, https://github.com/redis/redis-py/releases
6
6
  Project-URL: Code, https://github.com/redis/redis-py
@@ -19,7 +19,6 @@ Classifier: Operating System :: OS Independent
19
19
  Classifier: Programming Language :: Python
20
20
  Classifier: Programming Language :: Python :: 3
21
21
  Classifier: Programming Language :: Python :: 3 :: Only
22
- Classifier: Programming Language :: Python :: 3.8
23
22
  Classifier: Programming Language :: Python :: 3.9
24
23
  Classifier: Programming Language :: Python :: 3.10
25
24
  Classifier: Programming Language :: Python :: 3.11
@@ -27,10 +26,10 @@ Classifier: Programming Language :: Python :: 3.12
27
26
  Classifier: Programming Language :: Python :: 3.13
28
27
  Classifier: Programming Language :: Python :: Implementation :: CPython
29
28
  Classifier: Programming Language :: Python :: Implementation :: PyPy
30
- Requires-Python: >=3.8
29
+ Requires-Python: >=3.9
31
30
  Requires-Dist: async-timeout>=4.0.3; python_full_version < '3.11.3'
32
31
  Provides-Extra: hiredis
33
- Requires-Dist: hiredis>=3.0.0; extra == 'hiredis'
32
+ Requires-Dist: hiredis>=3.2.0; extra == 'hiredis'
34
33
  Provides-Extra: jwt
35
34
  Requires-Dist: pyjwt>=2.9.0; extra == 'jwt'
36
35
  Provides-Extra: ocsp
@@ -44,7 +43,7 @@ Description-Content-Type: text/markdown
44
43
  The Python interface to the Redis key-value store.
45
44
 
46
45
  [![CI](https://github.com/redis/redis-py/workflows/CI/badge.svg?branch=master)](https://github.com/redis/redis-py/actions?query=workflow%3ACI+branch%3Amaster)
47
- [![docs](https://readthedocs.org/projects/redis/badge/?version=stable&style=flat)](https://redis-py.readthedocs.io/en/stable/)
46
+ [![docs](https://readthedocs.org/projects/redis/badge/?version=stable&style=flat)](https://redis.readthedocs.io/en/stable/)
48
47
  [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
49
48
  [![pypi](https://badge.fury.io/py/redis.svg)](https://pypi.org/project/redis/)
50
49
  [![pre-release](https://img.shields.io/github/v/release/redis/redis-py?include_prereleases&label=latest-prerelease)](https://github.com/redis/redis-py/releases)
@@ -55,7 +54,7 @@ The Python interface to the Redis key-value store.
55
54
  ---------------------------------------------
56
55
 
57
56
  **Note:** redis-py 5.0 will be the last version of redis-py to support Python 3.7, as it has reached [end of life](https://devguide.python.org/versions/). redis-py 5.1 will support Python 3.8+.
58
-
57
+ **Note:** redis-py 6.1.0 will be the last version of redis-py to support Python 3.8, as it has reached [end of life](https://devguide.python.org/versions/). redis-py 6.2.0 will support Python 3.9+.
59
58
  ---------------------------------------------
60
59
 
61
60
  ## How do I Redis?
@@ -75,15 +74,14 @@ The Python interface to the Redis key-value store.
75
74
  Start a redis via docker (for Redis versions >= 8.0):
76
75
 
77
76
  ``` bash
78
- $ docker run -p 6379:6379 -it redis:latest
77
+ docker run -p 6379:6379 -it redis:latest
79
78
  ```
80
79
 
81
80
  Start a redis via docker (for Redis versions < 8.0):
82
81
 
83
82
  ``` bash
84
- $ docker run -p 6379:6379 -it redis/redis-stack:latest
83
+ docker run -p 6379:6379 -it redis/redis-stack:latest
85
84
  ```
86
-
87
85
  To install redis-py, simply:
88
86
 
89
87
  ``` bash
@@ -251,4 +249,4 @@ Special thanks to:
251
249
  system.
252
250
  - Paul Hubbard for initial packaging support.
253
251
 
254
- [![Redis](./docs/_static/logo-redis.svg)](https://redis.io)
252
+ [![Redis](./docs/_static/logo-redis.svg)](https://redis.io)
@@ -3,7 +3,7 @@
3
3
  The Python interface to the Redis key-value store.
4
4
 
5
5
  [![CI](https://github.com/redis/redis-py/workflows/CI/badge.svg?branch=master)](https://github.com/redis/redis-py/actions?query=workflow%3ACI+branch%3Amaster)
6
- [![docs](https://readthedocs.org/projects/redis/badge/?version=stable&style=flat)](https://redis-py.readthedocs.io/en/stable/)
6
+ [![docs](https://readthedocs.org/projects/redis/badge/?version=stable&style=flat)](https://redis.readthedocs.io/en/stable/)
7
7
  [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
8
8
  [![pypi](https://badge.fury.io/py/redis.svg)](https://pypi.org/project/redis/)
9
9
  [![pre-release](https://img.shields.io/github/v/release/redis/redis-py?include_prereleases&label=latest-prerelease)](https://github.com/redis/redis-py/releases)
@@ -14,7 +14,7 @@ The Python interface to the Redis key-value store.
14
14
  ---------------------------------------------
15
15
 
16
16
  **Note:** redis-py 5.0 will be the last version of redis-py to support Python 3.7, as it has reached [end of life](https://devguide.python.org/versions/). redis-py 5.1 will support Python 3.8+.
17
-
17
+ **Note:** redis-py 6.1.0 will be the last version of redis-py to support Python 3.8, as it has reached [end of life](https://devguide.python.org/versions/). redis-py 6.2.0 will support Python 3.9+.
18
18
  ---------------------------------------------
19
19
 
20
20
  ## How do I Redis?
@@ -34,15 +34,14 @@ The Python interface to the Redis key-value store.
34
34
  Start a redis via docker (for Redis versions >= 8.0):
35
35
 
36
36
  ``` bash
37
- $ docker run -p 6379:6379 -it redis:latest
37
+ docker run -p 6379:6379 -it redis:latest
38
38
  ```
39
39
 
40
40
  Start a redis via docker (for Redis versions < 8.0):
41
41
 
42
42
  ``` bash
43
- $ docker run -p 6379:6379 -it redis/redis-stack:latest
43
+ docker run -p 6379:6379 -it redis/redis-stack:latest
44
44
  ```
45
-
46
45
  To install redis-py, simply:
47
46
 
48
47
  ``` bash
@@ -210,4 +209,4 @@ Special thanks to:
210
209
  system.
211
210
  - Paul Hubbard for initial packaging support.
212
211
 
213
- [![Redis](./docs/_static/logo-redis.svg)](https://redis.io)
212
+ [![Redis](./docs/_static/logo-redis.svg)](https://redis.io)
@@ -13,4 +13,4 @@ ujson>=4.2.0
13
13
  uvloop
14
14
  vulture>=2.3.0
15
15
  numpy>=1.24.0
16
- redis-entraid==0.4.0b2
16
+ redis-entraid==1.0.0
@@ -8,7 +8,7 @@ dynamic = ["version"]
8
8
  description = "Python client for Redis database and key-value store"
9
9
  readme = "README.md"
10
10
  license = "MIT"
11
- requires-python = ">=3.8"
11
+ requires-python = ">=3.9"
12
12
  authors = [{ name = "Redis Inc.", email = "oss@redis.com" }]
13
13
  keywords = ["Redis", "database", "key-value-store"]
14
14
  classifiers = [
@@ -20,7 +20,6 @@ classifiers = [
20
20
  "Programming Language :: Python",
21
21
  "Programming Language :: Python :: 3",
22
22
  "Programming Language :: Python :: 3 :: Only",
23
- "Programming Language :: Python :: 3.8",
24
23
  "Programming Language :: Python :: 3.9",
25
24
  "Programming Language :: Python :: 3.10",
26
25
  "Programming Language :: Python :: 3.11",
@@ -33,7 +32,7 @@ dependencies = ['async-timeout>=4.0.3; python_full_version<"3.11.3"']
33
32
 
34
33
  [project.optional-dependencies]
35
34
  hiredis = [
36
- "hiredis>=3.0.0",
35
+ "hiredis>=3.2.0",
37
36
  ]
38
37
  ocsp = [
39
38
  "cryptography>=36.0.1",
@@ -83,7 +82,7 @@ filterwarnings = [
83
82
  ]
84
83
 
85
84
  [tool.ruff]
86
- target-version = "py38"
85
+ target-version = "py39"
87
86
  line-length = 88
88
87
  exclude = [
89
88
  "*.egg-info",
@@ -20,6 +20,7 @@ from redis.exceptions import (
20
20
  DataError,
21
21
  InvalidPipelineStack,
22
22
  InvalidResponse,
23
+ MaxConnectionsError,
23
24
  OutOfMemoryError,
24
25
  PubSubError,
25
26
  ReadOnlyError,
@@ -45,9 +46,8 @@ def int_or_str(value):
45
46
  return value
46
47
 
47
48
 
48
- # This is the version of redis-py that is being used
49
- # for building and installing the lib.
50
- __version__ = "6.1.1"
49
+ # This version is used when building the package for publishing
50
+ __version__ = "6.3.0"
51
51
  VERSION = tuple(map(int_or_str, __version__.split(".")))
52
52
 
53
53
 
@@ -67,6 +67,7 @@ __all__ = [
67
67
  "default_backoff",
68
68
  "InvalidPipelineStack",
69
69
  "InvalidResponse",
70
+ "MaxConnectionsError",
70
71
  "OutOfMemoryError",
71
72
  "PubSubError",
72
73
  "ReadOnlyError",
@@ -1,4 +1,9 @@
1
- from .base import BaseParser, _AsyncRESPBase
1
+ from .base import (
2
+ AsyncPushNotificationsParser,
3
+ BaseParser,
4
+ PushNotificationsParser,
5
+ _AsyncRESPBase,
6
+ )
2
7
  from .commands import AsyncCommandsParser, CommandsParser
3
8
  from .encoders import Encoder
4
9
  from .hiredis import _AsyncHiredisParser, _HiredisParser
@@ -11,10 +16,12 @@ __all__ = [
11
16
  "_AsyncRESPBase",
12
17
  "_AsyncRESP2Parser",
13
18
  "_AsyncRESP3Parser",
19
+ "AsyncPushNotificationsParser",
14
20
  "CommandsParser",
15
21
  "Encoder",
16
22
  "BaseParser",
17
23
  "_HiredisParser",
18
24
  "_RESP2Parser",
19
25
  "_RESP3Parser",
26
+ "PushNotificationsParser",
20
27
  ]
@@ -1,7 +1,7 @@
1
1
  import sys
2
2
  from abc import ABC
3
3
  from asyncio import IncompleteReadError, StreamReader, TimeoutError
4
- from typing import List, Optional, Union
4
+ from typing import Callable, List, Optional, Protocol, Union
5
5
 
6
6
  if sys.version_info.major >= 3 and sys.version_info.minor >= 11:
7
7
  from asyncio import timeout as async_timeout
@@ -158,6 +158,58 @@ class AsyncBaseParser(BaseParser):
158
158
  raise NotImplementedError()
159
159
 
160
160
 
161
+ _INVALIDATION_MESSAGE = [b"invalidate", "invalidate"]
162
+
163
+
164
+ class PushNotificationsParser(Protocol):
165
+ """Protocol defining RESP3-specific parsing functionality"""
166
+
167
+ pubsub_push_handler_func: Callable
168
+ invalidation_push_handler_func: Optional[Callable] = None
169
+
170
+ def handle_pubsub_push_response(self, response):
171
+ """Handle pubsub push responses"""
172
+ raise NotImplementedError()
173
+
174
+ def handle_push_response(self, response, **kwargs):
175
+ if response[0] not in _INVALIDATION_MESSAGE:
176
+ return self.pubsub_push_handler_func(response)
177
+ if self.invalidation_push_handler_func:
178
+ return self.invalidation_push_handler_func(response)
179
+
180
+ def set_pubsub_push_handler(self, pubsub_push_handler_func):
181
+ self.pubsub_push_handler_func = pubsub_push_handler_func
182
+
183
+ def set_invalidation_push_handler(self, invalidation_push_handler_func):
184
+ self.invalidation_push_handler_func = invalidation_push_handler_func
185
+
186
+
187
+ class AsyncPushNotificationsParser(Protocol):
188
+ """Protocol defining async RESP3-specific parsing functionality"""
189
+
190
+ pubsub_push_handler_func: Callable
191
+ invalidation_push_handler_func: Optional[Callable] = None
192
+
193
+ async def handle_pubsub_push_response(self, response):
194
+ """Handle pubsub push responses asynchronously"""
195
+ raise NotImplementedError()
196
+
197
+ async def handle_push_response(self, response, **kwargs):
198
+ """Handle push responses asynchronously"""
199
+ if response[0] not in _INVALIDATION_MESSAGE:
200
+ return await self.pubsub_push_handler_func(response)
201
+ if self.invalidation_push_handler_func:
202
+ return await self.invalidation_push_handler_func(response)
203
+
204
+ def set_pubsub_push_handler(self, pubsub_push_handler_func):
205
+ """Set the pubsub push handler function"""
206
+ self.pubsub_push_handler_func = pubsub_push_handler_func
207
+
208
+ def set_invalidation_push_handler(self, invalidation_push_handler_func):
209
+ """Set the invalidation push handler function"""
210
+ self.invalidation_push_handler_func = invalidation_push_handler_func
211
+
212
+
161
213
  class _AsyncRESPBase(AsyncBaseParser):
162
214
  """Base class for async resp parsing"""
163
215
 
@@ -676,7 +676,8 @@ def parse_client_info(value):
676
676
  "omem",
677
677
  "tot-mem",
678
678
  }:
679
- client_info[int_key] = int(client_info[int_key])
679
+ if int_key in client_info:
680
+ client_info[int_key] = int(client_info[int_key])
680
681
  return client_info
681
682
 
682
683
 
@@ -1,6 +1,7 @@
1
1
  import asyncio
2
2
  import socket
3
3
  import sys
4
+ from logging import getLogger
4
5
  from typing import Callable, List, Optional, TypedDict, Union
5
6
 
6
7
  if sys.version_info.major >= 3 and sys.version_info.minor >= 11:
@@ -11,7 +12,12 @@ else:
11
12
  from ..exceptions import ConnectionError, InvalidResponse, RedisError
12
13
  from ..typing import EncodableT
13
14
  from ..utils import HIREDIS_AVAILABLE
14
- from .base import AsyncBaseParser, BaseParser
15
+ from .base import (
16
+ AsyncBaseParser,
17
+ AsyncPushNotificationsParser,
18
+ BaseParser,
19
+ PushNotificationsParser,
20
+ )
15
21
  from .socket import (
16
22
  NONBLOCKING_EXCEPTION_ERROR_NUMBERS,
17
23
  NONBLOCKING_EXCEPTIONS,
@@ -32,7 +38,7 @@ class _HiredisReaderArgs(TypedDict, total=False):
32
38
  errors: Optional[str]
33
39
 
34
40
 
35
- class _HiredisParser(BaseParser):
41
+ class _HiredisParser(BaseParser, PushNotificationsParser):
36
42
  "Parser class for connections using Hiredis"
37
43
 
38
44
  def __init__(self, socket_read_size):
@@ -40,6 +46,9 @@ class _HiredisParser(BaseParser):
40
46
  raise RedisError("Hiredis is not installed")
41
47
  self.socket_read_size = socket_read_size
42
48
  self._buffer = bytearray(socket_read_size)
49
+ self.pubsub_push_handler_func = self.handle_pubsub_push_response
50
+ self.invalidation_push_handler_func = None
51
+ self._hiredis_PushNotificationType = None
43
52
 
44
53
  def __del__(self):
45
54
  try:
@@ -47,6 +56,11 @@ class _HiredisParser(BaseParser):
47
56
  except Exception:
48
57
  pass
49
58
 
59
+ def handle_pubsub_push_response(self, response):
60
+ logger = getLogger("push_response")
61
+ logger.debug("Push response: " + str(response))
62
+ return response
63
+
50
64
  def on_connect(self, connection, **kwargs):
51
65
  import hiredis
52
66
 
@@ -64,6 +78,12 @@ class _HiredisParser(BaseParser):
64
78
  self._reader = hiredis.Reader(**kwargs)
65
79
  self._next_response = NOT_ENOUGH_DATA
66
80
 
81
+ try:
82
+ self._hiredis_PushNotificationType = hiredis.PushNotification
83
+ except AttributeError:
84
+ # hiredis < 3.2
85
+ self._hiredis_PushNotificationType = None
86
+
67
87
  def on_disconnect(self):
68
88
  self._sock = None
69
89
  self._reader = None
@@ -109,7 +129,7 @@ class _HiredisParser(BaseParser):
109
129
  if custom_timeout:
110
130
  sock.settimeout(self._socket_timeout)
111
131
 
112
- def read_response(self, disable_decoding=False):
132
+ def read_response(self, disable_decoding=False, push_request=False):
113
133
  if not self._reader:
114
134
  raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR)
115
135
 
@@ -117,6 +137,16 @@ class _HiredisParser(BaseParser):
117
137
  if self._next_response is not NOT_ENOUGH_DATA:
118
138
  response = self._next_response
119
139
  self._next_response = NOT_ENOUGH_DATA
140
+ if self._hiredis_PushNotificationType is not None and isinstance(
141
+ response, self._hiredis_PushNotificationType
142
+ ):
143
+ response = self.handle_push_response(response)
144
+ if not push_request:
145
+ return self.read_response(
146
+ disable_decoding=disable_decoding, push_request=push_request
147
+ )
148
+ else:
149
+ return response
120
150
  return response
121
151
 
122
152
  if disable_decoding:
@@ -135,6 +165,16 @@ class _HiredisParser(BaseParser):
135
165
  # happened
136
166
  if isinstance(response, ConnectionError):
137
167
  raise response
168
+ elif self._hiredis_PushNotificationType is not None and isinstance(
169
+ response, self._hiredis_PushNotificationType
170
+ ):
171
+ response = self.handle_push_response(response)
172
+ if not push_request:
173
+ return self.read_response(
174
+ disable_decoding=disable_decoding, push_request=push_request
175
+ )
176
+ else:
177
+ return response
138
178
  elif (
139
179
  isinstance(response, list)
140
180
  and response
@@ -144,7 +184,7 @@ class _HiredisParser(BaseParser):
144
184
  return response
145
185
 
146
186
 
147
- class _AsyncHiredisParser(AsyncBaseParser):
187
+ class _AsyncHiredisParser(AsyncBaseParser, AsyncPushNotificationsParser):
148
188
  """Async implementation of parser class for connections using Hiredis"""
149
189
 
150
190
  __slots__ = ("_reader",)
@@ -154,6 +194,14 @@ class _AsyncHiredisParser(AsyncBaseParser):
154
194
  raise RedisError("Hiredis is not available.")
155
195
  super().__init__(socket_read_size=socket_read_size)
156
196
  self._reader = None
197
+ self.pubsub_push_handler_func = self.handle_pubsub_push_response
198
+ self.invalidation_push_handler_func = None
199
+ self._hiredis_PushNotificationType = None
200
+
201
+ async def handle_pubsub_push_response(self, response):
202
+ logger = getLogger("push_response")
203
+ logger.debug("Push response: " + str(response))
204
+ return response
157
205
 
158
206
  def on_connect(self, connection):
159
207
  import hiredis
@@ -171,6 +219,14 @@ class _AsyncHiredisParser(AsyncBaseParser):
171
219
  self._reader = hiredis.Reader(**kwargs)
172
220
  self._connected = True
173
221
 
222
+ try:
223
+ self._hiredis_PushNotificationType = getattr(
224
+ hiredis, "PushNotification", None
225
+ )
226
+ except AttributeError:
227
+ # hiredis < 3.2
228
+ self._hiredis_PushNotificationType = None
229
+
174
230
  def on_disconnect(self):
175
231
  self._connected = False
176
232
 
@@ -195,7 +251,7 @@ class _AsyncHiredisParser(AsyncBaseParser):
195
251
  return True
196
252
 
197
253
  async def read_response(
198
- self, disable_decoding: bool = False
254
+ self, disable_decoding: bool = False, push_request: bool = False
199
255
  ) -> Union[EncodableT, List[EncodableT]]:
200
256
  # If `on_disconnect()` has been called, prohibit any more reads
201
257
  # even if they could happen because data might be present.
@@ -207,6 +263,7 @@ class _AsyncHiredisParser(AsyncBaseParser):
207
263
  response = self._reader.gets(False)
208
264
  else:
209
265
  response = self._reader.gets()
266
+
210
267
  while response is NOT_ENOUGH_DATA:
211
268
  await self.read_from_socket()
212
269
  if disable_decoding:
@@ -219,6 +276,16 @@ class _AsyncHiredisParser(AsyncBaseParser):
219
276
  # happened
220
277
  if isinstance(response, ConnectionError):
221
278
  raise response
279
+ elif self._hiredis_PushNotificationType is not None and isinstance(
280
+ response, self._hiredis_PushNotificationType
281
+ ):
282
+ response = await self.handle_push_response(response)
283
+ if not push_request:
284
+ return await self.read_response(
285
+ disable_decoding=disable_decoding, push_request=push_request
286
+ )
287
+ else:
288
+ return response
222
289
  elif (
223
290
  isinstance(response, list)
224
291
  and response
@@ -3,13 +3,16 @@ from typing import Any, Union
3
3
 
4
4
  from ..exceptions import ConnectionError, InvalidResponse, ResponseError
5
5
  from ..typing import EncodableT
6
- from .base import _AsyncRESPBase, _RESPBase
6
+ from .base import (
7
+ AsyncPushNotificationsParser,
8
+ PushNotificationsParser,
9
+ _AsyncRESPBase,
10
+ _RESPBase,
11
+ )
7
12
  from .socket import SERVER_CLOSED_CONNECTION_ERROR
8
13
 
9
- _INVALIDATION_MESSAGE = [b"invalidate", "invalidate"]
10
14
 
11
-
12
- class _RESP3Parser(_RESPBase):
15
+ class _RESP3Parser(_RESPBase, PushNotificationsParser):
13
16
  """RESP3 protocol implementation"""
14
17
 
15
18
  def __init__(self, socket_read_size):
@@ -113,9 +116,7 @@ class _RESP3Parser(_RESPBase):
113
116
  )
114
117
  for _ in range(int(response))
115
118
  ]
116
- response = self.handle_push_response(
117
- response, disable_decoding, push_request
118
- )
119
+ response = self.handle_push_response(response)
119
120
  if not push_request:
120
121
  return self._read_response(
121
122
  disable_decoding=disable_decoding, push_request=push_request
@@ -129,20 +130,8 @@ class _RESP3Parser(_RESPBase):
129
130
  response = self.encoder.decode(response)
130
131
  return response
131
132
 
132
- def handle_push_response(self, response, disable_decoding, push_request):
133
- if response[0] not in _INVALIDATION_MESSAGE:
134
- return self.pubsub_push_handler_func(response)
135
- if self.invalidation_push_handler_func:
136
- return self.invalidation_push_handler_func(response)
137
-
138
- def set_pubsub_push_handler(self, pubsub_push_handler_func):
139
- self.pubsub_push_handler_func = pubsub_push_handler_func
140
-
141
- def set_invalidation_push_handler(self, invalidation_push_handler_func):
142
- self.invalidation_push_handler_func = invalidation_push_handler_func
143
-
144
133
 
145
- class _AsyncRESP3Parser(_AsyncRESPBase):
134
+ class _AsyncRESP3Parser(_AsyncRESPBase, AsyncPushNotificationsParser):
146
135
  def __init__(self, socket_read_size):
147
136
  super().__init__(socket_read_size)
148
137
  self.pubsub_push_handler_func = self.handle_pubsub_push_response
@@ -253,9 +242,7 @@ class _AsyncRESP3Parser(_AsyncRESPBase):
253
242
  )
254
243
  for _ in range(int(response))
255
244
  ]
256
- response = await self.handle_push_response(
257
- response, disable_decoding, push_request
258
- )
245
+ response = await self.handle_push_response(response)
259
246
  if not push_request:
260
247
  return await self._read_response(
261
248
  disable_decoding=disable_decoding, push_request=push_request
@@ -268,15 +255,3 @@ class _AsyncRESP3Parser(_AsyncRESPBase):
268
255
  if isinstance(response, bytes) and disable_decoding is False:
269
256
  response = self.encoder.decode(response)
270
257
  return response
271
-
272
- async def handle_push_response(self, response, disable_decoding, push_request):
273
- if response[0] not in _INVALIDATION_MESSAGE:
274
- return await self.pubsub_push_handler_func(response)
275
- if self.invalidation_push_handler_func:
276
- return await self.invalidation_push_handler_func(response)
277
-
278
- def set_pubsub_push_handler(self, pubsub_push_handler_func):
279
- self.pubsub_push_handler_func = pubsub_push_handler_func
280
-
281
- def set_invalidation_push_handler(self, invalidation_push_handler_func):
282
- self.invalidation_push_handler_func = invalidation_push_handler_func
@@ -70,7 +70,6 @@ from redis.exceptions import (
70
70
  )
71
71
  from redis.typing import ChannelT, EncodableT, KeyT
72
72
  from redis.utils import (
73
- HIREDIS_AVAILABLE,
74
73
  SSL_AVAILABLE,
75
74
  _set_info_logger,
76
75
  deprecated_args,
@@ -938,7 +937,7 @@ class PubSub:
938
937
  self.connection.register_connect_callback(self.on_connect)
939
938
  else:
940
939
  await self.connection.connect()
941
- if self.push_handler_func is not None and not HIREDIS_AVAILABLE:
940
+ if self.push_handler_func is not None:
942
941
  self.connection._parser.set_pubsub_push_handler(self.push_handler_func)
943
942
 
944
943
  self._event_dispatcher.dispatch(