Skip to content

Commit 82a5269

Browse files
authored
Merge pull request ManiMatter#346 from eadgbear/eadgbear/timeout_fix
[NEEDS CODE REVIEWER] (time-out related) Adding timeout property to instances.
2 parents 58510b1 + 3732748 commit 82a5269

11 files changed

Lines changed: 386 additions & 15 deletions

.github/workflows/build.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ jobs:
5757
needs: unit-tests
5858
runs-on: ubuntu-latest
5959
if: github.event_name == 'push'
60+
permissions:
61+
contents: read
62+
packages: write
6063
steps:
6164
- uses: actions/checkout@v4
6265
with:

config/config_example.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ general:
55
# request_timeout: 15 # Optional: timeout for all HTTP/API calls in seconds
66
# ignored_download_clients: ["emulerr"]
77
# ssl_verification: false # Optional: Defaults to true
8+
# request_timeout: 15 # Optional: Request timeout in seconds. Defaults to 15. Can be overridden per instance.
89
# private_tracker_handling: "obsolete_tag" # remove, skip, obsolete_tag. Optional. Default: remove
910
# public_tracker_handling: "remove" # remove, skip, obsolete_tag. Optional. Default: remove
1011
# obsolete_tag: "Obsolete" # optional. Default: "Obsolete"
@@ -53,6 +54,7 @@ instances:
5354
sonarr:
5455
- base_url: "http://sonarr:8989"
5556
api_key: "xxxx"
57+
# timeout: 30 # Optional: Overrides general timeout for this instance.
5658
radarr:
5759
- base_url: "http://radarr:7878"
5860
api_key: "xxxx"
@@ -72,6 +74,7 @@ download_clients:
7274
# username: xxxx # (optional -> if not provided, assuming not needed)
7375
# password: xxxx # (optional -> if not provided, assuming not needed)
7476
# name: "qBittorrent" # (optional -> if not provided, assuming "qBittorrent". Must correspond with what is specified in your *arr as download client name)
77+
# timeout: 30 # (optional -> overrides general timeout for this instance)
7578
# sabnzbd:
7679
# - base_url: "http://sabnzbd:8080" # SABnzbd server URL
7780
# api_key: "your_api_key_here" # (required -> SABnzbd API key)

src/settings/_download_clients_qbit.py

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,10 @@ def __init__(
4747
username: str = None,
4848
password: str = None,
4949
name: str = None,
50+
timeout: int | None = None,
5051
):
5152
self.settings = settings
53+
self._timeout = timeout
5254
if not base_url:
5355
logger.error("Skipping qBittorrent client entry: 'base_url' is required.")
5456
error = "qBittorrent client must have a 'base_url'."
@@ -68,6 +70,13 @@ def __init__(
6870

6971
self._remove_none_attributes()
7072

73+
@property
74+
def timeout(self):
75+
instance_timeout = getattr(self, "_timeout", None)
76+
if instance_timeout is not None:
77+
return instance_timeout
78+
return getattr(getattr(self.settings, "general", None), "request_timeout", 15)
79+
7180
def _remove_none_attributes(self):
7281
"""Remove attributes that are None to keep the object clean."""
7382
for attr in list(vars(self)):
@@ -95,6 +104,7 @@ def _connection_error():
95104
"post",
96105
endpoint,
97106
self.settings,
107+
timeout=self.timeout,
98108
data=data,
99109
headers=headers,
100110
ignore_test_run=True,
@@ -133,6 +143,7 @@ async def fetch_version(self):
133143
"get",
134144
endpoint,
135145
self.settings,
146+
timeout=self.timeout,
136147
cookies=self.cookie,
137148
)
138149
self.version = response.text[1:] # Remove the '_v' prefix
@@ -161,7 +172,7 @@ async def create_tag(self, tag: str):
161172
"_download_clients_qBit.py/create_tag: Checking if tag '{tag}' exists (and creating it if not)",
162173
)
163174
url = f"{self.api_url}/torrents/tags"
164-
response = await make_request("get", url, self.settings, cookies=self.cookie)
175+
response = await make_request("get", url, self.settings, timeout=self.timeout, cookies=self.cookie)
165176
current_tags = response.json()
166177

