mirror of
https://github.com/frappe/bench.git
synced 2024-11-13 16:56:33 +00:00
feat: Dynamic Output rendering
Each operation can be broken down to multiple jobs. For instance, the update operation can be broken down into: setting up bench dirs, env, backups, requirements, etc. Each step has a lot of output since Frappe requires a lot of complex stuff to be done as a pre-requisite. Bench tries to simplify a lot in a single command. Once, a step is completed, it's output is not really required. So, we can ignore it to reduce the noise. Here's where the `bench.cli.fancy` variable kicks in. Along with the refactored log and new step wrapper, it makes the above thing possible. At this point, there's no way to set this var from the end user...given I'm still developing this. In the later commits, the idea is to pass a flag similar to pip's --use-feature flag.
This commit is contained in:
parent
3995b9237b
commit
f117959801
@ -30,6 +30,8 @@ from bench.utils.bench import (
|
||||
restart_supervisor_processes,
|
||||
restart_systemd_processes,
|
||||
)
|
||||
from bench.utils.render import step
|
||||
|
||||
|
||||
logger = logging.getLogger(bench.PROJECT_NAME)
|
||||
|
||||
@ -137,6 +139,7 @@ class App(AppMeta):
|
||||
self.bench = bench
|
||||
super().__init__(name, branch, *args, **kwargs)
|
||||
|
||||
@step(title="Fetching App {repo}", success="App {repo} Fetched")
|
||||
def get(self):
|
||||
branch = f"--branch {self.tag}" if self.tag else ""
|
||||
shallow = "--depth 1" if self.bench.shallow_clone else ""
|
||||
@ -150,6 +153,7 @@ class App(AppMeta):
|
||||
cwd=os.path.join(self.bench.name, "apps"),
|
||||
)
|
||||
|
||||
@step(title="Archiving App {repo}", success="App {repo} Archived")
|
||||
def remove(self):
|
||||
active_app_path = os.path.join("apps", self.repo)
|
||||
archived_path = os.path.join("archived", "apps")
|
||||
@ -160,6 +164,7 @@ class App(AppMeta):
|
||||
log(f"App moved from {active_app_path} to {archived_app_path}")
|
||||
shutil.move(active_app_path, archived_app_path)
|
||||
|
||||
@step(title="Installing App {repo}", success="App {repo} Installed")
|
||||
def install(self, skip_assets=False, verbose=True):
|
||||
from bench.utils.app import get_app_name
|
||||
|
||||
@ -174,6 +179,7 @@ class App(AppMeta):
|
||||
app=app_name, bench_path=self.bench.name, verbose=verbose, skip_assets=skip_assets,
|
||||
)
|
||||
|
||||
@step(title="Uninstalling App {repo}", success="App {repo} Uninstalled")
|
||||
def uninstall(self):
|
||||
env_python = get_env_cmd("python", bench_path=self.bench.name)
|
||||
self.bench.run(f"{env_python} -m pip uninstall -y {self.repo}")
|
||||
|
@ -27,6 +27,7 @@ from bench.utils.bench import (
|
||||
get_venv_path,
|
||||
get_env_cmd,
|
||||
)
|
||||
from bench.utils.render import step
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -119,6 +120,7 @@ class Bench(Base, Validator):
|
||||
# self.build() - removed because it seems unnecessary
|
||||
self.reload()
|
||||
|
||||
@step(title="Building Bench Assets", success="Bench Assets Built")
|
||||
def build(self):
|
||||
# build assets & stuff
|
||||
run_frappe_cmd("build", bench_path=self.name)
|
||||
@ -214,12 +216,14 @@ class BenchSetup(Base):
|
||||
self.bench = bench
|
||||
self.cwd = self.bench.cwd
|
||||
|
||||
@step(title="Setting Up Directories", success="Directories Set Up")
|
||||
def dirs(self):
|
||||
os.makedirs(self.bench.name, exist_ok=True)
|
||||
|
||||
for dirname in paths_in_bench:
|
||||
os.makedirs(os.path.join(self.bench.name, dirname), exist_ok=True)
|
||||
|
||||
@step(title="Setting Up Environment", success="Environment Set Up")
|
||||
def env(self, python="python3"):
|
||||
"""Setup env folder
|
||||
- create env if not exists
|
||||
@ -238,6 +242,7 @@ class BenchSetup(Base):
|
||||
if os.path.exists(frappe):
|
||||
self.run(f"{env_python} -m pip install -q -U -e {frappe}")
|
||||
|
||||
@step(title="Setting Up Bench Config", success="Bench Config Set Up")
|
||||
def config(self, redis=True, procfile=True):
|
||||
"""Setup config folder
|
||||
- create pids folder
|
||||
@ -260,12 +265,14 @@ class BenchSetup(Base):
|
||||
|
||||
return setup_logging(bench_path=self.bench.name)
|
||||
|
||||
@step(title="Setting Up Bench Patches", success="Bench Patches Set Up")
|
||||
def patches(self):
|
||||
shutil.copy(
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), "patches", "patches.txt"),
|
||||
os.path.join(self.bench.name, "patches.txt"),
|
||||
)
|
||||
|
||||
@step(title="Setting Up Backups Cronjob", success="Backups Cronjob Set Up")
|
||||
def backups(self):
|
||||
# TODO: to something better for logging data? - maybe a wrapper that auto-logs with more context
|
||||
logger.log("setting up backups")
|
||||
@ -288,6 +295,7 @@ class BenchSetup(Base):
|
||||
|
||||
logger.log("backups were set up")
|
||||
|
||||
@step(title="Setting Up Bench Dependencies", success="Bench Dependencies Set Up")
|
||||
def requirements(self):
|
||||
from bench.utils.bench import update_requirements
|
||||
update_requirements(bench=self.bench)
|
||||
|
@ -29,9 +29,13 @@ from bench.utils import (
|
||||
)
|
||||
from bench.utils.bench import get_env_cmd
|
||||
|
||||
fancy = True
|
||||
from_command_line = False
|
||||
# these variables are used to show dynamic outputs on the terminal
|
||||
fancy = False
|
||||
bench.LOG_BUFFER = []
|
||||
|
||||
# set when commands are executed via the CLI
|
||||
from_command_line = False
|
||||
|
||||
change_uid_msg = "You should not run this command as root"
|
||||
src = os.path.dirname(__file__)
|
||||
|
||||
|
65
bench/utils/render.py
Normal file
65
bench/utils/render.py
Normal file
@ -0,0 +1,65 @@
|
||||
# imports - standard imports
|
||||
import sys
|
||||
from io import StringIO
|
||||
|
||||
# imports - third party imports
|
||||
import click
|
||||
|
||||
|
||||
class Capturing(list):
|
||||
"""
|
||||
Util to consume the stdout encompassed in it and push it to a list
|
||||
|
||||
with Capturing() as output:
|
||||
subprocess.check_output("ls", shell=True)
|
||||
|
||||
print(output)
|
||||
# ["b'Applications\\nDesktop\\nDocuments\\nDownloads\\n'"]
|
||||
"""
|
||||
|
||||
def __enter__(self):
|
||||
self._stdout = sys.stdout
|
||||
sys.stdout = self._stringio = StringIO()
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.extend(self._stringio.getvalue().splitlines())
|
||||
del self._stringio # free up some memory
|
||||
sys.stdout = self._stdout
|
||||
|
||||
|
||||
def step(title: str = None, success: str = None):
|
||||
"""Supposed to be wrapped around the smallest possible atomic step in a given operation.
|
||||
For instance, `building assets` is a step in the update operation.
|
||||
"""
|
||||
|
||||
def innfn(fn):
|
||||
def wrapper_fn(*args, **kwargs):
|
||||
import bench.cli
|
||||
|
||||
if bench.cli.from_command_line and bench.cli.fancy:
|
||||
kw = args[0].__dict__
|
||||
|
||||
_title = f"{click.style('⏼', fg='bright_yellow')} {title.format(**kw)}"
|
||||
click.secho(_title)
|
||||
|
||||
retval = fn(*args)
|
||||
|
||||
if bench.cli.from_command_line and bench.cli.fancy:
|
||||
click.clear()
|
||||
|
||||
for l in bench.LOG_BUFFER:
|
||||
click.secho(l["message"], fg=l["color"])
|
||||
|
||||
_success = f"{click.style('✔', fg='green')} {success.format(**kw)}"
|
||||
click.echo(_success)
|
||||
|
||||
bench.LOG_BUFFER.append(
|
||||
{"message": _success, "color": None,}
|
||||
)
|
||||
|
||||
return retval
|
||||
|
||||
return wrapper_fn
|
||||
|
||||
return innfn
|
Loading…
Reference in New Issue
Block a user