From 5d3272950fb32ce8486f2bcc9373093ee5a1898e Mon Sep 17 00:00:00 2001 From: yossi <54272821+Apakottur@users.noreply.github.com> Date: Fri, 10 Jul 2026 14:03:16 +0000 Subject: [PATCH 1/2] Fix empty wheel: adopt src/shpyx layout The uv migration (#50) moved the package from `shpyx/` to a flat `src/` directory but left `packages = ["shpyx"]`, so hatchling found no package and 0.0.35 shipped only dist-info with no importable code. Adopt the standard src-layout (`src/shpyx/`), which hatchling auto-detects from the project name (no `packages` config needed). Internal imports are now relative, and tests import the installed `shpyx` package rather than `src`, so a broken wheel would fail the test run instead of silently releasing. Bumps to 0.0.36 to release the fix. Co-Authored-By: Claude Opus 4.8 (1M context) --- linters/ruff.toml | 2 +- pyproject.toml | 5 +---- src/__init__.py | 12 ------------ src/shpyx/__init__.py | 12 ++++++++++++ src/{ => shpyx}/errors.py | 2 +- src/{ => shpyx}/py.typed | 0 src/{ => shpyx}/result.py | 0 src/{ => shpyx}/runner.py | 4 ++-- tests/fake_proc.py | 2 +- tests/pytest.ini | 2 +- tests/test_runner.py | 4 ++-- uv.lock | 2 +- 12 files changed, 22 insertions(+), 25 deletions(-) delete mode 100644 src/__init__.py create mode 100644 src/shpyx/__init__.py rename src/{ => shpyx}/errors.py (94%) rename src/{ => shpyx}/py.typed (100%) rename src/{ => shpyx}/result.py (100%) rename src/{ => shpyx}/runner.py (99%) diff --git a/linters/ruff.toml b/linters/ruff.toml index 1e49151..cb652fc 100644 --- a/linters/ruff.toml +++ b/linters/ruff.toml @@ -102,7 +102,7 @@ ignore = [ [lint.isort] # Import sorting configuration. split-on-trailing-comma = false -known-first-party = ["src"] +known-first-party = ["shpyx"] combine-as-imports = true [lint.pydocstyle] diff --git a/pyproject.toml b/pyproject.toml index 874f5a1..adf7ff6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ requires = ["hatchling"] [project] name = "shpyx" -version = "0.0.35" +version = "0.0.36" description = "Run shell commands in Python" authors = [{name = "Yossi Rozantsev"}] license = {text = "MIT"} @@ -34,8 +34,5 @@ homepage = "https://github.com/Apakottur/shpyx" repository = "https://github.com/Apakottur/shpyx" documentation = "https://github.com/Apakottur/shpyx" -[tool.hatch.build.targets.wheel] -packages = ["shpyx"] - [tool.uv] package = true diff --git a/src/__init__.py b/src/__init__.py deleted file mode 100644 index 72cf230..0000000 --- a/src/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -from src.errors import ShpyxInternalError, ShpyxOSNotSupportedError, ShpyxVerificationError -from src.result import ShellCmdResult -from src.runner import Runner, run - -__all__ = [ - "Runner", - "ShellCmdResult", - "ShpyxInternalError", - "ShpyxOSNotSupportedError", - "ShpyxVerificationError", - "run", -] diff --git a/src/shpyx/__init__.py b/src/shpyx/__init__.py new file mode 100644 index 0000000..985195b --- /dev/null +++ b/src/shpyx/__init__.py @@ -0,0 +1,12 @@ +from .errors import ShpyxInternalError, ShpyxOSNotSupportedError, ShpyxVerificationError +from .result import ShellCmdResult +from .runner import Runner, run + +__all__ = [ + "Runner", + "ShellCmdResult", + "ShpyxInternalError", + "ShpyxOSNotSupportedError", + "ShpyxVerificationError", + "run", +] diff --git a/src/errors.py b/src/shpyx/errors.py similarity index 94% rename from src/errors.py rename to src/shpyx/errors.py index 2118751..5f89098 100644 --- a/src/errors.py +++ b/src/shpyx/errors.py @@ -1,4 +1,4 @@ -from src.result import ShellCmdResult +from .result import ShellCmdResult class ShpyxError(Exception): diff --git a/src/py.typed b/src/shpyx/py.typed similarity index 100% rename from src/py.typed rename to src/shpyx/py.typed diff --git a/src/result.py b/src/shpyx/result.py similarity index 100% rename from src/result.py rename to src/shpyx/result.py diff --git a/src/runner.py b/src/shpyx/runner.py similarity index 99% rename from src/runner.py rename to src/shpyx/runner.py index 39325e3..6762845 100644 --- a/src/runner.py +++ b/src/shpyx/runner.py @@ -12,8 +12,8 @@ from collections.abc import Callable from typing import TYPE_CHECKING -from src.errors import ShpyxInternalError, ShpyxOSNotSupportedError, ShpyxVerificationError -from src.result import ShellCmdResult +from .errors import ShpyxInternalError, ShpyxOSNotSupportedError, ShpyxVerificationError +from .result import ShellCmdResult if TYPE_CHECKING: from pathlib import Path diff --git a/tests/fake_proc.py b/tests/fake_proc.py index 0f95700..a2b36df 100644 --- a/tests/fake_proc.py +++ b/tests/fake_proc.py @@ -40,4 +40,4 @@ def patch_fake_proc( stderr_chunks: list[bytes], ) -> None: proc = _FakeProc(stdout_chunks, stderr_chunks or []) - mocker.patch("src.runner.subprocess.Popen", return_value=proc) + mocker.patch("shpyx.runner.subprocess.Popen", return_value=proc) diff --git a/tests/pytest.ini b/tests/pytest.ini index f4bc6b5..f0f4f32 100644 --- a/tests/pytest.ini +++ b/tests/pytest.ini @@ -13,7 +13,7 @@ addopts = # Coverage configuration --cov-branch --cov-config=tests/pytest.ini - --cov=src + --cov=shpyx --cov-report=term-missing:skip-covered --cov-fail-under=100 --no-cov-on-fail diff --git a/tests/test_runner.py b/tests/test_runner.py index 9e5df3d..140c079 100644 --- a/tests/test_runner.py +++ b/tests/test_runner.py @@ -21,7 +21,7 @@ import pytest import pytest_mock -import src as shpyx +import shpyx # Platform OS. _SYSTEM = platform.system() @@ -196,7 +196,7 @@ def _popen(*args: Any, **kwargs: Any) -> Any: p.stderr = None return p - mocker.patch("src.runner.subprocess.Popen", _popen) + mocker.patch("shpyx.runner.subprocess.Popen", _popen) with pytest.raises(shpyx.ShpyxInternalError) as exc: shpyx.run("echo 1") diff --git a/uv.lock b/uv.lock index aa29bb0..562a6aa 100644 --- a/uv.lock +++ b/uv.lock @@ -590,7 +590,7 @@ wheels = [ [[package]] name = "shpyx" -version = "0.0.35" +version = "0.0.36" source = { editable = "." } dependencies = [ { name = "mypy" }, From 8a719fcf7f5027bc23e939ebc3ddb341cf92d6ce Mon Sep 17 00:00:00 2001 From: yossi <54272821+Apakottur@users.noreply.github.com> Date: Fri, 10 Jul 2026 14:07:02 +0000 Subject: [PATCH 2/2] Use absolute imports, ban relative imports in ruff Switch package-internal imports to absolute (`from shpyx.errors import ...`) and set flake8-tidy-imports `ban-relative-imports = "all"` so ruff enforces it (TID252 only bans parent-relative imports by default, which is why `from .x` was not flagged). Co-Authored-By: Claude Opus 4.8 (1M context) --- linters/ruff.toml | 4 ++++ src/shpyx/__init__.py | 6 +++--- src/shpyx/errors.py | 2 +- src/shpyx/runner.py | 4 ++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/linters/ruff.toml b/linters/ruff.toml index cb652fc..8d998f9 100644 --- a/linters/ruff.toml +++ b/linters/ruff.toml @@ -117,6 +117,10 @@ classmethod-decorators = ["classmethod", "pydantic.field_validator"] # Ignore list of builtin names. builtins-ignorelist = ["id"] +[lint.flake8-tidy-imports] +# Enforce absolute imports (TID252 only bans parent-relative by default). +ban-relative-imports = "all" + # Per file ignores. [lint.per-file-ignores] "tests/*" = [ diff --git a/src/shpyx/__init__.py b/src/shpyx/__init__.py index 985195b..12ebcd0 100644 --- a/src/shpyx/__init__.py +++ b/src/shpyx/__init__.py @@ -1,6 +1,6 @@ -from .errors import ShpyxInternalError, ShpyxOSNotSupportedError, ShpyxVerificationError -from .result import ShellCmdResult -from .runner import Runner, run +from shpyx.errors import ShpyxInternalError, ShpyxOSNotSupportedError, ShpyxVerificationError +from shpyx.result import ShellCmdResult +from shpyx.runner import Runner, run __all__ = [ "Runner", diff --git a/src/shpyx/errors.py b/src/shpyx/errors.py index 5f89098..01f4c44 100644 --- a/src/shpyx/errors.py +++ b/src/shpyx/errors.py @@ -1,4 +1,4 @@ -from .result import ShellCmdResult +from shpyx.result import ShellCmdResult class ShpyxError(Exception): diff --git a/src/shpyx/runner.py b/src/shpyx/runner.py index 6762845..7d4b0fb 100644 --- a/src/shpyx/runner.py +++ b/src/shpyx/runner.py @@ -12,8 +12,8 @@ from collections.abc import Callable from typing import TYPE_CHECKING -from .errors import ShpyxInternalError, ShpyxOSNotSupportedError, ShpyxVerificationError -from .result import ShellCmdResult +from shpyx.errors import ShpyxInternalError, ShpyxOSNotSupportedError, ShpyxVerificationError +from shpyx.result import ShellCmdResult if TYPE_CHECKING: from pathlib import Path