txredisapi 1.4.12__tar.gz → 1.6.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 (44) hide show
  1. {txredisapi-1.4.12/txredisapi.egg-info → txredisapi-1.6.0}/PKG-INFO +15 -5
  2. {txredisapi-1.4.12 → txredisapi-1.6.0}/setup.py +3 -3
  3. {txredisapi-1.4.12 → txredisapi-1.6.0}/tests/test_bitops.py +12 -14
  4. {txredisapi-1.4.12 → txredisapi-1.6.0}/tests/test_connection_charset.py +3 -5
  5. {txredisapi-1.4.12 → txredisapi-1.6.0}/tests/test_hash_ops.py +2 -4
  6. txredisapi-1.6.0/tests/test_ipv6.py +98 -0
  7. {txredisapi-1.4.12 → txredisapi-1.6.0}/tests/test_multibulk.py +2 -4
  8. {txredisapi-1.4.12 → txredisapi-1.6.0}/tests/test_number_conversions.py +1 -3
  9. {txredisapi-1.4.12 → txredisapi-1.6.0}/tests/test_pipelining.py +2 -4
  10. {txredisapi-1.4.12 → txredisapi-1.6.0}/tests/test_protocol.py +4 -6
  11. txredisapi-1.6.0/tests/test_reconnect.py +244 -0
  12. {txredisapi-1.4.12 → txredisapi-1.6.0}/tests/test_scripting.py +0 -2
  13. {txredisapi-1.4.12 → txredisapi-1.6.0}/tests/test_sentinel.py +74 -13
  14. txredisapi-1.6.0/tests/test_ssl.py +101 -0
  15. {txredisapi-1.4.12 → txredisapi-1.6.0}/tests/test_subscriber.py +7 -6
  16. {txredisapi-1.4.12 → txredisapi-1.6.0/txredisapi.egg-info}/PKG-INFO +15 -5
  17. {txredisapi-1.4.12 → txredisapi-1.6.0}/txredisapi.egg-info/SOURCES.txt +3 -0
  18. txredisapi-1.6.0/txredisapi.egg-info/requires.txt +1 -0
  19. {txredisapi-1.4.12 → txredisapi-1.6.0}/txredisapi.py +429 -161
  20. txredisapi-1.4.12/txredisapi.egg-info/requires.txt +0 -2
  21. {txredisapi-1.4.12 → txredisapi-1.6.0}/LICENSE +0 -0
  22. {txredisapi-1.4.12 → txredisapi-1.6.0}/MANIFEST.in +0 -0
  23. {txredisapi-1.4.12 → txredisapi-1.6.0}/README.md +0 -0
  24. {txredisapi-1.4.12 → txredisapi-1.6.0}/setup.cfg +0 -0
  25. {txredisapi-1.4.12 → txredisapi-1.6.0}/tests/__init__.py +0 -0
  26. {txredisapi-1.4.12 → txredisapi-1.6.0}/tests/mixins.py +0 -0
  27. {txredisapi-1.4.12 → txredisapi-1.6.0}/tests/test_basics.py +0 -0
  28. {txredisapi-1.4.12 → txredisapi-1.6.0}/tests/test_blocking.py +0 -0
  29. {txredisapi-1.4.12 → txredisapi-1.6.0}/tests/test_cancel.py +0 -0
  30. {txredisapi-1.4.12 → txredisapi-1.6.0}/tests/test_connection.py +0 -0
  31. {txredisapi-1.4.12 → txredisapi-1.6.0}/tests/test_hyperloglog.py +0 -0
  32. {txredisapi-1.4.12 → txredisapi-1.6.0}/tests/test_implicit_pipelining.py +0 -0
  33. {txredisapi-1.4.12 → txredisapi-1.6.0}/tests/test_list_ops.py +0 -0
  34. {txredisapi-1.4.12 → txredisapi-1.6.0}/tests/test_operations.py +0 -0
  35. {txredisapi-1.4.12 → txredisapi-1.6.0}/tests/test_publish.py +0 -0
  36. {txredisapi-1.4.12 → txredisapi-1.6.0}/tests/test_scan.py +0 -0
  37. {txredisapi-1.4.12 → txredisapi-1.6.0}/tests/test_sets.py +0 -0
  38. {txredisapi-1.4.12 → txredisapi-1.6.0}/tests/test_sort.py +0 -0
  39. {txredisapi-1.4.12 → txredisapi-1.6.0}/tests/test_sortedsets.py +0 -0
  40. {txredisapi-1.4.12 → txredisapi-1.6.0}/tests/test_transactions.py +0 -0
  41. {txredisapi-1.4.12 → txredisapi-1.6.0}/tests/test_unix_connection.py +0 -0
  42. {txredisapi-1.4.12 → txredisapi-1.6.0}/tests/test_watch.py +0 -0
  43. {txredisapi-1.4.12 → txredisapi-1.6.0}/txredisapi.egg-info/dependency_links.txt +0 -0
  44. {txredisapi-1.4.12 → txredisapi-1.6.0}/txredisapi.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: txredisapi
