Skip to content

Commit 3483e98

Browse files
committed
Replace dev services with manually defined ones
1 parent 265c73e commit 3483e98

File tree

9 files changed

+185
-229
lines changed

9 files changed

+185
-229
lines changed

bolt-dev/bolt/dev/README.md

-10
Original file line numberDiff line numberDiff line change
@@ -54,16 +54,6 @@ The `BASE_URL` setting is automatically set to the Codespace URL.
5454

5555
TODO
5656

57-
## `bolt dev services`
58-
59-
`REDIS_URL`
60-
`DATABASE_URL`
61-
62-
- up
63-
- down
64-
65-
TODO option to turn this off? pyproject.toml `tool.bolt.dev.services.enabled = false`
66-
6757
## `bolt dev db`
6858

6959
Only supports Postgres currently.

bolt-dev/bolt/dev/cli.py

+105-70
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import json
21
import os
32
import subprocess
43
import sys
@@ -11,7 +10,8 @@
1110
from bolt.runtime import APP_PATH
1211

1312
from .db import cli as db_cli
14-
from .services import cli as services_cli
13+
from .pid import Pid
14+
from .services import Services
1515
from .utils import boltpackage_installed, has_pyproject_toml
1616

1717
try:
@@ -35,82 +35,117 @@ def cli(ctx, port):
3535
if ctx.invoked_subcommand:
3636
return
3737

38-
# TODO check docker is available first
39-
project_root = APP_PATH.parent
40-
41-
bolt_env = {
42-
**os.environ,
43-
"PYTHONUNBUFFERED": "true",
44-
}
45-
46-
if "GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN" in os.environ:
47-
codespace_base_url = f"https://{os.environ['CODESPACE_NAME']}-{port}.{os.environ['GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN']}"
48-
click.secho(
49-
f"Automatically using Codespace BASE_URL={click.style(codespace_base_url, underline=True)}",
50-
bold=True,
51-
)
52-
bolt_env["BASE_URL"] = codespace_base_url
53-
54-
if subprocess.run(["bolt", "preflight"], env=bolt_env).returncode:
55-
click.secho("Preflight check failed!", fg="red")
56-
sys.exit(1)
57-
58-
bolt_db_installed = find_spec("bolt.db") is not None
59-
60-
manager = HonchoManager()
61-
62-
# TODO not necessarily watching the right .env...
63-
# could return path from env.load?
64-
extra_watch_files = []
65-
for f in os.listdir(project_root):
66-
if f.startswith(".env"):
67-
# Will include some extra, but good enough for now
68-
extra_watch_files.append(f)
69-
70-
reload_extra = " ".join(f"--reload-extra-file {f}" for f in extra_watch_files)
71-
gunicorn = f"gunicorn --bind 127.0.0.1:{port} --reload bolt.wsgi:app --timeout 0 --workers 2 --access-logfile - --error-logfile - {reload_extra} --access-logformat '\"%(r)s\" status=%(s)s length=%(b)s dur=%(M)sms'"
72-
73-
if bolt_db_installed:
74-
runserver_cmd = f"bolt db wait && bolt legacy migrate && {gunicorn}"
75-
manager.add_process("services", "bolt dev services up")
76-
else:
77-
runserver_cmd = gunicorn
78-
79-
manager.add_process("bolt", runserver_cmd, env=bolt_env)
80-
81-
if boltpackage_installed("tailwind"):
82-
manager.add_process("tailwind", "bolt tailwind compile --watch")
83-
84-
custom_env = {
85-
**bolt_env,
86-
"PORT": port,
87-
"PYTHONPATH": os.path.join(project_root, "app"),
88-
}
89-
90-
if project_root and has_pyproject_toml(project_root):
91-
with open(Path(project_root, "pyproject.toml"), "rb") as f:
38+
returncode = Dev(port=port).run()
39+
if returncode:
40+
sys.exit(returncode)
41+
42+
43+
class Dev:
44+
def __init__(self, *, port):
45+
self.manager = HonchoManager()
46+
self.port = port
47+
self.bolt_env = {
48+
**os.environ,
49+
"PYTHONUNBUFFERED": "true",
50+
}
51+
self.custom_process_env = {
52+
**self.bolt_env,
53+
"PORT": str(self.port),
54+
"PYTHONPATH": os.path.join(APP_PATH.parent, "app"),
55+
}
56+
57+
def run(self):
58+
pid = Pid()
59+
pid.write()
60+
61+
try:
62+
self.add_github_codespace_support()
63+
self.run_preflight()
64+
self.add_gunicorn()
65+
self.add_tailwind()
66+
self.add_pyproject_run()
67+
self.add_services()
68+
69+
self.manager.loop()
70+
71+
return self.manager.returncode
72+
finally:
73+
pid.rm()
74+
75+
def add_github_codespace_support(self):
76+
if "GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN" in os.environ:
77+
codespace_base_url = f"https://{os.environ['CODESPACE_NAME']}-{self.port}.{os.environ['GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN']}"
78+
click.secho(
79+
f"Automatically using Codespace BASE_URL={click.style(codespace_base_url, underline=True)}",
80+
bold=True,
81+
)
82+
83+
# Set BASE_URL for bolt and custom processes
84+
self.bolt_env["BASE_URL"] = codespace_base_url
85+
self.custom_process_env["BASE_URL"] = codespace_base_url
86+
87+
def run_preflight(self):
88+
if subprocess.run(["bolt", "preflight"], env=self.bolt_env).returncode:
89+
click.secho("Preflight check failed!", fg="red")
90+
sys.exit(1)
91+
92+
def add_gunicorn(self):
93+
bolt_db_installed = find_spec("bolt.db") is not None
94+
95+
# TODO not necessarily watching the right .env...
96+
# could return path from env.load?
97+
extra_watch_files = []
98+
for f in os.listdir(APP_PATH.parent):
99+
if f.startswith(".env"):
100+
# Will include some extra, but good enough for now
101+
extra_watch_files.append(f)
102+
103+
reload_extra = " ".join(f"--reload-extra-file {f}" for f in extra_watch_files)
104+
gunicorn = f"gunicorn --bind 127.0.0.1:{self.port} --reload bolt.wsgi:app --timeout 60 --access-logfile - --error-logfile - {reload_extra} --access-logformat '\"%(r)s\" status=%(s)s length=%(b)s dur=%(M)sms'"
105+
106+
if bolt_db_installed:
107+
runserver_cmd = f"bolt db wait && bolt legacy migrate && {gunicorn}"
108+
else:
109+
runserver_cmd = gunicorn
110+
111+
if "WEB_CONCURRENCY" not in self.bolt_env:
112+
# Default to two workers so request log etc are less
113+
# likely to get locked up
114+
self.bolt_env["WEB_CONCURRENCY"] = "2"
115+
116+
self.manager.add_process("bolt", runserver_cmd, env=self.bolt_env)
117+
118+
def add_tailwind(self):
119+
if not boltpackage_installed("tailwind"):
120+
return
121+
122+
self.manager.add_process("tailwind", "bolt tailwind compile --watch")
123+
124+
def add_pyproject_run(self):
125+
if not has_pyproject_toml(APP_PATH.parent):
126+
return
127+
128+
with open(Path(APP_PATH.parent, "pyproject.toml"), "rb") as f:
92129
pyproject = tomllib.load(f)
130+
93131
for name, data in (
94132
pyproject.get("tool", {}).get("bolt", {}).get("dev", {}).get("run", {})
95133
).items():
96134
env = {
97-
**custom_env,
135+
**self.custom_process_env,
98136
**data.get("env", {}),
99137
}
100-
manager.add_process(name, data["cmd"], env=env)
101-
102-
package_json = Path("package.json")
103-
if package_json.exists():
104-
with package_json.open() as f:
105-
package = json.load(f)
106-
107-
if package.get("scripts", {}).get("dev"):
108-
manager.add_process("npm", "npm run dev", env=custom_env)
138+
self.manager.add_process(name, data["cmd"], env=env)
109139

