piccolo 1.6.0__py3-none-any.whl → 1.8.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.8.0"
piccolo/columns/base.py CHANGED
@@ -201,6 +201,10 @@ class ColumnMeta:
201
201
  )
202
202
  return self._table
203
203
 
204
+ @table.setter
205
+ def table(self, value: t.Type[Table]):
206
+ self._table = value
207
+
204
208
  ###########################################################################
205
209
 
206
210
  # Used by Foreign Keys:
@@ -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,8 +1,10 @@
1
1
  from .aggregate import Avg, Count, Max, Min, Sum
2
2
  from .string import Length, Lower, Ltrim, Reverse, Rtrim, Upper
3
+ from .type_conversion import Cast
3
4
 
4
5
  __all__ = (
5
6
  "Avg",
7
+ "Cast",
6
8
  "Count",
7
9
  "Length",
8
10
  "Lower",
@@ -12,17 +12,17 @@ class Avg(Function):
12
12
 
13
13
  .. code-block:: python
14
14
 
15
- await Band.select(Avg(Band.popularity)).run()
15
+ await Band.select(Avg(Band.popularity))
16
16
 
17
17
  # We can use an alias. These two are equivalent:
18
18
 
19
19
  await Band.select(
20
20
  Avg(Band.popularity, alias="popularity_avg")
21
- ).run()
21
+ )
22
22
 
23
23
  await Band.select(
24
24
  Avg(Band.popularity).as_alias("popularity_avg")
25
- ).run()
25
+ )
26
26
 
27
27
  """
28
28
 
@@ -103,17 +103,17 @@ class Min(Function):
103
103
 
104
104
  .. code-block:: python
105
105
 
106
- await Band.select(Min(Band.popularity)).run()
106
+ await Band.select(Min(Band.popularity))
107
107
 
108
108
  # We can use an alias. These two are equivalent:
109
109
 
110
110
  await Band.select(
111
111
  Min(Band.popularity, alias="popularity_min")
112
- ).run()
112
+ )
113
113
 
114
114
  await Band.select(
115
115
  Min(Band.popularity).as_alias("popularity_min")
116
- ).run()
116
+ )
117
117
 
118
118
  """
119
119
 
@@ -128,17 +128,17 @@ class Max(Function):
128
128
 
129
129
  await Band.select(
130
130
  Max(Band.popularity)
131
- ).run()
131
+ )
132
132
 
133
133
  # We can use an alias. These two are equivalent:
134
134
 
135
135
  await Band.select(
136
136
  Max(Band.popularity, alias="popularity_max")
137
- ).run()
137
+ )
138
138
 
139
139
  await Band.select(
140
140
  Max(Band.popularity).as_alias("popularity_max")
141
- ).run()
141
+ )
142
142
 
143
143
  """
144
144
 
@@ -153,17 +153,17 @@ class Sum(Function):
153
153
 
154
154
  await Band.select(
155
155
  Sum(Band.popularity)
156
- ).run()
156
+ )
157
157
 
158
158
  # We can use an alias. These two are equivalent:
159
159
 
160
160
  await Band.select(
161
161
  Sum(Band.popularity, alias="popularity_sum")
162
- ).run()
162
+ )
163
163
 
164
164
  await Band.select(
165
165
  Sum(Band.popularity).as_alias("popularity_sum")
166
- ).run()
166
+ )
167
167
 
168
168
  """
169
169
 
