Skip to content

Commit 7596315

Browse files
committed
Make DNS error retryable
Configuring the driver with a URL that cannot be DNS resolved will raise a (retryable) `ServiceUnavailable` error instead of a `ValueError`.
1 parent d3fb09d commit 7596315

File tree

9 files changed

+194
-3
lines changed

9 files changed

+194
-3
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,8 @@ See also https://github.com/neo4j/neo4j-python-driver/wiki for a full changelog.
165165
- `neo4j.graph.Node`, `neo4j.graph.Relationship`, `neo4j.graph.Path`
166166
- `neo4j.time.Date`, `neo4j.time.Time`, `neo4j.time.DateTime`
167167
- `neo4j.spatial.Point` (and subclasses)
168+
- Configuring the driver with a URL that cannot be DNS resolved will raise a (retryable) `ServiceUnavailable` error
169+
instead of a `ValueError`.
168170

169171

170172
## Version 5.28

src/neo4j/_addressing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ def parse_list(
200200
201201
>>> Address.parse_list("localhost:7687", "[::1]:7687")
202202
[IPv4Address(('localhost', 7687)), IPv6Address(('::1', 7687, 0, 0))]
203-
>>> Address.parse_list("localhost:7687 [::1]:7687")
203+
>>> Address.parse_list("localhost:7687", "[::1]:7687")
204204
[IPv4Address(('localhost', 7687)), IPv6Address(('::1', 7687, 0, 0))]
205205
206206
:param s: The string(s) to parse.

src/neo4j/_async_compat/network/_util.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
Address,
2323
ResolvedAddress,
2424
)
25+
from ...exceptions import ServiceUnavailable
2526
from ..util import AsyncUtil
2627

2728

@@ -69,7 +70,9 @@ async def _dns_resolver(address, family=0):
6970
type=socket.SOCK_STREAM,
7071
)
7172
except OSError as e:
72-
raise ValueError(f"Cannot resolve address {address}") from e
73+
raise ServiceUnavailable(
74+
f"Failed to DNS resolve address {address}: {e}"
75+
) from e
7376
return list(_resolved_addresses_from_info(info, address._host_name))
7477

7578
@staticmethod
@@ -151,7 +154,9 @@ def _dns_resolver(address, family=0):
151154
type=socket.SOCK_STREAM,
152155
)
153156
except OSError as e:
154-
raise ValueError(f"Cannot resolve address {address}") from e
157+
raise ServiceUnavailable(
158+
f"Failed to DNS resolve address {address}"
159+
) from e
155160
return _resolved_addresses_from_info(info, address._host_name)
156161

157162
@staticmethod
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Copyright (c) "Neo4j"
2+
# Neo4j Sweden AB [https://neo4j.com]
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# https://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Copyright (c) "Neo4j"
2+
# Neo4j Sweden AB [https://neo4j.com]
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# https://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Copyright (c) "Neo4j"
2+
# Neo4j Sweden AB [https://neo4j.com]
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# https://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
17+
from __future__ import annotations
18+
19+
import socket
20+
21+
import pytest
22+
23+
from neo4j._addressing import (
24+
ResolvedAddress,
25+
ResolvedIPv4Address,
26+
ResolvedIPv6Address,
27+
)
28+
from neo4j._async_compat.network import AsyncNetworkUtil
29+
from neo4j.addressing import Address
30+
from neo4j.exceptions import ServiceUnavailable
31+
32+
from ....._async_compat import mark_async_test
33+
34+
35+
@mark_async_test
36+
async def test_resolve_address():
37+
resolved = [
38+
addr
39+
async for addr in AsyncNetworkUtil.resolve_address(
40+
Address(("localhost", 1234)),
41+
)
42+
]
43+
assert all(isinstance(addr, ResolvedAddress) for addr in resolved)
44+
for addr in resolved:
45+
if isinstance(addr, ResolvedIPv4Address):
46+
assert len(addr) == 2
47+
assert addr[0].startswith("127.0.0.")
48+
assert addr[1] == 1234
49+
elif isinstance(addr, ResolvedIPv6Address):
50+
assert len(addr) == 4
51+
assert addr[:2] == ("::1", 1234)
52+
53+
54+
@mark_async_test
55+
async def test_resolve_invalid_address():
56+
with pytest.raises(ServiceUnavailable) as exc:
57+
await anext(
58+
AsyncNetworkUtil.resolve_address(
59+
Address(("example.invalid", 1234)),
60+
)
61+
)
62+
cause = exc.value.__cause__
63+
assert isinstance(cause, socket.gaierror)
64+
assert cause.errno, socket.EAI_NONAME

tests/integration/sync/async_compat/__init__.py

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/integration/sync/async_compat/network/__init__.py

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/integration/sync/async_compat/network/test_util.py

Lines changed: 64 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)