From 091c2d11be2bb09e68b76bcd299913eba7cdf67a Mon Sep 17 00:00:00 2001 From: AzisK Date: Wed, 28 Jan 2026 00:52:07 +0100 Subject: [PATCH 01/13] refactor: simplify GitHub Actions workflows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename CI workflow to Tests (ci.yml โ†’ tests.yml) - Simplify PyPI publish: use uv build/publish instead of pypa action - Use ubuntu-slim for publish workflow (no longer needs Docker) - Replace explicit Python install with UV_PYTHON env var - Update actions/checkout to v6 - Update README badge URL --- .github/workflows/ci.yml | 2 +- .github/workflows/publish-pypi.yml | 32 ++++----- .github/workflows/tests.yml | 106 +++++++++++++++++++++++++++++ README.md | 2 +- 4 files changed, 121 insertions(+), 21 deletions(-) create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d44cc41..ad145a2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,5 @@ --- -name: CI +name: Tests & Lint on: workflow_dispatch: push: diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 121932c..c8b6198 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -3,41 +3,35 @@ name: Publish to PyPI on: push: tags: - - "v*" # Triggers on tags like v0.1.0 + - "v*" # Triggers on tags like v0.1.0 jobs: publish: - runs-on: ubuntu-latest + runs-on: ubuntu-slim permissions: - id-token: write # IMPORTANT: this permission is mandatory for trusted publishing + id-token: write # IMPORTANT: this permission is mandatory for trusted publishing steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Validate version matches tag run: | - PYPROJECT_VERSION=$(grep '^version = ' pyproject.toml | cut -d'"' -f2) - TAG_VERSION=${GITHUB_REF#refs/tags/v} - if [ "$PYPROJECT_VERSION" != "$TAG_VERSION" ]; then - echo "Version mismatch: pyproject.toml ($PYPROJECT_VERSION) != tag ($TAG_VERSION)" - exit 1 - fi + PYPROJECT_VERSION=$(grep '^version = ' pyproject.toml | cut -d'"' -f2) + TAG_VERSION=${GITHUB_REF#refs/tags/v} + if [ "$PYPROJECT_VERSION" != "$TAG_VERSION" ]; then + echo "Version mismatch: pyproject.toml ($PYPROJECT_VERSION) != tag ($TAG_VERSION)" + exit 1 + fi - name: Install uv uses: astral-sh/setup-uv@v7 with: enable-cache: true - - name: Install dependencies from uv.lock - run: uv sync --no-dev - - - name: Build package + - name: Build and publish to PyPI run: | - uv pip install --upgrade build - uv run python -m build - - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + uv build + uv publish --trusted-publishing always - name: Update package managers run: | diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..4703417 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,106 @@ +--- +name: Tests +on: + workflow_dispatch: + push: + branches: [main] + paths-ignore: + - "**.md" + pull_request: + branches: [main] + paths-ignore: + - "**.md" + +jobs: + test: + name: Test Python ${{ matrix.python-version }} on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + os: [ubuntu-slim, macos-latest, windows-latest] + python-version: + [ + "3.8", + "3.9", + "3.10", + "3.11", + "3.12", + "3.13", + "3.14", + "pypy3.9", + "pypy3.10", + "pypy3.11", + ] + + env: + UV_PYTHON: ${{ matrix.python-version }} + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + + - name: Install dependencies + run: uv sync + + - name: Run tests + run: uv run pytest -v + + - name: Run linter + run: uv run ruff check . + + - name: Check formatting + run: uv run ruff format --check . + + - name: Test CLI + run: | + uv run python main.py --help + uv run python main.py . -n 5 -m 1000 + + mypy: + name: Type Check + runs-on: ubuntu-slim + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + + - name: Install dependencies + run: uv sync + + - name: Type check with mypy + run: uv run mypy . + + post-dog: + if: github.event_name == 'pull_request' + name: Celebrate with a Dog! + needs: [test, mypy] # Only run if `test` and `mypy` succeed + runs-on: ubuntu-slim + + steps: + - name: Fetch a random dog + id: dog + run: | + DOG_URL=$(curl -s https://dog.ceo/api/breeds/image/random | jq -r '.message') + echo "dog_url=$DOG_URL" >> $GITHUB_OUTPUT + - name: Post dog in PR comment + uses: actions/github-script@v7 + with: + script: | + await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: `๐ŸŽ‰ **All tests passed! Here's a dog for you!** ๐Ÿถ\n\n![Dog](${{ steps.dog.outputs.dog_url }})` + }) diff --git a/README.md b/README.md index 69db154..c452804 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Zpace [![PyPI version](https://img.shields.io/pypi/v/zpace?color=blue)](https://pypi.org/project/zpace/) -[![Tests](https://github.com/AzisK/Zpace/actions/workflows/ci.yml/badge.svg)](https://github.com/AzisK/Zpace/actions/workflows/ci.yml) +[![Tests](https://github.com/AzisK/Zpace/actions/workflows/tests.yml/badge.svg)](https://github.com/AzisK/Zpace/actions/workflows/tests.yml) A CLI tool to discover what's hogging your disk space! From 87b60f73833f5f03ab2a16e40cabd27a6383d561 Mon Sep 17 00:00:00 2001 From: AzisK Date: Wed, 28 Jan 2026 00:57:11 +0100 Subject: [PATCH 02/13] Delete ci.yml --- .github/workflows/ci.yml | 94 ---------------------------------------- 1 file changed, 94 deletions(-) delete mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index ad145a2..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,94 +0,0 @@ ---- -name: Tests & Lint -on: - workflow_dispatch: - push: - branches: [ main ] - paths-ignore: - - '**.md' - pull_request: - branches: [ main ] - paths-ignore: - - '**.md' - -jobs: - test: - name: Test Python ${{ matrix.python-version }} on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - - strategy: - fail-fast: false - matrix: - os: [ubuntu-slim, macos-latest, windows-latest] - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14", "pypy3.9", "pypy3.10", "pypy3.11"] - - steps: - - name: Checkout repository - uses: actions/checkout@v5 - - - name: Install uv - uses: astral-sh/setup-uv@v7 - with: - enable-cache: true - - - name: Set up Python ${{ matrix.python-version }} - run: uv python install ${{ matrix.python-version }} - - - name: Install dependencies - run: uv sync - - - name: Run tests - run: uv run pytest -v - - - name: Run linter - run: uv run ruff check . - - - name: Check formatting - run: uv run ruff format --check . - - - name: Test CLI - run: | - uv run python main.py --help - uv run python main.py . -n 5 -m 1000 - - mypy: - name: Type Check - runs-on: ubuntu-slim - - steps: - - name: Checkout repository - uses: actions/checkout@v5 - - - name: Install uv - uses: astral-sh/setup-uv@v7 - with: - enable-cache: true - - - name: Install dependencies - run: uv sync - - - name: Type check with mypy - run: uv run mypy . - - post-dog: - if: github.event_name == 'pull_request' - name: Celebrate with a Dog! - needs: [test, mypy] # Only run if `test` and `mypy` succeed - runs-on: ubuntu-slim - - steps: - - name: Fetch a random dog - id: dog - run: | - DOG_URL=$(curl -s https://dog.ceo/api/breeds/image/random | jq -r '.message') - echo "dog_url=$DOG_URL" >> $GITHUB_OUTPUT - - name: Post dog in PR comment - uses: actions/github-script@v7 - with: - script: | - await github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: `๐ŸŽ‰ **All tests passed! Here's a dog for you!** ๐Ÿถ\n\n![Dog](${{ steps.dog.outputs.dog_url }})` - }) From cee1755fb7ba873242aee000d9b1bbdb81bb601c Mon Sep 17 00:00:00 2001 From: AzisK Date: Fri, 30 Jan 2026 23:02:31 +0100 Subject: [PATCH 03/13] Rename publish-pypi to publish --- .github/workflows/{publish-pypi.yml => publish.yml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/workflows/{publish-pypi.yml => publish.yml} (98%) diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish.yml similarity index 98% rename from .github/workflows/publish-pypi.yml rename to .github/workflows/publish.yml index c8b6198..8f35319 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish.yml @@ -1,5 +1,5 @@ --- -name: Publish to PyPI +name: Publish on: push: tags: From 5a174821f65c8ef1f27e6f4ee0efc629aacbda4e Mon Sep 17 00:00:00 2001 From: AzisK Date: Sat, 31 Jan 2026 21:29:40 +0100 Subject: [PATCH 04/13] fix: use local imports in user config tests Tests for user config loading were flaky across Python versions because USER_CONFIG_PATH was imported at module level before pyfakefs was active. Some Python versions handle this correctly, others don't. Move imports of USER_CONFIG_PATH, load_user_categories_config, and DEFAULT_CATEGORIES inside each test method to ensure they resolve after pyfakefs is active, making tests consistent across all Python/OS combos. --- CHANGELOG.md | 6 ++++++ test_unit.py | 57 +++++++++++++++++++++++++++++++++++++++++----------- uv.lock | 2 +- 3 files changed, 52 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d280f0..658ecf4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/). +## [Unreleased] + +### Tests + +- Fixed flaky user config tests across Python versions by using local imports for pyfakefs compatibility + ## [0.4.5] - 2026-01-27 ### Features diff --git a/test_unit.py b/test_unit.py index 44b9696..92e96a5 100644 --- a/test_unit.py +++ b/test_unit.py @@ -16,9 +16,6 @@ from zpace.config import ( MIN_FILE_SIZE, SKIP_DIRS, - DEFAULT_CATEGORIES, - load_user_categories_config, - USER_CONFIG_PATH, ) from io import StringIO import os @@ -778,7 +775,7 @@ def test_returns_defaults_when_no_config_file(self, fs): def test_returns_defaults_when_config_file_empty(self, fs): """Empty config file should return defaults.""" - from zpace.config import load_user_dirs_config, DEFAULT_SPECIAL_DIRS + from zpace.config import load_user_dirs_config, DEFAULT_SPECIAL_DIRS, USER_CONFIG_PATH fs.create_file(str(USER_CONFIG_PATH), contents="") result = load_user_dirs_config() @@ -786,7 +783,7 @@ def test_returns_defaults_when_config_file_empty(self, fs): def test_dirs_replaces_category(self, fs): """Using 'dirs' should replace all dirs in a category.""" - from zpace.config import load_user_dirs_config + from zpace.config import load_user_dirs_config, USER_CONFIG_PATH config_content = """ [directories."Node Modules"] @@ -798,7 +795,7 @@ def test_dirs_replaces_category(self, fs): def test_add_extends_category(self, fs): """Using 'add' should add dirs to existing category.""" - from zpace.config import load_user_dirs_config + from zpace.config import load_user_dirs_config, USER_CONFIG_PATH config_content = """ [directories."Virtual Environments"] @@ -812,7 +809,7 @@ def test_add_extends_category(self, fs): def test_remove_removes_from_category(self, fs): """Using 'remove' should remove dirs from category.""" - from zpace.config import load_user_dirs_config + from zpace.config import load_user_dirs_config, USER_CONFIG_PATH config_content = """ [directories."Package Caches"] @@ -826,7 +823,7 @@ def test_remove_removes_from_category(self, fs): def test_creates_new_custom_category(self, fs): """Should be able to create entirely new directory categories.""" - from zpace.config import load_user_dirs_config + from zpace.config import load_user_dirs_config, USER_CONFIG_PATH config_content = """ [directories."My Custom Dirs"] @@ -839,7 +836,7 @@ def test_creates_new_custom_category(self, fs): def test_add_to_new_category_creates_it(self, fs): """Using 'add' on non-existent category should create it.""" - from zpace.config import load_user_dirs_config + from zpace.config import load_user_dirs_config, USER_CONFIG_PATH config_content = """ [directories.NewDirCategory] @@ -852,7 +849,7 @@ def test_add_to_new_category_creates_it(self, fs): def test_combined_operations(self, fs): """Test dirs + add + remove in same category.""" - from zpace.config import load_user_dirs_config + from zpace.config import load_user_dirs_config, USER_CONFIG_PATH config_content = """ [directories.TestDirCat] @@ -866,7 +863,7 @@ def test_combined_operations(self, fs): def test_invalid_toml_returns_defaults(self, fs): """Malformed TOML should return defaults gracefully.""" - from zpace.config import load_user_dirs_config, DEFAULT_SPECIAL_DIRS + from zpace.config import load_user_dirs_config, DEFAULT_SPECIAL_DIRS, USER_CONFIG_PATH config_content = "this is not valid [ toml {" fs.create_file(str(USER_CONFIG_PATH), contents=config_content) @@ -875,7 +872,7 @@ def test_invalid_toml_returns_defaults(self, fs): def test_does_not_mutate_default_special_dirs(self, fs): """Ensure loading config doesn't mutate DEFAULT_SPECIAL_DIRS.""" - from zpace.config import load_user_dirs_config, DEFAULT_SPECIAL_DIRS + from zpace.config import load_user_dirs_config, DEFAULT_SPECIAL_DIRS, USER_CONFIG_PATH original_node_modules = DEFAULT_SPECIAL_DIRS["Node Modules"].copy() config_content = """ @@ -892,17 +889,31 @@ class TestLoadUserConfig: def test_returns_defaults_when_no_config_file(self, fs): """When config file doesn't exist, return default categories.""" + from zpace.config import load_user_categories_config, DEFAULT_CATEGORIES + result = load_user_categories_config() assert result == DEFAULT_CATEGORIES def test_returns_defaults_when_config_file_empty(self, fs): """Empty config file should return defaults.""" + from zpace.config import ( + load_user_categories_config, + DEFAULT_CATEGORIES, + USER_CONFIG_PATH, + ) + fs.create_file(str(USER_CONFIG_PATH), contents="") result = load_user_categories_config() assert result == DEFAULT_CATEGORIES def test_extensions_replaces_category(self, fs): """Using 'extensions' should replace all extensions in a category.""" + from zpace.config import ( + load_user_categories_config, + DEFAULT_CATEGORIES, + USER_CONFIG_PATH, + ) + config_content = """ [categories.Pictures] extensions = [".custom1", ".custom2"] @@ -915,6 +926,8 @@ def test_extensions_replaces_category(self, fs): def test_add_extends_category(self, fs): """Using 'add' should add extensions to existing category.""" + from zpace.config import load_user_categories_config, USER_CONFIG_PATH + config_content = """ [categories.Code] add = [".sql", ".graphql"] @@ -927,6 +940,8 @@ def test_add_extends_category(self, fs): def test_remove_removes_from_category(self, fs): """Using 'remove' should remove extensions from category.""" + from zpace.config import load_user_categories_config, USER_CONFIG_PATH + config_content = """ [categories.Documents] remove = [".md", ".txt"] @@ -939,6 +954,8 @@ def test_remove_removes_from_category(self, fs): def test_creates_new_custom_category(self, fs): """Should be able to create entirely new categories.""" + from zpace.config import load_user_categories_config, USER_CONFIG_PATH + config_content = """ [categories.Fonts] extensions = [".ttf", ".otf", ".woff"] @@ -950,6 +967,8 @@ def test_creates_new_custom_category(self, fs): def test_add_to_new_category_creates_it(self, fs): """Using 'add' on non-existent category should create it.""" + from zpace.config import load_user_categories_config, USER_CONFIG_PATH + config_content = """ [categories.NewCategory] add = [".new1", ".new2"] @@ -961,6 +980,8 @@ def test_add_to_new_category_creates_it(self, fs): def test_combined_operations(self, fs): """Test extensions + add + remove in same category.""" + from zpace.config import load_user_categories_config, USER_CONFIG_PATH + config_content = """ [categories.TestCat] extensions = [".a", ".b", ".c"] @@ -974,6 +995,12 @@ def test_combined_operations(self, fs): def test_invalid_toml_returns_defaults(self, fs): """Malformed TOML should return defaults gracefully.""" + from zpace.config import ( + load_user_categories_config, + DEFAULT_CATEGORIES, + USER_CONFIG_PATH, + ) + config_content = "this is not valid [ toml {" fs.create_file(str(USER_CONFIG_PATH), contents=config_content) result = load_user_categories_config() @@ -981,6 +1008,12 @@ def test_invalid_toml_returns_defaults(self, fs): def test_does_not_mutate_default_categories(self, fs): """Ensure loading config doesn't mutate DEFAULT_CATEGORIES.""" + from zpace.config import ( + load_user_categories_config, + DEFAULT_CATEGORIES, + USER_CONFIG_PATH, + ) + original_pictures = DEFAULT_CATEGORIES["Pictures"].copy() config_content = """ [categories.Pictures] diff --git a/uv.lock b/uv.lock index b0fc177..f4f2c7b 100644 --- a/uv.lock +++ b/uv.lock @@ -505,7 +505,7 @@ wheels = [ [[package]] name = "zpace" -version = "0.4.4" +version = "0.4.5" source = { editable = "." } dependencies = [ { name = "argparse" }, From 0d1f57a098d19a29495d8b8d643a3e4a62b62863 Mon Sep 17 00:00:00 2001 From: AzisK Date: Sat, 31 Jan 2026 21:37:10 +0100 Subject: [PATCH 05/13] Patch zpace.config.USER_CONFIG_PATH --- test_unit.py | 64 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/test_unit.py b/test_unit.py index 92e96a5..3bffff9 100644 --- a/test_unit.py +++ b/test_unit.py @@ -768,9 +768,10 @@ class TestLoadUserDirsConfig: def test_returns_defaults_when_no_config_file(self, fs): """When config file doesn't exist, return default special dirs.""" - from zpace.config import load_user_dirs_config, DEFAULT_SPECIAL_DIRS + from zpace.config import load_user_dirs_config, DEFAULT_SPECIAL_DIRS, USER_CONFIG_PATH - result = load_user_dirs_config() + with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): + result = load_user_dirs_config() assert result == DEFAULT_SPECIAL_DIRS def test_returns_defaults_when_config_file_empty(self, fs): @@ -778,7 +779,8 @@ def test_returns_defaults_when_config_file_empty(self, fs): from zpace.config import load_user_dirs_config, DEFAULT_SPECIAL_DIRS, USER_CONFIG_PATH fs.create_file(str(USER_CONFIG_PATH), contents="") - result = load_user_dirs_config() + with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): + result = load_user_dirs_config() assert result == DEFAULT_SPECIAL_DIRS def test_dirs_replaces_category(self, fs): @@ -790,7 +792,8 @@ def test_dirs_replaces_category(self, fs): dirs = ["my_modules", "custom_modules"] """ fs.create_file(str(USER_CONFIG_PATH), contents=config_content) - result = load_user_dirs_config() + with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): + result = load_user_dirs_config() assert result["Node Modules"] == {"my_modules", "custom_modules"} def test_add_extends_category(self, fs): @@ -802,7 +805,8 @@ def test_add_extends_category(self, fs): add = ["myenv", ".myenv"] """ fs.create_file(str(USER_CONFIG_PATH), contents=config_content) - result = load_user_dirs_config() + with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): + result = load_user_dirs_config() assert "myenv" in result["Virtual Environments"] assert ".myenv" in result["Virtual Environments"] assert ".venv" in result["Virtual Environments"] # Original still present @@ -816,7 +820,8 @@ def test_remove_removes_from_category(self, fs): remove = ["vendor", ".cache"] """ fs.create_file(str(USER_CONFIG_PATH), contents=config_content) - result = load_user_dirs_config() + with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): + result = load_user_dirs_config() assert "vendor" not in result["Package Caches"] assert ".cache" not in result["Package Caches"] assert ".npm" in result["Package Caches"] # Other dirs still present @@ -830,7 +835,8 @@ def test_creates_new_custom_category(self, fs): dirs = ["special_folder", "another_folder"] """ fs.create_file(str(USER_CONFIG_PATH), contents=config_content) - result = load_user_dirs_config() + with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): + result = load_user_dirs_config() assert "My Custom Dirs" in result assert result["My Custom Dirs"] == {"special_folder", "another_folder"} @@ -843,7 +849,8 @@ def test_add_to_new_category_creates_it(self, fs): add = ["new_dir1", "new_dir2"] """ fs.create_file(str(USER_CONFIG_PATH), contents=config_content) - result = load_user_dirs_config() + with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): + result = load_user_dirs_config() assert "NewDirCategory" in result assert result["NewDirCategory"] == {"new_dir1", "new_dir2"} @@ -858,7 +865,8 @@ def test_combined_operations(self, fs): remove = ["b"] """ fs.create_file(str(USER_CONFIG_PATH), contents=config_content) - result = load_user_dirs_config() + with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): + result = load_user_dirs_config() assert result["TestDirCat"] == {"a", "c", "d"} def test_invalid_toml_returns_defaults(self, fs): @@ -867,7 +875,8 @@ def test_invalid_toml_returns_defaults(self, fs): config_content = "this is not valid [ toml {" fs.create_file(str(USER_CONFIG_PATH), contents=config_content) - result = load_user_dirs_config() + with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): + result = load_user_dirs_config() assert result == DEFAULT_SPECIAL_DIRS def test_does_not_mutate_default_special_dirs(self, fs): @@ -880,7 +889,8 @@ def test_does_not_mutate_default_special_dirs(self, fs): dirs = ["custom"] """ fs.create_file(str(USER_CONFIG_PATH), contents=config_content) - load_user_dirs_config() + with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): + load_user_dirs_config() assert DEFAULT_SPECIAL_DIRS["Node Modules"] == original_node_modules @@ -889,9 +899,10 @@ class TestLoadUserConfig: def test_returns_defaults_when_no_config_file(self, fs): """When config file doesn't exist, return default categories.""" - from zpace.config import load_user_categories_config, DEFAULT_CATEGORIES + from zpace.config import load_user_categories_config, DEFAULT_CATEGORIES, USER_CONFIG_PATH - result = load_user_categories_config() + with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): + result = load_user_categories_config() assert result == DEFAULT_CATEGORIES def test_returns_defaults_when_config_file_empty(self, fs): @@ -903,7 +914,8 @@ def test_returns_defaults_when_config_file_empty(self, fs): ) fs.create_file(str(USER_CONFIG_PATH), contents="") - result = load_user_categories_config() + with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): + result = load_user_categories_config() assert result == DEFAULT_CATEGORIES def test_extensions_replaces_category(self, fs): @@ -919,7 +931,8 @@ def test_extensions_replaces_category(self, fs): extensions = [".custom1", ".custom2"] """ fs.create_file(str(USER_CONFIG_PATH), contents=config_content) - result = load_user_categories_config() + with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): + result = load_user_categories_config() assert result["Pictures"] == {".custom1", ".custom2"} # Other categories should remain unchanged assert result["Documents"] == DEFAULT_CATEGORIES["Documents"] @@ -933,7 +946,8 @@ def test_add_extends_category(self, fs): add = [".sql", ".graphql"] """ fs.create_file(str(USER_CONFIG_PATH), contents=config_content) - result = load_user_categories_config() + with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): + result = load_user_categories_config() assert ".sql" in result["Code"] assert ".graphql" in result["Code"] assert ".py" in result["Code"] # Original extension still present @@ -947,7 +961,8 @@ def test_remove_removes_from_category(self, fs): remove = [".md", ".txt"] """ fs.create_file(str(USER_CONFIG_PATH), contents=config_content) - result = load_user_categories_config() + with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): + result = load_user_categories_config() assert ".md" not in result["Documents"] assert ".txt" not in result["Documents"] assert ".pdf" in result["Documents"] # Other extensions still present @@ -961,7 +976,8 @@ def test_creates_new_custom_category(self, fs): extensions = [".ttf", ".otf", ".woff"] """ fs.create_file(str(USER_CONFIG_PATH), contents=config_content) - result = load_user_categories_config() + with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): + result = load_user_categories_config() assert "Fonts" in result assert result["Fonts"] == {".ttf", ".otf", ".woff"} @@ -974,7 +990,8 @@ def test_add_to_new_category_creates_it(self, fs): add = [".new1", ".new2"] """ fs.create_file(str(USER_CONFIG_PATH), contents=config_content) - result = load_user_categories_config() + with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): + result = load_user_categories_config() assert "NewCategory" in result assert result["NewCategory"] == {".new1", ".new2"} @@ -989,7 +1006,8 @@ def test_combined_operations(self, fs): remove = [".b"] """ fs.create_file(str(USER_CONFIG_PATH), contents=config_content) - result = load_user_categories_config() + with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): + result = load_user_categories_config() # extensions sets base, add adds, remove removes assert result["TestCat"] == {".a", ".c", ".d"} @@ -1003,7 +1021,8 @@ def test_invalid_toml_returns_defaults(self, fs): config_content = "this is not valid [ toml {" fs.create_file(str(USER_CONFIG_PATH), contents=config_content) - result = load_user_categories_config() + with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): + result = load_user_categories_config() assert result == DEFAULT_CATEGORIES def test_does_not_mutate_default_categories(self, fs): @@ -1020,7 +1039,8 @@ def test_does_not_mutate_default_categories(self, fs): extensions = [".custom"] """ fs.create_file(str(USER_CONFIG_PATH), contents=config_content) - load_user_categories_config() + with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): + load_user_categories_config() assert DEFAULT_CATEGORIES["Pictures"] == original_pictures From 0efcafc7146aa039131ac72d5bddee1e311b98ec Mon Sep 17 00:00:00 2001 From: AzisK Date: Sat, 31 Jan 2026 21:50:01 +0100 Subject: [PATCH 06/13] fix: reload zpace.config in pyfakefs tests --- test_unit.py | 155 ++++++++++++++++++++++++--------------------------- 1 file changed, 73 insertions(+), 82 deletions(-) diff --git a/test_unit.py b/test_unit.py index 3bffff9..c006ed7 100644 --- a/test_unit.py +++ b/test_unit.py @@ -2,6 +2,7 @@ from pathlib import Path from unittest.mock import patch, MagicMock import sys +import importlib from zpace.core import ( calculate_dir_size, @@ -763,27 +764,37 @@ def test_unicode_filenames(self, mock_tqdm, fs): assert "ใ“ใ‚“ใซใกใฏ.doc" in all_files +@pytest.fixture +def fs_with_config(fs): + """Fixture that reloads zpace.config after pyfakefs is active. + + This ensures USER_CONFIG_PATH is computed with the fake Path.home(). + """ + import zpace.config + + importlib.reload(zpace.config) + yield fs + + class TestLoadUserDirsConfig: """Test user directory configuration loading from ~/.zpace.toml.""" - def test_returns_defaults_when_no_config_file(self, fs): + def test_returns_defaults_when_no_config_file(self, fs_with_config): """When config file doesn't exist, return default special dirs.""" - from zpace.config import load_user_dirs_config, DEFAULT_SPECIAL_DIRS, USER_CONFIG_PATH + from zpace.config import load_user_dirs_config, DEFAULT_SPECIAL_DIRS - with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): - result = load_user_dirs_config() + result = load_user_dirs_config() assert result == DEFAULT_SPECIAL_DIRS - def test_returns_defaults_when_config_file_empty(self, fs): + def test_returns_defaults_when_config_file_empty(self, fs_with_config): """Empty config file should return defaults.""" from zpace.config import load_user_dirs_config, DEFAULT_SPECIAL_DIRS, USER_CONFIG_PATH - fs.create_file(str(USER_CONFIG_PATH), contents="") - with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): - result = load_user_dirs_config() + fs_with_config.create_file(str(USER_CONFIG_PATH), contents="") + result = load_user_dirs_config() assert result == DEFAULT_SPECIAL_DIRS - def test_dirs_replaces_category(self, fs): + def test_dirs_replaces_category(self, fs_with_config): """Using 'dirs' should replace all dirs in a category.""" from zpace.config import load_user_dirs_config, USER_CONFIG_PATH @@ -791,12 +802,11 @@ def test_dirs_replaces_category(self, fs): [directories."Node Modules"] dirs = ["my_modules", "custom_modules"] """ - fs.create_file(str(USER_CONFIG_PATH), contents=config_content) - with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): - result = load_user_dirs_config() + fs_with_config.create_file(str(USER_CONFIG_PATH), contents=config_content) + result = load_user_dirs_config() assert result["Node Modules"] == {"my_modules", "custom_modules"} - def test_add_extends_category(self, fs): + def test_add_extends_category(self, fs_with_config): """Using 'add' should add dirs to existing category.""" from zpace.config import load_user_dirs_config, USER_CONFIG_PATH @@ -804,14 +814,13 @@ def test_add_extends_category(self, fs): [directories."Virtual Environments"] add = ["myenv", ".myenv"] """ - fs.create_file(str(USER_CONFIG_PATH), contents=config_content) - with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): - result = load_user_dirs_config() + fs_with_config.create_file(str(USER_CONFIG_PATH), contents=config_content) + result = load_user_dirs_config() assert "myenv" in result["Virtual Environments"] assert ".myenv" in result["Virtual Environments"] assert ".venv" in result["Virtual Environments"] # Original still present - def test_remove_removes_from_category(self, fs): + def test_remove_removes_from_category(self, fs_with_config): """Using 'remove' should remove dirs from category.""" from zpace.config import load_user_dirs_config, USER_CONFIG_PATH @@ -819,14 +828,13 @@ def test_remove_removes_from_category(self, fs): [directories."Package Caches"] remove = ["vendor", ".cache"] """ - fs.create_file(str(USER_CONFIG_PATH), contents=config_content) - with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): - result = load_user_dirs_config() + fs_with_config.create_file(str(USER_CONFIG_PATH), contents=config_content) + result = load_user_dirs_config() assert "vendor" not in result["Package Caches"] assert ".cache" not in result["Package Caches"] assert ".npm" in result["Package Caches"] # Other dirs still present - def test_creates_new_custom_category(self, fs): + def test_creates_new_custom_category(self, fs_with_config): """Should be able to create entirely new directory categories.""" from zpace.config import load_user_dirs_config, USER_CONFIG_PATH @@ -834,13 +842,12 @@ def test_creates_new_custom_category(self, fs): [directories."My Custom Dirs"] dirs = ["special_folder", "another_folder"] """ - fs.create_file(str(USER_CONFIG_PATH), contents=config_content) - with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): - result = load_user_dirs_config() + fs_with_config.create_file(str(USER_CONFIG_PATH), contents=config_content) + result = load_user_dirs_config() assert "My Custom Dirs" in result assert result["My Custom Dirs"] == {"special_folder", "another_folder"} - def test_add_to_new_category_creates_it(self, fs): + def test_add_to_new_category_creates_it(self, fs_with_config): """Using 'add' on non-existent category should create it.""" from zpace.config import load_user_dirs_config, USER_CONFIG_PATH @@ -848,13 +855,12 @@ def test_add_to_new_category_creates_it(self, fs): [directories.NewDirCategory] add = ["new_dir1", "new_dir2"] """ - fs.create_file(str(USER_CONFIG_PATH), contents=config_content) - with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): - result = load_user_dirs_config() + fs_with_config.create_file(str(USER_CONFIG_PATH), contents=config_content) + result = load_user_dirs_config() assert "NewDirCategory" in result assert result["NewDirCategory"] == {"new_dir1", "new_dir2"} - def test_combined_operations(self, fs): + def test_combined_operations(self, fs_with_config): """Test dirs + add + remove in same category.""" from zpace.config import load_user_dirs_config, USER_CONFIG_PATH @@ -864,22 +870,20 @@ def test_combined_operations(self, fs): add = ["d"] remove = ["b"] """ - fs.create_file(str(USER_CONFIG_PATH), contents=config_content) - with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): - result = load_user_dirs_config() + fs_with_config.create_file(str(USER_CONFIG_PATH), contents=config_content) + result = load_user_dirs_config() assert result["TestDirCat"] == {"a", "c", "d"} - def test_invalid_toml_returns_defaults(self, fs): + def test_invalid_toml_returns_defaults(self, fs_with_config): """Malformed TOML should return defaults gracefully.""" from zpace.config import load_user_dirs_config, DEFAULT_SPECIAL_DIRS, USER_CONFIG_PATH config_content = "this is not valid [ toml {" - fs.create_file(str(USER_CONFIG_PATH), contents=config_content) - with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): - result = load_user_dirs_config() + fs_with_config.create_file(str(USER_CONFIG_PATH), contents=config_content) + result = load_user_dirs_config() assert result == DEFAULT_SPECIAL_DIRS - def test_does_not_mutate_default_special_dirs(self, fs): + def test_does_not_mutate_default_special_dirs(self, fs_with_config): """Ensure loading config doesn't mutate DEFAULT_SPECIAL_DIRS.""" from zpace.config import load_user_dirs_config, DEFAULT_SPECIAL_DIRS, USER_CONFIG_PATH @@ -888,24 +892,22 @@ def test_does_not_mutate_default_special_dirs(self, fs): [directories."Node Modules"] dirs = ["custom"] """ - fs.create_file(str(USER_CONFIG_PATH), contents=config_content) - with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): - load_user_dirs_config() + fs_with_config.create_file(str(USER_CONFIG_PATH), contents=config_content) + load_user_dirs_config() assert DEFAULT_SPECIAL_DIRS["Node Modules"] == original_node_modules class TestLoadUserConfig: """Test user configuration loading from ~/.zpace.toml.""" - def test_returns_defaults_when_no_config_file(self, fs): + def test_returns_defaults_when_no_config_file(self, fs_with_config): """When config file doesn't exist, return default categories.""" - from zpace.config import load_user_categories_config, DEFAULT_CATEGORIES, USER_CONFIG_PATH + from zpace.config import load_user_categories_config, DEFAULT_CATEGORIES - with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): - result = load_user_categories_config() + result = load_user_categories_config() assert result == DEFAULT_CATEGORIES - def test_returns_defaults_when_config_file_empty(self, fs): + def test_returns_defaults_when_config_file_empty(self, fs_with_config): """Empty config file should return defaults.""" from zpace.config import ( load_user_categories_config, @@ -913,12 +915,11 @@ def test_returns_defaults_when_config_file_empty(self, fs): USER_CONFIG_PATH, ) - fs.create_file(str(USER_CONFIG_PATH), contents="") - with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): - result = load_user_categories_config() + fs_with_config.create_file(str(USER_CONFIG_PATH), contents="") + result = load_user_categories_config() assert result == DEFAULT_CATEGORIES - def test_extensions_replaces_category(self, fs): + def test_extensions_replaces_category(self, fs_with_config): """Using 'extensions' should replace all extensions in a category.""" from zpace.config import ( load_user_categories_config, @@ -930,14 +931,12 @@ def test_extensions_replaces_category(self, fs): [categories.Pictures] extensions = [".custom1", ".custom2"] """ - fs.create_file(str(USER_CONFIG_PATH), contents=config_content) - with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): - result = load_user_categories_config() + fs_with_config.create_file(str(USER_CONFIG_PATH), contents=config_content) + result = load_user_categories_config() assert result["Pictures"] == {".custom1", ".custom2"} - # Other categories should remain unchanged assert result["Documents"] == DEFAULT_CATEGORIES["Documents"] - def test_add_extends_category(self, fs): + def test_add_extends_category(self, fs_with_config): """Using 'add' should add extensions to existing category.""" from zpace.config import load_user_categories_config, USER_CONFIG_PATH @@ -945,14 +944,13 @@ def test_add_extends_category(self, fs): [categories.Code] add = [".sql", ".graphql"] """ - fs.create_file(str(USER_CONFIG_PATH), contents=config_content) - with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): - result = load_user_categories_config() + fs_with_config.create_file(str(USER_CONFIG_PATH), contents=config_content) + result = load_user_categories_config() assert ".sql" in result["Code"] assert ".graphql" in result["Code"] assert ".py" in result["Code"] # Original extension still present - def test_remove_removes_from_category(self, fs): + def test_remove_removes_from_category(self, fs_with_config): """Using 'remove' should remove extensions from category.""" from zpace.config import load_user_categories_config, USER_CONFIG_PATH @@ -960,14 +958,13 @@ def test_remove_removes_from_category(self, fs): [categories.Documents] remove = [".md", ".txt"] """ - fs.create_file(str(USER_CONFIG_PATH), contents=config_content) - with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): - result = load_user_categories_config() + fs_with_config.create_file(str(USER_CONFIG_PATH), contents=config_content) + result = load_user_categories_config() assert ".md" not in result["Documents"] assert ".txt" not in result["Documents"] assert ".pdf" in result["Documents"] # Other extensions still present - def test_creates_new_custom_category(self, fs): + def test_creates_new_custom_category(self, fs_with_config): """Should be able to create entirely new categories.""" from zpace.config import load_user_categories_config, USER_CONFIG_PATH @@ -975,13 +972,12 @@ def test_creates_new_custom_category(self, fs): [categories.Fonts] extensions = [".ttf", ".otf", ".woff"] """ - fs.create_file(str(USER_CONFIG_PATH), contents=config_content) - with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): - result = load_user_categories_config() + fs_with_config.create_file(str(USER_CONFIG_PATH), contents=config_content) + result = load_user_categories_config() assert "Fonts" in result assert result["Fonts"] == {".ttf", ".otf", ".woff"} - def test_add_to_new_category_creates_it(self, fs): + def test_add_to_new_category_creates_it(self, fs_with_config): """Using 'add' on non-existent category should create it.""" from zpace.config import load_user_categories_config, USER_CONFIG_PATH @@ -989,13 +985,12 @@ def test_add_to_new_category_creates_it(self, fs): [categories.NewCategory] add = [".new1", ".new2"] """ - fs.create_file(str(USER_CONFIG_PATH), contents=config_content) - with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): - result = load_user_categories_config() + fs_with_config.create_file(str(USER_CONFIG_PATH), contents=config_content) + result = load_user_categories_config() assert "NewCategory" in result assert result["NewCategory"] == {".new1", ".new2"} - def test_combined_operations(self, fs): + def test_combined_operations(self, fs_with_config): """Test extensions + add + remove in same category.""" from zpace.config import load_user_categories_config, USER_CONFIG_PATH @@ -1005,13 +1000,11 @@ def test_combined_operations(self, fs): add = [".d"] remove = [".b"] """ - fs.create_file(str(USER_CONFIG_PATH), contents=config_content) - with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): - result = load_user_categories_config() - # extensions sets base, add adds, remove removes + fs_with_config.create_file(str(USER_CONFIG_PATH), contents=config_content) + result = load_user_categories_config() assert result["TestCat"] == {".a", ".c", ".d"} - def test_invalid_toml_returns_defaults(self, fs): + def test_invalid_toml_returns_defaults(self, fs_with_config): """Malformed TOML should return defaults gracefully.""" from zpace.config import ( load_user_categories_config, @@ -1020,12 +1013,11 @@ def test_invalid_toml_returns_defaults(self, fs): ) config_content = "this is not valid [ toml {" - fs.create_file(str(USER_CONFIG_PATH), contents=config_content) - with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): - result = load_user_categories_config() + fs_with_config.create_file(str(USER_CONFIG_PATH), contents=config_content) + result = load_user_categories_config() assert result == DEFAULT_CATEGORIES - def test_does_not_mutate_default_categories(self, fs): + def test_does_not_mutate_default_categories(self, fs_with_config): """Ensure loading config doesn't mutate DEFAULT_CATEGORIES.""" from zpace.config import ( load_user_categories_config, @@ -1038,9 +1030,8 @@ def test_does_not_mutate_default_categories(self, fs): [categories.Pictures] extensions = [".custom"] """ - fs.create_file(str(USER_CONFIG_PATH), contents=config_content) - with patch("zpace.config.USER_CONFIG_PATH", USER_CONFIG_PATH): - load_user_categories_config() + fs_with_config.create_file(str(USER_CONFIG_PATH), contents=config_content) + load_user_categories_config() assert DEFAULT_CATEGORIES["Pictures"] == original_pictures From 9c5789f711ec0de3c836e1298102c9c2ad2b1e2d Mon Sep 17 00:00:00 2001 From: AzisK Date: Sat, 31 Jan 2026 22:04:15 +0100 Subject: [PATCH 07/13] ci: split dependency groups and remove pre-commit from CI - Split dev dependencies into test, lint, and dev groups - test: pytest, pyfakefs, ruff (for test job) - lint: mypy, types-tqdm (for mypy job) - dev: includes both + pre-commit (for local development) - Remove tests for Python 3.8 --- .github/workflows/tests.yml | 5 ++--- pyproject.toml | 12 +++++++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4703417..9114f09 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,7 +22,6 @@ jobs: os: [ubuntu-slim, macos-latest, windows-latest] python-version: [ - "3.8", "3.9", "3.10", "3.11", @@ -47,7 +46,7 @@ jobs: enable-cache: true - name: Install dependencies - run: uv sync + run: uv sync --group test - name: Run tests run: uv run pytest -v @@ -77,7 +76,7 @@ jobs: enable-cache: true - name: Install dependencies - run: uv sync + run: uv sync --group lint - name: Type check with mypy run: uv run mypy . diff --git a/pyproject.toml b/pyproject.toml index 6a1b07d..f5a5824 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,14 +37,20 @@ dependencies = [ zpace = "zpace.main:main" [dependency-groups] -dev = [ - "mypy>=1.18.2", - "pre-commit>=4.3.0", +test = [ "pyfakefs>=5.10.2", "pytest>=8.4.2", "ruff>=0.14.1", +] +lint = [ + "mypy>=1.18.2", "types-tqdm>=4.67.0.20250809", ] +dev = [ + {include-group = "test"}, + {include-group = "lint"}, + "pre-commit>=4.3.0", +] [tool.ruff] line-length = 100 From 5d3df92ac6db47d42fb839ce8a39d8314dd2e64b Mon Sep 17 00:00:00 2001 From: AzisK Date: Sat, 31 Jan 2026 22:11:30 +0100 Subject: [PATCH 08/13] Update tests.yml --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9114f09..ea5085d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -46,7 +46,7 @@ jobs: enable-cache: true - name: Install dependencies - run: uv sync --group test + run: uv sync --only-group test - name: Run tests run: uv run pytest -v @@ -76,7 +76,7 @@ jobs: enable-cache: true - name: Install dependencies - run: uv sync --group lint + run: uv sync --only-group lint - name: Type check with mypy run: uv run mypy . From 243ec713fdb0174828fe923d4fbe43bf2fb115ec Mon Sep 17 00:00:00 2001 From: AzisK Date: Sat, 31 Jan 2026 22:20:51 +0100 Subject: [PATCH 09/13] Fix tests --- .github/workflows/tests.yml | 12 ++++++------ uv.lock | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ea5085d..71440b3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -49,18 +49,18 @@ jobs: run: uv sync --only-group test - name: Run tests - run: uv run pytest -v + run: uv run --no-sync pytest -v - name: Run linter - run: uv run ruff check . + run: uv run --no-sync ruff check . - name: Check formatting - run: uv run ruff format --check . + run: uv run --no-sync ruff format --check . - name: Test CLI run: | - uv run python main.py --help - uv run python main.py . -n 5 -m 1000 + uv run --no-sync python main.py --help + uv run --no-sync python main.py . -n 5 -m 1000 mypy: name: Type Check @@ -79,7 +79,7 @@ jobs: run: uv sync --only-group lint - name: Type check with mypy - run: uv run mypy . + run: uv run --no-sync mypy . post-dog: if: github.event_name == 'pull_request' diff --git a/uv.lock b/uv.lock index f4f2c7b..b4095d2 100644 --- a/uv.lock +++ b/uv.lock @@ -522,6 +522,15 @@ dev = [ { name = "ruff" }, { name = "types-tqdm" }, ] +lint = [ + { name = "mypy" }, + { name = "types-tqdm" }, +] +test = [ + { name = "pyfakefs" }, + { name = "pytest" }, + { name = "ruff" }, +] [package.metadata] requires-dist = [ @@ -539,3 +548,12 @@ dev = [ { name = "ruff", specifier = ">=0.14.1" }, { name = "types-tqdm", specifier = ">=4.67.0.20250809" }, ] +lint = [ + { name = "mypy", specifier = ">=1.18.2" }, + { name = "types-tqdm", specifier = ">=4.67.0.20250809" }, +] +test = [ + { name = "pyfakefs", specifier = ">=5.10.2" }, + { name = "pytest", specifier = ">=8.4.2" }, + { name = "ruff", specifier = ">=0.14.1" }, +] From fea577b4936b8f00747286444bd360e4fb499c06 Mon Sep 17 00:00:00 2001 From: AzisK Date: Sat, 31 Jan 2026 22:28:36 +0100 Subject: [PATCH 10/13] Add the main dependency to tests and lints --- .github/workflows/tests.yml | 4 ++-- pyproject.toml | 1 - uv.lock | 11 ----------- 3 files changed, 2 insertions(+), 14 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 71440b3..3ff5855 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -46,7 +46,7 @@ jobs: enable-cache: true - name: Install dependencies - run: uv sync --only-group test + run: uv sync --no-default-groups --group test - name: Run tests run: uv run --no-sync pytest -v @@ -76,7 +76,7 @@ jobs: enable-cache: true - name: Install dependencies - run: uv sync --only-group lint + run: uv sync --no-default-groups --group lint - name: Type check with mypy run: uv run --no-sync mypy . diff --git a/pyproject.toml b/pyproject.toml index f5a5824..e6ba550 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,6 @@ classifiers = [ ] dependencies = [ - "argparse>=1.4.0", "tqdm>=4.67.1", "tomli>=2.0.0; python_version < '3.11'", ] diff --git a/uv.lock b/uv.lock index b4095d2..1ffcd32 100644 --- a/uv.lock +++ b/uv.lock @@ -6,15 +6,6 @@ resolution-markers = [ "python_full_version < '3.10'", ] -[[package]] -name = "argparse" -version = "1.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/dd/e617cfc3f6210ae183374cd9f6a26b20514bbb5a792af97949c5aacddf0f/argparse-1.4.0.tar.gz", hash = "sha256:62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4", size = 70508, upload-time = "2015-09-12T20:22:16.217Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/94/3af39d34be01a24a6e65433d19e107099374224905f1e0cc6bbe1fd22a2f/argparse-1.4.0-py2.py3-none-any.whl", hash = "sha256:c31647edb69fd3d465a847ea3157d37bed1f95f19760b11a47aa91c04b666314", size = 23000, upload-time = "2015-09-14T16:03:16.137Z" }, -] - [[package]] name = "cfgv" version = "3.4.0" @@ -508,7 +499,6 @@ name = "zpace" version = "0.4.5" source = { editable = "." } dependencies = [ - { name = "argparse" }, { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "tqdm" }, ] @@ -534,7 +524,6 @@ test = [ [package.metadata] requires-dist = [ - { name = "argparse", specifier = ">=1.4.0" }, { name = "tomli", marker = "python_full_version < '3.11'", specifier = ">=2.0.0" }, { name = "tqdm", specifier = ">=4.67.1" }, ] From 22ee2b39b5418c08701a5f7c74a679262145ae6f Mon Sep 17 00:00:00 2001 From: AzisK Date: Sat, 31 Jan 2026 22:35:50 +0100 Subject: [PATCH 11/13] Handle shutils on Windows pypy --- zpace/main.py | 7 +++++-- zpace/utils.py | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/zpace/main.py b/zpace/main.py index feb54d5..4e69ee1 100644 --- a/zpace/main.py +++ b/zpace/main.py @@ -118,8 +118,11 @@ def main(): print("\nDISK USAGE") print("=" * terminal_width) - print(f" Free: {format_size(free)} / {format_size(total)}") - print(f" Used: {format_size(used)} ({used / total * 100:.1f}%)") + if total > 0: + print(f" Free: {format_size(free)} / {format_size(total)}") + print(f" Used: {format_size(used)} ({used / total * 100:.1f}%)") + else: + print(" (disk usage unavailable on this platform)") # Check Trash size trash_path = get_trash_path() diff --git a/zpace/utils.py b/zpace/utils.py index 8969af2..0767cee 100644 --- a/zpace/utils.py +++ b/zpace/utils.py @@ -6,8 +6,11 @@ def get_disk_usage(path: str): - total, used, free = shutil.disk_usage(path) - return total, used, free + try: + total, used, free = shutil.disk_usage(path) + return total, used, free + except (AttributeError, OSError): + return 0, 0, 0 def format_size(size: float) -> str: From 8645bdcade6188a4640af36faaf587ac1072feee Mon Sep 17 00:00:00 2001 From: AzisK Date: Sat, 31 Jan 2026 22:44:30 +0100 Subject: [PATCH 12/13] Exclude tests from linter --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index e6ba550..85092ea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,5 +63,8 @@ indent-style = "space" requires = ["hatchling"] build-backend = "hatchling.build" +[tool.mypy] +exclude = ["test_.*\\.py$"] + [tool.hatch.build.targets.wheel] include = ["zpace"] From 81d0a0e5b661003ddace033f917bd7f05a259169 Mon Sep 17 00:00:00 2001 From: AzisK Date: Sat, 31 Jan 2026 22:51:36 +0100 Subject: [PATCH 13/13] Update CHANGELOG.md --- CHANGELOG.md | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 658ecf4..817c1e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,26 @@ This project adheres to [Semantic Versioning](https://semver.org/). ## [Unreleased] -### Tests +### Fixed + +- PyPy on Windows now works gracefully when `shutil.disk_usage` is unavailable (displays message instead of crashing) +- Fixed flaky user config tests by reloading `zpace.config` module inside pyfakefs context + +### Changed + +- Dropped Python 3.8 support (already EOL since October 2024) +- Removed `argparse` from dependencies (already in standard library) + +### CI/CD -- Fixed flaky user config tests across Python versions by using local imports for pyfakefs compatibility +- Renamed workflows: `ci.yml` โ†’ `tests.yml`, `publish.yml` โ†’ `publish-pypi.yml` +- Simplified PyPI publish using `uv build`/`uv publish` instead of pypa action +- Switched to `ubuntu-slim` runners where Docker isn't needed +- Replaced explicit Python install with `UV_PYTHON` env var +- Updated `actions/checkout` to v6 +- Split dev dependencies into `test`, `lint`, and `dev` groups +- Excluded test files from mypy type checking +- Updated README badge URL ## [0.4.5] - 2026-01-27