@@ -0,0 +1,82 @@
1
+ import typing as t
2
+
3
+ from piccolo.columns.base import Column
4
+ from piccolo.querystring import QueryString
5
+
6
+
7
+ class Cast(QueryString):
8
+ def __init__(
9
+ self,
10
+ identifier: t.Union[Column, QueryString],
11
+ as_type: Column,
12
+ alias: t.Optional[str] = None,
13
+ ):
14
+ """
15
+ Cast a value to a different type. For example::
16
+
17
+ >>> from piccolo.query.functions import Cast
18
+
19
+ >>> await Concert.select(
20
+ ... Cast(Concert.starts, Time(), "start_time")
21
+ ... )
22
+ [{"start_time": datetime.time(19, 0)}]
23
+
24
+ :param identifier:
25
+ Identifies what is being converted (e.g. a column).
26
+ :param as_type:
27
+ The type to be converted to.
28
+
29
+ """
30
+ # Make sure the identifier is a supported type.
31
+
32
+ if not isinstance(identifier, (Column, QueryString)):
33
+ raise ValueError(
34
+ "The identifier is an unsupported type - only Column and "
35
+ "QueryString instances are allowed."
36
+ )
37
+
38
+ #######################################################################
39
+ # Convert `as_type` to a string which can be used in the query.
40
+
41
+ if not isinstance(as_type, Column):
42
+ raise ValueError("The `as_type` value must be a Column instance.")
43
+
44
+ # We need to give the column a reference to a table, and hence
45
+ # the database engine, as the column type is sometimes dependent
46
+ # on which database is being used.
47
+ from piccolo.table import Table, create_table_class
48
+
49
+ table: t.Optional[t.Type[Table]] = None
50
+
51
+ if isinstance(identifier, Column):
52
+ table = identifier._meta.table
53
+ elif isinstance(identifier, QueryString):
54
+ table = (
55
+ identifier.columns[0]._meta.table
56
+ if identifier.columns
57
+ else None
58
+ )
59
+
60
+ as_type._meta.table = table or create_table_class("Table")
61
+ as_type_string = as_type.column_type
62
+
63
+ #######################################################################
64
+ # Preserve the original alias from the column.
65
+
66
+ if isinstance(identifier, Column):
67
+ alias = (
68
+ alias
69
+ or identifier._alias
70
+ or identifier._meta.get_default_alias()
71
+ )
72
+
73
+ #######################################################################
74
+
75
+ super().__init__(
76
+ f"CAST({{}} AS {as_type_string})",
77
+ identifier,
78
+ alias=alias,
79
+ )
80
+
81
+
82
+ __all__ = ("Cast",)
piccolo/querystring.py CHANGED
@@ -270,6 +270,18 @@ class QueryString(Selectable):
270
270
  def __sub__(self, value) -> QueryString:
271
271
  return QueryString("{} - {}", self, value)
272
272
 
273
+ def __gt__(self, value) -> QueryString:
274
+ return QueryString("{} > {}", self, value)
275
+
276
+ def __ge__(self, value) -> QueryString:
277
+ return QueryString("{} >= {}", self, value)
278
+
279
+ def __lt__(self, value) -> QueryString:
280
+ return QueryString("{} < {}", self, value)
281
+
282
+ def __le__(self, value) -> QueryString:
283
+ return QueryString("{} <= {}", self, value)
284
+
273
285
  def is_in(self, value) -> QueryString:
274
286
  return QueryString("{} IN {}", self, value)
275
287
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: piccolo
3
- Version: 1.6.0
3
+ Version: 1.8.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,8 +1,8 @@
1
- piccolo/__init__.py,sha256=goBemmcyJmGj3ijZ-wUC400jA5cZbAGtScaxWt1tqms,22
1
+ piccolo/__init__.py,sha256=mRuIID3n4xkvLnANZAYlJEC5QgpM7rOXi2uxp1_jzHg,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
5
- piccolo/querystring.py,sha256=6uxfoCk7sj6bs_mDNV8W4ScgdG-h5wl1Y9HMlVW2abM,8671
5
+ piccolo/querystring.py,sha256=Mn00hb4HGOe9MIR7WOhJ3qO1uNex21zj0m2heOeDvZk,9057
6
6
  piccolo/schema.py,sha256=aWPuZxEulgBRD5NTqKN-RAZchxu-PoIrn0iFrWGZuq4,7731
7
7
  piccolo/table.py,sha256=DJT8jTgirPpzkydjSzaCgcG0DiC75XRtW_xtFqTyg80,49457
