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.
Files changed (35) hide show
  1. piccolo/__init__.py +1 -1
  2. piccolo/apps/playground/commands/run.py +70 -18
  3. piccolo/columns/base.py +31 -40
  4. piccolo/columns/column_types.py +11 -8
  5. piccolo/columns/m2m.py +16 -6
  6. piccolo/columns/readable.py +9 -7
  7. piccolo/query/__init__.py +1 -4
  8. piccolo/query/base.py +1 -5
  9. piccolo/query/functions/__init__.py +16 -0
  10. piccolo/query/functions/aggregate.py +179 -0
  11. piccolo/query/functions/base.py +21 -0
  12. piccolo/query/functions/string.py +73 -0
  13. piccolo/query/methods/__init__.py +18 -1
  14. piccolo/query/methods/count.py +3 -3
  15. piccolo/query/methods/delete.py +1 -1
  16. piccolo/query/methods/exists.py +1 -1
  17. piccolo/query/methods/objects.py +1 -1
  18. piccolo/query/methods/select.py +17 -232
  19. piccolo/query/methods/update.py +1 -1
  20. piccolo/query/mixins.py +9 -2
  21. piccolo/querystring.py +101 -13
  22. piccolo/table.py +8 -24
  23. {piccolo-1.5.1.dist-info → piccolo-1.6.0.dist-info}/METADATA +1 -1
  24. {piccolo-1.5.1.dist-info → piccolo-1.6.0.dist-info}/RECORD +35 -30
  25. tests/apps/migrations/commands/test_forwards_backwards.py +3 -0
  26. tests/apps/shell/commands/test_run.py +1 -0
  27. tests/conf/test_apps.py +6 -0
  28. tests/example_apps/music/tables.py +10 -0
  29. tests/query/test_functions.py +102 -0
  30. tests/table/test_output.py +88 -36
  31. tests/table/test_select.py +2 -9
  32. {piccolo-1.5.1.dist-info → piccolo-1.6.0.dist-info}/LICENSE +0 -0
  33. {piccolo-1.5.1.dist-info → piccolo-1.6.0.dist-info}/WHEEL +0 -0
  34. {piccolo-1.5.1.dist-info → piccolo-1.6.0.dist-info}/entry_points.txt +0 -0
  35. {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.columns.base import Selectable
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
- @dataclass
21
- class Unquoted:
22
+ class Selectable(metaclass=ABCMeta):
22
23
  """
23
- Used when we want the value to be unquoted because it's a Postgres
24
- keyword - for example DEFAULT.
24
+ Anything which inherits from this can be used in a select query.
25
25
  """
26
26
 
27
- __slots__ = ("value",)
27
+ __slots__ = ("_alias",)
28
28
 
29
- value: str
29
+ _alias: t.Optional[str]
30
30
 
31
- def __repr__(self):
32
- return f"{self.value}"
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 __str__(self):
35
- return f"{self.value}"
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, self.__class__):
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, Unquoted
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.columns import Selectable
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
- args_dict = {}
800
- for col in self._meta.columns:
801
- column_name = col._meta.name
802
- value = convert_to_sql_value(value=self[column_name], column=col)
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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: piccolo
3
- Version: 1.5.1
3
+ Version: 1.6.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,10 +1,10 @@
1
- piccolo/__init__.py,sha256=fhNN-7ULy_9pNKoacDRvSuTI6i1H6WO1D2dUk7PEiqQ,22
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=YZkenGtG4mVSUdM7-zZXedxgIzXOnNOZf16AIqMr2n0,5863
5
+ piccolo/querystring.py,sha256=6uxfoCk7sj6bs_mDNV8W4ScgdG-h5wl1Y9HMlVW2abM,8671
6
6
  piccolo/schema.py,sha256=aWPuZxEulgBRD5NTqKN-RAZchxu-PoIrn0iFrWGZuq4,7731
