mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-01-22 10:51:02 +01:00
- added option to set radarr/sonarr specific scores that profilarr's compiler will handle on import - revise design for arr settings container - now styled as a table - completely rewrote import module. Now uses connection pooling to reuse connections. - fixed import progress bug where 1 failed format causes all other formats to be labelled as failed (even if they succeeded) - fixed bug where on pull sync wasn't working - improve styling for link / unlink database modals - fixed issue where 0 score formats were removed in selective mode
150 lines
4.6 KiB
Python
150 lines
4.6 KiB
Python
"""ArrHandler class - manages all Arr API communication."""
|
|
import logging
|
|
import requests
|
|
from requests.adapters import HTTPAdapter
|
|
from urllib3.util.retry import Retry
|
|
from typing import Dict, List, Any, Optional
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ArrApiError(Exception):
|
|
"""Custom exception for Arr API errors."""
|
|
def __init__(self, message: str, status_code: Optional[int] = None):
|
|
super().__init__(message)
|
|
self.status_code = status_code
|
|
|
|
|
|
class ArrHandler:
|
|
"""Manages all communication with Radarr/Sonarr API."""
|
|
|
|
def __init__(self, base_url: str, api_key: str):
|
|
"""
|
|
Initialize the Arr API handler.
|
|
|
|
Args:
|
|
base_url: Base URL of the Arr instance
|
|
api_key: API key for authentication
|
|
"""
|
|
self.base_url = base_url.rstrip('/')
|
|
self.headers = {
|
|
'X-Api-Key': api_key,
|
|
'Content-Type': 'application/json'
|
|
}
|
|
self.session = self._create_session()
|
|
|
|
def _create_session(self) -> requests.Session:
|
|
"""Create a session with connection pooling and retry logic."""
|
|
session = requests.Session()
|
|
|
|
# Configure retry strategy
|
|
retry = Retry(
|
|
total=3,
|
|
backoff_factor=0.5,
|
|
status_forcelist=[500, 502, 503, 504]
|
|
)
|
|
|
|
# Configure connection pooling
|
|
adapter = HTTPAdapter(
|
|
pool_connections=5,
|
|
pool_maxsize=5,
|
|
max_retries=retry
|
|
)
|
|
|
|
session.mount('http://', adapter)
|
|
session.mount('https://', adapter)
|
|
session.headers.update(self.headers)
|
|
|
|
return session
|
|
|
|
def get(self, endpoint: str) -> Any:
|
|
"""
|
|
Make a GET request to the Arr API.
|
|
|
|
Args:
|
|
endpoint: API endpoint path
|
|
|
|
Returns:
|
|
JSON response data
|
|
|
|
Raises:
|
|
ArrApiError: If request fails
|
|
"""
|
|
url = f"{self.base_url}{endpoint}"
|
|
try:
|
|
response = self.session.get(url, timeout=30)
|
|
if response.status_code != 200:
|
|
raise ArrApiError(
|
|
f"GET {endpoint} failed: {response.text}",
|
|
response.status_code
|
|
)
|
|
return response.json()
|
|
except requests.RequestException as e:
|
|
raise ArrApiError(f"GET {endpoint} failed: {str(e)}")
|
|
|
|
def post(self, endpoint: str, data: Dict[str, Any]) -> Any:
|
|
"""
|
|
Make a POST request to the Arr API.
|
|
|
|
Args:
|
|
endpoint: API endpoint path
|
|
data: JSON data to send
|
|
|
|
Returns:
|
|
JSON response data
|
|
|
|
Raises:
|
|
ArrApiError: If request fails
|
|
"""
|
|
url = f"{self.base_url}{endpoint}"
|
|
try:
|
|
response = self.session.post(url, json=data, timeout=30)
|
|
if response.status_code not in [200, 201]:
|
|
raise ArrApiError(
|
|
f"POST {endpoint} failed: {response.text}",
|
|
response.status_code
|
|
)
|
|
return response.json()
|
|
except requests.RequestException as e:
|
|
raise ArrApiError(f"POST {endpoint} failed: {str(e)}")
|
|
|
|
def put(self, endpoint: str, data: Dict[str, Any]) -> Any:
|
|
"""
|
|
Make a PUT request to the Arr API.
|
|
|
|
Args:
|
|
endpoint: API endpoint path
|
|
data: JSON data to send
|
|
|
|
Returns:
|
|
JSON response data (if any)
|
|
|
|
Raises:
|
|
ArrApiError: If request fails
|
|
"""
|
|
url = f"{self.base_url}{endpoint}"
|
|
try:
|
|
response = self.session.put(url, json=data, timeout=30)
|
|
if response.status_code not in [200, 202, 204]:
|
|
raise ArrApiError(
|
|
f"PUT {endpoint} failed: {response.text}",
|
|
response.status_code
|
|
)
|
|
# 204 No Content won't have JSON
|
|
if response.status_code == 204:
|
|
return {}
|
|
return response.json()
|
|
except requests.RequestException as e:
|
|
raise ArrApiError(f"PUT {endpoint} failed: {str(e)}")
|
|
|
|
def get_all_formats(self) -> List[Dict[str, Any]]:
|
|
"""Get all custom formats from the Arr instance."""
|
|
return self.get("/api/v3/customformat")
|
|
|
|
def get_all_profiles(self) -> List[Dict[str, Any]]:
|
|
"""Get all quality profiles from the Arr instance."""
|
|
return self.get("/api/v3/qualityprofile")
|
|
|
|
def close(self):
|
|
"""Close the session."""
|
|
self.session.close() |