Skip to content

Commit

Permalink
Merge pull request #2 from graingert/resync
Browse files Browse the repository at this point in the history
backport from py3.10
  • Loading branch information
graingert committed Jul 21, 2021
2 parents 7c53272 + 7b31509 commit 15ac1dd
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 10 deletions.
59 changes: 49 additions & 10 deletions pkgutil_resolve_name.py
Original file line number Diff line number Diff line change
Expand Up @@ -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<pkg>{dotted_words})'
f'(?P<cln>:(?P<obj>{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('.')
Expand All @@ -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)
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

0 comments on commit 15ac1dd

Please sign in to comment.