redis 5.2.0__tar.gz → 5.2.1__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 (151) hide show
  1. {redis-5.2.0/redis.egg-info → redis-5.2.1}/PKG-INFO +1 -1
  2. {redis-5.2.0 → redis-5.2.1}/redis/_parsers/helpers.py +11 -4
  3. {redis-5.2.0 → redis-5.2.1}/redis/asyncio/connection.py +7 -1
  4. {redis-5.2.0 → redis-5.2.1}/redis/asyncio/sentinel.py +1 -5
  5. {redis-5.2.0 → redis-5.2.1/redis.egg-info}/PKG-INFO +1 -1
  6. {redis-5.2.0 → redis-5.2.1}/setup.py +1 -1
  7. {redis-5.2.0 → redis-5.2.1}/tests/conftest.py +20 -18
  8. redis-5.2.1/tests/ssl_utils.py +43 -0
  9. {redis-5.2.0 → redis-5.2.1}/tests/test_asyncio/test_cluster.py +14 -22
  10. {redis-5.2.0 → redis-5.2.1}/tests/test_asyncio/test_connect.py +16 -13
  11. {redis-5.2.0 → redis-5.2.1}/tests/test_asyncio/test_cwe_404.py +2 -2
  12. {redis-5.2.0 → redis-5.2.1}/tests/test_asyncio/test_graph.py +20 -20
  13. {redis-5.2.0 → redis-5.2.1}/tests/test_asyncio/test_hash.py +7 -6
  14. {redis-5.2.0 → redis-5.2.1}/tests/test_asyncio/test_search.py +2 -0
  15. {redis-5.2.0 → redis-5.2.1}/tests/test_asyncio/test_sentinel.py +20 -0
  16. {redis-5.2.0 → redis-5.2.1}/tests/test_asyncio/test_timeseries.py +5 -0
  17. {redis-5.2.0 → redis-5.2.1}/tests/test_cluster.py +7 -6
  18. {redis-5.2.0 → redis-5.2.1}/tests/test_connect.py +14 -13
  19. {redis-5.2.0 → redis-5.2.1}/tests/test_graph.py +23 -23
  20. {redis-5.2.0 → redis-5.2.1}/tests/test_graph_utils/test_edge.py +4 -4
  21. {redis-5.2.0 → redis-5.2.1}/tests/test_graph_utils/test_node.py +3 -3
  22. {redis-5.2.0 → redis-5.2.1}/tests/test_graph_utils/test_path.py +5 -5
  23. {redis-5.2.0 → redis-5.2.1}/tests/test_hash.py +7 -6
  24. {redis-5.2.0 → redis-5.2.1}/tests/test_json.py +2 -2
  25. {redis-5.2.0 → redis-5.2.1}/tests/test_search.py +13 -0
  26. {redis-5.2.0 → redis-5.2.1}/tests/test_ssl.py +29 -26
  27. redis-5.2.0/tests/ssl_utils.py +0 -14
  28. {redis-5.2.0 → redis-5.2.1}/INSTALL +0 -0
  29. {redis-5.2.0 → redis-5.2.1}/LICENSE +0 -0
  30. {redis-5.2.0 → redis-5.2.1}/MANIFEST.in +0 -0
  31. {redis-5.2.0 → redis-5.2.1}/README.md +0 -0
  32. {redis-5.2.0 → redis-5.2.1}/redis/__init__.py +0 -0
  33. {redis-5.2.0 → redis-5.2.1}/redis/_parsers/__init__.py +0 -0
  34. {redis-5.2.0 → redis-5.2.1}/redis/_parsers/base.py +0 -0
  35. {redis-5.2.0 → redis-5.2.1}/redis/_parsers/commands.py +0 -0
  36. {redis-5.2.0 → redis-5.2.1}/redis/_parsers/encoders.py +0 -0
  37. {redis-5.2.0 → redis-5.2.1}/redis/_parsers/hiredis.py +0 -0
  38. {redis-5.2.0 → redis-5.2.1}/redis/_parsers/resp2.py +0 -0
  39. {redis-5.2.0 → redis-5.2.1}/redis/_parsers/resp3.py +0 -0
  40. {redis-5.2.0 → redis-5.2.1}/redis/_parsers/socket.py +0 -0
  41. {redis-5.2.0 → redis-5.2.1}/redis/asyncio/__init__.py +0 -0
  42. {redis-5.2.0 → redis-5.2.1}/redis/asyncio/client.py +0 -0
  43. {redis-5.2.0 → redis-5.2.1}/redis/asyncio/cluster.py +0 -0
  44. {redis-5.2.0 → redis-5.2.1}/redis/asyncio/lock.py +0 -0
  45. {redis-5.2.0 → redis-5.2.1}/redis/asyncio/retry.py +0 -0
  46. {redis-5.2.0 → redis-5.2.1}/redis/asyncio/utils.py +0 -0
  47. {redis-5.2.0 → redis-5.2.1}/redis/backoff.py +0 -0
  48. {redis-5.2.0 → redis-5.2.1}/redis/cache.py +0 -0
  49. {redis-5.2.0 → redis-5.2.1}/redis/client.py +0 -0
  50. {redis-5.2.0 → redis-5.2.1}/redis/cluster.py +0 -0
  51. {redis-5.2.0 → redis-5.2.1}/redis/commands/__init__.py +0 -0
  52. {redis-5.2.0 → redis-5.2.1}/redis/commands/bf/__init__.py +0 -0
  53. {redis-5.2.0 → redis-5.2.1}/redis/commands/bf/commands.py +0 -0
  54. {redis-5.2.0 → redis-5.2.1}/redis/commands/bf/info.py +0 -0
  55. {redis-5.2.0 → redis-5.2.1}/redis/commands/cluster.py +0 -0
  56. {redis-5.2.0 → redis-5.2.1}/redis/commands/core.py +0 -0
  57. {redis-5.2.0 → redis-5.2.1}/redis/commands/graph/__init__.py +0 -0
  58. {redis-5.2.0 → redis-5.2.1}/redis/commands/graph/commands.py +0 -0
  59. {redis-5.2.0 → redis-5.2.1}/redis/commands/graph/edge.py +0 -0
  60. {redis-5.2.0 → redis-5.2.1}/redis/commands/graph/exceptions.py +0 -0
  61. {redis-5.2.0 → redis-5.2.1}/redis/commands/graph/execution_plan.py +0 -0
  62. {redis-5.2.0 → redis-5.2.1}/redis/commands/graph/node.py +0 -0
  63. {redis-5.2.0 → redis-5.2.1}/redis/commands/graph/path.py +0 -0
  64. {redis-5.2.0 → redis-5.2.1}/redis/commands/graph/query_result.py +0 -0
  65. {redis-5.2.0 → redis-5.2.1}/redis/commands/helpers.py +0 -0
  66. {redis-5.2.0 → redis-5.2.1}/redis/commands/json/__init__.py +0 -0
  67. {redis-5.2.0 → redis-5.2.1}/redis/commands/json/_util.py +0 -0
  68. {redis-5.2.0 → redis-5.2.1}/redis/commands/json/commands.py +0 -0
  69. {redis-5.2.0 → redis-5.2.1}/redis/commands/json/decoders.py +0 -0
  70. {redis-5.2.0 → redis-5.2.1}/redis/commands/json/path.py +0 -0
  71. {redis-5.2.0 → redis-5.2.1}/redis/commands/redismodules.py +0 -0
  72. {redis-5.2.0 → redis-5.2.1}/redis/commands/search/__init__.py +0 -0
  73. {redis-5.2.0 → redis-5.2.1}/redis/commands/search/_util.py +0 -0
  74. {redis-5.2.0 → redis-5.2.1}/redis/commands/search/aggregation.py +0 -0
  75. {redis-5.2.0 → redis-5.2.1}/redis/commands/search/commands.py +0 -0
  76. {redis-5.2.0 → redis-5.2.1}/redis/commands/search/document.py +0 -0
  77. {redis-5.2.0 → redis-5.2.1}/redis/commands/search/field.py +0 -0
  78. {redis-5.2.0 → redis-5.2.1}/redis/commands/search/indexDefinition.py +0 -0
  79. {redis-5.2.0 → redis-5.2.1}/redis/commands/search/query.py +0 -0
  80. {redis-5.2.0 → redis-5.2.1}/redis/commands/search/querystring.py +0 -0
  81. {redis-5.2.0 → redis-5.2.1}/redis/commands/search/reducers.py +0 -0
  82. {redis-5.2.0 → redis-5.2.1}/redis/commands/search/result.py +0 -0
  83. {redis-5.2.0 → redis-5.2.1}/redis/commands/search/suggestion.py +0 -0
  84. {redis-5.2.0 → redis-5.2.1}/redis/commands/sentinel.py +0 -0
  85. {redis-5.2.0 → redis-5.2.1}/redis/commands/timeseries/__init__.py +0 -0
  86. {redis-5.2.0 → redis-5.2.1}/redis/commands/timeseries/commands.py +0 -0
  87. {redis-5.2.0 → redis-5.2.1}/redis/commands/timeseries/info.py +0 -0
  88. {redis-5.2.0 → redis-5.2.1}/redis/commands/timeseries/utils.py +0 -0
  89. {redis-5.2.0 → redis-5.2.1}/redis/connection.py +0 -0
  90. {redis-5.2.0 → redis-5.2.1}/redis/crc.py +0 -0
  91. {redis-5.2.0 → redis-5.2.1}/redis/credentials.py +0 -0
  92. {redis-5.2.0 → redis-5.2.1}/redis/exceptions.py +0 -0
  93. {redis-5.2.0 → redis-5.2.1}/redis/lock.py +0 -0
  94. {redis-5.2.0 → redis-5.2.1}/redis/ocsp.py +0 -0
  95. {redis-5.2.0 → redis-5.2.1}/redis/retry.py +0 -0
  96. {redis-5.2.0 → redis-5.2.1}/redis/sentinel.py +0 -0
  97. {redis-5.2.0 → redis-5.2.1}/redis/typing.py +0 -0
  98. {redis-5.2.0 → redis-5.2.1}/redis/utils.py +0 -0
  99. {redis-5.2.0 → redis-5.2.1}/redis.egg-info/SOURCES.txt +0 -0
  100. {redis-5.2.0 → redis-5.2.1}/redis.egg-info/dependency_links.txt +0 -0
  101. {redis-5.2.0 → redis-5.2.1}/redis.egg-info/requires.txt +0 -0
  102. {redis-5.2.0 → redis-5.2.1}/redis.egg-info/top_level.txt +0 -0
  103. {redis-5.2.0 → redis-5.2.1}/setup.cfg +0 -0
  104. {redis-5.2.0 → redis-5.2.1}/tests/__init__.py +0 -0
  105. {redis-5.2.0 → redis-5.2.1}/tests/mocks.py +0 -0
  106. {redis-5.2.0 → redis-5.2.1}/tests/test_asyncio/__init__.py +0 -0
  107. {redis-5.2.0 → redis-5.2.1}/tests/test_asyncio/compat.py +0 -0
  108. {redis-5.2.0 → redis-5.2.1}/tests/test_asyncio/conftest.py +0 -0
  109. {redis-5.2.0 → redis-5.2.1}/tests/test_asyncio/mocks.py +0 -0
  110. {redis-5.2.0 → redis-5.2.1}/tests/test_asyncio/test_bloom.py +0 -0
  111. {redis-5.2.0 → redis-5.2.1}/tests/test_asyncio/test_commands.py +0 -0
  112. {redis-5.2.0 → redis-5.2.1}/tests/test_asyncio/test_connection.py +0 -0
  113. {redis-5.2.0 → redis-5.2.1}/tests/test_asyncio/test_connection_pool.py +0 -0
  114. {redis-5.2.0 → redis-5.2.1}/tests/test_asyncio/test_credentials.py +0 -0
  115. {redis-5.2.0 → redis-5.2.1}/tests/test_asyncio/test_encoding.py +0 -0
  116. {redis-5.2.0 → redis-5.2.1}/tests/test_asyncio/test_json.py +0 -0
  117. {redis-5.2.0 → redis-5.2.1}/tests/test_asyncio/test_lock.py +0 -0
  118. {redis-5.2.0 → redis-5.2.1}/tests/test_asyncio/test_monitor.py +0 -0
  119. {redis-5.2.0 → redis-5.2.1}/tests/test_asyncio/test_pipeline.py +0 -0
  120. {redis-5.2.0 → redis-5.2.1}/tests/test_asyncio/test_pubsub.py +0 -0
  121. {redis-5.2.0 → redis-5.2.1}/tests/test_asyncio/test_retry.py +0 -0
  122. {redis-5.2.0 → redis-5.2.1}/tests/test_asyncio/test_scripting.py +0 -0
  123. {redis-5.2.0 → redis-5.2.1}/tests/test_asyncio/test_sentinel_managed_connection.py +0 -0
  124. {redis-5.2.0 → redis-5.2.1}/tests/test_asyncio/testdata/jsontestdata.py +0 -0
  125. {redis-5.2.0 → redis-5.2.1}/tests/test_asyncio/testdata/titles.csv +0 -0
  126. {redis-5.2.0 → redis-5.2.1}/tests/test_asyncio/testdata/will_play_text.csv.bz2 +0 -0
  127. {redis-5.2.0 → redis-5.2.1}/tests/test_bloom.py +0 -0
  128. {redis-5.2.0 → redis-5.2.1}/tests/test_cache.py +0 -0
  129. {redis-5.2.0 → redis-5.2.1}/tests/test_command_parser.py +0 -0
  130. {redis-5.2.0 → redis-5.2.1}/tests/test_commands.py +0 -0
  131. {redis-5.2.0 → redis-5.2.1}/tests/test_connection.py +0 -0
  132. {redis-5.2.0 → redis-5.2.1}/tests/test_connection_pool.py +0 -0
  133. {redis-5.2.0 → redis-5.2.1}/tests/test_credentials.py +0 -0
  134. {redis-5.2.0 → redis-5.2.1}/tests/test_encoding.py +0 -0
  135. {redis-5.2.0 → redis-5.2.1}/tests/test_function.py +0 -0
  136. {redis-5.2.0 → redis-5.2.1}/tests/test_graph_utils/__init__.py +0 -0
  137. {redis-5.2.0 → redis-5.2.1}/tests/test_helpers.py +0 -0
  138. {redis-5.2.0 → redis-5.2.1}/tests/test_lock.py +0 -0
  139. {redis-5.2.0 → redis-5.2.1}/tests/test_monitor.py +0 -0
  140. {redis-5.2.0 → redis-5.2.1}/tests/test_multiprocessing.py +0 -0
  141. {redis-5.2.0 → redis-5.2.1}/tests/test_parsers/test_helpers.py +0 -0
  142. {redis-5.2.0 → redis-5.2.1}/tests/test_pipeline.py +0 -0
  143. {redis-5.2.0 → redis-5.2.1}/tests/test_pubsub.py +0 -0
  144. {redis-5.2.0 → redis-5.2.1}/tests/test_retry.py +0 -0
  145. {redis-5.2.0 → redis-5.2.1}/tests/test_scripting.py +0 -0
  146. {redis-5.2.0 → redis-5.2.1}/tests/test_sentinel.py +0 -0
  147. {redis-5.2.0 → redis-5.2.1}/tests/test_timeseries.py +0 -0
  148. {redis-5.2.0 → redis-5.2.1}/tests/test_utils.py +0 -0
  149. {redis-5.2.0 → redis-5.2.1}/tests/testdata/jsontestdata.py +0 -0
  150. {redis-5.2.0 → redis-5.2.1}/tests/testdata/titles.csv +0 -0
  151. {redis-5.2.0 → redis-5.2.1}/tests/testdata/will_play_text.csv.bz2 +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: redis
