Added sigterm handling to exit cleanly when running in Docker.

Fixed typos and various linting issues such as PEP violations.
Added ruff and fixed common issues and linting issues.
This commit is contained in:
NaruZosa
2025-05-25 16:54:51 +10:00
parent 22bdc9ab43
commit 2e6973bea4
56 changed files with 704 additions and 658 deletions

View File

@@ -1,16 +1,16 @@
import requests
from packaging import version
from src.utils.log_setup import logger
from src.settings._config_as_yaml import get_config_as_yaml
from src.settings._constants import (
ApiEndpoints,
MinVersions,
FullQueueParameter,
DetailItemKey,
DetailItemSearchCommand,
FullQueueParameter,
MinVersions,
)
from src.settings._config_as_yaml import get_config_as_yaml
from src.utils.common import make_request, wait_and_exit
from src.utils.log_setup import logger
class Tracker:
@@ -52,9 +52,9 @@ class Instances:
"""Return a list of arr instances matching the given arr_type."""
return [arr for arr in self.arrs if arr.arr_type == arr_type]
def config_as_yaml(self, hide_internal_attr=True):
"""Logs all configured Arr instances while masking sensitive attributes."""
internal_attributes={
def config_as_yaml(self, *, hide_internal_attr=True):
"""Log all configured Arr instances while masking sensitive attributes."""
internal_attributes = {
"settings",
"api_url",
"min_version",
@@ -65,7 +65,7 @@ class Instances:
"detail_item_id_key",
"detail_item_ids_key",
"detail_item_search_command",
}
}
outputs = []
for arr_type in ["sonarr", "radarr", "readarr", "lidarr", "whisparr"]:
@@ -81,8 +81,6 @@ class Instances:
return "\n".join(outputs)
def check_any_arrs(self):
"""Check if there are any ARR instances."""
if not self.arrs:
@@ -117,12 +115,11 @@ class ArrInstances(list):
arr_type=arr_type,
base_url=client_config["base_url"],
api_key=client_config["api_key"],
)
),
)
except KeyError as e:
logger.error(
f"Missing required key {e} in {arr_type} client config."
)
error = f"Missing required key {e} in {arr_type} client config."
logger.error(error)
class ArrInstance:
@@ -135,11 +132,13 @@ class ArrInstance:
def __init__(self, settings, arr_type: str, base_url: str, api_key: str):
if not base_url:
logger.error(f"Skipping {arr_type} client entry: 'base_url' is required.")
raise ValueError(f"{arr_type} client must have a 'base_url'.")
error = f"{arr_type} client must have a 'base_url'."
raise ValueError(error)
if not api_key:
logger.error(f"Skipping {arr_type} client entry: 'api_key' is required.")
raise ValueError(f"{arr_type} client must have an 'api_key'.")
error = f"{arr_type} client must have an 'api_key'."
raise ValueError(error)
self.settings = settings
self.arr_type = arr_type
@@ -151,8 +150,8 @@ class ArrInstance:
self.detail_item_key = getattr(DetailItemKey, arr_type)
self.detail_item_id_key = self.detail_item_key + "Id"
self.detail_item_ids_key = self.detail_item_key + "Ids"
self.detail_item_search_command = getattr(DetailItemSearchCommand, arr_type)
self.detail_item_search_command = getattr(DetailItemSearchCommand, arr_type)
async def _check_ui_language(self):
"""Check if the UI language is set to English."""
endpoint = self.api_url + "/config/ui"
@@ -162,25 +161,26 @@ class ArrInstance:
if ui_language > 1: # Not English
logger.error("!! %s Error: !!", self.name)
logger.error(
f"> Decluttarr only works correctly if UI language is set to English (under Settings/UI in {self.name})"
f"> Decluttarr only works correctly if UI language is set to English (under Settings/UI in {self.name})",
)
logger.error(
"> Details: https://github.com/ManiMatter/decluttarr/issues/132)"
"> Details: https://github.com/ManiMatter/decluttarr/issues/132)",
)
raise ArrError("Not English")
error = "Not English"
raise ArrError(error)
def _check_min_version(self, status):
"""Check if ARR instance meets minimum version requirements."""
self.version = status["version"]
min_version = getattr(self.settings.min_versions, self.arr_type)
if min_version:
if version.parse(self.version) < version.parse(min_version):
logger.error("!! %s Error: !!", self.name)
logger.error(
f"> Please update {self.name} ({self.base_url}) to at least version {min_version}. Current version: {self.version}"
)
raise ArrError("Not meeting minimum version requirements")
if min_version and version.parse(self.version) < version.parse(min_version):
logger.error("!! %s Error: !!", self.name)
logger.error(
f"> Please update {self.name} ({self.base_url}) to at least version {min_version}. Current version: {self.version}",
)
error = f"Not meeting minimum version requirements: {min_version}"
logger.error(error)
def _check_arr_type(self, status):
"""Check if the ARR instance is of the correct type."""
@@ -188,9 +188,10 @@ class ArrInstance:
if actual_arr_type.lower() != self.arr_type:
logger.error("!! %s Error: !!", self.name)
logger.error(
f"> Your {self.name} ({self.base_url}) points to a {actual_arr_type} instance, rather than {self.arr_type}. Did you specify the wrong IP?"
f"> Your {self.name} ({self.base_url}) points to a {actual_arr_type} instance, rather than {self.arr_type}. Did you specify the wrong IP?",
)
raise ArrError("Wrong Arr Type")
error = "Wrong Arr Type"
logger.error(error)
async def _check_reachability(self):
"""Check if ARR instance is reachable."""
@@ -198,14 +199,13 @@ class ArrInstance:
endpoint = self.api_url + "/system/status"
headers = {"X-Api-Key": self.api_key}
response = await make_request(
"get", endpoint, self.settings, headers=headers, log_error=False
"get", endpoint, self.settings, headers=headers, log_error=False,
)
status = response.json()
return status
return response.json()
except Exception as e:
if isinstance(e, requests.exceptions.HTTPError):
response = getattr(e, "response", None)
if response is not None and response.status_code == 401:
if response is not None and response.status_code == 401: # noqa: PLR2004
tip = "💡 Tip: Have you configured the API_KEY correctly?"
else:
tip = f"💡 Tip: HTTP error occurred. Status: {getattr(response, 'status_code', 'unknown')}"
@@ -218,7 +218,7 @@ class ArrInstance:
raise ArrError(e) from e
async def setup(self):
"""Checks on specific ARR instance"""
"""Check on specific ARR instance."""
try:
status = await self._check_reachability()
self.name = status.get("instanceName", self.arr_type)
@@ -230,7 +230,7 @@ class ArrInstance:
logger.info(f"OK | {self.name} ({self.base_url})")
logger.debug(f"Current version of {self.name}: {self.version}")
except Exception as e:
except Exception as e: # noqa: BLE001
if not isinstance(e, ArrError):
logger.error(f"Unhandled error: {e}", exc_info=True)
wait_and_exit()
@@ -253,17 +253,19 @@ class ArrInstance:
return client.get("implementation", None)
return None
async def remove_queue_item(self, queue_id, blocklist=False):
async def remove_queue_item(self, queue_id, *, blocklist=False):
"""
Remove a specific queue item from the queue by its qeue id.
Remove a specific queue item from the queue by its queue id.
Sends a delete request to the API to remove the item.
Args:
queue_id (str): The quueue ID of the queue item to be removed.
queue_id (str): The queue ID of the queue item to be removed.
blocklist (bool): Whether to add the item to the blocklist. Default is False.
Returns:
bool: Returns True if the removal was successful, False otherwise.
"""
endpoint = f"{self.api_url}/queue/{queue_id}"
headers = {"X-Api-Key": self.api_key}
@@ -271,17 +273,14 @@ class ArrInstance:
# Send the request to remove the download from the queue
response = await make_request(
"delete", endpoint, self.settings, headers=headers, json=json_payload
"delete", endpoint, self.settings, headers=headers, json=json_payload,
)
# If the response is successful, return True, else return False
if response.status_code == 200:
return True
else:
return False
return response.status_code == 200 # noqa: PLR2004
async def is_monitored(self, detail_id):
"""Check if detail item (like a book, series, etc) is monitored."""
"""Check if detail item (like a book, series, etc.) is monitored."""
endpoint = f"{self.api_url}/{self.detail_item_key}/{detail_id}"
headers = {"X-Api-Key": self.api_key}