Skip to content

Commit f40d684

Browse files
genzgdjovezhong
authored andcommitted
Release 0 8 16 (ClickHouse#485)
* Check for optional libraries in client methods * Log unexpected http next chunk unexpected * Log unexpected http next chunk unexpected * Updates for 0.8.16 release
1 parent c50f66e commit f40d684

File tree

5 files changed

+38
-9
lines changed

5 files changed

+38
-9
lines changed

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,23 @@ release (0.9.0), unrecognized arguments/keywords for these methods of creating a
2121
instead of being passed as ClickHouse server settings. This is in conjunction with some refactoring in Client construction.
2222
The supported method of passing ClickHouse server settings is to prefix such arguments/query parameters with`ch_`.
2323

24+
## 0.8.16, 2025-03-28
25+
### Bug Fixes
26+
- Don't send a setting value if the setting is already correct according to the `system.settings` table.
27+
Closes https://github.com/ClickHouse/clickhouse-connect/issues/469
28+
- Ensure that the http `user_agent` header is in ascii. Note this could lead to an incorrectly encoded `os_user` if the
29+
os_user is not an Ascii string. Closes https://github.com/ClickHouse/clickhouse-connect/issues/484
30+
- Fix "cannot access local variable" exception where the http client encounters an unexpected streaming error. Also
31+
log that unexpected streaming error to assist debugging. Closes https://github.com/ClickHouse/clickhouse-connect/issues/483
32+
- Check that arrow/pandas is installed when calling `query_df` and `query_arrow` and raise a more meaningful exception
33+
if the required library is absent. Closes https://github.com/ClickHouse/clickhouse-connect/issues/477
34+
35+
### Improvements
36+
- Some typing hints have been corrected. Thanks to [Avery Fischer](https://github.com/biggerfisch) for the PR!
37+
- The docker based tests have been fixed to work with security improvements in recent ClickHouse releases
38+
- Query string cleanup is now (in theory) microseconds faster. Thanks to [Sviatoslav Bobryshev](https://github.com/sbobryshev)
39+
for the optimization
40+
2441
## 0.8.15, 2025-01-25
2542
### Bug Fix
2643
- The async client was not shutting down its associated executor thread pool, result in a memory leak if multiple

timeplus_connect/common.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@ def build_client_name(client_name: str):
4141
os_user = f'; os_user:{getpass.getuser()}'
4242
except Exception: # pylint: disable=broad-except
4343
pass
44-
return (f'{client_name}{product_name}timeplus-connect/{version()}' +
44+
full_name = (f'{client_name}{product_name}timeplus-connect/{version()}' +
4545
f' (lv:py/{py_version}; mode:sync; os:{sys.platform}{os_user})')
46+
return full_name.encode('ascii', 'ignore').decode()
4647

4748

4849
def get_setting(name: str):

timeplus_connect/driver/client.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def __init__(self,
6868
self.uri = uri
6969
self._init_common_settings(apply_server_timezone)
7070

71-
def _init_common_settings(self, apply_server_timezone:Optional[Union[str, bool]] ):
71+
def _init_common_settings(self, apply_server_timezone: Optional[Union[str, bool]]):
7272
self.server_tz, dst_safe = pytz.UTC, True
7373
self.server_version, server_tz = \
7474
tuple(self.command('SELECT version(), timezone()', use_database=False))
@@ -122,14 +122,16 @@ def _validate_settings(self, settings: Optional[Dict[str, Any]]) -> Dict[str, st
122122
return validated
123123

124124
def _validate_setting(self, key: str, value: Any, invalid_action: str) -> Optional[str]:
125-
new_value = str(value)
125+
str_value = str(value)
126126
if value is True:
127-
new_value = '1'
127+
str_value = '1'
128128
elif value is False:
129-
new_value = '0'
129+
str_value = '0'
130130
if key not in self.valid_transport_settings:
131131
setting_def = self.server_settings.get(key)
132-
if setting_def is None or (setting_def.readonly and setting_def.value != new_value):
132+
if setting_def and setting_def.value == str_value:
133+
return None # don't send settings that are already the expected value
134+
if setting_def is None or setting_def.readonly:
133135
if key in self.optional_transport_settings:
134136
return None
135137
if invalid_action == 'send':
@@ -139,7 +141,7 @@ def _validate_setting(self, key: str, value: Any, invalid_action: str) -> Option
139141
return None
140142
else:
141143
raise ProgrammingError(f'Setting {key} is unknown or readonly') from None
142-
return new_value
144+
return str_value
143145

144146
def _setting_status(self, key: str) -> SettingStatus:
145147
comp_setting = self.server_settings.get(key)
@@ -342,6 +344,7 @@ def query_np(self,
342344
create_query_context method
343345
:return: Numpy array representing the result set
344346
"""
347+
check_numpy()
345348
return self._context_query(locals(), use_numpy=True).np_result
346349

347350
# pylint: disable=duplicate-code,too-many-arguments,unused-argument
@@ -361,6 +364,7 @@ def query_np_stream(self,
361364
create_query_context method
362365
:return: Generator that yield a numpy array per block representing the result set
363366
"""
367+
check_numpy()
364368
return self._context_query(locals(), use_numpy=True, streaming=True).np_stream
365369

366370
# pylint: disable=duplicate-code,unused-argument
@@ -384,6 +388,7 @@ def query_df(self,
384388
create_query_context method
385389
:return: Pandas dataframe representing the result set
386390
"""
391+
check_pandas()
387392
return self._context_query(locals(), use_numpy=True, as_pandas=True).df_result
388393

389394
# pylint: disable=duplicate-code,unused-argument
@@ -407,6 +412,7 @@ def query_df_stream(self,
407412
create_query_context method
408413
:return: Generator that yields a Pandas dataframe per block representing the result set
409414
"""
415+
check_pandas()
410416
return self._context_query(locals(), use_numpy=True,
411417
as_pandas=True,
412418
streaming=True).df_stream
@@ -519,6 +525,7 @@ def query_arrow(self,
519525
:param external_data ClickHouse "external data" to send with query
520526
:return: PyArrow.Table
521527
"""
528+
check_arrow()
522529
settings = self._update_arrow_settings(settings, use_strings)
523530
return to_arrow(self.raw_query(query,
524531
parameters,
@@ -541,6 +548,7 @@ def query_arrow_stream(self,
541548
:param external_data ClickHouse "external data" to send with query
542549
:return: Generator that yields a PyArrow.Table for per block representing the result set
543550
"""
551+
check_arrow()
544552
settings = self._update_arrow_settings(settings, use_strings)
545553
return to_arrow_batches(self.raw_stream(query,
546554
parameters,
@@ -661,6 +669,7 @@ def insert_df(self, table: str = None,
661669
different data batches
662670
:return: QuerySummary with summary information, throws exception if insert fails
663671
"""
672+
check_pandas()
664673
if context is None:
665674
if column_names is None:
666675
column_names = df.columns
@@ -686,6 +695,7 @@ def insert_arrow(self, table: str,
686695
:param settings: Optional dictionary of ClickHouse settings (key/string values)
687696
:return: QuerySummary with summary information, throws exception if insert fails
688697
"""
698+
check_arrow()
689699
full_table = table if '.' in table or not database else f'{database}.{table}'
690700
compression = self.write_compression if self.write_compression in ('zstd', 'lz4') else None
691701
column_names, insert_block = arrow_buffer(arrow_table, compression)

timeplus_connect/driver/httpclient.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,7 @@ def ping(self):
528528
return True
529529
# proton hasn't HTTP handle for path /ping
530530
# try:
531-
# response = self.http.request('GET', f'{self.url}/ping', timeout=3)
531+
# response = self.http.request('GET', f'{self.url}/ping', timeout=3, preload_content=True)
532532
# return 200 <= response.status < 300
533533
# except HTTPError:
534534
# logger.debug('ping failed', exc_info=True)

timeplus_connect/driver/httputil.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,12 +228,13 @@ def buffered():
228228
read_gen = response.stream(chunk_size, decompress is None)
229229
while True:
230230
while not done:
231+
chunk = None
231232
try:
232233
chunk = next(read_gen, None) # Always try to read at least one chunk if there are any left
233234
except Exception: # pylint: disable=broad-except
234235
# By swallowing an unexpected exception reading the stream, we will let consumers decide how to
235236
# handle the unexpected end of stream
236-
pass
237+
logger.warning('unexpected failure to read next chunk', exc_info=True)
237238
if not chunk:
238239
done = True
239240
break

0 commit comments

Comments
 (0)