Add client-level HTTP headers#776
Merged
Merged
Conversation
Need: clients behind Cloudflare Access or similar HTTP gateways must send authentication headers on every request, including the initialization queries that run during client creation. Design: expose an explicit headers keyword on sync and async factories instead of reusing transport_settings, because these values are actual HTTP headers and transport_settings already has overloaded request-level meaning. Merge after driver defaults so users can intentionally override headers like Authorization or User-Agent, while per-request transport_settings still wins for one-off requests. Tests: cover sync and async header merge behavior plus client_factory construction with Cloudflare-style headers.
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a new connection-level headers option to ClickHouse Connect HTTP clients so callers can attach HTTP headers to all requests (including the initialization queries executed during client construction).
Changes:
- Added
headers: dict[str, str] | Noneto syncHttpClientand asyncAsyncClientconstructors and applied them after driver-generated defaults. - Plumbed a
headerskeyword throughcreate_client/create_async_client(and thusget_client/get_async_client), including docstring updates. - Added unit and integration tests to validate initialization-time header presence and request-level override behavior.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
clickhouse_connect/driver/httpclient.py |
Adds a headers constructor arg and merges client-level headers into the default header set. |
clickhouse_connect/driver/asyncclient.py |
Adds a headers constructor arg and merges client-level headers into the default header set for async requests. |
clickhouse_connect/driver/__init__.py |
Exposes headers in sync/async client factory APIs and documents intended precedence. |
tests/unit_tests/test_driver/test_httpclient.py |
Adds unit tests for initialization-time headers and request-vs-client header precedence (sync/async). |
tests/integration_tests/test_client.py |
Adds an integration test verifying client-level headers are stored and normal commands still work. |
joe-clickhouse
left a comment
Contributor
There was a problem hiding this comment.
Hi @river-walras thanks for the contribution and thanks for addressing the comments so far. I have two things on my end:
- Sync
ping()currently bypasses the header merge in_raw_request()because it callsself.http.request(...)directly inhttpclient.py. Please build the ping headers from the client headers, e.g.headers=dict_copy(self.headers)so that ping has them as well. Note the asyncping()already sends them through the session defaults so we're good there. This will restore parity. A unit test for sync ping with client headers would be good to add. - I left a note in the code about Accept-Encoding in the docstrings.
Otherwise we're about there!
joe-clickhouse
approved these changes
Jun 8, 2026
joe-clickhouse
left a comment
Contributor
There was a problem hiding this comment.
Looks good, thanks @river-walras!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Add a connection-level
headerskeyword to both sync and async client creation so callers can attach HTTP headers to every request made by clickhouse-connect, including the initialization queries sent during client construction.Use Case
This supports ClickHouse deployments exposed through HTTP gateways that require request headers before traffic reaches ClickHouse. A concrete example is Cloudflare Tunnel with Cloudflare Access / Zero Trust service tokens:
Per-request
transport_settingscan already send HTTP headers for normal queries, but it cannot help with client initialization because_init_common_settings()runs before the caller can pass per-request options. Connection-level headers close that gap.Parameter Design
I used an explicit
headersparameter instead of promotingtransport_settingsto the constructor because the new values are plain HTTP headers, andtransport_settingsalready has overloaded request-level meaning in the driver. Keeping the names separate makes the public API easier to discover and avoids spreading that ambiguity to client creation.Header merge order is intentionally:
User-Agentheaderstransport_settingsHostfromserver_host_nameThis lets users intentionally override driver-generated headers when they need a gateway-specific escape hatch, while preserving per-request overrides for one-off calls.
Tests
Local validation run:
uv run ruff check clickhouse_connect/driver/__init__.py clickhouse_connect/driver/httpclient.py clickhouse_connect/driver/asyncclient.py tests/integration_tests/test_client.py tests/unit_tests/test_driver/test_httpclient.pyuv run pytest tests/unit_tests/test_driver/test_httpclient.py::TestHttpClientHeaders tests/unit_tests/test_driver/test_httpclient.py::TestAsyncClientHeaders -qThe focused integration test could not be completed locally because no ClickHouse server was listening on
localhost:8123.