From b30e9b9abd6bcfbfefa46ceeac61362889222cad Mon Sep 17 00:00:00 2001 From: EmoonX Date: Tue, 14 Jan 2025 14:00:56 -0300 Subject: [PATCH 1/3] chore: Add `pyupgrade` as a pre-commit hook --- .pre-commit-config.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 55478f22819..44316bfa1ef 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,6 +13,12 @@ repos: args: ["--autofix", "--no-sort-keys", "--indent", "4"] - id: check-added-large-files +- repo: https://github.com/asottile/pyupgrade + rev: v3.19.1 + hooks: + - id: pyupgrade + args: ["--py312-plus"] + - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.8.2 hooks: From 2cf21f67a6763f3003e28de8b8b63f35ae2bc882 Mon Sep 17 00:00:00 2001 From: Hari Rana Date: Thu, 16 Jan 2025 19:20:24 -0500 Subject: [PATCH 2/3] chore: Run `pyupgrade` for the entire codebase Co-authored-by: EmoonX --- bottles/backend/cabextract.py | 3 +-- bottles/backend/downloader.py | 3 +-- bottles/backend/globals.py | 5 ++-- bottles/backend/managers/component.py | 7 +++--- bottles/backend/managers/conf.py | 13 +++++----- bottles/backend/managers/data.py | 2 +- bottles/backend/managers/dependency.py | 4 ++- bottles/backend/managers/epicgamesstore.py | 2 +- bottles/backend/managers/installer.py | 3 +-- bottles/backend/managers/journal.py | 5 ++-- bottles/backend/managers/library.py | 2 +- bottles/backend/managers/manager.py | 22 ++++++++-------- bottles/backend/managers/sandbox.py | 9 +++---- bottles/backend/managers/steam.py | 17 ++++++------- bottles/backend/managers/template.py | 4 +-- bottles/backend/managers/ubisoftconnect.py | 9 +++---- bottles/backend/managers/versioning.py | 18 ++++++------- bottles/backend/models/config.py | 11 ++++---- bottles/backend/models/vdict.py | 18 ++++++------- bottles/backend/state.py | 25 ++++++++++--------- bottles/backend/utils/connection.py | 7 +++--- bottles/backend/utils/file.py | 6 ++--- bottles/backend/utils/generic.py | 3 +-- bottles/backend/utils/json.py | 10 ++++---- bottles/backend/utils/manager.py | 5 ++-- bottles/backend/utils/singleton.py | 2 +- bottles/backend/utils/steam.py | 8 +++--- bottles/backend/utils/threading.py | 2 +- bottles/backend/utils/vdf.py | 10 +++----- bottles/backend/wine/cmd.py | 6 ++--- bottles/backend/wine/executor.py | 25 +++++++++---------- bottles/backend/wine/explorer.py | 10 +++----- bottles/backend/wine/msiexec.py | 18 ++++++------- bottles/backend/wine/net.py | 8 +++--- bottles/backend/wine/notepad.py | 4 +-- bottles/backend/wine/reg.py | 15 +++++------ bottles/backend/wine/register.py | 7 +++--- bottles/backend/wine/start.py | 10 +++----- bottles/backend/wine/uninstaller.py | 6 ++--- bottles/backend/wine/wineboot.py | 2 +- bottles/backend/wine/winecommand.py | 19 +++++++------- bottles/backend/wine/winedbg.py | 5 ++-- bottles/backend/wine/wineprogram.py | 15 ++++++----- bottles/backend/wine/xcopy.py | 3 +-- bottles/frontend/bottle_details_page.py | 5 ++-- bottles/frontend/bottle_details_view.py | 3 +-- bottles/frontend/component_entry_row.py | 3 +-- bottles/frontend/details_dependencies_view.py | 3 +-- bottles/frontend/details_preferences_page.py | 2 +- bottles/frontend/details_task_manager_view.py | 5 ++-- bottles/frontend/gtk.py | 3 +-- bottles/frontend/new_bottle_dialog.py | 4 +-- bottles/frontend/operation.py | 3 +-- bottles/frontend/window.py | 5 ++-- bottles/tests/backend/utils/test_generic.py | 4 +-- 55 files changed, 193 insertions(+), 235 deletions(-) diff --git a/bottles/backend/cabextract.py b/bottles/backend/cabextract.py index 4fcf3702361..bfe6cd00b5a 100644 --- a/bottles/backend/cabextract.py +++ b/bottles/backend/cabextract.py @@ -19,7 +19,6 @@ import shlex import shutil import subprocess -from typing import Optional from bottles.backend.logger import Logger @@ -47,7 +46,7 @@ def run( self, path: str, name: str = "", - files: Optional[list] = None, + files: list | None = None, destination: str = "", ): if files is None: diff --git a/bottles/backend/downloader.py b/bottles/backend/downloader.py index 9772be16389..482e88f0908 100644 --- a/bottles/backend/downloader.py +++ b/bottles/backend/downloader.py @@ -18,7 +18,6 @@ import shutil import sys import time -from typing import Optional import requests @@ -38,7 +37,7 @@ class Downloader: """ def __init__( - self, url: str, file: str, update_func: Optional[TaskStreamUpdateHandler] = None + self, url: str, file: str, update_func: TaskStreamUpdateHandler | None = None ): self.start_time = None self.url = url diff --git a/bottles/backend/globals.py b/bottles/backend/globals.py index 488acea3154..8123b2396ba 100644 --- a/bottles/backend/globals.py +++ b/bottles/backend/globals.py @@ -18,7 +18,6 @@ import os import shutil from pathlib import Path -from typing import Dict from bottles.backend.utils import yaml, json @@ -82,7 +81,7 @@ class TrdyPaths: vmtouch_available = shutil.which("vmtouch") or False base_version = "" if os.path.isfile("/app/manifest.json"): - with open("/app/manifest.json", mode="r", encoding="utf-8") as file: + with open("/app/manifest.json", encoding="utf-8") as file: base_version = ( json.load(file) # type: ignore .get("base-version", "") @@ -90,4 +89,4 @@ class TrdyPaths: ) # encoding detection correction, following windows defaults -locale_encodings: Dict[str, str] = {"ja_JP": "cp932", "zh_CN": "gbk"} +locale_encodings: dict[str, str] = {"ja_JP": "cp932", "zh_CN": "gbk"} diff --git a/bottles/backend/managers/component.py b/bottles/backend/managers/component.py index b9ecacc41cf..9065f7f337f 100644 --- a/bottles/backend/managers/component.py +++ b/bottles/backend/managers/component.py @@ -20,7 +20,6 @@ import shutil import tarfile from functools import lru_cache -from typing import Optional import pycurl @@ -131,7 +130,7 @@ def download( file: str, rename: str = "", checksum: str = "", - func: Optional[TaskStreamUpdateHandler] = None, + func: TaskStreamUpdateHandler | None = None, ) -> bool: """Download a component from the Bottles repository.""" @@ -274,7 +273,7 @@ def extract(name: str, component: str, archive: str) -> bool: root_dir = tar.getnames()[0] tar.extractall(path) tar.close() - except (tarfile.TarError, IOError, EOFError): + except (tarfile.TarError, OSError, EOFError): with contextlib.suppress(FileNotFoundError): os.remove(os.path.join(Paths.temp, archive)) with contextlib.suppress(FileNotFoundError): @@ -301,7 +300,7 @@ def install( self, component_type: str, component_name: str, - func: Optional[TaskStreamUpdateHandler] = None, + func: TaskStreamUpdateHandler | None = None, ): """ This function is used to install a component. It automatically diff --git a/bottles/backend/managers/conf.py b/bottles/backend/managers/conf.py index 736627d4c31..2936700746b 100644 --- a/bottles/backend/managers/conf.py +++ b/bottles/backend/managers/conf.py @@ -1,16 +1,15 @@ import os from configparser import ConfigParser -from typing import Optional from bottles.backend.utils import yaml, json -class ConfigManager(object): +class ConfigManager: def __init__( self, - config_file: Optional[str] = None, + config_file: str | None = None, config_type: str = "ini", - config_string: Optional[str] = None, + config_string: str | None = None, ): self.config_file = config_file self.config_string = config_string @@ -44,10 +43,10 @@ def read(self): # noinspection PyProtectedMember res = config._sections elif self.config_type == "json": - with open(self.config_file, "r") as f: + with open(self.config_file) as f: res = json.load(f) elif self.config_type == "yaml" or self.config_type == "yml": - with open(self.config_file, "r") as f: + with open(self.config_file) as f: res = yaml.load(f) else: raise ValueError("Invalid configuration type") @@ -94,7 +93,7 @@ def write_ini(self): with open(self.config_file, "w") as f: config.write(f) - def write_dict(self, config_file: Optional[str] = None): + def write_dict(self, config_file: str | None = None): if self.config_file is None and config_file is None: raise ValueError("No config path specified") elif self.config_file is None and config_file is not None: diff --git a/bottles/backend/managers/data.py b/bottles/backend/managers/data.py index d5224eb1c02..fc463840ca4 100644 --- a/bottles/backend/managers/data.py +++ b/bottles/backend/managers/data.py @@ -45,7 +45,7 @@ def __init__(self): def __get_data(self): try: - with open(self.__p_data, "r") as s: + with open(self.__p_data) as s: self.__data = yaml.load(s) if self.__data is None: raise AttributeError diff --git a/bottles/backend/managers/dependency.py b/bottles/backend/managers/dependency.py index ce238094b56..0c95dfc8dbf 100644 --- a/bottles/backend/managers/dependency.py +++ b/bottles/backend/managers/dependency.py @@ -91,7 +91,9 @@ def install(self, config: BottleConfig, dependency: list) -> Result: task_id = TaskManager.add(Task(title=dependency[0])) logging.info( - "Installing dependency [%s] in bottle [%s]." % (dependency[0], config.Name), + "Installing dependency [{}] in bottle [{}].".format( + dependency[0], config.Name + ), ) manifest = self.get_dependency(dependency[0]) if not manifest: diff --git a/bottles/backend/managers/epicgamesstore.py b/bottles/backend/managers/epicgamesstore.py index 6fb93379513..127fe616f95 100644 --- a/bottles/backend/managers/epicgamesstore.py +++ b/bottles/backend/managers/epicgamesstore.py @@ -59,7 +59,7 @@ def get_installed_games(config: BottleConfig) -> list: if dat_path is None: return [] - with open(dat_path, "r") as dat: + with open(dat_path) as dat: data = json.load(dat) for game in data["InstallationList"]: diff --git a/bottles/backend/managers/installer.py b/bottles/backend/managers/installer.py index 0fb1e65f278..5bf63a53786 100644 --- a/bottles/backend/managers/installer.py +++ b/bottles/backend/managers/installer.py @@ -18,7 +18,6 @@ import subprocess import uuid from functools import lru_cache -from typing import Optional import markdown import pycurl @@ -358,7 +357,7 @@ def install( installer: dict, step_fn: callable, is_final: bool = True, - local_resources: Optional[dict] = None, + local_resources: dict | None = None, ): manifest = self.get_installer(installer[0]) _config = config diff --git a/bottles/backend/managers/journal.py b/bottles/backend/managers/journal.py index d993f763226..8c471ff180b 100644 --- a/bottles/backend/managers/journal.py +++ b/bottles/backend/managers/journal.py @@ -20,7 +20,6 @@ import shutil import uuid from datetime import datetime, timedelta -from typing import Optional from bottles.backend.globals import Paths from bottles.backend.utils import yaml @@ -52,7 +51,7 @@ def __get_journal() -> dict: with open(JournalManager.path, "w") as f: yaml.dump({}, f) - with open(JournalManager.path, "r") as f: + with open(JournalManager.path) as f: try: journal = yaml.load(f) except yaml.YAMLError: @@ -100,7 +99,7 @@ def __clean_old(): JournalManager.__save_journal(journal) @staticmethod - def __save_journal(journal: Optional[dict] = None): + def __save_journal(journal: dict | None = None): """Save the journal to the journal file.""" if journal is None: journal = JournalManager.__get_journal() diff --git a/bottles/backend/managers/library.py b/bottles/backend/managers/library.py index d2421344b0c..d462c8de511 100644 --- a/bottles/backend/managers/library.py +++ b/bottles/backend/managers/library.py @@ -49,7 +49,7 @@ def load_library(self, silent=False): self.__library = {} self.save_library() else: - with open(self.library_path, "r") as library_file: + with open(self.library_path) as library_file: self.__library = yaml.load(library_file) if self.__library is None: diff --git a/bottles/backend/managers/manager.py b/bottles/backend/managers/manager.py index 8b04c30e218..fb4504d1c6d 100644 --- a/bottles/backend/managers/manager.py +++ b/bottles/backend/managers/manager.py @@ -26,7 +26,7 @@ from datetime import datetime from gettext import gettext as _ from glob import glob -from typing import Any, Dict, List, Optional +from typing import Any import pathvalidate @@ -91,7 +91,7 @@ class Manager(metaclass=Singleton): vkd3d_available = [] nvapi_available = [] latencyflex_available = [] - local_bottles: Dict[str, BottleConfig] = {} + local_bottles: dict[str, BottleConfig] = {} supported_runtimes = {} supported_winebridge = {} supported_wine_runners = {} @@ -413,7 +413,7 @@ def check_runners(self, install_latest: bool = True) -> bool: if len(self.runners_available) > 0: logging.info( - "Runners found:\n - {0}".format("\n - ".join(self.runners_available)) + "Runners found:\n - {}".format("\n - ".join(self.runners_available)) ) tmp_runners = [x for x in self.runners_available if not x.startswith("sys-")] @@ -464,7 +464,7 @@ def check_runtimes(self, install_latest: bool = True) -> bool: manifest = os.path.join(Paths.runtimes, runtime, "manifest.yml") if os.path.exists(manifest): - with open(manifest, "r") as f: + with open(manifest) as f: data = yaml.load(f) version = data.get("version") if version: @@ -493,7 +493,7 @@ def check_winebridge( version_file = os.path.join(Paths.winebridge, "VERSION") if os.path.exists(version_file): - with open(version_file, "r") as f: + with open(version_file) as f: version = f.read().strip() if version: self.winebridge_available = [f"winebridge-{version}"] @@ -630,7 +630,7 @@ def __check_component( if len(component["available"]) > 0: logging.info( - "{0}s found:\n - {1}".format( + "{}s found:\n - {}".format( component_type.capitalize(), "\n - ".join(component["available"]) ) ) @@ -663,7 +663,7 @@ def __check_component( except ValueError: return sorted(component["available"], reverse=True) - def get_programs(self, config: BottleConfig) -> List[dict]: + def get_programs(self, config: BottleConfig) -> list[dict]: """ Get the list of programs (both from the drive and the user defined in the bottle configuration file). @@ -825,7 +825,7 @@ def process_bottle(bottle): _config = os.path.join(_bottle, "bottle.yml") if os.path.exists(_placeholder): - with open(_placeholder, "r") as f: + with open(_placeholder) as f: try: placeholder_yaml = yaml.load(f) if placeholder_yaml.get("Path"): @@ -951,7 +951,7 @@ def process_bottle(bottle): if len(self.local_bottles) > 0 and not silent: logging.info( - "Bottles found:\n - {0}".format("\n - ".join(self.local_bottles)) + "Bottles found:\n - {}".format("\n - ".join(self.local_bottles)) ) if ( @@ -1138,7 +1138,7 @@ def create_bottle( sandbox: bool = False, fn_logger: callable = None, arch: str = "win64", - custom_environment: Optional[str] = None, + custom_environment: str | None = None, ) -> Result[dict]: """ Create a new bottle from the given arguments. @@ -1392,7 +1392,7 @@ def components_check(): env = Samples.environments[environment.lower()] elif custom_environment: try: - with open(custom_environment, "r") as f: + with open(custom_environment) as f: env = yaml.load(f.read()) logging.warning("Using a custom environment recipe…") log_update(_("(!) Using a custom environment recipe…")) diff --git a/bottles/backend/managers/sandbox.py b/bottles/backend/managers/sandbox.py index c943e98995a..2f5661d994e 100644 --- a/bottles/backend/managers/sandbox.py +++ b/bottles/backend/managers/sandbox.py @@ -19,17 +19,16 @@ import os import shlex import subprocess -from typing import Optional class SandboxManager: def __init__( self, - envs: Optional[dict] = None, - chdir: Optional[str] = None, + envs: dict | None = None, + chdir: str | None = None, clear_env: bool = False, - share_paths_ro: Optional[list] = None, - share_paths_rw: Optional[list] = None, + share_paths_ro: list | None = None, + share_paths_rw: list | None = None, share_net: bool = False, share_user: bool = False, share_host_ro: bool = True, diff --git a/bottles/backend/managers/steam.py b/bottles/backend/managers/steam.py index 0596dec51a6..495214ad52d 100644 --- a/bottles/backend/managers/steam.py +++ b/bottles/backend/managers/steam.py @@ -24,7 +24,6 @@ from functools import lru_cache from glob import glob from pathlib import Path -from typing import Dict, Optional from bottles.backend.globals import Paths from bottles.backend.models.config import BottleConfig @@ -51,7 +50,7 @@ class SteamManager: def __init__( self, - config: Optional[BottleConfig] = None, + config: BottleConfig | None = None, is_windows: bool = False, check_only: bool = False, ): @@ -106,7 +105,7 @@ def get_acf_data(libraryfolder: str, app_id: str) -> dict | None: if not os.path.isfile(acf_path): return None - with open(acf_path, "r", errors="replace") as f: + with open(acf_path, errors="replace") as f: data = SteamUtils.parse_acf(f.read()) return data @@ -133,7 +132,7 @@ def __get_library_folders(self) -> list | None: logging.warning("Could not find the libraryfolders.vdf file") return None - with open(library_folders_path, "r", errors="replace") as f: + with open(library_folders_path, errors="replace") as f: _library_folders = SteamUtils.parse_vdf(f.read()) if _library_folders is None or not _library_folders.get("libraryfolders"): @@ -168,7 +167,7 @@ def __get_local_config(self) -> dict: if self.localconfig_path is None: return {} - with open(self.localconfig_path, "r", errors="replace") as f: + with open(self.localconfig_path, errors="replace") as f: data = SteamUtils.parse_vdf(f.read()) if data is None: @@ -192,14 +191,14 @@ def save_local_config(self, new_data: dict): @staticmethod @lru_cache - def get_runner_path(pfx_path: str) -> Optional[str]: + def get_runner_path(pfx_path: str) -> str | None: """Get runner path from config_info file""" config_info = os.path.join(pfx_path, "config_info") if not os.path.isfile(config_info): return None - with open(config_info, "r") as f: + with open(config_info) as f: lines = f.readlines() if len(lines) < 10: logging.error( @@ -273,7 +272,7 @@ def get_installed_apps_as_programs(self) -> list: return apps - def list_prefixes(self) -> Dict[str, BottleConfig]: + def list_prefixes(self) -> dict[str, BottleConfig]: apps = self.list_apps_ids() prefixes = {} @@ -391,7 +390,7 @@ def get_app_config(self, prefix: str) -> dict: return apps[prefix] - def get_launch_options(self, prefix: str, app_conf: Optional[dict] = None) -> {}: + def get_launch_options(self, prefix: str, app_conf: dict | None = None) -> {}: if app_conf is None: app_conf = self.get_app_config(prefix) diff --git a/bottles/backend/managers/template.py b/bottles/backend/managers/template.py index 1ab83ebfbdd..04bb82355fd 100644 --- a/bottles/backend/managers/template.py +++ b/bottles/backend/managers/template.py @@ -104,7 +104,7 @@ def __validate_template(template_uuid: str): logging.error(f"Template {template_uuid} is too small!") result = False - with open(os.path.join(template_path, "template.yml"), "r") as f: + with open(os.path.join(template_path, "template.yml")) as f: template = yaml.load(f) if template["uuid"] != template_uuid: logging.error(f"Template {template_uuid} has invalid uuid!") @@ -114,7 +114,7 @@ def __validate_template(template_uuid: str): @staticmethod def get_template_manifest(template: str): - with open(os.path.join(Paths.templates, template, "template.yml"), "r") as f: + with open(os.path.join(Paths.templates, template, "template.yml")) as f: return yaml.load(f) @staticmethod diff --git a/bottles/backend/managers/ubisoftconnect.py b/bottles/backend/managers/ubisoftconnect.py index 120d2cc9019..e56633e4094 100644 --- a/bottles/backend/managers/ubisoftconnect.py +++ b/bottles/backend/managers/ubisoftconnect.py @@ -17,7 +17,6 @@ import os import uuid -from typing import Optional from bottles.backend.models.config import BottleConfig from bottles.backend.utils.manager import ManagerUtils @@ -56,9 +55,9 @@ def get_installed_games(config: BottleConfig) -> list: """ found = {} games = [] - key: Optional[str] = None - appid: Optional[str] = None - thumb: Optional[str] = None + key: str | None = None + appid: str | None = None + thumb: str | None = None reg_key = ( "register: HKEY_LOCAL_MACHINE\\SOFTWARE\\Ubisoft\\Launcher\\Installs\\" ) @@ -71,7 +70,7 @@ def get_installed_games(config: BottleConfig) -> list: if conf_path is None: return [] - with open(conf_path, "r", encoding="iso-8859-15") as c: + with open(conf_path, encoding="iso-8859-15") as c: for r in c.readlines(): r = r.strip() diff --git a/bottles/backend/managers/versioning.py b/bottles/backend/managers/versioning.py index 30b63bc6abc..6f5163a1519 100644 --- a/bottles/backend/managers/versioning.py +++ b/bottles/backend/managers/versioning.py @@ -155,9 +155,7 @@ def set_state( status=True, message=_("State {0} restored successfully!").format(state_id), ) - task_id = TaskManager.add( - Task(title=_("Restoring state {} …".format(state_id))) - ) + task_id = TaskManager.add(Task(title=_(f"Restoring state {state_id} …"))) try: repo.restore_state(state_id, ignore=patterns) except FVSStateNotFound: @@ -204,25 +202,27 @@ def set_state( # perform file updates for file in remove_files: - os.remove("%s/drive_c/%s" % (bottle_path, file["file"])) + os.remove("{}/drive_c/{}".format(bottle_path, file["file"])) for file in add_files: - source = "%s/states/%s/drive_c/%s" % ( + source = "{}/states/{}/drive_c/{}".format( bottle_path, str(state_id), file["file"], ) - target = "%s/drive_c/%s" % (bottle_path, file["file"]) + target = "{}/drive_c/{}".format(bottle_path, file["file"]) shutil.copy2(source, target) for file in edit_files: for i in search_sources: - source = "%s/states/%s/drive_c/%s" % (bottle_path, str(i), file["file"]) + source = "{}/states/{}/drive_c/{}".format( + bottle_path, str(i), file["file"] + ) if os.path.isfile(source): checksum = FileUtils().get_checksum(source) if file["checksum"] == checksum: break - target = "%s/drive_c/%s" % (bottle_path, file["file"]) + target = "{}/drive_c/{}".format(bottle_path, file["file"]) shutil.copy2(source, target) # update State in bottle config @@ -250,7 +250,7 @@ def get_state_files( files = file.read() if plain else yaml.load(file.read()) file.close() return files - except (OSError, IOError, yaml.YAMLError): + except (OSError, yaml.YAMLError): logging.error("Could not read the state files file.") return {} diff --git a/bottles/backend/models/config.py b/bottles/backend/models/config.py index d42c6db74a3..9136f027ae2 100644 --- a/bottles/backend/models/config.py +++ b/bottles/backend/models/config.py @@ -3,7 +3,8 @@ import os from dataclasses import dataclass, field, replace, asdict, is_dataclass from io import IOBase -from typing import List, Dict, Optional, ItemsView, Container, IO +from typing import Optional, IO +from collections.abc import ItemsView, Container from bottles.backend.models.result import Result from bottles.backend.utils import yaml @@ -135,9 +136,9 @@ class BottleConfig(DictCompatMixIn): Parameters: BottleParams = field(default_factory=BottleParams) Sandbox: BottleSandboxParams = field(default_factory=BottleSandboxParams) Environment_Variables: dict = field(default_factory=dict) - Installed_Dependencies: List[str] = field(default_factory=list) + Installed_Dependencies: list[str] = field(default_factory=list) DLL_Overrides: dict = field(default_factory=dict) - External_Programs: Dict[str, dict] = field(default_factory=dict) + External_Programs: dict[str, dict] = field(default_factory=dict) Uninstallers: dict = field(default_factory=dict) session_arguments: str = "" run_in_terminal: bool = False @@ -272,8 +273,6 @@ def _filter(cls, data: dict, clazz: object = None) -> dict: v = cls._filter(v, field_type) new_data[k] = v else: - logging.warning( - "Skipping unexpected config '%s' in %s" % (k, clazz.__name__) - ) + logging.warning(f"Skipping unexpected config '{k}' in {clazz.__name__}") return new_data diff --git a/bottles/backend/models/vdict.py b/bottles/backend/models/vdict.py index cc795c074b3..9ee4608e16d 100644 --- a/bottles/backend/models/vdict.py +++ b/bottles/backend/models/vdict.py @@ -102,15 +102,15 @@ def __setitem__(self, key, value): raise KeyError("%s doesn't exist" % repr(key)) else: raise TypeError("Expected either a str or tuple for key") - super(VDFDict, self).__setitem__(key, value) + super().__setitem__(key, value) self.__kcount[key[1]] += 1 def __getitem__(self, key): - return super(VDFDict, self).__getitem__(self._normalize_key(key)) + return super().__getitem__(self._normalize_key(key)) def __delitem__(self, key): key = self._normalize_key(key) - result = super(VDFDict, self).__delitem__(key) + result = super().__delitem__(key) start_idx = self.__omap.index(key) del self.__omap[start_idx] @@ -124,8 +124,8 @@ def __delitem__(self, key): if self.__omap[idx][1] == skey: oldkey = self.__omap[idx] newkey = (dup_idx, skey) - super(VDFDict, self).__setitem__(newkey, self[oldkey]) - super(VDFDict, self).__delitem__(oldkey) + super().__setitem__(newkey, self[oldkey]) + super().__delitem__(oldkey) self.__omap[idx] = newkey dup_idx += 1 @@ -142,7 +142,7 @@ def __iter__(self): return iter(self.iterkeys()) def __contains__(self, key): - return super(VDFDict, self).__contains__(self._normalize_key(key)) + return super().__contains__(self._normalize_key(key)) def __eq__(self, other): if isinstance(other, VDFDict): @@ -154,12 +154,12 @@ def __ne__(self, other): return not self.__eq__(other) def clear(self): - super(VDFDict, self).clear() + super().clear() self.__kcount.clear() self.__omap = list() def get(self, key, *_args): - return super(VDFDict, self).get(self._normalize_key(key), *_args) + return super().get(self._normalize_key(key), *_args) def setdefault(self, key, default=None): if key not in self: @@ -217,7 +217,7 @@ def remove_all_for(self, key): raise TypeError("Key need to be a string.") for idx in _range(self.__kcount[key]): - super(VDFDict, self).__delitem__((idx, key)) + super().__delitem__((idx, key)) self.__omap = list(filter(lambda x: x[1] != key, self.__omap)) diff --git a/bottles/backend/state.py b/bottles/backend/state.py index 38a6ddb225c..e29bbe18862 100644 --- a/bottles/backend/state.py +++ b/bottles/backend/state.py @@ -2,7 +2,8 @@ from enum import Enum from gettext import gettext as _ from threading import Lock as PyLock, Event as PyEvent -from typing import Dict, Callable, Optional, Protocol, List +from typing import Protocol +from collections.abc import Callable from uuid import UUID, uuid4 from bottles.backend.logger import Logger @@ -59,12 +60,12 @@ def __call__( self, received_size: int = 0, total_size: int = 0, - status: Optional[Status] = None, + status: Status | None = None, ) -> None: ... class SignalHandler(Protocol): - def __call__(self, data: Optional[Result] = None) -> None: ... + def __call__(self, data: Result | None = None) -> None: ... @dataclasses.dataclass @@ -76,7 +77,7 @@ class Notification: @dataclasses.dataclass(init=False) class Task: - _task_id: Optional[UUID] = None # should only be set by TaskManager + _task_id: UUID | None = None # should only be set by TaskManager title: str = "Task" _subtitle: str = "" hidden: bool = False # hide from UI @@ -95,7 +96,7 @@ def __init__( self.cancellable = cancellable @property - def task_id(self) -> Optional[UUID]: + def task_id(self) -> UUID | None: return self._task_id @task_id.setter @@ -119,7 +120,7 @@ def stream_update( self, received_size: int = 0, total_size: int = 0, - status: Optional[Status] = None, + status: Status | None = None, ): """This is a default subtitle updating handler for streaming downloading progress""" match status: @@ -138,7 +139,7 @@ def stream_update( class LockManager: - _LOCKS: Dict[Locks, PyLock] = {} + _LOCKS: dict[Locks, PyLock] = {} @classmethod def lock(cls, name: Locks): @@ -168,7 +169,7 @@ class EventManager: Wait for an event that has already been set, will immediately return. """ - _EVENTS: Dict[Events, PyEvent] = {} + _EVENTS: dict[Events, PyEvent] = {} @classmethod def wait(cls, event: Events): @@ -194,10 +195,10 @@ def reset(cls, event: Events): class TaskManager: """Long-running tasks are registered here, for tracking and display them on UI""" - _TASKS: Dict[UUID, Task] = {} # {UUID4: Task} + _TASKS: dict[UUID, Task] = {} # {UUID4: Task} @classmethod - def get(cls, task_id: UUID) -> Optional[Task]: + def get(cls, task_id: UUID) -> Task | None: return cls._TASKS.get(task_id) @classmethod @@ -220,7 +221,7 @@ def remove(cls, task: UUID | Task): class SignalManager: """sync backend state to frontend via registered signal handlers""" - _SIGNALS: Dict[Signals, List[SignalHandler]] = {} + _SIGNALS: dict[Signals, list[SignalHandler]] = {} @classmethod def connect(cls, signal: Signals, handler: SignalHandler) -> None: @@ -228,7 +229,7 @@ def connect(cls, signal: Signals, handler: SignalHandler) -> None: cls._SIGNALS[signal].append(handler) @classmethod - def send(cls, signal: Signals, data: Optional[Result] = None) -> None: + def send(cls, signal: Signals, data: Result | None = None) -> None: """ Send signal should only be called by backend logic diff --git a/bottles/backend/utils/connection.py b/bottles/backend/utils/connection.py index 46d905adcbc..acc84a33636 100644 --- a/bottles/backend/utils/connection.py +++ b/bottles/backend/utils/connection.py @@ -18,7 +18,6 @@ import os from datetime import datetime from gettext import gettext as _ -from typing import Optional import pycurl @@ -36,7 +35,7 @@ class ConnectionUtils: notified and False will be returned, otherwise True. """ - _status: Optional[bool] = None + _status: bool | None = None last_check = None def __init__(self, force_offline=False, **kwargs): @@ -47,7 +46,7 @@ def __init__(self, force_offline=False, **kwargs): SignalManager.connect(Signals.ForceStopNetworking, self.stop_check) @property - def status(self) -> Optional[bool]: + def status(self) -> bool | None: return self._status @status.setter @@ -69,7 +68,7 @@ def stop_check(self, res: Result): if res.status: self.do_check_connection = False - def check_connection(self, show_notification=False) -> Optional[bool]: + def check_connection(self, show_notification=False) -> bool | None: """check network status, send result through signal NetworkReady and return""" if self.force_offline or "FORCE_OFFLINE" in os.environ: logging.info("Forcing offline mode") diff --git a/bottles/backend/utils/file.py b/bottles/backend/utils/file.py index f07dbbea00c..31f99d45329 100644 --- a/bottles/backend/utils/file.py +++ b/bottles/backend/utils/file.py @@ -49,7 +49,7 @@ def get_checksum(file): def use_insensitive_ext(string): """Converts a glob pattern into a case-insensitive glob pattern""" ext = string.split(".")[1] - globlist = ["[%s%s]" % (c.lower(), c.upper()) for c in ext] + globlist = [f"[{c.lower()}{c.upper()}]" for c in ext] return "*.%s" % "".join(globlist) @staticmethod @@ -66,10 +66,10 @@ def get_human_size_legacy(size: float) -> str: """Returns a human readable size from a given float size""" for unit in ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"]: if abs(size) < 1024.0: - return "%3.1f%s%s" % (size, unit, "B") + return "{:3.1f}{}{}".format(size, unit, "B") size /= 1024.0 - return "%.1f%s%s" % (size, "Yi", "B") + return "{:.1f}{}{}".format(size, "Yi", "B") def get_path_size(self, path: str, human: bool = True) -> str | float: """ diff --git a/bottles/backend/utils/generic.py b/bottles/backend/utils/generic.py index a6b607cbd05..6f44fd7cf3f 100644 --- a/bottles/backend/utils/generic.py +++ b/bottles/backend/utils/generic.py @@ -20,7 +20,6 @@ import re import string import subprocess -from typing import Optional import chardet @@ -42,7 +41,7 @@ def validate_url(url: str): return re.match(regex, url) is not None -def detect_encoding(text: bytes, locale_hint: str = None) -> Optional[str]: +def detect_encoding(text: bytes, locale_hint: str = None) -> str | None: """ Detect the encoding of a text by its bytes. Return None if it can't be detected. diff --git a/bottles/backend/utils/json.py b/bottles/backend/utils/json.py index a99b2ba32be..1e1e47006c0 100644 --- a/bottles/backend/utils/json.py +++ b/bottles/backend/utils/json.py @@ -2,7 +2,7 @@ import json import json as _json -from typing import Optional, IO, Any, Type +from typing import IO, Any from bottles.backend.models.config import DictCompatMixIn @@ -30,8 +30,8 @@ def dump( obj: Any, fp: IO[str], *, - indent: Optional[str | int] = None, - cls: Optional[Type[_json.JSONEncoder]] = None, + indent: str | int | None = None, + cls: type[_json.JSONEncoder] | None = None, ) -> None: """ Serialize obj as a JSON formatted stream to fp (a .write()-supporting file-like object). @@ -49,8 +49,8 @@ def dump( def dumps( obj: Any, *, - indent: Optional[str | int] = None, - cls: Optional[Type[_json.JSONEncoder]] = None, + indent: str | int | None = None, + cls: type[_json.JSONEncoder] | None = None, ) -> str: """ Serialize obj to a JSON formatted str. diff --git a/bottles/backend/utils/manager.py b/bottles/backend/utils/manager.py index bd5ecc58752..1a7e29d0ed8 100644 --- a/bottles/backend/utils/manager.py +++ b/bottles/backend/utils/manager.py @@ -20,7 +20,6 @@ from datetime import datetime from gettext import gettext as _ from glob import glob -from typing import Optional import icoextract # type: ignore [import-untyped] @@ -43,7 +42,7 @@ class ManagerUtils: @staticmethod def open_filemanager( - config: Optional[BottleConfig] = None, + config: BottleConfig | None = None, path_type: str = "bottle", component: str = "", custom_path: str = "", @@ -152,7 +151,7 @@ def move_file_to_bottle( fn_update(_size) fn_update(1) return file_new_path - except (OSError, IOError): + except OSError: logging.error(f"Could not copy file {file_path} to the bottle.") return False diff --git a/bottles/backend/utils/singleton.py b/bottles/backend/utils/singleton.py index 3776cb92d12..9767a5c7e1a 100644 --- a/bottles/backend/utils/singleton.py +++ b/bottles/backend/utils/singleton.py @@ -3,5 +3,5 @@ class Singleton(type): def __call__(cls, *args, **kwargs): if cls not in cls._instances: - cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) + cls._instances[cls] = super().__call__(*args, **kwargs) return cls._instances[cls] diff --git a/bottles/backend/utils/steam.py b/bottles/backend/utils/steam.py index 8e867acef60..d420d2c2125 100644 --- a/bottles/backend/utils/steam.py +++ b/bottles/backend/utils/steam.py @@ -17,7 +17,7 @@ import os import shlex -from typing import TextIO, Optional +from typing import TextIO from bottles.backend.logger import Logger from bottles.backend.models.vdict import VDFDict @@ -57,7 +57,7 @@ def is_proton(path: str) -> bool: if not os.path.isfile(toolmanifest): return False - f = open(toolmanifest, "r", errors="replace") + f = open(toolmanifest, errors="replace") data = SteamUtils.parse_vdf(f.read()) compat_layer_name = data.get("manifest", {}).get("compatmanager_layer_name", {}) @@ -66,7 +66,7 @@ def is_proton(path: str) -> bool: return "proton" in compat_layer_name or "proton" in commandline @staticmethod - def get_associated_runtime(path: str) -> Optional[str]: + def get_associated_runtime(path: str) -> str | None: """ Get the associated runtime of a Proton directory. """ @@ -76,7 +76,7 @@ def get_associated_runtime(path: str) -> Optional[str]: return None runtime = "scout" - f = open(toolmanifest, "r", errors="replace") + f = open(toolmanifest, errors="replace") data = SteamUtils.parse_vdf(f.read()) tool_appid = data.get("manifest", {}).get("require_tool_appid", {}) diff --git a/bottles/backend/utils/threading.py b/bottles/backend/utils/threading.py index 57beb6d1ec0..911dc0fb3a1 100644 --- a/bottles/backend/utils/threading.py +++ b/bottles/backend/utils/threading.py @@ -45,7 +45,7 @@ def __init__( f"(from main thread: {threading.current_thread() is threading.main_thread()})." ) - super(RunAsync, self).__init__(target=self.__target, args=args, kwargs=kwargs) + super().__init__(target=self.__target, args=args, kwargs=kwargs) self.task_func = task_func diff --git a/bottles/backend/utils/vdf.py b/bottles/backend/utils/vdf.py index 96c932b9ce1..14277c73eba 100644 --- a/bottles/backend/utils/vdf.py +++ b/bottles/backend/utils/vdf.py @@ -294,15 +294,14 @@ def _dump_gen(data, pretty=False, escaped=True, level=0): key = _escape(key) if isinstance(value, Mapping): - yield '%s"%s"\n%s{\n' % (line_indent, key, line_indent) - for chunk in _dump_gen(value, pretty, escaped, level + 1): - yield chunk + yield f'{line_indent}"{key}"\n{line_indent}{{\n' + yield from _dump_gen(value, pretty, escaped, level + 1) yield "%s}\n" % line_indent else: if escaped and isinstance(value, string_type): value = _escape(value) - yield '%s"%s" "%s"\n' % (line_indent, key, value) + yield f'{line_indent}"{key}" "{value}"\n' # binary VDF @@ -520,8 +519,7 @@ def _binary_dump_gen(obj, level=0, alt_format=False): if isinstance(value, Mapping): yield BIN_NONE + key + BIN_NONE - for chunk in _binary_dump_gen(value, level + 1, alt_format=alt_format): - yield chunk + yield from _binary_dump_gen(value, level + 1, alt_format=alt_format) elif isinstance(value, UINT_64): yield BIN_UINT64 + key + BIN_NONE + uint64.pack(value) elif isinstance(value, INT_64): diff --git a/bottles/backend/wine/cmd.py b/bottles/backend/wine/cmd.py index 2b1e9852d85..268e152a454 100644 --- a/bottles/backend/wine/cmd.py +++ b/bottles/backend/wine/cmd.py @@ -1,5 +1,3 @@ -from typing import Optional - from bottles.backend.logger import Logger from bottles.backend.wine.wineprogram import WineProgram @@ -15,8 +13,8 @@ def run_batch( batch: str, terminal: bool = True, args: str = "", - environment: Optional[dict] = None, - cwd: Optional[str] = None, + environment: dict | None = None, + cwd: str | None = None, ): args = f"/c {batch} {args}" diff --git a/bottles/backend/wine/executor.py b/bottles/backend/wine/executor.py index 7c4646e551c..368fee77aad 100644 --- a/bottles/backend/wine/executor.py +++ b/bottles/backend/wine/executor.py @@ -1,7 +1,6 @@ import os import shlex import uuid -from typing import Optional from bottles.backend.dlls.dxvk import DXVKComponent from bottles.backend.dlls.nvapi import NVAPIComponent @@ -28,19 +27,19 @@ def __init__( exec_path: str, args: str = "", terminal: bool = False, - environment: Optional[dict] = None, + environment: dict | None = None, move_file: bool = False, move_upd_fn: callable = None, - pre_script: Optional[str] = None, - post_script: Optional[str] = None, - cwd: Optional[str] = None, - monitoring: Optional[list] = None, - program_dxvk: Optional[bool] = None, - program_vkd3d: Optional[bool] = None, - program_nvapi: Optional[bool] = None, - program_fsr: Optional[bool] = None, - program_gamescope: Optional[bool] = None, - program_virt_desktop: Optional[bool] = None, + pre_script: str | None = None, + post_script: str | None = None, + cwd: str | None = None, + monitoring: list | None = None, + program_dxvk: bool | None = None, + program_vkd3d: bool | None = None, + program_nvapi: bool | None = None, + program_fsr: bool | None = None, + program_gamescope: bool | None = None, + program_virt_desktop: bool | None = None, ): logging.info("Launching an executable…") self.config = config @@ -345,7 +344,7 @@ def __set_monitors(self): if not self.monitoring: return - logging.info("Starting {} monitors".format(len(self.monitoring))) + logging.info(f"Starting {len(self.monitoring)} monitors") winedbg = WineDbg(self.config, silent=True) for m in self.monitoring: diff --git a/bottles/backend/wine/explorer.py b/bottles/backend/wine/explorer.py index 34212b92730..d6588cda075 100644 --- a/bottles/backend/wine/explorer.py +++ b/bottles/backend/wine/explorer.py @@ -1,5 +1,3 @@ -from typing import Optional - from bottles.backend.logger import Logger from bottles.backend.wine.wineprogram import WineProgram @@ -15,10 +13,10 @@ def launch_desktop( desktop: str = "shell", width: int = 0, height: int = 0, - program: Optional[str] = None, - args: Optional[str] = None, - environment: Optional[dict] = None, - cwd: Optional[str] = None, + program: str | None = None, + args: str | None = None, + environment: dict | None = None, + cwd: str | None = None, ): _args = f"/desktop={desktop}" diff --git a/bottles/backend/wine/msiexec.py b/bottles/backend/wine/msiexec.py index 3edfa8d0c1e..340d4f5aabb 100644 --- a/bottles/backend/wine/msiexec.py +++ b/bottles/backend/wine/msiexec.py @@ -1,5 +1,3 @@ -from typing import Optional - from bottles.backend.logger import Logger from bottles.backend.wine.wineprogram import WineProgram @@ -15,8 +13,8 @@ def install( pkg_path: str, # or product code args: str = "", terminal: bool = False, - cwd: Optional[str] = None, - environment: Optional[dict] = None, + cwd: str | None = None, + environment: dict | None = None, ): args = f"/i {pkg_path} {args}" @@ -43,7 +41,7 @@ def repair( all_computer_registry_keys: bool = False, all_shortcuts: bool = False, recache: bool = False, - cwd: Optional[str] = None, + cwd: str | None = None, ): """ NOTICE: I have not been able to use the repair in any way, it seems to show @@ -78,13 +76,13 @@ def repair( args=args, communicate=True, minimal=True, cwd=cwd, action_name="repair" ) - def uninstall(self, pkg_path: str, cwd: Optional[str] = None): + def uninstall(self, pkg_path: str, cwd: str | None = None): args = f"/x {pkg_path}" self.launch( args=args, communicate=True, minimal=True, cwd=cwd, action_name="uninstall" ) - def apply_patch(self, patch: str, update: bool = False, cwd: Optional[str] = None): + def apply_patch(self, patch: str, update: bool = False, cwd: str | None = None): args = f"/p {patch}" if update: args = f" /update {patch}" @@ -94,7 +92,7 @@ def apply_patch(self, patch: str, update: bool = False, cwd: Optional[str] = Non ) def uninstall_patch( - self, patch: str, product: Optional[str] = None, cwd: Optional[str] = None + self, patch: str, product: str | None = None, cwd: str | None = None ): args = f"/uninstall {patch}" if product: @@ -108,7 +106,7 @@ def uninstall_patch( action_name="uninstall_patch", ) - def register_module(self, module: str, cwd: Optional[str] = None): + def register_module(self, module: str, cwd: str | None = None): args = f"/y {module}" self.launch( args=args, @@ -118,7 +116,7 @@ def register_module(self, module: str, cwd: Optional[str] = None): action_name="register_module", ) - def unregister_module(self, module: str, cwd: Optional[str] = None): + def unregister_module(self, module: str, cwd: str | None = None): args = f"/z {module}" self.launch( args=args, diff --git a/bottles/backend/wine/net.py b/bottles/backend/wine/net.py index 62657879765..41da0793b2f 100644 --- a/bottles/backend/wine/net.py +++ b/bottles/backend/wine/net.py @@ -1,5 +1,3 @@ -from typing import Optional - from bottles.backend.logger import Logger from bottles.backend.wine.wineprogram import WineProgram @@ -10,7 +8,7 @@ class Net(WineProgram): program = "Wine Services manager" command = "net" - def start(self, name: Optional[str] = None): + def start(self, name: str | None = None): args = "start" if name is not None: @@ -18,7 +16,7 @@ def start(self, name: Optional[str] = None): return self.launch(args=args, communicate=True, action_name="start") - def stop(self, name: Optional[str] = None): + def stop(self, name: str | None = None): args = "stop" if name is not None: @@ -26,7 +24,7 @@ def stop(self, name: Optional[str] = None): return self.launch(args=args, communicate=True, action_name="stop") - def use(self, name: Optional[str] = None): + def use(self, name: str | None = None): # this command has no documentation, not tested yet args = "use" diff --git a/bottles/backend/wine/notepad.py b/bottles/backend/wine/notepad.py index e70b6406442..c2e361d7148 100644 --- a/bottles/backend/wine/notepad.py +++ b/bottles/backend/wine/notepad.py @@ -1,5 +1,3 @@ -from typing import Optional - from bottles.backend.logger import Logger from bottles.backend.wine.wineprogram import WineProgram @@ -18,7 +16,7 @@ def open(self, path: str, as_ansi: bool = False, as_utf16: bool = False): args = f"/w {path}" return self.launch(args=args, communicate=True, action_name="open") - def print(self, path: str, printer_name: Optional[str] = None): + def print(self, path: str, printer_name: str | None = None): args = f"/p {path}" if printer_name: args = f"/pt {path} {printer_name}" diff --git a/bottles/backend/wine/reg.py b/bottles/backend/wine/reg.py index f7737edb679..83c78a1e162 100644 --- a/bottles/backend/wine/reg.py +++ b/bottles/backend/wine/reg.py @@ -4,7 +4,6 @@ import uuid from datetime import datetime from itertools import groupby -from typing import List, Dict, Optional from bottles.backend.globals import Paths from bottles.backend.logger import Logger @@ -28,13 +27,13 @@ class Reg(WineProgram): program = "Wine Registry CLI" command = "reg" - def bulk_add(self, regs: List[RegItem]): + def bulk_add(self, regs: list[RegItem]): """Import multiple registries at once, with v5.00 reg file""" config = self.config logging.info(f"Importing {len(regs)} Key(s) to {config.Name} registry") winedbg = WineDbg(config) - mapping: Dict[str, List[RegItem]] = { + mapping: dict[str, list[RegItem]] = { k: list(v) for k, v in groupby(regs, lambda x: x.key) } reg_file_header = "Windows Registry Editor Version 5.00\n\n" @@ -74,17 +73,19 @@ def bulk_add(self, regs: List[RegItem]): ) logging.info(res.data) - def add(self, key: str, value: str, data: str, value_type: Optional[str] = None): + def add(self, key: str, value: str, data: str, value_type: str | None = None): config = self.config logging.info( f"Adding Key: [{key}] with Value: [{value}] and " f"Data: [{data}] in {config.Name} registry" ) winedbg = WineDbg(config) - args = "add '%s' /v '%s' /d '%s' /f" % (key, value, data) + args = f"add '{key}' /v '{value}' /d '{data}' /f" if value_type is not None: - args = "add '%s' /v '%s' /t %s /d '%s' /f" % (key, value, value_type, data) + args = "add '{}' /v '{}' /t {} /d '{}' /f".format( + key, value, value_type, data + ) # avoid conflicts when executing async winedbg.wait_for_process("reg.exe") @@ -99,7 +100,7 @@ def remove(self, key: str, value: str): f"Removing Value: [{key}] from Key: [{value}] in " f"{config.Name} registry" ) winedbg = WineDbg(config) - args = "delete '%s' /v %s /f" % (key, value) + args = f"delete '{key}' /v {value} /f" # avoid conflicts when executing async winedbg.wait_for_process("reg.exe") diff --git a/bottles/backend/wine/register.py b/bottles/backend/wine/register.py index 5d78237702b..4e850e4eb87 100644 --- a/bottles/backend/wine/register.py +++ b/bottles/backend/wine/register.py @@ -17,7 +17,6 @@ import os import uuid -from typing import Optional from bottles.backend.utils import json @@ -40,7 +39,7 @@ def new(self, path: str): def __get_header(self): """Return the header of the registry file.""" - with open(self.path, "r") as reg: + with open(self.path) as reg: header = reg.readlines(2) return header @@ -101,7 +100,7 @@ def __parse_dict(path: str): return _dict - def compare(self, path: Optional[str] = None, register: object = None): + def compare(self, path: str | None = None, register: object = None): """Compare the current register with the given path or register.""" if path is not None: register = WinRegister().new(path) @@ -133,7 +132,7 @@ def __get_diff(self, register: "WinRegister"): return diff - def update(self, diff: Optional[dict] = None): + def update(self, diff: dict | None = None): """Update the current register with the given diff.""" if diff is None: diff = self.diff # use last diff diff --git a/bottles/backend/wine/start.py b/bottles/backend/wine/start.py index b88738ec11c..7cde9e5ce6b 100644 --- a/bottles/backend/wine/start.py +++ b/bottles/backend/wine/start.py @@ -1,5 +1,3 @@ -from typing import Optional - from bottles.backend.logger import Logger from bottles.backend.wine.wineprogram import WineProgram from bottles.backend.wine.winepath import WinePath @@ -16,10 +14,10 @@ def run( file: str, terminal: bool = True, args: str = "", - environment: Optional[dict] = None, - pre_script: Optional[str] = None, - post_script: Optional[str] = None, - cwd: Optional[str] = None, + environment: dict | None = None, + pre_script: str | None = None, + post_script: str | None = None, + cwd: str | None = None, ): winepath = WinePath(self.config) diff --git a/bottles/backend/wine/uninstaller.py b/bottles/backend/wine/uninstaller.py index 5821251afb7..511db791bdc 100644 --- a/bottles/backend/wine/uninstaller.py +++ b/bottles/backend/wine/uninstaller.py @@ -1,5 +1,3 @@ -from typing import Optional - from bottles.backend.logger import Logger from bottles.backend.wine.wineprogram import WineProgram @@ -10,7 +8,7 @@ class Uninstaller(WineProgram): program = "Wine Uninstaller" command = "uninstaller" - def get_uuid(self, name: Optional[str] = None): + def get_uuid(self, name: str | None = None): args = " --list" if name is not None: @@ -18,7 +16,7 @@ def get_uuid(self, name: Optional[str] = None): return self.launch(args=args, communicate=True, action_name="get_uuid") - def from_uuid(self, uuid: Optional[str] = None): + def from_uuid(self, uuid: str | None = None): args = "" if uuid not in [None, ""]: diff --git a/bottles/backend/wine/wineboot.py b/bottles/backend/wine/wineboot.py index 715e5393a69..b489f321f79 100644 --- a/bottles/backend/wine/wineboot.py +++ b/bottles/backend/wine/wineboot.py @@ -67,7 +67,7 @@ def nv_stop_all_processes(self): for pid in os.listdir("/proc"): if pid.isdigit(): try: - with open(f"/proc/{pid}/environ", "r") as env_file: + with open(f"/proc/{pid}/environ") as env_file: env_vars = env_file.read() if f"BOTTLE={self.config.Path}" in env_vars: os.kill(int(pid), signal.SIGTERM) diff --git a/bottles/backend/wine/winecommand.py b/bottles/backend/wine/winecommand.py index 643335b9df6..5b62a7b64d9 100644 --- a/bottles/backend/wine/winecommand.py +++ b/bottles/backend/wine/winecommand.py @@ -4,7 +4,6 @@ import subprocess import tempfile import shlex -from typing import Optional from bottles.backend.globals import ( Paths, @@ -97,9 +96,9 @@ def __init__( communicate: bool = False, colors: str = "default", minimal: bool = False, # avoid gamemode/gamescope usage - pre_script: Optional[str] = None, - post_script: Optional[str] = None, - cwd: Optional[str] = None, + pre_script: str | None = None, + post_script: str | None = None, + cwd: str | None = None, ): _environment = environment.copy() self.config = self._get_config(config) @@ -156,7 +155,7 @@ def _get_cwd(self, cwd) -> str: def get_env( self, - environment: Optional[dict] = None, + environment: dict | None = None, return_steam_env: bool = False, return_clean_env: bool = False, ) -> dict: @@ -487,11 +486,11 @@ def _get_runner_info(self) -> tuple[str, str]: def get_cmd( self, command, - pre_script: Optional[str] = None, - post_script: Optional[str] = None, + pre_script: str | None = None, + post_script: str | None = None, return_steam_cmd: bool = False, return_clean_cmd: bool = False, - environment: Optional[dict] = None, + environment: dict | None = None, ) -> str: config = self.config params = config.Parameters @@ -536,7 +535,7 @@ def get_cmd( ) logging.info(f"Running Gamescope command: '{command}'") logging.info(f"{gamescope_run} contains:") - with open(gamescope_run, "r") as f: + with open(gamescope_run) as f: logging.info(f"\n\n{f.read()}") # Set file as executable @@ -686,7 +685,7 @@ def _get_sandbox_manager(self) -> SandboxManager: share_sound=self.config.Sandbox.share_sound, ) - def run(self) -> Result[Optional[str]]: + def run(self) -> Result[str | None]: """ Run command with pre-configured parameters diff --git a/bottles/backend/wine/winedbg.py b/bottles/backend/wine/winedbg.py index 4d4096ca84d..738d1137c33 100644 --- a/bottles/backend/wine/winedbg.py +++ b/bottles/backend/wine/winedbg.py @@ -1,7 +1,6 @@ import re import time import subprocess -from typing import Optional from bottles.backend.logger import Logger from bottles.backend.wine.wineprogram import WineProgram @@ -80,7 +79,7 @@ def wait_for_process(self, name: str, timeout: float = 0.5): time.sleep(timeout) return True - def kill_process(self, pid: Optional[str] = None, name: Optional[str] = None): + def kill_process(self, pid: str | None = None, name: str | None = None): """ Kill a process by its PID or name. """ @@ -109,7 +108,7 @@ def kill_process(self, pid: Optional[str] = None, name: Optional[str] = None): if p["name"] == name: self.kill_process(p["pid"], name) - def is_process_alive(self, pid: Optional[str] = None, name: Optional[str] = None): + def is_process_alive(self, pid: str | None = None, name: str | None = None): """ Check if a process is running on the wineprefix. """ diff --git a/bottles/backend/wine/wineprogram.py b/bottles/backend/wine/wineprogram.py index df111ab3130..5bcef08b58f 100644 --- a/bottles/backend/wine/wineprogram.py +++ b/bottles/backend/wine/wineprogram.py @@ -1,5 +1,4 @@ import os -from typing import Optional from bottles.backend.logger import Logger from bottles.backend.globals import Paths @@ -25,7 +24,7 @@ def __init__(self, config: BottleConfig, silent=False): self.config = config self.silent = silent - def get_command(self, args: Optional[str] = None): + def get_command(self, args: str | None = None): command = self.command if self.is_internal: @@ -42,10 +41,10 @@ def launch( terminal: bool = False, minimal: bool = True, communicate: bool = False, - environment: Optional[dict] = None, - pre_script: Optional[str] = None, - post_script: Optional[str] = None, - cwd: Optional[str] = None, + environment: dict | None = None, + pre_script: str | None = None, + post_script: str | None = None, + cwd: str | None = None, action_name: str = "launch", ): if environment is None: @@ -80,8 +79,8 @@ def launch( res = res.run() return res - def launch_terminal(self, args: Optional[str] = None): + def launch_terminal(self, args: str | None = None): self.launch(args=args, terminal=True, action_name="launch_terminal") - def launch_minimal(self, args: Optional[str] = None): + def launch_minimal(self, args: str | None = None): self.launch(args=args, minimal=True, action_name="launch_minimal") diff --git a/bottles/backend/wine/xcopy.py b/bottles/backend/wine/xcopy.py index 104852ae743..ba8e8c97daa 100644 --- a/bottles/backend/wine/xcopy.py +++ b/bottles/backend/wine/xcopy.py @@ -1,4 +1,3 @@ -from typing import Optional from datetime import datetime from bottles.backend.logger import Logger @@ -29,7 +28,7 @@ def copy( include_hidden_and_sys_files: bool = False, continue_if_error: bool = False, copy_attributes: bool = False, - after_date: Optional[datetime] = None, + after_date: datetime | None = None, ): args = f"{source} {dest} /i" diff --git a/bottles/frontend/bottle_details_page.py b/bottles/frontend/bottle_details_page.py index fb01c96b773..57e44ee046a 100644 --- a/bottles/frontend/bottle_details_page.py +++ b/bottles/frontend/bottle_details_page.py @@ -18,7 +18,6 @@ import uuid from datetime import datetime from gettext import gettext as _ -from typing import List, Optional from gi.repository import Gtk, Gio, Adw, Gdk, GLib, Xdp @@ -164,7 +163,7 @@ def __change_page(self, _widget, page_name): def on_drop(self, drop_target, value: Gdk.FileList, x, y, user_data=None): self.drop_overlay.set_visible(False) - files: List[Gio.File] = value.get_files() + files: list[Gio.File] = value.get_files() args = "" file = files[0] if ( @@ -286,7 +285,7 @@ def set_path(_dialog, response): dialog.show() def update_programs( - self, config: Optional[BottleConfig] = None, force_add: dict = None + self, config: BottleConfig | None = None, force_add: dict = None ): """ This function update the programs lists. diff --git a/bottles/frontend/bottle_details_view.py b/bottles/frontend/bottle_details_view.py index f1292ca3025..afe50ab1f82 100644 --- a/bottles/frontend/bottle_details_view.py +++ b/bottles/frontend/bottle_details_view.py @@ -17,7 +17,6 @@ from gettext import gettext as _ -from typing import Optional from gi.repository import Gtk, Adw, GLib @@ -62,7 +61,7 @@ class BottleDetailsView(Adw.Bin): # endregion - def __init__(self, window, config: Optional[BottleConfig] = None, **kwargs): + def __init__(self, window, config: BottleConfig | None = None, **kwargs): super().__init__(**kwargs) # common variables and references diff --git a/bottles/frontend/component_entry_row.py b/bottles/frontend/component_entry_row.py index 4175fa8c36c..f8ae055f2e8 100644 --- a/bottles/frontend/component_entry_row.py +++ b/bottles/frontend/component_entry_row.py @@ -16,7 +16,6 @@ # from gettext import gettext as _ -from typing import Optional from gi.repository import Gtk, GObject, Adw @@ -138,7 +137,7 @@ def update_progress( self, received_size: int = 0, total_size: int = 0, - status: Optional[Status] = None, + status: Status | None = None, ): if status == Status.FAILED: logging.error("Component installation failed") diff --git a/bottles/frontend/details_dependencies_view.py b/bottles/frontend/details_dependencies_view.py index 217ef84954f..41cc42ce427 100644 --- a/bottles/frontend/details_dependencies_view.py +++ b/bottles/frontend/details_dependencies_view.py @@ -16,7 +16,6 @@ # import time -from typing import Optional from gi.repository import Gtk, GLib, Adw @@ -91,7 +90,7 @@ def empty_list(self): r.get_parent().remove(r) self.__registry = [] - def update(self, _widget=False, config: Optional[BottleConfig] = None): + def update(self, _widget=False, config: BottleConfig | None = None): """ This function update the dependencies list with the supported by the manager. diff --git a/bottles/frontend/details_preferences_page.py b/bottles/frontend/details_preferences_page.py index ee89982dc75..b08dc861aeb 100644 --- a/bottles/frontend/details_preferences_page.py +++ b/bottles/frontend/details_preferences_page.py @@ -456,7 +456,7 @@ def set_config(self, config: BottleConfig): self.entry_name.set_text(config.Name) self.row_cwd.set_subtitle( - _('Directory that contains the data of "{}".'.format(config.Name)) + _(f'Directory that contains the data of "{config.Name}".') ) self.combo_language.set_selected( diff --git a/bottles/frontend/details_task_manager_view.py b/bottles/frontend/details_task_manager_view.py index af55ee80365..d51bca52171 100644 --- a/bottles/frontend/details_task_manager_view.py +++ b/bottles/frontend/details_task_manager_view.py @@ -15,7 +15,6 @@ # along with this program. If not, see . # -from typing import Optional from gi.repository import Gtk @@ -83,14 +82,14 @@ def show_kill_btn(self, widget): return self.btn_kill.set_sensitive(True) - def update(self, widget=False, config: Optional[BottleConfig] = None): + def update(self, widget=False, config: BottleConfig | None = None): """ This function scan for new processed and update the liststore_processes with the new data """ self.liststore_processes.clear() - def fetch_processes(config: Optional[BottleConfig] = None): + def fetch_processes(config: BottleConfig | None = None): if config is None: config = BottleConfig() self.config = config diff --git a/bottles/frontend/gtk.py b/bottles/frontend/gtk.py index dfad76e6552..e01166178de 100644 --- a/bottles/frontend/gtk.py +++ b/bottles/frontend/gtk.py @@ -15,7 +15,6 @@ # along with this program. If not, see . # -from typing import Optional from functools import wraps from inspect import signature @@ -75,7 +74,7 @@ def wrapper(*args, **kwargs): return wrapper @staticmethod - def get_parent_window() -> Optional[GObject.Object]: + def get_parent_window() -> GObject.Object | None: """Retrieve the parent window from a widget.""" toplevels = Gtk.Window.get_toplevels() return toplevels.get_item(0) diff --git a/bottles/frontend/new_bottle_dialog.py b/bottles/frontend/new_bottle_dialog.py index 65718cb799f..7cb47c9dbfb 100644 --- a/bottles/frontend/new_bottle_dialog.py +++ b/bottles/frontend/new_bottle_dialog.py @@ -16,7 +16,7 @@ # from gettext import gettext as _ -from typing import Any, Optional +from typing import Any from gi.repository import Gtk, Adw, Pango, Gio, Xdp, GObject, GLib from bottles.backend.models.config import BottleConfig @@ -231,7 +231,7 @@ def update_output(self, text: str) -> None: self.label_output.set_text(text) @GtkUtils.run_in_main_loop - def finish(self, result: Optional[Result], error=None) -> None: + def finish(self, result: Result | None, error=None) -> None: """Updates widgets based on whether it succeeded or failed.""" def send_notification(notification: Gio.Notification) -> None: diff --git a/bottles/frontend/operation.py b/bottles/frontend/operation.py index 8ed298cc9e3..086519a5e8b 100644 --- a/bottles/frontend/operation.py +++ b/bottles/frontend/operation.py @@ -14,7 +14,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -from typing import Dict from uuid import UUID from gi.repository import Gtk, Adw @@ -52,7 +51,7 @@ def update(self, subtitle: str): class TaskSyncer: """Keep task list updated with backend TaskManager""" - _TASK_WIDGETS: Dict[UUID, TaskRow] = {} + _TASK_WIDGETS: dict[UUID, TaskRow] = {} def __init__(self, window): self.window = window diff --git a/bottles/frontend/window.py b/bottles/frontend/window.py index 8e05ab88f91..c627e8de9b2 100644 --- a/bottles/frontend/window.py +++ b/bottles/frontend/window.py @@ -19,7 +19,6 @@ import os import webbrowser from gettext import gettext as _ -from typing import Optional from gi.repository import Gtk, GLib, Gio, Adw, GObject, Gdk, Xdp @@ -315,7 +314,7 @@ def send_notification(self, title, text, image="", ignore_user=False): def go_back(self, *_args): self.main_leaf.navigate(direction=Adw.NavigationDirection.BACK) - def show_details_view(self, widget=False, config: Optional[BottleConfig] = None): + def show_details_view(self, widget=False, config: BottleConfig | None = None): self.main_leaf.set_visible_child(self.page_details) self.page_details.set_config(config or BottleConfig()) @@ -352,7 +351,7 @@ def check_crash_log(self): log_path = f"{xdg_data_home}/bottles/crash.log" with contextlib.suppress(FileNotFoundError): - with open(log_path, "r") as log_file: + with open(log_path) as log_file: crash_log = log_file.readlines() os.remove(log_path) diff --git a/bottles/tests/backend/utils/test_generic.py b/bottles/tests/backend/utils/test_generic.py index 1fe1e0765d9..aebd901816b 100644 --- a/bottles/tests/backend/utils/test_generic.py +++ b/bottles/tests/backend/utils/test_generic.py @@ -1,5 +1,3 @@ -from typing import Optional - import pytest from bottles.backend.utils.generic import detect_encoding @@ -21,7 +19,7 @@ ("", None, "utf-8"), ], ) -def test_detect_encoding(text: str, hint: Optional[str], codec: Optional[str]): +def test_detect_encoding(text: str, hint: str | None, codec: str | None): text_bytes = text.encode(codec) guess = detect_encoding(text_bytes, hint) assert guess.lower() == codec.lower() From 7506b0acef7e90e3f6b14ac45bd89a7d71534792 Mon Sep 17 00:00:00 2001 From: Hari Rana Date: Thu, 16 Jan 2025 19:26:05 -0500 Subject: [PATCH 3/3] chore: Move autoflake above MyPy It makes more sense formatting/cosmetics before type checking. --- .pre-commit-config.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 44316bfa1ef..a75accd478b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,14 +26,14 @@ repos: args: [ "--fix" ] - id: ruff-format +- repo: https://github.com/PyCQA/autoflake + rev: v2.3.1 + hooks: + - id: autoflake + - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.13.0 hooks: - id: mypy args: ["--pretty"] additional_dependencies: ["pygobject-stubs", "types-PyYAML", "types-Markdown", "types-requests", "types-pycurl", "types-chardet", "pytest-stub", "types-orjson", "pathvalidate", "requirements-parser", "icoextract", "fvs", "patool", "git+https://gitlab.com/TheEvilSkeleton/vkbasalt-cli.git@main"] - -- repo: https://github.com/PyCQA/autoflake - rev: v2.3.1 - hooks: - - id: autoflake