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 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)
@@ -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.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.2
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=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,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=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
+ 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.19.2.dist-info/METADATA,sha256=QCcnxdO7IBSw7yuGVpbTjhyXl_IJ2gM9YC4feFMogZA,4244
31
- plain_dev-0.19.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
32
- plain_dev-0.19.2.dist-info/entry_points.txt,sha256=u3RYQlK2jhQvM0yi40D-oV-x2Hot6Ygw07MZ6QQg_28,159
33
- plain_dev-0.19.2.dist-info/licenses/LICENSE,sha256=Cx4Dq9yR2fLHthf8Ke36B8QJvE1bZFXVzDIGE8wGzsY,4132
34
- plain_dev-0.19.2.dist-info/RECORD,,
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,,
@@ -1,3 +1,6 @@
1
+ [console_scripts]
2
+ plain-contrib = plain.dev.contribute:cli
3
+
1
4
  [plain.cli]
2
5
  contrib = plain.dev.contribute:cli
3
6
  dev = plain.dev:cli
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