aboutsummaryrefslogtreecommitdiff
path: root/test/test_plugins.py
diff options
context:
space:
mode:
authorcoletdjnz <coletdjnz@protonmail.com>2025-02-23 11:00:46 +1300
committerGitHub <noreply@github.com>2025-02-23 11:00:46 +1300
commit4445f37a7a66b248dbd8376c43137e6e441f138e (patch)
treeb37561f1213bc25420f1f1004e8b6c8560e8b92f /test/test_plugins.py
parent3a1583ca75fb523cbad0e5e174387ea7b477d175 (diff)
[core] Load plugins on demand (#11305)
- Adds `--no-plugin-dirs` to disable plugin loading - `--plugin-dirs` now supports post-processors Authored by: coletdjnz, Grub4K, pukkandan
Diffstat (limited to 'test/test_plugins.py')
-rw-r--r--test/test_plugins.py198
1 files changed, 176 insertions, 22 deletions
diff --git a/test/test_plugins.py b/test/test_plugins.py
index 77545d136..195726b18 100644
--- a/test/test_plugins.py
+++ b/test/test_plugins.py
@@ -10,22 +10,71 @@ TEST_DATA_DIR = Path(os.path.dirname(os.path.abspath(__file__)), 'testdata')
sys.path.append(str(TEST_DATA_DIR))
importlib.invalidate_caches()
-from yt_dlp.utils import Config
-from yt_dlp.plugins import PACKAGE_NAME, directories, load_plugins
+from yt_dlp.plugins import (
+ PACKAGE_NAME,
+ PluginSpec,
+ directories,
+ load_plugins,
+ load_all_plugins,
+ register_plugin_spec,
+)
+
+from yt_dlp.globals import (
+ extractors,
+ postprocessors,
+ plugin_dirs,
+ plugin_ies,
+ plugin_pps,
+ all_plugins_loaded,
+ plugin_specs,
+)
+
+
+EXTRACTOR_PLUGIN_SPEC = PluginSpec(
+ module_name='extractor',
+ suffix='IE',
+ destination=extractors,
+ plugin_destination=plugin_ies,
+)
+
+POSTPROCESSOR_PLUGIN_SPEC = PluginSpec(
+ module_name='postprocessor',
+ suffix='PP',
+ destination=postprocessors,
+ plugin_destination=plugin_pps,
+)
+
+
+def reset_plugins():
+ plugin_ies.value = {}
+ plugin_pps.value = {}
+ plugin_dirs.value = ['default']
+ plugin_specs.value = {}
+ all_plugins_loaded.value = False
+ # Clearing override plugins is probably difficult
+ for module_name in tuple(sys.modules):
+ for plugin_type in ('extractor', 'postprocessor'):
+ if module_name.startswith(f'{PACKAGE_NAME}.{plugin_type}.'):
+ del sys.modules[module_name]
+
+ importlib.invalidate_caches()
class TestPlugins(unittest.TestCase):
TEST_PLUGIN_DIR = TEST_DATA_DIR / PACKAGE_NAME
+ def setUp(self):
+ reset_plugins()
+
+ def tearDown(self):
+ reset_plugins()
+
def test_directories_containing_plugins(self):
self.assertIn(self.TEST_PLUGIN_DIR, map(Path, directories()))
def test_extractor_classes(self):
- for module_name in tuple(sys.modules):
- if module_name.startswith(f'{PACKAGE_NAME}.extractor'):
- del sys.modules[module_name]
- plugins_ie = load_plugins('extractor', 'IE')
+ plugins_ie = load_plugins(EXTRACTOR_PLUGIN_SPEC)
self.assertIn(f'{PACKAGE_NAME}.extractor.normal', sys.modules.keys())
self.assertIn('NormalPluginIE', plugins_ie.keys())
@@ -35,17 +84,29 @@ class TestPlugins(unittest.TestCase):
f'{PACKAGE_NAME}.extractor._ignore' in sys.modules,
'loaded module beginning with underscore')
self.assertNotIn('IgnorePluginIE', plugins_ie.keys())
+ self.assertNotIn('IgnorePluginIE', plugin_ies.value)
# Don't load extractors with underscore prefix
self.assertNotIn('_IgnoreUnderscorePluginIE', plugins_ie.keys())
+ self.assertNotIn('_IgnoreUnderscorePluginIE', plugin_ies.value)
# Don't load extractors not specified in __all__ (if supplied)
self.assertNotIn('IgnoreNotInAllPluginIE', plugins_ie.keys())
+ self.assertNotIn('IgnoreNotInAllPluginIE', plugin_ies.value)
self.assertIn('InAllPluginIE', plugins_ie.keys())
+ self.assertIn('InAllPluginIE', plugin_ies.value)
+
+ # Don't load override extractors
+ self.assertNotIn('OverrideGenericIE', plugins_ie.keys())
+ self.assertNotIn('OverrideGenericIE', plugin_ies.value)
+ self.assertNotIn('_UnderscoreOverrideGenericIE', plugins_ie.keys())
+ self.assertNotIn('_UnderscoreOverrideGenericIE', plugin_ies.value)
def test_postprocessor_classes(self):
- plugins_pp = load_plugins('postprocessor', 'PP')
+ plugins_pp = load_plugins(POSTPROCESSOR_PLUGIN_SPEC)
self.assertIn('NormalPluginPP', plugins_pp.keys())
+ self.assertIn(f'{PACKAGE_NAME}.postprocessor.normal', sys.modules.keys())
+ self.assertIn('NormalPluginPP', plugin_pps.value)
def test_importing_zipped_module(self):
zip_path = TEST_DATA_DIR / 'zipped_plugins.zip'
@@ -58,10 +119,10 @@ class TestPlugins(unittest.TestCase):
package = importlib.import_module(f'{PACKAGE_NAME}.{plugin_type}')
self.assertIn(zip_path / PACKAGE_NAME / plugin_type, map(Path, package.__path__))
- plugins_ie = load_plugins('extractor', 'IE')
+ plugins_ie = load_plugins(EXTRACTOR_PLUGIN_SPEC)
self.assertIn('ZippedPluginIE', plugins_ie.keys())
- plugins_pp = load_plugins('postprocessor', 'PP')
+ plugins_pp = load_plugins(POSTPROCESSOR_PLUGIN_SPEC)
self.assertIn('ZippedPluginPP', plugins_pp.keys())
finally:
@@ -69,23 +130,116 @@ class TestPlugins(unittest.TestCase):
os.remove(zip_path)
importlib.invalidate_caches() # reset the import caches
- def test_plugin_dirs(self):
- # Internal plugin dirs hack for CLI --plugin-dirs
- # To be replaced with proper system later
- custom_plugin_dir = TEST_DATA_DIR / 'plugin_packages'
- Config._plugin_dirs = [str(custom_plugin_dir)]
- importlib.invalidate_caches() # reset the import caches
+ def test_reloading_plugins(self):
+ reload_plugins_path = TEST_DATA_DIR / 'reload_plugins'
+ load_plugins(EXTRACTOR_PLUGIN_SPEC)
+ load_plugins(POSTPROCESSOR_PLUGIN_SPEC)
+ # Remove default folder and add reload_plugin path
+ sys.path.remove(str(TEST_DATA_DIR))
+ sys.path.append(str(reload_plugins_path))
+ importlib.invalidate_caches()
try:
- package = importlib.import_module(f'{PACKAGE_NAME}.extractor')
- self.assertIn(custom_plugin_dir / 'testpackage' / PACKAGE_NAME / 'extractor', map(Path, package.__path__))
-
- plugins_ie = load_plugins('extractor', 'IE')
- self.assertIn('PackagePluginIE', plugins_ie.keys())
+ for plugin_type in ('extractor', 'postprocessor'):
+ package = importlib.import_module(f'{PACKAGE_NAME}.{plugin_type}')
+ self.assertIn(reload_plugins_path / PACKAGE_NAME / plugin_type, map(Path, package.__path__))
+
+ plugins_ie = load_plugins(EXTRACTOR_PLUGIN_SPEC)
+ self.assertIn('NormalPluginIE', plugins_ie.keys())
+ self.assertTrue(
+ plugins_ie['NormalPluginIE'].REPLACED,
+ msg='Reloading has not replaced original extractor plugin')
+ self.assertTrue(
+ extractors.value['NormalPluginIE'].REPLACED,
+ msg='Reloading has not replaced original extractor plugin globally')
+
+ plugins_pp = load_plugins(POSTPROCESSOR_PLUGIN_SPEC)
+ self.assertIn('NormalPluginPP', plugins_pp.keys())
+ self.assertTrue(plugins_pp['NormalPluginPP'].REPLACED,
+ msg='Reloading has not replaced original postprocessor plugin')
+ self.assertTrue(
+ postprocessors.value['NormalPluginPP'].REPLACED,
+ msg='Reloading has not replaced original postprocessor plugin globally')
finally:
- Config._plugin_dirs = []
- importlib.invalidate_caches() # reset the import caches
+ sys.path.remove(str(reload_plugins_path))
+ sys.path.append(str(TEST_DATA_DIR))
+ importlib.invalidate_caches()
+
+ def test_extractor_override_plugin(self):
+ load_plugins(EXTRACTOR_PLUGIN_SPEC)
+
+ from yt_dlp.extractor.generic import GenericIE
+
+ self.assertEqual(GenericIE.TEST_FIELD, 'override')
+ self.assertEqual(GenericIE.SECONDARY_TEST_FIELD, 'underscore-override')
+
+ self.assertEqual(GenericIE.IE_NAME, 'generic+override+underscore-override')
+ importlib.invalidate_caches()
+ # test that loading a second time doesn't wrap a second time
+ load_plugins(EXTRACTOR_PLUGIN_SPEC)
+ from yt_dlp.extractor.generic import GenericIE
+ self.assertEqual(GenericIE.IE_NAME, 'generic+override+underscore-override')
+
+ def test_load_all_plugin_types(self):
+
+ # no plugin specs registered
+ load_all_plugins()
+
+ self.assertNotIn(f'{PACKAGE_NAME}.extractor.normal', sys.modules.keys())
+ self.assertNotIn(f'{PACKAGE_NAME}.postprocessor.normal', sys.modules.keys())
+
+ register_plugin_spec(EXTRACTOR_PLUGIN_SPEC)
+ register_plugin_spec(POSTPROCESSOR_PLUGIN_SPEC)
+ load_all_plugins()
+ self.assertTrue(all_plugins_loaded.value)
+
+ self.assertIn(f'{PACKAGE_NAME}.extractor.normal', sys.modules.keys())
+ self.assertIn(f'{PACKAGE_NAME}.postprocessor.normal', sys.modules.keys())
+
+ def test_no_plugin_dirs(self):
+ register_plugin_spec(EXTRACTOR_PLUGIN_SPEC)
+ register_plugin_spec(POSTPROCESSOR_PLUGIN_SPEC)
+
+ plugin_dirs.value = []
+ load_all_plugins()
+
+ self.assertNotIn(f'{PACKAGE_NAME}.extractor.normal', sys.modules.keys())
+ self.assertNotIn(f'{PACKAGE_NAME}.postprocessor.normal', sys.modules.keys())
+
+ def test_set_plugin_dirs(self):
+ custom_plugin_dir = str(TEST_DATA_DIR / 'plugin_packages')
+ plugin_dirs.value = [custom_plugin_dir]
+
+ load_plugins(EXTRACTOR_PLUGIN_SPEC)
+
+ self.assertIn(f'{PACKAGE_NAME}.extractor.package', sys.modules.keys())
+ self.assertIn('PackagePluginIE', plugin_ies.value)
+
+ def test_invalid_plugin_dir(self):
+ plugin_dirs.value = ['invalid_dir']
+ with self.assertRaises(ValueError):
+ load_plugins(EXTRACTOR_PLUGIN_SPEC)
+
+ def test_append_plugin_dirs(self):
+ custom_plugin_dir = str(TEST_DATA_DIR / 'plugin_packages')
+
+ self.assertEqual(plugin_dirs.value, ['default'])
+ plugin_dirs.value.append(custom_plugin_dir)
+ self.assertEqual(plugin_dirs.value, ['default', custom_plugin_dir])
+
+ load_plugins(EXTRACTOR_PLUGIN_SPEC)
+
+ self.assertIn(f'{PACKAGE_NAME}.extractor.package', sys.modules.keys())
+ self.assertIn('PackagePluginIE', plugin_ies.value)
+
+ def test_get_plugin_spec(self):
+ register_plugin_spec(EXTRACTOR_PLUGIN_SPEC)
+ register_plugin_spec(POSTPROCESSOR_PLUGIN_SPEC)
+
+ self.assertEqual(plugin_specs.value.get('extractor'), EXTRACTOR_PLUGIN_SPEC)
+ self.assertEqual(plugin_specs.value.get('postprocessor'), POSTPROCESSOR_PLUGIN_SPEC)
+ self.assertIsNone(plugin_specs.value.get('invalid'))
if __name__ == '__main__':