integrated Python's logging module (wrap I/O operations with try/except, log exceptions to file)

This commit is contained in:
Bernd 2025-07-20 13:21:00 +05:00
parent 282556790c
commit f65a42cdce
3 changed files with 175 additions and 106 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
anime_backlog.db anime_backlog.db
images/ images/
__pycache__/ __pycache__/
anime_tracker.log

View File

@ -1,5 +1,10 @@
import sqlite3 import sqlite3
import csv import csv
import logging
# Set up logging
logging.basicConfig(filename='anime_tracker.log', level=logging.ERROR,
format='%(asctime)s - %(levelname)s - %(message)s')
class AnimeBackend: class AnimeBackend:
def __init__(self): def __init__(self):
@ -7,6 +12,7 @@ class AnimeBackend:
self.create_table() self.create_table()
def create_table(self): def create_table(self):
try:
cursor = self.db.cursor() cursor = self.db.cursor()
cursor.execute(""" cursor.execute("""
CREATE TABLE IF NOT EXISTS anime ( CREATE TABLE IF NOT EXISTS anime (
@ -21,54 +27,85 @@ class AnimeBackend:
) )
""") """)
self.db.commit() self.db.commit()
except Exception as e:
logging.error(f"Error creating table: {e}")
def get_pre_2010_entries(self): def get_pre_2010_entries(self):
try:
cursor = self.db.cursor() cursor = self.db.cursor()
cursor.execute("SELECT * FROM anime WHERE year < 2010 ORDER BY year DESC, name") cursor.execute("SELECT * FROM anime WHERE year < 2010 ORDER BY year DESC, name")
return cursor.fetchall() return cursor.fetchall()
except Exception as e:
logging.error(f"Error getting pre-2010 entries: {e}")
return []
def get_years(self): def get_years(self):
try:
cursor = self.db.cursor() cursor = self.db.cursor()
cursor.execute("SELECT DISTINCT year FROM anime WHERE year >= 2010 ORDER BY year DESC") cursor.execute("SELECT DISTINCT year FROM anime WHERE year >= 2010 ORDER BY year DESC")
return [row[0] for row in cursor.fetchall()] return [row[0] for row in cursor.fetchall()]
except Exception as e:
logging.error(f"Error getting years: {e}")
return []
def get_entries_for_season(self, year, season): def get_entries_for_season(self, year, season):
try:
cursor = self.db.cursor() cursor = self.db.cursor()
cursor.execute("SELECT * FROM anime WHERE year = ? AND season = ? ORDER BY name", (year, season)) cursor.execute("SELECT * FROM anime WHERE year = ? AND season = ? ORDER BY name", (year, season))
return cursor.fetchall() return cursor.fetchall()
except Exception as e:
logging.error(f"Error getting entries for season {season} in year {year}: {e}")
return []
def get_anime_by_id(self, anime_id): def get_anime_by_id(self, anime_id):
try:
cursor = self.db.cursor() cursor = self.db.cursor()
cursor.execute("SELECT * FROM anime WHERE id = ?", (anime_id,)) cursor.execute("SELECT * FROM anime WHERE id = ?", (anime_id,))
return cursor.fetchone() return cursor.fetchone()
except Exception as e:
logging.error(f"Error getting anime by id {anime_id}: {e}")
return None
def add_anime(self, data): def add_anime(self, data):
try:
cursor = self.db.cursor() cursor = self.db.cursor()
cursor.execute( cursor.execute(
"INSERT INTO anime (name, year, season, status, type, comment, url) VALUES (?, ?, ?, ?, ?, ?, ?)", "INSERT INTO anime (name, year, season, status, type, comment, url) VALUES (?, ?, ?, ?, ?, ?, ?)",
(data['name'], data['year'], data['season'], data['status'], data['type'], data['comment'], data['url']) (data['name'], data['year'], data['season'], data['status'], data['type'], data['comment'], data['url'])
) )
self.db.commit() self.db.commit()
except Exception as e:
logging.error(f"Error adding anime: {e}")
def edit_anime(self, anime_id, data): def edit_anime(self, anime_id, data):
try:
cursor = self.db.cursor() cursor = self.db.cursor()
cursor.execute( cursor.execute(
"UPDATE anime SET name=?, year=?, season=?, status=?, type=?, comment=?, url=? WHERE id=?", "UPDATE anime SET name=?, year=?, season=?, status=?, type=?, comment=?, url=? WHERE id=?",
(data['name'], data['year'], data['season'], data['status'], data['type'], data['comment'], data['url'], anime_id) (data['name'], data['year'], data['season'], data['status'], data['type'], data['comment'], data['url'], anime_id)
) )
self.db.commit() self.db.commit()
except Exception as e:
logging.error(f"Error editing anime id {anime_id}: {e}")
def delete_anime(self, anime_id): def delete_anime(self, anime_id):
try:
cursor = self.db.cursor() cursor = self.db.cursor()
cursor.execute("DELETE FROM anime WHERE id = ?", (anime_id,)) cursor.execute("DELETE FROM anime WHERE id = ?", (anime_id,))
self.db.commit() self.db.commit()
except Exception as e:
logging.error(f"Error deleting anime id {anime_id}: {e}")
def change_status(self, anime_id, new_status): def change_status(self, anime_id, new_status):
try:
cursor = self.db.cursor() cursor = self.db.cursor()
cursor.execute("UPDATE anime SET status = ? WHERE id = ?", (new_status, anime_id)) cursor.execute("UPDATE anime SET status = ? WHERE id = ?", (new_status, anime_id))
self.db.commit() self.db.commit()
except Exception as e:
logging.error(f"Error changing status for anime id {anime_id}: {e}")
def add_placeholders_for_year(self, year): def add_placeholders_for_year(self, year):
try:
cursor = self.db.cursor() cursor = self.db.cursor()
for season in ['winter', 'spring', 'summer', 'fall', '']: for season in ['winter', 'spring', 'summer', 'fall', '']:
cursor.execute( cursor.execute(
@ -76,8 +113,11 @@ class AnimeBackend:
('Placeholder', year, season, 'unwatched', '', 'Delete or edit me', '') ('Placeholder', year, season, 'unwatched', '', 'Delete or edit me', '')
) )
self.db.commit() self.db.commit()
except Exception as e:
logging.error(f"Error adding placeholders for year {year}: {e}")
def import_from_csv(self, file_name): def import_from_csv(self, file_name):
try:
with open(file_name, 'r', newline='') as f: with open(file_name, 'r', newline='') as f:
reader = csv.reader(f) reader = csv.reader(f)
header = next(reader, None) header = next(reader, None)
@ -104,8 +144,11 @@ class AnimeBackend:
(name, year, season, status, type_, comment, url) (name, year, season, status, type_, comment, url)
) )
self.db.commit() self.db.commit()
except Exception as e:
logging.error(f"Error importing from CSV {file_name}: {e}")
def export_to_csv(self, file_name): def export_to_csv(self, file_name):
try:
cursor = self.db.cursor() cursor = self.db.cursor()
cursor.execute("SELECT * FROM anime") cursor.execute("SELECT * FROM anime")
rows = cursor.fetchall() rows = cursor.fetchall()
@ -113,8 +156,13 @@ class AnimeBackend:
writer = csv.writer(f) writer = csv.writer(f)
writer.writerow(['id', 'name', 'year', 'season', 'status', 'type', 'comment', 'url']) writer.writerow(['id', 'name', 'year', 'season', 'status', 'type', 'comment', 'url'])
writer.writerows(rows) writer.writerows(rows)
except Exception as e:
logging.error(f"Error exporting to CSV {file_name}: {e}")
def delete_year(self, year): def delete_year(self, year):
try:
cursor = self.db.cursor() cursor = self.db.cursor()
cursor.execute("DELETE FROM anime WHERE year = ?", (year,)) cursor.execute("DELETE FROM anime WHERE year = ?", (year,))
self.db.commit() self.db.commit()
except Exception as e:
logging.error(f"Error deleting year {year}: {e}")

