[cleanup] Misc (#10807)
[yt-dlp.git] / yt_dlp / compat / compat_utils.py
blobd62b7d0488dc9510cd9c5ffb1a5d3f8d2f834c72
1 import collections
2 import contextlib
3 import functools
4 import importlib
5 import sys
6 import types
8 _NO_ATTRIBUTE = object()
10 _Package = collections.namedtuple('Package', ('name', 'version'))
13 def get_package_info(module):
14 return _Package(
15 name=getattr(module, '_yt_dlp__identifier', module.__name__),
16 version=str(next(filter(None, (
17 getattr(module, attr, None)
18 for attr in ('_yt_dlp__version', '__version__', 'version_string', 'version')
19 )), None)))
22 def _is_package(module):
23 return '__path__' in vars(module)
26 def _is_dunder(name):
27 return name.startswith('__') and name.endswith('__')
30 class EnhancedModule(types.ModuleType):
31 def __bool__(self):
32 return vars(self).get('__bool__', lambda: True)()
34 def __getattribute__(self, attr):
35 try:
36 ret = super().__getattribute__(attr)
37 except AttributeError:
38 if _is_dunder(attr):
39 raise
40 getter = getattr(self, '__getattr__', None)
41 if not getter:
42 raise
43 ret = getter(attr)
44 return ret.fget() if isinstance(ret, property) else ret
47 def passthrough_module(parent, child, allowed_attributes=(..., ), *, callback=lambda _: None):
48 """Passthrough parent module into a child module, creating the parent if necessary"""
49 def __getattr__(attr):
50 if _is_package(parent):
51 with contextlib.suppress(ModuleNotFoundError):
52 return importlib.import_module(f'.{attr}', parent.__name__)
54 ret = from_child(attr)
55 if ret is _NO_ATTRIBUTE:
56 raise AttributeError(f'module {parent.__name__} has no attribute {attr}')
57 callback(attr)
58 return ret
60 @functools.lru_cache(maxsize=None)
61 def from_child(attr):
62 nonlocal child
63 if attr not in allowed_attributes:
64 if ... not in allowed_attributes or _is_dunder(attr):
65 return _NO_ATTRIBUTE
67 if isinstance(child, str):
68 child = importlib.import_module(child, parent.__name__)
70 if _is_package(child):
71 with contextlib.suppress(ImportError):
72 return passthrough_module(f'{parent.__name__}.{attr}',
73 importlib.import_module(f'.{attr}', child.__name__))
75 with contextlib.suppress(AttributeError):
76 return getattr(child, attr)
78 return _NO_ATTRIBUTE
80 parent = sys.modules.get(parent, types.ModuleType(parent))
81 parent.__class__ = EnhancedModule
82 parent.__getattr__ = __getattr__
83 return parent