Skip to content

Commit ec23054

Browse files
committed
Add probe option to connect without a responding server
Version detection moves to first read, so an early connection works again.
1 parent 0941159 commit ec23054

4 files changed

Lines changed: 69 additions & 3 deletions

File tree

‎CHANGES.rst‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22
Changes for crate
33
=================
44

5+
Unreleased
6+
==========
7+
8+
- Added ``connect(..., probe=False)``, which creates a connection without
9+
contacting the servers. The lowest server version is then resolved when it
10+
is first read. Use it when the cluster may not respond yet, for example in
11+
tests and during startup.
12+
513
2026/09/17 2.3.0
614
================
715
- Added ``DefaultTypeConverter`` support that decodes ``DataType.UUID``

‎docs/by-example/connection.rst‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ This section sets up a connection object, and inspects some of its attributes.
2121
>>> connection.lowest_server_version.version
2222
(2, 0, 0)
2323

24+
With ``probe=False``, the connection is created without contacting any server.
25+
The lowest server version is then resolved when it is first read, and reports
26+
``0.0.0`` for as long as no server answers::
27+
28+
>>> connection = connect(client=ClientMocked(), probe=False)
29+
>>> connection.lowest_server_version.version
30+
(2, 0, 0)
31+
2432
cursor()
2533
========
2634

‎src/crate/client/connection.py‎

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ def __init__(
5353
time_zone=None,
5454
jwt_token=None,
5555
compress: Union[int, bool] = 8192,
56+
probe: bool = True,
5657
):
5758
"""
5859
:param servers:
@@ -139,6 +140,13 @@ def __init__(
139140
``False`` disables compression entirely.
140141
``True`` compresses every request regardless of size.
141142
An integer compresses only when the payload exceeds that many bytes.
143+
:param probe:
144+
(optional, defaults to ``True``)
145+
Contact the servers while the connection is created, and raise
146+
``ConnectionError`` when none of them responds.
147+
With ``False``, creating the connection performs no request, and
148+
the server version is resolved when it is first read. An
149+
unreachable cluster then fails on the first statement.
142150
""" # noqa: E501
143151

144152
self._converter = converter
@@ -168,9 +176,30 @@ def __init__(
168176
jwt_token=jwt_token,
169177
compress=compress,
170178
)
171-
self.lowest_server_version = self._lowest_server_version()
179+
self._version_cache: Union[Version, None] = None
180+
if probe:
181+
self._version_cache = self._lowest_server_version()
172182
self._closed = False
173183

184+
@property
185+
def lowest_server_version(self) -> Version:
186+
"""
187+
The lowest CrateDB version among the servers of this connection.
188+
189+
Without a version from the servers, comparisons against it still work,
190+
and report the servers as older than any released CrateDB.
191+
"""
192+
if self._version_cache is not None:
193+
return self._version_cache
194+
version = self._lowest_server_version(strict=False)
195+
if version > Version("0.0.0"):
196+
self._version_cache = version
197+
return version
198+
199+
@lowest_server_version.setter
200+
def lowest_server_version(self, version: Version) -> None:
201+
self._version_cache = version
202+
174203
def cursor(self, **kwargs) -> Cursor:
175204
"""
176205
Return a new Cursor Object using the connection.
@@ -208,7 +237,7 @@ def get_blob_container(self, container_name):
208237
"""
209238
return BlobContainer(container_name, self)
210239

211-
def _lowest_server_version(self):
240+
def _lowest_server_version(self, strict: bool = True) -> Version:
212241
lowest = None
213242
servers = self.client.active_servers
214243
connection_errors = []
@@ -223,7 +252,7 @@ def _lowest_server_version(self):
223252
continue
224253
if not lowest or version < lowest:
225254
lowest = version
226-
if connection_errors and not lowest:
255+
if strict and connection_errors and not lowest:
227256
raise ConnectionError("; ".join(str(e) for e in connection_errors))
228257
return lowest or Version("0.0.0")
229258

‎tests/client/test_connection.py‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,27 @@ def test_lowest_server_version():
6565
assert (1, 0, 3) == connection.lowest_server_version.version
6666

6767

68+
def test_connect_without_probe_does_not_need_a_server():
69+
"""
70+
Verify `probe=False` creates a connection without contacting any server.
71+
"""
72+
connection = connect("localhost:44441", probe=False)
73+
assert (0, 0, 0) == connection.lowest_server_version.version
74+
75+
76+
def test_lowest_server_version_resolved_on_first_read():
77+
"""
78+
Verify the server version is resolved when read, not when connecting.
79+
"""
80+
client = Client(servers="localhost:4200")
81+
client.server_infos = lambda server: (None, None, "5.5.2")
82+
83+
connection = connect(client=client, probe=False)
84+
assert connection._version_cache is None
85+
assert (5, 5, 2) == connection.lowest_server_version.version
86+
assert (5, 5, 2) == connection._version_cache.version
87+
88+
6889
def test_connection_closes_access():
6990
"""
7091
Verify that a connection closes on exit and that it also closes

0 commit comments

Comments
 (0)