3
- Version: 5.2.0
3
+ Version: 5.2.1
4
4
  Summary: Python client for Redis database and key-value store
5
5
  Home-page: https://github.com/redis/redis-py
6
6
  Author: Redis Inc.
@@ -396,13 +396,20 @@ def parse_slowlog_get(response, **options):
396
396
  # an O(N) complexity) instead of the command.
397
397
  if isinstance(item[3], list):
398
398
  result["command"] = space.join(item[3])
399
- result["client_address"] = item[4]
400
- result["client_name"] = item[5]
399
+
400
+ # These fields are optional, depends on environment.
401
+ if len(item) >= 6:
402
+ result["client_address"] = item[4]
403
+ result["client_name"] = item[5]
401
404
  else:
402
405
  result["complexity"] = item[3]
403
406
  result["command"] = space.join(item[4])
404
- result["client_address"] = item[5]
405
- result["client_name"] = item[6]
407
+
408
+ # These fields are optional, depends on environment.
409
+ if len(item) >= 7:
410
+ result["client_address"] = item[5]
411
+ result["client_name"] = item[6]
412
+
406
413
  return result
407
414
 
408
415
  return [parse_item(item) for item in response]
@@ -214,7 +214,13 @@ class AbstractConnection:
214
214
  _warnings.warn(
215
215
  f"unclosed Connection {self!r}", ResourceWarning, source=self
216
216
  )
