Skip to content

Commit 32c93d8

Browse files
add PEP 249 DB API constructors (#940)
* add PEP 249 DB API constructors * docs update
1 parent 0f05a3c commit 32c93d8

4 files changed

Lines changed: 54 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
### Bug Fixes
1515

16+
- DB API module now provides the PEP 249 type constructors `Binary`, `Date`, `Time`, `Timestamp`, `DateFromTicks`, `TimeFromTicks`, and `TimestampFromTicks`. SQLAlchemy `LargeBinary` inserts no longer raise `AttributeError`. Addresses the Binary constructor failure in [#919](https://github.com/ClickHouse/clickhouse-connect/issues/919).
1617
- Fractional `DateTime64` values before the Unix epoch now serialize with the correct second. The serializer truncated negative timestamps toward zero before adding the fractional component, which shifted affected values forward by one second. This affected Python `datetime` values and accepted ISO strings in both naive datetime insert modes. See [#938](https://github.com/ClickHouse/clickhouse-connect/issues/938).
1718
- Parsing a nested `Variant`, `Tuple`, `Nested`, or typed `JSON` column type whose element is an `Enum` with an escaped single quote in a value name no longer corrupts the escape sequence and fails while re-parsing the element type. Closes [#878](https://github.com/ClickHouse/clickhouse-connect/issues/878).
1819
- `None` nested inside an `Array` or `Tuple`, or inside a `Map` when `dict_parameter_format="map"`, now renders as the SQL `NULL` keyword instead of the `\N` sentinel used for top-level values. Top-level scalar `None` binds are unchanged. Closes [#879](https://github.com/ClickHouse/clickhouse-connect/issues/879).

clickhouse_connect/dbapi/__init__.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from datetime import date, datetime, time
2+
from time import localtime
13
from typing import Any
24

35
from clickhouse_connect.dbapi.connection import Connection
@@ -6,6 +8,24 @@
68
threadsafety = 2 # PEP 249 Threads may share the module and connections.
79
paramstyle = "pyformat" # PEP 249 Python extended format codes, e.g. ...WHERE name=%(name)s
810

11+
# PEP 249 type constructors.
12+
Date = date
13+
Time = time
14+
Timestamp = datetime
15+
Binary = bytes
16+
17+
18+
def DateFromTicks(ticks: float) -> date: # noqa: N802
19+
return date(*localtime(ticks)[:3])
20+
21+
22+
def TimeFromTicks(ticks: float) -> time: # noqa: N802
23+
return time(*localtime(ticks)[3:6])
24+
25+
26+
def TimestampFromTicks(ticks: float) -> datetime: # noqa: N802
27+
return datetime(*localtime(ticks)[:6])
28+
929

1030
class Error(Exception):
1131
pass

docs/driver-api.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -792,7 +792,7 @@ For advanced use cases requiring direct access to ClickHouse HTTP interfaces wit
792792

793793
## Python DB-API 2.0 {#python-db-api-20}
794794

795-
The `clickhouse_connect.dbapi` module implements the PEP 249 connection and cursor interface. It declares API level 2.0, `threadsafety=2`, and `paramstyle="pyformat"`.
795+
The `clickhouse_connect.dbapi` module implements the PEP 249 connection and cursor interface. It declares API level 2.0, `threadsafety=2`, and `paramstyle="pyformat"`. The module also provides the PEP 249 type constructors `Date`, `Time`, `Timestamp`, and `Binary`, and the `DateFromTicks`, `TimeFromTicks`, and `TimestampFromTicks` functions.
796796

797797
```python
798798
from clickhouse_connect import dbapi
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from datetime import date, datetime, time
2+
3+
import pytest
4+
5+
import clickhouse_connect.dbapi as dbapi
6+
7+
8+
def test_binary_constructor():
9+
assert dbapi.Binary(b"\x00\xff") == b"\x00\xff"
10+
assert dbapi.Binary([0, 255]) == b"\x00\xff"
11+
12+
13+
def test_date_time_timestamp_constructors():
14+
assert dbapi.Date(2026, 8, 5) == date(2026, 8, 5)
15+
assert dbapi.Time(13, 14, 15) == time(13, 14, 15)
16+
assert dbapi.Timestamp(2026, 8, 5, 13, 14, 15) == datetime(2026, 8, 5, 13, 14, 15)
17+
18+
19+
@pytest.mark.parametrize("ticks", [0, 1_799_821_913.792341])
20+
def test_tick_constructors_use_local_time(ticks):
21+
local_datetime = datetime.fromtimestamp(ticks)
22+
assert dbapi.DateFromTicks(ticks) == local_datetime.date()
23+
assert dbapi.TimeFromTicks(ticks) == local_datetime.time().replace(microsecond=0)
24+
assert dbapi.TimestampFromTicks(ticks) == local_datetime.replace(microsecond=0)
25+
26+
27+
def test_sqlalchemy_dialect_import_dbapi_exposes_binary():
28+
pytest.importorskip("sqlalchemy")
29+
30+
from clickhouse_connect.cc_sqlalchemy.dialect import ClickHouseDialect
31+
32+
assert ClickHouseDialect.import_dbapi().Binary(b"x") == b"x"

0 commit comments

Comments
 (0)