]> jfr.im git - yt-dlp.git/blob - yt_dlp/compat/compat_utils.py
373389a4661b88391419e89435e4d05caa15e99a
[yt-dlp.git] / yt_dlp / compat / compat_utils.py
1 import collections
2 import contextlib
3 import importlib
4 import sys
5 import types
6
7 _NO_ATTRIBUTE = object()
8
9 _Package = collections.namedtuple('Package', ('name', 'version'))
10
11
12 def get_package_info(module):
13 return _Package(
14 name=getattr(module, '_yt_dlp__identifier', module.__name__),
15 version=str(next(filter(None, (
16 getattr(module, attr, None)
17 for attr in ('__version__', 'version_string', 'version')
18 )), None)))
19
20
21 def _is_package(module):
22 return '__path__' in vars(module)
23
24
25 class EnhancedModule(types.ModuleType):
26 def __new__(cls, name, *args, **kwargs):
27 if name not in sys.modules:
28 return super().__new__(cls, name, *args, **kwargs)
29
30 assert not args and not kwargs, 'Cannot pass additional arguments to an existing module'
31 module = sys.modules[name]
32 module.__class__ = cls
33 return module
34
35 def __init__(self, name, *args, **kwargs):
36 # Prevent __new__ from trigerring __init__ again
37 if name not in sys.modules:
38 super().__init__(name, *args, **kwargs)
39
40 def __bool__(self):
41 return vars(self).get('__bool__', lambda: True)()
42
43 def __getattribute__(self, attr):
44 try:
45 ret = super().__getattribute__(attr)
46 except AttributeError:
47 if attr.startswith('__') and attr.endswith('__'):
48 raise
49 getter = getattr(self, '__getattr__', None)
50 if not getter:
51 raise
52 ret = getter(attr)
53 return ret.fget() if isinstance(ret, property) else ret
54
55
56 def passthrough_module(parent, child, allowed_attributes=None, *, callback=lambda _: None):
57 """Passthrough parent module into a child module, creating the parent if necessary"""
58 parent = EnhancedModule(parent)
59
60 def __getattr__(attr):
61 if _is_package(parent):
62 with contextlib.suppress(ImportError):
63 return importlib.import_module(f'.{attr}', parent.__name__)
64
65 ret = from_child(attr)
66 if ret is _NO_ATTRIBUTE:
67 raise AttributeError(f'module {parent.__name__} has no attribute {attr}')
68 callback(attr)
69 return ret
70
71 def from_child(attr):
72 nonlocal child
73
74 if allowed_attributes is None:
75 if attr.startswith('__') and attr.endswith('__'):
76 return _NO_ATTRIBUTE
77 elif attr not in allowed_attributes:
78 return _NO_ATTRIBUTE
79
80 if isinstance(child, str):
81 child = importlib.import_module(child, parent.__name__)
82
83 with contextlib.suppress(AttributeError):
84 return getattr(child, attr)
85
86 if _is_package(child):
87 with contextlib.suppress(ImportError):
88 return importlib.import_module(f'.{attr}', child.__name__)
89
90 return _NO_ATTRIBUTE
91
92 parent.__getattr__ = __getattr__
93 return parent