167178
if tag not in current_tags:
@@ -171,6 +182,7 @@ async def create_tag(self, tag: str):
171182
"post",
172183
self.api_url + "/torrents/createTags",
173184
self.settings,
185+
timeout=self.timeout,
174186
data=data,
175187
cookies=self.cookie,
176188
)
@@ -196,6 +208,7 @@ async def set_unwanted_folder(self):
196208
"get",
197209
endpoint,
198210
self.settings,
211+
timeout=self.timeout,
199212
cookies=self.cookie,
200213
)
201214
qbit_settings = response.json()
@@ -209,6 +222,7 @@ async def set_unwanted_folder(self):
209222
"post",
210223
self.api_url + "/app/setPreferences",
211224
self.settings,
225+
timeout=self.timeout,
212226
data=data,
213227
cookies=self.cookie,
214228
)
@@ -229,6 +243,7 @@ async def check_qbit_reachability(self):
229243
"post",
230244
endpoint,
231245
self.settings,
246+
timeout=self.timeout,
232247
data=data,
233248
headers=headers,
234249
log_error=False,
@@ -251,6 +266,7 @@ async def check_connected(self):
251266
"get",
252267
self.api_url + "/sync/maindata",
253268
self.settings,
269+
timeout=self.timeout,
254270
cookies=self.cookie,
255271
)
256272
).json()
@@ -353,6 +369,7 @@ async def set_tag(self, tags, hashes):
353369
"post",
354370
self.api_url + "/torrents/addTags",
355371
self.settings,
372+
timeout=self.timeout,
356373
data=data,
357374
cookies=self.cookie,
358375
)
@@ -376,6 +393,7 @@ async def get_qbit_items(self, hashes: list[str] | str | None = None) -> list[di
376393
method="get",
377394
endpoint=f"{self.api_url}/torrents/info",
378395
settings=self.settings,
396+
timeout=self.timeout,
379397
params=None, # Retrieve all torrents
380398
cookies=self.cookie,
381399
)
@@ -395,12 +413,13 @@ async def get_qbit_items(self, hashes: list[str] | str | None = None) -> list[di
395413
async def get_torrent_properties(self, qbit_hash):
396414
params = {"hash": qbit_hash.lower()}
397415
response = await make_request(
398-
"get",
399-
self.api_url + "/torrents/properties",
400-
self.settings,
401-
params=params,
402-
cookies=self.cookie,
403-
)
416+
"get",
417+
self.api_url + "/torrents/properties",
418+
self.settings,
419+
timeout=self.timeout,
420+
params=params,
421+
cookies=self.cookie,
422+
)
404423
return response.json()
405424

406425

@@ -411,6 +430,7 @@ async def get_torrent_files(self, download_id):
411430
method="get",
412431
endpoint=self.api_url + "/torrents/files",
413432
settings=self.settings,
433+
timeout=self.timeout,
414434
params={"hash": download_id.lower()},
415435
cookies=self.cookie,
416436
)
@@ -429,6 +449,7 @@ async def set_torrent_file_priority(self, download_id, file_id, priority=0):
429449
"post",
430450
self.api_url + "/torrents/filePrio",
431451
self.settings,
452+
timeout=self.timeout,
432453
data=data,
433454
cookies=self.cookie,
434455
)
@@ -440,6 +461,7 @@ async def set_bandwidth_usage(self):
440461
method="get",
441462
endpoint=self.api_url + "/transfer/info",
442463
settings=self.settings,
464+
timeout=self.timeout,
443465
cookies=self.cookie,
444466
)
445467
records = extract_json_from_response(response)
@@ -476,6 +498,7 @@ async def remove_download(self, download_hash: str, delete_files: bool = True):
476498
"post",
477499
f"{self.api_url}/torrents/delete",
478500
self.settings,
501+
timeout=self.timeout,
479502
data=data,
480503
cookies=self.cookie,
481504
)

