compat_utils.py 2.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. import collections
  2. import contextlib
  3. import functools
  4. import importlib
  5. import sys
  6. import types
  7. _NO_ATTRIBUTE = object()
  8. _Package = collections.namedtuple('Package', ('name', 'version'))
  9. def get_package_info(module):
  10. return _Package(
  11. name=getattr(module, '_yt_dlp__identifier', module.__name__),
  12. version=str(next(filter(None, (
  13. getattr(module, attr, None)
  14. for attr in ('_yt_dlp__version', '__version__', 'version_string', 'version')
  15. )), None)))
  16. def _is_package(module):
  17. return '__path__' in vars(module)
  18. def _is_dunder(name):
  19. return name.startswith('__') and name.endswith('__')
  20. class EnhancedModule(types.ModuleType):
  21. def __bool__(self):
  22. return vars(self).get('__bool__', lambda: True)()
  23. def __getattribute__(self, attr):
  24. try:
  25. ret = super().__getattribute__(attr)
  26. except AttributeError:
  27. if _is_dunder(attr):
  28. raise
  29. getter = getattr(self, '__getattr__', None)
  30. if not getter:
  31. raise
  32. ret = getter(attr)
  33. return ret.fget() if isinstance(ret, property) else ret
  34. def passthrough_module(parent, child, allowed_attributes=(..., ), *, callback=lambda _: None):
  35. """Passthrough parent module into a child module, creating the parent if necessary"""
  36. def __getattr__(attr):
  37. if _is_package(parent):
  38. with contextlib.suppress(ModuleNotFoundError):
  39. return importlib.import_module(f'.{attr}', parent.__name__)
  40. ret = from_child(attr)
  41. if ret is _NO_ATTRIBUTE:
  42. raise AttributeError(f'module {parent.__name__} has no attribute {attr}')
  43. callback(attr)
  44. return ret
  45. @functools.lru_cache(maxsize=None)
  46. def from_child(attr):
  47. nonlocal child
  48. if attr not in allowed_attributes:
  49. if ... not in allowed_attributes or _is_dunder(attr):
  50. return _NO_ATTRIBUTE
  51. if isinstance(child, str):
  52. child = importlib.import_module(child, parent.__name__)
  53. if _is_package(child):
  54. with contextlib.suppress(ImportError):
  55. return passthrough_module(f'{parent.__name__}.{attr}',
  56. importlib.import_module(f'.{attr}', child.__name__))
  57. with contextlib.suppress(AttributeError):
  58. return getattr(child, attr)
  59. return _NO_ATTRIBUTE
  60. parent = sys.modules.get(parent, types.ModuleType(parent))
  61. parent.__class__ = EnhancedModule
  62. parent.__getattr__ = __getattr__
  63. return parent