plain.dev 0.26.1__py3-none-any.whl → 0.27.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
@@ -2,8 +2,10 @@ import importlib
2
2
  import json
3
3
  import os
4
4
  import platform
5
+ import signal
5
6
  import subprocess
6
7
  import sys
8
+ import threading
7
9
  import time
8
10
  import tomllib
9
11
  from importlib.metadata import entry_points
@@ -21,7 +23,7 @@ from plain.runtime import APP_PATH, settings
21
23
  from .mkcert import MkcertManager
22
24
  from .poncho.manager import Manager as PonchoManager
23
25
  from .poncho.printer import Printer
24
- from .services import Services
26
+ from .services import Services, ServicesPid
25
27
  from .utils import has_pyproject_toml
26
28
 
27
29
  ENTRYPOINT_GROUP = "plain.dev"
@@ -153,30 +155,6 @@ class Dev:
153
155
  "PLAIN_DEV_URL": self.url,
154
156
  }
155
157
 
156
- self.console = Console(markup=False, highlight=False)
157
- self.poncho = PonchoManager(printer=Printer(lambda s: self.console.out(s)))
158
-
159
- def run(self):
160
- mkcert_manager = MkcertManager()
161
- mkcert_manager.setup_mkcert(install_path=Path.home() / ".plain" / "dev")
162
- self.ssl_cert_path, self.ssl_key_path = mkcert_manager.generate_certs(
163
- domain=self.hostname,
164
- storage_path=Path(settings.PLAIN_TEMP_PATH) / "dev" / "certs",
165
- )
166
-
167
- self.symlink_plain_src()
168
- self.modify_hosts_file()
169
- self.set_csrf_and_allowed_hosts()
170
- self.run_preflight()
171
-
172
- # Processes for poncho to run simultaneously
173
- self.add_gunicorn()
174
- self.add_entrypoints()
175
- self.add_pyproject_run()
176
- self.add_services()
177
-
178
- click.secho("\nStarting dev...", italic=True, dim=True)
179
-
180
158
  if self.tunnel_url:
181
159
  status_bar = Columns(
182
160
  [
@@ -205,12 +183,91 @@ class Dev:
205
183
  ],
206
184
  expand=True,
207
185
  )
186
+ self.console = Console(markup=False, highlight=False)
187
+ self.console_status = self.console.status(status_bar)
188
+
189
+ self.poncho = PonchoManager(printer=Printer(lambda s: self.console.out(s)))
190
+
191
+ def run(self):
192
+ mkcert_manager = MkcertManager()
193
+ mkcert_manager.setup_mkcert(install_path=Path.home() / ".plain" / "dev")
194
+ self.ssl_cert_path, self.ssl_key_path = mkcert_manager.generate_certs(
195
+ domain=self.hostname,
196
+ storage_path=Path(settings.PLAIN_TEMP_PATH) / "dev" / "certs",
197
+ )
208
198
 
209
- with self.console.status(status_bar):
199
+ self.symlink_plain_src()
200
+ self.modify_hosts_file()
201
+ self.set_csrf_and_allowed_hosts()
202
+ self.run_preflight()
203
+
204
+ # If we start services ourselves, we should manage the pidfile
205
+ services_pid = None
206
+
207
+ # Services start first (or are already running from a separate command)
208
+ if Services.are_running():
209
+ click.secho("Services already running", fg="yellow")
210
+ elif services := Services.get_services(APP_PATH.parent):
211
+ click.secho("\nStarting services...", italic=True, dim=True)
212
+ services_pid = ServicesPid()
213
+ services_pid.write()
214
+
215
+ for name, data in services.items():
216
+ env = {
217
+ **os.environ,
218
+ "PYTHONUNBUFFERED": "true",
219
+ **data.get("env", {}),
220
+ }
221
+ self.poncho.add_process(name, data["cmd"], env=env)
222
+
223
+ # If plain.models is installed (common) then we
224
+ # will do a couple extra things before starting all of the app-related
225
+ # processes (this way they don't all have to db-wait or anything)
226
+ if find_spec("plain.models") is not None:
227
+ # Use a custom signal to tell the main thread to add
228
+ # the app processes once the db is ready
229
+ signal.signal(signal.SIGUSR1, self.start_app)
230
+
231
+ def _thread(env):
232
+ subprocess.run(["plain", "models", "db-wait"], env=env, check=True)
233
+ subprocess.run(["plain", "migrate", "--backup"], env=env, check=True)
234
+ # preflight with db?
235
+ os.kill(os.getpid(), signal.SIGUSR1)
236
+
237
+ thread = threading.Thread(
238
+ target=_thread, daemon=True, args=(self.plain_env,)
239
+ )
240
+ thread.start()
241
+ else:
242
+ # Start the app processes immediately
243
+ self.start_app(None, None)
244
+
245
+ try:
246
+ # Start processes we know about and block the main thread
210
247
  self.poncho.loop()
