Skip to content

Commit 50569f4

Browse files
authored
Change http_headers to transport settings, add transport settings to async client and insert methods (#490)
1 parent 2e1fc3d commit 50569f4

File tree

8 files changed

+166
-106
lines changed

8 files changed

+166
-106
lines changed

CHANGELOG.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,21 @@ instead of being passed as ClickHouse server settings. This is in conjunction wi
2222
The supported method of passing ClickHouse server settings is to prefix such arguments/query parameters with`ch_`.
2323

2424
## 0.8.17, 2025-04-10
25-
### Bug Fix
26-
- Version 0.8.16 introduced a bug where changing a Client setting value and then changing that setting value back to the
27-
original server value would fail to restore the original setting. This has been fixed. Closes
28-
https://github.com/ClickHouse/clickhouse-connect/issues/487
2925

30-
### Improvement
26+
### Improvements
27+
- The parameter `transport_settings` has been added to the Client query and insert methods. For the HTTP client (currently
28+
the only option), this dictionary of string is directly translated into additional HTTP headers at a query level. This can
29+
be used to provide additional proxy directives or other extra 'non-ClickHouse' information that is passed via headers.
30+
Thanks to [Paweł Szczur](https://github.com/orian) of PostHog for the original PR!
3131
- There was previously no way to add a path to the ClickHouse server host in cases where the ClickHouse server was
3232
behind a proxy that used path based routing (such as `https://big_proxy:8080/clickhouse). The new `proxy_path`
3333
`get_client` argument can now be used to set that path. Closes https://github.com/ClickHouse/clickhouse-connect/issues/486
3434

35+
### Bug Fix
36+
- Version 0.8.16 introduced a bug where changing a Client setting value and then changing that setting value back to the
37+
original server value would fail to restore the original setting. This has been fixed. Closes
38+
https://github.com/ClickHouse/clickhouse-connect/issues/487
39+
3540
## 0.8.16, 2025-03-28
3641
### Bug Fixes
3742
- Don't send a setting value if the setting is already correct according to the `system.settings` table.

clickhouse_connect/driver/asyncclient.py

Lines changed: 81 additions & 39 deletions
Large diffs are not rendered by default.

clickhouse_connect/driver/client.py

Lines changed: 50 additions & 42 deletions
Large diffs are not rendered by default.

clickhouse_connect/driver/context.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ def __init__(self,
1616
column_formats: Optional[Dict[str, Union[str, Dict[str, str]]]] = None,
1717
encoding: Optional[str] = None,
1818
use_extended_dtypes: bool = False,
19-
use_numpy: bool = False):
19+
use_numpy: bool = False,
20+
transport_settings: Optional[Dict[str, str]] = None):
2021
self.settings = settings or {}
2122
if query_formats is None:
2223
self.type_formats = _empty_map
@@ -36,6 +37,7 @@ def __init__(self,
3637
for type_name, fmt in fmt.items()}
3738
self.query_formats = query_formats or {}
3839
self.column_formats = column_formats or {}
40+
self.transport_settings = transport_settings
3941
self.column_name = None
4042
self.encoding = encoding
4143
self.use_numpy = use_numpy

clickhouse_connect/driver/httpclient.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ def _prep_query(self, context: QueryContext):
204204
return final_query + fmt
205205

206206
def _query_with_context(self, context: QueryContext) -> QueryResult:
207-
headers = dict_copy(context.extra_http_headers)
207+
headers = {}
208208
params = {}
209209
if self.database:
210210
params['database'] = self.database
@@ -242,7 +242,7 @@ def _query_with_context(self, context: QueryContext) -> QueryResult:
242242
headers['Content-Type'] = 'text/plain; charset=utf-8'
243243
response = self._raw_request(body,
244244
params,
245-
headers,
245+
dict_copy(headers, context.transport_settings),
246246
stream=True,
247247
retries=self.query_retries,
248248
fields=fields,
@@ -280,7 +280,7 @@ def error_handler(resp: HTTPResponse):
280280
if self.database:
281281
params['database'] = self.database
282282
params.update(self._validate_settings(context.settings))
283-
283+
headers = dict_copy(headers, context.transport_settings)
284284
response = self._raw_request(block_gen, params, headers, error_handler=error_handler, server_wait=False)
285285
logger.debug('Context insert response code: %d, content: %s', response.status, response.data)
286286
context.data = None
@@ -291,7 +291,8 @@ def raw_insert(self, table: str = None,
291291
insert_block: Union[str, bytes, Generator[bytes, None, None], BinaryIO] = None,
292292
settings: Optional[Dict] = None,
293293
fmt: Optional[str] = None,
294-
compression: Optional[str] = None) -> QuerySummary:
294+
compression: Optional[str] = None,
295+
transport_settings: Optional[Dict[str, str]] = None) -> QuerySummary:
295296
"""
296297
See BaseClient doc_string for this method
297298
"""
@@ -311,6 +312,7 @@ def raw_insert(self, table: str = None,
311312
if self.database:
312313
params['database'] = self.database
313314
params.update(self._validate_settings(settings or {}))
315+
headers = dict_copy(headers, transport_settings)
314316
response = self._raw_request(insert_block, params, headers, server_wait=False)
315317
logger.debug('Raw insert response code: %d, content: %s', response.status, response.data)
316318
return QuerySummary(self._summary(response))
@@ -333,7 +335,7 @@ def command(self,
333335
settings: Optional[Dict] = None,
334336
use_database: int = True,
335337
external_data: Optional[ExternalData] = None,
336-
extra_http_headers: Optional[Dict[str, str]] = None) -> Union[str, int, Sequence[str], QuerySummary]:
338+
transport_settings: Optional[Dict[str, str]] = None) -> Union[str, int, Sequence[str], QuerySummary]:
337339
"""
338340
See BaseClient doc_string for this method
339341
"""
@@ -361,7 +363,7 @@ def command(self,
361363
if use_database and self.database:
362364
params['database'] = self.database
363365
params.update(self._validate_settings(settings or {}))
364-
366+
headers = dict_copy(headers, transport_settings)
365367
method = 'POST' if payload or fields else 'GET'
366368
response = self._raw_request(payload, params, headers, method, fields=fields, server_wait=False)
367369
if response.data:
@@ -484,26 +486,26 @@ def raw_query(self, query: str,
484486
fmt: str = None,
485487
use_database: bool = True,
486488
external_data: Optional[ExternalData] = None,
487-
extra_http_headers: Optional[Dict[str, str]] = None) -> bytes:
489+
transport_settings: Optional[Dict[str, str]] = None) -> bytes:
488490
"""
489491
See BaseClient doc_string for this method
490492
"""
491493
body, params, fields = self._prep_raw_query(query, parameters, settings, fmt, use_database, external_data)
492-
return self._raw_request(body, params, fields=fields, headers=extra_http_headers).data
494+
return self._raw_request(body, params, fields=fields, headers=transport_settings).data
493495

494496
def raw_stream(self, query: str,
495497
parameters: Optional[Union[Sequence, Dict[str, Any]]] = None,
496498
settings: Optional[Dict[str, Any]] = None,
497499
fmt: str = None,
498500
use_database: bool = True,
499501
external_data: Optional[ExternalData] = None,
500-
extra_http_headers: Optional[Dict[str, str]] = None) -> io.IOBase:
502+
transport_settings: Optional[Dict[str, str]] = None) -> io.IOBase:
501503
"""
502504
See BaseClient doc_string for this method
503505
"""
504506
body, params, fields = self._prep_raw_query(query, parameters, settings, fmt, use_database, external_data)
505507
return self._raw_request(body, params, fields=fields, stream=True, server_wait=False,
506-
headers=extra_http_headers)
508+
headers=transport_settings)
507509

508510
def _prep_raw_query(self, query: str,
509511
parameters: Optional[Union[Sequence, Dict[str, Any]]],

clickhouse_connect/driver/insert.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,9 @@ def __init__(self,
4242
compression: Optional[Union[str, bool]] = None,
4343
query_formats: Optional[Dict[str, str]] = None,
4444
column_formats: Optional[Dict[str, Union[str, Dict[str, str]]]] = None,
45-
block_size: Optional[int] = None):
46-
super().__init__(settings, query_formats, column_formats)
45+
block_size: Optional[int] = None,
46+
transport_settings: Optional[Dict[str, str]] = None):
47+
super().__init__(settings, query_formats, column_formats, transport_settings=transport_settings)
4748
self.table = table
4849
self.column_names = column_names
4950
self.column_types = column_types

clickhouse_connect/driver/query.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def __init__(self,
5353
streaming: bool = False,
5454
apply_server_tz: bool = False,
5555
external_data: Optional[ExternalData] = None,
56-
extra_http_headers: Optional[Dict[str, str]] = None):
56+
transport_settings: Optional[Dict[str, str]] = None):
5757
"""
5858
Initializes various configuration settings for the query context
5959
@@ -86,7 +86,8 @@ def __init__(self,
8686
column_formats,
8787
encoding,
8888
use_extended_dtypes if use_extended_dtypes is not None else False,
89-
use_numpy if use_numpy is not None else False)
89+
use_numpy if use_numpy is not None else False,
90+
transport_settings=transport_settings)
9091
self.query = query
9192
self.parameters = parameters or {}
9293
self.use_none = True if use_none is None else use_none
@@ -117,7 +118,6 @@ def __init__(self,
117118
self.as_pandas = as_pandas
118119
self.use_pandas_na = as_pandas and pd_extended_dtypes
119120
self.streaming = streaming
120-
self.extra_http_headers = extra_http_headers
121121
self._update_query()
122122

123123
@property
@@ -192,7 +192,7 @@ def updated_copy(self,
192192
as_pandas: bool = False,
193193
streaming: bool = False,
194194
external_data: Optional[ExternalData] = None,
195-
extra_http_headers: Optional[Dict[str, str]] = None) -> 'QueryContext':
195+
transport_settings: Optional[Dict[str, str]] = None) -> 'QueryContext':
196196
"""
197197
Creates Query context copy with parameters overridden/updated as appropriate.
198198
"""
@@ -214,7 +214,7 @@ def updated_copy(self,
214214
streaming,
215215
self.apply_server_tz,
216216
self.external_data if external_data is None else external_data,
217-
self.extra_http_headers if extra_http_headers is None else extra_http_headers)
217+
self.transport_settings if transport_settings is None else transport_settings)
218218

219219
def _update_query(self):
220220
self.final_query, self.bind_params = bind_query(self.query, self.parameters, self.server_tz)

tests/integration_tests/test_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ def test_client_name(test_client: Client):
4040
assert 'py/' in user_agent
4141

4242

43-
def test_extra_http_headers(test_client: Client):
43+
def test_transport_settings(test_client: Client):
4444
result = test_client.query('SELECT name,database FROM system.tables',
45-
extra_http_headers={'X-Workload': 'ONLINE'})
45+
transport_settings={'X-Workload': 'ONLINE'})
4646
assert result.column_names == ('name', 'database')
4747
assert len(result.result_set) > 0
4848

0 commit comments

Comments
 (0)