piccolo 1.21.0__py3-none-any.whl → 1.23.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 (31) hide show
  1. piccolo/__init__.py +1 -1
  2. piccolo/apps/asgi/commands/new.py +3 -0
  3. piccolo/apps/asgi/commands/templates/app/_falcon_app.py.jinja +60 -0
  4. piccolo/apps/asgi/commands/templates/app/_quart_app.py.jinja +119 -0
  5. piccolo/apps/asgi/commands/templates/app/_sanic_app.py.jinja +121 -0
  6. piccolo/apps/asgi/commands/templates/app/app.py.jinja +6 -0
  7. piccolo/apps/asgi/commands/templates/app/home/_falcon_endpoints.py.jinja +19 -0
  8. piccolo/apps/asgi/commands/templates/app/home/_quart_endpoints.py.jinja +18 -0
  9. piccolo/apps/asgi/commands/templates/app/home/_sanic_endpoints.py.jinja +17 -0
  10. piccolo/apps/asgi/commands/templates/app/home/endpoints.py.jinja +6 -0
  11. piccolo/apps/asgi/commands/templates/app/home/templates/home.html.jinja_raw +15 -0
  12. piccolo/apps/playground/commands/run.py +9 -0
  13. piccolo/columns/column_types.py +78 -39
  14. piccolo/columns/defaults/timestamptz.py +1 -0
  15. piccolo/engine/sqlite.py +14 -2
  16. piccolo/query/base.py +11 -6
  17. piccolo/query/operators/__init__.py +0 -0
  18. piccolo/query/operators/json.py +111 -0
  19. piccolo/querystring.py +14 -2
  20. piccolo/table_reflection.py +17 -5
  21. {piccolo-1.21.0.dist-info → piccolo-1.23.0.dist-info}/METADATA +34 -22
  22. {piccolo-1.21.0.dist-info → piccolo-1.23.0.dist-info}/RECORD +31 -20
  23. {piccolo-1.21.0.dist-info → piccolo-1.23.0.dist-info}/WHEEL +1 -1
  24. tests/columns/test_integer.py +32 -0
  25. tests/columns/test_jsonb.py +100 -43
  26. tests/query/operators/__init__.py +0 -0
  27. tests/query/operators/test_json.py +52 -0
  28. tests/table/test_insert.py +1 -1
  29. {piccolo-1.21.0.dist-info → piccolo-1.23.0.dist-info}/LICENSE +0 -0
  30. {piccolo-1.21.0.dist-info → piccolo-1.23.0.dist-info}/entry_points.txt +0 -0
  31. {piccolo-1.21.0.dist-info → piccolo-1.23.0.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  from piccolo.columns.column_types import JSONB, ForeignKey, Varchar
2
2
  from piccolo.table import Table
3
- from piccolo.testing.test_case import TableTest
3
+ from piccolo.testing.test_case import AsyncTableTest, TableTest
4
4
  from tests.base import engines_only, engines_skip
5
5
 
6
6
 
@@ -137,93 +137,150 @@ class TestJSONB(TableTest):
137
137
  [{"name": "Guitar", "studio_facilities": {"mixing_desk": True}}],
138
138
  )
139
139
 
140
- def test_arrow(self):
140
+
141
+ @engines_only("postgres", "cockroach")
142
+ class TestArrow(AsyncTableTest):
143
+ tables = [RecordingStudio, Instrument]
144
+
145
+ async def insert_row(self):
146
+ await RecordingStudio(
147
+ name="Abbey Road", facilities='{"mixing_desk": true}'
148
+ ).save()
149
+
150
+ async def test_arrow(self):
141
151
  """
142
152
  Test using the arrow function to retrieve a subset of the JSON.
143
153
  """
144
- RecordingStudio(
145
- name="Abbey Road", facilities='{"mixing_desk": true}'
146
- ).save().run_sync()
154
+ await self.insert_row()
147
155
 