src/settings/_download_clients_sabnzbd.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,10 @@ def __init__(
4343
base_url: str = None,
4444
api_key: str = None,
4545
name: str = None,
46+
timeout: int | None = None,
4647
):
4748
self.settings = settings
49+
self._timeout = timeout
4850
if not base_url:
4951
logger.error("Skipping SABnzbd client entry: 'base_url' is required.")
5052
error = "SABnzbd client must have a 'base_url'."
@@ -68,6 +70,13 @@ def __init__(
6870

6971
self._remove_none_attributes()
7072

73+
@property
74+
def timeout(self):
75+
instance_timeout = getattr(self, "_timeout", None)
76+
if instance_timeout is not None:
77+
return instance_timeout
78+
return getattr(getattr(self.settings, "general", None), "request_timeout", 15)
79+
7180
def _remove_none_attributes(self):
7281
"""Remove attributes that are None to keep the object clean."""
7382
for attr in list(vars(self)):
@@ -80,7 +89,7 @@ async def fetch_version(self):
8089
"_download_clients_sabnzbd.py/fetch_version: Getting SABnzbd Version"
8190
)
8291
params = {"mode": "version", "apikey": self.api_key, "output": "json"}
83-
response = await make_request("get", self.api_url, self.settings, params=params)
92+
response = await make_request("get", self.api_url, self.settings, timeout=self.timeout, params=params)
8493
response_data = response.json()
8594
self.version = response_data.get("version", "unknown")
8695
logger.debug(
@@ -109,6 +118,7 @@ async def check_sabnzbd_reachability(self):
109118
"get",
110119
self.api_url,
111120
self.settings,
121+
timeout=self.timeout,
112122
params=params,
113123
log_error=False,
114124
ignore_test_run=True,
@@ -129,6 +139,7 @@ async def check_connected(self):
129139
"get",
130140
self.api_url,
131141
self.settings,
142+
timeout=self.timeout,
132143
params=params,
133144
)
134145
status_data = response.json()
@@ -160,6 +171,7 @@ async def get_queue_items(self):
160171
"get",
161172
self.api_url,
162173
self.settings,
174+
timeout=self.timeout,
163175
params=params,
164176
)
165177
queue_data = response.json()
@@ -175,6 +187,7 @@ async def get_history_items(self):
175187
"get",
176188
self.api_url,
177189
self.settings,
190+
timeout=self.timeout,
178191
params=params,
179192
)
180193
history_data = response.json()
@@ -196,6 +209,7 @@ async def remove_download(self, nzo_id: str):
196209
"get",
197210
self.api_url,
198211
self.settings,
212+
timeout=self.timeout,
199213
params=params,
200214
)
201215

@@ -215,6 +229,7 @@ async def pause_download(self, nzo_id: str):
215229
"get",
216230
self.api_url,
217231
self.settings,
232+
timeout=self.timeout,
218233
params=params,
219234
)
220235

@@ -234,6 +249,7 @@ async def resume_download(self, nzo_id: str):
234249
"get",
235250
self.api_url,
236251
self.settings,
252+
timeout=self.timeout,
237253
params=params,
238254
)
239255

@@ -252,6 +268,7 @@ async def retry_download(self, nzo_id: str):
252268
"get",
253269
self.api_url,
254270
self.settings,
271+
timeout=self.timeout,
255272
params=params,
256273
)
257274

@@ -308,6 +325,7 @@ async def get_download_speed(self):
308325
"get",
309326
self.api_url,
310327
self.settings,
328+
timeout=self.timeout,
311329
params=params,
312330
)
313331
status_data = response.json()

src/settings/_general.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class General:
1313
timer: float = 10.0
1414
request_timeout: float = 15.0
1515
ssl_verification: bool = True
16+
request_timeout: int = 15
1617
ignored_download_clients: list = []
1718
private_tracker_handling: str = "remove"
1819
public_tracker_handling: str = "remove"
@@ -30,6 +31,7 @@ def __init__(self, config):
3031
self.ssl_verification = general_config.get(
3132
"ssl_verification", self.ssl_verification
3233
)
34+
self.request_timeout = general_config.get("request_timeout", self.request_timeout)
3335
self.ignored_download_clients = general_config.get(
3436
"ignored_download_clients", self.ignored_download_clients
3537
)

0 commit comments

Comments
 (0)