aboutsummaryrefslogtreecommitdiffstats
path: root/hypervideo_dl/compat/compat_utils.py
blob: 1bf6566538dd70c40ac3f05375ea58974de4884f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import collections
import contextlib
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, '_hypervideo_dl__identifier', parent), str(version))


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 _NO_ATTRIBUTE

            nonlocal child_module
            child_module = child_module or importlib.import_module(child, parent)

            with contextlib.suppress(AttributeError):
                return getattr(child_module, attr)

            if _is_package(child_module):
                with contextlib.suppress(ImportError):
                    return importlib.import_module(f'.{attr}', child)

            return _NO_ATTRIBUTE

    # Python 3.6 does not have module level __getattr__
    # https://peps.python.org/pep-0562/
    sys.modules[parent].__class__ = PassthroughModule