hassle.utilities
1import re 2import subprocess 3 4import coverage 5import pytest 6import requests 7from bs4 import BeautifulSoup 8from gitbetter import Git 9from pathier import Pathier 10 11root = Pathier(__file__).parent 12 13 14def swap_keys(data: dict, keys: tuple[str, str]): 15 """Convert between keys in `data`. 16 The order of `keys` doesn't matter. 17 >>> data = {"one two": 1} 18 >>> data = swap_keys(data, ("one two", "one-two")) 19 >>> print(data) 20 >>> {"one-two": 1} 21 >>> data = swap_keys(data, ("one two", "one-two")) 22 >>> print(data) 23 >>> {"one two": 1} 24 """ 25 key1, key2 = keys 26 data_keys = data.keys() 27 if key1 in data_keys: 28 data[key2] = data.pop(key1) 29 elif key2 in data_keys: 30 data[key1] = data.pop(key2) 31 return data 32 33 34def run_tests() -> bool: 35 """Invoke `coverage` and `pytest -s`. 36 37 Returns `True` if all tests passed or if no tests were found.""" 38 results = subprocess.run(["coverage", "run", "-m", "pytest", "-s"]) 39 subprocess.run(["coverage", "report", f"--include={Pathier.cwd()}/*"]) 40 subprocess.run(["coverage", "html", f"--include={Pathier.cwd()}/*"]) 41 return results.returncode in [0, 5] 42 43 44def check_pypi(package_name: str) -> bool: 45 """Check if a package with package_name already exists on `pypi.org`. 46 Returns `True` if package name exists. 47 Only checks the first page of results.""" 48 url = f"https://pypi.org/search/?q={package_name.lower()}" 49 response = requests.get(url) 50 if response.status_code != 200: 51 raise RuntimeError( 52 f"Error: pypi.org returned status code: {response.status_code}" 53 ) 54 soup = BeautifulSoup(response.text, "html.parser") 55 pypi_packages = [ 56 span.text.lower() 57 for span in soup.find_all("span", class_="package-snippet__name") 58 ] 59 return package_name in pypi_packages 60 61 62def get_answer(question: str) -> bool | None: 63 """Repeatedly ask the user a yes/no question until a 'y' or a 'n' is received.""" 64 ans = "" 65 question = question.strip() 66 if "?" not in question: 67 question += "?" 68 question += " (y/n): " 69 while ans not in ["y", "yes", "no", "n"]: 70 ans = input(question).strip().lower() 71 if ans in ["y", "yes"]: 72 return True 73 elif ans in ["n", "no"]: 74 return False 75 else: 76 print("Invalid answer.") 77 78 79def bump_version(current_version: str, bump_type: str) -> str: 80 """Bump `current_version` according to `bump_type` and return the new version. 81 82 #### :params: 83 84 `current_version`: A version string conforming to Semantic Versioning standards. 85 i.e. `{major}.{minor}.{patch}` 86 87 `bump_type` can be one of `major`, `minor`, or `patch`. 88 89 Raises an exception if `current_version` is formatted incorrectly or if `bump_type` isn't one of the aforementioned types. 90 """ 91 if not re.findall(r"[0-9]+.[0-9]+.[0-9]+", current_version): 92 raise ValueError( 93 f"{current_version} does not appear to match the required format of `x.x.x`." 94 ) 95 bump_type = bump_type.lower().strip() 96 if bump_type not in ["major", "minor", "patch"]: 97 raise ValueError( 98 f"`bump_type` {bump_type} is not one of `major`, `minor`, or `patch`." 99 ) 100 major, minor, patch = [int(part) for part in current_version.split(".")] 101 if bump_type == "major": 102 major += 1 103 minor = 0 104 patch = 0 105 elif bump_type == "minor": 106 minor += 1 107 patch = 0 108 elif bump_type == "patch": 109 patch += 1 110 return f"{major}.{minor}.{patch}" 111 112 113def on_primary_branch() -> bool: 114 """Returns `False` if repo is not currently on `main` or `master` branch.""" 115 git = Git(True) 116 if git.current_branch not in ["main", "master"]: 117 return False 118 return True
15def swap_keys(data: dict, keys: tuple[str, str]): 16 """Convert between keys in `data`. 17 The order of `keys` doesn't matter. 18 >>> data = {"one two": 1} 19 >>> data = swap_keys(data, ("one two", "one-two")) 20 >>> print(data) 21 >>> {"one-two": 1} 22 >>> data = swap_keys(data, ("one two", "one-two")) 23 >>> print(data) 24 >>> {"one two": 1} 25 """ 26 key1, key2 = keys 27 data_keys = data.keys() 28 if key1 in data_keys: 29 data[key2] = data.pop(key1) 30 elif key2 in data_keys: 31 data[key1] = data.pop(key2) 32 return data
Convert between keys in data
.
The order of keys
doesn't matter.
>>> data = {"one two": 1}
>>> data = swap_keys(data, ("one two", "one-two"))
>>> print(data)
>>> {"one-two": 1}
>>> data = swap_keys(data, ("one two", "one-two"))
>>> print(data)
>>> {"one two": 1}
35def run_tests() -> bool: 36 """Invoke `coverage` and `pytest -s`. 37 38 Returns `True` if all tests passed or if no tests were found.""" 39 results = subprocess.run(["coverage", "run", "-m", "pytest", "-s"]) 40 subprocess.run(["coverage", "report", f"--include={Pathier.cwd()}/*"]) 41 subprocess.run(["coverage", "html", f"--include={Pathier.cwd()}/*"]) 42 return results.returncode in [0, 5]
Invoke coverage
and pytest -s
.
Returns True
if all tests passed or if no tests were found.
45def check_pypi(package_name: str) -> bool: 46 """Check if a package with package_name already exists on `pypi.org`. 47 Returns `True` if package name exists. 48 Only checks the first page of results.""" 49 url = f"https://pypi.org/search/?q={package_name.lower()}" 50 response = requests.get(url) 51 if response.status_code != 200: 52 raise RuntimeError( 53 f"Error: pypi.org returned status code: {response.status_code}" 54 ) 55 soup = BeautifulSoup(response.text, "html.parser") 56 pypi_packages = [ 57 span.text.lower() 58 for span in soup.find_all("span", class_="package-snippet__name") 59 ] 60 return package_name in pypi_packages
Check if a package with package_name already exists on pypi.org
.
Returns True
if package name exists.
Only checks the first page of results.
63def get_answer(question: str) -> bool | None: 64 """Repeatedly ask the user a yes/no question until a 'y' or a 'n' is received.""" 65 ans = "" 66 question = question.strip() 67 if "?" not in question: 68 question += "?" 69 question += " (y/n): " 70 while ans not in ["y", "yes", "no", "n"]: 71 ans = input(question).strip().lower() 72 if ans in ["y", "yes"]: 73 return True 74 elif ans in ["n", "no"]: 75 return False 76 else: 77 print("Invalid answer.")
Repeatedly ask the user a yes/no question until a 'y' or a 'n' is received.
80def bump_version(current_version: str, bump_type: str) -> str: 81 """Bump `current_version` according to `bump_type` and return the new version. 82 83 #### :params: 84 85 `current_version`: A version string conforming to Semantic Versioning standards. 86 i.e. `{major}.{minor}.{patch}` 87 88 `bump_type` can be one of `major`, `minor`, or `patch`. 89 90 Raises an exception if `current_version` is formatted incorrectly or if `bump_type` isn't one of the aforementioned types. 91 """ 92 if not re.findall(r"[0-9]+.[0-9]+.[0-9]+", current_version): 93 raise ValueError( 94 f"{current_version} does not appear to match the required format of `x.x.x`." 95 ) 96 bump_type = bump_type.lower().strip() 97 if bump_type not in ["major", "minor", "patch"]: 98 raise ValueError( 99 f"`bump_type` {bump_type} is not one of `major`, `minor`, or `patch`." 100 ) 101 major, minor, patch = [int(part) for part in current_version.split(".")] 102 if bump_type == "major": 103 major += 1 104 minor = 0 105 patch = 0 106 elif bump_type == "minor": 107 minor += 1 108 patch = 0 109 elif bump_type == "patch": 110 patch += 1 111 return f"{major}.{minor}.{patch}"
Bump current_version
according to bump_type
and return the new version.
:params:
current_version
: A version string conforming to Semantic Versioning standards.
i.e. {major}.{minor}.{patch}
bump_type
can be one of major
, minor
, or patch
.
Raises an exception if current_version
is formatted incorrectly or if bump_type
isn't one of the aforementioned types.
114def on_primary_branch() -> bool: 115 """Returns `False` if repo is not currently on `main` or `master` branch.""" 116 git = Git(True) 117 if git.current_branch not in ["main", "master"]: 118 return False 119 return True
Returns False
if repo is not currently on main
or master
branch.