Skip to content

Commit 16d16d3

Browse files
authored
[4.4] Deprecate timeout config options introduced in 4.4.5 (#768)
* `update_routing_table_timeout` * `session_connection_timeout` Server-side keep-alives communicated through configuration hints together with `connection_acquisition_timeout` (which, starting with 4.4.5, includes the full handshake, i.e., with `HELLO` message) are sufficient to avoid the driver getting stuck.
1 parent 25a14f4 commit 16d16d3

File tree

3 files changed

+118
-23
lines changed

3 files changed

+118
-23
lines changed

docs/source/api.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,11 @@ larger than :ref:`update-routing-table-timeout-ref`,
194194

195195
.. versionadded:: 4.4.5
196196

197+
.. deprecated:: 4.4.6
198+
Will be removed in 5.0. Use server-side bolt-keep-alive together with
199+
:ref:`connection-acquisition-timeout-ref` instead to ensure the driver
200+
cannot hang.
201+
197202

198203
.. _update-routing-table-timeout-ref:
199204

@@ -216,6 +221,11 @@ This setting only has an effect for :ref:`neo4j-driver-ref`, but not for
216221

217222
.. versionadded:: 4.4.5
218223

224+
.. deprecated:: 4.4.6
225+
Will be removed in 5.0. Use server-side bolt-keep-alive together with
226+
:ref:`connection-acquisition-timeout-ref` instead to ensure the driver
227+
cannot hang.
228+
219229

220230
.. _connection-acquisition-timeout-ref:
221231

neo4j/conf.py

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,13 @@
2121

2222
from abc import ABCMeta
2323
from collections.abc import Mapping
24+
import warnings
2425
from warnings import warn
2526

26-
from neo4j.meta import get_user_agent
27+
from neo4j.meta import (
28+
deprecation_warn,
29+
get_user_agent,
30+
)
2731

2832
from neo4j.api import (
2933
TRUST_SYSTEM_CA_SIGNED_CERTIFICATES,
@@ -57,43 +61,58 @@ def __init__(self, new):
5761
self.new = new
5862

5963

64+
class DeprecatedOption:
65+
66+
def __init__(self, value):
67+
self.value = value
68+
69+
6070
class ConfigType(ABCMeta):
6171

6272
def __new__(mcs, name, bases, attributes):
6373
fields = []
6474
deprecated_aliases = {}
75+
deprecated_options = {}
6576

6677
for base in bases:
6778
if type(base) is mcs:
6879
fields += base.keys()
6980
deprecated_aliases.update(base._deprecated_aliases())
81+
deprecated_options.update(base._deprecated_options())
7082

7183
for k, v in attributes.items():
7284
if isinstance(v, DeprecatedAlias):
7385
deprecated_aliases[k] = v.new
7486
elif not k.startswith("_") and not callable(v):
7587
fields.append(k)
88+
if isinstance(v, DeprecatedOption):
89+
deprecated_options[k] = v.value
90+
attributes[k] = v.value
7691

7792
def keys(_):
78-
return fields
93+
return set(fields)
7994

8095
def _deprecated_aliases(_):
8196
return deprecated_aliases
8297

98+
def _deprecated_options(_):
99+
return deprecated_options
100+
83101
def _deprecated_keys(_):
84-
return list(deprecated_aliases)
102+
return set(deprecated_aliases.keys())
85103

86104
def _get_new(_, key):
87105
return deprecated_aliases.get(key)
88106

89107
attributes.setdefault("keys", classmethod(keys))
90108
attributes.setdefault("_deprecated_aliases", classmethod(_deprecated_aliases))
109+
attributes.setdefault("_deprecated_options", classmethod(_deprecated_options))
91110
attributes.setdefault("_deprecated_keys", classmethod(_deprecated_keys))
92111
attributes.setdefault("_get_new", classmethod(_get_new))
93112

94113
return super(ConfigType, mcs).__new__(mcs, name, bases,
95114
{k: v for k, v in attributes.items()
96-
if k not in deprecated_aliases})
115+
if k not in _deprecated_keys(None)})
97116

98117

99118
class Config(Mapping, metaclass=ConfigType):
@@ -120,7 +139,7 @@ def consume(cls, data):
120139
def _consume(cls, data):
121140
config = {}
122141
if data:
123-
for key in list(cls.keys()) + list(cls._deprecated_keys()):
142+
for key in cls.keys() | cls._deprecated_keys():
124143
try:
125144
value = data.pop(key)
126145
except KeyError:
@@ -134,12 +153,19 @@ def __update(self, data):
134153

