2021-06-22 22:25:35 +02:00
|
|
|
import re
|
2020-10-10 07:09:32 +02:00
|
|
|
import unittest
|
2023-01-06 22:54:16 +01:00
|
|
|
import warnings
|
plugin: remove Plugin.bind()
This changes the way how the Streamlink session and other objects like
the plugin cache and logger are stored on each plugin.
Previously, those objects were set as class attributes on every `Plugin`
class via `Plugin.bind()` when loading plugins via the session's
`load_plugins()` method that gets called on initialization.
This meant that whenever a new Streamlink session was initialized,
references to it (including a dict of every loaded plugin) were set
on each `Plugin` class as a class attribute, and Python's garbage
collector could not get rid of this memory when deleting the session
instance that was created last.
Removing `Plugin.bind()`, passing the session via the `Plugin.__init__`
constructor, and setting the cache, logger, etc. on `Plugin` instances
instead (only one gets initialized by `streamlink_cli`), removes those
static references that prevent the garbage collector to work.
Since the plugin "module" name now doesn't get set via `Plugin.bind()`
anymore, it derives its name via `self.__class__.__module__` on its own,
which means a change of the return type of `Streamlink.resolve_url()`
is necessary in order to pass the plugin name to `streamlink_cli`,
so that it can load config files and initialize plugin arguments, etc.
Breaking changes:
- Remove `Plugin.bind()`
- Pass the `session` instance via the Plugin constructor and set the
`module`, `cache` and `logger` on the plugin instance instead.
Derive `module` from the actual module name.
- Change the return type of `Session.resolve_url()` and include the
resolved plugin name in the returned tuple
Other changes:
- Remove `pluginclass.bind()` call from `Session.load_plugins()` and
use the loader's module name directly on the `Session.plugins` dict
- Remove initialization check from `Plugin` cookie methods
- Update streamlink_cli.main module according to breaking changes
- Update tests respectively
- Add explicit plugin initialization test
- Update tests with plugin constructors and custom plugin names
- Move testplugin override module, so that it shares the same module
name as the main testplugin module. Rel `Session.load_plugins()`
- Refactor most session tests and replace unneeded `resolve_url()`
wrappers in favor of calling `session.streams()`
2022-08-25 10:55:38 +02:00
|
|
|
from pathlib import Path
|
2021-01-09 18:45:04 +01:00
|
|
|
from socket import AF_INET, AF_INET6
|
|
|
|
from unittest.mock import Mock, call, patch
|
|
|
|
|
plugin: remove Plugin.bind()
This changes the way how the Streamlink session and other objects like
the plugin cache and logger are stored on each plugin.
Previously, those objects were set as class attributes on every `Plugin`
class via `Plugin.bind()` when loading plugins via the session's
`load_plugins()` method that gets called on initialization.
This meant that whenever a new Streamlink session was initialized,
references to it (including a dict of every loaded plugin) were set
on each `Plugin` class as a class attribute, and Python's garbage
collector could not get rid of this memory when deleting the session
instance that was created last.
Removing `Plugin.bind()`, passing the session via the `Plugin.__init__`
constructor, and setting the cache, logger, etc. on `Plugin` instances
instead (only one gets initialized by `streamlink_cli`), removes those
static references that prevent the garbage collector to work.
Since the plugin "module" name now doesn't get set via `Plugin.bind()`
anymore, it derives its name via `self.__class__.__module__` on its own,
which means a change of the return type of `Streamlink.resolve_url()`
is necessary in order to pass the plugin name to `streamlink_cli`,
so that it can load config files and initialize plugin arguments, etc.
Breaking changes:
- Remove `Plugin.bind()`
- Pass the `session` instance via the Plugin constructor and set the
`module`, `cache` and `logger` on the plugin instance instead.
Derive `module` from the actual module name.
- Change the return type of `Session.resolve_url()` and include the
resolved plugin name in the returned tuple
Other changes:
- Remove `pluginclass.bind()` call from `Session.load_plugins()` and
use the loader's module name directly on the `Session.plugins` dict
- Remove initialization check from `Plugin` cookie methods
- Update streamlink_cli.main module according to breaking changes
- Update tests respectively
- Add explicit plugin initialization test
- Update tests with plugin constructors and custom plugin names
- Move testplugin override module, so that it shares the same module
name as the main testplugin module. Rel `Session.load_plugins()`
- Refactor most session tests and replace unneeded `resolve_url()`
wrappers in favor of calling `session.streams()`
2022-08-25 10:55:38 +02:00
|
|
|
import pytest
|
2021-11-10 10:52:12 +01:00
|
|
|
import requests_mock
|
2022-05-11 14:37:09 +02:00
|
|
|
import urllib3
|
2012-11-19 03:47:13 +01:00
|
|
|
|
plugin: remove Plugin.bind()
This changes the way how the Streamlink session and other objects like
the plugin cache and logger are stored on each plugin.
Previously, those objects were set as class attributes on every `Plugin`
class via `Plugin.bind()` when loading plugins via the session's
`load_plugins()` method that gets called on initialization.
This meant that whenever a new Streamlink session was initialized,
references to it (including a dict of every loaded plugin) were set
on each `Plugin` class as a class attribute, and Python's garbage
collector could not get rid of this memory when deleting the session
instance that was created last.
Removing `Plugin.bind()`, passing the session via the `Plugin.__init__`
constructor, and setting the cache, logger, etc. on `Plugin` instances
instead (only one gets initialized by `streamlink_cli`), removes those
static references that prevent the garbage collector to work.
Since the plugin "module" name now doesn't get set via `Plugin.bind()`
anymore, it derives its name via `self.__class__.__module__` on its own,
which means a change of the return type of `Streamlink.resolve_url()`
is necessary in order to pass the plugin name to `streamlink_cli`,
so that it can load config files and initialize plugin arguments, etc.
Breaking changes:
- Remove `Plugin.bind()`
- Pass the `session` instance via the Plugin constructor and set the
`module`, `cache` and `logger` on the plugin instance instead.
Derive `module` from the actual module name.
- Change the return type of `Session.resolve_url()` and include the
resolved plugin name in the returned tuple
Other changes:
- Remove `pluginclass.bind()` call from `Session.load_plugins()` and
use the loader's module name directly on the `Session.plugins` dict
- Remove initialization check from `Plugin` cookie methods
- Update streamlink_cli.main module according to breaking changes
- Update tests respectively
- Add explicit plugin initialization test
- Update tests with plugin constructors and custom plugin names
- Move testplugin override module, so that it shares the same module
name as the main testplugin module. Rel `Session.load_plugins()`
- Refactor most session tests and replace unneeded `resolve_url()`
wrappers in favor of calling `session.streams()`
2022-08-25 10:55:38 +02:00
|
|
|
import tests.plugin
|
2023-03-24 15:33:48 +01:00
|
|
|
from streamlink.exceptions import NoPluginError, StreamlinkDeprecationWarning
|
2023-02-09 15:44:12 +01:00
|
|
|
from streamlink.plugin import HIGH_PRIORITY, LOW_PRIORITY, NO_PRIORITY, NORMAL_PRIORITY, Plugin, pluginmatcher
|
2023-03-24 15:33:48 +01:00
|
|
|
from streamlink.session import Streamlink
|
2021-09-20 09:46:12 +02:00
|
|
|
from streamlink.stream.hls import HLSStream
|
|
|
|
from streamlink.stream.http import HTTPStream
|
2012-11-19 03:47:13 +01:00
|
|
|
|
|
|
|
|
plugin: remove Plugin.bind()
This changes the way how the Streamlink session and other objects like
the plugin cache and logger are stored on each plugin.
Previously, those objects were set as class attributes on every `Plugin`
class via `Plugin.bind()` when loading plugins via the session's
`load_plugins()` method that gets called on initialization.
This meant that whenever a new Streamlink session was initialized,
references to it (including a dict of every loaded plugin) were set
on each `Plugin` class as a class attribute, and Python's garbage
collector could not get rid of this memory when deleting the session
instance that was created last.
Removing `Plugin.bind()`, passing the session via the `Plugin.__init__`
constructor, and setting the cache, logger, etc. on `Plugin` instances
instead (only one gets initialized by `streamlink_cli`), removes those
static references that prevent the garbage collector to work.
Since the plugin "module" name now doesn't get set via `Plugin.bind()`
anymore, it derives its name via `self.__class__.__module__` on its own,
which means a change of the return type of `Streamlink.resolve_url()`
is necessary in order to pass the plugin name to `streamlink_cli`,
so that it can load config files and initialize plugin arguments, etc.
Breaking changes:
- Remove `Plugin.bind()`
- Pass the `session` instance via the Plugin constructor and set the
`module`, `cache` and `logger` on the plugin instance instead.
Derive `module` from the actual module name.
- Change the return type of `Session.resolve_url()` and include the
resolved plugin name in the returned tuple
Other changes:
- Remove `pluginclass.bind()` call from `Session.load_plugins()` and
use the loader's module name directly on the `Session.plugins` dict
- Remove initialization check from `Plugin` cookie methods
- Update streamlink_cli.main module according to breaking changes
- Update tests respectively
- Add explicit plugin initialization test
- Update tests with plugin constructors and custom plugin names
- Move testplugin override module, so that it shares the same module
name as the main testplugin module. Rel `Session.load_plugins()`
- Refactor most session tests and replace unneeded `resolve_url()`
wrappers in favor of calling `session.streams()`
2022-08-25 10:55:38 +02:00
|
|
|
PATH_TESTPLUGINS = Path(tests.plugin.__path__[0])
|
|
|
|
PATH_TESTPLUGINS_OVERRIDE = PATH_TESTPLUGINS / "override"
|
|
|
|
|
2022-05-21 00:38:18 +02:00
|
|
|
_original_allowed_gai_family = urllib3.util.connection.allowed_gai_family # type: ignore[attr-defined]
|
2022-05-11 14:37:09 +02:00
|
|
|
|
|
|
|
|
2021-06-22 22:25:35 +02:00
|
|
|
class EmptyPlugin(Plugin):
|
|
|
|
def _get_streams(self):
|
|
|
|
pass # pragma: no cover
|
|
|
|
|
|
|
|
|
2023-01-06 22:54:16 +01:00
|
|
|
# TODO: rewrite using pytest
|
2012-11-19 03:47:13 +01:00
|
|
|
class TestSession(unittest.TestCase):
|
2021-11-10 10:52:12 +01:00
|
|
|
mocker: requests_mock.Mocker
|
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
self.mocker = requests_mock.Mocker()
|
|
|
|
self.mocker.register_uri(requests_mock.ANY, requests_mock.ANY, text="")
|
|
|
|
self.mocker.start()
|
|
|
|
|
|
|
|
def tearDown(self):
|
|
|
|
self.mocker.stop()
|
|
|
|
Streamlink.resolve_url.cache_clear()
|
|
|
|
|
2020-12-04 21:26:18 +01:00
|
|
|
def subject(self, load_plugins=True):
|
|
|
|
session = Streamlink()
|
|
|
|
if load_plugins:
|
plugin: remove Plugin.bind()
This changes the way how the Streamlink session and other objects like
the plugin cache and logger are stored on each plugin.
Previously, those objects were set as class attributes on every `Plugin`
class via `Plugin.bind()` when loading plugins via the session's
`load_plugins()` method that gets called on initialization.
This meant that whenever a new Streamlink session was initialized,
references to it (including a dict of every loaded plugin) were set
on each `Plugin` class as a class attribute, and Python's garbage
collector could not get rid of this memory when deleting the session
instance that was created last.
Removing `Plugin.bind()`, passing the session via the `Plugin.__init__`
constructor, and setting the cache, logger, etc. on `Plugin` instances
instead (only one gets initialized by `streamlink_cli`), removes those
static references that prevent the garbage collector to work.
Since the plugin "module" name now doesn't get set via `Plugin.bind()`
anymore, it derives its name via `self.__class__.__module__` on its own,
which means a change of the return type of `Streamlink.resolve_url()`
is necessary in order to pass the plugin name to `streamlink_cli`,
so that it can load config files and initialize plugin arguments, etc.
Breaking changes:
- Remove `Plugin.bind()`
- Pass the `session` instance via the Plugin constructor and set the
`module`, `cache` and `logger` on the plugin instance instead.
Derive `module` from the actual module name.
- Change the return type of `Session.resolve_url()` and include the
resolved plugin name in the returned tuple
Other changes:
- Remove `pluginclass.bind()` call from `Session.load_plugins()` and
use the loader's module name directly on the `Session.plugins` dict
- Remove initialization check from `Plugin` cookie methods
- Update streamlink_cli.main module according to breaking changes
- Update tests respectively
- Add explicit plugin initialization test
- Update tests with plugin constructors and custom plugin names
- Move testplugin override module, so that it shares the same module
name as the main testplugin module. Rel `Session.load_plugins()`
- Refactor most session tests and replace unneeded `resolve_url()`
wrappers in favor of calling `session.streams()`
2022-08-25 10:55:38 +02:00
|
|
|
session.load_plugins(str(PATH_TESTPLUGINS))
|
|
|
|
session.load_plugins(str(PATH_TESTPLUGINS_OVERRIDE))
|
2020-12-04 21:26:18 +01:00
|
|
|
|
|
|
|
return session
|
2012-11-19 03:47:13 +01:00
|
|
|
|
2021-11-10 10:52:12 +01:00
|
|
|
# ----
|
2012-11-19 03:47:13 +01:00
|
|
|
|
|
|
|
def test_load_plugins(self):
|
2020-12-04 21:26:18 +01:00
|
|
|
session = self.subject()
|
|
|
|
plugins = session.get_plugins()
|
plugin: remove Plugin.bind()
This changes the way how the Streamlink session and other objects like
the plugin cache and logger are stored on each plugin.
Previously, those objects were set as class attributes on every `Plugin`
class via `Plugin.bind()` when loading plugins via the session's
`load_plugins()` method that gets called on initialization.
This meant that whenever a new Streamlink session was initialized,
references to it (including a dict of every loaded plugin) were set
on each `Plugin` class as a class attribute, and Python's garbage
collector could not get rid of this memory when deleting the session
instance that was created last.
Removing `Plugin.bind()`, passing the session via the `Plugin.__init__`
constructor, and setting the cache, logger, etc. on `Plugin` instances
instead (only one gets initialized by `streamlink_cli`), removes those
static references that prevent the garbage collector to work.
Since the plugin "module" name now doesn't get set via `Plugin.bind()`
anymore, it derives its name via `self.__class__.__module__` on its own,
which means a change of the return type of `Streamlink.resolve_url()`
is necessary in order to pass the plugin name to `streamlink_cli`,
so that it can load config files and initialize plugin arguments, etc.
Breaking changes:
- Remove `Plugin.bind()`
- Pass the `session` instance via the Plugin constructor and set the
`module`, `cache` and `logger` on the plugin instance instead.
Derive `module` from the actual module name.
- Change the return type of `Session.resolve_url()` and include the
resolved plugin name in the returned tuple
Other changes:
- Remove `pluginclass.bind()` call from `Session.load_plugins()` and
use the loader's module name directly on the `Session.plugins` dict
- Remove initialization check from `Plugin` cookie methods
- Update streamlink_cli.main module according to breaking changes
- Update tests respectively
- Add explicit plugin initialization test
- Update tests with plugin constructors and custom plugin names
- Move testplugin override module, so that it shares the same module
name as the main testplugin module. Rel `Session.load_plugins()`
- Refactor most session tests and replace unneeded `resolve_url()`
wrappers in favor of calling `session.streams()`
2022-08-25 10:55:38 +02:00
|
|
|
assert "testplugin" in plugins
|
|
|
|
assert "testplugin_missing" not in plugins
|
|
|
|
assert "testplugin_invalid" not in plugins
|
2012-11-19 03:47:13 +01:00
|
|
|
|
2020-12-04 21:26:18 +01:00
|
|
|
def test_load_plugins_builtin(self):
|
|
|
|
session = self.subject()
|
|
|
|
plugins = session.get_plugins()
|
plugin: remove Plugin.bind()
This changes the way how the Streamlink session and other objects like
the plugin cache and logger are stored on each plugin.
Previously, those objects were set as class attributes on every `Plugin`
class via `Plugin.bind()` when loading plugins via the session's
`load_plugins()` method that gets called on initialization.
This meant that whenever a new Streamlink session was initialized,
references to it (including a dict of every loaded plugin) were set
on each `Plugin` class as a class attribute, and Python's garbage
collector could not get rid of this memory when deleting the session
instance that was created last.
Removing `Plugin.bind()`, passing the session via the `Plugin.__init__`
constructor, and setting the cache, logger, etc. on `Plugin` instances
instead (only one gets initialized by `streamlink_cli`), removes those
static references that prevent the garbage collector to work.
Since the plugin "module" name now doesn't get set via `Plugin.bind()`
anymore, it derives its name via `self.__class__.__module__` on its own,
which means a change of the return type of `Streamlink.resolve_url()`
is necessary in order to pass the plugin name to `streamlink_cli`,
so that it can load config files and initialize plugin arguments, etc.
Breaking changes:
- Remove `Plugin.bind()`
- Pass the `session` instance via the Plugin constructor and set the
`module`, `cache` and `logger` on the plugin instance instead.
Derive `module` from the actual module name.
- Change the return type of `Session.resolve_url()` and include the
resolved plugin name in the returned tuple
Other changes:
- Remove `pluginclass.bind()` call from `Session.load_plugins()` and
use the loader's module name directly on the `Session.plugins` dict
- Remove initialization check from `Plugin` cookie methods
- Update streamlink_cli.main module according to breaking changes
- Update tests respectively
- Add explicit plugin initialization test
- Update tests with plugin constructors and custom plugin names
- Move testplugin override module, so that it shares the same module
name as the main testplugin module. Rel `Session.load_plugins()`
- Refactor most session tests and replace unneeded `resolve_url()`
wrappers in favor of calling `session.streams()`
2022-08-25 10:55:38 +02:00
|
|
|
assert "twitch" in plugins
|
|
|
|
assert plugins["twitch"].__module__ == "streamlink.plugins.twitch"
|
2020-11-26 01:14:39 +01:00
|
|
|
|
2020-12-04 21:26:18 +01:00
|
|
|
@patch("streamlink.session.log")
|
|
|
|
def test_load_plugins_override(self, mock_log):
|
|
|
|
session = self.subject()
|
|
|
|
plugins = session.get_plugins()
|
plugin: remove Plugin.bind()
This changes the way how the Streamlink session and other objects like
the plugin cache and logger are stored on each plugin.
Previously, those objects were set as class attributes on every `Plugin`
class via `Plugin.bind()` when loading plugins via the session's
`load_plugins()` method that gets called on initialization.
This meant that whenever a new Streamlink session was initialized,
references to it (including a dict of every loaded plugin) were set
on each `Plugin` class as a class attribute, and Python's garbage
collector could not get rid of this memory when deleting the session
instance that was created last.
Removing `Plugin.bind()`, passing the session via the `Plugin.__init__`
constructor, and setting the cache, logger, etc. on `Plugin` instances
instead (only one gets initialized by `streamlink_cli`), removes those
static references that prevent the garbage collector to work.
Since the plugin "module" name now doesn't get set via `Plugin.bind()`
anymore, it derives its name via `self.__class__.__module__` on its own,
which means a change of the return type of `Streamlink.resolve_url()`
is necessary in order to pass the plugin name to `streamlink_cli`,
so that it can load config files and initialize plugin arguments, etc.
Breaking changes:
- Remove `Plugin.bind()`
- Pass the `session` instance via the Plugin constructor and set the
`module`, `cache` and `logger` on the plugin instance instead.
Derive `module` from the actual module name.
- Change the return type of `Session.resolve_url()` and include the
resolved plugin name in the returned tuple
Other changes:
- Remove `pluginclass.bind()` call from `Session.load_plugins()` and
use the loader's module name directly on the `Session.plugins` dict
- Remove initialization check from `Plugin` cookie methods
- Update streamlink_cli.main module according to breaking changes
- Update tests respectively
- Add explicit plugin initialization test
- Update tests with plugin constructors and custom plugin names
- Move testplugin override module, so that it shares the same module
name as the main testplugin module. Rel `Session.load_plugins()`
- Refactor most session tests and replace unneeded `resolve_url()`
wrappers in favor of calling `session.streams()`
2022-08-25 10:55:38 +02:00
|
|
|
assert "testplugin" in plugins
|
|
|
|
assert "testplugin_override" not in plugins
|
|
|
|
assert plugins["testplugin"].__name__ == "TestPluginOverride"
|
|
|
|
assert plugins["testplugin"].__module__ == "streamlink.plugins.testplugin"
|
|
|
|
assert mock_log.debug.call_args_list == [
|
|
|
|
call(f"Plugin testplugin is being overridden by {PATH_TESTPLUGINS_OVERRIDE / 'testplugin.py'}"),
|
|
|
|
]
|
2020-12-04 21:26:18 +01:00
|
|
|
|
|
|
|
@patch("streamlink.session.load_module")
|
|
|
|
@patch("streamlink.session.log")
|
|
|
|
def test_load_plugins_importerror(self, mock_log, mock_load_module):
|
|
|
|
mock_load_module.side_effect = ImportError()
|
|
|
|
session = self.subject()
|
plugin: remove Plugin.bind()
This changes the way how the Streamlink session and other objects like
the plugin cache and logger are stored on each plugin.
Previously, those objects were set as class attributes on every `Plugin`
class via `Plugin.bind()` when loading plugins via the session's
`load_plugins()` method that gets called on initialization.
This meant that whenever a new Streamlink session was initialized,
references to it (including a dict of every loaded plugin) were set
on each `Plugin` class as a class attribute, and Python's garbage
collector could not get rid of this memory when deleting the session
instance that was created last.
Removing `Plugin.bind()`, passing the session via the `Plugin.__init__`
constructor, and setting the cache, logger, etc. on `Plugin` instances
instead (only one gets initialized by `streamlink_cli`), removes those
static references that prevent the garbage collector to work.
Since the plugin "module" name now doesn't get set via `Plugin.bind()`
anymore, it derives its name via `self.__class__.__module__` on its own,
which means a change of the return type of `Streamlink.resolve_url()`
is necessary in order to pass the plugin name to `streamlink_cli`,
so that it can load config files and initialize plugin arguments, etc.
Breaking changes:
- Remove `Plugin.bind()`
- Pass the `session` instance via the Plugin constructor and set the
`module`, `cache` and `logger` on the plugin instance instead.
Derive `module` from the actual module name.
- Change the return type of `Session.resolve_url()` and include the
resolved plugin name in the returned tuple
Other changes:
- Remove `pluginclass.bind()` call from `Session.load_plugins()` and
use the loader's module name directly on the `Session.plugins` dict
- Remove initialization check from `Plugin` cookie methods
- Update streamlink_cli.main module according to breaking changes
- Update tests respectively
- Add explicit plugin initialization test
- Update tests with plugin constructors and custom plugin names
- Move testplugin override module, so that it shares the same module
name as the main testplugin module. Rel `Session.load_plugins()`
- Refactor most session tests and replace unneeded `resolve_url()`
wrappers in favor of calling `session.streams()`
2022-08-25 10:55:38 +02:00
|
|
|
assert not session.get_plugins()
|
|
|
|
assert len(mock_log.exception.call_args_list) > 0
|
2020-12-04 21:26:18 +01:00
|
|
|
|
|
|
|
@patch("streamlink.session.load_module")
|
|
|
|
@patch("streamlink.session.log")
|
|
|
|
def test_load_plugins_syntaxerror(self, mock_log, mock_load_module):
|
|
|
|
mock_load_module.side_effect = SyntaxError()
|
plugin: remove Plugin.bind()
This changes the way how the Streamlink session and other objects like
the plugin cache and logger are stored on each plugin.
Previously, those objects were set as class attributes on every `Plugin`
class via `Plugin.bind()` when loading plugins via the session's
`load_plugins()` method that gets called on initialization.
This meant that whenever a new Streamlink session was initialized,
references to it (including a dict of every loaded plugin) were set
on each `Plugin` class as a class attribute, and Python's garbage
collector could not get rid of this memory when deleting the session
instance that was created last.
Removing `Plugin.bind()`, passing the session via the `Plugin.__init__`
constructor, and setting the cache, logger, etc. on `Plugin` instances
instead (only one gets initialized by `streamlink_cli`), removes those
static references that prevent the garbage collector to work.
Since the plugin "module" name now doesn't get set via `Plugin.bind()`
anymore, it derives its name via `self.__class__.__module__` on its own,
which means a change of the return type of `Streamlink.resolve_url()`
is necessary in order to pass the plugin name to `streamlink_cli`,
so that it can load config files and initialize plugin arguments, etc.
Breaking changes:
- Remove `Plugin.bind()`
- Pass the `session` instance via the Plugin constructor and set the
`module`, `cache` and `logger` on the plugin instance instead.
Derive `module` from the actual module name.
- Change the return type of `Session.resolve_url()` and include the
resolved plugin name in the returned tuple
Other changes:
- Remove `pluginclass.bind()` call from `Session.load_plugins()` and
use the loader's module name directly on the `Session.plugins` dict
- Remove initialization check from `Plugin` cookie methods
- Update streamlink_cli.main module according to breaking changes
- Update tests respectively
- Add explicit plugin initialization test
- Update tests with plugin constructors and custom plugin names
- Move testplugin override module, so that it shares the same module
name as the main testplugin module. Rel `Session.load_plugins()`
- Refactor most session tests and replace unneeded `resolve_url()`
wrappers in favor of calling `session.streams()`
2022-08-25 10:55:38 +02:00
|
|
|
with pytest.raises(SyntaxError):
|
2020-12-04 21:26:18 +01:00
|
|
|
self.subject()
|
2012-11-19 03:47:13 +01:00
|
|
|
|
|
|
|
def test_resolve_url(self):
|
2020-12-04 21:26:18 +01:00
|
|
|
session = self.subject()
|
|
|
|
plugins = session.get_plugins()
|
2021-11-10 10:52:12 +01:00
|
|
|
|
2023-01-06 22:54:16 +01:00
|
|
|
with warnings.catch_warnings(record=True) as record_warnings:
|
2022-08-29 21:05:39 +02:00
|
|
|
pluginname, pluginclass, resolved_url = session.resolve_url("http://test.se/channel")
|
|
|
|
|
plugin: remove Plugin.bind()
This changes the way how the Streamlink session and other objects like
the plugin cache and logger are stored on each plugin.
Previously, those objects were set as class attributes on every `Plugin`
class via `Plugin.bind()` when loading plugins via the session's
`load_plugins()` method that gets called on initialization.
This meant that whenever a new Streamlink session was initialized,
references to it (including a dict of every loaded plugin) were set
on each `Plugin` class as a class attribute, and Python's garbage
collector could not get rid of this memory when deleting the session
instance that was created last.
Removing `Plugin.bind()`, passing the session via the `Plugin.__init__`
constructor, and setting the cache, logger, etc. on `Plugin` instances
instead (only one gets initialized by `streamlink_cli`), removes those
static references that prevent the garbage collector to work.
Since the plugin "module" name now doesn't get set via `Plugin.bind()`
anymore, it derives its name via `self.__class__.__module__` on its own,
which means a change of the return type of `Streamlink.resolve_url()`
is necessary in order to pass the plugin name to `streamlink_cli`,
so that it can load config files and initialize plugin arguments, etc.
Breaking changes:
- Remove `Plugin.bind()`
- Pass the `session` instance via the Plugin constructor and set the
`module`, `cache` and `logger` on the plugin instance instead.
Derive `module` from the actual module name.
- Change the return type of `Session.resolve_url()` and include the
resolved plugin name in the returned tuple
Other changes:
- Remove `pluginclass.bind()` call from `Session.load_plugins()` and
use the loader's module name directly on the `Session.plugins` dict
- Remove initialization check from `Plugin` cookie methods
- Update streamlink_cli.main module according to breaking changes
- Update tests respectively
- Add explicit plugin initialization test
- Update tests with plugin constructors and custom plugin names
- Move testplugin override module, so that it shares the same module
name as the main testplugin module. Rel `Session.load_plugins()`
- Refactor most session tests and replace unneeded `resolve_url()`
wrappers in favor of calling `session.streams()`
2022-08-25 10:55:38 +02:00
|
|
|
assert issubclass(pluginclass, Plugin)
|
|
|
|
assert pluginclass is plugins["testplugin"]
|
|
|
|
assert resolved_url == "http://test.se/channel"
|
|
|
|
assert hasattr(session.resolve_url, "cache_info"), "resolve_url has a lookup cache"
|
2023-01-06 22:54:16 +01:00
|
|
|
assert record_warnings == []
|
2012-11-19 03:47:13 +01:00
|
|
|
|
2021-11-10 10:52:12 +01:00
|
|
|
def test_resolve_url__noplugin(self):
|
|
|
|
session = self.subject()
|
|
|
|
self.mocker.get("http://invalid2", status_code=301, headers={"Location": "http://invalid3"})
|
|
|
|
|
plugin: remove Plugin.bind()
This changes the way how the Streamlink session and other objects like
the plugin cache and logger are stored on each plugin.
Previously, those objects were set as class attributes on every `Plugin`
class via `Plugin.bind()` when loading plugins via the session's
`load_plugins()` method that gets called on initialization.
This meant that whenever a new Streamlink session was initialized,
references to it (including a dict of every loaded plugin) were set
on each `Plugin` class as a class attribute, and Python's garbage
collector could not get rid of this memory when deleting the session
instance that was created last.
Removing `Plugin.bind()`, passing the session via the `Plugin.__init__`
constructor, and setting the cache, logger, etc. on `Plugin` instances
instead (only one gets initialized by `streamlink_cli`), removes those
static references that prevent the garbage collector to work.
Since the plugin "module" name now doesn't get set via `Plugin.bind()`
anymore, it derives its name via `self.__class__.__module__` on its own,
which means a change of the return type of `Streamlink.resolve_url()`
is necessary in order to pass the plugin name to `streamlink_cli`,
so that it can load config files and initialize plugin arguments, etc.
Breaking changes:
- Remove `Plugin.bind()`
- Pass the `session` instance via the Plugin constructor and set the
`module`, `cache` and `logger` on the plugin instance instead.
Derive `module` from the actual module name.
- Change the return type of `Session.resolve_url()` and include the
resolved plugin name in the returned tuple
Other changes:
- Remove `pluginclass.bind()` call from `Session.load_plugins()` and
use the loader's module name directly on the `Session.plugins` dict
- Remove initialization check from `Plugin` cookie methods
- Update streamlink_cli.main module according to breaking changes
- Update tests respectively
- Add explicit plugin initialization test
- Update tests with plugin constructors and custom plugin names
- Move testplugin override module, so that it shares the same module
name as the main testplugin module. Rel `Session.load_plugins()`
- Refactor most session tests and replace unneeded `resolve_url()`
wrappers in favor of calling `session.streams()`
2022-08-25 10:55:38 +02:00
|
|
|
with pytest.raises(NoPluginError):
|
|
|
|
session.resolve_url("http://invalid1")
|
|
|
|
with pytest.raises(NoPluginError):
|
|
|
|
session.resolve_url("http://invalid2")
|
2021-11-10 10:52:12 +01:00
|
|
|
|
|
|
|
def test_resolve_url__redirected(self):
|
|
|
|
session = self.subject()
|
|
|
|
plugins = session.get_plugins()
|
|
|
|
self.mocker.head("http://redirect1", status_code=501)
|
|
|
|
self.mocker.get("http://redirect1", status_code=301, headers={"Location": "http://redirect2"})
|
|
|
|
self.mocker.head("http://redirect2", status_code=301, headers={"Location": "http://test.se/channel"})
|
|
|
|
|
plugin: remove Plugin.bind()
This changes the way how the Streamlink session and other objects like
the plugin cache and logger are stored on each plugin.
Previously, those objects were set as class attributes on every `Plugin`
class via `Plugin.bind()` when loading plugins via the session's
`load_plugins()` method that gets called on initialization.
This meant that whenever a new Streamlink session was initialized,
references to it (including a dict of every loaded plugin) were set
on each `Plugin` class as a class attribute, and Python's garbage
collector could not get rid of this memory when deleting the session
instance that was created last.
Removing `Plugin.bind()`, passing the session via the `Plugin.__init__`
constructor, and setting the cache, logger, etc. on `Plugin` instances
instead (only one gets initialized by `streamlink_cli`), removes those
static references that prevent the garbage collector to work.
Since the plugin "module" name now doesn't get set via `Plugin.bind()`
anymore, it derives its name via `self.__class__.__module__` on its own,
which means a change of the return type of `Streamlink.resolve_url()`
is necessary in order to pass the plugin name to `streamlink_cli`,
so that it can load config files and initialize plugin arguments, etc.
Breaking changes:
- Remove `Plugin.bind()`
- Pass the `session` instance via the Plugin constructor and set the
`module`, `cache` and `logger` on the plugin instance instead.
Derive `module` from the actual module name.
- Change the return type of `Session.resolve_url()` and include the
resolved plugin name in the returned tuple
Other changes:
- Remove `pluginclass.bind()` call from `Session.load_plugins()` and
use the loader's module name directly on the `Session.plugins` dict
- Remove initialization check from `Plugin` cookie methods
- Update streamlink_cli.main module according to breaking changes
- Update tests respectively
- Add explicit plugin initialization test
- Update tests with plugin constructors and custom plugin names
- Move testplugin override module, so that it shares the same module
name as the main testplugin module. Rel `Session.load_plugins()`
- Refactor most session tests and replace unneeded `resolve_url()`
wrappers in favor of calling `session.streams()`
2022-08-25 10:55:38 +02:00
|
|
|
pluginname, pluginclass, resolved_url = session.resolve_url("http://redirect1")
|
|
|
|
assert issubclass(pluginclass, Plugin)
|
|
|
|
assert pluginclass is plugins["testplugin"]
|
|
|
|
assert resolved_url == "http://test.se/channel"
|
2021-11-10 10:52:12 +01:00
|
|
|
|
|
|
|
def test_resolve_url_no_redirect(self):
|
|
|
|
session = self.subject()
|
|
|
|
plugins = session.get_plugins()
|
|
|
|
|
plugin: remove Plugin.bind()
This changes the way how the Streamlink session and other objects like
the plugin cache and logger are stored on each plugin.
Previously, those objects were set as class attributes on every `Plugin`
class via `Plugin.bind()` when loading plugins via the session's
`load_plugins()` method that gets called on initialization.
This meant that whenever a new Streamlink session was initialized,
references to it (including a dict of every loaded plugin) were set
on each `Plugin` class as a class attribute, and Python's garbage
collector could not get rid of this memory when deleting the session
instance that was created last.
Removing `Plugin.bind()`, passing the session via the `Plugin.__init__`
constructor, and setting the cache, logger, etc. on `Plugin` instances
instead (only one gets initialized by `streamlink_cli`), removes those
static references that prevent the garbage collector to work.
Since the plugin "module" name now doesn't get set via `Plugin.bind()`
anymore, it derives its name via `self.__class__.__module__` on its own,
which means a change of the return type of `Streamlink.resolve_url()`
is necessary in order to pass the plugin name to `streamlink_cli`,
so that it can load config files and initialize plugin arguments, etc.
Breaking changes:
- Remove `Plugin.bind()`
- Pass the `session` instance via the Plugin constructor and set the
`module`, `cache` and `logger` on the plugin instance instead.
Derive `module` from the actual module name.
- Change the return type of `Session.resolve_url()` and include the
resolved plugin name in the returned tuple
Other changes:
- Remove `pluginclass.bind()` call from `Session.load_plugins()` and
use the loader's module name directly on the `Session.plugins` dict
- Remove initialization check from `Plugin` cookie methods
- Update streamlink_cli.main module according to breaking changes
- Update tests respectively
- Add explicit plugin initialization test
- Update tests with plugin constructors and custom plugin names
- Move testplugin override module, so that it shares the same module
name as the main testplugin module. Rel `Session.load_plugins()`
- Refactor most session tests and replace unneeded `resolve_url()`
wrappers in favor of calling `session.streams()`
2022-08-25 10:55:38 +02:00
|
|
|
pluginname, pluginclass, resolved_url = session.resolve_url_no_redirect("http://test.se/channel")
|
|
|
|
assert issubclass(pluginclass, Plugin)
|
|
|
|
assert pluginclass is plugins["testplugin"]
|
|
|
|
assert resolved_url == "http://test.se/channel"
|
2021-11-10 10:52:12 +01:00
|
|
|
|
|
|
|
def test_resolve_url_no_redirect__noplugin(self):
|
|
|
|
session = self.subject()
|
plugin: remove Plugin.bind()
This changes the way how the Streamlink session and other objects like
the plugin cache and logger are stored on each plugin.
Previously, those objects were set as class attributes on every `Plugin`
class via `Plugin.bind()` when loading plugins via the session's
`load_plugins()` method that gets called on initialization.
This meant that whenever a new Streamlink session was initialized,
references to it (including a dict of every loaded plugin) were set
on each `Plugin` class as a class attribute, and Python's garbage
collector could not get rid of this memory when deleting the session
instance that was created last.
Removing `Plugin.bind()`, passing the session via the `Plugin.__init__`
constructor, and setting the cache, logger, etc. on `Plugin` instances
instead (only one gets initialized by `streamlink_cli`), removes those
static references that prevent the garbage collector to work.
Since the plugin "module" name now doesn't get set via `Plugin.bind()`
anymore, it derives its name via `self.__class__.__module__` on its own,
which means a change of the return type of `Streamlink.resolve_url()`
is necessary in order to pass the plugin name to `streamlink_cli`,
so that it can load config files and initialize plugin arguments, etc.
Breaking changes:
- Remove `Plugin.bind()`
- Pass the `session` instance via the Plugin constructor and set the
`module`, `cache` and `logger` on the plugin instance instead.
Derive `module` from the actual module name.
- Change the return type of `Session.resolve_url()` and include the
resolved plugin name in the returned tuple
Other changes:
- Remove `pluginclass.bind()` call from `Session.load_plugins()` and
use the loader's module name directly on the `Session.plugins` dict
- Remove initialization check from `Plugin` cookie methods
- Update streamlink_cli.main module according to breaking changes
- Update tests respectively
- Add explicit plugin initialization test
- Update tests with plugin constructors and custom plugin names
- Move testplugin override module, so that it shares the same module
name as the main testplugin module. Rel `Session.load_plugins()`
- Refactor most session tests and replace unneeded `resolve_url()`
wrappers in favor of calling `session.streams()`
2022-08-25 10:55:38 +02:00
|
|
|
with pytest.raises(NoPluginError):
|
|
|
|
session.resolve_url_no_redirect("http://invalid")
|
2021-11-10 10:52:12 +01:00
|
|
|
|
2021-10-03 14:35:46 +02:00
|
|
|
def test_resolve_url_scheme(self):
|
|
|
|
@pluginmatcher(re.compile("http://insecure"))
|
|
|
|
class PluginHttp(EmptyPlugin):
|
|
|
|
pass
|
|
|
|
|
|
|
|
@pluginmatcher(re.compile("https://secure"))
|
|
|
|
class PluginHttps(EmptyPlugin):
|
|
|
|
pass
|
|
|
|
|
|
|
|
session = self.subject(load_plugins=False)
|
|
|
|
session.plugins = {
|
|
|
|
"insecure": PluginHttp,
|
|
|
|
"secure": PluginHttps,
|
|
|
|
}
|
|
|
|
|
plugin: remove Plugin.bind()
This changes the way how the Streamlink session and other objects like
the plugin cache and logger are stored on each plugin.
Previously, those objects were set as class attributes on every `Plugin`
class via `Plugin.bind()` when loading plugins via the session's
`load_plugins()` method that gets called on initialization.
This meant that whenever a new Streamlink session was initialized,
references to it (including a dict of every loaded plugin) were set
on each `Plugin` class as a class attribute, and Python's garbage
collector could not get rid of this memory when deleting the session
instance that was created last.
Removing `Plugin.bind()`, passing the session via the `Plugin.__init__`
constructor, and setting the cache, logger, etc. on `Plugin` instances
instead (only one gets initialized by `streamlink_cli`), removes those
static references that prevent the garbage collector to work.
Since the plugin "module" name now doesn't get set via `Plugin.bind()`
anymore, it derives its name via `self.__class__.__module__` on its own,
which means a change of the return type of `Streamlink.resolve_url()`
is necessary in order to pass the plugin name to `streamlink_cli`,
so that it can load config files and initialize plugin arguments, etc.
Breaking changes:
- Remove `Plugin.bind()`
- Pass the `session` instance via the Plugin constructor and set the
`module`, `cache` and `logger` on the plugin instance instead.
Derive `module` from the actual module name.
- Change the return type of `Session.resolve_url()` and include the
resolved plugin name in the returned tuple
Other changes:
- Remove `pluginclass.bind()` call from `Session.load_plugins()` and
use the loader's module name directly on the `Session.plugins` dict
- Remove initialization check from `Plugin` cookie methods
- Update streamlink_cli.main module according to breaking changes
- Update tests respectively
- Add explicit plugin initialization test
- Update tests with plugin constructors and custom plugin names
- Move testplugin override module, so that it shares the same module
name as the main testplugin module. Rel `Session.load_plugins()`
- Refactor most session tests and replace unneeded `resolve_url()`
wrappers in favor of calling `session.streams()`
2022-08-25 10:55:38 +02:00
|
|
|
with pytest.raises(NoPluginError):
|
|
|
|
session.resolve_url("insecure")
|
|
|
|
assert session.resolve_url("http://insecure")[1] is PluginHttp
|
|
|
|
with pytest.raises(NoPluginError):
|
|
|
|
session.resolve_url("https://insecure")
|
2021-10-03 14:35:46 +02:00
|
|
|
|
plugin: remove Plugin.bind()
This changes the way how the Streamlink session and other objects like
the plugin cache and logger are stored on each plugin.
Previously, those objects were set as class attributes on every `Plugin`
class via `Plugin.bind()` when loading plugins via the session's
`load_plugins()` method that gets called on initialization.
This meant that whenever a new Streamlink session was initialized,
references to it (including a dict of every loaded plugin) were set
on each `Plugin` class as a class attribute, and Python's garbage
collector could not get rid of this memory when deleting the session
instance that was created last.
Removing `Plugin.bind()`, passing the session via the `Plugin.__init__`
constructor, and setting the cache, logger, etc. on `Plugin` instances
instead (only one gets initialized by `streamlink_cli`), removes those
static references that prevent the garbage collector to work.
Since the plugin "module" name now doesn't get set via `Plugin.bind()`
anymore, it derives its name via `self.__class__.__module__` on its own,
which means a change of the return type of `Streamlink.resolve_url()`
is necessary in order to pass the plugin name to `streamlink_cli`,
so that it can load config files and initialize plugin arguments, etc.
Breaking changes:
- Remove `Plugin.bind()`
- Pass the `session` instance via the Plugin constructor and set the
`module`, `cache` and `logger` on the plugin instance instead.
Derive `module` from the actual module name.
- Change the return type of `Session.resolve_url()` and include the
resolved plugin name in the returned tuple
Other changes:
- Remove `pluginclass.bind()` call from `Session.load_plugins()` and
use the loader's module name directly on the `Session.plugins` dict
- Remove initialization check from `Plugin` cookie methods
- Update streamlink_cli.main module according to breaking changes
- Update tests respectively
- Add explicit plugin initialization test
- Update tests with plugin constructors and custom plugin names
- Move testplugin override module, so that it shares the same module
name as the main testplugin module. Rel `Session.load_plugins()`
- Refactor most session tests and replace unneeded `resolve_url()`
wrappers in favor of calling `session.streams()`
2022-08-25 10:55:38 +02:00
|
|
|
assert session.resolve_url("secure")[1] is PluginHttps
|
|
|
|
with pytest.raises(NoPluginError):
|
|
|
|
session.resolve_url("http://secure")
|
|
|
|
assert session.resolve_url("https://secure")[1] is PluginHttps
|
2021-10-03 14:35:46 +02:00
|
|
|
|
2017-08-03 14:45:00 +02:00
|
|
|
def test_resolve_url_priority(self):
|
2021-06-22 22:25:35 +02:00
|
|
|
@pluginmatcher(priority=HIGH_PRIORITY, pattern=re.compile(
|
2021-10-03 14:35:46 +02:00
|
|
|
"https://(high|normal|low|no)$",
|
2021-06-22 22:25:35 +02:00
|
|
|
))
|
|
|
|
class HighPriority(EmptyPlugin):
|
|
|
|
pass
|
|
|
|
|
|
|
|
@pluginmatcher(priority=NORMAL_PRIORITY, pattern=re.compile(
|
2021-10-03 14:35:46 +02:00
|
|
|
"https://(normal|low|no)$",
|
2021-06-22 22:25:35 +02:00
|
|
|
))
|
|
|
|
class NormalPriority(EmptyPlugin):
|
|
|
|
pass
|
|
|
|
|
|
|
|
@pluginmatcher(priority=LOW_PRIORITY, pattern=re.compile(
|
2021-10-03 14:35:46 +02:00
|
|
|
"https://(low|no)$",
|
2021-06-22 22:25:35 +02:00
|
|
|
))
|
|
|
|
class LowPriority(EmptyPlugin):
|
|
|
|
pass
|
|
|
|
|
|
|
|
@pluginmatcher(priority=NO_PRIORITY, pattern=re.compile(
|
2021-10-03 14:35:46 +02:00
|
|
|
"https://(no)$",
|
2021-06-22 22:25:35 +02:00
|
|
|
))
|
|
|
|
class NoPriority(EmptyPlugin):
|
|
|
|
pass
|
2017-08-03 14:45:00 +02:00
|
|
|
|
2020-12-04 21:26:18 +01:00
|
|
|
session = self.subject(load_plugins=False)
|
|
|
|
session.plugins = {
|
2021-06-22 22:25:35 +02:00
|
|
|
"high": HighPriority,
|
|
|
|
"normal": NormalPriority,
|
|
|
|
"low": LowPriority,
|
|
|
|
"no": NoPriority,
|
2017-08-03 14:45:00 +02:00
|
|
|
}
|
plugin: remove Plugin.bind()
This changes the way how the Streamlink session and other objects like
the plugin cache and logger are stored on each plugin.
Previously, those objects were set as class attributes on every `Plugin`
class via `Plugin.bind()` when loading plugins via the session's
`load_plugins()` method that gets called on initialization.
This meant that whenever a new Streamlink session was initialized,
references to it (including a dict of every loaded plugin) were set
on each `Plugin` class as a class attribute, and Python's garbage
collector could not get rid of this memory when deleting the session
instance that was created last.
Removing `Plugin.bind()`, passing the session via the `Plugin.__init__`
constructor, and setting the cache, logger, etc. on `Plugin` instances
instead (only one gets initialized by `streamlink_cli`), removes those
static references that prevent the garbage collector to work.
Since the plugin "module" name now doesn't get set via `Plugin.bind()`
anymore, it derives its name via `self.__class__.__module__` on its own,
which means a change of the return type of `Streamlink.resolve_url()`
is necessary in order to pass the plugin name to `streamlink_cli`,
so that it can load config files and initialize plugin arguments, etc.
Breaking changes:
- Remove `Plugin.bind()`
- Pass the `session` instance via the Plugin constructor and set the
`module`, `cache` and `logger` on the plugin instance instead.
Derive `module` from the actual module name.
- Change the return type of `Session.resolve_url()` and include the
resolved plugin name in the returned tuple
Other changes:
- Remove `pluginclass.bind()` call from `Session.load_plugins()` and
use the loader's module name directly on the `Session.plugins` dict
- Remove initialization check from `Plugin` cookie methods
- Update streamlink_cli.main module according to breaking changes
- Update tests respectively
- Add explicit plugin initialization test
- Update tests with plugin constructors and custom plugin names
- Move testplugin override module, so that it shares the same module
name as the main testplugin module. Rel `Session.load_plugins()`
- Refactor most session tests and replace unneeded `resolve_url()`
wrappers in favor of calling `session.streams()`
2022-08-25 10:55:38 +02:00
|
|
|
no = session.resolve_url_no_redirect("no")[1]
|
|
|
|
low = session.resolve_url_no_redirect("low")[1]
|
|
|
|
normal = session.resolve_url_no_redirect("normal")[1]
|
|
|
|
high = session.resolve_url_no_redirect("high")[1]
|
2021-06-22 22:25:35 +02:00
|
|
|
|
plugin: remove Plugin.bind()
This changes the way how the Streamlink session and other objects like
the plugin cache and logger are stored on each plugin.
Previously, those objects were set as class attributes on every `Plugin`
class via `Plugin.bind()` when loading plugins via the session's
`load_plugins()` method that gets called on initialization.
This meant that whenever a new Streamlink session was initialized,
references to it (including a dict of every loaded plugin) were set
on each `Plugin` class as a class attribute, and Python's garbage
collector could not get rid of this memory when deleting the session
instance that was created last.
Removing `Plugin.bind()`, passing the session via the `Plugin.__init__`
constructor, and setting the cache, logger, etc. on `Plugin` instances
instead (only one gets initialized by `streamlink_cli`), removes those
static references that prevent the garbage collector to work.
Since the plugin "module" name now doesn't get set via `Plugin.bind()`
anymore, it derives its name via `self.__class__.__module__` on its own,
which means a change of the return type of `Streamlink.resolve_url()`
is necessary in order to pass the plugin name to `streamlink_cli`,
so that it can load config files and initialize plugin arguments, etc.
Breaking changes:
- Remove `Plugin.bind()`
- Pass the `session` instance via the Plugin constructor and set the
`module`, `cache` and `logger` on the plugin instance instead.
Derive `module` from the actual module name.
- Change the return type of `Session.resolve_url()` and include the
resolved plugin name in the returned tuple
Other changes:
- Remove `pluginclass.bind()` call from `Session.load_plugins()` and
use the loader's module name directly on the `Session.plugins` dict
- Remove initialization check from `Plugin` cookie methods
- Update streamlink_cli.main module according to breaking changes
- Update tests respectively
- Add explicit plugin initialization test
- Update tests with plugin constructors and custom plugin names
- Move testplugin override module, so that it shares the same module
name as the main testplugin module. Rel `Session.load_plugins()`
- Refactor most session tests and replace unneeded `resolve_url()`
wrappers in favor of calling `session.streams()`
2022-08-25 10:55:38 +02:00
|
|
|
assert no is HighPriority
|
|
|
|
assert low is HighPriority
|
|
|
|
assert normal is HighPriority
|
|
|
|
assert high is HighPriority
|
2017-08-03 14:45:00 +02:00
|
|
|
|
2021-06-22 22:25:35 +02:00
|
|
|
session.resolve_url.cache_clear()
|
|
|
|
session.plugins = {
|
|
|
|
"no": NoPriority,
|
|
|
|
}
|
plugin: remove Plugin.bind()
This changes the way how the Streamlink session and other objects like
the plugin cache and logger are stored on each plugin.
Previously, those objects were set as class attributes on every `Plugin`
class via `Plugin.bind()` when loading plugins via the session's
`load_plugins()` method that gets called on initialization.
This meant that whenever a new Streamlink session was initialized,
references to it (including a dict of every loaded plugin) were set
on each `Plugin` class as a class attribute, and Python's garbage
collector could not get rid of this memory when deleting the session
instance that was created last.
Removing `Plugin.bind()`, passing the session via the `Plugin.__init__`
constructor, and setting the cache, logger, etc. on `Plugin` instances
instead (only one gets initialized by `streamlink_cli`), removes those
static references that prevent the garbage collector to work.
Since the plugin "module" name now doesn't get set via `Plugin.bind()`
anymore, it derives its name via `self.__class__.__module__` on its own,
which means a change of the return type of `Streamlink.resolve_url()`
is necessary in order to pass the plugin name to `streamlink_cli`,
so that it can load config files and initialize plugin arguments, etc.
Breaking changes:
- Remove `Plugin.bind()`
- Pass the `session` instance via the Plugin constructor and set the
`module`, `cache` and `logger` on the plugin instance instead.
Derive `module` from the actual module name.
- Change the return type of `Session.resolve_url()` and include the
resolved plugin name in the returned tuple
Other changes:
- Remove `pluginclass.bind()` call from `Session.load_plugins()` and
use the loader's module name directly on the `Session.plugins` dict
- Remove initialization check from `Plugin` cookie methods
- Update streamlink_cli.main module according to breaking changes
- Update tests respectively
- Add explicit plugin initialization test
- Update tests with plugin constructors and custom plugin names
- Move testplugin override module, so that it shares the same module
name as the main testplugin module. Rel `Session.load_plugins()`
- Refactor most session tests and replace unneeded `resolve_url()`
wrappers in favor of calling `session.streams()`
2022-08-25 10:55:38 +02:00
|
|
|
with pytest.raises(NoPluginError):
|
|
|
|
session.resolve_url_no_redirect("no")
|
2017-08-03 14:45:00 +02:00
|
|
|
|
2022-08-29 21:05:39 +02:00
|
|
|
def test_resolve_deprecated(self):
|
2021-06-23 09:17:07 +02:00
|
|
|
@pluginmatcher(priority=LOW_PRIORITY, pattern=re.compile(
|
2021-10-03 14:35:46 +02:00
|
|
|
"https://low",
|
2021-06-23 09:17:07 +02:00
|
|
|
))
|
|
|
|
class LowPriority(EmptyPlugin):
|
|
|
|
pass
|
|
|
|
|
|
|
|
class DeprecatedNormalPriority(EmptyPlugin):
|
|
|
|
# noinspection PyUnusedLocal
|
|
|
|
@classmethod
|
|
|
|
def can_handle_url(cls, url):
|
|
|
|
return True
|
|
|
|
|
|
|
|
class DeprecatedHighPriority(DeprecatedNormalPriority):
|
|
|
|
# noinspection PyUnusedLocal
|
|
|
|
@classmethod
|
|
|
|
def priority(cls, url):
|
|
|
|
return HIGH_PRIORITY
|
|
|
|
|
|
|
|
session = self.subject(load_plugins=False)
|
|
|
|
session.plugins = {
|
|
|
|
"empty": EmptyPlugin,
|
|
|
|
"low": LowPriority,
|
|
|
|
"dep-normal-one": DeprecatedNormalPriority,
|
|
|
|
"dep-normal-two": DeprecatedNormalPriority,
|
|
|
|
"dep-high": DeprecatedHighPriority,
|
|
|
|
}
|
|
|
|
|
2023-01-06 22:54:16 +01:00
|
|
|
with pytest.warns() as recwarn:
|
2022-08-29 21:05:39 +02:00
|
|
|
plugin = session.resolve_url_no_redirect("low")[1]
|
|
|
|
|
|
|
|
assert plugin is DeprecatedHighPriority
|
2023-01-06 22:54:16 +01:00
|
|
|
assert [(record.category, str(record.message)) for record in recwarn.list] == [
|
2023-01-12 18:58:54 +01:00
|
|
|
(StreamlinkDeprecationWarning, "Resolved plugin dep-normal-one with deprecated can_handle_url API"),
|
|
|
|
(StreamlinkDeprecationWarning, "Resolved plugin dep-high with deprecated can_handle_url API"),
|
plugin: remove Plugin.bind()
This changes the way how the Streamlink session and other objects like
the plugin cache and logger are stored on each plugin.
Previously, those objects were set as class attributes on every `Plugin`
class via `Plugin.bind()` when loading plugins via the session's
`load_plugins()` method that gets called on initialization.
This meant that whenever a new Streamlink session was initialized,
references to it (including a dict of every loaded plugin) were set
on each `Plugin` class as a class attribute, and Python's garbage
collector could not get rid of this memory when deleting the session
instance that was created last.
Removing `Plugin.bind()`, passing the session via the `Plugin.__init__`
constructor, and setting the cache, logger, etc. on `Plugin` instances
instead (only one gets initialized by `streamlink_cli`), removes those
static references that prevent the garbage collector to work.
Since the plugin "module" name now doesn't get set via `Plugin.bind()`
anymore, it derives its name via `self.__class__.__module__` on its own,
which means a change of the return type of `Streamlink.resolve_url()`
is necessary in order to pass the plugin name to `streamlink_cli`,
so that it can load config files and initialize plugin arguments, etc.
Breaking changes:
- Remove `Plugin.bind()`
- Pass the `session` instance via the Plugin constructor and set the
`module`, `cache` and `logger` on the plugin instance instead.
Derive `module` from the actual module name.
- Change the return type of `Session.resolve_url()` and include the
resolved plugin name in the returned tuple
Other changes:
- Remove `pluginclass.bind()` call from `Session.load_plugins()` and
use the loader's module name directly on the `Session.plugins` dict
- Remove initialization check from `Plugin` cookie methods
- Update streamlink_cli.main module according to breaking changes
- Update tests respectively
- Add explicit plugin initialization test
- Update tests with plugin constructors and custom plugin names
- Move testplugin override module, so that it shares the same module
name as the main testplugin module. Rel `Session.load_plugins()`
- Refactor most session tests and replace unneeded `resolve_url()`
wrappers in favor of calling `session.streams()`
2022-08-25 10:55:38 +02:00
|
|
|
]
|
2021-06-23 09:17:07 +02:00
|
|
|
|
2012-11-19 03:47:13 +01:00
|
|
|
def test_options(self):
|
2020-12-04 21:26:18 +01:00
|
|
|
session = self.subject()
|
|
|
|
session.set_option("test_option", "option")
|
2023-02-16 00:51:25 +01:00
|
|
|
assert session.get_option("test_option") == "option"
|
|
|
|
assert session.get_option("non_existing") is None
|
2012-11-19 03:47:13 +01:00
|
|
|
|
2023-02-16 00:51:25 +01:00
|
|
|
assert session.get_plugin_option("testplugin", "a_option") == "default"
|
2020-12-04 21:26:18 +01:00
|
|
|
session.set_plugin_option("testplugin", "another_option", "test")
|
2023-02-16 00:51:25 +01:00
|
|
|
assert session.get_plugin_option("testplugin", "another_option") == "test"
|
|
|
|
assert session.get_plugin_option("non_existing", "non_existing") is None
|
|
|
|
assert session.get_plugin_option("testplugin", "non_existing") is None
|
2012-11-19 03:47:13 +01:00
|
|
|
|
plugin: remove Plugin.bind()
This changes the way how the Streamlink session and other objects like
the plugin cache and logger are stored on each plugin.
Previously, those objects were set as class attributes on every `Plugin`
class via `Plugin.bind()` when loading plugins via the session's
`load_plugins()` method that gets called on initialization.
This meant that whenever a new Streamlink session was initialized,
references to it (including a dict of every loaded plugin) were set
on each `Plugin` class as a class attribute, and Python's garbage
collector could not get rid of this memory when deleting the session
instance that was created last.
Removing `Plugin.bind()`, passing the session via the `Plugin.__init__`
constructor, and setting the cache, logger, etc. on `Plugin` instances
instead (only one gets initialized by `streamlink_cli`), removes those
static references that prevent the garbage collector to work.
Since the plugin "module" name now doesn't get set via `Plugin.bind()`
anymore, it derives its name via `self.__class__.__module__` on its own,
which means a change of the return type of `Streamlink.resolve_url()`
is necessary in order to pass the plugin name to `streamlink_cli`,
so that it can load config files and initialize plugin arguments, etc.
Breaking changes:
- Remove `Plugin.bind()`
- Pass the `session` instance via the Plugin constructor and set the
`module`, `cache` and `logger` on the plugin instance instead.
Derive `module` from the actual module name.
- Change the return type of `Session.resolve_url()` and include the
resolved plugin name in the returned tuple
Other changes:
- Remove `pluginclass.bind()` call from `Session.load_plugins()` and
use the loader's module name directly on the `Session.plugins` dict
- Remove initialization check from `Plugin` cookie methods
- Update streamlink_cli.main module according to breaking changes
- Update tests respectively
- Add explicit plugin initialization test
- Update tests with plugin constructors and custom plugin names
- Move testplugin override module, so that it shares the same module
name as the main testplugin module. Rel `Session.load_plugins()`
- Refactor most session tests and replace unneeded `resolve_url()`
wrappers in favor of calling `session.streams()`
2022-08-25 10:55:38 +02:00
|
|
|
def test_streams(self):
|
2020-12-04 21:26:18 +01:00
|
|
|
session = self.subject()
|
plugin: remove Plugin.bind()
This changes the way how the Streamlink session and other objects like
the plugin cache and logger are stored on each plugin.
Previously, those objects were set as class attributes on every `Plugin`
class via `Plugin.bind()` when loading plugins via the session's
`load_plugins()` method that gets called on initialization.
This meant that whenever a new Streamlink session was initialized,
references to it (including a dict of every loaded plugin) were set
on each `Plugin` class as a class attribute, and Python's garbage
collector could not get rid of this memory when deleting the session
instance that was created last.
Removing `Plugin.bind()`, passing the session via the `Plugin.__init__`
constructor, and setting the cache, logger, etc. on `Plugin` instances
instead (only one gets initialized by `streamlink_cli`), removes those
static references that prevent the garbage collector to work.
Since the plugin "module" name now doesn't get set via `Plugin.bind()`
anymore, it derives its name via `self.__class__.__module__` on its own,
which means a change of the return type of `Streamlink.resolve_url()`
is necessary in order to pass the plugin name to `streamlink_cli`,
so that it can load config files and initialize plugin arguments, etc.
Breaking changes:
- Remove `Plugin.bind()`
- Pass the `session` instance via the Plugin constructor and set the
`module`, `cache` and `logger` on the plugin instance instead.
Derive `module` from the actual module name.
- Change the return type of `Session.resolve_url()` and include the
resolved plugin name in the returned tuple
Other changes:
- Remove `pluginclass.bind()` call from `Session.load_plugins()` and
use the loader's module name directly on the `Session.plugins` dict
- Remove initialization check from `Plugin` cookie methods
- Update streamlink_cli.main module according to breaking changes
- Update tests respectively
- Add explicit plugin initialization test
- Update tests with plugin constructors and custom plugin names
- Move testplugin override module, so that it shares the same module
name as the main testplugin module. Rel `Session.load_plugins()`
- Refactor most session tests and replace unneeded `resolve_url()`
wrappers in favor of calling `session.streams()`
2022-08-25 10:55:38 +02:00
|
|
|
streams = session.streams("http://test.se/channel")
|
2012-11-19 03:47:13 +01:00
|
|
|
|
plugin: remove Plugin.bind()
This changes the way how the Streamlink session and other objects like
the plugin cache and logger are stored on each plugin.
Previously, those objects were set as class attributes on every `Plugin`
class via `Plugin.bind()` when loading plugins via the session's
`load_plugins()` method that gets called on initialization.
This meant that whenever a new Streamlink session was initialized,
references to it (including a dict of every loaded plugin) were set
on each `Plugin` class as a class attribute, and Python's garbage
collector could not get rid of this memory when deleting the session
instance that was created last.
Removing `Plugin.bind()`, passing the session via the `Plugin.__init__`
constructor, and setting the cache, logger, etc. on `Plugin` instances
instead (only one gets initialized by `streamlink_cli`), removes those
static references that prevent the garbage collector to work.
Since the plugin "module" name now doesn't get set via `Plugin.bind()`
anymore, it derives its name via `self.__class__.__module__` on its own,
which means a change of the return type of `Streamlink.resolve_url()`
is necessary in order to pass the plugin name to `streamlink_cli`,
so that it can load config files and initialize plugin arguments, etc.
Breaking changes:
- Remove `Plugin.bind()`
- Pass the `session` instance via the Plugin constructor and set the
`module`, `cache` and `logger` on the plugin instance instead.
Derive `module` from the actual module name.
- Change the return type of `Session.resolve_url()` and include the
resolved plugin name in the returned tuple
Other changes:
- Remove `pluginclass.bind()` call from `Session.load_plugins()` and
use the loader's module name directly on the `Session.plugins` dict
- Remove initialization check from `Plugin` cookie methods
- Update streamlink_cli.main module according to breaking changes
- Update tests respectively
- Add explicit plugin initialization test
- Update tests with plugin constructors and custom plugin names
- Move testplugin override module, so that it shares the same module
name as the main testplugin module. Rel `Session.load_plugins()`
- Refactor most session tests and replace unneeded `resolve_url()`
wrappers in favor of calling `session.streams()`
2022-08-25 10:55:38 +02:00
|
|
|
assert "best" in streams
|
|
|
|
assert "worst" in streams
|
|
|
|
assert streams["best"] is streams["1080p"]
|
|
|
|
assert streams["worst"] is streams["350k"]
|
|
|
|
assert isinstance(streams["http"], HTTPStream)
|
|
|
|
assert isinstance(streams["hls"], HLSStream)
|
2012-11-19 03:47:13 +01:00
|
|
|
|
plugin: remove Plugin.bind()
This changes the way how the Streamlink session and other objects like
the plugin cache and logger are stored on each plugin.
Previously, those objects were set as class attributes on every `Plugin`
class via `Plugin.bind()` when loading plugins via the session's
`load_plugins()` method that gets called on initialization.
This meant that whenever a new Streamlink session was initialized,
references to it (including a dict of every loaded plugin) were set
on each `Plugin` class as a class attribute, and Python's garbage
collector could not get rid of this memory when deleting the session
instance that was created last.
Removing `Plugin.bind()`, passing the session via the `Plugin.__init__`
constructor, and setting the cache, logger, etc. on `Plugin` instances
instead (only one gets initialized by `streamlink_cli`), removes those
static references that prevent the garbage collector to work.
Since the plugin "module" name now doesn't get set via `Plugin.bind()`
anymore, it derives its name via `self.__class__.__module__` on its own,
which means a change of the return type of `Streamlink.resolve_url()`
is necessary in order to pass the plugin name to `streamlink_cli`,
so that it can load config files and initialize plugin arguments, etc.
Breaking changes:
- Remove `Plugin.bind()`
- Pass the `session` instance via the Plugin constructor and set the
`module`, `cache` and `logger` on the plugin instance instead.
Derive `module` from the actual module name.
- Change the return type of `Session.resolve_url()` and include the
resolved plugin name in the returned tuple
Other changes:
- Remove `pluginclass.bind()` call from `Session.load_plugins()` and
use the loader's module name directly on the `Session.plugins` dict
- Remove initialization check from `Plugin` cookie methods
- Update streamlink_cli.main module according to breaking changes
- Update tests respectively
- Add explicit plugin initialization test
- Update tests with plugin constructors and custom plugin names
- Move testplugin override module, so that it shares the same module
name as the main testplugin module. Rel `Session.load_plugins()`
- Refactor most session tests and replace unneeded `resolve_url()`
wrappers in favor of calling `session.streams()`
2022-08-25 10:55:38 +02:00
|
|
|
def test_streams_stream_types(self):
|
2020-12-04 21:26:18 +01:00
|
|
|
session = self.subject()
|
2012-12-29 21:48:10 +01:00
|
|
|
|
plugin: remove Plugin.bind()
This changes the way how the Streamlink session and other objects like
the plugin cache and logger are stored on each plugin.
Previously, those objects were set as class attributes on every `Plugin`
class via `Plugin.bind()` when loading plugins via the session's
`load_plugins()` method that gets called on initialization.
This meant that whenever a new Streamlink session was initialized,
references to it (including a dict of every loaded plugin) were set
on each `Plugin` class as a class attribute, and Python's garbage
collector could not get rid of this memory when deleting the session
instance that was created last.
Removing `Plugin.bind()`, passing the session via the `Plugin.__init__`
constructor, and setting the cache, logger, etc. on `Plugin` instances
instead (only one gets initialized by `streamlink_cli`), removes those
static references that prevent the garbage collector to work.
Since the plugin "module" name now doesn't get set via `Plugin.bind()`
anymore, it derives its name via `self.__class__.__module__` on its own,
which means a change of the return type of `Streamlink.resolve_url()`
is necessary in order to pass the plugin name to `streamlink_cli`,
so that it can load config files and initialize plugin arguments, etc.
Breaking changes:
- Remove `Plugin.bind()`
- Pass the `session` instance via the Plugin constructor and set the
`module`, `cache` and `logger` on the plugin instance instead.
Derive `module` from the actual module name.
- Change the return type of `Session.resolve_url()` and include the
resolved plugin name in the returned tuple
Other changes:
- Remove `pluginclass.bind()` call from `Session.load_plugins()` and
use the loader's module name directly on the `Session.plugins` dict
- Remove initialization check from `Plugin` cookie methods
- Update streamlink_cli.main module according to breaking changes
- Update tests respectively
- Add explicit plugin initialization test
- Update tests with plugin constructors and custom plugin names
- Move testplugin override module, so that it shares the same module
name as the main testplugin module. Rel `Session.load_plugins()`
- Refactor most session tests and replace unneeded `resolve_url()`
wrappers in favor of calling `session.streams()`
2022-08-25 10:55:38 +02:00
|
|
|
streams = session.streams("http://test.se/channel", stream_types=["http", "hls"])
|
|
|
|
assert isinstance(streams["480p"], HTTPStream)
|
|
|
|
assert isinstance(streams["480p_hls"], HLSStream)
|
2012-12-29 21:48:10 +01:00
|
|
|
|
plugin: remove Plugin.bind()
This changes the way how the Streamlink session and other objects like
the plugin cache and logger are stored on each plugin.
Previously, those objects were set as class attributes on every `Plugin`
class via `Plugin.bind()` when loading plugins via the session's
`load_plugins()` method that gets called on initialization.
This meant that whenever a new Streamlink session was initialized,
references to it (including a dict of every loaded plugin) were set
on each `Plugin` class as a class attribute, and Python's garbage
collector could not get rid of this memory when deleting the session
instance that was created last.
Removing `Plugin.bind()`, passing the session via the `Plugin.__init__`
constructor, and setting the cache, logger, etc. on `Plugin` instances
instead (only one gets initialized by `streamlink_cli`), removes those
static references that prevent the garbage collector to work.
Since the plugin "module" name now doesn't get set via `Plugin.bind()`
anymore, it derives its name via `self.__class__.__module__` on its own,
which means a change of the return type of `Streamlink.resolve_url()`
is necessary in order to pass the plugin name to `streamlink_cli`,
so that it can load config files and initialize plugin arguments, etc.
Breaking changes:
- Remove `Plugin.bind()`
- Pass the `session` instance via the Plugin constructor and set the
`module`, `cache` and `logger` on the plugin instance instead.
Derive `module` from the actual module name.
- Change the return type of `Session.resolve_url()` and include the
resolved plugin name in the returned tuple
Other changes:
- Remove `pluginclass.bind()` call from `Session.load_plugins()` and
use the loader's module name directly on the `Session.plugins` dict
- Remove initialization check from `Plugin` cookie methods
- Update streamlink_cli.main module according to breaking changes
- Update tests respectively
- Add explicit plugin initialization test
- Update tests with plugin constructors and custom plugin names
- Move testplugin override module, so that it shares the same module
name as the main testplugin module. Rel `Session.load_plugins()`
- Refactor most session tests and replace unneeded `resolve_url()`
wrappers in favor of calling `session.streams()`
2022-08-25 10:55:38 +02:00
|
|
|
streams = session.streams("http://test.se/channel", stream_types=["hls", "http"])
|
|
|
|
assert isinstance(streams["480p"], HLSStream)
|
|
|
|
assert isinstance(streams["480p_http"], HTTPStream)
|
2012-12-29 21:48:10 +01:00
|
|
|
|
plugin: remove Plugin.bind()
This changes the way how the Streamlink session and other objects like
the plugin cache and logger are stored on each plugin.
Previously, those objects were set as class attributes on every `Plugin`
class via `Plugin.bind()` when loading plugins via the session's
`load_plugins()` method that gets called on initialization.
This meant that whenever a new Streamlink session was initialized,
references to it (including a dict of every loaded plugin) were set
on each `Plugin` class as a class attribute, and Python's garbage
collector could not get rid of this memory when deleting the session
instance that was created last.
Removing `Plugin.bind()`, passing the session via the `Plugin.__init__`
constructor, and setting the cache, logger, etc. on `Plugin` instances
instead (only one gets initialized by `streamlink_cli`), removes those
static references that prevent the garbage collector to work.
Since the plugin "module" name now doesn't get set via `Plugin.bind()`
anymore, it derives its name via `self.__class__.__module__` on its own,
which means a change of the return type of `Streamlink.resolve_url()`
is necessary in order to pass the plugin name to `streamlink_cli`,
so that it can load config files and initialize plugin arguments, etc.
Breaking changes:
- Remove `Plugin.bind()`
- Pass the `session` instance via the Plugin constructor and set the
`module`, `cache` and `logger` on the plugin instance instead.
Derive `module` from the actual module name.
- Change the return type of `Session.resolve_url()` and include the
resolved plugin name in the returned tuple
Other changes:
- Remove `pluginclass.bind()` call from `Session.load_plugins()` and
use the loader's module name directly on the `Session.plugins` dict
- Remove initialization check from `Plugin` cookie methods
- Update streamlink_cli.main module according to breaking changes
- Update tests respectively
- Add explicit plugin initialization test
- Update tests with plugin constructors and custom plugin names
- Move testplugin override module, so that it shares the same module
name as the main testplugin module. Rel `Session.load_plugins()`
- Refactor most session tests and replace unneeded `resolve_url()`
wrappers in favor of calling `session.streams()`
2022-08-25 10:55:38 +02:00
|
|
|
def test_streams_stream_sorting_excludes(self):
|
2020-12-04 21:26:18 +01:00
|
|
|
session = self.subject()
|
plugin: remove Plugin.bind()
This changes the way how the Streamlink session and other objects like
the plugin cache and logger are stored on each plugin.
Previously, those objects were set as class attributes on every `Plugin`
class via `Plugin.bind()` when loading plugins via the session's
`load_plugins()` method that gets called on initialization.
This meant that whenever a new Streamlink session was initialized,
references to it (including a dict of every loaded plugin) were set
on each `Plugin` class as a class attribute, and Python's garbage
collector could not get rid of this memory when deleting the session
instance that was created last.
Removing `Plugin.bind()`, passing the session via the `Plugin.__init__`
constructor, and setting the cache, logger, etc. on `Plugin` instances
instead (only one gets initialized by `streamlink_cli`), removes those
static references that prevent the garbage collector to work.
Since the plugin "module" name now doesn't get set via `Plugin.bind()`
anymore, it derives its name via `self.__class__.__module__` on its own,
which means a change of the return type of `Streamlink.resolve_url()`
is necessary in order to pass the plugin name to `streamlink_cli`,
so that it can load config files and initialize plugin arguments, etc.
Breaking changes:
- Remove `Plugin.bind()`
- Pass the `session` instance via the Plugin constructor and set the
`module`, `cache` and `logger` on the plugin instance instead.
Derive `module` from the actual module name.
- Change the return type of `Session.resolve_url()` and include the
resolved plugin name in the returned tuple
Other changes:
- Remove `pluginclass.bind()` call from `Session.load_plugins()` and
use the loader's module name directly on the `Session.plugins` dict
- Remove initialization check from `Plugin` cookie methods
- Update streamlink_cli.main module according to breaking changes
- Update tests respectively
- Add explicit plugin initialization test
- Update tests with plugin constructors and custom plugin names
- Move testplugin override module, so that it shares the same module
name as the main testplugin module. Rel `Session.load_plugins()`
- Refactor most session tests and replace unneeded `resolve_url()`
wrappers in favor of calling `session.streams()`
2022-08-25 10:55:38 +02:00
|
|
|
|
|
|
|
streams = session.streams("http://test.se/channel", sorting_excludes=[])
|
|
|
|
assert "best" in streams
|
|
|
|
assert "worst" in streams
|
|
|
|
assert "best-unfiltered" not in streams
|
|
|
|
assert "worst-unfiltered" not in streams
|
|
|
|
assert streams["worst"] is streams["350k"]
|
|
|
|
assert streams["best"] is streams["1080p"]
|
|
|
|
|
|
|
|
streams = session.streams("http://test.se/channel", sorting_excludes=["1080p", "3000k"])
|
|
|
|
assert "best" in streams
|
|
|
|
assert "worst" in streams
|
|
|
|
assert "best-unfiltered" not in streams
|
|
|
|
assert "worst-unfiltered" not in streams
|
|
|
|
assert streams["worst"] is streams["350k"]
|
|
|
|
assert streams["best"] is streams["1500k"]
|
|
|
|
|
|
|
|
streams = session.streams("http://test.se/channel", sorting_excludes=[">=1080p", ">1500k"])
|
|
|
|
assert streams["best"] is streams["1500k"]
|
|
|
|
|
|
|
|
streams = session.streams("http://test.se/channel", sorting_excludes=lambda q: not q.endswith("p"))
|
|
|
|
assert streams["best"] is streams["3000k"]
|
|
|
|
|
|
|
|
streams = session.streams("http://test.se/channel", sorting_excludes=lambda q: False)
|
|
|
|
assert "best" not in streams
|
|
|
|
assert "worst" not in streams
|
|
|
|
assert "best-unfiltered" in streams
|
|
|
|
assert "worst-unfiltered" in streams
|
|
|
|
assert streams["worst-unfiltered"] is streams["350k"]
|
|
|
|
assert streams["best-unfiltered"] is streams["1080p"]
|
|
|
|
|
|
|
|
streams = session.streams("http://test.se/UnsortableStreamNames")
|
|
|
|
assert "best" not in streams
|
|
|
|
assert "worst" not in streams
|
|
|
|
assert "best-unfiltered" not in streams
|
|
|
|
assert "worst-unfiltered" not in streams
|
|
|
|
assert "vod" in streams
|
|
|
|
assert "vod_alt" in streams
|
|
|
|
assert "vod_alt2" in streams
|
2018-10-22 14:12:29 +02:00
|
|
|
|
2018-06-29 20:31:37 +02:00
|
|
|
def test_set_and_get_locale(self):
|
|
|
|
session = Streamlink()
|
|
|
|
session.set_option("locale", "en_US")
|
2023-02-16 00:51:25 +01:00
|
|
|
assert session.localization.country.alpha2 == "US"
|
|
|
|
assert session.localization.language.alpha2 == "en"
|
|
|
|
assert session.localization.language_code == "en_US"
|
2018-06-29 20:31:37 +02:00
|
|
|
|
2021-11-06 12:33:53 +01:00
|
|
|
@patch("streamlink.session.HTTPSession")
|
|
|
|
def test_interface(self, mock_httpsession):
|
2021-01-09 18:45:04 +01:00
|
|
|
adapter_http = Mock(poolmanager=Mock(connection_pool_kw={}))
|
|
|
|
adapter_https = Mock(poolmanager=Mock(connection_pool_kw={}))
|
|
|
|
adapter_foo = Mock(poolmanager=Mock(connection_pool_kw={}))
|
2021-11-06 12:33:53 +01:00
|
|
|
mock_httpsession.return_value = Mock(adapters={
|
2021-01-09 18:45:04 +01:00
|
|
|
"http://": adapter_http,
|
|
|
|
"https://": adapter_https,
|
|
|
|
"foo://": adapter_foo,
|
|
|
|
})
|
|
|
|
session = self.subject(load_plugins=False)
|
2023-02-16 00:51:25 +01:00
|
|
|
assert session.get_option("interface") is None
|
2021-01-09 18:45:04 +01:00
|
|
|
|
|
|
|
session.set_option("interface", "my-interface")
|
2023-02-16 00:51:25 +01:00
|
|
|
assert adapter_http.poolmanager.connection_pool_kw == {"source_address": ("my-interface", 0)}
|
|
|
|
assert adapter_https.poolmanager.connection_pool_kw == {"source_address": ("my-interface", 0)}
|
|
|
|
assert adapter_foo.poolmanager.connection_pool_kw == {}
|
|
|
|
assert session.get_option("interface") == "my-interface"
|
2021-01-09 18:45:04 +01:00
|
|
|
|
|
|
|
session.set_option("interface", None)
|
2023-02-16 00:51:25 +01:00
|
|
|
assert adapter_http.poolmanager.connection_pool_kw == {}
|
|
|
|
assert adapter_https.poolmanager.connection_pool_kw == {}
|
|
|
|
assert adapter_foo.poolmanager.connection_pool_kw == {}
|
|
|
|
assert session.get_option("interface") is None
|
2021-01-09 18:45:04 +01:00
|
|
|
|
2022-05-11 14:37:09 +02:00
|
|
|
@patch("streamlink.session.urllib3_util_connection", allowed_gai_family=_original_allowed_gai_family)
|
|
|
|
def test_ipv4_ipv6(self, mock_urllib3_util_connection):
|
2021-01-09 18:45:04 +01:00
|
|
|
session = self.subject(load_plugins=False)
|
2022-05-11 14:37:09 +02:00
|
|
|
assert session.get_option("ipv4") is False
|
|
|
|
assert session.get_option("ipv6") is False
|
|
|
|
assert mock_urllib3_util_connection.allowed_gai_family is _original_allowed_gai_family
|
2021-01-09 18:45:04 +01:00
|
|
|
|
|
|
|
session.set_option("ipv4", True)
|
2022-05-11 14:37:09 +02:00
|
|
|
assert session.get_option("ipv4") is True
|
|
|
|
assert session.get_option("ipv6") is False
|
|
|
|
assert mock_urllib3_util_connection.allowed_gai_family is not _original_allowed_gai_family
|
|
|
|
assert mock_urllib3_util_connection.allowed_gai_family() is AF_INET
|
2021-01-09 18:45:04 +01:00
|
|
|
|
|
|
|
session.set_option("ipv4", False)
|
2022-05-11 14:37:09 +02:00
|
|
|
assert session.get_option("ipv4") is False
|
|
|
|
assert session.get_option("ipv6") is False
|
|
|
|
assert mock_urllib3_util_connection.allowed_gai_family is _original_allowed_gai_family
|
2021-01-09 18:45:04 +01:00
|
|
|
|
|
|
|
session.set_option("ipv6", True)
|
2022-05-11 14:37:09 +02:00
|
|
|
assert session.get_option("ipv4") is False
|
|
|
|
assert session.get_option("ipv6") is True
|
|
|
|
assert mock_urllib3_util_connection.allowed_gai_family is not _original_allowed_gai_family
|
|
|
|
assert mock_urllib3_util_connection.allowed_gai_family() is AF_INET6
|
2021-01-09 18:45:04 +01:00
|
|
|
|
|
|
|
session.set_option("ipv6", False)
|
2022-05-11 14:37:09 +02:00
|
|
|
assert session.get_option("ipv4") is False
|
|
|
|
assert session.get_option("ipv6") is False
|
|
|
|
assert mock_urllib3_util_connection.allowed_gai_family is _original_allowed_gai_family
|
2021-01-09 18:45:04 +01:00
|
|
|
|
|
|
|
session.set_option("ipv4", True)
|
|
|
|
session.set_option("ipv6", False)
|
2022-05-11 14:37:09 +02:00
|
|
|
assert session.get_option("ipv4") is True
|
|
|
|
assert session.get_option("ipv6") is False
|
|
|
|
assert mock_urllib3_util_connection.allowed_gai_family is _original_allowed_gai_family
|
|
|
|
|
|
|
|
@patch("streamlink.session.urllib3_util_ssl", DEFAULT_CIPHERS="foo:!bar:baz")
|
|
|
|
def test_http_disable_dh(self, mock_urllib3_util_ssl):
|
|
|
|
session = self.subject(load_plugins=False)
|
|
|
|
assert mock_urllib3_util_ssl.DEFAULT_CIPHERS == "foo:!bar:baz"
|
|
|
|
|
|
|
|
session.set_option("http-disable-dh", True)
|
|
|
|
assert mock_urllib3_util_ssl.DEFAULT_CIPHERS == "foo:!bar:baz:!DH"
|
|
|
|
|
|
|
|
session.set_option("http-disable-dh", True)
|
|
|
|
assert mock_urllib3_util_ssl.DEFAULT_CIPHERS == "foo:!bar:baz:!DH"
|
|
|
|
|
|
|
|
session.set_option("http-disable-dh", False)
|
|
|
|
assert mock_urllib3_util_ssl.DEFAULT_CIPHERS == "foo:!bar:baz"
|
2021-01-09 18:45:04 +01:00
|
|
|
|
2022-08-29 21:05:39 +02:00
|
|
|
|
|
|
|
class TestSessionOptionHttpProxy:
|
|
|
|
@pytest.fixture()
|
2023-02-16 00:51:04 +01:00
|
|
|
def _no_deprecation(self, recwarn: pytest.WarningsRecorder):
|
2022-08-29 21:05:39 +02:00
|
|
|
yield
|
2023-01-06 22:54:16 +01:00
|
|
|
assert recwarn.list == []
|
2022-08-29 21:05:39 +02:00
|
|
|
|
|
|
|
@pytest.fixture()
|
2023-02-16 00:51:04 +01:00
|
|
|
def _logs_deprecation(self, recwarn: pytest.WarningsRecorder):
|
2022-08-29 21:05:39 +02:00
|
|
|
yield
|
2023-03-24 15:33:48 +01:00
|
|
|
assert [(record.category, str(record.message), record.filename) for record in recwarn.list] == [
|
2023-01-12 18:58:54 +01:00
|
|
|
(
|
|
|
|
StreamlinkDeprecationWarning,
|
|
|
|
"The `https-proxy` option has been deprecated in favor of a single `http-proxy` option",
|
2023-03-24 15:33:48 +01:00
|
|
|
__file__,
|
2023-01-12 18:58:54 +01:00
|
|
|
),
|
2022-08-29 21:05:39 +02:00
|
|
|
]
|
|
|
|
|
2023-02-16 00:51:04 +01:00
|
|
|
@pytest.mark.usefixtures("_no_deprecation")
|
|
|
|
def test_https_proxy_default(self, session: Streamlink):
|
2019-07-12 14:59:04 +02:00
|
|
|
session.set_option("http-proxy", "http://testproxy.com")
|
|
|
|
|
2022-08-29 21:05:39 +02:00
|
|
|
assert session.http.proxies["http"] == "http://testproxy.com"
|
|
|
|
assert session.http.proxies["https"] == "http://testproxy.com"
|
2019-07-12 14:59:04 +02:00
|
|
|
|
2023-02-16 00:51:04 +01:00
|
|
|
@pytest.mark.usefixtures("_logs_deprecation")
|
|
|
|
def test_https_proxy_set_first(self, session: Streamlink):
|
2019-07-12 14:59:04 +02:00
|
|
|
session.set_option("https-proxy", "https://testhttpsproxy.com")
|
|
|
|
session.set_option("http-proxy", "http://testproxy.com")
|
|
|
|
|
2022-08-29 21:05:39 +02:00
|
|
|
assert session.http.proxies["http"] == "http://testproxy.com"
|
|
|
|
assert session.http.proxies["https"] == "http://testproxy.com"
|
2019-07-12 14:59:04 +02:00
|
|
|
|
2023-02-16 00:51:04 +01:00
|
|
|
@pytest.mark.usefixtures("_logs_deprecation")
|
|
|
|
def test_https_proxy_default_override(self, session: Streamlink):
|
2019-07-12 14:59:04 +02:00
|
|
|
session.set_option("http-proxy", "http://testproxy.com")
|
|
|
|
session.set_option("https-proxy", "https://testhttpsproxy.com")
|
|
|
|
|
2022-08-29 21:05:39 +02:00
|
|
|
assert session.http.proxies["http"] == "https://testhttpsproxy.com"
|
|
|
|
assert session.http.proxies["https"] == "https://testhttpsproxy.com"
|
2019-07-12 14:59:04 +02:00
|
|
|
|
2023-02-16 00:51:04 +01:00
|
|
|
@pytest.mark.usefixtures("_logs_deprecation")
|
|
|
|
def test_https_proxy_set_only(self, session: Streamlink):
|
2019-07-12 14:59:04 +02:00
|
|
|
session.set_option("https-proxy", "https://testhttpsproxy.com")
|
|
|
|
|
2022-08-29 21:05:39 +02:00
|
|
|
assert session.http.proxies["http"] == "https://testhttpsproxy.com"
|
|
|
|
assert session.http.proxies["https"] == "https://testhttpsproxy.com"
|
2021-10-03 14:56:13 +02:00
|
|
|
|
2023-02-16 00:51:04 +01:00
|
|
|
@pytest.mark.usefixtures("_no_deprecation")
|
|
|
|
def test_http_proxy_socks(self, session: Streamlink):
|
2021-10-03 14:56:13 +02:00
|
|
|
session.set_option("http-proxy", "socks5://localhost:1234")
|
|
|
|
|
2022-08-29 21:05:39 +02:00
|
|
|
assert session.http.proxies["http"] == "socks5://localhost:1234"
|
|
|
|
assert session.http.proxies["https"] == "socks5://localhost:1234"
|
2021-10-03 14:56:13 +02:00
|
|
|
|
2023-02-16 00:51:04 +01:00
|
|
|
@pytest.mark.usefixtures("_logs_deprecation")
|
|
|
|
def test_https_proxy_socks(self, session: Streamlink):
|
2021-10-03 14:56:13 +02:00
|
|
|
session.set_option("https-proxy", "socks5://localhost:1234")
|
|
|
|
|
2022-08-29 21:05:39 +02:00
|
|
|
assert session.http.proxies["http"] == "socks5://localhost:1234"
|
|
|
|
assert session.http.proxies["https"] == "socks5://localhost:1234"
|
plugin.api.http_session: remove parse_* methods
The `parse_{cookies,headers,query_params}` methods were added when the
subclass of `requests.Session` was implemented in order to support
setting cookies, headers and query parameters via `k1=v1;k2=v2` strings
(in addition to key-value dicts) via the session API and via the CLI:
- 936e66dd90f67377451be27951a7d5553a09088c
- c6e54fd57a71a90bbbbdad0508ac1615ccb9ac65
Since these methods implement logic purely for the `Streamlink` session
interface and are not meant to be called by any plugin or stream
implementations which use the session's `HTTPSession` instance, they
should be removed. Cookies, headers and query string parameters should
be set directly on their respective `HTTPSession` attributes:
- `cookies`: instance of `requests.cookies.RequestsCookieJar`
- `headers`: instance of `requests.structures.CaseInsensitiveDict`
- `params`: instance of `dict`
Also, at least in regards to HTTP headers, the `key=value` syntax
does not reflect the syntax of raw HTTP requests/responses or interfaces
of other tools like cURL, etc., so having these methods on the
`HTTPSession` class makes it unnecessarily confusing. The method names
themselves are also confusing, as they suggest that the input gets
parsed and that some result gets returned, which is wrong.
This commit therefore moves the `k1=v1;k2=v2` string logic from the
`http_session` module to the `session` module where it belongs and it
also simplifies the option setter.
2022-09-05 14:58:23 +02:00
|
|
|
|
2023-02-16 00:51:04 +01:00
|
|
|
@pytest.mark.usefixtures("_no_deprecation")
|
|
|
|
def test_get_http_proxy(self, session: Streamlink):
|
2023-01-07 20:50:16 +01:00
|
|
|
session.http.proxies["http"] = "http://testproxy1.com"
|
|
|
|
session.http.proxies["https"] = "http://testproxy2.com"
|
|
|
|
assert session.get_option("http-proxy") == "http://testproxy1.com"
|
|
|
|
|
2023-02-16 00:51:04 +01:00
|
|
|
@pytest.mark.usefixtures("_logs_deprecation")
|
|
|
|
def test_get_https_proxy(self, session: Streamlink):
|
2023-01-07 20:50:16 +01:00
|
|
|
session.http.proxies["http"] = "http://testproxy1.com"
|
|
|
|
session.http.proxies["https"] = "http://testproxy2.com"
|
|
|
|
assert session.get_option("https-proxy") == "http://testproxy2.com"
|
|
|
|
|
2023-03-24 15:33:48 +01:00
|
|
|
@pytest.mark.usefixtures("_logs_deprecation")
|
|
|
|
def test_https_proxy_get_directly(self, session: Streamlink):
|
|
|
|
# The DeprecationWarning's origin must point to this call, even without the set_option() wrapper
|
|
|
|
session.options.get("https-proxy")
|
|
|
|
|
|
|
|
@pytest.mark.usefixtures("_logs_deprecation")
|
|
|
|
def test_https_proxy_set_directly(self, session: Streamlink):
|
|
|
|
# The DeprecationWarning's origin must point to this call, even without the set_option() wrapper
|
|
|
|
session.options.set("https-proxy", "https://foo")
|
|
|
|
|
plugin.api.http_session: remove parse_* methods
The `parse_{cookies,headers,query_params}` methods were added when the
subclass of `requests.Session` was implemented in order to support
setting cookies, headers and query parameters via `k1=v1;k2=v2` strings
(in addition to key-value dicts) via the session API and via the CLI:
- 936e66dd90f67377451be27951a7d5553a09088c
- c6e54fd57a71a90bbbbdad0508ac1615ccb9ac65
Since these methods implement logic purely for the `Streamlink` session
interface and are not meant to be called by any plugin or stream
implementations which use the session's `HTTPSession` instance, they
should be removed. Cookies, headers and query string parameters should
be set directly on their respective `HTTPSession` attributes:
- `cookies`: instance of `requests.cookies.RequestsCookieJar`
- `headers`: instance of `requests.structures.CaseInsensitiveDict`
- `params`: instance of `dict`
Also, at least in regards to HTTP headers, the `key=value` syntax
does not reflect the syntax of raw HTTP requests/responses or interfaces
of other tools like cURL, etc., so having these methods on the
`HTTPSession` class makes it unnecessarily confusing. The method names
themselves are also confusing, as they suggest that the input gets
parsed and that some result gets returned, which is wrong.
This commit therefore moves the `k1=v1;k2=v2` string logic from the
`http_session` module to the `session` module where it belongs and it
also simplifies the option setter.
2022-09-05 14:58:23 +02:00
|
|
|
|
|
|
|
class TestOptionsKeyEqualsValue:
|
|
|
|
@pytest.fixture()
|
|
|
|
def option(self, request, session: Streamlink):
|
|
|
|
option, attr = request.param
|
|
|
|
httpsessionattr = getattr(session.http, attr)
|
|
|
|
assert session.get_option(option) is httpsessionattr
|
|
|
|
assert "foo" not in httpsessionattr
|
|
|
|
assert "bar" not in httpsessionattr
|
|
|
|
yield option
|
|
|
|
assert httpsessionattr.get("foo") == "foo=bar"
|
|
|
|
assert httpsessionattr.get("bar") == "123"
|
|
|
|
|
2023-02-17 17:44:51 +01:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"option",
|
|
|
|
[
|
|
|
|
pytest.param(("http-cookies", "cookies"), id="http-cookies"),
|
|
|
|
pytest.param(("http-headers", "headers"), id="http-headers"),
|
|
|
|
pytest.param(("http-query-params", "params"), id="http-query-params"),
|
|
|
|
],
|
|
|
|
indirect=["option"],
|
|
|
|
)
|
plugin.api.http_session: remove parse_* methods
The `parse_{cookies,headers,query_params}` methods were added when the
subclass of `requests.Session` was implemented in order to support
setting cookies, headers and query parameters via `k1=v1;k2=v2` strings
(in addition to key-value dicts) via the session API and via the CLI:
- 936e66dd90f67377451be27951a7d5553a09088c
- c6e54fd57a71a90bbbbdad0508ac1615ccb9ac65
Since these methods implement logic purely for the `Streamlink` session
interface and are not meant to be called by any plugin or stream
implementations which use the session's `HTTPSession` instance, they
should be removed. Cookies, headers and query string parameters should
be set directly on their respective `HTTPSession` attributes:
- `cookies`: instance of `requests.cookies.RequestsCookieJar`
- `headers`: instance of `requests.structures.CaseInsensitiveDict`
- `params`: instance of `dict`
Also, at least in regards to HTTP headers, the `key=value` syntax
does not reflect the syntax of raw HTTP requests/responses or interfaces
of other tools like cURL, etc., so having these methods on the
`HTTPSession` class makes it unnecessarily confusing. The method names
themselves are also confusing, as they suggest that the input gets
parsed and that some result gets returned, which is wrong.
This commit therefore moves the `k1=v1;k2=v2` string logic from the
`http_session` module to the `session` module where it belongs and it
also simplifies the option setter.
2022-09-05 14:58:23 +02:00
|
|
|
def test_dict(self, session: Streamlink, option: str):
|
|
|
|
session.set_option(option, {"foo": "foo=bar", "bar": "123"})
|
|
|
|
|
2023-02-17 17:44:51 +01:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
("option", "value"),
|
|
|
|
[
|
|
|
|
pytest.param(("http-cookies", "cookies"), "foo=foo=bar;bar=123;baz", id="http-cookies"),
|
|
|
|
pytest.param(("http-headers", "headers"), "foo=foo=bar;bar=123;baz", id="http-headers"),
|
|
|
|
pytest.param(("http-query-params", "params"), "foo=foo=bar&bar=123&baz", id="http-query-params"),
|
|
|
|
],
|
|
|
|
indirect=["option"],
|
|
|
|
)
|
|
|
|
def test_string(self, session: Streamlink, option: str, value: str):
|
|
|
|
session.set_option(option, value)
|
2023-02-17 17:21:37 +01:00
|
|
|
|
|
|
|
|
2023-02-23 22:06:26 +01:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
("option", "attr", "default", "value"),
|
|
|
|
[
|
|
|
|
("http-ssl-cert", "cert", None, "foo"),
|
|
|
|
("http-ssl-verify", "verify", True, False),
|
|
|
|
("http-trust-env", "trust_env", True, False),
|
|
|
|
("http-timeout", "timeout", 20.0, 30.0),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
def test_options_http_other(session: Streamlink, option: str, attr: str, default, value):
|
|
|
|
httpsessionattr = getattr(session.http, attr)
|
|
|
|
assert httpsessionattr == default
|
|
|
|
assert session.get_option(option) == httpsessionattr
|
|
|
|
|
|
|
|
session.set_option(option, value)
|
|
|
|
assert session.get_option(option) == value
|
|
|
|
|
|
|
|
|
2023-02-17 17:21:37 +01:00
|
|
|
class TestOptionsDocumentation:
|
|
|
|
@pytest.fixture()
|
|
|
|
def docstring(self, session: Streamlink):
|
|
|
|
docstring = session.set_option.__doc__
|
|
|
|
assert docstring is not None
|
|
|
|
return docstring
|
|
|
|
|
|
|
|
def test_default_option_is_documented(self, session: Streamlink, docstring: str):
|
|
|
|
assert session.options.keys()
|
|
|
|
for option in session.options:
|
|
|
|
assert f"* - {option}" in docstring, f"Option '{option}' is documented"
|
|
|
|
|
|
|
|
def test_documented_option_exists(self, session: Streamlink, docstring: str):
|
|
|
|
options = session.options
|
|
|
|
setters = options._MAP_SETTERS.keys()
|
|
|
|
documented = re.compile(r"\* - (\S+)").findall(docstring)[1:]
|
|
|
|
assert documented
|
|
|
|
for option in documented:
|
|
|
|
assert option in options or option in setters, f"Documented option '{option}' exists"
|