8
8
  piccolo/table_reflection.py,sha256=jrN1nHerDJ4tU09GtNN3hz7ap-7rXnSUjljFO6LB2H0,7094
@@ -115,9 +115,9 @@ piccolo/apps/user/piccolo_migrations/2020-06-11T21-38-55.py,sha256=JG_LFPrEljnSE
115
115
  piccolo/apps/user/piccolo_migrations/2021-04-30T16-14-15.py,sha256=Y_Dj4ROSxjnPsRDqcnpWeyk8UpF8c80T08_O2uq-GoA,1219
116
116
  piccolo/apps/user/piccolo_migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
117
117
  piccolo/columns/__init__.py,sha256=OYhO_n9anMiU9nL-K6ATq9FhAtm8RyMpqYQ7fTVbhxI,1120
118
- piccolo/columns/base.py,sha256=XUhhx-wNc6nBPd39VIYuNfFERTaFzow9SHGfZjJ2YC0,31288
118
+ piccolo/columns/base.py,sha256=FuMLSplFuNoK919nkas2Fn4kZjmPLMmutc4cRtmLhyY,31378
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,15 +144,16 @@ 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
151
151
  piccolo/query/proxy.py,sha256=Yq4jNc7IWJvdeO3u7_7iPyRy2WhVj8KsIUcIYHBIi9Q,1839
152
- piccolo/query/functions/__init__.py,sha256=O_uuMZbwMVAe-ebr-COdc9QZtvUSQFomPa29me6cscs,266
153
- piccolo/query/functions/aggregate.py,sha256=qSDb-2Of9FYXUKsdCsvaoPjGOefyhoxawWpA5oG3fQQ,4320
152
+ piccolo/query/functions/__init__.py,sha256=9ikQo6qPkCBaWSiuimEACWbYRk9KHoLboRsEC1VcSVw,312
153
+ piccolo/query/functions/aggregate.py,sha256=OdjDjr_zyD4S9UbrZ2C3V5mz4OT2sIfAFAdTGr4WL54,4248
154
154
  piccolo/query/functions/base.py,sha256=Go2bg2r7GaVoyyX-wTb80WEQmtiU4OFYWQlq9eQ6Zcc,478
155
155
  piccolo/query/functions/string.py,sha256=srxsQJFS6L4gPvFjvuAFQj7QtnCF7X6YoJNKARR2XP0,1236
156
+ piccolo/query/functions/type_conversion.py,sha256=OYbZc6TEk6b5yTwCMw2rmZ-UiQiUiWZOyxwMLzUjXwE,2583
156
157
  piccolo/query/methods/__init__.py,sha256=tm4gLeV_obDqpgnouVjFbGubbaoJcqm_cbNd4LPo48Q,622
157
158
  piccolo/query/methods/alter.py,sha256=AI9YkJeip2EitrWJN_TDExXhA8HGAG3XuDz1NR-KirQ,16728
158
159
  piccolo/query/methods/count.py,sha256=Vxn_7Ry-rleC6OGRxh-cLbuEMsy1DNjAZJThGED-_do,1748
@@ -208,12 +209,12 @@ tests/apps/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
208
209
  tests/apps/migrations/test_migration.py,sha256=JmPLtf2BCWX3Yofe0GQe40m8I_yWa_-3vk1lDfFDfIo,308
209
210
  tests/apps/migrations/auto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
210
211
  tests/apps/migrations/auto/test_diffable_table.py,sha256=bok3G9pwEYnE3AL6UG4iEHrVBZJQ_ovYCdKC3we5JVQ,2932
211
- tests/apps/migrations/auto/test_migration_manager.py,sha256=XXdHHiimsyeNN6sILGQxN5RDLdR9MH_NlTC0PumLu3c,34741
212
+ tests/apps/migrations/auto/test_migration_manager.py,sha256=NTNx4y5B0bMVLUR9BybX3zS4jxFI3_weLej8zOn3BkI,34798
212
213
  tests/apps/migrations/auto/test_schema_differ.py,sha256=UdsaZisA02j15wr1bXkXD6Cqu3p0A23NwFQLXsJdQL4,19391
