Support module level __bool__ and property

This commit is contained in:
pukkandan 2023-02-08 07:25:36 +05:30
parent 7aefd19afe
commit 754c84e2e4
No known key found for this signature in database
GPG Key ID: 7EEE9E1E817D0A39
2 changed files with 64 additions and 37 deletions

View File

@ -8,7 +8,7 @@ from .compat_utils import passthrough_module
# XXX: Implement this the same way as other DeprecationWarnings without circular import # XXX: Implement this the same way as other DeprecationWarnings without circular import
passthrough_module(__name__, '._legacy', callback=lambda attr: warnings.warn( passthrough_module(__name__, '._legacy', callback=lambda attr: warnings.warn(
DeprecationWarning(f'{__name__}.{attr} is deprecated'), stacklevel=3)) DeprecationWarning(f'{__name__}.{attr} is deprecated'), stacklevel=5))
# HTMLParseError has been deprecated in Python 3.3 and removed in # HTMLParseError has been deprecated in Python 3.3 and removed in

View File

@ -23,48 +23,75 @@ def get_package_info(module):
def _is_package(module): def _is_package(module):
return '__path__' in vars(module)
class EnhancedModule(types.ModuleType):
def __new__(cls, name, *args, **kwargs):
if name not in sys.modules:
return super().__new__(cls, name, *args, **kwargs)
assert not args and not kwargs, 'Cannot pass additional arguments to an existing module'
module = sys.modules[name]
module.__class__ = cls
return module
def __init__(self, name, *args, **kwargs):
# Prevent __new__ from trigerring __init__ again
if name not in sys.modules:
super().__init__(name, *args, **kwargs)
def __bool__(self):
return vars(self).get('__bool__', lambda: True)()
def __getattribute__(self, attr):
try: try:
module.__getattribute__('__path__') ret = super().__getattribute__(attr)
except AttributeError: except AttributeError:
return False if attr.startswith('__') and attr.endswith('__'):
return True raise
getter = getattr(self, '__getattr__', None)
if not getter:
raise
ret = getter(attr)
return ret.fget() if isinstance(ret, property) else ret
def passthrough_module(parent, child, allowed_attributes=None, *, callback=lambda _: None): def passthrough_module(parent, child, allowed_attributes=None, *, callback=lambda _: None):
parent_module = importlib.import_module(parent) """Passthrough parent module into a child module, creating the parent if necessary"""
child_module = None # Import child module only as needed parent = EnhancedModule(parent)
class PassthroughModule(types.ModuleType): def __getattr__(attr):
def __getattr__(self, attr): if _is_package(parent):
if _is_package(parent_module):
with contextlib.suppress(ImportError): with contextlib.suppress(ImportError):
return importlib.import_module(f'.{attr}', parent) return importlib.import_module(f'.{attr}', parent.__name__)
ret = self.__from_child(attr) ret = from_child(attr)
if ret is _NO_ATTRIBUTE: if ret is _NO_ATTRIBUTE:
raise AttributeError(f'module {parent} has no attribute {attr}') raise AttributeError(f'module {parent.__name__} has no attribute {attr}')
callback(attr) callback(attr)
return ret return ret
def __from_child(self, attr): def from_child(attr):
nonlocal child
if allowed_attributes is None: if allowed_attributes is None:
if attr.startswith('__') and attr.endswith('__'): if attr.startswith('__') and attr.endswith('__'):
return _NO_ATTRIBUTE return _NO_ATTRIBUTE
elif attr not in allowed_attributes: elif attr not in allowed_attributes:
return _NO_ATTRIBUTE return _NO_ATTRIBUTE
nonlocal child_module if isinstance(child, str):
child_module = child_module or importlib.import_module(child, parent) child = importlib.import_module(child, parent.__name__)
with contextlib.suppress(AttributeError): with contextlib.suppress(AttributeError):
return getattr(child_module, attr) return getattr(child, attr)
if _is_package(child_module): if _is_package(child):
with contextlib.suppress(ImportError): with contextlib.suppress(ImportError):
return importlib.import_module(f'.{attr}', child) return importlib.import_module(f'.{attr}', child.__name__)
return _NO_ATTRIBUTE return _NO_ATTRIBUTE
# Python 3.6 does not have module level __getattr__ parent.__getattr__ = __getattr__
# https://peps.python.org/pep-0562/ return parent
sys.modules[parent].__class__ = PassthroughModule