3
- Version: 1.4.12
3
+ Version: 1.6.0
4
4
  Summary: non-blocking redis client for python
5
5
  Home-page: http://github.com/IlyaSkriblovsky/txredisapi
6
6
  Author: Alexandre Fiori
@@ -11,7 +11,6 @@ Classifier: Intended Audience :: Developers
11
11
  Classifier: Operating System :: OS Independent
12
12
  Classifier: License :: OSI Approved :: Apache Software License
13
13
  Classifier: Programming Language :: Python
14
- Classifier: Programming Language :: Python :: 2.7
15
14
  Classifier: Programming Language :: Python :: 3.5
16
15
  Classifier: Programming Language :: Python :: 3.6
17
16
  Classifier: Programming Language :: Python :: 3.7
@@ -20,10 +19,21 @@ Classifier: Programming Language :: Python :: 3.9
20
19
  Classifier: Programming Language :: Python :: 3.10
21
20
  Classifier: Programming Language :: Python :: 3.11
22
21
  Classifier: Programming Language :: Python :: Implementation :: PyPy
22
+ Requires-Python: >=3.5
23
23
  Description-Content-Type: text/markdown
24
24
  License-File: LICENSE
25
- Requires-Dist: twisted
26
- Requires-Dist: six
25
+ Requires-Dist: twisted[tls]>=18.7.0
26
+ Dynamic: author
27
+ Dynamic: author-email
28
+ Dynamic: classifier
29
+ Dynamic: description
30
+ Dynamic: description-content-type
31
+ Dynamic: home-page
32
+ Dynamic: license
33
+ Dynamic: license-file
34
+ Dynamic: requires-dist
35
+ Dynamic: requires-python
36
+ Dynamic: summary
27
37
 
28
38
  txredisapi
29
39
  ==========
@@ -23,9 +23,10 @@ long_description = (this_directory / "README.md").read_text()
23
23
 