217
- self._close()
217
+
218
+ try:
219
+ asyncio.get_running_loop()
220
+ self._close()
221
+ except RuntimeError:
222
+ # No actions been taken if pool already closed.
223
+ pass
218
224
 
219
225
  def _close(self):
220
226
  """
@@ -29,11 +29,7 @@ class SentinelManagedConnection(Connection):
29
29
  super().__init__(**kwargs)
30
30
 
31
31
  def __repr__(self):
32
- pool = self.connection_pool
33
- s = (
34
- f"<{self.__class__.__module__}.{self.__class__.__name__}"
35
- f"(service={pool.service_name}"
36
- )
32
+ s = f"<{self.__class__.__module__}.{self.__class__.__name__}"
37
33
  if self.host:
38
34
  host_info = f",host={self.host},port={self.port}"
39
35
  s += host_info
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: redis
3
- Version: 5.2.0
3
+ Version: 5.2.1
4
4
  Summary: Python client for Redis database and key-value store
5
5
  Home-page: https://github.com/redis/redis-py
6
6
  Author: Redis Inc.
@@ -8,7 +8,7 @@ setup(
8
8
  long_description_content_type="text/markdown",
9
9
  keywords=["Redis", "key-value store", "database"],
10
10
  license="MIT",
11
- version="5.2.0",
11
+ version="5.2.1",
12
12
  packages=find_packages(
13
13
  include=[
14
14
  "redis",
@@ -21,7 +21,7 @@ from redis.cache import (
21
21
  from redis.connection import Connection, ConnectionInterface, SSLConnection, parse_url
22
22
  from redis.exceptions import RedisClusterException
23
23
  from redis.retry import Retry
24
- from tests.ssl_utils import get_ssl_filename
24
+ from tests.ssl_utils import get_tls_certificates
25
25
 
26
26
  REDIS_INFO = {}
27
27
  default_redis_url = "redis://localhost:6379/0"
@@ -103,6 +103,13 @@ def pytest_addoption(parser):
103
103
  help="Redis connection string, defaults to `%(default)s`",
104
104
  )
105
105
 
106
+ parser.addoption(
107
+ "--redis-mod-url",
108
+ default=default_redismod_url,
109
+ action="store",
110
+ help="Redis with modules connection string, defaults to `%(default)s`",
111
+ )
112
+
106
113
  parser.addoption(
107
114
  "--protocol",
108
115
  default=default_protocol,
@@ -177,14 +184,14 @@ def pytest_sessionstart(session):
177
184
  REDIS_INFO["version"] = version
178
185
  REDIS_INFO["arch_bits"] = arch_bits
179
186
  REDIS_INFO["cluster_enabled"] = cluster_enabled
187
+ REDIS_INFO["tls_cert_subdir"] = "cluster" if cluster_enabled else "standalone"
180
188
  REDIS_INFO["enterprise"] = enterprise
181
189
  # store REDIS_INFO in config so that it is available from "condition strings"
182
190
  session.config.REDIS_INFO = REDIS_INFO
183
191
 
184
192
  # module info
185
- stack_url = redis_url
186
- if stack_url == default_redis_url:
187
- stack_url = default_redismod_url
193
+ stack_url = session.config.getoption("--redis-mod-url")
194
+
188
195
  try:
189
196
  stack_info = _get_info(stack_url)
190
197
  REDIS_INFO["modules"] = stack_info["modules"]
@@ -325,6 +332,9 @@ def _get_client(
325
332
  redis_url = request.config.getoption("--redis-url")
326
333
  else:
327
334
  redis_url = from_url
335
+
336
+ redis_tls_url = request.config.getoption("--redis-ssl-url")
337
+
328
338
  if "protocol" not in redis_url and kwargs.get("protocol") is None:
329
339
  kwargs["protocol"] = request.config.getoption("--protocol")
330
340
 
@@ -335,15 +345,11 @@ def _get_client(
335
345
  connection_class = Connection
336
346
  if ssl:
337
347
  connection_class = SSLConnection
338
- kwargs["ssl_certfile"] = get_ssl_filename("client-cert.pem")
339
- kwargs["ssl_keyfile"] = get_ssl_filename("client-key.pem")
340
- # When you try to assign "required" as single string
341
- # it assigns tuple instead of string.
342
- # Probably some reserved keyword
343
- # I can't explain how does it work -_-
344
- kwargs["ssl_cert_reqs"] = "require" + "d"
345
- kwargs["ssl_ca_certs"] = get_ssl_filename("ca-cert.pem")
346
- kwargs["port"] = 6666
348
+ kwargs["ssl_certfile"], kwargs["ssl_keyfile"], kwargs["ssl_ca_certs"] = (
349
+ get_tls_certificates()
350
+ )
351
+ kwargs["ssl_cert_reqs"] = "required"
352
+ kwargs["port"] = urlparse(redis_tls_url).port
347
353
  kwargs["connection_class"] = connection_class
348
354
  url_options.update(kwargs)
349
355
  pool = redis.ConnectionPool(**url_options)
@@ -393,11 +399,7 @@ def r(request):
393
399
 
394
400
  @pytest.fixture()
395
401
  def stack_url(request):
396
- stack_url = request.config.getoption("--redis-url", default=default_redismod_url)
397
- if stack_url == default_redis_url:
398
- return default_redismod_url
399
- else:
400
- return stack_url
402
+ return request.config.getoption("--redis-mod-url", default=default_redismod_url)
401
403
 
402
404
 
403
405
  @pytest.fixture()
@@ -0,0 +1,43 @@
1
+ import enum
2
+ import os
3
+ from collections import namedtuple
4
+
5
+ CLIENT_CERT_NAME = "client.crt"
6
+ CLIENT_KEY_NAME = "client.key"
7
+ SERVER_CERT_NAME = "redis.crt"
8
+ SERVER_KEY_NAME = "redis.key"
9
+ CA_CERT_NAME = "ca.crt"
10
+
11
+
12
+ class CertificateType(str, enum.Enum):
13
+ client = "client"
14
+ server = "server"
15
+
16
+
17
+ TLSFiles = namedtuple("TLSFiles", ["certfile", "keyfile", "ca_certfile"])
18
+
19
+
20
+ def get_tls_certificates(
21
+ subdir: str = "standalone",
22
+ cert_type: CertificateType = CertificateType.client,
23
+ ):
24
+ root = os.path.join(os.path.dirname(__file__), "..")
25
+ cert_subdir = ("dockers", subdir, "tls")
26
+ cert_dir = os.path.abspath(os.path.join(root, *cert_subdir))
27
+ if not os.path.isdir(cert_dir): # github actions package validation case
28
+ cert_dir = os.path.abspath(os.path.join(root, "..", *cert_subdir))
29
+ if not os.path.isdir(cert_dir):
30
+ raise OSError(f"No SSL certificates found. They should be in {cert_dir}")
31
+
32
+ if cert_type == CertificateType.client:
33
+ return TLSFiles(
34
+ os.path.join(cert_dir, CLIENT_CERT_NAME),
35
+ os.path.join(cert_dir, CLIENT_KEY_NAME),
36
+ os.path.join(cert_dir, CA_CERT_NAME),
37
+ )
38
+ elif cert_type == CertificateType.server:
39
+ return TLSFiles(
40
+ os.path.join(cert_dir, SERVER_CERT_NAME),
41
+ os.path.join(cert_dir, SERVER_KEY_NAME),
42
+ os.path.join(cert_dir, CA_CERT_NAME),
43
+ )
@@ -33,12 +33,11 @@ from tests.conftest import (
33
33
  assert_resp_response,
34
34
  is_resp2_connection,
35
35
  skip_if_redis_enterprise,
36
- skip_if_server_version_gte,
37
36
  skip_if_server_version_lt,
38
37
  skip_unless_arch_bits,
39
38
  )
40
39
 
41
- from ..ssl_utils import get_ssl_filename
40
+ from ..ssl_utils import get_tls_certificates
42
41
  from .compat import aclosing, mock
43
42
 
44
43
  pytestmark = pytest.mark.onlycluster
@@ -380,20 +379,22 @@ class TestRedisClusterObj:
380
379
  async with RedisCluster.from_url(url) as rc_default:
381
380
  # Test default retry
382
381
  retry = rc_default.connection_kwargs.get("retry")
382
+
383
+ # FIXME: Workaround for https://github.com/redis/redis-py/issues/3030
384
+ host = rc_default.get_default_node().host
385
+
383
386
  assert isinstance(retry, Retry)
384
387
  assert retry._retries == 3
385
388
  assert isinstance(retry._backoff, type(default_backoff()))
386
- assert rc_default.get_node("127.0.0.1", 16379).connection_kwargs.get(
389
+ assert rc_default.get_node(host, 16379).connection_kwargs.get(
387
390
  "retry"
388
- ) == rc_default.get_node("127.0.0.1", 16380).connection_kwargs.get("retry")
391
+ ) == rc_default.get_node(host, 16380).connection_kwargs.get("retry")
389
392
 
390
393
  retry = Retry(ExponentialBackoff(10, 5), 5)
391
394
  async with RedisCluster.from_url(url, retry=retry) as rc_custom_retry:
392
395
  # Test custom retry
393
396
  assert (
394
- rc_custom_retry.get_node("127.0.0.1", 16379).connection_kwargs.get(
395
- "retry"
396
- )
397
+ rc_custom_retry.get_node(host, 16379).connection_kwargs.get("retry")
397
398
  == retry
398
399
  )
399
400
 
@@ -402,9 +403,7 @@ class TestRedisClusterObj:
402
403
  ) as rc_no_retries:
403
404
  # Test no connection retries
404
405
  assert (
405
- rc_no_retries.get_node("127.0.0.1", 16379).connection_kwargs.get(
406
- "retry"
407
- )
406
+ rc_no_retries.get_node(host, 16379).connection_kwargs.get("retry")
408
407
  is None
409
408
  )
410
409
 
@@ -412,7 +411,7 @@ class TestRedisClusterObj:
412
411
  url, retry=Retry(NoBackoff(), 0)
413
412
  ) as rc_no_retries:
414
413
  assert (
415
- rc_no_retries.get_node("127.0.0.1", 16379)
414
+ rc_no_retries.get_node(host, 16379)
416
415
  .connection_kwargs.get("retry")
417
416
  ._retries
418
417
  == 0
@@ -493,8 +492,8 @@ class TestRedisClusterObj:
493
492
  Test command execution with nodes flag REPLICAS
494
493
  """
