99import os .path
1010import sys
1111
12+ lazy import re
13+
14+
1215__all__ = [
1316 'get_importer' , 'iter_importers' ,
1417 'walk_packages' , 'iter_modules' , 'get_data' ,
@@ -398,9 +401,10 @@ def get_data(package, resource):
398401 return loader .get_data (resource_name )
399402
400403
401- _NAME_PATTERN = None
404+ _LENIENT_PATTERN = None
405+ _STRICT_PATTERN = None
402406
403- def resolve_name (name ):
407+ def resolve_name (name , * , strict = False ):
404408 """
405409 Resolve a name to an object.
406410
@@ -410,6 +414,7 @@ def resolve_name(name):
410414
411415 W(.W)*
412416 W(.W)*:(W(.W)*)?
417+ W(.W)*:(W(.W)*)
413418
414419 The first form is intended for backward compatibility only. It assumes that
415420 some part of the dotted name is a package, and the rest is an object
@@ -424,6 +429,11 @@ def resolve_name(name):
424429 hierarchy within that package. Only one import is needed in this form. If
425430 it ends with the colon, then a module object is returned.
426431
432+ The first two forms are accepted when `strict=False` (the default).
433+
434+ The third form requires both the module name and callable, separated by
435+ a colon. Only this form is accepted when `strict=True`.
436+
427437 The function will return an object (which might be a module), or raise one
428438 of the following exceptions:
429439
@@ -432,18 +442,26 @@ def resolve_name(name):
432442 AttributeError - if a failure occurred when traversing the object hierarchy
433443 within the imported package to get to the desired object.
434444 """
435- global _NAME_PATTERN
436- if _NAME_PATTERN is None :
437- # Lazy import to speedup Python startup time
438- import re
439- dotted_words = r'(?!\d)(\w+)(\.(?!\d)(\w+))*'
440- _NAME_PATTERN = re .compile (f'^(?P<pkg>{ dotted_words } )'
441- f'(?P<cln>:(?P<obj>{ dotted_words } )?)?$' ,
442- re .UNICODE )
443-
444- m = _NAME_PATTERN .match (name )
445- if not m :
445+ global _LENIENT_PATTERN , _STRICT_PATTERN
446+ dotted_words = r'(?!\d)(\w+)(\.(?!\d)(\w+))*'
447+ if strict :
448+ if _STRICT_PATTERN is None :
449+ _STRICT_PATTERN = re .compile (
450+ f'^(?P<pkg>{ dotted_words } )'
451+ f'(?P<cln>:(?P<obj>{ dotted_words } ))$' ,
452+ re .UNICODE )
453+ pattern = _STRICT_PATTERN
454+ else :
455+ if _LENIENT_PATTERN is None :
456+ _LENIENT_PATTERN = re .compile (
457+ f'^(?P<pkg>{ dotted_words } )'
458+ f'(?P<cln>:(?P<obj>{ dotted_words } )?)?$' ,
459+ re .UNICODE )
460+ pattern = _LENIENT_PATTERN
461+
462+ if (m := pattern .match (name )) is None :
446463 raise ValueError (f'invalid format: { name !r} ' )
464+
447465 gd = m .groupdict ()
448466 if gd .get ('cln' ):
449467 # there is a colon - a one-step import is all that's needed
0 commit comments