plain 0.27.0__py3-none-any.whl → 0.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.
- plain/__main__.py +1 -1
- plain/cli/README.md +0 -16
- plain/cli/__init__.py +2 -2
- plain/cli/{cli.py → core.py} +10 -19
- plain/cli/registry.py +62 -0
- plain/packages/config.py +0 -2
- plain/runtime/__init__.py +15 -0
- plain/runtime/user_settings.py +0 -13
- {plain-0.27.0.dist-info → plain-0.29.0.dist-info}/METADATA +1 -1
- {plain-0.27.0.dist-info → plain-0.29.0.dist-info}/RECORD +13 -13
- plain-0.29.0.dist-info/entry_points.txt +2 -0
- plain/cli/packages.py +0 -94
- plain-0.27.0.dist-info/entry_points.txt +0 -2
- {plain-0.27.0.dist-info → plain-0.29.0.dist-info}/WHEEL +0 -0
- {plain-0.27.0.dist-info → plain-0.29.0.dist-info}/licenses/LICENSE +0 -0
plain/__main__.py
CHANGED
plain/cli/README.md
CHANGED
@@ -99,19 +99,3 @@ then any commands you defined.
|
|
99
99
|
$ plain <pkg> hello
|
100
100
|
Hello, world!
|
101
101
|
```
|
102
|
-
|
103
|
-
### Add CLI commands to published packages
|
104
|
-
|
105
|
-
Some packages, like [plain-dev](https://plainframework.com/docs/plain-dev/),
|
106
|
-
never show up in `INSTALLED_PACKAGES` but still have CLI commands.
|
107
|
-
These are detected via Python entry points.
|
108
|
-
|
109
|
-
An example with `pyproject.toml` and UV:
|
110
|
-
|
111
|
-
```toml
|
112
|
-
# pyproject.toml
|
113
|
-
[project.entry-points."plain.cli"]
|
114
|
-
"dev" = "plain.dev:cli"
|
115
|
-
"pre-commit" = "plain.dev.precommit:cli"
|
116
|
-
"contrib" = "plain.dev.contribute:cli"
|
117
|
-
```
|
plain/cli/__init__.py
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
from .
|
1
|
+
from .registry import register_cli
|
2
2
|
|
3
|
-
__all__ = ["
|
3
|
+
__all__ = ["register_cli"]
|
plain/cli/{cli.py → core.py}
RENAMED
@@ -1,4 +1,3 @@
|
|
1
|
-
import importlib
|
2
1
|
import os
|
3
2
|
import shutil
|
4
3
|
import subprocess
|
@@ -6,7 +5,6 @@ import sys
|
|
6
5
|
import tomllib
|
7
6
|
import traceback
|
8
7
|
from importlib.metadata import entry_points
|
9
|
-
from importlib.util import find_spec
|
10
8
|
from pathlib import Path
|
11
9
|
|
12
10
|
import click
|
@@ -20,7 +18,7 @@ from plain.packages import packages_registry
|
|
20
18
|
from plain.utils.crypto import get_random_string
|
21
19
|
|
22
20
|
from .formatting import PlainContext
|
23
|
-
from .
|
21
|
+
from .registry import cli_registry
|
24
22
|
|
25
23
|
|
26
24
|
@click.group()
|
@@ -500,25 +498,21 @@ def urls(flat):
|
|
500
498
|
print_tree(resolver.url_patterns)
|
501
499
|
|
502
500
|
|
503
|
-
class
|
501
|
+
class CLIRegistryGroup(click.Group):
|
504
502
|
"""
|
505
|
-
|
503
|
+
Click Group that exposes commands from the CLI registry.
|
506
504
|
"""
|
507
505
|
|
508
|
-
|
506
|
+
def __init__(self, *args, **kwargs):
|
507
|
+
super().__init__(*args, **kwargs)
|
508
|
+
cli_registry.import_modules()
|
509
509
|
|
510
510
|
def list_commands(self, ctx):
|
511
|
-
|
512
|
-
return ["app"]
|
513
|
-
else:
|
514
|
-
return []
|
511
|
+
return sorted(cli_registry.get_commands().keys())
|
515
512
|
|
516
513
|
def get_command(self, ctx, name):
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
cli = importlib.import_module(self.MODULE_NAME)
|
521
|
-
return cli.cli
|
514
|
+
commands = cli_registry.get_commands()
|
515
|
+
return commands.get(name)
|
522
516
|
|
523
517
|
|
524
518
|
class PlainCommandCollection(click.CommandCollection):
|
@@ -531,9 +525,7 @@ class PlainCommandCollection(click.CommandCollection):
|
|
531
525
|
plain.runtime.setup()
|
532
526
|
|
533
527
|
sources = [
|
534
|
-
|
535
|
-
EntryPointGroup(),
|
536
|
-
AppCLIGroup(),
|
528
|
+
CLIRegistryGroup(),
|
537
529
|
plain_cli,
|
538
530
|
]
|
539
531
|
except plain.runtime.AppPathNotFound:
|
@@ -545,7 +537,6 @@ class PlainCommandCollection(click.CommandCollection):
|
|
545
537
|
)
|
546
538
|
|
547
539
|
sources = [
|
548
|
-
EntryPointGroup(),
|
549
540
|
plain_cli,
|
550
541
|
]
|
551
542
|
except ImproperlyConfigured as e:
|
plain/cli/registry.py
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
from importlib import import_module
|
2
|
+
from importlib.util import find_spec
|
3
|
+
|
4
|
+
from plain.packages import packages_registry
|
5
|
+
|
6
|
+
|
7
|
+
class CLIRegistry:
|
8
|
+
def __init__(self):
|
9
|
+
self._commands = {}
|
10
|
+
|
11
|
+
def register_command(self, cmd, name):
|
12
|
+
"""
|
13
|
+
Register a CLI command or group with the specified name.
|
14
|
+
"""
|
15
|
+
self._commands[name] = cmd
|
16
|
+
|
17
|
+
def import_modules(self):
|
18
|
+
"""
|
19
|
+
Import modules from installed packages and app to trigger registration.
|
20
|
+
"""
|
21
|
+
# Import from installed packages
|
22
|
+
for package_config in packages_registry.get_package_configs():
|
23
|
+
import_name = f"{package_config.name}.cli"
|
24
|
+
try:
|
25
|
+
import_module(import_name)
|
26
|
+
except ModuleNotFoundError:
|
27
|
+
pass
|
28
|
+
|
29
|
+
# Import from app
|
30
|
+
import_name = "app.cli"
|
31
|
+
if find_spec(import_name):
|
32
|
+
try:
|
33
|
+
import_module(import_name)
|
34
|
+
except ModuleNotFoundError:
|
35
|
+
pass
|
36
|
+
|
37
|
+
def get_commands(self):
|
38
|
+
"""
|
39
|
+
Get all registered commands.
|
40
|
+
"""
|
41
|
+
return self._commands
|
42
|
+
|
43
|
+
|
44
|
+
cli_registry = CLIRegistry()
|
45
|
+
|
46
|
+
|
47
|
+
def register_cli(name):
|
48
|
+
"""
|
49
|
+
Register a CLI command or group with the given name.
|
50
|
+
|
51
|
+
Usage:
|
52
|
+
@register_cli("users")
|
53
|
+
@click.group()
|
54
|
+
def users_cli():
|
55
|
+
pass
|
56
|
+
"""
|
57
|
+
|
58
|
+
def wrapper(cmd):
|
59
|
+
cli_registry.register_command(cmd, name)
|
60
|
+
return cmd
|
61
|
+
|
62
|
+
return wrapper
|
plain/packages/config.py
CHANGED
@@ -10,8 +10,6 @@ CONFIG_MODULE_NAME = "config"
|
|
10
10
|
class PackageConfig:
|
11
11
|
"""Class representing a Plain application and its configuration."""
|
12
12
|
|
13
|
-
migrations_module = "migrations"
|
14
|
-
|
15
13
|
def __init__(self, name, *, label=""):
|
16
14
|
# Full Python path to the application e.g. 'plain.admin.admin'.
|
17
15
|
self.name = name
|
plain/runtime/__init__.py
CHANGED
@@ -51,9 +51,24 @@ def setup():
|
|
51
51
|
packages_registry.populate(settings.INSTALLED_PACKAGES)
|
52
52
|
|
53
53
|
|
54
|
+
class SettingsReference(str):
|
55
|
+
"""
|
56
|
+
String subclass which references a current settings value. It's treated as
|
57
|
+
the value in memory but serializes to a settings.NAME attribute reference.
|
58
|
+
"""
|
59
|
+
|
60
|
+
def __new__(self, setting_name):
|
61
|
+
value = getattr(settings, setting_name)
|
62
|
+
return str.__new__(self, value)
|
63
|
+
|
64
|
+
def __init__(self, setting_name):
|
65
|
+
self.setting_name = setting_name
|
66
|
+
|
67
|
+
|
54
68
|
__all__ = [
|
55
69
|
"setup",
|
56
70
|
"settings",
|
71
|
+
"SettingsReference",
|
57
72
|
"APP_PATH",
|
58
73
|
"__version__",
|
59
74
|
]
|
plain/runtime/user_settings.py
CHANGED
@@ -298,16 +298,3 @@ class SettingDefinition:
|
|
298
298
|
|
299
299
|
def __str__(self):
|
300
300
|
return f"SettingDefinition(name={self.name}, value={self.value}, source={self.source})"
|
301
|
-
|
302
|
-
|
303
|
-
class SettingsReference(str):
|
304
|
-
"""
|
305
|
-
String subclass which references a current settings value. It's treated as
|
306
|
-
the value in memory but serializes to a settings.NAME attribute reference.
|
307
|
-
"""
|
308
|
-
|
309
|
-
def __new__(self, value, setting_name):
|
310
|
-
return str.__new__(self, value)
|
311
|
-
|
312
|
-
def __init__(self, value, setting_name):
|
313
|
-
self.setting_name = setting_name
|
@@ -1,5 +1,5 @@
|
|
1
1
|
plain/README.md,sha256=nW3Ioj3IxPb6aoCGaFMN2n7Cd7LMx0s8Lph6pMkKnh4,8
|
2
|
-
plain/__main__.py,sha256=
|
2
|
+
plain/__main__.py,sha256=GK39854Lc_LO_JP8DzY9Y2MIQ4cQEl7SXFJy244-lC8,110
|
3
3
|
plain/debug.py,sha256=abPkJY4aSbBYGEYSZST_ZY3ohXPGDdz9uWQBYRqfd3M,730
|
4
4
|
plain/exceptions.py,sha256=Z9cbPE5im_Y-bjVq8cqC85gBoqOr80rLFG5wTKixrwE,5894
|
5
5
|
plain/json.py,sha256=McJdsbMT1sYwkGRG--f2NSZz0hVXPMix9x3nKaaak2o,1262
|
@@ -14,12 +14,12 @@ plain/assets/finders.py,sha256=rhkHG5QW3H3IlBGHB5WJf9J6VTdDWgUC0qEs6u2Z4RQ,1233
|
|
14
14
|
plain/assets/fingerprints.py,sha256=1NKAnnXVlncY5iimXztr0NL3RIjBKsNlZRIe6nmItJc,931
|
15
15
|
plain/assets/urls.py,sha256=lW7VzKNzTKY11JqbszhJQ1Yy0HtljZlsHDnnkTPdLOM,992
|
16
16
|
plain/assets/views.py,sha256=z7noLzoelGw_8-MXcvGKjXs9KZ43Tivmy2TIfnZIpgw,9253
|
17
|
-
plain/cli/README.md,sha256=
|
18
|
-
plain/cli/__init__.py,sha256=
|
19
|
-
plain/cli/
|
17
|
+
plain/cli/README.md,sha256=t3k4jmSK0QFALO3bVWTUsJC09KhY4CvauStTvVLLUdI,1922
|
18
|
+
plain/cli/__init__.py,sha256=6w9T7K2WrPwh6DcaMb2oNt_CWU6Bc57nUTO2Bt1p38Y,63
|
19
|
+
plain/cli/core.py,sha256=5_4hsXxzk2ocIez5BBY2YoYtFUdyfKks-2twpqy-zoU,18256
|
20
20
|
plain/cli/formatting.py,sha256=1hZH13y1qwHcU2K2_Na388nw9uvoeQH8LrWL-O9h8Yc,2207
|
21
|
-
plain/cli/packages.py,sha256=GLvDgQ1o93tSHae_B2i0YNimpt3LGu4QMQpFYrO48d8,2758
|
22
21
|
plain/cli/print.py,sha256=XraUYrgODOJquIiEv78wSCYGRBplHXtXSS9QtFG5hqY,217
|
22
|
+
plain/cli/registry.py,sha256=yKVMSDjW8g10nlV9sPXFGJQmhC_U-k4J4kM7N2OQVLA,1467
|
23
23
|
plain/cli/startup.py,sha256=3LIz9JrIZoF52Sa0j0SCypQwEaBDkhvuGaBdtiQLr5Q,680
|
24
24
|
plain/csrf/README.md,sha256=RXMWMtHmzf30gVVNOfj0kD4xlSqFIPgJh-n7dIciaEM,163
|
25
25
|
plain/csrf/middleware.py,sha256=FYhT7KPJ664Sm0nKjeej1OIXalvVTYiotQX3ytI0dfY,17417
|
@@ -61,7 +61,7 @@ plain/logs/loggers.py,sha256=iz9SYcwP9w5QAuwpULl48SFkVyJuuMoQ_fdLgdCHpNg,2121
|
|
61
61
|
plain/logs/utils.py,sha256=9UzdCCQXJinGDs71Ngw297mlWkhgZStSd67ya4NOW98,1257
|
62
62
|
plain/packages/README.md,sha256=Vq1Nw3mmEmZ2IriQavuVi4BjcQC2nb8k7YIbnm8QjIg,799
|
63
63
|
plain/packages/__init__.py,sha256=OpQny0xLplPdPpozVUUkrW2gB-IIYyDT1b4zMzOcCC4,160
|
64
|
-
plain/packages/config.py,sha256=
|
64
|
+
plain/packages/config.py,sha256=uOO7uE9jajqDhqFBafJQ3ZnfLmQiHikTzOSJ1AlP7ZM,3289
|
65
65
|
plain/packages/registry.py,sha256=Aklno7y7UrBZlidtUR_YO3B5xqF46UbUtalReNcYHm8,7937
|
66
66
|
plain/preflight/README.md,sha256=-PKVd0RBMh4ROiMkegPS2PgvT1Kq9qqN1KfNkmUSdFc,177
|
67
67
|
plain/preflight/__init__.py,sha256=H-TNRvaddPtOGmv4RXoc1fxDV1AOb7_K3u7ECF8mV58,607
|
@@ -71,9 +71,9 @@ plain/preflight/registry.py,sha256=7s7f_iEwURzv-Ye515P5lJWcHltd5Ca2fsX1Wpbf1wQ,2
|
|
71
71
|
plain/preflight/security.py,sha256=sNpv5AHobPcaO48cOUGRNe2EjusTducjY8vyShR8EhI,2645
|
72
72
|
plain/preflight/urls.py,sha256=OSTLvCpftAD_8VbQ0V3p1CTPlRRwtlnXVBZeWgr7l2k,2881
|
73
73
|
plain/runtime/README.md,sha256=Q8VVO7JRGuYrDxzuYL6ptoilhclbecxKzpRXKgbWGkU,2061
|
74
|
-
plain/runtime/__init__.py,sha256=
|
74
|
+
plain/runtime/__init__.py,sha256=o2RVETiL8U0lMFBpbtfnxflhw_4MFllMV6CEpX3RqZs,1965
|
75
75
|
plain/runtime/global_settings.py,sha256=SfOhwzpZe2zpNqSpdx3hHgCN89xdbW9KJVR4KJfS_Gk,5498
|
76
|
-
plain/runtime/user_settings.py,sha256=
|
76
|
+
plain/runtime/user_settings.py,sha256=uRHHVfzUvHon91_fOKj7K2WaBYwJ1gCPLfeXqKj5CTs,10902
|
77
77
|
plain/signals/README.md,sha256=cd3tKEgH-xc88CUWyDxl4-qv-HBXx8VT32BXVwA5azA,230
|
78
78
|
plain/signals/__init__.py,sha256=eAs0kLqptuP6I31dWXeAqRNji3svplpAV4Ez6ktjwXM,131
|
79
79
|
plain/signals/dispatch/__init__.py,sha256=FzEygqV9HsM6gopio7O2Oh_X230nA4d5Q9s0sUjMq0E,292
|
@@ -134,8 +134,8 @@ plain/views/forms.py,sha256=RhlaUcZCkeqokY_fvv-NOS-kgZAG4XhDLOPbf9K_Zlc,2691
|
|
134
134
|
plain/views/objects.py,sha256=g5Lzno0Zsv0K449UpcCtxwCoO7WMRAWqKlxxV2V0_qg,8263
|
135
135
|
plain/views/redirect.py,sha256=9zHZgKvtSkdrMX9KmsRM8hJTPmBktxhc4d8OitbuniI,1724
|
136
136
|
plain/views/templates.py,sha256=cBkFNCSXgVi8cMqQbhsqJ4M_rIQYVl8cUvq9qu4YIes,1951
|
137
|
-
plain-0.
|
138
|
-
plain-0.
|
139
|
-
plain-0.
|
140
|
-
plain-0.
|
141
|
-
plain-0.
|
137
|
+
plain-0.29.0.dist-info/METADATA,sha256=ONh32xHnWT3cqrGSkuvVsTvkci_KvLkY95bBGf3GW2Y,319
|
138
|
+
plain-0.29.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
139
|
+
plain-0.29.0.dist-info/entry_points.txt,sha256=1Ys2lsSeMepD1vz8RSrJopna0RQfUd951vYvCRsvl6A,45
|
140
|
+
plain-0.29.0.dist-info/licenses/LICENSE,sha256=m0D5O7QoH9l5Vz_rrX_9r-C8d9UNr_ciK6Qwac7o6yo,3175
|
141
|
+
plain-0.29.0.dist-info/RECORD,,
|
plain/cli/packages.py
DELETED
@@ -1,94 +0,0 @@
|
|
1
|
-
import importlib
|
2
|
-
from importlib.metadata import entry_points
|
3
|
-
from importlib.util import find_spec
|
4
|
-
|
5
|
-
import click
|
6
|
-
|
7
|
-
from plain.packages import packages_registry
|
8
|
-
|
9
|
-
|
10
|
-
class InstalledPackagesGroup(click.Group):
|
11
|
-
"""
|
12
|
-
Packages in INSTALLED_PACKAGES with a cli.py module
|
13
|
-
will be discovered automatically.
|
14
|
-
"""
|
15
|
-
|
16
|
-
PLAIN_APPS_PREFIX = "plain."
|
17
|
-
APP_PREFIX = "app."
|
18
|
-
MODULE_NAME = "cli"
|
19
|
-
|
20
|
-
def list_commands(self, ctx):
|
21
|
-
command_names = []
|
22
|
-
|
23
|
-
# Get installed packages with a cli.py module
|
24
|
-
for app in packages_registry.get_package_configs():
|
25
|
-
if not find_spec(f"{app.name}.{self.MODULE_NAME}"):
|
26
|
-
continue
|
27
|
-
|
28
|
-
cli_name = app.name
|
29
|
-
|
30
|
-
# Change plain.{pkg} to just {pkg}
|
31
|
-
if cli_name.startswith(self.PLAIN_APPS_PREFIX):
|
32
|
-
cli_name = cli_name[len(self.PLAIN_APPS_PREFIX) :]
|
33
|
-
|
34
|
-
# Change app.{pkg} to just {pkg}
|
35
|
-
if cli_name.startswith(self.APP_PREFIX):
|
36
|
-
cli_name = cli_name[len(self.APP_PREFIX) :]
|
37
|
-
|
38
|
-
if cli_name in command_names:
|
39
|
-
raise ValueError(
|
40
|
-
f"Duplicate command name {cli_name} found in installed packages."
|
41
|
-
)
|
42
|
-
|
43
|
-
command_names.append(cli_name)
|
44
|
-
|
45
|
-
return command_names
|
46
|
-
|
47
|
-
def get_command(self, ctx, name):
|
48
|
-
# Try it as plain.x, app.x, and just x (we don't know ahead of time which it is)
|
49
|
-
for n in [self.PLAIN_APPS_PREFIX + name, self.APP_PREFIX + name, name]:
|
50
|
-
try:
|
51
|
-
if not find_spec(n):
|
52
|
-
# plain.<name> doesn't exist at all
|
53
|
-
continue
|
54
|
-
except ModuleNotFoundError:
|
55
|
-
continue
|
56
|
-
|
57
|
-
try:
|
58
|
-
if not find_spec(f"{n}.{self.MODULE_NAME}"):
|
59
|
-
continue
|
60
|
-
except ModuleNotFoundError:
|
61
|
-
continue
|
62
|
-
|
63
|
-
cli = importlib.import_module(f"{n}.{self.MODULE_NAME}")
|
64
|
-
|
65
|
-
# Get the app's cli.py group
|
66
|
-
try:
|
67
|
-
return cli.cli
|
68
|
-
except AttributeError:
|
69
|
-
continue
|
70
|
-
|
71
|
-
|
72
|
-
class EntryPointGroup(click.Group):
|
73
|
-
"""
|
74
|
-
Python packages can be added to the Plain CLI
|
75
|
-
via the plain_cli entrypoint in their setup.py.
|
76
|
-
|
77
|
-
This is intended for packages that don't go in INSTALLED_PACKAGES.
|
78
|
-
"""
|
79
|
-
|
80
|
-
ENTRYPOINT_NAME = "plain.cli"
|
81
|
-
|
82
|
-
def list_commands(self, ctx):
|
83
|
-
rv = []
|
84
|
-
|
85
|
-
for entry_point in entry_points().select(group=self.ENTRYPOINT_NAME):
|
86
|
-
rv.append(entry_point.name)
|
87
|
-
|
88
|
-
rv.sort()
|
89
|
-
return rv
|
90
|
-
|
91
|
-
def get_command(self, ctx, name):
|
92
|
-
for entry_point in entry_points().select(group=self.ENTRYPOINT_NAME):
|
93
|
-
if entry_point.name == name:
|
94
|
-
return entry_point.load()
|
File without changes
|
File without changes
|