From 7fd703f6798c89db59d85b9a6727f7bc766e2633 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 21 Jul 2021 08:58:13 +0100 Subject: [PATCH 1/2] backport from py3.10 --- pkgutil_resolve_name.py | 59 ++++++++++++++++++++++++++++++++++------- 1 file changed, 49 insertions(+), 10 deletions(-) diff --git a/pkgutil_resolve_name.py b/pkgutil_resolve_name.py index 0d1fd3a..10bb99c 100644 --- a/pkgutil_resolve_name.py +++ b/pkgutil_resolve_name.py @@ -33,22 +33,61 @@ import importlib import re -__version__ = "1.0.0" +__version__ = "1.3.10" -_DOTTED_WORDS = r'[a-z_]\w*(\.[a-z_]\w*)*' -_NAME_PATTERN = re.compile('^({_DOTTED_WORDS})(:({_DOTTED_WORDS})?)?$'.format(_DOTTED_WORDS=_DOTTED_WORDS), re.I) -del _DOTTED_WORDS +_NAME_PATTERN = None def resolve_name(name): + """ + Resolve a name to an object. + + It is expected that `name` will be a string in one of the following + formats, where W is shorthand for a valid Python identifier and dot stands + for a literal period in these pseudo-regexes: + + W(.W)* + W(.W)*:(W(.W)*)? + + The first form is intended for backward compatibility only. It assumes that + some part of the dotted name is a package, and the rest is an object + somewhere within that package, possibly nested inside other objects. + Because the place where the package stops and the object hierarchy starts + can't be inferred by inspection, repeated attempts to import must be done + with this form. + + In the second form, the caller makes the division point clear through the + provision of a single colon: the dotted name to the left of the colon is a + package to be imported, and the dotted name to the right is the object + hierarchy within that package. Only one import is needed in this form. If + it ends with the colon, then a module object is returned. + + The function will return an object (which might be a module), or raise one + of the following exceptions: + + ValueError - if `name` isn't in a recognised format + ImportError - if an import failed when it shouldn't have + AttributeError - if a failure occurred when traversing the object hierarchy + within the imported package to get to the desired object) + """ + global _NAME_PATTERN + if _NAME_PATTERN is None: + # Lazy import to speedup Python startup time + import re + dotted_words = r'(?!\d)(\w+)(\.(?!\d)(\w+))*' + _NAME_PATTERN = re.compile(f'^(?P{dotted_words})' + f'(?P:(?P{dotted_words})?)?$', + re.UNICODE) + m = _NAME_PATTERN.match(name) if not m: - raise ValueError('invalid format: {name!r}'.format(name=name)) - groups = m.groups() - if groups[2]: + raise ValueError(f'invalid format: {name!r}') + gd = m.groupdict() + if gd.get('cln'): # 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 [] + mod = importlib.import_module(gd['pkg']) + parts = gd.get('obj') + parts = parts.split('.') if parts else [] else: # no colon - have to iterate to find the package boundary parts = name.split('.') @@ -57,7 +96,7 @@ def resolve_name(name): mod = importlib.import_module(modname) while parts: p = parts[0] - s = '{modname}.{p}'.format(modname=modname, p=p) + s = f'{modname}.{p}' try: mod = importlib.import_module(s) parts.pop(0) From 7b31509ed72c5a16a82d55effb26f0c8fc00bc51 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 21 Jul 2021 09:01:00 +0100 Subject: [PATCH 2/2] add requires-python for f-strings --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index db5fbb2..13e2b43 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,3 +11,4 @@ maintainer = "Thomas Grainger" maintainer-email = "pkgutil-resolve-name@graingert.co.uk" home-page = "https://github.com/graingert/pkgutil-resolve-name" classifiers = ["License :: OSI Approved :: MIT License"] +requires-python = ">=3.6"