Module psdi_data_conversion.utils

utils.py

Miscellaneous utility functions used by this project

Functions

def confirm_editable_mode()
Expand source code
def confirm_editable_mode():
    """Checks if the `psdi_data_conversion` module is installed in editable mode, and exits the program if not
    """
    if not in_editable_mode():
        print_wrap(f"{TextColors.RED}ERROR:{TextColors.OFF} To run this script, the package must be installed in "
                   f"editable mode. Please reinstall with:\n")
        print_wrap("pip install --editable .\n", color=TextColors.WARNING)
        print_wrap("and re-run this script.")
        exit(1)

Checks if the psdi_data_conversion module is installed in editable mode, and exits the program if not

def disable_colors()
Expand source code
def disable_colors():
    """Globally disable color formatting in output text"""
    tc.disable()

Globally disable color formatting in output text

def displaylen(s: str)
Expand source code
def displaylen(s: str):
    """Get the length of a string as it would be displayed in the terminal - this is, stripping out control codes"""
    return len(strip_control_codes(s))

Get the length of a string as it would be displayed in the terminal - this is, stripping out control codes

def enable_colors()
Expand source code
def enable_colors():
    """Globally (re)enable color formatting in output text"""
    tc.enable()

Globally (re)enable color formatting in output text

def get_project_path() ‑> pathlib.Path
Expand source code
@lru_cache(maxsize=1)
def get_project_path() -> Path:
    """Gets the absolute path to where the project is on disk, using the package path to find it and checking that it
    contains the expected files
    """

    project_path = (get_package_path() / "..").resolve()

    # Check that the project path contains the expected test_data folder
    if not (project_path / "pyproject.toml").is_file():
        raise FileNotFoundError(f"Project path was expected to be '{project_path}', but this does not contain the "
                                f"expected file 'pyproject.toml'")

    return project_path

Gets the absolute path to where the project is on disk, using the package path to find it and checking that it contains the expected files

def get_wrapped_str(s: str, color: str | None = None, **kwargs)
Expand source code
def get_wrapped_str(s: str, color: str | None = None, **kwargs):
    """Get a string wrapped to the terminal width"""
    if color:
        s_colored = color+s+TextColors.OFF
    else:
        s_colored = s
    return wraptext.fill(s_colored, width=TERM_WIDTH, **kwargs)

Get a string wrapped to the terminal width

def in_editable_mode()
Expand source code
def in_editable_mode():
    """Checks if the `psdi_data_conversion` module is installed in editable mode
    """

    direct_url = Distribution.from_name("psdi_data_conversion").read_text("direct_url.json")
    is_editable: bool = json.loads(direct_url).get("dir_info", {}).get("editable", False)

    return is_editable

Checks if the psdi_data_conversion module is installed in editable mode

def print_header(s: str, **kwargs)
Expand source code
def print_header(s: str,  **kwargs):
    """Print a string formatted as a header"""
    print_wrap(f"\n{tc.HEADER}{s}{tc.OFF}", newline=True, initial_indent=" "*2, subsequent_indent=" "*2, **kwargs)

Print a string formatted as a header

def print_wrap(s: str, newline=False, err=False, **kwargs)
Expand source code
def print_wrap(s: str, newline=False, err=False, **kwargs):
    """Print a string wrapped to the terminal width
    """

    code_re = re.compile("(" + tc.CODE.replace('[', r'\[') + "`.*?`" + tc.OFF.replace('[', r'\[') + ")")

    if err:
        file = sys.stderr
    else:
        file = sys.stdout
    for line in s.split("\n"):
        # Check for any code segments in the line
        l_segments = code_re.split(line)
        if len(l_segments) == 1:
            print(get_wrapped_str(line, **kwargs), file=file)
        else:
            # Make all code segments non-breaking while determining the wrap, then undo it when printing
            non_breaking_line = ""
            for i, segment in enumerate(l_segments):
                if i % 2 == 0:
                    non_breaking_line += segment
                else:
                    non_breaking_line += segment.replace(" ", "\u00a0").replace("-", "\u2011")
            wrapped_line = get_wrapped_str(non_breaking_line, break_long_words=False,
                                           **kwargs).replace("\u00a0", " ").replace("\u2011", "-")
            print(wrapped_line, file=file)
    if newline:
        print("")

Print a string wrapped to the terminal width

def regularize_name(name: str)
Expand source code
def regularize_name(name: str):
    """Regularizes a name for comparisons, making it lowercase and stripping spaces

    Parameters
    ----------
    name : str
        The name, e.g. "Open Babel"

    Returns
    -------
    str
        The regularized name, e.g. "openbabel"
    """
    return name.lower().replace(" ", "")

Regularizes a name for comparisons, making it lowercase and stripping spaces

Parameters

name : str
The name, e.g. "Open Babel"

Returns

str
The regularized name, e.g. "openbabel"
def replace_extension(filename: str, new_ext: str)
Expand source code
def replace_extension(filename: str, new_ext: str):
    """Replace the extension of a filename with a new extension"""

    # Get the extension with a full-stop at the beginning
    if new_ext.startswith("."):
        ext = new_ext
    else:
        ext = "." + new_ext

    return strip_extension(filename) + ext

Replace the extension of a filename with a new extension

def strip_control_codes(s: str)
Expand source code
def strip_control_codes(s: str):
    """Strip all control codes from a string"""
    return CONTROL_CODE_RE.sub("", str(s))

