Source code for pykern.pkcompat

"""Backwards and forward compatible Python utilities

Functions here will be available forever, even when functions are
removed from Python.

Some functions are no longer necessary, e.g. `unicode_getcwd`. Others
are still convenient, e.g. `from_bytes` is useful when you don't know
whether it is a `str` or not.

:copyright: Copyright (c) 2015-2023 RadiaSoft LLC.  All Rights Reserved.
:license: http://www.apache.org/licenses/LICENSE-2.0.html
"""

# Limit pykern imports so avoid dependency issues for pkconfig
import datetime
import inspect
import itertools
import locale
import os
import subprocess
import sys
import time


[docs] def from_bytes(value): """Converts value to a str If `value` is not a str, decode with utf-8. If already str, does nothing. Args: value (object): The string or object to be decoded. Returns: bytes: encoded string """ if isinstance(value, str): return value _assert_type(value, (bytes, bytearray)) return value.decode("utf-8")
[docs] def locale_str(value): """Converts a value to a unicode str unless already unicode. Args: value (object): The string or object to be decoded, may be None. Returns: str: decoded string (PY2: type unicode) """ if value is None: return None if isinstance(value, (bytes, bytearray)): return value.decode(locale.getpreferredencoding()) _assert_type(value, str) return value
[docs] def to_bytes(value): """Converts a value to bytes If `value` is a str, encode with utf-8. If already bytes, does nothing. Args: value (object): The string or object to be encoded. Returns: bytes: encoded string """ if isinstance(value, bytes): return value _assert_type(value, str) return bytes(value, "utf-8")
[docs] def unicode_getcwd(): """:func:`os.getcwd` unicode wrapper Returns: str: current directory """ return os.getcwd
[docs] def unicode_unescape(value): """Convert escaped unicode and Python backslash values in str Args: value (str): contains escaped characters Returns: str: unescaped string """ return value.encode("utf-8").decode("unicode-escape")
[docs] def utcnow(): """Replace deprecated datetime.utcnow datetime.UTC does not exist in 3.9 so easier to wrap here. Returns: datetime.datetime: current time in UTC """ return datetime.datetime.fromtimestamp(time.time())
[docs] def zip_strict(*iterables): """`zip` where iterables must be exact. ``strict`` option was added in 3.10. This function is always strict. Args: iterables (object): things to be zipped Returns: object: Iterator for zipping """ if _version_ok(3, 10): return zip(*iterables, strict=True) return _zip_strict(iterables)
def _assert_type(value, typ): if not isinstance(value, typ): raise TypeError( '"value={:.20}<SNIP>" is not a {}, actual type={}'.format( repr(value), typ, type(value), ), ) def _version_ok(major, minor): return sys.version_info >= (major, minor) def _zip_strict(iterables): """Code from https://stackoverflow.com/a/69485272""" def _first_tail(): nonlocal first_stopped first_stopped = True return yield def _zip_tail(): if not first_stopped: raise ValueError("first iterable is longer than rest") for x in itertools.chain.from_iterable(rest): raise ValueError(f"left over data={str(x):.100} in iterable") yield if len(iterables) < 2: return zip(*iterables) first_stopped = False iterables = iter(iterables) first = itertools.chain(next(iterables), _first_tail()) rest = list(map(iter, iterables)) return itertools.chain(zip(first, *rest), _zip_tail())