24
24
  setuptools.setup(
25
25
  name="txredisapi",
26
- version="1.4.12",
26
+ version="1.6.0",
27
27
  py_modules=["txredisapi"],
28
- install_requires=["twisted", "six"],
28
+ install_requires=["twisted[tls]>=18.7.0"],
29
+ python_requires=">=3.5",
29
30
  author="Alexandre Fiori",
30
31
  author_email="fiorix@gmail.com",
31
32
  url="http://github.com/IlyaSkriblovsky/txredisapi",
@@ -39,7 +40,6 @@ setuptools.setup(
39
40
  'Operating System :: OS Independent',
40
41
  'License :: OSI Approved :: Apache Software License',
41
42
  'Programming Language :: Python',
42
- 'Programming Language :: Python :: 2.7',
43
43
  'Programming Language :: Python :: 3.5',
44
44
  'Programming Language :: Python :: 3.6',
45
45
  'Programming Language :: Python :: 3.7',
@@ -13,8 +13,6 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
 
16
- import six
17
-
18
16
  import sys
19
17
  import operator
20
18
 
@@ -48,7 +46,7 @@ class TestBitOps(unittest.TestCase, Redis26CheckMixin):
48
46
  @defer.inlineCallbacks
49
47
  def test_getbit(self):
50
48
  key = self._KEYS[0]
51
- yield self.db.set(key, six.b('\xaa'))
49
+ yield self.db.set(key, b'\xaa')
52
50
  l = [1, 0, 1, 0, 1, 0, 1, 0]
53
51
  for x in range(8):
54
52
  r = yield self.db.getbit(key, x)
@@ -86,30 +84,30 @@ class TestBitOps(unittest.TestCase, Redis26CheckMixin):
86
84
  def test_bitop_not(self):
87
85
  return self._test_bitop([operator.__not__, operator.not_,
88
86
  'not', 'NOT', 'NoT'],
89
- six.b('\x0f\x0f\x0f\x0f'),
87
+ b'\x0f\x0f\x0f\x0f',
90
88
  None,
91
- six.b('\xf0\xf0\xf0\xf0'))
89
+ b'\xf0\xf0\xf0\xf0')
92
90
 
93
91
  def test_bitop_or(self):
94
92
  return self._test_bitop([operator.__or__, operator.or_,
95
93
  'or', 'OR', 'oR'],
96
- six.b('\x0f\x0f\x0f\x0f'),
97
- six.b('\xf0\xf0\xf0\xf0'),
98
- six.b('\xff\xff\xff\xff'))
94
+ b'\x0f\x0f\x0f\x0f',
95
+ b'\xf0\xf0\xf0\xf0',
96
+ b'\xff\xff\xff\xff')
99
97
 
100
98
  def test_bitop_and(self):
101
99
  return self._test_bitop([operator.__and__, operator.and_,
102
100
  'and', 'AND', 'AnD'],
103
- six.b('\x0f\x0f\x0f\x0f'),
104
- six.b('\xf0\xf0\xf0\xf0'),
105
- six.b('\x00\x00\x00\x00'))
101
+ b'\x0f\x0f\x0f\x0f',
102
+ b'\xf0\xf0\xf0\xf0',
103
+ b'\x00\x00\x00\x00')
106
104
 
107
105
  def test_bitop_xor(self):
108
106
  return self._test_bitop([operator.__xor__, operator.xor,
109
107
  'xor', 'XOR', 'XoR'],
110
- six.b('\x9c\x9c\x9c\x9c'),
111
- six.b('\x6c\x6c\x6c\x6c'),
112
- six.b('\xf0\xf0\xf0\xf0'))
108
+ b'\x9c\x9c\x9c\x9c',
109
+ b'\x6c\x6c\x6c\x6c',
110
+ b'\xf0\xf0\xf0\xf0')
113
111
 
114
112
  def test_bitop_invalid(self):
