diff --git a/docs/source/behavior&configuration.rst b/docs/source/behavior&configuration.rst
index 8f7893555..b0f8b56d0 100644
--- a/docs/source/behavior&configuration.rst
+++ b/docs/source/behavior&configuration.rst
@@ -3,13 +3,17 @@ Behavior & Configuration
Host, Username and Password
***************************
-* The authentication credentials can be provided when instantiating :class:`~qbittorrentapi.client.Client`:
+* The authentication credentials can be provided when instantiating
+ :class:`~qbittorrentapi.client.Client`:
.. code:: python
qbt_client = Client(host="localhost:8080", username='...', password='...')
-* The credentials can also be specified after :class:`~qbittorrentapi.client.Client` is created but calling :meth:`~qbittorrentapi.auth.AuthAPIMixIn.auth_log_in` is not strictly necessary to authenticate the client; this will happen automatically for any API request.
+* The credentials can also be specified after :class:`~qbittorrentapi.client.Client`
+ is created but calling :meth:`~qbittorrentapi.auth.AuthAPIMixIn.auth_log_in` is not
+ strictly necessary to authenticate the client; this will happen automatically for any
+ API request.
.. code:: python
@@ -23,10 +27,16 @@ Host, Username and Password
qBittorrent Session Management
******************************
-* Any time a connection is established with qBittorrent, it instantiates a session to manage authentication for all subsequent API requests.
-* This client will transparently manage sessions by ensuring the client is always logged in in-line with any API request including requesting a new session upon expiration of an existing session.
-* However, each new :class:`~qbittorrentapi.client.Client` instantiation will create a new session in qBittorrent.
-* Therefore, if many :class:`~qbittorrentapi.client.Client` instances will be created be sure to call :class:`~qbittorrentapi.auth.AuthAPIMixIn.auth_log_out` for each instance or use a context manager.
+* Any time a connection is established with qBittorrent, it instantiates a session to
+ manage authentication for all subsequent API requests.
+* This client will transparently manage sessions by ensuring the client is always logged
+ in in-line with any API request including requesting a new session upon expiration of
+ an existing session.
+* However, each new :class:`~qbittorrentapi.client.Client` instantiation will create a
+ new session in qBittorrent.
+* Therefore, if many :class:`~qbittorrentapi.client.Client` instances will be created be
+ sure to call :class:`~qbittorrentapi.auth.AuthAPIMixIn.auth_log_out` for each instance
+ or use a context manager.
* Otherwise, qBittorrent may experience abnormally high memory usage.
.. code:: python
@@ -35,12 +45,17 @@ qBittorrent Session Management
if qbt_client.torrents_add(urls="...") != "Ok.":
raise Exception("Failed to add torrent.")
-Untrusted WebUI Certificate
-***************************
-* qBittorrent allows you to configure HTTPS with an untrusted certificate; this commonly includes self-signed certificates.
-* When using such a certificate, instantiate Client with ``VERIFY_WEBUI_CERTIFICATE=False`` or set environment variable ``QBITTORRENTAPI_DO_NOT_VERIFY_WEBUI_CERTIFICATE`` to a non-null value.
+Untrusted Web API Certificate
+*****************************
+* qBittorrent allows you to configure HTTPS with an untrusted certificate; this commonly
+ includes self-signed certificates.
+* When using such a certificate, instantiate Client with
+ ``VERIFY_WEBUI_CERTIFICATE=False`` or set environment variable
+ ``QBITTORRENTAPI_DO_NOT_VERIFY_WEBUI_CERTIFICATE`` to a non-null value.
* Failure to do this for will cause connections to qBittorrent to fail.
-* As a word of caution, doing this actually does turn off certificate verification. Therefore, for instance, potential man-in-the-middle attacks will not be detected and reported (since the error is suppressed). However, the connection will remain encrypted.
+* As a word of caution, doing this actually does turn off certificate verification.
+ Therefore, for instance, potential man-in-the-middle attacks will not be detected and
+ reported (since the error is suppressed). However, the connection will remain encrypted.
.. code:: python
@@ -48,11 +63,18 @@ Untrusted WebUI Certificate
Requests Configuration
**********************
-* The `Requests `_ package is used to issue HTTP requests to qBittorrent to facilitate this API.
-* Much of `Requests` configuration for making HTTP requests can be controlled with parameters passed along with the request payload.
-* For instance, HTTP Basic Authorization credentials can be provided via ``auth``, timeouts via ``timeout``, or Cookies via ``cookies``. See `Requests documentation `_ for full details.
-* These parameters are exposed here in two ways; the examples below tell ``Requests`` to use a connect timeout of 3.1 seconds and a read timeout of 30 seconds.
-* When you instantiate :class:`~qbittorrentapi.client.Client`, you can specify the parameters to use in all HTTP requests to qBittorrent:
+* The `Requests `_ package is used to issue
+ HTTP requests to qBittorrent to facilitate this API.
+* Much of ``Requests`` configuration for making HTTP requests can be controlled with
+ parameters passed along with the request payload.
+* For instance, HTTP Basic Authorization credentials can be provided via ``auth``,
+ timeouts via ``timeout``, or Cookies via ``cookies``. See
+ `Requests documentation `_
+ for full details.
+* These parameters are exposed here in two ways; the examples below tell ``Requests`` to
+ use a connect timeout of 3.1 seconds and a read timeout of 30 seconds.
+* When you instantiate :class:`~qbittorrentapi.client.Client`, you can specify the
+ parameters to use in all HTTP requests to qBittorrent:
.. code:: python
@@ -64,11 +86,22 @@ Requests Configuration
qbt_client.torrents_info(..., requests_args={'timeout': (3.1, 30)})
+* Additionally, configuration for the :class:`~requests.adapters.HTTPAdapter` for the
+ :class:`~requests.Session` can be specified via the ``HTTPADAPTER_ARGS`` parameter for
+ :class:`~qbittorrentapi.client.Client`:
+
+.. code:: python
+
+ qbt_client = Client(..., HTTPADAPTER_ARGS={"pool_connections": 100, "pool_maxsize": 100}
+
Additional HTTP Headers
***********************
-* For consistency, HTTP Headers can be specified using the method above; for backwards compatibility, the methods below are supported as well.
-* Either way, these additional headers will be incorporated (using clobbering) into the rest of the headers to be sent.
-* To send a custom HTTP header in all requests made from an instantiated client, declare them during instantiation:
+* For consistency, HTTP Headers can be specified using the method above; for backwards
+ compatibility, the methods below are supported as well.
+* Either way, these additional headers will be incorporated (using clobbering) into the
+ rest of the headers to be sent.
+* To send a custom HTTP header in all requests made from an instantiated client, declare
+ them during instantiation:
.. code:: python
@@ -82,9 +115,12 @@ Additional HTTP Headers
Unimplemented API Endpoints
***************************
-* Since the qBittorrent Web API has evolved over time, some endpoints may not be available from the qBittorrent host.
-* By default, if a request is made to endpoint that doesn't exist for the version of the qBittorrent host (e.g., the Search endpoints were introduced in Web API v2.1.1), there's a debug logger output and None is returned.
-* To raise ``NotImplementedError`` instead, instantiate Client with:
+* Since the qBittorrent Web API has evolved over time, some endpoints may not be
+ available from the qBittorrent host.
+* By default, if a request is made to endpoint that doesn't exist for the version of the
+ qBittorrent host (e.g., the Search endpoints were introduced in Web API v2.1.1),
+ there's a debug logger output and None is returned.
+* To raise :any:`NotImplementedError` instead, instantiate Client with:
.. code:: python
@@ -92,15 +128,20 @@ Unimplemented API Endpoints
qBittorrent Version Checking
****************************
-* It is also possible to either raise an Exception for qBittorrent hosts that are not "fully" supported or manually check for support.
-* The most likely situation for this to occur is if the qBittorrent team publishes a new release but its changes have not been incorporated in to this client yet.
-* Instantiate Client like below to raise ``UnsupportedQbittorrentVersion`` exception for versions not fully supported:
+* It is also possible to either raise an Exception for qBittorrent hosts that are not
+ "fully" supported or manually check for support.
+* The most likely situation for this to occur is if the qBittorrent team publishes a new
+ release but its changes have not been incorporated in to this client yet.
+* Instantiate Client like below to raise
+ :class:`~qbittorrentapi.exceptions.UnsupportedQbittorrentVersion` exception for versions
+ not fully supported:
.. code:: python
qbt_client = Client(..., RAISE_ERROR_FOR_UNSUPPORTED_QBITTORRENT_VERSIONS=True)
-* Additionally, the :doc:`qbittorrentapi.Version ` class can be used for manual introspection of the versions.
+* Additionally, :class:`~qbittorrentapi._version_support.Version` can be used for manual introspection of
+ the versions.
.. code:: python
@@ -108,7 +149,8 @@ qBittorrent Version Checking
Disable Logging Debug Output
****************************
-* Instantiate Client with ``DISABLE_LOGGING_DEBUG_OUTPUT=True`` or manually disable logging for the relevant packages:
+* Instantiate Client with ``DISABLE_LOGGING_DEBUG_OUTPUT=True`` or manually disable
+ logging for the relevant packages:
.. code:: python
diff --git a/src/qbittorrentapi/client.py b/src/qbittorrentapi/client.py
index 951e25400..8178105a6 100644
--- a/src/qbittorrentapi/client.py
+++ b/src/qbittorrentapi/client.py
@@ -66,7 +66,7 @@ class Client(
>>> client = Client(host='localhost:8080', username='admin', password='adminadmin')
>>> torrents = client.torrents_info()
- :param host: hostname for qBittorrent Web API, ``[http[s]://]localhost[:8080][/path]``
+ :param host: hostname for qBittorrent Web API, ``[http[s]://]hostname[:port][/path]``
:param port: port number for qBittorrent Web API (ignored if host contains a port)
:param username: username for qBittorrent Web API
:param password: password for qBittorrent Web API
@@ -79,27 +79,30 @@ class Client(
back. Alternatively, set this to True only for an individual method call.
For instance, when requesting the files for a torrent:
``client.torrents_files(torrent_hash='...', SIMPLE_RESPONSES=True)``
- :param VERIFY_WEBUI_CERTIFICATE: Set to False to skip verify certificate for
+ :param VERIFY_WEBUI_CERTIFICATE: Set to ``False`` to skip verify certificate for
HTTPS connections; for instance, if the connection is using a self-signed
- certificate. Not setting this to False for self-signed certs will cause a
+ certificate. Not setting this to ``False`` for self-signed certs will cause a
:class:`~qbittorrentapi.exceptions.APIConnectionError` exception to be raised.
:param EXTRA_HEADERS: Dictionary of HTTP Headers to include in all requests
made to qBittorrent.
- :param REQUESTS_ARGS: Dictionary of configuration for Requests package:
- ``_
+ :param REQUESTS_ARGS: Dictionary of configuration for each HTTP request made by
+ :any:`requests.request`.
+ :param HTTPADAPTER_ARGS: Dictionary of configuration for
+ :class:`~requests.adapters.HTTPAdapter`.
:param FORCE_SCHEME_FROM_HOST: If a scheme (i.e. ``http`` or ``https``) is
specified in host, it will be used regardless of whether qBittorrent is
configured for HTTP or HTTPS communication. Normally, this client will
attempt to determine which scheme qBittorrent is actually listening on...
but this can cause problems in rare cases. Defaults ``False``.
- :param RAISE_NOTIMPLEMENTEDERROR_FOR_UNIMPLEMENTED_API_ENDPOINTS: Some Endpoints
- may not be implemented in older versions of qBittorrent. Setting this to True
- will raise a :class:`NotImplementedError` instead of just returning``None``.
- :param RAISE_ERROR_FOR_UNSUPPORTED_QBITTORRENT_VERSIONS: raise the
- UnsupportedQbittorrentVersion exception if the connected version of
- qBittorrent is not fully supported by this client. Defaults ``False``.
+ :param RAISE_NOTIMPLEMENTEDERROR_FOR_UNIMPLEMENTED_API_ENDPOINTS: Some endpoints
+ may not be implemented in older versions of qBittorrent. Setting this to ``True``
+ will raise a :class:`NotImplementedError` instead of just returning ``None``.
+ :param RAISE_ERROR_FOR_UNSUPPORTED_QBITTORRENT_VERSIONS: Raise
+ :class:`~qbittorrentapi.exceptions.UnsupportedQbittorrentVersion` if the
+ connected version of qBittorrent is not fully supported by this client.
+ Defaults ``False``.
:param DISABLE_LOGGING_DEBUG_OUTPUT: Turn off debug output from logging for
- this package as well as Requests & urllib3.
+ this package as well as ``requests`` & ``urllib3``.
""" # noqa: E501
def __init__(
@@ -108,8 +111,10 @@ def __init__(
port: str | int | None = None,
username: str | None = None,
password: str | None = None,
+ *,
EXTRA_HEADERS: Mapping[str, str] | None = None,
REQUESTS_ARGS: Mapping[str, Any] | None = None,
+ HTTPADAPTER_ARGS: Mapping[str, Any] | None = None,
VERIFY_WEBUI_CERTIFICATE: bool = True,
FORCE_SCHEME_FROM_HOST: bool = False,
RAISE_NOTIMPLEMENTEDERROR_FOR_UNIMPLEMENTED_API_ENDPOINTS: bool = False,
@@ -125,6 +130,7 @@ def __init__(
password=password,
EXTRA_HEADERS=EXTRA_HEADERS,
REQUESTS_ARGS=REQUESTS_ARGS,
+ HTTPADAPTER_ARGS=HTTPADAPTER_ARGS,
VERIFY_WEBUI_CERTIFICATE=VERIFY_WEBUI_CERTIFICATE,
FORCE_SCHEME_FROM_HOST=FORCE_SCHEME_FROM_HOST,
RAISE_NOTIMPLEMENTEDERROR_FOR_UNIMPLEMENTED_API_ENDPOINTS=RAISE_NOTIMPLEMENTEDERROR_FOR_UNIMPLEMENTED_API_ENDPOINTS, # noqa: E501
diff --git a/src/qbittorrentapi/request.py b/src/qbittorrentapi/request.py
index 7ed6bbbbc..73877267c 100644
--- a/src/qbittorrentapi/request.py
+++ b/src/qbittorrentapi/request.py
@@ -240,6 +240,7 @@ def __init__(
password: str | None = None,
EXTRA_HEADERS: Mapping[str, str] | None = None,
REQUESTS_ARGS: Mapping[str, Any] | None = None,
+ HTTPADAPTER_ARGS: Mapping[str, Any] | None = None,
VERIFY_WEBUI_CERTIFICATE: bool = True,
FORCE_SCHEME_FROM_HOST: bool = False,
RAISE_NOTIMPLEMENTEDERROR_FOR_UNIMPLEMENTED_API_ENDPOINTS: bool = False,
@@ -256,6 +257,7 @@ def __init__(
self._initialize_settings(
EXTRA_HEADERS=EXTRA_HEADERS,
REQUESTS_ARGS=REQUESTS_ARGS,
+ HTTPADAPTER_ARGS=HTTPADAPTER_ARGS,
VERIFY_WEBUI_CERTIFICATE=VERIFY_WEBUI_CERTIFICATE,
FORCE_SCHEME_FROM_HOST=FORCE_SCHEME_FROM_HOST,
RAISE_NOTIMPLEMENTEDERROR_FOR_UNIMPLEMENTED_API_ENDPOINTS=(
@@ -325,6 +327,7 @@ def _initialize_settings(
self,
EXTRA_HEADERS: Mapping[str, str] | None = None,
REQUESTS_ARGS: Mapping[str, Any] | None = None,
+ HTTPADAPTER_ARGS: Mapping[str, Any] | None = None,
VERIFY_WEBUI_CERTIFICATE: bool = True,
FORCE_SCHEME_FROM_HOST: bool = False,
RAISE_NOTIMPLEMENTEDERROR_FOR_UNIMPLEMENTED_API_ENDPOINTS: bool = False,
@@ -338,6 +341,9 @@ def _initialize_settings(
# Configuration parameters
self._EXTRA_HEADERS = dict(EXTRA_HEADERS) if EXTRA_HEADERS is not None else {}
self._REQUESTS_ARGS = dict(REQUESTS_ARGS) if REQUESTS_ARGS is not None else {}
+ self._HTTPADAPTER_ARGS = (
+ dict(HTTPADAPTER_ARGS) if HTTPADAPTER_ARGS is not None else {}
+ )
self._VERIFY_WEBUI_CERTIFICATE = bool(VERIFY_WEBUI_CERTIFICATE)
self._VERBOSE_RESPONSE_LOGGING = bool(VERBOSE_RESPONSE_LOGGING)
self._SIMPLE_RESPONSES = bool(SIMPLE_RESPONSES)
@@ -909,14 +915,20 @@ def _session(self) -> QbittorrentSession:
# at any rate, the retries count in request_manager should always be
# at least 2 to accommodate significant settings changes in qBittorrent
# such as enabling HTTPs in Web UI settings.
- adapter = HTTPAdapter(
- max_retries=Retry(
+ default_adapter_config = {
+ "max_retries": Retry(
total=1,
read=1,
connect=1,
status_forcelist={500, 502, 504},
raise_on_status=False,
)
+ }
+ adapter = HTTPAdapter(
+ **{
+ **default_adapter_config,
+ **self._HTTPADAPTER_ARGS,
+ }
)
self._http_session.mount("http://", adapter)
self._http_session.mount("https://", adapter)
diff --git a/tests/test_request.py b/tests/test_request.py
index 2e61e8da5..a27313a2a 100644
--- a/tests/test_request.py
+++ b/tests/test_request.py
@@ -10,6 +10,7 @@
from qbittorrentapi.request import Request
from qbittorrentapi.torrents import TorrentDictionary, TorrentInfoList
from requests import Response
+from requests.adapters import DEFAULT_POOLBLOCK, DEFAULT_POOLSIZE
from tests.conftest import IS_QBT_DEV
from tests.utils import mkpath
@@ -708,3 +709,33 @@ def test_not_implemented_error(monkeypatch, client):
monkeypatch.setattr(client, "app_web_api_version", MagicMock(return_value="10.0.0"))
with pytest.raises(NotImplementedError, match=r"This endpoint was removed"):
client.search_categories()
+
+
+def test_http_adapter_defaults():
+ client = Client(
+ RAISE_NOTIMPLEMENTEDERROR_FOR_UNIMPLEMENTED_API_ENDPOINTS=True,
+ VERIFY_WEBUI_CERTIFICATE=False,
+ )
+ assert client._session.adapters["http://"] is client._session.adapters["https://"]
+ assert client._session.adapters["http://"].max_retries.total == 1
+ assert client._session.adapters["http://"]._pool_connections == DEFAULT_POOLSIZE
+ assert client._session.adapters["http://"]._pool_maxsize == DEFAULT_POOLSIZE
+ assert client._session.adapters["http://"]._pool_block is DEFAULT_POOLBLOCK
+
+
+def test_http_adapter_overrides():
+ client = Client(
+ RAISE_NOTIMPLEMENTEDERROR_FOR_UNIMPLEMENTED_API_ENDPOINTS=True,
+ VERIFY_WEBUI_CERTIFICATE=False,
+ HTTPADAPTER_ARGS=dict(
+ pool_connections=100,
+ pool_maxsize=50,
+ max_retries=10,
+ pool_block=True,
+ ),
+ )
+ assert client._session.adapters["http://"] is client._session.adapters["https://"]
+ assert client._session.adapters["http://"].max_retries.total == 10
+ assert client._session.adapters["http://"]._pool_connections == 100
+ assert client._session.adapters["http://"]._pool_maxsize == 50
+ assert client._session.adapters["http://"]._pool_block is True