Skip to content

Commit

Permalink
feat: New methods in Bench class
Browse files Browse the repository at this point in the history
New methods added in Bench class:
* excluded_apps property
* shallow_clone property
* apps property - check if frappe app & sort
* sync: Sync the apps.txt file from the installed set of Frappe apps
* build: Build bench assets
* reload: Reload the Frappe processes - supervisor restart

New methods added in BenchApps class:
* add/append: Adding an App object to this, installs the app on the
  Bench
* remove: Removing an App from here, uninstalls and archives the app
  • Loading branch information
gavindsouza committed Nov 12, 2021
1 parent 54f9741 commit 53e811f
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 90 deletions.
76 changes: 3 additions & 73 deletions bench/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,87 +289,18 @@ def install_app(app, bench_path=".", verbose=False, no_cache=False, restart_benc
restart_systemd_processes(bench_path=bench_path)


def remove_app(app, bench_path='.'):
import shutil

bench = Bench(bench_path)
app_path = os.path.join(bench_path, 'apps', app)
py = os.path.join(bench_path, 'env', 'bin', 'python')

# validate app removal
if app not in bench.apps:
print(f"No app named {app}")
sys.exit(1)

validate_app_installed_on_sites(app, bench_path=bench_path)

# remove app from bench
exec_cmd("{0} -m pip uninstall -y {1}".format(py, app), cwd=bench_path)
remove_from_appstxt(app, bench_path)
shutil.rmtree(app_path)

# re-build assets and restart processes
run_frappe_cmd("build", bench_path=bench_path)

if bench.conf.get('restart_supervisor_on_update'):
restart_supervisor_processes(bench_path=bench_path)
if bench.conf.get('restart_systemd_on_update'):
restart_systemd_processes(bench_path=bench_path)


def validate_app_installed_on_sites(app, bench_path="."):
print("Checking if app installed on active sites...")
ret = check_app_installed(app, bench_path=bench_path)

if ret is None:
check_app_installed_legacy(app, bench_path=bench_path)
else:
return ret


def check_app_installed(app, bench_path="."):
try:
out = subprocess.check_output(
["bench", "--site", "all", "list-apps", "--format", "json"],
stderr=open(os.devnull, "wb"),
cwd=bench_path,
).decode('utf-8')
except subprocess.CalledProcessError:
return None

try:
apps_sites_dict = json.loads(out)
except JSONDecodeError:
return None

for site, apps in apps_sites_dict.items():
if app in apps:
print("Cannot remove, app is installed on site: {0}".format(site))
sys.exit(1)


def check_app_installed_legacy(app, bench_path="."):
site_path = os.path.join(bench_path, 'sites')

for site in os.listdir(site_path):
req_file = os.path.join(site_path, site, 'site_config.json')
if os.path.exists(req_file):
out = subprocess.check_output(["bench", "--site", site, "list-apps"], cwd=bench_path).decode('utf-8')
if re.search(r'\b' + app + r'\b', out):
print(f"Cannot remove, app is installed on site: {site}")
sys.exit(1)


def pull_apps(apps=None, bench_path='.', reset=False):
'''Check all apps if there no local changes, pull'''
from bench.utils.app import get_remote, get_current_branch

bench = Bench(bench_path)
rebase = '--rebase' if bench.conf.get('rebase_on_pull') else ''
apps = apps or bench.apps
excluded_apps = bench.excluded_apps

# check for local changes
if not reset:
for app in apps:
excluded_apps = get_excluded_apps()
if app in excluded_apps:
print(f"Skipping reset for app {app}")
continue
Expand All @@ -391,7 +322,6 @@ def pull_apps(apps=None, bench_path='.', reset=False):
wait for them to be merged in the core.''')
sys.exit(1)

excluded_apps = get_excluded_apps()
for app in apps:
if app in excluded_apps:
print(f"Skipping pull for app {app}")
Expand Down
101 changes: 84 additions & 17 deletions bench/bench.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,67 @@
import os
import shutil
import sys
import typing
import logging
from typing import MutableSequence

import bench
from bench.utils import remove_backups_crontab, folders_in_bench, get_venv_path, exec_cmd, get_env_cmd
from bench.exceptions import ValidationError
from bench.config.common_site_config import setup_config
from bench.utils import paths_in_bench, get_venv_path, exec_cmd, get_env_cmd, is_frappe_app, get_git_version, run_frappe_cmd
from bench.utils.bench import validate_app_installed_on_sites, restart_supervisor_processes, restart_systemd_processes, remove_backups_crontab


if typing.TYPE_CHECKING:
from bench.app import App

logger = logging.getLogger(bench.PROJECT_NAME)


class Base:
def run(self, cmd):
return exec_cmd(cmd, cwd=self.cwd)
def run(self, cmd, cwd=None):
return exec_cmd(cmd, cwd=cwd or self.cwd)


class Validator:
def validate_app_uninstall(self, app):
if app not in self.apps:
raise ValidationError(f"No app named {app}")
validate_app_installed_on_sites(app, bench_path=self.name)


class Bench(Base):
class Bench(Base, Validator):
def __init__(self, path):
self.name = path
self.cwd = os.path.abspath(path)
self.exists = os.path.exists(self.name)

self.setup = BenchSetup(self)
self.teardown = BenchTearDown(self)
self.apps = BenchApps(self)

self.apps_txt = os.path.join(self.name, 'sites', 'apps.txt')
self.excluded_apps_txt = os.path.join(self.name, 'sites', 'excluded_apps.txt')

@property
def shallow_clone(self):
config = self.conf

if config:
if config.get('release_bench') or not config.get('shallow_clone'):
return False

if get_git_version() > 1.9:
return True

@property
def excluded_apps(self):
try:
with open(self.excluded_apps_txt) as f:
return f.read().strip().split('\n')
except Exception:
return []

@property
def sites(self):
return [
Expand Down Expand Up @@ -59,17 +95,33 @@ def install(self, app, branch=None):
from bench.app import App

app = App(app, branch=branch)

# get app?
# install app to env
# add to apps.txt
return
self.apps.append(app)
self.sync()

def uninstall(self, app):
# remove from apps.txt
# uninstall app from env
# remove app?
return
from bench.app import App

self.validate_app_uninstall(app)
self.apps.remove(App(app, bench=self))
self.sync()
self.build()
self.reload()

def build(self):
# build assets & stuff
run_frappe_cmd("build", bench_path=self.name)

def reload(self):
conf = self.conf
if conf.get('restart_supervisor_on_update'):
restart_supervisor_processes(bench_path=self.name)
if conf.get('restart_systemd_on_update'):
restart_systemd_processes(bench_path=self.name)

def sync(self):
self.apps.initialize_apps()
with open(self.apps_txt, "w") as f:
return f.write("\n".join(self.apps))


class BenchApps(MutableSequence):
Expand All @@ -79,9 +131,10 @@ def __init__(self, bench : Bench):

def initialize_apps(self):
try:
self.apps = open(
os.path.join(self.bench.name, "sites", "apps.txt")
).read().splitlines()
self.apps = [x for x in os.listdir(
os.path.join(self.bench.name, "apps")
) if is_frappe_app(os.path.join(self.bench.name, "apps", x))]
self.apps.sort()
except FileNotFoundError:
self.apps = []

Expand All @@ -108,6 +161,20 @@ def insert(self, key, value):
# TODO: fetch and install app to bench
self.apps.insert(key, value)

def add(self, app: "App"):
app.get()
app.install()
super().append(app.repo)
self.apps.sort()

def remove(self, app: "App"):
app.uninstall()
app.remove()
super().remove(app.repo)

def append(self, app : "App"):
return self.add(app)

def __repr__(self):
return self.__str__()

Expand All @@ -123,7 +190,7 @@ def __init__(self, bench : Bench):
def dirs(self):
os.makedirs(self.bench.name, exist_ok=True)

for dirname in folders_in_bench:
for dirname in paths_in_bench:
os.makedirs(os.path.join(self.bench.name, dirname), exist_ok=True)

def env(self, python="python3"):
Expand Down

0 comments on commit 53e811f

Please sign in to comment.