115
113
  self.assertFailure(self.db.bitop('test', 'test', 'test'),
@@ -13,8 +13,6 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
 
16
- import six
17
-
18
16
  from twisted.internet import defer
19
17
  from twisted.trial import unittest
20
18
 
@@ -25,7 +23,7 @@ from tests.mixins import REDIS_HOST, REDIS_PORT
25
23
 
26
24
  class TestConnectionCharset(unittest.TestCase):
27
25
  TEST_KEY = 'txredisapi:test_key'
28
- TEST_VALUE_UNICODE = six.text_type('\u262d' * 3)
26
+ TEST_VALUE_UNICODE = str('\u262d' * 3)
29
27
  TEST_VALUE_BINARY = b'\x00\x01' * 3
30
28
 
31
29
  @defer.inlineCallbacks
@@ -34,7 +32,7 @@ class TestConnectionCharset(unittest.TestCase):
34
32
 
35
33
  yield db.set(self.TEST_KEY, self.TEST_VALUE_BINARY)
36
34
  result = yield db.get(self.TEST_KEY)
37
- self.assertTrue(type(result) == six.binary_type)
35
+ self.assertTrue(type(result) == bytes)
38
36
  self.assertEqual(result, self.TEST_VALUE_BINARY)
39
37
 
40
38
  yield db.delete(self.TEST_KEY)
@@ -47,7 +45,7 @@ class TestConnectionCharset(unittest.TestCase):
47
45
  yield db.set(self.TEST_KEY, self.TEST_VALUE_UNICODE)
48
46
  result = yield db.get(self.TEST_KEY)
49
47
  self.assertEqual(result, self.TEST_VALUE_UNICODE)
50
- self.assertTrue(type(result) == six.text_type)
48
+ self.assertTrue(type(result) == str)
51
49
 
52
50
  yield db.delete(self.TEST_KEY)
53
51
  yield db.disconnect()
@@ -13,8 +13,6 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
 
16
- import six
17
-
18
16
  from twisted.internet import defer
19
17
  from twisted.trial import unittest
20
18
 
@@ -58,8 +56,8 @@ class TestRedisHashOperations(unittest.TestCase):
58
56
  t_dict['key2'] = 'dos'
59
57
  yield db.hmset("txredisapi:HKeysHVals", t_dict)
60
58
 
61
- vs_u = [six.text_type(v) for v in t_dict.values()]
62
- ks_u = [six.text_type(k) for k in t_dict.keys()]
59
+ vs_u = [str(v) for v in t_dict.values()]
60
+ ks_u = [str(k) for k in t_dict.keys()]
63
61
  k_res = yield db.hkeys("txredisapi:HKeysHVals")
64
62
  v_res = yield db.hvals("txredisapi:HKeysHVals")
65
63
  self.assertEqual(sorted(ks_u), sorted(k_res))
@@ -0,0 +1,98 @@
1
+ # coding: utf-8
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from twisted.internet import defer, reactor
16
+ from twisted.internet.address import IPv6Address
17
+ from twisted.internet.error import CannotListenError
18
+ from twisted.internet.interfaces import IHostnameResolver, IHostResolution
19
+ from twisted.trial import unittest
20
+ from zope.interface import implementer
21
+
22
+ import txredisapi as redis
23
+
24
+ from tests.test_sentinel import FakeRedisFactory
25
+
26
+
27
+ @implementer(IHostResolution)
28
+ class Resolution(object):
29
+ def __init__(self, name):
30
+ self.name = name
31
+
32
+ def cancel(self):
33
+ pass
34
+
35
+
36
+ @implementer(IHostnameResolver)
37
+ class IPv6OnlyResolver(object):
38
+ """
39
+ Resolves a single hostname to a single IPv6 address and knows nothing
40
+ else, just like a name that has an AAAA record and no A record.
41
+ """
42
+
43
+ def __init__(self, hostname, address="::1"):
44
+ self.hostname = hostname
45
+ self.address = address
46
+
47
+ def resolveHostName(self, resolutionReceiver, hostName, portNumber=0,
48
+ addressTypes=None, transportSemantics="TCP"):
49
+ resolution = Resolution(hostName)
50
+ resolutionReceiver.resolutionBegan(resolution)
51
+ if hostName == self.hostname and \
52
+ (addressTypes is None or IPv6Address in addressTypes):
53
+ resolutionReceiver.addressResolved(
54
+ IPv6Address("TCP", self.address, portNumber))
55
+ resolutionReceiver.resolutionComplete()
56
+ return resolution
57
+
58
+
59
+ class TestIPv6Connections(unittest.TestCase):
60
+ """
61
+ Servers reachable over IPv6 only must be reachable as well: names are
62
+ resolved with getaddrinfo(), not with the IPv4-only gethostbyname().
63
+ """
64
+
65
+ hostname = "ipv6-only.invalid"
66
+
67
+ # connecting to the loopback is instant; don't sit through trial's default
68
+ # timeout if it ever stops working
69
+ timeout = 15
70
+
71
+ def setUp(self):
72
+ try:
73
+ self.listener = reactor.listenTCP(0, FakeRedisFactory(),
74
+ interface="::1")
75
+ except CannotListenError:
76
+ raise unittest.SkipTest("no IPv6 loopback available")
77
+
78
+ self.port = self.listener.getHost().port
79
+ self.addCleanup(self.listener.stopListening)
80
+
81
+ @defer.inlineCallbacks
82
+ def test_aaaa_only_hostname(self):
83
+ previous = reactor.installNameResolver(IPv6OnlyResolver(self.hostname))
84
+ self.addCleanup(reactor.installNameResolver, previous)
85
+
86
+ db = yield redis.Connection(self.hostname, self.port, reconnect=False)
87
+ self.addCleanup(db.disconnect)
88
+
89
+ role = yield db.role()
90
+ self.assertEqual(role[0], "master")
91
+
92
+ @defer.inlineCallbacks
93
+ def test_ipv6_literal(self):
94
+ db = yield redis.Connection("::1", self.port, reconnect=False)
95
+ self.addCleanup(db.disconnect)
96
+
97
+ role = yield db.role()
98
+ self.assertEqual(role[0], "master")
@@ -13,8 +13,6 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
 
16
- import six
17
-
18
16
  import os
19
17
  import base64
20
18
 
@@ -61,7 +59,7 @@ class LargeMultiBulk(unittest.TestCase):
61
59
  @defer.inlineCallbacks
62
60
  def test_bulk_numeric(self):
63
61
  test_values = [
64
- six.b(''), six.b('.hello'), six.b('+world'), six.b('123test'),
62
+ b'', b'.hello', b'+world', b'123test',
65
63
  +1, 0.1, 0.01, -0.1, 0, -10]
66
64
  for v in test_values:
67
65
  yield self.db.set(self._KEY, v)
@@ -76,7 +74,7 @@ class LargeMultiBulk(unittest.TestCase):
76
74
  if there's a '.' in the string.
77
75
  This test is to ensure this behavior isn't broken in the future.
78
76
  '''
79
- values = [six.b('+inf'), six.b('-inf'), six.b('NaN')]
77
+ values = [b'+inf', b'-inf', b'NaN']
80
78
  for x in values:
81
79
  yield self.db.set(self._KEY, x)
82
80
  r = yield self.db.get(self._KEY)
@@ -13,8 +13,6 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
 
16
- import six
17
-
18
16
  import txredisapi as redis
19
17
  from twisted.internet import defer
20
18
  from twisted.trial import unittest
@@ -58,7 +56,7 @@ class TestNumberConversions(unittest.TestCase):
58
56
  expected = format(k, "f")
59
57
  else:
60
58
  expected = str(k)
61
- self.assertIsInstance(result, six.string_types)
59
+ self.assertIsInstance(result, str)
62
60
  self.assertEqual(result, expected)
63
61
 
64
62
  @defer.inlineCallbacks
@@ -15,8 +15,6 @@
15
15
 
16
16
  import sys
17
17
 
18
- import six
19
-
20
18
  from twisted.trial import unittest
21
19
  from twisted.internet import defer
22
20
  from twisted.python import log
@@ -71,8 +69,8 @@ class TestRedisConnections(unittest.TestCase):
71
69
 
72
70
  # Make sure that all SET commands were sent in a single pipelined write.
73
71
  write_history = pipeline.transport.write_history
74
- lines_in_first_write = write_history[0].split(six.b("\n"))
75
- sets_in_first_write = sum(1 for w in lines_in_first_write if six.b("SET") in w)
72
+ lines_in_first_write = write_history[0].split(b"\n")
73
+ sets_in_first_write = sum(1 for w in lines_in_first_write if b"SET" in w)
76
74
  self.assertEqual(sets_in_first_write, 3)
77
75
 
78
76
  @defer.inlineCallbacks
@@ -13,8 +13,6 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
 
16
- import six
17
-
18
16
  import txredisapi as redis
19
17
 
20
18
  from twisted.trial import unittest
@@ -36,7 +34,7 @@ class LineReceiverSubclass(redis.LineReceiver):
36
34
 
37
35
 
38
36
  class TestLineReciever(unittest.TestCase):
39
- S = six.b('TEST')
37
+ S = b'TEST'
40
38
 
41
39
  def setUp(self):
42
40
  self.proto = LineReceiverSubclass()
@@ -47,14 +45,14 @@ class TestLineReciever(unittest.TestCase):
47
45
 
48
46
  def test_excess_line_length(self):
49
47
  self.assertTrue(self.transport.connected)
50
- self.proto.dataReceived(six.b('\x00') * (self.proto.MAX_LENGTH + 1))
48
+ self.proto.dataReceived(b'\x00' * (self.proto.MAX_LENGTH + 1))
51
49
  self.assertFalse(self.transport.connected)
52
50
 
53
51
  def test_excess_delimited_line(self):
54
52
  self.assertTrue(self.transport.connected)
55
53
  self.proto.dataReceived(self.S + self.proto.delimiter)
56
54
  self.assertEqual(self.proto._rcvd_line, self.S.decode())
57
- s = (six.b('\x00') * (self.proto.MAX_LENGTH + 1)) + self.proto.delimiter
55
+ s = (b'\x00' * (self.proto.MAX_LENGTH + 1)) + self.proto.delimiter
58
56
  self.proto._rcvd_line = None
59
57
  self.proto.dataReceived(s)
60
58
  self.assertFalse(self.transport.connected)
@@ -94,4 +92,4 @@ class TestBaseRedisProtocol(unittest.TestCase):
94
92
 
95
93
  def test_build_ping(self):
96
94
  s = self._protocol._build_command("PING")
97
- self.assertEqual(s, six.b('*1\r\n$4\r\nPING\r\n'))
95
+ self.assertEqual(s, b'*1\r\n$4\r\nPING\r\n')
@@ -0,0 +1,244 @@
1
+ # coding: utf-8
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from twisted.internet import defer, error, reactor, task
16
+ from twisted.internet.endpoints import HostnameEndpoint
17
+ from twisted.internet.interfaces import IStreamClientEndpoint
18
+ from twisted.internet.protocol import Factory
19
+ from twisted.trial import unittest
20
+ from zope.interface import implementer
21
+
22
+ import txredisapi as redis
23
+
24
+ from tests.test_sentinel import TrackingRedisFactory
25
+
26
+
27
+ @implementer(IStreamClientEndpoint)
28
+ class FailingEndpoint(object):
29
+ """
30
+ Endpoint that fails synchronously, the way UNIXClientEndpoint does when
31
+ the socket does not exist.
32
+ """
33
+
34
+ def __init__(self):
35
+ self.attempts = 0
36
+
37
+ def connect(self, protocolFactory):
38
+ self.attempts += 1
39
+ return defer.fail(error.ConnectionRefusedError())
40
+
41
+
42
+ @implementer(IStreamClientEndpoint)
43
+ class GatedEndpoint(object):
44
+ """
45
+ Endpoint that lets the first connection through and refuses the ones
46
+ after it, but not before the given gate fires.
47
+ """
48
+
49
+ def __init__(self, endpoint, gate):
50
+ self.endpoint = endpoint
51
+ self.gate = gate
52
+ self.attempts = 0
53
+
54
+ def connect(self, protocolFactory):
55
+ self.attempts += 1
56
+ if self.attempts == 1:
57
+ return self.endpoint.connect(protocolFactory)
58
+
59
+ refused = defer.Deferred()
60
+ self.gate.addCallback(
61
+ lambda _: refused.errback(error.ConnectionRefusedError()))
62
+ return refused
63
+
64
+
65
+ class TestReconnect(unittest.TestCase):
66
+ timeout = 30
67
+
68
+ def setUp(self):
69
+ self.server = TrackingRedisFactory()
70
+ self.listener = reactor.listenTCP(0, self.server,
71
+ interface="127.0.0.1")
72
+ self.port = self.listener.getHost().port
73
+ self.addCleanup(self.listener.stopListening)
74
+
75
+ def endpoint(self):
76
+ return HostnameEndpoint(reactor, "127.0.0.1", self.port)
77
+
78
+ @defer.inlineCallbacks
79
+ def waitFor(self, predicate, message):
80
+ deadline = reactor.seconds() + 15
81
+ while not predicate():
82
+ if reactor.seconds() > deadline:
83
+ self.fail(message)
84
+ yield task.deferLater(reactor, 0.05, lambda: None)
85
+
86
+ @defer.inlineCallbacks
87
+ def closedPort(self):
88
+ listener = reactor.listenTCP(0, Factory(), interface="127.0.0.1")
89
+ port = listener.getHost().port
90
+ yield listener.stopListening()
91
+ return port
92
+
93
+ @defer.inlineCallbacks
94
+ def test_reconnects(self):
95
+ db = yield redis.Connection("127.0.0.1", self.port)
96
+ self.addCleanup(db.disconnect)
97
+ yield db.role()
98
+
99
+ self.server.protocols[0].transport.loseConnection()
100
+
101
+ yield self.waitFor(lambda: len(self.server.protocols) > 1,
102
+ "connection has not been reestablished")
103
+ role = yield db.role()
104
+ self.assertEqual(role[0], "master")
105
+
106
+ @defer.inlineCallbacks
107
+ def test_does_not_reconnect_when_disabled(self):
108
+ # short enough for the wait below to cover several attempts
109
+ self.patch(redis.RedisFactory, "initialDelay", 0.05)
110
+
111
+ db = yield redis.Connection("127.0.0.1", self.port, reconnect=False)
112
+ self.addCleanup(db.disconnect)
113
+ yield db.role()
114
+
115
+ self.server.protocols[0].transport.loseConnection()
116
+ yield self.waitFor(lambda: db._factory.size == 0,
117
+ "connection has not been dropped")
118
+
119
+ # give a reconnect a chance to happen, it should not
120
+ yield task.deferLater(reactor, 0.3, lambda: None)
121
+ self.assertEqual(len(self.server.protocols), 1)
122
+
123
+ yield self.assertFailure(db.role(), redis.ConnectionError)
124
+
125
+ @defer.inlineCallbacks
126
+ def test_failed_connection_is_reported(self):
127
+ port = yield self.closedPort()
128
+ yield self.assertFailure(
129
+ redis.Connection("127.0.0.1", port, reconnect=False), ValueError)
130
+
131
+ @defer.inlineCallbacks
132
+ def test_first_failure_is_final(self):
133
+ # long enough for a second attempt to be noticed as a hang
134
+ self.patch(redis.RedisFactory, "initialDelay", 30)
135
+
136
+ endpoint = FailingEndpoint()
137
+ factory = redis.RedisFactory(None, dbid=None, poolsize=1)
138
+ factory.reconnect = False
139
+ deferred = factory.deferred
140
+ factory.startConnecting(endpoint)
141
+
142
+ yield self.assertFailure(deferred, ValueError)
143
+ self.assertEqual(endpoint.attempts, 1)
144
+
145
+ @defer.inlineCallbacks
146
+ def test_failed_pool_startup_closes_connections(self):
147
+ gate = defer.Deferred()
148
+ endpoint = GatedEndpoint(self.endpoint(), gate)
149
+
150
+ factory = redis.RedisFactory(None, dbid=None, poolsize=2)
151
+ factory.reconnect = False
152
+ deferred = factory.deferred
153
+ factory.startConnecting(endpoint)
154
+
155
+ yield self.waitFor(lambda: factory.size == 1,
156
+ "the first connection has not been established")
157
+ gate.callback(None)
158
+
159
+ yield self.assertFailure(deferred, ValueError)
160
+
161
+ # the caller got an error instead of the handler, so it can't close
162
+ # the connection that did come up - we have to
163
+ yield self.waitFor(lambda: not self.server.protocols[0].connected,
164
+ "the established connection has not been closed")
165
+
166
+ @defer.inlineCallbacks
167
+ def test_failed_lazy_pool_keeps_working_connections(self):
168
+ gate = defer.Deferred()
169
+ endpoint = GatedEndpoint(self.endpoint(), gate)
170
+
171
+ factory = redis.RedisFactory(None, dbid=None, poolsize=2, isLazy=True)
172
+ factory.reconnect = False
173
+ # nobody consumes the Deferred of a lazy factory
174
+ factory.deferred.addErrback(lambda failure: None)
175
+ factory.startConnecting(endpoint)
176
+
177
+ yield self.waitFor(lambda: factory.size == 1,
178
+ "the first connection has not been established")
179
+ gate.callback(None)
180
+ yield task.deferLater(reactor, 0.3, lambda: None)
181
+
182
+ # the handler is in the caller's hands already, so the connection that
183
+ # did come up stays usable
184
+ self.assertEqual(factory.size, 1)
185
+ role = yield factory.handler.role()
186
+ self.assertEqual(role[0], "master")
187
+
188
+ yield factory.handler.disconnect()
189
+
190
+ @defer.inlineCallbacks
191
+ def test_disconnect_cancels_reconnection(self):
192
+ # long enough for the reconnection to still be scheduled below
193
+ self.patch(redis.RedisFactory, "initialDelay", 0.5)
194
+
195
+ db = yield redis.Connection("127.0.0.1", self.port)
196
+ yield db.role()
197
+
198
+ self.server.protocols[0].transport.loseConnection()
199
+ yield self.waitFor(lambda: db._factory.size == 0,
200
+ "connection has not been dropped")
201
+
202
+ # a scheduled reconnection attempt must not survive disconnect()
203
+ yield db.disconnect()
204
+ yield task.deferLater(reactor, 1, lambda: None)
205
+ self.assertEqual(len(self.server.protocols), 1)
206
+
207
+ @defer.inlineCallbacks
208
+ def test_stop_trying_from_connection_made(self):
209
+ # the pattern examples/subscriber.py shows: give up on the connection
210
+ # from inside connectionMade, while the endpoint is still finishing
211
+ # it. Neither hook chains to the base protocol, the way protocols
212
+ # written for a SubscriberFactory usually don't
213
+ class StoppingProtocol(redis.RedisProtocol):
214
+ def connectionMade(self):
215
+ self.factory.stopTrying()
216
+ self.transport.loseConnection()
217
+
218
+ def connectionLost(self, reason):
219
+ pass
220
+
221
+ factory = redis.RedisFactory(None, dbid=None, poolsize=1)
222
+ factory.protocol = StoppingProtocol
223
+ factory.startConnecting(self.endpoint())
224
+
225
+ yield self.waitFor(lambda: self.server.protocols and
226
+ not self.server.protocols[0].connected,
227
+ "the connection has not been closed")
228
+
229
+ yield task.deferLater(reactor, 0.3, lambda: None)
230
+ self.assertEqual(len(self.server.protocols), 1)
231
+
232
+ def test_dead_connection_is_not_pooled(self):
233
+ factory = redis.RedisFactory(None, dbid=None, poolsize=1)
234
+ conn = factory.buildProtocol(None)
235
+ # died during the reactor turn addConnection is delayed by
236
+ conn.connected = 0
237
+
238
+ factory.addConnection(conn)
239
+ self.assertEqual(factory.size, 0)
240
+
241
+ def test_retry_delay_does_not_overflow(self):
242
+ factory = redis.RedisFactory(None, dbid=None, poolsize=1)
243
+ # a long outage keeps increasing the attempt number
244
+ self.assertLess(factory.retryDelay(10 ** 6), factory.maxDelay * 2)
@@ -16,8 +16,6 @@
16
16
  import sys
17
17
  import hashlib
18
18
 
19
- import six
20
-
21
19
  import txredisapi as redis
22
20
 
23
21
  from twisted.internet import defer