piccolo 1.20.0__py3-none-any.whl → 1.22.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
piccolo/__init__.py CHANGED
@@ -1 +1 @@
1
- __VERSION__ = "1.20.0"
1
+ __VERSION__ = "1.22.0"
@@ -233,6 +233,11 @@ def populate():
233
233
  RecordingStudio.facilities: {
234
234
  "restaurant": True,
235
235
  "mixing_desk": True,
236
+ "instruments": {"electric_guitars": 10, "drum_kits": 2},
237
+ "technicians": [
238
+ {"name": "Alice Jones"},
239
+ {"name": "Bob Williams"},
240
+ ],
236
241
  },
237
242
  }
238
243
  )
@@ -244,6 +249,10 @@ def populate():
244
249
  RecordingStudio.facilities: {
245
250
  "restaurant": False,
246
251
  "mixing_desk": True,
252
+ "instruments": {"electric_guitars": 6, "drum_kits": 3},
253
+ "technicians": [
254
+ {"name": "Frank Smith"},
255
+ ],
247
256
  },
248
257
  },
249
258
  )
@@ -70,6 +70,10 @@ from piccolo.utils.warnings import colored_warning
70
70
 
71
71
  if t.TYPE_CHECKING: # pragma: no cover
72
72
  from piccolo.columns.base import ColumnMeta
73
+ from piccolo.query.operators.json import (
74
+ GetChildElement,
75
+ GetElementFromPath,
76
+ )
73
77
  from piccolo.table import Table
74
78
 
75
79
 
@@ -2319,6 +2323,76 @@ class JSON(Column):
2319
2323
  else:
2320
2324
  return "JSON"
2321
2325
 
2326
+ ###########################################################################
2327
+
2328
+ def arrow(self, key: t.Union[str, int, QueryString]) -> GetChildElement:
2329
+ """
2330
+ Allows a child element of the JSON structure to be returned - for
2331
+ example::
2332
+
2333
+ >>> await RecordingStudio.select(
2334
+ ... RecordingStudio.facilities.arrow("restaurant")
2335
+ ... )
2336
+
2337
+ """
2338
+ from piccolo.query.operators.json import GetChildElement
2339
+
2340
+ alias = self._alias or self._meta.get_default_alias()
2341
+ return GetChildElement(identifier=self, key=key, alias=alias)
2342
+
2343
+ def __getitem__(
2344
+ self, value: t.Union[str, int, QueryString]
2345
+ ) -> GetChildElement:
2346
+ """
2347
+ A shortcut for the ``arrow`` method, used for retrieving a child
2348
+ element.
2349
+
2350
+ For example:
2351
+
2352
+ .. code-block:: python
2353
+
2354
+ >>> await RecordingStudio.select(
2355
+ ... RecordingStudio.facilities["restaurant"]
2356
+ ... )
2357
+
2358
+ """
2359
+ return self.arrow(key=value)
2360
+
2361
+ def from_path(
2362
+ self,
2363
+ path: t.List[t.Union[str, int]],
2364
+ ) -> GetElementFromPath:
2365
+ """
2366
+ Allows an element of the JSON structure to be returned, which can be
2367
+ arbitrarily deep. For example::
2368
+
2369
+ >>> await RecordingStudio.select(
2370
+ ... RecordingStudio.facilities.from_path([
2371
+ ... "technician",
2372
+ ... 0,
2373
+ ... "first_name"
2374
+ ... ])
2375
+ ... )
2376
+
2377
+ It's the same as calling ``arrow`` multiple times, but is more
2378
+ efficient / convenient if extracting highly nested data::
2379
+
2380
+ >>> await RecordingStudio.select(
2381
+ ... RecordingStudio.facilities.arrow(
2382
+ ... "technician"
2383
+ ... ).arrow(
2384
+ ... 0
2385
+ ... ).arrow(
2386
+ ... "first_name"
2387
+ ... )
2388
+ ... )
2389
+
2390
+ """
2391
+ from piccolo.query.operators.json import GetElementFromPath
2392
+
2393
+ alias = self._alias or self._meta.get_default_alias()
2394
+ return GetElementFromPath(identifier=self, path=path, alias=alias)
2395
+
2322
2396
  ###########################################################################
2323
2397
  # Descriptors
2324
2398
 
@@ -2337,10 +2411,10 @@ class JSON(Column):
2337
2411
 
2338
2412
  class JSONB(JSON):
