piccolo 1.14.0__py3-none-any.whl → 1.16.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/user/tables.py +6 -2
- piccolo/query/base.py +1 -3
- piccolo/query/methods/refresh.py +91 -10
- piccolo/query/methods/select.py +3 -0
- piccolo/table.py +8 -2
- piccolo/testing/test_case.py +120 -0
- piccolo/utils/encoding.py +42 -1
- {piccolo-1.14.0.dist-info → piccolo-1.16.0.dist-info}/METADATA +1 -1
- {piccolo-1.14.0.dist-info → piccolo-1.16.0.dist-info}/RECORD +43 -41
- {piccolo-1.14.0.dist-info → piccolo-1.16.0.dist-info}/WHEEL +1 -1
- tests/apps/user/test_tables.py +12 -4
- tests/columns/m2m/test_m2m.py +7 -0
- tests/columns/test_array.py +2 -1
- tests/columns/test_bigint.py +2 -1
- tests/columns/test_boolean.py +1 -1
- tests/columns/test_bytea.py +1 -1
- tests/columns/test_choices.py +2 -1
- tests/columns/test_date.py +1 -1
- tests/columns/test_double_precision.py +1 -1
- tests/columns/test_interval.py +1 -1
- tests/columns/test_json.py +1 -1
- tests/columns/test_jsonb.py +2 -1
- tests/columns/test_numeric.py +1 -1
- tests/columns/test_primary_key.py +1 -1
- tests/columns/test_readable.py +1 -1
- tests/columns/test_real.py +1 -1
- tests/columns/test_reference.py +1 -1
- tests/columns/test_reserved_column_names.py +1 -1
- tests/columns/test_smallint.py +2 -2
- tests/columns/test_time.py +2 -1
- tests/columns/test_timestamp.py +1 -1
- tests/columns/test_timestamptz.py +1 -1
- tests/columns/test_uuid.py +1 -1
- tests/columns/test_varchar.py +2 -2
- tests/query/functions/base.py +1 -1
- tests/query/functions/test_datetime.py +2 -1
- tests/query/functions/test_math.py +1 -1
- tests/table/test_refresh.py +218 -7
- tests/testing/test_test_case.py +65 -0
- {piccolo-1.14.0.dist-info → piccolo-1.16.0.dist-info}/LICENSE +0 -0
- {piccolo-1.14.0.dist-info → piccolo-1.16.0.dist-info}/entry_points.txt +0 -0
- {piccolo-1.14.0.dist-info → piccolo-1.16.0.dist-info}/top_level.txt +0 -0
piccolo/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__VERSION__ = "1.
|
1
|
+
__VERSION__ = "1.16.0"
|
piccolo/apps/user/tables.py
CHANGED
@@ -91,10 +91,14 @@ class BaseUser(Table, tablename="piccolo_user"):
|
|
91
91
|
raise ValueError("A password must be provided.")
|
92
92
|
|
93
93
|
if len(password) < cls._min_password_length:
|
94
|
-
raise ValueError(
|
94
|
+
raise ValueError(
|
95
|
+
f"The password is too short. (min {cls._min_password_length})"
|
96
|
+
)
|
95
97
|
|
96
98
|
if len(password) > cls._max_password_length:
|
97
|
-
raise ValueError(
|
99
|
+
raise ValueError(
|
100
|
+
f"The password is too long. (max {cls._max_password_length})"
|
101
|
+
)
|
98
102
|
|
99
103
|
if password.startswith("pbkdf2_sha256"):
|
100
104
|
logger.warning(
|
piccolo/query/base.py
CHANGED
@@ -79,9 +79,7 @@ class Query(t.Generic[TableInstance, QueryResponseType]):
|
|
79
79
|
if column._alias is not None:
|
80
80
|
json_column_names.append(column._alias)
|
81
81
|
elif len(column._meta.call_chain) > 0:
|
82
|
-
json_column_names.append(
|
83
|
-
column._meta.get_default_alias().replace("$", ".")
|
84
|
-
)
|
82
|
+
json_column_names.append(column._meta.get_default_alias())
|
85
83
|
else:
|
86
84
|
json_column_names.append(column._meta.name)
|
87
85
|
|
piccolo/query/methods/refresh.py
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import typing as t
|
4
|
-
from dataclasses import dataclass
|
5
4
|
|
5
|
+
from piccolo.utils.encoding import JSONDict
|
6
6
|
from piccolo.utils.sync import run_sync
|
7
7
|
|
8
8
|
if t.TYPE_CHECKING: # pragma: no cover
|
@@ -10,7 +10,6 @@ if t.TYPE_CHECKING: # pragma: no cover
|
|
10
10
|
from piccolo.table import Table
|
11
11
|
|
12
12
|
|
13
|
-
@dataclass
|
14
13
|
class Refresh:
|
15
14
|
"""
|
16
15
|
Used to refresh :class:`Table <piccolo.table.Table>` instances with the
|
@@ -22,11 +21,33 @@ class Refresh:
|
|
22
21
|
:param columns:
|
23
22
|
Which columns to refresh - it not specified, then all columns are
|
24
23
|
refreshed.
|
24
|
+
:param load_json:
|
25
|
+
Whether to load ``JSON`` / ``JSONB`` columns as objects, instead of
|
26
|
+
just a string.
|
25
27
|
|
26
28
|
"""
|
27
29
|
|
28
|
-
|
29
|
-
|
30
|
+
def __init__(
|
31
|
+
self,
|
32
|
+
instance: Table,
|
33
|
+
columns: t.Optional[t.Sequence[Column]] = None,
|
34
|
+
load_json: bool = False,
|
35
|
+
):
|
36
|
+
self.instance = instance
|
37
|
+
|
38
|
+
if columns:
|
39
|
+
for column in columns:
|
40
|
+
if len(column._meta.call_chain) > 0:
|
41
|
+
raise ValueError(
|
42
|
+
"We can't currently selectively refresh certain "
|
43
|
+
"columns on child objects (e.g. Concert.band_1.name). "
|
44
|
+
"Please just specify top level columns (e.g. "
|
45
|
+
"Concert.band_1), and the entire child object will be "
|
46
|
+
"refreshed."
|
47
|
+
)
|
48
|
+
|
49
|
+
self.columns = columns
|
50
|
+
self.load_json = load_json
|
30
51
|
|
31
52
|
@property
|
32
53
|
def _columns(self) -> t.Sequence[Column]:
|
@@ -40,6 +61,63 @@ class Refresh:
|
|
40
61
|
i for i in self.instance._meta.columns if not i._meta.primary_key
|
41
62
|
]
|
42
63
|
|
64
|
+
def _get_columns(self, instance: Table, columns: t.Sequence[Column]):
|
65
|
+
"""
|
66
|
+
If `prefetch` was used on the object, for example::
|
67
|
+
|
68
|
+
>>> await Band.objects(Band.manager)
|
69
|
+
|
70
|
+
We should also update the prefetched object.
|
71
|
+
|
72
|
+
It works multiple level deep. If we refresh this::
|
73
|
+
|
74
|
+
>>> await Album.objects(Album.band.manager).first()
|
75
|
+
|
76
|
+
It will update the nested `band` object, and also the `manager`
|
77
|
+
object.
|
78
|
+
|
79
|
+
"""
|
80
|
+
from piccolo.columns.column_types import ForeignKey
|
81
|
+
from piccolo.table import Table
|
82
|
+
|
83
|
+
select_columns = []
|
84
|
+
|
85
|
+
for column in columns:
|
86
|
+
if isinstance(column, ForeignKey) and isinstance(
|
87
|
+
(child_instance := getattr(instance, column._meta.name)),
|
88
|
+
Table,
|
89
|
+
):
|
90
|
+
select_columns.extend(
|
91
|
+
self._get_columns(
|
92
|
+
child_instance,
|
93
|
+
# Fetch all columns (even the primary key, just in
|
94
|
+
# case the foreign key now references a different row).
|
95
|
+
column.all_columns(),
|
96
|
+
)
|
97
|
+
)
|
98
|
+
else:
|
99
|
+
select_columns.append(column)
|
100
|
+
|
101
|
+
return select_columns
|
102
|
+
|
103
|
+
def _update_instance(self, instance: Table, data_dict: t.Dict):
|
104
|
+
"""
|
105
|
+
Update the table instance. It is called recursively, if the instance
|
106
|
+
has child instances.
|
107
|
+
"""
|
108
|
+
for key, value in data_dict.items():
|
109
|
+
if isinstance(value, dict) and not isinstance(value, JSONDict):
|
110
|
+
# If the value is a dict, then it's a child instance.
|
111
|
+
if all(i is None for i in value.values()):
|
112
|
+
# If all values in the nested object are None, then we can
|
113
|
+
# safely assume that the object itself is null, as the
|
114
|
+
# primary key value must be null.
|
115
|
+
setattr(instance, key, None)
|
116
|
+
else:
|
117
|
+
self._update_instance(getattr(instance, key), value)
|
118
|
+
else:
|
119
|
+
setattr(instance, key, value)
|
120
|
+
|
43
121
|
async def run(
|
44
122
|
self, in_pool: bool = True, node: t.Optional[str] = None
|
45
123
|
) -> Table:
|
@@ -54,7 +132,6 @@ class Refresh:
|
|
54
132
|
Modifies the instance in place, but also returns it as a convenience.
|
55
133
|
|
56
134
|
"""
|
57
|
-
|
58
135
|
instance = self.instance
|
59
136
|
|
60
137
|
if not instance._exists_in_db:
|
@@ -71,20 +148,24 @@ class Refresh:
|
|
71
148
|
if not columns:
|
72
149
|
raise ValueError("No columns to fetch.")
|
73
150
|
|
74
|
-
|
75
|
-
|
151
|
+
select_columns = self._get_columns(
|
152
|
+
instance=self.instance, columns=columns
|
153
|
+
)
|
154
|
+
|
155
|
+
data_dict = (
|
156
|
+
await instance.__class__.select(*select_columns)
|
76
157
|
.where(pk_column == primary_key_value)
|
158
|
+
.output(nested=True, load_json=self.load_json)
|
77
159
|
.first()
|
78
160
|
.run(node=node, in_pool=in_pool)
|
79
161
|
)
|
80
162
|
|
81
|
-
if
|
163
|
+
if data_dict is None:
|
82
164
|
raise ValueError(
|
83
165
|
"The object doesn't exist in the database any more."
|
84
166
|
)
|
85
167
|
|
86
|
-
|
87
|
-
setattr(instance, key, value)
|
168
|
+
self._update_instance(instance=instance, data_dict=data_dict)
|
88
169
|
|
89
170
|
return instance
|
90
171
|
|
piccolo/query/methods/select.py
CHANGED
@@ -406,6 +406,9 @@ class Select(Query[TableInstance, t.List[t.Dict[str, t.Any]]]):
|
|
406
406
|
def output(self: Self, *, load_json: bool, as_list: bool) -> SelectJSON: # type: ignore # noqa: E501
|
407
407
|
...
|
408
408
|
|
409
|
+
@t.overload
|
410
|
+
def output(self: Self, *, load_json: bool, nested: bool) -> Self: ...
|
411
|
+
|
409
412
|
@t.overload
|
410
413
|
def output(self: Self, *, nested: bool) -> Self: ...
|
411
414
|
|
piccolo/table.py
CHANGED
@@ -541,7 +541,9 @@ class Table(metaclass=TableMetaclass):
|
|
541
541
|
)
|
542
542
|
|
543
543
|
def refresh(
|
544
|
-
self,
|
544
|
+
self,
|
545
|
+
columns: t.Optional[t.Sequence[Column]] = None,
|
546
|
+
load_json: bool = False,
|
545
547
|
) -> Refresh:
|
546
548
|
"""
|
547
549
|
Used to fetch the latest data for this instance from the database.
|
@@ -551,6 +553,10 @@ class Table(metaclass=TableMetaclass):
|
|
551
553
|
If you only want to refresh certain columns, specify them here.
|
552
554
|
Otherwise all columns are refreshed.
|
553
555
|
|
556
|
+
:param load_json:
|
557
|
+
Whether to load ``JSON`` / ``JSONB`` columns as objects, instead of
|
558
|
+
just a string.
|
559
|
+
|
554
560
|
Example usage::
|
555
561
|
|
556
562
|
# Get an instance from the database.
|
@@ -564,7 +570,7 @@ class Table(metaclass=TableMetaclass):
|
|
564
570
|
instance.refresh().run_sync()
|
565
571
|
|
566
572
|
"""
|
567
|
-
return Refresh(instance=self, columns=columns)
|
573
|
+
return Refresh(instance=self, columns=columns, load_json=load_json)
|
568
574
|
|
569
575
|
@t.overload
|
570
576
|
def get_related(
|
@@ -0,0 +1,120 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import typing as t
|
4
|
+
from unittest import IsolatedAsyncioTestCase, TestCase
|
5
|
+
|
6
|
+
from piccolo.engine import Engine, engine_finder
|
7
|
+
from piccolo.table import (
|
8
|
+
Table,
|
9
|
+
create_db_tables,
|
10
|
+
create_db_tables_sync,
|
11
|
+
drop_db_tables,
|
12
|
+
drop_db_tables_sync,
|
13
|
+
)
|
14
|
+
|
15
|
+
|
16
|
+
class TableTest(TestCase):
|
17
|
+
"""
|
18
|
+
Identical to :class:`AsyncTableTest <piccolo.testing.test_case.AsyncTableTest>`,
|
19
|
+
except it only work for sync tests. Only use this if you can't make your
|
20
|
+
tests async (perhaps you're on Python 3.7 where ``IsolatedAsyncioTestCase``
|
21
|
+
isn't available).
|
22
|
+
|
23
|
+
For example::
|
24
|
+
|
25
|
+
class TestBand(TableTest):
|
26
|
+
tables = [Band]
|
27
|
+
|
28
|
+
def test_band(self):
|
29
|
+
...
|
30
|
+
|
31
|
+
""" # noqa: E501
|
32
|
+
|
33
|
+
tables: t.List[t.Type[Table]]
|
34
|
+
|
35
|
+
def setUp(self) -> None:
|
36
|
+
create_db_tables_sync(*self.tables)
|
37
|
+
|
38
|
+
def tearDown(self) -> None:
|
39
|
+
drop_db_tables_sync(*self.tables)
|
40
|
+
|
41
|
+
|
42
|
+
class AsyncTableTest(IsolatedAsyncioTestCase):
|
43
|
+
"""
|
44
|
+
Used for tests where we need to create Piccolo tables - they will
|
45
|
+
automatically be created and dropped.
|
46
|
+
|
47
|
+
For example::
|
48
|
+
|
49
|
+
class TestBand(AsyncTableTest):
|
50
|
+
tables = [Band]
|
51
|
+
|
52
|
+
async def test_band(self):
|
53
|
+
...
|
54
|
+
|
55
|
+
"""
|
56
|
+
|
57
|
+
tables: t.List[t.Type[Table]]
|
58
|
+
|
59
|
+
async def asyncSetUp(self) -> None:
|
60
|
+
await create_db_tables(*self.tables)
|
61
|
+
|
62
|
+
async def asyncTearDown(self) -> None:
|
63
|
+
await drop_db_tables(*self.tables)
|
64
|
+
|
65
|
+
|
66
|
+
class AsyncTransactionTest(IsolatedAsyncioTestCase):
|
67
|
+
"""
|
68
|
+
Wraps each test in a transaction, which is automatically rolled back when
|
69
|
+
the test finishes.
|
70
|
+
|
71
|
+
.. warning::
|
72
|
+
Python 3.11 and above only.
|
73
|
+
|
74
|
+
If your test suite just contains ``AsyncTransactionTest`` tests, then you
|
75
|
+
can setup your database tables once before your test suite runs. Any
|
76
|
+
changes made to your tables by the tests will be rolled back automatically.
|
77
|
+
|
78
|
+
Here's an example::
|
79
|
+
|
80
|
+
from piccolo.testing.test_case import AsyncTransactionTest
|
81
|
+
|
82
|
+
|
83
|
+
class TestBandEndpoint(AsyncTransactionTest):
|
84
|
+
|
85
|
+
async def test_band_response(self):
|
86
|
+
\"\"\"
|
87
|
+
Make sure the endpoint returns a 200.
|
88
|
+
\"\"\"
|
89
|
+
band = Band({Band.name: "Pythonistas"})
|
90
|
+
await band.save()
|
91
|
+
|
92
|
+
# Using an API testing client, like httpx:
|
93
|
+
response = await client.get(f"/bands/{band.id}/")
|
94
|
+
self.assertEqual(response.status_code, 200)
|
95
|
+
|
96
|
+
We add a ``Band`` to the database, but any subsequent tests won't see it,
|
97
|
+
as the changes are rolled back automatically.
|
98
|
+
|
99
|
+
"""
|
100
|
+
|
101
|
+
# We use `engine_finder` to find the current `Engine`, but you can
|
102
|
+
# explicity set it here if you prefer:
|
103
|
+
#
|
104
|
+
# class MyTest(AsyncTransactionTest):
|
105
|
+
# db = DB
|
106
|
+
#
|
107
|
+
# ...
|
108
|
+
#
|
109
|
+
db: t.Optional[Engine] = None
|
110
|
+
|
111
|
+
async def asyncSetUp(self) -> None:
|
112
|
+
db = self.db or engine_finder()
|
113
|
+
assert db is not None
|
114
|
+
self.transaction = db.transaction()
|
115
|
+
# This is only available in Python 3.11 and above:
|
116
|
+
await self.enterAsyncContext(cm=self.transaction) # type: ignore
|
117
|
+
|
118
|
+
async def asyncTearDown(self):
|
119
|
+
await super().asyncTearDown()
|
120
|
+
await self.transaction.rollback()
|
piccolo/utils/encoding.py
CHANGED
@@ -29,5 +29,46 @@ def dump_json(data: t.Any, pretty: bool = False) -> str:
|
|
29
29
|
return json.dumps(data, **params) # type: ignore
|
30
30
|
|
31
31
|
|
32
|
+
class JSONDict(dict):
|
33
|
+
"""
|
34
|
+
Once we have parsed a JSON string into a dictionary, we can't distinguish
|
35
|
+
it from other dictionaries.
|
36
|
+
|
37
|
+
Sometimes we might want to - for example::
|
38
|
+
|
39
|
+
>>> await Album.select(
|
40
|
+
... Album.all_columns(),
|
41
|
+
... Album.recording_studio.all_columns()
|
42
|
+
... ).output(
|
43
|
+
... nested=True,
|
44
|
+
... load_json=True
|
45
|
+
... )
|
46
|
+
|
47
|
+
[{
|
48
|
+
'id': 1,
|
49
|
+
'band': 1,
|
50
|
+
'name': 'Awesome album 1',
|
51
|
+
'recorded_at': {
|
52
|
+
'id': 1,
|
53
|
+
'facilities': {'restaurant': True, 'mixing_desk': True},
|
54
|
+
'name': 'Abbey Road'
|
55
|
+
},
|
56
|
+
'release_date': datetime.date(2021, 1, 1)
|
57
|
+
}]
|
58
|
+
|
59
|
+
Facilities could be mistaken for a table.
|
60
|
+
|
61
|
+
"""
|
62
|
+
|
63
|
+
...
|
64
|
+
|
65
|
+
|
32
66
|
def load_json(data: str) -> t.Any:
|
33
|
-
|
67
|
+
response = (
|
68
|
+
orjson.loads(data) if ORJSON else json.loads(data) # type: ignore
|
69
|
+
)
|
70
|
+
|
71
|
+
if isinstance(response, dict):
|
72
|
+
return JSONDict(**response)
|
73
|
+
|
74
|
+
return response
|
@@ -1,10 +1,10 @@
|
|
1
|
-
piccolo/__init__.py,sha256=
|
1
|
+
piccolo/__init__.py,sha256=ysxGt_oXPqT-SJ1UbC7NdyUSaoEEi94z0BdzyvNNY2Y,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
5
|
piccolo/querystring.py,sha256=yZdURtiVSlxkkEVJoFKAmL2OfNxrUr8xfsfuBBB7IuY,9662
|
6
6
|
piccolo/schema.py,sha256=qNNy4tG_HqnXR9t3hHMgYXtGxHabwQAhUpc6RKLJ_gE,7960
|
7
|
-
piccolo/table.py,sha256=
|
7
|
+
piccolo/table.py,sha256=nS3zuhGNPZ4H9s_E3ieFbrE_u1Tr6TenBVw_nrrtmRQ,49766
|
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
|
@@ -104,7 +104,7 @@ piccolo/apps/tester/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
|
|
104
104
|
piccolo/apps/tester/commands/run.py,sha256=phFxim2ogARAviW-YT11y9F-L5SJxSioAIepUzQeAWU,2431
|
105
105
|
piccolo/apps/user/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
106
106
|
piccolo/apps/user/piccolo_app.py,sha256=yfw1J9FEnBWdgGdUdNMnqp06Wzm5_s9TO2r5KLchrLM,842
|
107
|
-
piccolo/apps/user/tables.py,sha256=
|
107
|
+
piccolo/apps/user/tables.py,sha256=KgPqONdl1SDV-3cGq5aS-za_v_5_TGHQqNFYXF0M3Jw,9082
|
108
108
|
piccolo/apps/user/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
109
109
|
piccolo/apps/user/commands/change_password.py,sha256=F7mlhtTUY7uENSK8vds5oibIDJTz7QBQdrl7EB-lF9Q,649
|
110
110
|
piccolo/apps/user/commands/change_permissions.py,sha256=LScsKJUMJqIi54cZ1SgS9Wb356KB0t7smu94FDXLfVk,1463
|
@@ -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=iI9Fv3oOw7T4ZWZvRKRwdtClvQtSaAepslH24vwxZVA,14616
|
150
150
|
piccolo/query/mixins.py,sha256=EFEFb9It4y1mR6_JXLn139h5M9KgeP750STYy5M4MLs,21951
|
151
151
|
piccolo/query/proxy.py,sha256=Yq4jNc7IWJvdeO3u7_7iPyRy2WhVj8KsIUcIYHBIi9Q,1839
|
152
152
|
piccolo/query/functions/__init__.py,sha256=pZkzOIh7Sg9HPNOeegOwAS46Oxt31ATlSVmwn-lxCbc,605
|
@@ -168,16 +168,17 @@ piccolo/query/methods/indexes.py,sha256=J-QUqaBJwpgahskUH0Cu0Mq7zEKcfVAtDsUVIVX-
|
|
168
168
|
piccolo/query/methods/insert.py,sha256=ssLJ_wn08KnOwwr7t-VILyn1P4hrvM63CfPIcAJWT5k,4701
|
169
169
|
piccolo/query/methods/objects.py,sha256=iahDUziUtlx7pJ2uBAhdm3hCTmg2AS9C8cal1my5KR0,11705
|
170
170
|
piccolo/query/methods/raw.py,sha256=VhYpCB52mZk4zqFTsqK5CHKTDGskUjISXTBV7UjohmA,600
|
171
|
-
piccolo/query/methods/refresh.py,sha256=
|
172
|
-
piccolo/query/methods/select.py,sha256=
|
171
|
+
piccolo/query/methods/refresh.py,sha256=wg1zghKfwz-VmqK4uWa4GNMiDtK-skTqow591Hb3ONM,5854
|
172
|
+
piccolo/query/methods/select.py,sha256=UH-y2g3Ub7bEowfLObrrhw0W-HepTXWCmuMPhk13roE,21406
|
173
173
|
piccolo/query/methods/table_exists.py,sha256=0yb3n6Jd2ovSBWlZ-gl00K4E7Jnbj7J8qAAX5d7hvNk,1259
|
174
174
|
piccolo/query/methods/update.py,sha256=LfWqIXEl1aecc0rkVssTFmwyD6wXGhlKcTrUVhtlEsw,3705
|
175
175
|
piccolo/testing/__init__.py,sha256=pRFSqRInfx95AakOq54atmvqoB-ue073q2aR8u8zR40,83
|
176
176
|
piccolo/testing/model_builder.py,sha256=lVEiEe71xrH8SSjzFc2l0s-VaCXHeg9Bo5oAYOEbLrI,6545
|
177
177
|
piccolo/testing/random_builder.py,sha256=0LkGpanQ7P1R82gLIMQyK9cm1LdZkPvxbShTEf3jeH4,2128
|
178
|
+
piccolo/testing/test_case.py,sha256=JHQCIAeuO6H2ZbcFHQUhlvUWVFjzKoakDlqVYJvYhtg,3281
|
178
179
|
piccolo/utils/__init__.py,sha256=SDFFraauI9Op8dCRkreQv1dwUcab8Mi1eC-n0EwlTy8,36
|
179
180
|
piccolo/utils/dictionary.py,sha256=8vRPxgaXadDVhqihP1UxL7nUBgM6Gpe_Eu3xJq7zzGM,1886
|
180
|
-
piccolo/utils/encoding.py,sha256=
|
181
|
+
piccolo/utils/encoding.py,sha256=CtSODJOkT3TVHfGlTDXozDsClBCJbGGqluc6_UlJ-7c,1761
|
181
182
|
piccolo/utils/lazy_loader.py,sha256=T8GChEqtKWcBegn-6g_BQ7hOg2Xu1bedFh7Z8E7xcOY,1912
|
182
183
|
piccolo/utils/list.py,sha256=4hPGiksJWxL226W7gyYBcqVGgMTgVa2jP8zvalc3zw8,1541
|
183
184
|
piccolo/utils/naming.py,sha256=d7_mMscguK799RMhxFDifRgn8Ply5wiy2k1KkP22WUs,276
|
@@ -237,43 +238,43 @@ tests/apps/sql_shell/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5N
|
|
237
238
|
tests/apps/sql_shell/commands/test_run.py,sha256=6p0nqCoG_qNLrKeBuHspmer_SrMwEF-vfp9LbPj2W2E,425
|
238
239
|
tests/apps/tester/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
239
240
|
tests/apps/user/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
240
|
-
tests/apps/user/test_tables.py,sha256=
|
241
|
+
tests/apps/user/test_tables.py,sha256=5ImGAWpIKxlqweGeJRMiVOHaBJxRwSzsp3GZ7x6lzCo,9807
|
241
242
|
tests/apps/user/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
242
243
|
tests/apps/user/commands/test_change_password.py,sha256=IEIlnny68jt4UFOaUOwHk9C7WHED51wYoSNX-YIi7rU,1051
|
243
244
|
tests/apps/user/commands/test_change_permissions.py,sha256=uVKEiT1EKot3VA2TDETdQ1hsWL-83rLQrJl4jIxPgqo,2108
|
244
245
|
tests/apps/user/commands/test_create.py,sha256=iJ3Tti62rHwvdcTwNXrc5JPam6vR1qxKRdMN456vm3o,2250
|
245
246
|
tests/apps/user/commands/test_list.py,sha256=ipPfGdW6fH7q-Jc7JcYUvlioGmH9GQU0WImZGC2m-XQ,2840
|
246
247
|
tests/columns/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
247
|
-
tests/columns/test_array.py,sha256=
|
248
|
+
tests/columns/test_array.py,sha256=niIkAPRQp49bkpbctaMgpcDai4TMAbM9qkrTGhPFsQY,11627
|
248
249
|
tests/columns/test_base.py,sha256=CTqCNcrqAJTjLXe3MCZgTczrmB3jcVRcOpU4FilpLoQ,3918
|
249
|
-
tests/columns/test_bigint.py,sha256=
|
250
|
-
tests/columns/test_boolean.py,sha256=
|
251
|
-
tests/columns/test_bytea.py,sha256
|
252
|
-
tests/columns/test_choices.py,sha256=
|
250
|
+
tests/columns/test_bigint.py,sha256=KzfiItv0cYQZR35V_TL485AitDnOZQfTzC3C2YCLSn4,1058
|
251
|
+
tests/columns/test_boolean.py,sha256=qm0tg-gKcoYHNV1WVFxgbG4CsngxAJlg-hsGeTNklN8,1626
|
252
|
+
tests/columns/test_bytea.py,sha256=doN8S1eFVU4ntSXIg4IgMSZcbvqW1WJ-AEm3OjKLGkI,1382
|
253
|
+
tests/columns/test_choices.py,sha256=q8TLe7nvGERXyGO_XEryEBR-DuWwFY1jPpscsrXjdXo,4066
|
253
254
|
tests/columns/test_combination.py,sha256=BuBwR7k5X1EkOWraZpjqU6gvtb6ow_k-7N1KQBiW2RA,1681
|
254
|
-
tests/columns/test_date.py,sha256=
|
255
|
+
tests/columns/test_date.py,sha256=QLC6kJMQwM-1mbUP4ksJVM7P8WwjzGZyynH3rHHdSew,1030
|
255
256
|
tests/columns/test_db_column_name.py,sha256=v0QFOQp_atqzMB1n40simVwHeBDi5nyN1N2bSPX5k6w,7670
|
256
257
|
tests/columns/test_defaults.py,sha256=rwlU1fXt3cCl7C51eLlZXqgWkE-K5W0pHvTrwkAKyCo,2896
|
257
|
-
tests/columns/test_double_precision.py,sha256=
|
258
|
+
tests/columns/test_double_precision.py,sha256=7rhcSfDkb2fBh_zEG4UGwD_GW1sy6U9-8NooHuCS09Q,544
|
258
259
|
tests/columns/test_get_sql_value.py,sha256=mKgsInN374jzV99y9mg_ZiG-AvnJgz36SZi89xL7RZM,1768
|
259
|
-
tests/columns/test_interval.py,sha256=
|
260
|
-
tests/columns/test_json.py,sha256=
|
261
|
-
tests/columns/test_jsonb.py,sha256=
|
262
|
-
tests/columns/test_numeric.py,sha256=
|
263
|
-
tests/columns/test_primary_key.py,sha256=
|
264
|
-
tests/columns/test_readable.py,sha256=
|
265
|
-
tests/columns/test_real.py,sha256=
|
266
|
-
tests/columns/test_reference.py,sha256
|
267
|
-
tests/columns/test_reserved_column_names.py,sha256=
|
268
|
-
tests/columns/test_smallint.py,sha256=
|
269
|
-
tests/columns/test_time.py,sha256=
|
270
|
-
tests/columns/test_timestamp.py,sha256=
|
271
|
-
tests/columns/test_timestamptz.py,sha256=
|
272
|
-
tests/columns/test_uuid.py,sha256=
|
273
|
-
tests/columns/test_varchar.py,sha256=
|
260
|
+
tests/columns/test_interval.py,sha256=2M18pfoGxLLosEvwTmuC4zQkM6jWwU0Nv2fqViW3xOs,2780
|
261
|
+
tests/columns/test_json.py,sha256=_cziJvw2uT8e_4u9lKhmU56lgQeE7bEqCXYf6AzfChA,3482
|
262
|
+
tests/columns/test_jsonb.py,sha256=eJQoxpyuQ4yrX-GMJhRkZntyd4tX6M6RhAxYn2-ISII,6727
|
263
|
+
tests/columns/test_numeric.py,sha256=AkTvdvjSsfRsMM79tx4AskUpsTizGBLMY_tC2OII9U4,751
|
264
|
+
tests/columns/test_primary_key.py,sha256=foNG9eTQUJ5yiEVQ7faIEMycW_VuZ7vgzknYXaZ-QXM,4886
|
265
|
+
tests/columns/test_readable.py,sha256=xKVfJuxZcfyncNVKXNryl2WFREX655jwD9DxiLArQiU,758
|
266
|
+
tests/columns/test_real.py,sha256=O_lwiNU4RIHSMY33QuWT0WTvNzV-2ATYYtdbI46E42c,511
|
267
|
+
tests/columns/test_reference.py,sha256=-pDKZl0kjRHz17U-_bldHD4rpg7Ga27lIzXMKOazbWA,2344
|
268
|
+
tests/columns/test_reserved_column_names.py,sha256=S39hq9Ex8QXlbnfjlPLtzwaxICTkXe43Nr2um1S37jI,1419
|
269
|
+
tests/columns/test_smallint.py,sha256=5tm8BpyUmun7uIROaKBgSb27dGu-waqtQAAvpvS66ek,1024
|
270
|
+
tests/columns/test_time.py,sha256=eQ4S-FMhULvkp6OwQaMbheBgQUVFCJ9JGMsKFpsM_vg,1597
|
271
|
+
tests/columns/test_timestamp.py,sha256=vD7F0J_8rZkX_PngfjaMcs5Ugq3lM8GUJXzU7cQLNZM,1686
|
272
|
+
tests/columns/test_timestamptz.py,sha256=P7zblPC6Fjjdk6iOhVUGIKnFFzbbUPVNSY98qbuqE7U,2630
|
273
|
+
tests/columns/test_uuid.py,sha256=taFYNvRZjQztMPbTQHYtwQutvcLnKPt6_aUxsf2o04Q,372
|
274
|
+
tests/columns/test_varchar.py,sha256=fbwBdimHoGaylfrqkFIgQ5m2q80umSoUNHIwofM6j_c,721
|
274
275
|
tests/columns/m2m/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
275
276
|
tests/columns/m2m/base.py,sha256=uH92ECQuY5AjpQXPySwrlruZPZzB4LH2V2FUSXmHRLQ,14563
|
276
|
-
tests/columns/m2m/test_m2m.py,sha256=
|
277
|
+
tests/columns/m2m/test_m2m.py,sha256=0ObmIHUJF6CZoNBznc5xXVr5_BbGBqOmWwtpg8IcPt4,13055
|
277
278
|
tests/columns/m2m/test_m2m_schema.py,sha256=oxu7eAjFFpDjnq9Eq-5OTNmlnsEIMFWx18OItfpVs-s,339
|
278
279
|
tests/conf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
279
280
|
tests/conf/example.py,sha256=K8sTttLpEac8rQlOLDY500IGkHj3P3NoyFbCMnT1EqY,347
|
@@ -303,10 +304,10 @@ tests/query/test_gather.py,sha256=okWANrBoh0Ut1RomWoffiWNpFqiITF6qti-Aa3uYtRk,73
|
|
303
304
|
tests/query/test_querystring.py,sha256=QrqyjwUlFlf5LrsJ7DgjCruq811I0UvrDFPud6rfZNI,5019
|
304
305
|
tests/query/test_slots.py,sha256=I9ZjAYqAJNSFAWg9UyAqy7bm-Z52KiyQ2C_yHk2qqqI,1010
|
305
306
|
tests/query/functions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
306
|
-
tests/query/functions/base.py,sha256=
|
307
|
-
tests/query/functions/test_datetime.py,sha256=
|
307
|
+
tests/query/functions/base.py,sha256=XbLpoSp05PPbGl7aSPW29GGrDKL2B-KsCxji7GhhPNI,515
|
308
|
+
tests/query/functions/test_datetime.py,sha256=X_5LR9XouPiQL-GQWen-NtokunCBHDuZ5gk9elNJNd4,3000
|
308
309
|
tests/query/functions/test_functions.py,sha256=510fqRrOrAZ9NyFoZtlF6lIdiiLriWhZ7vvveWZ8rsc,1984
|
309
|
-
tests/query/functions/test_math.py,sha256=
|
310
|
+
tests/query/functions/test_math.py,sha256=wHaGQdEKISI8WeG9zGVNv62IrWSp4mMH3dPxVxoHy3s,1286
|
310
311
|
tests/query/functions/test_string.py,sha256=RMojkBUzw1Ikrb3nTa7VjJ4FsKfrjpuHUyxQDA-F5Cs,1800
|
311
312
|
tests/query/functions/test_type_conversion.py,sha256=WeYR9UfJnbidle07-akQ1g9hFCd93qT8xUhDF3c58n4,3235
|
312
313
|
tests/query/mixins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -336,7 +337,7 @@ tests/table/test_objects.py,sha256=bir86ks-Ngy8x9Eu9bekOrh6twBYdEkIgTdbBWY6x9s,8
|
|
336
337
|
tests/table/test_output.py,sha256=ZnpPbgVp79JcB6E_ooWQxOpOlhkwNUlMxC-1LSIEc2Y,4304
|
337
338
|
tests/table/test_raw.py,sha256=9PTvYngQi41nYd5lKzkJdTqsEcwrdOXcvZjq-W26CwQ,1683
|
338
339
|
tests/table/test_ref.py,sha256=eYNRnYHzNMXuMbV3B1ca5EidpIg4500q6hr1ccuVaso,269
|
339
|
-
tests/table/test_refresh.py,sha256
|
340
|
+
tests/table/test_refresh.py,sha256=-BaLS6fZiR2RtQaFa7D9WGBjrbrss1-tt5xz1NE_m8E,9250
|
340
341
|
tests/table/test_repr.py,sha256=uahz3_GffGQrf2mDE-4-Pu4AmSLBAyso6-9rbohCl58,446
|
341
342
|
tests/table/test_select.py,sha256=jgeiahIlNFVijxYb3a54g1sJWVfH3llaYrsTBmdicrs,40390
|
342
343
|
tests/table/test_str.py,sha256=eztWNULcjARR1fr9X5n4tojhDNgDfatVyNHwuYrzHAo,1731
|
@@ -353,6 +354,7 @@ tests/table/instance/test_to_dict.py,sha256=gkiYkmcI5qcy5E-ERWWmO-Q8uyVSFfcpJ8d5
|
|
353
354
|
tests/testing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
354
355
|
tests/testing/test_model_builder.py,sha256=bgs-cOYXtnrfgWydU2sZvs2N_Lt2T7La7AAMfIsOev4,6206
|
355
356
|
tests/testing/test_random_builder.py,sha256=Upz9P1bhICVo0udI6Li-5eEdrXKbv8rMMLe0uK6pqB0,1694
|
357
|
+
tests/testing/test_test_case.py,sha256=qyDWYT44EZNyuWhaZXgSOpX48RaRw5u4FgNi87FYt2k,1691
|
356
358
|
tests/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
357
359
|
tests/utils/test_dictionary.py,sha256=GdWujlYQy6t09p2aQHPibkkPNbBYwkFwomKrVnztJTo,1480
|
358
360
|
tests/utils/test_encoding.py,sha256=x1CHLm0rRheZaXz7at3SgkgcrZOmtMB-NtvJC77wQKE,326
|
@@ -365,9 +367,9 @@ tests/utils/test_sql_values.py,sha256=vzxRmy16FfLZPH-sAQexBvsF9MXB8n4smr14qoEOS5
|
|
365
367
|
tests/utils/test_sync.py,sha256=9ytVo56y2vPQePvTeIi9lHIouEhWJbodl1TmzkGFrSo,799
|
366
368
|
tests/utils/test_table_reflection.py,sha256=SIzuat-IpcVj1GCFyOWKShI8YkhdOPPFH7qVrvfyPNE,3794
|
367
369
|
tests/utils/test_warnings.py,sha256=NvSC_cvJ6uZcwAGf1m-hLzETXCqprXELL8zg3TNLVMw,269
|
368
|
-
piccolo-1.
|
369
|
-
piccolo-1.
|
370
|
-
piccolo-1.
|
371
|
-
piccolo-1.
|
372
|
-
piccolo-1.
|
373
|
-
piccolo-1.
|
370
|
+
piccolo-1.16.0.dist-info/LICENSE,sha256=zFIpi-16uIJ420UMIG75NU0JbDBykvrdnXcj5U_EYBI,1059
|
371
|
+
piccolo-1.16.0.dist-info/METADATA,sha256=qsjW9UTKSwkT62sKfdGnTFyllEcn8bE1yKIj54iu4Ek,5178
|
372
|
+
piccolo-1.16.0.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
|
373
|
+
piccolo-1.16.0.dist-info/entry_points.txt,sha256=SJPHET4Fi1bN5F3WqcKkv9SClK3_F1I7m4eQjk6AFh0,46
|
374
|
+
piccolo-1.16.0.dist-info/top_level.txt,sha256=-SR74VGbk43VoPy1HH-mHm97yoGukLK87HE5kdBW6qM,24
|
375
|
+
piccolo-1.16.0.dist-info/RECORD,,
|
tests/apps/user/test_tables.py
CHANGED
@@ -109,7 +109,7 @@ class TestLogin(TestCase):
|
|
109
109
|
BaseUser.update_password_sync(username, malicious_password)
|
110
110
|
self.assertEqual(
|
111
111
|
manager.exception.__str__(),
|
112
|
-
"The password is too long.",
|
112
|
+
f"The password is too long. (max {BaseUser._max_password_length})",
|
113
113
|
)
|
114
114
|
|
115
115
|
# Test short passwords
|
@@ -118,7 +118,10 @@ class TestLogin(TestCase):
|
|
118
118
|
BaseUser.update_password_sync(username, short_password)
|
119
119
|
self.assertEqual(
|
120
120
|
manager.exception.__str__(),
|
121
|
-
|
121
|
+
(
|
122
|
+
"The password is too short. (min "
|
123
|
+
f"{BaseUser._min_password_length})"
|
124
|
+
),
|
122
125
|
)
|
123
126
|
|
124
127
|
# Test no password
|
@@ -205,7 +208,11 @@ class TestCreateUser(TestCase):
|
|
205
208
|
BaseUser.create_user_sync(username="bob", password="abc")
|
206
209
|
|
207
210
|
self.assertEqual(
|
208
|
-
manager.exception.__str__(),
|
211
|
+
manager.exception.__str__(),
|
212
|
+
(
|
213
|
+
"The password is too short. (min "
|
214
|
+
f"{BaseUser._min_password_length})"
|
215
|
+
),
|
209
216
|
)
|
210
217
|
|
211
218
|
def test_long_password_error(self):
|
@@ -216,7 +223,8 @@ class TestCreateUser(TestCase):
|
|
216
223
|
)
|
217
224
|
|
218
225
|
self.assertEqual(
|
219
|
-
manager.exception.__str__(),
|
226
|
+
manager.exception.__str__(),
|
227
|
+
f"The password is too long. (max {BaseUser._max_password_length})",
|
220
228
|
)
|
221
229
|
|
222
230
|
def test_no_username_error(self):
|
tests/columns/m2m/test_m2m.py
CHANGED
@@ -4,6 +4,7 @@ import decimal
|
|
4
4
|
import uuid
|
5
5
|
from unittest import TestCase
|
6
6
|
|
7
|
+
from piccolo.utils.encoding import JSONDict
|
7
8
|
from tests.base import engines_skip
|
8
9
|
|
9
10
|
try:
|
@@ -376,6 +377,9 @@ class TestM2MComplexSchema(TestCase):
|
|
376
377
|
|
377
378
|
if isinstance(column, UUID):
|
378
379
|
self.assertIn(type(returned_value), (uuid.UUID, asyncpgUUID))
|
380
|
+
elif isinstance(column, (JSON, JSONB)):
|
381
|
+
self.assertEqual(type(returned_value), JSONDict)
|
382
|
+
self.assertEqual(original_value, returned_value)
|
379
383
|
else:
|
380
384
|
self.assertEqual(
|
381
385
|
type(original_value),
|
@@ -401,6 +405,9 @@ class TestM2MComplexSchema(TestCase):
|
|
401
405
|
if isinstance(column, UUID):
|
402
406
|
self.assertIn(type(returned_value), (uuid.UUID, asyncpgUUID))
|
403
407
|
self.assertEqual(str(original_value), str(returned_value))
|
408
|
+
elif isinstance(column, (JSON, JSONB)):
|
409
|
+
self.assertEqual(type(returned_value), JSONDict)
|
410
|
+
self.assertEqual(original_value, returned_value)
|
404
411
|
else:
|
405
412
|
self.assertEqual(
|
406
413
|
type(original_value),
|
tests/columns/test_array.py
CHANGED
@@ -14,7 +14,8 @@ from piccolo.columns.column_types import (
|
|
14
14
|
)
|
15
15
|
from piccolo.querystring import QueryString
|
16
16
|
from piccolo.table import Table
|
17
|
-
from
|
17
|
+
from piccolo.testing.test_case import TableTest
|
18
|
+
from tests.base import engines_only, engines_skip, sqlite_only
|
18
19
|
|
19
20
|
|
20
21
|
class MyTable(Table):
|
tests/columns/test_bigint.py
CHANGED
tests/columns/test_boolean.py
CHANGED
tests/columns/test_bytea.py
CHANGED
tests/columns/test_choices.py
CHANGED
@@ -2,7 +2,8 @@ import enum
|
|
2
2
|
|
3
3
|
from piccolo.columns.column_types import Array, Varchar
|
4
4
|
from piccolo.table import Table
|
5
|
-
from
|
5
|
+
from piccolo.testing.test_case import TableTest
|
6
|
+
from tests.base import engines_only
|
6
7
|
from tests.example_apps.music.tables import Shirt
|
7
8
|
|
8
9
|
|
tests/columns/test_date.py
CHANGED
tests/columns/test_interval.py
CHANGED
@@ -3,7 +3,7 @@ import datetime
|
|
3
3
|
from piccolo.columns.column_types import Interval
|
4
4
|
from piccolo.columns.defaults.interval import IntervalCustom
|
5
5
|
from piccolo.table import Table
|
6
|
-
from
|
6
|
+
from piccolo.testing.test_case import TableTest
|
7
7
|
|
8
8
|
|
9
9
|
class MyTable(Table):
|
tests/columns/test_json.py
CHANGED
tests/columns/test_jsonb.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
from piccolo.columns.column_types import JSONB, ForeignKey, Varchar
|
2
2
|
from piccolo.table import Table
|
3
|
-
from
|
3
|
+
from piccolo.testing.test_case import TableTest
|
4
|
+
from tests.base import engines_only, engines_skip
|
4
5
|
|
5
6
|
|
6
7
|
class RecordingStudio(Table):
|
tests/columns/test_numeric.py
CHANGED
tests/columns/test_readable.py
CHANGED
tests/columns/test_real.py
CHANGED
tests/columns/test_reference.py
CHANGED
@@ -8,7 +8,7 @@ from unittest import TestCase
|
|
8
8
|
from piccolo.columns import ForeignKey, Varchar
|
9
9
|
from piccolo.columns.reference import LazyTableReference
|
10
10
|
from piccolo.table import Table
|
11
|
-
from
|
11
|
+
from piccolo.testing.test_case import TableTest
|
12
12
|
|
13
13
|
|
14
14
|
class Band(Table):
|
tests/columns/test_smallint.py
CHANGED
tests/columns/test_time.py
CHANGED
@@ -4,7 +4,8 @@ from functools import partial
|
|
4
4
|
from piccolo.columns.column_types import Time
|
5
5
|
from piccolo.columns.defaults.time import TimeNow
|
6
6
|
from piccolo.table import Table
|
7
|
-
from
|
7
|
+
from piccolo.testing.test_case import TableTest
|
8
|
+
from tests.base import engines_skip
|
8
9
|
|
9
10
|
|
10
11
|
class MyTable(Table):
|
tests/columns/test_timestamp.py
CHANGED
@@ -3,7 +3,7 @@ import datetime
|
|
3
3
|
from piccolo.columns.column_types import Timestamp
|
4
4
|
from piccolo.columns.defaults.timestamp import TimestampNow
|
5
5
|
from piccolo.table import Table
|
6
|
-
from
|
6
|
+
from piccolo.testing.test_case import TableTest
|
7
7
|
|
8
8
|
|
9
9
|
class MyTable(Table):
|
tests/columns/test_uuid.py
CHANGED
tests/columns/test_varchar.py
CHANGED
tests/query/functions/base.py
CHANGED
@@ -12,7 +12,8 @@ from piccolo.query.functions.datetime import (
|
|
12
12
|
Year,
|
13
13
|
)
|
14
14
|
from piccolo.table import Table
|
15
|
-
from
|
15
|
+
from piccolo.testing.test_case import TableTest
|
16
|
+
from tests.base import engines_only, sqlite_only
|
16
17
|
|
17
18
|
|
18
19
|
class Concert(Table):
|
tests/table/test_refresh.py
CHANGED
@@ -1,5 +1,14 @@
|
|
1
|
+
import typing as t
|
2
|
+
|
3
|
+
from piccolo.testing.test_case import TableTest
|
1
4
|
from tests.base import DBTestCase
|
2
|
-
from tests.example_apps.music.tables import
|
5
|
+
from tests.example_apps.music.tables import (
|
6
|
+
Band,
|
7
|
+
Concert,
|
8
|
+
Manager,
|
9
|
+
RecordingStudio,
|
10
|
+
Venue,
|
11
|
+
)
|
3
12
|
|
4
13
|
|
5
14
|
class TestRefresh(DBTestCase):
|
@@ -24,9 +33,55 @@ class TestRefresh(DBTestCase):
|
|
24
33
|
# Refresh `band`, and make sure it has the correct data.
|
25
34
|
band.refresh().run_sync()
|
26
35
|
|
27
|
-
self.
|
28
|
-
self.
|
29
|
-
self.
|
36
|
+
self.assertEqual(band.name, "Pythonistas!!!")
|
37
|
+
self.assertEqual(band.popularity, 8000)
|
38
|
+
self.assertEqual(band.id, initial_data["id"])
|
39
|
+
|
40
|
+
def test_refresh_with_prefetch(self) -> None:
|
41
|
+
"""
|
42
|
+
Make sure ``refresh`` works, when the object used prefetch to get
|
43
|
+
nested objets (the nested objects should be updated too).
|
44
|
+
"""
|
45
|
+
band = (
|
46
|
+
Band.objects(Band.manager)
|
47
|
+
.where(Band.name == "Pythonistas")
|
48
|
+
.first()
|
49
|
+
.run_sync()
|
50
|
+
)
|
51
|
+
assert band is not None
|
52
|
+
|
53
|
+
# Modify the data in the database.
|
54
|
+
Manager.update({Manager.name: "Guido!!!"}).where(
|
55
|
+
Manager.name == "Guido"
|
56
|
+
).run_sync()
|
57
|
+
|
58
|
+
# Refresh `band`, and make sure it has the correct data.
|
59
|
+
band.refresh().run_sync()
|
60
|
+
|
61
|
+
self.assertEqual(band.manager.name, "Guido!!!")
|
62
|
+
|
63
|
+
def test_refresh_with_prefetch_multiple_layers_deep(self) -> None:
|
64
|
+
"""
|
65
|
+
Make sure ``refresh`` works, when the object used prefetch to get
|
66
|
+
nested objets (the nested objects should be updated too).
|
67
|
+
"""
|
68
|
+
band = (
|
69
|
+
Band.objects(Band.manager)
|
70
|
+
.where(Band.name == "Pythonistas")
|
71
|
+
.first()
|
72
|
+
.run_sync()
|
73
|
+
)
|
74
|
+
assert band is not None
|
75
|
+
|
76
|
+
# Modify the data in the database.
|
77
|
+
Manager.update({Manager.name: "Guido!!!"}).where(
|
78
|
+
Manager.name == "Guido"
|
79
|
+
).run_sync()
|
80
|
+
|
81
|
+
# Refresh `band`, and make sure it has the correct data.
|
82
|
+
band.refresh().run_sync()
|
83
|
+
|
84
|
+
self.assertEqual(band.manager.name, "Guido!!!")
|
30
85
|
|
31
86
|
def test_columns(self) -> None:
|
32
87
|
"""
|
@@ -50,9 +105,9 @@ class TestRefresh(DBTestCase):
|
|
50
105
|
)
|
51
106
|
query.run_sync()
|
52
107
|
|
53
|
-
self.
|
54
|
-
self.
|
55
|
-
self.
|
108
|
+
self.assertEqual(band.name, "Pythonistas!!!")
|
109
|
+
self.assertEqual(band.popularity, initial_data["popularity"])
|
110
|
+
self.assertEqual(band.id, initial_data["id"])
|
56
111
|
|
57
112
|
def test_error_when_not_in_db(self) -> None:
|
58
113
|
"""
|
@@ -85,3 +140,159 @@ class TestRefresh(DBTestCase):
|
|
85
140
|
"The instance's primary key value isn't defined.",
|
86
141
|
str(manager.exception),
|
87
142
|
)
|
143
|
+
|
144
|
+
|
145
|
+
class TestRefreshWithPrefetch(TableTest):
|
146
|
+
|
147
|
+
tables = [Manager, Band, Concert, Venue]
|
148
|
+
|
149
|
+
def setUp(self):
|
150
|
+
super().setUp()
|
151
|
+
|
152
|
+
self.manager = Manager({Manager.name: "Guido"})
|
153
|
+
self.manager.save().run_sync()
|
154
|
+
|
155
|
+
self.band = Band(
|
156
|
+
{Band.name: "Pythonistas", Band.manager: self.manager}
|
157
|
+
)
|
158
|
+
self.band.save().run_sync()
|
159
|
+
|
160
|
+
self.concert = Concert({Concert.band_1: self.band})
|
161
|
+
self.concert.save().run_sync()
|
162
|
+
|
163
|
+
def test_single_layer(self) -> None:
|
164
|
+
"""
|
165
|
+
Make sure ``refresh`` works, when the object used prefetch to get
|
166
|
+
nested objects (the nested objects should be updated too).
|
167
|
+
"""
|
168
|
+
band = (
|
169
|
+
Band.objects(Band.manager)
|
170
|
+
.where(Band.name == "Pythonistas")
|
171
|
+
.first()
|
172
|
+
.run_sync()
|
173
|
+
)
|
174
|
+
assert band is not None
|
175
|
+
|
176
|
+
# Modify the data in the database.
|
177
|
+
self.manager.name = "Guido!!!"
|
178
|
+
self.manager.save().run_sync()
|
179
|
+
|
180
|
+
# Refresh `band`, and make sure it has the correct data.
|
181
|
+
band.refresh().run_sync()
|
182
|
+
self.assertEqual(band.manager.name, "Guido!!!")
|
183
|
+
|
184
|
+
def test_multiple_layers(self) -> None:
|
185
|
+
"""
|
186
|
+
Make sure ``refresh`` works when ``prefetch`` was used to fetch objects
|
187
|
+
multiple layers deep.
|
188
|
+
"""
|
189
|
+
concert = (
|
190
|
+
Concert.objects(Concert.band_1._.manager)
|
191
|
+
.where(Concert.band_1._.name == "Pythonistas")
|
192
|
+
.first()
|
193
|
+
.run_sync()
|
194
|
+
)
|
195
|
+
assert concert is not None
|
196
|
+
|
197
|
+
# Modify the data in the database.
|
198
|
+
self.manager.name = "Guido!!!"
|
199
|
+
self.manager.save().run_sync()
|
200
|
+
|
201
|
+
concert.refresh().run_sync()
|
202
|
+
self.assertEqual(concert.band_1.manager.name, "Guido!!!")
|
203
|
+
|
204
|
+
def test_updated_foreign_key(self) -> None:
|
205
|
+
"""
|
206
|
+
If a foreign key now references a different row, make sure this
|
207
|
+
is refreshed correctly.
|
208
|
+
"""
|
209
|
+
band = (
|
210
|
+
Band.objects(Band.manager)
|
211
|
+
.where(Band.name == "Pythonistas")
|
212
|
+
.first()
|
213
|
+
.run_sync()
|
214
|
+
)
|
215
|
+
assert band is not None
|
216
|
+
|
217
|
+
# Assign a different manager to the band
|
218
|
+
new_manager = Manager({Manager.name: "New Manager"})
|
219
|
+
new_manager.save().run_sync()
|
220
|
+
Band.update({Band.manager: new_manager.id}, force=True).run_sync()
|
221
|
+
|
222
|
+
# Refresh `band`, and make sure it references the new manager.
|
223
|
+
band.refresh().run_sync()
|
224
|
+
self.assertEqual(band.manager.id, new_manager.id)
|
225
|
+
self.assertEqual(band.manager.name, "New Manager")
|
226
|
+
|
227
|
+
def test_foreign_key_set_to_null(self):
|
228
|
+
"""
|
229
|
+
Make sure that if the foreign key was set to null, that ``refresh``
|
230
|
+
sets the nested object to ``None``.
|
231
|
+
"""
|
232
|
+
band = (
|
233
|
+
Band.objects(Band.manager)
|
234
|
+
.where(Band.name == "Pythonistas")
|
235
|
+
.first()
|
236
|
+
.run_sync()
|
237
|
+
)
|
238
|
+
assert band is not None
|
239
|
+
|
240
|
+
# Remove the manager from band
|
241
|
+
Band.update({Band.manager: None}, force=True).run_sync()
|
242
|
+
|
243
|
+
# Refresh `band`, and make sure the foreign key value is now `None`,
|
244
|
+
# instead of a nested object.
|
245
|
+
band.refresh().run_sync()
|
246
|
+
self.assertIsNone(band.manager)
|
247
|
+
|
248
|
+
def test_exception(self) -> None:
|
249
|
+
"""
|
250
|
+
We don't currently let the user refresh specific fields from nested
|
251
|
+
objects - an exception should be raised.
|
252
|
+
"""
|
253
|
+
with self.assertRaises(ValueError):
|
254
|
+
self.concert.refresh(columns=[Concert.band_1._.manager]).run_sync()
|
255
|
+
|
256
|
+
# Shouldn't raise an exception:
|
257
|
+
self.concert.refresh(columns=[Concert.band_1]).run_sync()
|
258
|
+
|
259
|
+
|
260
|
+
class TestRefreshWithLoadJSON(TableTest):
|
261
|
+
|
262
|
+
tables = [RecordingStudio]
|
263
|
+
|
264
|
+
def setUp(self):
|
265
|
+
super().setUp()
|
266
|
+
|
267
|
+
self.recording_studio = RecordingStudio(
|
268
|
+
{RecordingStudio.facilities: {"piano": True}}
|
269
|
+
)
|
270
|
+
self.recording_studio.save().run_sync()
|
271
|
+
|
272
|
+
def test_load_json(self):
|
273
|
+
"""
|
274
|
+
Make sure we can refresh an object, and load the JSON as a Python
|
275
|
+
object.
|
276
|
+
"""
|
277
|
+
RecordingStudio.update(
|
278
|
+
{RecordingStudio.facilities: {"electric piano": True}},
|
279
|
+
force=True,
|
280
|
+
).run_sync()
|
281
|
+
|
282
|
+
# Refresh without load_json:
|
283
|
+
self.recording_studio.refresh().run_sync()
|
284
|
+
|
285
|
+
self.assertEqual(
|
286
|
+
# Remove the white space, because some versions of Python add
|
287
|
+
# whitespace around JSON, and some don't.
|
288
|
+
self.recording_studio.facilities.replace(" ", ""),
|
289
|
+
'{"electricpiano":true}',
|
290
|
+
)
|
291
|
+
|
292
|
+
# Refresh with load_json:
|
293
|
+
self.recording_studio.refresh(load_json=True).run_sync()
|
294
|
+
|
295
|
+
self.assertDictEqual(
|
296
|
+
t.cast(dict, self.recording_studio.facilities),
|
297
|
+
{"electric piano": True},
|
298
|
+
)
|
@@ -0,0 +1,65 @@
|
|
1
|
+
import sys
|
2
|
+
|
3
|
+
import pytest
|
4
|
+
|
5
|
+
from piccolo.engine import engine_finder
|
6
|
+
from piccolo.testing.test_case import (
|
7
|
+
AsyncTableTest,
|
8
|
+
AsyncTransactionTest,
|
9
|
+
TableTest,
|
10
|
+
)
|
11
|
+
from tests.example_apps.music.tables import Band, Manager
|
12
|
+
|
13
|
+
|
14
|
+
class TestTableTest(TableTest):
|
15
|
+
"""
|
16
|
+
Make sure the tables are created automatically.
|
17
|
+
"""
|
18
|
+
|
19
|
+
tables = [Band, Manager]
|
20
|
+
|
21
|
+
async def test_tables_created(self):
|
22
|
+
self.assertTrue(Band.table_exists().run_sync())
|
23
|
+
self.assertTrue(Manager.table_exists().run_sync())
|
24
|
+
|
25
|
+
|
26
|
+
class TestAsyncTableTest(AsyncTableTest):
|
27
|
+
"""
|
28
|
+
Make sure the tables are created automatically in async tests.
|
29
|
+
"""
|
30
|
+
|
31
|
+
tables = [Band, Manager]
|
32
|
+
|
33
|
+
async def test_tables_created(self):
|
34
|
+
self.assertTrue(await Band.table_exists())
|
35
|
+
self.assertTrue(await Manager.table_exists())
|
36
|
+
|
37
|
+
|
38
|
+
@pytest.mark.skipif(sys.version_info <= (3, 11), reason="Python 3.11 required")
|
39
|
+
class TestAsyncTransaction(AsyncTransactionTest):
|
40
|
+
"""
|
41
|
+
Make sure that the test exists within a transaction.
|
42
|
+
"""
|
43
|
+
|
44
|
+
async def test_transaction_exists(self):
|
45
|
+
db = engine_finder()
|
46
|
+
assert db is not None
|
47
|
+
self.assertTrue(db.transaction_exists())
|
48
|
+
|
49
|
+
|
50
|
+
@pytest.mark.skipif(sys.version_info <= (3, 11), reason="Python 3.11 required")
|
51
|
+
class TestAsyncTransactionRolledBack(AsyncTransactionTest):
|
52
|
+
"""
|
53
|
+
Make sure that the changes get rolled back automatically.
|
54
|
+
"""
|
55
|
+
|
56
|
+
async def asyncTearDown(self):
|
57
|
+
await super().asyncTearDown()
|
58
|
+
|
59
|
+
assert Manager.table_exists().run_sync() is False
|
60
|
+
|
61
|
+
async def test_insert_data(self):
|
62
|
+
await Manager.create_table()
|
63
|
+
|
64
|
+
manager = Manager({Manager.name: "Guido"})
|
65
|
+
await manager.save()
|
File without changes
|
File without changes
|
File without changes
|