495
494
  replicas = r.get_replicas()
496
- if not replicas:
497
- r = await get_mocked_redis_client(default_host, default_port)
495
+ assert len(replicas) != 0, "This test requires Cluster with 1 replica"
496
+
498
497
  primaries = r.get_primaries()
499
498
  mock_all_nodes_resp(r, "PONG")
500
499
  assert await r.ping(target_nodes=RedisCluster.REPLICAS) is True
@@ -2804,7 +2803,6 @@ class TestClusterPipeline:
2804
2803
  assert ask_node._free.pop().read_response.await_count
2805
2804
  assert res == ["MOCK_OK"]
2806
2805
 
2807
- @skip_if_server_version_gte("7.0.0")
2808
2806
  async def test_moved_redirection_on_slave_with_default(
2809
2807
  self, r: RedisCluster
2810
2808
  ) -> None:
@@ -2824,11 +2822,7 @@ class TestClusterPipeline:
2824
2822
  async def parse_response(
2825
2823
  self, connection: Connection, command: str, **kwargs: Any
2826
2824
  ) -> Any:
2827
- if (
2828
- command == "GET"
2829
- and self.host != primary.host
2830
- and self.port != primary.port
2831
- ):
2825
+ if command == "GET" and self.port != primary.port:
2832
2826
  raise MovedError(moved_error)
