2
0
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:
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_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}")

View File

@ -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)

View File

@ -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
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