135154
def set_attr(k, v):
136155
if k in self.keys():
156+
if k in self._deprecated_options():
157+
deprecation_warn("The '{}' config key is "
158+
"deprecated".format(k))
137159
setattr(self, k, v)
138160
elif k in self._deprecated_keys():
139161
k0 = self._get_new(k)
140162
if k0 in data_dict:
141-
raise ValueError("Cannot specify both '{}' and '{}' in config".format(k0, k))
142-
warn("The '{}' config key is deprecated, please use '{}' instead".format(k, k0))
163+
raise ValueError("Cannot specify both '{}' and '{}' in "
164+
"config".format(k0, k))
165+
deprecation_warn(
166+
"The '{}' config key is deprecated, please use "
167+
"'{}' instead".format(k, k0)
168+
)
143169
set_attr(k0, v)
144170
else:
145171
raise AttributeError(k)
@@ -150,7 +176,13 @@ def set_attr(k, v):
150176

151177
def __init__(self, *args, **kwargs):
152178
for arg in args:
153-
self.__update(arg)
179+
if isinstance(arg, Config):
180+
with warnings.catch_warnings():
181+
warnings.filterwarnings("ignore",
182+
category=DeprecationWarning)
183+
self.__update(arg)
184+
else:
185+
self.__update(arg)
154186
self.__update(kwargs)
155187

156188
def __repr__(self):
@@ -186,7 +218,7 @@ class PoolConfig(Config):
186218
# The maximum amount of time to wait for a TCP connection to be established.
187219

