hassle.utilities API documentation

# hassle.utilities

View Source

      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

def swap_keys(data: dict, keys: tuple[str, str]): View Source

    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}

def run_tests() -> bool: View Source

    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.

def check_pypi(package_name: str) -> bool: View Source

    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.

def get_answer(question: str) -> bool | None: View Source

    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.

def bump_version(current_version: str, bump_type: str) -> str: View Source

     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.

def on_primary_branch() -> bool: View Source

    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.