From c53e6d822de15d414301ee00a10b13af8f840c4a Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Fri, 18 Feb 2022 21:39:00 +0530 Subject: [PATCH 1/3] feat: parse app name from tags and urls --- frappe/exceptions.py | 3 +++ frappe/installer.py | 53 +++++++++++++++++++++++++++++++++++++--- frappe/utils/__init__.py | 5 ++++ 3 files changed, 58 insertions(+), 3 deletions(-) diff --git a/frappe/exceptions.py b/frappe/exceptions.py index 6ee72b5f81..fcac349708 100644 --- a/frappe/exceptions.py +++ b/frappe/exceptions.py @@ -110,3 +110,6 @@ class InvalidAuthorizationPrefix(CSRFTokenError): pass class InvalidAuthorizationToken(CSRFTokenError): pass class InvalidDatabaseFile(ValidationError): pass class ExecutableNotFound(FileNotFoundError): pass + +class InvalidRemoteException(Exception): + pass diff --git a/frappe/installer.py b/frappe/installer.py index d10dc78286..9bb2d9993f 100644 --- a/frappe/installer.py +++ b/frappe/installer.py @@ -5,10 +5,11 @@ import json import os import sys from collections import OrderedDict -from typing import List, Dict +from typing import List, Dict, Tuple import frappe from frappe.defaults import _clear_cache +from frappe.utils import is_git_url def _new_site( @@ -34,7 +35,6 @@ def _new_site( from frappe.commands.scheduler import _is_scheduler_enabled from frappe.utils import get_site_path, scheduler, touch_file - if not force and os.path.exists(site): print("Site {0} already exists".format(site)) sys.exit(1) @@ -124,6 +124,52 @@ def install_db(root_login=None, root_password=None, db_name=None, source_sql=Non frappe.flags.in_install_db = False +def find_org(org_repo: str) -> Tuple[str, str]: + from frappe.exceptions import InvalidRemoteException + import requests + + org_repo = org_repo[0] + + for org in ["frappe", "erpnext"]: + res = requests.head(f"https://api.github.com/repos/{org}/{org_repo}") + if res.ok: + return org, org_repo + + raise InvalidRemoteException + + +def fetch_details_from_tag(_tag: str) -> Tuple[str, str, str]: + app_tag = _tag.split("@") + org_repo = app_tag[0].split("/") + + try: + repo, tag = app_tag + except ValueError: + repo, tag = app_tag + [None] + + try: + org, repo = org_repo + except Exception: + org, repo = find_org(org_repo) + + return org, repo, tag + + +def parse_app_name(name: str): + name = name.rstrip("/") + if os.path.exists(name): + repo = os.path.split(name)[-1] + elif is_git_url(name): + if name.startswith("git@") or name.startswith("ssh://"): + _repo = name.split(":")[1].rsplit("/", 1)[1] + else: + _repo = name.rsplit("/", 2)[2] + repo = _repo.split(".")[0] + else: + _, repo, _ = fetch_details_from_tag(name) + return repo + + def install_app(name, verbose=False, set_as_patched=True): from frappe.core.doctype.scheduled_job_type.scheduled_job_type import sync_jobs from frappe.model.sync import sync_for @@ -140,7 +186,8 @@ def install_app(name, verbose=False, set_as_patched=True): # install pre-requisites if app_hooks.required_apps: for app in app_hooks.required_apps: - install_app(app, verbose=verbose) + name = parse_app_name(name) + install_app(name, verbose=verbose) frappe.flags.in_install = name frappe.clear_cache() diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py index c361b5b430..4a6d578a9c 100644 --- a/frappe/utils/__init__.py +++ b/frappe/utils/__init__.py @@ -918,3 +918,8 @@ def add_user_info(user, user_info): email = info.email, time_zone = info.time_zone ) + +def is_git_url(url: str) -> bool: + # modified to allow without the tailing .git from https://github.com/jonschlinkert/is-git-url.git + pattern = r"(?:git|ssh|https?|\w*@[-\w.]+):(\/\/)?(.*?)(\.git)?(\/?|\#[-\d\w._]+?)$" + return bool(re.match(pattern, url)) From f8c40985856f60d43247f8b5de66c25f3b6c240f Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Fri, 11 Mar 2022 14:44:53 +0530 Subject: [PATCH 2/3] docs: docstings and refs --- frappe/installer.py | 42 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/frappe/installer.py b/frappe/installer.py index 9bb2d9993f..e28a942f01 100644 --- a/frappe/installer.py +++ b/frappe/installer.py @@ -125,11 +125,22 @@ def install_db(root_login=None, root_password=None, db_name=None, source_sql=Non def find_org(org_repo: str) -> Tuple[str, str]: + """ find the org a repo is in + + find_org() + ref -> https://github.com/frappe/bench/blob/develop/bench/utils/__init__.py#L390 + + :param org_repo: + :type org_repo: str + + :raises InvalidRemoteException: if the org is not found + + :return: organisation and repository + :rtype: Tuple[str, str] + """ from frappe.exceptions import InvalidRemoteException import requests - org_repo = org_repo[0] - for org in ["frappe", "erpnext"]: res = requests.head(f"https://api.github.com/repos/{org}/{org_repo}") if res.ok: @@ -139,6 +150,17 @@ def find_org(org_repo: str) -> Tuple[str, str]: def fetch_details_from_tag(_tag: str) -> Tuple[str, str, str]: + """ parse org, repo, tag from string + + fetch_details_from_tag() + ref -> https://github.com/frappe/bench/blob/develop/bench/utils/__init__.py#L403 + + :param _tag: input string + :type _tag: str + + :return: organisation, repostitory, tag + :rtype: Tuple[str, str, str] + """ app_tag = _tag.split("@") org_repo = app_tag[0].split("/") @@ -150,12 +172,24 @@ def fetch_details_from_tag(_tag: str) -> Tuple[str, str, str]: try: org, repo = org_repo except Exception: - org, repo = find_org(org_repo) + org, repo = find_org(org_repo[0]) return org, repo, tag -def parse_app_name(name: str): +def parse_app_name(name: str) -> str: + """parse repo name from name + + __setup_details_from_git() + ref -> https://github.com/frappe/bench/blob/develop/bench/app.py#L114 + + + :param name: git tag + :type name: str + + :return: repository name + :rtype: str + """ name = name.rstrip("/") if os.path.exists(name): repo = os.path.split(name)[-1] From 1507751a01172e63462b4004c94d1abf085ee0b4 Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Mon, 21 Mar 2022 19:34:38 +0530 Subject: [PATCH 3/3] test: test_app_name_parser --- frappe/tests/test_utils.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/frappe/tests/test_utils.py b/frappe/tests/test_utils.py index 18fca9de8c..fa6b5a3820 100644 --- a/frappe/tests/test_utils.py +++ b/frappe/tests/test_utils.py @@ -2,6 +2,7 @@ # License: MIT. See LICENSE import io +import os import json import unittest from datetime import date, datetime, time, timedelta @@ -14,13 +15,14 @@ import pytz from PIL import Image import frappe -from frappe.utils import ceil, evaluate_filters, floor, format_timedelta +from frappe.utils import ceil, evaluate_filters, floor, format_timedelta, get_bench_path from frappe.utils import get_url, money_in_words, parse_timedelta, scrub_urls from frappe.utils import validate_email_address, validate_url from frappe.utils.data import cast, get_time, get_timedelta, nowtime, now_datetime, validate_python_code from frappe.utils.diff import _get_value_from_version, get_version_diff, version_query from frappe.utils.image import optimize_image, strip_exif_data from frappe.utils.response import json_handler +from frappe.installer import parse_app_name class TestFilters(unittest.TestCase): @@ -510,3 +512,13 @@ class TestLinkTitle(unittest.TestCase): todo.delete() user.delete() prop_setter.delete() + +class TestAppParser(unittest.TestCase): + def test_app_name_parser(self): + bench_path = get_bench_path() + frappe_app = os.path.join(bench_path, "apps", "frappe") + self.assertEqual("frappe", parse_app_name(frappe_app)) + self.assertEqual("healthcare", parse_app_name("healthcare")) + self.assertEqual("healthcare", parse_app_name("https://github.com/frappe/healthcare.git")) + self.assertEqual("healthcare", parse_app_name("git@github.com:frappe/healthcare.git")) + self.assertEqual("healthcare", parse_app_name("frappe/healthcare@develop"))