[cleanup] Remove obsolete Python compatibility code (#17357)

Authored by: doe1080
This commit is contained in:
doe1080
2026-08-25 01:32:13 +00:00
committed by GitHub
parent 81ecd58b13
commit 88a9516584
17 changed files with 43 additions and 118 deletions
+1 -2
View File
@@ -66,8 +66,7 @@ def convert_code_blocks(readme):
def move_sections(readme): def move_sections(readme):
MOVE_TAG_TEMPLATE = '<!-- MANPAGE: MOVE "%s" SECTION HERE -->' MOVE_TAG_TEMPLATE = '<!-- MANPAGE: MOVE "%s" SECTION HERE -->'
sections = re.findall(r'(?m)^%s$' % ( sections = re.findall(r'(?m)^%s$' % (re.escape(MOVE_TAG_TEMPLATE) % '(.+)'), readme)
re.escape(MOVE_TAG_TEMPLATE).replace(r'\%', '%') % '(.+)'), readme)
for section_name in sections: for section_name in sections:
move_tag = MOVE_TAG_TEMPLATE % section_name move_tag = MOVE_TAG_TEMPLATE % section_name
+1 -37
View File
@@ -3,7 +3,6 @@ import hashlib
import json import json
import os.path import os.path
import re import re
import ssl
import sys import sys
import types import types
@@ -318,36 +317,6 @@ def expect_info_dict(self, got_dict, expected_dict):
'Missing keys in test definition: {}'.format(', '.join(sorted(missing_keys)))) 'Missing keys in test definition: {}'.format(', '.join(sorted(missing_keys))))
def assertRegexpMatches(self, text, regexp, msg=None):
if hasattr(self, 'assertRegexp'):
return self.assertRegexp(text, regexp, msg)
else:
m = re.match(regexp, text)
if not m:
note = f'Regexp didn\'t match: {regexp!r} not found'
if len(text) < 1000:
note += f' in {text!r}'
if msg is None:
msg = note
else:
msg = note + ', ' + msg
self.assertTrue(m, msg)
def assertGreaterEqual(self, got, expected, msg=None):
if not (got >= expected):
if msg is None:
msg = f'{got!r} not greater than or equal to {expected!r}'
self.assertTrue(got >= expected, msg)
def assertLessEqual(self, got, expected, msg=None):
if not (got <= expected):
if msg is None:
msg = f'{got!r} not less than or equal to {expected!r}'
self.assertTrue(got <= expected, msg)
def assertEqual(self, got, expected, msg=None): def assertEqual(self, got, expected, msg=None):
if got != expected: if got != expected:
if msg is None: if msg is None:
@@ -366,12 +335,7 @@ def expect_warnings(ydl, warnings_re):
def http_server_port(httpd): def http_server_port(httpd):
if os.name == 'java' and isinstance(httpd.socket, ssl.SSLSocket): return httpd.server_address[1]
# In Jython SSLSocket is not a subclass of socket.socket
sock = httpd.socket.sock
else:
sock = httpd.socket
return sock.getsockname()[1]
def verify_address_availability(address): def verify_address_availability(address):
+3 -3
View File
@@ -15,7 +15,7 @@ import contextlib
import copy import copy
import json import json
from test.helper import FakeYDL, assertRegexpMatches, try_rm from test.helper import FakeYDL, try_rm
from yt_dlp import YoutubeDL from yt_dlp import YoutubeDL
from yt_dlp.extractor.common import InfoExtractor from yt_dlp.extractor.common import InfoExtractor
from yt_dlp.postprocessor.common import PostProcessor from yt_dlp.postprocessor.common import PostProcessor
@@ -860,10 +860,10 @@ class TestYoutubeDL(unittest.TestCase):
def test_format_note(self): def test_format_note(self):
ydl = YoutubeDL() ydl = YoutubeDL()
self.assertEqual(ydl._format_note({}), '') self.assertEqual(ydl._format_note({}), '')
assertRegexpMatches(self, ydl._format_note({ self.assertRegex(ydl._format_note({
'vbr': 10, 'vbr': 10,
}), r'^\s*10k$') }), r'^\s*10k$')
assertRegexpMatches(self, ydl._format_note({ self.assertRegex(ydl._format_note({
'fps': 30, 'fps': 30,
}), r'^30fps$') }), r'^30fps$')
+6 -8
View File
@@ -13,8 +13,6 @@ import hashlib
import json import json
from test.helper import ( from test.helper import (
assertGreaterEqual,
assertLessEqual,
expect_info_dict, expect_info_dict,
expect_warnings, expect_warnings,
get_params, get_params,
@@ -201,8 +199,8 @@ def generator(test_case, tname):
num_entries = len(res_dict.get('entries', [])) num_entries = len(res_dict.get('entries', []))
if 'playlist_mincount' in test_case: if 'playlist_mincount' in test_case:
mincount = test_case['playlist_mincount'] mincount = test_case['playlist_mincount']
assertGreaterEqual( self.assertGreaterEqual(
self, num_entries, mincount, num_entries, mincount,
f'Expected at least {mincount} entries in playlist {test_url}, but got only {num_entries}') f'Expected at least {mincount} entries in playlist {test_url}, but got only {num_entries}')
if 'playlist_count' in test_case: if 'playlist_count' in test_case:
count = test_case['playlist_count'] count = test_case['playlist_count']
@@ -212,8 +210,8 @@ def generator(test_case, tname):
f'Expected exactly {count} entries in playlist {test_url}, but got {got}') f'Expected exactly {count} entries in playlist {test_url}, but got {got}')
if 'playlist_maxcount' in test_case: if 'playlist_maxcount' in test_case:
maxcount = test_case['playlist_maxcount'] maxcount = test_case['playlist_maxcount']
assertLessEqual( self.assertLessEqual(
self, num_entries, maxcount, num_entries, maxcount,
f'Expected at most {maxcount} entries in playlist {test_url}, but got more') f'Expected at most {maxcount} entries in playlist {test_url}, but got more')
if 'playlist_duration_sum' in test_case: if 'playlist_duration_sum' in test_case:
got_duration = sum(e['duration'] for e in res_dict['entries']) got_duration = sum(e['duration'] for e in res_dict['entries'])
@@ -241,8 +239,8 @@ def generator(test_case, tname):
if params.get('test'): if params.get('test'):
expected_minsize = max(expected_minsize, 10000) expected_minsize = max(expected_minsize, 10000)
got_fsize = os.path.getsize(tc_filename) got_fsize = os.path.getsize(tc_filename)
assertGreaterEqual( self.assertGreaterEqual(
self, got_fsize, expected_minsize, got_fsize, expected_minsize,
f'Expected {tc_filename} to be at least {format_bytes(expected_minsize)}, ' f'Expected {tc_filename} to be at least {format_bytes(expected_minsize)}, '
f'but it\'s only {format_bytes(got_fsize)} ') f'but it\'s only {format_bytes(got_fsize)} ')
if 'md5' in tc: if 'md5' in tc:
+5 -18
View File
@@ -984,28 +984,15 @@ class TestUrllibRequestHandler(TestRequestHandlerBase):
): ):
validate_and_send(rh, Request(f'https://127.0.0.1:{self.https_port}/headers')) validate_and_send(rh, Request(f'https://127.0.0.1:{self.https_port}/headers'))
@pytest.mark.parametrize('req,match,version_check', [ @pytest.mark.parametrize('req,match', [
# https://github.com/python/cpython/blob/987b712b4aeeece336eed24fcc87a950a756c3e2/Lib/http/client.py#L1256 # https://github.com/python/cpython/blob/987b712b4aeeece336eed24fcc87a950a756c3e2/Lib/http/client.py#L1256
# bpo-39603: Check implemented in 3.7.9+, 3.8.5+ (Request('http://127.0.0.1', method='GET\n'), 'method can\'t contain control characters'),
(
Request('http://127.0.0.1', method='GET\n'),
'method can\'t contain control characters',
lambda v: v < (3, 7, 9) or (3, 8, 0) <= v < (3, 8, 5),
),
# https://github.com/python/cpython/blob/987b712b4aeeece336eed24fcc87a950a756c3e2/Lib/http/client.py#L1265 # https://github.com/python/cpython/blob/987b712b4aeeece336eed24fcc87a950a756c3e2/Lib/http/client.py#L1265
# bpo-38576: Check implemented in 3.7.8+, 3.8.3+ (Request('http://127.0.0. 1', method='GET'), 'URL can\'t contain control characters'),
(
Request('http://127.0.0. 1', method='GET'),
'URL can\'t contain control characters',
lambda v: v < (3, 7, 8) or (3, 8, 0) <= v < (3, 8, 3),
),
# https://github.com/python/cpython/blob/987b712b4aeeece336eed24fcc87a950a756c3e2/Lib/http/client.py#L1288C31-L1288C50 # https://github.com/python/cpython/blob/987b712b4aeeece336eed24fcc87a950a756c3e2/Lib/http/client.py#L1288C31-L1288C50
(Request('http://127.0.0.1', headers={'foo\n': 'bar'}), 'Invalid header name', None), (Request('http://127.0.0.1', headers={'foo\n': 'bar'}), 'Invalid header name'),
]) ])
def test_httplib_validation_errors(self, handler, req, match, version_check): def test_httplib_validation_errors(self, handler, req, match):
if version_check and version_check(sys.version_info):
pytest.skip(f'Python {sys.version} version does not have the required validation for this test.')
with handler() as rh: with handler() as rh:
with pytest.raises(RequestError, match=match) as exc_info: with pytest.raises(RequestError, match=match) as exc_info:
validate_and_send(rh, req) validate_and_send(rh, req)
+2 -9
View File
@@ -1347,15 +1347,8 @@ class TestUtil(unittest.TestCase):
self.assertEqual(extract_attributes('<e _:funny-name1=1>'), {'_:funny-name1': '1'}) self.assertEqual(extract_attributes('<e _:funny-name1=1>'), {'_:funny-name1': '1'})
self.assertEqual(extract_attributes('<e x="Fáilte 世界 \U0001f600">'), {'x': 'Fáilte 世界 \U0001f600'}) self.assertEqual(extract_attributes('<e x="Fáilte 世界 \U0001f600">'), {'x': 'Fáilte 世界 \U0001f600'})
self.assertEqual(extract_attributes('<e x="décompose&#769;">'), {'x': 'décompose\u0301'}) self.assertEqual(extract_attributes('<e x="décompose&#769;">'), {'x': 'décompose\u0301'})
# "Narrow" Python builds don't support unicode code points outside BMP. self.assertEqual(extract_attributes('<e x="Smile &#128512;!">'), {'x': 'Smile \U0001f600!'})
try: # Malformed HTML should not break attribute extraction
chr(0x10000)
supports_outside_bmp = True
except ValueError:
supports_outside_bmp = False
if supports_outside_bmp:
self.assertEqual(extract_attributes('<e x="Smile &#128512;!">'), {'x': 'Smile \U0001f600!'})
# Malformed HTML should not break attributes extraction on older Python
self.assertEqual(extract_attributes('<mal"formed/>'), {}) self.assertEqual(extract_attributes('<mal"formed/>'), {})
def test_clean_html(self): def test_clean_html(self):
+1 -2
View File
@@ -2398,8 +2398,7 @@ class YoutubeDL:
selectors = [] selectors = []
current_selector = None current_selector = None
for type_, string_, start, _, _ in tokens: for type_, string_, start, _, _ in tokens:
# ENCODING is only defined in Python 3.x if type_ == tokenize.ENCODING:
if type_ == getattr(tokenize, 'ENCODING', None):
continue continue
elif type_ in [tokenize.NAME, tokenize.NUMBER]: elif type_ in [tokenize.NAME, tokenize.NUMBER]:
current_selector = FormatSelector(SINGLE, string_, []) current_selector = FormatSelector(SINGLE, string_, [])
+1 -1
View File
@@ -293,7 +293,7 @@ def aes_decrypt_text(data, password, key_size_bytes):
- Mode of operation is 'counter' - Mode of operation is 'counter'
@param {str} data Base64 encoded string @param {str} data Base64 encoded string
@param {str,unicode} password Password (will be encoded with utf-8) @param {str} password Password (will be encoded with UTF-8)
@param {int} key_size_bytes Possible values: 16 for 128-Bit, 24 for 192-Bit or 32 for 256-Bit @param {int} key_size_bytes Possible values: 16 for 128-Bit, 24 for 192-Bit or 32 for 256-Bit
@returns {str} Decrypted data @returns {str} Decrypted data
""" """
+2 -3
View File
@@ -8,9 +8,8 @@ passthrough_module(__name__, '._deprecated')
del passthrough_module del passthrough_module
# HTMLParseError has been deprecated in Python 3.3 and removed in # HTMLParseError was deprecated in Python 3.3 and removed in Python 3.5.
# Python 3.5. Introducing dummy exception for Python >3.5 for compatible # Keep a replacement for API compatibility and uniform exception handling.
# and uniform cross-version exception handling
class compat_HTMLParseError(ValueError): class compat_HTMLParseError(ValueError):
pass pass
+1 -1
View File
@@ -154,7 +154,7 @@ def write_piff_header(stream, params):
sample_entry_payload += u16.pack(0x18) # depth sample_entry_payload += u16.pack(0x18) # depth
sample_entry_payload += s16.pack(-1) # pre defined sample_entry_payload += s16.pack(-1) # pre defined
codec_private_data = binascii.unhexlify(params['codec_private_data'].encode()) codec_private_data = binascii.unhexlify(params['codec_private_data'])
if fourcc in ('H264', 'AVC1'): if fourcc in ('H264', 'AVC1'):
sps, pps = codec_private_data.split(u32.pack(1))[1:] sps, pps = codec_private_data.split(u32.pack(1))[1:]
avcc_payload = u8.pack(1) # configuration version avcc_payload = u8.pack(1) # configuration version
+6 -7
View File
@@ -432,29 +432,28 @@ class InfoExtractor:
chapter: Name or title of the chapter the video belongs to. chapter: Name or title of the chapter the video belongs to.
chapter_number: Number of the chapter the video belongs to, as an integer. chapter_number: Number of the chapter the video belongs to, as an integer.
chapter_id: Id of the chapter the video belongs to, as a unicode string. chapter_id: Id of the chapter the video belongs to.
The following fields should only be used when the video is an episode of some The following fields should only be used when the video is an episode of some
series, programme or podcast: series, programme or podcast:
series: Title of the series or programme the video episode belongs to. series: Title of the series or programme the video episode belongs to.
series_id: Id of the series or programme the video episode belongs to, as a unicode string. series_id: Id of the series or programme the video episode belongs to.
season: Title of the season the video episode belongs to. season: Title of the season the video episode belongs to.
season_number: Number of the season the video episode belongs to, as an integer. season_number: Number of the season the video episode belongs to, as an integer.
season_id: Id of the season the video episode belongs to, as a unicode string. season_id: Id of the season the video episode belongs to.
episode: Title of the video episode. Unlike mandatory video title field, episode: Title of the video episode. Unlike mandatory video title field,
this field should denote the exact title of the video episode this field should denote the exact title of the video episode
without any kind of decoration. without any kind of decoration.
episode_number: Number of the video episode within a season, as an integer. episode_number: Number of the video episode within a season, as an integer.
episode_id: Id of the video episode, as a unicode string. episode_id: Id of the video episode.
The following fields should only be used when the media is a track or a part of The following fields should only be used when the media is a track or a part of
a music album: a music album:
track: Title of the track. track: Title of the track.
track_number: Number of the track within an album or a disc, as an integer. track_number: Number of the track within an album or a disc, as an integer.
track_id: Id of the track (useful in case of custom indexing, e.g. 6.iii), track_id: Id of the track (useful for custom indexing, e.g. 6.iii).
as a unicode string.
artists: List of artists of the track. artists: List of artists of the track.
composers: List of composers of the piece. composers: List of composers of the piece.
genres: List of genres of the track. genres: List of genres of the track.
@@ -487,7 +486,7 @@ class InfoExtractor:
creator: Use "creators" instead. creator: Use "creators" instead.
The creator of the video. The creator of the video.
Unless mentioned otherwise, the fields should be Unicode strings. Unless mentioned otherwise, the fields should be strings.
Unless mentioned otherwise, None is equivalent to absence of information. Unless mentioned otherwise, None is equivalent to absence of information.
+2 -2
View File
@@ -114,7 +114,7 @@ class ITVIE(InfoExtractor):
# See: https://github.com/yt-dlp/yt-dlp/issues/986 # See: https://github.com/yt-dlp/yt-dlp/issues/986
platform_tag_subs, featureset_subs = next( platform_tag_subs, featureset_subs = next(
((platform_tag, featureset) ((platform_tag, featureset)
for platform_tag, featuresets in reversed(list(variants.items())) for featureset in featuresets for platform_tag, featuresets in reversed(variants.items()) for featureset in featuresets
if try_get(featureset, lambda x: x[2]) == 'outband-webvtt'), if try_get(featureset, lambda x: x[2]) == 'outband-webvtt'),
(None, None)) (None, None))
@@ -143,7 +143,7 @@ class ITVIE(InfoExtractor):
# See: https://github.com/yt-dlp/yt-dlp/issues/986 # See: https://github.com/yt-dlp/yt-dlp/issues/986
platform_tag_video, featureset_video = next( platform_tag_video, featureset_video = next(
((platform_tag, featureset) ((platform_tag, featureset)
for platform_tag, featuresets in reversed(list(variants.items())) for featureset in featuresets for platform_tag, featuresets in reversed(variants.items()) for featureset in featuresets
if set(try_get(featureset, lambda x: x[:2]) or []) == {'aes', 'hls'}), if set(try_get(featureset, lambda x: x[:2]) or []) == {'aes', 'hls'}),
(None, None)) (None, None))
if not platform_tag_video or not featureset_video: if not platform_tag_video or not featureset_video:
+1 -1
View File
@@ -76,7 +76,7 @@ class YoutubeIEContentProviderLogger(IEContentProviderLogger):
if self.log_level <= self.LogLevel.ERROR: if self.log_level <= self.LogLevel.ERROR:
self.__ie._downloader.report_error( self.__ie._downloader.report_error(
self._format_msg(message), is_error=False, self._format_msg(message), is_error=False,
tb=''.join(traceback.format_exception(None, cause, cause.__traceback__)) if cause else None) tb=''.join(traceback.format_exception(cause)) if cause else None)
class PoTokenCache: class PoTokenCache:
+1 -3
View File
@@ -108,9 +108,7 @@ def make_ssl_context(
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.check_hostname = verify context.check_hostname = verify
context.verify_mode = ssl.CERT_REQUIRED if verify else ssl.CERT_NONE context.verify_mode = ssl.CERT_REQUIRED if verify else ssl.CERT_NONE
# OpenSSL 1.1.1+ Python 3.8+ keylog file context.keylog_filename = os.environ.get('SSLKEYLOGFILE') or None
if hasattr(context, 'keylog_filename'):
context.keylog_filename = os.environ.get('SSLKEYLOGFILE') or None
# Some servers may reject requests if ALPN extension is not sent. See: # Some servers may reject requests if ALPN extension is not sent. See:
# https://github.com/python/cpython/issues/85140 # https://github.com/python/cpython/issues/85140
+1 -1
View File
@@ -123,7 +123,7 @@ class RequestsResponseAdapter(Response):
# Work around issue with `.read(amt)` then `.read()` # Work around issue with `.read(amt)` then `.read()`
# See: https://github.com/urllib3/urllib3/issues/3636 # See: https://github.com/urllib3/urllib3/issues/3636
if amt is None: if amt is None:
# compat: py3.9: Python 3.9 preallocates the whole read buffer, read in chunks # Read in chunks to avoid preallocating a large buffer
read_chunk = functools.partial(self.fp.read, 1 << 20, decode_content=True) read_chunk = functools.partial(self.fp.read, 1 << 20, decode_content=True)
return b''.join(iter(read_chunk, b'')) return b''.join(iter(read_chunk, b''))
# Interact with urllib3 response directly. # Interact with urllib3 response directly.
+1 -5
View File
@@ -296,13 +296,9 @@ class UrllibResponseAdapter(Response):
""" """
def __init__(self, res: http.client.HTTPResponse | urllib.response.addinfourl): def __init__(self, res: http.client.HTTPResponse | urllib.response.addinfourl):
# addinfourl: In Python 3.9+, .status was introduced and .getcode() was deprecated [1]
# HTTPResponse: .getcode() was deprecated, .status always existed [2]
# 1. https://docs.python.org/3/library/urllib.request.html#urllib.response.addinfourl.getcode
# 2. https://docs.python.org/3.10/library/http.client.html#http.client.HTTPResponse.status
super().__init__( super().__init__(
fp=res, headers=res.headers, url=res.url, fp=res, headers=res.headers, url=res.url,
status=getattr(res, 'status', None) or res.getcode(), reason=getattr(res, 'reason', None)) status=res.status, reason=getattr(res, 'reason', None))
def read(self, amt=None): def read(self, amt=None):
if self.closed: if self.closed:
+8 -15
View File
@@ -238,11 +238,9 @@ def find_xpath_attr(node, xpath, key, val=None):
expr = xpath + (f'[@{key}]' if val is None else f"[@{key}='{val}']") expr = xpath + (f'[@{key}]' if val is None else f"[@{key}='{val}']")
return node.find(expr) return node.find(expr)
# On python2.6 the xml.etree.ElementTree.Element methods don't support
# the namespace parameter
def xpath_with_ns(path, ns_map): def xpath_with_ns(path, ns_map):
"""Expand namespace-prefixed names to Clark notation."""
components = [c.split(':') for c in path.split('/')] components = [c.split(':') for c in path.split('/')]
replaced = [] replaced = []
for c in components: for c in components:
@@ -876,7 +874,7 @@ class Popen(subprocess.Popen):
self.__text_mode = kwargs.get('encoding') or kwargs.get('errors') or text or kwargs.get('universal_newlines') self.__text_mode = kwargs.get('encoding') or kwargs.get('errors') or text or kwargs.get('universal_newlines')
if text is True: if text is True:
kwargs['universal_newlines'] = True # For 3.6 compatibility kwargs['text'] = True
kwargs.setdefault('encoding', 'utf-8') kwargs.setdefault('encoding', 'utf-8')
kwargs.setdefault('errors', 'replace') kwargs.setdefault('errors', 'replace')
@@ -1012,7 +1010,7 @@ class ExtractorError(YoutubeDLError):
def format_traceback(self): def format_traceback(self):
return join_nonempty( return join_nonempty(
self.traceback and ''.join(traceback.format_tb(self.traceback)), self.traceback and ''.join(traceback.format_tb(self.traceback)),
self.cause and ''.join(traceback.format_exception(None, self.cause, self.cause.__traceback__)[1:]), self.cause and ''.join(traceback.format_exception(self.cause)[1:]),
delim='\n') or None delim='\n') or None
def __setattr__(self, name, value): def __setattr__(self, name, value):
@@ -1960,11 +1958,7 @@ def setproctitle(title):
libc = ctypes.cdll.LoadLibrary('libc.so.6') libc = ctypes.cdll.LoadLibrary('libc.so.6')
except OSError: except OSError:
return return
except TypeError:
# LoadLibrary in Windows Python 2.7.13 only expects
# a bytestring, but since unicode_literals turns
# every string into a unicode string, it fails.
return
title_bytes = title.encode() title_bytes = title.encode()
buf = ctypes.create_string_buffer(len(title_bytes)) buf = ctypes.create_string_buffer(len(title_bytes))
buf.value = title_bytes buf.value = title_bytes
@@ -2655,11 +2649,10 @@ def multipart_encode(data, boundary=None):
Encode a dict to RFC 7578-compliant form-data Encode a dict to RFC 7578-compliant form-data
data: data:
A dict where keys and values can be either Unicode or bytes-like A dict where keys and values can be either str or bytes-like objects.
objects.
boundary: boundary:
If specified a Unicode object, it's used as the boundary. Otherwise An ASCII string to use as the boundary. If omitted, a random boundary
a random boundary is generated. is generated.
Reference: https://tools.ietf.org/html/rfc7578 Reference: https://tools.ietf.org/html/rfc7578
""" """
@@ -3422,7 +3415,7 @@ def ass_subtitles_timecode(seconds):
def dfxp2srt(dfxp_data): def dfxp2srt(dfxp_data):
""" """
@param dfxp_data A bytes-like object containing DFXP data @param dfxp_data A bytes-like object containing DFXP data
@returns A unicode object containing converted SRT data @returns A string containing the converted SRT data
""" """
LEGACY_NAMESPACES = ( LEGACY_NAMESPACES = (
(b'http://www.w3.org/ns/ttml', [ (b'http://www.w3.org/ns/ttml', [