2833
2827
 
2834
2828
  return await parse_response_orig(connection, command, **kwargs)
@@ -2899,9 +2893,7 @@ class TestSSL:
2899
2893
  appropriate port.
2900
2894
  """
2901
2895
 
2902
- CA_CERT = get_ssl_filename("ca-cert.pem")
2903
- CLIENT_CERT = get_ssl_filename("client-cert.pem")
2904
- CLIENT_KEY = get_ssl_filename("client-key.pem")
2896
+ CLIENT_CERT, CLIENT_KEY, CA_CERT = get_tls_certificates("cluster")
2905
2897
 
2906
2898
  @pytest_asyncio.fixture()
2907
2899
  def create_client(self, request: FixtureRequest) -> Callable[..., RedisCluster]:
@@ -11,7 +11,7 @@ from redis.asyncio.connection import (
11
11
  )
12
12
  from redis.exceptions import ConnectionError
13
13
 
14
- from ..ssl_utils import get_ssl_filename
14
+ from ..ssl_utils import CertificateType, get_tls_certificates
15
15
 
16
16
  _CLIENT_NAME = "test-suite-client"
17
17
  _CMD_SEP = b"\r\n"
@@ -57,19 +57,21 @@ async def test_uds_connect(uds_address):
57
57
  )
58
58
  async def test_tcp_ssl_tls12_custom_ciphers(tcp_address, ssl_ciphers):
59
59
  host, port = tcp_address
60
- certfile = get_ssl_filename("client-cert.pem")
61
- keyfile = get_ssl_filename("client-key.pem")
62
- ca_certfile = get_ssl_filename("ca-cert.pem")
60
+
61
+ server_certs = get_tls_certificates(cert_type=CertificateType.server)
62
+
63
63
  conn = SSLConnection(
64
64
  host=host,
65
65
  port=port,
66
66
  client_name=_CLIENT_NAME,
67
- ssl_ca_certs=ca_certfile,
67
+ ssl_ca_certs=server_certs.ca_certfile,
68
68
  socket_timeout=10,
69
69
  ssl_min_version=ssl.TLSVersion.TLSv1_2,
70
70
  ssl_ciphers=ssl_ciphers,
71
71
  )
72
- await _assert_connect(conn, tcp_address, certfile=certfile, keyfile=keyfile)
72
+ await _assert_connect(
73
+ conn, tcp_address, certfile=server_certs.certfile, keyfile=server_certs.keyfile
74
+ )
73
75
  await conn.disconnect()
74
76
 
75
77
 
@@ -86,18 +88,20 @@ async def test_tcp_ssl_tls12_custom_ciphers(tcp_address, ssl_ciphers):
86
88
  )
87
89
  async def test_tcp_ssl_connect(tcp_address, ssl_min_version):
88
90
  host, port = tcp_address
89
- certfile = get_ssl_filename("client-cert.pem")
90
- keyfile = get_ssl_filename("client-key.pem")
91
- ca_certfile = get_ssl_filename("ca-cert.pem")
91
+
92
+ server_certs = get_tls_certificates(cert_type=CertificateType.server)
93
+
92
94
  conn = SSLConnection(
93
95
  host=host,
94
96
  port=port,
95
97
  client_name=_CLIENT_NAME,
96
- ssl_ca_certs=ca_certfile,
98
+ ssl_ca_certs=server_certs.ca_certfile,
97
99
  socket_timeout=10,
98
100
  ssl_min_version=ssl_min_version,
99
101
  )
100
- await _assert_connect(conn, tcp_address, certfile=certfile, keyfile=keyfile)
102
+ await _assert_connect(
103
+ conn, tcp_address, certfile=server_certs.certfile, keyfile=server_certs.keyfile
104
+ )
101
105
  await conn.disconnect()
102
106
 
103
107
 
@@ -105,8 +109,7 @@ async def test_tcp_ssl_connect(tcp_address, ssl_min_version):
105
109
  @pytest.mark.skipif(not ssl.HAS_TLSv1_3, reason="requires TLSv1.3")
106
110
  async def test_tcp_ssl_version_mismatch(tcp_address):
107
111
  host, port = tcp_address
108
- certfile = get_ssl_filename("server-cert.pem")
109
- keyfile = get_ssl_filename("server-key.pem")
112
+ certfile, keyfile, _ = get_tls_certificates(cert_type=CertificateType.server)
110
113
  conn = SSLConnection(
111
114
  host=host,
112
115
  port=port,
@@ -208,7 +208,7 @@ async def test_cluster(master_host):
208
208
  port = cluster_port + i
209
209
  remapped = remap_base + i
210
210
  forward_addr = hostname, port
211
- proxy = DelayProxy(addr=("127.0.0.1", remapped), redis_addr=forward_addr)
211
+ proxy = DelayProxy(addr=(hostname, remapped), redis_addr=forward_addr)
212
212
  proxies.append(proxy)
213
213
 
214
214
  def all_clear():
@@ -233,7 +233,7 @@ async def test_cluster(master_host):
233
233
  await stack.enter_async_context(p)
234
234
 
235
235
  r = RedisCluster.from_url(
236
- f"redis://127.0.0.1:{remap_base}", address_remap=remap
236
+ f"redis://{hostname}:{remap_base}", address_remap=remap
237
237
  )
238
238
  try:
239
239
  await r.initialize()
@@ -12,7 +12,7 @@ async def decoded_r(create_redis, stack_url):
12
12
  return await create_redis(decode_responses=True, url="redis://localhost:6480")
13
13
 
14
14
 
15
- @pytest.mark.redismod
15
+ @pytest.mark.graph
16
16
  @skip_if_resp_version(3)
17
17
  async def test_bulk(decoded_r):
18
18
  with pytest.raises(NotImplementedError):
@@ -20,7 +20,7 @@ async def test_bulk(decoded_r):
20
20
  await decoded_r.graph().bulk(foo="bar!")
21
21
 
22
22
 
23
- @pytest.mark.redismod
23
+ @pytest.mark.graph
24
24
  @skip_if_resp_version(3)
25
25
  async def test_graph_creation(decoded_r: redis.Redis):
26
26
  graph = decoded_r.graph()
@@ -66,7 +66,7 @@ async def test_graph_creation(decoded_r: redis.Redis):
66
66
  await graph.delete()
67
67
 
68
68
 
69
- @pytest.mark.redismod
69
+ @pytest.mark.graph
70
70
  @skip_if_resp_version(3)
71
71
  async def test_array_functions(decoded_r: redis.Redis):
72
72
  graph = decoded_r.graph()
@@ -90,7 +90,7 @@ async def test_array_functions(decoded_r: redis.Redis):
90
90
  assert [a] == result.result_set[0][0]
91
91
 
92
92
 
93
- @pytest.mark.redismod
93
+ @pytest.mark.graph
94
94
  @skip_if_resp_version(3)
95
95
  async def test_path(decoded_r: redis.Redis):
96
96
  node0 = Node(node_id=0, label="L1")
@@ -111,7 +111,7 @@ async def test_path(decoded_r: redis.Redis):
111
111
  assert expected_results == result.result_set
112
112
 
113
113
 
114
- @pytest.mark.redismod
114
+ @pytest.mark.graph
115
115
  @skip_if_resp_version(3)
116
116
  async def test_param(decoded_r: redis.Redis):
117
117
  params = [1, 2.3, "str", True, False, None, [0, 1, 2]]
@@ -122,7 +122,7 @@ async def test_param(decoded_r: redis.Redis):
122
122
  assert expected_results == result.result_set
123
123
 
124
124
 
125
- @pytest.mark.redismod
125
+ @pytest.mark.graph
126
126
  @skip_if_resp_version(3)
127
127
  async def test_map(decoded_r: redis.Redis):
128
128
  query = "RETURN {a:1, b:'str', c:NULL, d:[1,2,3], e:True, f:{x:1, y:2}}"
@@ -140,7 +140,7 @@ async def test_map(decoded_r: redis.Redis):
140
140
  assert actual == expected
141
141
 
142
142
 
143
- @pytest.mark.redismod
143
+ @pytest.mark.graph
144
144
  @skip_if_resp_version(3)
145
145
  async def test_point(decoded_r: redis.Redis):
146
146
  query = "RETURN point({latitude: 32.070794860, longitude: 34.820751118})"
@@ -158,7 +158,7 @@ async def test_point(decoded_r: redis.Redis):
158
158
  assert abs(actual["longitude"] - expected_lon) < 0.001
159
159
 
160
160
 
161
- @pytest.mark.redismod
161
+ @pytest.mark.graph
162
162
  @skip_if_resp_version(3)
163
163
  async def test_index_response(decoded_r: redis.Redis):
164
164
  result_set = await decoded_r.graph().query("CREATE INDEX ON :person(age)")
@@ -174,7 +174,7 @@ async def test_index_response(decoded_r: redis.Redis):
174
174
  await decoded_r.graph().query("DROP INDEX ON :person(age)")
175
175
 
176
176
 
177
- @pytest.mark.redismod
177
+ @pytest.mark.graph
178
178
  @skip_if_resp_version(3)
179
179
  async def test_stringify_query_result(decoded_r: redis.Redis):
180
180
  graph = decoded_r.graph()
@@ -229,7 +229,7 @@ async def test_stringify_query_result(decoded_r: redis.Redis):
229
229
  await graph.delete()
230
230
 
231
231
 
232
- @pytest.mark.redismod
232
+ @pytest.mark.graph
233
233
  @skip_if_resp_version(3)
234
234
  async def test_optional_match(decoded_r: redis.Redis):
235
235
  # Build a graph of form (a)-[R]->(b)
@@ -255,7 +255,7 @@ async def test_optional_match(decoded_r: redis.Redis):
255
255
  await graph.delete()
256
256
 
257
257
 
258
- @pytest.mark.redismod
258
+ @pytest.mark.graph
259
259
  @skip_if_resp_version(3)
260
260
  async def test_cached_execution(decoded_r: redis.Redis):
261
261
  await decoded_r.graph().query("CREATE ()")
@@ -276,7 +276,7 @@ async def test_cached_execution(decoded_r: redis.Redis):
276
276
  assert cached_result.cached_execution
277
277
 
278
278
 
279
- @pytest.mark.redismod
279
+ @pytest.mark.graph
280
280
  @skip_if_resp_version(3)
281
281
  async def test_slowlog(decoded_r: redis.Redis):
282
282
  create_query = """CREATE
