redis 6.4.0__tar.gz → 7.0.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 (221) hide show
  1. {redis-6.4.0 → redis-7.0.0}/PKG-INFO +7 -4
  2. {redis-6.4.0 → redis-7.0.0}/README.md +4 -3
  3. redis-7.0.0/dev_requirements.txt +30 -0
  4. {redis-6.4.0 → redis-7.0.0}/pyproject.toml +9 -0
  5. {redis-6.4.0 → redis-7.0.0}/redis/__init__.py +1 -1
  6. {redis-6.4.0 → redis-7.0.0}/redis/_parsers/base.py +193 -8
  7. {redis-6.4.0 → redis-7.0.0}/redis/_parsers/helpers.py +64 -6
  8. {redis-6.4.0 → redis-7.0.0}/redis/_parsers/hiredis.py +16 -10
  9. {redis-6.4.0 → redis-7.0.0}/redis/_parsers/resp3.py +11 -5
  10. {redis-6.4.0 → redis-7.0.0}/redis/asyncio/client.py +65 -8
  11. {redis-6.4.0 → redis-7.0.0}/redis/asyncio/cluster.py +57 -5
  12. {redis-6.4.0 → redis-7.0.0}/redis/asyncio/connection.py +62 -2
  13. redis-7.0.0/redis/asyncio/http/http_client.py +265 -0
  14. redis-7.0.0/redis/asyncio/multidb/client.py +530 -0
  15. redis-7.0.0/redis/asyncio/multidb/command_executor.py +339 -0
  16. redis-7.0.0/redis/asyncio/multidb/config.py +210 -0
  17. redis-7.0.0/redis/asyncio/multidb/database.py +69 -0
  18. redis-7.0.0/redis/asyncio/multidb/event.py +84 -0
  19. redis-7.0.0/redis/asyncio/multidb/failover.py +125 -0
  20. redis-7.0.0/redis/asyncio/multidb/failure_detector.py +38 -0
  21. redis-7.0.0/redis/asyncio/multidb/healthcheck.py +285 -0
  22. redis-7.0.0/redis/background.py +204 -0
  23. {redis-6.4.0 → redis-7.0.0}/redis/cache.py +1 -0
  24. {redis-6.4.0 → redis-7.0.0}/redis/client.py +97 -16
  25. {redis-6.4.0 → redis-7.0.0}/redis/cluster.py +14 -3
  26. {redis-6.4.0 → redis-7.0.0}/redis/commands/core.py +348 -313
  27. {redis-6.4.0 → redis-7.0.0}/redis/commands/helpers.py +0 -20
  28. {redis-6.4.0 → redis-7.0.0}/redis/commands/json/commands.py +2 -2
  29. {redis-6.4.0 → redis-7.0.0}/redis/commands/search/__init__.py +2 -2
  30. {redis-6.4.0 → redis-7.0.0}/redis/commands/search/aggregation.py +24 -26
  31. {redis-6.4.0 → redis-7.0.0}/redis/commands/search/commands.py +10 -10
  32. {redis-6.4.0 → redis-7.0.0}/redis/commands/search/query.py +23 -23
  33. {redis-6.4.0 → redis-7.0.0}/redis/commands/vectorset/commands.py +43 -25
  34. {redis-6.4.0 → redis-7.0.0}/redis/commands/vectorset/utils.py +40 -4
  35. {redis-6.4.0 → redis-7.0.0}/redis/connection.py +1257 -83
  36. redis-7.0.0/redis/data_structure.py +81 -0
  37. {redis-6.4.0 → redis-7.0.0}/redis/event.py +84 -10
  38. {redis-6.4.0 → redis-7.0.0}/redis/exceptions.py +8 -0
  39. redis-7.0.0/redis/http/http_client.py +425 -0
  40. redis-7.0.0/redis/maint_notifications.py +810 -0
  41. redis-7.0.0/redis/multidb/circuit.py +144 -0
  42. redis-7.0.0/redis/multidb/client.py +526 -0
  43. redis-7.0.0/redis/multidb/command_executor.py +350 -0
  44. redis-7.0.0/redis/multidb/config.py +207 -0
  45. redis-7.0.0/redis/multidb/database.py +130 -0
  46. redis-7.0.0/redis/multidb/event.py +89 -0
  47. redis-7.0.0/redis/multidb/exception.py +17 -0
  48. redis-7.0.0/redis/multidb/failover.py +125 -0
  49. redis-7.0.0/redis/multidb/failure_detector.py +104 -0
  50. redis-7.0.0/redis/multidb/healthcheck.py +282 -0
  51. redis-7.0.0/redis/py.typed +0 -0
  52. {redis-6.4.0 → redis-7.0.0}/redis/retry.py +14 -1
  53. {redis-6.4.0 → redis-7.0.0}/redis/utils.py +34 -0
  54. redis-7.0.0/tests/__init__.py +0 -0
  55. {redis-6.4.0 → redis-7.0.0}/tests/conftest.py +16 -0
  56. redis-7.0.0/tests/test_asyncio/__init__.py +0 -0
  57. {redis-6.4.0 → redis-7.0.0}/tests/test_asyncio/test_commands.py +60 -7
  58. {redis-6.4.0 → redis-7.0.0}/tests/test_asyncio/test_connection_pool.py +31 -5
  59. redis-7.0.0/tests/test_asyncio/test_multidb/__init__.py +0 -0
  60. redis-7.0.0/tests/test_asyncio/test_multidb/conftest.py +131 -0
  61. redis-7.0.0/tests/test_asyncio/test_multidb/test_client.py +628 -0
  62. redis-7.0.0/tests/test_asyncio/test_multidb/test_command_executor.py +181 -0
  63. redis-7.0.0/tests/test_asyncio/test_multidb/test_config.py +166 -0
  64. redis-7.0.0/tests/test_asyncio/test_multidb/test_failover.py +169 -0
  65. redis-7.0.0/tests/test_asyncio/test_multidb/test_failure_detector.py +128 -0
  66. redis-7.0.0/tests/test_asyncio/test_multidb/test_healthcheck.py +401 -0
  67. redis-7.0.0/tests/test_asyncio/test_multidb/test_pipeline.py +488 -0
  68. redis-7.0.0/tests/test_asyncio/test_scenario/__init__.py +0 -0
  69. redis-7.0.0/tests/test_asyncio/test_scenario/conftest.py +116 -0
  70. redis-7.0.0/tests/test_asyncio/test_scenario/test_active_active.py +420 -0
  71. {redis-6.4.0 → redis-7.0.0}/tests/test_asyncio/test_search.py +17 -0
  72. redis-7.0.0/tests/test_asyncio/test_ssl.py +143 -0
  73. redis-7.0.0/tests/test_asyncio/test_usage_counter.py +16 -0
  74. {redis-6.4.0 → redis-7.0.0}/tests/test_asyncio/test_vsets.py +115 -3
  75. redis-7.0.0/tests/test_auth/__init__.py +0 -0
  76. redis-7.0.0/tests/test_background.py +94 -0
  77. {redis-6.4.0 → redis-7.0.0}/tests/test_cluster.py +16 -0
  78. {redis-6.4.0 → redis-7.0.0}/tests/test_commands.py +108 -13
  79. {redis-6.4.0 → redis-7.0.0}/tests/test_connection_pool.py +108 -4
  80. {redis-6.4.0 → redis-7.0.0}/tests/test_credentials.py +6 -1
  81. redis-7.0.0/tests/test_data_structure.py +94 -0
  82. redis-7.0.0/tests/test_event.py +67 -0
  83. redis-7.0.0/tests/test_http/__init__.py +0 -0
  84. redis-7.0.0/tests/test_http/test_http_client.py +371 -0
  85. redis-7.0.0/tests/test_maint_notifications.py +896 -0
  86. redis-7.0.0/tests/test_maint_notifications_handling.py +2242 -0
  87. redis-7.0.0/tests/test_multidb/__init__.py +0 -0
  88. redis-7.0.0/tests/test_multidb/conftest.py +131 -0
  89. redis-7.0.0/tests/test_multidb/test_circuit.py +57 -0
  90. redis-7.0.0/tests/test_multidb/test_client.py +615 -0
  91. redis-7.0.0/tests/test_multidb/test_command_executor.py +173 -0
  92. redis-7.0.0/tests/test_multidb/test_config.py +161 -0
  93. redis-7.0.0/tests/test_multidb/test_failover.py +161 -0
  94. redis-7.0.0/tests/test_multidb/test_failure_detector.py +117 -0
  95. redis-7.0.0/tests/test_multidb/test_healthcheck.py +385 -0
  96. redis-7.0.0/tests/test_multidb/test_pipeline.py +489 -0
  97. {redis-6.4.0 → redis-7.0.0}/tests/test_multiprocessing.py +8 -0
  98. redis-7.0.0/tests/test_parsers/test_errors.py +167 -0
  99. {redis-6.4.0 → redis-7.0.0}/tests/test_parsers/test_helpers.py +21 -1
  100. {redis-6.4.0 → redis-7.0.0}/tests/test_pubsub.py +10 -2
  101. redis-7.0.0/tests/test_scenario/__init__.py +0 -0
  102. redis-7.0.0/tests/test_scenario/conftest.py +237 -0
  103. redis-7.0.0/tests/test_scenario/fault_injector_client.py +150 -0
  104. redis-7.0.0/tests/test_scenario/maint_notifications_helpers.py +341 -0
  105. redis-7.0.0/tests/test_scenario/test_active_active.py +460 -0
  106. redis-7.0.0/tests/test_scenario/test_maint_notifications.py +1096 -0
  107. {redis-6.4.0 → redis-7.0.0}/tests/test_search.py +15 -0
  108. {redis-6.4.0 → redis-7.0.0}/tests/test_ssl.py +97 -0
  109. {redis-6.4.0 → redis-7.0.0}/tests/test_vsets.py +113 -3
  110. redis-6.4.0/dev_requirements.txt +0 -16
  111. redis-6.4.0/tests/test_asyncio/test_ssl.py +0 -56
  112. {redis-6.4.0 → redis-7.0.0}/.gitignore +0 -0
  113. {redis-6.4.0 → redis-7.0.0}/LICENSE +0 -0
  114. {redis-6.4.0 → redis-7.0.0}/redis/_parsers/__init__.py +0 -0
  115. {redis-6.4.0 → redis-7.0.0}/redis/_parsers/commands.py +0 -0
  116. {redis-6.4.0 → redis-7.0.0}/redis/_parsers/encoders.py +0 -0
  117. {redis-6.4.0 → redis-7.0.0}/redis/_parsers/resp2.py +0 -0
  118. {redis-6.4.0 → redis-7.0.0}/redis/_parsers/socket.py +0 -0
  119. {redis-6.4.0 → redis-7.0.0}/redis/asyncio/__init__.py +0 -0
  120. {redis-6.4.0/redis/auth → redis-7.0.0/redis/asyncio/http}/__init__.py +0 -0
  121. {redis-6.4.0 → redis-7.0.0}/redis/asyncio/lock.py +0 -0
  122. {redis-6.4.0/tests → redis-7.0.0/redis/asyncio/multidb}/__init__.py +0 -0
  123. {redis-6.4.0 → redis-7.0.0}/redis/asyncio/retry.py +0 -0
  124. {redis-6.4.0 → redis-7.0.0}/redis/asyncio/sentinel.py +0 -0
  125. {redis-6.4.0 → redis-7.0.0}/redis/asyncio/utils.py +0 -0
  126. {redis-6.4.0/tests/test_asyncio → redis-7.0.0/redis/auth}/__init__.py +0 -0
  127. {redis-6.4.0 → redis-7.0.0}/redis/auth/err.py +0 -0
  128. {redis-6.4.0 → redis-7.0.0}/redis/auth/idp.py +0 -0
  129. {redis-6.4.0 → redis-7.0.0}/redis/auth/token.py +0 -0
  130. {redis-6.4.0 → redis-7.0.0}/redis/auth/token_manager.py +0 -0
  131. {redis-6.4.0 → redis-7.0.0}/redis/backoff.py +0 -0
  132. {redis-6.4.0 → redis-7.0.0}/redis/commands/__init__.py +0 -0
  133. {redis-6.4.0 → redis-7.0.0}/redis/commands/bf/__init__.py +0 -0
  134. {redis-6.4.0 → redis-7.0.0}/redis/commands/bf/commands.py +0 -0
  135. {redis-6.4.0 → redis-7.0.0}/redis/commands/bf/info.py +0 -0
  136. {redis-6.4.0 → redis-7.0.0}/redis/commands/cluster.py +0 -0
  137. {redis-6.4.0 → redis-7.0.0}/redis/commands/json/__init__.py +0 -0
  138. {redis-6.4.0 → redis-7.0.0}/redis/commands/json/_util.py +0 -0
  139. {redis-6.4.0 → redis-7.0.0}/redis/commands/json/decoders.py +0 -0
  140. {redis-6.4.0 → redis-7.0.0}/redis/commands/json/path.py +0 -0
  141. {redis-6.4.0 → redis-7.0.0}/redis/commands/redismodules.py +0 -0
  142. {redis-6.4.0 → redis-7.0.0}/redis/commands/search/_util.py +0 -0
  143. {redis-6.4.0 → redis-7.0.0}/redis/commands/search/dialect.py +0 -0
  144. {redis-6.4.0 → redis-7.0.0}/redis/commands/search/document.py +0 -0
  145. {redis-6.4.0 → redis-7.0.0}/redis/commands/search/field.py +2 -2
  146. {redis-6.4.0 → redis-7.0.0}/redis/commands/search/index_definition.py +0 -0
  147. {redis-6.4.0 → redis-7.0.0}/redis/commands/search/profile_information.py +0 -0
  148. {redis-6.4.0 → redis-7.0.0}/redis/commands/search/querystring.py +0 -0
  149. {redis-6.4.0 → redis-7.0.0}/redis/commands/search/reducers.py +0 -0
  150. {redis-6.4.0 → redis-7.0.0}/redis/commands/search/result.py +0 -0
  151. {redis-6.4.0 → redis-7.0.0}/redis/commands/search/suggestion.py +0 -0
  152. {redis-6.4.0 → redis-7.0.0}/redis/commands/sentinel.py +0 -0
  153. {redis-6.4.0 → redis-7.0.0}/redis/commands/timeseries/__init__.py +0 -0
  154. {redis-6.4.0 → redis-7.0.0}/redis/commands/timeseries/commands.py +0 -0
  155. {redis-6.4.0 → redis-7.0.0}/redis/commands/timeseries/info.py +0 -0
  156. {redis-6.4.0 → redis-7.0.0}/redis/commands/timeseries/utils.py +0 -0
  157. {redis-6.4.0 → redis-7.0.0}/redis/commands/vectorset/__init__.py +1 -1
  158. {redis-6.4.0 → redis-7.0.0}/redis/crc.py +0 -0
  159. {redis-6.4.0 → redis-7.0.0}/redis/credentials.py +0 -0
  160. {redis-6.4.0/tests/test_auth → redis-7.0.0/redis/http}/__init__.py +0 -0
  161. {redis-6.4.0 → redis-7.0.0}/redis/lock.py +0 -0
  162. /redis-6.4.0/redis/py.typed → /redis-7.0.0/redis/multidb/__init__.py +0 -0
  163. {redis-6.4.0 → redis-7.0.0}/redis/ocsp.py +0 -0
  164. {redis-6.4.0 → redis-7.0.0}/redis/sentinel.py +0 -0
  165. {redis-6.4.0 → redis-7.0.0}/redis/typing.py +0 -0
  166. {redis-6.4.0 → redis-7.0.0}/tests/entraid_utils.py +0 -0
  167. {redis-6.4.0 → redis-7.0.0}/tests/mocks.py +0 -0
  168. {redis-6.4.0 → redis-7.0.0}/tests/ssl_utils.py +0 -0
  169. {redis-6.4.0 → redis-7.0.0}/tests/test_asyncio/compat.py +0 -0
  170. {redis-6.4.0 → redis-7.0.0}/tests/test_asyncio/conftest.py +0 -0
  171. {redis-6.4.0 → redis-7.0.0}/tests/test_asyncio/mocks.py +0 -0
  172. {redis-6.4.0 → redis-7.0.0}/tests/test_asyncio/test_bloom.py +0 -0
  173. {redis-6.4.0 → redis-7.0.0}/tests/test_asyncio/test_cluster.py +0 -0
  174. {redis-6.4.0 → redis-7.0.0}/tests/test_asyncio/test_cluster_transaction.py +0 -0
  175. {redis-6.4.0 → redis-7.0.0}/tests/test_asyncio/test_connect.py +0 -0
  176. {redis-6.4.0 → redis-7.0.0}/tests/test_asyncio/test_connection.py +0 -0
  177. {redis-6.4.0 → redis-7.0.0}/tests/test_asyncio/test_credentials.py +0 -0
  178. {redis-6.4.0 → redis-7.0.0}/tests/test_asyncio/test_cwe_404.py +0 -0
  179. {redis-6.4.0 → redis-7.0.0}/tests/test_asyncio/test_encoding.py +0 -0
  180. {redis-6.4.0 → redis-7.0.0}/tests/test_asyncio/test_hash.py +0 -0
  181. {redis-6.4.0 → redis-7.0.0}/tests/test_asyncio/test_json.py +0 -0
  182. {redis-6.4.0 → redis-7.0.0}/tests/test_asyncio/test_lock.py +0 -0
  183. {redis-6.4.0 → redis-7.0.0}/tests/test_asyncio/test_monitor.py +0 -0
  184. {redis-6.4.0 → redis-7.0.0}/tests/test_asyncio/test_pipeline.py +0 -0
  185. {redis-6.4.0 → redis-7.0.0}/tests/test_asyncio/test_pubsub.py +0 -0
  186. {redis-6.4.0 → redis-7.0.0}/tests/test_asyncio/test_retry.py +0 -0
  187. {redis-6.4.0 → redis-7.0.0}/tests/test_asyncio/test_scripting.py +0 -0
  188. {redis-6.4.0 → redis-7.0.0}/tests/test_asyncio/test_sentinel.py +0 -0
  189. {redis-6.4.0 → redis-7.0.0}/tests/test_asyncio/test_sentinel_managed_connection.py +0 -0
  190. {redis-6.4.0 → redis-7.0.0}/tests/test_asyncio/test_timeseries.py +0 -0
  191. {redis-6.4.0 → redis-7.0.0}/tests/test_asyncio/test_utils.py +0 -0
  192. {redis-6.4.0 → redis-7.0.0}/tests/test_asyncio/testdata/jsontestdata.py +0 -0
  193. {redis-6.4.0 → redis-7.0.0}/tests/test_asyncio/testdata/titles.csv +0 -0
  194. {redis-6.4.0 → redis-7.0.0}/tests/test_asyncio/testdata/will_play_text.csv.bz2 +0 -0
  195. {redis-6.4.0 → redis-7.0.0}/tests/test_auth/test_token.py +0 -0
  196. {redis-6.4.0 → redis-7.0.0}/tests/test_auth/test_token_manager.py +0 -0
  197. {redis-6.4.0 → redis-7.0.0}/tests/test_backoff.py +0 -0
  198. {redis-6.4.0 → redis-7.0.0}/tests/test_bloom.py +0 -0
  199. {redis-6.4.0 → redis-7.0.0}/tests/test_cache.py +0 -0
  200. {redis-6.4.0 → redis-7.0.0}/tests/test_cluster_transaction.py +0 -0
  201. {redis-6.4.0 → redis-7.0.0}/tests/test_command_parser.py +0 -0
  202. {redis-6.4.0 → redis-7.0.0}/tests/test_connect.py +0 -0
  203. {redis-6.4.0 → redis-7.0.0}/tests/test_connection.py +0 -0
  204. {redis-6.4.0 → redis-7.0.0}/tests/test_encoding.py +0 -0
  205. {redis-6.4.0 → redis-7.0.0}/tests/test_function.py +0 -0
  206. {redis-6.4.0 → redis-7.0.0}/tests/test_hash.py +0 -0
  207. {redis-6.4.0 → redis-7.0.0}/tests/test_helpers.py +0 -0
  208. {redis-6.4.0 → redis-7.0.0}/tests/test_json.py +0 -0
  209. {redis-6.4.0 → redis-7.0.0}/tests/test_lock.py +0 -0
  210. {redis-6.4.0 → redis-7.0.0}/tests/test_max_connections_error.py +0 -0
  211. {redis-6.4.0 → redis-7.0.0}/tests/test_monitor.py +0 -0
  212. {redis-6.4.0 → redis-7.0.0}/tests/test_pipeline.py +0 -0
  213. {redis-6.4.0 → redis-7.0.0}/tests/test_retry.py +0 -0
  214. {redis-6.4.0 → redis-7.0.0}/tests/test_scripting.py +0 -0
  215. {redis-6.4.0 → redis-7.0.0}/tests/test_sentinel.py +0 -0
  216. {redis-6.4.0 → redis-7.0.0}/tests/test_sentinel_managed_connection.py +0 -0
  217. {redis-6.4.0 → redis-7.0.0}/tests/test_timeseries.py +0 -0
  218. {redis-6.4.0 → redis-7.0.0}/tests/test_utils.py +0 -0
  219. {redis-6.4.0 → redis-7.0.0}/tests/testdata/jsontestdata.py +0 -0
  220. {redis-6.4.0 → redis-7.0.0}/tests/testdata/titles.csv +0 -0
  221. {redis-6.4.0 → redis-7.0.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.4.0
3
+ Version: 7.0.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
@@ -28,6 +28,8 @@ Classifier: Programming Language :: Python :: Implementation :: CPython
28
28
  Classifier: Programming Language :: Python :: Implementation :: PyPy
29
29
  Requires-Python: >=3.9
30
30
  Requires-Dist: async-timeout>=4.0.3; python_full_version < '3.11.3'
31
+ Provides-Extra: circuit-breaker
32
+ Requires-Dist: pybreaker>=1.4.0; extra == 'circuit-breaker'
31
33
  Provides-Extra: hiredis
32
34
  Requires-Dist: hiredis>=3.2.0; extra == 'hiredis'
33
35
  Provides-Extra: jwt
@@ -53,8 +55,9 @@ The Python interface to the Redis key-value store.
53
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+.
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+.
58
+ **Note:** redis-py 5.0 is the last version of redis-py that supports Python 3.7, as it has reached [end of life](https://devguide.python.org/versions/). redis-py 5.1 supports Python 3.8+.<br>
59
+ **Note:** redis-py 6.1.0 is the last version of redis-py that supports Python 3.8, as it has reached [end of life](https://devguide.python.org/versions/). redis-py 6.2.0 supports Python 3.9+.
60
+
58
61
  ---------------------------------------------
59
62
 
60
63
  ## How do I Redis?
@@ -99,7 +102,7 @@ Looking for a high-level library to handle object mapping? See [redis-om-python]
99
102
 
100
103
  ## Supported Redis Versions
101
104
 
102
- The most recent version of this library supports Redis version [7.2](https://github.com/redis/redis/blob/7.2/00-RELEASENOTES), [7.4](https://github.com/redis/redis/blob/7.4/00-RELEASENOTES) and [8.0](https://github.com/redis/redis/blob/8.0/00-RELEASENOTES).
105
+ The most recent version of this library supports Redis version [7.2](https://github.com/redis/redis/blob/7.2/00-RELEASENOTES), [7.4](https://github.com/redis/redis/blob/7.4/00-RELEASENOTES), [8.0](https://github.com/redis/redis/blob/8.0/00-RELEASENOTES) and [8.2](https://github.com/redis/redis/blob/8.2/00-RELEASENOTES).
103
106
 
104
107
  The table below highlights version compatibility of the most-recent library versions and redis versions.
105
108
 
@@ -13,8 +13,9 @@ The Python interface to the Redis key-value store.
13
13
 
14
14
  ---------------------------------------------
15
15
 
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
- **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+.
16
+ **Note:** redis-py 5.0 is the last version of redis-py that supports Python 3.7, as it has reached [end of life](https://devguide.python.org/versions/). redis-py 5.1 supports Python 3.8+.<br>
17
+ **Note:** redis-py 6.1.0 is the last version of redis-py that supports Python 3.8, as it has reached [end of life](https://devguide.python.org/versions/). redis-py 6.2.0 supports Python 3.9+.
18
+
18
19
  ---------------------------------------------
19
20
 
20
21
  ## How do I Redis?
@@ -59,7 +60,7 @@ Looking for a high-level library to handle object mapping? See [redis-om-python]
59
60
 
60
61
  ## Supported Redis Versions
61
62
 
62
- The most recent version of this library supports Redis version [7.2](https://github.com/redis/redis/blob/7.2/00-RELEASENOTES), [7.4](https://github.com/redis/redis/blob/7.4/00-RELEASENOTES) and [8.0](https://github.com/redis/redis/blob/8.0/00-RELEASENOTES).
63
+ The most recent version of this library supports Redis version [7.2](https://github.com/redis/redis/blob/7.2/00-RELEASENOTES), [7.4](https://github.com/redis/redis/blob/7.4/00-RELEASENOTES), [8.0](https://github.com/redis/redis/blob/8.0/00-RELEASENOTES) and [8.2](https://github.com/redis/redis/blob/8.2/00-RELEASENOTES).
63
64
 
64
65
  The table below highlights version compatibility of the most-recent library versions and redis versions.
65
66
 
@@ -0,0 +1,30 @@
1
+ build
2
+ build==1.2.2.post1 ; platform_python_implementation == "PyPy" or python_version < "3.10"
3
+ click==8.0.4
4
+ invoke==2.2.0
5
+ mock
6
+ mock==5.1.0 ; platform_python_implementation == "PyPy" or python_version < "3.10"
7
+ packaging>=20.4
8
+ packaging==24.2 ; platform_python_implementation == "PyPy" or python_version < "3.10"
9
+
10
+ pytest
11
+ pytest==8.3.4 ; platform_python_implementation == "PyPy" or python_version < "3.10"
12
+ pytest-asyncio>=0.23.0
13
+ pytest-asyncio==1.1.0 ; platform_python_implementation == "PyPy" or python_version < "3.10"
14
+ pytest-cov
15
+ pytest-cov==6.0.0 ; platform_python_implementation == "PyPy" or python_version < "3.10"
16
+ coverage==7.6.12 ; platform_python_implementation == "PyPy" or python_version < "3.10"
17
+ pytest-profiling==1.8.1
18
+ pytest-timeout
19
+ pytest-timeout==2.3.1 ; platform_python_implementation == "PyPy" or python_version < "3.10"
20
+
21
+ ruff==0.9.6
22
+ ujson>=4.2.0
23
+ uvloop<=0.21.0; platform_python_implementation == "CPython"
24
+ vulture>=2.3.0
25
+
26
+ numpy>=1.24.0 ; platform_python_implementation == "CPython"
27
+ numpy>=1.24.0,<2.0 ; platform_python_implementation == "PyPy" or python_version < "3.10"
28
+
29
+ redis-entraid==1.0.0
30
+ pybreaker>=1.4.0
@@ -42,6 +42,9 @@ ocsp = [
42
42
  jwt = [
43
43
  "PyJWT>=2.9.0",
44
44
  ]
45
+ circuit_breaker = [
46
+ "pybreaker>=1.4.0"
47
+ ]
45
48
 
46
49
  [project.urls]
47
50
  Changes = "https://github.com/redis/redis-py/releases"
@@ -80,6 +83,12 @@ filterwarnings = [
80
83
  # Ignore a coverage warning when COVERAGE_CORE=sysmon for Pythons < 3.12.
81
84
  "ignore:sys.monitoring isn't available:coverage.exceptions.CoverageWarning",
82
85
  ]
86
+ log_cli_level = "INFO"
87
+ log_cli_date_format = "%H:%M:%S:%f"
88
+ log_cli = false
89
+ log_cli_format = "%(asctime)s %(levelname)s %(threadName)s: %(message)s"
90
+ log_level = "INFO"
91
+ capture = "yes"
83
92
 
84
93
  [tool.ruff]
85
94
  target-version = "py39"
@@ -46,7 +46,7 @@ def int_or_str(value):
46
46
  return value
47
47
 
48
48
 
49
- __version__ = "6.4.0"
49
+ __version__ = "7.0.0"
50
50
  VERSION = tuple(map(int_or_str, __version__.split(".")))
51
51
 
52
52
 
@@ -1,7 +1,17 @@
1
+ import logging
1
2
  import sys
2
3
  from abc import ABC
3
4
  from asyncio import IncompleteReadError, StreamReader, TimeoutError
4
- from typing import Callable, List, Optional, Protocol, Union
5
+ from typing import Awaitable, Callable, List, Optional, Protocol, Union
6
+
7
+ from redis.maint_notifications import (
8
+ MaintenanceNotification,
9
+ NodeFailedOverNotification,
10
+ NodeFailingOverNotification,
11
+ NodeMigratedNotification,
12
+ NodeMigratingNotification,
13
+ NodeMovingNotification,
14
+ )
5
15
 
6
16
  if sys.version_info.major >= 3 and sys.version_info.minor >= 11:
7
17
  from asyncio import timeout as async_timeout
@@ -17,6 +27,7 @@ from ..exceptions import (
17
27
  ClusterDownError,
18
28
  ConnectionError,
19
29
  ExecAbortError,
30
+ ExternalAuthProviderError,
20
31
  MasterDownError,
21
32
  ModuleError,
22
33
  MovedError,
@@ -50,6 +61,12 @@ NO_AUTH_SET_ERROR = {
50
61
  "Client sent AUTH, but no password is set": AuthenticationError,
51
62
  }
52
63
 
64
+ EXTERNAL_AUTH_PROVIDER_ERROR = {
65
+ "problem with LDAP service": ExternalAuthProviderError,
66
+ }
67
+
68
+ logger = logging.getLogger(__name__)
69
+
53
70
 
54
71
  class BaseParser(ABC):
55
72
  EXCEPTION_CLASSES = {
@@ -69,6 +86,7 @@ class BaseParser(ABC):
69
86
  NO_SUCH_MODULE_ERROR: ModuleError,
70
87
  MODULE_UNLOAD_NOT_POSSIBLE_ERROR: ModuleError,
71
88
  **NO_AUTH_SET_ERROR,
89
+ **EXTERNAL_AUTH_PROVIDER_ERROR,
72
90
  },
73
91
  "OOM": OutOfMemoryError,
74
92
  "WRONGPASS": AuthenticationError,
@@ -158,7 +176,77 @@ class AsyncBaseParser(BaseParser):
158
176
  raise NotImplementedError()
159
177
 
160
178
 
161
- _INVALIDATION_MESSAGE = [b"invalidate", "invalidate"]
179
+ class MaintenanceNotificationsParser:
180
+ """Protocol defining maintenance push notification parsing functionality"""
181
+
182
+ @staticmethod
183
+ def parse_maintenance_start_msg(response, notification_type):
184
+ # Expected message format is: <notification_type> <seq_number> <time>
185
+ id = response[1]
186
+ ttl = response[2]
187
+ return notification_type(id, ttl)
188
+
189
+ @staticmethod
190
+ def parse_maintenance_completed_msg(response, notification_type):
191
+ # Expected message format is: <notification_type> <seq_number>
192
+ id = response[1]
193
+ return notification_type(id)
194
+
195
+ @staticmethod
196
+ def parse_moving_msg(response):
197
+ # Expected message format is: MOVING <seq_number> <time> <endpoint>
198
+ id = response[1]
199
+ ttl = response[2]
200
+ if response[3] is None:
201
+ host, port = None, None
202
+ else:
203
+ value = response[3]
204
+ if isinstance(value, bytes):
205
+ value = value.decode()
206
+ host, port = value.split(":")
207
+ port = int(port) if port is not None else None
208
+
209
+ return NodeMovingNotification(id, host, port, ttl)
210
+
211
+
212
+ _INVALIDATION_MESSAGE = "invalidate"
213
+ _MOVING_MESSAGE = "MOVING"
214
+ _MIGRATING_MESSAGE = "MIGRATING"
215
+ _MIGRATED_MESSAGE = "MIGRATED"
216
+ _FAILING_OVER_MESSAGE = "FAILING_OVER"
217
+ _FAILED_OVER_MESSAGE = "FAILED_OVER"
218
+
219
+ _MAINTENANCE_MESSAGES = (
220
+ _MIGRATING_MESSAGE,
221
+ _MIGRATED_MESSAGE,
222
+ _FAILING_OVER_MESSAGE,
223
+ _FAILED_OVER_MESSAGE,
224
+ )
225
+
226
+ MSG_TYPE_TO_MAINT_NOTIFICATION_PARSER_MAPPING: dict[
227
+ str, tuple[type[MaintenanceNotification], Callable]
228
+ ] = {
229
+ _MIGRATING_MESSAGE: (
230
+ NodeMigratingNotification,
231
+ MaintenanceNotificationsParser.parse_maintenance_start_msg,
232
+ ),
233
+ _MIGRATED_MESSAGE: (
234
+ NodeMigratedNotification,
235
+ MaintenanceNotificationsParser.parse_maintenance_completed_msg,
236
+ ),
237
+ _FAILING_OVER_MESSAGE: (
238
+ NodeFailingOverNotification,
239
+ MaintenanceNotificationsParser.parse_maintenance_start_msg,
240
+ ),
241
+ _FAILED_OVER_MESSAGE: (
242
+ NodeFailedOverNotification,
243
+ MaintenanceNotificationsParser.parse_maintenance_completed_msg,
244
+ ),
245
+ _MOVING_MESSAGE: (
246
+ NodeMovingNotification,
247
+ MaintenanceNotificationsParser.parse_moving_msg,
248
+ ),
249
+ }
162
250
 
163
251
 
164
252
  class PushNotificationsParser(Protocol):
@@ -166,16 +254,57 @@ class PushNotificationsParser(Protocol):
166
254
 
167
255
  pubsub_push_handler_func: Callable
168
256
  invalidation_push_handler_func: Optional[Callable] = None
257
+ node_moving_push_handler_func: Optional[Callable] = None
258
+ maintenance_push_handler_func: Optional[Callable] = None
169
259
 
170
260
  def handle_pubsub_push_response(self, response):
171
261
  """Handle pubsub push responses"""
172
262
  raise NotImplementedError()
173
263
 
174
264
  def handle_push_response(self, response, **kwargs):
175
- if response[0] not in _INVALIDATION_MESSAGE:
265
+ msg_type = response[0]
266
+ if isinstance(msg_type, bytes):
267
+ msg_type = msg_type.decode()
268
+
269
+ if msg_type not in (
270
+ _INVALIDATION_MESSAGE,
271
+ *_MAINTENANCE_MESSAGES,
272
+ _MOVING_MESSAGE,
273
+ ):
176
274
  return self.pubsub_push_handler_func(response)
177
- if self.invalidation_push_handler_func:
178
- return self.invalidation_push_handler_func(response)
275
+
276
+ try:
277
+ if (
278
+ msg_type == _INVALIDATION_MESSAGE
279
+ and self.invalidation_push_handler_func
280
+ ):
281
+ return self.invalidation_push_handler_func(response)
282
+
283
+ if msg_type == _MOVING_MESSAGE and self.node_moving_push_handler_func:
284
+ parser_function = MSG_TYPE_TO_MAINT_NOTIFICATION_PARSER_MAPPING[
285
+ msg_type
286
+ ][1]
287
+
288
+ notification = parser_function(response)
289
+ return self.node_moving_push_handler_func(notification)
290
+
291
+ if msg_type in _MAINTENANCE_MESSAGES and self.maintenance_push_handler_func:
292
+ parser_function = MSG_TYPE_TO_MAINT_NOTIFICATION_PARSER_MAPPING[
293
+ msg_type
294
+ ][1]
295
+ notification_type = MSG_TYPE_TO_MAINT_NOTIFICATION_PARSER_MAPPING[
296
+ msg_type
297
+ ][0]
298
+ notification = parser_function(response, notification_type)
299
+
300
+ if notification is not None:
301
+ return self.maintenance_push_handler_func(notification)
302
+ except Exception as e:
303
+ logger.error(
304
+ "Error handling {} message ({}): {}".format(msg_type, response, e)
305
+ )
306
+
307
+ return None
179
308
 
180
309
  def set_pubsub_push_handler(self, pubsub_push_handler_func):
181
310
  self.pubsub_push_handler_func = pubsub_push_handler_func
@@ -183,12 +312,20 @@ class PushNotificationsParser(Protocol):
183
312
  def set_invalidation_push_handler(self, invalidation_push_handler_func):
184
313
  self.invalidation_push_handler_func = invalidation_push_handler_func
185
314
 
315
+ def set_node_moving_push_handler(self, node_moving_push_handler_func):
316
+ self.node_moving_push_handler_func = node_moving_push_handler_func
317
+
318
+ def set_maintenance_push_handler(self, maintenance_push_handler_func):
319
+ self.maintenance_push_handler_func = maintenance_push_handler_func
320
+
186
321
 
187
322
  class AsyncPushNotificationsParser(Protocol):
188
323
  """Protocol defining async RESP3-specific parsing functionality"""
189
324
 
190
325
  pubsub_push_handler_func: Callable
191
326
  invalidation_push_handler_func: Optional[Callable] = None
327
+ node_moving_push_handler_func: Optional[Callable[..., Awaitable[None]]] = None
328
+ maintenance_push_handler_func: Optional[Callable[..., Awaitable[None]]] = None
192
329
 
193
330
  async def handle_pubsub_push_response(self, response):
194
331
  """Handle pubsub push responses asynchronously"""
@@ -196,10 +333,52 @@ class AsyncPushNotificationsParser(Protocol):
196
333
 
197
334
  async def handle_push_response(self, response, **kwargs):
198
335
  """Handle push responses asynchronously"""
199
- if response[0] not in _INVALIDATION_MESSAGE:
336
+
337
+ msg_type = response[0]
338
+ if isinstance(msg_type, bytes):
339
+ msg_type = msg_type.decode()
340
+
341
+ if msg_type not in (
342
+ _INVALIDATION_MESSAGE,
343
+ *_MAINTENANCE_MESSAGES,
344
+ _MOVING_MESSAGE,
345
+ ):
200
346
  return await self.pubsub_push_handler_func(response)
201
- if self.invalidation_push_handler_func:
202
- return await self.invalidation_push_handler_func(response)
347
+
348
+ try:
349
+ if (
350
+ msg_type == _INVALIDATION_MESSAGE
351
+ and self.invalidation_push_handler_func
352
+ ):
353
+ return await self.invalidation_push_handler_func(response)
354
+
355
+ if isinstance(msg_type, bytes):
356
+ msg_type = msg_type.decode()
357
+
358
+ if msg_type == _MOVING_MESSAGE and self.node_moving_push_handler_func:
359
+ parser_function = MSG_TYPE_TO_MAINT_NOTIFICATION_PARSER_MAPPING[
360
+ msg_type
361
+ ][1]
362
+ notification = parser_function(response)
363
+ return await self.node_moving_push_handler_func(notification)
364
+
365
+ if msg_type in _MAINTENANCE_MESSAGES and self.maintenance_push_handler_func:
366
+ parser_function = MSG_TYPE_TO_MAINT_NOTIFICATION_PARSER_MAPPING[
367
+ msg_type
368
+ ][1]
369
+ notification_type = MSG_TYPE_TO_MAINT_NOTIFICATION_PARSER_MAPPING[
370
+ msg_type
371
+ ][0]
372
+ notification = parser_function(response, notification_type)
373
+
374
+ if notification is not None:
375
+ return await self.maintenance_push_handler_func(notification)
376
+ except Exception as e:
377
+ logger.error(
378
+ "Error handling {} message ({}): {}".format(msg_type, response, e)
379
+ )
380
+
381
+ return None
203
382
 
204
383
  def set_pubsub_push_handler(self, pubsub_push_handler_func):
205
384
  """Set the pubsub push handler function"""
@@ -209,6 +388,12 @@ class AsyncPushNotificationsParser(Protocol):
209
388
  """Set the invalidation push handler function"""
210
389
  self.invalidation_push_handler_func = invalidation_push_handler_func
211
390
 
391
+ def set_node_moving_push_handler(self, node_moving_push_handler_func):
392
+ self.node_moving_push_handler_func = node_moving_push_handler_func
393
+
394
+ def set_maintenance_push_handler(self, maintenance_push_handler_func):
395
+ self.maintenance_push_handler_func = maintenance_push_handler_func
396
+
212
397
 
213
398
  class _AsyncRESPBase(AsyncBaseParser):
214
399
  """Base class for async resp parsing"""
@@ -224,6 +224,39 @@ def zset_score_pairs(response, **options):
224
224
  return list(zip(it, map(score_cast_func, it)))
225
225
 
226
226
 
227
+ def zset_score_for_rank(response, **options):
228
+ """
229
+ If ``withscores`` is specified in the options, return the response as
230
+ a [value, score] pair
231
+ """
232
+ if not response or not options.get("withscore"):
233
+ return response
234
+ score_cast_func = options.get("score_cast_func", float)
235
+ return [response[0], score_cast_func(response[1])]
236
+
237
+
238
+ def zset_score_pairs_resp3(response, **options):
239
+ """
240
+ If ``withscores`` is specified in the options, return the response as
241
+ a list of [value, score] pairs
242
+ """
243
+ if not response or not options.get("withscores"):
244
+ return response
245
+ score_cast_func = options.get("score_cast_func", float)
246
+ return [[name, score_cast_func(val)] for name, val in response]
247
+
248
+
249
+ def zset_score_for_rank_resp3(response, **options):
250
+ """
251
+ If ``withscores`` is specified in the options, return the response as
252
+ a [value, score] pair
253
+ """
254
+ if not response or not options.get("withscore"):
255
+ return response
256
+ score_cast_func = options.get("score_cast_func", float)
257
+ return [response[0], score_cast_func(response[1])]
258
+
259
+
227
260
  def sort_return_tuples(response, **options):
228
261
  """
229
262
  If ``groups`` is specified, return the response as a list of
@@ -349,8 +382,22 @@ def parse_zadd(response, **options):
349
382
  def parse_client_list(response, **options):
350
383
  clients = []
351
384
  for c in str_if_bytes(response).splitlines():
352
- # Values might contain '='
353
- clients.append(dict(pair.split("=", 1) for pair in c.split(" ")))
385
+ client_dict = {}
386
+ tokens = c.split(" ")
387
+ last_key = None
388
+ for token in tokens:
389
+ if "=" in token:
390
+ # Values might contain '='
391
+ key, value = token.split("=", 1)
392
+ client_dict[key] = value
393
+ last_key = key
394
+ else:
395
+ # Values may include spaces. For instance, when running Redis via a Unix socket — such as
396
+ # "/tmp/redis sock/redis.sock" — the addr or laddr field will include a space.
397
+ client_dict[last_key] += " " + token
398
+
399
+ if client_dict:
400
+ clients.append(client_dict)
354
401
  return clients
355
402
 
356
403
 
@@ -797,10 +844,14 @@ _RedisCallbacksRESP2 = {
797
844
  "SDIFF SINTER SMEMBERS SUNION", lambda r: r and set(r) or set()
798
845
  ),
799
846
  **string_keys_to_dict(
800
- "ZDIFF ZINTER ZPOPMAX ZPOPMIN ZRANGE ZRANGEBYSCORE ZRANK ZREVRANGE "
801
- "ZREVRANGEBYSCORE ZREVRANK ZUNION",
847
+ "ZDIFF ZINTER ZPOPMAX ZPOPMIN ZRANGE ZRANGEBYSCORE ZREVRANGE "
848
+ "ZREVRANGEBYSCORE ZUNION",
802
849
  zset_score_pairs,
803
850
  ),
851
+ **string_keys_to_dict(
852
+ "ZREVRANK ZRANK",
853
+ zset_score_for_rank,
854
+ ),
804
855
  **string_keys_to_dict("ZINCRBY ZSCORE", float_or_none),
805
856
  **string_keys_to_dict("BGREWRITEAOF BGSAVE", lambda r: True),
806
857
  **string_keys_to_dict("BLPOP BRPOP", lambda r: r and tuple(r) or None),
@@ -844,10 +895,17 @@ _RedisCallbacksRESP3 = {
844
895
  "SDIFF SINTER SMEMBERS SUNION", lambda r: r and set(r) or set()
845
896
  ),
846
897
  **string_keys_to_dict(
847
- "ZRANGE ZINTER ZPOPMAX ZPOPMIN ZRANGEBYSCORE ZREVRANGE ZREVRANGEBYSCORE "
848
- "ZUNION HGETALL XREADGROUP",
898
+ "ZRANGE ZINTER ZPOPMAX ZPOPMIN HGETALL XREADGROUP",
849
899
  lambda r, **kwargs: r,
850
900
  ),
901
+ **string_keys_to_dict(
902
+ "ZRANGE ZRANGEBYSCORE ZREVRANGE ZREVRANGEBYSCORE ZUNION",
903
+ zset_score_pairs_resp3,
904
+ ),
905
+ **string_keys_to_dict(
906
+ "ZREVRANK ZRANK",
907
+ zset_score_for_rank_resp3,
908
+ ),
851
909
  **string_keys_to_dict("XREAD XREADGROUP", parse_xread_resp3),
852
910
  "ACL LOG": lambda r: (
853
911
  [
@@ -47,6 +47,8 @@ class _HiredisParser(BaseParser, PushNotificationsParser):
47
47
  self.socket_read_size = socket_read_size
48
48
  self._buffer = bytearray(socket_read_size)
49
49
  self.pubsub_push_handler_func = self.handle_pubsub_push_response
50
+ self.node_moving_push_handler_func = None
51
+ self.maintenance_push_handler_func = None
50
52
  self.invalidation_push_handler_func = None
51
53
  self._hiredis_PushNotificationType = None
52
54
 
@@ -141,12 +143,15 @@ class _HiredisParser(BaseParser, PushNotificationsParser):
141
143
  response, self._hiredis_PushNotificationType
142
144
  ):
143
145
  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:
146
+
147
+ # if this is a push request return the push response
148
+ if push_request:
149
149
  return response
150
+
151
+ return self.read_response(
152
+ disable_decoding=disable_decoding,
153
+ push_request=push_request,
154
+ )
150
155
  return response
151
156
 
152
157
  if disable_decoding:
@@ -169,12 +174,13 @@ class _HiredisParser(BaseParser, PushNotificationsParser):
169
174
  response, self._hiredis_PushNotificationType
170
175
  ):
171
176
  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
+ if push_request:
177
178
  return response
179
+ return self.read_response(
180
+ disable_decoding=disable_decoding,
181
+ push_request=push_request,
182
+ )
183
+
178
184
  elif (
179
185
  isinstance(response, list)
180
186
  and response
@@ -18,6 +18,8 @@ class _RESP3Parser(_RESPBase, PushNotificationsParser):
18
18
  def __init__(self, socket_read_size):
19
19
  super().__init__(socket_read_size)
20
20
  self.pubsub_push_handler_func = self.handle_pubsub_push_response
21
+ self.node_moving_push_handler_func = None
22
+ self.maintenance_push_handler_func = None
21
23
  self.invalidation_push_handler_func = None
22
24
 
23
25
  def handle_pubsub_push_response(self, response):
@@ -117,17 +119,21 @@ class _RESP3Parser(_RESPBase, PushNotificationsParser):
117
119
  for _ in range(int(response))
118
120
  ]
119
121
  response = self.handle_push_response(response)
120
- if not push_request:
121
- return self._read_response(
122
- disable_decoding=disable_decoding, push_request=push_request
123
- )
124
- else:
122
+
123
+ # if this is a push request return the push response
124
+ if push_request:
125
125
  return response
126
+
127
+ return self._read_response(
128
+ disable_decoding=disable_decoding,
129
+ push_request=push_request,
130
+ )
126
131
  else:
127
132
  raise InvalidResponse(f"Protocol Error: {raw!r}")
128
133
 
129
134
  if isinstance(response, bytes) and disable_decoding is False:
130
135
  response = self.encoder.decode(response)
136
+
131
137
  return response
132
138
 
133
139