piccolo 1.27.1__py3-none-any.whl → 1.29.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 +2 -3
- piccolo/apps/asgi/commands/templates/app/_blacksheep_app.py.jinja +57 -29
- piccolo/apps/asgi/commands/templates/app/_esmerald_app.py.jinja +48 -21
- piccolo/apps/asgi/commands/templates/app/_falcon_app.py.jinja +63 -8
- piccolo/apps/asgi/commands/templates/app/_fastapi_app.py.jinja +51 -24
- piccolo/apps/asgi/commands/templates/app/_litestar_app.py.jinja +34 -10
- piccolo/apps/asgi/commands/templates/app/_quart_app.py.jinja +38 -15
- piccolo/apps/asgi/commands/templates/app/_sanic_app.py.jinja +34 -11
- 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 +72 -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 +299 -177
- 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 +105 -98
- piccolo/table_reflection.py +9 -9
- piccolo/testing/model_builder.py +16 -13
- piccolo/testing/random_builder.py +14 -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.1.dist-info → piccolo-1.29.0.dist-info}/METADATA +1 -1
- {piccolo-1.27.1.dist-info → piccolo-1.29.0.dist-info}/RECORD +132 -131
- 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 +20 -49
- 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_metaclass.py +7 -3
- tests/table/test_refresh.py +2 -2
- tests/table/test_select.py +58 -0
- tests/table/test_str.py +30 -22
- tests/table/test_update.py +18 -3
- tests/testing/test_model_builder.py +1 -2
- tests/testing/test_random_builder.py +5 -0
- tests/utils/test_pydantic.py +152 -134
- tests/utils/test_table_reflection.py +1 -2
- {piccolo-1.27.1.dist-info → piccolo-1.29.0.dist-info}/WHEEL +0 -0
- {piccolo-1.27.1.dist-info → piccolo-1.29.0.dist-info}/entry_points.txt +0 -0
- {piccolo-1.27.1.dist-info → piccolo-1.29.0.dist-info}/licenses/LICENSE +0 -0
- {piccolo-1.27.1.dist-info → piccolo-1.29.0.dist-info}/top_level.txt +0 -0
@@ -5,9 +5,9 @@ import dataclasses
|
|
5
5
|
import itertools
|
6
6
|
import json
|
7
7
|
import re
|
8
|
-
import typing as t
|
9
8
|
import uuid
|
10
9
|
from datetime import date, datetime
|
10
|
+
from typing import TYPE_CHECKING, Any, Literal, Optional, Union
|
11
11
|
|
12
12
|
import black
|
13
13
|
|
@@ -43,7 +43,7 @@ from piccolo.engine.postgres import PostgresEngine
|
|
43
43
|
from piccolo.table import Table, create_table_class, sort_table_classes
|
44
44
|
from piccolo.utils.naming import _snake_to_camel
|
45
45
|
|
46
|
-
if
|
46
|
+
if TYPE_CHECKING: # pragma: no cover
|
47
47
|
from piccolo.engine.base import Engine
|
48
48
|
|
49
49
|
|
@@ -61,13 +61,13 @@ class ConstraintTable:
|
|
61
61
|
class RowMeta:
|
62
62
|
column_default: str
|
63
63
|
column_name: str
|
64
|
-
is_nullable:
|
64
|
+
is_nullable: Literal["YES", "NO"]
|
65
65
|
table_name: str
|
66
|
-
character_maximum_length:
|
66
|
+
character_maximum_length: Optional[int]
|
67
67
|
data_type: str
|
68
|
-
numeric_precision:
|
69
|
-
numeric_scale:
|
70
|
-
numeric_precision_radix:
|
68
|
+
numeric_precision: Optional[Union[int, str]]
|
69
|
+
numeric_scale: Optional[Union[int, str]]
|
70
|
+
numeric_precision_radix: Optional[Literal[2, 10]]
|
71
71
|
|
72
72
|
@classmethod
|
73
73
|
def get_column_name_str(cls) -> str:
|
@@ -76,10 +76,10 @@ class RowMeta:
|
|
76
76
|
|
77
77
|
@dataclasses.dataclass
|
78
78
|
class Constraint:
|
79
|
-
constraint_type:
|
79
|
+
constraint_type: Literal["PRIMARY KEY", "UNIQUE", "FOREIGN KEY", "CHECK"]
|
80
80
|
constraint_name: str
|
81
|
-
constraint_schema:
|
82
|
-
column_name:
|
81
|
+
constraint_schema: Optional[str] = None
|
82
|
+
column_name: Optional[str] = None
|
83
83
|
|
84
84
|
|
85
85
|
@dataclasses.dataclass
|
@@ -89,12 +89,12 @@ class TableConstraints:
|
|
89
89
|
"""
|
90
90
|
|
91
91
|
tablename: str
|
92
|
-
constraints:
|
92
|
+
constraints: list[Constraint]
|
93
93
|
|
94
94
|
def __post_init__(self) -> None:
|
95
|
-
foreign_key_constraints:
|
96
|
-
unique_constraints:
|
97
|
-
primary_key_constraints:
|
95
|
+
foreign_key_constraints: list[Constraint] = []
|
96
|
+
unique_constraints: list[Constraint] = []
|
97
|
+
primary_key_constraints: list[Constraint] = []
|
98
98
|
|
99
99
|
for constraint in self.constraints:
|
100
100
|
if constraint.constraint_type == "FOREIGN KEY":
|
@@ -141,7 +141,7 @@ class Trigger:
|
|
141
141
|
table_name: str
|
142
142
|
column_name: str
|
143
143
|
on_update: str
|
144
|
-
on_delete:
|
144
|
+
on_delete: Literal[
|
145
145
|
"NO ACTION", "RESTRICT", "CASCADE", "SET NULL", "SET_DEFAULT"
|
146
146
|
]
|
147
147
|
references_table: str
|
@@ -155,14 +155,14 @@ class TableTriggers:
|
|
155
155
|
"""
|
156
156
|
|
157
157
|
tablename: str
|
158
|
-
triggers:
|
158
|
+
triggers: list[Trigger]
|
159
159
|
|
160
|
-
def get_column_triggers(self, column_name: str) ->
|
160
|
+
def get_column_triggers(self, column_name: str) -> list[Trigger]:
|
161
161
|
return [i for i in self.triggers if i.column_name == column_name]
|
162
162
|
|
163
163
|
def get_column_ref_trigger(
|
164
164
|
self, column_name: str, references_table: str
|
165
|
-
) ->
|
165
|
+
) -> Optional[Trigger]:
|
166
166
|
for trigger in self.triggers:
|
167
167
|
if (
|
168
168
|
trigger.column_name == column_name
|
@@ -221,14 +221,14 @@ class TableIndexes:
|
|
221
221
|
"""
|
222
222
|
|
223
223
|
tablename: str
|
224
|
-
indexes:
|
224
|
+
indexes: list[Index]
|
225
225
|
|
226
|
-
def get_column_index(self, column_name: str) ->
|
226
|
+
def get_column_index(self, column_name: str) -> Optional[Index]:
|
227
227
|
return next(
|
228
228
|
(i for i in self.indexes if i.column_name == column_name), None
|
229
229
|
)
|
230
230
|
|
231
|
-
def get_warnings(self) ->
|
231
|
+
def get_warnings(self) -> list[str]:
|
232
232
|
return list(
|
233
233
|
itertools.chain(*[index.warnings for index in self.indexes])
|
234
234
|
)
|
@@ -250,13 +250,13 @@ class OutputSchema:
|
|
250
250
|
e.g. ["class MyTable(Table): ..."]
|
251
251
|
"""
|
252
252
|
|
253
|
-
imports:
|
254
|
-
warnings:
|
255
|
-
index_warnings:
|
256
|
-
trigger_warnings:
|
257
|
-
tables:
|
253
|
+
imports: list[str] = dataclasses.field(default_factory=list)
|
254
|
+
warnings: list[str] = dataclasses.field(default_factory=list)
|
255
|
+
index_warnings: list[str] = dataclasses.field(default_factory=list)
|
256
|
+
trigger_warnings: list[str] = dataclasses.field(default_factory=list)
|
257
|
+
tables: list[type[Table]] = dataclasses.field(default_factory=list)
|
258
258
|
|
259
|
-
def get_table_with_name(self, tablename: str) ->
|
259
|
+
def get_table_with_name(self, tablename: str) -> Optional[type[Table]]:
|
260
260
|
"""
|
261
261
|
Used to search for a table by name.
|
262
262
|
"""
|
@@ -287,7 +287,7 @@ class OutputSchema:
|
|
287
287
|
return self
|
288
288
|
|
289
289
|
|
290
|
-
COLUMN_TYPE_MAP:
|
290
|
+
COLUMN_TYPE_MAP: dict[str, type[Column]] = {
|
291
291
|
"bigint": BigInt,
|
292
292
|
"boolean": Boolean,
|
293
293
|
"bytea": Bytea,
|
@@ -308,12 +308,12 @@ COLUMN_TYPE_MAP: t.Dict[str, t.Type[Column]] = {
|
|
308
308
|
}
|
309
309
|
|
310
310
|
# Re-map for Cockroach compatibility.
|
311
|
-
COLUMN_TYPE_MAP_COCKROACH:
|
311
|
+
COLUMN_TYPE_MAP_COCKROACH: dict[str, type[Column]] = {
|
312
312
|
**COLUMN_TYPE_MAP,
|
313
313
|
**{"integer": BigInt, "json": JSONB},
|
314
314
|
}
|
315
315
|
|
316
|
-
COLUMN_DEFAULT_PARSER:
|
316
|
+
COLUMN_DEFAULT_PARSER: dict[type[Column], Any] = {
|
317
317
|
BigInt: re.compile(r"^'?(?P<value>-?[0-9]\d*)'?(?:::bigint)?$"),
|
318
318
|
Boolean: re.compile(r"^(?P<value>true|false)$"),
|
319
319
|
Bytea: re.compile(r"'(?P<value>.*)'::bytea$"),
|
@@ -373,15 +373,15 @@ COLUMN_DEFAULT_PARSER: t.Dict[t.Type[Column], t.Any] = {
|
|
373
373
|
}
|
374
374
|
|
375
375
|
# Re-map for Cockroach compatibility.
|
376
|
-
COLUMN_DEFAULT_PARSER_COCKROACH:
|
376
|
+
COLUMN_DEFAULT_PARSER_COCKROACH: dict[type[Column], Any] = {
|
377
377
|
**COLUMN_DEFAULT_PARSER,
|
378
378
|
BigInt: re.compile(r"^(?P<value>-?\d+)$"),
|
379
379
|
}
|
380
380
|
|
381
381
|
|
382
382
|
def get_column_default(
|
383
|
-
column_type:
|
384
|
-
) ->
|
383
|
+
column_type: type[Column], column_default: str, engine_type: str
|
384
|
+
) -> Any:
|
385
385
|
if engine_type == "cockroach":
|
386
386
|
pat = COLUMN_DEFAULT_PARSER_COCKROACH.get(column_type)
|
387
387
|
else:
|
@@ -455,7 +455,7 @@ def get_column_default(
|
|
455
455
|
return column_type.value_type(value["value"])
|
456
456
|
|
457
457
|
|
458
|
-
INDEX_METHOD_MAP:
|
458
|
+
INDEX_METHOD_MAP: dict[str, IndexMethod] = {
|
459
459
|
"btree": IndexMethod.btree,
|
460
460
|
"hash": IndexMethod.hash,
|
461
461
|
"gist": IndexMethod.gist,
|
@@ -465,7 +465,7 @@ INDEX_METHOD_MAP: t.Dict[str, IndexMethod] = {
|
|
465
465
|
|
466
466
|
# 'Indices' seems old-fashioned and obscure in this context.
|
467
467
|
async def get_indexes( # noqa: E302
|
468
|
-
table_class:
|
468
|
+
table_class: type[Table], tablename: str, schema_name: str = "public"
|
469
469
|
) -> TableIndexes:
|
470
470
|
"""
|
471
471
|
Get all of the constraints for a table.
|
@@ -492,7 +492,7 @@ async def get_indexes( # noqa: E302
|
|
492
492
|
|
493
493
|
|
494
494
|
async def get_fk_triggers(
|
495
|
-
table_class:
|
495
|
+
table_class: type[Table], tablename: str, schema_name: str = "public"
|
496
496
|
) -> TableTriggers:
|
497
497
|
"""
|
498
498
|
Get all of the constraints for a table.
|
@@ -540,7 +540,7 @@ async def get_fk_triggers(
|
|
540
540
|
|
541
541
|
|
542
542
|
async def get_constraints(
|
543
|
-
table_class:
|
543
|
+
table_class: type[Table], tablename: str, schema_name: str = "public"
|
544
544
|
) -> TableConstraints:
|
545
545
|
"""
|
546
546
|
Get all of the constraints for a table.
|
@@ -572,8 +572,8 @@ async def get_constraints(
|
|
572
572
|
|
573
573
|
|
574
574
|
async def get_tablenames(
|
575
|
-
table_class:
|
576
|
-
) ->
|
575
|
+
table_class: type[Table], schema_name: str = "public"
|
576
|
+
) -> list[str]:
|
577
577
|
"""
|
578
578
|
Get the tablenames for the schema.
|
579
579
|
|
@@ -598,8 +598,8 @@ async def get_tablenames(
|
|
598
598
|
|
599
599
|
|
600
600
|
async def get_table_schema(
|
601
|
-
table_class:
|
602
|
-
) ->
|
601
|
+
table_class: type[Table], tablename: str, schema_name: str = "public"
|
602
|
+
) -> list[RowMeta]:
|
603
603
|
"""
|
604
604
|
Get the schema from the database.
|
605
605
|
|
@@ -629,7 +629,7 @@ async def get_table_schema(
|
|
629
629
|
|
630
630
|
|
631
631
|
async def get_foreign_key_reference(
|
632
|
-
table_class:
|
632
|
+
table_class: type[Table], constraint_name: str, constraint_schema: str
|
633
633
|
) -> ConstraintTable:
|
634
634
|
"""
|
635
635
|
Retrieve the name of the table that a foreign key is referencing.
|
@@ -652,7 +652,7 @@ async def get_foreign_key_reference(
|
|
652
652
|
|
653
653
|
|
654
654
|
async def create_table_class_from_db(
|
655
|
-
table_class:
|
655
|
+
table_class: type[Table],
|
656
656
|
tablename: str,
|
657
657
|
schema_name: str,
|
658
658
|
engine_type: str,
|
@@ -674,7 +674,7 @@ async def create_table_class_from_db(
|
|
674
674
|
table_class=table_class, tablename=tablename, schema_name=schema_name
|
675
675
|
)
|
676
676
|
|
677
|
-
columns:
|
677
|
+
columns: dict[str, Column] = {}
|
678
678
|
|
679
679
|
for pg_row_meta in table_schema:
|
680
680
|
data_type = pg_row_meta.data_type
|
@@ -692,7 +692,7 @@ async def create_table_class_from_db(
|
|
692
692
|
)
|
693
693
|
column_type = Column
|
694
694
|
|
695
|
-
kwargs:
|
695
|
+
kwargs: dict[str, Any] = {
|
696
696
|
"null": pg_row_meta.is_nullable == "YES",
|
697
697
|
"unique": constraints.is_unique(column_name=column_name),
|
698
698
|
}
|
@@ -721,7 +721,7 @@ async def create_table_class_from_db(
|
|
721
721
|
constraint_schema=fk_constraint_table.schema,
|
722
722
|
)
|
723
723
|
if constraint_table.name:
|
724
|
-
referenced_table:
|
724
|
+
referenced_table: Union[str, Optional[type[Table]]]
|
725
725
|
|
726
726
|
if constraint_table.name == tablename:
|
727
727
|
referenced_output_schema = output_schema
|
@@ -805,9 +805,9 @@ async def create_table_class_from_db(
|
|
805
805
|
|
806
806
|
async def get_output_schema(
|
807
807
|
schema_name: str = "public",
|
808
|
-
include:
|
809
|
-
exclude:
|
810
|
-
engine:
|
808
|
+
include: Optional[list[str]] = None,
|
809
|
+
exclude: Optional[list[str]] = None,
|
810
|
+
engine: Optional[Engine] = None,
|
811
811
|
) -> OutputSchema:
|
812
812
|
"""
|
813
813
|
:param schema_name:
|
@@ -5,7 +5,7 @@ Credit to the Django Extensions team for inspiring this tool.
|
|
5
5
|
import dataclasses
|
6
6
|
import os
|
7
7
|
import sys
|
8
|
-
|
8
|
+
from typing import Optional
|
9
9
|
|
10
10
|
import jinja2
|
11
11
|
|
@@ -29,7 +29,7 @@ class GraphColumn:
|
|
29
29
|
@dataclasses.dataclass
|
30
30
|
class GraphTable:
|
31
31
|
name: str
|
32
|
-
columns:
|
32
|
+
columns: list[GraphColumn]
|
33
33
|
|
34
34
|
|
35
35
|
@dataclasses.dataclass
|
@@ -45,7 +45,7 @@ def render_template(**kwargs):
|
|
45
45
|
|
46
46
|
|
47
47
|
def graph(
|
48
|
-
apps: str = "all", direction: str = "LR", output:
|
48
|
+
apps: str = "all", direction: str = "LR", output: Optional[str] = None
|
49
49
|
):
|
50
50
|
"""
|
51
51
|
Prints out a graphviz .dot file for your schema.
|
@@ -73,8 +73,8 @@ def graph(
|
|
73
73
|
sys.exit(f"These apps aren't recognised: {', '.join(delta)}.")
|
74
74
|
app_names = given_app_names
|
75
75
|
|
76
|
-
tables:
|
77
|
-
relations:
|
76
|
+
tables: list[GraphTable] = []
|
77
|
+
relations: list[GraphRelation] = []
|
78
78
|
|
79
79
|
for app_name in app_names:
|
80
80
|
app_config = finder.get_app_config(app_name=app_name)
|
@@ -1,5 +1,4 @@
|
|
1
1
|
import sys
|
2
|
-
import typing as t
|
3
2
|
|
4
3
|
from piccolo.conf.apps import Finder
|
5
4
|
from piccolo.table import Table
|
@@ -13,7 +12,7 @@ except ImportError:
|
|
13
12
|
IPYTHON = False
|
14
13
|
|
15
14
|
|
16
|
-
def start_ipython_shell(**tables:
|
15
|
+
def start_ipython_shell(**tables: type[Table]): # pragma: no cover
|
17
16
|
if not IPYTHON:
|
18
17
|
sys.exit(
|
19
18
|
"Install iPython using `pip install ipython` to use this feature."
|
@@ -2,7 +2,7 @@ import os
|
|
2
2
|
import signal
|
3
3
|
import subprocess
|
4
4
|
import sys
|
5
|
-
|
5
|
+
from typing import cast
|
6
6
|
|
7
7
|
from piccolo.engine.finder import engine_finder
|
8
8
|
from piccolo.engine.postgres import PostgresEngine
|
@@ -24,7 +24,7 @@ def run() -> None:
|
|
24
24
|
|
25
25
|
# Heavily inspired by Django's dbshell command
|
26
26
|
if isinstance(engine, PostgresEngine):
|
27
|
-
engine =
|
27
|
+
engine = cast(PostgresEngine, engine)
|
28
28
|
|
29
29
|
args = ["psql"]
|
30
30
|
|
@@ -56,9 +56,9 @@ def run() -> None:
|
|
56
56
|
signal.signal(signal.SIGINT, sigint_handler)
|
57
57
|
|
58
58
|
elif isinstance(engine, SQLiteEngine):
|
59
|
-
engine =
|
59
|
+
engine = cast(SQLiteEngine, engine)
|
60
60
|
|
61
|
-
database =
|
61
|
+
database = cast(str, engine.connection_kwargs.get("database"))
|
62
62
|
if not database:
|
63
63
|
sys.exit("Unable to determine which database to connect to.")
|
64
64
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import os
|
4
4
|
import sys
|
5
|
-
|
5
|
+
from typing import Optional
|
6
6
|
|
7
7
|
from piccolo.table import TABLE_REGISTRY
|
8
8
|
|
@@ -25,7 +25,7 @@ class set_env_var:
|
|
25
25
|
def set_var(self, value: str):
|
26
26
|
os.environ[self.var_name] = value
|
27
27
|
|
28
|
-
def get_var(self) ->
|
28
|
+
def get_var(self) -> Optional[str]:
|
29
29
|
return os.environ.get(self.var_name)
|
30
30
|
|
31
31
|
def __enter__(self):
|
@@ -39,7 +39,7 @@ class set_env_var:
|
|
39
39
|
self.set_var(self.existing_value)
|
40
40
|
|
41
41
|
|
42
|
-
def run_pytest(pytest_args:
|
42
|
+
def run_pytest(pytest_args: list[str]) -> int: # pragma: no cover
|
43
43
|
try:
|
44
44
|
import pytest
|
45
45
|
except ImportError:
|
@@ -1,17 +1,17 @@
|
|
1
|
-
|
1
|
+
from typing import TYPE_CHECKING, Optional, Union
|
2
2
|
|
3
3
|
from piccolo.apps.user.tables import BaseUser
|
4
4
|
from piccolo.utils.warnings import Level, colored_string
|
5
5
|
|
6
|
-
if
|
6
|
+
if TYPE_CHECKING: # pragma: no cover
|
7
7
|
from piccolo.columns import Column
|
8
8
|
|
9
9
|
|
10
10
|
async def change_permissions(
|
11
11
|
username: str,
|
12
|
-
admin:
|
13
|
-
superuser:
|
14
|
-
active:
|
12
|
+
admin: Optional[bool] = None,
|
13
|
+
superuser: Optional[bool] = None,
|
14
|
+
active: Optional[bool] = None,
|
15
15
|
):
|
16
16
|
"""
|
17
17
|
Change a user's permissions.
|
@@ -34,7 +34,7 @@ async def change_permissions(
|
|
34
34
|
)
|
35
35
|
return
|
36
36
|
|
37
|
-
params:
|
37
|
+
params: dict[Union[Column, str], bool] = {}
|
38
38
|
|
39
39
|
if admin is not None:
|
40
40
|
params[BaseUser.admin] = admin
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import sys
|
2
|
-
import typing as t
|
3
2
|
from getpass import getpass, getuser
|
3
|
+
from typing import Optional
|
4
4
|
|
5
5
|
from piccolo.apps.user.tables import BaseUser
|
6
6
|
|
@@ -57,12 +57,12 @@ def get_is_active() -> bool:
|
|
57
57
|
|
58
58
|
|
59
59
|
def create(
|
60
|
-
username:
|
61
|
-
email:
|
62
|
-
password:
|
63
|
-
is_admin:
|
64
|
-
is_superuser:
|
65
|
-
is_active:
|
60
|
+
username: Optional[str] = None,
|
61
|
+
email: Optional[str] = None,
|
62
|
+
password: Optional[str] = None,
|
63
|
+
is_admin: Optional[bool] = None,
|
64
|
+
is_superuser: Optional[bool] = None,
|
65
|
+
is_active: Optional[bool] = None,
|
66
66
|
):
|
67
67
|
"""
|
68
68
|
Create a new user.
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
from typing import Any
|
2
2
|
|
3
3
|
from piccolo.apps.user.tables import BaseUser
|
4
4
|
from piccolo.columns import Column
|
@@ -11,7 +11,7 @@ ORDER_BY_COLUMN_NAMES = [
|
|
11
11
|
|
12
12
|
async def get_users(
|
13
13
|
order_by: Column, ascending: bool, limit: int, page: int
|
14
|
-
) ->
|
14
|
+
) -> list[dict[str, Any]]:
|
15
15
|
return (
|
16
16
|
await BaseUser.select(
|
17
17
|
*BaseUser.all_columns(exclude=[BaseUser.password])
|
piccolo/apps/user/tables.py
CHANGED
@@ -8,7 +8,7 @@ import datetime
|
|
8
8
|
import hashlib
|
9
9
|
import logging
|
10
10
|
import secrets
|
11
|
-
|
11
|
+
from typing import Any, Optional, Union
|
12
12
|
|
13
13
|
from piccolo.columns import Boolean, Secret, Timestamp, Varchar
|
14
14
|
from piccolo.columns.column_types import Serial
|
@@ -109,14 +109,14 @@ class BaseUser(Table, tablename="piccolo_user"):
|
|
109
109
|
###########################################################################
|
110
110
|
|
111
111
|
@classmethod
|
112
|
-
def update_password_sync(cls, user:
|
112
|
+
def update_password_sync(cls, user: Union[str, int], password: str):
|
113
113
|
"""
|
114
114
|
A sync equivalent of :meth:`update_password`.
|
115
115
|
"""
|
116
116
|
return run_sync(cls.update_password(user, password))
|
117
117
|
|
118
118
|
@classmethod
|
119
|
-
async def update_password(cls, user:
|
119
|
+
async def update_password(cls, user: Union[str, int], password: str):
|
120
120
|
"""
|
121
121
|
The password is the raw password string e.g. ``'password123'``.
|
122
122
|
The user can be a user ID, or a username.
|
@@ -139,7 +139,7 @@ class BaseUser(Table, tablename="piccolo_user"):
|
|
139
139
|
|
140
140
|
@classmethod
|
141
141
|
def hash_password(
|
142
|
-
cls, password: str, salt: str = "", iterations:
|
142
|
+
cls, password: str, salt: str = "", iterations: Optional[int] = None
|
143
143
|
) -> str:
|
144
144
|
"""
|
145
145
|
Hashes the password, ready for storage, and for comparing during
|
@@ -167,7 +167,7 @@ class BaseUser(Table, tablename="piccolo_user"):
|
|
167
167
|
).hex()
|
168
168
|
return f"pbkdf2_sha256${iterations}${salt}${hashed}"
|
169
169
|
|
170
|
-
def __setattr__(self, name: str, value:
|
170
|
+
def __setattr__(self, name: str, value: Any):
|
171
171
|
"""
|
172
172
|
Make sure that if the password is set, it's stored in a hashed form.
|
173
173
|
"""
|
@@ -177,7 +177,7 @@ class BaseUser(Table, tablename="piccolo_user"):
|
|
177
177
|
super().__setattr__(name, value)
|
178
178
|
|
179
179
|
@classmethod
|
180
|
-
def split_stored_password(cls, password: str) ->
|
180
|
+
def split_stored_password(cls, password: str) -> list[str]:
|
181
181
|
elements = password.split("$")
|
182
182
|
if len(elements) != 4:
|
183
183
|
raise ValueError("Unable to split hashed password")
|
@@ -186,14 +186,14 @@ class BaseUser(Table, tablename="piccolo_user"):
|
|
186
186
|
###########################################################################
|
187
187
|
|
188
188
|
@classmethod
|
189
|
-
def login_sync(cls, username: str, password: str) ->
|
189
|
+
def login_sync(cls, username: str, password: str) -> Optional[int]:
|
190
190
|
"""
|
191
191
|
A sync equivalent of :meth:`login`.
|
192
192
|
"""
|
193
193
|
return run_sync(cls.login(username, password))
|
194
194
|
|
195
195
|
@classmethod
|
196
|
-
async def login(cls, username: str, password: str) ->
|
196
|
+
async def login(cls, username: str, password: str) -> Optional[int]:
|
197
197
|
"""
|
198
198
|
Make sure the user exists and the password is valid. If so, the
|
199
199
|
``last_login`` value is updated in the database.
|