2339
2413
  """
2340
- Used for storing JSON strings - Postgres only. The data is stored in a
2341
- binary format, and can be queried. Insertion can be slower (as it needs to
2342
- be converted to the binary format). The benefits of JSONB generally
2343
- outweigh the downsides.
2414
+ Used for storing JSON strings - Postgres / CochroachDB only. The data is
2415
+ stored in a binary format, and can be queried more efficiently. Insertion
2416
+ can be slower (as it needs to be converted to the binary format). The
2417
+ benefits of JSONB generally outweigh the downsides.
2344
2418
 
2345
2419
  :param default:
2346
2420
  Either a JSON string can be provided, or a Python ``dict`` or ``list``
@@ -2352,41 +2426,6 @@ class JSONB(JSON):
2352
2426
  def column_type(self):
2353
2427
  return "JSONB" # Must be defined, we override column_type() in JSON()
2354
2428
 
2355
- def arrow(self, key: str) -> JSONB:
2356
- """
2357
- Allows part of the JSON structure to be returned - for example,
2358
- for {"a": 1}, and a key value of "a", then 1 will be returned.
2359
- """
2360
- instance = t.cast(JSONB, self.copy())
2361
- instance.json_operator = f"-> '{key}'"
2362
- return instance
2363
-
2364
- def get_select_string(
2365
- self, engine_type: str, with_alias: bool = True
2366
- ) -> QueryString:
2367
- select_string = self._meta.get_full_name(with_alias=False)
2368
-
2369
- if self.json_operator is not None:
2370
- select_string += f" {self.json_operator}"
2371
-
2372
- if with_alias:
2373
- alias = self._alias or self._meta.get_default_alias()
2374
- select_string += f' AS "{alias}"'
2375
-
2376
- return QueryString(select_string)
2377
-
2378
- def eq(self, value) -> Where:
2379
- """
2380
- See ``Boolean.eq`` for more details.
2381
- """
2382
- return self.__eq__(value)
2383
-
2384
- def ne(self, value) -> Where:
2385
- """
2386
- See ``Boolean.ne`` for more details.
2387
- """
2388
- return self.__ne__(value)
2389
-
2390
2429
  ###########################################################################
2391
2430
  # Descriptors
2392
2431
 
piccolo/query/base.py CHANGED
@@ -6,6 +6,7 @@ from time import time
6
6
  from piccolo.columns.column_types import JSON, JSONB
7
7
  from piccolo.custom_types import QueryResponseType, TableInstance
8
8
  from piccolo.query.mixins import ColumnsDelegate
9
+ from piccolo.query.operators.json import JSONQueryString
9
10
  from piccolo.querystring import QueryString
10
11
  from piccolo.utils.encoding import load_json
11
12
  from piccolo.utils.objects import make_nested_object
@@ -65,16 +66,20 @@ class Query(t.Generic[TableInstance, QueryResponseType]):
65
66
  self, "columns_delegate", None
66
67
  )
67
68
 
69
+ json_column_names: t.List[str] = []
70
+
68
71
  if columns_delegate is not None:
69
- json_columns = [
70
- i
71
- for i in columns_delegate.selected_columns
72
- if isinstance(i, (JSON, JSONB))
73
- ]
72
+ json_columns: t.List[t.Union[JSON, JSONB]] = []
73
+
74
+ for column in columns_delegate.selected_columns:
75
+ if isinstance(column, (JSON, JSONB)):
76
+ json_columns.append(column)
77
+ elif isinstance(column, JSONQueryString):
78
+ if alias := column._alias:
79
+ json_column_names.append(alias)
74
80
  else:
75
81
  json_columns = self.table._meta.json_columns
76
82
 
77
- json_column_names = []
78
83
  for column in json_columns:
79
84
  if column._alias is not None:
80
85
  json_column_names.append(column._alias)
@@ -534,7 +534,7 @@ class Select(Query[TableInstance, t.List[t.Dict[str, t.Any]]]):
534
534
  _joins.append(
535
535
  f'LEFT JOIN {right_tablename} "{table_alias}"'
536
536
  " ON "
537
- f'({left_tablename}."{key._meta.name}" = "{table_alias}"."{pk_name}")' # noqa: E501
537
+ f'({left_tablename}."{key._meta.db_column_name}" = "{table_alias}"."{pk_name}")' # noqa: E501
538
538
  )
539
539
 
540
540
  joins.extend(_joins)
File without changes
@@ -0,0 +1,111 @@
1
+ from __future__ import annotations
2
+
3
+ import typing as t
4
+
5
+ from piccolo.querystring import QueryString
6
+ from piccolo.utils.encoding import dump_json
7
+
8
+ if t.TYPE_CHECKING:
9
+ from piccolo.columns.column_types import JSON
10
+
11
+
12
+ class JSONQueryString(QueryString):
13
+
14
+ def clean_value(self, value: t.Any):
15
+ if not isinstance(value, (str, QueryString)):
16
+ value = dump_json(value)
17
+ return value
18
+
19
+ def __eq__(self, value) -> QueryString: # type: ignore[override]
20
+ value = self.clean_value(value)
21
+ return QueryString("{} = {}", self, value)
22
+
23
+ def __ne__(self, value) -> QueryString: # type: ignore[override]
24
+ value = self.clean_value(value)
25
+ return QueryString("{} != {}", self, value)
26
+
27
+ def eq(self, value) -> QueryString:
28
+ return self.__eq__(value)
29
+
30
+ def ne(self, value) -> QueryString:
31
+ return self.__ne__(value)
32
+
33
+
34
+ class GetChildElement(JSONQueryString):
35
+ """
36
+ Allows you to get a child element from a JSON object.
37
+
38
+ You can access this via the ``arrow`` function on ``JSON`` and ``JSONB``
39
+ columns.
40
+
41
+ """
42
+
43
+ def __init__(
44
+ self,
45
+ identifier: t.Union[JSON, QueryString],
46
+ key: t.Union[str, int, QueryString],
47
+ alias: t.Optional[str] = None,
48
+ ):
49
+ if isinstance(key, int):
50
+ # asyncpg only accepts integer keys if we explicitly mark it as an
51
+ # int.
52
+ key = QueryString("{}::int", key)
53
+
54
+ super().__init__("{} -> {}", identifier, key, alias=alias)
55
+
56
+ def arrow(self, key: t.Union[str, int, QueryString]) -> GetChildElement:
57
+ """
58
+ This allows you to drill multiple levels deep into a JSON object if
59
+ needed.
60
+
61
+ For example::
62
+
63
+ >>> await RecordingStudio.select(
64
+ ... RecordingStudio.name,
65
+ ... RecordingStudio.facilities.arrow(
66
+ ... "instruments"
67
+ ... ).arrow(
68
+ ... "drum_kits"
69
+ ... ).as_alias("drum_kits")
70
+ ... ).output(load_json=True)
71
+ [
72
+ {'name': 'Abbey Road', 'drum_kits': 2},
73
+ {'name': 'Electric Lady', 'drum_kits': 3}
74
+ ]
75
+
76
+ """
77
+ return GetChildElement(identifier=self, key=key, alias=self._alias)
78
+
79
+ def __getitem__(
80
+ self, value: t.Union[str, int, QueryString]
81
+ ) -> GetChildElement:
82
+ return GetChildElement(identifier=self, key=value, alias=self._alias)
83
+
84
+
85
+ class GetElementFromPath(JSONQueryString):
86
+ """
87
+ Allows you to retrieve an element from a JSON object by specifying a path.
88
+ It can be several levels deep.
89
+
90
+ You can access this via the ``from_path`` function on ``JSON`` and
91
+ ``JSONB`` columns.
92
+
93
+ """
94
+
95
+ def __init__(
96
+ self,
97
+ identifier: t.Union[JSON, QueryString],
98
+ path: t.List[t.Union[str, int]],
99
+ alias: t.Optional[str] = None,
100
+ ):
101
+ """
102
+ :param path:
103
+ For example: ``["technician", 0, "name"]``.
104
+
105
+ """
106
+ super().__init__(
107
+ "{} #> {}",
108
+ identifier,
109
+ [str(i) if isinstance(i, int) else i for i in path],
110
+ alias=alias,
111
+ )
piccolo/querystring.py CHANGED
@@ -259,10 +259,22 @@ class QueryString(Selectable):
259
259
  # Basic logic
260
260
 
261
261
  def __eq__(self, value) -> QueryString: # type: ignore[override]
262
- return QueryString("{} = {}", self, value)
262
+ if value is None:
263
+ return QueryString("{} IS NULL", self)
264
+ else:
265
+ return QueryString("{} = {}", self, value)
263
266
 
264
267
  def __ne__(self, value) -> QueryString: # type: ignore[override]
265
- return QueryString("{} != {}", self, value)
268
+ if value is None:
269
+ return QueryString("{} IS NOT NULL", self, value)
270
+ else:
271
+ return QueryString("{} != {}", self, value)
272
+
273
+ def eq(self, value) -> QueryString:
274
+ return self.__eq__(value)
275
+
276
+ def ne(self, value) -> QueryString:
277
+ return self.__ne__(value)
266
278
 
267
279
  def __add__(self, value) -> QueryString:
268
280
  return QueryString("{} + {}", self, value)
@@ -8,6 +8,8 @@ import typing as t
8
8
  from dataclasses import dataclass
9
9
 
10
10
  from piccolo.apps.schema.commands.generate import get_output_schema
11
+ from piccolo.engine import engine_finder
12
+ from piccolo.engine.base import Engine
11
13
  from piccolo.table import Table
12
14
 
13
15
 
@@ -78,9 +80,16 @@ class TableStorage(metaclass=Singleton):
78
80
  works with Postgres.
79
81
  """
