Skip to content

Commit a444682

Browse files
committed
added Date and Time column types
1 parent ef1f133 commit a444682

File tree

8 files changed

+165
-48
lines changed

8 files changed

+165
-48
lines changed

piccolo/columns/column_types.py

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
from __future__ import annotations
22
import copy
3-
from datetime import datetime
3+
from datetime import datetime, date, time
44
import decimal
55
import typing as t
66
import uuid
77

88
from piccolo.columns.base import Column, OnDelete, OnUpdate, ForeignKeyMeta
99
from piccolo.columns.operators.string import ConcatPostgres, ConcatSQLite
10-
from piccolo.custom_types import Datetime, UUIDDefault
10+
from piccolo.custom_types import UUIDDefault
1111
from piccolo.querystring import Unquoted, QueryString
1212

1313
if t.TYPE_CHECKING:
14-
from piccolo.table import Table # noqa
15-
from piccolo.custom_types import Datetime, UUID_
14+
from piccolo.table import Table
15+
from piccolo.custom_types import DatetimeArg, UUIDArg, TimeArg, DateArg
1616

1717

1818
###############################################################################
@@ -195,20 +195,11 @@ class UUID(Column):
195195

196196
value_type = uuid.UUID
197197

198-
def __init__(self, default: UUID_ = UUIDDefault.uuid4, **kwargs) -> None:
198+
def __init__(self, default: UUIDArg = UUIDDefault.uuid4, **kwargs) -> None:
199199
self.default = default
200200
kwargs.update({"default": default})
201201
super().__init__(**kwargs)
202202

203-
@property
204-
def column_type(self):
205-
engine_type = self._meta.table._meta.db.engine_type
206-
if engine_type == "postgres":
207-
return "UUID"
208-
elif engine_type == "sqlite":
209-
return "VARCHAR"
210-
raise Exception("Unrecognized engine type")
211-
212203

213204
class Integer(Column):
214205

@@ -353,16 +344,40 @@ def __init__(self, **kwargs) -> None:
353344
super().__init__(**kwargs)
354345

355346

347+
###############################################################################
348+
349+
356350
class Timestamp(Column):
357351

358352
value_type = datetime
359353

360-
def __init__(self, default: Datetime = None, **kwargs) -> None:
354+
def __init__(self, default: DatetimeArg = None, **kwargs) -> None:
355+
self.default = default
356+
kwargs.update({"default": default})
357+
super().__init__(**kwargs)
358+
359+
360+
class Date(Column):
361+
value_type = date
362+
363+
def __init__(self, default: DatetimeArg = None, **kwargs) -> None:
361364
self.default = default
362365
kwargs.update({"default": default})
363366
super().__init__(**kwargs)
364367

365368

369+
class Time(Column):
370+
value_type = time
371+
372+
def __init__(self, default: TimeArg = None, **kwargs) -> None:
373+
self.default = default
374+
kwargs.update({"default": default})
375+
super().__init__(**kwargs)
376+
377+
378+
###############################################################################
379+
380+
366381
class Boolean(Column):
367382

368383
value_type = bool

piccolo/custom_types.py

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
Iterable = t.Iterable[t.Any]
1414

1515

16+
###############################################################################
17+
18+
1619
@dataclass
1720
class Default:
1821
python: t.Callable
@@ -28,22 +31,29 @@ class DatetimeDefault(Enum):
2831
)
2932

3033

34+
class DateDefault(Enum):
35+
now = Default(
36+
python=lambda: datetime.datetime.now().date(),
37+
postgres="current_date",
38+
sqlite="current_date",
39+
)
40+
41+
42+
class TimeDefault(Enum):
43+
now = Default(
44+
python=lambda: datetime.datetime.now().time(),
45+
postgres="current_time",
46+
sqlite="current_time",
47+
)
48+
49+
3150
class UUIDDefault(Enum):
3251
uuid4 = Default(
3352
python=uuid.uuid4, postgres="uuid_generate_v4()", sqlite="''"
3453
)
3554

3655

37-
UUID_ = t.Union[UUIDDefault, uuid.UUID]
38-
Datetime = t.Union[datetime.datetime, DatetimeDefault, None]
39-
40-
41-
__all__ = (
42-
"Combinable",
43-
"Iterable",
44-
"Datetime",
45-
"DatetimeDefault",
46-
"Default",
47-
"UUID_",
48-
"UUIDDefault",
49-
)
56+
UUIDArg = t.Union[UUIDDefault, uuid.UUID]
57+
DatetimeArg = t.Union[datetime.datetime, DatetimeDefault, None]
58+
DateArg = t.Union[datetime.date, DateDefault, None]
59+
TimeArg = t.Union[datetime.time, TimeDefault, None]

piccolo/engine/postgres.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ def prep_database(self):
253253
print(
254254
"Unable to create uuid-ossp extension - UUID columns might "
255255
"not behave as expected. Make sure your database user has "
256-
"permission to create extensions."
256+
"permission to create extensions, or add it manually."
257257
)
258258

259259
###########################################################################

piccolo/engine/sqlite.py

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22
import contextvars
33
from dataclasses import dataclass
4+
import datetime
45
from decimal import Decimal
56
import os
67
import sqlite3
@@ -33,49 +34,65 @@ def convert_uuid_in(value):
3334
"""
3435
Converts the UUID value being passed into sqlite.
3536
"""
36-
return "uuid:" + str(value)
37+
return str(value)
3738

