piccolo 1.5.1__py3-none-any.whl → 1.6.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/apps/playground/commands/run.py +70 -18
- piccolo/columns/base.py +31 -40
- piccolo/columns/column_types.py +11 -8
- piccolo/columns/m2m.py +16 -6
- piccolo/columns/readable.py +9 -7
- piccolo/query/__init__.py +1 -4
- piccolo/query/base.py +1 -5
- piccolo/query/functions/__init__.py +16 -0
- piccolo/query/functions/aggregate.py +179 -0
- piccolo/query/functions/base.py +21 -0
- piccolo/query/functions/string.py +73 -0
- piccolo/query/methods/__init__.py +18 -1
- piccolo/query/methods/count.py +3 -3
- piccolo/query/methods/delete.py +1 -1
- piccolo/query/methods/exists.py +1 -1
- piccolo/query/methods/objects.py +1 -1
- piccolo/query/methods/select.py +17 -232
- piccolo/query/methods/update.py +1 -1
- piccolo/query/mixins.py +9 -2
- piccolo/querystring.py +101 -13
- piccolo/table.py +8 -24
- {piccolo-1.5.1.dist-info → piccolo-1.6.0.dist-info}/METADATA +1 -1
- {piccolo-1.5.1.dist-info → piccolo-1.6.0.dist-info}/RECORD +35 -30
- tests/apps/migrations/commands/test_forwards_backwards.py +3 -0
- tests/apps/shell/commands/test_run.py +1 -0
- tests/conf/test_apps.py +6 -0
- tests/example_apps/music/tables.py +10 -0
- tests/query/test_functions.py +102 -0
- tests/table/test_output.py +88 -36
- tests/table/test_select.py +2 -9
- {piccolo-1.5.1.dist-info → piccolo-1.6.0.dist-info}/LICENSE +0 -0
- {piccolo-1.5.1.dist-info → piccolo-1.6.0.dist-info}/WHEEL +0 -0
- {piccolo-1.5.1.dist-info → piccolo-1.6.0.dist-info}/entry_points.txt +0 -0
- {piccolo-1.5.1.dist-info → piccolo-1.6.0.dist-info}/top_level.txt +0 -0
piccolo/query/mixins.py
CHANGED
@@ -9,13 +9,14 @@ from enum import Enum, auto
|
|
9
9
|
|
10
10
|
from piccolo.columns import And, Column, Or, Where
|
11
11
|
from piccolo.columns.column_types import ForeignKey
|
12
|
+
from piccolo.columns.combination import WhereRaw
|
12
13
|
from piccolo.custom_types import Combinable
|
13
14
|
from piccolo.querystring import QueryString
|
14
15
|
from piccolo.utils.list import flatten
|
15
16
|
from piccolo.utils.sql_values import convert_to_sql_value
|
16
17
|
|
17
18
|
if t.TYPE_CHECKING: # pragma: no cover
|
18
|
-
from piccolo.
|
19
|
+
from piccolo.querystring import Selectable
|
19
20
|
from piccolo.table import Table # noqa
|
20
21
|
|
21
22
|
|
@@ -254,8 +255,10 @@ class WhereDelegate:
|
|
254
255
|
elif isinstance(combinable, (And, Or)):
|
255
256
|
self._extract_columns(combinable.first)
|
256
257
|
self._extract_columns(combinable.second)
|
258
|
+
elif isinstance(combinable, WhereRaw):
|
259
|
+
self._where_columns.extend(combinable.querystring.columns)
|
257
260
|
|
258
|
-
def where(self, *where: Combinable):
|
261
|
+
def where(self, *where: t.Union[Combinable, QueryString]):
|
259
262
|
for arg in where:
|
260
263
|
if isinstance(arg, bool):
|
261
264
|
raise ValueError(
|
@@ -265,6 +268,10 @@ class WhereDelegate:
|
|
265
268
|
"`.where(MyTable.some_column.is_null())`."
|
266
269
|
)
|
267
270
|
|
271
|
+
if isinstance(arg, QueryString):
|
272
|
+
# If a raw QueryString is passed in.
|
273
|
+
arg = WhereRaw(arg.template, *arg.args)
|
274
|
+
|
268
275
|
self._where = And(self._where, arg) if self._where else arg
|
269
276
|
|
270
277
|
|
piccolo/querystring.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import typing as t
|
4
|
+
from abc import ABCMeta, abstractmethod
|
4
5
|
from dataclasses import dataclass
|
5
6
|
from datetime import datetime
|
6
7
|
from importlib.util import find_spec
|
@@ -8,6 +9,7 @@ from string import Formatter
|
|
8
9
|
|
9
10
|
if t.TYPE_CHECKING: # pragma: no cover
|
10
11
|
from piccolo.table import Table
|
12
|
+
from piccolo.columns import Column
|
11
13
|
|
12
14
|
from uuid import UUID
|
13
15
|
|
@@ -17,22 +19,32 @@ else:
|
|
17
19
|
apgUUID = UUID
|
18
20
|
|
19
21
|
|
20
|
-
|
21
|
-
class Unquoted:
|
22
|
+
class Selectable(metaclass=ABCMeta):
|
22
23
|
"""
|
23
|
-
|
24
|
-
keyword - for example DEFAULT.
|
24
|
+
Anything which inherits from this can be used in a select query.
|
25
25
|
"""
|
26
26
|
|
27
|
-
__slots__ = ("
|
27
|
+
__slots__ = ("_alias",)
|
28
28
|
|
29
|
-
|
29
|
+
_alias: t.Optional[str]
|
30
30
|
|
31
|
-
|
32
|
-
|
31
|
+
@abstractmethod
|
32
|
+
def get_select_string(
|
33
|
+
self, engine_type: str, with_alias: bool = True
|
34
|
+
) -> QueryString:
|
35
|
+
"""
|
36
|
+
In a query, what to output after the select statement - could be a
|
37
|
+
column name, a sub query, a function etc. For a column it will be the
|
38
|
+
column name.
|
39
|
+
"""
|
40
|
+
raise NotImplementedError()
|
33
41
|
|
34
|
-
def
|
35
|
-
|
42
|
+
def as_alias(self, alias: str) -> Selectable:
|
43
|
+
"""
|
44
|
+
Allows column names to be changed in the result of a select.
|
45
|
+
"""
|
46
|
+
self._alias = alias
|
47
|
+
return self
|
36
48
|
|
37
49
|
|
38
50
|
@dataclass
|
@@ -42,7 +54,7 @@ class Fragment:
|
|
42
54
|
no_arg: bool = False
|
43
55
|
|
44
56
|
|
45
|
-
class QueryString:
|
57
|
+
class QueryString(Selectable):
|
46
58
|
"""
|
47
59
|
When we're composing complex queries, we're combining QueryStrings, rather
|
48
60
|
than concatenating strings directly. The reason for this is QueryStrings
|
@@ -56,6 +68,7 @@ class QueryString:
|
|
56
68
|
"query_type",
|
57
69
|
"table",
|
58
70
|
"_frozen_compiled_strings",
|
71
|
+
"columns",
|
59
72
|
)
|
60
73
|
|
61
74
|
def __init__(
|
@@ -64,6 +77,7 @@ class QueryString:
|
|
64
77
|
*args: t.Any,
|
65
78
|
query_type: str = "generic",
|
66
79
|
table: t.Optional[t.Type[Table]] = None,
|
80
|
+
alias: t.Optional[str] = None,
|
67
81
|
) -> None:
|
68
82
|
"""
|
69
83
|
:param template:
|
@@ -83,12 +97,42 @@ class QueryString:
|
|
83
97
|
|
84
98
|
"""
|
85
99
|
self.template = template
|
86
|
-
self.args = args
|
87
100
|
self.query_type = query_type
|
88
101
|
self.table = table
|
89
102
|
self._frozen_compiled_strings: t.Optional[
|
90
103
|
t.Tuple[str, t.List[t.Any]]
|
91
104
|
] = None
|
105
|
+
self._alias = alias
|
106
|
+
self.args, self.columns = self.process_args(args)
|
107
|
+
|
108
|
+
def process_args(
|
109
|
+
self, args: t.Sequence[t.Any]
|
110
|
+
) -> t.Tuple[t.Sequence[t.Any], t.Sequence[Column]]:
|
111
|
+
"""
|
112
|
+
If a Column is passed in, we convert it to the name of the column
|
113
|
+
(including joins).
|
114
|
+
"""
|
115
|
+
from piccolo.columns import Column
|
116
|
+
|
117
|
+
processed_args = []
|
118
|
+
columns = []
|
119
|
+
|
120
|
+
for arg in args:
|
121
|
+
if isinstance(arg, Column):
|
122
|
+
columns.append(arg)
|
123
|
+
arg = QueryString(
|
124
|
+
f"{arg._meta.get_full_name(with_alias=False)}"
|
125
|
+
)
|
126
|
+
elif isinstance(arg, QueryString):
|
127
|
+
columns.extend(arg.columns)
|
128
|
+
|
129
|
+
processed_args.append(arg)
|
130
|
+
|
131
|
+
return (processed_args, columns)
|
132
|
+
|
133
|
+
def as_alias(self, alias: str) -> QueryString:
|
134
|
+
self._alias = alias
|
135
|
+
return self
|
92
136
|
|
93
137
|
def __str__(self):
|
94
138
|
"""
|
@@ -143,7 +187,7 @@ class QueryString:
|
|
143
187
|
fragment.no_arg = True
|
144
188
|
bundled.append(fragment)
|
145
189
|
else:
|
146
|
-
if isinstance(value,
|
190
|
+
if isinstance(value, QueryString):
|
147
191
|
fragment.no_arg = True
|
148
192
|
bundled.append(fragment)
|
149
193
|
|
@@ -195,3 +239,47 @@ class QueryString:
|
|
195
239
|
self._frozen_compiled_strings = self.compile_string(
|
196
240
|
engine_type=engine_type
|
197
241
|
)
|
242
|
+
|
243
|
+
###########################################################################
|
244
|
+
|
245
|
+
def get_select_string(
|
246
|
+
self, engine_type: str, with_alias: bool = True
|
247
|
+
) -> QueryString:
|
248
|
+
if with_alias and self._alias:
|
249
|
+
return QueryString("{} AS " + self._alias, self)
|
250
|
+
else:
|
251
|
+
return self
|
252
|
+
|
253
|
+
def get_where_string(self, engine_type: str) -> QueryString:
|
254
|
+
return self.get_select_string(
|
255
|
+
engine_type=engine_type, with_alias=False
|
256
|
+
)
|
257
|
+
|
258
|
+
###########################################################################
|
259
|
+
# Basic logic
|
260
|
+
|
261
|
+
def __eq__(self, value) -> QueryString: # type: ignore[override]
|
262
|
+
return QueryString("{} = {}", self, value)
|
263
|
+
|
264
|
+
def __ne__(self, value) -> QueryString: # type: ignore[override]
|
265
|
+
return QueryString("{} != {}", self, value)
|
266
|
+
|
267
|
+
def __add__(self, value) -> QueryString:
|
268
|
+
return QueryString("{} + {}", self, value)
|
269
|
+
|
270
|
+
def __sub__(self, value) -> QueryString:
|
271
|
+
return QueryString("{} - {}", self, value)
|
272
|
+
|
273
|
+
def is_in(self, value) -> QueryString:
|
274
|
+
return QueryString("{} IN {}", self, value)
|
275
|
+
|
276
|
+
def not_in(self, value) -> QueryString:
|
277
|
+
return QueryString("{} NOT IN {}", self, value)
|
278
|
+
|
279
|
+
|
280
|
+
class Unquoted(QueryString):
|
281
|
+
"""
|
282
|
+
This is deprecated - just use QueryString directly.
|
283
|
+
"""
|
284
|
+
|
285
|
+
pass
|
piccolo/table.py
CHANGED
@@ -48,7 +48,7 @@ from piccolo.query.methods.create_index import CreateIndex
|
|
48
48
|
from piccolo.query.methods.indexes import Indexes
|
49
49
|
from piccolo.query.methods.objects import First
|
50
50
|
from piccolo.query.methods.refresh import Refresh
|
51
|
-
from piccolo.querystring import QueryString
|
51
|
+
from piccolo.querystring import QueryString
|
52
52
|
from piccolo.utils import _camel_to_snake
|
53
53
|
from piccolo.utils.graphlib import TopologicalSorter
|
54
54
|
from piccolo.utils.sql_values import convert_to_sql_value
|
@@ -56,7 +56,7 @@ from piccolo.utils.sync import run_sync
|
|
56
56
|
from piccolo.utils.warnings import colored_warning
|
57
57
|
|
58
58
|
if t.TYPE_CHECKING: # pragma: no cover
|
59
|
-
from piccolo.
|
59
|
+
from piccolo.querystring import Selectable
|
60
60
|
|
61
61
|
PROTECTED_TABLENAMES = ("user",)
|
62
62
|
TABLENAME_WARNING = (
|
@@ -796,30 +796,14 @@ class Table(metaclass=TableMetaclass):
|
|
796
796
|
"""
|
797
797
|
Used when inserting rows.
|
798
798
|
"""
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
args_dict[column_name] = value
|
804
|
-
|
805
|
-
def is_unquoted(arg):
|
806
|
-
return isinstance(arg, Unquoted)
|
807
|
-
|
808
|
-
# Strip out any args which are unquoted.
|
809
|
-
filtered_args = [i for i in args_dict.values() if not is_unquoted(i)]
|
799
|
+
args = [
|
800
|
+
convert_to_sql_value(value=self[column._meta.name], column=column)
|
801
|
+
for column in self._meta.columns
|
802
|
+
]
|
810
803
|
|
811
804
|
# If unquoted, dump it straight into the query.
|
812
|
-
query = ",".join(
|
813
|
-
|
814
|
-
(
|
815
|
-
args_dict[column._meta.name].value
|
816
|
-
if is_unquoted(args_dict[column._meta.name])
|
817
|
-
else "{}"
|
818
|
-
)
|
819
|
-
for column in self._meta.columns
|
820
|
-
]
|
821
|
-
)
|
822
|
-
return QueryString(f"({query})", *filtered_args)
|
805
|
+
query = ",".join(["{}" for _ in args])
|
806
|
+
return QueryString(f"({query})", *args)
|
823
807
|
|
824
808
|
def __str__(self) -> str:
|
825
809
|
return self.querystring.__str__()
|
@@ -1,10 +1,10 @@
|
|
1
|
-
piccolo/__init__.py,sha256=
|
1
|
+
piccolo/__init__.py,sha256=goBemmcyJmGj3ijZ-wUC400jA5cZbAGtScaxWt1tqms,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=
|
5
|
+
piccolo/querystring.py,sha256=6uxfoCk7sj6bs_mDNV8W4ScgdG-h5wl1Y9HMlVW2abM,8671
|
6
6
|
piccolo/schema.py,sha256=aWPuZxEulgBRD5NTqKN-RAZchxu-PoIrn0iFrWGZuq4,7731
|
7
|
-
piccolo/table.py,sha256=
|
7
|
+
piccolo/table.py,sha256=DJT8jTgirPpzkydjSzaCgcG0DiC75XRtW_xtFqTyg80,49457
|
8
8
|
piccolo/table_reflection.py,sha256=jrN1nHerDJ4tU09GtNN3hz7ap-7rXnSUjljFO6LB2H0,7094
|
9
9
|
piccolo/apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
10
|
piccolo/apps/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -77,7 +77,7 @@ piccolo/apps/migrations/commands/templates/migration.py.jinja,sha256=wMC8RTIcQj3
|
|
77
77
|
piccolo/apps/playground/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
78
78
|
piccolo/apps/playground/piccolo_app.py,sha256=zs6nGxt-lgUF8nEwI0uDTNZDKQqjZaNDH8le5RqrMNE,222
|
79
79
|
piccolo/apps/playground/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
80
|
-
piccolo/apps/playground/commands/run.py,sha256=
|
80
|
+
piccolo/apps/playground/commands/run.py,sha256=RhuxsnQj8m7iE2ww_de7Jz-dT25gbqMdx1MWeHQ2mCg,8401
|
81
81
|
piccolo/apps/project/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
82
82
|
piccolo/apps/project/piccolo_app.py,sha256=mT3O0m3QcCfS0oOr3jt0QZ9TX6gUavGPjJeNn2C_fdM,220
|
83
83
|
piccolo/apps/project/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -115,13 +115,13 @@ 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=
|
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=
|
120
|
+
piccolo/columns/column_types.py,sha256=tFOHOMOaZ3xPl6glQEd23N1oyeLGbu3qGlTuxPb7ToQ,81069
|
121
121
|
piccolo/columns/combination.py,sha256=vMXC2dfY7pvnCFhsT71XFVyb4gdQzfRsCMaiduu04Ss,6900
|
122
122
|
piccolo/columns/indexes.py,sha256=NfNok3v_791jgDlN28KmhP9ZCjl6031BXmjxV3ovXJk,372
|
123
|
-
piccolo/columns/m2m.py,sha256=
|
124
|
-
piccolo/columns/readable.py,sha256=
|
123
|
+
piccolo/columns/m2m.py,sha256=vRJZqBcBP3TQ9Mmb7UEqTgg0QoxIIjIu6JfGLAi4X8Q,14595
|
124
|
+
piccolo/columns/readable.py,sha256=hganxUPfIK5ZXn-qgteBxsOJfBJucgr9U0QLsLFYcuI,1562
|
125
125
|
piccolo/columns/reference.py,sha256=FqE9rpMBMwNNkKXR3Wi4ce-fyT2Vh4KM8YpdC21s6gg,3574
|
126
126
|
piccolo/columns/defaults/__init__.py,sha256=7hpB13baEJgc1zbZjRKDFr-5hltxM2VGj8KnKfOiS8c,145
|
127
127
|
piccolo/columns/defaults/base.py,sha256=kxh5jgU9G1zpcncmqISZgwMeHnNPBgNCvuqPPQYO_zs,1854
|
@@ -145,26 +145,30 @@ piccolo/engine/exceptions.py,sha256=X8xZiTF-L9PIqFT-KDXnv1jFIIOZMF8fYK692chttJE,
|
|
145
145
|
piccolo/engine/finder.py,sha256=GjzBNtzRzH79fjtRn7OI3nZiOXE8JfoQWAvHVPrPNx4,507
|
146
146
|
piccolo/engine/postgres.py,sha256=zUY6x52QrZ8waiqEUuqlVFiXyzAXrsFi3PY5EJnv3DM,18276
|
147
147
|
piccolo/engine/sqlite.py,sha256=h5RrrDqy-28ck8L9SkLURfZWFSTcVdojTLDt1w8cTgk,22099
|
148
|
-
piccolo/query/__init__.py,sha256=
|
149
|
-
piccolo/query/base.py,sha256=
|
150
|
-
piccolo/query/mixins.py,sha256=
|
148
|
+
piccolo/query/__init__.py,sha256=bcsMV4813rMRAIqGv4DxI4eyO4FmpXkDv9dfTk5pt3A,699
|
149
|
+
piccolo/query/base.py,sha256=G8Mwz0GcHY4Xs5Co9ubCNMI-3orfOsDdRDOnFRws7TU,15212
|
150
|
+
piccolo/query/mixins.py,sha256=1RyhORDRwTZF9m_2uEgc6sOSd2uViXivBAaFN8geq5g,21982
|
151
151
|
piccolo/query/proxy.py,sha256=Yq4jNc7IWJvdeO3u7_7iPyRy2WhVj8KsIUcIYHBIi9Q,1839
|
152
|
-
piccolo/query/
|
152
|
+
piccolo/query/functions/__init__.py,sha256=O_uuMZbwMVAe-ebr-COdc9QZtvUSQFomPa29me6cscs,266
|
153
|
+
piccolo/query/functions/aggregate.py,sha256=qSDb-2Of9FYXUKsdCsvaoPjGOefyhoxawWpA5oG3fQQ,4320
|
154
|
+
piccolo/query/functions/base.py,sha256=Go2bg2r7GaVoyyX-wTb80WEQmtiU4OFYWQlq9eQ6Zcc,478
|
155
|
+
piccolo/query/functions/string.py,sha256=srxsQJFS6L4gPvFjvuAFQj7QtnCF7X6YoJNKARR2XP0,1236
|
156
|
+
piccolo/query/methods/__init__.py,sha256=tm4gLeV_obDqpgnouVjFbGubbaoJcqm_cbNd4LPo48Q,622
|
153
157
|
piccolo/query/methods/alter.py,sha256=AI9YkJeip2EitrWJN_TDExXhA8HGAG3XuDz1NR-KirQ,16728
|
154
|
-
piccolo/query/methods/count.py,sha256=
|
158
|
+
piccolo/query/methods/count.py,sha256=Vxn_7Ry-rleC6OGRxh-cLbuEMsy1DNjAZJThGED-_do,1748
|
155
159
|
piccolo/query/methods/create.py,sha256=hJ-6VVsWczzKDH6fQRN1WmYhcitixuXJ-eNOuCo_JgM,2742
|
156
160
|
piccolo/query/methods/create_index.py,sha256=RV9yVHwPvfQCk-g6YpmUTKamgOj0uxWe8Zr97YHIPGo,2216
|
157
|
-
piccolo/query/methods/delete.py,sha256=
|
161
|
+
piccolo/query/methods/delete.py,sha256=3QNh8wsn2hUP1Ce9nz5ps1huU6ySHjyqkjdP-VYN-U8,2234
|
158
162
|
piccolo/query/methods/drop_index.py,sha256=SOX5wfm-Tbb5TrN6kaLRVHUWdEhyrmCQwF33JfWdtwE,1043
|
159
|
-
piccolo/query/methods/exists.py,sha256=
|
163
|
+
piccolo/query/methods/exists.py,sha256=lTMjtrFPFygZmaPV3sfQKXc3K0sVqJ2S6PDc3fRK6YQ,1203
|
160
164
|
piccolo/query/methods/indexes.py,sha256=J-QUqaBJwpgahskUH0Cu0Mq7zEKcfVAtDsUVIVX-C4c,943
|
161
165
|
piccolo/query/methods/insert.py,sha256=ssLJ_wn08KnOwwr7t-VILyn1P4hrvM63CfPIcAJWT5k,4701
|
162
|
-
piccolo/query/methods/objects.py,sha256=
|
166
|
+
piccolo/query/methods/objects.py,sha256=Kw0T1LB4qQkV5vQDKb6HxRw90qDUq6Fgp95_ayX6buo,11727
|
163
167
|
piccolo/query/methods/raw.py,sha256=VhYpCB52mZk4zqFTsqK5CHKTDGskUjISXTBV7UjohmA,600
|
164
168
|
piccolo/query/methods/refresh.py,sha256=P1Eo_HYU_L7kcGM_cvDDgyLi1boCXY7Pc4tv_eDAzvc,2769
|
165
|
-
piccolo/query/methods/select.py,sha256=
|
169
|
+
piccolo/query/methods/select.py,sha256=soeBlUXMKvKdmHOkur1O7SOnCpHjRD1tCD4W-fKrLdg,21053
|
166
170
|
piccolo/query/methods/table_exists.py,sha256=0yb3n6Jd2ovSBWlZ-gl00K4E7Jnbj7J8qAAX5d7hvNk,1259
|
167
|
-
piccolo/query/methods/update.py,sha256=
|
171
|
+
piccolo/query/methods/update.py,sha256=LfWqIXEl1aecc0rkVssTFmwyD6wXGhlKcTrUVhtlEsw,3705
|
168
172
|
piccolo/testing/__init__.py,sha256=pRFSqRInfx95AakOq54atmvqoB-ue073q2aR8u8zR40,83
|
169
173
|
piccolo/testing/model_builder.py,sha256=lVEiEe71xrH8SSjzFc2l0s-VaCXHeg9Bo5oAYOEbLrI,6545
|
170
174
|
piccolo/testing/random_builder.py,sha256=0LkGpanQ7P1R82gLIMQyK9cm1LdZkPvxbShTEf3jeH4,2128
|
@@ -214,7 +218,7 @@ tests/apps/migrations/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5
|
|
214
218
|
tests/apps/migrations/commands/test_base.py,sha256=NgHgVjNd3Hil9eODvW7Ic2D9muTa_grNaH3YpRFfR8I,1829
|
215
219
|
tests/apps/migrations/commands/test_check.py,sha256=hOX_sVk1nfCRfbQ8tJoFEUBFhih9O4QuQLHTp5TQaiY,630
|
216
220
|
tests/apps/migrations/commands/test_clean.py,sha256=lPzLLXhoUyyffY3EQIiyRj-QfP07UVNTha21cEZivfY,1124
|
217
|
-
tests/apps/migrations/commands/test_forwards_backwards.py,sha256
|
221
|
+
tests/apps/migrations/commands/test_forwards_backwards.py,sha256=-rwQ3r61eq6lfoMdM-fajK09SAftPn5cri_gSkF2lMk,7107
|
218
222
|
tests/apps/migrations/commands/test_new.py,sha256=dKXOuU6t_6zziHHLvX_JdM_Oiee2Lc7FEuADZsMlNQA,4249
|
219
223
|
tests/apps/migrations/commands/test_migrations/2020-03-31T20-38-22.py,sha256=9pYiFNDi-7TJy5TZ3MeNThttjjcUg6cEQ4J5Yv9wQQ8,601
|
220
224
|
tests/apps/migrations/commands/test_migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -224,7 +228,7 @@ tests/apps/project/commands/test_new.py,sha256=lqeRN9xXXmxJ9Uu0uqkotFsqfkYAexxfF
|
|
224
228
|
tests/apps/schema/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
225
229
|
tests/apps/shell/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
226
230
|
tests/apps/shell/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
227
|
-
tests/apps/shell/commands/test_run.py,sha256=
|
231
|
+
tests/apps/shell/commands/test_run.py,sha256=wH3ORQwJ1a02kA-WnZUCNmb0AlwXpRKoTntOZVUZAqI,1170
|
228
232
|
tests/apps/sql_shell/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
229
233
|
tests/apps/sql_shell/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
230
234
|
tests/apps/sql_shell/commands/test_run.py,sha256=6p0nqCoG_qNLrKeBuHspmer_SrMwEF-vfp9LbPj2W2E,425
|
@@ -269,7 +273,7 @@ tests/columns/m2m/test_m2m.py,sha256=LtNsHQ8xAzBFLiZVZhWEB56zu25FnaWtzJ62FZH3heI
|
|
269
273
|
tests/columns/m2m/test_m2m_schema.py,sha256=oxu7eAjFFpDjnq9Eq-5OTNmlnsEIMFWx18OItfpVs-s,339
|
270
274
|
tests/conf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
271
275
|
tests/conf/example.py,sha256=K8sTttLpEac8rQlOLDY500IGkHj3P3NoyFbCMnT1EqY,347
|
272
|
-
tests/conf/test_apps.py,sha256=
|
276
|
+
tests/conf/test_apps.py,sha256=aUKH74siXYlrxrjwPMUQi3Xm1LWK9PGh-lulSTwwGsk,8623
|
273
277
|
tests/engine/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
274
278
|
tests/engine/test_extra_nodes.py,sha256=xW5gflHzM6ou26DqRSAZoaAbYVzF1IuMkW3vzNmB954,1298
|
275
279
|
tests/engine/test_logging.py,sha256=VLf9A3QuoV7OhV8lttLDB4gzZemnG63kSr-Uyan005U,1287
|
@@ -285,12 +289,13 @@ tests/example_apps/mega/piccolo_migrations/2021-09-20T21-23-25-698988.py,sha256=
|
|
285
289
|
tests/example_apps/mega/piccolo_migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
286
290
|
tests/example_apps/music/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
287
291
|
tests/example_apps/music/piccolo_app.py,sha256=a3RVnqNwb4o74G1c0eEm1zbimilTXZlmy--2PkLmS5I,361
|
288
|
-
tests/example_apps/music/tables.py,sha256=
|
292
|
+
tests/example_apps/music/tables.py,sha256=uMM7QeFOeWJdRcgwZD7SZRj7953u1s5yn-G1pAHoBpw,2595
|
289
293
|
tests/example_apps/music/tables_detailed.py,sha256=1US-6XO5aipmldAqF_ughIH8ju8i_010tKfqEu_TCeU,2240
|
290
294
|
tests/query/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
291
295
|
tests/query/test_await.py,sha256=imGazmG0l4qilveNPwsxvYQogFJtos4YB8N9iggPEFU,412
|
292
296
|
tests/query/test_camelcase.py,sha256=AcL2gZera1GfpVJNpuKuh5ZBosNCY_ezPWh6-duU5vU,1765
|
293
297
|
tests/query/test_freeze.py,sha256=p3iXqHzgV39YWlqzXtZvaDa7iKZaaaelOGX3UZ8CMf0,3887
|
298
|
+
tests/query/test_functions.py,sha256=_dYGLqsrYkWMxjb3MIlpsCbY1nC9n39IiRsrGhhrYJs,3182
|
294
299
|
tests/query/test_gather.py,sha256=okWANrBoh0Ut1RomWoffiWNpFqiITF6qti-Aa3uYtRk,730
|
295
300
|
tests/query/test_querystring.py,sha256=hHljfdnOTlwIMs-7Q2yP5YekYXTT2It-Q-3mP6T9e58,880
|
296
301
|
tests/query/test_slots.py,sha256=I9ZjAYqAJNSFAWg9UyAqy7bm-Z52KiyQ2C_yHk2qqqI,1010
|
@@ -318,12 +323,12 @@ tests/table/test_join.py,sha256=Ukgvjc8NweBGHM7fVFytGQYG9P9thRaMeEvWXYs2Qes,1591
|
|
318
323
|
tests/table/test_join_on.py,sha256=cdAV39JwHi0kIas2p9cw7mcsUv6mKLZD--_SUA0zLfI,2771
|
319
324
|
tests/table/test_metaclass.py,sha256=pMv0PHh-2a9p74bweQXCXnq1OFsJ7Gk0uWRFdCTMf58,4123
|
320
325
|
tests/table/test_objects.py,sha256=bir86ks-Ngy8x9Eu9bekOrh6twBYdEkIgTdbBWY6x9s,8187
|
321
|
-
tests/table/test_output.py,sha256=
|
326
|
+
tests/table/test_output.py,sha256=ZnpPbgVp79JcB6E_ooWQxOpOlhkwNUlMxC-1LSIEc2Y,4304
|
322
327
|
tests/table/test_raw.py,sha256=9PTvYngQi41nYd5lKzkJdTqsEcwrdOXcvZjq-W26CwQ,1683
|
323
328
|
tests/table/test_ref.py,sha256=eYNRnYHzNMXuMbV3B1ca5EidpIg4500q6hr1ccuVaso,269
|
324
329
|
tests/table/test_refresh.py,sha256=ZXGLGHeMZcWnhZPB4eCasv1RkojPt6nUbxaE7WlyJbo,2804
|
325
330
|
tests/table/test_repr.py,sha256=uahz3_GffGQrf2mDE-4-Pu4AmSLBAyso6-9rbohCl58,446
|
326
|
-
tests/table/test_select.py,sha256
|
331
|
+
tests/table/test_select.py,sha256=jgeiahIlNFVijxYb3a54g1sJWVfH3llaYrsTBmdicrs,40390
|
327
332
|
tests/table/test_str.py,sha256=eztWNULcjARR1fr9X5n4tojhDNgDfatVyNHwuYrzHAo,1731
|
328
333
|
tests/table/test_table_exists.py,sha256=upv2e9UD32V2QZOShzmcw0reMqRbYiX_jxWx57p25jg,1082
|
329
334
|
tests/table/test_update.py,sha256=Cqi0xX3kEuJ0k-x_emPGB3arXuGWZ9e3CJ3HPFnw9Zw,20505
|
@@ -350,9 +355,9 @@ tests/utils/test_sql_values.py,sha256=vzxRmy16FfLZPH-sAQexBvsF9MXB8n4smr14qoEOS5
|
|
350
355
|
tests/utils/test_sync.py,sha256=9ytVo56y2vPQePvTeIi9lHIouEhWJbodl1TmzkGFrSo,799
|
351
356
|
tests/utils/test_table_reflection.py,sha256=SIzuat-IpcVj1GCFyOWKShI8YkhdOPPFH7qVrvfyPNE,3794
|
352
357
|
tests/utils/test_warnings.py,sha256=NvSC_cvJ6uZcwAGf1m-hLzETXCqprXELL8zg3TNLVMw,269
|
353
|
-
piccolo-1.
|
354
|
-
piccolo-1.
|
355
|
-
piccolo-1.
|
356
|
-
piccolo-1.
|
357
|
-
piccolo-1.
|
358
|
-
piccolo-1.
|
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,,
|
@@ -13,6 +13,7 @@ from tests.base import engines_only
|
|
13
13
|
from tests.example_apps.music.tables import (
|
14
14
|
Band,
|
15
15
|
Concert,
|
16
|
+
Instrument,
|
16
17
|
Manager,
|
17
18
|
Poster,
|
18
19
|
RecordingStudio,
|
@@ -33,6 +34,7 @@ TABLE_CLASSES: t.List[t.Type[Table]] = [
|
|
33
34
|
Poster,
|
34
35
|
Shirt,
|
35
36
|
RecordingStudio,
|
37
|
+
Instrument,
|
36
38
|
]
|
37
39
|
|
38
40
|
|
@@ -211,6 +213,7 @@ class TestForwardsBackwards(TestCase):
|
|
211
213
|
"2021-07-25T22:38:48:009306",
|
212
214
|
"2021-09-06T13:58:23:024723",
|
213
215
|
"2021-11-13T14:01:46:114725",
|
216
|
+
"2024-05-28T23:15:41:018844",
|
214
217
|
],
|
215
218
|
)
|
216
219
|
|
tests/conf/test_apps.py
CHANGED
@@ -9,6 +9,7 @@ from tests.example_apps.mega.tables import MegaTable, SmallTable
|
|
9
9
|
from tests.example_apps.music.tables import (
|
10
10
|
Band,
|
11
11
|
Concert,
|
12
|
+
Instrument,
|
12
13
|
Manager,
|
13
14
|
Poster,
|
14
15
|
RecordingStudio,
|
@@ -113,6 +114,7 @@ class TestTableFinder(TestCase):
|
|
113
114
|
[
|
114
115
|
"Band",
|
115
116
|
"Concert",
|
117
|
+
"Instrument",
|
116
118
|
"Manager",
|
117
119
|
"Poster",
|
118
120
|
"RecordingStudio",
|
@@ -139,6 +141,7 @@ class TestTableFinder(TestCase):
|
|
139
141
|
[
|
140
142
|
"Band",
|
141
143
|
"Concert",
|
144
|
+
"Instrument",
|
142
145
|
"Manager",
|
143
146
|
"Poster",
|
144
147
|
"RecordingStudio",
|
@@ -182,6 +185,7 @@ class TestTableFinder(TestCase):
|
|
182
185
|
[
|
183
186
|
"Band",
|
184
187
|
"Concert",
|
188
|
+
"Instrument",
|
185
189
|
"Manager",
|
186
190
|
"RecordingStudio",
|
187
191
|
"Shirt",
|
@@ -228,6 +232,7 @@ class TestFinder(TestCase):
|
|
228
232
|
[
|
229
233
|
Band,
|
230
234
|
Concert,
|
235
|
+
Instrument,
|
231
236
|
Manager,
|
232
237
|
MegaTable,
|
233
238
|
Poster,
|
@@ -247,6 +252,7 @@ class TestFinder(TestCase):
|
|
247
252
|
[
|
248
253
|
Band,
|
249
254
|
Concert,
|
255
|
+
Instrument,
|
250
256
|
Manager,
|
251
257
|
Poster,
|
252
258
|
RecordingStudio,
|
@@ -115,3 +115,13 @@ class RecordingStudio(Table):
|
|
115
115
|
id: Serial
|
116
116
|
facilities = JSON()
|
117
117
|
facilities_b = JSONB()
|
118
|
+
|
119
|
+
|
120
|
+
class Instrument(Table):
|
121
|
+
"""
|
122
|
+
Used for testing foreign keys to a table with a JSON column.
|
123
|
+
"""
|
124
|
+
|
125
|
+
id: Serial
|
126
|
+
name = Varchar()
|
127
|
+
recording_studio = ForeignKey(RecordingStudio)
|
@@ -0,0 +1,102 @@
|
|
1
|
+
from unittest import TestCase
|
2
|
+
|
3
|
+
from piccolo.query.functions.string import Reverse, Upper
|
4
|
+
from piccolo.querystring import QueryString
|
5
|
+
from piccolo.table import create_db_tables_sync, drop_db_tables_sync
|
6
|
+
from tests.base import engines_skip
|
7
|
+
from tests.example_apps.music.tables import Band, Manager
|
8
|
+
|
9
|
+
|
10
|
+
class FunctionTest(TestCase):
|
11
|
+
tables = (Band, Manager)
|
12
|
+
|
13
|
+
def setUp(self) -> None:
|
14
|
+
create_db_tables_sync(*self.tables)
|
15
|
+
|
16
|
+
manager = Manager({Manager.name: "Guido"})
|
17
|
+
manager.save().run_sync()
|
18
|
+
|
19
|
+
band = Band({Band.name: "Pythonistas", Band.manager: manager})
|
20
|
+
band.save().run_sync()
|
21
|
+
|
22
|
+
def tearDown(self) -> None:
|
23
|
+
drop_db_tables_sync(*self.tables)
|
24
|
+
|
25
|
+
|
26
|
+
class TestUpperFunction(FunctionTest):
|
27
|
+
|
28
|
+
def test_column(self):
|
29
|
+
"""
|
30
|
+
Make sure we can uppercase a column's value.
|
31
|
+
"""
|
32
|
+
response = Band.select(Upper(Band.name)).run_sync()
|
33
|
+
self.assertListEqual(response, [{"upper": "PYTHONISTAS"}])
|
34
|
+
|
35
|
+
def test_alias(self):
|
36
|
+
response = Band.select(Upper(Band.name, alias="name")).run_sync()
|
37
|
+
self.assertListEqual(response, [{"name": "PYTHONISTAS"}])
|
38
|
+
|
39
|
+
def test_joined_column(self):
|
40
|
+
"""
|
41
|
+
Make sure we can uppercase a column's value from a joined table.
|
42
|
+
"""
|
43
|
+
response = Band.select(Upper(Band.manager._.name)).run_sync()
|
44
|
+
self.assertListEqual(response, [{"upper": "GUIDO"}])
|
45
|
+
|
46
|
+
|
47
|
+
@engines_skip("sqlite")
|
48
|
+
class TestNested(FunctionTest):
|
49
|
+
"""
|
50
|
+
Skip the the test for SQLite, as it doesn't support ``Reverse``.
|
51
|
+
"""
|
52
|
+
|
53
|
+
def test_nested(self):
|
54
|
+
"""
|
55
|
+
Make sure we can nest functions.
|
56
|
+
"""
|
57
|
+
response = Band.select(Upper(Reverse(Band.name))).run_sync()
|
58
|
+
self.assertListEqual(response, [{"upper": "SATSINOHTYP"}])
|
59
|
+
|
60
|
+
def test_nested_with_joined_column(self):
|
61
|
+
"""
|
62
|
+
Make sure nested functions can be used on a column from a joined table.
|
63
|
+
"""
|
64
|
+
response = Band.select(Upper(Reverse(Band.manager._.name))).run_sync()
|
65
|
+
self.assertListEqual(response, [{"upper": "ODIUG"}])
|
66
|
+
|
67
|
+
def test_nested_within_querystring(self):
|
68
|
+
"""
|
69
|
+
If we wrap a function in a custom QueryString - make sure the columns
|
70
|
+
are still accessible, so joins are successful.
|
71
|
+
"""
|
72
|
+
response = Band.select(
|
73
|
+
QueryString("CONCAT({}, '!')", Upper(Band.manager._.name)),
|
74
|
+
).run_sync()
|
75
|
+
|
76
|
+
self.assertListEqual(response, [{"concat": "GUIDO!"}])
|
77
|
+
|
78
|
+
|
79
|
+
class TestWhereClause(FunctionTest):
|
80
|
+
|
81
|
+
def test_where(self):
|
82
|
+
"""
|
83
|
+
Make sure where clauses work with functions.
|
84
|
+
"""
|
85
|
+
response = (
|
86
|
+
Band.select(Band.name)
|
87
|
+
.where(Upper(Band.name) == "PYTHONISTAS")
|
88
|
+
.run_sync()
|
89
|
+
)
|
90
|
+
self.assertListEqual(response, [{"name": "Pythonistas"}])
|
91
|
+
|
92
|
+
def test_where_with_joined_column(self):
|
93
|
+
"""
|
94
|
+
Make sure where clauses work with functions, when a joined column is
|
95
|
+
used.
|
96
|
+
"""
|
97
|
+
response = (
|
98
|
+
Band.select(Band.name)
|
99
|
+
.where(Upper(Band.manager._.name) == "GUIDO")
|
100
|
+
.run_sync()
|
101
|
+
)
|
102
|
+
self.assertListEqual(response, [{"name": "Pythonistas"}])
|