aboutsummaryrefslogtreecommitdiffstats
path: root/hypervideo_dl/compat
diff options
context:
space:
mode:
Diffstat (limited to 'hypervideo_dl/compat')
-rw-r--r--hypervideo_dl/compat/__init__.py19
-rw-r--r--hypervideo_dl/compat/_deprecated.py9
-rw-r--r--hypervideo_dl/compat/_legacy.py37
-rw-r--r--hypervideo_dl/compat/compat_utils.py111
-rw-r--r--hypervideo_dl/compat/types.py13
-rw-r--r--hypervideo_dl/compat/urllib/__init__.py10
-rw-r--r--hypervideo_dl/compat/urllib/request.py40
7 files changed, 167 insertions, 72 deletions
diff --git a/hypervideo_dl/compat/__init__.py b/hypervideo_dl/compat/__init__.py
index 2f2621b..445178d 100644
--- a/hypervideo_dl/compat/__init__.py
+++ b/hypervideo_dl/compat/__init__.py
@@ -1,14 +1,11 @@
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))
+passthrough_module(__name__, '._deprecated')
+del passthrough_module
# HTMLParseError has been deprecated in Python 3.3 and removed in
@@ -72,7 +69,11 @@ 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
+def urllib_req_to_req(urllib_request):
+ """Convert urllib Request to a networking Request"""
+ from ..networking import Request
+ from ..utils.networking import HTTPHeaderDict
+ return Request(
+ urllib_request.get_full_url(), data=urllib_request.data, method=urllib_request.get_method(),
+ headers=HTTPHeaderDict(urllib_request.headers, urllib_request.unredirected_hdrs),
+ extensions={'timeout': urllib_request.timeout} if hasattr(urllib_request, 'timeout') else None)
diff --git a/hypervideo_dl/compat/_deprecated.py b/hypervideo_dl/compat/_deprecated.py
index 342f1f8..607bae9 100644
--- a/hypervideo_dl/compat/_deprecated.py
+++ b/hypervideo_dl/compat/_deprecated.py
@@ -1,4 +1,12 @@
"""Deprecated - New code should avoid these"""
+import warnings
+
+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=6))
+del passthrough_module
import base64
import urllib.error
@@ -8,7 +16,6 @@ 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
diff --git a/hypervideo_dl/compat/_legacy.py b/hypervideo_dl/compat/_legacy.py
index d19333d..90ccf0f 100644
--- a/hypervideo_dl/compat/_legacy.py
+++ b/hypervideo_dl/compat/_legacy.py
@@ -1,5 +1,6 @@
""" Do not use! """
+import base64
import collections
import ctypes
import getpass
@@ -15,12 +16,12 @@ import shlex
import shutil
import socket
import struct
+import subprocess
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
@@ -29,10 +30,11 @@ 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 . import compat_expanduser, compat_HTMLParseError, compat_realpath
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
+from ..dependencies.Cryptodome import AES as compat_pycrypto_AES # noqa: F401
passthrough_module(__name__, '...utils', ('WINDOWS_VT_MODE', 'windows_enable_vt_mode'))
@@ -47,41 +49,48 @@ def compat_setenv(key, value, env=os.environ):
env[key] = value
+compat_base64_b64decode = base64.b64decode
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_cookiejar = compat_http_cookiejar = http.cookiejar
+compat_cookiejar_Cookie = compat_http_cookiejar_Cookie = http.cookiejar.Cookie
+compat_cookies = compat_http_cookies = http.cookies
+compat_cookies_SimpleCookie = compat_http_cookies_SimpleCookie = http.cookies.SimpleCookie
+compat_etree_Element = compat_xml_etree_ElementTree_Element = etree.Element
+compat_etree_register_namespace = compat_xml_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_getpass = compat_getpass_getpass = getpass.getpass
compat_html_entities = html.entities
compat_html_entities_html5 = html.entities.html5
-compat_HTMLParser = html.parser.HTMLParser
+compat_html_parser_HTMLParseError = compat_HTMLParseError
+compat_HTMLParser = compat_html_parser_HTMLParser = html.parser.HTMLParser
compat_http_client = http.client
compat_http_server = http.server
+compat_HTTPError = urllib.error.HTTPError
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_os_path_expanduser = compat_expanduser
+compat_os_path_realpath = compat_realpath
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_subprocess_get_DEVNULL = lambda: subprocess.DEVNULL
compat_tokenize_tokenize = tokenize.tokenize
compat_urllib_error = urllib.error
+compat_urllib_HTTPError = urllib.error.HTTPError
compat_urllib_parse = urllib.parse
+compat_urllib_parse_parse_qs = urllib.parse.parse_qs
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
@@ -90,8 +99,10 @@ 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_urlretrieve = compat_urllib_request_urlretrieve = urllib.request.urlretrieve
+compat_xml_parse_error = compat_xml_etree_ElementTree_ParseError = etree.ParseError
compat_xpath = lambda xpath: xpath
compat_zip = zip
workaround_optparse_bug9161 = lambda: None
+
+legacy = []
diff --git a/hypervideo_dl/compat/compat_utils.py b/hypervideo_dl/compat/compat_utils.py
index 1bf6566..8e94125 100644
--- a/hypervideo_dl/compat/compat_utils.py
+++ b/hypervideo_dl/compat/compat_utils.py
@@ -1,5 +1,6 @@
import collections
import contextlib
+import functools
import importlib
import sys
import types
@@ -10,61 +11,73 @@ _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))
+ return _Package(
+ name=getattr(module, '_hypervideo_dl__identifier', module.__name__),
+ version=str(next(filter(None, (
+ getattr(module, attr, None)
+ for attr in ('__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
diff --git a/hypervideo_dl/compat/types.py b/hypervideo_dl/compat/types.py
new file mode 100644
index 0000000..4aa3b0e
--- /dev/null
+++ b/hypervideo_dl/compat/types.py
@@ -0,0 +1,13 @@
+# flake8: noqa: F405
+from types import * # noqa: F403
+
+from .compat_utils import passthrough_module
+
+passthrough_module(__name__, 'types')
+del passthrough_module
+
+try:
+ # NB: pypy has builtin NoneType, so checking NameError won't work
+ from types import NoneType # >= 3.10
+except ImportError:
+ NoneType = type(None)
diff --git a/hypervideo_dl/compat/urllib/__init__.py b/hypervideo_dl/compat/urllib/__init__.py
new file mode 100644
index 0000000..b27cc61
--- /dev/null
+++ b/hypervideo_dl/compat/urllib/__init__.py
@@ -0,0 +1,10 @@
+# flake8: noqa: F405
+from urllib import * # noqa: F403
+
+del request
+from . import request # noqa: F401
+
+from ..compat_utils import passthrough_module
+
+passthrough_module(__name__, 'urllib')
+del passthrough_module
diff --git a/hypervideo_dl/compat/urllib/request.py b/hypervideo_dl/compat/urllib/request.py
new file mode 100644
index 0000000..ff63b2f
--- /dev/null
+++ b/hypervideo_dl/compat/urllib/request.py
@@ -0,0 +1,40 @@
+# flake8: noqa: F405
+from urllib.request import * # noqa: F403
+
+from ..compat_utils import passthrough_module
+
+passthrough_module(__name__, 'urllib.request')
+del passthrough_module
+
+
+from .. import compat_os_name
+
+if compat_os_name == 'nt':
+ # On older python versions, proxies are extracted from Windows registry erroneously. [1]
+ # If the https proxy in the registry does not have a scheme, urllib will incorrectly add https:// to it. [2]
+ # It is unlikely that the user has actually set it to be https, so we should be fine to safely downgrade
+ # it to http on these older python versions to avoid issues
+ # This also applies for ftp proxy type, as ftp:// proxy scheme is not supported.
+ # 1: https://github.com/python/cpython/issues/86793
+ # 2: https://github.com/python/cpython/blob/51f1ae5ceb0673316c4e4b0175384e892e33cc6e/Lib/urllib/request.py#L2683-L2698
+ import sys
+ from urllib.request import getproxies_environment, getproxies_registry
+
+ def getproxies_registry_patched():
+ proxies = getproxies_registry()
+ if (
+ sys.version_info >= (3, 10, 5) # https://docs.python.org/3.10/whatsnew/changelog.html#python-3-10-5-final
+ or (3, 9, 13) <= sys.version_info < (3, 10) # https://docs.python.org/3.9/whatsnew/changelog.html#python-3-9-13-final
+ ):
+ return proxies
+
+ for scheme in ('https', 'ftp'):
+ if scheme in proxies and proxies[scheme].startswith(f'{scheme}://'):
+ proxies[scheme] = 'http' + proxies[scheme][len(scheme):]
+
+ return proxies
+
+ def getproxies():
+ return getproxies_environment() or getproxies_registry_patched()
+
+del compat_os_name