diff --git a/devscripts/lazy_load_template.py b/devscripts/lazy_load_template.py index 626b85d62..c8815e01b 100644 --- a/devscripts/lazy_load_template.py +++ b/devscripts/lazy_load_template.py @@ -10,7 +10,7 @@ from ..utils import ( ) # These bloat the lazy_extractors, so allow them to passthrough silently -ALLOWED_CLASSMETHODS = {'get_testcases', 'extract_from_webpage'} +ALLOWED_CLASSMETHODS = {'extract_from_webpage', 'get_testcases', 'get_webpage_testcases'} _WARNED = False diff --git a/devscripts/make_lazy_extractors.py b/devscripts/make_lazy_extractors.py index 2d4530eb9..c502bdf89 100644 --- a/devscripts/make_lazy_extractors.py +++ b/devscripts/make_lazy_extractors.py @@ -14,10 +14,17 @@ from devscripts.utils import get_filename_args, read_file, write_file NO_ATTR = object() STATIC_CLASS_PROPERTIES = [ - 'IE_NAME', 'IE_DESC', 'SEARCH_KEY', '_VALID_URL', '_WORKING', '_ENABLED', '_NETRC_MACHINE', 'age_limit' + 'IE_NAME', '_ENABLED', '_VALID_URL', # Used for URL matching + '_WORKING', 'IE_DESC', '_NETRC_MACHINE', 'SEARCH_KEY', # Used for --extractor-descriptions + 'age_limit', # Used for --age-limit (evaluated) + '_RETURN_TYPE', # Accessed in CLI only with instance (evaluated) ] CLASS_METHODS = [ - 'ie_key', 'working', 'description', 'suitable', '_match_valid_url', '_match_id', 'get_temp_id', 'is_suitable' + 'ie_key', 'suitable', '_match_valid_url', # Used for URL matching + 'working', 'get_temp_id', '_match_id', # Accessed just before instance creation + 'description', # Used for --extractor-descriptions + 'is_suitable', # Used for --age-limit + 'supports_login', 'is_single_video', # Accessed in CLI only with instance ] IE_TEMPLATE = ''' class {name}({bases}): diff --git a/test/parameters.json b/test/parameters.json index bc4561374..8789ce14b 100644 --- a/test/parameters.json +++ b/test/parameters.json @@ -44,5 +44,6 @@ "writesubtitles": false, "allsubtitles": false, "listsubtitles": false, - "fixup": "never" + "fixup": "never", + "allow_playlist_files": false } diff --git a/yt_dlp/YoutubeDL.py b/yt_dlp/YoutubeDL.py index 525d3ab6e..20940085e 100644 --- a/yt_dlp/YoutubeDL.py +++ b/yt_dlp/YoutubeDL.py @@ -1357,7 +1357,7 @@ class YoutubeDL: return self.get_output_path(dir_type, filename) def _match_entry(self, info_dict, incomplete=False, silent=False): - """ Returns None if the file should be downloaded """ + """Returns None if the file should be downloaded""" _type = info_dict.get('_type', 'video') assert incomplete or _type == 'video', 'Only video result can be considered complete' @@ -1381,6 +1381,7 @@ class YoutubeDL: if rejecttitle: if re.search(rejecttitle, title, re.IGNORECASE): return '"' + title + '" title matched reject pattern "' + rejecttitle + '"' + date = info_dict.get('upload_date') if date is not None: dateRange = self.params.get('daterange', DateRange()) @@ -2953,8 +2954,6 @@ class YoutubeDL: if 'format' not in info_dict and 'ext' in info_dict: info_dict['format'] = info_dict['ext'] - # This is mostly just for backward compatibility of process_info - # As a side-effect, this allows for format-specific filters if self._match_entry(info_dict) is not None: info_dict['__write_download_archive'] = 'ignore' return diff --git a/yt_dlp/extractor/adobepass.py b/yt_dlp/extractor/adobepass.py index ec1be008a..e5944f714 100644 --- a/yt_dlp/extractor/adobepass.py +++ b/yt_dlp/extractor/adobepass.py @@ -1352,7 +1352,7 @@ MSO_INFO = { } -class AdobePassIE(InfoExtractor): +class AdobePassIE(InfoExtractor): # XXX: Conventionally, base classes should end with BaseIE/InfoExtractor _SERVICE_PROVIDER_TEMPLATE = 'https://sp.auth.adobe.com/adobe-services/%s' _USER_AGENT = 'Mozilla/5.0 (X11; Linux i686; rv:47.0) Gecko/20100101 Firefox/47.0' _MVPD_CACHE = 'ap-mvpd' diff --git a/yt_dlp/extractor/aenetworks.py b/yt_dlp/extractor/aenetworks.py index 516cb6302..094c57bf9 100644 --- a/yt_dlp/extractor/aenetworks.py +++ b/yt_dlp/extractor/aenetworks.py @@ -8,7 +8,7 @@ from ..utils import ( ) -class AENetworksBaseIE(ThePlatformIE): +class AENetworksBaseIE(ThePlatformIE): # XXX: Do not subclass from concrete IE _BASE_URL_REGEX = r'''(?x)https?:// (?:(?:www|play|watch)\.)? (?P @@ -304,7 +304,6 @@ class HistoryTopicIE(AENetworksBaseIE): class HistoryPlayerIE(AENetworksBaseIE): IE_NAME = 'history:player' _VALID_URL = r'https?://(?:www\.)?(?P(?:history|biography)\.com)/player/(?P\d+)' - _TESTS = [] def _real_extract(self, url): domain, video_id = self._match_valid_url(url).groups() diff --git a/yt_dlp/extractor/afreecatv.py b/yt_dlp/extractor/afreecatv.py index b0fd158f6..bfcc08030 100644 --- a/yt_dlp/extractor/afreecatv.py +++ b/yt_dlp/extractor/afreecatv.py @@ -380,7 +380,7 @@ class AfreecaTVIE(InfoExtractor): return info -class AfreecaTVLiveIE(AfreecaTVIE): +class AfreecaTVLiveIE(AfreecaTVIE): # XXX: Do not subclass from concrete IE IE_NAME = 'afreecatv:live' _VALID_URL = r'https?://play\.afreeca(?:tv)?\.com/(?P[^/]+)(?:/(?P\d+))?' diff --git a/yt_dlp/extractor/alura.py b/yt_dlp/extractor/alura.py index b76ccb2a1..ae7115f9f 100644 --- a/yt_dlp/extractor/alura.py +++ b/yt_dlp/extractor/alura.py @@ -113,7 +113,7 @@ class AluraIE(InfoExtractor): raise ExtractorError('Unable to log in') -class AluraCourseIE(AluraIE): +class AluraCourseIE(AluraIE): # XXX: Do not subclass from concrete IE _VALID_URL = r'https?://(?:cursos\.)?alura\.com\.br/course/(?P[^/]+)' _LOGIN_URL = 'https://cursos.alura.com.br/loginForm?urlAfterLogin=/loginForm' diff --git a/yt_dlp/extractor/amcnetworks.py b/yt_dlp/extractor/amcnetworks.py index e04ecf65f..9369a66f7 100644 --- a/yt_dlp/extractor/amcnetworks.py +++ b/yt_dlp/extractor/amcnetworks.py @@ -9,7 +9,7 @@ from ..utils import ( ) -class AMCNetworksIE(ThePlatformIE): +class AMCNetworksIE(ThePlatformIE): # XXX: Do not subclass from concrete IE _VALID_URL = r'https?://(?:www\.)?(?Pamc|bbcamerica|ifc|(?:we|sundance)tv)\.com/(?P(?:movies|shows(?:/[^/]+)+)/[^/?#&]+)' _TESTS = [{ 'url': 'https://www.bbcamerica.com/shows/the-graham-norton-show/videos/tina-feys-adorable-airline-themed-family-dinner--51631', diff --git a/yt_dlp/extractor/amp.py b/yt_dlp/extractor/amp.py index 73b72b085..6015baad5 100644 --- a/yt_dlp/extractor/amp.py +++ b/yt_dlp/extractor/amp.py @@ -10,7 +10,7 @@ from ..utils import ( ) -class AMPIE(InfoExtractor): +class AMPIE(InfoExtractor): # XXX: Conventionally, base classes should end with BaseIE/InfoExtractor # parse Akamai Adaptive Media Player feed def _extract_feed_info(self, url): feed = self._download_json( diff --git a/yt_dlp/extractor/aol.py b/yt_dlp/extractor/aol.py index b67db2adc..5200f9d9d 100644 --- a/yt_dlp/extractor/aol.py +++ b/yt_dlp/extractor/aol.py @@ -9,7 +9,7 @@ from ..utils import ( ) -class AolIE(YahooIE): +class AolIE(YahooIE): # XXX: Do not subclass from concrete IE IE_NAME = 'aol.com' _VALID_URL = r'(?:aol-video:|https?://(?:www\.)?aol\.(?:com|ca|co\.uk|de|jp)/video/(?:[^/]+/)*)(?P\d{9}|[0-9a-f]{24}|[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12})' diff --git a/yt_dlp/extractor/audius.py b/yt_dlp/extractor/audius.py index 0105d9db8..6448b449b 100644 --- a/yt_dlp/extractor/audius.py +++ b/yt_dlp/extractor/audius.py @@ -168,7 +168,7 @@ class AudiusIE(AudiusBaseIE): } -class AudiusTrackIE(AudiusIE): +class AudiusTrackIE(AudiusIE): # XXX: Do not subclass from concrete IE _VALID_URL = r'''(?x)(?:audius:)(?:https?://(?:www\.)?.+/v1/tracks/)?(?P\w+)''' IE_NAME = 'audius:track' IE_DESC = 'Audius track ID or API link. Prepend with "audius:"' @@ -243,7 +243,7 @@ class AudiusPlaylistIE(AudiusBaseIE): playlist_data.get('description')) -class AudiusProfileIE(AudiusPlaylistIE): +class AudiusProfileIE(AudiusPlaylistIE): # XXX: Do not subclass from concrete IE IE_NAME = 'audius:artist' IE_DESC = 'Audius.co profile/artist pages' _VALID_URL = r'https?://(?:www)?audius\.co/(?P[^\/]+)/?(?:[?#]|$)' diff --git a/yt_dlp/extractor/aws.py b/yt_dlp/extractor/aws.py index c2b22922b..eb831a153 100644 --- a/yt_dlp/extractor/aws.py +++ b/yt_dlp/extractor/aws.py @@ -6,7 +6,7 @@ from .common import InfoExtractor from ..compat import compat_urllib_parse_urlencode -class AWSIE(InfoExtractor): +class AWSIE(InfoExtractor): # XXX: Conventionally, base classes should end with BaseIE/InfoExtractor _AWS_ALGORITHM = 'AWS4-HMAC-SHA256' _AWS_REGION = 'us-east-1' diff --git a/yt_dlp/extractor/bandaichannel.py b/yt_dlp/extractor/bandaichannel.py index 2e3233376..e438d16ea 100644 --- a/yt_dlp/extractor/bandaichannel.py +++ b/yt_dlp/extractor/bandaichannel.py @@ -2,7 +2,7 @@ from .brightcove import BrightcoveNewIE from ..utils import extract_attributes -class BandaiChannelIE(BrightcoveNewIE): +class BandaiChannelIE(BrightcoveNewIE): # XXX: Do not subclass from concrete IE IE_NAME = 'bandaichannel' _VALID_URL = r'https?://(?:www\.)?b-ch\.com/titles/(?P\d+/\d+)' _TESTS = [{ diff --git a/yt_dlp/extractor/bandcamp.py b/yt_dlp/extractor/bandcamp.py index a864ff9ac..7dcace2c6 100644 --- a/yt_dlp/extractor/bandcamp.py +++ b/yt_dlp/extractor/bandcamp.py @@ -211,7 +211,7 @@ class BandcampIE(InfoExtractor): } -class BandcampAlbumIE(BandcampIE): +class BandcampAlbumIE(BandcampIE): # XXX: Do not subclass from concrete IE IE_NAME = 'Bandcamp:album' _VALID_URL = r'https?://(?:(?P[^.]+)\.)?bandcamp\.com/album/(?P[^/?#&]+)' @@ -314,7 +314,7 @@ class BandcampAlbumIE(BandcampIE): } -class BandcampWeeklyIE(BandcampIE): +class BandcampWeeklyIE(BandcampIE): # XXX: Do not subclass from concrete IE IE_NAME = 'Bandcamp:weekly' _VALID_URL = r'https?://(?:www\.)?bandcamp\.com/?\?(?:.*?&)?show=(?P\d+)' _TESTS = [{ diff --git a/yt_dlp/extractor/bbc.py b/yt_dlp/extractor/bbc.py index fe122af85..35a7a165c 100644 --- a/yt_dlp/extractor/bbc.py +++ b/yt_dlp/extractor/bbc.py @@ -588,7 +588,7 @@ class BBCCoUkIE(InfoExtractor): } -class BBCIE(BBCCoUkIE): +class BBCIE(BBCCoUkIE): # XXX: Do not subclass from concrete IE IE_NAME = 'bbc' IE_DESC = 'BBC' _VALID_URL = r'''(?x) diff --git a/yt_dlp/extractor/bfmtv.py b/yt_dlp/extractor/bfmtv.py index 48526e38b..d86d283fa 100644 --- a/yt_dlp/extractor/bfmtv.py +++ b/yt_dlp/extractor/bfmtv.py @@ -42,7 +42,7 @@ class BFMTVIE(BFMTVBaseIE): return self._brightcove_url_result(video_block['videoid'], video_block) -class BFMTVLiveIE(BFMTVIE): +class BFMTVLiveIE(BFMTVIE): # XXX: Do not subclass from concrete IE IE_NAME = 'bfmtv:live' _VALID_URL = BFMTVBaseIE._VALID_URL_BASE + '(?P(?:[^/]+/)?en-direct)' _TESTS = [{ diff --git a/yt_dlp/extractor/bilibili.py b/yt_dlp/extractor/bilibili.py index de28aa4b7..8a0e10da8 100644 --- a/yt_dlp/extractor/bilibili.py +++ b/yt_dlp/extractor/bilibili.py @@ -65,7 +65,7 @@ class BilibiliBaseIE(InfoExtractor): missing_formats = format_names.keys() - set(traverse_obj(formats, (..., 'quality'))) if missing_formats: self.to_screen(f'Format(s) {", ".join(format_names[i] for i in missing_formats)} are missing; ' - 'you have to login or become premium member to download them') + f'you have to login or become premium member to download them. {self._login_hint()}') self._sort_formats(formats) return formats diff --git a/yt_dlp/extractor/cbs.py b/yt_dlp/extractor/cbs.py index e32539c9e..9515806ed 100644 --- a/yt_dlp/extractor/cbs.py +++ b/yt_dlp/extractor/cbs.py @@ -10,7 +10,7 @@ from ..utils import ( ) -class CBSBaseIE(ThePlatformFeedIE): +class CBSBaseIE(ThePlatformFeedIE): # XXX: Do not subclass from concrete IE def _parse_smil_subtitles(self, smil, namespace=None, subtitles_lang='en'): subtitles = {} for k, ext in [('sMPTE-TTCCURL', 'tt'), ('ClosedCaptionURL', 'ttml'), ('webVTTCaptionURL', 'vtt')]: diff --git a/yt_dlp/extractor/cbsinteractive.py b/yt_dlp/extractor/cbsinteractive.py index 7abeecf78..b09e9823e 100644 --- a/yt_dlp/extractor/cbsinteractive.py +++ b/yt_dlp/extractor/cbsinteractive.py @@ -2,7 +2,7 @@ from .cbs import CBSIE from ..utils import int_or_none -class CBSInteractiveIE(CBSIE): +class CBSInteractiveIE(CBSIE): # XXX: Do not subclass from concrete IE _VALID_URL = r'https?://(?:www\.)?(?Pcnet|zdnet)\.com/(?:videos|video(?:/share)?)/(?P[^/?]+)' _TESTS = [{ 'url': 'http://www.cnet.com/videos/hands-on-with-microsofts-windows-8-1-update/', diff --git a/yt_dlp/extractor/cbslocal.py b/yt_dlp/extractor/cbslocal.py index c6495c95f..3d50b0499 100644 --- a/yt_dlp/extractor/cbslocal.py +++ b/yt_dlp/extractor/cbslocal.py @@ -7,7 +7,7 @@ from ..utils import ( ) -class CBSLocalIE(AnvatoIE): +class CBSLocalIE(AnvatoIE): # XXX: Do not subclass from concrete IE _VALID_URL_BASE = r'https?://[a-z]+\.cbslocal\.com/' _VALID_URL = _VALID_URL_BASE + r'video/(?P\d+)' @@ -47,7 +47,7 @@ class CBSLocalIE(AnvatoIE): 'anvato:anvato_cbslocal_app_web_prod_547f3e49241ef0e5d30c79b2efbca5d92c698f67:' + mcp_id, 'Anvato', mcp_id) -class CBSLocalArticleIE(AnvatoIE): +class CBSLocalArticleIE(AnvatoIE): # XXX: Do not subclass from concrete IE _VALID_URL = CBSLocalIE._VALID_URL_BASE + r'\d+/\d+/\d+/(?P[0-9a-z-]+)' _TESTS = [{ diff --git a/yt_dlp/extractor/cbsnews.py b/yt_dlp/extractor/cbsnews.py index 76925b4f9..98ec28df0 100644 --- a/yt_dlp/extractor/cbsnews.py +++ b/yt_dlp/extractor/cbsnews.py @@ -12,7 +12,7 @@ from ..utils import ( ) -class CBSNewsEmbedIE(CBSIE): +class CBSNewsEmbedIE(CBSIE): # XXX: Do not subclass from concrete IE IE_NAME = 'cbsnews:embed' _VALID_URL = r'https?://(?:www\.)?cbsnews\.com/embed/video[^#]*#(?P.+)' _TESTS = [{ @@ -27,7 +27,7 @@ class CBSNewsEmbedIE(CBSIE): return self._extract_video_info(item['mpxRefId'], 'cbsnews') -class CBSNewsIE(CBSIE): +class CBSNewsIE(CBSIE): # XXX: Do not subclass from concrete IE IE_NAME = 'cbsnews' IE_DESC = 'CBS News' _VALID_URL = r'https?://(?:www\.)?cbsnews\.com/(?:news|video)/(?P[\da-z_-]+)' diff --git a/yt_dlp/extractor/cmt.py b/yt_dlp/extractor/cmt.py index 4eec066dd..8aed7708b 100644 --- a/yt_dlp/extractor/cmt.py +++ b/yt_dlp/extractor/cmt.py @@ -3,7 +3,7 @@ from .mtv import MTVIE # TODO Remove - Reason: Outdated Site -class CMTIE(MTVIE): +class CMTIE(MTVIE): # XXX: Do not subclass from concrete IE IE_NAME = 'cmt.com' _VALID_URL = r'https?://(?:www\.)?cmt\.com/(?:videos|shows|(?:full-)?episodes|video-clips)/(?P[^/]+)' diff --git a/yt_dlp/extractor/common.py b/yt_dlp/extractor/common.py index 14984fd6f..3a1af3290 100644 --- a/yt_dlp/extractor/common.py +++ b/yt_dlp/extractor/common.py @@ -3676,12 +3676,13 @@ class InfoExtractor: @classmethod def get_testcases(cls, include_onlymatching=False): - t = getattr(cls, '_TEST', None) + # Do not look in super classes + t = vars(cls).get('_TEST') if t: assert not hasattr(cls, '_TESTS'), f'{cls.ie_key()}IE has _TEST and _TESTS' tests = [t] else: - tests = getattr(cls, '_TESTS', []) + tests = vars(cls).get('_TESTS', []) for t in tests: if not include_onlymatching and t.get('only_matching', False): continue @@ -3690,12 +3691,12 @@ class InfoExtractor: @classmethod def get_webpage_testcases(cls): - tests = getattr(cls, '_WEBPAGE_TESTS', []) + tests = vars(cls).get('_WEBPAGE_TESTS', []) for t in tests: t['name'] = cls.ie_key() return tests - @classproperty + @classproperty(cache=True) def age_limit(cls): """Get age limit from the testcases""" return max(traverse_obj( diff --git a/yt_dlp/extractor/corus.py b/yt_dlp/extractor/corus.py index 7b83c0390..8c920e3ab 100644 --- a/yt_dlp/extractor/corus.py +++ b/yt_dlp/extractor/corus.py @@ -7,7 +7,7 @@ from ..utils import ( ) -class CorusIE(ThePlatformFeedIE): +class CorusIE(ThePlatformFeedIE): # XXX: Do not subclass from concrete IE _VALID_URL = r'''(?x) https?:// (?:www\.)? diff --git a/yt_dlp/extractor/daum.py b/yt_dlp/extractor/daum.py index a1f197b0b..3ef514065 100644 --- a/yt_dlp/extractor/daum.py +++ b/yt_dlp/extractor/daum.py @@ -125,7 +125,7 @@ class DaumClipIE(DaumBaseIE): self._KAKAO_EMBED_BASE + video_id, 'Kakao', video_id) -class DaumListIE(InfoExtractor): +class DaumListIE(InfoExtractor): # XXX: Conventionally, base classes should end with BaseIE/InfoExtractor def _get_entries(self, list_id, list_id_type): name = None entries = [] diff --git a/yt_dlp/extractor/dreisat.py b/yt_dlp/extractor/dreisat.py index 80a724607..8a59c23ab 100644 --- a/yt_dlp/extractor/dreisat.py +++ b/yt_dlp/extractor/dreisat.py @@ -1,7 +1,7 @@ from .zdf import ZDFIE -class DreiSatIE(ZDFIE): +class DreiSatIE(ZDFIE): # XXX: Do not subclass from concrete IE IE_NAME = '3sat' _VALID_URL = r'https?://(?:www\.)?3sat\.de/(?:[^/]+/)*(?P[^/?#&]+)\.html' _TESTS = [{ diff --git a/yt_dlp/extractor/extremetube.py b/yt_dlp/extractor/extremetube.py index 99520b6a0..2c1969899 100644 --- a/yt_dlp/extractor/extremetube.py +++ b/yt_dlp/extractor/extremetube.py @@ -2,7 +2,7 @@ from ..utils import str_to_int from .keezmovies import KeezMoviesIE -class ExtremeTubeIE(KeezMoviesIE): +class ExtremeTubeIE(KeezMoviesIE): # XXX: Do not subclass from concrete IE _VALID_URL = r'https?://(?:www\.)?extremetube\.com/(?:[^/]+/)?video/(?P[^/#?&]+)' _TESTS = [{ 'url': 'http://www.extremetube.com/video/music-video-14-british-euro-brit-european-cumshots-swallow-652431', diff --git a/yt_dlp/extractor/fancode.py b/yt_dlp/extractor/fancode.py index 9716e581a..1b5db818a 100644 --- a/yt_dlp/extractor/fancode.py +++ b/yt_dlp/extractor/fancode.py @@ -125,7 +125,7 @@ class FancodeVodIE(InfoExtractor): } -class FancodeLiveIE(FancodeVodIE): +class FancodeLiveIE(FancodeVodIE): # XXX: Do not subclass from concrete IE IE_NAME = 'fancode:live' _VALID_URL = r'https?://(www\.)?fancode\.com/match/(?P[0-9]+).+' diff --git a/yt_dlp/extractor/hitbox.py b/yt_dlp/extractor/hitbox.py index 6ecdd390c..fdcf6770d 100644 --- a/yt_dlp/extractor/hitbox.py +++ b/yt_dlp/extractor/hitbox.py @@ -127,7 +127,7 @@ class HitboxIE(InfoExtractor): return metadata -class HitboxLiveIE(HitboxIE): +class HitboxLiveIE(HitboxIE): # XXX: Do not subclass from concrete IE IE_NAME = 'hitbox:live' _VALID_URL = r'https?://(?:www\.)?(?:hitbox|smashcast)\.tv/(?P[^/?#&]+)' _TESTS = [{ diff --git a/yt_dlp/extractor/imgur.py b/yt_dlp/extractor/imgur.py index a3bb47615..21c56d879 100644 --- a/yt_dlp/extractor/imgur.py +++ b/yt_dlp/extractor/imgur.py @@ -138,7 +138,7 @@ class ImgurGalleryIE(InfoExtractor): return self.url_result('http://imgur.com/%s' % gallery_id, ImgurIE.ie_key(), gallery_id) -class ImgurAlbumIE(ImgurGalleryIE): +class ImgurAlbumIE(ImgurGalleryIE): # XXX: Do not subclass from concrete IE IE_NAME = 'imgur:album' _VALID_URL = r'https?://(?:i\.)?imgur\.com/a/(?P[a-zA-Z0-9]+)' diff --git a/yt_dlp/extractor/jamendo.py b/yt_dlp/extractor/jamendo.py index d960ee51c..578e57a67 100644 --- a/yt_dlp/extractor/jamendo.py +++ b/yt_dlp/extractor/jamendo.py @@ -134,7 +134,7 @@ class JamendoIE(InfoExtractor): } -class JamendoAlbumIE(JamendoIE): +class JamendoAlbumIE(JamendoIE): # XXX: Do not subclass from concrete IE _VALID_URL = r'https?://(?:www\.)?jamendo\.com/album/(?P[0-9]+)' _TESTS = [{ 'url': 'https://www.jamendo.com/album/121486/duck-on-cover', diff --git a/yt_dlp/extractor/la7.py b/yt_dlp/extractor/la7.py index 5d52decdb..8ce44cc13 100644 --- a/yt_dlp/extractor/la7.py +++ b/yt_dlp/extractor/la7.py @@ -194,7 +194,7 @@ class LA7PodcastEpisodeIE(InfoExtractor): return self._extract_info(webpage, video_id) -class LA7PodcastIE(LA7PodcastEpisodeIE): +class LA7PodcastIE(LA7PodcastEpisodeIE): # XXX: Do not subclass from concrete IE IE_NAME = 'la7.it:podcast' _VALID_URL = r'(https?://)?(www\.)?la7\.it/(?P[^/]+)/podcast/?(?:$|[#?])' diff --git a/yt_dlp/extractor/laola1tv.py b/yt_dlp/extractor/laola1tv.py index 4014a9256..a90ed16a0 100644 --- a/yt_dlp/extractor/laola1tv.py +++ b/yt_dlp/extractor/laola1tv.py @@ -118,7 +118,7 @@ class Laola1TvEmbedIE(InfoExtractor): } -class Laola1TvBaseIE(Laola1TvEmbedIE): +class Laola1TvBaseIE(Laola1TvEmbedIE): # XXX: Do not subclass from concrete IE def _extract_video(self, url): display_id = self._match_id(url) webpage = self._download_webpage(url, display_id) diff --git a/yt_dlp/extractor/lcp.py b/yt_dlp/extractor/lcp.py index 87543d56f..9846319e0 100644 --- a/yt_dlp/extractor/lcp.py +++ b/yt_dlp/extractor/lcp.py @@ -2,7 +2,7 @@ from .common import InfoExtractor from .arkena import ArkenaIE -class LcpPlayIE(ArkenaIE): +class LcpPlayIE(ArkenaIE): # XXX: Do not subclass from concrete IE _VALID_URL = r'https?://play\.lcp\.fr/embed/(?P[^/]+)/(?P[^/]+)/[^/]+/[^/]+' _TESTS = [{ 'url': 'http://play.lcp.fr/embed/327336/131064/darkmatter/0', diff --git a/yt_dlp/extractor/mediaset.py b/yt_dlp/extractor/mediaset.py index ebe894f74..a3b5491d2 100644 --- a/yt_dlp/extractor/mediaset.py +++ b/yt_dlp/extractor/mediaset.py @@ -286,7 +286,7 @@ class MediasetIE(ThePlatformBaseIE): return info -class MediasetShowIE(MediasetIE): +class MediasetShowIE(MediasetIE): # XXX: Do not subclass from concrete IE _VALID_URL = r'''(?x) (?: https?:// diff --git a/yt_dlp/extractor/mitele.py b/yt_dlp/extractor/mitele.py index 12b2b2432..ea2998672 100644 --- a/yt_dlp/extractor/mitele.py +++ b/yt_dlp/extractor/mitele.py @@ -5,7 +5,7 @@ from ..utils import ( ) -class MiTeleIE(TelecincoIE): +class MiTeleIE(TelecincoIE): # XXX: Do not subclass from concrete IE IE_DESC = 'mitele.es' _VALID_URL = r'https?://(?:www\.)?mitele\.es/(?:[^/]+/)+(?P[^/]+)/player' diff --git a/yt_dlp/extractor/mofosex.py b/yt_dlp/extractor/mofosex.py index 4221ef3e3..9cb6980c1 100644 --- a/yt_dlp/extractor/mofosex.py +++ b/yt_dlp/extractor/mofosex.py @@ -7,7 +7,7 @@ from ..utils import ( from .keezmovies import KeezMoviesIE -class MofosexIE(KeezMoviesIE): +class MofosexIE(KeezMoviesIE): # XXX: Do not subclass from concrete IE _VALID_URL = r'https?://(?:www\.)?mofosex\.com/videos/(?P\d+)/(?P[^/?#&.]+)\.html' _TESTS = [{ 'url': 'http://www.mofosex.com/videos/318131/amateur-teen-playing-and-masturbating-318131.html', diff --git a/yt_dlp/extractor/mtv.py b/yt_dlp/extractor/mtv.py index 10cd304eb..b2009dc5b 100644 --- a/yt_dlp/extractor/mtv.py +++ b/yt_dlp/extractor/mtv.py @@ -536,7 +536,7 @@ class MTVItaliaIE(MTVServicesInfoExtractor): } -class MTVItaliaProgrammaIE(MTVItaliaIE): +class MTVItaliaProgrammaIE(MTVItaliaIE): # XXX: Do not subclass from concrete IE IE_NAME = 'mtv.it:programma' _VALID_URL = r'https?://(?:www\.)?mtv\.it/(?:programmi|playlist)/(?P[0-9a-z]+)' _TESTS = [{ diff --git a/yt_dlp/extractor/murrtube.py b/yt_dlp/extractor/murrtube.py index 508d51247..6cdbbda16 100644 --- a/yt_dlp/extractor/murrtube.py +++ b/yt_dlp/extractor/murrtube.py @@ -99,7 +99,7 @@ query Medium($id: ID!) { } -class MurrtubeUserIE(MurrtubeIE): +class MurrtubeUserIE(MurrtubeIE): # XXX: Do not subclass from concrete IE IE_DESC = 'Murrtube user profile' _VALID_URL = r'https?://murrtube\.net/(?P[^/]+)$' _TEST = { diff --git a/yt_dlp/extractor/musicdex.py b/yt_dlp/extractor/musicdex.py index 4d8e74f6b..48f29702c 100644 --- a/yt_dlp/extractor/musicdex.py +++ b/yt_dlp/extractor/musicdex.py @@ -97,7 +97,7 @@ class MusicdexAlbumIE(MusicdexBaseIE): } -class MusicdexPageIE(MusicdexBaseIE): +class MusicdexPageIE(MusicdexBaseIE): # XXX: Conventionally, base classes should end with BaseIE/InfoExtractor def _entries(self, id): next_page_url = self._API_URL % id while next_page_url: diff --git a/yt_dlp/extractor/nationalgeographic.py b/yt_dlp/extractor/nationalgeographic.py index f22317d56..ad525c258 100644 --- a/yt_dlp/extractor/nationalgeographic.py +++ b/yt_dlp/extractor/nationalgeographic.py @@ -59,7 +59,7 @@ class NationalGeographicVideoIE(InfoExtractor): } -class NationalGeographicTVIE(FOXIE): +class NationalGeographicTVIE(FOXIE): # XXX: Do not subclass from concrete IE _VALID_URL = r'https?://(?:www\.)?nationalgeographic\.com/tv/watch/(?P[\da-fA-F]+)' _TESTS = [{ 'url': 'https://www.nationalgeographic.com/tv/watch/6a875e6e734b479beda26438c9f21138/', diff --git a/yt_dlp/extractor/nbc.py b/yt_dlp/extractor/nbc.py index 3de8c1508..dbc82de9f 100644 --- a/yt_dlp/extractor/nbc.py +++ b/yt_dlp/extractor/nbc.py @@ -24,7 +24,7 @@ from ..utils import ( ) -class NBCIE(ThePlatformIE): +class NBCIE(ThePlatformIE): # XXX: Do not subclass from concrete IE _VALID_URL = r'https?(?P://(?:www\.)?nbc\.com/(?:classic-tv/)?[^/]+/video/[^/]+/(?Pn?\d+))' _TESTS = [ @@ -315,7 +315,7 @@ class NBCSportsStreamIE(AdobePassIE): } -class NBCNewsIE(ThePlatformIE): +class NBCNewsIE(ThePlatformIE): # XXX: Do not subclass from concrete IE _VALID_URL = r'(?x)https?://(?:www\.)?(?:nbcnews|today|msnbc)\.com/([^/]+/)*(?:.*-)?(?P[^/?]+)' _EMBED_REGEX = [r']+src=(["\'])(?P(?:https?:)?//www\.nbcnews\.com/widget/video-embed/[^"\']+)\1'] diff --git a/yt_dlp/extractor/ndr.py b/yt_dlp/extractor/ndr.py index ad8dbd7a7..90a658cd8 100644 --- a/yt_dlp/extractor/ndr.py +++ b/yt_dlp/extractor/ndr.py @@ -218,7 +218,7 @@ class NJoyIE(NDRBaseIE): } -class NDREmbedBaseIE(InfoExtractor): +class NDREmbedBaseIE(InfoExtractor): # XXX: Conventionally, Concrete class names do not end in BaseIE IE_NAME = 'ndr:embed:base' _VALID_URL = r'(?:ndr:(?P[\da-z]+)|https?://www\.ndr\.de/(?P[\da-z]+)-ppjson\.json)' _TESTS = [{ @@ -315,7 +315,7 @@ class NDREmbedBaseIE(InfoExtractor): } -class NDREmbedIE(NDREmbedBaseIE): +class NDREmbedIE(NDREmbedBaseIE): # XXX: Do not subclass from concrete IE IE_NAME = 'ndr:embed' _VALID_URL = r'https?://(?:\w+\.)*ndr\.de/(?:[^/]+/)*(?P[\da-z]+)-(?:(?:ard)?player|externalPlayer)\.html' _TESTS = [{ @@ -413,7 +413,7 @@ class NDREmbedIE(NDREmbedBaseIE): }] -class NJoyEmbedIE(NDREmbedBaseIE): +class NJoyEmbedIE(NDREmbedBaseIE): # XXX: Do not subclass from concrete IE IE_NAME = 'njoy:embed' _VALID_URL = r'https?://(?:www\.)?n-joy\.de/(?:[^/]+/)*(?P[\da-z]+)-(?:player|externalPlayer)_[^/]+\.html' _TESTS = [{ diff --git a/yt_dlp/extractor/nextmedia.py b/yt_dlp/extractor/nextmedia.py index 1f83089fc..0e47a4d45 100644 --- a/yt_dlp/extractor/nextmedia.py +++ b/yt_dlp/extractor/nextmedia.py @@ -77,7 +77,7 @@ class NextMediaIE(InfoExtractor): return self._og_search_property('description', page) -class NextMediaActionNewsIE(NextMediaIE): +class NextMediaActionNewsIE(NextMediaIE): # XXX: Do not subclass from concrete IE IE_DESC = '蘋果日報 - 動新聞' _VALID_URL = r'https?://hk\.dv\.nextmedia\.com/actionnews/[^/]+/(?P\d+)/(?P\d+)/\d+' _TESTS = [{ @@ -102,7 +102,7 @@ class NextMediaActionNewsIE(NextMediaIE): return self._extract_from_nextmedia_page(news_id, url, article_page) -class AppleDailyIE(NextMediaIE): +class AppleDailyIE(NextMediaIE): # XXX: Do not subclass from concrete IE IE_DESC = '臺灣蘋果日報' _VALID_URL = r'https?://(www|ent)\.appledaily\.com\.tw/[^/]+/[^/]+/[^/]+/(?P\d+)/(?P\d+)(/.*)?' _TESTS = [{ diff --git a/yt_dlp/extractor/nick.py b/yt_dlp/extractor/nick.py index 2a228d8de..de22cb8d6 100644 --- a/yt_dlp/extractor/nick.py +++ b/yt_dlp/extractor/nick.py @@ -188,7 +188,7 @@ class NickDeIE(MTVServicesInfoExtractor): return self._remove_template_parameter(config['feedWithQueryParams']) -class NickNightIE(NickDeIE): +class NickNightIE(NickDeIE): # XXX: Do not subclass from concrete IE IE_NAME = 'nicknight' _VALID_URL = r'https?://(?:www\.)(?Pnicknight\.(?:de|at|tv))/(?:playlist|shows)/(?:[^/]+/)*(?P[^/?#&]+)' _TESTS = [{ diff --git a/yt_dlp/extractor/npo.py b/yt_dlp/extractor/npo.py index 0b5f32c2e..b307e6a78 100644 --- a/yt_dlp/extractor/npo.py +++ b/yt_dlp/extractor/npo.py @@ -599,7 +599,7 @@ class NPORadioFragmentIE(InfoExtractor): } -class NPODataMidEmbedIE(InfoExtractor): +class NPODataMidEmbedIE(InfoExtractor): # XXX: Conventionally, base classes should end with BaseIE/InfoExtractor def _real_extract(self, url): display_id = self._match_id(url) webpage = self._download_webpage(url, display_id) @@ -653,7 +653,7 @@ class HetKlokhuisIE(NPODataMidEmbedIE): } -class NPOPlaylistBaseIE(NPOIE): +class NPOPlaylistBaseIE(NPOIE): # XXX: Do not subclass from concrete IE def _real_extract(self, url): playlist_id = self._match_id(url) diff --git a/yt_dlp/extractor/nrk.py b/yt_dlp/extractor/nrk.py index 7eb5b21cb..14951f8e1 100644 --- a/yt_dlp/extractor/nrk.py +++ b/yt_dlp/extractor/nrk.py @@ -735,7 +735,7 @@ class NRKTVSeriesIE(NRKTVSerieBaseIE): entries, series_id, titles.get('title'), titles.get('subtitle')) -class NRKTVDirekteIE(NRKTVIE): +class NRKTVDirekteIE(NRKTVIE): # XXX: Do not subclass from concrete IE IE_DESC = 'NRK TV Direkte and NRK Radio Direkte' _VALID_URL = r'https?://(?:tv|radio)\.nrk\.no/direkte/(?P[^/?#&]+)' diff --git a/yt_dlp/extractor/once.py b/yt_dlp/extractor/once.py index 460b82d02..989f10abb 100644 --- a/yt_dlp/extractor/once.py +++ b/yt_dlp/extractor/once.py @@ -3,7 +3,7 @@ import re from .common import InfoExtractor -class OnceIE(InfoExtractor): +class OnceIE(InfoExtractor): # XXX: Conventionally, base classes should end with BaseIE/InfoExtractor _VALID_URL = r'https?://.+?\.unicornmedia\.com/now/(?:ads/vmap/)?[^/]+/[^/]+/(?P[^/]+)/(?P[^/]+)/(?:[^/]+/)?(?P[^/]+)/content\.(?:once|m3u8|mp4)' ADAPTIVE_URL_TEMPLATE = 'http://once.unicornmedia.com/now/master/playlist/%s/%s/%s/content.m3u8' PROGRESSIVE_URL_TEMPLATE = 'http://once.unicornmedia.com/now/media/progressive/%s/%s/%s/%s/content.mp4' diff --git a/yt_dlp/extractor/peekvids.py b/yt_dlp/extractor/peekvids.py index f1c4469d6..fd25b5adb 100644 --- a/yt_dlp/extractor/peekvids.py +++ b/yt_dlp/extractor/peekvids.py @@ -51,7 +51,7 @@ class PeekVidsIE(InfoExtractor): return info -class PlayVidsIE(PeekVidsIE): +class PlayVidsIE(PeekVidsIE): # XXX: Do not subclass from concrete IE _VALID_URL = r'https?://(?:www\.)?playvids\.com/(?:embed/|[^/]{2}/)?(?P[^/?#]*)' _TESTS = [{ 'url': 'https://www.playvids.com/U3pBrYhsjXM/pc/dane-jones-cute-redhead-with-perfect-tits-with-mini-vamp', diff --git a/yt_dlp/extractor/radlive.py b/yt_dlp/extractor/radlive.py index d89c9563b..ed38a07f0 100644 --- a/yt_dlp/extractor/radlive.py +++ b/yt_dlp/extractor/radlive.py @@ -94,7 +94,7 @@ class RadLiveIE(InfoExtractor): return result -class RadLiveSeasonIE(RadLiveIE): +class RadLiveSeasonIE(RadLiveIE): # XXX: Do not subclass from concrete IE IE_NAME = 'radlive:season' _VALID_URL = r'https?://(?:www\.)?rad\.live/content/season/(?P[a-f0-9-]+)' _TESTS = [{ @@ -134,7 +134,7 @@ class RadLiveSeasonIE(RadLiveIE): return self.playlist_result(entries, season_id, video_info.get('title')) -class RadLiveChannelIE(RadLiveIE): +class RadLiveChannelIE(RadLiveIE): # XXX: Do not subclass from concrete IE IE_NAME = 'radlive:channel' _VALID_URL = r'https?://(?:www\.)?rad\.live/content/channel/(?P[a-f0-9-]+)' _TESTS = [{ diff --git a/yt_dlp/extractor/rai.py b/yt_dlp/extractor/rai.py index 6ed8227eb..cd19ec07b 100644 --- a/yt_dlp/extractor/rai.py +++ b/yt_dlp/extractor/rai.py @@ -356,7 +356,7 @@ class RaiPlayIE(RaiBaseIE): } -class RaiPlayLiveIE(RaiPlayIE): +class RaiPlayLiveIE(RaiPlayIE): # XXX: Do not subclass from concrete IE _VALID_URL = r'(?Phttps?://(?:www\.)?raiplay\.it/dirette/(?P[^/?#&]+))' _TESTS = [{ 'url': 'http://www.raiplay.it/dirette/rainews24', @@ -504,7 +504,7 @@ class RaiPlaySoundIE(RaiBaseIE): } -class RaiPlaySoundLiveIE(RaiPlaySoundIE): +class RaiPlaySoundLiveIE(RaiPlaySoundIE): # XXX: Do not subclass from concrete IE _VALID_URL = r'(?Phttps?://(?:www\.)?raiplaysound\.it/(?P[^/?#&]+)$)' _TESTS = [{ 'url': 'https://www.raiplaysound.it/radio2', @@ -717,7 +717,7 @@ class RaiIE(RaiBaseIE): } -class RaiNewsIE(RaiIE): +class RaiNewsIE(RaiIE): # XXX: Do not subclass from concrete IE _VALID_URL = rf'https?://(www\.)?rainews\.it/(?!articoli)[^?#]+-(?P{RaiBaseIE._UUID_RE})(?:-[^/?#]+)?\.html' _EMBED_REGEX = [rf']+data-src="(?P/iframe/[^?#]+?{RaiBaseIE._UUID_RE}\.html)'] _TESTS = [{ diff --git a/yt_dlp/extractor/redbulltv.py b/yt_dlp/extractor/redbulltv.py index 2f0e41c5b..50e61ba6e 100644 --- a/yt_dlp/extractor/redbulltv.py +++ b/yt_dlp/extractor/redbulltv.py @@ -110,7 +110,7 @@ class RedBullTVIE(InfoExtractor): return self.extract_info(video_id) -class RedBullEmbedIE(RedBullTVIE): +class RedBullEmbedIE(RedBullTVIE): # XXX: Do not subclass from concrete IE _VALID_URL = r'https?://(?:www\.)?redbull\.com/embed/(?Prrn:content:[^:]+:[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}:[a-z]{2}-[A-Z]{2,3})' _TESTS = [{ # HLS manifest accessible only using assetId diff --git a/yt_dlp/extractor/rts.py b/yt_dlp/extractor/rts.py index e5ba1a26b..6644538ed 100644 --- a/yt_dlp/extractor/rts.py +++ b/yt_dlp/extractor/rts.py @@ -12,7 +12,7 @@ from ..utils import ( ) -class RTSIE(SRGSSRIE): +class RTSIE(SRGSSRIE): # XXX: Do not subclass from concrete IE IE_DESC = 'RTS.ch' _VALID_URL = r'rts:(?P\d+)|https?://(?:.+?\.)?rts\.ch/(?:[^/]+/){2,}(?P[0-9]+)-(?P.+?)\.html' diff --git a/yt_dlp/extractor/rtve.py b/yt_dlp/extractor/rtve.py index 798dde7fa..b9b181feb 100644 --- a/yt_dlp/extractor/rtve.py +++ b/yt_dlp/extractor/rtve.py @@ -170,7 +170,7 @@ class RTVEALaCartaIE(InfoExtractor): for s in subs) -class RTVEAudioIE(RTVEALaCartaIE): +class RTVEAudioIE(RTVEALaCartaIE): # XXX: Do not subclass from concrete IE IE_NAME = 'rtve.es:audio' IE_DESC = 'RTVE audio' _VALID_URL = r'https?://(?:www\.)?rtve\.es/(alacarta|play)/audios/[^/]+/[^/]+/(?P[0-9]+)' @@ -257,7 +257,7 @@ class RTVEAudioIE(RTVEALaCartaIE): } -class RTVEInfantilIE(RTVEALaCartaIE): +class RTVEInfantilIE(RTVEALaCartaIE): # XXX: Do not subclass from concrete IE IE_NAME = 'rtve.es:infantil' IE_DESC = 'RTVE infantil' _VALID_URL = r'https?://(?:www\.)?rtve\.es/infantil/serie/[^/]+/video/[^/]+/(?P[0-9]+)/' @@ -276,7 +276,7 @@ class RTVEInfantilIE(RTVEALaCartaIE): }] -class RTVELiveIE(RTVEALaCartaIE): +class RTVELiveIE(RTVEALaCartaIE): # XXX: Do not subclass from concrete IE IE_NAME = 'rtve.es:live' IE_DESC = 'RTVE.es live streams' _VALID_URL = r'https?://(?:www\.)?rtve\.es/directo/(?P[a-zA-Z0-9-]+)' diff --git a/yt_dlp/extractor/rutube.py b/yt_dlp/extractor/rutube.py index 34af0d594..cad3caa60 100644 --- a/yt_dlp/extractor/rutube.py +++ b/yt_dlp/extractor/rutube.py @@ -240,7 +240,6 @@ class RutubeMovieIE(RutubePlaylistBaseIE): IE_NAME = 'rutube:movie' IE_DESC = 'Rutube movies' _VALID_URL = r'https?://rutube\.ru/metainfo/tv/(?P\d+)' - _TESTS = [] _MOVIE_TEMPLATE = 'http://rutube.ru/api/metainfo/tv/%s/?format=json' _PAGE_TEMPLATE = 'http://rutube.ru/api/metainfo/tv/%s/video?page=%s&format=json' diff --git a/yt_dlp/extractor/sevenplus.py b/yt_dlp/extractor/sevenplus.py index 8e95bc230..36d1a86fd 100644 --- a/yt_dlp/extractor/sevenplus.py +++ b/yt_dlp/extractor/sevenplus.py @@ -13,7 +13,7 @@ from ..utils import ( ) -class SevenPlusIE(BrightcoveNewIE): +class SevenPlusIE(BrightcoveNewIE): # XXX: Do not subclass from concrete IE IE_NAME = '7plus' _VALID_URL = r'https?://(?:www\.)?7plus\.com\.au/(?P[^?]+\?.*?\bepisode-id=(?P[^&#]+))' _TESTS = [{ diff --git a/yt_dlp/extractor/skyit.py b/yt_dlp/extractor/skyit.py index 2daaaf75c..9e4d7d35d 100644 --- a/yt_dlp/extractor/skyit.py +++ b/yt_dlp/extractor/skyit.py @@ -70,7 +70,7 @@ class SkyItPlayerIE(InfoExtractor): return self._parse_video(video, video_id) -class SkyItVideoIE(SkyItPlayerIE): +class SkyItVideoIE(SkyItPlayerIE): # XXX: Do not subclass from concrete IE IE_NAME = 'video.sky.it' _VALID_URL = r'https?://(?:masterchef|video|xfactor)\.sky\.it(?:/[^/]+)*/video/[0-9a-z-]+-(?P\d+)' _TESTS = [{ @@ -99,7 +99,7 @@ class SkyItVideoIE(SkyItPlayerIE): return self._player_url_result(video_id) -class SkyItVideoLiveIE(SkyItPlayerIE): +class SkyItVideoLiveIE(SkyItPlayerIE): # XXX: Do not subclass from concrete IE IE_NAME = 'video.sky.it:live' _VALID_URL = r'https?://video\.sky\.it/diretta/(?P[^/?&#]+)' _TEST = { @@ -127,7 +127,7 @@ class SkyItVideoLiveIE(SkyItPlayerIE): return self._parse_video(livestream, asset_id) -class SkyItIE(SkyItPlayerIE): +class SkyItIE(SkyItPlayerIE): # XXX: Do not subclass from concrete IE IE_NAME = 'sky.it' _VALID_URL = r'https?://(?:sport|tg24)\.sky\.it(?:/[^/]+)*/\d{4}/\d{2}/\d{2}/(?P[^/?&#]+)' _TESTS = [{ @@ -166,7 +166,7 @@ class SkyItIE(SkyItPlayerIE): return self._player_url_result(video_id) -class SkyItArteIE(SkyItIE): +class SkyItArteIE(SkyItIE): # XXX: Do not subclass from concrete IE IE_NAME = 'arte.sky.it' _VALID_URL = r'https?://arte\.sky\.it/video/(?P[^/?&#]+)' _TESTS = [{ @@ -187,7 +187,7 @@ class SkyItArteIE(SkyItIE): _VIDEO_ID_REGEX = r'"embedUrl"\s*:\s*"(?:https:)?//player\.sky\.it/player/external\.html\?[^"]*\bid=(\d+)' -class CieloTVItIE(SkyItIE): +class CieloTVItIE(SkyItIE): # XXX: Do not subclass from concrete IE IE_NAME = 'cielotv.it' _VALID_URL = r'https?://(?:www\.)?cielotv\.it/video/(?P[^.]+)\.html' _TESTS = [{ @@ -208,7 +208,7 @@ class CieloTVItIE(SkyItIE): _VIDEO_ID_REGEX = r'videoId\s*=\s*"(\d+)"' -class TV8ItIE(SkyItVideoIE): +class TV8ItIE(SkyItVideoIE): # XXX: Do not subclass from concrete IE IE_NAME = 'tv8.it' _VALID_URL = r'https?://(?:www\.)?tv8\.it/(?:show)?video/[0-9a-z-]+-(?P\d+)' _TESTS = [{ diff --git a/yt_dlp/extractor/southpark.py b/yt_dlp/extractor/southpark.py index 7381ac362..e23f192a1 100644 --- a/yt_dlp/extractor/southpark.py +++ b/yt_dlp/extractor/southpark.py @@ -34,7 +34,7 @@ class SouthParkIE(MTVServicesInfoExtractor): } -class SouthParkEsIE(SouthParkIE): +class SouthParkEsIE(SouthParkIE): # XXX: Do not subclass from concrete IE IE_NAME = 'southpark.cc.com:español' _VALID_URL = r'https?://(?:www\.)?(?Psouthpark\.cc\.com/es/episodios/(?P.+?)(\?|#|$))' _LANG = 'es' @@ -50,7 +50,7 @@ class SouthParkEsIE(SouthParkIE): }] -class SouthParkDeIE(SouthParkIE): +class SouthParkDeIE(SouthParkIE): # XXX: Do not subclass from concrete IE IE_NAME = 'southpark.de' _VALID_URL = r'https?://(?:www\.)?(?Psouthpark\.de/(?:(en/(videoclip|collections|episodes|video-clips))|(videoclip|collections|folgen))/(?P(?P.+?)/.+?)(?:\?|#|$))' _TESTS = [{ @@ -109,7 +109,7 @@ class SouthParkDeIE(SouthParkIE): return -class SouthParkLatIE(SouthParkIE): +class SouthParkLatIE(SouthParkIE): # XXX: Do not subclass from concrete IE IE_NAME = 'southpark.lat' _VALID_URL = r'https?://(?:www\.)?southpark\.lat/(?:en/)?(?:video-?clips?|collections|episod(?:e|io)s)/(?P[^/?#&]+)' _TESTS = [{ @@ -152,7 +152,7 @@ class SouthParkLatIE(SouthParkIE): return -class SouthParkNlIE(SouthParkIE): +class SouthParkNlIE(SouthParkIE): # XXX: Do not subclass from concrete IE IE_NAME = 'southpark.nl' _VALID_URL = r'https?://(?:www\.)?(?Psouthpark\.nl/(?:clips|(?:full-)?episodes|collections)/(?P.+?)(\?|#|$))' _FEED_URL = 'http://www.southpark.nl/feeds/video-player/mrss/' @@ -167,7 +167,7 @@ class SouthParkNlIE(SouthParkIE): }] -class SouthParkDkIE(SouthParkIE): +class SouthParkDkIE(SouthParkIE): # XXX: Do not subclass from concrete IE IE_NAME = 'southparkstudios.dk' _VALID_URL = r'https?://(?:www\.)?(?Psouthparkstudios\.(?:dk|nu)/(?:clips|full-episodes|collections)/(?P.+?)(\?|#|$))' _FEED_URL = 'http://www.southparkstudios.dk/feeds/video-player/mrss/' diff --git a/yt_dlp/extractor/tele5.py b/yt_dlp/extractor/tele5.py index 58d343b44..9260db2b4 100644 --- a/yt_dlp/extractor/tele5.py +++ b/yt_dlp/extractor/tele5.py @@ -6,7 +6,7 @@ from ..utils import ( ) -class Tele5IE(DPlayIE): +class Tele5IE(DPlayIE): # XXX: Do not subclass from concrete IE _VALID_URL = r'https?://(?:www\.)?tele5\.de/(?:[^/]+/)*(?P[^/?#&]+)' _GEO_COUNTRIES = ['DE'] _TESTS = [{ diff --git a/yt_dlp/extractor/theweatherchannel.py b/yt_dlp/extractor/theweatherchannel.py index 9e94cd1ea..4f6d2ecba 100644 --- a/yt_dlp/extractor/theweatherchannel.py +++ b/yt_dlp/extractor/theweatherchannel.py @@ -8,7 +8,7 @@ from ..utils import ( ) -class TheWeatherChannelIE(ThePlatformIE): +class TheWeatherChannelIE(ThePlatformIE): # XXX: Do not subclass from concrete IE _VALID_URL = r'https?://(?:www\.)?weather\.com(?P(?:/(?P[a-z]{2}-[A-Z]{2}))?/(?:[^/]+/)*video/(?P[^/?#]+))' _TESTS = [{ 'url': 'https://weather.com/series/great-outdoors/video/ice-climber-is-in-for-a-shock', diff --git a/yt_dlp/extractor/tiktok.py b/yt_dlp/extractor/tiktok.py index 4a35a241c..79a223861 100644 --- a/yt_dlp/extractor/tiktok.py +++ b/yt_dlp/extractor/tiktok.py @@ -655,7 +655,7 @@ class TikTokUserIE(TikTokBaseIE): return self.playlist_result(self._entries_api(user_id, videos), user_id, user_name, thumbnail=thumbnail) -class TikTokBaseListIE(TikTokBaseIE): +class TikTokBaseListIE(TikTokBaseIE): # XXX: Conventionally, base classes should end with BaseIE/InfoExtractor def _entries(self, list_id, display_id): query = { self._QUERY_NAME: list_id, @@ -764,7 +764,7 @@ class TikTokTagIE(TikTokBaseListIE): return self.playlist_result(self._entries(tag_id, display_id), tag_id, display_id) -class DouyinIE(TikTokIE): +class DouyinIE(TikTokIE): # XXX: Do not subclass from concrete IE _VALID_URL = r'https?://(?:www\.)?douyin\.com/video/(?P[0-9]+)' _TESTS = [{ 'url': 'https://www.douyin.com/video/6961737553342991651', diff --git a/yt_dlp/extractor/toutv.py b/yt_dlp/extractor/toutv.py index 349c0bded..f60c199f0 100644 --- a/yt_dlp/extractor/toutv.py +++ b/yt_dlp/extractor/toutv.py @@ -9,7 +9,7 @@ from ..utils import ( ) -class TouTvIE(RadioCanadaIE): +class TouTvIE(RadioCanadaIE): # XXX: Do not subclass from concrete IE _NETRC_MACHINE = 'toutv' IE_NAME = 'tou.tv' _VALID_URL = r'https?://ici\.tou\.tv/(?P[a-zA-Z0-9_-]+(?:/S[0-9]+[EC][0-9]+)?)' diff --git a/yt_dlp/extractor/tube8.py b/yt_dlp/extractor/tube8.py index b092ecad5..77ed05ffd 100644 --- a/yt_dlp/extractor/tube8.py +++ b/yt_dlp/extractor/tube8.py @@ -7,7 +7,7 @@ from ..utils import ( from .keezmovies import KeezMoviesIE -class Tube8IE(KeezMoviesIE): +class Tube8IE(KeezMoviesIE): # XXX: Do not subclass from concrete IE _VALID_URL = r'https?://(?:www\.)?tube8\.com/(?:[^/]+/)+(?P[^/]+)/(?P\d+)' _EMBED_REGEX = [r']+\bsrc=["\'](?P(?:https?:)?//(?:www\.)?tube8\.com/embed/(?:[^/]+/)+\d+)'] _TESTS = [{ diff --git a/yt_dlp/extractor/tvnow.py b/yt_dlp/extractor/tvnow.py index 4aa558d83..24add5260 100644 --- a/yt_dlp/extractor/tvnow.py +++ b/yt_dlp/extractor/tvnow.py @@ -426,7 +426,7 @@ class TVNowIE(TVNowNewBaseIE): return self._extract_video(info, video_id, display_id) -class TVNowFilmIE(TVNowIE): +class TVNowFilmIE(TVNowIE): # XXX: Do not subclass from concrete IE _VALID_URL = r'''(?x) (?Phttps?:// (?:www\.)?tvnow\.(?:de|at|ch)/ diff --git a/yt_dlp/extractor/udemy.py b/yt_dlp/extractor/udemy.py index 1dc2dbdc4..2c8a35473 100644 --- a/yt_dlp/extractor/udemy.py +++ b/yt_dlp/extractor/udemy.py @@ -405,7 +405,7 @@ class UdemyIE(InfoExtractor): } -class UdemyCourseIE(UdemyIE): +class UdemyCourseIE(UdemyIE): # XXX: Do not subclass from concrete IE IE_NAME = 'udemy:course' _VALID_URL = r'https?://(?:[^/]+\.)?udemy\.com/(?P[^/?#&]+)' _TESTS = [{ diff --git a/yt_dlp/extractor/uplynk.py b/yt_dlp/extractor/uplynk.py index 04c96f388..9b560f719 100644 --- a/yt_dlp/extractor/uplynk.py +++ b/yt_dlp/extractor/uplynk.py @@ -52,10 +52,9 @@ class UplynkIE(InfoExtractor): return self._extract_uplynk_info(url) -class UplynkPreplayIE(UplynkIE): +class UplynkPreplayIE(UplynkIE): # XXX: Do not subclass from concrete IE IE_NAME = 'uplynk:preplay' _VALID_URL = r'https?://.*?\.uplynk\.com/preplay2?/(?Pext/[0-9a-f]{32}/(?P[^/?&]+)|(?P[0-9a-f]{32}))\.json' - _TEST = None def _real_extract(self, url): path, external_id, video_id = self._match_valid_url(url).groups() diff --git a/yt_dlp/extractor/usanetwork.py b/yt_dlp/extractor/usanetwork.py index d6b58a51c..4a06a9ad4 100644 --- a/yt_dlp/extractor/usanetwork.py +++ b/yt_dlp/extractor/usanetwork.py @@ -1,7 +1,7 @@ from .nbc import NBCIE -class USANetworkIE(NBCIE): +class USANetworkIE(NBCIE): # XXX: Do not subclass from concrete IE _VALID_URL = r'https?(?P://(?:www\.)?usanetwork\.com/(?:[^/]+/videos?|movies?)/(?:[^/]+/)?(?P\d+))' _TESTS = [{ 'url': 'https://www.usanetwork.com/peacock-trailers/video/intelligence-trailer/4185302', diff --git a/yt_dlp/extractor/veoh.py b/yt_dlp/extractor/veoh.py index a32c2fccb..d9b3ab115 100644 --- a/yt_dlp/extractor/veoh.py +++ b/yt_dlp/extractor/veoh.py @@ -130,7 +130,7 @@ class VeohIE(InfoExtractor): } -class VeohUserIE(VeohIE): +class VeohUserIE(VeohIE): # XXX: Do not subclass from concrete IE _VALID_URL = r'https?://(?:www\.)?veoh\.com/users/(?P[\w-]+)' IE_NAME = 'veoh:user' diff --git a/yt_dlp/extractor/vgtv.py b/yt_dlp/extractor/vgtv.py index 3e0af7fb2..b637afddf 100644 --- a/yt_dlp/extractor/vgtv.py +++ b/yt_dlp/extractor/vgtv.py @@ -9,7 +9,7 @@ from ..utils import ( ) -class VGTVIE(XstreamIE): +class VGTVIE(XstreamIE): # XXX: Do not subclass from concrete IE IE_DESC = 'VGTV, BTTV, FTV, Aftenposten and Aftonbladet' _GEO_BYPASS = False diff --git a/yt_dlp/extractor/vimeo.py b/yt_dlp/extractor/vimeo.py index 2e36b8861..1b21c0050 100644 --- a/yt_dlp/extractor/vimeo.py +++ b/yt_dlp/extractor/vimeo.py @@ -1004,7 +1004,7 @@ class VimeoIE(VimeoBaseInfoExtractor): return merge_dicts(info_dict, info_dict_config, json_ld) -class VimeoOndemandIE(VimeoIE): +class VimeoOndemandIE(VimeoIE): # XXX: Do not subclass from concrete IE IE_NAME = 'vimeo:ondemand' _VALID_URL = r'https?://(?:www\.)?vimeo\.com/ondemand/(?:[^/]+/)?(?P[^/?#&]+)' _TESTS = [{ @@ -1129,7 +1129,7 @@ class VimeoChannelIE(VimeoBaseInfoExtractor): return self._extract_videos(channel_id, self._BASE_URL_TEMPL % channel_id) -class VimeoUserIE(VimeoChannelIE): +class VimeoUserIE(VimeoChannelIE): # XXX: Do not subclass from concrete IE IE_NAME = 'vimeo:user' _VALID_URL = r'https://vimeo\.com/(?!(?:[0-9]+|watchlater)(?:$|[?#/]))(?P[^/]+)(?:/videos)?/?(?:$|[?#])' _TITLE_RE = r']+?class="user">([^<>]+?)' @@ -1239,7 +1239,7 @@ class VimeoAlbumIE(VimeoBaseInfoExtractor): entries, album_id, album.get('name'), album.get('description')) -class VimeoGroupsIE(VimeoChannelIE): +class VimeoGroupsIE(VimeoChannelIE): # XXX: Do not subclass from concrete IE IE_NAME = 'vimeo:group' _VALID_URL = r'https://vimeo\.com/groups/(?P[^/]+)(?:/(?!videos?/\d+)|$)' _TESTS = [{ @@ -1331,7 +1331,7 @@ class VimeoReviewIE(VimeoBaseInfoExtractor): return info_dict -class VimeoWatchLaterIE(VimeoChannelIE): +class VimeoWatchLaterIE(VimeoChannelIE): # XXX: Do not subclass from concrete IE IE_NAME = 'vimeo:watchlater' IE_DESC = 'Vimeo watch later list, ":vimeowatchlater" keyword (requires authentication)' _VALID_URL = r'https://vimeo\.com/(?:home/)?watchlater|:vimeowatchlater' @@ -1354,7 +1354,7 @@ class VimeoWatchLaterIE(VimeoChannelIE): return self._extract_videos('watchlater', 'https://vimeo.com/watchlater') -class VimeoLikesIE(VimeoChannelIE): +class VimeoLikesIE(VimeoChannelIE): # XXX: Do not subclass from concrete IE _VALID_URL = r'https://(?:www\.)?vimeo\.com/(?P[^/]+)/likes/?(?:$|[?#]|sort:)' IE_NAME = 'vimeo:likes' IE_DESC = 'Vimeo user likes' diff --git a/yt_dlp/extractor/vvvvid.py b/yt_dlp/extractor/vvvvid.py index f0156d10c..0c3e83a0a 100644 --- a/yt_dlp/extractor/vvvvid.py +++ b/yt_dlp/extractor/vvvvid.py @@ -242,7 +242,7 @@ class VVVVIDIE(InfoExtractor): return info -class VVVVIDShowIE(VVVVIDIE): +class VVVVIDShowIE(VVVVIDIE): # XXX: Do not subclass from concrete IE _VALID_URL = r'(?P%s(?P\d+)(?:/(?P[^/?&#]+))?)/?(?:[?#&]|$)' % VVVVIDIE._VALID_URL_BASE _TESTS = [{ 'url': 'https://www.vvvvid.it/show/156/psyco-pass', diff --git a/yt_dlp/extractor/wdr.py b/yt_dlp/extractor/wdr.py index d0ad69477..7b2e7c8e0 100644 --- a/yt_dlp/extractor/wdr.py +++ b/yt_dlp/extractor/wdr.py @@ -133,7 +133,7 @@ class WDRIE(InfoExtractor): } -class WDRPageIE(WDRIE): +class WDRPageIE(WDRIE): # XXX: Do not subclass from concrete IE _MAUS_REGEX = r'https?://(?:www\.)wdrmaus.de/(?:[^/]+/)*?(?P[^/?#.]+)(?:/?|/index\.php5|\.php5)$' _PAGE_REGEX = r'/(?:mediathek/)?(?:[^/]+/)*(?P[^/]+)\.html' _VALID_URL = r'https?://(?:www\d?\.)?(?:(?:kinder\.)?wdr\d?|sportschau)\.de' + _PAGE_REGEX + '|' + _MAUS_REGEX diff --git a/yt_dlp/extractor/youtube.py b/yt_dlp/extractor/youtube.py index 9d51f38ba..7e3530c0f 100644 --- a/yt_dlp/extractor/youtube.py +++ b/yt_dlp/extractor/youtube.py @@ -1051,7 +1051,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor): ]*\bhref="(?Phttps://www\.youtube\.com/watch\?v=[0-9A-Za-z_-]{11})" \s[^>]*\bclass="[^"]*\blazy-load-youtube''', ] - _RETURN_TYPE = 'video' # While there are "multifeed" test cases, they don't seem to actually exist anymore + _RETURN_TYPE = 'video' # XXX: How to handle multifeed? _PLAYER_INFO_RE = ( r'/s/player/(?P[a-zA-Z0-9_-]{8,})/player', @@ -1582,66 +1582,99 @@ class YoutubeIE(YoutubeBaseInfoExtractor): 'skip': 'This live event has ended.', }, { - # Multifeed videos (multiple cameras), URL is for Main Camera - 'url': 'https://www.youtube.com/watch?v=jvGDaLqkpTg', + # Multifeed videos (multiple cameras), URL can be of any Camera + 'url': 'https://www.youtube.com/watch?v=zaPI8MvL8pg', 'info_dict': { - 'id': 'jvGDaLqkpTg', - 'title': 'Tom Clancy Free Weekend Rainbow Whatever', - 'description': 'md5:e03b909557865076822aa169218d6a5d', + 'id': 'zaPI8MvL8pg', + 'title': 'Terraria 1.2 Live Stream | Let\'s Play - Part 04', + 'description': 'md5:563ccbc698b39298481ca3c571169519', }, 'playlist': [{ 'info_dict': { - 'id': 'jvGDaLqkpTg', + 'id': 'j5yGuxZ8lLU', 'ext': 'mp4', - 'title': 'Tom Clancy Free Weekend Rainbow Whatever (Main Camera)', - 'description': 'md5:e03b909557865076822aa169218d6a5d', - 'duration': 10643, - 'upload_date': '20161111', - 'uploader': 'Team PGP', - 'uploader_id': 'UChORY56LMMETTuGjXaJXvLg', - 'uploader_url': r're:https?://(?:www\.)?youtube\.com/channel/UChORY56LMMETTuGjXaJXvLg', + 'title': 'Terraria 1.2 Live Stream | Let\'s Play - Part 04 (Chris)', + 'uploader': 'WiiLikeToPlay', + 'description': 'md5:563ccbc698b39298481ca3c571169519', + 'uploader_url': 'http://www.youtube.com/user/WiiRikeToPray', + 'duration': 10120, + 'channel_follower_count': int, + 'channel_url': 'https://www.youtube.com/channel/UCN2XePorRokPB9TEgRZpddg', + 'availability': 'public', + 'playable_in_embed': True, + 'upload_date': '20131105', + 'uploader_id': 'WiiRikeToPray', + 'categories': ['Gaming'], + 'live_status': 'was_live', + 'tags': 'count:24', + 'release_timestamp': 1383701910, + 'thumbnail': 'https://i.ytimg.com/vi/j5yGuxZ8lLU/maxresdefault.jpg', + 'comment_count': int, + 'age_limit': 0, + 'like_count': int, + 'channel_id': 'UCN2XePorRokPB9TEgRZpddg', + 'channel': 'WiiLikeToPlay', + 'view_count': int, + 'release_date': '20131106', }, }, { 'info_dict': { - 'id': '3AKt1R1aDnw', + 'id': 'zaPI8MvL8pg', 'ext': 'mp4', - 'title': 'Tom Clancy Free Weekend Rainbow Whatever (Camera 2)', - 'description': 'md5:e03b909557865076822aa169218d6a5d', - 'duration': 10991, - 'upload_date': '20161111', - 'uploader': 'Team PGP', - 'uploader_id': 'UChORY56LMMETTuGjXaJXvLg', - 'uploader_url': r're:https?://(?:www\.)?youtube\.com/channel/UChORY56LMMETTuGjXaJXvLg', + 'title': 'Terraria 1.2 Live Stream | Let\'s Play - Part 04 (Tyson)', + 'uploader_id': 'WiiRikeToPray', + 'availability': 'public', + 'channel_url': 'https://www.youtube.com/channel/UCN2XePorRokPB9TEgRZpddg', + 'channel': 'WiiLikeToPlay', + 'uploader_url': 'http://www.youtube.com/user/WiiRikeToPray', + 'channel_follower_count': int, + 'description': 'md5:563ccbc698b39298481ca3c571169519', + 'duration': 10108, + 'age_limit': 0, + 'like_count': int, + 'tags': 'count:24', + 'channel_id': 'UCN2XePorRokPB9TEgRZpddg', + 'uploader': 'WiiLikeToPlay', + 'release_timestamp': 1383701915, + 'comment_count': int, + 'upload_date': '20131105', + 'thumbnail': 'https://i.ytimg.com/vi/zaPI8MvL8pg/maxresdefault.jpg', + 'release_date': '20131106', + 'playable_in_embed': True, + 'live_status': 'was_live', + 'categories': ['Gaming'], + 'view_count': int, }, }, { 'info_dict': { - 'id': 'RtAMM00gpVc', + 'id': 'R7r3vfO7Hao', 'ext': 'mp4', - 'title': 'Tom Clancy Free Weekend Rainbow Whatever (Camera 3)', - 'description': 'md5:e03b909557865076822aa169218d6a5d', - 'duration': 10995, - 'upload_date': '20161111', - 'uploader': 'Team PGP', - 'uploader_id': 'UChORY56LMMETTuGjXaJXvLg', - 'uploader_url': r're:https?://(?:www\.)?youtube\.com/channel/UChORY56LMMETTuGjXaJXvLg', - }, - }, { - 'info_dict': { - 'id': '6N2fdlP3C5U', - 'ext': 'mp4', - 'title': 'Tom Clancy Free Weekend Rainbow Whatever (Camera 4)', - 'description': 'md5:e03b909557865076822aa169218d6a5d', - 'duration': 10990, - 'upload_date': '20161111', - 'uploader': 'Team PGP', - 'uploader_id': 'UChORY56LMMETTuGjXaJXvLg', - 'uploader_url': r're:https?://(?:www\.)?youtube\.com/channel/UChORY56LMMETTuGjXaJXvLg', + 'title': 'Terraria 1.2 Live Stream | Let\'s Play - Part 04 (Spencer)', + 'thumbnail': 'https://i.ytimg.com/vi/R7r3vfO7Hao/maxresdefault.jpg', + 'channel_id': 'UCN2XePorRokPB9TEgRZpddg', + 'like_count': int, + 'availability': 'public', + 'playable_in_embed': True, + 'upload_date': '20131105', + 'description': 'md5:563ccbc698b39298481ca3c571169519', + 'uploader_id': 'WiiRikeToPray', + 'uploader_url': 'http://www.youtube.com/user/WiiRikeToPray', + 'channel_follower_count': int, + 'tags': 'count:24', + 'release_date': '20131106', + 'uploader': 'WiiLikeToPlay', + 'comment_count': int, + 'channel_url': 'https://www.youtube.com/channel/UCN2XePorRokPB9TEgRZpddg', + 'channel': 'WiiLikeToPlay', + 'categories': ['Gaming'], + 'release_timestamp': 1383701914, + 'live_status': 'was_live', + 'age_limit': 0, + 'duration': 10128, + 'view_count': int, }, }], - 'params': { - 'skip_download': True, - }, - 'skip': 'Not multifeed anymore', + 'params': {'skip_download': True}, }, { # Multifeed video with comma in title (see https://github.com/ytdl-org/youtube-dl/issues/8536) diff --git a/yt_dlp/utils.py b/yt_dlp/utils.py index a6bf897dc..7cba13678 100644 --- a/yt_dlp/utils.py +++ b/yt_dlp/utils.py @@ -5839,7 +5839,7 @@ def cached_method(f): bound_args.apply_defaults() key = tuple(bound_args.arguments.values())[1:] - cache = vars(self).setdefault('__cached_method__cache', {}).setdefault(f.__name__, {}) + cache = vars(self).setdefault('_cached_method__cache', {}).setdefault(f.__name__, {}) if key not in cache: cache[key] = f(self, *args, **kwargs) return cache[key]