plain.dev 0.19.3__py3-none-any.whl → 0.20.1__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 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 = f"plain models db-wait && plain migrate && {gunicorn}"
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)
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.default_namespace == "dev":
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
- urlpatterns = [
8
- path("", views.RequestsView, name="requests"),
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.19.3
3
+ Version: 0.20.1
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
@@ -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=4QYJuADGccwAZl0e_jSGO70NPabtiTB43IFXb2hNi30,12926
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,17 +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=v4ucDL8d6I0s5kF8O8HRvSc6Snq1Z_SMQv7wGGAI_Us,6847
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=b4NL2I6Ok-t7nTPjRnKoz_LQRttE3_mp8l2NlmeYQ9I,146
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
16
  plain/dev/contribute/README.md,sha256=v9Ympugu2wvDEe_045WJnF1dmC4ZH7v_Bnxkpfaf_rM,329
17
17
  plain/dev/contribute/__init__.py,sha256=9ByBOIdM8DebChjNz-RH2atdz4vWe8somlwNEsbhwh4,40
18
18
  plain/dev/contribute/cli.py,sha256=mIK4u1rhee8iMovUpuHTNjfWolqNgS24_gTb8ZaSY-8,2432
19
- plain/dev/db/__init__.py,sha256=9ByBOIdM8DebChjNz-RH2atdz4vWe8somlwNEsbhwh4,40
20
- plain/dev/db/cli.py,sha256=058HjRKLGz-FxauQEpwsPoh_LCiy-_NEIpRZl9W1ZKM,2855
21
- plain/dev/db/container.py,sha256=RlPJU_CCMKA-zN8Kp0sYAu3jabOizxYAj8fSCsjCf60,5147
22
19
  plain/dev/poncho/__init__.py,sha256=MDOk2rhhoR3V-I-rg6tMHFeX60vTGJuQ14RI-_N6tQY,97
23
20
  plain/dev/poncho/color.py,sha256=Dk77inPR9qNc9vCaZOGk8W9skXfRgoUlxp_E6mhPNns,610
24
21
  plain/dev/poncho/compat.py,sha256=l66WZLR7kRpO8P8DI5-aUsbNlohPaXEurQ5xXESQYDs,1276
@@ -28,8 +25,8 @@ plain/dev/poncho/process.py,sha256=JJOKy-C6vMCg7-6JMCtu6C649h7HmOBSJqDP_hnX49I,2
28
25
  plain/dev/precommit/__init__.py,sha256=9ByBOIdM8DebChjNz-RH2atdz4vWe8somlwNEsbhwh4,40
29
26
  plain/dev/precommit/cli.py,sha256=divzCT1LY_aH89nJuzdFqFHhyA4R7jEcV_R6uZsu4bo,3383
30
27
  plain/dev/templates/dev/requests.html,sha256=kQKJZq5L77juuL_t8UjcAehEU61U4RXNnKaAET-wAm8,7627
31
- plain_dev-0.19.3.dist-info/METADATA,sha256=PwT2uIZ7JCp60bgoFg6DTQ2vvNTsrlOCh9BedkLUzEA,4144
32
- plain_dev-0.19.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
33
- plain_dev-0.19.3.dist-info/entry_points.txt,sha256=UtjUbxI-2TMZJGCfRTeU-YGr48XLwnc_pMuzDGD9uTg,219
34
- plain_dev-0.19.3.dist-info/licenses/LICENSE,sha256=Cx4Dq9yR2fLHthf8Ke36B8QJvE1bZFXVzDIGE8wGzsY,4132
35
- plain_dev-0.19.3.dist-info/RECORD,,
28
+ plain_dev-0.20.1.dist-info/METADATA,sha256=nsxwDZjhrdCwNaVasMolQgnroyHjIg-GdHL5kAGl27s,4167
29
+ plain_dev-0.20.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
30
+ plain_dev-0.20.1.dist-info/entry_points.txt,sha256=UtjUbxI-2TMZJGCfRTeU-YGr48XLwnc_pMuzDGD9uTg,219
31
+ plain_dev-0.20.1.dist-info/licenses/LICENSE,sha256=Cx4Dq9yR2fLHthf8Ke36B8QJvE1bZFXVzDIGE8wGzsY,4132
32
+ plain_dev-0.20.1.dist-info/RECORD,,
plain/dev/db/__init__.py DELETED
@@ -1,3 +0,0 @@
1
- from .cli import cli
2
-
3
- __all__ = ["cli"]
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