Source code for linux_utils

# linux-utils: Linux system administration tools for Python.
#
# Author: Peter Odding <peter@peterodding.com>
# Last Change: February 9, 2020
# URL: https://linux-utils.readthedocs.io

"""
Linux system administration tools for Python.

The :mod:`linux_utils` module contains generic functions
to be used by the other modules in the package.
"""

# Standard library modules.
import numbers
import os
import shlex

# External dependencies.
from executor.contexts import AbstractContext, LocalContext
from humanfriendly import parse_size
from six import string_types

# Public identifiers that require documentation.
__all__ = (
    '__version__',
    'coerce_context',
    'coerce_device_file',
    'coerce_size',
)

__version__ = '0.7'
"""Semi-standard module versioning."""


[docs]def coerce_context(value): """ Coerce the given value to an execution context. :param value: The value to coerce (an execution context created by :mod:`executor.contexts` or :data:`None`). :returns: An execution context created by :mod:`executor.contexts`. :raises: :exc:`~exceptions.ValueError` when `value` isn't :data:`None` but also isn't a valid execution context. This function is used throughout the modules in the :mod:`linux_utils` package to abstract away the details of external command execution using `dependency injection`_: - If no execution context is provided (`value` is :data:`None`) :class:`executor.contexts.LocalContext` is used by default. - Callers can provide :class:`executor.contexts.RemoteContext` in which case the :mod:`linux_utils` package seamlessly adapts to command execution on a remote system over :man:`ssh`. .. _dependency injection: https://en.wikipedia.org/wiki/Dependency_injection """ if value is None: value = LocalContext() if not isinstance(value, AbstractContext): msg = "Expected execution context or None, got %r instead!" raise ValueError(msg % type(value)) return value
[docs]def coerce_device_file(expression): r""" Coerce a device identifier to a device file. :param expression: The device identifier (a string). :returns: The pathname of the device file (a string). :raises: :exc:`~exceptions.ValueError` when an unsupported device identifier is encountered. If you pass in a ``LABEL="..."`` or ``UUID=...`` expression (as found in e.g. ``/etc/fstab``) you will get back a pathname starting with ``/dev/disk/by-label`` or ``/dev/disk/by-uuid``: >>> from linux_utils import coerce_device_file >>> print(coerce_device_file('LABEL="Linux Boot"')) /dev/disk/by-label/Linux\x20Boot >>> print(coerce_device_file('UUID=7801a1c2-7ad7-4c0b-9fbb-2a47ae802f71')) /dev/disk/by-uuid/7801a1c2-7ad7-4c0b-9fbb-2a47ae802f71 If `expression` is already a pathname it will pass through untouched: >>> coerce_device_file('/dev/mapper/backups') '/dev/mapper/backups' Unsupported device identifiers raise an exception: >>> coerce_device_file('PARTUUID=e6c021cc-d0d8-400c-8f5c-b10adeff65fe') Traceback (most recent call last): File "linux_utils/__init__.py", line 90, in coerce_device_file raise ValueError(msg % expression) ValueError: Unsupported device identifier! ('PARTUUID=e6c021cc-d0d8-400c-8f5c-b10adeff65fe') """ if '=' in expression: name, _, value = expression.partition('=') # Handle LABEL="User defined label" expressions. if name.upper() == 'LABEL': # Abuse shlex.split() to strip the quotes from the label (because # it's slightly better than naively stripping all leading and # trailing quotes). tokens = shlex.split(value) if len(tokens) == 1: # Gotcha: Make sure to properly encode spaces. label = tokens[0].replace(' ', r'\x20') return os.path.join('/dev/disk/by-label', label) # Handle UUID=6f31b39a-8e3b-4d2c-af74-36653110bfc5 expressions. if name.upper() == 'UUID': return os.path.join('/dev/disk/by-uuid', value.lower()) # Complain about unhandled device identifiers. msg = "Unsupported device identifier! (%r)" raise ValueError(msg % expression) return expression
[docs]def coerce_size(value): """ Coerce a human readable data size to the number of bytes. :param value: The value to coerce (a number or string). :returns: The number of bytes (a number). :raises: :exc:`~exceptions.ValueError` when `value` isn't a number or a string supported by :func:`~humanfriendly.parse_size()`. """ if isinstance(value, string_types): value = parse_size(value) if not isinstance(value, numbers.Number): msg = "Unsupported data size! (%r)" raise ValueError(msg % value) return value