188220
#: Update Routing Table Timout
189-
update_routing_table_timeout = 90.0 # seconds
221+
update_routing_table_timeout = DeprecatedOption(90.0) # seconds
190222
# The maximum amount of time to wait for updating the routing table.
191223
# This includes everything necessary for this to happen.
192224
# Including opening sockets, requesting and receiving the routing table,
@@ -264,7 +296,7 @@ class WorkspaceConfig(Config):
264296
"""
265297

266298
#: Session Connection Timeout
267-
session_connection_timeout = float("inf") # seconds
299+
session_connection_timeout = DeprecatedOption(float("inf")) # seconds
268300
# The maximum amount of time to wait for a session to obtain a usable
269301
# read/write connection. This includes everything necessary for this to
270302
# happen. Including fetching routing tables, opening sockets, etc.

tests/unit/test_conf.py

Lines changed: 66 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
# limitations under the License.
2020

2121

22+
from contextlib import contextmanager
23+
import warnings
24+
2225
import pytest
2326

2427
from neo4j.exceptions import (
@@ -72,11 +75,26 @@
7275
config_function_names = ["consume_chain", "consume"]
7376

7477

78+
@contextmanager
79+
def _pool_config_deprecations():
80+
with pytest.warns(DeprecationWarning,
81+
match="update_routing_table_timeout") as warnings:
82+
yield warnings
83+
84+
85+
@contextmanager
86+
def _session_config_deprecations():
87+
with pytest.warns(DeprecationWarning,
88+
match="session_connection_timeout") as warnings:
89+
yield warnings
90+
91+
7592
def test_pool_config_consume():
7693

7794
test_config = dict(test_pool_config)
7895

79-
consumed_pool_config = PoolConfig.consume(test_config)
96+
with _pool_config_deprecations():
97+
consumed_pool_config = PoolConfig.consume(test_config)
8098

8199
assert isinstance(consumed_pool_config, PoolConfig)
82100

@@ -89,7 +107,8 @@ def test_pool_config_consume():
89107
if key not in config_function_names:
90108
assert test_pool_config[key] == consumed_pool_config[key]
91109

92-
assert len(consumed_pool_config) - len(config_function_names) == len(test_pool_config)
110+
assert (len(consumed_pool_config) - len(config_function_names)
111+
== len(test_pool_config))
93112

94113

95114
def test_pool_config_consume_default_values():
@@ -114,7 +133,11 @@ def test_pool_config_consume_key_not_valid():
114133
test_config["not_valid_key"] = "test"
115134

116135
with pytest.raises(ConfigurationError) as error:
117-
consumed_pool_config = PoolConfig.consume(test_config)
136+
# might or might not warn DeprecationWarning, but we're only
137+
# interested in the error
138+
with warnings.catch_warnings():
139+
warnings.filterwarnings("ignore", category=DeprecationWarning)
140+
_ = PoolConfig.consume(test_config)
118141

119142
error.match("Unexpected config keys: not_valid_key")
120143

@@ -123,7 +146,8 @@ def test_pool_config_set_value():
123146

124147
test_config = dict(test_pool_config)
125148

126-
consumed_pool_config = PoolConfig.consume(test_config)
149+
with _pool_config_deprecations():
150+
consumed_pool_config = PoolConfig.consume(test_config)
127151

128152
assert consumed_pool_config.get("encrypted") is False
129153
assert consumed_pool_config["encrypted"] is False
@@ -141,28 +165,37 @@ def test_pool_config_set_value():
141165
def test_pool_config_consume_and_then_consume_again():
142166

143167
test_config = dict(test_pool_config)
144-
consumed_pool_config = PoolConfig.consume(test_config)
168+
with _pool_config_deprecations():
169+
consumed_pool_config = PoolConfig.consume(test_config)
145170
assert consumed_pool_config.encrypted is False
146171
consumed_pool_config.encrypted = "test"
147172

148173
with pytest.raises(AttributeError):
149-
consumed_pool_config = PoolConfig.consume(consumed_pool_config)
174+
_ = PoolConfig.consume(consumed_pool_config)
150175

151-
consumed_pool_config = PoolConfig.consume(dict(consumed_pool_config.items()))
152-
consumed_pool_config = PoolConfig.consume(dict(consumed_pool_config.items()))
176+
with _pool_config_deprecations():
177+
consumed_pool_config = PoolConfig.consume(
178+
dict(consumed_pool_config.items())
179+
)
180+
with _pool_config_deprecations():
181+
consumed_pool_config = PoolConfig.consume(
182+
dict(consumed_pool_config.items())
183+
)
153184

154185
assert consumed_pool_config.encrypted == "test"
155186

156187

157188
def test_config_consume_chain():
158189

159190
test_config = {}
160-
161191
test_config.update(test_pool_config)
162-
163192
test_config.update(test_session_config)
164193

165-
consumed_pool_config, consumed_session_config = Config.consume_chain(test_config, PoolConfig, SessionConfig)
194+
with warnings.catch_warnings():
195+
warnings.filterwarnings("ignore", category=DeprecationWarning)
196+
consumed_pool_config, consumed_session_config = Config.consume_chain(
197+
test_config, PoolConfig, SessionConfig
198+
)
166199

167200
assert isinstance(consumed_pool_config, PoolConfig)
168201
assert isinstance(consumed_session_config, SessionConfig)
@@ -176,9 +209,11 @@ def test_config_consume_chain():
176209
if key not in config_function_names:
177210
assert test_pool_config[key] == val
178211

179-
assert len(consumed_pool_config) - len(config_function_names) == len(test_pool_config)
212+
assert (len(consumed_pool_config) - len(config_function_names)
213+
== len(test_pool_config))
180214

181-
assert len(consumed_session_config) - len(config_function_names) == len(test_session_config)
215+
assert (len(consumed_session_config) - len(config_function_names)
216+
== len(test_session_config))
182217

183218

184219
def test_init_session_config_merge():
@@ -226,3 +261,21 @@ def test_init_session_config_with_not_valid_key():
226261
_ = SessionConfig.consume(test_config_b)
227262

228263
assert session_config.connection_acquisition_timeout == 333
264+
265+
266+
def test_pool_config_deprecated_update_routing_table_timeout():
267+
with _pool_config_deprecations():
268+
_ = PoolConfig.consume({"update_routing_table_timeout": 1})
269+
270+
with warnings.catch_warnings():
271+
warnings.simplefilter("error")
272+
_ = PoolConfig.consume({})
273+
274+
275+
def test_session_config_deprecated_session_connection_timeout():
276+
with _session_config_deprecations():
277+
_ = SessionConfig.consume({"session_connection_timeout": 1})
278+
279+
with warnings.catch_warnings():
280+
warnings.simplefilter("error")
281+
_ = SessionConfig.consume({})

0 commit comments

Comments
 (0)