213
214
  tests/apps/migrations/auto/test_schema_snapshot.py,sha256=ZyvGZqn3N3cwd-3S-FME5AJ8buDSHesw7yPIvY6mE5k,6196
214
215
  tests/apps/migrations/auto/test_serialisation.py,sha256=EFkhES1w9h51UCamWrhxs3mf4I718ggeP7Yl5J_UID4,13548
215
216
  tests/apps/migrations/auto/integration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
216
- tests/apps/migrations/auto/integration/test_migrations.py,sha256=G3iqDlNCC6S3N9pm7w0bf5YelDWDi1vz7OE0A2IEokk,46131
217
+ tests/apps/migrations/auto/integration/test_migrations.py,sha256=7rmATPGZNuchabUb2y5C9QMmv6XFChn5EHlYoRVChd4,46744
217
218
  tests/apps/migrations/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
218
219
  tests/apps/migrations/commands/test_base.py,sha256=NgHgVjNd3Hil9eODvW7Ic2D9muTa_grNaH3YpRFfR8I,1829
219
220
  tests/apps/migrations/commands/test_check.py,sha256=hOX_sVk1nfCRfbQ8tJoFEUBFhih9O4QuQLHTp5TQaiY,630
@@ -241,7 +242,7 @@ tests/apps/user/commands/test_change_permissions.py,sha256=uVKEiT1EKot3VA2TDETdQ
241
242
  tests/apps/user/commands/test_create.py,sha256=iJ3Tti62rHwvdcTwNXrc5JPam6vR1qxKRdMN456vm3o,2250
242
243
  tests/apps/user/commands/test_list.py,sha256=ipPfGdW6fH7q-Jc7JcYUvlioGmH9GQU0WImZGC2m-XQ,2840
243
244
  tests/columns/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
244
- tests/columns/test_array.py,sha256=R6dnUBGcMHi5pVhczI4ydWpXsu2FqRASu6KtkhDWfZo,6660
245
+ tests/columns/test_array.py,sha256=Kd8yy3C4cXJIgutY-cv0MrE4mvgr52qqVUdkcazW_uI,9298
245
246
  tests/columns/test_base.py,sha256=CTqCNcrqAJTjLXe3MCZgTczrmB3jcVRcOpU4FilpLoQ,3918
246
247
  tests/columns/test_bigint.py,sha256=a0B4y1H02ww5qaW574X2lyenbY6o29ztOhiaqybPC0c,1149
247
248
  tests/columns/test_boolean.py,sha256=kDESp6FnRtSZhuqIu0dBRwKMSpS5TFbbs3sz2MyZSs8,1720
@@ -295,7 +296,7 @@ tests/query/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
295
296
  tests/query/test_await.py,sha256=imGazmG0l4qilveNPwsxvYQogFJtos4YB8N9iggPEFU,412
296
297
  tests/query/test_camelcase.py,sha256=AcL2gZera1GfpVJNpuKuh5ZBosNCY_ezPWh6-duU5vU,1765
297
298
  tests/query/test_freeze.py,sha256=p3iXqHzgV39YWlqzXtZvaDa7iKZaaaelOGX3UZ8CMf0,3887
298
- tests/query/test_functions.py,sha256=_dYGLqsrYkWMxjb3MIlpsCbY1nC9n39IiRsrGhhrYJs,3182
299
+ tests/query/test_functions.py,sha256=B_M-giOf1xGvdCaYgJdKWLSoV8vtTiRQtEyvL4-7eCY,6401
299
300
  tests/query/test_gather.py,sha256=okWANrBoh0Ut1RomWoffiWNpFqiITF6qti-Aa3uYtRk,730
300
301
  tests/query/test_querystring.py,sha256=hHljfdnOTlwIMs-7Q2yP5YekYXTT2It-Q-3mP6T9e58,880
