From d41e05e24ffff934ef71a2aa10d91f975123cccd Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Wed, 19 Jan 2022 13:43:58 +0530 Subject: [PATCH 01/78] feat: introduced simple resolver --- bench/app.py | 37 ++++++++++++++++++++++++++++++++++++- bench/utils/app.py | 6 ++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/bench/app.py b/bench/app.py index 0dc2fba7..fd9b16dc 100755 --- a/bench/app.py +++ b/bench/app.py @@ -1,4 +1,5 @@ # imports - standard imports +from collections import OrderedDict import functools import json import logging @@ -46,6 +47,7 @@ class AppMeta: 3. frappe/healthcare@develop 4. healthcare 5. healthcare@develop, healthcare@v13.12.1 + 6. erpnext References for Version Identifiers: * https://www.python.org/dev/peps/pep-0440/#version-specifiers @@ -91,6 +93,7 @@ class AppMeta: self._setup_details_from_name_tag() def _setup_details_from_mounted_disk(self): + self.branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"]) self.org, self.repo, self.tag = os.path.split(self.name)[-2:] + (self.branch,) def _setup_details_from_name_tag(self): @@ -98,6 +101,7 @@ class AppMeta: self.tag = self.tag or self.branch def _setup_details_from_installed_apps(self): + self.branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"]) self.org, self.repo, self.tag = os.path.split( os.path.join(self.bench.name, "apps", self.name) )[-2:] + (self.branch,) @@ -197,6 +201,35 @@ class App(AppMeta): def uninstall(self): self.bench.run(f"{self.bench.python} -m pip uninstall -y {self.repo}") + def _get_dependencies(self): + from bench.utils.app import get_required_deps_url + import toml + + info_file = None + required_url = get_required_deps_url(self.url, self.tag) + print(required_url) + try: + info_file = toml.loads(requests.get(required_url).text) + except Exception: + click.echo("\nNo toml file found \n", err=True) + + return info_file["required_apps"] if info_file else {} + +def make_resolution_plan(app: App, bench): + """ + decide what apps and versions to install and in what order + """ + resolution = OrderedDict() + resolution[app.repo] = app + for app_name, branch in app._get_dependencies().items(): + dep_app = App(app_name, branch=branch, bench=bench) + resolution[dep_app.repo] = dep_app + resolution.update(make_resolution_plan(dep_app, bench)) + if app_name in resolution: + print("Resolve this conflict!") + return resolution + + def add_to_appstxt(app, bench_path="."): from bench.bench import Bench @@ -310,6 +343,7 @@ def get_app( repo_name = app.repo branch = app.tag bench_setup = False + apps_to_install = make_resolution_plan(app, bench) if not is_bench_directory(bench_path): if not init_bench: @@ -320,8 +354,9 @@ def get_app( from bench.utils.system import init + frappe_app = apps_to_install["frappe"] bench_path = get_available_folder_name(f"{app.repo}-bench", bench_path) - init(path=bench_path, frappe_branch=branch) + init(path=bench_path, frappe_branch=frappe_app.tag) os.chdir(bench_path) bench_setup = True diff --git a/bench/utils/app.py b/bench/utils/app.py index ae980743..f7a6d5cc 100644 --- a/bench/utils/app.py +++ b/bench/utils/app.py @@ -164,6 +164,12 @@ def get_current_branch(app, bench_path="."): repo_dir = get_repo_dir(app, bench_path=bench_path) return get_cmd_output("basename $(git symbolic-ref -q HEAD)", cwd=repo_dir) +def get_required_deps_url(git_url, branch="master", deps="info.toml"): + git_url = ( + git_url.replace(".git", "").replace("github.com", "raw.github.com") + ) + git_url += f"/{branch}/{deps}" if branch else f"/{deps}" + return git_url def get_remote(app, bench_path="."): repo_dir = get_repo_dir(app, bench_path=bench_path) From 7a311a242d574396df82a06cec1dc5b491a26bd8 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Wed, 19 Jan 2022 23:50:35 +0530 Subject: [PATCH 02/78] feat: Added bench command for resolve and install --- bench/commands/__init__.py | 2 ++ bench/commands/make.py | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/bench/commands/__init__.py b/bench/commands/__init__.py index 08a493b9..4cdc8882 100755 --- a/bench/commands/__init__.py +++ b/bench/commands/__init__.py @@ -39,6 +39,7 @@ from bench.commands.make import ( new_app, pip, remove_app, + resolve_and_install, ) bench_command.add_command(init) @@ -48,6 +49,7 @@ bench_command.add_command(new_app) bench_command.add_command(remove_app) bench_command.add_command(exclude_app_for_update) bench_command.add_command(include_app_for_update) +bench_command.add_command(resolve_and_install) bench_command.add_command(pip) diff --git a/bench/commands/make.py b/bench/commands/make.py index 88b37c61..c6b7748b 100755 --- a/bench/commands/make.py +++ b/bench/commands/make.py @@ -147,6 +147,25 @@ def get_app( init_bench=init_bench, ) +@click.command("resolve-and-install", help="Resolve dependencies and install apps") +@click.argument("git-url") +@click.option("--branch", default=None) +@click.option("--skip-assets", is_flag=True, default=False, help="Do not build assets") +@click.option( + "--init-bench", is_flag=True, default=False, help="Initialize Bench if not in one" +) +@click.option("--skip-assets", is_flag=True, default=False, help="Do not build assets") +@click.option("--verbose", is_flag=True, default=False, help="Verbosity") +def resolve_and_install(git_url, branch, skip_assets, verbose, init_bench): + from bench.app import resolve_and_install + + resolve_and_install( + git_url=git_url, + branch=branch, + skip_assets=skip_assets, + init_bench=init_bench, + verbose=verbose, + ) @click.command("new-app", help="Create a new Frappe application under apps folder") @click.option( From 549e8e2a1d6fd59fb87fc30dbdfab1322f8f4a19 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Thu, 20 Jan 2022 15:04:43 +0530 Subject: [PATCH 03/78] feat: Added app states --- bench/app.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ bench/bench.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/bench/app.py b/bench/app.py index fd9b16dc..f4ea84ec 100755 --- a/bench/app.py +++ b/bench/app.py @@ -13,6 +13,7 @@ from datetime import date # imports - third party imports import click +import requests # imports - module imports import bench @@ -197,6 +198,12 @@ class App(AppMeta): app=app_name, bench_path=self.bench.name, verbose=verbose, skip_assets=skip_assets, ) + @step(title="Cloning and installing {repo}", success="App {repo} Installed") + def install_resolved_apps(self, *args, **kwargs): + self.get() + self.install(*args, **kwargs, resolved=True) + self.bench.apps.update_apps_states(self.repo, self.tag) + @step(title="Uninstalling App {repo}", success="App {repo} Uninstalled") def uninstall(self): self.bench.run(f"{self.bench.python} -m pip uninstall -y {self.repo}") @@ -395,6 +402,49 @@ def get_app( ): app.install(verbose=verbose, skip_assets=skip_assets) +def resolve_and_install( + git_url, + branch=None, + bench_path=".", + skip_assets=False, + verbose=False, + init_bench=False, +): + from bench.cli import Bench + from bench.utils.system import init + from bench.utils.app import check_existing_dir + + bench = Bench(bench_path) + app = App(git_url, branch=branch, bench=bench) + + resolution = make_resolution_plan(app, bench) + if "frappe" in resolution: + # Terminal dependency + del resolution["frappe"] + + if init_bench: + bench_path = get_available_folder_name(f"{app.repo}-bench", bench_path) + init( + path=bench_path, + frappe_branch=branch, + skip_assets=skip_assets, + verbose=verbose, + ) + os.chdir(bench_path) + + for repo_name, app in reversed(resolution.items()): + existing_dir, cloned_path = check_existing_dir(bench_path, repo_name) + if existing_dir: + if click.confirm( + f"A directory for the application '{repo_name}' already exists. " + "Do you want to continue and overwrite it?" + ): + click.secho(f"Removing {repo_name}", fg="yellow") + shutil.rmtree(cloned_path) + app.install_resolved_apps(skip_assets=skip_assets, verbose=verbose) + + continue + app.install_resolved_apps(skip_assets=skip_assets, verbose=verbose) def new_app(app, no_git=None, bench_path="."): if bench.FRAPPE_VERSION in (0, None): diff --git a/bench/bench.py b/bench/bench.py index db04fd34..6a0081c3 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -2,6 +2,7 @@ import functools import os import shutil +import json import sys import logging from typing import List, MutableSequence, TYPE_CHECKING @@ -30,6 +31,7 @@ from bench.utils.bench import ( get_env_cmd, ) from bench.utils.render import job, step +from bench.utils.app import get_current_version if TYPE_CHECKING: @@ -55,6 +57,7 @@ class Bench(Base, Validator): def __init__(self, path): self.name = path self.cwd = os.path.abspath(path) + self.apps_states = os.path.join(self.name, "sites", "apps_states.json") self.exists = is_bench_directory(self.name) self.setup = BenchSetup(self) @@ -122,6 +125,7 @@ class Bench(Base, Validator): self.validate_app_uninstall(app) self.apps.remove(App(app, bench=self, to_clone=False)) self.apps.sync() + self.apps.update_apps_states() # self.build() - removed because it seems unnecessary self.reload() @@ -148,6 +152,38 @@ class BenchApps(MutableSequence): def __init__(self, bench: Bench): self.bench = bench self.initialize_apps() + self.initialize_states() + + def initialize_states(self): + try: + with open(self.bench.apps_states, "r") as f: + self.states = json.loads(f.read() or "{}") + except FileNotFoundError: + with open(self.bench.apps_states, "w") as f: + self.states = json.loads(f.read() or "{}") + + def update_apps_states(self, app_name: str = None, resolution: str = None): + # Only tracking for app state for apps installed via `bench resolve-and-install`, + # Not all states can be maintained as some apps may not be install via bench resolve-and-install + # or may not be compatible with versioning + self.initialize_apps() + apps_to_remove = [] + for app in self.states: + if app not in self.apps: + apps_to_remove.append(app) + + for app in apps_to_remove: + del self.states[app] + + if app_name and resolution: + version = get_current_version(app_name) + self.states[app_name] = { + "resolution": resolution, + "version": version, + } + + with open(self.bench.apps_states, "w") as f: + f.write(json.dumps(self.states, indent=4)) def sync(self): self.initialize_apps() From 2363fe38d5a8e83d266ba92d2de6e1bc700a4ccb Mon Sep 17 00:00:00 2001 From: Aradhya Date: Fri, 28 Jan 2022 21:20:14 +0530 Subject: [PATCH 04/78] refactor: moved resolve and install to get-app --- bench/app.py | 36 +++++++++++++++--------------------- bench/commands/__init__.py | 2 -- bench/commands/make.py | 35 ++++++++++++++--------------------- bench/utils/system.py | 14 ++++++++++++-- 4 files changed, 41 insertions(+), 46 deletions(-) diff --git a/bench/app.py b/bench/app.py index f4ea84ec..ab22eea2 100755 --- a/bench/app.py +++ b/bench/app.py @@ -222,7 +222,7 @@ class App(AppMeta): return info_file["required_apps"] if info_file else {} -def make_resolution_plan(app: App, bench): +def make_resolution_plan(app: App, bench: "Bench"): """ decide what apps and versions to install and in what order """ @@ -332,6 +332,7 @@ def get_app( verbose=False, overwrite=False, init_bench=False, + resolve=False, ): """bench get-app clones a Frappe App from remote (GitHub or any other git server), and installs it on the current bench. This also resolves dependencies based on the @@ -343,6 +344,7 @@ def get_app( from bench.bench import Bench import bench as _bench import bench.cli as bench_cli + from bench.utils.app import check_existing_dir bench = Bench(bench_path) app = App(git_url, branch=branch, bench=bench) @@ -375,9 +377,17 @@ def get_app( "color": None, }) + if resolve: + resolve_and_install( + app=app, + bench=bench, + bench_path=bench_path, + skip_assets=skip_assets, + verbose=verbose, + ) + return - cloned_path = os.path.join(bench_path, "apps", repo_name) - dir_already_exists = os.path.isdir(cloned_path) + dir_already_exists, cloned_path = check_existing_dir(bench_path, repo_name) to_clone = not dir_already_exists # application directory already exists @@ -403,35 +413,20 @@ def get_app( app.install(verbose=verbose, skip_assets=skip_assets) def resolve_and_install( - git_url, - branch=None, + app: App, + bench: "Bench", bench_path=".", skip_assets=False, verbose=False, - init_bench=False, ): - from bench.cli import Bench - from bench.utils.system import init from bench.utils.app import check_existing_dir - bench = Bench(bench_path) - app = App(git_url, branch=branch, bench=bench) resolution = make_resolution_plan(app, bench) if "frappe" in resolution: # Terminal dependency del resolution["frappe"] - if init_bench: - bench_path = get_available_folder_name(f"{app.repo}-bench", bench_path) - init( - path=bench_path, - frappe_branch=branch, - skip_assets=skip_assets, - verbose=verbose, - ) - os.chdir(bench_path) - for repo_name, app in reversed(resolution.items()): existing_dir, cloned_path = check_existing_dir(bench_path, repo_name) if existing_dir: @@ -510,7 +505,6 @@ def install_app( if restart_bench: bench.reload() - def pull_apps(apps=None, bench_path=".", reset=False): """Check all apps if there no local changes, pull""" from bench.bench import Bench diff --git a/bench/commands/__init__.py b/bench/commands/__init__.py index 4cdc8882..08a493b9 100755 --- a/bench/commands/__init__.py +++ b/bench/commands/__init__.py @@ -39,7 +39,6 @@ from bench.commands.make import ( new_app, pip, remove_app, - resolve_and_install, ) bench_command.add_command(init) @@ -49,7 +48,6 @@ bench_command.add_command(new_app) bench_command.add_command(remove_app) bench_command.add_command(exclude_app_for_update) bench_command.add_command(include_app_for_update) -bench_command.add_command(resolve_and_install) bench_command.add_command(pip) diff --git a/bench/commands/make.py b/bench/commands/make.py index c6b7748b..c19e71cd 100755 --- a/bench/commands/make.py +++ b/bench/commands/make.py @@ -133,8 +133,20 @@ def drop(path): @click.option( "--init-bench", is_flag=True, default=False, help="Initialize Bench if not in one" ) +@click.option( + "--resolve/--no-resolve", + is_flag=True, + default=False, + help="Resolve dependencies before installing app", +) def get_app( - git_url, branch, name=None, overwrite=False, skip_assets=False, init_bench=False + git_url, + branch, + name=None, + overwrite=False, + skip_assets=False, + init_bench=False, + resolve=False, ): "clone an app from the internet and set it up in your bench" from bench.app import get_app @@ -145,26 +157,7 @@ def get_app( skip_assets=skip_assets, overwrite=overwrite, init_bench=init_bench, - ) - -@click.command("resolve-and-install", help="Resolve dependencies and install apps") -@click.argument("git-url") -@click.option("--branch", default=None) -@click.option("--skip-assets", is_flag=True, default=False, help="Do not build assets") -@click.option( - "--init-bench", is_flag=True, default=False, help="Initialize Bench if not in one" -) -@click.option("--skip-assets", is_flag=True, default=False, help="Do not build assets") -@click.option("--verbose", is_flag=True, default=False, help="Verbosity") -def resolve_and_install(git_url, branch, skip_assets, verbose, init_bench): - from bench.app import resolve_and_install - - resolve_and_install( - git_url=git_url, - branch=branch, - skip_assets=skip_assets, - init_bench=init_bench, - verbose=verbose, + resolve=resolve, ) @click.command("new-app", help="Create a new Frappe application under apps folder") diff --git a/bench/utils/system.py b/bench/utils/system.py index 50c8b1d8..a72ec2ba 100644 --- a/bench/utils/system.py +++ b/bench/utils/system.py @@ -76,7 +76,12 @@ def init( frappe_path = frappe_path or "https://github.com/frappe/frappe.git" get_app( - frappe_path, branch=frappe_branch, bench_path=path, skip_assets=True, verbose=verbose + frappe_path, + branch=frappe_branch, + bench_path=path, + skip_assets=True, + verbose=verbose, + resolve=False, ) # fetch remote apps using config file - deprecate this! @@ -86,7 +91,12 @@ def init( # getting app on bench init using --install-app if install_app: get_app( - install_app, branch=frappe_branch, bench_path=path, skip_assets=True, verbose=verbose + install_app, + branch=frappe_branch, + bench_path=path, + skip_assets=True, + verbose=verbose, + resolve=False, ) if not skip_assets: From e3bd34c12ca6f1e86150e811c6d23f3ab1196504 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Fri, 4 Feb 2022 20:50:35 +0530 Subject: [PATCH 05/78] fix: fixed init in get-app and frappe versions --- .vscode/settings.json | 3 +++ bench/app.py | 22 +++++++++++++--------- bench/utils/__init__.py | 7 +++++++ bench/utils/system.py | 7 ++++++- 4 files changed, 29 insertions(+), 10 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..de288e1e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.formatting.provider": "black" +} \ No newline at end of file diff --git a/bench/app.py b/bench/app.py index ab22eea2..70370c17 100755 --- a/bench/app.py +++ b/bench/app.py @@ -354,6 +354,10 @@ def get_app( bench_setup = False apps_to_install = make_resolution_plan(app, bench) + if resolve: + resolution = make_resolution_plan(app, bench) + frappe_path, frappe_branch = resolution["frappe"].url, resolution["frappe"].tag + if not is_bench_directory(bench_path): if not init_bench: raise NotInBenchDirectoryError( @@ -365,7 +369,11 @@ def get_app( frappe_app = apps_to_install["frappe"] bench_path = get_available_folder_name(f"{app.repo}-bench", bench_path) - init(path=bench_path, frappe_branch=frappe_app.tag) + init( + path=bench_path, + frappe_path=frappe_path if resolve else None, + frappe_branch=frappe_branch if resolve else branch, + ) os.chdir(bench_path) bench_setup = True @@ -378,9 +386,8 @@ def get_app( }) if resolve: - resolve_and_install( - app=app, - bench=bench, + install_resolved_deps( + resolution, bench_path=bench_path, skip_assets=skip_assets, verbose=verbose, @@ -412,17 +419,14 @@ def get_app( ): app.install(verbose=verbose, skip_assets=skip_assets) -def resolve_and_install( - app: App, - bench: "Bench", +def install_resolved_deps( + resolution, bench_path=".", skip_assets=False, verbose=False, ): from bench.utils.app import check_existing_dir - - resolution = make_resolution_plan(app, bench) if "frappe" in resolution: # Terminal dependency del resolution["frappe"] diff --git a/bench/utils/__init__.py b/bench/utils/__init__.py index f75fe263..87998d69 100644 --- a/bench/utils/__init__.py +++ b/bench/utils/__init__.py @@ -11,6 +11,7 @@ from typing import List, Tuple # imports - third party imports import click +import requests # imports - module imports from bench import PROJECT_NAME, VERSION @@ -48,6 +49,12 @@ def is_frappe_app(directory: str) -> bool: return bool(is_frappe_app) +def is_valid_frappe_branch(frappe_path: str, frappe_branch: str): + url = frappe_path + url += "/tree/" + frappe_branch if frappe_branch else "" + return requests.get(url).status_code != 404 + + def log(message, level=0, no_log=False): import bench import bench.cli diff --git a/bench/utils/system.py b/bench/utils/system.py index a72ec2ba..2ad8cd16 100644 --- a/bench/utils/system.py +++ b/bench/utils/system.py @@ -14,6 +14,7 @@ from bench.utils import ( run_frappe_cmd, sudoers_file, which, + is_valid_frappe_branch, ) from bench.utils.bench import build_assets, clone_apps_from from bench.utils.render import job @@ -74,7 +75,11 @@ def init( # remote apps else: frappe_path = frappe_path or "https://github.com/frappe/frappe.git" - + frappe_branch = ( + frappe_branch + if is_valid_frappe_branch(frappe_path, frappe_branch) + else None + ) get_app( frappe_path, branch=frappe_branch, From 42f4d74beb5418de2cfb418ab103dc6e568871d1 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Mon, 7 Feb 2022 10:13:15 +0530 Subject: [PATCH 06/78] fix: fixed app states and checking/updating frappe version on install fix: fixed valid branch check in init --- bench/app.py | 11 ++++++----- bench/bench.py | 5 +---- bench/utils/__init__.py | 8 +++++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/bench/app.py b/bench/app.py index 70370c17..4299b032 100755 --- a/bench/app.py +++ b/bench/app.py @@ -352,11 +352,13 @@ def get_app( repo_name = app.repo branch = app.tag bench_setup = False - apps_to_install = make_resolution_plan(app, bench) + frappe_path, frappe_branch = None, None if resolve: resolution = make_resolution_plan(app, bench) - frappe_path, frappe_branch = resolution["frappe"].url, resolution["frappe"].tag + if "frappe" in resolution: + # Todo: Make frappe a terminal dependency for all frappe apps. + frappe_path, frappe_branch = resolution["frappe"].url, resolution["frappe"].tag if not is_bench_directory(bench_path): if not init_bench: @@ -367,12 +369,11 @@ def get_app( from bench.utils.system import init - frappe_app = apps_to_install["frappe"] bench_path = get_available_folder_name(f"{app.repo}-bench", bench_path) init( path=bench_path, - frappe_path=frappe_path if resolve else None, - frappe_branch=frappe_branch if resolve else branch, + frappe_path=frappe_path, + frappe_branch=frappe_branch if frappe_branch else branch, ) os.chdir(bench_path) bench_setup = True diff --git a/bench/bench.py b/bench/bench.py index 6a0081c3..804abf8f 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -152,7 +152,6 @@ class BenchApps(MutableSequence): def __init__(self, bench: Bench): self.bench = bench self.initialize_apps() - self.initialize_states() def initialize_states(self): try: @@ -163,9 +162,7 @@ class BenchApps(MutableSequence): self.states = json.loads(f.read() or "{}") def update_apps_states(self, app_name: str = None, resolution: str = None): - # Only tracking for app state for apps installed via `bench resolve-and-install`, - # Not all states can be maintained as some apps may not be install via bench resolve-and-install - # or may not be compatible with versioning + self.initialize_states() self.initialize_apps() apps_to_remove = [] for app in self.states: diff --git a/bench/utils/__init__.py b/bench/utils/__init__.py index 87998d69..b0402bee 100644 --- a/bench/utils/__init__.py +++ b/bench/utils/__init__.py @@ -50,9 +50,11 @@ def is_frappe_app(directory: str) -> bool: def is_valid_frappe_branch(frappe_path: str, frappe_branch: str): - url = frappe_path - url += "/tree/" + frappe_branch if frappe_branch else "" - return requests.get(url).status_code != 404 + if "http" in frappe_path: + url = frappe_path + url += "/tree/" + frappe_branch if frappe_branch else "" + return requests.get(url).status_code != 404 + return True def log(message, level=0, no_log=False): From b7e46aab6fce2aa5d2ec22fe3138870be11a8858 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Mon, 7 Feb 2022 10:18:14 +0530 Subject: [PATCH 07/78] fix: removed erroneous file push --- .vscode/settings.json | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index de288e1e..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "python.formatting.provider": "black" -} \ No newline at end of file From c8dfe39c05469a67560b25bb0e5fca5584dd6bab Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sun, 6 Feb 2022 01:22:48 +0530 Subject: [PATCH 08/78] refactor: using hooks.py instead of toml files to read dependencies --- bench/app.py | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/bench/app.py b/bench/app.py index 4299b032..3e7c5243 100755 --- a/bench/app.py +++ b/bench/app.py @@ -1,5 +1,4 @@ # imports - standard imports -from collections import OrderedDict import functools import json import logging @@ -9,6 +8,7 @@ import shutil import subprocess import sys import typing +from collections import OrderedDict from datetime import date # imports - third party imports @@ -210,15 +210,15 @@ class App(AppMeta): def _get_dependencies(self): from bench.utils.app import get_required_deps_url - import toml - info_file = None - required_url = get_required_deps_url(self.url, self.tag) - print(required_url) + required_url = get_required_deps_url(git_url=self.url, repo_name=self.repo, branch=self.tag) try: - info_file = toml.loads(requests.get(required_url).text) - except Exception: - click.echo("\nNo toml file found \n", err=True) + f = requests.get(required_url).text + lines = [x for x in f.split("\n") if x.strip().startswith("required_apps")] + required_apps = eval(lines[0].strip("required_apps").strip().lstrip("=").strip()) + return required_apps + except Exception as e: + return [] return info_file["required_apps"] if info_file else {} @@ -228,16 +228,17 @@ def make_resolution_plan(app: App, bench: "Bench"): """ resolution = OrderedDict() resolution[app.repo] = app - for app_name, branch in app._get_dependencies().items(): - dep_app = App(app_name, branch=branch, bench=bench) + + for app_name in app._get_dependencies(): + dep_app = App(app_name, bench=bench) + if dep_app.repo in resolution: + click.secho(f"{dep_app.repo} is already resolved skipping", fg="yellow") + continue resolution[dep_app.repo] = dep_app resolution.update(make_resolution_plan(dep_app, bench)) - if app_name in resolution: - print("Resolve this conflict!") return resolution - def add_to_appstxt(app, bench_path="."): from bench.bench import Bench @@ -332,7 +333,7 @@ def get_app( verbose=False, overwrite=False, init_bench=False, - resolve=False, + resolve_deps=False, ): """bench get-app clones a Frappe App from remote (GitHub or any other git server), and installs it on the current bench. This also resolves dependencies based on the @@ -341,9 +342,9 @@ def get_app( If the bench_path is not a bench directory, a new bench is created named using the git_url parameter. """ - from bench.bench import Bench import bench as _bench import bench.cli as bench_cli + from bench.bench import Bench from bench.utils.app import check_existing_dir bench = Bench(bench_path) @@ -354,7 +355,7 @@ def get_app( bench_setup = False frappe_path, frappe_branch = None, None - if resolve: + if resolve_deps: resolution = make_resolution_plan(app, bench) if "frappe" in resolution: # Todo: Make frappe a terminal dependency for all frappe apps. @@ -386,7 +387,7 @@ def get_app( "color": None, }) - if resolve: + if resolve_deps: install_resolved_deps( resolution, bench_path=bench_path, From 80cfb9dfb4d065040b705b0615d2229f05544c3e Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sun, 6 Feb 2022 01:24:41 +0530 Subject: [PATCH 09/78] refactor: changed resolve to resolve-deps --- bench/commands/make.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bench/commands/make.py b/bench/commands/make.py index c19e71cd..ac48f410 100755 --- a/bench/commands/make.py +++ b/bench/commands/make.py @@ -134,7 +134,7 @@ def drop(path): "--init-bench", is_flag=True, default=False, help="Initialize Bench if not in one" ) @click.option( - "--resolve/--no-resolve", + "--resolve-deps", is_flag=True, default=False, help="Resolve dependencies before installing app", @@ -146,7 +146,7 @@ def get_app( overwrite=False, skip_assets=False, init_bench=False, - resolve=False, + resolve_deps=False, ): "clone an app from the internet and set it up in your bench" from bench.app import get_app @@ -157,7 +157,7 @@ def get_app( skip_assets=skip_assets, overwrite=overwrite, init_bench=init_bench, - resolve=resolve, + resolve_deps=resolve_deps, ) @click.command("new-app", help="Create a new Frappe application under apps folder") From 7f678a304713c33905156242f275e7521983f41c Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sun, 6 Feb 2022 01:25:22 +0530 Subject: [PATCH 10/78] feat: added support for fetching dependencies from hooks.py --- bench/utils/app.py | 5 +++-- bench/utils/system.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bench/utils/app.py b/bench/utils/app.py index f7a6d5cc..13241290 100644 --- a/bench/utils/app.py +++ b/bench/utils/app.py @@ -164,11 +164,12 @@ def get_current_branch(app, bench_path="."): repo_dir = get_repo_dir(app, bench_path=bench_path) return get_cmd_output("basename $(git symbolic-ref -q HEAD)", cwd=repo_dir) -def get_required_deps_url(git_url, branch="master", deps="info.toml"): +def get_required_deps_url(git_url, repo_name, branch, deps="hooks.py"): git_url = ( git_url.replace(".git", "").replace("github.com", "raw.github.com") ) - git_url += f"/{branch}/{deps}" if branch else f"/{deps}" + branch = branch if branch else "develop" + git_url += f"/{branch}/{repo_name}/{deps}" return git_url def get_remote(app, bench_path="."): diff --git a/bench/utils/system.py b/bench/utils/system.py index 2ad8cd16..5d6aaea9 100644 --- a/bench/utils/system.py +++ b/bench/utils/system.py @@ -86,7 +86,7 @@ def init( bench_path=path, skip_assets=True, verbose=verbose, - resolve=False, + resolve_deps=False, ) # fetch remote apps using config file - deprecate this! From 7e28a3dd9e8a5670083912f30d6ff06cc216b0f0 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Mon, 7 Feb 2022 12:24:50 +0530 Subject: [PATCH 11/78] fix: added code removed via erroneous commit --- bench/app.py | 31 ++++++++++++++----------------- bench/bench.py | 16 ++++++++-------- bench/utils/app.py | 4 ++++ 3 files changed, 26 insertions(+), 25 deletions(-) diff --git a/bench/app.py b/bench/app.py index 3e7c5243..ef73f040 100755 --- a/bench/app.py +++ b/bench/app.py @@ -94,7 +94,6 @@ class AppMeta: self._setup_details_from_name_tag() def _setup_details_from_mounted_disk(self): - self.branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"]) self.org, self.repo, self.tag = os.path.split(self.name)[-2:] + (self.branch,) def _setup_details_from_name_tag(self): @@ -102,7 +101,6 @@ class AppMeta: self.tag = self.tag or self.branch def _setup_details_from_installed_apps(self): - self.branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"]) self.org, self.repo, self.tag = os.path.split( os.path.join(self.bench.name, "apps", self.name) )[-2:] + (self.branch,) @@ -178,31 +176,30 @@ class App(AppMeta): shutil.move(active_app_path, archived_app_path) @step(title="Installing App {repo}", success="App {repo} Installed") - def install(self, skip_assets=False, verbose=False): + def install(self, skip_assets=False, verbose=False, resolved=False): import bench.cli from bench.utils.app import get_app_name verbose = bench.cli.verbose or verbose app_name = get_app_name(self.bench.name, self.repo) - - # TODO: this should go inside install_app only tho - issue: default/resolved branch - setup_app_dependencies( - repo_name=self.repo, - bench_path=self.bench.name, - branch=self.tag, - verbose=verbose, - skip_assets=skip_assets, - ) + if not resolved: + # TODO: this should go inside install_app only tho - issue: default/resolved branch + setup_app_dependencies( + repo_name=self.repo, + bench_path=self.bench.name, + branch=self.tag, + verbose=verbose, + skip_assets=skip_assets, + ) install_app( - app=app_name, bench_path=self.bench.name, verbose=verbose, skip_assets=skip_assets, + app=app_name, tag=self.tag, bench_path=self.bench.name, verbose=verbose, skip_assets=skip_assets, ) @step(title="Cloning and installing {repo}", success="App {repo} Installed") def install_resolved_apps(self, *args, **kwargs): self.get() self.install(*args, **kwargs, resolved=True) - self.bench.apps.update_apps_states(self.repo, self.tag) @step(title="Uninstalling App {repo}", success="App {repo} Uninstalled") def uninstall(self): @@ -217,11 +214,9 @@ class App(AppMeta): lines = [x for x in f.split("\n") if x.strip().startswith("required_apps")] required_apps = eval(lines[0].strip("required_apps").strip().lstrip("=").strip()) return required_apps - except Exception as e: + except Exception: return [] - return info_file["required_apps"] if info_file else {} - def make_resolution_plan(app: App, bench: "Bench"): """ decide what apps and versions to install and in what order @@ -473,6 +468,7 @@ def new_app(app, no_git=None, bench_path="."): def install_app( app, + tag, bench_path=".", verbose=False, no_cache=False, @@ -504,6 +500,7 @@ def install_app( bench.run("yarn install", cwd=app_path) bench.apps.sync() + bench.apps.update_apps_states(app, tag) if not skip_assets: build_assets(bench_path=bench_path, app=app) diff --git a/bench/bench.py b/bench/bench.py index 804abf8f..87dcc3c6 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -57,7 +57,6 @@ class Bench(Base, Validator): def __init__(self, path): self.name = path self.cwd = os.path.abspath(path) - self.apps_states = os.path.join(self.name, "sites", "apps_states.json") self.exists = is_bench_directory(self.name) self.setup = BenchSetup(self) @@ -151,19 +150,20 @@ class Bench(Base, Validator): class BenchApps(MutableSequence): def __init__(self, bench: Bench): self.bench = bench + self.states_path = os.path.join(self.bench.name, "sites", "apps_states.json") self.initialize_apps() - def initialize_states(self): + def set_states(self): try: - with open(self.bench.apps_states, "r") as f: + with open(self.states_path, "r") as f: self.states = json.loads(f.read() or "{}") except FileNotFoundError: - with open(self.bench.apps_states, "w") as f: + with open(self.states_path, "w+") as f: self.states = json.loads(f.read() or "{}") def update_apps_states(self, app_name: str = None, resolution: str = None): - self.initialize_states() self.initialize_apps() + self.set_states() apps_to_remove = [] for app in self.states: if app not in self.apps: @@ -172,14 +172,14 @@ class BenchApps(MutableSequence): for app in apps_to_remove: del self.states[app] - if app_name and resolution: - version = get_current_version(app_name) + if app_name: + version = get_current_version(app_name, self.bench.name) self.states[app_name] = { "resolution": resolution, "version": version, } - with open(self.bench.apps_states, "w") as f: + with open(self.states_path, "w") as f: f.write(json.dumps(self.states, indent=4)) def sync(self): diff --git a/bench/utils/app.py b/bench/utils/app.py index 13241290..e86a0304 100644 --- a/bench/utils/app.py +++ b/bench/utils/app.py @@ -208,6 +208,10 @@ def get_app_name(bench_path, repo_name): return repo_name +def check_existing_dir(bench_path, repo_name): + cloned_path = os.path.join(bench_path, "apps", repo_name) + dir_already_exists = os.path.isdir(cloned_path) + return dir_already_exists, cloned_path def get_current_version(app, bench_path="."): current_version = None From 129e2fd922d6d459e9cd653d577273563a122164 Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Fri, 11 Feb 2022 16:51:34 +0530 Subject: [PATCH 12/78] test: get-app resolve_deps --- bench/tests/test_init.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/bench/tests/test_init.py b/bench/tests/test_init.py index 52c23282..f34a777e 100755 --- a/bench/tests/test_init.py +++ b/bench/tests/test_init.py @@ -105,6 +105,20 @@ class TestBenchInit(TestBenchBase): app_installed_in_env = TEST_FRAPPE_APP in subprocess.check_output(["bench", "pip", "freeze"], cwd=bench_path).decode('utf8') self.assertTrue(app_installed_in_env) + def test_get_app_resolve_deps(self): + FRAPPE_APP = "healthcare" + self.init_bench("test-bench") + bench_path = os.path.join(self.benches_path, "test-bench") + exec_cmd(f"bench get-app {FRAPPE_APP} --resolve-deps", cwd=bench_path) + self.assertTrue(os.path.exists(os.path.join(bench_path, "apps", FRAPPE_APP))) + + states_path = os.path.join(bench_path, "sites", "apps_states.json") + self.assert_(os.path.exists(states_path)) + + with open(states_path, "r") as f: + states = json.load(f) + + self.assert_(FRAPPE_APP in states) def test_install_app(self): bench_name = "test-bench" From bba4019579390533b11e06cef8be9b5644fb7dc8 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Thu, 17 Feb 2022 21:38:01 +0530 Subject: [PATCH 13/78] feat: printing resolved dependencies when using resolve-deps flag wih get-app --- bench/app.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bench/app.py b/bench/app.py index ef73f040..f415f3f9 100755 --- a/bench/app.py +++ b/bench/app.py @@ -352,6 +352,8 @@ def get_app( if resolve_deps: resolution = make_resolution_plan(app, bench) + click.secho("Apps to be installed:", fg="yellow") + print("\n".join([app.name for app in reversed(resolution.values())])) if "frappe" in resolution: # Todo: Make frappe a terminal dependency for all frappe apps. frappe_path, frappe_branch = resolution["frappe"].url, resolution["frappe"].tag From e629ca6f8679d2144394c143602a160424660484 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Thu, 17 Feb 2022 21:48:40 +0530 Subject: [PATCH 14/78] fix: minor fixes --- bench/app.py | 2 +- bench/utils/system.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bench/app.py b/bench/app.py index f415f3f9..7f42889b 100755 --- a/bench/app.py +++ b/bench/app.py @@ -470,7 +470,7 @@ def new_app(app, no_git=None, bench_path="."): def install_app( app, - tag, + tag=None, bench_path=".", verbose=False, no_cache=False, diff --git a/bench/utils/system.py b/bench/utils/system.py index 5d6aaea9..ab27893c 100644 --- a/bench/utils/system.py +++ b/bench/utils/system.py @@ -101,7 +101,7 @@ def init( bench_path=path, skip_assets=True, verbose=verbose, - resolve=False, + resolve_deps=False, ) if not skip_assets: From 8fb6796250a6158c826380cf465ed4e981018062 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Wed, 23 Feb 2022 20:42:22 +0530 Subject: [PATCH 15/78] test: Added tests for app utils --- .travis.yml | 6 +++--- bench/tests/test_utils.py | 25 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 bench/tests/test_utils.py diff --git a/.travis.yml b/.travis.yml index e2b718ae..9c57f8ca 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,17 +48,17 @@ matrix: - name: "Python 3.7 Tests" python: 3.7 env: TEST=bench - script: python -m unittest -v bench.tests.test_init + script: python -m unittest -v bench.tests.test_init && python -m unittest -v bench.tests.test_utils - name: "Python 3.8 Tests" python: 3.8 env: TEST=bench - script: python -m unittest -v bench.tests.test_init + script: python -m unittest -v bench.tests.test_init && python -m unittest -v bench.tests.test_utils - name: "Python 3.9 Tests" python: 3.9 env: TEST=bench - script: python -m unittest -v bench.tests.test_init + script: python -m unittest -v bench.tests.test_init && python -m unittest -v bench.tests.test_utils - name: "Python 3.7 Easy Install" python: 3.7 diff --git a/bench/tests/test_utils.py b/bench/tests/test_utils.py new file mode 100644 index 00000000..91ff66ec --- /dev/null +++ b/bench/tests/test_utils.py @@ -0,0 +1,25 @@ +import unittest + +from bench.app import App +from bench.bench import Bench +from bench.utils import exec_cmd + + +class TestUtils(unittest.TestCase): + def test_app_utils(self): + git_url = "https://github.com/frappe/frappe" + branch = "develop" + app = App(name=git_url, branch=branch, bench=Bench(".")) + self.assertTrue( + all( + [ + app.name == git_url, + app.branch == branch, + app.tag == branch, + app.is_url == True, + app.on_disk == False, + app.org == "frappe", + app.url == git_url, + ] + ) + ) \ No newline at end of file From df76415e6b8046d348f1dc0d3c16c42ef6c0b662 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Thu, 24 Feb 2022 12:40:00 +0530 Subject: [PATCH 16/78] refactor: running utils tests before bench tests --- .travis.yml | 6 +++--- bench/tests/test_utils.py | 34 +++++++++++++++++----------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9c57f8ca..97296634 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,17 +48,17 @@ matrix: - name: "Python 3.7 Tests" python: 3.7 env: TEST=bench - script: python -m unittest -v bench.tests.test_init && python -m unittest -v bench.tests.test_utils + script: python -m unittest -v bench.tests.test_utils && python -m unittest -v bench.tests.test_init - name: "Python 3.8 Tests" python: 3.8 env: TEST=bench - script: python -m unittest -v bench.tests.test_init && python -m unittest -v bench.tests.test_utils + script: python -m unittest -v bench.tests.test_utils && python -m unittest -v bench.tests.test_init - name: "Python 3.9 Tests" python: 3.9 env: TEST=bench - script: python -m unittest -v bench.tests.test_init && python -m unittest -v bench.tests.test_utils + script: python -m unittest -v bench.tests.test_utils && python -m unittest -v bench.tests.test_init - name: "Python 3.7 Easy Install" python: 3.7 diff --git a/bench/tests/test_utils.py b/bench/tests/test_utils.py index 91ff66ec..602dbb38 100644 --- a/bench/tests/test_utils.py +++ b/bench/tests/test_utils.py @@ -6,20 +6,20 @@ from bench.utils import exec_cmd class TestUtils(unittest.TestCase): - def test_app_utils(self): - git_url = "https://github.com/frappe/frappe" - branch = "develop" - app = App(name=git_url, branch=branch, bench=Bench(".")) - self.assertTrue( - all( - [ - app.name == git_url, - app.branch == branch, - app.tag == branch, - app.is_url == True, - app.on_disk == False, - app.org == "frappe", - app.url == git_url, - ] - ) - ) \ No newline at end of file + def test_app_utils(self): + git_url = "https://github.com/frappe/frappe" + branch = "develop" + app = App(name=git_url, branch=branch, bench=Bench(".")) + self.assertTrue( + all( + [ + app.name == git_url, + app.branch == branch, + app.tag == branch, + app.is_url == True, + app.on_disk == False, + app.org == "frappe", + app.url == git_url, + ] + ) + ) From bb911b5e5a4a06d32c98e5f7951a9afe31f96081 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Fri, 25 Feb 2022 16:51:06 +0530 Subject: [PATCH 17/78] refactor: ignoring app deps when install app without --resolve-deps --- bench/app.py | 43 ++++++------------------------------------- 1 file changed, 6 insertions(+), 37 deletions(-) diff --git a/bench/app.py b/bench/app.py index db1e34cb..aaf32b9b 100755 --- a/bench/app.py +++ b/bench/app.py @@ -191,13 +191,9 @@ class App(AppMeta): verbose = bench.cli.verbose or verbose app_name = get_app_name(self.bench.name, self.repo) if not resolved: - # TODO: this should go inside install_app only tho - issue: default/resolved branch - setup_app_dependencies( - repo_name=self.repo, - bench_path=self.bench.name, - branch=self.tag, - verbose=verbose, - skip_assets=skip_assets, + click.secho( + f"Ignoring dependencies of {self.name} to install dependencies use --resolve-deps", + fg="yellow", ) install_app( @@ -303,35 +299,6 @@ def remove_from_excluded_apps_txt(app, bench_path="."): return write_excluded_apps_txt(apps, bench_path=bench_path) -def setup_app_dependencies( - repo_name, bench_path=".", branch=None, skip_assets=False, verbose=False -): - # branch kwarg is somewhat of a hack here; since we're assuming the same branches for all apps - # for eg: if you're installing erpnext@develop, you'll want frappe@develop and healthcare@develop too - import glob - import bench.cli - from bench.bench import Bench - - verbose = bench.cli.verbose or verbose - apps_path = os.path.join(os.path.abspath(bench_path), "apps") - files = glob.glob(os.path.join(apps_path, repo_name, "**", "hooks.py")) - - if files: - with open(files[0]) as f: - lines = [x for x in f.read().split("\n") if x.strip().startswith("required_apps")] - if lines: - required_apps = eval(lines[0].strip("required_apps").strip().lstrip("=").strip()) - # TODO: when the time comes, add version check here - for app in required_apps: - if app not in Bench(bench_path).apps: - get_app( - app, - bench_path=bench_path, - branch=branch, - skip_assets=skip_assets, - verbose=verbose, - ) - def get_app( git_url, @@ -366,7 +333,9 @@ def get_app( if resolve_deps: resolution = make_resolution_plan(app, bench) click.secho("Apps to be installed:", fg="yellow") - print("\n".join([app.name for app in reversed(resolution.values())])) + for idx, app in enumerate(reversed(resolution.values()), start=1): + print(f"{idx}. {app.name}") + if "frappe" in resolution: # Todo: Make frappe a terminal dependency for all frappe apps. frappe_path, frappe_branch = resolution["frappe"].url, resolution["frappe"].tag From de315f27ad31b88ccf557e2257d455a3bed762b9 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Mon, 28 Feb 2022 14:29:05 +0530 Subject: [PATCH 18/78] fix: logging when in init-bench --- bench/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bench/app.py b/bench/app.py index aaf32b9b..37ba560a 100755 --- a/bench/app.py +++ b/bench/app.py @@ -190,7 +190,7 @@ class App(AppMeta): verbose = bench.cli.verbose or verbose app_name = get_app_name(self.bench.name, self.repo) - if not resolved: + if not resolved and self.repo != "frappe": click.secho( f"Ignoring dependencies of {self.name} to install dependencies use --resolve-deps", fg="yellow", From bfd69c37ebabdb7165484b997cb8c9b7ae7f76e8 Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Wed, 19 Jan 2022 13:43:58 +0530 Subject: [PATCH 19/78] feat: introduced simple resolver --- bench/app.py | 36 +++++++++++++++++++++++++++++++++++- bench/utils/app.py | 6 ++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/bench/app.py b/bench/app.py index 5ce0f8fc..59d9b799 100755 --- a/bench/app.py +++ b/bench/app.py @@ -1,4 +1,5 @@ # imports - standard imports +from collections import OrderedDict import functools import json import logging @@ -47,6 +48,7 @@ class AppMeta: 3. frappe/healthcare@develop 4. healthcare 5. healthcare@develop, healthcare@v13.12.1 + 6. erpnext References for Version Identifiers: * https://www.python.org/dev/peps/pep-0440/#version-specifiers @@ -104,6 +106,7 @@ class AppMeta: self.tag = self.tag or self.branch def _setup_details_from_installed_apps(self): + self.branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"]) self.org, self.repo, self.tag = os.path.split( os.path.join(self.bench.name, "apps", self.name) )[-2:] + (self.branch,) @@ -207,6 +210,35 @@ class App(AppMeta): def uninstall(self): self.bench.run(f"{self.bench.python} -m pip uninstall -y {self.repo}") + def _get_dependencies(self): + from bench.utils.app import get_required_deps_url + import toml + + info_file = None + required_url = get_required_deps_url(self.url, self.tag) + print(required_url) + try: + info_file = toml.loads(requests.get(required_url).text) + except Exception: + click.echo("\nNo toml file found \n", err=True) + + return info_file["required_apps"] if info_file else {} + +def make_resolution_plan(app: App, bench): + """ + decide what apps and versions to install and in what order + """ + resolution = OrderedDict() + resolution[app.repo] = app + for app_name, branch in app._get_dependencies().items(): + dep_app = App(app_name, branch=branch, bench=bench) + resolution[dep_app.repo] = dep_app + resolution.update(make_resolution_plan(dep_app, bench)) + if app_name in resolution: + print("Resolve this conflict!") + return resolution + + def add_to_appstxt(app, bench_path="."): from bench.bench import Bench @@ -320,6 +352,7 @@ def get_app( repo_name = app.repo branch = app.tag bench_setup = False + apps_to_install = make_resolution_plan(app, bench) if not is_bench_directory(bench_path): if not init_bench: @@ -330,8 +363,9 @@ def get_app( from bench.utils.system import init + frappe_app = apps_to_install["frappe"] bench_path = get_available_folder_name(f"{app.repo}-bench", bench_path) - init(path=bench_path, frappe_branch=branch) + init(path=bench_path, frappe_branch=frappe_app.tag) os.chdir(bench_path) bench_setup = True diff --git a/bench/utils/app.py b/bench/utils/app.py index ae980743..f7a6d5cc 100644 --- a/bench/utils/app.py +++ b/bench/utils/app.py @@ -164,6 +164,12 @@ def get_current_branch(app, bench_path="."): repo_dir = get_repo_dir(app, bench_path=bench_path) return get_cmd_output("basename $(git symbolic-ref -q HEAD)", cwd=repo_dir) +def get_required_deps_url(git_url, branch="master", deps="info.toml"): + git_url = ( + git_url.replace(".git", "").replace("github.com", "raw.github.com") + ) + git_url += f"/{branch}/{deps}" if branch else f"/{deps}" + return git_url def get_remote(app, bench_path="."): repo_dir = get_repo_dir(app, bench_path=bench_path) From cdaeea1d4a332d3c28cfb39191f46b5081a090ee Mon Sep 17 00:00:00 2001 From: Aradhya Date: Wed, 19 Jan 2022 23:50:35 +0530 Subject: [PATCH 20/78] feat: Added bench command for resolve and install --- bench/commands/__init__.py | 2 ++ bench/commands/make.py | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/bench/commands/__init__.py b/bench/commands/__init__.py index 08a493b9..4cdc8882 100755 --- a/bench/commands/__init__.py +++ b/bench/commands/__init__.py @@ -39,6 +39,7 @@ from bench.commands.make import ( new_app, pip, remove_app, + resolve_and_install, ) bench_command.add_command(init) @@ -48,6 +49,7 @@ bench_command.add_command(new_app) bench_command.add_command(remove_app) bench_command.add_command(exclude_app_for_update) bench_command.add_command(include_app_for_update) +bench_command.add_command(resolve_and_install) bench_command.add_command(pip) diff --git a/bench/commands/make.py b/bench/commands/make.py index 88b37c61..c6b7748b 100755 --- a/bench/commands/make.py +++ b/bench/commands/make.py @@ -147,6 +147,25 @@ def get_app( init_bench=init_bench, ) +@click.command("resolve-and-install", help="Resolve dependencies and install apps") +@click.argument("git-url") +@click.option("--branch", default=None) +@click.option("--skip-assets", is_flag=True, default=False, help="Do not build assets") +@click.option( + "--init-bench", is_flag=True, default=False, help="Initialize Bench if not in one" +) +@click.option("--skip-assets", is_flag=True, default=False, help="Do not build assets") +@click.option("--verbose", is_flag=True, default=False, help="Verbosity") +def resolve_and_install(git_url, branch, skip_assets, verbose, init_bench): + from bench.app import resolve_and_install + + resolve_and_install( + git_url=git_url, + branch=branch, + skip_assets=skip_assets, + init_bench=init_bench, + verbose=verbose, + ) @click.command("new-app", help="Create a new Frappe application under apps folder") @click.option( From fdab757f9b4972e3a782fed75850595dbe9892d1 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Thu, 20 Jan 2022 15:04:43 +0530 Subject: [PATCH 21/78] feat: Added app states --- bench/app.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ bench/bench.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/bench/app.py b/bench/app.py index 59d9b799..c667c155 100755 --- a/bench/app.py +++ b/bench/app.py @@ -14,6 +14,7 @@ from urllib.parse import urlparse # imports - third party imports import click +import requests # imports - module imports import bench @@ -206,6 +207,12 @@ class App(AppMeta): restart_bench=restart_bench ) + @step(title="Cloning and installing {repo}", success="App {repo} Installed") + def install_resolved_apps(self, *args, **kwargs): + self.get() + self.install(*args, **kwargs, resolved=True) + self.bench.apps.update_apps_states(self.repo, self.tag) + @step(title="Uninstalling App {repo}", success="App {repo} Uninstalled") def uninstall(self): self.bench.run(f"{self.bench.python} -m pip uninstall -y {self.repo}") @@ -404,6 +411,49 @@ def get_app( ): app.install(verbose=verbose, skip_assets=skip_assets) +def resolve_and_install( + git_url, + branch=None, + bench_path=".", + skip_assets=False, + verbose=False, + init_bench=False, +): + from bench.cli import Bench + from bench.utils.system import init + from bench.utils.app import check_existing_dir + + bench = Bench(bench_path) + app = App(git_url, branch=branch, bench=bench) + + resolution = make_resolution_plan(app, bench) + if "frappe" in resolution: + # Terminal dependency + del resolution["frappe"] + + if init_bench: + bench_path = get_available_folder_name(f"{app.repo}-bench", bench_path) + init( + path=bench_path, + frappe_branch=branch, + skip_assets=skip_assets, + verbose=verbose, + ) + os.chdir(bench_path) + + for repo_name, app in reversed(resolution.items()): + existing_dir, cloned_path = check_existing_dir(bench_path, repo_name) + if existing_dir: + if click.confirm( + f"A directory for the application '{repo_name}' already exists. " + "Do you want to continue and overwrite it?" + ): + click.secho(f"Removing {repo_name}", fg="yellow") + shutil.rmtree(cloned_path) + app.install_resolved_apps(skip_assets=skip_assets, verbose=verbose) + + continue + app.install_resolved_apps(skip_assets=skip_assets, verbose=verbose) def new_app(app, no_git=None, bench_path="."): if bench.FRAPPE_VERSION in (0, None): diff --git a/bench/bench.py b/bench/bench.py index f359be7c..4e82f402 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -2,6 +2,7 @@ import functools import os import shutil +import json import sys import logging from typing import List, MutableSequence, TYPE_CHECKING @@ -30,6 +31,7 @@ from bench.utils.bench import ( get_env_cmd, ) from bench.utils.render import job, step +from bench.utils.app import get_current_version if TYPE_CHECKING: @@ -55,6 +57,7 @@ class Bench(Base, Validator): def __init__(self, path): self.name = path self.cwd = os.path.abspath(path) + self.apps_states = os.path.join(self.name, "sites", "apps_states.json") self.exists = is_bench_directory(self.name) self.setup = BenchSetup(self) @@ -122,6 +125,7 @@ class Bench(Base, Validator): self.validate_app_uninstall(app) self.apps.remove(App(app, bench=self, to_clone=False)) self.apps.sync() + self.apps.update_apps_states() # self.build() - removed because it seems unnecessary self.reload() @@ -156,6 +160,38 @@ class BenchApps(MutableSequence): def __init__(self, bench: Bench): self.bench = bench self.initialize_apps() + self.initialize_states() + + def initialize_states(self): + try: + with open(self.bench.apps_states, "r") as f: + self.states = json.loads(f.read() or "{}") + except FileNotFoundError: + with open(self.bench.apps_states, "w") as f: + self.states = json.loads(f.read() or "{}") + + def update_apps_states(self, app_name: str = None, resolution: str = None): + # Only tracking for app state for apps installed via `bench resolve-and-install`, + # Not all states can be maintained as some apps may not be install via bench resolve-and-install + # or may not be compatible with versioning + self.initialize_apps() + apps_to_remove = [] + for app in self.states: + if app not in self.apps: + apps_to_remove.append(app) + + for app in apps_to_remove: + del self.states[app] + + if app_name and resolution: + version = get_current_version(app_name) + self.states[app_name] = { + "resolution": resolution, + "version": version, + } + + with open(self.bench.apps_states, "w") as f: + f.write(json.dumps(self.states, indent=4)) def sync(self): self.initialize_apps() From 210146d2227ae2e6a51d5d82998b81232ceb6ce1 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Fri, 28 Jan 2022 21:20:14 +0530 Subject: [PATCH 22/78] refactor: moved resolve and install to get-app --- bench/app.py | 36 +++++++++++++++--------------------- bench/commands/__init__.py | 2 -- bench/commands/make.py | 35 ++++++++++++++--------------------- bench/utils/system.py | 14 ++++++++++++-- 4 files changed, 41 insertions(+), 46 deletions(-) diff --git a/bench/app.py b/bench/app.py index c667c155..fca841f7 100755 --- a/bench/app.py +++ b/bench/app.py @@ -231,7 +231,7 @@ class App(AppMeta): return info_file["required_apps"] if info_file else {} -def make_resolution_plan(app: App, bench): +def make_resolution_plan(app: App, bench: "Bench"): """ decide what apps and versions to install and in what order """ @@ -341,6 +341,7 @@ def get_app( verbose=False, overwrite=False, init_bench=False, + resolve=False, ): """bench get-app clones a Frappe App from remote (GitHub or any other git server), and installs it on the current bench. This also resolves dependencies based on the @@ -352,6 +353,7 @@ def get_app( from bench.bench import Bench import bench as _bench import bench.cli as bench_cli + from bench.utils.app import check_existing_dir bench = Bench(bench_path) app = App(git_url, branch=branch, bench=bench) @@ -384,9 +386,17 @@ def get_app( "color": None, }) + if resolve: + resolve_and_install( + app=app, + bench=bench, + bench_path=bench_path, + skip_assets=skip_assets, + verbose=verbose, + ) + return - cloned_path = os.path.join(bench_path, "apps", repo_name) - dir_already_exists = os.path.isdir(cloned_path) + dir_already_exists, cloned_path = check_existing_dir(bench_path, repo_name) to_clone = not dir_already_exists # application directory already exists @@ -412,35 +422,20 @@ def get_app( app.install(verbose=verbose, skip_assets=skip_assets) def resolve_and_install( - git_url, - branch=None, + app: App, + bench: "Bench", bench_path=".", skip_assets=False, verbose=False, - init_bench=False, ): - from bench.cli import Bench - from bench.utils.system import init from bench.utils.app import check_existing_dir - bench = Bench(bench_path) - app = App(git_url, branch=branch, bench=bench) resolution = make_resolution_plan(app, bench) if "frappe" in resolution: # Terminal dependency del resolution["frappe"] - if init_bench: - bench_path = get_available_folder_name(f"{app.repo}-bench", bench_path) - init( - path=bench_path, - frappe_branch=branch, - skip_assets=skip_assets, - verbose=verbose, - ) - os.chdir(bench_path) - for repo_name, app in reversed(resolution.items()): existing_dir, cloned_path = check_existing_dir(bench_path, repo_name) if existing_dir: @@ -519,7 +514,6 @@ def install_app( if restart_bench: bench.reload() - def pull_apps(apps=None, bench_path=".", reset=False): """Check all apps if there no local changes, pull""" from bench.bench import Bench diff --git a/bench/commands/__init__.py b/bench/commands/__init__.py index 4cdc8882..08a493b9 100755 --- a/bench/commands/__init__.py +++ b/bench/commands/__init__.py @@ -39,7 +39,6 @@ from bench.commands.make import ( new_app, pip, remove_app, - resolve_and_install, ) bench_command.add_command(init) @@ -49,7 +48,6 @@ bench_command.add_command(new_app) bench_command.add_command(remove_app) bench_command.add_command(exclude_app_for_update) bench_command.add_command(include_app_for_update) -bench_command.add_command(resolve_and_install) bench_command.add_command(pip) diff --git a/bench/commands/make.py b/bench/commands/make.py index c6b7748b..c19e71cd 100755 --- a/bench/commands/make.py +++ b/bench/commands/make.py @@ -133,8 +133,20 @@ def drop(path): @click.option( "--init-bench", is_flag=True, default=False, help="Initialize Bench if not in one" ) +@click.option( + "--resolve/--no-resolve", + is_flag=True, + default=False, + help="Resolve dependencies before installing app", +) def get_app( - git_url, branch, name=None, overwrite=False, skip_assets=False, init_bench=False + git_url, + branch, + name=None, + overwrite=False, + skip_assets=False, + init_bench=False, + resolve=False, ): "clone an app from the internet and set it up in your bench" from bench.app import get_app @@ -145,26 +157,7 @@ def get_app( skip_assets=skip_assets, overwrite=overwrite, init_bench=init_bench, - ) - -@click.command("resolve-and-install", help="Resolve dependencies and install apps") -@click.argument("git-url") -@click.option("--branch", default=None) -@click.option("--skip-assets", is_flag=True, default=False, help="Do not build assets") -@click.option( - "--init-bench", is_flag=True, default=False, help="Initialize Bench if not in one" -) -@click.option("--skip-assets", is_flag=True, default=False, help="Do not build assets") -@click.option("--verbose", is_flag=True, default=False, help="Verbosity") -def resolve_and_install(git_url, branch, skip_assets, verbose, init_bench): - from bench.app import resolve_and_install - - resolve_and_install( - git_url=git_url, - branch=branch, - skip_assets=skip_assets, - init_bench=init_bench, - verbose=verbose, + resolve=resolve, ) @click.command("new-app", help="Create a new Frappe application under apps folder") diff --git a/bench/utils/system.py b/bench/utils/system.py index 50c8b1d8..a72ec2ba 100644 --- a/bench/utils/system.py +++ b/bench/utils/system.py @@ -76,7 +76,12 @@ def init( frappe_path = frappe_path or "https://github.com/frappe/frappe.git" get_app( - frappe_path, branch=frappe_branch, bench_path=path, skip_assets=True, verbose=verbose + frappe_path, + branch=frappe_branch, + bench_path=path, + skip_assets=True, + verbose=verbose, + resolve=False, ) # fetch remote apps using config file - deprecate this! @@ -86,7 +91,12 @@ def init( # getting app on bench init using --install-app if install_app: get_app( - install_app, branch=frappe_branch, bench_path=path, skip_assets=True, verbose=verbose + install_app, + branch=frappe_branch, + bench_path=path, + skip_assets=True, + verbose=verbose, + resolve=False, ) if not skip_assets: From 88c23673d6dc52d580444775dceab6783377829f Mon Sep 17 00:00:00 2001 From: Aradhya Date: Fri, 4 Feb 2022 20:50:35 +0530 Subject: [PATCH 23/78] fix: fixed init in get-app and frappe versions --- .vscode/settings.json | 3 +++ bench/app.py | 22 +++++++++++++--------- bench/utils/__init__.py | 7 +++++++ bench/utils/system.py | 7 ++++++- 4 files changed, 29 insertions(+), 10 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..de288e1e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.formatting.provider": "black" +} \ No newline at end of file diff --git a/bench/app.py b/bench/app.py index fca841f7..7ac7aa2c 100755 --- a/bench/app.py +++ b/bench/app.py @@ -363,6 +363,10 @@ def get_app( bench_setup = False apps_to_install = make_resolution_plan(app, bench) + if resolve: + resolution = make_resolution_plan(app, bench) + frappe_path, frappe_branch = resolution["frappe"].url, resolution["frappe"].tag + if not is_bench_directory(bench_path): if not init_bench: raise NotInBenchDirectoryError( @@ -374,7 +378,11 @@ def get_app( frappe_app = apps_to_install["frappe"] bench_path = get_available_folder_name(f"{app.repo}-bench", bench_path) - init(path=bench_path, frappe_branch=frappe_app.tag) + init( + path=bench_path, + frappe_path=frappe_path if resolve else None, + frappe_branch=frappe_branch if resolve else branch, + ) os.chdir(bench_path) bench_setup = True @@ -387,9 +395,8 @@ def get_app( }) if resolve: - resolve_and_install( - app=app, - bench=bench, + install_resolved_deps( + resolution, bench_path=bench_path, skip_assets=skip_assets, verbose=verbose, @@ -421,17 +428,14 @@ def get_app( ): app.install(verbose=verbose, skip_assets=skip_assets) -def resolve_and_install( - app: App, - bench: "Bench", +def install_resolved_deps( + resolution, bench_path=".", skip_assets=False, verbose=False, ): from bench.utils.app import check_existing_dir - - resolution = make_resolution_plan(app, bench) if "frappe" in resolution: # Terminal dependency del resolution["frappe"] diff --git a/bench/utils/__init__.py b/bench/utils/__init__.py index f75fe263..87998d69 100644 --- a/bench/utils/__init__.py +++ b/bench/utils/__init__.py @@ -11,6 +11,7 @@ from typing import List, Tuple # imports - third party imports import click +import requests # imports - module imports from bench import PROJECT_NAME, VERSION @@ -48,6 +49,12 @@ def is_frappe_app(directory: str) -> bool: return bool(is_frappe_app) +def is_valid_frappe_branch(frappe_path: str, frappe_branch: str): + url = frappe_path + url += "/tree/" + frappe_branch if frappe_branch else "" + return requests.get(url).status_code != 404 + + def log(message, level=0, no_log=False): import bench import bench.cli diff --git a/bench/utils/system.py b/bench/utils/system.py index a72ec2ba..2ad8cd16 100644 --- a/bench/utils/system.py +++ b/bench/utils/system.py @@ -14,6 +14,7 @@ from bench.utils import ( run_frappe_cmd, sudoers_file, which, + is_valid_frappe_branch, ) from bench.utils.bench import build_assets, clone_apps_from from bench.utils.render import job @@ -74,7 +75,11 @@ def init( # remote apps else: frappe_path = frappe_path or "https://github.com/frappe/frappe.git" - + frappe_branch = ( + frappe_branch + if is_valid_frappe_branch(frappe_path, frappe_branch) + else None + ) get_app( frappe_path, branch=frappe_branch, From 30a4ec3b42db8d7e953521ef311c9a80515b4948 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Mon, 7 Feb 2022 10:13:15 +0530 Subject: [PATCH 24/78] fix: fixed app states and checking/updating frappe version on install fix: fixed valid branch check in init --- bench/app.py | 11 ++++++----- bench/bench.py | 5 +---- bench/utils/__init__.py | 8 +++++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/bench/app.py b/bench/app.py index 7ac7aa2c..7aa9b2e8 100755 --- a/bench/app.py +++ b/bench/app.py @@ -361,11 +361,13 @@ def get_app( repo_name = app.repo branch = app.tag bench_setup = False - apps_to_install = make_resolution_plan(app, bench) + frappe_path, frappe_branch = None, None if resolve: resolution = make_resolution_plan(app, bench) - frappe_path, frappe_branch = resolution["frappe"].url, resolution["frappe"].tag + if "frappe" in resolution: + # Todo: Make frappe a terminal dependency for all frappe apps. + frappe_path, frappe_branch = resolution["frappe"].url, resolution["frappe"].tag if not is_bench_directory(bench_path): if not init_bench: @@ -376,12 +378,11 @@ def get_app( from bench.utils.system import init - frappe_app = apps_to_install["frappe"] bench_path = get_available_folder_name(f"{app.repo}-bench", bench_path) init( path=bench_path, - frappe_path=frappe_path if resolve else None, - frappe_branch=frappe_branch if resolve else branch, + frappe_path=frappe_path, + frappe_branch=frappe_branch if frappe_branch else branch, ) os.chdir(bench_path) bench_setup = True diff --git a/bench/bench.py b/bench/bench.py index 4e82f402..25b26c1f 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -160,7 +160,6 @@ class BenchApps(MutableSequence): def __init__(self, bench: Bench): self.bench = bench self.initialize_apps() - self.initialize_states() def initialize_states(self): try: @@ -171,9 +170,7 @@ class BenchApps(MutableSequence): self.states = json.loads(f.read() or "{}") def update_apps_states(self, app_name: str = None, resolution: str = None): - # Only tracking for app state for apps installed via `bench resolve-and-install`, - # Not all states can be maintained as some apps may not be install via bench resolve-and-install - # or may not be compatible with versioning + self.initialize_states() self.initialize_apps() apps_to_remove = [] for app in self.states: diff --git a/bench/utils/__init__.py b/bench/utils/__init__.py index 87998d69..b0402bee 100644 --- a/bench/utils/__init__.py +++ b/bench/utils/__init__.py @@ -50,9 +50,11 @@ def is_frappe_app(directory: str) -> bool: def is_valid_frappe_branch(frappe_path: str, frappe_branch: str): - url = frappe_path - url += "/tree/" + frappe_branch if frappe_branch else "" - return requests.get(url).status_code != 404 + if "http" in frappe_path: + url = frappe_path + url += "/tree/" + frappe_branch if frappe_branch else "" + return requests.get(url).status_code != 404 + return True def log(message, level=0, no_log=False): From f2fe56962fd93953b80a7b304c00c797afb1bb57 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Mon, 7 Feb 2022 10:18:14 +0530 Subject: [PATCH 25/78] fix: removed erroneous file push --- .vscode/settings.json | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index de288e1e..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "python.formatting.provider": "black" -} \ No newline at end of file From 4feb07684a2552a2da8403fc2615888248390366 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sun, 6 Feb 2022 01:22:48 +0530 Subject: [PATCH 26/78] refactor: using hooks.py instead of toml files to read dependencies --- bench/app.py | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/bench/app.py b/bench/app.py index 7aa9b2e8..242ebf36 100755 --- a/bench/app.py +++ b/bench/app.py @@ -1,5 +1,4 @@ # imports - standard imports -from collections import OrderedDict import functools import json import logging @@ -9,6 +8,7 @@ import shutil import subprocess import sys import typing +from collections import OrderedDict from datetime import date from urllib.parse import urlparse @@ -219,15 +219,15 @@ class App(AppMeta): def _get_dependencies(self): from bench.utils.app import get_required_deps_url - import toml - info_file = None - required_url = get_required_deps_url(self.url, self.tag) - print(required_url) + required_url = get_required_deps_url(git_url=self.url, repo_name=self.repo, branch=self.tag) try: - info_file = toml.loads(requests.get(required_url).text) - except Exception: - click.echo("\nNo toml file found \n", err=True) + f = requests.get(required_url).text + lines = [x for x in f.split("\n") if x.strip().startswith("required_apps")] + required_apps = eval(lines[0].strip("required_apps").strip().lstrip("=").strip()) + return required_apps + except Exception as e: + return [] return info_file["required_apps"] if info_file else {} @@ -237,16 +237,17 @@ def make_resolution_plan(app: App, bench: "Bench"): """ resolution = OrderedDict() resolution[app.repo] = app - for app_name, branch in app._get_dependencies().items(): - dep_app = App(app_name, branch=branch, bench=bench) + + for app_name in app._get_dependencies(): + dep_app = App(app_name, bench=bench) + if dep_app.repo in resolution: + click.secho(f"{dep_app.repo} is already resolved skipping", fg="yellow") + continue resolution[dep_app.repo] = dep_app resolution.update(make_resolution_plan(dep_app, bench)) - if app_name in resolution: - print("Resolve this conflict!") return resolution - def add_to_appstxt(app, bench_path="."): from bench.bench import Bench @@ -341,7 +342,7 @@ def get_app( verbose=False, overwrite=False, init_bench=False, - resolve=False, + resolve_deps=False, ): """bench get-app clones a Frappe App from remote (GitHub or any other git server), and installs it on the current bench. This also resolves dependencies based on the @@ -350,9 +351,9 @@ def get_app( If the bench_path is not a bench directory, a new bench is created named using the git_url parameter. """ - from bench.bench import Bench import bench as _bench import bench.cli as bench_cli + from bench.bench import Bench from bench.utils.app import check_existing_dir bench = Bench(bench_path) @@ -363,7 +364,7 @@ def get_app( bench_setup = False frappe_path, frappe_branch = None, None - if resolve: + if resolve_deps: resolution = make_resolution_plan(app, bench) if "frappe" in resolution: # Todo: Make frappe a terminal dependency for all frappe apps. @@ -395,7 +396,7 @@ def get_app( "color": None, }) - if resolve: + if resolve_deps: install_resolved_deps( resolution, bench_path=bench_path, From 1cd914933f27ab36121bd5c995e8de74c1f0aa1c Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sun, 6 Feb 2022 01:24:41 +0530 Subject: [PATCH 27/78] refactor: changed resolve to resolve-deps --- bench/commands/make.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bench/commands/make.py b/bench/commands/make.py index c19e71cd..ac48f410 100755 --- a/bench/commands/make.py +++ b/bench/commands/make.py @@ -134,7 +134,7 @@ def drop(path): "--init-bench", is_flag=True, default=False, help="Initialize Bench if not in one" ) @click.option( - "--resolve/--no-resolve", + "--resolve-deps", is_flag=True, default=False, help="Resolve dependencies before installing app", @@ -146,7 +146,7 @@ def get_app( overwrite=False, skip_assets=False, init_bench=False, - resolve=False, + resolve_deps=False, ): "clone an app from the internet and set it up in your bench" from bench.app import get_app @@ -157,7 +157,7 @@ def get_app( skip_assets=skip_assets, overwrite=overwrite, init_bench=init_bench, - resolve=resolve, + resolve_deps=resolve_deps, ) @click.command("new-app", help="Create a new Frappe application under apps folder") From 58319a21b7b40ddc76704e7505ce8ca90a2ae882 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sun, 6 Feb 2022 01:25:22 +0530 Subject: [PATCH 28/78] feat: added support for fetching dependencies from hooks.py --- bench/utils/app.py | 5 +++-- bench/utils/system.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bench/utils/app.py b/bench/utils/app.py index f7a6d5cc..13241290 100644 --- a/bench/utils/app.py +++ b/bench/utils/app.py @@ -164,11 +164,12 @@ def get_current_branch(app, bench_path="."): repo_dir = get_repo_dir(app, bench_path=bench_path) return get_cmd_output("basename $(git symbolic-ref -q HEAD)", cwd=repo_dir) -def get_required_deps_url(git_url, branch="master", deps="info.toml"): +def get_required_deps_url(git_url, repo_name, branch, deps="hooks.py"): git_url = ( git_url.replace(".git", "").replace("github.com", "raw.github.com") ) - git_url += f"/{branch}/{deps}" if branch else f"/{deps}" + branch = branch if branch else "develop" + git_url += f"/{branch}/{repo_name}/{deps}" return git_url def get_remote(app, bench_path="."): diff --git a/bench/utils/system.py b/bench/utils/system.py index 2ad8cd16..5d6aaea9 100644 --- a/bench/utils/system.py +++ b/bench/utils/system.py @@ -86,7 +86,7 @@ def init( bench_path=path, skip_assets=True, verbose=verbose, - resolve=False, + resolve_deps=False, ) # fetch remote apps using config file - deprecate this! From 5b641758cf64dd82362905cf79c192bf9ce0f502 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Mon, 7 Feb 2022 12:24:50 +0530 Subject: [PATCH 29/78] fix: added code removed via erroneous commit --- bench/app.py | 29 ++++++++++++++--------------- bench/bench.py | 16 ++++++++-------- bench/utils/app.py | 4 ++++ 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/bench/app.py b/bench/app.py index 242ebf36..9c467a8a 100755 --- a/bench/app.py +++ b/bench/app.py @@ -107,7 +107,6 @@ class AppMeta: self.tag = self.tag or self.branch def _setup_details_from_installed_apps(self): - self.branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"]) self.org, self.repo, self.tag = os.path.split( os.path.join(self.bench.name, "apps", self.name) )[-2:] + (self.branch,) @@ -183,24 +182,25 @@ class App(AppMeta): shutil.move(active_app_path, archived_app_path) @step(title="Installing App {repo}", success="App {repo} Installed") - def install(self, skip_assets=False, verbose=False, restart_bench=True): + def install(self, skip_assets=False, verbose=False, restart_bench=True, resolved=False): import bench.cli from bench.utils.app import get_app_name verbose = bench.cli.verbose or verbose app_name = get_app_name(self.bench.name, self.repo) - - # TODO: this should go inside install_app only tho - issue: default/resolved branch - setup_app_dependencies( - repo_name=self.repo, - bench_path=self.bench.name, - branch=self.tag, - verbose=verbose, - skip_assets=skip_assets, - ) + if not resolved: + # TODO: this should go inside install_app only tho - issue: default/resolved branch + setup_app_dependencies( + repo_name=self.repo, + bench_path=self.bench.name, + branch=self.tag, + verbose=verbose, + skip_assets=skip_assets, + ) install_app( app=app_name, + tag=self.tag, bench_path=self.bench.name, verbose=verbose, skip_assets=skip_assets, @@ -211,7 +211,6 @@ class App(AppMeta): def install_resolved_apps(self, *args, **kwargs): self.get() self.install(*args, **kwargs, resolved=True) - self.bench.apps.update_apps_states(self.repo, self.tag) @step(title="Uninstalling App {repo}", success="App {repo} Uninstalled") def uninstall(self): @@ -226,11 +225,9 @@ class App(AppMeta): lines = [x for x in f.split("\n") if x.strip().startswith("required_apps")] required_apps = eval(lines[0].strip("required_apps").strip().lstrip("=").strip()) return required_apps - except Exception as e: + except Exception: return [] - return info_file["required_apps"] if info_file else {} - def make_resolution_plan(app: App, bench: "Bench"): """ decide what apps and versions to install and in what order @@ -482,6 +479,7 @@ def new_app(app, no_git=None, bench_path="."): def install_app( app, + tag, bench_path=".", verbose=False, no_cache=False, @@ -513,6 +511,7 @@ def install_app( bench.run("yarn install", cwd=app_path) bench.apps.sync() + bench.apps.update_apps_states(app, tag) if not skip_assets: build_assets(bench_path=bench_path, app=app) diff --git a/bench/bench.py b/bench/bench.py index 25b26c1f..21c40c3d 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -57,7 +57,6 @@ class Bench(Base, Validator): def __init__(self, path): self.name = path self.cwd = os.path.abspath(path) - self.apps_states = os.path.join(self.name, "sites", "apps_states.json") self.exists = is_bench_directory(self.name) self.setup = BenchSetup(self) @@ -159,19 +158,20 @@ class Bench(Base, Validator): class BenchApps(MutableSequence): def __init__(self, bench: Bench): self.bench = bench + self.states_path = os.path.join(self.bench.name, "sites", "apps_states.json") self.initialize_apps() - def initialize_states(self): + def set_states(self): try: - with open(self.bench.apps_states, "r") as f: + with open(self.states_path, "r") as f: self.states = json.loads(f.read() or "{}") except FileNotFoundError: - with open(self.bench.apps_states, "w") as f: + with open(self.states_path, "w+") as f: self.states = json.loads(f.read() or "{}") def update_apps_states(self, app_name: str = None, resolution: str = None): - self.initialize_states() self.initialize_apps() + self.set_states() apps_to_remove = [] for app in self.states: if app not in self.apps: @@ -180,14 +180,14 @@ class BenchApps(MutableSequence): for app in apps_to_remove: del self.states[app] - if app_name and resolution: - version = get_current_version(app_name) + if app_name: + version = get_current_version(app_name, self.bench.name) self.states[app_name] = { "resolution": resolution, "version": version, } - with open(self.bench.apps_states, "w") as f: + with open(self.states_path, "w") as f: f.write(json.dumps(self.states, indent=4)) def sync(self): diff --git a/bench/utils/app.py b/bench/utils/app.py index 13241290..e86a0304 100644 --- a/bench/utils/app.py +++ b/bench/utils/app.py @@ -208,6 +208,10 @@ def get_app_name(bench_path, repo_name): return repo_name +def check_existing_dir(bench_path, repo_name): + cloned_path = os.path.join(bench_path, "apps", repo_name) + dir_already_exists = os.path.isdir(cloned_path) + return dir_already_exists, cloned_path def get_current_version(app, bench_path="."): current_version = None From 95b0834932145008ba13259781938e16d04b7e85 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Thu, 17 Feb 2022 21:38:01 +0530 Subject: [PATCH 30/78] feat: printing resolved dependencies when using resolve-deps flag wih get-app --- bench/app.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bench/app.py b/bench/app.py index 9c467a8a..1f8df84e 100755 --- a/bench/app.py +++ b/bench/app.py @@ -363,6 +363,8 @@ def get_app( if resolve_deps: resolution = make_resolution_plan(app, bench) + click.secho("Apps to be installed:", fg="yellow") + print("\n".join([app.name for app in reversed(resolution.values())])) if "frappe" in resolution: # Todo: Make frappe a terminal dependency for all frappe apps. frappe_path, frappe_branch = resolution["frappe"].url, resolution["frappe"].tag From cccd76729f271d08179473ea18755b7062270d17 Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Fri, 11 Feb 2022 16:51:34 +0530 Subject: [PATCH 31/78] test: get-app resolve_deps --- bench/tests/test_init.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/bench/tests/test_init.py b/bench/tests/test_init.py index 80018b59..69221b14 100755 --- a/bench/tests/test_init.py +++ b/bench/tests/test_init.py @@ -107,6 +107,20 @@ class TestBenchInit(TestBenchBase): app_installed_in_env = TEST_FRAPPE_APP in subprocess.check_output(["bench", "pip", "freeze"], cwd=bench_path).decode('utf8') self.assertTrue(app_installed_in_env) + def test_get_app_resolve_deps(self): + FRAPPE_APP = "healthcare" + self.init_bench("test-bench") + bench_path = os.path.join(self.benches_path, "test-bench") + exec_cmd(f"bench get-app {FRAPPE_APP} --resolve-deps", cwd=bench_path) + self.assertTrue(os.path.exists(os.path.join(bench_path, "apps", FRAPPE_APP))) + + states_path = os.path.join(bench_path, "sites", "apps_states.json") + self.assert_(os.path.exists(states_path)) + + with open(states_path, "r") as f: + states = json.load(f) + + self.assert_(FRAPPE_APP in states) def test_install_app(self): bench_name = "test-bench" From 94105c0aa8e565a25d6243fe9484098629de258d Mon Sep 17 00:00:00 2001 From: Aradhya Date: Thu, 17 Feb 2022 21:48:40 +0530 Subject: [PATCH 32/78] fix: minor fixes --- bench/app.py | 2 +- bench/utils/system.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bench/app.py b/bench/app.py index 1f8df84e..951e0a54 100755 --- a/bench/app.py +++ b/bench/app.py @@ -481,7 +481,7 @@ def new_app(app, no_git=None, bench_path="."): def install_app( app, - tag, + tag=None, bench_path=".", verbose=False, no_cache=False, diff --git a/bench/utils/system.py b/bench/utils/system.py index 5d6aaea9..ab27893c 100644 --- a/bench/utils/system.py +++ b/bench/utils/system.py @@ -101,7 +101,7 @@ def init( bench_path=path, skip_assets=True, verbose=verbose, - resolve=False, + resolve_deps=False, ) if not skip_assets: From 0d51206241881c268065d60169f012386d173ead Mon Sep 17 00:00:00 2001 From: Aradhya Date: Fri, 25 Feb 2022 16:51:06 +0530 Subject: [PATCH 33/78] refactor: ignoring app deps when install app without --resolve-deps --- bench/app.py | 43 ++++++------------------------------------- 1 file changed, 6 insertions(+), 37 deletions(-) diff --git a/bench/app.py b/bench/app.py index 951e0a54..7700ab0b 100755 --- a/bench/app.py +++ b/bench/app.py @@ -189,13 +189,9 @@ class App(AppMeta): verbose = bench.cli.verbose or verbose app_name = get_app_name(self.bench.name, self.repo) if not resolved: - # TODO: this should go inside install_app only tho - issue: default/resolved branch - setup_app_dependencies( - repo_name=self.repo, - bench_path=self.bench.name, - branch=self.tag, - verbose=verbose, - skip_assets=skip_assets, + click.secho( + f"Ignoring dependencies of {self.name} to install dependencies use --resolve-deps", + fg="yellow", ) install_app( @@ -301,35 +297,6 @@ def remove_from_excluded_apps_txt(app, bench_path="."): return write_excluded_apps_txt(apps, bench_path=bench_path) -def setup_app_dependencies( - repo_name, bench_path=".", branch=None, skip_assets=False, verbose=False -): - # branch kwarg is somewhat of a hack here; since we're assuming the same branches for all apps - # for eg: if you're installing erpnext@develop, you'll want frappe@develop and healthcare@develop too - import glob - import bench.cli - from bench.bench import Bench - - verbose = bench.cli.verbose or verbose - apps_path = os.path.join(os.path.abspath(bench_path), "apps") - files = glob.glob(os.path.join(apps_path, repo_name, "**", "hooks.py")) - - if files: - with open(files[0]) as f: - lines = [x for x in f.read().split("\n") if x.strip().startswith("required_apps")] - if lines: - required_apps = eval(lines[0].strip("required_apps").strip().lstrip("=").strip()) - # TODO: when the time comes, add version check here - for app in required_apps: - if app not in Bench(bench_path).apps: - get_app( - app, - bench_path=bench_path, - branch=branch, - skip_assets=skip_assets, - verbose=verbose, - ) - def get_app( git_url, @@ -364,7 +331,9 @@ def get_app( if resolve_deps: resolution = make_resolution_plan(app, bench) click.secho("Apps to be installed:", fg="yellow") - print("\n".join([app.name for app in reversed(resolution.values())])) + for idx, app in enumerate(reversed(resolution.values()), start=1): + print(f"{idx}. {app.name}") + if "frappe" in resolution: # Todo: Make frappe a terminal dependency for all frappe apps. frappe_path, frappe_branch = resolution["frappe"].url, resolution["frappe"].tag From a26f3ce564035e601e154ebadaa3af2c4deab4b9 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Mon, 28 Feb 2022 14:29:05 +0530 Subject: [PATCH 34/78] fix: logging when in init-bench --- bench/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bench/app.py b/bench/app.py index 7700ab0b..f7b114d7 100755 --- a/bench/app.py +++ b/bench/app.py @@ -188,7 +188,7 @@ class App(AppMeta): verbose = bench.cli.verbose or verbose app_name = get_app_name(self.bench.name, self.repo) - if not resolved: + if not resolved and self.repo != "frappe": click.secho( f"Ignoring dependencies of {self.name} to install dependencies use --resolve-deps", fg="yellow", From a1e1572937a32609f5d9cc89bf0384de4493dbfd Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Tue, 1 Mar 2022 18:11:13 +0530 Subject: [PATCH 35/78] test: get_required_deps_url() --- bench/tests/test_utils.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/bench/tests/test_utils.py b/bench/tests/test_utils.py index 602dbb38..9cae1924 100644 --- a/bench/tests/test_utils.py +++ b/bench/tests/test_utils.py @@ -23,3 +23,15 @@ class TestUtils(unittest.TestCase): ] ) ) + + def test_get_required_deps_url(self): + self.assertEqual( + get_required_deps_url(git_url="https://github.com/frappe/frappe.git", branch=None, repo_name="frappe"), + "https://raw.github.com/frappe/frappe/develop/frappe/hooks.py", + ) + self.assertEqual( + get_required_deps_url( + git_url="https://github.com/frappe/frappe.git", branch="version-13", repo_name="frappe" + ), + "https://raw.github.com/frappe/frappe/version-13/frappe/hooks.py", + ) From 6609ad20c4747424c9e039ab72151310993026bc Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Tue, 1 Mar 2022 18:11:46 +0530 Subject: [PATCH 36/78] test: is_valid_frappe_branch() --- bench/tests/test_utils.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/bench/tests/test_utils.py b/bench/tests/test_utils.py index 9cae1924..772f6aaf 100644 --- a/bench/tests/test_utils.py +++ b/bench/tests/test_utils.py @@ -35,3 +35,10 @@ class TestUtils(unittest.TestCase): ), "https://raw.github.com/frappe/frappe/version-13/frappe/hooks.py", ) + + def test_is_valid_frappe_branch(self): + self.assertTrue(is_valid_frappe_branch("https://github.com/frappe/frappe", frappe_branch="")) + self.assertTrue(is_valid_frappe_branch("https://github.com/frappe/frappe", frappe_branch="develop")) + self.assertTrue(is_valid_frappe_branch("https://github.com/frappe/erpnext", frappe_branch="version-13")) + self.assertFalse(is_valid_frappe_branch("https://github.com/frappe/erpnext", frappe_branch="version-1")) + From 03a435330b18ce57af00e2afc21fff5da2850da1 Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Tue, 1 Mar 2022 18:15:34 +0530 Subject: [PATCH 37/78] test: app states creation & update --- bench/tests/test_utils.py | 38 +++++++++++++++++++++++++++++++++++++- bench/utils/app.py | 10 ++++++++-- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/bench/tests/test_utils.py b/bench/tests/test_utils.py index 772f6aaf..bcf58f6c 100644 --- a/bench/tests/test_utils.py +++ b/bench/tests/test_utils.py @@ -1,8 +1,11 @@ +import os +import shutil import unittest from bench.app import App from bench.bench import Bench -from bench.utils import exec_cmd +from bench.utils import exec_cmd, is_valid_frappe_branch +from bench.utils.app import get_required_deps_url class TestUtils(unittest.TestCase): @@ -42,3 +45,36 @@ class TestUtils(unittest.TestCase): self.assertTrue(is_valid_frappe_branch("https://github.com/frappe/erpnext", frappe_branch="version-13")) self.assertFalse(is_valid_frappe_branch("https://github.com/frappe/erpnext", frappe_branch="version-1")) + def test_app_states(self): + bench_dir = "./sandbox" + sites_dir = os.path.join(bench_dir, "sites") + + if not os.path.exists(sites_dir): + os.makedirs(sites_dir) + + fake_bench = Bench(bench_dir) + + fake_bench.apps.set_states() + + self.assertTrue(hasattr(fake_bench.apps, "states")) + self.assertTrue(os.path.exists(os.path.join(sites_dir, "apps_states.json"))) + + fake_bench.apps.states = {"frappe": {"resolution": None, "version": "14.0.0-dev"}} + fake_bench.apps.update_apps_states() + + self.assertEqual(fake_bench.apps.states, {}) + + frappe_path = os.path.join(bench_dir, "apps", "frappe", "frappe") + + os.makedirs(frappe_path) + + with open(os.path.join(frappe_path, "__init__.py"), "w+") as f: + f.write("__version__ = '11.0'") + + fake_bench.apps.update_apps_states("frappe") + + self.assertIn("frappe", fake_bench.apps.states) + self.assertIn("version", fake_bench.apps.states["frappe"]) + self.assertEqual("11.0", fake_bench.apps.states["frappe"]["version"]) + + shutil.rmtree(bench_dir) diff --git a/bench/utils/app.py b/bench/utils/app.py index e86a0304..caa4bbe0 100644 --- a/bench/utils/app.py +++ b/bench/utils/app.py @@ -107,7 +107,9 @@ def switch_to_develop(apps=None, bench_path=".", upgrade=True): def get_version_from_string(contents, field="__version__"): - match = re.search(r"^(\s*%s\s*=\s*['\\\"])(.+?)(['\"])(?sm)" % field, contents) + match = re.search(r"^(\s*%s\s*=\s*['\\\"])(.+?)(['\"])" % field, contents, flags=(re.S | re.M)) + if not match: + raise Exception("No match was found") return match.group(2) @@ -164,14 +166,16 @@ def get_current_branch(app, bench_path="."): repo_dir = get_repo_dir(app, bench_path=bench_path) return get_cmd_output("basename $(git symbolic-ref -q HEAD)", cwd=repo_dir) + def get_required_deps_url(git_url, repo_name, branch, deps="hooks.py"): git_url = ( git_url.replace(".git", "").replace("github.com", "raw.github.com") ) branch = branch if branch else "develop" - git_url += f"/{branch}/{repo_name}/{deps}" + git_url += f"/{branch}/{repo_name}/{deps}" return git_url + def get_remote(app, bench_path="."): repo_dir = get_repo_dir(app, bench_path=bench_path) contents = subprocess.check_output( @@ -208,11 +212,13 @@ def get_app_name(bench_path, repo_name): return repo_name + def check_existing_dir(bench_path, repo_name): cloned_path = os.path.join(bench_path, "apps", repo_name) dir_already_exists = os.path.isdir(cloned_path) return dir_already_exists, cloned_path + def get_current_version(app, bench_path="."): current_version = None repo_dir = get_repo_dir(app, bench_path=bench_path) From 7ea09ce7c903910907d55ebfd6b979e75426fa1a Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Wed, 2 Mar 2022 15:11:18 +0530 Subject: [PATCH 38/78] test: get_dependencies() --- bench/tests/test_utils.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bench/tests/test_utils.py b/bench/tests/test_utils.py index bcf58f6c..63147df2 100644 --- a/bench/tests/test_utils.py +++ b/bench/tests/test_utils.py @@ -78,3 +78,9 @@ class TestUtils(unittest.TestCase): self.assertEqual("11.0", fake_bench.apps.states["frappe"]["version"]) shutil.rmtree(bench_dir) + + def test_get_dependencies(self): + fake_app = App("frappe/healthcare@develop") + self.assertIn("erpnext", fake_app._get_dependencies()) + fake_app = App("frappe/not_healthcare@not-a-branch") + self.assertTrue(len(fake_app._get_dependencies()) == 0) From b0ae3ae359c691fd9587562aa3912c6e9b60447b Mon Sep 17 00:00:00 2001 From: Aradhya Date: Wed, 2 Mar 2022 22:44:12 +0530 Subject: [PATCH 39/78] perf: Using github API --- bench/app.py | 21 +++++++++++++++------ bench/utils/app.py | 16 +++++++++------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/bench/app.py b/bench/app.py index 37ba560a..c750b18f 100755 --- a/bench/app.py +++ b/bench/app.py @@ -215,17 +215,26 @@ class App(AppMeta): self.bench.run(f"{self.bench.python} -m pip uninstall -y {self.repo}") def _get_dependencies(self): - from bench.utils.app import get_required_deps_url + from bench.utils.app import get_required_deps - required_url = get_required_deps_url(git_url=self.url, repo_name=self.repo, branch=self.tag) try: - f = requests.get(required_url).text - lines = [x for x in f.split("\n") if x.strip().startswith("required_apps")] - required_apps = eval(lines[0].strip("required_apps").strip().lstrip("=").strip()) - return required_apps + required_deps = get_required_deps( + self.org, self.repo, self.tag or self.branch + ) + lines = [ + x + for x in required_deps.split("\n") + if x.strip().startswith("required_apps") + ] + required_apps = eval( + lines[0].strip("required_apps").strip().lstrip("=").strip() + ) except Exception: return [] + return required_apps + + def make_resolution_plan(app: App, bench: "Bench"): """ decide what apps and versions to install and in what order diff --git a/bench/utils/app.py b/bench/utils/app.py index e86a0304..6d5f0f32 100644 --- a/bench/utils/app.py +++ b/bench/utils/app.py @@ -164,13 +164,15 @@ def get_current_branch(app, bench_path="."): repo_dir = get_repo_dir(app, bench_path=bench_path) return get_cmd_output("basename $(git symbolic-ref -q HEAD)", cwd=repo_dir) -def get_required_deps_url(git_url, repo_name, branch, deps="hooks.py"): - git_url = ( - git_url.replace(".git", "").replace("github.com", "raw.github.com") - ) - branch = branch if branch else "develop" - git_url += f"/{branch}/{repo_name}/{deps}" - return git_url +def get_required_deps(org, name, branch, deps="hooks.py"): + import requests + import base64 + + url = f"https://api.github.com/repos/{org}/{name}/contents/{name}/{deps}" + params = {"branch": branch or "develop"} + res = requests.get(url=url, params=params).json() + return base64.decodebytes(res["content"].encode()).decode() + def get_remote(app, bench_path="."): repo_dir = get_repo_dir(app, bench_path=bench_path) From fbee987490dbd937439d3dc2c67ffbc148b41082 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Wed, 2 Mar 2022 23:08:31 +0530 Subject: [PATCH 40/78] test: removed required urls tests as it's covered in test_get_dependencies --- bench/tests/test_utils.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/bench/tests/test_utils.py b/bench/tests/test_utils.py index 63147df2..f3708f95 100644 --- a/bench/tests/test_utils.py +++ b/bench/tests/test_utils.py @@ -4,8 +4,7 @@ import unittest from bench.app import App from bench.bench import Bench -from bench.utils import exec_cmd, is_valid_frappe_branch -from bench.utils.app import get_required_deps_url +from bench.utils import is_valid_frappe_branch class TestUtils(unittest.TestCase): @@ -27,18 +26,6 @@ class TestUtils(unittest.TestCase): ) ) - def test_get_required_deps_url(self): - self.assertEqual( - get_required_deps_url(git_url="https://github.com/frappe/frappe.git", branch=None, repo_name="frappe"), - "https://raw.github.com/frappe/frappe/develop/frappe/hooks.py", - ) - self.assertEqual( - get_required_deps_url( - git_url="https://github.com/frappe/frappe.git", branch="version-13", repo_name="frappe" - ), - "https://raw.github.com/frappe/frappe/version-13/frappe/hooks.py", - ) - def test_is_valid_frappe_branch(self): self.assertTrue(is_valid_frappe_branch("https://github.com/frappe/frappe", frappe_branch="")) self.assertTrue(is_valid_frappe_branch("https://github.com/frappe/frappe", frappe_branch="develop")) From c71fd096ccd916cf41b3144d07bc8ac7f29fb5b4 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Wed, 2 Mar 2022 23:09:20 +0530 Subject: [PATCH 41/78] refactor: removed code added through merge commit --- bench/utils/app.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/bench/utils/app.py b/bench/utils/app.py index a04d60ac..eedd2400 100644 --- a/bench/utils/app.py +++ b/bench/utils/app.py @@ -166,6 +166,7 @@ def get_current_branch(app, bench_path="."): repo_dir = get_repo_dir(app, bench_path=bench_path) return get_cmd_output("basename $(git symbolic-ref -q HEAD)", cwd=repo_dir) + def get_required_deps(org, name, branch, deps="hooks.py"): import requests import base64 @@ -176,15 +177,6 @@ def get_required_deps(org, name, branch, deps="hooks.py"): return base64.decodebytes(res["content"].encode()).decode() -def get_required_deps_url(git_url, repo_name, branch, deps="hooks.py"): - git_url = ( - git_url.replace(".git", "").replace("github.com", "raw.github.com") - ) - branch = branch if branch else "develop" - git_url += f"/{branch}/{repo_name}/{deps}" - return git_url - - def get_remote(app, bench_path="."): repo_dir = get_repo_dir(app, bench_path=bench_path) contents = subprocess.check_output( @@ -226,11 +218,6 @@ def check_existing_dir(bench_path, repo_name): dir_already_exists = os.path.isdir(cloned_path) return dir_already_exists, cloned_path -def check_existing_dir(bench_path, repo_name): - cloned_path = os.path.join(bench_path, "apps", repo_name) - dir_already_exists = os.path.isdir(cloned_path) - return dir_already_exists, cloned_path - def get_current_version(app, bench_path="."): current_version = None From 6c15327348789cefe474fa183c2d39cca6d45d78 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Mon, 7 Mar 2022 18:06:03 +0530 Subject: [PATCH 42/78] fix: fixed frappe branch check --- bench/utils/__init__.py | 15 ++++++++++----- bench/utils/system.py | 6 +----- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/bench/utils/__init__.py b/bench/utils/__init__.py index b0402bee..160387d9 100644 --- a/bench/utils/__init__.py +++ b/bench/utils/__init__.py @@ -49,12 +49,17 @@ def is_frappe_app(directory: str) -> bool: return bool(is_frappe_app) -def is_valid_frappe_branch(frappe_path: str, frappe_branch: str): +def is_valid_frappe_branch(frappe_path, frappe_branch): if "http" in frappe_path: - url = frappe_path - url += "/tree/" + frappe_branch if frappe_branch else "" - return requests.get(url).status_code != 404 - return True + frappe_path = frappe_path.replace(".git", "") + try: + owner, repo = frappe_path.split("/")[3], frappe_path.split("/")[4] + except IndexError: + raise InvalidRemoteException + git_api = f"https://api.github.com/repos/{owner}/{repo}/branches" + res = requests.get(git_api).json() + if "message" in res or frappe_branch not in [x["name"] for x in res]: + raise InvalidRemoteException def log(message, level=0, no_log=False): diff --git a/bench/utils/system.py b/bench/utils/system.py index ab27893c..152759d0 100644 --- a/bench/utils/system.py +++ b/bench/utils/system.py @@ -75,11 +75,7 @@ def init( # remote apps else: frappe_path = frappe_path or "https://github.com/frappe/frappe.git" - frappe_branch = ( - frappe_branch - if is_valid_frappe_branch(frappe_path, frappe_branch) - else None - ) + is_valid_frappe_branch(frappe_path=frappe_path, frappe_branch=frappe_branch) get_app( frappe_path, branch=frappe_branch, From 17888a5ce5193e7c4b1fedcd2a4ea86c91cefed6 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Mon, 7 Mar 2022 19:16:29 +0530 Subject: [PATCH 43/78] test: Updated tests for is_valid_frappe_branch --- bench/tests/test_utils.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/bench/tests/test_utils.py b/bench/tests/test_utils.py index f3708f95..da52a0cf 100644 --- a/bench/tests/test_utils.py +++ b/bench/tests/test_utils.py @@ -5,7 +5,7 @@ import unittest from bench.app import App from bench.bench import Bench from bench.utils import is_valid_frappe_branch - +from bench.exceptions import InvalidRemoteException class TestUtils(unittest.TestCase): def test_app_utils(self): @@ -27,10 +27,11 @@ class TestUtils(unittest.TestCase): ) def test_is_valid_frappe_branch(self): - self.assertTrue(is_valid_frappe_branch("https://github.com/frappe/frappe", frappe_branch="")) - self.assertTrue(is_valid_frappe_branch("https://github.com/frappe/frappe", frappe_branch="develop")) - self.assertTrue(is_valid_frappe_branch("https://github.com/frappe/erpnext", frappe_branch="version-13")) - self.assertFalse(is_valid_frappe_branch("https://github.com/frappe/erpnext", frappe_branch="version-1")) + with self.assertRaises(InvalidRemoteException): + is_valid_frappe_branch("https://github.com/frappe/frappe.git", frappe_branch="random-branch") + is_valid_frappe_branch("https://github.com/random/random.git", frappe_branch="random-branch") + + is_valid_frappe_branch("https://github.com/frappe/frappe.git", frappe_branch="develop") def test_app_states(self): bench_dir = "./sandbox" From 79765e6b25a09eb93e6ca67c6ff8e5ea1f565fba Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Thu, 10 Mar 2022 12:59:14 +0530 Subject: [PATCH 44/78] fix: check for valid branch in resolve --- bench/app.py | 2 ++ bench/utils/__init__.py | 10 +++------- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/bench/app.py b/bench/app.py index c750b18f..b2cddf75 100755 --- a/bench/app.py +++ b/bench/app.py @@ -24,6 +24,7 @@ from bench.utils import ( get_available_folder_name, is_bench_directory, is_git_url, + is_valid_frappe_branch, log, run_frappe_cmd, ) @@ -244,6 +245,7 @@ def make_resolution_plan(app: App, bench: "Bench"): for app_name in app._get_dependencies(): dep_app = App(app_name, bench=bench) + is_valid_frappe_branch(dep_app.url, dep_app.branch) if dep_app.repo in resolution: click.secho(f"{dep_app.repo} is already resolved skipping", fg="yellow") continue diff --git a/bench/utils/__init__.py b/bench/utils/__init__.py index 160387d9..ada125a8 100644 --- a/bench/utils/__init__.py +++ b/bench/utils/__init__.py @@ -58,7 +58,7 @@ def is_valid_frappe_branch(frappe_path, frappe_branch): raise InvalidRemoteException git_api = f"https://api.github.com/repos/{owner}/{repo}/branches" res = requests.get(git_api).json() - if "message" in res or frappe_branch not in [x["name"] for x in res]: + if "message" in res or (frappe_branch and frappe_branch not in [x["name"] for x in res]): raise InvalidRemoteException @@ -76,9 +76,7 @@ def log(message, level=0, no_log=False): color, prefix = levels.get(level, levels[0]) if bench.cli.from_command_line and bench.cli.dynamic_feed: - bench.LOG_BUFFER.append( - {"prefix": prefix, "message": message, "color": color} - ) + bench.LOG_BUFFER.append({"prefix": prefix, "message": message, "color": color}) if no_log: click.secho(message, fg=color) @@ -196,9 +194,7 @@ def get_git_version() -> float: def get_cmd_output(cmd, cwd=".", _raise=True): output = "" try: - output = subprocess.check_output( - cmd, cwd=cwd, shell=True, stderr=subprocess.PIPE, encoding="utf-8" - ).strip() + output = subprocess.check_output(cmd, cwd=cwd, shell=True, stderr=subprocess.PIPE, encoding="utf-8").strip() except subprocess.CalledProcessError as e: if e.output: output = e.output From d178b08abbfbc287b6f92424401cd72c941f9ff3 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Thu, 10 Mar 2022 18:33:49 +0530 Subject: [PATCH 45/78] feat: verbose env setup & checking for compatible versions --- bench/app.py | 23 ++++++++++++++--------- bench/bench.py | 9 +++++---- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/bench/app.py b/bench/app.py index b2cddf75..4f74fc32 100755 --- a/bench/app.py +++ b/bench/app.py @@ -343,7 +343,7 @@ def get_app( if resolve_deps: resolution = make_resolution_plan(app, bench) - click.secho("Apps to be installed:", fg="yellow") + click.secho("Following apps will be installed", fg="yellow") for idx, app in enumerate(reversed(resolution.values()), start=1): print(f"{idx}. {app.name}") @@ -379,6 +379,7 @@ def get_app( if resolve_deps: install_resolved_deps( + bench, resolution, bench_path=bench_path, skip_assets=skip_assets, @@ -412,6 +413,7 @@ def get_app( app.install(verbose=verbose, skip_assets=skip_assets) def install_resolved_deps( + bench, resolution, bench_path=".", skip_assets=False, @@ -424,16 +426,19 @@ def install_resolved_deps( del resolution["frappe"] for repo_name, app in reversed(resolution.items()): - existing_dir, cloned_path = check_existing_dir(bench_path, repo_name) + existing_dir, _ = check_existing_dir(bench_path, repo_name) if existing_dir: - if click.confirm( - f"A directory for the application '{repo_name}' already exists. " - "Do you want to continue and overwrite it?" - ): - click.secho(f"Removing {repo_name}", fg="yellow") - shutil.rmtree(cloned_path) + if bench.apps.states[repo_name]["resolution"] != app.tag: + click.secho( + f"Incompatible version of {repo_name} is already installed", + fg="yellow", + ) app.install_resolved_apps(skip_assets=skip_assets, verbose=verbose) - + else: + click.secho( + f"Compatible version of {repo_name} is already installed", + fg="yellow", + ) continue app.install_resolved_apps(skip_assets=skip_assets, verbose=verbose) diff --git a/bench/bench.py b/bench/bench.py index 21c40c3d..93a83956 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -160,18 +160,16 @@ class BenchApps(MutableSequence): self.bench = bench self.states_path = os.path.join(self.bench.name, "sites", "apps_states.json") self.initialize_apps() + self.set_states() def set_states(self): try: with open(self.states_path, "r") as f: self.states = json.loads(f.read() or "{}") except FileNotFoundError: - with open(self.states_path, "w+") as f: - self.states = json.loads(f.read() or "{}") + self.states = {} def update_apps_states(self, app_name: str = None, resolution: str = None): - self.initialize_apps() - self.set_states() apps_to_remove = [] for app in self.states: if app not in self.apps: @@ -281,6 +279,9 @@ class BenchSetup(Base): - install frappe python dependencies """ import bench.cli + import click + + click.secho("Setting Up Environment", fg="yellow") frappe = os.path.join(self.bench.name, "apps", "frappe") virtualenv = get_venv_path() From 9004e1dd6a473a40bfb43fe663867e117e076f95 Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Thu, 10 Mar 2022 23:06:47 +0530 Subject: [PATCH 46/78] feat: add commit hash and branch in `states.json` --- bench/app.py | 4 ++-- bench/bench.py | 23 ++++++++++++++++++++--- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/bench/app.py b/bench/app.py index 4f74fc32..7ec14047 100755 --- a/bench/app.py +++ b/bench/app.py @@ -428,7 +428,7 @@ def install_resolved_deps( for repo_name, app in reversed(resolution.items()): existing_dir, _ = check_existing_dir(bench_path, repo_name) if existing_dir: - if bench.apps.states[repo_name]["resolution"] != app.tag: + if bench.apps.states[repo_name]["resolution"]["branch"] != app.tag: click.secho( f"Incompatible version of {repo_name} is already installed", fg="yellow", @@ -500,7 +500,7 @@ def install_app( bench.run("yarn install", cwd=app_path) bench.apps.sync() - bench.apps.update_apps_states(app, tag) + bench.apps.update_apps_states(app, branch=tag) if not skip_assets: build_assets(bench_path=bench_path, app=app) diff --git a/bench/bench.py b/bench/bench.py index 93a83956..3c757349 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -1,11 +1,12 @@ # imports - standard imports +import subprocess import functools import os import shutil import json import sys import logging -from typing import List, MutableSequence, TYPE_CHECKING +from typing import List, MutableSequence, TYPE_CHECKING, Union # imports - module imports import bench @@ -159,6 +160,7 @@ class BenchApps(MutableSequence): def __init__(self, bench: Bench): self.bench = bench self.states_path = os.path.join(self.bench.name, "sites", "apps_states.json") + self.apps_path = os.path.join(self.bench.name, "apps") self.initialize_apps() self.set_states() @@ -169,7 +171,7 @@ class BenchApps(MutableSequence): except FileNotFoundError: self.states = {} - def update_apps_states(self, app_name: str = None, resolution: str = None): + def update_apps_states(self, app_name: Union[str, None] = None, branch: Union[str, None] = None): apps_to_remove = [] for app in self.states: if app not in self.apps: @@ -180,8 +182,23 @@ class BenchApps(MutableSequence): if app_name: version = get_current_version(app_name, self.bench.name) + + app_dir = os.path.join(self.apps_path, app_name) + if not branch: + branch = ( + subprocess + .check_output("git rev-parse --abbrev-ref HEAD", shell=True, cwd=app_dir) + .decode("utf-8") + .rstrip() + ) + + commit_hash = subprocess.check_output(f"git rev-parse {branch}", shell=True, cwd=app_dir).decode("utf-8").rstrip() + self.states[app_name] = { - "resolution": resolution, + "resolution": { + "commit_hash":commit_hash, + "branch": branch + }, "version": version, } From 5bb3b52f23bf01ca138a359eaf22e020007375f8 Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Thu, 10 Mar 2022 23:07:15 +0530 Subject: [PATCH 47/78] fix: print required by in output --- bench/app.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bench/app.py b/bench/app.py index 7ec14047..7a873b78 100755 --- a/bench/app.py +++ b/bench/app.py @@ -155,6 +155,7 @@ class App(AppMeta): self, name: str, branch: str = None, bench: "Bench" = None, *args, **kwargs ): self.bench = bench + self.required_by = None super().__init__(name, branch, *args, **kwargs) @step(title="Fetching App {repo}", success="App {repo} Fetched") @@ -246,6 +247,7 @@ def make_resolution_plan(app: App, bench: "Bench"): for app_name in app._get_dependencies(): dep_app = App(app_name, bench=bench) is_valid_frappe_branch(dep_app.url, dep_app.branch) + dep_app.required_by = app.name if dep_app.repo in resolution: click.secho(f"{dep_app.repo} is already resolved skipping", fg="yellow") continue @@ -345,7 +347,7 @@ def get_app( resolution = make_resolution_plan(app, bench) click.secho("Following apps will be installed", fg="yellow") for idx, app in enumerate(reversed(resolution.values()), start=1): - print(f"{idx}. {app.name}") + print(f"{idx}. {app.name} {f'required by {app.required_by}' if app.required_by else ''}") if "frappe" in resolution: # Todo: Make frappe a terminal dependency for all frappe apps. From 56f7016e6926a04d0f5c6538f2e51d8601c24e07 Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Thu, 10 Mar 2022 23:07:44 +0530 Subject: [PATCH 48/78] docs: rename states.json file --- bench/bench.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bench/bench.py b/bench/bench.py index 3c757349..2f92b67e 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -159,7 +159,7 @@ class Bench(Base, Validator): class BenchApps(MutableSequence): def __init__(self, bench: Bench): self.bench = bench - self.states_path = os.path.join(self.bench.name, "sites", "apps_states.json") + self.states_path = os.path.join(self.bench.name, "sites", "apps.json") self.apps_path = os.path.join(self.bench.name, "apps") self.initialize_apps() self.set_states() From c71432de3ce569d5646dfffcaa213e7fe7f6d854 Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Thu, 10 Mar 2022 23:09:53 +0530 Subject: [PATCH 49/78] test: update remove file creation check --- bench/tests/test_utils.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/bench/tests/test_utils.py b/bench/tests/test_utils.py index da52a0cf..69e959f5 100644 --- a/bench/tests/test_utils.py +++ b/bench/tests/test_utils.py @@ -1,11 +1,14 @@ import os import shutil +import subprocess import unittest +from tabnanny import check from bench.app import App from bench.bench import Bench -from bench.utils import is_valid_frappe_branch from bench.exceptions import InvalidRemoteException +from bench.utils import is_valid_frappe_branch + class TestUtils(unittest.TestCase): def test_app_utils(self): @@ -42,23 +45,27 @@ class TestUtils(unittest.TestCase): fake_bench = Bench(bench_dir) - fake_bench.apps.set_states() - self.assertTrue(hasattr(fake_bench.apps, "states")) - self.assertTrue(os.path.exists(os.path.join(sites_dir, "apps_states.json"))) - fake_bench.apps.states = {"frappe": {"resolution": None, "version": "14.0.0-dev"}} + fake_bench.apps.states = { + "frappe": {"resolution": {"branch": "develop", "commit_hash": "234rwefd"}, "version": "14.0.0-dev"} + } fake_bench.apps.update_apps_states() self.assertEqual(fake_bench.apps.states, {}) - frappe_path = os.path.join(bench_dir, "apps", "frappe", "frappe") + frappe_path = os.path.join(bench_dir, "apps", "frappe") - os.makedirs(frappe_path) + os.makedirs(os.path.join(frappe_path, "frappe")) - with open(os.path.join(frappe_path, "__init__.py"), "w+") as f: + subprocess.run(["git", "init"], cwd=frappe_path, capture_output=True, check=True) + + with open(os.path.join(frappe_path, "frappe", "__init__.py"), "w+") as f: f.write("__version__ = '11.0'") + subprocess.run(["git", "add", "."], cwd=frappe_path, capture_output=True, check=True) + subprocess.run(["git", "commit", "-m", "temp"], cwd=frappe_path, capture_output=True, check=True) + fake_bench.apps.update_apps_states("frappe") self.assertIn("frappe", fake_bench.apps.states) From 2287f3f7ec04cbf872049029809b65da829f6efd Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Fri, 11 Mar 2022 00:59:26 +0530 Subject: [PATCH 50/78] fix: check current branch --- bench/app.py | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/bench/app.py b/bench/app.py index 7a873b78..fcfa6abe 100755 --- a/bench/app.py +++ b/bench/app.py @@ -428,19 +428,34 @@ def install_resolved_deps( del resolution["frappe"] for repo_name, app in reversed(resolution.items()): - existing_dir, _ = check_existing_dir(bench_path, repo_name) + existing_dir, path_to_app = check_existing_dir(bench_path, repo_name) if existing_dir: - if bench.apps.states[repo_name]["resolution"]["branch"] != app.tag: - click.secho( - f"Incompatible version of {repo_name} is already installed", - fg="yellow", + is_compatible = False + + installed_branch = bench.apps.states[repo_name]["resolution"]["branch"].strip() + if app.tag is None: + current_remote = ( + subprocess.check_output(f"git config branch.{installed_branch}.remote", shell=True, cwd=path_to_app) + .decode("utf-8") + .rstrip() ) - app.install_resolved_apps(skip_assets=skip_assets, verbose=verbose) + + default_branch = ( + subprocess.check_output( + f"git symbolic-ref refs/remotes/{current_remote}/HEAD", shell=True, cwd=path_to_app + ) + .decode("utf-8") + .rsplit("/")[-1] + .strip() + ) + is_compatible = default_branch == installed_branch else: - click.secho( - f"Compatible version of {repo_name} is already installed", - fg="yellow", - ) + is_compatible = bench.apps.states[repo_name]["resolution"]["branch"] == app.tag + + click.secho( + f"{'C' if is_compatible else 'Inc'}ompatible version of {repo_name} is already installed", + fg="yellow", + ) continue app.install_resolved_apps(skip_assets=skip_assets, verbose=verbose) From 7de1fa7c4def60f10826694a8647a4910c0b0711 Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Fri, 11 Mar 2022 01:00:09 +0530 Subject: [PATCH 51/78] style: black-ish --- bench/app.py | 69 ++++++++++++++++++++-------------------------------- 1 file changed, 26 insertions(+), 43 deletions(-) diff --git a/bench/app.py b/bench/app.py index fcfa6abe..8045766d 100755 --- a/bench/app.py +++ b/bench/app.py @@ -50,7 +50,6 @@ class AppMeta: 3. frappe/healthcare@develop 4. healthcare 5. healthcare@develop, healthcare@v13.12.1 - 6. erpnext References for Version Identifiers: * https://www.python.org/dev/peps/pep-0440/#version-specifiers @@ -59,7 +58,7 @@ class AppMeta: class Healthcare(AppConfig): dependencies = [{"frappe/erpnext": "~13.17.0"}] """ - self.name = name.rstrip('/') + self.name = name.rstrip("/") self.remote_server = "github.com" self.to_clone = to_clone self.on_disk = False @@ -67,9 +66,7 @@ class AppMeta: self.from_apps = False self.is_url = False self.branch = branch - self.mount_path = os.path.abspath( - os.path.join(urlparse(self.name).netloc, urlparse(self.name).path) - ) + self.mount_path = os.path.abspath(os.path.join(urlparse(self.name).netloc, urlparse(self.name).path)) self.setup_details() def setup_details(self): @@ -99,9 +96,7 @@ class AppMeta: self._setup_details_from_name_tag() def _setup_details_from_mounted_disk(self): - self.org, self.repo, self.tag = os.path.split(self.mount_path)[-2:] + ( - self.branch, - ) + self.org, self.repo, self.tag = os.path.split(self.mount_path)[-2:] + (self.branch,) def _setup_details_from_name_tag(self): self.org, self.repo, self.tag = fetch_details_from_tag(self.name) @@ -151,9 +146,7 @@ class AppMeta: @functools.lru_cache(maxsize=None) class App(AppMeta): - def __init__( - self, name: str, branch: str = None, bench: "Bench" = None, *args, **kwargs - ): + def __init__(self, name: str, branch: str = None, bench: "Bench" = None, *args, **kwargs): self.bench = bench self.required_by = None super().__init__(name, branch, *args, **kwargs) @@ -176,17 +169,13 @@ class App(AppMeta): def remove(self): active_app_path = os.path.join("apps", self.repo) archived_path = os.path.join("archived", "apps") - archived_name = get_available_folder_name( - f"{self.repo}-{date.today()}", archived_path - ) + archived_name = get_available_folder_name(f"{self.repo}-{date.today()}", archived_path) archived_app_path = os.path.join(archived_path, archived_name) 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=False, resolved=False, restart_bench=True - ): + def install(self, skip_assets=False, verbose=False, resolved=False, restart_bench=True): import bench.cli from bench.utils.app import get_app_name @@ -220,17 +209,9 @@ class App(AppMeta): from bench.utils.app import get_required_deps try: - required_deps = get_required_deps( - self.org, self.repo, self.tag or self.branch - ) - lines = [ - x - for x in required_deps.split("\n") - if x.strip().startswith("required_apps") - ] - required_apps = eval( - lines[0].strip("required_apps").strip().lstrip("=").strip() - ) + required_deps = get_required_deps(self.org, self.repo, self.tag or self.branch) + lines = [x for x in required_deps.split("\n") if x.strip().startswith("required_apps")] + required_apps = eval(lines[0].strip("required_apps").strip().lstrip("=").strip()) except Exception: return [] @@ -312,7 +293,6 @@ def remove_from_excluded_apps_txt(app, bench_path="."): return write_excluded_apps_txt(apps, bench_path=bench_path) - def get_app( git_url, branch=None, @@ -372,12 +352,14 @@ def get_app( bench_setup = True if bench_setup and bench_cli.from_command_line and bench_cli.dynamic_feed: - _bench.LOG_BUFFER.append({ - "message": f"Fetching App {repo_name}", - "prefix": click.style('⏼', fg='bright_yellow'), - "is_parent": True, - "color": None, - }) + _bench.LOG_BUFFER.append( + { + "message": f"Fetching App {repo_name}", + "prefix": click.style("⏼", fg="bright_yellow"), + "is_parent": True, + "color": None, + } + ) if resolve_deps: install_resolved_deps( @@ -414,6 +396,7 @@ def get_app( ): app.install(verbose=verbose, skip_assets=skip_assets) + def install_resolved_deps( bench, resolution, @@ -459,11 +442,10 @@ def install_resolved_deps( continue app.install_resolved_apps(skip_assets=skip_assets, verbose=verbose) + def new_app(app, no_git=None, bench_path="."): if bench.FRAPPE_VERSION in (0, None): - raise NotInBenchDirectoryError( - f"{os.path.realpath(bench_path)} is not a valid bench directory." - ) + raise NotInBenchDirectoryError(f"{os.path.realpath(bench_path)} is not a valid bench directory.") # For backwards compatibility app = app.lower().replace(" ", "_").replace("-", "_") @@ -471,10 +453,7 @@ def new_app(app, no_git=None, bench_path="."): args = ["make-app", apps, app] if no_git: if bench.FRAPPE_VERSION < 14: - click.secho( - "Frappe v14 or greater is needed for '--no-git' flag", - fg="red" - ) + click.secho("Frappe v14 or greater is needed for '--no-git' flag", fg="red") return args.append(no_git) @@ -525,6 +504,7 @@ def install_app( if restart_bench: bench.reload() + def pull_apps(apps=None, bench_path=".", reset=False): """Check all apps if there no local changes, pull""" from bench.bench import Bench @@ -615,7 +595,10 @@ def install_apps_from_path(path, bench_path="."): apps = get_apps_json(path) for app in apps: get_app( - app["url"], branch=app.get("branch"), bench_path=bench_path, skip_assets=True, + app["url"], + branch=app.get("branch"), + bench_path=bench_path, + skip_assets=True, ) From 67df07b732389e04f1847d978c47c5f21e9f028a Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Fri, 11 Mar 2022 13:22:49 +0530 Subject: [PATCH 52/78] fix: empty states json condition --- bench/app.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/bench/app.py b/bench/app.py index 8045766d..b9e5e80a 100755 --- a/bench/app.py +++ b/bench/app.py @@ -415,7 +415,11 @@ def install_resolved_deps( if existing_dir: is_compatible = False - installed_branch = bench.apps.states[repo_name]["resolution"]["branch"].strip() + try: + installed_branch = bench.apps.states[repo_name]["resolution"]["branch"].strip() + except: + installed_branch = None + if app.tag is None: current_remote = ( subprocess.check_output(f"git config branch.{installed_branch}.remote", shell=True, cwd=path_to_app) @@ -433,7 +437,7 @@ def install_resolved_deps( ) is_compatible = default_branch == installed_branch else: - is_compatible = bench.apps.states[repo_name]["resolution"]["branch"] == app.tag + is_compatible = installed_branch == app.tag click.secho( f"{'C' if is_compatible else 'Inc'}ompatible version of {repo_name} is already installed", From af3c871632843ffb8c8d0e78de097e15f0b8fd1e Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Fri, 11 Mar 2022 13:34:54 +0530 Subject: [PATCH 53/78] fix: missing installed branch --- bench/app.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/bench/app.py b/bench/app.py index b9e5e80a..80d75410 100755 --- a/bench/app.py +++ b/bench/app.py @@ -418,8 +418,14 @@ def install_resolved_deps( try: installed_branch = bench.apps.states[repo_name]["resolution"]["branch"].strip() except: - installed_branch = None - + installed_branch = ( + subprocess. + check_output("git rev-parse --abbrev-ref HEAD", shell=True, cwd=path_to_app) + .decode("utf-8") + .rstrip() + ) + + if app.tag is None: current_remote = ( subprocess.check_output(f"git config branch.{installed_branch}.remote", shell=True, cwd=path_to_app) From 9ac091b4d9caac1567fe5cd4f16ba5096cbc8e22 Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Fri, 11 Mar 2022 14:12:31 +0530 Subject: [PATCH 54/78] feat: add required and order of install --- bench/app.py | 52 ++++++++++++++++++++++++++++++-------------------- bench/bench.py | 10 ++++++---- 2 files changed, 37 insertions(+), 25 deletions(-) diff --git a/bench/app.py b/bench/app.py index 80d75410..6053e50c 100755 --- a/bench/app.py +++ b/bench/app.py @@ -149,6 +149,7 @@ class App(AppMeta): def __init__(self, name: str, branch: str = None, bench: "Bench" = None, *args, **kwargs): self.bench = bench self.required_by = None + self.local_resolution = [] super().__init__(name, branch, *args, **kwargs) @step(title="Fetching App {repo}", success="App {repo} Fetched") @@ -194,6 +195,7 @@ class App(AppMeta): verbose=verbose, skip_assets=skip_assets, restart_bench=restart_bench, + resolution = self.local_resolution ) @step(title="Cloning and installing {repo}", success="App {repo} Installed") @@ -217,6 +219,11 @@ class App(AppMeta): return required_apps + def update_app_state(self): + from bench.bench import Bench + bench = Bench(self.bench.name) + bench.apps.sync(self.name, self.tag, self.local_resolution) + def make_resolution_plan(app: App, bench: "Bench"): """ @@ -234,6 +241,7 @@ def make_resolution_plan(app: App, bench: "Bench"): continue resolution[dep_app.repo] = dep_app resolution.update(make_resolution_plan(dep_app, bench)) + app.local_resolution = [repo_name for repo_name, _ in reversed(resolution.items())] return resolution @@ -424,31 +432,33 @@ def install_resolved_deps( .decode("utf-8") .rstrip() ) - - - if app.tag is None: - current_remote = ( - subprocess.check_output(f"git config branch.{installed_branch}.remote", shell=True, cwd=path_to_app) - .decode("utf-8") - .rstrip() - ) - - default_branch = ( - subprocess.check_output( - f"git symbolic-ref refs/remotes/{current_remote}/HEAD", shell=True, cwd=path_to_app + try: + if app.tag is None: + current_remote = ( + subprocess.check_output(f"git config branch.{installed_branch}.remote", shell=True, cwd=path_to_app) + .decode("utf-8") + .rstrip() ) - .decode("utf-8") - .rsplit("/")[-1] - .strip() - ) - is_compatible = default_branch == installed_branch - else: - is_compatible = installed_branch == app.tag + + default_branch = ( + subprocess.check_output( + f"git symbolic-ref refs/remotes/{current_remote}/HEAD", shell=True, cwd=path_to_app + ) + .decode("utf-8") + .rsplit("/")[-1] + .strip() + ) + is_compatible = default_branch == installed_branch + else: + is_compatible = installed_branch == app.tag + except: + is_compatible = False click.secho( f"{'C' if is_compatible else 'Inc'}ompatible version of {repo_name} is already installed", fg="yellow", ) + app.update_app_state() continue app.install_resolved_apps(skip_assets=skip_assets, verbose=verbose) @@ -480,6 +490,7 @@ def install_app( no_cache=False, restart_bench=True, skip_assets=False, + resolution=[] ): import bench.cli as bench_cli from bench.bench import Bench @@ -505,8 +516,7 @@ def install_app( if os.path.exists(os.path.join(app_path, "package.json")): bench.run("yarn install", cwd=app_path) - bench.apps.sync() - bench.apps.update_apps_states(app, branch=tag) + bench.apps.sync(app, required=resolution, branch=tag) if not skip_assets: build_assets(bench_path=bench_path, app=app) diff --git a/bench/bench.py b/bench/bench.py index 2f92b67e..0baa47a4 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -125,7 +125,6 @@ class Bench(Base, Validator): self.validate_app_uninstall(app) self.apps.remove(App(app, bench=self, to_clone=False)) self.apps.sync() - self.apps.update_apps_states() # self.build() - removed because it seems unnecessary self.reload() @@ -171,7 +170,7 @@ class BenchApps(MutableSequence): except FileNotFoundError: self.states = {} - def update_apps_states(self, app_name: Union[str, None] = None, branch: Union[str, None] = None): + def update_apps_states(self, app_name: Union[str, None] = None, branch: Union[str, None] = None, required:List = []): apps_to_remove = [] for app in self.states: if app not in self.apps: @@ -199,16 +198,19 @@ class BenchApps(MutableSequence): "commit_hash":commit_hash, "branch": branch }, + "required":required, + "order of install":len(self.states) + 1, "version": version, } with open(self.states_path, "w") as f: f.write(json.dumps(self.states, indent=4)) - def sync(self): + def sync(self,app_name: Union[str, None] = None, branch: Union[str, None] = None, required:List = []): self.initialize_apps() with open(self.bench.apps_txt, "w") as f: - return f.write("\n".join(self.apps)) + f.write("\n".join(self.apps)) + self.update_apps_states(app_name, branch, required) def initialize_apps(self): is_installed = lambda app: app in installed_packages From bb16b733587fe03719266c70445cf42e3ad9c57f Mon Sep 17 00:00:00 2001 From: Aradhya Date: Fri, 11 Mar 2022 18:38:21 +0530 Subject: [PATCH 55/78] test: Updated tests --- bench/tests/test_init.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bench/tests/test_init.py b/bench/tests/test_init.py index 69221b14..e3002307 100755 --- a/bench/tests/test_init.py +++ b/bench/tests/test_init.py @@ -114,7 +114,7 @@ class TestBenchInit(TestBenchBase): exec_cmd(f"bench get-app {FRAPPE_APP} --resolve-deps", cwd=bench_path) self.assertTrue(os.path.exists(os.path.join(bench_path, "apps", FRAPPE_APP))) - states_path = os.path.join(bench_path, "sites", "apps_states.json") + states_path = os.path.join(bench_path, "sites", "apps.json") self.assert_(os.path.exists(states_path)) with open(states_path, "r") as f: From 8dd92c32b1b9dc0f51e7fe1f1bfc7f9738dedbb8 Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Mon, 14 Mar 2022 05:17:08 +0530 Subject: [PATCH 56/78] feat: alternative request to validate branch --- bench/utils/__init__.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/bench/utils/__init__.py b/bench/utils/__init__.py index ada125a8..84401203 100644 --- a/bench/utils/__init__.py +++ b/bench/utils/__init__.py @@ -50,15 +50,22 @@ def is_frappe_app(directory: str) -> bool: def is_valid_frappe_branch(frappe_path, frappe_branch): - if "http" in frappe_path: + if "http" in frappe_path and frappe_branch: frappe_path = frappe_path.replace(".git", "") try: owner, repo = frappe_path.split("/")[3], frappe_path.split("/")[4] except IndexError: raise InvalidRemoteException - git_api = f"https://api.github.com/repos/{owner}/{repo}/branches" - res = requests.get(git_api).json() - if "message" in res or (frappe_branch and frappe_branch not in [x["name"] for x in res]): + git_api_req = f"https://api.github.com/repos/{owner}/{repo}/branches" + res = requests.get(git_api_req).json() + + if "message" in res: + # slower alternative with no rate limit + github_req = f'https://github.com/{owner}/{repo}/tree/{frappe_branch}' + if requests.get(github_req).status_code != 200: + raise InvalidRemoteException + + elif frappe_branch not in [x["name"] for x in res]: raise InvalidRemoteException From adbe19ac1b55fdf6d86d9b331bad535c5bf16918 Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Mon, 14 Mar 2022 05:17:29 +0530 Subject: [PATCH 57/78] docs: is_valid_frappe_branch() docstring --- bench/utils/__init__.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/bench/utils/__init__.py b/bench/utils/__init__.py index 84401203..4427cac7 100644 --- a/bench/utils/__init__.py +++ b/bench/utils/__init__.py @@ -49,7 +49,18 @@ def is_frappe_app(directory: str) -> bool: return bool(is_frappe_app) -def is_valid_frappe_branch(frappe_path, frappe_branch): +def is_valid_frappe_branch(frappe_path:str, frappe_branch:str): + """ Check if a branch exists in a repo. Throws InvalidRemoteException if branch is not found + + Uses github's api without auth to query branch. + If rate limited by gitapi, requests are sent to github.com + + :param frappe_path: git url + :type frappe_path: str + :param frappe_branch: branch to check + :type frappe_branch: str + :raises InvalidRemoteException: branch for this repo doesn't exist + """ if "http" in frappe_path and frappe_branch: frappe_path = frappe_path.replace(".git", "") try: From d0bd294dc2aa1a7b5c08616bc1c802346aaf60d4 Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Mon, 14 Mar 2022 05:33:44 +0530 Subject: [PATCH 58/78] feat: slower alternative for get_required_deps --- bench/utils/app.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/bench/utils/app.py b/bench/utils/app.py index eedd2400..434af655 100644 --- a/bench/utils/app.py +++ b/bench/utils/app.py @@ -171,9 +171,14 @@ def get_required_deps(org, name, branch, deps="hooks.py"): import requests import base64 - url = f"https://api.github.com/repos/{org}/{name}/contents/{name}/{deps}" + git_api_url = f"https://api.github.com/repos/{org}/{name}/contents/{name}/{deps}" params = {"branch": branch or "develop"} - res = requests.get(url=url, params=params).json() + res = requests.get(url=git_api_url, params=params).json() + + if "message" in res: + git_url = f"https://raw.githubusercontent.com/{org}/{name}/{params['branch']}/{deps}" + return requests.get(git_url).text + return base64.decodebytes(res["content"].encode()).decode() From d33d987b2c6286401d7cf3f9902e348d2fad356d Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 17 Mar 2022 11:41:34 +0530 Subject: [PATCH 59/78] fix: User messages on get-app actions --- bench/app.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/bench/app.py b/bench/app.py index 6053e50c..cb7b0786 100755 --- a/bench/app.py +++ b/bench/app.py @@ -184,7 +184,7 @@ class App(AppMeta): app_name = get_app_name(self.bench.name, self.repo) if not resolved and self.repo != "frappe": click.secho( - f"Ignoring dependencies of {self.name} to install dependencies use --resolve-deps", + f"Ignoring dependencies of {self.name}. To install dependencies use --resolve-deps", fg="yellow", ) @@ -333,9 +333,9 @@ def get_app( if resolve_deps: resolution = make_resolution_plan(app, bench) - click.secho("Following apps will be installed", fg="yellow") + click.secho("Following apps will be installed", fg="bright_blue") for idx, app in enumerate(reversed(resolution.values()), start=1): - print(f"{idx}. {app.name} {f'required by {app.required_by}' if app.required_by else ''}") + print(f"{idx}. {app.name} {f'(required by {app.required_by})' if app.required_by else ''}") if "frappe" in resolution: # Todo: Make frappe a terminal dependency for all frappe apps. @@ -454,9 +454,10 @@ def install_resolved_deps( except: is_compatible = False + prefix = 'C' if is_compatible else 'Inc' click.secho( - f"{'C' if is_compatible else 'Inc'}ompatible version of {repo_name} is already installed", - fg="yellow", + f"{prefix}ompatible version of {repo_name} is already installed", + fg="green" if is_compatible else "red", ) app.update_app_state() continue From ad000f1be7c8a74746d276e2633651513d5297ff Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 17 Mar 2022 11:41:57 +0530 Subject: [PATCH 60/78] fix: Don't handle `BaseException`s --- bench/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bench/app.py b/bench/app.py index cb7b0786..d7889a69 100755 --- a/bench/app.py +++ b/bench/app.py @@ -425,7 +425,7 @@ def install_resolved_deps( try: installed_branch = bench.apps.states[repo_name]["resolution"]["branch"].strip() - except: + except Exception: installed_branch = ( subprocess. check_output("git rev-parse --abbrev-ref HEAD", shell=True, cwd=path_to_app) @@ -451,7 +451,7 @@ def install_resolved_deps( is_compatible = default_branch == installed_branch else: is_compatible = installed_branch == app.tag - except: + except Exception: is_compatible = False prefix = 'C' if is_compatible else 'Inc' From bbca3e9fab11491f071d7e5b9ef27d9d9b32c75c Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sun, 20 Mar 2022 22:15:41 +0530 Subject: [PATCH 61/78] feat: mimicking get-app behaviour when app is already installed --- bench/app.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/bench/app.py b/bench/app.py index d7889a69..78244347 100755 --- a/bench/app.py +++ b/bench/app.py @@ -460,7 +460,13 @@ def install_resolved_deps( fg="green" if is_compatible else "red", ) app.update_app_state() - continue + if click.confirm( + f"Do you wish to clone and install the already installed {prefix}ompatible app" + ): + click.secho(f"Removing installed app {app.name}", fg="yellow") + shutil.rmtree(path_to_app) + else: + continue app.install_resolved_apps(skip_assets=skip_assets, verbose=verbose) From 67638b19b70f7159679173b96dd0175ed51f3a1d Mon Sep 17 00:00:00 2001 From: Aradhya Date: Mon, 21 Mar 2022 14:52:04 +0530 Subject: [PATCH 62/78] refactor: added ignore resolution to ignore messages in install app --- bench/app.py | 13 ++++++++++--- bench/bench.py | 4 +++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/bench/app.py b/bench/app.py index 78244347..043c7c8e 100755 --- a/bench/app.py +++ b/bench/app.py @@ -176,13 +176,20 @@ class App(AppMeta): shutil.move(active_app_path, archived_app_path) @step(title="Installing App {repo}", success="App {repo} Installed") - def install(self, skip_assets=False, verbose=False, resolved=False, restart_bench=True): + def install( + self, + skip_assets=False, + verbose=False, + resolved=False, + restart_bench=True, + ignore_resolution=False, + ): import bench.cli from bench.utils.app import get_app_name verbose = bench.cli.verbose or verbose app_name = get_app_name(self.bench.name, self.repo) - if not resolved and self.repo != "frappe": + if not resolved and self.repo != "frappe" and not ignore_resolution: click.secho( f"Ignoring dependencies of {self.name}. To install dependencies use --resolve-deps", fg="yellow", @@ -195,7 +202,7 @@ class App(AppMeta): verbose=verbose, skip_assets=skip_assets, restart_bench=restart_bench, - resolution = self.local_resolution + resolution=self.local_resolution ) @step(title="Cloning and installing {repo}", success="App {repo} Installed") diff --git a/bench/bench.py b/bench/bench.py index 0baa47a4..b0f6d50f 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -392,7 +392,9 @@ class BenchSetup(Base): print(f"Installing {len(apps)} applications...") for app in apps: - App(app, bench=self.bench, to_clone=False).install( skip_assets=True, restart_bench=False) + App(app, bench=self.bench, to_clone=False).install( + skip_assets=True, restart_bench=False, ignore_resolution=True + ) def python(self, apps=None): """Install and upgrade Python dependencies for specified / all installed apps on given Bench From b28cfab5502096e28dfbda332337b14635278fb2 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Mon, 21 Mar 2022 23:57:52 +0530 Subject: [PATCH 63/78] fix: add code removed via erroneous commit --- bench/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bench/app.py b/bench/app.py index 480d5d7e..9463c106 100755 --- a/bench/app.py +++ b/bench/app.py @@ -336,6 +336,7 @@ def get_app( repo_name = app.repo branch = app.tag bench_setup = False + restart_bench = not init_bench frappe_path, frappe_branch = None, None if resolve_deps: From ddd498ebd03f3858705f1c6c8cbb8b4e2eac1a91 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Wed, 23 Mar 2022 18:18:37 +0530 Subject: [PATCH 64/78] feat: Added exception for invalid version --- bench/exceptions.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bench/exceptions.py b/bench/exceptions.py index 0d517fe4..b3340915 100644 --- a/bench/exceptions.py +++ b/bench/exceptions.py @@ -30,3 +30,7 @@ class FeatureDoesNotExistError(CommandFailedError): class NotInBenchDirectoryError(Exception): pass + + +class VersionNotFound(Exception): + pass \ No newline at end of file From cd583f7822a5aaa29f09cc9ba016b6b23c1d9cc2 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Wed, 23 Mar 2022 18:20:49 +0530 Subject: [PATCH 65/78] refactor: Added messages on invalid remote exceptions --- bench/utils/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bench/utils/__init__.py b/bench/utils/__init__.py index 8f4cc05c..b815fb3a 100644 --- a/bench/utils/__init__.py +++ b/bench/utils/__init__.py @@ -66,7 +66,7 @@ def is_valid_frappe_branch(frappe_path:str, frappe_branch:str): try: owner, repo = frappe_path.split("/")[3], frappe_path.split("/")[4] except IndexError: - raise InvalidRemoteException + raise InvalidRemoteException("Invalid git url") git_api_req = f"https://api.github.com/repos/{owner}/{repo}/branches" res = requests.get(git_api_req).json() @@ -74,10 +74,10 @@ def is_valid_frappe_branch(frappe_path:str, frappe_branch:str): # slower alternative with no rate limit github_req = f'https://github.com/{owner}/{repo}/tree/{frappe_branch}' if requests.get(github_req).status_code != 200: - raise InvalidRemoteException + raise InvalidRemoteException("Invalid git url") elif frappe_branch not in [x["name"] for x in res]: - raise InvalidRemoteException + raise InvalidRemoteException("Frappe branch does not exist") def log(message, level=0, no_log=False): From 3ec774946c7a581f4a0bf16de9e38ad177dfba66 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Wed, 23 Mar 2022 18:22:54 +0530 Subject: [PATCH 66/78] refactor: Appropriate message for invalid version --- bench/utils/app.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bench/utils/app.py b/bench/utils/app.py index 434af655..98c53514 100644 --- a/bench/utils/app.py +++ b/bench/utils/app.py @@ -7,6 +7,7 @@ from bench.exceptions import ( InvalidRemoteException, InvalidBranchException, CommandFailedError, + VersionNotFound, ) from bench.app import get_repo_dir @@ -109,7 +110,7 @@ def switch_to_develop(apps=None, bench_path=".", upgrade=True): def get_version_from_string(contents, field="__version__"): match = re.search(r"^(\s*%s\s*=\s*['\\\"])(.+?)(['\"])" % field, contents, flags=(re.S | re.M)) if not match: - raise Exception("No match was found") + raise VersionNotFound(f"{contents} is not a valid version") return match.group(2) From 0b862c07457c9914840bd0ce87a90306fec37456 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Fri, 25 Mar 2022 22:27:58 +0530 Subject: [PATCH 67/78] feat: Added support for mounted app in get_dependencies --- bench/app.py | 13 ++++++++++--- bench/utils/app.py | 8 ++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/bench/app.py b/bench/app.py index 9463c106..790850b9 100755 --- a/bench/app.py +++ b/bench/app.py @@ -215,12 +215,19 @@ class App(AppMeta): self.bench.run(f"{self.bench.python} -m pip uninstall -y {self.repo}") def _get_dependencies(self): - from bench.utils.app import get_required_deps + from bench.utils.app import get_required_deps, required_apps_from_hooks + if self.on_disk: + required_deps = os.path.join(self.mount_path, self.repo,'hooks.py') + try: + print(required_apps_from_hooks(required_deps)) + except IndexError: + print(f"No dependencies for {self.repo}") + finally: + return try: required_deps = get_required_deps(self.org, self.repo, self.tag or self.branch) - lines = [x for x in required_deps.split("\n") if x.strip().startswith("required_apps")] - required_apps = eval(lines[0].strip("required_apps").strip().lstrip("=").strip()) + return required_apps_from_hooks(required_deps) except Exception: return [] diff --git a/bench/utils/app.py b/bench/utils/app.py index 98c53514..f04b0020 100644 --- a/bench/utils/app.py +++ b/bench/utils/app.py @@ -183,6 +183,14 @@ def get_required_deps(org, name, branch, deps="hooks.py"): return base64.decodebytes(res["content"].encode()).decode() +def required_apps_from_hooks(required_deps): + with open(required_deps) as f: + required_deps = f.read() + lines = [x for x in required_deps.split("\n") if x.strip().startswith("required_apps")] + required_apps = eval(lines[0].strip("required_apps").strip().lstrip("=").strip()) + return required_apps + + def get_remote(app, bench_path="."): repo_dir = get_repo_dir(app, bench_path=bench_path) contents = subprocess.check_output( From 2f17da1bf08168a21b97587b52755f84e04a3e90 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sat, 26 Mar 2022 00:06:17 +0530 Subject: [PATCH 68/78] fix: support for local and remote apps when looking for dependencies --- bench/app.py | 4 +--- bench/utils/app.py | 7 ++++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/bench/app.py b/bench/app.py index 790850b9..b14c6d82 100755 --- a/bench/app.py +++ b/bench/app.py @@ -220,7 +220,7 @@ class App(AppMeta): if self.on_disk: required_deps = os.path.join(self.mount_path, self.repo,'hooks.py') try: - print(required_apps_from_hooks(required_deps)) + print(required_apps_from_hooks(required_deps, local=True)) except IndexError: print(f"No dependencies for {self.repo}") finally: @@ -231,8 +231,6 @@ class App(AppMeta): except Exception: return [] - return required_apps - def update_app_state(self): from bench.bench import Bench bench = Bench(self.bench.name) diff --git a/bench/utils/app.py b/bench/utils/app.py index f04b0020..32e958f3 100644 --- a/bench/utils/app.py +++ b/bench/utils/app.py @@ -183,9 +183,10 @@ def get_required_deps(org, name, branch, deps="hooks.py"): return base64.decodebytes(res["content"].encode()).decode() -def required_apps_from_hooks(required_deps): - with open(required_deps) as f: - required_deps = f.read() +def required_apps_from_hooks(required_deps, local=False): + if local: + with open(required_deps) as f: + required_deps = f.read() lines = [x for x in required_deps.split("\n") if x.strip().startswith("required_apps")] required_apps = eval(lines[0].strip("required_apps").strip().lstrip("=").strip()) return required_apps From fc0be250c2a7f10b9bb9941df09dba8b0f749662 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sat, 26 Mar 2022 22:46:28 +0530 Subject: [PATCH 69/78] feat: Exiting when app does not exist --- bench/app.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/bench/app.py b/bench/app.py index b14c6d82..a55de782 100755 --- a/bench/app.py +++ b/bench/app.py @@ -70,6 +70,11 @@ class AppMeta: self.setup_details() def setup_details(self): + if not urlparse(self.name).netloc and not os.path.exists( + os.path.realpath(self.name) + ): + click.secho(f"{self.name} does not exist!", fg="yellow") + return # fetch meta from installed apps if ( not self.to_clone @@ -220,11 +225,9 @@ class App(AppMeta): if self.on_disk: required_deps = os.path.join(self.mount_path, self.repo,'hooks.py') try: - print(required_apps_from_hooks(required_deps, local=True)) + return required_apps_from_hooks(required_deps, local=True) except IndexError: - print(f"No dependencies for {self.repo}") - finally: - return + return [] try: required_deps = get_required_deps(self.org, self.repo, self.tag or self.branch) return required_apps_from_hooks(required_deps) From 4252b1100ec60bdc77964c55045ae43e2c59fa65 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sun, 27 Mar 2022 16:54:30 +0530 Subject: [PATCH 70/78] test: updated tests --- bench/tests/test_utils.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bench/tests/test_utils.py b/bench/tests/test_utils.py index 69e959f5..d61e714b 100644 --- a/bench/tests/test_utils.py +++ b/bench/tests/test_utils.py @@ -75,7 +75,10 @@ class TestUtils(unittest.TestCase): shutil.rmtree(bench_dir) def test_get_dependencies(self): - fake_app = App("frappe/healthcare@develop") + git_url = "https://github.com/frappe/healthcare" + branch = "develop" + fake_app = App(git_url, branch=branch) self.assertIn("erpnext", fake_app._get_dependencies()) - fake_app = App("frappe/not_healthcare@not-a-branch") + git_url = git_url.replace("healthcare", "erpnext") + fake_app = App(git_url) self.assertTrue(len(fake_app._get_dependencies()) == 0) From ff98aaf198a34eac0afbcb67ddac0fb52b5df880 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sun, 27 Mar 2022 17:21:27 +0530 Subject: [PATCH 71/78] fix: reverted erroneous commits --- bench/app.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/bench/app.py b/bench/app.py index a55de782..fc2c421f 100755 --- a/bench/app.py +++ b/bench/app.py @@ -70,11 +70,6 @@ class AppMeta: self.setup_details() def setup_details(self): - if not urlparse(self.name).netloc and not os.path.exists( - os.path.realpath(self.name) - ): - click.secho(f"{self.name} does not exist!", fg="yellow") - return # fetch meta from installed apps if ( not self.to_clone From 4441528d3f0ee1dd345506ac96c2de7ebc7977e7 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Mon, 28 Mar 2022 21:00:41 +0530 Subject: [PATCH 72/78] style: black-ish code --- bench/app.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bench/app.py b/bench/app.py index fc2c421f..3321fab9 100755 --- a/bench/app.py +++ b/bench/app.py @@ -14,7 +14,6 @@ from urllib.parse import urlparse # imports - third party imports import click -import requests # imports - module imports import bench @@ -66,7 +65,9 @@ class AppMeta: self.from_apps = False self.is_url = False self.branch = branch - self.mount_path = os.path.abspath(os.path.join(urlparse(self.name).netloc, urlparse(self.name).path)) + self.mount_path = os.path.abspath( + os.path.join(urlparse(self.name).netloc, urlparse(self.name).path) + ) self.setup_details() def setup_details(self): From bd105a66095456add51e3e04e0840af93dacdb6d Mon Sep 17 00:00:00 2001 From: Aradhya Date: Mon, 28 Mar 2022 21:02:38 +0530 Subject: [PATCH 73/78] perf: caching deps and orgs, repos to avoid time taking checks & added slower version to check org and repo --- bench/utils/__init__.py | 4 ++++ bench/utils/app.py | 2 ++ 2 files changed, 6 insertions(+) diff --git a/bench/utils/__init__.py b/bench/utils/__init__.py index b815fb3a..450d9492 100644 --- a/bench/utils/__init__.py +++ b/bench/utils/__init__.py @@ -8,6 +8,7 @@ import sys from glob import glob from shlex import split from typing import List, Tuple +from functools import lru_cache # imports - third party imports import click @@ -421,6 +422,7 @@ def clear_command_cache(bench_path="."): print("Bench command cache doesn't exist in this folder!") +@lru_cache(maxsize=5) def find_org(org_repo): import requests @@ -428,6 +430,8 @@ def find_org(org_repo): for org in ["frappe", "erpnext"]: res = requests.head(f"https://api.github.com/repos/{org}/{org_repo}") + if "message" in res.json(): + res = requests.head(f"https://github.com/{org}/{org_repo}") if res.ok: return org, org_repo diff --git a/bench/utils/app.py b/bench/utils/app.py index 32e958f3..d07422e1 100644 --- a/bench/utils/app.py +++ b/bench/utils/app.py @@ -10,6 +10,7 @@ from bench.exceptions import ( VersionNotFound, ) from bench.app import get_repo_dir +from functools import lru_cache def is_version_upgrade(app="frappe", bench_path=".", branch=None): @@ -168,6 +169,7 @@ def get_current_branch(app, bench_path="."): return get_cmd_output("basename $(git symbolic-ref -q HEAD)", cwd=repo_dir) +@lru_cache(maxsize=5) def get_required_deps(org, name, branch, deps="hooks.py"): import requests import base64 From dbb19046df0fe72ed82fd87220fa7aa5a37d0e56 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Mon, 28 Mar 2022 21:59:17 +0530 Subject: [PATCH 74/78] fix: checking status code to determine rate limit --- bench/utils/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bench/utils/__init__.py b/bench/utils/__init__.py index 450d9492..902a79c7 100644 --- a/bench/utils/__init__.py +++ b/bench/utils/__init__.py @@ -422,7 +422,6 @@ def clear_command_cache(bench_path="."): print("Bench command cache doesn't exist in this folder!") -@lru_cache(maxsize=5) def find_org(org_repo): import requests @@ -430,7 +429,7 @@ def find_org(org_repo): for org in ["frappe", "erpnext"]: res = requests.head(f"https://api.github.com/repos/{org}/{org_repo}") - if "message" in res.json(): + if res.status_code == 400: res = requests.head(f"https://github.com/{org}/{org_repo}") if res.ok: return org, org_repo From 3ac19ca4c6d3ab48065849e92878b3263ed9bdcf Mon Sep 17 00:00:00 2001 From: Aradhya Date: Fri, 1 Apr 2022 11:33:39 +0530 Subject: [PATCH 75/78] refactor: not sorting apps to maintain order of install --- bench/bench.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bench/bench.py b/bench/bench.py index b0f6d50f..4981f200 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -230,7 +230,6 @@ class BenchApps(MutableSequence): and is_installed(x) ) ] - self.apps.sort() except FileNotFoundError: self.apps = [] From 339875b2b14c7038fa9d2e355aba1357f5f6d10d Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sat, 2 Apr 2022 15:05:24 +0530 Subject: [PATCH 76/78] refactor: added message while raising invalidremote exception --- bench/utils/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bench/utils/__init__.py b/bench/utils/__init__.py index 902a79c7..0c67e305 100644 --- a/bench/utils/__init__.py +++ b/bench/utils/__init__.py @@ -434,7 +434,7 @@ def find_org(org_repo): if res.ok: return org, org_repo - raise InvalidRemoteException + raise InvalidRemoteException(f"{org_repo} Not foung in frappe or erpnext") def fetch_details_from_tag(_tag: str) -> Tuple[str, str, str]: From a1a35eed0e4729d337894474bb56f2da7a32ba49 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sat, 2 Apr 2022 15:41:21 +0530 Subject: [PATCH 77/78] feat: Added support for backward compatibility in states --- bench/bench.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/bench/bench.py b/bench/bench.py index 4981f200..35e97422 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -171,6 +171,22 @@ class BenchApps(MutableSequence): self.states = {} def update_apps_states(self, app_name: Union[str, None] = None, branch: Union[str, None] = None, required:List = []): + if self.apps and not os.path.exists(self.states_path): + # idx according to apps listed in apps.txt (backwards compatibility) + print("Found existing apps updating states...") + for idx, app in enumerate(self.apps, start=1): + self.states[app] = { + "resolution": { + "commit_hash": None, + "branch": None + }, + "required": required, + "idx": idx, + "version": get_current_version(app, self.bench.name), + } + with open(self.states_path, "w") as f: + f.write(json.dumps(self.states, indent=4)) + apps_to_remove = [] for app in self.states: if app not in self.apps: @@ -199,7 +215,7 @@ class BenchApps(MutableSequence): "branch": branch }, "required":required, - "order of install":len(self.states) + 1, + "idx":len(self.states) + 1, "version": version, } From 281d96ac160c84588f394beac43ddf4594c3bad3 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Mon, 4 Apr 2022 23:42:52 +0530 Subject: [PATCH 78/78] fix: Fixed order of install --- bench/bench.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/bench/bench.py b/bench/bench.py index 35e97422..110d2859 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -173,6 +173,13 @@ class BenchApps(MutableSequence): def update_apps_states(self, app_name: Union[str, None] = None, branch: Union[str, None] = None, required:List = []): if self.apps and not os.path.exists(self.states_path): # idx according to apps listed in apps.txt (backwards compatibility) + # Keeping frappe as the first app. + if "frappe" in self.apps: + self.apps.remove("frappe") + self.apps.insert(0, "frappe") + with open(self.bench.apps_txt, "w") as f: + f.write("\n".join(self.apps)) + print("Found existing apps updating states...") for idx, app in enumerate(self.apps, start=1): self.states[app] = { @@ -184,8 +191,6 @@ class BenchApps(MutableSequence): "idx": idx, "version": get_current_version(app, self.bench.name), } - with open(self.states_path, "w") as f: - f.write(json.dumps(self.states, indent=4)) apps_to_remove = [] for app in self.states: @@ -195,7 +200,7 @@ class BenchApps(MutableSequence): for app in apps_to_remove: del self.states[app] - if app_name: + if app_name and app_name not in self.states: version = get_current_version(app_name, self.bench.name) app_dir = os.path.join(self.apps_path, app_name)