Strip all control codes from a string

def strip_extension(filename: str)
Expand source code
def strip_extension(filename: str):
    """Strip all extensions from a filename"""

    last_stripped_filename = filename
    stripped_filename, _ = os.path.splitext(filename)
    while stripped_filename != last_stripped_filename:
        last_stripped_filename = stripped_filename
        stripped_filename, _ = os.path.splitext(stripped_filename)
    return stripped_filename

Strip all extensions from a filename

Classes

class CustomHelpFormatter (prog, indent_increment=2, max_help_position=24, width=None)
Expand source code
class CustomHelpFormatter(argparse.HelpFormatter):
    """Custom formatter for argparse
    """

    def _format_action(self, action):
        return super()._format_action(action) + "\n"

Custom formatter for argparse

Ancestors

  • argparse.HelpFormatter
class TextColors
Expand source code
class TextColors:
    """ANSI escape codes that can be used to color text printed to the terminal. E.g. to give text the header color,
    you could do `print(f"{TextColors.MAGENTA}Header text{TextColors.OFF}")`
    """

    # Text color codes

    RED = ERROR = FAIL = "\033[91m"
    """Start coloring red"""

    DARKRED = "\033[31m"
    """Start coloring dark red"""

    GREEN = SUCCESS = "\033[92m"
    """Start coloring green"""

    DARKGREEN = "\033[32m"
    """Start coloring dark green"""

    YELLOW = CODE = "\033[93m"
    """Start coloring yellow"""

    DARKYELLOW = WARNING = "\033[33m"
    """Start coloring dark yellow"""

    BLUE = PATH = ID = "\033[94m"
    """Start coloring blue"""

    DARKBLUE = "\033[34m"
    """Start coloring dark blue"""

    MAGENTA = "\033[95m"
    """Start coloring magenta"""

    DARKMAGENTA = "\033[35m"
    """Start coloring dark magenta"""

    CYAN = MESSAGE = NUMBER = "\033[96m"
    """Start coloring cyan"""

    DARKCYAN = DARKNUMBER = "\033[36m"
    """Start coloring dark cyan"""

    # Text formatting codes

    BOLD = "\033[1m"
    """Start formatting bold - NOT compatible with coloring"""

    DIM = "\033[2m"
    """Start formatting dim (opposite of bold) - NOT compatible with coloring"""

    UNDERLINE = "\033[4m"
    """Start underlining - compatible with coloring"""

    # Combined codes

    HEADER = "\033[95m\033[4m"
    """Start header section - magenta underlined"""

    LINK = "\033[96m\033[4m"
    """Start link - cyan underlined"""

    # Other codes

    OFF = "\033[0m"
    """End all coloring and formatting"""

    @classmethod
    def _get_codes(cls):
        return [x for x in dir(cls) if not x.startswith("_") and x.upper() == x]

    def display(self):
        """Displays all color codes"""
        l_codes_and_vals = [(x, getattr(self, x)) for x in self._get_codes()]
        l_codes_and_vals.sort(key=lambda x: int(x[1].replace("\033[", "").replace("m", "")) if x[1] else x[0])
        for code, val in l_codes_and_vals:
            print(f"{val}{code}{self.OFF}")

    def enable(self):
        """Enable colors"""
        l_codes = self._get_codes()
        for code in l_codes:
            setattr(self, code, getattr(type(self), code))

    def disable(self):
        """Disable colors"""
        l_codes = self._get_codes()
        for code in l_codes:
            setattr(self, code, "")

ANSI escape codes that can be used to color text printed to the terminal. E.g. to give text the header color, you could do print(f"{TextColors.MAGENTA}Header text{TextColors.OFF}")

Class variables

var BLUE
var BOLD

Start formatting bold - NOT compatible with coloring

var CODE
var CYAN
var DARKBLUE

Start coloring dark blue

var DARKCYAN
var DARKGREEN

Start coloring dark green

var DARKMAGENTA

Start coloring dark magenta

var DARKNUMBER
var DARKRED

Start coloring dark red

var DARKYELLOW
var DIM

Start formatting dim (opposite of bold) - NOT compatible with coloring

var ERROR
var FAIL
var GREEN
var HEADER

Start header section - magenta underlined

var ID

Start link - cyan underlined

var MAGENTA

Start coloring magenta

var MESSAGE
var NUMBER
var OFF

End all coloring and formatting

var PATH
var RED
var SUCCESS
var UNDERLINE

Start underlining - compatible with coloring

var WARNING
var YELLOW

Methods

def disable(self)
Expand source code
def disable(self):
    """Disable colors"""
    l_codes = self._get_codes()
    for code in l_codes:
        setattr(self, code, "")

Disable colors

def display(self)
Expand source code
def display(self):
    """Displays all color codes"""
    l_codes_and_vals = [(x, getattr(self, x)) for x in self._get_codes()]
    l_codes_and_vals.sort(key=lambda x: int(x[1].replace("\033[", "").replace("m", "")) if x[1] else x[0])
    for code, val in l_codes_and_vals:
        print(f"{val}{code}{self.OFF}")

Displays all color codes

def enable(self)
Expand source code
def enable(self):
    """Enable colors"""
    l_codes = self._get_codes()
    for code in l_codes:
        setattr(self, code, getattr(type(self), code))

Enable colors