@@ -291,7 +291,7 @@ async def test_slowlog(decoded_r: redis.Redis):
291
291
 
292
292
 
293
293
  @pytest.mark.xfail(strict=False)
294
- @pytest.mark.redismod
294
+ @pytest.mark.graph
295
295
  @skip_if_resp_version(3)
296
296
  async def test_query_timeout(decoded_r: redis.Redis):
297
297
  # Build a sample graph with 1000 nodes.
@@ -306,7 +306,7 @@ async def test_query_timeout(decoded_r: redis.Redis):
306
306
  assert False is False
307
307
 
308
308
 
309
- @pytest.mark.redismod
309
+ @pytest.mark.graph
310
310
  @skip_if_resp_version(3)
311
311
  async def test_read_only_query(decoded_r: redis.Redis):
312
312
  with pytest.raises(Exception):
@@ -316,7 +316,7 @@ async def test_read_only_query(decoded_r: redis.Redis):
316
316
  assert False is False
317
317
 
318
318
 
319
- @pytest.mark.redismod
319
+ @pytest.mark.graph
320
320
  @skip_if_resp_version(3)
321
321
  async def test_profile(decoded_r: redis.Redis):
322
322
  q = """UNWIND range(1, 3) AS x CREATE (p:Person {v:x})"""