7
- piccolo/table.py,sha256=w582fj4V66e-EYbF5PPO4TWYCOdIzzylPsc4rNWhPEs,50001
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=ajXYy2UqU47QGQ3vmXbSvpwR8h7kbMV8W4QFCLJBHhE,7351
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=7ZcqdJc1GuqmVPDHJlEkCHnEIKkXms0BlkS4fLIx80E,31620
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=REqafGESK3Zu8YbuCHM2v3R_KC-2OjGZs7D7VppH2KM,81013
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=BtcMM2FG7B442DifrbPxCNh2Wa4ZOQ-4ezEWYD0trVg,14365
124
- piccolo/columns/readable.py,sha256=vKIEc_vWxKo4GSPOeJZz-q5a1i4DNaoBcCNG_i_2OSA,1485
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=1R0cRo8xoycUTFbSZzmW5rndH-wW4DDqsWJQgTELiWU,683
149
- piccolo/query/base.py,sha256=dcYkEy_fhOUoiRn7u7lVsoWUKe90Z7jDu5zir1teRB8,15397
150
- piccolo/query/mixins.py,sha256=3oFksbYSaNGz_F7XJRhTTMHK4Pdf8-FUfDMpdy7rsNM,21639
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/methods/__init__.py,sha256=_PfGUdOd6AsKq1sqXeZUHhESHE-e1cNpwFr8Lyz7QoY,421
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=vSwn52IG0wlhPC6L-jYVlCsD4BPb2EkGHGwWn7z7gH4,1717
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=c4LO6-sGKfX-pi1nTZPC3aKvevgKWXuO28sFetbQ7WY,2212
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=LAeWpGKEMYZJeNGEcxbucxWDxAjn84jduNz2ZjzukPc,1181
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=4QFyK03sa0RXIvNCr8cl2RpvpxuO6DVeJ1vxGNwBNLM,11705
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=qP-EnhdcuQOmOQ_ADnnYvA4OFSnmU7avCiQ8yoP5OHs,26883
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=K0sq7XlIDr2mw5CwCvD4s9-jLcpYG6MI2hGScRGmdPc,3683
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=TjuZz86dEzaUTmcbnRh9lzYp07JXkxsKhJPH8mEbwss,7029
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=F6BJNcsSTFodz0K1iHOkw4qsXOnGkdtX2s1yQ71y4UM,1132
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=PrU4SRY0HvADGre5pMpALGmbWxPnydBug5ejSYf1e88,8461
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=5Rp_qatvr3eWW86mYyOei2Rb34KsvER_yD0pVqT7OXw,2399
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=_9DydB4bt8GOtzgx7xYrAs6KG8EwRTcZ45EaAHMdEBk,3049
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=-DoQ0mvAn4HnQl7BMBOp7KKJFZoF74qPWUc8Kzwklwg,40607
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.5.1.dist-info/LICENSE,sha256=zFIpi-16uIJ420UMIG75NU0JbDBykvrdnXcj5U_EYBI,1059
354
- piccolo-1.5.1.dist-info/METADATA,sha256=yWYssQ8mhB4JiWPgB5P4td5YAvYoqZX2l11bfp97LLI,5177
355
- piccolo-1.5.1.dist-info/WHEEL,sha256=00yskusixUoUt5ob_CiUp6LsnN5lqzTJpoqOFg_FVIc,92
356
- piccolo-1.5.1.dist-info/entry_points.txt,sha256=SJPHET4Fi1bN5F3WqcKkv9SClK3_F1I7m4eQjk6AFh0,46
357
- piccolo-1.5.1.dist-info/top_level.txt,sha256=-SR74VGbk43VoPy1HH-mHm97yoGukLK87HE5kdBW6qM,24
358
- piccolo-1.5.1.dist-info/RECORD,,
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
 
@@ -20,6 +20,7 @@ class TestRun(TestCase):
20
20
  call("Importing music tables:"),
21
21
  call("- Band"),
22
22
  call("- Concert"),
23
+ call("- Instrument"),
23
24
  call("- Manager"),
24
25
  call("- Poster"),
25
26
  call("- RecordingStudio"),
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"}])