From 9dc7a1d65f8badb4d5f4cf6e7ceab7cf3afd4bf2 Mon Sep 17 00:00:00 2001 From: DevilXD Date: Wed, 26 Apr 2023 17:57:51 +0200 Subject: [PATCH] Fix ResourceWarning on unclosed icon file --- gui.py | 24 ++++++++++-------------- main.py | 12 +++++------- utils.py | 10 ++++++++++ 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/gui.py b/gui.py index 1f7320f..43d2b8a 100644 --- a/gui.py +++ b/gui.py @@ -29,7 +29,7 @@ if sys.platform == "win32": from translate import _ from cache import ImageCache from exceptions import ExitRequest -from utils import resource_path, Game, _T +from utils import resource_path, get_photo_image, Game, _T from constants import ( SELF_PATH, OUTPUT_FORMATTER, WS_TOPICS_LIMIT, MAX_WEBSOCKETS, WINDOW_TITLE, State ) @@ -982,9 +982,13 @@ class TrayIcon: def __init__(self, manager: GUIManager, master: ttk.Widget): self._manager = manager self.icon: pystray.Icon | None = None + self.icon_image = Image_module.open(resource_path("pickaxe.ico")) self._button = ttk.Button(master, command=self.minimize, text=_("gui", "tray", "minimize")) self._button.grid(column=0, row=0, sticky="ne") + def __del__(self) -> None: + self.icon_image.close() + def is_tray(self) -> bool: return self.icon is not None @@ -1013,12 +1017,7 @@ class TrayIcon: pystray.Menu.SEPARATOR, pystray.MenuItem(_("gui", "tray", "quit"), bridge(self.quit)), ) - self.icon = pystray.Icon( - "twitch_miner", - Image_module.open(resource_path("pickaxe.ico")), - self.get_title(drop), - menu, - ) + self.icon = pystray.Icon("twitch_miner", self.icon_image, self.get_title(drop), menu) self.icon.run_detached() def stop(self): @@ -1779,13 +1778,10 @@ class GUIManager: # withdraw immediately to prevent the window from flashing self._root.withdraw() # root.resizable(False, True) - root.iconphoto( # window icon - True, - PhotoImage( - master=root, - image=Image_module.open(resource_path("pickaxe.ico")), - ) - ) + icon_photo = get_photo_image(root, resource_path("pickaxe.ico")) + root.iconphoto(True, icon_photo) # window icon + # keep a reference to the PhotoImage to avoid the ResourceWarning + root._icon_image = icon_photo # type: ignore[attr-defined] root.title(WINDOW_TITLE) # window title root.bind_all("", self.unfocus) # pressing ESC unfocuses selection # Image cache for displaying images diff --git a/main.py b/main.py index 7311139..d2485e5 100644 --- a/main.py +++ b/main.py @@ -18,18 +18,15 @@ if __name__ == "__main__": from tkinter import messagebox from typing import IO, NoReturn - from PIL.ImageTk import PhotoImage - from PIL import Image as Image_module - if sys.platform == "win32": import win32gui from translate import _ from twitch import Twitch from settings import Settings - from utils import resource_path from version import __version__ from exceptions import CaptchaRequired + from utils import resource_path, get_photo_image from constants import CALL, SELF_PATH, FILE_FORMATTER, LOG_PATH, WINDOW_TITLE warnings.simplefilter("default", ResourceWarning) @@ -95,9 +92,10 @@ if __name__ == "__main__": root = tk.Tk() root.overrideredirect(True) root.withdraw() - root.iconphoto( - True, PhotoImage(master=root, image=Image_module.open(resource_path("pickaxe.ico"))) - ) + icon_photo = get_photo_image(root, resource_path("pickaxe.ico")) + root.iconphoto(True, icon_photo) + # keep a reference to the PhotoImage to avoid the ResourceWarning + root._icon_image = icon_photo # type: ignore[attr-defined] root.update() parser = Parser( SELF_PATH.name, diff --git a/utils.py b/utils.py index 373c014..baf2cdd 100644 --- a/utils.py +++ b/utils.py @@ -6,6 +6,7 @@ import string import asyncio import logging import traceback +import tkinter as tk from enum import Enum from pathlib import Path from functools import wraps @@ -17,6 +18,8 @@ from typing import ( ) import yarl +from PIL.ImageTk import PhotoImage +from PIL import Image as Image_module from constants import JsonType from exceptions import ExitRequest, ReloadRequest @@ -38,6 +41,13 @@ _JSON_T = TypeVar("_JSON_T", bound=Mapping[Any, Any]) logger = logging.getLogger("TwitchDrops") +def get_photo_image(master: tk.Misc, path: Path | str) -> PhotoImage: + image = Image_module.open(path) + photo = PhotoImage(master=master, image=image) + image.close() + return photo + + async def first_to_complete(coros: abc.Iterable[abc.Coroutine[Any, Any, _T]]) -> _T: # In Python 3.11, we need to explicitly wrap awaitables tasks = [asyncio.ensure_future(coro) for coro in coros]