@@ -333,7 +333,7 @@ async def test_profile(decoded_r: redis.Redis):
333
333
 
334
334
 
335
335
  @skip_if_redis_enterprise()
336
- @pytest.mark.redismod
336
+ @pytest.mark.graph
337
337
  @skip_if_resp_version(3)
338
338
  async def test_config(decoded_r: redis.Redis):
339
339
  config_name = "RESULTSET_SIZE"
@@ -366,7 +366,7 @@ async def test_config(decoded_r: redis.Redis):
366
366
 
367
367
 
368
368
  @pytest.mark.onlynoncluster
369
- @pytest.mark.redismod
369
+ @pytest.mark.graph
370
370
  @skip_if_resp_version(3)
371
371
  async def test_list_keys(decoded_r: redis.Redis):
372
372
  result = await decoded_r.graph().list_keys()
@@ -390,7 +390,7 @@ async def test_list_keys(decoded_r: redis.Redis):
390
390
  assert result == []
391
391
 
392
392
 
393
- @pytest.mark.redismod
393
+ @pytest.mark.graph
394
394
  @skip_if_resp_version(3)
395
395
  async def test_multi_label(decoded_r: redis.Redis):
396
396
  redis_graph = decoded_r.graph("g")
@@ -417,7 +417,7 @@ async def test_multi_label(decoded_r: redis.Redis):
417
417
  assert True