110-
manager.loop()
111-
112-
sys.exit(manager.returncode)
140+
def add_services(self):
141+
services = Services.get_services(APP_PATH.parent)
142+
for name, data in services.items():
143+
env = {
144+
**os.environ,
145+
"PYTHONUNBUFFERED": "true",
146+
**data.get("env", {}),
147+
}
148+
self.manager.add_process(name, data["cmd"], env=env)
113149

114150

115151
cli.add_command(db_cli)
116-
cli.add_command(services_cli)

bolt-dev/bolt/dev/pid.py

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import os
2+
3+
from bolt.runtime import settings
4+
5+
6+
class Pid:
7+
def __init__(self):
8+
self.pidfile = settings.BOLT_TEMP_PATH / "dev.pid"
9+
10+
def write(self):
11+
pid = os.getpid()
12+
with self.pidfile.open("w+") as f:
13+
f.write(str(pid))
14+
15+
def rm(self):
16+
self.pidfile.unlink()
17+
18+
def exists(self):
19+
return self.pidfile.exists()

bolt-dev/bolt/dev/services.py

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import os
2+
from pathlib import Path
3+
4+
import click
5+
from honcho.manager import Manager as HonchoManager
6+
7+
from bolt.runtime import APP_PATH
8+
9+
from .pid import Pid
10+
from .utils import has_pyproject_toml
11+
12+
try:
13+
import tomllib
14+
except ModuleNotFoundError:
15+
import tomli as tomllib
16+
17+
18+
class Services:
19+
@staticmethod
20+
def get_services(root):
21+
if not has_pyproject_toml(root):
22+
return {}
23+
24+
with open(Path(root, "pyproject.toml"), "rb") as f:
25+
pyproject = tomllib.load(f)
26+
27+
return (
28+
pyproject.get("tool", {}).get("bolt", {}).get("dev", {}).get("services", {})
29+
)
30+
31+
def __init__(self):
32+
self.manager = HonchoManager()
33+
34+
def __enter__(self):
35+
if Pid.exists():
36+
click.secho("Services already running in `bolt dev` command", yellow=True)
37+
return
38+
39+
services = self.get_services(APP_PATH.parent)
40+
for name, data in services.items():
41+
env = {
42+
**os.environ,
43+
"PYTHONUNBUFFERED": "true",
44+
**data.get("env", {}),
45+
}
46+
self.manager.add_process(name, data["cmd"], env=env)
47+
48+
self.manager.loop()
49+
50+
def __exit__(self, *args):
51+
self.manager.terminate()

bolt-dev/bolt/dev/services/__init__.py

-4
This file was deleted.

bolt-dev/bolt/dev/services/cli.py

-24
This file was deleted.

bolt-dev/bolt/dev/services/compose.yml

-26
This file was deleted.

0 commit comments

Comments
 (0)