piccolo 1.27.1__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.1.dist-info → piccolo-1.28.0.dist-info}/METADATA +1 -1
- {piccolo-1.27.1.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.1.dist-info → piccolo-1.28.0.dist-info}/WHEEL +0 -0
- {piccolo-1.27.1.dist-info → piccolo-1.28.0.dist-info}/entry_points.txt +0 -0
- {piccolo-1.27.1.dist-info → piccolo-1.28.0.dist-info}/licenses/LICENSE +0 -0
- {piccolo-1.27.1.dist-info → piccolo-1.28.0.dist-info}/top_level.txt +0 -0
piccolo/conf/apps.py
CHANGED
@@ -6,11 +6,12 @@ import itertools
|
|
6
6
|
import os
|
7
7
|
import pathlib
|
8
8
|
import traceback
|
9
|
-
import typing as t
|
10
9
|
from abc import abstractmethod
|
10
|
+
from collections.abc import Callable, Sequence
|
11
11
|
from dataclasses import dataclass, field
|
12
12
|
from importlib import import_module
|
13
13
|
from types import ModuleType
|
14
|
+
from typing import Optional, Union, cast
|
14
15
|
|
15
16
|
import black
|
16
17
|
|
@@ -45,12 +46,12 @@ def get_package(name: str) -> str:
|
|
45
46
|
|
46
47
|
|
47
48
|
def table_finder(
|
48
|
-
modules:
|
49
|
-
package:
|
50
|
-
include_tags:
|
51
|
-
exclude_tags:
|
49
|
+
modules: Sequence[str],
|
50
|
+
package: Optional[str] = None,
|
51
|
+
include_tags: Optional[Sequence[str]] = None,
|
52
|
+
exclude_tags: Optional[Sequence[str]] = None,
|
52
53
|
exclude_imported: bool = False,
|
53
|
-
) ->
|
54
|
+
) -> list[type[Table]]:
|
54
55
|
"""
|
55
56
|
Rather than explicitly importing and registering table classes with the
|
56
57
|
``AppConfig``, ``table_finder`` can be used instead. It imports any ``Table``
|
@@ -95,7 +96,7 @@ def table_finder(
|
|
95
96
|
# 'blog.tables', instead of ['blog.tables'].
|
96
97
|
modules = [modules]
|
97
98
|
|
98
|
-
table_subclasses:
|
99
|
+
table_subclasses: list[type[Table]] = []
|
99
100
|
|
100
101
|
for module_path in modules:
|
101
102
|
full_module_path = (
|
@@ -151,9 +152,9 @@ class Command:
|
|
151
152
|
|
152
153
|
"""
|
153
154
|
|
154
|
-
callable:
|
155
|
-
command_name:
|
156
|
-
aliases:
|
155
|
+
callable: Callable
|
156
|
+
command_name: Optional[str] = None
|
157
|
+
aliases: list[str] = field(default_factory=list)
|
157
158
|
|
158
159
|
|
159
160
|
@dataclass
|
@@ -181,12 +182,10 @@ class AppConfig:
|
|
181
182
|
"""
|
182
183
|
|
183
184
|
app_name: str
|
184
|
-
migrations_folder_path:
|
185
|
-
table_classes:
|
186
|
-
migration_dependencies:
|
187
|
-
commands:
|
188
|
-
default_factory=list
|
189
|
-
)
|
185
|
+
migrations_folder_path: Union[str, pathlib.Path]
|
186
|
+
table_classes: list[type[Table]] = field(default_factory=list)
|
187
|
+
migration_dependencies: list[str] = field(default_factory=list)
|
188
|
+
commands: list[Union[Callable, Command]] = field(default_factory=list)
|
190
189
|
|
191
190
|
@property
|
192
191
|
def resolved_migrations_folder_path(self) -> str:
|
@@ -197,21 +196,21 @@ class AppConfig:
|
|
197
196
|
)
|
198
197
|
|
199
198
|
def __post_init__(self) -> None:
|
200
|
-
self._migration_dependency_app_configs:
|
201
|
-
|
202
|
-
|
199
|
+
self._migration_dependency_app_configs: Optional[list[AppConfig]] = (
|
200
|
+
None
|
201
|
+
)
|
203
202
|
|
204
|
-
def register_table(self, table_class:
|
203
|
+
def register_table(self, table_class: type[Table]):
|
205
204
|
self.table_classes.append(table_class)
|
206
205
|
return table_class
|
207
206
|
|
208
|
-
def get_commands(self) ->
|
207
|
+
def get_commands(self) -> list[Command]:
|
209
208
|
return [
|
210
209
|
i if isinstance(i, Command) else Command(i) for i in self.commands
|
211
210
|
]
|
212
211
|
|
213
212
|
@property
|
214
|
-
def migration_dependency_app_configs(self) ->
|
213
|
+
def migration_dependency_app_configs(self) -> list[AppConfig]:
|
215
214
|
"""
|
216
215
|
Get all of the ``AppConfig`` instances from this app's migration
|
217
216
|
dependencies.
|
@@ -219,8 +218,8 @@ class AppConfig:
|
|
219
218
|
# We cache the value so it's more efficient, and also so we can set the
|
220
219
|
# underlying value in unit tests for easier mocking.
|
221
220
|
if self._migration_dependency_app_configs is None:
|
222
|
-
modules:
|
223
|
-
|
221
|
+
modules: list[PiccoloAppModule] = [
|
222
|
+
cast(PiccoloAppModule, import_module(module_path))
|
224
223
|
for module_path in self.migration_dependencies
|
225
224
|
]
|
226
225
|
self._migration_dependency_app_configs = [
|
@@ -229,7 +228,7 @@ class AppConfig:
|
|
229
228
|
|
230
229
|
return self._migration_dependency_app_configs
|
231
230
|
|
232
|
-
def get_table_with_name(self, table_class_name: str) ->
|
231
|
+
def get_table_with_name(self, table_class_name: str) -> type[Table]:
|
233
232
|
"""
|
234
233
|
Returns a ``Table`` subclass with the given name from this app, if it
|
235
234
|
exists. Otherwise raises a ``ValueError``.
|
@@ -256,9 +255,9 @@ class AppRegistry:
|
|
256
255
|
|
257
256
|
"""
|
258
257
|
|
259
|
-
def __init__(self, apps:
|
258
|
+
def __init__(self, apps: Optional[list[str]] = None):
|
260
259
|
self.apps = apps or []
|
261
|
-
self.app_configs:
|
260
|
+
self.app_configs: dict[str, AppConfig] = {}
|
262
261
|
app_names = []
|
263
262
|
|
264
263
|
for app in self.apps:
|
@@ -282,7 +281,7 @@ class AppRegistry:
|
|
282
281
|
self._validate_app_names(app_names)
|
283
282
|
|
284
283
|
@staticmethod
|
285
|
-
def _validate_app_names(app_names:
|
284
|
+
def _validate_app_names(app_names: list[str]):
|
286
285
|
"""
|
287
286
|
Raise a ValueError if an app_name is repeated.
|
288
287
|
"""
|
@@ -298,10 +297,10 @@ class AppRegistry:
|
|
298
297
|
"multiple times."
|
299
298
|
)
|
300
299
|
|
301
|
-
def get_app_config(self, app_name: str) ->
|
300
|
+
def get_app_config(self, app_name: str) -> Optional[AppConfig]:
|
302
301
|
return self.app_configs.get(app_name)
|
303
302
|
|
304
|
-
def get_table_classes(self, app_name: str) ->
|
303
|
+
def get_table_classes(self, app_name: str) -> list[type[Table]]:
|
305
304
|
"""
|
306
305
|
Returns each Table subclass defined in the given app if it exists.
|
307
306
|
Otherwise raises a ValueError.
|
@@ -317,7 +316,7 @@ class AppRegistry:
|
|
317
316
|
|
318
317
|
def get_table_with_name(
|
319
318
|
self, app_name: str, table_class_name: str
|
320
|
-
) ->
|
319
|
+
) -> Optional[type[Table]]:
|
321
320
|
"""
|
322
321
|
Returns a Table subclass registered with the given app if it exists.
|
323
322
|
Otherwise raises a ValueError.
|
@@ -357,8 +356,8 @@ class Finder:
|
|
357
356
|
self.diagnose = diagnose
|
358
357
|
|
359
358
|
def _deduplicate(
|
360
|
-
self, config_modules:
|
361
|
-
) ->
|
359
|
+
self, config_modules: list[PiccoloAppModule]
|
360
|
+
) -> list[PiccoloAppModule]:
|
362
361
|
"""
|
363
362
|
Remove all duplicates - just leaving the first instance.
|
364
363
|
"""
|
@@ -366,8 +365,8 @@ class Finder:
|
|
366
365
|
return list({c: None for c in config_modules}.keys())
|
367
366
|
|
368
367
|
def _import_app_modules(
|
369
|
-
self, config_module_paths:
|
370
|
-
) ->
|
368
|
+
self, config_module_paths: list[str]
|
369
|
+
) -> list[PiccoloAppModule]:
|
371
370
|
"""
|
372
371
|
Import all piccolo_app.py modules within your apps, and all
|
373
372
|
dependencies.
|
@@ -376,7 +375,7 @@ class Finder:
|
|
376
375
|
|
377
376
|
for config_module_path in config_module_paths:
|
378
377
|
try:
|
379
|
-
config_module =
|
378
|
+
config_module = cast(
|
380
379
|
PiccoloAppModule, import_module(config_module_path)
|
381
380
|
)
|
382
381
|
except ImportError as e:
|
@@ -392,8 +391,8 @@ class Finder:
|
|
392
391
|
return config_modules
|
393
392
|
|
394
393
|
def get_piccolo_conf_module(
|
395
|
-
self, module_name:
|
396
|
-
) ->
|
394
|
+
self, module_name: Optional[str] = None
|
395
|
+
) -> Optional[PiccoloConfModule]:
|
397
396
|
"""
|
398
397
|
Searches the path for a 'piccolo_conf.py' module to import. The
|
399
398
|
location searched can be overriden by:
|
@@ -413,7 +412,7 @@ class Finder:
|
|
413
412
|
module_name = DEFAULT_MODULE_NAME
|
414
413
|
|
415
414
|
try:
|
416
|
-
module =
|
415
|
+
module = cast(PiccoloConfModule, import_module(module_name))
|
417
416
|
except ModuleNotFoundError as exc:
|
418
417
|
if self.diagnose:
|
419
418
|
colored_warning(
|
@@ -459,10 +458,10 @@ class Finder:
|
|
459
458
|
return getattr(piccolo_conf_module, "APP_REGISTRY")
|
460
459
|
|
461
460
|
def get_engine(
|
462
|
-
self, module_name:
|
463
|
-
) ->
|
461
|
+
self, module_name: Optional[str] = None
|
462
|
+
) -> Optional[Engine]:
|
464
463
|
piccolo_conf = self.get_piccolo_conf_module(module_name=module_name)
|
465
|
-
engine:
|
464
|
+
engine: Optional[Engine] = getattr(piccolo_conf, ENGINE_VAR, None)
|
466
465
|
|
467
466
|
if not engine:
|
468
467
|
colored_warning(
|
@@ -478,7 +477,7 @@ class Finder:
|
|
478
477
|
|
479
478
|
return engine
|
480
479
|
|
481
|
-
def get_app_modules(self) ->
|
480
|
+
def get_app_modules(self) -> list[PiccoloAppModule]:
|
482
481
|
"""
|
483
482
|
Returns the ``piccolo_app.py`` modules for each registered Piccolo app
|
484
483
|
in your project.
|
@@ -493,7 +492,7 @@ class Finder:
|
|
493
492
|
|
494
493
|
def get_app_names(
|
495
494
|
self, sort_by_migration_dependencies: bool = True
|
496
|
-
) ->
|
495
|
+
) -> list[str]:
|
497
496
|
"""
|
498
497
|
Return all of the app names.
|
499
498
|
|
@@ -509,15 +508,15 @@ class Finder:
|
|
509
508
|
)
|
510
509
|
]
|
511
510
|
|
512
|
-
def get_sorted_app_names(self) ->
|
511
|
+
def get_sorted_app_names(self) -> list[str]:
|
513
512
|
"""
|
514
513
|
Just here for backwards compatibility - use ``get_app_names`` directly.
|
515
514
|
"""
|
516
515
|
return self.get_app_names(sort_by_migration_dependencies=True)
|
517
516
|
|
518
517
|
def sort_app_configs(
|
519
|
-
self, app_configs:
|
520
|
-
) ->
|
518
|
+
self, app_configs: list[AppConfig]
|
519
|
+
) -> list[AppConfig]:
|
521
520
|
app_config_map = {
|
522
521
|
app_config.app_name: app_config for app_config in app_configs
|
523
522
|
}
|
@@ -536,7 +535,7 @@ class Finder:
|
|
536
535
|
|
537
536
|
def get_app_configs(
|
538
537
|
self, sort_by_migration_dependencies: bool = True
|
539
|
-
) ->
|
538
|
+
) -> list[AppConfig]:
|
540
539
|
"""
|
541
540
|
Returns a list of ``AppConfig``, optionally sorted by migration
|
542
541
|
dependencies.
|
@@ -560,7 +559,7 @@ class Finder:
|
|
560
559
|
|
561
560
|
def get_table_with_name(
|
562
561
|
self, app_name: str, table_class_name: str
|
563
|
-
) ->
|
562
|
+
) -> type[Table]:
|
564
563
|
"""
|
565
564
|
Returns a ``Table`` class registered with the given app if it exists.
|
566
565
|
Otherwise it raises an ``ValueError``.
|
@@ -572,9 +571,9 @@ class Finder:
|
|
572
571
|
|
573
572
|
def get_table_classes(
|
574
573
|
self,
|
575
|
-
include_apps:
|
576
|
-
exclude_apps:
|
577
|
-
) ->
|
574
|
+
include_apps: Optional[list[str]] = None,
|
575
|
+
exclude_apps: Optional[list[str]] = None,
|
576
|
+
) -> list[type[Table]]:
|
578
577
|
"""
|
579
578
|
Returns all ``Table`` classes registered with the given apps. If
|
580
579
|
``include_apps`` is ``None``, then ``Table`` classes will be returned
|
@@ -590,7 +589,7 @@ class Finder:
|
|
590
589
|
if exclude_apps:
|
591
590
|
app_names = [i for i in app_names if i not in exclude_apps]
|
592
591
|
|
593
|
-
tables:
|
592
|
+
tables: list[type[Table]] = []
|
594
593
|
|
595
594
|
for app_name in app_names:
|
596
595
|
app_config = self.get_app_config(app_name=app_name)
|
@@ -604,7 +603,7 @@ class Finder:
|
|
604
603
|
|
605
604
|
class PiccoloConfUpdater:
|
606
605
|
|
607
|
-
def __init__(self, piccolo_conf_path:
|
606
|
+
def __init__(self, piccolo_conf_path: Optional[str] = None):
|
608
607
|
"""
|
609
608
|
:param piccolo_conf_path:
|
610
609
|
The path to the piccolo_conf.py (e.g. `./piccolo_conf.py`). If not
|
piccolo/custom_types.py
CHANGED
@@ -1,19 +1,41 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
import
|
3
|
+
import datetime
|
4
|
+
import decimal
|
5
|
+
import uuid
|
6
|
+
from collections.abc import Iterable
|
7
|
+
from typing import TYPE_CHECKING, Any, TypeVar, Union
|
4
8
|
|
5
|
-
|
9
|
+
from typing_extensions import TypeAlias
|
10
|
+
|
11
|
+
if TYPE_CHECKING: # pragma: no cover
|
6
12
|
from piccolo.columns.combination import And, Or, Where, WhereRaw # noqa
|
7
13
|
from piccolo.table import Table
|
8
14
|
|
9
15
|
|
10
|
-
Combinable =
|
11
|
-
|
16
|
+
Combinable = Union["Where", "WhereRaw", "And", "Or"]
|
17
|
+
CustomIterable = Iterable[Any]
|
18
|
+
|
12
19
|
|
20
|
+
TableInstance = TypeVar("TableInstance", bound="Table")
|
21
|
+
QueryResponseType = TypeVar("QueryResponseType", bound=Any)
|
13
22
|
|
14
|
-
TableInstance = t.TypeVar("TableInstance", bound="Table")
|
15
|
-
QueryResponseType = t.TypeVar("QueryResponseType", bound=t.Any)
|
16
23
|
|
24
|
+
# These are types we can reasonably expect to send to the database.
|
25
|
+
BasicTypes: TypeAlias = Union[
|
26
|
+
bytes,
|
27
|
+
datetime.date,
|
28
|
+
datetime.datetime,
|
29
|
+
datetime.time,
|
30
|
+
datetime.timedelta,
|
31
|
+
decimal.Decimal,
|
32
|
+
dict,
|
33
|
+
float,
|
34
|
+
int,
|
35
|
+
list,
|
36
|
+
str,
|
37
|
+
uuid.UUID,
|
38
|
+
]
|
17
39
|
|
18
40
|
###############################################################################
|
19
41
|
# For backwards compatibility:
|
piccolo/engine/base.py
CHANGED
@@ -4,8 +4,8 @@ import contextvars
|
|
4
4
|
import logging
|
5
5
|
import pprint
|
6
6
|
import string
|
7
|
-
import typing as t
|
8
7
|
from abc import ABCMeta, abstractmethod
|
8
|
+
from typing import TYPE_CHECKING, Final, Generic, Optional, TypeVar, Union
|
9
9
|
|
10
10
|
from typing_extensions import Self
|
11
11
|
|
@@ -13,14 +13,14 @@ from piccolo.querystring import QueryString
|
|
13
13
|
from piccolo.utils.sync import run_sync
|
14
14
|
from piccolo.utils.warnings import Level, colored_string, colored_warning
|
15
15
|
|
16
|
-
if
|
16
|
+
if TYPE_CHECKING: # pragma: no cover
|
17
17
|
from piccolo.query.base import DDL, Query
|
18
18
|
|
19
19
|
|
20
20
|
logger = logging.getLogger(__name__)
|
21
21
|
# This is a set to speed up lookups from O(n) when
|
22
22
|
# using str vs O(1) when using set[str]
|
23
|
-
VALID_SAVEPOINT_CHARACTERS:
|
23
|
+
VALID_SAVEPOINT_CHARACTERS: Final[set[str]] = set(
|
24
24
|
string.ascii_letters + string.digits + "-" + "_"
|
25
25
|
)
|
26
26
|
|
@@ -45,12 +45,12 @@ class BaseBatch(metaclass=ABCMeta):
|
|
45
45
|
def __aiter__(self: Self) -> Self: ...
|
46
46
|
|
47
47
|
@abstractmethod
|
48
|
-
async def __anext__(self) ->
|
48
|
+
async def __anext__(self) -> list[dict]: ...
|
49
49
|
|
50
50
|
|
51
51
|
class BaseTransaction(metaclass=ABCMeta):
|
52
52
|
|
53
|
-
__slots__:
|
53
|
+
__slots__: tuple[str, ...] = tuple()
|
54
54
|
|
55
55
|
@abstractmethod
|
56
56
|
async def __aenter__(self, *args, **kwargs): ...
|
@@ -61,10 +61,10 @@ class BaseTransaction(metaclass=ABCMeta):
|
|
61
61
|
|
62
62
|
class BaseAtomic(metaclass=ABCMeta):
|
63
63
|
|
64
|
-
__slots__:
|
64
|
+
__slots__: tuple[str, ...] = tuple()
|
65
65
|
|
66
66
|
@abstractmethod
|
67
|
-
def add(self, *query:
|
67
|
+
def add(self, *query: Union[Query, DDL]): ...
|
68
68
|
|
69
69
|
@abstractmethod
|
70
70
|
async def run(self): ...
|
@@ -76,10 +76,10 @@ class BaseAtomic(metaclass=ABCMeta):
|
|
76
76
|
def __await__(self): ...
|
77
77
|
|
78
78
|
|
79
|
-
TransactionClass =
|
79
|
+
TransactionClass = TypeVar("TransactionClass", bound=BaseTransaction)
|
80
80
|
|
81
81
|
|
82
|
-
class Engine(
|
82
|
+
class Engine(Generic[TransactionClass], metaclass=ABCMeta):
|
83
83
|
__slots__ = (
|
84
84
|
"query_id",
|
85
85
|
"log_queries",
|
@@ -92,7 +92,7 @@ class Engine(t.Generic[TransactionClass], metaclass=ABCMeta):
|
|
92
92
|
def __init__(
|
93
93
|
self,
|
94
94
|
engine_type: str,
|
95
|
-
min_version_number:
|
95
|
+
min_version_number: Union[int, float],
|
96
96
|
log_queries: bool = False,
|
97
97
|
log_responses: bool = False,
|
98
98
|
):
|
@@ -122,7 +122,7 @@ class Engine(t.Generic[TransactionClass], metaclass=ABCMeta):
|
|
122
122
|
self,
|
123
123
|
query: Query,
|
124
124
|
batch_size: int = 100,
|
125
|
-
node:
|
125
|
+
node: Optional[str] = None,
|
126
126
|
) -> BaseBatch:
|
127
127
|
pass
|
128
128
|
|
@@ -132,7 +132,7 @@ class Engine(t.Generic[TransactionClass], metaclass=ABCMeta):
|
|
132
132
|
):
|
133
133
|
pass
|
134
134
|
|
135
|
-
def transform_response_to_dicts(self, results) ->
|
135
|
+
def transform_response_to_dicts(self, results) -> list[dict]:
|
136
136
|
"""
|
137
137
|
If the database adapter returns something other than a list of
|
138
138
|
dictionaries, it should perform the transformation here.
|
@@ -196,7 +196,7 @@ class Engine(t.Generic[TransactionClass], metaclass=ABCMeta):
|
|
196
196
|
|
197
197
|
###########################################################################
|
198
198
|
|
199
|
-
current_transaction: contextvars.ContextVar[
|
199
|
+
current_transaction: contextvars.ContextVar[Optional[TransactionClass]]
|
200
200
|
|
201
201
|
def transaction_exists(self) -> bool:
|
202
202
|
"""
|
@@ -222,7 +222,7 @@ class Engine(t.Generic[TransactionClass], metaclass=ABCMeta):
|
|
222
222
|
print(colored_string(f"\nQuery {query_id}:"))
|
223
223
|
print(query)
|
224
224
|
|
225
|
-
def print_response(self, query_id: int, response:
|
225
|
+
def print_response(self, query_id: int, response: list):
|
226
226
|
print(
|
227
227
|
colored_string(f"\nQuery {query_id} response:", level=Level.high)
|
228
228
|
)
|
piccolo/engine/cockroach.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
|
3
|
+
from collections.abc import Sequence
|
4
|
+
from typing import Any, Optional
|
4
5
|
|
5
6
|
from piccolo.utils.lazy_loader import LazyLoader
|
6
7
|
from piccolo.utils.warnings import Level, colored_warning
|
@@ -18,11 +19,11 @@ class CockroachEngine(PostgresEngine):
|
|
18
19
|
|
19
20
|
def __init__(
|
20
21
|
self,
|
21
|
-
config:
|
22
|
-
extensions:
|
22
|
+
config: dict[str, Any],
|
23
|
+
extensions: Sequence[str] = (),
|
23
24
|
log_queries: bool = False,
|
24
25
|
log_responses: bool = False,
|
25
|
-
extra_nodes:
|
26
|
+
extra_nodes: Optional[dict[str, CockroachEngine]] = None,
|
26
27
|
) -> None:
|
27
28
|
super().__init__(
|
28
29
|
config=config,
|
piccolo/engine/finder.py
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
|
3
|
+
from typing import Optional
|
4
4
|
|
5
5
|
from piccolo.engine.base import Engine
|
6
6
|
|
7
7
|
|
8
|
-
def engine_finder(module_name:
|
8
|
+
def engine_finder(module_name: Optional[str] = None) -> Optional[Engine]:
|
9
9
|
"""
|
10
10
|
An example module name is `my_piccolo_conf`.
|
11
11
|
|
piccolo/engine/postgres.py
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import contextvars
|
4
|
-
|
4
|
+
from collections.abc import Sequence
|
5
5
|
from dataclasses import dataclass
|
6
|
+
from typing import TYPE_CHECKING, Any, Mapping, Optional, Union
|
6
7
|
|
7
8
|
from typing_extensions import Self
|
8
9
|
|
@@ -22,7 +23,7 @@ from piccolo.utils.warnings import Level, colored_warning
|
|
22
23
|
|
23
24
|
asyncpg = LazyLoader("asyncpg", globals(), "asyncpg")
|
24
25
|
|
25
|
-
if
|
26
|
+
if TYPE_CHECKING: # pragma: no cover
|
26
27
|
from asyncpg.connection import Connection
|
27
28
|
from asyncpg.cursor import Cursor
|
28
29
|
from asyncpg.pool import Pool
|
@@ -36,8 +37,8 @@ class AsyncBatch(BaseBatch):
|
|
36
37
|
batch_size: int
|
37
38
|
|
38
39
|
# Set internally
|
39
|
-
_transaction:
|
40
|
-
_cursor:
|
40
|
+
_transaction: Optional[Transaction] = None
|
41
|
+
_cursor: Optional[Cursor] = None
|
41
42
|
|
42
43
|
@property
|
43
44
|
def cursor(self) -> Cursor:
|
@@ -51,14 +52,14 @@ class AsyncBatch(BaseBatch):
|
|
51
52
|
raise ValueError("The transaction can't be found.")
|
52
53
|
return self._transaction
|
53
54
|
|
54
|
-
async def next(self) ->
|
55
|
+
async def next(self) -> list[dict]:
|
55
56
|
data = await self.cursor.fetch(self.batch_size)
|
56
57
|
return await self.query._process_results(data)
|
57
58
|
|
58
59
|
def __aiter__(self: Self) -> Self:
|
59
60
|
return self
|
60
61
|
|
61
|
-
async def __anext__(self) ->
|
62
|
+
async def __anext__(self) -> list[dict]:
|
62
63
|
response = await self.next()
|
63
64
|
if response == []:
|
64
65
|
raise StopAsyncIteration()
|
@@ -107,9 +108,9 @@ class Atomic(BaseAtomic):
|
|
107
108
|
|
108
109
|
def __init__(self, engine: PostgresEngine):
|
109
110
|
self.engine = engine
|
110
|
-
self.queries:
|
111
|
+
self.queries: list[Union[Query, DDL]] = []
|
111
112
|
|
112
|
-
def add(self, *query:
|
113
|
+
def add(self, *query: Union[Query, DDL]):
|
113
114
|
self.queries += list(query)
|
114
115
|
|
115
116
|
async def run(self):
|
@@ -250,7 +251,7 @@ class PostgresTransaction(BaseTransaction):
|
|
250
251
|
self._savepoint_id += 1
|
251
252
|
return self._savepoint_id
|
252
253
|
|
253
|
-
async def savepoint(self, name:
|
254
|
+
async def savepoint(self, name: Optional[str] = None) -> Savepoint:
|
254
255
|
name = name or f"savepoint_{self.get_savepoint_id()}"
|
255
256
|
validate_savepoint_name(name)
|
256
257
|
await self.connection.execute(f"SAVEPOINT {name}")
|
@@ -352,11 +353,11 @@ class PostgresEngine(Engine[PostgresTransaction]):
|
|
352
353
|
|
353
354
|
def __init__(
|
354
355
|
self,
|
355
|
-
config:
|
356
|
-
extensions:
|
356
|
+
config: dict[str, Any],
|
357
|
+
extensions: Sequence[str] = ("uuid-ossp",),
|
357
358
|
log_queries: bool = False,
|
358
359
|
log_responses: bool = False,
|
359
|
-
extra_nodes:
|
360
|
+
extra_nodes: Optional[Mapping[str, PostgresEngine]] = None,
|
360
361
|
) -> None:
|
361
362
|
if extra_nodes is None:
|
362
363
|
extra_nodes = {}
|
@@ -366,7 +367,7 @@ class PostgresEngine(Engine[PostgresTransaction]):
|
|
366
367
|
self.log_queries = log_queries
|
367
368
|
self.log_responses = log_responses
|
368
369
|
self.extra_nodes = extra_nodes
|
369
|
-
self.pool:
|
370
|
+
self.pool: Optional[Pool] = None
|
370
371
|
database_name = config.get("database", "Unknown")
|
371
372
|
self.current_transaction = contextvars.ContextVar(
|
372
373
|
f"pg_current_transaction_{database_name}", default=None
|
@@ -396,7 +397,7 @@ class PostgresEngine(Engine[PostgresTransaction]):
|
|
396
397
|
Returns the version of Postgres being run.
|
397
398
|
"""
|
398
399
|
try:
|
399
|
-
response:
|
400
|
+
response: Sequence[dict] = await self._run_in_new_connection(
|
400
401
|
"SHOW server_version"
|
401
402
|
)
|
402
403
|
except ConnectionRefusedError as exception:
|
@@ -483,7 +484,7 @@ class PostgresEngine(Engine[PostgresTransaction]):
|
|
483
484
|
self,
|
484
485
|
query: Query,
|
485
486
|
batch_size: int = 100,
|
486
|
-
node:
|
487
|
+
node: Optional[str] = None,
|
487
488
|
) -> AsyncBatch:
|
488
489
|
"""
|
489
490
|
:param query:
|
@@ -494,7 +495,7 @@ class PostgresEngine(Engine[PostgresTransaction]):
|
|
494
495
|
Which node to run the query on (see ``extra_nodes``). If not
|
495
496
|
specified, it runs on the main Postgres node.
|
496
497
|
"""
|
497
|
-
engine:
|
498
|
+
engine: Any = self.extra_nodes.get(node) if node else self
|
498
499
|
connection = await engine.get_new_connection()
|
499
500
|
return AsyncBatch(
|
500
501
|
connection=connection, query=query, batch_size=batch_size
|
@@ -503,7 +504,7 @@ class PostgresEngine(Engine[PostgresTransaction]):
|
|
503
504
|
###########################################################################
|
504
505
|
|
505
506
|
async def _run_in_pool(
|
506
|
-
self, query: str, args:
|
507
|
+
self, query: str, args: Optional[Sequence[Any]] = None
|
507
508
|
):
|
508
509
|
if args is None:
|
509
510
|
args = []
|
@@ -516,7 +517,7 @@ class PostgresEngine(Engine[PostgresTransaction]):
|
|
516
517
|
return response
|
517
518
|
|
518
519
|
async def _run_in_new_connection(
|
519
|
-
self, query: str, args:
|
520
|
+
self, query: str, args: Optional[Sequence[Any]] = None
|
520
521
|
):
|
521
522
|
if args is None:
|
522
523
|
args = []
|
@@ -579,7 +580,7 @@ class PostgresEngine(Engine[PostgresTransaction]):
|
|
579
580
|
|
580
581
|
return response
|
581
582
|
|
582
|
-
def transform_response_to_dicts(self, results) ->
|
583
|
+
def transform_response_to_dicts(self, results) -> list[dict]:
|
583
584
|
"""
|
584
585
|
asyncpg returns a special Record object, so we need to convert it to
|
585
586
|
a dict.
|