301
302
  tests/query/test_slots.py,sha256=I9ZjAYqAJNSFAWg9UyAqy7bm-Z52KiyQ2C_yHk2qqqI,1010
@@ -355,9 +356,9 @@ tests/utils/test_sql_values.py,sha256=vzxRmy16FfLZPH-sAQexBvsF9MXB8n4smr14qoEOS5
355
356
  tests/utils/test_sync.py,sha256=9ytVo56y2vPQePvTeIi9lHIouEhWJbodl1TmzkGFrSo,799
356
357
  tests/utils/test_table_reflection.py,sha256=SIzuat-IpcVj1GCFyOWKShI8YkhdOPPFH7qVrvfyPNE,3794
357
358
  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,,
359
+ piccolo-1.8.0.dist-info/LICENSE,sha256=zFIpi-16uIJ420UMIG75NU0JbDBykvrdnXcj5U_EYBI,1059
360
+ piccolo-1.8.0.dist-info/METADATA,sha256=Uoe1LLvND5fOBdQKOSgFI1T047qzIlbICmwXV1zg7e4,5177
361
+ piccolo-1.8.0.dist-info/WHEEL,sha256=00yskusixUoUt5ob_CiUp6LsnN5lqzTJpoqOFg_FVIc,92
362
+ piccolo-1.8.0.dist-info/entry_points.txt,sha256=SJPHET4Fi1bN5F3WqcKkv9SClK3_F1I7m4eQjk6AFh0,46
363
+ piccolo-1.8.0.dist-info/top_level.txt,sha256=-SR74VGbk43VoPy1HH-mHm97yoGukLK87HE5kdBW6qM,24
364
+ piccolo-1.8.0.dist-info/RECORD,,
@@ -288,7 +288,12 @@ class TestMigrations(MigrationTestCase):
288
288
  [
289
289
  x.data_type == "text",
290
290
  x.is_nullable == "NO",
291
- x.column_default in ("''::text", "'':::STRING"),
291
+ x.column_default
292
+ in (
293
+ "''",
294
+ "''::text",
295
+ "'':::STRING",
296
+ ),
292
297
  ]
293
298
  ),
294
299
  )
@@ -461,6 +466,7 @@ class TestMigrations(MigrationTestCase):
461
466
  in (
462
467
  "now()",
463
468
  "CURRENT_TIMESTAMP",
469
+ "current_timestamp()::TIMESTAMP",
464
470
  "current_timestamp():::TIMESTAMPTZ::TIMESTAMP",
465
471
  ),
466
472
  ]
@@ -541,7 +547,11 @@ class TestMigrations(MigrationTestCase):
541
547
  x.data_type == "interval",
542
548
  x.is_nullable == "NO",
543
549
  x.column_default
544
- in ("'00:00:00'::interval", "'00:00:00':::INTERVAL"),
550
+ in (
551
+ "'00:00:00'",
552
+ "'00:00:00'::interval",
553
+ "'00:00:00':::INTERVAL",
554
+ ),
545
555
  ]
546
556
  ),
547
557
  )
@@ -743,7 +753,12 @@ class TestMigrations(MigrationTestCase):
743
753
  [
744
754
  x.data_type == "jsonb",
745
755
  x.is_nullable == "NO",
746
- x.column_default in ("'{}'::jsonb", "'{}':::JSONB"),
756
+ x.column_default
757
+ in (
758
+ "'{}'",
759
+ "'{}'::jsonb",
760
+ "'{}':::JSONB",
761
+ ),
747
762
  ]
748
763
  ),
749
764
  )
@@ -766,7 +781,11 @@ class TestMigrations(MigrationTestCase):
766
781
  x.data_type == "character varying",
767
782
  x.is_nullable == "NO",
768
783
  x.column_default
769
- in ("''::character varying", "'':::STRING"),
784
+ in (
785
+ "''",
786
+ "''::character varying",
787
+ "'':::STRING",
788
+ ),
770
789
  ]
