plain.dev 0.19.2__py3-none-any.whl → 0.20.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/dev/cli.py +3 -5
- plain/dev/contribute/README.md +5 -0
- plain/dev/requests.py +1 -1
- plain/dev/urls.py +7 -5
- {plain_dev-0.19.2.dist-info → plain_dev-0.20.0.dist-info}/METADATA +2 -3
- {plain_dev-0.19.2.dist-info → plain_dev-0.20.0.dist-info}/RECORD +9 -11
- {plain_dev-0.19.2.dist-info → plain_dev-0.20.0.dist-info}/entry_points.txt +3 -0
- plain/dev/db/__init__.py +0 -3
- plain/dev/db/cli.py +0 -113
- plain/dev/db/container.py +0 -151
- {plain_dev-0.19.2.dist-info → plain_dev-0.20.0.dist-info}/WHEEL +0 -0
- {plain_dev-0.19.2.dist-info → plain_dev-0.20.0.dist-info}/licenses/LICENSE +0 -0
plain/dev/cli.py
CHANGED
@@ -17,7 +17,6 @@ from rich.text import Text
|
|
17
17
|
|
18
18
|
from plain.runtime import APP_PATH, settings
|
19
19
|
|
20
|
-
from .db import cli as db_cli
|
21
20
|
from .mkcert import MkcertManager
|
22
21
|
from .pid import Pid
|
23
22
|
from .poncho.manager import Manager as PonchoManager
|
@@ -342,7 +341,9 @@ class Dev:
|
|
342
341
|
gunicorn = " ".join(gunicorn_cmd)
|
343
342
|
|
344
343
|
if plain_db_installed:
|
345
|
-
runserver_cmd =
|
344
|
+
runserver_cmd = (
|
345
|
+
f"plain models db-wait && plain migrate --backup && {gunicorn}"
|
346
|
+
)
|
346
347
|
else:
|
347
348
|
runserver_cmd = gunicorn
|
348
349
|
|
@@ -388,6 +389,3 @@ class Dev:
|
|
388
389
|
**data.get("env", {}),
|
389
390
|
}
|
390
391
|
self.poncho.add_process(name, data["cmd"], env=env)
|
391
|
-
|
392
|
-
|
393
|
-
cli.add_command(db_cli)
|
@@ -0,0 +1,5 @@
|
|
1
|
+
## FAQs
|
2
|
+
|
3
|
+
### What if the plain cli isn't working?
|
4
|
+
|
5
|
+
When working on packages locally you can sometimes end up in a weird state where Plain can't load. The `plain contrib` command is also available as `plain-contrib`, which won't go through any of the setup processes for Plain, so you can always run that directly if you need to.
|
plain/dev/requests.py
CHANGED
@@ -184,7 +184,7 @@ def should_capture_request(request):
|
|
184
184
|
if not settings.DEBUG:
|
185
185
|
return False
|
186
186
|
|
187
|
-
if request.resolver_match and request.resolver_match.
|
187
|
+
if request.resolver_match and request.resolver_match.namespace == "dev":
|
188
188
|
return False
|
189
189
|
|
190
190
|
if request.path in settings.DEV_REQUESTS_IGNORE_PATHS:
|
plain/dev/urls.py
CHANGED
@@ -1,9 +1,11 @@
|
|
1
|
-
from plain.urls import path
|
1
|
+
from plain.urls import RouterBase, path, register_router
|
2
2
|
|
3
3
|
from . import views
|
4
4
|
|
5
|
-
default_namespace = "dev"
|
6
5
|
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
@register_router
|
7
|
+
class Router(RouterBase):
|
8
|
+
namespace = "dev"
|
9
|
+
urls = [
|
10
|
+
path("", views.RequestsView, name="requests"),
|
11
|
+
]
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: plain.dev
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.20.0
|
4
4
|
Summary: Local development tools for Plain.
|
5
5
|
Author-email: Dave Gaeddert <dave.gaeddert@dropseed.dev>
|
6
6
|
License-Expression: BSD-3-Clause
|
@@ -8,6 +8,7 @@ License-File: LICENSE
|
|
8
8
|
Requires-Python: >=3.11
|
9
9
|
Requires-Dist: click>=8.0.0
|
10
10
|
Requires-Dist: gunicorn>20
|
11
|
+
Requires-Dist: inotify
|
11
12
|
Requires-Dist: plain<1.0.0
|
12
13
|
Requires-Dist: psycopg[binary]~=3.2.2
|
13
14
|
Requires-Dist: python-dotenv~=1.0.0
|
@@ -15,8 +16,6 @@ Requires-Dist: requests>=2.0.0
|
|
15
16
|
Requires-Dist: rich
|
16
17
|
Description-Content-Type: text/markdown
|
17
18
|
|
18
|
-
<!-- This file is compiled from plain-dev/plain/dev/README.md. Do not edit this file directly. -->
|
19
|
-
|
20
19
|
# plain.dev
|
21
20
|
|
22
21
|
A single command that runs everything you need for local development.
|
@@ -1,6 +1,6 @@
|
|
1
1
|
plain/dev/README.md,sha256=-yR4qzwEP7vDVvCAHJ22aaQHQ7rXVW9uy0KEZjCzmz4,3662
|
2
2
|
plain/dev/__init__.py,sha256=nRX1B0Br8gmqhJLqo5Z9PqzReDahBtbmwH6C-7hzuls,103
|
3
|
-
plain/dev/cli.py,sha256=
|
3
|
+
plain/dev/cli.py,sha256=DkuMpWpfP0FxSFyl4jI7iXkChDWs7cfv_u4RM34rwH4,12911
|
4
4
|
plain/dev/debug.py,sha256=Ka84K8zUdF0kMYNyqiLYDrdzU1jU8LSOkts3hcw_Gok,1005
|
5
5
|
plain/dev/default_settings.py,sha256=uXWYORWP_aRDwXIFXdu5kHyiBFUZzARIJdhPeFaX35c,75
|
6
6
|
plain/dev/entrypoints.py,sha256=TCjX9aCLQag_0UjWE6PU5MnuaNWHAqsZQh7opSjr8zM,386
|
@@ -8,16 +8,14 @@ plain/dev/gunicorn_logging.json,sha256=yU23jzs5G4YGdDWBNiyAc3KypR4HirR6afIkD7w6D
|
|
8
8
|
plain/dev/mkcert.py,sha256=u284XXQeTgwSkKKh0gMrYpnNGdd9OOUJKLQAXcvGnr4,3459
|
9
9
|
plain/dev/pdb.py,sha256=4ru3rlIIyuYVXteyI7v42i4MmdBIjpJP0IJemBpf83A,3742
|
10
10
|
plain/dev/pid.py,sha256=gRMBf7aGndrra1TnmKtPghTijnd0i0Xeo63mzfPWp7M,436
|
11
|
-
plain/dev/requests.py,sha256=
|
11
|
+
plain/dev/requests.py,sha256=D1NX0qC8ADtf_zwsGMTp9aiE6Sh9QmoxFJCU_V8Wers,6839
|
12
12
|
plain/dev/services.py,sha256=26WnVNjNAh37BA-wHQSGQY_9Ofw8oGgJdHhxnFaSKkg,2059
|
13
|
-
plain/dev/urls.py,sha256=
|
13
|
+
plain/dev/urls.py,sha256=AzGhzJ_6nnHQCqGgiriykOnXmQUvUQSa3ji9NKHjk4I,219
|
14
14
|
plain/dev/utils.py,sha256=4wMzpvj1Is_c0QxhsTu34_P9wAYlzw4glNPfVtZr_0A,123
|
15
15
|
plain/dev/views.py,sha256=r2Ivk7OXytpRhXq4DZpsb7FXNP9vzmEE3D5kLajYG4w,1073
|
16
|
+
plain/dev/contribute/README.md,sha256=v9Ympugu2wvDEe_045WJnF1dmC4ZH7v_Bnxkpfaf_rM,329
|
16
17
|
plain/dev/contribute/__init__.py,sha256=9ByBOIdM8DebChjNz-RH2atdz4vWe8somlwNEsbhwh4,40
|
17
18
|
plain/dev/contribute/cli.py,sha256=mIK4u1rhee8iMovUpuHTNjfWolqNgS24_gTb8ZaSY-8,2432
|
18
|
-
plain/dev/db/__init__.py,sha256=9ByBOIdM8DebChjNz-RH2atdz4vWe8somlwNEsbhwh4,40
|
19
|
-
plain/dev/db/cli.py,sha256=058HjRKLGz-FxauQEpwsPoh_LCiy-_NEIpRZl9W1ZKM,2855
|
20
|
-
plain/dev/db/container.py,sha256=RlPJU_CCMKA-zN8Kp0sYAu3jabOizxYAj8fSCsjCf60,5147
|
21
19
|
plain/dev/poncho/__init__.py,sha256=MDOk2rhhoR3V-I-rg6tMHFeX60vTGJuQ14RI-_N6tQY,97
|
22
20
|
plain/dev/poncho/color.py,sha256=Dk77inPR9qNc9vCaZOGk8W9skXfRgoUlxp_E6mhPNns,610
|
23
21
|
plain/dev/poncho/compat.py,sha256=l66WZLR7kRpO8P8DI5-aUsbNlohPaXEurQ5xXESQYDs,1276
|
@@ -27,8 +25,8 @@ plain/dev/poncho/process.py,sha256=JJOKy-C6vMCg7-6JMCtu6C649h7HmOBSJqDP_hnX49I,2
|
|
27
25
|
plain/dev/precommit/__init__.py,sha256=9ByBOIdM8DebChjNz-RH2atdz4vWe8somlwNEsbhwh4,40
|
28
26
|
plain/dev/precommit/cli.py,sha256=divzCT1LY_aH89nJuzdFqFHhyA4R7jEcV_R6uZsu4bo,3383
|
29
27
|
plain/dev/templates/dev/requests.html,sha256=kQKJZq5L77juuL_t8UjcAehEU61U4RXNnKaAET-wAm8,7627
|
30
|
-
plain_dev-0.
|
31
|
-
plain_dev-0.
|
32
|
-
plain_dev-0.
|
33
|
-
plain_dev-0.
|
34
|
-
plain_dev-0.
|
28
|
+
plain_dev-0.20.0.dist-info/METADATA,sha256=AoaaHtDPcdIQOHsFAI6XTRw9A8HhTrljtzKpjK17Kwg,4167
|
29
|
+
plain_dev-0.20.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
30
|
+
plain_dev-0.20.0.dist-info/entry_points.txt,sha256=UtjUbxI-2TMZJGCfRTeU-YGr48XLwnc_pMuzDGD9uTg,219
|
31
|
+
plain_dev-0.20.0.dist-info/licenses/LICENSE,sha256=Cx4Dq9yR2fLHthf8Ke36B8QJvE1bZFXVzDIGE8wGzsY,4132
|
32
|
+
plain_dev-0.20.0.dist-info/RECORD,,
|
plain/dev/db/__init__.py
DELETED
plain/dev/db/cli.py
DELETED
@@ -1,113 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
import sys
|
3
|
-
|
4
|
-
import click
|
5
|
-
|
6
|
-
from ..services import Services
|
7
|
-
from .container import DBContainer
|
8
|
-
|
9
|
-
|
10
|
-
@click.group("db")
|
11
|
-
def cli():
|
12
|
-
"""Start, stop, and manage the local Postgres database"""
|
13
|
-
pass
|
14
|
-
|
15
|
-
|
16
|
-
# @cli.command()
|
17
|
-
# def reset():
|
18
|
-
# DBContainer().reset(create=True)
|
19
|
-
# click.secho("Local development database reset", fg="green")
|
20
|
-
|
21
|
-
|
22
|
-
@cli.command()
|
23
|
-
@click.argument("export_path", default="")
|
24
|
-
def export(export_path):
|
25
|
-
"""Export the local database to a file"""
|
26
|
-
if not export_path:
|
27
|
-
current_dir_name = os.path.basename(os.getcwd())
|
28
|
-
export_path = f"{current_dir_name}-dev-db.sql"
|
29
|
-
with Services():
|
30
|
-
export_successful = DBContainer().export(export_path)
|
31
|
-
|
32
|
-
if export_successful:
|
33
|
-
click.secho(f"Local development database exported to {export_path}", fg="green")
|
34
|
-
else:
|
35
|
-
click.secho("Export failed", fg="red")
|
36
|
-
sys.exit(1)
|
37
|
-
|
38
|
-
|
39
|
-
@cli.command("import")
|
40
|
-
@click.argument("sql_file")
|
41
|
-
def import_db(sql_file):
|
42
|
-
"""Import a database file into the local database"""
|
43
|
-
|
44
|
-
print(f"Importing {sql_file} ({os.path.getsize(sql_file) / 1024 / 1024:.2f} MB)")
|
45
|
-
|
46
|
-
with Services():
|
47
|
-
successful = DBContainer().import_sql(sql_file)
|
48
|
-
|
49
|
-
if successful:
|
50
|
-
click.secho(f"Local development database imported from {sql_file}", fg="green")
|
51
|
-
else:
|
52
|
-
click.secho("Import failed", fg="red")
|
53
|
-
sys.exit(1)
|
54
|
-
|
55
|
-
|
56
|
-
@cli.group()
|
57
|
-
def snapshot():
|
58
|
-
"""Manage local database snapshots"""
|
59
|
-
pass
|
60
|
-
|
61
|
-
|
62
|
-
@snapshot.command("create")
|
63
|
-
@click.argument("name")
|
64
|
-
@click.pass_context
|
65
|
-
def snapshot_create(ctx, name):
|
66
|
-
"""Create a snapshot of the main database"""
|
67
|
-
created = DBContainer().create_snapshot(name)
|
68
|
-
if not created:
|
69
|
-
click.secho(f'Snapshot "{name}" already exists', fg="red")
|
70
|
-
sys.exit(1)
|
71
|
-
|
72
|
-
click.secho(f'Snapshot "{name}" created', fg="green")
|
73
|
-
print()
|
74
|
-
ctx.invoke(snapshot_list)
|
75
|
-
|
76
|
-
|
77
|
-
@snapshot.command("list")
|
78
|
-
def snapshot_list():
|
79
|
-
"""List all snapshots"""
|
80
|
-
DBContainer().list_snapshots()
|
81
|
-
|
82
|
-
|
83
|
-
@snapshot.command("restore")
|
84
|
-
@click.argument("name")
|
85
|
-
@click.option("--yes", "-y", is_flag=True)
|
86
|
-
def snapshot_restore(name, yes):
|
87
|
-
"""Restore a snapshot to the main database"""
|
88
|
-
if not yes:
|
89
|
-
click.confirm(
|
90
|
-
f'Are you sure you want to restore snapshot "{name}" to the main database?',
|
91
|
-
abort=True,
|
92
|
-
)
|
93
|
-
|
94
|
-
DBContainer().restore_snapshot(name)
|
95
|
-
click.secho(f'Snapshot "{name}" restored', fg="green")
|
96
|
-
|
97
|
-
|
98
|
-
@snapshot.command("delete")
|
99
|
-
@click.argument("name")
|
100
|
-
@click.pass_context
|
101
|
-
def snapshot_delete(ctx, name):
|
102
|
-
"""Delete a snapshot"""
|
103
|
-
deleted = DBContainer().delete_snapshot(name)
|
104
|
-
if not deleted:
|
105
|
-
click.secho(f'Snapshot "{name}" does not exist', fg="red")
|
106
|
-
sys.exit(1)
|
107
|
-
click.secho(f'Snapshot "{name}" deleted', fg="green")
|
108
|
-
print()
|
109
|
-
ctx.invoke(snapshot_list)
|
110
|
-
|
111
|
-
|
112
|
-
if __name__ == "__main__":
|
113
|
-
cli()
|
plain/dev/db/container.py
DELETED
@@ -1,151 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
import shlex
|
3
|
-
import subprocess
|
4
|
-
|
5
|
-
from plain.runtime import APP_PATH, settings
|
6
|
-
|
7
|
-
SNAPSHOT_DB_PREFIX = "plaindb_snapshot_"
|
8
|
-
|
9
|
-
|
10
|
-
class DBContainer:
|
11
|
-
def __init__(self):
|
12
|
-
project_root = APP_PATH.parent
|
13
|
-
tmp_dir = settings.PLAIN_TEMP_PATH
|
14
|
-
|
15
|
-
name = os.path.basename(project_root) + "-postgres-1"
|
16
|
-
|
17
|
-
if "DATABASE_URL" in os.environ:
|
18
|
-
from plain.models import database_url
|
19
|
-
|
20
|
-
postgres_version = os.environ.get("POSTGRES_VERSION")
|
21
|
-
parsed_db_url = database_url.parse(os.environ.get("DATABASE_URL"))
|
22
|
-
|
23
|
-
self.name = name
|
24
|
-
self.tmp_dir = os.path.abspath(tmp_dir)
|
25
|
-
self.postgres_version = postgres_version or "13"
|
26
|
-
self.postgres_port = parsed_db_url.get("PORT", "5432")
|
27
|
-
self.postgres_db = parsed_db_url.get("NAME", "postgres")
|
28
|
-
self.postgres_user = parsed_db_url.get("USER", "postgres")
|
29
|
-
self.postgres_password = parsed_db_url.get("PASSWORD", "postgres")
|
30
|
-
|
31
|
-
def execute(self, command, *args, **kwargs):
|
32
|
-
docker_flags = kwargs.pop("docker_flags", "-it")
|
33
|
-
return subprocess.run(
|
34
|
-
[
|
35
|
-
"docker",
|
36
|
-
"exec",
|
37
|
-
docker_flags,
|
38
|
-
self.name,
|
39
|
-
*shlex.split(command),
|
40
|
-
]
|
41
|
-
+ list(args),
|
42
|
-
check=True,
|
43
|
-
**kwargs,
|
44
|
-
)
|
45
|
-
|
46
|
-
def reset(self, create=False):
|
47
|
-
try:
|
48
|
-
self.execute(
|
49
|
-
f"dropdb {self.postgres_db} --force -U {self.postgres_user}",
|
50
|
-
stdout=subprocess.PIPE,
|
51
|
-
stderr=subprocess.PIPE,
|
52
|
-
)
|
53
|
-
except subprocess.CalledProcessError as e:
|
54
|
-
if "does not exist" not in e.stdout.decode():
|
55
|
-
print(e.stderr.decode())
|
56
|
-
raise
|
57
|
-
|
58
|
-
if create:
|
59
|
-
self.execute(
|
60
|
-
f"createdb {self.postgres_db} -U {self.postgres_user}",
|
61
|
-
stdout=subprocess.PIPE,
|
62
|
-
stderr=subprocess.PIPE,
|
63
|
-
)
|
64
|
-
|
65
|
-
def terminate_connections(self):
|
66
|
-
self.execute(
|
67
|
-
f"psql -U {self.postgres_user} {self.postgres_db} -c",
|
68
|
-
f"SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = '{self.postgres_db}' AND pid <> pg_backend_pid();",
|
69
|
-
stdout=subprocess.DEVNULL,
|
70
|
-
)
|
71
|
-
|
72
|
-
def create_snapshot(self, name):
|
73
|
-
snapshot_name = f"{SNAPSHOT_DB_PREFIX}{name}"
|
74
|
-
current_git_branch = (
|
75
|
-
subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"])
|
76
|
-
.decode()
|
77
|
-
.strip()
|
78
|
-
)
|
79
|
-
description = f"branch={current_git_branch}"
|
80
|
-
|
81
|
-
self.terminate_connections()
|
82
|
-
try:
|
83
|
-
self.execute(
|
84
|
-
f"createdb {snapshot_name} '{description}' -U {self.postgres_user} -T {self.postgres_db}",
|
85
|
-
stdout=subprocess.PIPE,
|
86
|
-
stderr=subprocess.PIPE,
|
87
|
-
)
|
88
|
-
except subprocess.CalledProcessError as e:
|
89
|
-
if "already exists" in e.stdout.decode():
|
90
|
-
return False
|
91
|
-
else:
|
92
|
-
raise
|
93
|
-
|
94
|
-
return True
|
95
|
-
|
96
|
-
def list_snapshots(self):
|
97
|
-
self.execute(
|
98
|
-
f"psql -U {self.postgres_user} {self.postgres_db} -c",
|
99
|
-
f"SELECT REPLACE(datname, '{SNAPSHOT_DB_PREFIX}', '') as name, pg_size_pretty(pg_database_size(datname)) as size, pg_catalog.shobj_description(oid, 'pg_database') AS description, (pg_stat_file('base/'||oid ||'/PG_VERSION')).modification as created FROM pg_catalog.pg_database WHERE datname LIKE '{SNAPSHOT_DB_PREFIX}%' ORDER BY created;",
|
100
|
-
)
|
101
|
-
|
102
|
-
def delete_snapshot(self, name):
|
103
|
-
snapshot_name = f"{SNAPSHOT_DB_PREFIX}{name}"
|
104
|
-
try:
|
105
|
-
self.execute(
|
106
|
-
f"dropdb {snapshot_name} -U {self.postgres_user}",
|
107
|
-
stdout=subprocess.PIPE,
|
108
|
-
stderr=subprocess.PIPE,
|
109
|
-
)
|
110
|
-
except subprocess.CalledProcessError as e:
|
111
|
-
if "does not exist" in e.stdout.decode():
|
112
|
-
return False
|
113
|
-
else:
|
114
|
-
raise
|
115
|
-
|
116
|
-
return True
|
117
|
-
|
118
|
-
def restore_snapshot(self, name):
|
119
|
-
snapshot_name = f"{SNAPSHOT_DB_PREFIX}{name}"
|
120
|
-
self.reset(create=False)
|
121
|
-
self.execute(
|
122
|
-
f"createdb {self.postgres_db} -U {self.postgres_user} -T {snapshot_name}",
|
123
|
-
)
|
124
|
-
|
125
|
-
def export(self, export_path):
|
126
|
-
successful = (
|
127
|
-
subprocess.run(
|
128
|
-
[
|
129
|
-
"docker",
|
130
|
-
"exec",
|
131
|
-
self.name,
|
132
|
-
"/bin/bash",
|
133
|
-
"-c",
|
134
|
-
f"pg_dump -U {self.postgres_user} {self.postgres_db}",
|
135
|
-
],
|
136
|
-
stdout=open(export_path, "w+"),
|
137
|
-
).returncode
|
138
|
-
== 0
|
139
|
-
)
|
140
|
-
return successful
|
141
|
-
|
142
|
-
def import_sql(self, sql_file):
|
143
|
-
self.reset(create=True)
|
144
|
-
successful = (
|
145
|
-
subprocess.run(
|
146
|
-
f"docker exec -i {self.name} psql -U {self.postgres_user} {self.postgres_db} < {shlex.quote(sql_file)}",
|
147
|
-
shell=True,
|
148
|
-
).returncode
|
149
|
-
== 0
|
150
|
-
)
|
151
|
-
return successful
|
File without changes
|
File without changes
|