418
418
 
419
419
 
420
- @pytest.mark.redismod
420
+ @pytest.mark.graph
421
421
  @skip_if_resp_version(3)
422
422
  async def test_execution_plan(decoded_r: redis.Redis):
423
423
  redis_graph = decoded_r.graph("execution_plan")
@@ -437,7 +437,7 @@ async def test_execution_plan(decoded_r: redis.Redis):
437
437
  await redis_graph.delete()
438
438
 
439
439
 
440
- @pytest.mark.redismod
440
+ @pytest.mark.graph
441
441
  @skip_if_resp_version(3)
442
442
  async def test_explain(decoded_r: redis.Redis):
443
443
  redis_graph = decoded_r.graph("execution_plan")
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ import math
2
3
  from datetime import datetime, timedelta
3
4
 
4
5
  from tests.conftest import skip_if_server_version_lt
@@ -128,9 +129,9 @@ async def test_hpexpire_multiple_fields(r):
128
129
  async def test_hexpireat_basic(r):
129
130
  await r.delete("test:hash")
130
131
  await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"})
131
- exp_time = int((datetime.now() + timedelta(seconds=1)).timestamp())
132
+ exp_time = math.ceil((datetime.now() + timedelta(seconds=1)).timestamp())
132
133
  assert await r.hexpireat("test:hash", exp_time, "field1") == [1]
133
- await asyncio.sleep(1.1)
134
+ await asyncio.sleep(2.1)
134
135
  assert await r.hexists("test:hash", "field1") is False
135
136
  assert await r.hexists("test:hash", "field2") is True
136
137
 
@@ -139,9 +140,9 @@ async def test_hexpireat_basic(r):
139
140
  async def test_hexpireat_with_datetime(r):
140
141
  await r.delete("test:hash")
141
142
  await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"})
142
- exp_time = datetime.now() + timedelta(seconds=1)
143
+ exp_time = (datetime.now() + timedelta(seconds=2)).replace(microsecond=0)
143
144
  assert await r.hexpireat("test:hash", exp_time, "field1") == [1]
144
- await asyncio.sleep(1.1)
145
+ await asyncio.sleep(2.1)
145
146
  assert await r.hexists("test:hash", "field1") is False
146
147
  assert await r.hexists("test:hash", "field2") is True
147
148
 
@@ -175,9 +176,9 @@ async def test_hexpireat_multiple_fields(r):
175
176
  "test:hash",
176
177
  mapping={"field1": "value1", "field2": "value2", "field3": "value3"},
177
178
  )
178
- exp_time = int((datetime.now() + timedelta(seconds=1)).timestamp())
179
+ exp_time = math.ceil((datetime.now() + timedelta(seconds=1)).timestamp())
179
180
  assert await r.hexpireat("test:hash", exp_time, "field1", "field2") == [1, 1]
180
- await asyncio.sleep(1.5)
181
+ await asyncio.sleep(2.1)
181
182
  assert await r.hexists("test:hash", "field1") is False
182
183
  assert await r.hexists("test:hash", "field2") is False
183
184
  assert await r.hexists("test:hash", "field3") is True
@@ -1025,6 +1025,8 @@ async def test_phonetic_matcher(decoded_r: redis.Redis):
1025
1025
 
1026
1026
  @pytest.mark.redismod
1027
1027
  @pytest.mark.onlynoncluster
1028
+ # NOTE(imalinovskyi): This test contains hardcoded scores valid only for RediSearch 2.8+
1029
+ @skip_ifmodversion_lt("2.8.0", "search")
1028
1030
  async def test_scorer(decoded_r: redis.Redis):
1029
1031
  await decoded_r.ft().create_index((TextField("description"),))
1030
1032
 
@@ -264,3 +264,23 @@ async def test_auto_close_pool(cluster, sentinel, method_name):
264
264
 
265
265
  assert calls == 1
266
266
  await pool.disconnect()
267
+
268
+
269
+ @pytest.mark.onlynoncluster
270
+ async def test_repr_correctly_represents_connection_object(sentinel):
271
+ pool = SentinelConnectionPool("mymaster", sentinel)
272
+ connection = await pool.get_connection("PING")
273
+
274
+ assert (
275
+ str(connection)
276
+ == "<redis.asyncio.sentinel.SentinelManagedConnection,host=127.0.0.1,port=6379)>" # noqa: E501
277
+ )
278
+ assert connection.connection_pool == pool
279
+ await pool.release(connection)
280
+
281
+ del pool
282
+
283
+ assert (
284
+ str(connection)
285
+ == "<redis.asyncio.sentinel.SentinelManagedConnection,host=127.0.0.1,port=6379)>" # noqa: E501
286
+ )