aboutsummaryrefslogtreecommitdiffstats
path: root/hypervideo_dl/compat
diff options
context:
space:
mode:
Diffstat (limited to 'hypervideo_dl/compat')
-rw-r--r--hypervideo_dl/compat/__init__.py78
-rw-r--r--hypervideo_dl/compat/_deprecated.py16
-rw-r--r--hypervideo_dl/compat/_legacy.py97
-rw-r--r--hypervideo_dl/compat/compat_utils.py70
-rw-r--r--hypervideo_dl/compat/functools.py26
-rw-r--r--hypervideo_dl/compat/imghdr.py16
-rw-r--r--hypervideo_dl/compat/shutil.py30
7 files changed, 333 insertions, 0 deletions
diff --git a/hypervideo_dl/compat/__init__.py b/hypervideo_dl/compat/__init__.py
new file mode 100644
index 0000000..2f2621b
--- /dev/null
+++ b/hypervideo_dl/compat/__init__.py
@@ -0,0 +1,78 @@
+import os
+import sys
+import warnings
+import xml.etree.ElementTree as etree
+
+from ._deprecated import * # noqa: F401, F403
+from .compat_utils import passthrough_module
+
+# XXX: Implement this the same way as other DeprecationWarnings without circular import
+passthrough_module(__name__, '._legacy', callback=lambda attr: warnings.warn(
+ DeprecationWarning(f'{__name__}.{attr} is deprecated'), stacklevel=3))
+
+
+# HTMLParseError has been deprecated in Python 3.3 and removed in
+# Python 3.5. Introducing dummy exception for Python >3.5 for compatible
+# and uniform cross-version exception handling
+class compat_HTMLParseError(ValueError):
+ pass
+
+
+class _TreeBuilder(etree.TreeBuilder):
+ def doctype(self, name, pubid, system):
+ pass
+
+
+def compat_etree_fromstring(text):
+ return etree.XML(text, parser=etree.XMLParser(target=_TreeBuilder()))
+
+
+compat_os_name = os._name if os.name == 'java' else os.name
+
+
+if compat_os_name == 'nt':
+ def compat_shlex_quote(s):
+ import re
+ return s if re.match(r'^[-_\w./]+$', s) else '"%s"' % s.replace('"', '\\"')
+else:
+ from shlex import quote as compat_shlex_quote # noqa: F401
+
+
+def compat_ord(c):
+ return c if isinstance(c, int) else ord(c)
+
+
+if compat_os_name == 'nt' and sys.version_info < (3, 8):
+ # os.path.realpath on Windows does not follow symbolic links
+ # prior to Python 3.8 (see https://bugs.python.org/issue9949)
+ def compat_realpath(path):
+ while os.path.islink(path):
+ path = os.path.abspath(os.readlink(path))
+ return os.path.realpath(path)
+else:
+ compat_realpath = os.path.realpath
+
+
+# Python 3.8+ does not honor %HOME% on windows, but this breaks compatibility with youtube-dl
+# See https://github.com/hypervideo/hypervideo/issues/792
+# https://docs.python.org/3/library/os.path.html#os.path.expanduser
+if compat_os_name in ('nt', 'ce'):
+ def compat_expanduser(path):
+ HOME = os.environ.get('HOME')
+ if not HOME:
+ return os.path.expanduser(path)
+ elif not path.startswith('~'):
+ return path
+ i = path.replace('\\', '/', 1).find('/') # ~user
+ if i < 0:
+ i = len(path)
+ userhome = os.path.join(os.path.dirname(HOME), path[1:i]) if i > 1 else HOME
+ return userhome + path[i:]
+else:
+ compat_expanduser = os.path.expanduser
+
+
+# NB: Add modules that are imported dynamically here so that PyInstaller can find them
+# See https://github.com/pyinstaller/pyinstaller-hooks-contrib/issues/438
+if False:
+ from . import _legacy # noqa: F401
diff --git a/hypervideo_dl/compat/_deprecated.py b/hypervideo_dl/compat/_deprecated.py
new file mode 100644
index 0000000..342f1f8
--- /dev/null
+++ b/hypervideo_dl/compat/_deprecated.py
@@ -0,0 +1,16 @@
+"""Deprecated - New code should avoid these"""
+
+import base64
+import urllib.error
+import urllib.parse
+
+compat_str = str
+
+compat_b64decode = base64.b64decode
+
+compat_HTTPError = urllib.error.HTTPError
+compat_urlparse = urllib.parse
+compat_parse_qs = urllib.parse.parse_qs
+compat_urllib_parse_unquote = urllib.parse.unquote
+compat_urllib_parse_urlencode = urllib.parse.urlencode
+compat_urllib_parse_urlparse = urllib.parse.urlparse
diff --git a/hypervideo_dl/compat/_legacy.py b/hypervideo_dl/compat/_legacy.py
new file mode 100644
index 0000000..d19333d
--- /dev/null
+++ b/hypervideo_dl/compat/_legacy.py
@@ -0,0 +1,97 @@
+""" Do not use! """
+
+import collections
+import ctypes
+import getpass
+import html.entities
+import html.parser
+import http.client
+import http.cookiejar
+import http.cookies
+import http.server
+import itertools
+import os
+import shlex
+import shutil
+import socket
+import struct
+import tokenize
+import urllib.error
+import urllib.parse
+import urllib.request
+import xml.etree.ElementTree as etree
+from subprocess import DEVNULL
+
+# isort: split
+import asyncio # noqa: F401
+import re # noqa: F401
+from asyncio import run as compat_asyncio_run # noqa: F401
+from re import Pattern as compat_Pattern # noqa: F401
+from re import match as compat_Match # noqa: F401
+
+from .compat_utils import passthrough_module
+from ..dependencies import Cryptodome_AES as compat_pycrypto_AES # noqa: F401
+from ..dependencies import brotli as compat_brotli # noqa: F401
+from ..dependencies import websockets as compat_websockets # noqa: F401
+
+passthrough_module(__name__, '...utils', ('WINDOWS_VT_MODE', 'windows_enable_vt_mode'))
+
+
+# compat_ctypes_WINFUNCTYPE = ctypes.WINFUNCTYPE
+# will not work since ctypes.WINFUNCTYPE does not exist in UNIX machines
+def compat_ctypes_WINFUNCTYPE(*args, **kwargs):
+ return ctypes.WINFUNCTYPE(*args, **kwargs)
+
+
+def compat_setenv(key, value, env=os.environ):
+ env[key] = value
+
+
+compat_basestring = str
+compat_casefold = str.casefold
+compat_chr = chr
+compat_collections_abc = collections.abc
+compat_cookiejar = http.cookiejar
+compat_cookiejar_Cookie = http.cookiejar.Cookie
+compat_cookies = http.cookies
+compat_cookies_SimpleCookie = http.cookies.SimpleCookie
+compat_etree_Element = etree.Element
+compat_etree_register_namespace = etree.register_namespace
+compat_filter = filter
+compat_get_terminal_size = shutil.get_terminal_size
+compat_getenv = os.getenv
+compat_getpass = getpass.getpass
+compat_html_entities = html.entities
+compat_html_entities_html5 = html.entities.html5
+compat_HTMLParser = html.parser.HTMLParser
+compat_http_client = http.client
+compat_http_server = http.server
+compat_input = input
+compat_integer_types = (int, )
+compat_itertools_count = itertools.count
+compat_kwargs = lambda kwargs: kwargs
+compat_map = map
+compat_numeric_types = (int, float, complex)
+compat_print = print
+compat_shlex_split = shlex.split
+compat_socket_create_connection = socket.create_connection
+compat_Struct = struct.Struct
+compat_struct_pack = struct.pack
+compat_struct_unpack = struct.unpack
+compat_subprocess_get_DEVNULL = lambda: DEVNULL
+compat_tokenize_tokenize = tokenize.tokenize
+compat_urllib_error = urllib.error
+compat_urllib_parse = urllib.parse
+compat_urllib_parse_quote = urllib.parse.quote
+compat_urllib_parse_quote_plus = urllib.parse.quote_plus
+compat_urllib_parse_unquote_plus = urllib.parse.unquote_plus
+compat_urllib_parse_unquote_to_bytes = urllib.parse.unquote_to_bytes
+compat_urllib_parse_urlunparse = urllib.parse.urlunparse
+compat_urllib_request = urllib.request
+compat_urllib_request_DataHandler = urllib.request.DataHandler
+compat_urllib_response = urllib.response
+compat_urlretrieve = urllib.request.urlretrieve
+compat_xml_parse_error = etree.ParseError
+compat_xpath = lambda xpath: xpath
+compat_zip = zip
+workaround_optparse_bug9161 = lambda: None
diff --git a/hypervideo_dl/compat/compat_utils.py b/hypervideo_dl/compat/compat_utils.py
new file mode 100644
index 0000000..1bf6566
--- /dev/null
+++ b/hypervideo_dl/compat/compat_utils.py
@@ -0,0 +1,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
diff --git a/hypervideo_dl/compat/functools.py b/hypervideo_dl/compat/functools.py
new file mode 100644
index 0000000..ec003ea
--- /dev/null
+++ b/hypervideo_dl/compat/functools.py
@@ -0,0 +1,26 @@
+# flake8: noqa: F405
+from functools import * # noqa: F403
+
+from .compat_utils import passthrough_module
+
+passthrough_module(__name__, 'functools')
+del passthrough_module
+
+try:
+ cache # >= 3.9
+except NameError:
+ cache = lru_cache(maxsize=None)
+
+try:
+ cached_property # >= 3.8
+except NameError:
+ class cached_property:
+ def __init__(self, func):
+ update_wrapper(self, func)
+ self.func = func
+
+ def __get__(self, instance, _):
+ if instance is None:
+ return self
+ setattr(instance, self.func.__name__, self.func(instance))
+ return getattr(instance, self.func.__name__)
diff --git a/hypervideo_dl/compat/imghdr.py b/hypervideo_dl/compat/imghdr.py
new file mode 100644
index 0000000..5d64ab0
--- /dev/null
+++ b/hypervideo_dl/compat/imghdr.py
@@ -0,0 +1,16 @@
+tests = {
+ 'webp': lambda h: h[0:4] == b'RIFF' and h[8:] == b'WEBP',
+ 'png': lambda h: h[:8] == b'\211PNG\r\n\032\n',
+ 'jpeg': lambda h: h[6:10] in (b'JFIF', b'Exif'),
+ 'gif': lambda h: h[:6] in (b'GIF87a', b'GIF89a'),
+}
+
+
+def what(file=None, h=None):
+ """Detect format of image (Currently supports jpeg, png, webp, gif only)
+ Ref: https://github.com/python/cpython/blob/3.10/Lib/imghdr.py
+ """
+ if h is None:
+ with open(file, 'rb') as f:
+ h = f.read(12)
+ return next((type_ for type_, test in tests.items() if test(h)), None)
diff --git a/hypervideo_dl/compat/shutil.py b/hypervideo_dl/compat/shutil.py
new file mode 100644
index 0000000..23239d5
--- /dev/null
+++ b/hypervideo_dl/compat/shutil.py
@@ -0,0 +1,30 @@
+# flake8: noqa: F405
+from shutil import * # noqa: F403
+
+from .compat_utils import passthrough_module
+
+passthrough_module(__name__, 'shutil')
+del passthrough_module
+
+
+import sys
+
+if sys.platform.startswith('freebsd'):
+ import errno
+ import os
+ import shutil
+
+ # Workaround for PermissionError when using restricted ACL mode on FreeBSD
+ def copy2(src, dst, *args, **kwargs):
+ if os.path.isdir(dst):
+ dst = os.path.join(dst, os.path.basename(src))
+ shutil.copyfile(src, dst, *args, **kwargs)
+ try:
+ shutil.copystat(src, dst, *args, **kwargs)
+ except PermissionError as e:
+ if e.errno != getattr(errno, 'EPERM', None):
+ raise
+ return dst
+
+ def move(*args, copy_function=copy2, **kwargs):
+ return shutil.move(*args, copy_function=copy_function, **kwargs)