piccolo 1.24.1__py3-none-any.whl → 1.25.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 +47 -9
- piccolo/apps/app/commands/templates/piccolo_app.py.jinja +3 -2
- piccolo/apps/asgi/commands/templates/app/_esmerald_app.py.jinja +1 -1
- piccolo/columns/combination.py +5 -5
- piccolo/conf/apps.py +125 -5
- piccolo/query/methods/delete.py +1 -1
- piccolo/query/methods/update.py +1 -1
- piccolo/table.py +2 -0
- {piccolo-1.24.1.dist-info → piccolo-1.25.0.dist-info}/METADATA +1 -1
- {piccolo-1.24.1.dist-info → piccolo-1.25.0.dist-info}/RECORD +19 -19
- {piccolo-1.24.1.dist-info → piccolo-1.25.0.dist-info}/WHEEL +1 -1
- tests/apps/app/commands/test_new.py +40 -1
- tests/conf/test_apps.py +49 -1
- tests/table/instance/test_remove.py +2 -0
- tests/table/test_delete.py +15 -0
- {piccolo-1.24.1.dist-info → piccolo-1.25.0.dist-info}/entry_points.txt +0 -0
- {piccolo-1.24.1.dist-info → piccolo-1.25.0.dist-info}/licenses/LICENSE +0 -0
- {piccolo-1.24.1.dist-info → piccolo-1.25.0.dist-info}/top_level.txt +0 -0
piccolo/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__VERSION__ = "1.
|
1
|
+
__VERSION__ = "1.25.0"
|
piccolo/apps/app/commands/new.py
CHANGED
@@ -2,12 +2,16 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import importlib
|
4
4
|
import os
|
5
|
+
import pathlib
|
6
|
+
import string
|
5
7
|
import sys
|
6
8
|
import typing as t
|
7
9
|
|
8
10
|
import black
|
9
11
|
import jinja2
|
10
12
|
|
13
|
+
from piccolo.conf.apps import PiccoloConfUpdater
|
14
|
+
|
11
15
|
TEMPLATE_DIRECTORY = os.path.join(
|
12
16
|
os.path.dirname(os.path.abspath(__file__)), "templates"
|
13
17
|
)
|
@@ -30,13 +34,36 @@ def module_exists(module_name: str) -> bool:
|
|
30
34
|
return True
|
31
35
|
|
32
36
|
|
33
|
-
|
34
|
-
print(f"Creating {app_name} app ...")
|
37
|
+
APP_NAME_ALLOWED_CHARACTERS = [*string.ascii_lowercase, *string.digits, "_"]
|
35
38
|
|
36
|
-
app_root = os.path.join(root, app_name)
|
37
39
|
|
38
|
-
|
39
|
-
|
40
|
+
def validate_app_name(app_name: str):
|
41
|
+
"""
|
42
|
+
Make sure the app name is something which is a valid Python package name.
|
43
|
+
|
44
|
+
:raises ValueError:
|
45
|
+
If ``app_name`` isn't valid.
|
46
|
+
|
47
|
+
"""
|
48
|
+
for char in app_name:
|
49
|
+
if not char.lower() in APP_NAME_ALLOWED_CHARACTERS:
|
50
|
+
raise ValueError(
|
51
|
+
f"The app name contains a disallowed character: `{char}`. "
|
52
|
+
"It must only include a-z, 0-9, and _ characters."
|
53
|
+
)
|
54
|
+
|
55
|
+
|
56
|
+
def get_app_module(app_name: str, root: str) -> str:
|
57
|
+
return ".".join([*pathlib.Path(root).parts, app_name, "piccolo_app"])
|
58
|
+
|
59
|
+
|
60
|
+
def new_app(app_name: str, root: str = ".", register: bool = False):
|
61
|
+
print(f"Creating {app_name} app ...")
|
62
|
+
|
63
|
+
try:
|
64
|
+
validate_app_name(app_name=app_name)
|
65
|
+
except ValueError as exception:
|
66
|
+
sys.exit(str(exception))
|
40
67
|
|
41
68
|
if module_exists(app_name):
|
42
69
|
sys.exit(
|
@@ -44,7 +71,12 @@ def new_app(app_name: str, root: str = "."):
|
|
44
71
|
"Python module. Please choose a different name for your app."
|
45
72
|
)
|
46
73
|
|
47
|
-
os.
|
74
|
+
app_root = os.path.join(root, app_name)
|
75
|
+
|
76
|
+
if os.path.exists(app_root):
|
77
|
+
sys.exit("Folder already exists - exiting.")
|
78
|
+
|
79
|
+
os.makedirs(app_root)
|
48
80
|
|
49
81
|
with open(os.path.join(app_root, "__init__.py"), "w"):
|
50
82
|
pass
|
@@ -69,16 +101,22 @@ def new_app(app_name: str, root: str = "."):
|
|
69
101
|
with open(os.path.join(migrations_folder_path, "__init__.py"), "w"):
|
70
102
|
pass
|
71
103
|
|
104
|
+
if register:
|
105
|
+
app_module = get_app_module(app_name=app_name, root=root)
|
106
|
+
PiccoloConfUpdater().register_app(app_module=app_module)
|
107
|
+
|
72
108
|
|
73
|
-
def new(app_name: str, root: str = "."):
|
109
|
+
def new(app_name: str, root: str = ".", register: bool = False):
|
74
110
|
"""
|
75
111
|
Creates a new Piccolo app.
|
76
112
|
|
77
113
|
:param app_name:
|
78
114
|
The name of the new app.
|
79
115
|
:param root:
|
80
|
-
Where to create the app e.g.
|
116
|
+
Where to create the app e.g. ./my/folder. By default it creates the
|
81
117
|
app in the current directory.
|
118
|
+
:param register:
|
119
|
+
If True, the app is registered automatically in piccolo_conf.py.
|
82
120
|
|
83
121
|
"""
|
84
|
-
new_app(app_name=app_name, root=root)
|
122
|
+
new_app(app_name=app_name, root=root, register=register)
|
@@ -5,7 +5,7 @@ the APP_CONFIG.
|
|
5
5
|
|
6
6
|
import os
|
7
7
|
|
8
|
-
from piccolo.conf.apps import AppConfig, table_finder
|
8
|
+
from piccolo.conf.apps import AppConfig, table_finder, get_package
|
9
9
|
|
10
10
|
|
11
11
|
CURRENT_DIRECTORY = os.path.dirname(os.path.abspath(__file__))
|
@@ -18,7 +18,8 @@ APP_CONFIG = AppConfig(
|
|
18
18
|
'piccolo_migrations'
|
19
19
|
),
|
20
20
|
table_classes=table_finder(
|
21
|
-
modules=["
|
21
|
+
modules=[".tables"],
|
22
|
+
package=get_package(__name__),
|
22
23
|
exclude_imported=True
|
23
24
|
),
|
24
25
|
migration_dependencies=[],
|
@@ -12,7 +12,7 @@ from esmerald import (
|
|
12
12
|
post,
|
13
13
|
put,
|
14
14
|
)
|
15
|
-
from esmerald.config import StaticFilesConfig
|
15
|
+
from esmerald.core.config import StaticFilesConfig
|
16
16
|
from piccolo.engine import engine_finder
|
17
17
|
from piccolo.utils.pydantic import create_pydantic_model
|
18
18
|
from piccolo_admin.endpoints import create_admin
|
piccolo/columns/combination.py
CHANGED
@@ -45,11 +45,11 @@ class Combination(CombinableMixin):
|
|
45
45
|
)
|
46
46
|
|
47
47
|
@property
|
48
|
-
def
|
48
|
+
def querystring_for_update_and_delete(self) -> QueryString:
|
49
49
|
return QueryString(
|
50
50
|
"({} " + self.operator + " {})",
|
51
|
-
self.first.
|
52
|
-
self.second.
|
51
|
+
self.first.querystring_for_update_and_delete,
|
52
|
+
self.second.querystring_for_update_and_delete,
|
53
53
|
)
|
54
54
|
|
55
55
|
def __str__(self):
|
@@ -131,7 +131,7 @@ class WhereRaw(CombinableMixin):
|
|
131
131
|
self.querystring = QueryString(sql, *args)
|
132
132
|
|
133
133
|
@property
|
134
|
-
def
|
134
|
+
def querystring_for_update_and_delete(self) -> QueryString:
|
135
135
|
return self.querystring
|
136
136
|
|
137
137
|
def __str__(self):
|
@@ -218,7 +218,7 @@ class Where(CombinableMixin):
|
|
218
218
|
return QueryString(template, *args)
|
219
219
|
|
220
220
|
@property
|
221
|
-
def
|
221
|
+
def querystring_for_update_and_delete(self) -> QueryString:
|
222
222
|
args: t.List[t.Any] = []
|
223
223
|
if self.value != UNDEFINED:
|
224
224
|
args.append(self.value)
|
piccolo/conf/apps.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
import ast
|
3
4
|
import inspect
|
4
5
|
import itertools
|
5
6
|
import os
|
@@ -11,6 +12,8 @@ from dataclasses import dataclass, field
|
|
11
12
|
from importlib import import_module
|
12
13
|
from types import ModuleType
|
13
14
|
|
15
|
+
import black
|
16
|
+
|
14
17
|
from piccolo.apps.migrations.auto.migration_manager import MigrationManager
|
15
18
|
from piccolo.engine.base import Engine
|
16
19
|
from piccolo.table import Table
|
@@ -32,8 +35,18 @@ class PiccoloAppModule(ModuleType):
|
|
32
35
|
APP_CONFIG: AppConfig
|
33
36
|
|
34
37
|
|
38
|
+
def get_package(name: str) -> str:
|
39
|
+
"""
|
40
|
+
:param name:
|
41
|
+
The __name__ variable from a Python file.
|
42
|
+
|
43
|
+
"""
|
44
|
+
return ".".join(name.split(".")[:-1])
|
45
|
+
|
46
|
+
|
35
47
|
def table_finder(
|
36
48
|
modules: t.Sequence[str],
|
49
|
+
package: t.Optional[str] = None,
|
37
50
|
include_tags: t.Optional[t.Sequence[str]] = None,
|
38
51
|
exclude_tags: t.Optional[t.Sequence[str]] = None,
|
39
52
|
exclude_imported: bool = False,
|
@@ -46,8 +59,10 @@ def table_finder(
|
|
46
59
|
|
47
60
|
:param modules:
|
48
61
|
The module paths to check for ``Table`` subclasses. For example,
|
49
|
-
``['blog.tables']``.
|
50
|
-
|
62
|
+
``['blog.tables']``.
|
63
|
+
:param package:
|
64
|
+
This must be passed in if the modules are relative paths (e.g.
|
65
|
+
if ``modules=['.tables']`` then ``package='blog'``).
|
51
66
|
:param include_tags:
|
52
67
|
If the ``Table`` subclass has one of these tags, it will be
|
53
68
|
imported. The special tag ``'__all__'`` will import all ``Table``
|
@@ -83,10 +98,19 @@ def table_finder(
|
|
83
98
|
table_subclasses: t.List[t.Type[Table]] = []
|
84
99
|
|
85
100
|
for module_path in modules:
|
101
|
+
full_module_path = (
|
102
|
+
".".join([package, module_path.lstrip(".")])
|
103
|
+
if package
|
104
|
+
else module_path
|
105
|
+
)
|
106
|
+
|
86
107
|
try:
|
87
|
-
module = import_module(
|
108
|
+
module = import_module(
|
109
|
+
module_path,
|
110
|
+
package=package,
|
111
|
+
)
|
88
112
|
except ImportError as exception:
|
89
|
-
print(f"Unable to import {
|
113
|
+
print(f"Unable to import {full_module_path}")
|
90
114
|
raise exception from exception
|
91
115
|
|
92
116
|
object_names = [i for i in dir(module) if not i.startswith("_")]
|
@@ -100,7 +124,7 @@ def table_finder(
|
|
100
124
|
):
|
101
125
|
table: Table = _object # type: ignore
|
102
126
|
|
103
|
-
if exclude_imported and table.__module__ !=
|
127
|
+
if exclude_imported and table.__module__ != full_module_path:
|
104
128
|
continue
|
105
129
|
|
106
130
|
if exclude_tags and set(table._meta.tags).intersection(
|
@@ -416,6 +440,17 @@ class Finder:
|
|
416
440
|
else:
|
417
441
|
return module
|
418
442
|
|
443
|
+
def get_piccolo_conf_path(self) -> str:
|
444
|
+
piccolo_conf_module = self.get_piccolo_conf_module()
|
445
|
+
|
446
|
+
if piccolo_conf_module is None:
|
447
|
+
raise ModuleNotFoundError("piccolo_conf.py not found.")
|
448
|
+
|
449
|
+
module_file_path = piccolo_conf_module.__file__
|
450
|
+
assert module_file_path
|
451
|
+
|
452
|
+
return module_file_path
|
453
|
+
|
419
454
|
def get_app_registry(self) -> AppRegistry:
|
420
455
|
"""
|
421
456
|
Returns the ``AppRegistry`` instance within piccolo_conf.
|
@@ -562,3 +597,88 @@ class Finder:
|
|
562
597
|
tables.extend(app_config.table_classes)
|
563
598
|
|
564
599
|
return tables
|
600
|
+
|
601
|
+
|
602
|
+
###############################################################################
|
603
|
+
|
604
|
+
|
605
|
+
class PiccoloConfUpdater:
|
606
|
+
|
607
|
+
def __init__(self, piccolo_conf_path: t.Optional[str] = None):
|
608
|
+
"""
|
609
|
+
:param piccolo_conf_path:
|
610
|
+
The path to the piccolo_conf.py (e.g. `./piccolo_conf.py`). If not
|
611
|
+
passed in, we use our ``Finder`` class to get it.
|
612
|
+
"""
|
613
|
+
self.piccolo_conf_path = (
|
614
|
+
piccolo_conf_path or Finder().get_piccolo_conf_path()
|
615
|
+
)
|
616
|
+
|
617
|
+
def _modify_app_registry_src(self, src: str, app_module: str) -> str:
|
618
|
+
"""
|
619
|
+
:param src:
|
620
|
+
The contents of the ``piccolo_conf.py`` file.
|
621
|
+
:param app_module:
|
622
|
+
The app to add to the registry e.g. ``'music.piccolo_app'``.
|
623
|
+
:returns:
|
624
|
+
Updated Python source code string.
|
625
|
+
|
626
|
+
"""
|
627
|
+
ast_root = ast.parse(src)
|
628
|
+
|
629
|
+
parsing_successful = False
|
630
|
+
|
631
|
+
for node in ast.walk(ast_root):
|
632
|
+
if isinstance(node, ast.Call):
|
633
|
+
if (
|
634
|
+
isinstance(node.func, ast.Name)
|
635
|
+
and node.func.id == "AppRegistry"
|
636
|
+
):
|
637
|
+
if len(node.keywords) > 0:
|
638
|
+
keyword = node.keywords[0]
|
639
|
+
if keyword.arg == "apps":
|
640
|
+
apps = keyword.value
|
641
|
+
if isinstance(apps, ast.List):
|
642
|
+
apps.elts.append(
|
643
|
+
ast.Constant(app_module, kind="str")
|
644
|
+
)
|
645
|
+
parsing_successful = True
|
646
|
+
break
|
647
|
+
|
648
|
+
if not parsing_successful:
|
649
|
+
raise SyntaxError(
|
650
|
+
"Unable to parse piccolo_conf.py - `AppRegistry(apps=...)` "
|
651
|
+
"not found)."
|
652
|
+
)
|
653
|
+
|
654
|
+
new_contents = ast.unparse(ast_root)
|
655
|
+
|
656
|
+
formatted_contents = black.format_str(
|
657
|
+
new_contents, mode=black.FileMode(line_length=80)
|
658
|
+
)
|
659
|
+
|
660
|
+
return formatted_contents
|
661
|
+
|
662
|
+
def register_app(self, app_module: str):
|
663
|
+
"""
|
664
|
+
Adds the given app to the ``AppRegistry`` in ``piccolo_conf.py``.
|
665
|
+
|
666
|
+
This is used by command line tools like:
|
667
|
+
|
668
|
+
.. code-block:: bash
|
669
|
+
|
670
|
+
piccolo app new my_app --register
|
671
|
+
|
672
|
+
:param app_module:
|
673
|
+
The module of the app, e.g. ``'music.piccolo_app'``.
|
674
|
+
|
675
|
+
"""
|
676
|
+
with open(self.piccolo_conf_path) as f:
|
677
|
+
piccolo_conf_src = f.read()
|
678
|
+
|
679
|
+
new_contents = self._modify_app_registry_src(
|
680
|
+
src=piccolo_conf_src, app_module=app_module
|
681
|
+
)
|
682
|
+
|
683
|
+
with open(self.piccolo_conf_path, "wt") as f:
|
684
|
+
f.write(new_contents)
|
piccolo/query/methods/delete.py
CHANGED
piccolo/query/methods/update.py
CHANGED
@@ -104,7 +104,7 @@ class Update(Query[TableInstance, t.List[t.Any]]):
|
|
104
104
|
querystring = QueryString(
|
105
105
|
"{} WHERE {}",
|
106
106
|
querystring,
|
107
|
-
self.where_delegate._where.
|
107
|
+
self.where_delegate._where.querystring_for_update_and_delete,
|
108
108
|
)
|
109
109
|
|
110
110
|
if self.returning_delegate._returning:
|
piccolo/table.py
CHANGED
@@ -1,18 +1,18 @@
|
|
1
|
-
piccolo/__init__.py,sha256=
|
1
|
+
piccolo/__init__.py,sha256=BvmUclKrZZEa9RXkZ0XNwqYKihj1aFvSDrl7jJkQmPc,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=kb7RYTvQZEyPsC4GH8vR2b_w35wnM-ita242S0_eyvQ,10013
|
6
6
|
piccolo/schema.py,sha256=qNNy4tG_HqnXR9t3hHMgYXtGxHabwQAhUpc6RKLJ_gE,7960
|
7
|
-
piccolo/table.py,sha256=
|
7
|
+
piccolo/table.py,sha256=vA4H5oebvpIsUYUpAIOFMqAG8wtNBWmu18zEmbfen0I,50629
|
8
8
|
piccolo/table_reflection.py,sha256=02baOSLX6f2LEo0kruFZYF_nPPTbIvaCTH_KPGe0DKw,7540
|
9
9
|
piccolo/apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
10
|
piccolo/apps/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
11
|
piccolo/apps/app/piccolo_app.py,sha256=8z2ITpxQQ-McxSYwQ5H_vyEnRXbY6cyAh2JSqhiylYk,340
|
12
12
|
piccolo/apps/app/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
|
-
piccolo/apps/app/commands/new.py,sha256=
|
13
|
+
piccolo/apps/app/commands/new.py,sha256=XzcVBBHiEezIt0VvyNezLDSgcSRDn5rmefdVulReCSk,3382
|
14
14
|
piccolo/apps/app/commands/show_all.py,sha256=46Hv3SipMT0YeMgAobU62O0mR7xvN1pn7xjU9Y2spKM,252
|
15
|
-
piccolo/apps/app/commands/templates/piccolo_app.py.jinja,sha256=
|
15
|
+
piccolo/apps/app/commands/templates/piccolo_app.py.jinja,sha256=QjaBEQucryT-vCv37Qm47I2hdirGxYE725WUGqF2WR0,592
|
16
16
|
piccolo/apps/app/commands/templates/tables.py.jinja,sha256=revzdrvDDwe78VedBKz0zYSwcsxyv2IURun6q6qmV1Y,32
|
17
17
|
piccolo/apps/asgi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
18
18
|
piccolo/apps/asgi/piccolo_app.py,sha256=7VUvqQJbB-ScO0A62S6MiJmQL9F5DS-SdlqlDLbAblE,217
|
@@ -20,7 +20,7 @@ piccolo/apps/asgi/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
|
|
20
20
|
piccolo/apps/asgi/commands/new.py,sha256=718mXx7XdDTN0CKK0ZB1WVMkOrQtVfqT5bqO1kDKnRk,4335
|
21
21
|
piccolo/apps/asgi/commands/templates/app/README.md.jinja,sha256=As3gNEZt9qcRmTVkjCzNtXJ8r4-3g0fCSe7Q-P39ezI,214
|
22
22
|
piccolo/apps/asgi/commands/templates/app/_blacksheep_app.py.jinja,sha256=IKOql1G5wrEKm5qErlizOmrwYKlnxkm-d8NY5uVg9KA,3186
|
23
|
-
piccolo/apps/asgi/commands/templates/app/_esmerald_app.py.jinja,sha256=
|
23
|
+
piccolo/apps/asgi/commands/templates/app/_esmerald_app.py.jinja,sha256=fIbD106Us4j2DBv_41PimvFRVw2FuZhOfc3xFPkNjuY,2715
|
24
24
|
piccolo/apps/asgi/commands/templates/app/_falcon_app.py.jinja,sha256=LOn3auJFeXNW48rtHzRbH3MzxWbRNhFib6Fm6wDS53E,1684
|
25
25
|
piccolo/apps/asgi/commands/templates/app/_fastapi_app.py.jinja,sha256=mKnYfUOnYyWJA1jFoRLCUOGQlK6imaxx_1qaauGjeeQ,2627
|
26
26
|
piccolo/apps/asgi/commands/templates/app/_lilya_app.py.jinja,sha256=PUph5Jj_AXVpxXZmpUzzHXogUchU8vjKBL_7WvgrfCU,1260
|
@@ -124,7 +124,7 @@ piccolo/columns/__init__.py,sha256=OYhO_n9anMiU9nL-K6ATq9FhAtm8RyMpqYQ7fTVbhxI,1
|
|
124
124
|
piccolo/columns/base.py,sha256=_bg9yMWjMwE76Z7RDqi9iYSmtRuFx5bkx9uYJsFHKjQ,32487
|
125
125
|
piccolo/columns/choices.py,sha256=-HNQuk9vMmVZIPZ5PMeXGTfr23o4nzKPSAkvcG1k0y8,723
|
126
126
|
piccolo/columns/column_types.py,sha256=Wo6g14aL1vpOFugsY-6n-q6JUJaKih-cIn9NBp-f3fI,84759
|
127
|
-
piccolo/columns/combination.py,sha256=
|
127
|
+
piccolo/columns/combination.py,sha256=NUOxmYcx84JW-2FcoF1XJVp_4R01aTJyl3waPzfZ4Tc,6955
|
128
128
|
piccolo/columns/indexes.py,sha256=NfNok3v_791jgDlN28KmhP9ZCjl6031BXmjxV3ovXJk,372
|
129
129
|
piccolo/columns/m2m.py,sha256=QMeSOnm4DT2cG9U5jC6sOZ6z9DxCWwDyZMSqk0wR2q4,14682
|
130
130
|
piccolo/columns/readable.py,sha256=hganxUPfIK5ZXn-qgteBxsOJfBJucgr9U0QLsLFYcuI,1562
|
@@ -143,7 +143,7 @@ piccolo/columns/operators/comparison.py,sha256=G7bI_O-EXqun_zHwbNcZ9z9gsY8OK-0oB
|
|
143
143
|
piccolo/columns/operators/math.py,sha256=knsUZzYOVdsFn3bTS0XC0ZzfNObeJcMvZ8Q_QwmGxjU,325
|
144
144
|
piccolo/columns/operators/string.py,sha256=M5ifxHP-ttJaE_wYCl23W5sJof4i5S5_QDIOv34VxDM,142
|
145
145
|
piccolo/conf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
146
|
-
piccolo/conf/apps.py,sha256=
|
146
|
+
piccolo/conf/apps.py,sha256=t5_Wn9A8_NRgSKmkkXqnq36yi-qucnpll41_XeX9nYE,22590
|
147
147
|
piccolo/engine/__init__.py,sha256=Z0QR5NAA9jTFenY7pEJv1C8jZXBaFZojBUR3z3nx1cw,283
|
148
148
|
piccolo/engine/base.py,sha256=4NISWRuvgp5ShqJbOEK6g8ok8Ijqtp2gzuM9J6o_wSU,6444
|
149
149
|
piccolo/engine/cockroach.py,sha256=gGnihplotMZMWqHwRnZYnnbKU3jFrwttwOlNtktoeLE,1522
|
@@ -167,7 +167,7 @@ piccolo/query/methods/alter.py,sha256=AI9YkJeip2EitrWJN_TDExXhA8HGAG3XuDz1NR-Kir
|
|
167
167
|
piccolo/query/methods/count.py,sha256=Vxn_7Ry-rleC6OGRxh-cLbuEMsy1DNjAZJThGED-_do,1748
|
168
168
|
piccolo/query/methods/create.py,sha256=hJ-6VVsWczzKDH6fQRN1WmYhcitixuXJ-eNOuCo_JgM,2742
|
169
169
|
piccolo/query/methods/create_index.py,sha256=gip_cRXZkLfpJqCL7KHk2l_7HLptoa-Ae8qu6I5d5c8,2224
|
170
|
-
piccolo/query/methods/delete.py,sha256=
|
170
|
+
piccolo/query/methods/delete.py,sha256=X11IjaQ4kIPGDtYGVQBqbkm3MP6fDI2WuP4h1ri4zSQ,2256
|
171
171
|
piccolo/query/methods/drop_index.py,sha256=5x3vHpoOmQ1SMhj6L7snKXX6M9l9j1E1PFSO6LMMkpY,1051
|
172
172
|
piccolo/query/methods/exists.py,sha256=lTMjtrFPFygZmaPV3sfQKXc3K0sVqJ2S6PDc3fRK6YQ,1203
|
173
173
|
piccolo/query/methods/indexes.py,sha256=J-QUqaBJwpgahskUH0Cu0Mq7zEKcfVAtDsUVIVX-C4c,943
|
@@ -177,7 +177,7 @@ piccolo/query/methods/raw.py,sha256=wQWR8b-yA_Gr-5lqRMZe9BOAAMBAw8CqTx37qVYvM1A,
|
|
177
177
|
piccolo/query/methods/refresh.py,sha256=wg1zghKfwz-VmqK4uWa4GNMiDtK-skTqow591Hb3ONM,5854
|
178
178
|
piccolo/query/methods/select.py,sha256=41OW-DIE_wr5VdxSusMKNT2aUhzQsCwK2Qh1XqgXHg0,22424
|
179
179
|
piccolo/query/methods/table_exists.py,sha256=0yb3n6Jd2ovSBWlZ-gl00K4E7Jnbj7J8qAAX5d7hvNk,1259
|
180
|
-
piccolo/query/methods/update.py,sha256=
|
180
|
+
piccolo/query/methods/update.py,sha256=KNrx5yzY3gshTNzXf392M7Aamz7TPZzZFHLhbDiEDu8,3716
|
181
181
|
piccolo/query/operators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
182
182
|
piccolo/query/operators/json.py,sha256=hdo1M6N9qTHJTJ0sRV9Bwt_iQZTgs4VdCKOPH1sXe-k,3168
|
183
183
|
piccolo/testing/__init__.py,sha256=pRFSqRInfx95AakOq54atmvqoB-ue073q2aR8u8zR40,83
|
@@ -199,13 +199,13 @@ piccolo/utils/sync.py,sha256=j9Abkxn5HHS6HyvfpMzb1zV_teTkFHVhaIxu9rrSwSU,819
|
|
199
199
|
piccolo/utils/warnings.py,sha256=ONrurw3HVCClUuHnpenMjg45dcFesrXqMgG9ifgP4_8,1247
|
200
200
|
piccolo/utils/graphlib/__init__.py,sha256=SUJ5Yh7LiRun3nkBuLUSVmGNHF6fANrxSoYan0mtYB0,200
|
201
201
|
piccolo/utils/graphlib/_graphlib.py,sha256=9FNGDSmTIEAk86FktniCe_J2yXjSE_sRZHDBAJJAUOw,9677
|
202
|
-
piccolo-1.
|
202
|
+
piccolo-1.25.0.dist-info/licenses/LICENSE,sha256=zFIpi-16uIJ420UMIG75NU0JbDBykvrdnXcj5U_EYBI,1059
|
203
203
|
profiling/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
204
204
|
profiling/run_profile.py,sha256=264qsSFu93NTpExePnKQ9GkcN5fiuRBQ72WOSt0ZHck,829
|
205
205
|
tests/apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
206
206
|
tests/apps/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
207
207
|
tests/apps/app/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
208
|
-
tests/apps/app/commands/test_new.py,sha256=
|
208
|
+
tests/apps/app/commands/test_new.py,sha256=Z4Vt3G60sVw6Ab7npn3PS5daq5BTWEzlHu7LcZIorfg,2194
|
209
209
|
tests/apps/app/commands/test_show_all.py,sha256=ca1afRhePYyVw-x2swxnoj659_qdu53uY8sVGIqBuNg,551
|
210
210
|
tests/apps/asgi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
211
211
|
tests/apps/asgi/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -288,7 +288,7 @@ tests/columns/m2m/test_m2m.py,sha256=0ObmIHUJF6CZoNBznc5xXVr5_BbGBqOmWwtpg8IcPt4
|
|
288
288
|
tests/columns/m2m/test_m2m_schema.py,sha256=oxu7eAjFFpDjnq9Eq-5OTNmlnsEIMFWx18OItfpVs-s,339
|
289
289
|
tests/conf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
290
290
|
tests/conf/example.py,sha256=K8sTttLpEac8rQlOLDY500IGkHj3P3NoyFbCMnT1EqY,347
|
291
|
-
tests/conf/test_apps.py,sha256=
|
291
|
+
tests/conf/test_apps.py,sha256=5Cl9zL_nJr5NDS7KiTIIne219RGSouxP2gWhQK99F7A,9925
|
292
292
|
tests/engine/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
293
293
|
tests/engine/test_extra_nodes.py,sha256=xW5gflHzM6ou26DqRSAZoaAbYVzF1IuMkW3vzNmB954,1298
|
294
294
|
tests/engine/test_logging.py,sha256=VLf9A3QuoV7OhV8lttLDB4gzZemnG63kSr-Uyan005U,1287
|
@@ -335,7 +335,7 @@ tests/table/test_count.py,sha256=qm4dwlQJ5gv8FPSsgYTS-3Gsd_KLgvWlFnmXweKydxw,229
|
|
335
335
|
tests/table/test_create.py,sha256=d-X7faDGI6NesvElu9JhrAo0Ialmrl6163Whd_LAUDQ,2887
|
336
336
|
tests/table/test_create_db_tables.py,sha256=9ZVlv9jGX5uneMfM5c2j1LlOphgorFNHN1cQ1pay4gM,903
|
337
337
|
tests/table/test_create_table_class.py,sha256=jOAxc38OhHBhdMzjsTOlYyzRjygj_5LXtsk46vmON_E,1745
|
338
|
-
tests/table/test_delete.py,sha256=
|
338
|
+
tests/table/test_delete.py,sha256=NTUv3Dgo4cZp2kmIId4eBug3eW1IYVEtvs4AaQS14iA,1630
|
339
339
|
tests/table/test_drop_db_tables.py,sha256=0a_aBZ8BMSLnu_DFXE_29X01B0jLdaa_WQ5_qTaZ5XY,1060
|
340
340
|
tests/table/test_exists.py,sha256=AHvhodkRof7PVd4IDdGQ2nyOj_1Cag1Rpg1H84s4jU0,283
|
341
341
|
tests/table/test_from_dict.py,sha256=I4PMxuzgkgi3-adaw9Gr3u5tQHexc31Vrq7RSPcPcJs,840
|
@@ -361,7 +361,7 @@ tests/table/instance/test_create.py,sha256=JD0l7L9dDK1FKPhUs6WC_B2bruPR1qQ8aIqXp
|
|
361
361
|
tests/table/instance/test_get_related.py,sha256=eracFunh4Qlj5BEkI7OsrOyefRZM0rxrXnFX92VL1ZE,3285
|
362
362
|
tests/table/instance/test_get_related_readable.py,sha256=QDMMZykxPsTWcsl8ZIZtmQVLwSGCw7QBilLepAAAnWg,4694
|
363
363
|
tests/table/instance/test_instantiate.py,sha256=jvtaqSa_zN1lHQiykN4EnwitZqkWAbXle5IJtyhKuHY,958
|
364
|
-
tests/table/instance/test_remove.py,sha256=
|
364
|
+
tests/table/instance/test_remove.py,sha256=P-8wWodlA2PukdWhaj8x2HYZI1U8Q7oIuFBe2sAkcdo,757
|
365
365
|
tests/table/instance/test_save.py,sha256=ccdDz-bR3aYDa16_RGQP7JTXprgm1mT6-NpF1y3RXyo,4388
|
366
366
|
tests/table/instance/test_to_dict.py,sha256=gkiYkmcI5qcy5E-ERWWmO-Q8uyVSFfcpJ8d53LlzCuI,3442
|
367
367
|
tests/testing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -380,8 +380,8 @@ tests/utils/test_sql_values.py,sha256=vzxRmy16FfLZPH-sAQexBvsF9MXB8n4smr14qoEOS5
|
|
380
380
|
tests/utils/test_sync.py,sha256=9ytVo56y2vPQePvTeIi9lHIouEhWJbodl1TmzkGFrSo,799
|
381
381
|
tests/utils/test_table_reflection.py,sha256=SIzuat-IpcVj1GCFyOWKShI8YkhdOPPFH7qVrvfyPNE,3794
|
382
382
|
tests/utils/test_warnings.py,sha256=NvSC_cvJ6uZcwAGf1m-hLzETXCqprXELL8zg3TNLVMw,269
|
383
|
-
piccolo-1.
|
384
|
-
piccolo-1.
|
385
|
-
piccolo-1.
|
386
|
-
piccolo-1.
|
387
|
-
piccolo-1.
|
383
|
+
piccolo-1.25.0.dist-info/METADATA,sha256=zdbTbYmUa-EhWuXfpt53Hr1RBENaNyiXBk80Ii_bFK8,5531
|
384
|
+
piccolo-1.25.0.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
|
385
|
+
piccolo-1.25.0.dist-info/entry_points.txt,sha256=SJPHET4Fi1bN5F3WqcKkv9SClK3_F1I7m4eQjk6AFh0,46
|
386
|
+
piccolo-1.25.0.dist-info/top_level.txt,sha256=-SR74VGbk43VoPy1HH-mHm97yoGukLK87HE5kdBW6qM,24
|
387
|
+
piccolo-1.25.0.dist-info/RECORD,,
|
@@ -3,7 +3,12 @@ import shutil
|
|
3
3
|
import tempfile
|
4
4
|
from unittest import TestCase
|
5
5
|
|
6
|
-
from piccolo.apps.app.commands.new import
|
6
|
+
from piccolo.apps.app.commands.new import (
|
7
|
+
get_app_module,
|
8
|
+
module_exists,
|
9
|
+
new,
|
10
|
+
validate_app_name,
|
11
|
+
)
|
7
12
|
|
8
13
|
|
9
14
|
class TestModuleExists(TestCase):
|
@@ -43,3 +48,37 @@ class TestNewApp(TestCase):
|
|
43
48
|
"A module called sys already exists"
|
44
49
|
)
|
45
50
|
)
|
51
|
+
|
52
|
+
|
53
|
+
class TestValidateAppName(TestCase):
|
54
|
+
|
55
|
+
def test_validate_app_name(self):
|
56
|
+
"""
|
57
|
+
Make sure only app names which work as valid Python package names are
|
58
|
+
allowed.
|
59
|
+
"""
|
60
|
+
# Should be rejected:
|
61
|
+
for app_name in ("MY APP", "app/my_app", "my.app"):
|
62
|
+
with self.assertRaises(ValueError):
|
63
|
+
validate_app_name(app_name=app_name)
|
64
|
+
|
65
|
+
# Should work fine:
|
66
|
+
validate_app_name(app_name="music")
|
67
|
+
|
68
|
+
|
69
|
+
class TestGetAppIdentifier(TestCase):
|
70
|
+
|
71
|
+
def test_get_app_module(self):
|
72
|
+
"""
|
73
|
+
Make sure the the ``root`` argument is handled correctly.
|
74
|
+
"""
|
75
|
+
self.assertEqual(
|
76
|
+
get_app_module(app_name="music", root="."),
|
77
|
+
"music.piccolo_app",
|
78
|
+
)
|
79
|
+
|
80
|
+
for root in ("apps", "./apps", "./apps/"):
|
81
|
+
self.assertEqual(
|
82
|
+
get_app_module(app_name="music", root=root),
|
83
|
+
"apps.music.piccolo_app",
|
84
|
+
)
|
tests/conf/test_apps.py
CHANGED
@@ -1,10 +1,17 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import pathlib
|
4
|
+
import tempfile
|
4
5
|
from unittest import TestCase
|
5
6
|
|
6
7
|
from piccolo.apps.user.tables import BaseUser
|
7
|
-
from piccolo.conf.apps import
|
8
|
+
from piccolo.conf.apps import (
|
9
|
+
AppConfig,
|
10
|
+
AppRegistry,
|
11
|
+
Finder,
|
12
|
+
PiccoloConfUpdater,
|
13
|
+
table_finder,
|
14
|
+
)
|
8
15
|
from tests.example_apps.mega.tables import MegaTable, SmallTable
|
9
16
|
from tests.example_apps.music.tables import (
|
10
17
|
Band,
|
@@ -310,3 +317,44 @@ class TestFinder(TestCase):
|
|
310
317
|
self.assertListEqual(
|
311
318
|
[i.app_name for i in sorted_app_configs], ["app_2", "app_1"]
|
312
319
|
)
|
320
|
+
|
321
|
+
|
322
|
+
class TestPiccoloConfUpdater(TestCase):
|
323
|
+
|
324
|
+
def test_modify_app_registry_src(self):
|
325
|
+
"""
|
326
|
+
Make sure the `piccolo_conf.py` source code can be modified
|
327
|
+
successfully.
|
328
|
+
"""
|
329
|
+
updater = PiccoloConfUpdater()
|
330
|
+
|
331
|
+
new_src = updater._modify_app_registry_src(
|
332
|
+
src="APP_REGISTRY = AppRegistry(apps=[])",
|
333
|
+
app_module="music.piccolo_app",
|
334
|
+
)
|
335
|
+
self.assertEqual(
|
336
|
+
new_src.strip(),
|
337
|
+
'APP_REGISTRY = AppRegistry(apps=["music.piccolo_app"])',
|
338
|
+
)
|
339
|
+
|
340
|
+
def test_register_app(self):
|
341
|
+
"""
|
342
|
+
Make sure the new contents get written to disk.
|
343
|
+
"""
|
344
|
+
temp_dir = tempfile.gettempdir()
|
345
|
+
piccolo_conf_path = pathlib.Path(temp_dir) / "piccolo_conf.py"
|
346
|
+
|
347
|
+
src = "APP_REGISTRY = AppRegistry(apps=[])"
|
348
|
+
|
349
|
+
with open(piccolo_conf_path, "wt") as f:
|
350
|
+
f.write(src)
|
351
|
+
|
352
|
+
updater = PiccoloConfUpdater(piccolo_conf_path=str(piccolo_conf_path))
|
353
|
+
updater.register_app(app_module="music.piccolo_app")
|
354
|
+
|
355
|
+
with open(piccolo_conf_path) as f:
|
356
|
+
contents = f.read().strip()
|
357
|
+
|
358
|
+
self.assertEqual(
|
359
|
+
contents, 'APP_REGISTRY = AppRegistry(apps=["music.piccolo_app"])'
|
360
|
+
)
|
@@ -17,9 +17,11 @@ class TestRemove(TestCase):
|
|
17
17
|
"Maz"
|
18
18
|
in Manager.select(Manager.name).output(as_list=True).run_sync()
|
19
19
|
)
|
20
|
+
self.assertEqual(manager._exists_in_db, True)
|
20
21
|
|
21
22
|
manager.remove().run_sync()
|
22
23
|
self.assertTrue(
|
23
24
|
"Maz"
|
24
25
|
not in Manager.select(Manager.name).output(as_list=True).run_sync()
|
25
26
|
)
|
27
|
+
self.assertEqual(manager._exists_in_db, False)
|
tests/table/test_delete.py
CHANGED
@@ -44,3 +44,18 @@ class TestDelete(DBTestCase):
|
|
44
44
|
Band.delete().run_sync()
|
45
45
|
|
46
46
|
Band.delete(force=True).run_sync()
|
47
|
+
|
48
|
+
def test_delete_with_joins(self):
|
49
|
+
"""
|
50
|
+
Make sure delete works if the `where` clause specifies joins.
|
51
|
+
"""
|
52
|
+
|
53
|
+
self.insert_rows()
|
54
|
+
|
55
|
+
Band.delete().where(Band.manager._.name == "Guido").run_sync()
|
56
|
+
|
57
|
+
response = (
|
58
|
+
Band.count().where(Band.manager._.name == "Guido").run_sync()
|
59
|
+
)
|
60
|
+
|
61
|
+
self.assertEqual(response, 0)
|
File without changes
|
File without changes
|
File without changes
|