211
248
 
249
+ # Remove the status bar
250
+ self.console_status.stop()
251
+ finally:
252
+ # Make sure the services pid gets removed if we set it
253
+ if services_pid:
254
+ services_pid.rm()
255
+
212
256
  return self.poncho.returncode
213
257
 
258
+ def start_app(self, signum, frame):
259
+ # This runs in the main thread when SIGUSR1 is received
260
+ # (or called directly if no thread).
261
+ click.secho("\nStarting app...", italic=True, dim=True)
262
+
263
+ # Manually start the status bar now so it isn't bungled by
264
+ # another thread checking db stuff...
265
+ self.console_status.start()
266
+
267
+ self.add_gunicorn()
268
+ self.add_entrypoints()
269
+ self.add_pyproject_run()
270
+
214
271
  def symlink_plain_src(self):
215
272
  """Symlink the plain package into .plain so we can look at it easily"""
216
273
  plain_path = Path(
@@ -318,13 +375,13 @@ class Dev:
318
375
  sys.exit(1)
319
376
 
320
377
  def add_gunicorn(self):
321
- plain_db_installed = find_spec("plain.models") is not None
322
-
323
378
  # Watch .env files for reload
324
379
  extra_watch_files = []
325
380
  for f in os.listdir(APP_PATH.parent):
326
381
  if f.startswith(".env"):
327
- extra_watch_files.append(f)
382
+ # Needs to be absolute or "./" for inotify to work on Linux...
383
+ # https://github.com/dropseed/plain/issues/26
384
+ extra_watch_files.append(str(Path(APP_PATH.parent) / f))
328
385
 
329
386
  reload_extra = " ".join(f"--reload-extra-file {f}" for f in extra_watch_files)
330
387
  gunicorn_cmd = [
@@ -355,14 +412,7 @@ class Dev:
355
412
  ]
356
413
  gunicorn = " ".join(gunicorn_cmd)
357
414
 
358
- if plain_db_installed:
359
- runserver_cmd = (
360
- f"plain models db-wait && plain migrate --backup && {gunicorn}"
361
- )
362
- else:
363
- runserver_cmd = gunicorn
364
-
365
- self.poncho.add_process("plain", runserver_cmd, env=self.plain_env)
415
+ self.poncho.add_process("plain", gunicorn, env=self.plain_env)
366
416
 
367
417
  def add_entrypoints(self):
368
418
  for entry_point in entry_points().select(group=ENTRYPOINT_GROUP):
@@ -389,22 +439,3 @@ class Dev:
389
439
  **data.get("env", {}),
390
440
  }
391
441
  self.poncho.add_process(name, data["cmd"], env=env)
392
-
393
- def add_services(self):
394
- """Services are things that also run during tests (like a database), and are critical for the app to function."""
395
-
396
- if Services.are_running():
397
- click.secho("Services already running", fg="yellow")
398
- return
399
-
400
- # TODO need to set services pid here somehow...
401
-
402
- # Split each service into a separate process
403
- services = Services.get_services(APP_PATH.parent)
404
- for name, data in services.items():
405
- env = {
406
- **os.environ,
407
- "PYTHONUNBUFFERED": "true",
408
- **data.get("env", {}),
409
- }
410
- self.poncho.add_process(name, data["cmd"], env=env)
@@ -76,6 +76,10 @@ class Manager:
76
76
  # Update printer width to accommodate this process name
77
77
  self._printer.width = max(self._printer.width, len(name))
78
78
 
79
+ # If the loop is already running, we need to start the process now.
80
+ if self._is_running():
81
+ self._start_process(name)
82
+
79
83
  return proc
80
84
 
81
85
  def loop(self):
@@ -97,7 +101,8 @@ class Manager:
97
101
  signal.signal(signal.SIGTERM, _terminate)
98
102
  signal.signal(signal.SIGINT, _terminate)
99
103
 
100
- self._start()
104
+ for name in self._processes.keys():
105
+ self._start_process(name)
101
106
 
102
107
  exit = False
103
108
  exit_start = None
@@ -172,12 +177,15 @@ class Manager:
172
177
  else:
173
178
  self._procmgr.terminate(p["pid"])
174
179
 
