piccolo 1.6.0__py3-none-any.whl → 1.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
piccolo/__init__.py CHANGED
@@ -1 +1 @@
1
- __VERSION__ = "1.6.0"
1
+ __VERSION__ = "1.7.0"
@@ -2532,7 +2532,14 @@ class Array(Column):
2532
2532
  if engine_type in ("postgres", "cockroach"):
2533
2533
  return f"{self.base_column.column_type}[]"
2534
2534
  elif engine_type == "sqlite":
2535
- return "ARRAY"
2535
+ inner_column = self._get_inner_column()
2536
+ return (
2537
+ f"ARRAY_{inner_column.column_type}"
2538
+ if isinstance(
2539
+ inner_column, (Date, Timestamp, Timestamptz, Time)
2540
+ )
2541
+ else "ARRAY"
2542
+ )
2536
2543
  raise Exception("Unrecognized engine type")
2537
2544
 
2538
2545
  def _setup_base_column(self, table_class: t.Type[Table]):
@@ -2564,6 +2571,23 @@ class Array(Column):
2564
2571
  else:
2565
2572
  return start + 1
2566
2573
 
2574
+ def _get_inner_column(self) -> Column:
2575
+ """
2576
+ A helper function to get the innermost ``Column`` for the array. For
2577
+ example::
2578
+
2579
+ >>> Array(Varchar())._get_inner_column()
2580
+ Varchar
2581
+
2582
+ >>> Array(Array(Varchar()))._get_inner_column()
2583
+ Varchar
2584
+
2585
+ """
2586
+ if isinstance(self.base_column, Array):
2587
+ return self.base_column._get_inner_column()
2588
+ else:
2589
+ return self.base_column
2590
+
2567
2591
  def _get_inner_value_type(self) -> t.Type:
2568
2592
  """
2569
2593
  A helper function to get the innermost value type for the array. For
@@ -2576,10 +2600,7 @@ class Array(Column):
2576
2600
  str
2577
2601
 
2578
2602
  """
2579
- if isinstance(self.base_column, Array):
2580
- return self.base_column._get_inner_value_type()
2581
- else:
2582
- return self.base_column.value_type
2603
+ return self._get_inner_column().value_type
2583
2604
 
2584
2605
  def __getitem__(self, value: int) -> Array:
2585
2606
  """
piccolo/engine/sqlite.py CHANGED
@@ -9,6 +9,7 @@ import typing as t
9
9
  import uuid
10
10
  from dataclasses import dataclass
11
11
  from decimal import Decimal
12
+ from functools import partial, wraps
12
13
 
13
14
  from piccolo.engine.base import Batch, Engine, validate_savepoint_name
14
15
  from piccolo.engine.exceptions import TransactionError
@@ -35,14 +36,14 @@ if t.TYPE_CHECKING: # pragma: no cover
35
36
  # In
36
37
 
37
38
 
38
- def convert_numeric_in(value):
39
+ def convert_numeric_in(value: Decimal) -> float:
39
40
  """
40
41
  Convert any Decimal values into floats.
41
42
  """
42
43
  return float(value)
43
44
 
44
45
 
45
- def convert_uuid_in(value) -> str:
46
+ def convert_uuid_in(value: uuid.UUID) -> str:
46
47
  """
47
48
  Converts the UUID value being passed into sqlite.
48
49
  """
@@ -56,7 +57,7 @@ def convert_time_in(value: datetime.time) -> str:
56
57
  return value.isoformat()
57
58
 
58
59
 
59
- def convert_date_in(value: datetime.date):
60
+ def convert_date_in(value: datetime.date) -> str:
60
61
  """
61
62
  Converts the date value being passed into sqlite.
62
63
  """
@@ -74,122 +75,235 @@ def convert_datetime_in(value: datetime.datetime) -> str:
74
75
  return str(value)
75
76
 
76
77
 
77
- def convert_timedelta_in(value: datetime.timedelta):
78
+ def convert_timedelta_in(value: datetime.timedelta) -> float:
78
79
  """
