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
(name)
| 401 | _NAME_PATTERN = None |
| 402 | |
| 403 | def resolve_name(name): |
| 404 | """ |
| 405 | Resolve a name to an object. |
| 406 | |
| 407 | It is expected that `name` will be a string in one of the following |
| 408 | formats, where W is shorthand for a valid Python identifier and dot stands |
| 409 | for a literal period in these pseudo-regexes: |
| 410 | |
| 411 | W(.W)* |
| 412 | W(.W)*:(W(.W)*)? |
| 413 | |
| 414 | The first form is intended for backward compatibility only. It assumes that |
| 415 | some part of the dotted name is a package, and the rest is an object |
| 416 | somewhere within that package, possibly nested inside other objects. |
| 417 | Because the place where the package stops and the object hierarchy starts |
| 418 | can't be inferred by inspection, repeated attempts to import must be done |
| 419 | with this form. |
| 420 | |
| 421 | In the second form, the caller makes the division point clear through the |
| 422 | provision of a single colon: the dotted name to the left of the colon is a |
| 423 | package to be imported, and the dotted name to the right is the object |
| 424 | hierarchy within that package. Only one import is needed in this form. If |
| 425 | it ends with the colon, then a module object is returned. |
| 426 | |
| 427 | The function will return an object (which might be a module), or raise one |
| 428 | of the following exceptions: |
| 429 | |
| 430 | ValueError - if `name` isn't in a recognised format |
| 431 | ImportError - if an import failed when it shouldn't have |
| 432 | AttributeError - if a failure occurred when traversing the object hierarchy |
| 433 | within the imported package to get to the desired object. |
| 434 | """ |
| 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: |
| 446 | raise ValueError(f'invalid format: {name!r}') |
| 447 | gd = m.groupdict() |
| 448 | if gd.get('cln'): |
| 449 | # there is a colon - a one-step import is all that's needed |
| 450 | mod = importlib.import_module(gd['pkg']) |
| 451 | parts = gd.get('obj') |
| 452 | parts = parts.split('.') if parts else [] |
| 453 | else: |
| 454 | # no colon - have to iterate to find the package boundary |
| 455 | parts = name.split('.') |
| 456 | modname = parts.pop(0) |
| 457 | # first part *must* be a module/package. |
| 458 | mod = importlib.import_module(modname) |
| 459 | while parts: |
| 460 | p = parts[0] |