piccolo 1.10.0__py3-none-any.whl → 1.11.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 +1 -1
- piccolo/columns/column_types.py +28 -35
- piccolo/query/functions/__init__.py +11 -1
- piccolo/query/functions/datetime.py +260 -0
- piccolo/query/functions/string.py +45 -0
- {piccolo-1.10.0.dist-info → piccolo-1.11.0.dist-info}/METADATA +1 -1
- {piccolo-1.10.0.dist-info → piccolo-1.11.0.dist-info}/RECORD +13 -11
- tests/query/functions/test_datetime.py +114 -0
- tests/query/functions/test_string.py +34 -2
- {piccolo-1.10.0.dist-info → piccolo-1.11.0.dist-info}/LICENSE +0 -0
- {piccolo-1.10.0.dist-info → piccolo-1.11.0.dist-info}/WHEEL +0 -0
- {piccolo-1.10.0.dist-info → piccolo-1.11.0.dist-info}/entry_points.txt +0 -0
- {piccolo-1.10.0.dist-info → piccolo-1.11.0.dist-info}/top_level.txt +0 -0
piccolo/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__VERSION__ = "1.
|
1
|
+
__VERSION__ = "1.11.0"
|
piccolo/columns/column_types.py
CHANGED
@@ -86,47 +86,38 @@ class ConcatDelegate:
|
|
86
86
|
|
87
87
|
def get_querystring(
|
88
88
|
self,
|
89
|
-
|
90
|
-
value: t.Union[str,
|
89
|
+
column: Column,
|
90
|
+
value: t.Union[str, Column, QueryString],
|
91
91
|
reverse: bool = False,
|
92
92
|
) -> QueryString:
|
93
|
-
|
94
|
-
|
93
|
+
"""
|
94
|
+
:param reverse:
|
95
|
+
By default the value is appended to the column's value. If
|
96
|
+
``reverse=True`` then the value is prepended to the column's
|
97
|
+
value instead.
|
98
|
+
|
99
|
+
"""
|
100
|
+
if isinstance(value, Column):
|
95
101
|
if len(column._meta.call_chain) > 0:
|
96
102
|
raise ValueError(
|
97
103
|
"Adding values across joins isn't currently supported."
|
98
104
|
)
|
99
|
-
other_column_name = column._meta.db_column_name
|
100
|
-
if reverse:
|
101
|
-
return QueryString(
|
102
|
-
Concat.template.format(
|
103
|
-
value_1=other_column_name, value_2=column_name
|
104
|
-
)
|
105
|
-
)
|
106
|
-
else:
|
107
|
-
return QueryString(
|
108
|
-
Concat.template.format(
|
109
|
-
value_1=column_name, value_2=other_column_name
|
110
|
-
)
|
111
|
-
)
|
112
105
|
elif isinstance(value, str):
|
113
|
-
|
114
|
-
|
115
|
-
return QueryString(
|
116
|
-
Concat.template.format(value_1="{}", value_2=column_name),
|
117
|
-
value_1,
|
118
|
-
)
|
119
|
-
else:
|
120
|
-
value_2 = QueryString("CAST({} AS text)", value)
|
121
|
-
return QueryString(
|
122
|
-
Concat.template.format(value_1=column_name, value_2="{}"),
|
123
|
-
value_2,
|
124
|
-
)
|
125
|
-
else:
|
106
|
+
value = QueryString("CAST({} AS TEXT)", value)
|
107
|
+
elif not isinstance(value, QueryString):
|
126
108
|
raise ValueError(
|
127
|
-
"Only str,
|
109
|
+
"Only str, Column and QueryString values can be added."
|
128
110
|
)
|
129
111
|
|
112
|
+
args = [value, column] if reverse else [column, value]
|
113
|
+
|
114
|
+
# We use the concat operator instead of the concat function, because
|
115
|
+
# this is what we historically used, and they treat null values
|
116
|
+
# differently.
|
117
|
+
return QueryString(
|
118
|
+
Concat.template.format(value_1="{}", value_2="{}"), *args
|
119
|
+
)
|
120
|
+
|
130
121
|
|
131
122
|
class MathDelegate:
|
132
123
|
"""
|
@@ -340,12 +331,13 @@ class Varchar(Column):
|
|
340
331
|
|
341
332
|
def __add__(self, value: t.Union[str, Varchar, Text]) -> QueryString:
|
342
333
|
return self.concat_delegate.get_querystring(
|
343
|
-
|
334
|
+
column=self,
|
335
|
+
value=value,
|
344
336
|
)
|
345
337
|
|
346
338
|
def __radd__(self, value: t.Union[str, Varchar, Text]) -> QueryString:
|
347
339
|
return self.concat_delegate.get_querystring(
|
348
|
-
|
340
|
+
column=self,
|
349
341
|
value=value,
|
350
342
|
reverse=True,
|
351
343
|
)
|
@@ -442,12 +434,13 @@ class Text(Column):
|
|
442
434
|
|
443
435
|
def __add__(self, value: t.Union[str, Varchar, Text]) -> QueryString:
|
444
436
|
return self.concat_delegate.get_querystring(
|
445
|
-
|
437
|
+
column=self,
|
438
|
+
value=value,
|
446
439
|
)
|
447
440
|
|
448
441
|
def __radd__(self, value: t.Union[str, Varchar, Text]) -> QueryString:
|
449
442
|
return self.concat_delegate.get_querystring(
|
450
|
-
|
443
|
+
column=self,
|
451
444
|
value=value,
|
452
445
|
reverse=True,
|
453
446
|
)
|
@@ -1,6 +1,7 @@
|
|
1
1
|
from .aggregate import Avg, Count, Max, Min, Sum
|
2
|
+
from .datetime import Day, Extract, Hour, Month, Second, Strftime, Year
|
2
3
|
from .math import Abs, Ceil, Floor, Round
|
3
|
-
from .string import Length, Lower, Ltrim, Reverse, Rtrim, Upper
|
4
|
+
from .string import Concat, Length, Lower, Ltrim, Reverse, Rtrim, Upper
|
4
5
|
from .type_conversion import Cast
|
5
6
|
|
6
7
|
__all__ = (
|
@@ -8,16 +9,25 @@ __all__ = (
|
|
8
9
|
"Avg",
|
9
10
|
"Cast",
|
10
11
|
"Ceil",
|
12
|
+
"Concat",
|
11
13
|
"Count",
|
14
|
+
"Day",
|
15
|
+
"Extract",
|
16
|
+
"Extract",
|
12
17
|
"Floor",
|
18
|
+
"Hour",
|
13
19
|
"Length",
|
14
20
|
"Lower",
|
15
21
|
"Ltrim",
|
16
22
|
"Max",
|
17
23
|
"Min",
|
24
|
+
"Month",
|
18
25
|
"Reverse",
|
19
26
|
"Round",
|
20
27
|
"Rtrim",
|
28
|
+
"Second",
|
29
|
+
"Strftime",
|
21
30
|
"Sum",
|
22
31
|
"Upper",
|
32
|
+
"Year",
|
23
33
|
)
|
@@ -0,0 +1,260 @@
|
|
1
|
+
import typing as t
|
2
|
+
|
3
|
+
from piccolo.columns.base import Column
|
4
|
+
from piccolo.columns.column_types import (
|
5
|
+
Date,
|
6
|
+
Integer,
|
7
|
+
Time,
|
8
|
+
Timestamp,
|
9
|
+
Timestamptz,
|
10
|
+
)
|
11
|
+
from piccolo.querystring import QueryString
|
12
|
+
|
13
|
+
from .type_conversion import Cast
|
14
|
+
|
15
|
+
###############################################################################
|
16
|
+
# Postgres / Cockroach
|
17
|
+
|
18
|
+
ExtractComponent = t.Literal[
|
19
|
+
"century",
|
20
|
+
"day",
|
21
|
+
"decade",
|
22
|
+
"dow",
|
23
|
+
"doy",
|
24
|
+
"epoch",
|
25
|
+
"hour",
|
26
|
+
"isodow",
|
27
|
+
"isoyear",
|
28
|
+
"julian",
|
29
|
+
"microseconds",
|
30
|
+
"millennium",
|
31
|
+
"milliseconds",
|
32
|
+
"minute",
|
33
|
+
"month",
|
34
|
+
"quarter",
|
35
|
+
"second",
|
36
|
+
"timezone",
|
37
|
+
"timezone_hour",
|
38
|
+
"timezone_minute",
|
39
|
+
"week",
|
40
|
+
"year",
|
41
|
+
]
|
42
|
+
|
43
|
+
|
44
|
+
class Extract(QueryString):
|
45
|
+
def __init__(
|
46
|
+
self,
|
47
|
+
identifier: t.Union[Date, Time, Timestamp, Timestamptz, QueryString],
|
48
|
+
datetime_component: ExtractComponent,
|
49
|
+
alias: t.Optional[str] = None,
|
50
|
+
):
|
51
|
+
"""
|
52
|
+
.. note:: This is for Postgres / Cockroach only.
|
53
|
+
|
54
|
+
Extract a date or time component from a ``Date`` / ``Time`` /
|
55
|
+
``Timestamp`` / ``Timestamptz`` column. For example, getting the month
|
56
|
+
from a timestamp:
|
57
|
+
|
58
|
+
.. code-block:: python
|
59
|
+
|
60
|
+
>>> from piccolo.query.functions import Extract
|
61
|
+
>>> await Concert.select(
|
62
|
+
... Extract(Concert.starts, "month", alias="start_month")
|
63
|
+
... )
|
64
|
+
[{"start_month": 12}]
|
65
|
+
|
66
|
+
:param identifier:
|
67
|
+
Identifies the column.
|
68
|
+
:param datetime_component:
|
69
|
+
The date or time component to extract from the column.
|
70
|
+
|
71
|
+
"""
|
72
|
+
if datetime_component.lower() not in t.get_args(ExtractComponent):
|
73
|
+
raise ValueError("The date time component isn't recognised.")
|
74
|
+
|
75
|
+
super().__init__(
|
76
|
+
f"EXTRACT({datetime_component} FROM {{}})",
|
77
|
+
identifier,
|
78
|
+
alias=alias,
|
79
|
+
)
|
80
|
+
|
81
|
+
|
82
|
+
###############################################################################
|
83
|
+
# SQLite
|
84
|
+
|
85
|
+
|
86
|
+
class Strftime(QueryString):
|
87
|
+
def __init__(
|
88
|
+
self,
|
89
|
+
identifier: t.Union[Date, Time, Timestamp, Timestamptz, QueryString],
|
90
|
+
datetime_format: str,
|
91
|
+
alias: t.Optional[str] = None,
|
92
|
+
):
|
93
|
+
"""
|
94
|
+
.. note:: This is for SQLite only.
|
95
|
+
|
96
|
+
Format a datetime value. For example:
|
97
|
+
|
98
|
+
.. code-block:: python
|
99
|
+
|
100
|
+
>>> from piccolo.query.functions import Strftime
|
101
|
+
>>> await Concert.select(
|
102
|
+
... Strftime(Concert.starts, "%Y", alias="start_year")
|
103
|
+
... )
|
104
|
+
[{"start_month": "2024"}]
|
105
|
+
|
106
|
+
:param identifier:
|
107
|
+
Identifies the column.
|
108
|
+
:param datetime_format:
|
109
|
+
A string describing the output format (see SQLite's
|
110
|
+
`documentation <https://www.sqlite.org/lang_datefunc.html>`_
|
111
|
+
for more info).
|
112
|
+
|
113
|
+
"""
|
114
|
+
super().__init__(
|
115
|
+
f"strftime('{datetime_format}', {{}})",
|
116
|
+
identifier,
|
117
|
+
alias=alias,
|
118
|
+
)
|
119
|
+
|
120
|
+
|
121
|
+
###############################################################################
|
122
|
+
# Database agnostic
|
123
|
+
|
124
|
+
|
125
|
+
def _get_engine_type(identifier: t.Union[Column, QueryString]) -> str:
|
126
|
+
if isinstance(identifier, Column):
|
127
|
+
return identifier._meta.engine_type
|
128
|
+
elif isinstance(identifier, QueryString) and (
|
129
|
+
columns := identifier.columns
|
130
|
+
):
|
131
|
+
return columns[0]._meta.engine_type
|
132
|
+
else:
|
133
|
+
raise ValueError("Unable to determine the engine type")
|
134
|
+
|
135
|
+
|
136
|
+
def _extract_component(
|
137
|
+
identifier: t.Union[Date, Time, Timestamp, Timestamptz, QueryString],
|
138
|
+
sqlite_format: str,
|
139
|
+
postgres_format: ExtractComponent,
|
140
|
+
alias: t.Optional[str],
|
141
|
+
) -> QueryString:
|
142
|
+
engine_type = _get_engine_type(identifier=identifier)
|
143
|
+
|
144
|
+
return Cast(
|
145
|
+
(
|
146
|
+
Strftime(
|
147
|
+
identifier=identifier,
|
148
|
+
datetime_format=sqlite_format,
|
149
|
+
)
|
150
|
+
if engine_type == "sqlite"
|
151
|
+
else Extract(
|
152
|
+
identifier=identifier,
|
153
|
+
datetime_component=postgres_format,
|
154
|
+
)
|
155
|
+
),
|
156
|
+
Integer(),
|
157
|
+
alias=alias,
|
158
|
+
)
|
159
|
+
|
160
|
+
|
161
|
+
def Year(
|
162
|
+
identifier: t.Union[Date, Timestamp, Timestamptz, QueryString],
|
163
|
+
alias: t.Optional[str] = None,
|
164
|
+
) -> QueryString:
|
165
|
+
"""
|
166
|
+
Extract the year as an integer.
|
167
|
+
"""
|
168
|
+
return _extract_component(
|
169
|
+
identifier=identifier,
|
170
|
+
sqlite_format="%Y",
|
171
|
+
postgres_format="year",
|
172
|
+
alias=alias,
|
173
|
+
)
|
174
|
+
|
175
|
+
|
176
|
+
def Month(
|
177
|
+
identifier: t.Union[Date, Timestamp, Timestamptz, QueryString],
|
178
|
+
alias: t.Optional[str] = None,
|
179
|
+
) -> QueryString:
|
180
|
+
"""
|
181
|
+
Extract the month as an integer.
|
182
|
+
"""
|
183
|
+
return _extract_component(
|
184
|
+
identifier=identifier,
|
185
|
+
sqlite_format="%m",
|
186
|
+
postgres_format="month",
|
187
|
+
alias=alias,
|
188
|
+
)
|
189
|
+
|
190
|
+
|
191
|
+
def Day(
|
192
|
+
identifier: t.Union[Date, Timestamp, Timestamptz, QueryString],
|
193
|
+
alias: t.Optional[str] = None,
|
194
|
+
) -> QueryString:
|
195
|
+
"""
|
196
|
+
Extract the day as an integer.
|
197
|
+
"""
|
198
|
+
return _extract_component(
|
199
|
+
identifier=identifier,
|
200
|
+
sqlite_format="%d",
|
201
|
+
postgres_format="day",
|
202
|
+
alias=alias,
|
203
|
+
)
|
204
|
+
|
205
|
+
|
206
|
+
def Hour(
|
207
|
+
identifier: t.Union[Time, Timestamp, Timestamptz, QueryString],
|
208
|
+
alias: t.Optional[str] = None,
|
209
|
+
) -> QueryString:
|
210
|
+
"""
|
211
|
+
Extract the hour as an integer.
|
212
|
+
"""
|
213
|
+
return _extract_component(
|
214
|
+
identifier=identifier,
|
215
|
+
sqlite_format="%H",
|
216
|
+
postgres_format="hour",
|
217
|
+
alias=alias,
|
218
|
+
)
|
219
|
+
|
220
|
+
|
221
|
+
def Minute(
|
222
|
+
identifier: t.Union[Time, Timestamp, Timestamptz, QueryString],
|
223
|
+
alias: t.Optional[str] = None,
|
224
|
+
) -> QueryString:
|
225
|
+
"""
|
226
|
+
Extract the minute as an integer.
|
227
|
+
"""
|
228
|
+
return _extract_component(
|
229
|
+
identifier=identifier,
|
230
|
+
sqlite_format="%M",
|
231
|
+
postgres_format="minute",
|
232
|
+
alias=alias,
|
233
|
+
)
|
234
|
+
|
235
|
+
|
236
|
+
def Second(
|
237
|
+
identifier: t.Union[Time, Timestamp, Timestamptz, QueryString],
|
238
|
+
alias: t.Optional[str] = None,
|
239
|
+
) -> QueryString:
|
240
|
+
"""
|
241
|
+
Extract the second as an integer.
|
242
|
+
"""
|
243
|
+
return _extract_component(
|
244
|
+
identifier=identifier,
|
245
|
+
sqlite_format="%S",
|
246
|
+
postgres_format="second",
|
247
|
+
alias=alias,
|
248
|
+
)
|
249
|
+
|
250
|
+
|
251
|
+
__all__ = (
|
252
|
+
"Extract",
|
253
|
+
"Strftime",
|
254
|
+
"Year",
|
255
|
+
"Month",
|
256
|
+
"Day",
|
257
|
+
"Hour",
|
258
|
+
"Minute",
|
259
|
+
"Second",
|
260
|
+
)
|
@@ -5,6 +5,12 @@ https://www.postgresql.org/docs/current/functions-string.html
|
|
5
5
|
|
6
6
|
"""
|
7
7
|
|
8
|
+
import typing as t
|
9
|
+
|
10
|
+
from piccolo.columns.base import Column
|
11
|
+
from piccolo.columns.column_types import Text, Varchar
|
12
|
+
from piccolo.querystring import QueryString
|
13
|
+
|
8
14
|
from .base import Function
|
9
15
|
|
10
16
|
|
@@ -63,6 +69,44 @@ class Upper(Function):
|
|
63
69
|
function_name = "UPPER"
|
64
70
|
|
65
71
|
|
72
|
+
class Concat(QueryString):
|
73
|
+
def __init__(
|
74
|
+
self,
|
75
|
+
*args: t.Union[Column, QueryString, str],
|
76
|
+
alias: t.Optional[str] = None,
|
77
|
+
):
|
78
|
+
"""
|
79
|
+
Concatenate multiple values into a single string.
|
80
|
+
|
81
|
+
.. note::
|
82
|
+
Null values are ignored, so ``null + '!!!'`` returns ``!!!``,
|
83
|
+
not ``null``.
|
84
|
+
|
85
|
+
.. warning::
|
86
|
+
For SQLite, this is only available in version 3.44.0 and above.
|
87
|
+
|
88
|
+
"""
|
89
|
+
if len(args) < 2:
|
90
|
+
raise ValueError("At least two values must be passed in.")
|
91
|
+
|
92
|
+
placeholders = ", ".join("{}" for _ in args)
|
93
|
+
|
94
|
+
processed_args: t.List[t.Union[QueryString, Column]] = []
|
95
|
+
|
96
|
+
for arg in args:
|
97
|
+
if isinstance(arg, str) or (
|
98
|
+
isinstance(arg, Column)
|
99
|
+
and not isinstance(arg, (Varchar, Text))
|
100
|
+
):
|
101
|
+
processed_args.append(QueryString("CAST({} AS TEXT)", arg))
|
102
|
+
else:
|
103
|
+
processed_args.append(arg)
|
104
|
+
|
105
|
+
super().__init__(
|
106
|
+
f"CONCAT({placeholders})", *processed_args, alias=alias
|
107
|
+
)
|
108
|
+
|
109
|
+
|
66
110
|
__all__ = (
|
67
111
|
"Length",
|
68
112
|
"Lower",
|
@@ -70,4 +114,5 @@ __all__ = (
|
|
70
114
|
"Reverse",
|
71
115
|
"Rtrim",
|
72
116
|
"Upper",
|
117
|
+
"Concat",
|
73
118
|
)
|
@@ -1,4 +1,4 @@
|
|
1
|
-
piccolo/__init__.py,sha256
|
1
|
+
piccolo/__init__.py,sha256=-4wiKExnJa5IxSz9dOFdTv99ibuBl-uX3D-HM6f-_V8,23
|
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=sgMiBvq-xLW6_W86g6XZTMc_3cskyeoMF6yIvIlnXsA,32487
|
119
119
|
piccolo/columns/choices.py,sha256=-HNQuk9vMmVZIPZ5PMeXGTfr23o4nzKPSAkvcG1k0y8,723
|
120
|
-
piccolo/columns/column_types.py,sha256=
|
120
|
+
piccolo/columns/column_types.py,sha256=gwd93EWIULo5pGcuo7wHZdwvFEfFwtBy660pDBVqixw,81921
|
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=17NY0wU7ta2rUTHYUkeA2HQhTDlJ_lyv9FxqvJiiUbY,14602
|
@@ -149,11 +149,12 @@ piccolo/query/__init__.py,sha256=bcsMV4813rMRAIqGv4DxI4eyO4FmpXkDv9dfTk5pt3A,699
|
|
149
149
|
piccolo/query/base.py,sha256=G8Mwz0GcHY4Xs5Co9ubCNMI-3orfOsDdRDOnFRws7TU,15212
|
150
150
|
piccolo/query/mixins.py,sha256=EFEFb9It4y1mR6_JXLn139h5M9KgeP750STYy5M4MLs,21951
|
151
151
|
piccolo/query/proxy.py,sha256=Yq4jNc7IWJvdeO3u7_7iPyRy2WhVj8KsIUcIYHBIi9Q,1839
|
152
|
-
piccolo/query/functions/__init__.py,sha256=
|
152
|
+
piccolo/query/functions/__init__.py,sha256=pZkzOIh7Sg9HPNOeegOwAS46Oxt31ATlSVmwn-lxCbc,605
|
153
153
|
piccolo/query/functions/aggregate.py,sha256=OdjDjr_zyD4S9UbrZ2C3V5mz4OT2sIfAFAdTGr4WL54,4248
|
154
154
|
piccolo/query/functions/base.py,sha256=Go2bg2r7GaVoyyX-wTb80WEQmtiU4OFYWQlq9eQ6Zcc,478
|
155
|
+
piccolo/query/functions/datetime.py,sha256=6YSpc_MiZK_019KUhCo01Ss_1AjXJ31M61R9-zKmoZs,6251
|
155
156
|
piccolo/query/functions/math.py,sha256=2Wapq0lpXZh77z0uzXUhnOfmUkbkM0xjQ4tiyuCsbiE,661
|
156
|
-
piccolo/query/functions/string.py,sha256=
|
157
|
+
piccolo/query/functions/string.py,sha256=X3g_4qomJJCkYOcKcK-zZEqC6qJBrS4VTogPp9Xw4Cs,2506
|
157
158
|
piccolo/query/functions/type_conversion.py,sha256=OYbZc6TEk6b5yTwCMw2rmZ-UiQiUiWZOyxwMLzUjXwE,2583
|
158
159
|
piccolo/query/methods/__init__.py,sha256=tm4gLeV_obDqpgnouVjFbGubbaoJcqm_cbNd4LPo48Q,622
|
159
160
|
piccolo/query/methods/alter.py,sha256=AI9YkJeip2EitrWJN_TDExXhA8HGAG3XuDz1NR-KirQ,16728
|
@@ -303,9 +304,10 @@ tests/query/test_querystring.py,sha256=QrqyjwUlFlf5LrsJ7DgjCruq811I0UvrDFPud6rfZ
|
|
303
304
|
tests/query/test_slots.py,sha256=I9ZjAYqAJNSFAWg9UyAqy7bm-Z52KiyQ2C_yHk2qqqI,1010
|
304
305
|
tests/query/functions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
305
306
|
tests/query/functions/base.py,sha256=RLCzLT7iN_Z5DtIFZqVESTJGES2JKb8VDU25sv5OtN4,811
|
307
|
+
tests/query/functions/test_datetime.py,sha256=8GG5ERLq6GM8NqA3J6mycNPfCUMOEICGccyZifiwEqw,2987
|
306
308
|
tests/query/functions/test_functions.py,sha256=510fqRrOrAZ9NyFoZtlF6lIdiiLriWhZ7vvveWZ8rsc,1984
|
307
309
|
tests/query/functions/test_math.py,sha256=Qw2MXqgY_y7vGd0bLtPhWW7HB3tJkot1o-Rh9nCmmBk,1273
|
308
|
-
tests/query/functions/test_string.py,sha256=
|
310
|
+
tests/query/functions/test_string.py,sha256=RMojkBUzw1Ikrb3nTa7VjJ4FsKfrjpuHUyxQDA-F5Cs,1800
|
309
311
|
tests/query/functions/test_type_conversion.py,sha256=WeYR9UfJnbidle07-akQ1g9hFCd93qT8xUhDF3c58n4,3235
|
310
312
|
tests/query/mixins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
311
313
|
tests/query/mixins/test_columns_delegate.py,sha256=Zw9uaqOEb7kpPQzzO9yz0jhQEeCfoPSjsy-BCLg_8XU,2032
|
@@ -363,9 +365,9 @@ tests/utils/test_sql_values.py,sha256=vzxRmy16FfLZPH-sAQexBvsF9MXB8n4smr14qoEOS5
|
|
363
365
|
tests/utils/test_sync.py,sha256=9ytVo56y2vPQePvTeIi9lHIouEhWJbodl1TmzkGFrSo,799
|
364
366
|
tests/utils/test_table_reflection.py,sha256=SIzuat-IpcVj1GCFyOWKShI8YkhdOPPFH7qVrvfyPNE,3794
|
365
367
|
tests/utils/test_warnings.py,sha256=NvSC_cvJ6uZcwAGf1m-hLzETXCqprXELL8zg3TNLVMw,269
|
366
|
-
piccolo-1.
|
367
|
-
piccolo-1.
|
368
|
-
piccolo-1.
|
369
|
-
piccolo-1.
|
370
|
-
piccolo-1.
|
371
|
-
piccolo-1.
|
368
|
+
piccolo-1.11.0.dist-info/LICENSE,sha256=zFIpi-16uIJ420UMIG75NU0JbDBykvrdnXcj5U_EYBI,1059
|
369
|
+
piccolo-1.11.0.dist-info/METADATA,sha256=th4damYz6EsRNzUVjj7Rkn5Eu3jv1LDiCHAHV-N_piE,5178
|
370
|
+
piccolo-1.11.0.dist-info/WHEEL,sha256=00yskusixUoUt5ob_CiUp6LsnN5lqzTJpoqOFg_FVIc,92
|
371
|
+
piccolo-1.11.0.dist-info/entry_points.txt,sha256=SJPHET4Fi1bN5F3WqcKkv9SClK3_F1I7m4eQjk6AFh0,46
|
372
|
+
piccolo-1.11.0.dist-info/top_level.txt,sha256=-SR74VGbk43VoPy1HH-mHm97yoGukLK87HE5kdBW6qM,24
|
373
|
+
piccolo-1.11.0.dist-info/RECORD,,
|
@@ -0,0 +1,114 @@
|
|
1
|
+
import datetime
|
2
|
+
|
3
|
+
from piccolo.columns import Timestamp
|
4
|
+
from piccolo.query.functions.datetime import (
|
5
|
+
Day,
|
6
|
+
Extract,
|
7
|
+
Hour,
|
8
|
+
Minute,
|
9
|
+
Month,
|
10
|
+
Second,
|
11
|
+
Strftime,
|
12
|
+
Year,
|
13
|
+
)
|
14
|
+
from piccolo.table import Table
|
15
|
+
from tests.base import engines_only, sqlite_only
|
16
|
+
|
17
|
+
from .base import FunctionTest
|
18
|
+
|
19
|
+
|
20
|
+
class Concert(Table):
|
21
|
+
starts = Timestamp()
|
22
|
+
|
23
|
+
|
24
|
+
class DatetimeTest(FunctionTest):
|
25
|
+
tables = [Concert]
|
26
|
+
|
27
|
+
def setUp(self) -> None:
|
28
|
+
super().setUp()
|
29
|
+
self.concert = Concert(
|
30
|
+
{
|
31
|
+
Concert.starts: datetime.datetime(
|
32
|
+
year=2024, month=6, day=14, hour=23, minute=46, second=10
|
33
|
+
)
|
34
|
+
}
|
35
|
+
)
|
36
|
+
self.concert.save().run_sync()
|
37
|
+
|
38
|
+
|
39
|
+
@engines_only("postgres", "cockroach")
|
40
|
+
class TestExtract(DatetimeTest):
|
41
|
+
def test_extract(self):
|
42
|
+
self.assertEqual(
|
43
|
+
Concert.select(
|
44
|
+
Extract(Concert.starts, "year", alias="starts_year")
|
45
|
+
).run_sync(),
|
46
|
+
[{"starts_year": self.concert.starts.year}],
|
47
|
+
)
|
48
|
+
|
49
|
+
def test_invalid_format(self):
|
50
|
+
with self.assertRaises(ValueError):
|
51
|
+
Extract(
|
52
|
+
Concert.starts,
|
53
|
+
"abc123", # type: ignore
|
54
|
+
alias="starts_year",
|
55
|
+
)
|
56
|
+
|
57
|
+
|
58
|
+
@sqlite_only
|
59
|
+
class TestStrftime(DatetimeTest):
|
60
|
+
def test_strftime(self):
|
61
|
+
self.assertEqual(
|
62
|
+
Concert.select(
|
63
|
+
Strftime(Concert.starts, "%Y", alias="starts_year")
|
64
|
+
).run_sync(),
|
65
|
+
[{"starts_year": str(self.concert.starts.year)}],
|
66
|
+
)
|
67
|
+
|
68
|
+
|
69
|
+
class TestDatabaseAgnostic(DatetimeTest):
|
70
|
+
def test_year(self):
|
71
|
+
self.assertEqual(
|
72
|
+
Concert.select(
|
73
|
+
Year(Concert.starts, alias="starts_year")
|
74
|
+
).run_sync(),
|
75
|
+
[{"starts_year": self.concert.starts.year}],
|
76
|
+
)
|
77
|
+
|
78
|
+
def test_month(self):
|
79
|
+
self.assertEqual(
|
80
|
+
Concert.select(
|
81
|
+
Month(Concert.starts, alias="starts_month")
|
82
|
+
).run_sync(),
|
83
|
+
[{"starts_month": self.concert.starts.month}],
|
84
|
+
)
|
85
|
+
|
86
|
+
def test_day(self):
|
87
|
+
self.assertEqual(
|
88
|
+
Concert.select(Day(Concert.starts, alias="starts_day")).run_sync(),
|
89
|
+
[{"starts_day": self.concert.starts.day}],
|
90
|
+
)
|
91
|
+
|
92
|
+
def test_hour(self):
|
93
|
+
self.assertEqual(
|
94
|
+
Concert.select(
|
95
|
+
Hour(Concert.starts, alias="starts_hour")
|
96
|
+
).run_sync(),
|
97
|
+
[{"starts_hour": self.concert.starts.hour}],
|
98
|
+
)
|
99
|
+
|
100
|
+
def test_minute(self):
|
101
|
+
self.assertEqual(
|
102
|
+
Concert.select(
|
103
|
+
Minute(Concert.starts, alias="starts_minute")
|
104
|
+
).run_sync(),
|
105
|
+
[{"starts_minute": self.concert.starts.minute}],
|
106
|
+
)
|
107
|
+
|
108
|
+
def test_second(self):
|
109
|
+
self.assertEqual(
|
110
|
+
Concert.select(
|
111
|
+
Second(Concert.starts, alias="starts_second")
|
112
|
+
).run_sync(),
|
113
|
+
[{"starts_second": self.concert.starts.second}],
|
114
|
+
)
|
@@ -1,10 +1,13 @@
|
|
1
|
-
|
1
|
+
import pytest
|
2
|
+
|
3
|
+
from piccolo.query.functions.string import Concat, Upper
|
4
|
+
from tests.base import engine_version_lt, is_running_sqlite
|
2
5
|
from tests.example_apps.music.tables import Band
|
3
6
|
|
4
7
|
from .base import BandTest
|
5
8
|
|
6
9
|
|
7
|
-
class
|
10
|
+
class TestUpper(BandTest):
|
8
11
|
|
9
12
|
def test_column(self):
|
10
13
|
"""
|
@@ -23,3 +26,32 @@ class TestUpperFunction(BandTest):
|
|
23
26
|
"""
|
24
27
|
response = Band.select(Upper(Band.manager._.name)).run_sync()
|
25
28
|
self.assertListEqual(response, [{"upper": "GUIDO"}])
|
29
|
+
|
30
|
+
|
31
|
+
@pytest.mark.skipif(
|
32
|
+
is_running_sqlite() and engine_version_lt(3.44),
|
33
|
+
reason="SQLite version not supported",
|
34
|
+
)
|
35
|
+
class TestConcat(BandTest):
|
36
|
+
|
37
|
+
def test_column_and_string(self):
|
38
|
+
response = Band.select(
|
39
|
+
Concat(Band.name, "!!!", alias="name")
|
40
|
+
).run_sync()
|
41
|
+
self.assertListEqual(response, [{"name": "Pythonistas!!!"}])
|
42
|
+
|
43
|
+
def test_column_and_column(self):
|
44
|
+
response = Band.select(
|
45
|
+
Concat(Band.name, Band.popularity, alias="name")
|
46
|
+
).run_sync()
|
47
|
+
self.assertListEqual(response, [{"name": "Pythonistas1000"}])
|
48
|
+
|
49
|
+
def test_join(self):
|
50
|
+
response = Band.select(
|
51
|
+
Concat(Band.name, "-", Band.manager._.name, alias="name")
|
52
|
+
).run_sync()
|
53
|
+
self.assertListEqual(response, [{"name": "Pythonistas-Guido"}])
|
54
|
+
|
55
|
+
def test_min_args(self):
|
56
|
+
with self.assertRaises(ValueError):
|
57
|
+
Concat()
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|