From 038fd1fba95a316863d5b1f3239692b50cb584bc Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Fri, 24 Apr 2026 01:48:23 +0300 Subject: [PATCH 1/2] ImageWidget: check if C++ object has been deleted before interacting with it --- rare/widgets/image_widget.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rare/widgets/image_widget.py b/rare/widgets/image_widget.py index 5e23f824d..4e0dfb0e8 100644 --- a/rare/widgets/image_widget.py +++ b/rare/widgets/image_widget.py @@ -1,6 +1,6 @@ -from contextlib import suppress from enum import Enum +import shiboken6 from PySide6.QtCore import QRectF, QSize, Qt from PySide6.QtGui import ( QBrush, @@ -67,7 +67,7 @@ def setPixmap(self, pixmap: QPixmap) -> None: self.paint_image = self.paint_image_empty # FIXME: this suppresss the RuntimeError raised by Qt if the widget has already # deleted. Temporary until I look into it again. - with suppress(RuntimeError): + if shiboken6.isValid(self): self.update() def sizeHint(self) -> QSize: @@ -200,7 +200,8 @@ def fetchPixmap(self, url: str, params: dict = None): def _on_image_ready(self, data): super()._on_image_ready(data) - self.spinner.stop() + if shiboken6.isValid(self.spinner): + self.spinner.stop() __all__ = ['ImageSize', 'ImageWidget', 'LoadingImageWidget', 'LoadingSpinnerImageWidget'] From 0812f80b9c387d917bc378ee4b35e904b3a98388 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Fri, 24 Apr 2026 11:19:45 +0300 Subject: [PATCH 2/2] chore: remove typing from class factories --- pylintrc | 6 +-- pyproject.toml | 2 +- .../tabs/settings/widgets/overlay.py | 6 +-- .../tabs/store/api/models/diesel.py | 12 +++--- .../tabs/store/api/models/response.py | 40 +++++++++---------- rare/models/steam.py | 4 +- 6 files changed, 33 insertions(+), 37 deletions(-) diff --git a/pylintrc b/pylintrc index c9e02674e..51e79eb98 100644 --- a/pylintrc +++ b/pylintrc @@ -100,10 +100,6 @@ recursive=yes # source root. source-roots= -# When enabled, pylint would attempt to guess common misconfiguration and emit -# user-friendly hints instead of false-positive error messages. -suggestion-mode=yes - # Allow loading of arbitrary C extensions. Extensions are imported into the # active Python interpreter and may run arbitrary code. unsafe-load-any-extension=no @@ -567,7 +563,7 @@ contextmanager-decorators=contextlib.contextmanager # List of members which are set dynamically and missed by pylint inference # system, and so shouldn't trigger E1101 when accessed. Python regular # expressions are accepted. -generated-members=PyQt5.*,orjson.* +generated-members=PyQt5.*,orjson.*,shiboken6.* # Tells whether to warn about missing members when the owner of the attribute # is inferred to be None. diff --git a/pyproject.toml b/pyproject.toml index 685a236dd..9d3688a3d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -146,7 +146,7 @@ indent-width = 4 [tool.ruff.lint] extend-select = ["E", "F", "UP", "B", "SIM", "I"] -extend-ignore = [ "UP008", "SIM102", "SIM108" ] +extend-ignore = [ "UP008", "SIM102", "SIM108", "E501" ] [tool.ruff.format] quote-style = "single" diff --git a/rare/components/tabs/settings/widgets/overlay.py b/rare/components/tabs/settings/widgets/overlay.py index a12b22e4e..6b38c6f00 100644 --- a/rare/components/tabs/settings/widgets/overlay.py +++ b/rare/components/tabs/settings/widgets/overlay.py @@ -27,7 +27,7 @@ def getValue(self) -> str | None: return f'{self.option}={text}' if (text := self.text()) else None def setValue(self, options: dict[str, str]): - if (value := options.get(self.option, None)) is not None: + if (value := options.get(self.option)) is not None: self.setText(value) options.pop(self.option) else: @@ -47,7 +47,7 @@ def getValue(self) -> str | None: return f'{self.option}={self.currentData(Qt.ItemDataRole.UserRole)}' if self.currentIndex() > 0 else None def setValue(self, options: dict[str, str]): - if (value := options.get(self.option, None)) is not None: + if (value := options.get(self.option)) is not None: self.setCurrentIndex(self.findData(value, Qt.ItemDataRole.UserRole)) options.pop(self.option) else: @@ -85,7 +85,7 @@ def getValue(self) -> str | None: return value if checked ^ self.default_enabled else None def setValue(self, options: dict[str, str]): - if options.get(self.option, None) is not None: + if options.get(self.option) is not None: if self.values: self.setChecked(bool(self.values.index(options[self.option]))) else: diff --git a/rare/components/tabs/store/api/models/diesel.py b/rare/components/tabs/store/api/models/diesel.py index 589c58f17..b5d9c4d25 100644 --- a/rare/components/tabs/store/api/models/diesel.py +++ b/rare/components/tabs/store/api/models/diesel.py @@ -17,7 +17,7 @@ class DieselSystemDetailItem: unmapped: dict[str, Any] = field(default_factory=dict) @classmethod - def from_dict(cls: type['DieselSystemDetailItem'], src: dict[str, Any]) -> 'DieselSystemDetailItem': + def from_dict(cls, src: dict[str, Any]) -> 'DieselSystemDetailItem': d = src.copy() return cls( _type=d.pop('_type', ''), @@ -36,7 +36,7 @@ class DieselSystemDetail: unmapped: dict[str, Any] = field(default_factory=dict) @classmethod - def from_dict(cls: type['DieselSystemDetail'], src: dict[str, Any]) -> 'DieselSystemDetail': + def from_dict(cls, src: dict[str, Any]) -> 'DieselSystemDetail': d = src.copy() details = tuple(map(DieselSystemDetailItem.from_dict, d.pop('details', []))) return cls( @@ -56,7 +56,7 @@ class DieselSystemDetails: unmapped: dict[str, Any] = field(default_factory=dict) @classmethod - def from_dict(cls: type['DieselSystemDetails'], src: dict[str, Any]) -> 'DieselSystemDetails': + def from_dict(cls, src: dict[str, Any]) -> 'DieselSystemDetails': d = src.copy() systems = tuple(map(DieselSystemDetail.from_dict, d.pop('systems', []))) return cls( @@ -78,7 +78,7 @@ class DieselProductAbout: unmapped: dict[str, Any] = field(default_factory=dict) @classmethod - def from_dict(cls: type['DieselProductAbout'], src: dict[str, Any]) -> 'DieselProductAbout': + def from_dict(cls, src: dict[str, Any]) -> 'DieselProductAbout': d = src.copy() return cls( _type=d.pop('_type', ''), @@ -99,7 +99,7 @@ class DieselProductDetail: unmapped: dict[str, Any] = field(default_factory=dict) @classmethod - def from_dict(cls: type['DieselProductDetail'], src: dict[str, Any]) -> 'DieselProductDetail': + def from_dict(cls, src: dict[str, Any]) -> 'DieselProductDetail': d = src.copy() about = DieselProductAbout.from_dict(x) if (x := d.pop('about'), {}) else None requirements = DieselSystemDetails.from_dict(x) if (x := d.pop('requirements', {})) else None @@ -127,7 +127,7 @@ class DieselProduct: unmapped: dict[str, Any] = field(default_factory=dict) @classmethod - def from_dict(cls: type['DieselProduct'], src: dict[str, Any]) -> 'DieselProduct': + def from_dict(cls, src: dict[str, Any]) -> 'DieselProduct': d = src.copy() pages = tuple(map(DieselProduct.from_dict, d.pop('pages', []))) data = DieselProductDetail.from_dict(x) if (x := d.pop('data', {})) else None diff --git a/rare/components/tabs/store/api/models/response.py b/rare/components/tabs/store/api/models/response.py index 13ae7e039..2e4f16ed1 100644 --- a/rare/components/tabs/store/api/models/response.py +++ b/rare/components/tabs/store/api/models/response.py @@ -34,7 +34,7 @@ def as_dict(self) -> dict[str, Any]: return tmp @classmethod - def from_dict(cls: type['ImageUrlModel'], src: dict[str, Any]) -> 'ImageUrlModel': + def from_dict(cls, src: dict[str, Any]) -> 'ImageUrlModel': d = src.copy() return cls(type=d.pop('type', ''), url=d.pop('url', '')) @@ -66,7 +66,7 @@ def __bool__(self): return bool(self.key_images) @classmethod - def from_list(cls: type['KeyImagesModel'], src: list[dict]): + def from_list(cls, src: list[dict]): d = src.copy() key_images = tuple(map(ImageUrlModel.from_dict, d)) return cls(key_images=key_images) @@ -112,7 +112,7 @@ class TotalPriceModel: unmapped: dict[str, Any] = field(default_factory=dict) @classmethod - def from_dict(cls: type['TotalPriceModel'], src: dict[str, Any]) -> 'TotalPriceModel': + def from_dict(cls, src: dict[str, Any]) -> 'TotalPriceModel': d = src.copy() return cls( discountPrice=d.pop('discountPrice', 0), @@ -133,7 +133,7 @@ class GetPriceResModel: unmapped: dict[str, Any] = field(default_factory=dict) @classmethod - def from_dict(cls: type['GetPriceResModel'], src: dict[str, Any]) -> 'GetPriceResModel': + def from_dict(cls, src: dict[str, Any]) -> 'GetPriceResModel': d = src.copy() total_price = TotalPriceModel.from_dict(x) if (x := d.pop('totalPrice', {})) else None return cls(totalPrice=total_price, lineOffers=d.pop('lineOffers', {}), unmapped=d) @@ -150,7 +150,7 @@ class PromotionalOfferModel: unmapped: dict[str, Any] = field(default_factory=dict) @classmethod - def from_dict(cls: type['PromotionalOfferModel'], src: dict[str, Any]) -> 'PromotionalOfferModel': + def from_dict(cls, src: dict[str, Any]) -> 'PromotionalOfferModel': d = src.copy() start_date = parse_date(x) if (x := d.pop('startDate', '')) else None end_date = parse_date(x) if (x := d.pop('endDate', '')) else None @@ -168,7 +168,7 @@ class PromotionalOffersModel: unmapped: dict[str, Any] = field(default_factory=dict) @classmethod - def from_list(cls: type['PromotionalOffersModel'], src: dict[str, list]) -> 'PromotionalOffersModel': + def from_list(cls, src: dict[str, list]) -> 'PromotionalOffersModel': d = src.copy() promotional_offers = tuple(map(PromotionalOfferModel.from_dict, d.pop('promotionalOffers', []))) return cls(promotionalOffers=promotional_offers, unmapped=d) @@ -181,7 +181,7 @@ class PromotionsModel: unmapped: dict[str, Any] = field(default_factory=dict) @classmethod - def from_dict(cls: type['PromotionsModel'], src: dict[str, Any]) -> 'PromotionsModel': + def from_dict(cls, src: dict[str, Any]) -> 'PromotionsModel': d = src.copy() promotional_offers = tuple(map(PromotionalOffersModel.from_list, d.pop('promotionalOffers', []))) upcoming_promotional_offers = tuple(map(PromotionalOffersModel.from_list, d.pop('upcomingPromotionalOffers', []))) @@ -220,7 +220,7 @@ class CatalogOfferModel: unmapped: dict[str, Any] = field(default_factory=dict) @classmethod - def from_dict(cls: type['CatalogOfferModel'], src: dict[str, Any]) -> 'CatalogOfferModel': + def from_dict(cls, src: dict[str, Any]) -> 'CatalogOfferModel': d = src.copy() effective_date = parse_date(x) if (x := d.pop('effectiveDate', '')) else None expiry_date = parse_date(x) if (x := d.pop('expiryDate', '')) else None @@ -269,7 +269,7 @@ class WishlistItemModel: unmapped: dict[str, Any] = field(default_factory=dict) @classmethod - def from_dict(cls: type['WishlistItemModel'], src: dict[str, Any]) -> 'WishlistItemModel': + def from_dict(cls, src: dict[str, Any]) -> 'WishlistItemModel': d = src.copy() created = parse_date(x) if (x := d.pop('created', '')) else None offer = CatalogOfferModel.from_dict(x) if (x := d.pop('offer', {})) else None @@ -294,7 +294,7 @@ class PagingModel: unmapped: dict[str, Any] = field(default_factory=dict) @classmethod - def from_dict(cls: type['PagingModel'], src: dict[str, Any]) -> 'PagingModel': + def from_dict(cls, src: dict[str, Any]) -> 'PagingModel': d = src.copy() count = d.pop('count', 0) total = d.pop('total', 0) @@ -308,7 +308,7 @@ class SearchStoreModel: unmapped: dict[str, Any] = field(default_factory=dict) @classmethod - def from_dict(cls: type['SearchStoreModel'], src: dict[str, Any]) -> 'SearchStoreModel': + def from_dict(cls, src: dict[str, Any]) -> 'SearchStoreModel': d = src.copy() _elements = d.pop('elements', []) elements = tuple(map(CatalogOfferModel.from_dict, _elements)) @@ -322,7 +322,7 @@ class CatalogModel: unmapped: dict[str, Any] = field(default_factory=dict) @classmethod - def from_dict(cls: type['CatalogModel'], src: dict[str, Any]) -> 'CatalogModel': + def from_dict(cls, src: dict[str, Any]) -> 'CatalogModel': d = src.copy() search_store = SearchStoreModel.from_dict(x) if (x := d.pop('searchStore', {})) else None return cls(searchStore=search_store, unmapped=d) @@ -335,7 +335,7 @@ class WishlistItemsModel: unmapped: dict[str, Any] = field(default_factory=dict) @classmethod - def from_dict(cls: type['WishlistItemsModel'], src: dict[str, Any]) -> 'WishlistItemsModel': + def from_dict(cls, src: dict[str, Any]) -> 'WishlistItemsModel': d = src.copy() _elements = d.pop('elements', []) elements = tuple(map(WishlistItemModel.from_dict, _elements)) @@ -349,7 +349,7 @@ class RemoveFromWishlistModel: unmapped: dict[str, Any] = field(default_factory=dict) @classmethod - def from_dict(cls: type['RemoveFromWishlistModel'], src: dict[str, Any]) -> 'RemoveFromWishlistModel': + def from_dict(cls, src: dict[str, Any]) -> 'RemoveFromWishlistModel': d = src.copy() return cls(success=d.pop('success', False), unmapped=d) @@ -361,7 +361,7 @@ class AddToWishlistModel: unmapped: dict[str, Any] = field(default_factory=dict) @classmethod - def from_dict(cls: type['AddToWishlistModel'], src: dict[str, Any]) -> 'AddToWishlistModel': + def from_dict(cls, src: dict[str, Any]) -> 'AddToWishlistModel': d = src.copy() wishlist_item = WishlistItemModel.from_dict(x) if (x := d.pop('wishlistItem', {})) else None return cls(wishlistItem=wishlist_item, success=d.pop('success', False), unmapped=d) @@ -375,7 +375,7 @@ class WishlistModel: unmapped: dict[str, Any] = field(default_factory=dict) @classmethod - def from_dict(cls: type['WishlistModel'], src: dict[str, Any]) -> 'WishlistModel': + def from_dict(cls, src: dict[str, Any]) -> 'WishlistModel': d = src.copy() wishlist_items = WishlistItemsModel.from_dict(x) if (x := d.pop('wishlistItems', {})) else None remove_from_wishlist = RemoveFromWishlistModel.from_dict(x) if (x := d.pop('removeFromWishlist', {})) else None @@ -399,7 +399,7 @@ class DataModel: unmapped: dict[str, Any] = field(default_factory=dict) @classmethod - def from_dict(cls: type['DataModel'], src: dict[str, Any]) -> 'DataModel': + def from_dict(cls, src: dict[str, Any]) -> 'DataModel': d = src.copy() catalog = CatalogModel.from_dict(x) if (x := d.pop('Catalog', {})) else None wishlist = WishlistModel.from_dict(x) if (x := d.pop('Wishlist', {})) else None @@ -417,7 +417,7 @@ def __str__(self): return f'{self.correlationId} - {self.message}' @classmethod - def from_dict(cls: type['ErrorModel'], src: dict[str, Any]) -> 'ErrorModel': + def from_dict(cls, src: dict[str, Any]) -> 'ErrorModel': d = src.copy() return cls( message=d.pop('message', ''), @@ -432,7 +432,7 @@ class ExtensionsModel: unmapped: dict[str, Any] = field(default_factory=dict) @classmethod - def from_dict(cls: type['ExtensionsModel'], src: dict[str, Any]) -> 'ExtensionsModel': + def from_dict(cls, src: dict[str, Any]) -> 'ExtensionsModel': d = src.copy() return cls(unmapped=d) @@ -445,7 +445,7 @@ class ResponseModel: unmapped: dict[str, Any] = field(default_factory=dict) @classmethod - def from_dict(cls: type['ResponseModel'], src: dict[str, Any]) -> 'ResponseModel': + def from_dict(cls, src: dict[str, Any]) -> 'ResponseModel': d = src.copy() data = DataModel.from_dict(x) if (x := d.pop('data', {})) else None _errors = d.pop('errors', []) diff --git a/rare/models/steam.py b/rare/models/steam.py index 5651c5516..3f6867df2 100644 --- a/rare/models/steam.py +++ b/rare/models/steam.py @@ -72,7 +72,7 @@ class SteamShortcut: tags: dict = field(default_factory=dict) @classmethod - def from_dict(cls: type['SteamShortcut'], src: dict[str, Any]) -> 'SteamShortcut': + def from_dict(cls, src: dict[str, Any]) -> 'SteamShortcut': d = src.copy() tmp = cls( appid=d.pop('appid', 0), @@ -97,7 +97,7 @@ def from_dict(cls: type['SteamShortcut'], src: dict[str, Any]) -> 'SteamShortcut @classmethod def create( - cls: type['SteamShortcut'], + cls, app_name: str, app_title: str, executable: str,