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 +1 -1
- piccolo/apps/playground/commands/run.py +9 -0
- piccolo/columns/column_types.py +78 -39
- piccolo/query/base.py +11 -6
- piccolo/query/methods/select.py +1 -1
- piccolo/query/operators/__init__.py +0 -0
- piccolo/query/operators/json.py +111 -0
- piccolo/querystring.py +14 -2
- piccolo/table_reflection.py +17 -5
- {piccolo-1.20.0.dist-info → piccolo-1.22.0.dist-info}/METADATA +8 -5
- {piccolo-1.20.0.dist-info → piccolo-1.22.0.dist-info}/RECORD +19 -15
- {piccolo-1.20.0.dist-info → piccolo-1.22.0.dist-info}/WHEEL +1 -1
- {piccolo-1.20.0.dist-info → piccolo-1.22.0.dist-info}/entry_points.txt +1 -0
- tests/columns/test_db_column_name.py +58 -13
- tests/columns/test_jsonb.py +100 -43
- tests/query/operators/__init__.py +0 -0
- tests/query/operators/test_json.py +52 -0
- {piccolo-1.20.0.dist-info → piccolo-1.22.0.dist-info}/LICENSE +0 -0
- {piccolo-1.20.0.dist-info → piccolo-1.22.0.dist-info}/top_level.txt +0 -0
piccolo/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__VERSION__ = "1.
|
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
|
)
|
piccolo/columns/column_types.py
CHANGED
@@ -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
|
2341
|
-
binary format, and can be queried. Insertion
|
2342
|
-
be converted to the binary format). The
|
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
|
-
|
71
|
-
|
72
|
-
if isinstance(
|
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)
|
piccolo/query/methods/select.py
CHANGED
@@ -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.
|
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
|
-
|
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
|
-
|
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)
|
piccolo/table_reflection.py
CHANGED
@@ -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
|
-
|
132
|
+
exclude_list += self._schema_tables.get(schema_name, [])
|
124
133
|
|
125
134
|
output_schema = await get_output_schema(
|
126
|
-
schema_name=schema_name,
|
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.
|
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.
|
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.
|
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.
|
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=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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.
|
372
|
-
piccolo-1.
|
373
|
-
piccolo-1.
|
374
|
-
piccolo-1.
|
375
|
-
piccolo-1.
|
376
|
-
piccolo-1.
|
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,12 +1,20 @@
|
|
1
|
-
|
2
|
-
|
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
|
33
|
+
create_db_tables_sync(Band, Manager)
|
26
34
|
|
27
35
|
def tearDown(self):
|
28
|
-
Band
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
)
|
tests/columns/test_jsonb.py
CHANGED
@@ -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
|
-
|
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
|
-
|
145
|
-
name="Abbey Road", facilities='{"mixing_desk": true}'
|
146
|
-
).save().run_sync()
|
154
|
+
await self.insert_row()
|
147
155
|
|
148
|
-
row = (
|
149
|
-
RecordingStudio.
|
150
|
-
|
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
|
-
|
174
|
-
name="Abbey Road", facilities='{"mixing_desk": true}'
|
175
|
-
).save().run_sync()
|
176
|
+
await self.insert_row()
|
176
177
|
|
177
|
-
row = (
|
178
|
-
RecordingStudio.
|
179
|
-
|
180
|
-
"mixing_desk"
|
181
|
-
)
|
178
|
+
row = await RecordingStudio.select(
|
179
|
+
RecordingStudio.facilities.arrow("mixing_desk").as_alias(
|
180
|
+
"mixing_desk"
|
182
181
|
)
|
183
|
-
|
184
|
-
|
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
|
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
|
-
|
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
|
-
|
200
|
-
|
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
|
-
|
207
|
-
|
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
|
-
)
|
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"]])
|
File without changes
|
File without changes
|