Coverage for src\hassle\utilities.py: 55%
65 statements
« prev ^ index » next coverage.py v7.2.2, created at 2024-02-15 19:47 -0600
« prev ^ index » next coverage.py v7.2.2, created at 2024-02-15 19:47 -0600
1import re
3import coverage
4import pytest
5import requests
6from bs4 import BeautifulSoup
7from gitbetter import Git
8from pathier import Pathier
9import subprocess
11root = Pathier(__file__).parent
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
34def run_tests() -> bool:
35 """Invoke `coverage` and `pytest -s`.
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]
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
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.")
79def bump_version(current_version: str, bump_type: str) -> str:
80 """Bump `current_version` according to `bump_type` and return the new version.
82 #### :params:
84 `current_version`: A version string conforming to Semantic Versioning standards.
85 i.e. `{major}.{minor}.{patch}`
87 `bump_type` can be one of `major`, `minor`, or `patch`.
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}"
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