From 5b48f31f34d1d9ac6bdc14571ac6fdeac2066132 Mon Sep 17 00:00:00 2001 From: "Gili \"OpenBagTwo\" Barlev" Date: Fri, 28 Mar 2025 15:07:59 -0400 Subject: [PATCH 1/9] Dust off all the cruft --- .github/workflows/docs_branch_update.yml | 2 +- .github/workflows/publish.yml | 2 +- .github/workflows/pull_request.yml | 12 ++++++------ .pre-commit-config.yaml | 12 ++++++------ 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/docs_branch_update.yml b/.github/workflows/docs_branch_update.yml index 8d341788..f3750b68 100644 --- a/.github/workflows/docs_branch_update.yml +++ b/.github/workflows/docs_branch_update.yml @@ -14,7 +14,7 @@ jobs: docs_rebuild: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: set up development environment diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 66d64dd4..1c121668 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - run: python3 -m pip install --upgrade build && python3 -m build diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index a484aff4..eaf0be8a 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -9,11 +9,11 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Set up Python 3.12 + - uses: actions/checkout@v4 + - name: Set up Python 3.13 uses: actions/setup-python@v3 with: - python-version: '3.12' + python-version: '3.13' - name: Install linters run: | python -m pip install --upgrade pip @@ -26,12 +26,12 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - py: ['3.10', '3.11', '3.12'] + py: ['3.10', '3.11', '3.12', '3.13'] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.py }} - name: Configure SSH diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 34232dfb..b3554003 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,25 +1,25 @@ repos: - repo: https://github.com/psf/black - rev: 24.4.2 + rev: 25.1.0 hooks: - id: black - repo: https://github.com/pycqa/isort - rev: 5.13.2 + rev: 6.0.1 hooks: - id: isort - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.10.0 + rev: v1.15.0 hooks: - id: mypy additional_dependencies: [types-paramiko] - repo: https://github.com/nbQA-dev/nbQA - rev: 1.8.5 + rev: 1.9.1 hooks: - id: nbqa-isort - id: nbqa-mypy args: [--ignore-missing-imports] - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks - rev: v2.13.0 + rev: v2.14.0 hooks: - id: pretty-format-ini args: [--autofix] @@ -27,7 +27,7 @@ repos: - id: pretty-format-yaml args: [--autofix] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v5.0.0 hooks: - id: check-json - id: pretty-format-json From 2097b26dfdbf405a3b235a5ccb717ead6668b1f6 Mon Sep 17 00:00:00 2001 From: "Gili \"OpenBagTwo\" Barlev" Date: Fri, 28 Mar 2025 15:11:15 -0400 Subject: [PATCH 2/9] Run pre-commit on all files --- enderchest/instance.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/enderchest/instance.py b/enderchest/instance.py index 1549554c..0b3b48e9 100644 --- a/enderchest/instance.py +++ b/enderchest/instance.py @@ -62,7 +62,9 @@ def from_cfg(cls, section: SectionProxy) -> "InstanceSpec": tuple( parse_version(version.strip()) for version in cfg.parse_ini_list( - section.get("minecraft-version", section.get("minecraft_version")) + section.get( + "minecraft-version", section.get("minecraft_version", "") + ) ) ), normalize_modloader(section.get("modloader", None))[0], From f3e8d6352775ab9af31a7e5e1d072621490f2ec0 Mon Sep 17 00:00:00 2001 From: "Gili \"OpenBagTwo\" Barlev" Date: Fri, 28 Mar 2025 15:30:59 -0400 Subject: [PATCH 3/9] Have one testing instance root be dotless --- enderchest/test/conftest.py | 4 +-- enderchest/test/test_craft.py | 4 +-- enderchest/test/test_gather.py | 28 ++++++++++++-------- enderchest/test/test_place.py | 14 +++++----- enderchest/test/test_sync.py | 2 +- enderchest/test/testing_files/enderchest.cfg | 2 +- enderchest/test/utils.py | 18 ++++++------- 7 files changed, 40 insertions(+), 32 deletions(-) diff --git a/enderchest/test/conftest.py b/enderchest/test/conftest.py index 7ef7540d..c6c75ec5 100644 --- a/enderchest/test/conftest.py +++ b/enderchest/test/conftest.py @@ -35,7 +35,7 @@ def file_system(tmp_path) -> Generator[tuple[Path, Path], None, None]: minecraft_root = tmp_path / "minecraft" minecraft_root.mkdir(parents=True) - populate_instances_folder(minecraft_root / "instances") + populate_instances_folder(minecraft_root) home = tmp_path / "home" home.mkdir(parents=True) @@ -103,7 +103,7 @@ def file_system(tmp_path) -> Generator[tuple[Path, Path], None, None]: minecraft_root / "instances" / "chest-boat" - / ".minecraft" + / "minecraft" / "mods" / "BME.jar" ): (mod_builds_folder / "BME_1.19_alpha.jar"), diff --git a/enderchest/test/test_craft.py b/enderchest/test/test_craft.py index 22ebceaf..d91ed573 100644 --- a/enderchest/test/test_craft.py +++ b/enderchest/test/test_craft.py @@ -795,7 +795,7 @@ def get_instances(): 1. official (~/.minecraft) 2. axolotl (instances/axolotl/.minecraft) 3. bee (instances/bee/.minecraft) - 4. Chest Boat (instances/chest-boat/.minecraft)""" + 4. Chest Boat (instances/chest-boat/minecraft)""" ) return utils.TESTING_INSTANCES @@ -821,7 +821,7 @@ def get_instances(): 1. official (~/.minecraft) 2. axolotl (instances/axolotl/.minecraft) 3. bee (instances/bee/.minecraft) - 4. Chest Boat (instances/chest-boat/.minecraft)""" in "\n".join( + 4. Chest Boat (instances/chest-boat/minecraft)""" in "\n".join( record.msg for record in caplog.records ) diff --git a/enderchest/test/test_gather.py b/enderchest/test/test_gather.py index 58c6d072..1e3b5d09 100644 --- a/enderchest/test/test_gather.py +++ b/enderchest/test/test_gather.py @@ -39,17 +39,20 @@ def test_instance_search_finds_official_instance(self, minecraft_root, home): ) @pytest.mark.parametrize( - "instance, idx", + "instance, idx, dot_root", ( - ("axolotl", 1), - ("bee", 2), - ("chest-boat", 3), + ("axolotl", 1, True), + ("bee", 2, True), + ("chest-boat", 3, False), ), ) - def test_mmc_instance_parsing(self, minecraft_root, instance, idx): + def test_mmc_instance_parsing(self, minecraft_root, instance, idx, dot_root): assert utils.normalize_instance( gather.gather_metadata_for_mmc_instance( - minecraft_root / "instances" / instance / ".minecraft" + minecraft_root + / "instances" + / instance + / (".minecraft" if dot_root else "minecraft") ) ) == utils.normalize_instance( # we're not testing aliasing right now @@ -403,14 +406,17 @@ def setup_teardown(self, minecraft_root): ) utils.populate_mmc_instance_folder( - minecraft_root / "instances" / "talespin", "1.20", "Quilt", "talespin" + minecraft_root / "instances" / "talespin" / "minecraft", + "1.20", + "Quilt", + "talespin", ) ( minecraft_root / "instances" / "talespin" - / ".minecraft" + / "minecraft" / "allowed_symlinks.txt" ).write_text( "my_development_folder\n" @@ -423,7 +429,7 @@ def setup_teardown(self, minecraft_root): minecraft_root / "instances" / "talespin" - / ".minecraft" + / "minecraft" / "allowed_symlinks.txt" ) .read_text() @@ -451,7 +457,7 @@ def test_ender_chest_does_not_write_allowlist_without_consent( minecraft_root / "instances" / "talespin" - / ".minecraft" + / "minecraft" / "allowed_symlinks.txt" ).read_text() == "my_development_folder\n" @@ -493,7 +499,7 @@ def test_ender_chest_will_write_allowlists_with_consent( minecraft_root / "instances" / "talespin" - / ".minecraft" + / "minecraft" / "allowed_symlinks.txt" ).read_text() == f"my_development_folder\n{ender_chest_path}\n" diff --git a/enderchest/test/test_place.py b/enderchest/test/test_place.py index 7fa164a0..e4499df2 100644 --- a/enderchest/test/test_place.py +++ b/enderchest/test/test_place.py @@ -38,9 +38,9 @@ def test_max_depth_of_two_returns_subdirectories(self, minecraft_root) -> None: for instance in utils.TESTING_INSTANCES[1:]: expected.extend( ( - instances_folder / instance.root.parent.name / ".minecraft", - instances_folder / instance.root.parent.name / "instance.cfg", - instances_folder / instance.root.parent.name / "mmc-pack.json", + minecraft_root / instance.root, + minecraft_root / instance.root.parent / "instance.cfg", + minecraft_root / instance.root.parent / "mmc-pack.json", ) ) expected.sort() @@ -928,6 +928,8 @@ def test_multi_shulker_place_correctly_identifies_matches( assert ( f'{os.path.join(instance, ".minecraft")} to {shulker_box}' in link_log + ) or ( + f'{os.path.join(instance, "minecraft")} to {shulker_box}' in link_log ) is should_match @pytest.mark.parametrize("error_handling", ("ignore", "skip")) @@ -1048,7 +1050,7 @@ def test_skip_instance(self, home, minecraft_root, prompt, monkeypatch, capsys): assert placements["Chest Boat"][Path("options.txt")] == ["1.19"] assert ( - minecraft_root / "instances" / "chest-boat" / ".minecraft" / "options.txt" + minecraft_root / "instances" / "chest-boat" / "minecraft" / "options.txt" ).read_text() == "autoJump:true" assert not (home / ".minecraft" / "data" / "achievements.txt").exists() @@ -1070,7 +1072,7 @@ def test_skip_shulker_box(self, home, minecraft_root, prompt, monkeypatch, capsy ).read_text() == "Spelled acheivements correctly!" assert not ( - minecraft_root / "instances" / "chest-boat" / ".minecraft" / "options.txt" + minecraft_root / "instances" / "chest-boat" / "minecraft" / "options.txt" ).exists() def test_raise_on_invalid_error_handling_arg(self, home, minecraft_root, caplog): @@ -1158,7 +1160,7 @@ def test_skip_shulker_box_that_doesnt_match_host(self, home, minecraft_root): assert "1.19" not in set(sum(placements["Chest Boat"].values(), [])) assert not ( - minecraft_root / "instances" / "chest-boat" / ".minecraft" / "options.txt" + minecraft_root / "instances" / "chest-boat" / "minecraft" / "options.txt" ).exists() assert ( diff --git a/enderchest/test/test_sync.py b/enderchest/test/test_sync.py index e6583012..3c3b9592 100644 --- a/enderchest/test/test_sync.py +++ b/enderchest/test/test_sync.py @@ -570,7 +570,7 @@ def test_never_places_after_close(self, minecraft_root, remote): @pytest.mark.parametrize( "operation, mode", - itertools.product(("pull", "push"), ("immediate", "dry_run_first")), + list(itertools.product(("pull", "push"), ("immediate", "dry_run_first"))), ) def test_sync_respects_exclude( self, minecraft_root, remote, caplog, operation, mode diff --git a/enderchest/test/testing_files/enderchest.cfg b/enderchest/test/testing_files/enderchest.cfg index d3f4b4cf..47fe8b03 100644 --- a/enderchest/test/testing_files/enderchest.cfg +++ b/enderchest/test/testing_files/enderchest.cfg @@ -29,7 +29,7 @@ modloader = Forge groups = Modded [Chest Boat] -root = instances/chest-boat/.minecraft +root = instances/chest-boat/minecraft minecraft_version = 1.19.0 modloader = Fabric Loader groups = Vanilla Plus diff --git a/enderchest/test/utils.py b/enderchest/test/utils.py index d0142b3e..e2ca7a84 100644 --- a/enderchest/test/utils.py +++ b/enderchest/test/utils.py @@ -194,34 +194,34 @@ def populate_mmc_instance_folder( name : str, optional The name of the instance (if different from the name of the folder) """ - _set_up_minecraft_folder(instance_folder / ".minecraft", official=False) + _set_up_minecraft_folder(instance_folder, official=False) create_mmc_pack_file( - instance_folder, minecraft_version=minecraft_version, loader=loader + instance_folder.parent, minecraft_version=minecraft_version, loader=loader ) - create_instance_cfg(instance_folder, name or instance_folder.name) + create_instance_cfg(instance_folder.parent, name or instance_folder.parent.name) -def populate_instances_folder(instances_folder: Path) -> None: +def populate_instances_folder(minecraft_root: Path) -> None: """Populate an MMC-style "instances" folder according to what's already in enderchest.cfg Parameters ---------- - instances_folder : Path - The path of the instances folder + minecraft_root : Path + The parent of the instances folder """ - instances_folder.mkdir(parents=True) + (minecraft_root / "instances").mkdir(parents=True) for instance_spec in TESTING_INSTANCES: if instance_spec.name == "official": continue populate_mmc_instance_folder( - instances_folder / instance_spec.root.parent.name, + minecraft_root / instance_spec.root, instance_spec.minecraft_versions[0], instance_spec.modloader, ) with as_file(testing_files.INSTGROUPS) as instgroups: - shutil.copy(instgroups, instances_folder) + shutil.copy(instgroups, minecraft_root / "instances") def pre_populate_enderchest( From a999fc37015d76b7d2c9f671fe6c07dec5643e04 Mon Sep 17 00:00:00 2001 From: "Gili \"OpenBagTwo\" Barlev" Date: Fri, 28 Mar 2025 16:05:08 -0400 Subject: [PATCH 4/9] Search for dotless minecraft folders as well Resolves #140 --- enderchest/filesystem.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/enderchest/filesystem.py b/enderchest/filesystem.py index 046242e1..017303c1 100644 --- a/enderchest/filesystem.py +++ b/enderchest/filesystem.py @@ -1,8 +1,9 @@ """Functionality for managing the EnderChest and shulker box config files and folders""" +import itertools import os +from collections.abc import Iterable from pathlib import Path -from typing import Iterable from .loggers import INVENTORY_LOGGER @@ -193,7 +194,9 @@ def minecraft_folders(search_path: Path) -> Iterable[Path]: This method does not check to make sure that those .minecraft folders contain valid minecraft instances, just that they exist """ - return search_path.rglob(".minecraft") + return itertools.chain( + search_path.rglob(".minecraft"), search_path.rglob("minecraft") + ) def links_into_enderchest( From b74380acd1cb49888dd0da517659e9dc25cfdc00 Mon Sep 17 00:00:00 2001 From: "Gili \"OpenBagTwo\" Barlev" Date: Fri, 28 Mar 2025 16:14:10 -0400 Subject: [PATCH 5/9] Get way ahead of an upcoming deprecation by impoting abcs from the proper place --- enderchest/_version.py | 6 +++--- enderchest/cli.py | 3 ++- enderchest/config.py | 3 ++- enderchest/craft.py | 2 +- enderchest/enderchest.py | 3 ++- enderchest/gather.py | 3 ++- enderchest/inventory.py | 2 +- enderchest/place.py | 2 +- enderchest/remote.py | 2 +- enderchest/shulker_box.py | 3 ++- enderchest/sync/__init__.py | 2 +- enderchest/sync/file.py | 2 +- enderchest/sync/rsync.py | 2 +- enderchest/sync/sftp.py | 3 ++- enderchest/sync/utils.py | 3 ++- enderchest/test/conftest.py | 2 +- enderchest/test/mock_paramiko.py | 3 ++- enderchest/test/test_cli.py | 2 +- enderchest/test/utils.py | 2 +- enderchest/uninstall.py | 3 +-- versioneer.py | 6 +++--- 21 files changed, 33 insertions(+), 26 deletions(-) diff --git a/enderchest/_version.py b/enderchest/_version.py index 9e796638..eeebbb9c 100644 --- a/enderchest/_version.py +++ b/enderchest/_version.py @@ -16,7 +16,7 @@ import re import subprocess import sys -from typing import Callable, Dict +from collections.abc import Callable def get_keywords(): @@ -54,8 +54,8 @@ class NotThisMethod(Exception): """Exception raised if a method is not valid for the current scenario.""" -LONG_VERSION_PY: Dict[str, str] = {} -HANDLERS: Dict[str, Dict[str, Callable]] = {} +LONG_VERSION_PY: dict[str, str] = {} +HANDLERS: dict[str, dict[str, Callable]] = {} def register_vcs_handler(vcs, method): # decorator diff --git a/enderchest/cli.py b/enderchest/cli.py index 696c1ad8..61008133 100644 --- a/enderchest/cli.py +++ b/enderchest/cli.py @@ -6,8 +6,9 @@ import os import sys from argparse import ArgumentParser, RawTextHelpFormatter +from collections.abc import Iterable, Sequence from pathlib import Path -from typing import Any, Iterable, Protocol, Sequence +from typing import Any, Protocol from . import craft, gather, inventory, loggers, place, remote, uninstall from ._version import get_versions diff --git a/enderchest/config.py b/enderchest/config.py index d5f48fbd..e9415c44 100644 --- a/enderchest/config.py +++ b/enderchest/config.py @@ -2,10 +2,11 @@ import ast import datetime as dt +from collections.abc import Iterable, Mapping, Sequence from configparser import ConfigParser, ParsingError from io import StringIO from pathlib import Path -from typing import Any, Iterable, Mapping, Sequence +from typing import Any from ._version import get_versions diff --git a/enderchest/craft.py b/enderchest/craft.py index 48933a6e..787a614a 100644 --- a/enderchest/craft.py +++ b/enderchest/craft.py @@ -3,8 +3,8 @@ import logging import re from collections import Counter +from collections.abc import Callable, Iterable, Sequence from pathlib import Path -from typing import Callable, Iterable, Sequence from urllib.parse import ParseResult from pathvalidate import is_valid_filename diff --git a/enderchest/enderchest.py b/enderchest/enderchest.py index eac4f667..3863e012 100644 --- a/enderchest/enderchest.py +++ b/enderchest/enderchest.py @@ -1,9 +1,10 @@ """Specification and configuration of an EnderChest""" +from collections.abc import Iterable from dataclasses import dataclass from pathlib import Path from socket import gethostname -from typing import Any, Iterable +from typing import Any from urllib.parse import ParseResult, urlparse from . import config as cfg diff --git a/enderchest/gather.py b/enderchest/gather.py index f818451b..f8986a03 100644 --- a/enderchest/gather.py +++ b/enderchest/gather.py @@ -5,9 +5,10 @@ import logging import os import re +from collections.abc import Iterable from configparser import ConfigParser, ParsingError from pathlib import Path -from typing import Any, Iterable, TypedDict +from typing import Any, TypedDict from urllib.parse import ParseResult from . import filesystem as fs diff --git a/enderchest/inventory.py b/enderchest/inventory.py index 52725dd0..fb7af0bf 100644 --- a/enderchest/inventory.py +++ b/enderchest/inventory.py @@ -1,8 +1,8 @@ """Functionality for resolving EnderChest and shulker box states""" import logging +from collections.abc import Iterable, Sequence from pathlib import Path -from typing import Iterable, Sequence from urllib.parse import ParseResult from enderchest.sync import render_remote diff --git a/enderchest/place.py b/enderchest/place.py index 23701977..14abbd05 100644 --- a/enderchest/place.py +++ b/enderchest/place.py @@ -6,8 +6,8 @@ import logging import os from collections import defaultdict +from collections.abc import Iterable, Sequence from pathlib import Path -from typing import Iterable, Sequence from . import filesystem as fs from .inventory import load_ender_chest, load_ender_chest_instances, load_shulker_boxes diff --git a/enderchest/remote.py b/enderchest/remote.py index 6f521f7e..4f300546 100644 --- a/enderchest/remote.py +++ b/enderchest/remote.py @@ -1,9 +1,9 @@ """Higher-level functionality around synchronizing with different EnderCherts""" import logging +from collections.abc import Sequence from pathlib import Path from time import sleep -from typing import Sequence from urllib.parse import ParseResult, urlparse from . import filesystem as fs diff --git a/enderchest/shulker_box.py b/enderchest/shulker_box.py index 19874a3c..3ea512d5 100644 --- a/enderchest/shulker_box.py +++ b/enderchest/shulker_box.py @@ -3,8 +3,9 @@ import fnmatch import os import re +from collections.abc import Iterable from pathlib import Path -from typing import Any, Iterable, NamedTuple +from typing import Any, NamedTuple import semantic_version as semver diff --git a/enderchest/sync/__init__.py b/enderchest/sync/__init__.py index f4aa30b7..65a680b6 100644 --- a/enderchest/sync/__init__.py +++ b/enderchest/sync/__init__.py @@ -1,10 +1,10 @@ """Low-level functionality for synchronizing across different machines""" import importlib +from collections.abc import Collection, Generator from contextlib import contextmanager from pathlib import Path from tempfile import TemporaryDirectory -from typing import Collection, Generator from urllib.parse import ParseResult from ..loggers import SYNC_LOGGER diff --git a/enderchest/sync/file.py b/enderchest/sync/file.py index a23582ee..f4612bde 100644 --- a/enderchest/sync/file.py +++ b/enderchest/sync/file.py @@ -5,8 +5,8 @@ import os import shutil import stat +from collections.abc import Callable, Collection from pathlib import Path -from typing import Callable, Collection from urllib.parse import ParseResult from . import ( diff --git a/enderchest/sync/rsync.py b/enderchest/sync/rsync.py index 50352a7b..eb92ed72 100644 --- a/enderchest/sync/rsync.py +++ b/enderchest/sync/rsync.py @@ -5,8 +5,8 @@ import shutil import subprocess from collections import defaultdict +from collections.abc import Iterable from pathlib import Path -from typing import Iterable from urllib.parse import ParseResult, unquote from . import SYNC_LOGGER, get_default_netloc, uri_to_ssh diff --git a/enderchest/sync/sftp.py b/enderchest/sync/sftp.py index 1136c294..ce7e910c 100644 --- a/enderchest/sync/sftp.py +++ b/enderchest/sync/sftp.py @@ -3,9 +3,10 @@ import os import posixpath import stat +from collections.abc import Collection, Generator from contextlib import contextmanager from pathlib import Path -from typing import Any, Collection, Generator +from typing import Any from urllib.parse import ParseResult, unquote from urllib.request import url2pathname diff --git a/enderchest/sync/utils.py b/enderchest/sync/utils.py index d7c46fc5..1c4c7441 100644 --- a/enderchest/sync/utils.py +++ b/enderchest/sync/utils.py @@ -6,9 +6,10 @@ import socket import stat from collections import defaultdict +from collections.abc import Collection, Generator, Iterable from enum import Enum, auto from pathlib import Path -from typing import Any, Collection, Generator, Iterable, Protocol, TypeVar +from typing import Any, Protocol, TypeVar from urllib.parse import ParseResult, unquote from urllib.request import url2pathname diff --git a/enderchest/test/conftest.py b/enderchest/test/conftest.py index c6c75ec5..3faf0d50 100644 --- a/enderchest/test/conftest.py +++ b/enderchest/test/conftest.py @@ -1,8 +1,8 @@ """Useful setup / teardown fixtures""" +from collections.abc import Generator from importlib.resources import as_file from pathlib import Path -from typing import Generator import pytest diff --git a/enderchest/test/mock_paramiko.py b/enderchest/test/mock_paramiko.py index 037bcda2..26f401bf 100644 --- a/enderchest/test/mock_paramiko.py +++ b/enderchest/test/mock_paramiko.py @@ -3,10 +3,11 @@ import json import os import shutil +from collections.abc import Callable, Generator from contextlib import contextmanager from importlib.resources import as_file from pathlib import Path -from typing import Callable, Generator, NamedTuple +from typing import NamedTuple from urllib.parse import ParseResult from urllib.request import url2pathname diff --git a/enderchest/test/test_cli.py b/enderchest/test/test_cli.py index cfa834dd..99728606 100644 --- a/enderchest/test/test_cli.py +++ b/enderchest/test/test_cli.py @@ -2,8 +2,8 @@ import logging import os +from collections.abc import Generator from pathlib import Path -from typing import Generator import pytest diff --git a/enderchest/test/utils.py b/enderchest/test/utils.py index e2ca7a84..3053774f 100644 --- a/enderchest/test/utils.py +++ b/enderchest/test/utils.py @@ -2,9 +2,9 @@ import json import shutil +from collections.abc import Callable, Iterable from importlib.resources import as_file from pathlib import Path -from typing import Callable, Iterable import pytest diff --git a/enderchest/uninstall.py b/enderchest/uninstall.py index 7e3e438a..a58186f3 100644 --- a/enderchest/uninstall.py +++ b/enderchest/uninstall.py @@ -1,10 +1,9 @@ """Functionality for copying all files into their instances""" -import logging import os import shutil +from collections.abc import Iterable from pathlib import Path -from typing import Iterable from . import filesystem as fs from .enderchest import create_ender_chest diff --git a/versioneer.py b/versioneer.py index d251127c..d0f3afde 100644 --- a/versioneer.py +++ b/versioneer.py @@ -310,8 +310,8 @@ import re import subprocess import sys +from collections.abc import Callable from pathlib import Path -from typing import Callable, Dict try: import tomli @@ -411,8 +411,8 @@ class NotThisMethod(Exception): # these dictionaries contain VCS-specific tools -LONG_VERSION_PY: Dict[str, str] = {} -HANDLERS: Dict[str, Dict[str, Callable]] = {} +LONG_VERSION_PY: dict[str, str] = {} +HANDLERS: dict[str, dict[str, Callable]] = {} def register_vcs_handler(vcs, method): # decorator From c80d58ea1b14f9273e4a793be9e27172ae78a707 Mon Sep 17 00:00:00 2001 From: "Gili \"OpenBagTwo\" Barlev" Date: Fri, 28 Mar 2025 17:42:15 -0400 Subject: [PATCH 6/9] Use %s formatting for most logs Resolves #136 --- enderchest/craft.py | 43 +++++++++++------------ enderchest/enderchest.py | 4 +-- enderchest/filesystem.py | 2 +- enderchest/gather.py | 18 +++++----- enderchest/inventory.py | 64 +++++++++++++++++++++++------------ enderchest/place.py | 34 +++++++++++-------- enderchest/remote.py | 17 ++++++---- enderchest/shulker_box.py | 4 +-- enderchest/sync/file.py | 14 ++++---- enderchest/sync/rsync.py | 5 +-- enderchest/sync/sftp.py | 7 ++-- enderchest/sync/utils.py | 5 +-- enderchest/test/test_craft.py | 2 +- enderchest/test/test_place.py | 26 +++++++++----- enderchest/test/test_sync.py | 16 ++++++--- 15 files changed, 156 insertions(+), 105 deletions(-) diff --git a/enderchest/craft.py b/enderchest/craft.py index 787a614a..75a3c114 100644 --- a/enderchest/craft.py +++ b/enderchest/craft.py @@ -70,7 +70,7 @@ def craft_ender_chest( passing in, say "/" """ if not minecraft_root.exists(): - CRAFT_LOGGER.error(f"The directory {minecraft_root} does not exist") + CRAFT_LOGGER.error("The directory %s does not exist", minecraft_root) CRAFT_LOGGER.error("Aborting") return if ( @@ -89,12 +89,13 @@ def craft_ender_chest( try: fs.ender_chest_config(minecraft_root, check_exists=True) exist_message = ( - f"There is already an EnderChest installed to {minecraft_root}" + "There is already an EnderChest installed to %s", + minecraft_root, ) if overwrite: - CRAFT_LOGGER.warning(exist_message) + CRAFT_LOGGER.warning(*exist_message) else: - CRAFT_LOGGER.error(exist_message) + CRAFT_LOGGER.error(*exist_message) CRAFT_LOGGER.error("Aborting") return except FileNotFoundError: @@ -116,7 +117,7 @@ def craft_ender_chest( ender_chest.register_remote(remote, alias) except (RuntimeError, ValueError) as fetch_fail: CRAFT_LOGGER.error( - f"Could not fetch remotes from {copy_from}:\n {fetch_fail}" + "Could not fetch remotes from %s:\n %s", copy_from, fetch_fail ) CRAFT_LOGGER.error("Aborting.") return @@ -182,7 +183,7 @@ def craft_shulker_box( to ensure that they are valid or actively in use """ if not is_valid_filename(name): - CRAFT_LOGGER.error(f"{name} is not a valid name: must be usable as a filename") + CRAFT_LOGGER.error("%s is not a valid name: must be usable as a filename", name) return try: @@ -205,13 +206,14 @@ def craft_shulker_box( config_path = fs.shulker_box_config(minecraft_root, name) if config_path.exists(): exist_message = ( - f"There is already a shulker box named {name}" - f" in {fs.ender_chest_folder(minecraft_root)}" + "There is already a shulker box named %s in %s", + name, + fs.ender_chest_folder(minecraft_root), ) if overwrite: - CRAFT_LOGGER.warning(exist_message) + CRAFT_LOGGER.warning(*exist_message) else: - CRAFT_LOGGER.error(exist_message) + CRAFT_LOGGER.error(*exist_message) CRAFT_LOGGER.error("Aborting") return match_criteria: list[tuple[str, tuple[str, ...]]] = [] @@ -252,14 +254,14 @@ def specify_ender_chest_from_prompt(minecraft_root: Path) -> EnderChest: try: root = fs.ender_chest_folder(minecraft_root) CRAFT_LOGGER.info( - f"This will overwrite the EnderChest configuration at {root}." + "This will overwrite the EnderChest configuration at %s.", root ) if not confirm(default=False): message = f"Aborting: {fs.ender_chest_config(minecraft_root)} exists." raise FileExistsError(message) except FileNotFoundError: # good! Then we don't already have an EnderChest here - CRAFT_LOGGER.debug(f"{minecraft_root} does not already contain an EnderChest") + CRAFT_LOGGER.debug("%s does not already contain an EnderChest", minecraft_root) instances: list[InstanceSpec] = [] @@ -322,7 +324,7 @@ def specify_ender_chest_from_prompt(minecraft_root: Path) -> EnderChest: remotes.extend(fetch_remotes_from_a_remote_ender_chest(remote_uri)) except Exception as fetch_fail: CRAFT_LOGGER.error( - f"Could not fetch remotes from {remote_uri}\n {fetch_fail}" + "Could not fetch remotes from %s\n %s", remote_uri, fetch_fail ) if not confirm(default=True): continue @@ -383,7 +385,7 @@ def specify_ender_chest_from_prompt(minecraft_root: Path) -> EnderChest: ) if name in (alias for _, alias in remotes): CRAFT_LOGGER.error( - f"The name {name} is already in use. Choose a different name." + f"The name %s is already in use. Choose a different name.", name ) continue break @@ -425,7 +427,7 @@ def specify_shulker_box_from_prompt(minecraft_root: Path, name: str) -> ShulkerB f"A file named {name} already exists in your EnderChest folder." ) CRAFT_LOGGER.warning( - f"There is already a folder named {name} in your EnderChest folder." + "There is already a folder named %s in your EnderChest folder.", name ) if not confirm(default=False): raise FileExistsError( @@ -869,7 +871,7 @@ def _prompt_for_instance_numbers( # luckily we don't need to worry about negative numbers index = int(value) - 1 if index < 0 or index >= len(instances): - CRAFT_LOGGER.error(f"Invalid selection: {entry} is out of range\n") + CRAFT_LOGGER.error("Invalid selection: %s is out of range\n", entry) return _prompt_for_instance_numbers( shulker_box, instance_loader(), instance_loader, exclude=exclude ) @@ -878,13 +880,13 @@ def _prompt_for_instance_numbers( bounds = tuple(int(bound) for bound in match.groups()) if bounds[0] > bounds[1]: CRAFT_LOGGER.error( - f"Invalid selection: {entry} is not a valid range\n" + "Invalid selection: %s is not a valid range\n", entry ) return _prompt_for_instance_numbers( shulker_box, instance_loader(), instance_loader, exclude=exclude ) if max(bounds) > len(instances) or min(bounds) < 1: - CRAFT_LOGGER.error(f"Invalid selection: {entry} is out of range\n") + CRAFT_LOGGER.error("Invalid selection: %s is out of range\n", entry) return _prompt_for_instance_numbers( shulker_box, instance_loader(), instance_loader, exclude=exclude ) @@ -904,9 +906,8 @@ def _prompt_for_instance_numbers( return shulker_box CRAFT_LOGGER.info( - "You selected to {} the instances:\n%s".format( - "explicitly exclude" if exclude else "include" - ), + "You selected to %s the instances:\n%s", + "explicitly exclude" if exclude else "include", "\n".join([f" - {name}" for name in choices]), ) if not confirm(default=True): diff --git a/enderchest/enderchest.py b/enderchest/enderchest.py index 3863e012..af425f94 100644 --- a/enderchest/enderchest.py +++ b/enderchest/enderchest.py @@ -228,7 +228,7 @@ def register_instance(self, instance: i.InstanceSpec) -> i.InstanceSpec: counter += 1 name = f"{instance.name}.{counter}" - GATHER_LOGGER.debug(f"Registering instance {name} at {instance.root}") + GATHER_LOGGER.debug("Registering instance %s at %s", name, instance.root) self._instances.append(instance._replace(name=name)) return self._instances[-1] @@ -481,4 +481,4 @@ def create_ender_chest(minecraft_root: Path, ender_chest: EnderChest) -> None: config_path = fs.ender_chest_config(minecraft_root, check_exists=False) ender_chest.write_to_cfg(config_path) - CRAFT_LOGGER.info(f"EnderChest configuration written to {config_path}") + CRAFT_LOGGER.info("EnderChest configuration written to %s", config_path) diff --git a/enderchest/filesystem.py b/enderchest/filesystem.py index 017303c1..f533fa0f 100644 --- a/enderchest/filesystem.py +++ b/enderchest/filesystem.py @@ -172,7 +172,7 @@ def shulker_box_configs(minecraft_root: Path) -> Iterable[Path]: This method does not check to make sure those config files are valid, just that they exist """ - INVENTORY_LOGGER.debug(f"Searching for shulker configs within {minecraft_root}") + INVENTORY_LOGGER.debug("Searching for shulker configs within %s", minecraft_root) return ender_chest_folder(minecraft_root).glob(f"*/{SHULKER_BOX_CONFIG_NAME}") diff --git a/enderchest/gather.py b/enderchest/gather.py index f8986a03..7e7f8dfa 100644 --- a/enderchest/gather.py +++ b/enderchest/gather.py @@ -58,39 +58,41 @@ def gather_minecraft_instances( except FileNotFoundError: # because this method can be called during crafting ender_chest = EnderChest(minecraft_root) - GATHER_LOGGER.debug(f"Searching for Minecraft folders inside {search_path}") + GATHER_LOGGER.debug("Searching for Minecraft folders inside %s", search_path) instances: list[InstanceSpec] = [] for folder in fs.minecraft_folders(search_path): folder_path = folder.absolute() - GATHER_LOGGER.debug(f"Found minecraft installation at {folder}") + GATHER_LOGGER.debug("Found minecraft installation at %s", folder) if official is not False: try: instances.append(gather_metadata_for_official_instance(folder_path)) GATHER_LOGGER.info( - f"Gathered official Minecraft installation from {folder}" + "Gathered official Minecraft installation from %s", folder ) _check_for_allowed_symlinks(ender_chest, instances[-1]) continue except ValueError as not_official: GATHER_LOGGER.log( logging.DEBUG if official is None else logging.WARNING, - (f"{folder} is not an official instance:" f"\n{not_official}",), + ("%s is not an official instance:" f"\n%s", folder, not_official), ) if official is not True: try: instances.append(gather_metadata_for_mmc_instance(folder_path)) GATHER_LOGGER.info( - f"Gathered MMC-like Minecraft installation from {folder}" + "Gathered MMC-like Minecraft installation from %s", folder ) _check_for_allowed_symlinks(ender_chest, instances[-1]) continue except ValueError as not_mmc: GATHER_LOGGER.log( logging.DEBUG if official is None else logging.WARNING, - f"{folder} is not an MMC-like instance:\n{not_mmc}", + "%s is not an MMC-like instance:\n%s", + folder, + not_mmc, ) GATHER_LOGGER.warning( - f"{folder_path} does not appear to be a valid Minecraft instance" + "%s does not appear to be a valid Minecraft instance", folder_path ) for i, mc_instance in enumerate(instances): try: @@ -102,7 +104,7 @@ def gather_minecraft_instances( pass # instance isn't inside the minecraft root if not instances: GATHER_LOGGER.warning( - f"Could not find any Minecraft instances inside {search_path}" + "Could not find any Minecraft instances inside %s", search_path ) return instances diff --git a/enderchest/inventory.py b/enderchest/inventory.py index fb7af0bf..c63eb700 100644 --- a/enderchest/inventory.py +++ b/enderchest/inventory.py @@ -3,6 +3,7 @@ import logging from collections.abc import Iterable, Sequence from pathlib import Path +from typing import Any from urllib.parse import ParseResult from enderchest.sync import render_remote @@ -36,9 +37,9 @@ def load_ender_chest(minecraft_root: Path) -> EnderChest: If the EnderChest configuration is invalid and could not be parsed """ config_path = fs.ender_chest_config(minecraft_root) - INVENTORY_LOGGER.debug(f"Loading {config_path}") + INVENTORY_LOGGER.debug("Loading %s", config_path) ender_chest = EnderChest.from_cfg(config_path) - INVENTORY_LOGGER.debug(f"Parsed EnderChest installation from {minecraft_root}") + INVENTORY_LOGGER.debug("Parsed EnderChest installation from %s", minecraft_root) return ender_chest @@ -74,18 +75,19 @@ def load_ender_chest_instances( instances: Sequence[InstanceSpec] = ender_chest.instances except (FileNotFoundError, ValueError) as bad_chest: INVENTORY_LOGGER.error( - f"Could not load EnderChest from {minecraft_root}:\n {bad_chest}" + "Could not load EnderChest from %s:\n %s", minecraft_root, bad_chest ) instances = [] if len(instances) == 0: INVENTORY_LOGGER.warning( - f"There are no instances registered to the {minecraft_root} EnderChest", + "There are no instances registered to the %s EnderChest", minecraft_root ) else: INVENTORY_LOGGER.log( log_level, "These are the instances that are currently registered" - f" to the {minecraft_root} EnderChest:\n%s", + " to the %s EnderChest:\n%s", + minecraft_root, "\n".join( [ f" {i + 1}. {render_instance(instance)}" @@ -148,12 +150,15 @@ def load_shulker_boxes( shulker_boxes.append(_load_shulker_box(shulker_config)) except (FileNotFoundError, ValueError) as bad_shulker: INVENTORY_LOGGER.warning( - f"{bad_shulker}\n Skipping shulker box {shulker_config.parent.name}" + "%s\n Skipping shulker box %s", + bad_shulker, + shulker_config.parent.name, ) except FileNotFoundError: INVENTORY_LOGGER.error( - f"There is no EnderChest installed within {minecraft_root}" + "There is no EnderChest installed within %s", + minecraft_root, ) return [] @@ -162,7 +167,7 @@ def load_shulker_boxes( if len(shulker_boxes) == 0: if log_level >= logging.INFO: INVENTORY_LOGGER.warning( - f"There are no shulker boxes within the {minecraft_root} EnderChest" + "There are no shulker boxes within the %s EnderChest", minecraft_root ) else: report_shulker_boxes( @@ -174,7 +179,22 @@ def load_shulker_boxes( def report_shulker_boxes( shulker_boxes: Iterable[ShulkerBox], log_level: int, ender_chest_name: str ) -> None: - """Log the list of shulker boxes in the order they'll be linked""" + """Log the list of shulker boxes in the order they'll be linked + + Parameters + ---------- + shulker_boxes : list of ShulkerBoxes + The shulker boxes to report on + log_level : int + The log level of the report + ender_chest_name : str + Which chest is this? + + Returns + ------- + None + """ + # TODO: properly log minecraft_root when one appears in the message INVENTORY_LOGGER.log( log_level, f"These are the shulker boxes within {ender_chest_name}" @@ -207,9 +227,9 @@ def _load_shulker_box(config_file: Path) -> ShulkerBox: ValueError If there was a problem parsing the config file """ - INVENTORY_LOGGER.debug(f"Attempting to parse {config_file}") + INVENTORY_LOGGER.debug("Attempting to parse %s", config_file) shulker_box = ShulkerBox.from_cfg(config_file) - INVENTORY_LOGGER.debug(f"Successfully parsed {_render_shulker_box(shulker_box)}") + INVENTORY_LOGGER.debug("Successfully parsed %s", _render_shulker_box(shulker_box)) return shulker_box @@ -265,23 +285,23 @@ def load_ender_chest_remotes( remotes: Sequence[tuple[ParseResult, str]] = ender_chest.remotes except (FileNotFoundError, ValueError) as bad_chest: INVENTORY_LOGGER.error( - f"Could not load EnderChest from {minecraft_root}:\n {bad_chest}" + "Could not load EnderChest from %s:\n %s", minecraft_root, bad_chest ) remotes = () if len(remotes) == 0: if log_level >= logging.INFO: INVENTORY_LOGGER.warning( - f"There are no remotes registered to the {minecraft_root} EnderChest" + "There are no remotes registered to the %s EnderChest", minecraft_root ) return [] report = ( "These are the remote EnderChest installations registered" - f" to the one installed at {minecraft_root}" + " to the one installed at %s" ) remote_list: list[tuple[ParseResult, str]] = [] - log_args: list[str] = [] + log_args: list[Any] = [minecraft_root] for remote, alias in remotes: report += "\n - %s" log_args.append(render_remote(alias, remote)) @@ -312,7 +332,7 @@ def get_shulker_boxes_matching_instance( chest = load_ender_chest(minecraft_root) except (FileNotFoundError, ValueError) as bad_chest: INVENTORY_LOGGER.error( - f"Could not load EnderChest from {minecraft_root}:\n {bad_chest}" + "Could not load EnderChest from %s:\n %s", minecraft_root, bad_chest ) return [] for mc in chest.instances: @@ -333,11 +353,12 @@ def get_shulker_boxes_matching_instance( if len(matches) == 0: report = "does not link to any shulker boxes in this chest" else: + # TODO: render as args report = "links to the following shulker boxes:\n" + "\n".join( f" - {_render_shulker_box(box)}" for box in matches ) - INVENTORY_LOGGER.info(f"The instance {render_instance(mc)} {report}") + INVENTORY_LOGGER.info("The instance %s %s", render_instance(mc), report) return matches @@ -363,13 +384,13 @@ def get_instances_matching_shulker_box( try: config_file = fs.shulker_box_config(minecraft_root, shulker_box_name) except FileNotFoundError: - INVENTORY_LOGGER.error(f"No EnderChest is installed in {minecraft_root}") + INVENTORY_LOGGER.error("No EnderChest is installed in %s", minecraft_root) return [] try: shulker_box = _load_shulker_box(config_file) except (FileNotFoundError, ValueError) as bad_box: INVENTORY_LOGGER.error( - f"Could not load shulker box {shulker_box_name}\n {bad_box}" + "Could not load shulker box %s\n %s", shulker_box_name, bad_box ) return [] @@ -391,7 +412,8 @@ def get_instances_matching_shulker_box( INVENTORY_LOGGER.debug( "These are the instances that are currently registered" - f" to the {minecraft_root} EnderChest:\n%s", + " to the %s EnderChest:\n%s", + minecraft_root, "\n".join( [ f" {i + 1}. {render_instance(instance)}" @@ -412,7 +434,7 @@ def get_instances_matching_shulker_box( ) INVENTORY_LOGGER.info( - f"The shulker box {_render_shulker_box(shulker_box)} {report}" + "The shulker box %s %s", _render_shulker_box(shulker_box), report ) return matches diff --git a/enderchest/place.py b/enderchest/place.py index 14abbd05..71216ec9 100644 --- a/enderchest/place.py +++ b/enderchest/place.py @@ -100,7 +100,7 @@ def place_ender_chest( host = load_ender_chest(minecraft_root).name except (FileNotFoundError, ValueError) as bad_chest: PLACE_LOGGER.error( - f"Could not load EnderChest from {minecraft_root}:\n {bad_chest}" + "Could not load EnderChest from %s:\n %s", minecraft_root, bad_chest ) return {} @@ -111,7 +111,9 @@ def place_ender_chest( for shulker_box in load_shulker_boxes(minecraft_root, log_level=logging.DEBUG): if not shulker_box.matches_host(host): PLACE_LOGGER.debug( - f"{shulker_box.name} is not intended for linking to this host ({host})" + "%s is not intended for linking to this host (%s)", + shulker_box.name, + host, ) continue shulker_boxes.append(shulker_box) @@ -205,8 +207,8 @@ def handle_error(shulker_box: ShulkerBox | None) -> str: break PLACE_LOGGER.error( - "No minecraft instance exists at" - f" {instance_root.expanduser().absolute()}" + "No minecraft instance exists at %s", + instance_root.expanduser().absolute(), ) handling = handle_error(None) if handling is not None: @@ -224,7 +226,7 @@ def handle_error(shulker_box: ShulkerBox | None) -> str: if file.is_symlink(): if fs.links_into_enderchest(minecraft_root, file): PLACE_LOGGER.debug( - f"Removing old link: {file} -> {os.readlink(file)}" + "Removing old link: %s -> %s", file, os.readlink(file) ) file.unlink() @@ -236,7 +238,7 @@ def handle_error(shulker_box: ShulkerBox | None) -> str: box_root = shulker_box.root.expanduser().absolute() - PLACE_LOGGER.info(f"Linking {instance.root} to {shulker_box.name}") + PLACE_LOGGER.info("Linking %s to %s", instance.root, shulker_box.name) resources = set(_rglob(box_root, shulker_box.max_link_depth)) @@ -255,10 +257,11 @@ def handle_error(shulker_box: ShulkerBox | None) -> str: handling = None except OSError: PLACE_LOGGER.error( - f"Error linking shulker box {shulker_box.name}" - f" to instance {instance.name}:" - f"\n {(instance.root / link_folder)} is a" - " non-empty directory" + "Error linking shulker box %s to instance %s:" + "\n %s is a non-empty directory", + shulker_box.name, + instance.name, + (instance.root / link_folder), ) handling = handle_error(shulker_box) if handling is not None: @@ -305,10 +308,11 @@ def handle_error(shulker_box: ShulkerBox | None) -> str: handling = None except OSError: PLACE_LOGGER.error( - f"Error linking shulker box {shulker_box.name}" - f" to instance {instance.name}:" - f"\n {(instance.root / resource_path)}" - " already exists" + "Error linking shulker box %s to instance %s:" + "\n %s already exists", + shulker_box.name, + instance.name, + instance.root / resource_path, ) handling = handle_error(shulker_box) if handling is not None: @@ -329,7 +333,7 @@ def handle_error(shulker_box: ShulkerBox | None) -> str: # we clean up as we go, just in case of a failure for file in instance_root.rglob("*"): if not file.exists(): - PLACE_LOGGER.debug(f"Removing broken link: {file}") + PLACE_LOGGER.debug("Removing broken link: %s", file) file.unlink() if match_exit == "break": diff --git a/enderchest/remote.py b/enderchest/remote.py index 4f300546..6f88ed8e 100644 --- a/enderchest/remote.py +++ b/enderchest/remote.py @@ -149,7 +149,7 @@ def sync_with_remotes( ) except (FileNotFoundError, ValueError) as bad_chest: SYNC_LOGGER.error( - f"Could not load EnderChest from {minecraft_root}:\n {bad_chest}" + "Could not load EnderChest from %s:\n %s", minecraft_root, bad_chest ) return if not remotes: @@ -175,7 +175,8 @@ def sync_with_remotes( if pull_or_push == "pull": SYNC_LOGGER.log( IMPORTANT, - f"{prefix} to pull changes from %s", + "%s to pull changes from %s", + prefix, render_remote(alias, remote_uri), ) remote_chest_folder = remote_uri._replace( @@ -202,8 +203,9 @@ def sync_with_remotes( else: SYNC_LOGGER.log( IMPORTANT, - f"{prefix} to push changes" - f" to {render_remote(alias, remote_uri)}", + "%s to push changes to %s", + prefix, + render_remote(alias, remote_uri), ) local_chest = fs.ender_chest_folder(minecraft_root) push( @@ -225,8 +227,9 @@ def sync_with_remotes( RuntimeError, ) as sync_fail: SYNC_LOGGER.warning( - f"Could not sync changes with {render_remote(alias, remote_uri)}:" - f"\n {sync_fail}" + "Could not sync changes with %s:\n %s", + render_remote(alias, remote_uri), + sync_fail, ) break if do_dry_run == runs[-1]: @@ -236,7 +239,7 @@ def sync_with_remotes( SYNC_LOGGER.error("Aborting") return else: - SYNC_LOGGER.debug(f"Waiting for {sync_confirm_wait} seconds") + SYNC_LOGGER.debug("Waiting for %d seconds", sync_confirm_wait) sleep(sync_confirm_wait) else: synced_somewhere = True diff --git a/enderchest/shulker_box.py b/enderchest/shulker_box.py index 3ea512d5..7bafbeeb 100644 --- a/enderchest/shulker_box.py +++ b/enderchest/shulker_box.py @@ -397,9 +397,9 @@ def create_shulker_box( root.mkdir(exist_ok=True) for folder in (*folders, *shulker_box.link_folders): - CRAFT_LOGGER.debug(f"Creating {root / folder}") + CRAFT_LOGGER.debug("Creating %s", root / folder) (root / folder).mkdir(exist_ok=True, parents=True) config_path = fs.shulker_box_config(minecraft_root, shulker_box.name) shulker_box.write_to_cfg(config_path) - CRAFT_LOGGER.info(f"Shulker box configuration written to {config_path}") + CRAFT_LOGGER.info("Shulker box configuration written to %s", config_path) diff --git a/enderchest/sync/file.py b/enderchest/sync/file.py index f4612bde..e56b470a 100644 --- a/enderchest/sync/file.py +++ b/enderchest/sync/file.py @@ -40,7 +40,7 @@ def get_contents(path: Path) -> list[tuple[Path, os.stat_result]]: directories come before their children) - The paths returned are all relative to the provided path """ - SYNC_LOGGER.debug(f"Getting contents of {path}") + SYNC_LOGGER.debug("Getting contents of %s", path) return sorted( ((p.relative_to(path), p.lstat()) for p in path.rglob("**/*")), key=lambda x: len(str(x[0])), @@ -79,7 +79,7 @@ def copy( """ ignore = ignore_patterns(*exclude) - SYNC_LOGGER.debug(f"Ignoring patterns: {exclude}") + SYNC_LOGGER.debug("Ignoring patterns: %s", exclude) destination_path = destination_folder / source_path.name if destination_path.is_symlink() and not destination_path.is_dir(): @@ -100,7 +100,7 @@ def copy( if not dry_run: destination_folder.mkdir(parents=True, exist_ok=True) - SYNC_LOGGER.debug(f"Copying {source_path} into {destination_folder}") + SYNC_LOGGER.debug("Copying %s into %s", source_path, destination_folder) if source_path.exists() and not source_path.is_dir(): if destination_path.exists() and is_identical( @@ -214,22 +214,22 @@ def clean( for path in contents: if path.name in ignore_me: - SYNC_LOGGER.debug(f"Skipping {path}") + SYNC_LOGGER.debug("Skipping %s", path) continue if path.is_symlink(): - SYNC_LOGGER.log(log_level, f"Removing symlink {path}") + SYNC_LOGGER.log(log_level, f"Removing symlink %s", path) if not dry_run: path.unlink() elif path.is_dir(): clean(path, ignore, dry_run) else: - SYNC_LOGGER.log(log_level, f"Deleting {path}") + SYNC_LOGGER.log(log_level, f"Deleting %s", path) if not dry_run: path.unlink() # check if folder is now empty if not list(root.iterdir()): - SYNC_LOGGER.log(log_level, f"Removing empty {root}") + SYNC_LOGGER.log(log_level, f"Removing empty %s", root) if not dry_run: root.rmdir() diff --git a/enderchest/sync/rsync.py b/enderchest/sync/rsync.py index eb92ed72..c67fa0a4 100644 --- a/enderchest/sync/rsync.py +++ b/enderchest/sync/rsync.py @@ -311,10 +311,11 @@ def summarize_rsync_report(raw_output: str, depth: int = 2) -> list[str]: for path_key, report in sorted(summary.items()): if isinstance(report, str): # nice that these verbs follow the same pattern - SYNC_LOGGER.info(f"{report[:-1].title()}ing {path_key}") + SYNC_LOGGER.info(f"{report[:-1].title()}ing %s", path_key) else: SYNC_LOGGER.info( - f"Within {path_key}...\n%s", + "Within %s...\n%s", + path_key, "\n".join( f" - {op[:-1].title()}ing {count} file{'' if count == 1 else 's'}" for op, count in report.items() diff --git a/enderchest/sync/sftp.py b/enderchest/sync/sftp.py index ce7e910c..493caf61 100644 --- a/enderchest/sync/sftp.py +++ b/enderchest/sync/sftp.py @@ -71,12 +71,13 @@ def connect( ) SYNC_LOGGER.warning( - f"This machine is not set up for passwordless login to {target}" + "This machine is not set up for passwordless login to %s" "\nFor instructions on setting up public key-based authentication," " which is both" "\nmore convenient and more secure, see:" "\nhttps://openbagtwo.github.io/EnderChest" - "/dev/suggestions/#passwordless-ssh-authentication" + "/dev/suggestions/#passwordless-ssh-authentication", + target, ) password = prompt(f"Please enter the password for {target}", is_password=True) try: @@ -192,7 +193,7 @@ def rglob( - The paths returned are *absolute* - The search is performed depth-first """ - SYNC_LOGGER.debug(f"ls {path}") + SYNC_LOGGER.debug("ls %s", path) top_level = client.listdir_attr(path) contents: list[tuple[Path, paramiko.sftp_attr.SFTPAttributes]] = [] for remote_object in top_level: diff --git a/enderchest/sync/utils.py b/enderchest/sync/utils.py index 1c4c7441..3af65c2a 100644 --- a/enderchest/sync/utils.py +++ b/enderchest/sync/utils.py @@ -290,10 +290,11 @@ def generate_sync_report( for path_key, report in sorted(summary.items()): if isinstance(report, Operation): # nice that these verbs follow the same pattern - SYNC_LOGGER.info(f"{report.name[:-1].title()}ing {path_key}") + SYNC_LOGGER.info(f"{report.name[:-1].title()}ing %s", path_key) else: SYNC_LOGGER.info( - f"Within {path_key}...\n%s", + "Within %s...\n%s", + path_key, "\n".join( f" - {op.name[:-1].title()}ing {count} file{'' if count == 1 else 's'}" for op, count in report.items() diff --git a/enderchest/test/test_craft.py b/enderchest/test/test_craft.py index d91ed573..5b2ad1c9 100644 --- a/enderchest/test/test_craft.py +++ b/enderchest/test/test_craft.py @@ -822,7 +822,7 @@ def get_instances(): 2. axolotl (instances/axolotl/.minecraft) 3. bee (instances/bee/.minecraft) 4. Chest Boat (instances/chest-boat/minecraft)""" in "\n".join( - record.msg for record in caplog.records + record.getMessage() for record in caplog.records ) assert shulker_box.match_criteria == ( diff --git a/enderchest/test/test_place.py b/enderchest/test/test_place.py index e4499df2..51934168 100644 --- a/enderchest/test/test_place.py +++ b/enderchest/test/test_place.py @@ -441,7 +441,9 @@ def test_place_will_not_overwrite_a_non_empty_folder( assert placements[instance.name].get(resource_path, []) == [] error_log = "\n".join( - record.msg for record in caplog.records if record.levelno == logging.ERROR + record.getMessage() + for record in caplog.records + if record.levelno == logging.ERROR ) assert re.search( rf"{instance.name}((.|\n)*)screenshots((.|\n)*)empty", error_log @@ -463,7 +465,9 @@ def test_place_will_not_overwrite_a_file(self, minecraft_root, instance, caplog) assert placements[instance.name].get(resource_path, []) == [] error_log = "\n".join( - record.msg for record in caplog.records if record.levelname == "ERROR" + record.getMessage() + for record in caplog.records + if record.levelname == "ERROR" ) assert re.search( rf"{instance.name}((.|\n)*)usercache.json((.|\n)*)exists", error_log @@ -923,7 +927,9 @@ def test_multi_shulker_place_correctly_identifies_matches( # TODO: test placements dict link_log = "\n".join( - record.msg for record in caplog.records if record.levelname == "INFO" + record.getMessage() + for record in caplog.records + if record.levelname == "INFO" ) assert ( @@ -960,7 +966,9 @@ def test_default_behavior_is_to_stop_at_first_error(self, minecraft_root, caplog ).exists() error_log = "\n".join( - record.msg for record in caplog.records if record.levelname == "ERROR" + record.getMessage() + for record in caplog.records + if record.levelname == "ERROR" ) assert error_log.endswith( @@ -991,7 +999,7 @@ def test_ignore_failures( assert len(errors) == 1 error_idx = errors[0] - assert "options.txt already exists" in caplog.records[error_idx].msg + assert "options.txt already exists" in caplog.records[error_idx].getMessage() # note: there's actually no guarantee that this link didn't generate # before the failure... @@ -1018,7 +1026,7 @@ def test_skip_match( # meta-tests that I found the right line assert len(errors) == 1 error_idx = errors[0] - assert "options.txt already exists" in caplog.records[error_idx].msg + assert "options.txt already exists" in caplog.records[error_idx].getMessage() assert ( caplog.records[error_idx + 1].levelname, @@ -1090,7 +1098,7 @@ def test_prompt_and_quit(self, home, minecraft_root, caplog, monkeypatch, capsys ] assert len(errors) == 2 error_idx = errors[0] - assert "options.txt already exists" in caplog.records[error_idx].msg + assert "options.txt already exists" in caplog.records[error_idx].getMessage() # and then make sure that it actually did abort @@ -1125,7 +1133,9 @@ def ope_lemme_delete_that(prompt: str | None = None) -> str: # meta-tests that I found the right line assert len(errors) == 1 error_idx = errors[0] - assert "options.txt already exists" in caplog.records[error_idx].msg + assert ( + "options.txt already exists" in caplog.records[error_idx].getMessage() + ) assert conflict_file_path.is_symlink() finally: diff --git a/enderchest/test/test_sync.py b/enderchest/test/test_sync.py index 3c3b9592..c251446d 100644 --- a/enderchest/test/test_sync.py +++ b/enderchest/test/test_sync.py @@ -544,7 +544,9 @@ def test_close_will_attempt_to_push_to_all(self, minecraft_root, remote, caplog) ) r.sync_with_remotes(minecraft_root, "push", verbosity=-1) warnings = [ - record.msg for record in caplog.records if record.levelname == "WARNING" + record.getMessage() + for record in caplog.records + if record.levelname == "WARNING" ] assert len(warnings) == 1 @@ -891,9 +893,9 @@ def test_rsync_summary_doesnt_report_details_if_the_whole_shulker_is_being_creat debug_log = "" for record in caplog.records: if record.levelname == "INFO": - info_log += record.msg + "\n" + info_log += record.getMessage() + "\n" elif record.levelname == "DEBUG": - debug_log += record.msg + "\n" + debug_log += record.getMessage() + "\n" # meta-test--make sure that the creation is actually happening assert ( @@ -924,7 +926,9 @@ def mock_summarize(*args, **kwargs): r.sync_with_remotes(minecraft_root, op, dry_run=True, verbosity=len(verbosity)) debug_log = "\n".join( - record.msg for record in caplog.records if record.levelname == "DEBUG" + record.getMessage() + for record in caplog.records + if record.levelname == "DEBUG" ) # this wouldn't be in the summary @@ -938,7 +942,9 @@ def test_quiet_dry_run_still_reports_stats( r.sync_with_remotes(minecraft_root, op, dry_run=True, verbosity=-1) printed_log = "\n".join( - record.msg for record in caplog.records if record.levelno > logging.INFO + record.getMessage() + for record in caplog.records + if record.levelno > logging.INFO ) assert "Number of created files" in printed_log From 19e059dcdca36498c3fa6fc3e8c4ed13c9b4d1bc Mon Sep 17 00:00:00 2001 From: "Gili \"OpenBagTwo\" Barlev" Date: Tue, 29 Jul 2025 16:57:20 -0400 Subject: [PATCH 7/9] Ope. Remove unnecessary f's --- enderchest/craft.py | 4 ++-- enderchest/gather.py | 2 +- enderchest/sync/file.py | 6 +++--- enderchest/uninstall.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/enderchest/craft.py b/enderchest/craft.py index 75a3c114..833250f3 100644 --- a/enderchest/craft.py +++ b/enderchest/craft.py @@ -385,7 +385,7 @@ def specify_ender_chest_from_prompt(minecraft_root: Path) -> EnderChest: ) if name in (alias for _, alias in remotes): CRAFT_LOGGER.error( - f"The name %s is already in use. Choose a different name.", name + "The name %s is already in use. Choose a different name.", name ) continue break @@ -894,7 +894,7 @@ def _prompt_for_instance_numbers( instance.name for instance in instances[bounds[0] - 1 : bounds[1]] ) case _: - CRAFT_LOGGER.error(f"Invalid selection.\n") + CRAFT_LOGGER.error("Invalid selection.\n") return _prompt_for_instance_numbers( shulker_box, instance_loader(), instance_loader, exclude=exclude ) diff --git a/enderchest/gather.py b/enderchest/gather.py index 7e7f8dfa..75be4ada 100644 --- a/enderchest/gather.py +++ b/enderchest/gather.py @@ -74,7 +74,7 @@ def gather_minecraft_instances( except ValueError as not_official: GATHER_LOGGER.log( logging.DEBUG if official is None else logging.WARNING, - ("%s is not an official instance:" f"\n%s", folder, not_official), + ("%s is not an official instance:\n%s", folder, not_official), ) if official is not True: try: diff --git a/enderchest/sync/file.py b/enderchest/sync/file.py index e56b470a..d936cdbb 100644 --- a/enderchest/sync/file.py +++ b/enderchest/sync/file.py @@ -217,19 +217,19 @@ def clean( SYNC_LOGGER.debug("Skipping %s", path) continue if path.is_symlink(): - SYNC_LOGGER.log(log_level, f"Removing symlink %s", path) + SYNC_LOGGER.log(log_level, "Removing symlink %s", path) if not dry_run: path.unlink() elif path.is_dir(): clean(path, ignore, dry_run) else: - SYNC_LOGGER.log(log_level, f"Deleting %s", path) + SYNC_LOGGER.log(log_level, "Deleting %s", path) if not dry_run: path.unlink() # check if folder is now empty if not list(root.iterdir()): - SYNC_LOGGER.log(log_level, f"Removing empty %s", root) + SYNC_LOGGER.log(log_level, "Removing empty %s", root) if not dry_run: root.rmdir() diff --git a/enderchest/uninstall.py b/enderchest/uninstall.py index a58186f3..93f27728 100644 --- a/enderchest/uninstall.py +++ b/enderchest/uninstall.py @@ -72,7 +72,7 @@ def break_instances(minecraft_root: Path, instance_names: Iterable[str]) -> None instances.append(instance_lookup[name]) except KeyError: BREAK_LOGGER.warning( - f'No instance named "%s" is registered to this EnderChest.' + 'No instance named "%s" is registered to this EnderChest.' "\nSkipping.", name, ) From ee4bc10b009f52114e0d417ef9cb751a6ebc1b44 Mon Sep 17 00:00:00 2001 From: "Gili \"OpenBagTwo\" Barlev" Date: Tue, 29 Jul 2025 16:57:38 -0400 Subject: [PATCH 8/9] Update pre-commit --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b3554003..105057a7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,7 +8,7 @@ repos: hooks: - id: isort - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.15.0 + rev: v1.17.0 hooks: - id: mypy additional_dependencies: [types-paramiko] @@ -19,7 +19,7 @@ repos: - id: nbqa-mypy args: [--ignore-missing-imports] - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks - rev: v2.14.0 + rev: v2.15.0 hooks: - id: pretty-format-ini args: [--autofix] From ea8092116b0519aacdef9f77022b52f03c864d69 Mon Sep 17 00:00:00 2001 From: "Gili \"OpenBagTwo\" Barlev" Date: Tue, 29 Jul 2025 17:08:51 -0400 Subject: [PATCH 9/9] General linting --- enderchest/cli.py | 4 ++-- enderchest/craft.py | 3 +-- enderchest/enderchest.py | 8 ++++---- enderchest/gather.py | 29 +++++++++++++++-------------- enderchest/shulker_box.py | 8 ++++---- 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/enderchest/cli.py b/enderchest/cli.py index 61008133..a581a512 100644 --- a/enderchest/cli.py +++ b/enderchest/cli.py @@ -794,11 +794,11 @@ def parse_args(argv: Sequence[str]) -> tuple[Action, Path, int, dict[str, Any]]: log_level = loggers.verbosity_to_log_level(verbosity) - MINECRAFT_ROOT = os.getenv("MINECRAFT_ROOT") + env_root = os.getenv("MINECRAFT_ROOT") return ( actions[aliases[command]], - Path(root_arg or root_flag or MINECRAFT_ROOT or os.getcwd()), + Path(root_arg or root_flag or env_root or os.getcwd()), log_level, action_kwargs, ) diff --git a/enderchest/craft.py b/enderchest/craft.py index 833250f3..ec4bfd1f 100644 --- a/enderchest/craft.py +++ b/enderchest/craft.py @@ -852,8 +852,7 @@ def _prompt_for_instance_numbers( if selections == "": if exclude: return shulker_box - else: - selections = "*" + selections = "*" if re.search("[^0-9-,* ]", selections): # check for invalid characters CRAFT_LOGGER.error("Invalid selection.\n") diff --git a/enderchest/enderchest.py b/enderchest/enderchest.py index af425f94..6dfffc3c 100644 --- a/enderchest/enderchest.py +++ b/enderchest/enderchest.py @@ -320,13 +320,13 @@ def from_cfg(cls, config_file: Path) -> "EnderChest": offer_to_update_symlink_allowlist = config[section].getboolean( "offer-to-update-symlink-allowlist", True ) - if "do-not-sync" in config[section].keys(): + if "do-not-sync" in config[section]: do_not_sync = cfg.parse_ini_list( config[section]["do-not-sync"] or "" ) - for setting in folder_defaults.keys(): + for setting in folder_defaults: setting_key = setting.replace("_", "-") - if setting_key in config[section].keys(): + if setting_key in config[section]: folder_defaults[setting] = cfg.parse_ini_list( config[section][setting_key] or "" ) @@ -389,7 +389,7 @@ def from_cfg(cls, config_file: Path) -> "EnderChest": ) ender_chest.do_not_sync.insert(0, chest_cfg_exclusion) requires_rewrite = True - for setting in folder_defaults.keys(): + for setting in folder_defaults: if folder_defaults[setting] is None: folder_defaults[setting] = dict(_DEFAULTS)[setting] # type: ignore # requires_rewrite = True # though I'm considering it diff --git a/enderchest/gather.py b/enderchest/gather.py index 75be4ada..3bebcb25 100644 --- a/enderchest/gather.py +++ b/enderchest/gather.py @@ -168,10 +168,11 @@ def gather_metadata_for_official_instance( raise ValueError( f"{version_manifest_file} is corrupt and could not be parsed" ) from bad_json - except KeyError as weird_json: + except KeyError: GATHER_LOGGER.warning( - f"{version_manifest_file} has no latest-version lookup." + "%s has no latest-version lookup." "\nPlease check the parsed metadata to ensure that it's accurate.", + version_manifest_file, ) version_lookup = {} @@ -275,16 +276,16 @@ def gather_metadata_for_mmc_instance( if name in metadata.get("instances", ()): instance_groups.append(group) - except FileNotFoundError as no_json: + except FileNotFoundError: GATHER_LOGGER.warning( - f"Could not find {instgroups_file} and thus could not load tags" + "Could not find %s and thus could not load tags", instgroups_file ) - except json.JSONDecodeError as bad_json: + except json.JSONDecodeError: GATHER_LOGGER.warning( - f"{instgroups_file} is corrupt and could not be parsed for tags" + "%s is corrupt and could not be parsed for tags", instgroups_file ) - except KeyError as weird_json: - GATHER_LOGGER.warning(f"Could not parse tags from {instgroups_file}") + except KeyError: + GATHER_LOGGER.warning("Could not parse tags from %s", instgroups_file) instance_cfg = minecraft_folder.parent / "instance.cfg" @@ -292,16 +293,16 @@ def gather_metadata_for_mmc_instance( parser = ConfigParser(allow_no_value=True, interpolation=None) parser.read_string("[instance]\n" + instance_cfg.read_text()) name = parser["instance"]["name"] - except FileNotFoundError as no_cfg: + except FileNotFoundError: GATHER_LOGGER.warning( - f"Could not find {instance_cfg} and thus could not load the instance name" + "Could not find %s and thus could not load the instance name", instance_cfg ) - except ParsingError as no_cfg: + except ParsingError: GATHER_LOGGER.warning( - f"{instance_cfg} is corrupt and could not be parsed the instance name" + "is corrupt and could not be parsed the instance name", instance_cfg ) - except KeyError as weird_json: - GATHER_LOGGER.warning(f"Could not parse instance name from {instance_cfg}") + except KeyError: + GATHER_LOGGER.warning(f"Could not parse instance name from %s", instance_cfg) if name == "": raise ValueError("Could not determine the name of the instance.") diff --git a/enderchest/shulker_box.py b/enderchest/shulker_box.py index 7bafbeeb..0ad57502 100644 --- a/enderchest/shulker_box.py +++ b/enderchest/shulker_box.py @@ -342,13 +342,13 @@ def _matches_string(pattern: str, value: str, case_sensitive: bool = False) -> b Parameters ---------- pattern : str - The pattern to match. This can be a literal value, an fnmatch pattern with wildcards or a regular expression - if quoted and prefixed with an "r". + The pattern to match. This can be a literal value, an fnmatch pattern + with wildcards or a regular expression if quoted and prefixed with an "r". value : str The value to check case_sensitive : bool, optional - Whether the matching is case-sensitive. Default is False. Note that this is **ignored** if the provided pattern - is a regular expression. + Whether the matching is case-sensitive. Default is False. Note that this + is **ignored** if the provided pattern is a regular expression. Returns -------