View File

@ -4,6 +4,7 @@ import random
import re import re
import hashlib import hashlib
import html import html
import logging
from datetime import datetime from datetime import datetime
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QMainWindow, QTabWidget, QScrollArea, QWidget, QVBoxLayout, QTableWidget, QTableWidgetItem, QMainWindow, QTabWidget, QScrollArea, QWidget, QVBoxLayout, QTableWidget, QTableWidgetItem,
@ -16,6 +17,10 @@ from PyQt5.QtGui import QColor, QIcon, QKeySequence
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
from backend import AnimeBackend from backend import AnimeBackend
# Set up logging
logging.basicConfig(filename='anime_tracker.log', level=logging.ERROR,
format='%(asctime)s - %(levelname)s - %(message)s')
class AnimeDialog(QDialog): class AnimeDialog(QDialog):
def __init__(self, parent, entry=None, default_year=None, default_season=None): def __init__(self, parent, entry=None, default_year=None, default_season=None):
super().__init__(parent) super().__init__(parent)
@ -161,7 +166,10 @@ class AnimeTracker(QMainWindow):
self.resize(800, 600) self.resize(800, 600)
self.backend = AnimeBackend() self.backend = AnimeBackend()
self.image_cache_dir = 'images' self.image_cache_dir = 'images'
try:
os.makedirs(self.image_cache_dir, exist_ok=True) os.makedirs(self.image_cache_dir, exist_ok=True)
except Exception as e:
logging.error(f"Error creating image cache directory: {e}")
self.network_manager = QNetworkAccessManager(self) self.network_manager = QNetworkAccessManager(self)
self.tab_widget = QTabWidget() self.tab_widget = QTabWidget()
self.setCentralWidget(self.tab_widget) self.setCentralWidget(self.tab_widget)
@ -461,14 +469,19 @@ class AnimeTracker(QMainWindow):
if os.path.exists(image_file): if os.path.exists(image_file):
label.setToolTip(f'<img src="{image_file}" width="200">') label.setToolTip(f'<img src="{image_file}" width="200">')
return return
try:
request = QNetworkRequest(QUrl(url)) request = QNetworkRequest(QUrl(url))
reply = self.network_manager.get(request) reply = self.network_manager.get(request)
reply.finished.connect(lambda: self.handle_html_reply(reply, image_file, label)) reply.finished.connect(lambda: self.handle_html_reply(reply, image_file, label))
except Exception as e:
logging.error(f"Error fetching poster from {url}: {e}")
label.setToolTip('Failed to load poster')
def handle_html_reply(self, reply, image_file, label): def handle_html_reply(self, reply, image_file, label):
if reply.error() != QNetworkReply.NoError: if reply.error() != QNetworkReply.NoError:
label.setToolTip('Failed to load poster') label.setToolTip('Failed to load poster')
return return
try:
html = reply.readAll().data().decode('utf-8', errors='ignore') html = reply.readAll().data().decode('utf-8', errors='ignore')
match = re.search(r'<meta property="og:image" content="([^"]+)"', html) match = re.search(r'<meta property="og:image" content="([^"]+)"', html)
if match: if match:
@ -478,12 +491,19 @@ class AnimeTracker(QMainWindow):
img_reply.finished.connect(lambda: self.handle_image_reply(img_reply, image_file, label)) img_reply.finished.connect(lambda: self.handle_image_reply(img_reply, image_file, label))
else: else:
label.setToolTip('No poster found') label.setToolTip('No poster found')
except Exception as e:
logging.error(f"Error handling HTML reply: {e}")
label.setToolTip('Failed to load poster')
def handle_image_reply(self, reply, image_file, label): def handle_image_reply(self, reply, image_file, label):
if reply.error() == QNetworkReply.NoError: if reply.error() == QNetworkReply.NoError:
try:
with open(image_file, 'wb') as f: with open(image_file, 'wb') as f:
f.write(reply.readAll().data()) f.write(reply.readAll().data())
label.setToolTip(f'<img src="{image_file}" width="200">') label.setToolTip(f'<img src="{image_file}" width="200">')
except Exception as e:
logging.error(f"Error saving image to {image_file}: {e}")
label.setToolTip('Failed to load poster')
else: else:
label.setToolTip('Failed to load poster') label.setToolTip('Failed to load poster')