80
82
 
81
- def __init__(self):
83
+ def __init__(self, engine: t.Optional[Engine] = None):
84
+ """
85
+ :param engine:
86
+ Which engine to use to make the database queries. If not specified,
87
+ we try importing an engine from ``piccolo_conf.py``.
88
+
89
+ """
90
+ self.engine = engine or engine_finder()
82
91
  self.tables = ImmutableDict()
83
- self._schema_tables = {}
92
+ self._schema_tables: t.Dict[str, t.List[str]] = {}
84
93
 
85
94
  async def reflect(
86
95
  self,
@@ -120,10 +129,13 @@ class TableStorage(metaclass=Singleton):
120
129
  exclude_list = self._to_list(exclude)
121
130
 
122
131
  if keep_existing:
123
- exclude += self._schema_tables.get(schema_name, [])
132
+ exclude_list += self._schema_tables.get(schema_name, [])
124
133
 
125
134
  output_schema = await get_output_schema(
126
- schema_name=schema_name, include=include_list, exclude=exclude_list
135
+ schema_name=schema_name,
136
+ include=include_list,
137
+ exclude=exclude_list,
138
+ engine=self.engine,
127
139
  )
128
140
  add_tables = [
129
141
  self._add_table(schema_name=schema_name, table=table)
@@ -177,7 +189,7 @@ class TableStorage(metaclass=Singleton):
177
189
 
178
190
  def _add_to_schema_tables(self, schema_name: str, table_name: str) -> None:
179
191
  """
180
- We keep record of schemas and their tables for easy use. This method
192
+ We keep a record of schemas and their tables for easy use. This method
181
193
  adds a table to its schema.
182
194
 
183
195
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: piccolo
3
- Version: 1.20.0
3
+ Version: 1.22.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
@@ -9,19 +9,20 @@ License: MIT
9
9
  Project-URL: Documentation, https://piccolo-orm.readthedocs.io/en/latest/index.html
10
10
  Project-URL: Source, https://github.com/piccolo-orm/piccolo
11
11
  Project-URL: Tracker, https://github.com/piccolo-orm/piccolo/issues
12
+ Platform: UNKNOWN
12
13
  Classifier: License :: OSI Approved :: MIT License
13
14
  Classifier: Programming Language :: Python
14
15
  Classifier: Programming Language :: Python :: 3
15
- Classifier: Programming Language :: Python :: 3.8
16
16
  Classifier: Programming Language :: Python :: 3.9
17
17
  Classifier: Programming Language :: Python :: 3.10
18
18
  Classifier: Programming Language :: Python :: 3.11
19
19
  Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
20
21
  Classifier: Programming Language :: Python :: Implementation :: CPython
21
22
  Classifier: Framework :: AsyncIO
22
23
  Classifier: Typing :: Typed
23
24
  Classifier: Topic :: Database
24
- Requires-Python: >=3.8.0
25
+ Requires-Python: >=3.9.0
25
26
  Description-Content-Type: text/markdown
26
27
  License-File: LICENSE
27
28
  Requires-Dist: black
@@ -34,7 +35,7 @@ Requires-Dist: pydantic[email] (==2.*)
34
35
  Provides-Extra: all
35
36
  Requires-Dist: orjson (>=3.5.1) ; extra == 'all'
36
37
  Requires-Dist: ipython ; extra == 'all'
37
- Requires-Dist: asyncpg (>=0.21.0) ; extra == 'all'
38
+ Requires-Dist: asyncpg (>=0.30.0) ; extra == 'all'
38
39
  Requires-Dist: aiosqlite (>=0.16.0) ; extra == 'all'
39
40
  Requires-Dist: uvloop (>=0.12.0) ; (sys_platform != "win32") and extra == 'all'
40
41
  Provides-Extra: orjson
@@ -42,7 +43,7 @@ Requires-Dist: orjson (>=3.5.1) ; extra == 'orjson'
42
43
  Provides-Extra: playground
43
44
  Requires-Dist: ipython ; extra == 'playground'
44
45
  Provides-Extra: postgres
45
- Requires-Dist: asyncpg (>=0.21.0) ; extra == 'postgres'
46
+ Requires-Dist: asyncpg (>=0.30.0) ; extra == 'postgres'
46
47
  Provides-Extra: sqlite
47
48
  Requires-Dist: aiosqlite (>=0.16.0) ; extra == 'sqlite'
48
49
  Provides-Extra: uvloop
@@ -155,3 +156,5 @@ We have a handy page which shows the equivalent of [common Django queries in Pic
155
156
  Our documentation is on [Read the docs](https://piccolo-orm.readthedocs.io/en/latest/piccolo/getting_started/index.html).
156
157
 
157
158
  We also have some great [tutorial videos on YouTube](https://www.youtube.com/channel/UCE7x5nm1Iy9KDfXPNrNQ5lA).
159
+
160
+
@@ -1,11 +1,11 @@
1
- piccolo/__init__.py,sha256=9C2I7AvMRyI69S3jlrRv3hQ4kYCgUZBXUC_VSvY3Dv0,23
1
+ piccolo/__init__.py,sha256=aVoeQDKbkLtZX6_B3ZNdv8mqfI_4pHUT1Go9Ek88OVI,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
5
- piccolo/querystring.py,sha256=yZdURtiVSlxkkEVJoFKAmL2OfNxrUr8xfsfuBBB7IuY,9662
5
+ piccolo/querystring.py,sha256=kb7RYTvQZEyPsC4GH8vR2b_w35wnM-ita242S0_eyvQ,10013
6
6
  piccolo/schema.py,sha256=qNNy4tG_HqnXR9t3hHMgYXtGxHabwQAhUpc6RKLJ_gE,7960
7
7
  piccolo/table.py,sha256=UvEbagMYRkTbyFHTUwUshZlL_dC4UKDP7vUOwF8OXmg,50593
8
- piccolo/table_reflection.py,sha256=jrN1nHerDJ4tU09GtNN3hz7ap-7rXnSUjljFO6LB2H0,7094
8
+ piccolo/table_reflection.py,sha256=02baOSLX6f2LEo0kruFZYF_nPPTbIvaCTH_KPGe0DKw,7540
9
9
  piccolo/apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  piccolo/apps/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  piccolo/apps/app/piccolo_app.py,sha256=8z2ITpxQQ-McxSYwQ5H_vyEnRXbY6cyAh2JSqhiylYk,340
@@ -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=S8osLV4s_mWdvvVYGn-49wel-d1SFRB17fO3ZtMcv8Y,8629
80
+ piccolo/apps/playground/commands/run.py,sha256=lpCbVmXabWBlsgwE-8cK4woIK_78E3nq9CTfA5hEJFI,9014
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
@@ -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=_bg9yMWjMwE76Z7RDqi9iYSmtRuFx5bkx9uYJsFHKjQ,32487
119
119
  piccolo/columns/choices.py,sha256=-HNQuk9vMmVZIPZ5PMeXGTfr23o4nzKPSAkvcG1k0y8,723
120
- piccolo/columns/column_types.py,sha256=X_ZsA0C4WBNVonV2OsizRdt1osMshgtQ3Ob6JN-amfg,83485
120
+ piccolo/columns/column_types.py,sha256=XVI6qA_qsP1BD6bqVqQdj7a8dldoYz4VgOPI-wW696I,84747
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=QMeSOnm4DT2cG9U5jC6sOZ6z9DxCWwDyZMSqk0wR2q4,14682
@@ -146,7 +146,7 @@ piccolo/engine/finder.py,sha256=GjzBNtzRzH79fjtRn7OI3nZiOXE8JfoQWAvHVPrPNx4,507
146
146
  piccolo/engine/postgres.py,sha256=DekL3KafCdzSAEQ6_EgOiUB1ERXh2xpePYwI9QvmN-c,18955
147
147
  piccolo/engine/sqlite.py,sha256=KwJc3UttBP_8qSREbLJshqEfROF17ENf0Ju9BwI5_so,25236
148
148
  piccolo/query/__init__.py,sha256=bcsMV4813rMRAIqGv4DxI4eyO4FmpXkDv9dfTk5pt3A,699
149
- piccolo/query/base.py,sha256=iI9Fv3oOw7T4ZWZvRKRwdtClvQtSaAepslH24vwxZVA,14616
149
+ piccolo/query/base.py,sha256=sO5VyicbWjgYaQukr6jqUqUUrOctL6QJ1MjcsgDKHXM,14912
150
150
  piccolo/query/mixins.py,sha256=X9HEYnj6uOjgTkGr4vgqTwN_dokJPzVagwbFx385atQ,24468
151
151
  piccolo/query/proxy.py,sha256=Yq4jNc7IWJvdeO3u7_7iPyRy2WhVj8KsIUcIYHBIi9Q,1839
152
152
  piccolo/query/functions/__init__.py,sha256=pZkzOIh7Sg9HPNOeegOwAS46Oxt31ATlSVmwn-lxCbc,605
@@ -169,9 +169,11 @@ piccolo/query/methods/insert.py,sha256=ssLJ_wn08KnOwwr7t-VILyn1P4hrvM63CfPIcAJWT
169
169
  piccolo/query/methods/objects.py,sha256=BfCOIbNMj7FWkmK5STaINkfDFmwzZvDZi60jCumjs1o,15627
170
170
  piccolo/query/methods/raw.py,sha256=wQWR8b-yA_Gr-5lqRMZe9BOAAMBAw8CqTx37qVYvM1A,987
171
171
  piccolo/query/methods/refresh.py,sha256=wg1zghKfwz-VmqK4uWa4GNMiDtK-skTqow591Hb3ONM,5854
172
- piccolo/query/methods/select.py,sha256=jNR7CsEPUarffYMsXytKm7iScrQ_nqpRX-5mTPrXfjg,22414
172
+ piccolo/query/methods/select.py,sha256=41OW-DIE_wr5VdxSusMKNT2aUhzQsCwK2Qh1XqgXHg0,22424
173
173
  piccolo/query/methods/table_exists.py,sha256=0yb3n6Jd2ovSBWlZ-gl00K4E7Jnbj7J8qAAX5d7hvNk,1259
174
174
  piccolo/query/methods/update.py,sha256=LfWqIXEl1aecc0rkVssTFmwyD6wXGhlKcTrUVhtlEsw,3705
175
+ piccolo/query/operators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
176
+ piccolo/query/operators/json.py,sha256=hdo1M6N9qTHJTJ0sRV9Bwt_iQZTgs4VdCKOPH1sXe-k,3168
175
177
  piccolo/testing/__init__.py,sha256=pRFSqRInfx95AakOq54atmvqoB-ue073q2aR8u8zR40,83
176
178
  piccolo/testing/model_builder.py,sha256=lVEiEe71xrH8SSjzFc2l0s-VaCXHeg9Bo5oAYOEbLrI,6545
177
179
  piccolo/testing/random_builder.py,sha256=0LkGpanQ7P1R82gLIMQyK9cm1LdZkPvxbShTEf3jeH4,2128
@@ -253,13 +255,13 @@ tests/columns/test_bytea.py,sha256=doN8S1eFVU4ntSXIg4IgMSZcbvqW1WJ-AEm3OjKLGkI,1
253
255
  tests/columns/test_choices.py,sha256=q8TLe7nvGERXyGO_XEryEBR-DuWwFY1jPpscsrXjdXo,4066
254
256
  tests/columns/test_combination.py,sha256=BuBwR7k5X1EkOWraZpjqU6gvtb6ow_k-7N1KQBiW2RA,1681
255
257
  tests/columns/test_date.py,sha256=QLC6kJMQwM-1mbUP4ksJVM7P8WwjzGZyynH3rHHdSew,1030
256
- tests/columns/test_db_column_name.py,sha256=v0QFOQp_atqzMB1n40simVwHeBDi5nyN1N2bSPX5k6w,7670
258
+ tests/columns/test_db_column_name.py,sha256=0wz6y4GNGy4nhMdHmYzEnChQGpK2UhWFFKrnmmML3Mk,9027
257
259
  tests/columns/test_defaults.py,sha256=rwlU1fXt3cCl7C51eLlZXqgWkE-K5W0pHvTrwkAKyCo,2896
258
260
  tests/columns/test_double_precision.py,sha256=7rhcSfDkb2fBh_zEG4UGwD_GW1sy6U9-8NooHuCS09Q,544
259
261
  tests/columns/test_get_sql_value.py,sha256=mKgsInN374jzV99y9mg_ZiG-AvnJgz36SZi89xL7RZM,1768
260
262
  tests/columns/test_interval.py,sha256=2M18pfoGxLLosEvwTmuC4zQkM6jWwU0Nv2fqViW3xOs,2780
261
263
  tests/columns/test_json.py,sha256=_cziJvw2uT8e_4u9lKhmU56lgQeE7bEqCXYf6AzfChA,3482
262
- tests/columns/test_jsonb.py,sha256=eJQoxpyuQ4yrX-GMJhRkZntyd4tX6M6RhAxYn2-ISII,6727
264
+ tests/columns/test_jsonb.py,sha256=KXPgJTchobzHNss86Gb0CeTDlaa5S3pQ8cM3D06-7J8,8592
263
265
  tests/columns/test_numeric.py,sha256=AkTvdvjSsfRsMM79tx4AskUpsTizGBLMY_tC2OII9U4,751
264
266
  tests/columns/test_primary_key.py,sha256=foNG9eTQUJ5yiEVQ7faIEMycW_VuZ7vgzknYXaZ-QXM,4886
265
267
  tests/columns/test_readable.py,sha256=xKVfJuxZcfyncNVKXNryl2WFREX655jwD9DxiLArQiU,758
@@ -313,6 +315,8 @@ tests/query/functions/test_type_conversion.py,sha256=WeYR9UfJnbidle07-akQ1g9hFCd
313
315
  tests/query/mixins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
314
316
  tests/query/mixins/test_columns_delegate.py,sha256=Zw9uaqOEb7kpPQzzO9yz0jhQEeCfoPSjsy-BCLg_8XU,2032
315
317
  tests/query/mixins/test_order_by_delegate.py,sha256=mOV3Gxs0XeliONxjWSOniI1z6lbZ_xTfcGYd53JLnaY,507
318
+ tests/query/operators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
319
+ tests/query/operators/test_json.py,sha256=SEYEdbyF0wB3nvONqyBGFlLe8OhgtSIvxx19P2uJ8Bw,1269
316
320
  tests/table/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
317
321
  tests/table/test_all_columns.py,sha256=wZ7i9mTT9wKWLE2BoQ9jDbPaqnBHfV-ZlsROp7SZq7k,667
318
322
  tests/table/test_alter.py,sha256=pMD38BIFfta1vxFqp8YoaRfMxdwxhQSwcxYO4erpUi8,12394
@@ -368,9 +372,9 @@ tests/utils/test_sql_values.py,sha256=vzxRmy16FfLZPH-sAQexBvsF9MXB8n4smr14qoEOS5
368
372
  tests/utils/test_sync.py,sha256=9ytVo56y2vPQePvTeIi9lHIouEhWJbodl1TmzkGFrSo,799
369
373
  tests/utils/test_table_reflection.py,sha256=SIzuat-IpcVj1GCFyOWKShI8YkhdOPPFH7qVrvfyPNE,3794
370
374
  tests/utils/test_warnings.py,sha256=NvSC_cvJ6uZcwAGf1m-hLzETXCqprXELL8zg3TNLVMw,269
371
- piccolo-1.20.0.dist-info/LICENSE,sha256=zFIpi-16uIJ420UMIG75NU0JbDBykvrdnXcj5U_EYBI,1059
372
- piccolo-1.20.0.dist-info/METADATA,sha256=xOURp1sxZOGQ4Q_cnDb9pYcV5XuMlDGEOZ89k9KC5f0,5178
373
- piccolo-1.20.0.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
374
- piccolo-1.20.0.dist-info/entry_points.txt,sha256=SJPHET4Fi1bN5F3WqcKkv9SClK3_F1I7m4eQjk6AFh0,46
375
- piccolo-1.20.0.dist-info/top_level.txt,sha256=-SR74VGbk43VoPy1HH-mHm97yoGukLK87HE5kdBW6qM,24
376
- piccolo-1.20.0.dist-info/RECORD,,
375
+ piccolo-1.22.0.dist-info/LICENSE,sha256=zFIpi-16uIJ420UMIG75NU0JbDBykvrdnXcj5U_EYBI,1059
376
+ piccolo-1.22.0.dist-info/METADATA,sha256=eL8y2tQr2Vd5Wmmqe0ICPO145ccpTL_5cthuK_zXIVU,5199
377
+ piccolo-1.22.0.dist-info/WHEEL,sha256=00yskusixUoUt5ob_CiUp6LsnN5lqzTJpoqOFg_FVIc,92
378
+ piccolo-1.22.0.dist-info/entry_points.txt,sha256=zYhu-YNtMlh2N_8wptCS8YWKOgc81UPL3Ji5gly8ouc,47
379
+ piccolo-1.22.0.dist-info/top_level.txt,sha256=-SR74VGbk43VoPy1HH-mHm97yoGukLK87HE5kdBW6qM,24
380
+ piccolo-1.22.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.1.0)
2
+ Generator: bdist_wheel (0.38.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,2 +1,3 @@
1
1
  [console_scripts]
2
2
  piccolo = piccolo.main:main
3
+
@@ -1,12 +1,20 @@
1
- from piccolo.columns.column_types import Integer, Serial, Varchar
2
- from piccolo.table import Table
1
+ import typing as t
2
+
3
+ from piccolo.columns.column_types import ForeignKey, Integer, Serial, Varchar
4
+ from piccolo.table import Table, create_db_tables_sync, drop_db_tables_sync
3
5
  from tests.base import DBTestCase, engine_is, engines_only, engines_skip
4
6
 
5
7
 
8
+ class Manager(Table):
9
+ id: Serial
10
+ name = Varchar()
11
+
12
+
6
13
  class Band(Table):
7
14
  id: Serial
8
15
  name = Varchar(db_column_name="regrettable_column_name")
9
16
  popularity = Integer()
17
+ manager = ForeignKey(Manager, db_column_name="manager_fk")
10
18
 
11
19
 
12
20
  class TestDBColumnName(DBTestCase):
@@ -22,10 +30,15 @@ class TestDBColumnName(DBTestCase):
22
30
  """
23
31
 
24
32
  def setUp(self):
25
- Band.create_table().run_sync()
33
+ create_db_tables_sync(Band, Manager)
26
34
 
27
35
  def tearDown(self):
28
- Band.alter().drop_table().run_sync()
36
+ drop_db_tables_sync(Band, Manager)
37
+
38
+ def insert_band(self, manager: t.Optional[Manager] = None) -> Band:
39
+ band = Band(name="Pythonistas", popularity=1000, manager=manager)
40
+ band.save().run_sync()
41
+ return band
29
42
 
30
43
  @engines_only("postgres", "cockroach")
31
44
  def test_column_name_correct(self):
@@ -45,8 +58,7 @@ class TestDBColumnName(DBTestCase):
45
58
  """
46
59
  Make sure save queries work correctly.
47
60
  """
48
- band = Band(name="Pythonistas", popularity=1000)
49
- band.save().run_sync()
61
+ self.insert_band()
50
62
 
51
63
  band_from_db = Band.objects().first().run_sync()
52
64
  assert band_from_db is not None
@@ -56,11 +68,7 @@ class TestDBColumnName(DBTestCase):
56
68
  """
57
69
  Make sure create queries work correctly.
58
70
  """
59
- band = (
60
- Band.objects()
61
- .create(name="Pythonistas", popularity=1000)
62
- .run_sync()
63
- )
71
+ band = self.insert_band()
64
72
  self.assertEqual(band.name, "Pythonistas")
65
73
 
66
74
  band_from_db = Band.objects().first().run_sync()
@@ -74,7 +82,7 @@ class TestDBColumnName(DBTestCase):
74
82
  name to it's alias, but it's hard to predict what behaviour the user
75
83
  wants.
76
84
  """
77
- Band.objects().create(name="Pythonistas", popularity=1000).run_sync()
85
+ self.insert_band()
78
86
 
79
87
  # Make sure we can select all columns
80
88
  bands = Band.select().run_sync()
@@ -86,6 +94,7 @@ class TestDBColumnName(DBTestCase):
86
94
  "id": bands[0]["id"],
87
95
  "regrettable_column_name": "Pythonistas",
88
96
  "popularity": 1000,
97
+ "manager_fk": None,
89
98
  }
90
99
  ],
91
100
  )
@@ -97,6 +106,7 @@ class TestDBColumnName(DBTestCase):
97
106
  "id": 1,
98
107
  "regrettable_column_name": "Pythonistas",
99
108
  "popularity": 1000,
109
+ "manager_fk": None,
100
110
  }
101
111
  ],
102
112
  )
@@ -123,11 +133,36 @@ class TestDBColumnName(DBTestCase):
123
133
  ],
124
134
  )
125
135
 
136
+ def test_join(self):
137
+ """
138
+ Make sure that foreign keys with a ``db_column_name`` specified still
139
+ work for joins.
140
+
141
+ https://github.com/piccolo-orm/piccolo/issues/1101
142
+
143
+ """
144
+ manager = Manager.objects().create(name="Guido").run_sync()
145
+ band = self.insert_band(manager=manager)
146
+
147
+ bands = Band.select().where(Band.manager.name == "Guido").run_sync()
148
+
149
+ self.assertListEqual(
150
+ bands,
151
+ [
152
+ {
153
+ "id": band.id,
154
+ "manager_fk": manager.id,
155
+ "popularity": 1000,
156
+ "regrettable_column_name": "Pythonistas",
157
+ }
158
+ ],
159
+ )
160
+
126
161
  def test_update(self):
127
162
  """
128
163
  Make sure update queries work correctly.
129
164
  """
130
- Band.objects().create(name="Pythonistas", popularity=1000).run_sync()
165
+ self.insert_band()
131
166
 
132
167
  Band.update({Band.name: "Pythonistas 2"}, force=True).run_sync()
133
168
 
@@ -140,6 +175,7 @@ class TestDBColumnName(DBTestCase):
140
175
  "id": bands[0]["id"],
141
176
  "regrettable_column_name": "Pythonistas 2",
142
177
  "popularity": 1000,
178
+ "manager_fk": None,
143
179
  }
144
180
  ],
145
181
  )
@@ -151,6 +187,7 @@ class TestDBColumnName(DBTestCase):
151
187
  "id": 1,
152
188
  "regrettable_column_name": "Pythonistas 2",
153
189
  "popularity": 1000,
190
+ "manager_fk": None,
154
191
  }
155
192
  ],
156
193
  )
@@ -166,6 +203,7 @@ class TestDBColumnName(DBTestCase):
166
203
  "id": bands[0]["id"],
167
204
  "regrettable_column_name": "Pythonistas 3",
168
205
  "popularity": 1000,
206
+ "manager_fk": None,
169
207
  }
170
208
  ],
171
209
  )
@@ -177,6 +215,7 @@ class TestDBColumnName(DBTestCase):
177
215
  "id": 1,
178
216
  "regrettable_column_name": "Pythonistas 3",
179
217
  "popularity": 1000,
218
+ "manager_fk": None,
180
219
  }
181
220
  ],
182
221
  )
@@ -199,11 +238,13 @@ class TestDBColumnName(DBTestCase):
199
238
  "id": 1,
200
239
  "regrettable_column_name": "Pythonistas",
201
240
  "popularity": 1000,
241
+ "manager_fk": None,
202
242
  },
203
243
  {
204
244
  "id": 2,
205
245
  "regrettable_column_name": "Rustaceans",
206
246
  "popularity": 500,
247
+ "manager_fk": None,
207
248
  },
208
249
  ],
209
250
  )
@@ -218,6 +259,7 @@ class TestDBColumnName(DBTestCase):
218
259
  "id": 1,
219
260
  "regrettable_column_name": "Pythonistas",
220
261
  "popularity": 1000,
262
+ "manager_fk": None,
221
263
  }
222
264
  ],
223
265
  )
@@ -244,11 +286,13 @@ class TestDBColumnName(DBTestCase):
244
286
  "id": result[0]["id"],
245
287
  "regrettable_column_name": "Pythonistas",
246
288
  "popularity": 1000,
289
+ "manager_fk": None,
247
290
  },
248
291
  {
249
292
  "id": result[1]["id"],
250
293
  "regrettable_column_name": "Rustaceans",
251
294
  "popularity": 500,
295
+ "manager_fk": None,
252
296
  },
253
297
  ],
254
298
  )
@@ -263,6 +307,7 @@ class TestDBColumnName(DBTestCase):
263
307
  "id": result[0]["id"],
264
308
  "regrettable_column_name": "Pythonistas",
265
309
  "popularity": 1000,
310
+ "manager_fk": None,
266
311
  }
267
312
  ],
268
313
  )
@@ -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"]])