Created
September 11, 2017 08:34
-
-
Save anthonyrisinger/b04f40a3611fd7cde10eed6bb68e8824 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Import: | |
def __init__(self, args, attr): | |
self.args = args | |
self.attr = attr | |
self.done = False | |
def __import__(self): | |
module = __import__(*self.args) | |
if not self.attr: | |
return module | |
try: | |
return getattr(module, self.attr) | |
except AttributeError as e: | |
raise ImportError(f'getattr({module!r}, {self.attr!r})') from e | |
class DeferredImportNamespace(dict): | |
def __init__(self, *args, **kwds): | |
super().__init__(*args, **kwds) | |
self.deferred = {} | |
def __defer__(self, args, *names): | |
# If __import__ is called and globals.__defer__() is defined, names to | |
# bind are non-empty, each name is either missing from globals.deferred | |
# or still marked done=False, then it should call: | |
# | |
# globals.__defer__(args, *names) | |
# | |
# where `args` are the original arguments and `names` are the bindings: | |
# | |
# from spam.ham import eggs, sausage as saus | |
# __defer__(('spam.ham', self, self, ['eggs', 'sausage'], 0), 'eggs', 'saus') | |
# | |
# Records the import and what names would have been used. | |
for i, name in enumerate(names): | |
if name not in self.deferred: | |
attr = args[3][i] if args[3] else None | |
self.deferred[name] = Import(args, attr) | |
def __missing__(self, name): | |
# Raise KeyError if not a deferred import. | |
deferred = self.deferred[name] | |
try: | |
# Replay original __import__ call. | |
resolved = deferred.__import__() | |
except KeyError as e: | |
# KeyError -> ImportError so it's not swallowed by __missing__. | |
raise ImportError(f'{name} = __import__{deferred.args}') from e | |
else: | |
# TODO: Still need a way to avoid binds... or maybe opt-in? | |
# | |
# from spam.ham import eggs, sausage using methods=True | |
# | |
# Save the import to namespace! | |
self[name] = resolved | |
finally: | |
# Set after import to avoid recursion. | |
deferred.done = True | |
# Return import to original requestor. | |
return resolved | |
class MetaModuleType(type): | |
@classmethod | |
def __prepare__(cls, name, bases, defer=True, **kwds): | |
return DeferredImportNamespace() if defer else {} | |
class ModuleType(metaclass=MetaModuleType): | |
# Simulate what we want to happen in a module block! | |
__defer__ = locals().__defer__ | |
# from os.path import realpath as rpath | |
__defer__(('os.path', locals(), locals(), ['realpath'], 0), 'rpath') | |
# from spam.ham import eggs, sausage as saus | |
__defer__(('spam.ham', locals(), locals(), ['eggs', 'sausage'], 0), 'eggs', 'saus') | |
# Good import. | |
print(rpath) | |
print(rpath('.')) | |
# Bad import. | |
print(saus) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment