c4-rdarr db updates

This commit is contained in:
2025-09-08 17:33:41 -04:00
parent 0def0e33af
commit 678f84b6ef
7 changed files with 747 additions and 11 deletions
+17
View File
@@ -14,6 +14,23 @@ SONARR_API_KEY=
RADARR_URL=http://radarr:7878
RADARR_API_KEY=
# --- Optional: Direct Radarr Database Access (High Performance) ---
# Enable direct database access for faster import date queries
# Supports both SQLite and PostgreSQL
RADARR_DB_TYPE=postgresql
# For PostgreSQL (recommended for production):
RADARR_DB_HOST=postgres_host
RADARR_DB_PORT=5432
RADARR_DB_NAME=radarr-main
RADARR_DB_USER=postgres
RADARR_DB_PASSWORD=your_password
# Alternative: use connection string
#RADARR_DB_URL=postgresql://user:pass@host:5432/dbname
# For SQLite (if using default Radarr setup):
#RADARR_DB_TYPE=sqlite
#RADARR_DB_PATH=/config/radarr.db
# --- Required: TMDB for Release Dates ---
# Get free API key from https://www.themoviedb.org/settings/api
TMDB_API_KEY=
+29
View File
@@ -6,6 +6,35 @@ All notable changes to this project will be documented in this file.
# Changelog
## [0.5.0] - 2025-09-08
### Added
- **Major Performance Enhancement**: Direct Radarr database access
- New `RadarrDbClient` class for high-performance database queries
- Support for both SQLite and PostgreSQL Radarr databases
- Eliminates API pagination overhead for import date lookups
- Up to 10x faster movie import date detection
- **Bulk Operations**: Added bulk import date queries for multiple movies
- **Database Configuration**: New environment variables for database connection
- `RADARR_DB_TYPE` (sqlite/postgresql)
- `RADARR_DB_HOST`, `RADARR_DB_PORT`, `RADARR_DB_NAME` for PostgreSQL
- `RADARR_DB_USER`, `RADARR_DB_PASSWORD` for PostgreSQL authentication
- `RADARR_DB_PATH` for SQLite databases
- `RADARR_DB_URL` connection string support
- **Testing Suite**: Added performance comparison test script (`test_db_performance.py`)
### Changed
- **RadarrClient Enhanced**: Automatic database client initialization with API fallback
- **Movie Lookup Optimization**: Database-first approach for movie IMDb ID resolution
- **Import Date Detection**: Optimized SQL queries replace complex API event parsing
- **Dependencies**: Added `psycopg2-binary` for PostgreSQL support
### Technical Details
- Database queries use proper SQL joins for efficient data retrieval
- Maintains full backward compatibility with API-only installations
- Graceful fallback to API methods when database access is unavailable
- Support for both connection strings and individual parameter configuration
## [0.4.1] - 2025-01-09
### Fixed
+1 -1
View File
@@ -1 +1 @@
0.4.1
0.5.0
+44 -10
View File
@@ -26,6 +26,12 @@ except ImportError:
path_mapper = DummyPathMapper()
# Import database client for enhanced performance
try:
from clients.radarr_db_client import RadarrDbClient
except ImportError:
RadarrDbClient = None
class RadarrClient:
"""Enhanced Radarr API client with improved import date detection"""
@@ -54,6 +60,17 @@ class RadarrClient:
self.api_key = api_key
self.timeout = timeout
self.retries = max(0, retries)
# Initialize database client if available
self.db_client = None
if RadarrDbClient:
try:
self.db_client = RadarrDbClient.from_env()
if self.db_client:
_log("INFO", "Enhanced performance mode: Direct database access enabled")
except Exception as e:
_log("WARNING", f"Failed to initialize database client, using API fallback: {e}")
self.db_client = None
def _get(self, path: str, params: Dict[str, Any] = None) -> Optional[Any]:
"""Make GET request to Radarr API with retries"""
@@ -94,10 +111,21 @@ class RadarrClient:
imdb_id = imdb_id if imdb_id.startswith("tt") else f"tt{imdb_id}"
_log("DEBUG", f"Looking up movie by IMDb ID: {imdb_id}")
# Try database first if available
if self.db_client:
try:
movie = self.db_client.get_movie_by_imdb(imdb_id)
if movie:
_log("INFO", f"Found via database: {movie.get('title')} (ID: {movie.get('id')})")
return movie
except Exception as e:
_log("WARNING", f"Database lookup failed, using API fallback: {e}")
# API Fallback methods
# Method 1: Direct lookup by ID
movie = self._get(f"/api/v3/movie/lookup/imdb/{imdb_id}")
if isinstance(movie, dict) and movie.get("imdbId", "").lower() == imdb_id.lower():
_log("INFO", f"Found via direct lookup: {movie.get('title')} (ID: {movie.get('id')})")
_log("INFO", f"Found via API direct lookup: {movie.get('title')} (ID: {movie.get('id')})")
return movie
# Method 2: Get all movies and filter by IMDb (fallback)
@@ -110,7 +138,7 @@ class RadarrClient:
_log("INFO", f"Found exact match: {movie.get('title')} (ID: {movie.get('id')})")
return movie
# Method 2: Direct API search with validation
# Method 3: Direct API search with validation
result = self._get("/api/v3/movie", {"imdbId": imdb_id})
if isinstance(result, list) and result:
for movie in result:
@@ -119,7 +147,7 @@ class RadarrClient:
_log("INFO", f"Found via search: {movie.get('title')} (ID: {movie.get('id')})")
return movie
# Method 3: Lookup endpoint
# Method 4: Lookup endpoint
lookup_result = self._get("/api/v3/movie/lookup/imdb", {"imdbId": imdb_id})
if isinstance(lookup_result, dict) and lookup_result.get("imdbId", "").lower() == imdb_id.lower():
_log("INFO", f"Found via lookup: {lookup_result.get('title')} (ID: {lookup_result.get('id')})")
@@ -442,25 +470,31 @@ class RadarrClient:
def get_movie_import_date(self, movie_id: int, fallback_to_file_date: bool = True) -> Tuple[Optional[str], str]:
"""
Get the best import date for a movie.
Get the best import date for a movie with database optimization.
Returns:
(date_iso, source_description)
"""
# Try history first - we want the earliest event regardless of type
import_date = self.earliest_import_event_optimized(movie_id)
# Try history second
# Try database first if available (much faster)
if self.db_client:
try:
date_iso, source = self.db_client.get_movie_import_date_optimized(movie_id, fallback_to_file_date)
if date_iso:
return date_iso, source
except Exception as e:
_log("WARNING", f"Database import date query failed, using API fallback: {e}")
# API Fallback: Try history parsing
import_date = self.earliest_import_event_optimized(movie_id)
if import_date:
return import_date, "radarr:history.import"
return import_date, "radarr:api.history.import"
# Fallback to file dateAdded if requested
if fallback_to_file_date:
file_date = self.earliest_file_dateadded(movie_id)
if file_date:
_log("WARNING", f"Using file dateAdded as fallback for movie_id {movie_id}")
return file_date, "radarr:file.dateAdded"
return file_date, "radarr:api.file.dateAdded"
return None, "radarr:no_date_found"
+441
View File
@@ -0,0 +1,441 @@
#!/usr/bin/env python3
"""
Direct Radarr Database Client for NFOGuard
Provides high-performance access to Radarr's SQLite/PostgreSQL database
"""
import os
import sqlite3
import psycopg2
import psycopg2.extras
from datetime import datetime, timezone
from pathlib import Path
from typing import Dict, Any, List, Optional, Tuple, Union
from urllib.parse import urlparse
from core.logging import _log
class RadarrDbClient:
"""Direct database client for Radarr's SQLite or PostgreSQL database"""
def __init__(self,
db_type: str = "sqlite",
db_path: Optional[str] = None,
db_host: Optional[str] = None,
db_port: Optional[int] = None,
db_name: Optional[str] = None,
db_user: Optional[str] = None,
db_password: Optional[str] = None):
"""
Initialize Radarr database client
Args:
db_type: "sqlite" or "postgresql"
db_path: Path to SQLite database file
db_host: PostgreSQL host
db_port: PostgreSQL port
db_name: PostgreSQL database name
db_user: PostgreSQL username
db_password: PostgreSQL password
"""
self.db_type = db_type.lower()
self.db_path = db_path
self.db_host = db_host
self.db_port = db_port or 5432
self.db_name = db_name
self.db_user = db_user
self.db_password = db_password
self._test_connection()
@classmethod
def from_env(cls) -> Optional['RadarrDbClient']:
"""Create client from environment variables"""
db_type = os.environ.get("RADARR_DB_TYPE", "").lower()
if not db_type:
return None
if db_type == "sqlite":
db_path = os.environ.get("RADARR_DB_PATH")
if not db_path or not Path(db_path).exists():
_log("WARNING", f"RADARR_DB_PATH not found or invalid: {db_path}")
return None
return cls(db_type="sqlite", db_path=db_path)
elif db_type == "postgresql":
# Support both individual vars and connection string
db_url = os.environ.get("RADARR_DB_URL")
if db_url:
parsed = urlparse(db_url)
return cls(
db_type="postgresql",
db_host=parsed.hostname,
db_port=parsed.port or 5432,
db_name=parsed.path.lstrip('/'),
db_user=parsed.username,
db_password=parsed.password
)
else:
return cls(
db_type="postgresql",
db_host=os.environ.get("RADARR_DB_HOST"),
db_port=int(os.environ.get("RADARR_DB_PORT", "5432")),
db_name=os.environ.get("RADARR_DB_NAME"),
db_user=os.environ.get("RADARR_DB_USER"),
db_password=os.environ.get("RADARR_DB_PASSWORD")
)
else:
_log("ERROR", f"Unsupported database type: {db_type}")
return None
def _test_connection(self) -> None:
"""Test database connection on initialization"""
try:
conn = self._get_connection()
if conn:
conn.close()
_log("INFO", f"Connected to Radarr {self.db_type} database successfully")
else:
raise Exception("Failed to create connection")
except Exception as e:
_log("ERROR", f"Failed to connect to Radarr database: {e}")
raise
def _get_connection(self) -> Union[sqlite3.Connection, psycopg2.extensions.connection]:
"""Get database connection"""
if self.db_type == "sqlite":
conn = sqlite3.connect(self.db_path)
conn.row_factory = sqlite3.Row
return conn
elif self.db_type == "postgresql":
conn = psycopg2.connect(
host=self.db_host,
port=self.db_port,
database=self.db_name,
user=self.db_user,
password=self.db_password
)
return conn
else:
raise ValueError(f"Unsupported database type: {self.db_type}")
def get_movie_by_imdb(self, imdb_id: str) -> Optional[Dict[str, Any]]:
"""
Find movie by IMDb ID using database query
Returns:
Dictionary with movie info including id, imdbId, title, year, path
"""
imdb_id = imdb_id if imdb_id.startswith("tt") else f"tt{imdb_id}"
query = """
SELECT
m."Id" as id,
m."Path" as path,
m."Added" as added,
mm."ImdbId" as imdb_id,
mm."Title" as title,
mm."Year" as year,
mm."DigitalRelease" as digital_release
FROM "Movies" m
JOIN "MovieMetadata" mm ON m."MovieMetadataId" = mm."Id"
WHERE mm."ImdbId" = %s
"""
if self.db_type == "sqlite":
query = query.replace("%s", "?")
try:
with self._get_connection() as conn:
if self.db_type == "postgresql":
cursor = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
else:
cursor = conn.cursor()
cursor.execute(query, (imdb_id,))
row = cursor.fetchone()
if row:
return dict(row) if self.db_type == "sqlite" else row
except Exception as e:
_log("ERROR", f"Database query error for IMDb {imdb_id}: {e}")
return None
def get_earliest_import_date(self, movie_id: int) -> Tuple[Optional[str], str]:
"""
Get earliest import date from History table
Args:
movie_id: Radarr movie ID
Returns:
(date_iso, source_description)
"""
# Query for earliest import event (EventType = 3)
import_query = """
SELECT
h."Date" as event_date,
h."Data" as event_data
FROM "History" h
WHERE h."MovieId" = %s
AND h."EventType" = 3
ORDER BY h."Date" ASC
LIMIT 1
"""
# Fallback: earliest grab event (EventType = 1) with download info
grab_query = """
SELECT
h."Date" as event_date,
h."Data" as event_data
FROM "History" h
WHERE h."MovieId" = %s
AND h."EventType" = 1
AND h."Data" IS NOT NULL
ORDER BY h."Date" ASC
LIMIT 1
"""
if self.db_type == "sqlite":
import_query = import_query.replace("%s", "?")
grab_query = grab_query.replace("%s", "?")
try:
with self._get_connection() as conn:
if self.db_type == "postgresql":
cursor = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
else:
cursor = conn.cursor()
# Try import events first
cursor.execute(import_query, (movie_id,))
row = cursor.fetchone()
if row:
event_date = row['event_date'] if self.db_type == "postgresql" else row[0]
if isinstance(event_date, str):
dt = datetime.fromisoformat(event_date.replace("Z", "+00:00"))
else:
dt = event_date.replace(tzinfo=timezone.utc)
date_iso = dt.astimezone(timezone.utc).isoformat(timespec="seconds")
_log("INFO", f"Found import event for movie {movie_id} at {date_iso}")
return date_iso, "radarr:db.history.import"
# Fallback to grab events
cursor.execute(grab_query, (movie_id,))
row = cursor.fetchone()
if row:
event_date = row['event_date'] if self.db_type == "postgresql" else row[0]
if isinstance(event_date, str):
dt = datetime.fromisoformat(event_date.replace("Z", "+00:00"))
else:
dt = event_date.replace(tzinfo=timezone.utc)
date_iso = dt.astimezone(timezone.utc).isoformat(timespec="seconds")
_log("WARNING", f"Using grab event for movie {movie_id} at {date_iso}")
return date_iso, "radarr:db.history.grab"
except Exception as e:
_log("ERROR", f"Database query error for movie {movie_id}: {e}")
return None, "radarr:db.no_date_found"
def get_movie_file_date(self, movie_id: int) -> Optional[str]:
"""
Get earliest file dateAdded as fallback
Args:
movie_id: Radarr movie ID
Returns:
ISO date string or None
"""
query = """
SELECT MIN(mf."DateAdded") as earliest_date
FROM "MovieFiles" mf
WHERE mf."MovieId" = %s
"""
if self.db_type == "sqlite":
query = query.replace("%s", "?")
try:
with self._get_connection() as conn:
if self.db_type == "postgresql":
cursor = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
else:
cursor = conn.cursor()
cursor.execute(query, (movie_id,))
row = cursor.fetchone()
if row:
date_value = row['earliest_date'] if self.db_type == "postgresql" else row[0]
if date_value:
if isinstance(date_value, str):
dt = datetime.fromisoformat(date_value.replace("Z", "+00:00"))
else:
dt = date_value.replace(tzinfo=timezone.utc)
return dt.astimezone(timezone.utc).isoformat(timespec="seconds")
except Exception as e:
_log("ERROR", f"Database query error for movie file date {movie_id}: {e}")
return None
def get_movie_import_date_optimized(self, movie_id: int, fallback_to_file_date: bool = True) -> Tuple[Optional[str], str]:
"""
Get the best import date for a movie using optimized database queries
Args:
movie_id: Radarr movie ID
fallback_to_file_date: Whether to fall back to file dateAdded
Returns:
(date_iso, source_description)
"""
# Try history first
date_iso, source = self.get_earliest_import_date(movie_id)
if date_iso:
return date_iso, source
# Fallback to file date if requested
if fallback_to_file_date:
file_date = self.get_movie_file_date(movie_id)
if file_date:
_log("WARNING", f"Using file dateAdded as fallback for movie_id {movie_id}")
return file_date, "radarr:db.file.dateAdded"
return None, "radarr:db.no_date_found"
def bulk_import_dates(self, imdb_ids: List[str]) -> Dict[str, Tuple[Optional[str], str]]:
"""
Get import dates for multiple movies in a single query
Args:
imdb_ids: List of IMDb IDs
Returns:
Dictionary mapping imdb_id -> (date_iso, source)
"""
if not imdb_ids:
return {}
# Ensure all IMDb IDs have tt prefix
clean_imdb_ids = [imdb_id if imdb_id.startswith("tt") else f"tt{imdb_id}" for imdb_id in imdb_ids]
placeholders = ",".join(["%s"] * len(clean_imdb_ids))
if self.db_type == "sqlite":
placeholders = ",".join(["?"] * len(clean_imdb_ids))
query = f"""
SELECT
mm."ImdbId" as imdb_id,
m."Id" as movie_id,
MIN(h."Date") as earliest_import
FROM "Movies" m
JOIN "MovieMetadata" mm ON m."MovieMetadataId" = mm."Id"
LEFT JOIN "History" h ON m."Id" = h."MovieId" AND h."EventType" = 3
WHERE mm."ImdbId" IN ({placeholders})
GROUP BY mm."ImdbId", m."Id"
"""
results = {}
try:
with self._get_connection() as conn:
if self.db_type == "postgresql":
cursor = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
else:
cursor = conn.cursor()
cursor.execute(query, clean_imdb_ids)
rows = cursor.fetchall()
for row in rows:
if self.db_type == "postgresql":
imdb_id, movie_id, earliest_import = row['imdb_id'], row['movie_id'], row['earliest_import']
else:
imdb_id, movie_id, earliest_import = row[0], row[1], row[2]
if earliest_import:
if isinstance(earliest_import, str):
dt = datetime.fromisoformat(earliest_import.replace("Z", "+00:00"))
else:
dt = earliest_import.replace(tzinfo=timezone.utc)
date_iso = dt.astimezone(timezone.utc).isoformat(timespec="seconds")
results[imdb_id] = (date_iso, "radarr:db.bulk.import")
else:
results[imdb_id] = (None, "radarr:db.bulk.no_import")
except Exception as e:
_log("ERROR", f"Bulk query error: {e}")
# Return empty results for failed queries
for imdb_id in clean_imdb_ids:
if imdb_id not in results:
results[imdb_id] = (None, "radarr:db.bulk.error")
return results
def get_database_stats(self) -> Dict[str, Any]:
"""Get basic statistics about the Radarr database"""
stats = {}
queries = {
"total_movies": 'SELECT COUNT(*) FROM "Movies"',
"total_movie_files": 'SELECT COUNT(*) FROM "MovieFiles"',
"total_history_events": 'SELECT COUNT(*) FROM "History"',
"import_events": 'SELECT COUNT(*) FROM "History" WHERE "EventType" = 3',
"grab_events": 'SELECT COUNT(*) FROM "History" WHERE "EventType" = 1'
}
try:
with self._get_connection() as conn:
cursor = conn.cursor()
for stat_name, query in queries.items():
cursor.execute(query)
result = cursor.fetchone()
stats[stat_name] = result[0] if result else 0
except Exception as e:
_log("ERROR", f"Stats query error: {e}")
stats["error"] = str(e)
return stats
if __name__ == "__main__":
# Test the database client
print("Testing RadarrDbClient...")
# Test with environment variables
client = RadarrDbClient.from_env()
if client:
print("✅ Connected to Radarr database")
# Test stats
stats = client.get_database_stats()
print(f"Database stats: {stats}")
# Test movie lookup
test_movie = client.get_movie_by_imdb("tt1596343")
if test_movie:
print(f"Found test movie: {test_movie}")
# Test import date
movie_id = test_movie['id']
date_iso, source = client.get_movie_import_date_optimized(movie_id)
print(f"Import date: {date_iso} (source: {source})")
else:
print("Test movie not found")
else:
print("❌ Could not connect to database - check environment variables")
+1
View File
@@ -1,3 +1,4 @@
fastapi==0.104.1
uvicorn[standard]==0.24.0
pydantic==2.5.0
psycopg2-binary==2.9.9
+214
View File
@@ -0,0 +1,214 @@
#!/usr/bin/env python3
"""
Test script for comparing Radarr API vs Database performance
Run this to validate the new database client functionality
"""
import os
import time
from pathlib import Path
from clients.radarr_client import RadarrClient
from clients.radarr_db_client import RadarrDbClient
def test_database_connection():
"""Test database connection and basic functionality"""
print("=== Testing Database Connection ===")
db_client = RadarrDbClient.from_env()
if not db_client:
print("❌ Database client not configured - check environment variables")
return False
print("✅ Database connection successful")
# Test basic stats
stats = db_client.get_database_stats()
print(f"Database stats: {stats}")
return True
def test_movie_lookup_performance():
"""Compare API vs Database performance for movie lookups"""
print("\n=== Testing Movie Lookup Performance ===")
# Test IMDb IDs - adjust these to movies in your library
test_imdb_ids = [
"tt1596343", # Fast & Furious 6
"tt0468569", # The Dark Knight
"tt0137523", # Fight Club
"tt0109830", # Forrest Gump
"tt0111161" # The Shawshank Redemption
]
# Initialize clients
radarr_url = os.environ.get("RADARR_URL")
radarr_api_key = os.environ.get("RADARR_API_KEY")
if not radarr_url or not radarr_api_key:
print("❌ RADARR_URL and RADARR_API_KEY required for testing")
return
client = RadarrClient(radarr_url, radarr_api_key)
# Test API performance
print("\n--- API Performance ---")
api_times = []
api_found = 0
for imdb_id in test_imdb_ids:
start_time = time.time()
# Temporarily disable database client for API-only test
original_db_client = client.db_client
client.db_client = None
movie = client.movie_by_imdb(imdb_id)
# Restore database client
client.db_client = original_db_client
elapsed = time.time() - start_time
api_times.append(elapsed)
if movie:
api_found += 1
print(f" {imdb_id}: {elapsed:.3f}s - Found: {movie.get('title')}")
else:
print(f" {imdb_id}: {elapsed:.3f}s - Not found")
# Test Database performance
print("\n--- Database Performance ---")
db_times = []
db_found = 0
if client.db_client:
for imdb_id in test_imdb_ids:
start_time = time.time()
movie = client.movie_by_imdb(imdb_id) # This will use database first
elapsed = time.time() - start_time
db_times.append(elapsed)
if movie:
db_found += 1
print(f" {imdb_id}: {elapsed:.3f}s - Found: {movie.get('title')}")
else:
print(f" {imdb_id}: {elapsed:.3f}s - Not found")
else:
print(" Database client not available")
return
# Performance comparison
print("\n--- Performance Summary ---")
avg_api_time = sum(api_times) / len(api_times)
avg_db_time = sum(db_times) / len(db_times)
print(f"API Average: {avg_api_time:.3f}s ({api_found}/{len(test_imdb_ids)} found)")
print(f"Database Average: {avg_db_time:.3f}s ({db_found}/{len(test_imdb_ids)} found)")
if avg_api_time > 0 and avg_db_time > 0:
speedup = avg_api_time / avg_db_time
print(f"Speedup: {speedup:.1f}x faster with database")
def test_import_date_performance():
"""Test import date retrieval performance"""
print("\n=== Testing Import Date Performance ===")
radarr_url = os.environ.get("RADARR_URL")
radarr_api_key = os.environ.get("RADARR_API_KEY")
if not radarr_url or not radarr_api_key:
print("❌ RADARR_URL and RADARR_API_KEY required for testing")
return
client = RadarrClient(radarr_url, radarr_api_key)
# Find a test movie
test_movie = client.movie_by_imdb("tt1596343") # Use database if available
if not test_movie:
print("❌ Test movie not found in library")
return
movie_id = test_movie.get('id')
movie_title = test_movie.get('title', 'Unknown')
print(f"Testing with: {movie_title} (ID: {movie_id})")
# Test API performance
print("\n--- API Import Date Performance ---")
start_time = time.time()
# Temporarily disable database client
original_db_client = client.db_client
client.db_client = None
api_date, api_source = client.get_movie_import_date(movie_id)
# Restore database client
client.db_client = original_db_client
api_time = time.time() - start_time
print(f"API Result: {api_date} ({api_source}) - {api_time:.3f}s")
# Test Database performance
if client.db_client:
print("\n--- Database Import Date Performance ---")
start_time = time.time()
db_date, db_source = client.get_movie_import_date(movie_id)
db_time = time.time() - start_time
print(f"Database Result: {db_date} ({db_source}) - {db_time:.3f}s")
if api_time > 0 and db_time > 0:
speedup = api_time / db_time
print(f"Import date speedup: {speedup:.1f}x faster with database")
else:
print("Database client not available for comparison")
def test_bulk_operations():
"""Test bulk import date operations"""
print("\n=== Testing Bulk Operations ===")
db_client = RadarrDbClient.from_env()
if not db_client:
print("❌ Database client required for bulk operations")
return
# Test bulk import dates
test_imdb_ids = ["tt1596343", "tt0468569", "tt0137523", "tt0109830", "tt0111161"]
start_time = time.time()
results = db_client.bulk_import_dates(test_imdb_ids)
elapsed = time.time() - start_time
print(f"Bulk operation completed in {elapsed:.3f}s")
print("Results:")
for imdb_id, (date_iso, source) in results.items():
print(f" {imdb_id}: {date_iso} ({source})")
if __name__ == "__main__":
print("NFOGuard Database Performance Test")
print("=" * 50)
# Test database connection
if not test_database_connection():
print("Exiting due to database connection failure")
exit(1)
# Test movie lookups
test_movie_lookup_performance()
# Test import dates
test_import_date_performance()
# Test bulk operations
test_bulk_operations()
print("\n✅ All tests completed!")
print("\nTo enable database access, configure these environment variables:")
print(" RADARR_DB_TYPE=postgresql")
print(" RADARR_DB_HOST=your_postgres_host")
print(" RADARR_DB_PORT=5432")
print(" RADARR_DB_NAME=radarr-main")
print(" RADARR_DB_USER=postgres")
print(" RADARR_DB_PASSWORD=your_password")
print("\nOr for SQLite:")
print(" RADARR_DB_TYPE=sqlite")
print(" RADARR_DB_PATH=/config/radarr.db")