"""Utility modules and functions"""from__future__importannotationsimportosfromtypesimportTracebackTypefromtypingimportIterable,Iterator,Optional,Tuple,Type,TypeVarfrompackaging.versionimportVersionfrom.pathimportnormalize_N=TypeVar("_N",float,int)_T=TypeVar("_T")_ExecInfoType=Tuple[Type[BaseException],BaseException,Optional[TracebackType]]
[docs]defnatural_size(num:float,unit:str="B",sep:bool=True)->str:""" Convert number to a human-readable string with decimal prefix. :param float num: Value in given unit. :param unit: Unit suffix. :param sep: Whether to separate unit and value with a space. :returns: Human-readable string with decimal prefixes. """sep_char=" "ifsepelse""forprefixin("","K","M","G"):ifabs(num)<1000.0:returnf"{num:3.1f}{sep_char}{prefix}{unit}"num/=1000.0prefix="T"returnf"{num:.1f}{sep_char}{prefix}{unit}"
[docs]defchunks(lst:list[_T],n:int,consume:bool=False)->Iterator[list[_T]]:""" Partitions a list into chunks of length ``n``. :param lst: Iterable to partition. :param n: Chunk size. :param consume: If True, the list will be consumed (emptied) during the iteration. :returns: Iterator over chunks. """ifconsume:whilelst:chunk=lst[0:n]dellst[0:n]yieldchunkelse:foriinrange(0,len(lst),n):yieldlst[i:i+n]
[docs]defclamp(n:_N,minn:_N,maxn:_N)->_N:""" Clamps a number between a minimum and maximum value. :param n: Original value. :param minn: Minimum allowed value. :param maxn: Maximum allowed value. :returns: Clamped value. """ifn>maxn:returnmaxnelifn<minn:returnminnelse:returnn
[docs]defget_newer_version(version:str,releases:Iterable[str])->Optional[str]:""" Checks a given release version against a version list of releases to see if an update is available. Only offers newer versions if they are not a prerelease. :param version: The current version. :param releases: A list of valid cleaned releases. :returns: The version string of the latest release if a newer release is available. """releases=[rforrinreleasesifnotVersion(r).is_prerelease]releases.sort(key=Version)latest_release=releases[-1]returnlatest_releaseifVersion(version)<Version(latest_release)elseNone
[docs]defremoveprefix(string:str,prefix:str,case_sensitive:bool=True)->str:""" Removes the given prefix from a string. Only the first instance of the prefix is removed. The original string is returned if it does not start with the given prefix. This follows the Python 3.9 implementation of ``str.removeprefix``. :param string: Original string. :param prefix: Prefix to remove. :param case_sensitive: Whether to do case-sensitive prefix removal. :returns: String without prefix. """ifcase_sensitive:string_lower=stringprefix_lower=prefixelse:string_lower=normalize(string)prefix_lower=normalize(prefix)ifstring_lower.startswith(prefix_lower):returnstring[len(prefix):]else:raiseValueError(f'"{string}" does not start with "{prefix}"')
[docs]defsanitize_string(string:str)->str:""" Converts a string provided by file system APIs, which may contain surrogate escapes for bytes with unknown encoding, to a string which can always be displayed or printed. This is done by replacing invalid characters with "�". :param string: Original string. :returns: Sanitised path where all surrogate escapes have been replaced with "�". """returnos.fsencode(string).decode(errors="replace")
[docs]defexc_info_tuple(exc:BaseException)->_ExecInfoType:"""Creates an exc-info tuple from an exception."""returntype(exc),exc,exc.__traceback__