3839

39-
def convert_numeric_out(value: bytes):
40+
def convert_time_in(value):
41+
"""
42+
Converts the time value being passed into sqlite.
43+
"""
44+
return value.isoformat()
45+
46+
47+
def convert_date_in(value):
48+
"""
49+
Converts the date value being passed into sqlite.
50+
"""
51+
return value.isoformat()
52+
53+
54+
def convert_numeric_out(value: bytes) -> Decimal:
4055
"""
4156
Convert float values into Decimals.
4257
"""
4358
return Decimal(value.decode("ascii"))
4459

4560

46-
def convert_int_out(value: bytes):
61+
def convert_int_out(value: bytes) -> int:
4762
"""
4863
Make sure Integer values are actually of type int.
4964
"""
5065
return int(float(value))
5166

5267

53-
def convert_uuid_out(value: bytes):
68+
def convert_uuid_out(value: bytes) -> uuid.UUID:
5469
"""
5570
If the value is a uuid, convert it to a UUID instance.
71+
"""
72+
return uuid.UUID(value.decode("utf8"))
73+
74+
75+
def convert_date_out(value: bytes) -> datetime.date:
76+
return datetime.date.fromisoformat(value.decode("utf8"))
77+
5678

57-
Performance wise, this isn't great, but SQLite isn't the preferred solution
58-
in production, so it's acceptable.
79+
def convert_time_out(value: bytes) -> datetime.time:
5980
"""
60-
decoded = value.decode("utf8")
61-
if decoded.startswith("uuid:"):
62-
uuid_string = decoded.split("uuid:", 1)[1]
63-
try:
64-
_uuid = uuid.UUID(uuid_string)
65-
except ValueError:
66-
return decoded
67-
else:
68-
return _uuid
69-
else:
70-
return decoded
81+
If the value is a uuid, convert it to a UUID instance.
82+
"""
83+
return datetime.time.fromisoformat(value.decode("utf8"))
7184

7285

7386
sqlite3.register_converter("Numeric", convert_numeric_out)
7487
sqlite3.register_converter("Integer", convert_int_out)
75-
sqlite3.register_converter("Varchar", convert_uuid_out)
88+
sqlite3.register_converter("UUID", convert_uuid_out)
89+
sqlite3.register_converter("Date", convert_date_out)
90+
sqlite3.register_converter("Time", convert_time_out)
7691

7792
sqlite3.register_adapter(Decimal, convert_numeric_in)
7893
sqlite3.register_adapter(uuid.UUID, convert_uuid_in)
94+
sqlite3.register_adapter(datetime.time, convert_time_in)
95+
sqlite3.register_adapter(datetime.date, convert_date_in)
7996

8097
###############################################################################
8198

tests/columns/test_date.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import datetime
2+
from unittest import TestCase
3+
4+
from piccolo.table import Table
5+
from piccolo.columns.column_types import Date
6+
7+
8+
class MyTable(Table):
9+
created_on = Date()
10+
11+
12+
class TestDate(TestCase):
13+
def setUp(self):
14+
MyTable.create_table().run_sync()
15+
16+
def tearDown(self):
17+
MyTable.alter().drop_table().run_sync()
18+
19+
def test_timestamp(self):
20+
created_on = datetime.datetime.now().date()
21+
row = MyTable(created_on=created_on)
22+
row.save().run_sync()
23+
24+
result = MyTable.objects().first().run_sync()
25+
self.assertEqual(result.created_on, created_on)

tests/columns/test_time.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import datetime
2+
from unittest import TestCase
3+
4+
from piccolo.table import Table
5+
from piccolo.columns.column_types import Time
6+
7+
8+
class MyTable(Table):
9+
created_on = Time()
10+
11+
12+
class TestTime(TestCase):
13+
def setUp(self):
14+
MyTable.create_table().run_sync()
15+
16+
def tearDown(self):
17+
MyTable.alter().drop_table().run_sync()
18+
19+
def test_timestamp(self):
20+
created_on = datetime.datetime.now().time()
21+
row = MyTable(created_on=created_on)
22+
row.save().run_sync()
23+
24+
result = MyTable.objects().first().run_sync()
25+
self.assertEqual(result.created_on, created_on)

tests/columns/test_timestamp.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import datetime
2+
from unittest import TestCase
3+
4+
from piccolo.table import Table
5+
from piccolo.columns.column_types import Timestamp
6+
7+
8+
class MyTable(Table):
9+
created_on = Timestamp()
10+
11+
12+
class TestTimestamp(TestCase):
13+
def setUp(self):
14+
MyTable.create_table().run_sync()
15+
16+
def tearDown(self):
17+
MyTable.alter().drop_table().run_sync()
18+
19+
def test_timestamp(self):
20+
created_on = datetime.datetime.now()
21+
row = MyTable(created_on=created_on)
22+
row.save().run_sync()
23+
24+
result = MyTable.objects().first().run_sync()
25+
self.assertEqual(result.created_on, created_on)

tests/columns/test_uuid.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def setUp(self):
1616
def tearDown(self):
1717
MyTable.alter().drop_table().run_sync()
1818

19-
def test_creation(self):
19+
def test_return_type(self):
2020
row = MyTable()
2121
row.save().run_sync()
2222

0 commit comments

Comments
 (0)