]> jfr.im git - yt-dlp.git/blobdiff - yt_dlp/compat/compat_utils.py
[ie/podbayfm] Fix extraction (#10195)
[yt-dlp.git] / yt_dlp / compat / compat_utils.py
index 79e253056b584380905b002749cfc8a78f7acbe5..d62b7d0488dc9510cd9c5ffb1a5d3f8d2f834c72 100644 (file)
@@ -1,71 +1,83 @@
 import collections
 import contextlib
+import functools
 import importlib
 import sys
 import types
 
-
 _NO_ATTRIBUTE = object()
 
 _Package = collections.namedtuple('Package', ('name', 'version'))
 
 
 def get_package_info(module):
-    parent = module.__name__.split('.')[0]
-    parent_module = None
-    with contextlib.suppress(ImportError):
-        parent_module = importlib.import_module(parent)
-
-    for attr in ('__version__', 'version_string', 'version'):
-        version = getattr(parent_module, attr, None)
-        if version is not None:
-            break
-    return _Package(getattr(module, '_yt_dlp__identifier', parent), str(version))
+    return _Package(
+        name=getattr(module, '_yt_dlp__identifier', module.__name__),
+        version=str(next(filter(None, (
+            getattr(module, attr, None)
+            for attr in ('_yt_dlp__version', '__version__', 'version_string', 'version')
+        )), None)))
 
 
 def _is_package(module):
-    try:
-        module.__getattribute__('__path__')
-    except AttributeError:
-        return False
-    return True
-
-
-def passthrough_module(parent, child, allowed_attributes=None, *, callback=lambda _: None):
-    parent_module = importlib.import_module(parent)
-    child_module = None  # Import child module only as needed
-
-    class PassthroughModule(types.ModuleType):
-        def __getattr__(self, attr):
-            if _is_package(parent_module):
-                with contextlib.suppress(ImportError):
-                    return importlib.import_module(f'.{attr}', parent)
-
-            ret = self.__from_child(attr)
-            if ret is _NO_ATTRIBUTE:
-                raise AttributeError(f'module {parent} has no attribute {attr}')
-            callback(attr)
-            return ret
-
-        def __from_child(self, attr):
-            if allowed_attributes is None:
-                if attr.startswith('__') and attr.endswith('__'):
-                    return _NO_ATTRIBUTE
-            elif attr not in allowed_attributes:
+    return '__path__' in vars(module)
+
+
+def _is_dunder(name):
+    return name.startswith('__') and name.endswith('__')
+
+
+class EnhancedModule(types.ModuleType):
+    def __bool__(self):
+        return vars(self).get('__bool__', lambda: True)()
+
+    def __getattribute__(self, attr):
+        try:
+            ret = super().__getattribute__(attr)
+        except AttributeError:
+            if _is_dunder(attr):
+                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=(..., ), *, callback=lambda _: None):
+    """Passthrough parent module into a child module, creating the parent if necessary"""
+    def __getattr__(attr):
+        if _is_package(parent):
+            with contextlib.suppress(ModuleNotFoundError):
+                return importlib.import_module(f'.{attr}', parent.__name__)
+
+        ret = from_child(attr)
+        if ret is _NO_ATTRIBUTE:
+            raise AttributeError(f'module {parent.__name__} has no attribute {attr}')
+        callback(attr)
+        return ret
+
+    @functools.lru_cache(maxsize=None)
+    def from_child(attr):
+        nonlocal child
+        if attr not in allowed_attributes:
+            if ... not in allowed_attributes or _is_dunder(attr):
                 return _NO_ATTRIBUTE
 
-            nonlocal child_module
-            child_module = child_module or importlib.import_module(child, parent)
+        if isinstance(child, str):
+            child = importlib.import_module(child, parent.__name__)
 
-            with contextlib.suppress(AttributeError):
-                return getattr(child_module, attr)
+        if _is_package(child):
+            with contextlib.suppress(ImportError):
+                return passthrough_module(f'{parent.__name__}.{attr}',
+                                          importlib.import_module(f'.{attr}', child.__name__))
 
-            if _is_package(child_module):
-                with contextlib.suppress(ImportError):
-                    return importlib.import_module(f'.{attr}', child)
+        with contextlib.suppress(AttributeError):
+            return getattr(child, attr)
 
-            return _NO_ATTRIBUTE
+        return _NO_ATTRIBUTE
 
-    # Python 3.6 does not have module level __getattr__
-    # https://peps.python.org/pep-0562/
-    sys.modules[parent].__class__ = PassthroughModule
+    parent = sys.modules.get(parent, types.ModuleType(parent))
+    parent.__class__ = EnhancedModule
+    parent.__getattr__ = __getattr__
+    return parent