Coverage for src\hassle\utilities.py: 32%

1 import re

2

3 import coverage

4 import pytest

5 import requests

6 from bs4 import BeautifulSoup

7 from gitbetter import Git

8 from pathier import Pathier

9

10 root = Pathier(__file__).parent

11

12

13 def 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

33 def 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

45 def 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

63 def 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

80 def 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

114 def 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