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