148
- row = (
149
- RecordingStudio.select(
150
- RecordingStudio.facilities.arrow("mixing_desk")
151
- )
152
- .first()
153
- .run_sync()
154
- )
156
+ row = await RecordingStudio.select(
157
+ RecordingStudio.facilities.arrow("mixing_desk")
158
+ ).first()
155
159
  assert row is not None
156
160
  self.assertEqual(row["facilities"], "true")
157
161
 
158
- row = (
162
+ row = await (
159
163
  RecordingStudio.select(
160
164
  RecordingStudio.facilities.arrow("mixing_desk")
161
165
  )
162
166
  .output(load_json=True)
163
167
  .first()
164
- .run_sync()
165
168
  )
166
169
  assert row is not None
167
170
  self.assertEqual(row["facilities"], True)
168
171
 
169
- def test_arrow_as_alias(self):
172
+ async def test_arrow_as_alias(self):
170
173
  """
171
174
  Test using the arrow function to retrieve a subset of the JSON.
172
175
  """
173
- RecordingStudio(
174
- name="Abbey Road", facilities='{"mixing_desk": true}'
175
- ).save().run_sync()
176
+ await self.insert_row()
176
177
 
177
- row = (
178
- RecordingStudio.select(
179
- RecordingStudio.facilities.arrow("mixing_desk").as_alias(
180
- "mixing_desk"
181
- )
178
+ row = await RecordingStudio.select(
179
+ RecordingStudio.facilities.arrow("mixing_desk").as_alias(
180
+ "mixing_desk"
182
181
  )
183
- .first()
184
- .run_sync()
185
- )
182
+ ).first()
183
+ assert row is not None
184
+ self.assertEqual(row["mixing_desk"], "true")
185
+
186
+ async def test_square_brackets(self):
187
+ """
188
+ Make sure we can use square brackets instead of calling ``arrow``
189
+ explicitly.
190
+ """
191
+ await self.insert_row()
192
+
193
+ row = await RecordingStudio.select(
194
+ RecordingStudio.facilities["mixing_desk"].as_alias("mixing_desk")
195
+ ).first()
186
196
  assert row is not None
187
197
  self.assertEqual(row["mixing_desk"], "true")
188
198
 
189
- def test_arrow_where(self):
199
+ async def test_multiple_levels_deep(self):
200
+ """
201
+ Make sure elements can be extracted multiple levels deep, and using
202
+ array indexes.
203
+ """
204
+ await RecordingStudio(
205
+ name="Abbey Road",
206
+ facilities={
207
+ "technicians": [
208
+ {"name": "Alice Jones"},
209
+ {"name": "Bob Williams"},
210
+ ]
211
+ },
212
+ ).save()
213
+
214
+ response = await RecordingStudio.select(
215
+ RecordingStudio.facilities["technicians"][0]["name"].as_alias(
216
+ "technician_name"
217
+ )
218
+ ).output(load_json=True)
219
+ assert response is not None
220
+ self.assertListEqual(response, [{"technician_name": "Alice Jones"}])
221
+
222
+ async def test_arrow_where(self):
190
223
  """
191
224
  Make sure the arrow function can be used within a WHERE clause.
192
225
  """
193
- RecordingStudio(
194
- name="Abbey Road", facilities='{"mixing_desk": true}'
195
- ).save().run_sync()
226
+ await self.insert_row()
196
227
 
197
228
  self.assertEqual(
198
- RecordingStudio.count()
199
- .where(RecordingStudio.facilities.arrow("mixing_desk").eq(True))
200
- .run_sync(),
229
+ await RecordingStudio.count().where(
230
+ RecordingStudio.facilities.arrow("mixing_desk").eq(True)
231
+ ),
201
232
  1,
202
233
  )
203
234
 
204
235
  self.assertEqual(
205
- RecordingStudio.count()
206
- .where(RecordingStudio.facilities.arrow("mixing_desk").eq(False))
207
- .run_sync(),
236
+ await RecordingStudio.count().where(
237
+ RecordingStudio.facilities.arrow("mixing_desk").eq(False)
238
+ ),
208
239
  0,
209
240
  )
210
241
 
211
- def test_arrow_first(self):
242
+ async def test_arrow_first(self):
212
243
  """
213
244
  Make sure the arrow function can be used with the first clause.
214
245
  """
215
- RecordingStudio.insert(
246
+ await RecordingStudio.insert(
216
247
  RecordingStudio(facilities='{"mixing_desk": true}'),
217
248
  RecordingStudio(facilities='{"mixing_desk": false}'),
218
- ).run_sync()
249
+ )
219
250
 
220
251
  self.assertEqual(
221
- RecordingStudio.select(
252
+ await RecordingStudio.select(
222
253
  RecordingStudio.facilities.arrow("mixing_desk").as_alias(
223
254
  "mixing_desk"
224
255
  )
225
- )
226
- .first()
227
- .run_sync(),
256
+ ).first(),
228
257
  {"mixing_desk": "true"},
229
258
  )
259
+
260
+
261
+ @engines_only("postgres", "cockroach")
262
+ class TestFromPath(AsyncTableTest):
263
+
264
+ tables = [RecordingStudio, Instrument]
265
+
266
+ async def test_from_path(self):
267
+ """
268
+ Make sure ``from_path`` can be used for complex nested data.
269
+ """
270
+ await RecordingStudio(
271
+ name="Abbey Road",
272
+ facilities={
273
+ "technicians": [
274
+ {"name": "Alice Jones"},
275
+ {"name": "Bob Williams"},
276
+ ]
277
+ },
278
+ ).save()
279
+
280
+ response = await RecordingStudio.select(
281
+ RecordingStudio.facilities.from_path(
282
+ ["technicians", 0, "name"]
283
+ ).as_alias("technician_name")
284
+ ).output(load_json=True)
285
+ assert response is not None
286
+ self.assertListEqual(response, [{"technician_name": "Alice Jones"}])
File without changes
@@ -0,0 +1,52 @@
1
+ from unittest import TestCase
2
+
3
+ from piccolo.columns import JSONB
4
+ from piccolo.query.operators.json import GetChildElement, GetElementFromPath
5
+ from piccolo.table import Table
6
+ from tests.base import engines_skip
7
+
8
+
9
+ class RecordingStudio(Table):
10
+ facilities = JSONB(null=True)
11
+
12
+
13
+ @engines_skip("sqlite")
14
+ class TestGetChildElement(TestCase):
15
+
16
+ def test_query(self):
17
+ """
18
+ Make sure the generated SQL looks correct.
19
+ """
20
+ querystring = GetChildElement(
21
+ GetChildElement(RecordingStudio.facilities, "a"), "b"
22
+ )
23
+
24
+ sql, query_args = querystring.compile_string()
25
+
26
+ self.assertEqual(
27
+ sql,
28
+ '"recording_studio"."facilities" -> $1 -> $2',
29
+ )
30
+
31
+ self.assertListEqual(query_args, ["a", "b"])
32
+
33
+
34
+ @engines_skip("sqlite")
35
+ class TestGetElementFromPath(TestCase):
36
+
37
+ def test_query(self):
38
+ """
39
+ Make sure the generated SQL looks correct.
40
+ """
41
+ querystring = GetElementFromPath(
42
+ RecordingStudio.facilities, ["a", "b"]
43
+ )
44
+
45
+ sql, query_args = querystring.compile_string()
46
+
47
+ self.assertEqual(
48
+ sql,
49
+ '"recording_studio"."facilities" #> $1',
50
+ )
51
+
52
+ self.assertListEqual(query_args, [["a", "b"]])
@@ -201,7 +201,7 @@ class TestOnConflict(TestCase):
201
201
  Make sure that a composite unique constraint can be used as a target.
202
202
 
203
203
  We only run it on Postgres and Cockroach because we use ALTER TABLE
204
- to add a contraint, which SQLite doesn't support.
204
+ to add a constraint, which SQLite doesn't support.
205
205
  """
206
206
  Band = self.Band
207
207