2
0
mirror of https://github.com/frappe/bench.git synced 2025-01-09 16:36:25 +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:
Gavin D'souza 2021-11-19 10:16:19 +05:30
parent 3995b9237b
commit f117959801
4 changed files with 85 additions and 2 deletions

View File

@ -30,6 +30,8 @@ from bench.utils.bench import (
restart_supervisor_processes, restart_supervisor_processes,
restart_systemd_processes, restart_systemd_processes,
) )
from bench.utils.render import step
logger = logging.getLogger(bench.PROJECT_NAME) logger = logging.getLogger(bench.PROJECT_NAME)
@ -137,6 +139,7 @@ class App(AppMeta):
self.bench = bench self.bench = bench
super().__init__(name, branch, *args, **kwargs) super().__init__(name, branch, *args, **kwargs)
@step(title="Fetching App {repo}", success="App {repo} Fetched")
def get(self): def get(self):
branch = f"--branch {self.tag}" if self.tag else "" branch = f"--branch {self.tag}" if self.tag else ""
shallow = "--depth 1" if self.bench.shallow_clone 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"), cwd=os.path.join(self.bench.name, "apps"),
) )
@step(title="Archiving App {repo}", success="App {repo} Archived")
def remove(self): def remove(self):
active_app_path = os.path.join("apps", self.repo) active_app_path = os.path.join("apps", self.repo)
archived_path = os.path.join("archived", "apps") 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}") log(f"App moved from {active_app_path} to {archived_app_path}")
shutil.move(active_app_path, 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): def install(self, skip_assets=False, verbose=True):
from bench.utils.app import get_app_name 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, 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): def uninstall(self):
env_python = get_env_cmd("python", bench_path=self.bench.name) env_python = get_env_cmd("python", bench_path=self.bench.name)
self.bench.run(f"{env_python} -m pip uninstall -y {self.repo}") self.bench.run(f"{env_python} -m pip uninstall -y {self.repo}")

View File

@ -27,6 +27,7 @@ from bench.utils.bench import (
get_venv_path, get_venv_path,
get_env_cmd, get_env_cmd,
) )
from bench.utils.render import step
if TYPE_CHECKING: if TYPE_CHECKING:
@ -119,6 +120,7 @@ class Bench(Base, Validator):
# self.build() - removed because it seems unnecessary # self.build() - removed because it seems unnecessary
self.reload() self.reload()
@step(title="Building Bench Assets", success="Bench Assets Built")
def build(self): def build(self):
# build assets & stuff # build assets & stuff
run_frappe_cmd("build", bench_path=self.name) run_frappe_cmd("build", bench_path=self.name)
@ -214,12 +216,14 @@ class BenchSetup(Base):
self.bench = bench self.bench = bench
self.cwd = self.bench.cwd self.cwd = self.bench.cwd
@step(title="Setting Up Directories", success="Directories Set Up")
def dirs(self): def dirs(self):
os.makedirs(self.bench.name, exist_ok=True) os.makedirs(self.bench.name, exist_ok=True)
for dirname in paths_in_bench: for dirname in paths_in_bench:
os.makedirs(os.path.join(self.bench.name, dirname), exist_ok=True) 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"): def env(self, python="python3"):
"""Setup env folder """Setup env folder
- create env if not exists - create env if not exists
@ -238,6 +242,7 @@ class BenchSetup(Base):
if os.path.exists(frappe): if os.path.exists(frappe):
self.run(f"{env_python} -m pip install -q -U -e {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): def config(self, redis=True, procfile=True):
"""Setup config folder """Setup config folder
- create pids folder - create pids folder
@ -260,12 +265,14 @@ class BenchSetup(Base):
return setup_logging(bench_path=self.bench.name) return setup_logging(bench_path=self.bench.name)
@step(title="Setting Up Bench Patches", success="Bench Patches Set Up")
def patches(self): def patches(self):
shutil.copy( shutil.copy(
os.path.join(os.path.dirname(os.path.abspath(__file__)), "patches", "patches.txt"), os.path.join(os.path.dirname(os.path.abspath(__file__)), "patches", "patches.txt"),
os.path.join(self.bench.name, "patches.txt"), os.path.join(self.bench.name, "patches.txt"),
) )
@step(title="Setting Up Backups Cronjob", success="Backups Cronjob Set Up")
def backups(self): def backups(self):
# TODO: to something better for logging data? - maybe a wrapper that auto-logs with more context # TODO: to something better for logging data? - maybe a wrapper that auto-logs with more context
logger.log("setting up backups") logger.log("setting up backups")
@ -288,6 +295,7 @@ class BenchSetup(Base):
logger.log("backups were set up") logger.log("backups were set up")
@step(title="Setting Up Bench Dependencies", success="Bench Dependencies Set Up")
def requirements(self): def requirements(self):
from bench.utils.bench import update_requirements from bench.utils.bench import update_requirements
update_requirements(bench=self.bench) update_requirements(bench=self.bench)

View File

@ -29,9 +29,13 @@ from bench.utils import (
) )
from bench.utils.bench import get_env_cmd from bench.utils.bench import get_env_cmd
fancy = True # these variables are used to show dynamic outputs on the terminal
from_command_line = False fancy = False
bench.LOG_BUFFER = [] 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" change_uid_msg = "You should not run this command as root"
src = os.path.dirname(__file__) src = os.path.dirname(__file__)

65
bench/utils/render.py Normal file
View 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