771
790
  ),
772
791
  )
@@ -788,7 +807,11 @@ class TestMigrations(MigrationTestCase):
788
807
  x.data_type == "character varying",
789
808
  x.is_nullable == "NO",
790
809
  x.column_default
791
- in ("''::character varying", "'':::STRING"),
810
+ in (
811
+ "''",
812
+ "''::character varying",
813
+ "'':::STRING",
814
+ ),
792
815
  ]
793
816
  ),
794
817
  )
@@ -768,15 +768,15 @@ class TestMigrationManager(DBTestCase):
768
768
  )
769
769
 
770
770
  asyncio.run(manager.run())
771
- self.assertEqual(
772
- self._get_column_default(),
773
- [{"column_default": "'Unknown':::STRING"}],
771
+ self.assertIn(
772
+ self._get_column_default()[0]["column_default"],
773
+ ["'Unknown'", "'Unknown':::STRING"],
774
774
  )
775
775
 
776
776
  asyncio.run(manager.run(backwards=True))
777
- self.assertEqual(
778
- self._get_column_default(),
779
- [{"column_default": "'':::STRING"}],
777
+ self.assertIn(
778
+ self._get_column_default()[0]["column_default"],
779
+ ["''", "'':::STRING"],
780
780
  )
781
781
 
782
782
  @engines_only("postgres")
@@ -856,9 +856,9 @@ class TestMigrationManager(DBTestCase):
856
856
  old_params={"default": None},
857
857
  )
858
858
  asyncio.run(manager_1.run())
859
- self.assertEqual(
860
- self._get_column_default(),
861
- [{"column_default": "'Mr Manager':::STRING"}],
859
+ self.assertIn(
860
+ self._get_column_default()[0]["column_default"],
861
+ ["'Mr Manager'", "'Mr Manager':::STRING"],
862
862
  )
863
863
 
864
864
  # Drop the default.
@@ -879,9 +879,9 @@ class TestMigrationManager(DBTestCase):
879
879
  # And add it back once more to be sure.
880
880
  manager_3 = manager_1
881
881
  asyncio.run(manager_3.run())
882
- self.assertEqual(
883
- self._get_column_default(),
884
- [{"column_default": "'Mr Manager':::STRING"}],
882
+ self.assertIn(
883
+ self._get_column_default()[0]["column_default"],
884
+ ["'Mr Manager'", "'Mr Manager':::STRING"],
885
885
  )
886
886
 
887
887
  # Run them all backwards
@@ -892,9 +892,9 @@ class TestMigrationManager(DBTestCase):
892
892
  )
893
893
 
894
894
  asyncio.run(manager_2.run(backwards=True))
895
- self.assertEqual(
896
- self._get_column_default(),
897
- [{"column_default": "'Mr Manager':::STRING"}],
895
+ self.assertIn(
896
+ self._get_column_default()[0]["column_default"],
897
+ ["'Mr Manager'", "'Mr Manager':::STRING"],
898
898
  )
899
899
 
900
900
  asyncio.run(manager_1.run(backwards=True))
@@ -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
 
@@ -1,6 +1,7 @@
1
1
  from unittest import TestCase
2
2
 
3
- from piccolo.query.functions.string import Reverse, Upper
3
+ from piccolo.columns import Integer, Text, Varchar
4
+ from piccolo.query.functions import Cast, Length, Reverse, Upper
4
5
  from piccolo.querystring import QueryString
5
6
  from piccolo.table import create_db_tables_sync, drop_db_tables_sync
6
7
  from tests.base import engines_skip
@@ -16,7 +17,13 @@ class FunctionTest(TestCase):
16
17
  manager = Manager({Manager.name: "Guido"})
17
18
  manager.save().run_sync()
18
19
 
19
- band = Band({Band.name: "Pythonistas", Band.manager: manager})
20
+ band = Band(
21
+ {
22
+ Band.name: "Pythonistas",
23
+ Band.manager: manager,
24
+ Band.popularity: 1000,
25
+ }
26
+ )
20
27
  band.save().run_sync()
21
28
 
22
29
  def tearDown(self) -> None:
@@ -100,3 +107,132 @@ class TestWhereClause(FunctionTest):
100
107
  .run_sync()
101
108
  )
102
109
  self.assertListEqual(response, [{"name": "Pythonistas"}])
110
+
111
+
112
+ class TestCast(FunctionTest):
113
+ def test_varchar(self):
114
+ """
115
+ Make sure that casting to ``Varchar`` works.
116
+ """
117
+ response = Band.select(
118
+ Cast(
119
+ Band.popularity,
120
+ as_type=Varchar(),
121
+ )
122
+ ).run_sync()
123
+
124
+ self.assertListEqual(
125
+ response,
126
+ [{"popularity": "1000"}],
127
+ )
128
+
129
+ def test_text(self):
130
+ """
131
+ Make sure that casting to ``Text`` works.
132
+ """
133
+ response = Band.select(
134
+ Cast(
135
+ Band.popularity,
136
+ as_type=Text(),
137
+ )
138
+ ).run_sync()
139
+
140
+ self.assertListEqual(
141
+ response,
142
+ [{"popularity": "1000"}],
143
+ )
144
+
145
+ def test_integer(self):
146
+ """
147
+ Make sure that casting to ``Integer`` works.
148
+ """
149
+ Band.update({Band.name: "1111"}, force=True).run_sync()
150
+
151
+ response = Band.select(
152
+ Cast(
153
+ Band.name,
154
+ as_type=Integer(),
155
+ )
156
+ ).run_sync()
157
+
158
+ self.assertListEqual(
159
+ response,
160
+ [{"name": 1111}],
161
+ )
162
+
163
+ def test_join(self):
164
+ """
165
+ Make sure that casting works with joins.
166
+ """
167
+ Manager.update({Manager.name: "1111"}, force=True).run_sync()
168
+
169
+ response = Band.select(
170
+ Band.name,
171
+ Cast(
172
+ Band.manager.name,
173
+ as_type=Integer(),
174
+ ),
175
+ ).run_sync()
176
+
177
+ self.assertListEqual(
178
+ response,
179
+ [
180
+ {
181
+ "name": "Pythonistas",
182
+ "manager.name": 1111,
183
+ }
184
+ ],
185
+ )
186
+
187
+ def test_nested_inner(self):
188
+ """
189
+ Make sure ``Cast`` can be passed into other functions.
190
+ """
191
+ Band.update({Band.name: "1111"}, force=True).run_sync()
192
+
193
+ response = Band.select(
194
+ Length(
195
+ Cast(
196
+ Band.popularity,
197
+ as_type=Varchar(),
198
+ )
199
+ )
200
+ ).run_sync()
201
+
202
+ self.assertListEqual(
203
+ response,
204
+ [{"length": 4}],
205
+ )
206
+
207
+ def test_nested_outer(self):
208
+ """
209
+ Make sure a querystring can be passed into ``Cast`` (meaning it can be
210
+ nested).
211
+ """
212
+ response = Band.select(
213
+ Cast(
214
+ Length(Band.name),
215
+ as_type=Varchar(),
216
+ alias="length",
217
+ )
218
+ ).run_sync()
219
+
220
+ self.assertListEqual(
221
+ response,
222
+ [{"length": str(len("Pythonistas"))}],
223
+ )
224
+
225
+ def test_where_clause(self):
226
+ """
227
+ Make sure ``Cast`` works in a where clause.
228
+ """
229
+ response = (
230
+ Band.select(Band.name, Band.popularity)
231
+ .where(Cast(Band.popularity, Varchar()) == "1000")
232
+ .run_sync()
233
+ )
234
+
235
+ self.assertListEqual(
236
+ response,
237
+ [{"name": "Pythonistas", "popularity": 1000}],
238
+ )