175
- def _start(self):
176
- for name, p in self._processes.items():
177
- p["process"] = multiprocessing.Process(
178
- name=name, target=p["obj"].run, args=(self.events, True)
179
- )
180
- p["process"].start()
180
+ def _start_process(self, name):
181
+ p = self._processes[name]
182
+ p["process"] = multiprocessing.Process(
183
+ name=name, target=p["obj"].run, args=(self.events, True)
184
+ )
185
+ p["process"].start()
186
+
187
+ def _is_running(self):
188
+ return any(p.get("pid") is not None for _, p in self._processes.items())
181
189
 
182
190
  def _all_started(self):
183
191
  return all(p.get("pid") is not None for _, p in self._processes.items())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plain.dev
3
- Version: 0.26.1
3
+ Version: 0.27.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
@@ -1,6 +1,6 @@
1
1
  plain/dev/README.md,sha256=EQJ9lF-JQ9KQWJZ_j6CD3jbRquOLgDRlIPbgT7A2gG4,3658
2
2
  plain/dev/__init__.py,sha256=nRX1B0Br8gmqhJLqo5Z9PqzReDahBtbmwH6C-7hzuls,103
3
- plain/dev/cli.py,sha256=akKprg7UV6JHweCt2dhXxEE6OgIt0bDI4CKj8B1J-68,13308
3
+ plain/dev/cli.py,sha256=z6xDeU1hcu3dTI55H7MllKZBQz4fnAO_Y-VoIKPLO7M,14843
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=diqNwA6eydUMtoO7p_rH-DtSYsw5-GBmjFe1Z5bHagc,579
@@ -18,14 +18,14 @@ plain/dev/contribute/cli.py,sha256=jSfocR754VGMzcnb62zJA2lxxjry1Bdzupbsm3McgdY,3
18
18
  plain/dev/poncho/__init__.py,sha256=MDOk2rhhoR3V-I-rg6tMHFeX60vTGJuQ14RI-_N6tQY,97
19
19
  plain/dev/poncho/color.py,sha256=Dk77inPR9qNc9vCaZOGk8W9skXfRgoUlxp_E6mhPNns,610
20
20
  plain/dev/poncho/compat.py,sha256=l66WZLR7kRpO8P8DI5-aUsbNlohPaXEurQ5xXESQYDs,1276
21
- plain/dev/poncho/manager.py,sha256=xT8ur72ZaVrWYWtg5FnBSiZsENWKUHP7eG_A_ZUP-40,6363
21
+ plain/dev/poncho/manager.py,sha256=wxurOpqhAgLQgC9DK-Pd6rukuwNgjxHu2Jyc2dkZknI,6662
22
22
  plain/dev/poncho/printer.py,sha256=wt1ioaGcPnVyrPy-UjvdsR9zfcr4DTTycmapW1MIdSU,1785
23
23
  plain/dev/poncho/process.py,sha256=JJOKy-C6vMCg7-6JMCtu6C649h7HmOBSJqDP_hnX49I,2637
24
24
  plain/dev/precommit/__init__.py,sha256=9ByBOIdM8DebChjNz-RH2atdz4vWe8somlwNEsbhwh4,40
25
25
  plain/dev/precommit/cli.py,sha256=KVHcG3Y_JZJNu3_MLIrO5s6yMYQfAArIU5L0hNWZUjg,3441
26
26
  plain/dev/templates/dev/requests.html,sha256=kQKJZq5L77juuL_t8UjcAehEU61U4RXNnKaAET-wAm8,7627
27
- plain_dev-0.26.1.dist-info/METADATA,sha256=HaZlzBK_kha_IyXGIPhvPai7uAuyK5DEMbwe6SFMH7g,4163
28
- plain_dev-0.26.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
29
- plain_dev-0.26.1.dist-info/entry_points.txt,sha256=zrcTOiFk_MLKsnYVlwVP7aMm1XLEqq7w4EBkJ-3ge-g,114
30
- plain_dev-0.26.1.dist-info/licenses/LICENSE,sha256=Cx4Dq9yR2fLHthf8Ke36B8QJvE1bZFXVzDIGE8wGzsY,4132
31
- plain_dev-0.26.1.dist-info/RECORD,,
27
+ plain_dev-0.27.1.dist-info/METADATA,sha256=CgZpO23_EYRFL2OX0AC8glbya4WORC1kPaduysWuq-c,4163
28
+ plain_dev-0.27.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
29
+ plain_dev-0.27.1.dist-info/entry_points.txt,sha256=zrcTOiFk_MLKsnYVlwVP7aMm1XLEqq7w4EBkJ-3ge-g,114
30
+ plain_dev-0.27.1.dist-info/licenses/LICENSE,sha256=Cx4Dq9yR2fLHthf8Ke36B8QJvE1bZFXVzDIGE8wGzsY,4132
31
+ plain_dev-0.27.1.dist-info/RECORD,,