piccolo 1.27.0__py3-none-any.whl → 1.28.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/app/commands/new.py +3 -3
- piccolo/apps/asgi/commands/new.py +1 -2
- piccolo/apps/fixtures/commands/dump.py +8 -8
- piccolo/apps/fixtures/commands/load.py +5 -5
- piccolo/apps/fixtures/commands/shared.py +9 -9
- piccolo/apps/migrations/auto/diffable_table.py +12 -12
- piccolo/apps/migrations/auto/migration_manager.py +59 -66
- piccolo/apps/migrations/auto/operations.py +14 -14
- piccolo/apps/migrations/auto/schema_differ.py +35 -34
- piccolo/apps/migrations/auto/schema_snapshot.py +3 -4
- piccolo/apps/migrations/auto/serialisation.py +27 -24
- piccolo/apps/migrations/auto/serialisation_legacy.py +2 -2
- piccolo/apps/migrations/commands/backwards.py +1 -2
- piccolo/apps/migrations/commands/base.py +12 -12
- piccolo/apps/migrations/commands/check.py +2 -3
- piccolo/apps/migrations/commands/clean.py +3 -3
- piccolo/apps/migrations/commands/forwards.py +1 -2
- piccolo/apps/migrations/commands/new.py +6 -6
- piccolo/apps/migrations/tables.py +3 -3
- piccolo/apps/playground/commands/run.py +29 -13
- piccolo/apps/schema/commands/generate.py +49 -49
- piccolo/apps/schema/commands/graph.py +5 -5
- piccolo/apps/shell/commands/run.py +1 -2
- piccolo/apps/sql_shell/commands/run.py +4 -4
- piccolo/apps/tester/commands/run.py +3 -3
- piccolo/apps/user/commands/change_permissions.py +6 -6
- piccolo/apps/user/commands/create.py +7 -7
- piccolo/apps/user/commands/list.py +2 -2
- piccolo/apps/user/tables.py +8 -8
- piccolo/columns/base.py +84 -52
- piccolo/columns/choices.py +2 -2
- piccolo/columns/column_types.py +297 -175
- piccolo/columns/combination.py +15 -12
- piccolo/columns/defaults/base.py +4 -4
- piccolo/columns/defaults/date.py +4 -3
- piccolo/columns/defaults/interval.py +4 -3
- piccolo/columns/defaults/time.py +4 -3
- piccolo/columns/defaults/timestamp.py +4 -3
- piccolo/columns/defaults/timestamptz.py +4 -3
- piccolo/columns/defaults/uuid.py +3 -2
- piccolo/columns/m2m.py +28 -35
- piccolo/columns/readable.py +4 -3
- piccolo/columns/reference.py +9 -9
- piccolo/conf/apps.py +53 -54
- piccolo/custom_types.py +28 -6
- piccolo/engine/base.py +14 -14
- piccolo/engine/cockroach.py +5 -4
- piccolo/engine/finder.py +2 -2
- piccolo/engine/postgres.py +20 -19
- piccolo/engine/sqlite.py +23 -22
- piccolo/query/base.py +30 -29
- piccolo/query/functions/__init__.py +12 -0
- piccolo/query/functions/aggregate.py +4 -3
- piccolo/query/functions/array.py +151 -0
- piccolo/query/functions/base.py +3 -3
- piccolo/query/functions/datetime.py +22 -22
- piccolo/query/functions/string.py +4 -4
- piccolo/query/functions/type_conversion.py +30 -15
- piccolo/query/methods/alter.py +47 -46
- piccolo/query/methods/count.py +11 -10
- piccolo/query/methods/create.py +6 -5
- piccolo/query/methods/create_index.py +9 -8
- piccolo/query/methods/delete.py +7 -6
- piccolo/query/methods/drop_index.py +7 -6
- piccolo/query/methods/exists.py +6 -5
- piccolo/query/methods/indexes.py +4 -4
- piccolo/query/methods/insert.py +21 -14
- piccolo/query/methods/objects.py +60 -50
- piccolo/query/methods/raw.py +7 -6
- piccolo/query/methods/refresh.py +8 -7
- piccolo/query/methods/select.py +56 -49
- piccolo/query/methods/table_exists.py +5 -5
- piccolo/query/methods/update.py +8 -7
- piccolo/query/mixins.py +56 -61
- piccolo/query/operators/json.py +11 -11
- piccolo/query/proxy.py +8 -9
- piccolo/querystring.py +14 -15
- piccolo/schema.py +10 -10
- piccolo/table.py +93 -94
- piccolo/table_reflection.py +9 -9
- piccolo/testing/model_builder.py +12 -11
- piccolo/testing/random_builder.py +2 -2
- piccolo/testing/test_case.py +4 -4
- piccolo/utils/dictionary.py +3 -3
- piccolo/utils/encoding.py +5 -5
- piccolo/utils/lazy_loader.py +3 -3
- piccolo/utils/list.py +7 -8
- piccolo/utils/objects.py +4 -6
- piccolo/utils/pydantic.py +21 -24
- piccolo/utils/sql_values.py +3 -3
- piccolo/utils/sync.py +4 -3
- piccolo/utils/warnings.py +1 -2
- {piccolo-1.27.0.dist-info → piccolo-1.28.0.dist-info}/METADATA +1 -1
- {piccolo-1.27.0.dist-info → piccolo-1.28.0.dist-info}/RECORD +122 -121
- tests/apps/fixtures/commands/test_dump_load.py +1 -2
- tests/apps/migrations/auto/integration/test_migrations.py +32 -7
- tests/apps/migrations/auto/test_migration_manager.py +2 -2
- tests/apps/migrations/auto/test_schema_differ.py +22 -23
- tests/apps/migrations/commands/test_forwards_backwards.py +3 -3
- tests/columns/m2m/base.py +2 -2
- tests/columns/test_array.py +176 -10
- tests/columns/test_boolean.py +2 -4
- tests/columns/test_combination.py +29 -1
- tests/columns/test_db_column_name.py +2 -2
- tests/engine/test_extra_nodes.py +2 -2
- tests/engine/test_pool.py +3 -3
- tests/engine/test_transaction.py +4 -4
- tests/query/test_freeze.py +4 -4
- tests/table/instance/test_get_related.py +2 -2
- tests/table/test_alter.py +4 -4
- tests/table/test_indexes.py +1 -2
- tests/table/test_refresh.py +2 -2
- tests/table/test_select.py +58 -0
- tests/table/test_update.py +3 -3
- tests/testing/test_model_builder.py +1 -2
- tests/utils/test_pydantic.py +36 -36
- tests/utils/test_table_reflection.py +1 -2
- {piccolo-1.27.0.dist-info → piccolo-1.28.0.dist-info}/WHEEL +0 -0
- {piccolo-1.27.0.dist-info → piccolo-1.28.0.dist-info}/entry_points.txt +0 -0
- {piccolo-1.27.0.dist-info → piccolo-1.28.0.dist-info}/licenses/LICENSE +0 -0
- {piccolo-1.27.0.dist-info → piccolo-1.28.0.dist-info}/top_level.txt +0 -0
@@ -2,8 +2,8 @@ import datetime
|
|
2
2
|
import enum
|
3
3
|
import random
|
4
4
|
import string
|
5
|
-
import typing as t
|
6
5
|
import uuid
|
6
|
+
from typing import Any
|
7
7
|
|
8
8
|
|
9
9
|
class RandomBuilder:
|
@@ -36,7 +36,7 @@ class RandomBuilder:
|
|
36
36
|
)
|
37
37
|
|
38
38
|
@classmethod
|
39
|
-
def next_enum(cls, e:
|
39
|
+
def next_enum(cls, e: type[enum.Enum]) -> Any:
|
40
40
|
return random.choice([item.value for item in e])
|
41
41
|
|
42
42
|
@classmethod
|
piccolo/testing/test_case.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
|
3
|
+
from typing import Optional
|
4
4
|
from unittest import IsolatedAsyncioTestCase, TestCase
|
5
5
|
|
6
6
|
from piccolo.engine import Engine, engine_finder
|
@@ -30,7 +30,7 @@ class TableTest(TestCase):
|
|
30
30
|
|
31
31
|
""" # noqa: E501
|
32
32
|
|
33
|
-
tables:
|
33
|
+
tables: list[type[Table]]
|
34
34
|
|
35
35
|
def setUp(self) -> None:
|
36
36
|
create_db_tables_sync(*self.tables)
|
@@ -54,7 +54,7 @@ class AsyncTableTest(IsolatedAsyncioTestCase):
|
|
54
54
|
|
55
55
|
"""
|
56
56
|
|
57
|
-
tables:
|
57
|
+
tables: list[type[Table]]
|
58
58
|
|
59
59
|
async def asyncSetUp(self) -> None:
|
60
60
|
await create_db_tables(*self.tables)
|
@@ -106,7 +106,7 @@ class AsyncTransactionTest(IsolatedAsyncioTestCase):
|
|
106
106
|
#
|
107
107
|
# ...
|
108
108
|
#
|
109
|
-
db:
|
109
|
+
db: Optional[Engine] = None
|
110
110
|
|
111
111
|
async def asyncSetUp(self) -> None:
|
112
112
|
db = self.db or engine_finder()
|
piccolo/utils/dictionary.py
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
|
3
|
+
from typing import Any
|
4
4
|
|
5
5
|
|
6
|
-
def make_nested(dictionary:
|
6
|
+
def make_nested(dictionary: dict[str, Any]) -> dict[str, Any]:
|
7
7
|
"""
|
8
8
|
Rows are returned from the database as a flat dictionary, with keys such
|
9
9
|
as ``'manager.name'`` if the column belongs to a related table.
|
@@ -20,7 +20,7 @@ def make_nested(dictionary: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]:
|
|
20
20
|
{'name': 'Pythonistas', 'band': {'name': 'Guido'}}
|
21
21
|
|
22
22
|
"""
|
23
|
-
output:
|
23
|
+
output: dict[str, Any] = {}
|
24
24
|
|
25
25
|
items = list(dictionary.items())
|
26
26
|
items.sort(key=lambda x: x[0])
|
piccolo/utils/encoding.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
|
3
|
+
from typing import Any
|
4
4
|
|
5
5
|
try:
|
6
6
|
import orjson
|
@@ -12,9 +12,9 @@ except ImportError:
|
|
12
12
|
ORJSON = False
|
13
13
|
|
14
14
|
|
15
|
-
def dump_json(data:
|
15
|
+
def dump_json(data: Any, pretty: bool = False) -> str:
|
16
16
|
if ORJSON:
|
17
|
-
orjson_params:
|
17
|
+
orjson_params: dict[str, Any] = {"default": str}
|
18
18
|
if pretty:
|
19
19
|
orjson_params["option"] = (
|
20
20
|
orjson.OPT_INDENT_2 | orjson.OPT_APPEND_NEWLINE # type: ignore
|
@@ -23,7 +23,7 @@ def dump_json(data: t.Any, pretty: bool = False) -> str:
|
|
23
23
|
"utf8"
|
24
24
|
)
|
25
25
|
else:
|
26
|
-
params:
|
26
|
+
params: dict[str, Any] = {"default": str}
|
27
27
|
if pretty:
|
28
28
|
params["indent"] = 2
|
29
29
|
return json.dumps(data, **params) # type: ignore
|
@@ -63,7 +63,7 @@ class JSONDict(dict):
|
|
63
63
|
...
|
64
64
|
|
65
65
|
|
66
|
-
def load_json(data: str) ->
|
66
|
+
def load_json(data: str) -> Any:
|
67
67
|
response = (
|
68
68
|
orjson.loads(data) if ORJSON else json.loads(data) # type: ignore
|
69
69
|
)
|
piccolo/utils/lazy_loader.py
CHANGED
@@ -3,7 +3,7 @@ from __future__ import absolute_import, division, print_function
|
|
3
3
|
|
4
4
|
import importlib
|
5
5
|
import types
|
6
|
-
|
6
|
+
from typing import Any
|
7
7
|
|
8
8
|
|
9
9
|
class LazyLoader(types.ModuleType):
|
@@ -48,10 +48,10 @@ class LazyLoader(types.ModuleType):
|
|
48
48
|
else:
|
49
49
|
raise exc from exc
|
50
50
|
|
51
|
-
def __getattr__(self, item) ->
|
51
|
+
def __getattr__(self, item) -> Any:
|
52
52
|
module = self._load()
|
53
53
|
return getattr(module, item)
|
54
54
|
|
55
|
-
def __dir__(self) ->
|
55
|
+
def __dir__(self) -> list[str]:
|
56
56
|
module = self._load()
|
57
57
|
return dir(module)
|
piccolo/utils/list.py
CHANGED
@@ -1,11 +1,12 @@
|
|
1
|
-
|
1
|
+
from collections.abc import Sequence
|
2
|
+
from typing import TypeVar, Union
|
2
3
|
|
3
|
-
ElementType =
|
4
|
+
ElementType = TypeVar("ElementType")
|
4
5
|
|
5
6
|
|
6
7
|
def flatten(
|
7
|
-
items:
|
8
|
-
) ->
|
8
|
+
items: Sequence[Union[ElementType, list[ElementType]]]
|
9
|
+
) -> list[ElementType]:
|
9
10
|
"""
|
10
11
|
Takes a sequence of elements, and flattens it out. For example::
|
11
12
|
|
@@ -17,7 +18,7 @@ def flatten(
|
|
17
18
|
await Band.select(Band.name, Band.manager.all_columns())
|
18
19
|
|
19
20
|
"""
|
20
|
-
_items:
|
21
|
+
_items: list[ElementType] = []
|
21
22
|
for item in items:
|
22
23
|
if isinstance(item, list):
|
23
24
|
_items.extend(item)
|
@@ -27,9 +28,7 @@ def flatten(
|
|
27
28
|
return _items
|
28
29
|
|
29
30
|
|
30
|
-
def batch(
|
31
|
-
data: t.List[ElementType], chunk_size: int
|
32
|
-
) -> t.List[t.List[ElementType]]:
|
31
|
+
def batch(data: list[ElementType], chunk_size: int) -> list[list[ElementType]]:
|
33
32
|
"""
|
34
33
|
Breaks the list down into sublists of the given ``chunk_size``. The last
|
35
34
|
sublist may have fewer elements than ``chunk_size``::
|
piccolo/utils/objects.py
CHANGED
@@ -1,16 +1,14 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
|
3
|
+
from typing import TYPE_CHECKING, Any
|
4
4
|
|
5
5
|
from piccolo.columns.column_types import ForeignKey
|
6
6
|
|
7
|
-
if
|
7
|
+
if TYPE_CHECKING: # pragma: no cover
|
8
8
|
from piccolo.table import Table
|
9
9
|
|
10
10
|
|
11
|
-
def make_nested_object(
|
12
|
-
row: t.Dict[str, t.Any], table_class: t.Type[Table]
|
13
|
-
) -> Table:
|
11
|
+
def make_nested_object(row: dict[str, Any], table_class: type[Table]) -> Table:
|
14
12
|
"""
|
15
13
|
Takes a nested dictionary such as this:
|
16
14
|
|
@@ -38,7 +36,7 @@ def make_nested_object(
|
|
38
36
|
1
|
39
37
|
|
40
38
|
"""
|
41
|
-
table_params:
|
39
|
+
table_params: dict[str, Any] = {}
|
42
40
|
|
43
41
|
for key, value in row.items():
|
44
42
|
if isinstance(value, dict):
|
piccolo/utils/pydantic.py
CHANGED
@@ -2,9 +2,10 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import itertools
|
4
4
|
import json
|
5
|
-
import typing as t
|
6
5
|
from collections import defaultdict
|
6
|
+
from collections.abc import Callable
|
7
7
|
from functools import partial
|
8
|
+
from typing import Any, Optional, Union
|
8
9
|
|
9
10
|
import pydantic
|
10
11
|
|
@@ -30,7 +31,7 @@ except ImportError:
|
|
30
31
|
JsonDict = dict # type: ignore
|
31
32
|
|
32
33
|
|
33
|
-
def pydantic_json_validator(value:
|
34
|
+
def pydantic_json_validator(value: Optional[str], required: bool = True):
|
34
35
|
if value is None:
|
35
36
|
if required:
|
36
37
|
raise ValueError("The JSON value wasn't provided.")
|
@@ -45,7 +46,7 @@ def pydantic_json_validator(value: t.Optional[str], required: bool = True):
|
|
45
46
|
return value
|
46
47
|
|
47
48
|
|
48
|
-
def is_table_column(column: Column, table:
|
49
|
+
def is_table_column(column: Column, table: type[Table]) -> bool:
|
49
50
|
"""
|
50
51
|
Verify that the given ``Column`` belongs to the given ``Table``.
|
51
52
|
"""
|
@@ -60,9 +61,7 @@ def is_table_column(column: Column, table: t.Type[Table]) -> bool:
|
|
60
61
|
return False
|
61
62
|
|
62
63
|
|
63
|
-
def validate_columns(
|
64
|
-
columns: t.Tuple[Column, ...], table: t.Type[Table]
|
65
|
-
) -> bool:
|
64
|
+
def validate_columns(columns: tuple[Column, ...], table: type[Table]) -> bool:
|
66
65
|
"""
|
67
66
|
Verify that each column is a ``Column``` instance, and its parent is the
|
68
67
|
given ``Table``.
|
@@ -74,9 +73,7 @@ def validate_columns(
|
|
74
73
|
)
|
75
74
|
|
76
75
|
|
77
|
-
def get_array_value_type(
|
78
|
-
column: Array, inner: t.Optional[t.Type] = None
|
79
|
-
) -> t.Type:
|
76
|
+
def get_array_value_type(column: Array, inner: Optional[type] = None) -> type:
|
80
77
|
"""
|
81
78
|
Gets the correct type for an ``Array`` column (which might be
|
82
79
|
multidimensional).
|
@@ -86,14 +83,14 @@ def get_array_value_type(
|
|
86
83
|
else:
|
87
84
|
inner_type = get_pydantic_value_type(column.base_column)
|
88
85
|
|
89
|
-
return
|
86
|
+
return list[inner_type] # type: ignore
|
90
87
|
|
91
88
|
|
92
|
-
def get_pydantic_value_type(column: Column) ->
|
89
|
+
def get_pydantic_value_type(column: Column) -> type:
|
93
90
|
"""
|
94
91
|
Map the Piccolo ``Column`` to a Pydantic type.
|
95
92
|
"""
|
96
|
-
value_type:
|
93
|
+
value_type: type
|
97
94
|
|
98
95
|
if isinstance(column, (Decimal, Numeric)):
|
99
96
|
value_type = pydantic.condecimal(
|
@@ -112,20 +109,20 @@ def get_pydantic_value_type(column: Column) -> t.Type:
|
|
112
109
|
|
113
110
|
|
114
111
|
def create_pydantic_model(
|
115
|
-
table:
|
116
|
-
nested:
|
117
|
-
exclude_columns:
|
118
|
-
include_columns:
|
112
|
+
table: type[Table],
|
113
|
+
nested: Union[bool, tuple[ForeignKey, ...]] = False,
|
114
|
+
exclude_columns: tuple[Column, ...] = (),
|
115
|
+
include_columns: tuple[Column, ...] = (),
|
119
116
|
include_default_columns: bool = False,
|
120
117
|
include_readable: bool = False,
|
121
118
|
all_optional: bool = False,
|
122
|
-
model_name:
|
119
|
+
model_name: Optional[str] = None,
|
123
120
|
deserialize_json: bool = False,
|
124
121
|
recursion_depth: int = 0,
|
125
122
|
max_recursion_depth: int = 5,
|
126
|
-
pydantic_config:
|
127
|
-
json_schema_extra:
|
128
|
-
) ->
|
123
|
+
pydantic_config: Optional[pydantic.config.ConfigDict] = None,
|
124
|
+
json_schema_extra: Optional[dict[str, Any]] = None,
|
125
|
+
) -> type[pydantic.BaseModel]:
|
129
126
|
"""
|
130
127
|
Create a Pydantic model representing a table.
|
131
128
|
|
@@ -205,8 +202,8 @@ def create_pydantic_model(
|
|
205
202
|
|
206
203
|
###########################################################################
|
207
204
|
|
208
|
-
columns:
|
209
|
-
validators:
|
205
|
+
columns: dict[str, Any] = {}
|
206
|
+
validators: dict[str, Callable] = {}
|
210
207
|
|
211
208
|
piccolo_columns = tuple(
|
212
209
|
table._meta.columns
|
@@ -264,11 +261,11 @@ def create_pydantic_model(
|
|
264
261
|
else:
|
265
262
|
value_type = get_pydantic_value_type(column=column)
|
266
263
|
|
267
|
-
_type =
|
264
|
+
_type = Optional[value_type] if is_optional else value_type
|
268
265
|
|
269
266
|
#######################################################################
|
270
267
|
|
271
|
-
params:
|
268
|
+
params: dict[str, Any] = {}
|
272
269
|
if is_optional:
|
273
270
|
params["default"] = None
|
274
271
|
|
piccolo/utils/sql_values.py
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import functools
|
4
|
-
import typing as t
|
5
4
|
from enum import Enum
|
5
|
+
from typing import TYPE_CHECKING, Any
|
6
6
|
|
7
7
|
from piccolo.utils.encoding import dump_json
|
8
8
|
from piccolo.utils.warnings import colored_warning
|
9
9
|
|
10
|
-
if
|
10
|
+
if TYPE_CHECKING: # pragma: no cover
|
11
11
|
from piccolo.columns import Column
|
12
12
|
|
13
13
|
|
14
|
-
def convert_to_sql_value(value:
|
14
|
+
def convert_to_sql_value(value: Any, column: Column) -> Any:
|
15
15
|
"""
|
16
16
|
Some values which can be passed into Piccolo queries aren't valid in the
|
17
17
|
database. For example, Enums, Table instances, and dictionaries for JSON
|
piccolo/utils/sync.py
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import asyncio
|
4
|
-
|
4
|
+
from collections.abc import Coroutine
|
5
5
|
from concurrent.futures import Future, ThreadPoolExecutor
|
6
|
+
from typing import Any, TypeVar
|
6
7
|
|
7
|
-
ReturnType =
|
8
|
+
ReturnType = TypeVar("ReturnType")
|
8
9
|
|
9
10
|
|
10
11
|
def run_sync(
|
11
|
-
coroutine:
|
12
|
+
coroutine: Coroutine[Any, Any, ReturnType],
|
12
13
|
) -> ReturnType:
|
13
14
|
"""
|
14
15
|
Run the coroutine synchronously - trying to accommodate as many edge cases
|
piccolo/utils/warnings.py
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
import typing as t
|
4
3
|
import warnings
|
5
4
|
from enum import Enum
|
6
5
|
|
@@ -21,7 +20,7 @@ def colored_string(message: str, level: Level = Level.medium) -> str:
|
|
21
20
|
|
22
21
|
def colored_warning(
|
23
22
|
message: str,
|
24
|
-
category:
|
23
|
+
category: type[Warning] = Warning,
|
25
24
|
stacklevel: int = 3,
|
26
25
|
level: Level = Level.medium,
|
27
26
|
):
|