79
80
  Converts the timedelta value being passed into sqlite.
80
81
  """
81
82
  return value.total_seconds()
82
83
 
83
84
 
84
- def convert_array_in(value: list):
85
+ def convert_array_in(value: list) -> str:
85
86
  """
86
- Converts a list value into a string.
87
+ Converts a list value into a string (it handles nested lists, and type like
88
+ dateime/ time / date which aren't usually JSON serialisable.).
89
+
87
90
  """
88
- if value and type(value[0]) not in [str, int, float, list]:
89
- raise ValueError("Can only serialise str, int, float, and list.")
90
91
 
91
- return dump_json(value)
92
+ def serialise(data: list):
93
+ output = []
94
+
95
+ for item in data:
96
+ if isinstance(item, list):
97
+ output.append(serialise(item))
98
+ elif isinstance(
99
+ item, (datetime.datetime, datetime.time, datetime.date)
100
+ ):
101
+ if adapter := ADAPTERS.get(type(item)):
102
+ output.append(adapter(item))
103
+ else:
104
+ raise ValueError("The adapter wasn't found.")
105
+ elif item is None or isinstance(item, (str, int, float, list)):
106
+ # We can safely JSON serialise these.
107
+ output.append(item)
108
+ else:
109
+ raise ValueError("We can't currently serialise this value.")
110
+
111
+ return output
112
+
113
+ return dump_json(serialise(value))
114
+
115
+
116
+ ###############################################################################
117
+
118
+ # Register adapters
119
+
120
+ ADAPTERS: t.Dict[t.Type, t.Callable[[t.Any], t.Any]] = {
121
+ Decimal: convert_numeric_in,
122
+ uuid.UUID: convert_uuid_in,
123
+ datetime.time: convert_time_in,
124
+ datetime.date: convert_date_in,
125
+ datetime.datetime: convert_datetime_in,
126
+ datetime.timedelta: convert_timedelta_in,
127
+ list: convert_array_in,
128
+ }
92
129
 
130
+ for value_type, adapter in ADAPTERS.items():
131
+ sqlite3.register_adapter(value_type, adapter)
132
+
133
+ ###############################################################################
93
134
 
94
135
  # Out
95
136
 
96
137
 
97
- def convert_numeric_out(value: bytes) -> Decimal:
138
+ def decode_to_string(converter: t.Callable[[str], t.Any]):
139
+ """
140
+ This means we can use our converters with string and bytes. They are
141
+ passed bytes when used directly via SQLite, and are passed strings when
142
+ used by the array converters.
143
+ """
144
+
145
+ @wraps(converter)
146
+ def wrapper(value: t.Union[str, bytes]) -> t.Any:
147
+ if isinstance(value, bytes):
148
+ return converter(value.decode("utf8"))
149
+ elif isinstance(value, str):
150
+ return converter(value)
151
+ else:
152
+ raise ValueError("Unsupported type")
153
+
154
+ return wrapper
155
+
156
+
157
+ @decode_to_string
158
+ def convert_numeric_out(value: str) -> Decimal:
98
159
  """
99
160
  Convert float values into Decimals.
100
161
  """
101
- return Decimal(value.decode("ascii"))
162
+ return Decimal(value)
102
163
 
103
164
 
104
- def convert_int_out(value: bytes) -> int:
165
+ @decode_to_string
166
+ def convert_int_out(value: str) -> int:
105
167
  """
106
168
  Make sure Integer values are actually of type int.
107
169
  """
108
170
  return int(float(value))
109
171
 
110
172
 
111
- def convert_uuid_out(value: bytes) -> uuid.UUID:
173
+ @decode_to_string
174
+ def convert_uuid_out(value: str) -> uuid.UUID:
112
175
  """
113
176
  If the value is a uuid, convert it to a UUID instance.
114
177
  """
115
- return uuid.UUID(value.decode("utf8"))
178
+ return uuid.UUID(value)
116
179
 
117
180
 
118
- def convert_date_out(value: bytes) -> datetime.date:
119
- return datetime.date.fromisoformat(value.decode("utf8"))
181
+ @decode_to_string
182
+ def convert_date_out(value: str) -> datetime.date:
183
+ return datetime.date.fromisoformat(value)
120
184
 
121
185
 
122
- def convert_time_out(value: bytes) -> datetime.time:
186
+ @decode_to_string
187
+ def convert_time_out(value: str) -> datetime.time:
123
188
  """
124
189
  If the value is a time, convert it to a UUID instance.
125
190
  """
126
- return datetime.time.fromisoformat(value.decode("utf8"))
191
+ return datetime.time.fromisoformat(value)
127
192
 
128
193
 
129
- def convert_seconds_out(value: bytes) -> datetime.timedelta:
194
+ @decode_to_string
195
+ def convert_seconds_out(value: str) -> datetime.timedelta:
130
196
  """
131
197
  If the value is from a seconds column, convert it to a timedelta instance.
132
198
  """
133
- return datetime.timedelta(seconds=float(value.decode("utf8")))
199
+ return datetime.timedelta(seconds=float(value))
134
200
 
135
201
 
136
- def convert_boolean_out(value: bytes) -> bool:
202
+ @decode_to_string
203
+ def convert_boolean_out(value: str) -> bool:
137
204
  """
138
205
  If the value is from a boolean column, convert it to a bool value.
139
206
  """
140
- _value = value.decode("utf8")
141
- return _value == "1"
207
+ return value == "1"
142
208
 
143
209
 
144
- def convert_timestamp_out(value: bytes) -> datetime.datetime:
210
+ @decode_to_string
211
+ def convert_timestamp_out(value: str) -> datetime.datetime:
145
212
  """
146
213
  If the value is from a timestamp column, convert it to a datetime value.
147
214
  """
148
- return datetime.datetime.fromisoformat(value.decode("utf8"))
215
+ return datetime.datetime.fromisoformat(value)
149
216
 
150
217
 
151
- def convert_timestamptz_out(value: bytes) -> datetime.datetime:
218
+ @decode_to_string
219
+ def convert_timestamptz_out(value: str) -> datetime.datetime:
152
220
  """
153
221
  If the value is from a timestamptz column, convert it to a datetime value,
154
222
  with a timezone of UTC.
155
223
  """
156
- _value = datetime.datetime.fromisoformat(value.decode("utf8"))
157
- _value = _value.replace(tzinfo=datetime.timezone.utc)
158
- return _value
224
+ return datetime.datetime.fromisoformat(value).replace(
225
+ tzinfo=datetime.timezone.utc
226
+ )
159
227
 
160
228
 
161
- def convert_array_out(value: bytes) -> t.List:
229
+ @decode_to_string
230
+ def convert_array_out(value: str) -> t.List:
162
231
  """
163
232
  If the value if from an array column, deserialise the string back into a
164
233
  list.
165
234
  """
166
- return load_json(value.decode("utf8"))
167
-
168
-
169
- def convert_M2M_out(value: bytes) -> t.List:
170
- _value = value.decode("utf8")
171
- return _value.split(",")
172
-
173
-
174
- sqlite3.register_converter("Numeric", convert_numeric_out)
175
- sqlite3.register_converter("Integer", convert_int_out)
176
- sqlite3.register_converter("UUID", convert_uuid_out)
177
- sqlite3.register_converter("Date", convert_date_out)
178
- sqlite3.register_converter("Time", convert_time_out)
179
- sqlite3.register_converter("Seconds", convert_seconds_out)
180
- sqlite3.register_converter("Boolean", convert_boolean_out)
181
- sqlite3.register_converter("Timestamp", convert_timestamp_out)
182
- sqlite3.register_converter("Timestamptz", convert_timestamptz_out)
183
- sqlite3.register_converter("Array", convert_array_out)
184
- sqlite3.register_converter("M2M", convert_M2M_out)
185
-
186
- sqlite3.register_adapter(Decimal, convert_numeric_in)
187
- sqlite3.register_adapter(uuid.UUID, convert_uuid_in)
188
- sqlite3.register_adapter(datetime.time, convert_time_in)
189
- sqlite3.register_adapter(datetime.date, convert_date_in)
190
- sqlite3.register_adapter(datetime.datetime, convert_datetime_in)
191
- sqlite3.register_adapter(datetime.timedelta, convert_timedelta_in)
192
- sqlite3.register_adapter(list, convert_array_in)
235
+ return load_json(value)
236
+
237
+
238
+ def convert_complex_array_out(value: bytes, converter: t.Callable):
239
+ """
240
+ This is used to handle arrays of things like timestamps, which we can't
241
+ just load from JSON without doing additional work to convert the elements
242
+ back into Python objects.
243
+ """
244
+ parsed = load_json(value.decode("utf8"))
245
+
246
+ def convert_list(list_value: t.List):
247
+ output = []
248
+
249
+ for value in list_value:
250
+ if isinstance(value, list):
251
+ # For nested arrays
252
+ output.append(convert_list(value))
253
+ elif isinstance(value, str):
254
+ output.append(converter(value))
255
+ else:
256
+ output.append(value)
257
+
258
+ return output
259
+
260
+ if isinstance(parsed, list):
261
+ return convert_list(parsed)
262
+ else:
263
+ return parsed
264
+
265
+
266
+ @decode_to_string
267
+ def convert_M2M_out(value: str) -> t.List:
268
+ return value.split(",")
269
+
270
+
271
+ ###############################################################################
272
+ # Register the basic converters
273
+
274
+ CONVERTERS = {
275
+ "NUMERIC": convert_numeric_out,
276
+ "INTEGER": convert_int_out,
277
+ "UUID": convert_uuid_out,
278
+ "DATE": convert_date_out,
279
+ "TIME": convert_time_out,
280
+ "SECONDS": convert_seconds_out,
281
+ "BOOLEAN": convert_boolean_out,
282
+ "TIMESTAMP": convert_timestamp_out,
283
+ "TIMESTAMPTZ": convert_timestamptz_out,
284
+ "M2M": convert_M2M_out,
285
+ }
286
+
287
+ for column_name, converter in CONVERTERS.items():
288
+ sqlite3.register_converter(column_name, converter)
289
+
290
+ ###############################################################################
291
+ # Register the array converters
292
+
293
+ # The ARRAY column type handles values which can be easily serialised to and
294
+ # from JSON.
295
+ sqlite3.register_converter("ARRAY", convert_array_out)
296
+
297
+ # We have special column types for arrays of timestamps etc, as simply loading
298
+ # the JSON isn't sufficient.
299
+ for column_name in ("TIMESTAMP", "TIMESTAMPTZ", "DATE", "TIME"):
300
+ sqlite3.register_converter(
301
+ f"ARRAY_{column_name}",
302
+ partial(
303
+ convert_complex_array_out,
304
+ converter=CONVERTERS[column_name],
305
+ ),
306
+ )
193
307
 
194
308
  ###############################################################################
195
309
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: piccolo
3
- Version: 1.6.0
3
+ Version: 1.7.0
4
4
  Summary: A fast, user friendly ORM and query builder which supports asyncio.
5
5
  Home-page: https://github.com/piccolo-orm/piccolo
6
6
  Author: Daniel Townsend
@@ -1,4 +1,4 @@
1
- piccolo/__init__.py,sha256=goBemmcyJmGj3ijZ-wUC400jA5cZbAGtScaxWt1tqms,22
1
+ piccolo/__init__.py,sha256=h4xA2N06XGCcrqnSQbI2FfqW4dGycKVDmFXCAklesxA,22
2
2
  piccolo/custom_types.py,sha256=7HMQAze-5mieNLfbQ5QgbRQgR2abR7ol0qehv2SqROY,604
3
3
  piccolo/main.py,sha256=1VsFV67FWTUikPTysp64Fmgd9QBVa_9wcwKfwj2UCEA,5117
4
4
  piccolo/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -117,7 +117,7 @@ piccolo/apps/user/piccolo_migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeu
117
117
  piccolo/columns/__init__.py,sha256=OYhO_n9anMiU9nL-K6ATq9FhAtm8RyMpqYQ7fTVbhxI,1120
118
118
  piccolo/columns/base.py,sha256=XUhhx-wNc6nBPd39VIYuNfFERTaFzow9SHGfZjJ2YC0,31288
119
119
  piccolo/columns/choices.py,sha256=-HNQuk9vMmVZIPZ5PMeXGTfr23o4nzKPSAkvcG1k0y8,723
120
- piccolo/columns/column_types.py,sha256=tFOHOMOaZ3xPl6glQEd23N1oyeLGbu3qGlTuxPb7ToQ,81069
120
+ piccolo/columns/column_types.py,sha256=CzbNnP_VWvz6_r4aaRcMHiHZOaWHeq5IGaN8WJ7JGPA,81685
121
121
  piccolo/columns/combination.py,sha256=vMXC2dfY7pvnCFhsT71XFVyb4gdQzfRsCMaiduu04Ss,6900
122
122
  piccolo/columns/indexes.py,sha256=NfNok3v_791jgDlN28KmhP9ZCjl6031BXmjxV3ovXJk,372
123
123
  piccolo/columns/m2m.py,sha256=vRJZqBcBP3TQ9Mmb7UEqTgg0QoxIIjIu6JfGLAi4X8Q,14595
@@ -144,7 +144,7 @@ piccolo/engine/cockroach.py,sha256=7anXR3JPpGuR6-OpDLHM0FxKZhjuTvbRUuZV6fv9lXU,1
144
144
  piccolo/engine/exceptions.py,sha256=X8xZiTF-L9PIqFT-KDXnv1jFIIOZMF8fYK692chttJE,44
145
145
  piccolo/engine/finder.py,sha256=GjzBNtzRzH79fjtRn7OI3nZiOXE8JfoQWAvHVPrPNx4,507
146
146
  piccolo/engine/postgres.py,sha256=zUY6x52QrZ8waiqEUuqlVFiXyzAXrsFi3PY5EJnv3DM,18276
147
- piccolo/engine/sqlite.py,sha256=h5RrrDqy-28ck8L9SkLURfZWFSTcVdojTLDt1w8cTgk,22099
147
+ piccolo/engine/sqlite.py,sha256=edwACs4RWsbzgoozBJRN5l9_Vq4nCMefRRLzuZYsF7M,25033
148
148
  piccolo/query/__init__.py,sha256=bcsMV4813rMRAIqGv4DxI4eyO4FmpXkDv9dfTk5pt3A,699
149
149
  piccolo/query/base.py,sha256=G8Mwz0GcHY4Xs5Co9ubCNMI-3orfOsDdRDOnFRws7TU,15212
150
150
  piccolo/query/mixins.py,sha256=1RyhORDRwTZF9m_2uEgc6sOSd2uViXivBAaFN8geq5g,21982
@@ -241,7 +241,7 @@ tests/apps/user/commands/test_change_permissions.py,sha256=uVKEiT1EKot3VA2TDETdQ
241
241
  tests/apps/user/commands/test_create.py,sha256=iJ3Tti62rHwvdcTwNXrc5JPam6vR1qxKRdMN456vm3o,2250
242
242
  tests/apps/user/commands/test_list.py,sha256=ipPfGdW6fH7q-Jc7JcYUvlioGmH9GQU0WImZGC2m-XQ,2840
243
243
  tests/columns/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
244
- tests/columns/test_array.py,sha256=R6dnUBGcMHi5pVhczI4ydWpXsu2FqRASu6KtkhDWfZo,6660
244
+ tests/columns/test_array.py,sha256=Kd8yy3C4cXJIgutY-cv0MrE4mvgr52qqVUdkcazW_uI,9298
245
245
  tests/columns/test_base.py,sha256=CTqCNcrqAJTjLXe3MCZgTczrmB3jcVRcOpU4FilpLoQ,3918
246
246
  tests/columns/test_bigint.py,sha256=a0B4y1H02ww5qaW574X2lyenbY6o29ztOhiaqybPC0c,1149
247
247
  tests/columns/test_boolean.py,sha256=kDESp6FnRtSZhuqIu0dBRwKMSpS5TFbbs3sz2MyZSs8,1720
@@ -355,9 +355,9 @@ tests/utils/test_sql_values.py,sha256=vzxRmy16FfLZPH-sAQexBvsF9MXB8n4smr14qoEOS5
355
355
  tests/utils/test_sync.py,sha256=9ytVo56y2vPQePvTeIi9lHIouEhWJbodl1TmzkGFrSo,799
356
356
  tests/utils/test_table_reflection.py,sha256=SIzuat-IpcVj1GCFyOWKShI8YkhdOPPFH7qVrvfyPNE,3794
357
357
  tests/utils/test_warnings.py,sha256=NvSC_cvJ6uZcwAGf1m-hLzETXCqprXELL8zg3TNLVMw,269
358
- piccolo-1.6.0.dist-info/LICENSE,sha256=zFIpi-16uIJ420UMIG75NU0JbDBykvrdnXcj5U_EYBI,1059
359
- piccolo-1.6.0.dist-info/METADATA,sha256=48RdfV_g7BsGWelO0pWdGenSa6Ri1kSbN0bvWiaH85Q,5177
360
- piccolo-1.6.0.dist-info/WHEEL,sha256=00yskusixUoUt5ob_CiUp6LsnN5lqzTJpoqOFg_FVIc,92
361
- piccolo-1.6.0.dist-info/entry_points.txt,sha256=SJPHET4Fi1bN5F3WqcKkv9SClK3_F1I7m4eQjk6AFh0,46
362
- piccolo-1.6.0.dist-info/top_level.txt,sha256=-SR74VGbk43VoPy1HH-mHm97yoGukLK87HE5kdBW6qM,24
363
- piccolo-1.6.0.dist-info/RECORD,,
358
+ piccolo-1.7.0.dist-info/LICENSE,sha256=zFIpi-16uIJ420UMIG75NU0JbDBykvrdnXcj5U_EYBI,1059
359
+ piccolo-1.7.0.dist-info/METADATA,sha256=AIBs_jqCxL694qW6TwjwE0C4_LF_RqzaVCZ_-6Ag6po,5177
360
+ piccolo-1.7.0.dist-info/WHEEL,sha256=00yskusixUoUt5ob_CiUp6LsnN5lqzTJpoqOFg_FVIc,92
361
+ piccolo-1.7.0.dist-info/entry_points.txt,sha256=SJPHET4Fi1bN5F3WqcKkv9SClK3_F1I7m4eQjk6AFh0,46
362
+ piccolo-1.7.0.dist-info/top_level.txt,sha256=-SR74VGbk43VoPy1HH-mHm97yoGukLK87HE5kdBW6qM,24
363
+ piccolo-1.7.0.dist-info/RECORD,,
@@ -1,6 +1,15 @@
1
+ import datetime
1
2
  from unittest import TestCase
2
3
 
3
- from piccolo.columns.column_types import Array, BigInt, Integer
4
+ from piccolo.columns.column_types import (
5
+ Array,
6
+ BigInt,
7
+ Date,
8
+ Integer,
9
+ Time,
10
+ Timestamp,
11
+ Timestamptz,
12
+ )
4
13
  from piccolo.table import Table
5
14
  from tests.base import engines_only, sqlite_only
6
15
 
@@ -22,7 +31,7 @@ class TestArrayDefault(TestCase):
22
31
 
23
32
  class TestArray(TestCase):
24
33
  """
25
- Make sure an Array column can be created, and work correctly.
34
+ Make sure an Array column can be created, and works correctly.
26
35
  """
27
36
 
28
37
  def setUp(self):
@@ -166,6 +175,84 @@ class TestArray(TestCase):
166
175
  )
167
176
 
168
177
 
178
+ ###############################################################################
179
+ # Date and time arrays
180
+
181
+
182
+ class DateTimeArrayTable(Table):
183
+ date = Array(Date())
184
+ time = Array(Time())
185
+ timestamp = Array(Timestamp())
186
+ timestamptz = Array(Timestamptz())
187
+ date_nullable = Array(Date(), null=True)
188
+ time_nullable = Array(Time(), null=True)
189
+ timestamp_nullable = Array(Timestamp(), null=True)
190
+ timestamptz_nullable = Array(Timestamptz(), null=True)
191
+
192
+
193
+ class TestDateTimeArray(TestCase):
194
+ """
195
+ Make sure that data can be stored and retrieved when using arrays of
196
+ date / time / timestamp.
197
+
198
+ We have to serialise / deserialise it in a special way in SQLite, hence
199
+ the tests.
200
+
201
+ """
202
+
203
+ def setUp(self):
204
+ DateTimeArrayTable.create_table().run_sync()
205
+
206
+ def tearDown(self):
207
+ DateTimeArrayTable.alter().drop_table().run_sync()
208
+
209
+ @engines_only("postgres", "sqlite")
210
+ def test_storage(self):
211
+ test_date = datetime.date(year=2024, month=1, day=1)
212
+ test_time = datetime.time(hour=12, minute=0)
213
+ test_timestamp = datetime.datetime(
214
+ year=2024, month=1, day=1, hour=12, minute=0
215
+ )
216
+ test_timestamptz = datetime.datetime(
217
+ year=2024,
218
+ month=1,
219
+ day=1,
220
+ hour=12,
221
+ minute=0,
222
+ tzinfo=datetime.timezone.utc,
223
+ )
224
+
225
+ DateTimeArrayTable(
226
+ {
227
+ DateTimeArrayTable.date: [test_date],
228
+ DateTimeArrayTable.time: [test_time],
229
+ DateTimeArrayTable.timestamp: [test_timestamp],
230
+ DateTimeArrayTable.timestamptz: [test_timestamptz],
231
+ DateTimeArrayTable.date_nullable: None,
232
+ DateTimeArrayTable.time_nullable: None,
233
+ DateTimeArrayTable.timestamp_nullable: None,
234
+ DateTimeArrayTable.timestamptz_nullable: None,
235
+ }
236
+ ).save().run_sync()
237
+
238
+ row = DateTimeArrayTable.objects().first().run_sync()
239
+ assert row is not None
240
+
241
+ self.assertListEqual(row.date, [test_date])
242
+ self.assertListEqual(row.time, [test_time])
243
+ self.assertListEqual(row.timestamp, [test_timestamp])
244
+ self.assertListEqual(row.timestamptz, [test_timestamptz])
245
+
246
+ self.assertIsNone(row.date_nullable)
247
+ self.assertIsNone(row.time_nullable)
248
+ self.assertIsNone(row.timestamp_nullable)
249
+ self.assertIsNone(row.timestamptz_nullable)
250
+
251
+
252
+ ###############################################################################
253
+ # Nested arrays
254
+
255
+
169
256
  class NestedArrayTable(Table):
170
257
  value = Array(base_column=Array(base_column=BigInt()))
171
258