Skip to content

Instantly share code, notes, and snippets.

@anthonyrisinger
Created September 11, 2017 08:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anthonyrisinger/b04f40a3611fd7cde10eed6bb68e8824 to your computer and use it in GitHub Desktop.
Save anthonyrisinger/b04f40a3611fd7cde10eed6bb68e8824 to your computer and use it in GitHub Desktop.
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