]>
Commit | Line | Data |
---|---|---|
1d485a1a | 1 | import collections |
9196cbfe | 2 | import contextlib |
88426d94 | 3 | import functools |
9196cbfe | 4 | import importlib |
5 | import sys | |
6 | import types | |
7 | ||
1d485a1a | 8 | _NO_ATTRIBUTE = object() |
9 | ||
10 | _Package = collections.namedtuple('Package', ('name', 'version')) | |
11 | ||
12 | ||
13 | def get_package_info(module): | |
f6a765ce | 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 ('__version__', 'version_string', 'version') | |
19 | )), None))) | |
1d485a1a | 20 | |
21 | ||
9196cbfe | 22 | def _is_package(module): |
754c84e2 | 23 | return '__path__' in vars(module) |
24 | ||
25 | ||
88426d94 | 26 | def _is_dunder(name): |
27 | return name.startswith('__') and name.endswith('__') | |
28 | ||
29 | ||
754c84e2 | 30 | class EnhancedModule(types.ModuleType): |
31 | def __new__(cls, name, *args, **kwargs): | |
32 | if name not in sys.modules: | |
33 | return super().__new__(cls, name, *args, **kwargs) | |
34 | ||
35 | assert not args and not kwargs, 'Cannot pass additional arguments to an existing module' | |
36 | module = sys.modules[name] | |
37 | module.__class__ = cls | |
38 | return module | |
39 | ||
40 | def __init__(self, name, *args, **kwargs): | |
41 | # Prevent __new__ from trigerring __init__ again | |
42 | if name not in sys.modules: | |
43 | super().__init__(name, *args, **kwargs) | |
44 | ||
45 | def __bool__(self): | |
46 | return vars(self).get('__bool__', lambda: True)() | |
47 | ||
48 | def __getattribute__(self, attr): | |
49 | try: | |
50 | ret = super().__getattribute__(attr) | |
51 | except AttributeError: | |
88426d94 | 52 | if _is_dunder(attr): |
754c84e2 | 53 | raise |
54 | getter = getattr(self, '__getattr__', None) | |
55 | if not getter: | |
56 | raise | |
57 | ret = getter(attr) | |
58 | return ret.fget() if isinstance(ret, property) else ret | |
9196cbfe | 59 | |
60 | ||
88426d94 | 61 | def passthrough_module(parent, child, allowed_attributes=(..., ), *, callback=lambda _: None): |
754c84e2 | 62 | """Passthrough parent module into a child module, creating the parent if necessary""" |
63 | parent = EnhancedModule(parent) | |
3c5386cd | 64 | |
754c84e2 | 65 | def __getattr__(attr): |
66 | if _is_package(parent): | |
67 | with contextlib.suppress(ImportError): | |
68 | return importlib.import_module(f'.{attr}', parent.__name__) | |
560738f3 | 69 | |
754c84e2 | 70 | ret = from_child(attr) |
71 | if ret is _NO_ATTRIBUTE: | |
72 | raise AttributeError(f'module {parent.__name__} has no attribute {attr}') | |
73 | callback(attr) | |
74 | return ret | |
9196cbfe | 75 | |
88426d94 | 76 | @functools.lru_cache(maxsize=None) |
754c84e2 | 77 | def from_child(attr): |
78 | nonlocal child | |
88426d94 | 79 | if attr not in allowed_attributes: |
80 | if ... not in allowed_attributes or _is_dunder(attr): | |
754c84e2 | 81 | return _NO_ATTRIBUTE |
9196cbfe | 82 | |
754c84e2 | 83 | if isinstance(child, str): |
84 | child = importlib.import_module(child, parent.__name__) | |
85 | ||
754c84e2 | 86 | if _is_package(child): |
87 | with contextlib.suppress(ImportError): | |
88426d94 | 88 | return passthrough_module(f'{parent.__name__}.{attr}', |
89 | importlib.import_module(f'.{attr}', child.__name__)) | |
90 | ||
91 | with contextlib.suppress(AttributeError): | |
92 | return getattr(child, attr) | |
754c84e2 | 93 | |
94 | return _NO_ATTRIBUTE | |
95 | ||
96 | parent.__getattr__ = __getattr__ | |
97 | return parent |