Source code for stouputils.continuous_delivery.cd_utils

""" This module contains utilities for continuous delivery, such as loading credentials from a file.
It is mainly used by the `stouputils.continuous_delivery.github` module.
"""

# Imports
import os
from typing import Any

import requests
import yaml

from ..decorators import handle_error
from ..io import clean_path, super_json_load
from ..print import warning


# Load credentials from file
[docs] @handle_error() def load_credentials(credentials_path: str) -> dict[str, Any]: """ Load credentials from a JSON or YAML file into a dictionary. Loads credentials from either a JSON or YAML file and returns them as a dictionary. The file must contain the required credentials in the appropriate format. Args: credentials_path (str): Path to the credentials file (.json or .yml) Returns: dict[str, Any]: Dictionary containing the credentials Example JSON format: .. code-block:: json { "github": { "username": "Stoupy51", "api_key": "ghp_XXXXXXXXXXXXXXXXXXXXXXXXXX" } } Example YAML format: .. code-block:: yaml github: username: "Stoupy51" api_key: "ghp_XXXXXXXXXXXXXXXXXXXXXXXXXX" """ # Get the absolute path of the credentials file warning( "Be cautious when loading credentials from external sources like this, " "as they might contain malicious code that could compromise your credentials without your knowledge" ) credentials_path = clean_path(credentials_path) # Check if the file exists if not os.path.exists(credentials_path): raise FileNotFoundError(f"Credentials file not found at '{credentials_path}'") # Load the file if it's a JSON file if credentials_path.endswith(".json"): return super_json_load(credentials_path) # Else, load the file if it's a YAML file elif credentials_path.endswith((".yml", ".yaml")): with open(credentials_path) as f: return yaml.safe_load(f) # Else, raise an error else: raise ValueError("Credentials file must be .json or .yml format")
# Handle a response
[docs] def handle_response(response: requests.Response, error_message: str) -> None: """ Handle a response from the API by raising an error if the response is not successful (status code not in 200-299). Args: response (requests.Response): The response from the API error_message (str): The error message to raise if the response is not successful """ if response.status_code < 200 or response.status_code >= 300: try: raise ValueError(f"{error_message}, response code {response.status_code} with response {response.json()}") except requests.exceptions.JSONDecodeError as e: raise ValueError(f"{error_message}, response code {response.status_code} with response {response.text}") from e
# Clean a version string
[docs] def clean_version(version: str, keep: str = "") -> str: """ Clean a version string Args: version (str): The version string to clean keep (str): The characters to keep in the version string Returns: str: The cleaned version string >>> clean_version("v1.e0.zfezf0.1.2.3zefz") '1.0.0.1.2.3' >>> clean_version("v1.e0.zfezf0.1.2.3zefz", keep="v") 'v1.0.0.1.2.3' >>> clean_version("v1.2.3b", keep="ab") '1.2.3b' """ return "".join(c for c in version if c in "0123456789." + keep)
# Convert a version string to a float
[docs] def version_to_float(version: str) -> float: """ Converts a version string into a float for comparison purposes. The version string is expected to follow the format of major.minor.patch.something_else...., where each part is separated by a dot and can be extended indefinitely. Args: version (str): The version string to convert. (e.g. "v1.0.0.1.2.3") Returns: float: The float representation of the version. (e.g. 0) >>> version_to_float("v1.0.0") 1.0 >>> version_to_float("v1.0.0.1") 1.000000001 >>> version_to_float("v2.3.7") 2.003007 >>> version_to_float("v1.0.0.1.2.3") 1.0000000010020031 >>> version_to_float("v2.0") > version_to_float("v1.0.0.1") True """ # Clean the version string by keeping only the numbers and dots version = clean_version(version) # Split the version string into parts version_parts: list[str] = version.split(".") total: float = 0.0 multiplier: float = 1.0 # Iterate over the parts and add lesser and lesser weight to each part for part in version_parts: total += int(part) * multiplier multiplier /= 1_000 return total