Skip to content

Commit c820aa8

Browse files
committed
Add timezone support
1 parent 07a7546 commit c820aa8

File tree

6 files changed

+73
-22
lines changed

6 files changed

+73
-22
lines changed

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
sqlalchemy >= 2.0.7, < 3.0.0
2-
ydb >= 3.2.0
2+
ydb >= 3.11.3

test-requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
sqlalchemy==2.0.7
2-
ydb==3.2.0
2+
ydb==3.11.3
33

44
requests<2.29
55
pytest==7.2.2

test/test_core.py

Lines changed: 51 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import asyncio
2-
from datetime import date, datetime
2+
import datetime
33
from decimal import Decimal
44
from typing import NamedTuple
55

@@ -187,39 +187,42 @@ class TestTypes(TablesTest):
187187
__backend__ = True
188188

189189
@classmethod
190-
def define_tables(cls, metadata):
190+
def define_tables(cls, metadata: sa.MetaData):
191191
Table(
192-
"test_types",
192+
"test_primitive_types",
193193
metadata,
194-
Column("id", Integer, primary_key=True),
194+
Column("int", sa.Integer, primary_key=True),
195195
# Column("bin", sa.BINARY),
196196
Column("str", sa.String),
197-
Column("num", sa.Float),
198-
Column("bl", sa.Boolean),
199-
Column("ts", sa.TIMESTAMP),
197+
Column("float", sa.Float),
198+
Column("bool", sa.Boolean),
199+
)
200+
Table(
201+
"test_datetime_types",
202+
metadata,
203+
Column("datetime", sa.DateTime, primary_key=True),
204+
Column("datetime_tz", sa.DateTime(timezone=True)),
205+
Column("timestamp", sa.TIMESTAMP),
206+
Column("timestamp_tz", sa.TIMESTAMP(timezone=True)),
200207
Column("date", sa.Date),
201208
# Column("interval", sa.Interval),
202209
)
203210

204-
def test_select_types(self, connection):
205-
tb = self.tables.test_types
206-
207-
now, today = datetime.now(), date.today()
211+
def test_primitive_types(self, connection):
212+
table = self.tables.test_primitive_types
208213

209-
stm = tb.insert().values(
210-
id=1,
214+
statement = sa.insert(table).values(
215+
int=42,
211216
# bin=b"abc",
212217
str="Hello World!",
213-
num=3.5,
214-
bl=True,
215-
ts=now,
216-
date=today,
218+
float=3.5,
219+
bool=True,
217220
# interval=timedelta(minutes=45),
218221
)
219-
connection.execute(stm)
222+
connection.execute(statement)
220223

221-
row = connection.execute(sa.select(tb)).fetchone()
222-
assert row == (1, "Hello World!", 3.5, True, now, today)
224+
row = connection.execute(sa.select(table)).fetchone()
225+
assert row == (42, "Hello World!", 3.5, True)
223226

224227
def test_integer_types(self, connection):
225228
stmt = sa.Select(
@@ -236,6 +239,34 @@ def test_integer_types(self, connection):
236239
result = connection.execute(stmt).fetchone()
237240
assert result == (b"Uint8", b"Uint16", b"Uint32", b"Uint64", b"Int8", b"Int16", b"Int32", b"Int64")
238241

242+
def test_datetime_types(self, connection: sa.Connection):
243+
table = self.tables.test_datetime_types
244+
245+
now_dt = datetime.datetime.now()
246+
now_dt_tz = now_dt.replace(tzinfo=datetime.timezone(datetime.timedelta(hours=3, minutes=42)))
247+
today = now_dt.date()
248+
249+
statement = sa.insert(table).values(
250+
datetime=now_dt,
251+
datetime_tz=now_dt_tz,
252+
timestamp=now_dt,
253+
timestamp_tz=now_dt_tz,
254+
date=today,
255+
# interval=datetime.timedelta(minutes=45),
256+
)
257+
connection.execute(statement)
258+
259+
row = connection.execute(sa.select(table)).fetchone()
260+
261+
now_dt_tz_utc = now_dt.replace(tzinfo=datetime.timezone.utc) - datetime.timedelta(hours=3, minutes=42)
262+
assert row == (
263+
now_dt,
264+
now_dt_tz_utc, # YDB doesn't store timezone, so it is always utc
265+
now_dt,
266+
now_dt_tz_utc, # YDB doesn't store timezone, so it is always utc
267+
today,
268+
)
269+
239270

240271
class TestWithClause(TablesTest):
241272
__backend__ = True

ydb_sqlalchemy/sqlalchemy/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,7 @@ class YqlDialect(StrCompileDialect):
597597
colspecs = {
598598
sa.types.JSON: types.YqlJSON,
599599
sa.types.JSON.JSONPathType: types.YqlJSON.YqlJSONPathType,
600+
sa.types.DateTime: types.YqlDateTime,
600601
}
601602

602603
construct_arguments = [
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import datetime
2+
from typing import Optional
3+
4+
from sqlalchemy import Dialect
5+
from sqlalchemy import types as sqltypes
6+
from sqlalchemy.sql.type_api import _ResultProcessorType
7+
8+
9+
class YqlDateTime(sqltypes.DateTime):
10+
def result_processor(self, dialect: Dialect, coltype: str) -> Optional[_ResultProcessorType[datetime.datetime]]:
11+
def process(value: Optional[datetime.datetime]) -> Optional[datetime.datetime]:
12+
if value is None:
13+
return None
14+
return value.replace(tzinfo=datetime.timezone.utc)
15+
16+
if self.timezone:
17+
return process
18+
return None

ydb_sqlalchemy/sqlalchemy/types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from sqlalchemy import ARRAY, ColumnElement, exc, types
44
from sqlalchemy.sql import type_api
55

6+
from .datetime_types import YqlDateTime # noqa: F401
67
from .json import YqlJSON # noqa: F401
78

89

0 commit comments

Comments
 (0)