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 i
(name)
| 643 | _NAME_PATTERN = None |
| 644 | |
| 645 | def resolve_name(name): |
| 646 | """ |
| 647 | Resolve a name to an object. |
| 648 | |
| 649 | It is expected that `name` will be a string in one of the following |
| 650 | formats, where W is shorthand for a valid Python identifier and dot stands |
| 651 | for a literal period in these pseudo-regexes: |
| 652 | |
| 653 | W(.W)* |
| 654 | W(.W)*:(W(.W)*)? |
| 655 | |
| 656 | The first form is intended for backward compatibility only. It assumes that |
| 657 | some part of the dotted name is a package, and the rest is an object |
| 658 | somewhere within that package, possibly nested inside other objects. |
| 659 | Because the place where the package stops and the object hierarchy starts |
| 660 | can't be inferred by inspection, repeated attempts to import must be done |
| 661 | with this form. |
| 662 | |
| 663 | In the second form, the caller makes the division point clear through the |
| 664 | provision of a single colon: the dotted name to the left of the colon is a |
| 665 | package to be imported, and the dotted name to the right is the object |
| 666 | hierarchy within that package. Only one import is needed in this form. If |
| 667 | it ends with the colon, then a module object is returned. |
| 668 | |
| 669 | The function will return an object (which might be a module), or raise one |
| 670 | of the following exceptions: |
| 671 | |
| 672 | ValueError - if `name` isn't in a recognised format |
| 673 | ImportError - if an import failed when it shouldn't have |
| 674 | AttributeError - if a failure occurred when traversing the object hierarchy |
| 675 | within the imported package to get to the desired object. |
| 676 | """ |
| 677 | global _NAME_PATTERN |
| 678 | if _NAME_PATTERN is None: |
| 679 | # Lazy import to speedup Python startup time |
| 680 | import re |
| 681 | dotted_words = r'(?!\d)(\w+)(\.(?!\d)(\w+))*' |
| 682 | _NAME_PATTERN = re.compile(f'^(?P<pkg>{dotted_words})' |
| 683 | f'(?P<cln>:(?P<obj>{dotted_words})?)?$', |
| 684 | re.UNICODE) |
| 685 | |
| 686 | m = _NAME_PATTERN.match(name) |
| 687 | if not m: |
| 688 | raise ValueError(f'invalid format: {name!r}') |
| 689 | gd = m.groupdict() |
| 690 | if gd.get('cln'): |
| 691 | # there is a colon - a one-step import is all that's needed |
| 692 | mod = importlib.import_module(gd['pkg']) |
| 693 | parts = gd.get('obj') |
| 694 | parts = parts.split('.') if parts else [] |
| 695 | else: |
| 696 | # no colon - have to iterate to find the package boundary |
| 697 | parts = name.split('.') |
| 698 | modname = parts.pop(0) |
| 699 | # first part *must* be a module/package. |
| 700 | mod = importlib.import_module(modname) |
| 701 | while parts: |
| 702 | p = parts[0] |