diff --git a/pytools/__init__.py b/pytools/__init__.py index bb7994c0a70285f19836a6f1065c8ae794cf4c37..ec878b57659ff92db73ad5c67361ed8817097007 100644 --- a/pytools/__init__.py +++ b/pytools/__init__.py @@ -25,6 +25,7 @@ THE SOFTWARE. """ +import re from functools import reduce, wraps import operator import sys @@ -163,6 +164,11 @@ Sorting in natural order .. autofunction:: natorder .. autofunction:: natsorted +Backports of newer Python functionality +--------------------------------------- + +.. autofunction:: resolve_name + Type Variables Used ------------------- @@ -2543,6 +2549,62 @@ def natsorted(iterable, key=None, reverse=False): # }}} +# {{{ resolve_name + +# https://github.com/python/cpython/commit/1ed61617a4a6632905ad6a0b440cd2cafb8b6414 + +_DOTTED_WORDS = r"[a-z_]\w*(\.[a-z_]\w*)*" +_NAME_PATTERN = re.compile(f"^({_DOTTED_WORDS})(:({_DOTTED_WORDS})?)?$", re.I) +del _DOTTED_WORDS + + +def resolve_name(name): + """A backport of :func:`pkgutil.resolve_name` (added in Python 3.9). + + .. versionadded:: 2021.1.2 + """ + # Delete the tail of the function and deprecate this once we require Python 3.9. + if sys.version_info >= (3, 9): + # use the official version + import pkgutil + return pkgutil.resolve_name(name) # pylint: disable=no-member + + import importlib + + m = _NAME_PATTERN.match(name) + if not m: + raise ValueError(f"invalid format: {name!r}") + groups = m.groups() + if groups[2]: + # there is a colon - a one-step import is all that's needed + mod = importlib.import_module(groups[0]) + parts = groups[3].split(".") if groups[3] else [] + else: + # no colon - have to iterate to find the package boundary + parts = name.split(".") + modname = parts.pop(0) + # first part *must* be a module/package. + mod = importlib.import_module(modname) + while parts: + p = parts[0] + s = f"{modname}.{p}" + try: + mod = importlib.import_module(s) + parts.pop(0) + modname = s + except ImportError: + break + # if we reach this point, mod is the module, already imported, and + # parts is the list of parts in the object hierarchy to be traversed, or + # an empty list if just the module is wanted. + result = mod + for p in parts: + result = getattr(result, p) + return result + +# }}} + + def _test(): import doctest doctest.testmod() diff --git a/pytools/version.py b/pytools/version.py index 2d25ff45eabb27341762df1c288ac7a149e00b13..8b36d4f9e9fce9d4f7b324fbb69d561f854d7a36 100644 --- a/pytools/version.py +++ b/pytools/version.py @@ -1,3 +1,3 @@ -VERSION = (2021, 1, 1) +VERSION = (2021, 1, 2) VERSION_STATUS = "" VERSION_TEXT = ".".join(str(x) for x in VERSION) + VERSION_STATUS