mirror of
https://github.com/Second-Hand-Friends/kleinanzeigen-bot.git
synced 2026-10-09 15:21:20 +02:00
fix: use PDM interpreter in generation and formatting scripts (#1277)
## ℹ️ Description `pdm run generate-artifacts` could fail while creating the default config when the shell's `python` differed from PDM's selected interpreter. For example, Python 3.13 on PATH loaded dependencies from `__pypackages__/3.12`, causing `ModuleNotFoundError: pydantic_core._pydantic_core`. Use PDM's interpreter consistently for config generation and Python post-formatting. This fix is independent of login-entry PR #1276. ## 📋 Changes Summary - Replace the shell-based config generator with a Python script that invokes the CLI through `sys.executable`. - Split `format:py` into a PDM composite so the Python post-formatter also uses PDM's interpreter. - Add an integration regression test that regenerates the exact config snapshot with an unusable Python executable on PATH. - Preserve supported Python versions, CLI generation behavior, and generated artifacts. No dependency or user-configuration changes; no documentation updates needed. Validation: `pdm run format`, `pdm run lint`, `pdm run test`, `pdm run generate-artifacts`, and generated-artifact verification passed. The generator was also verified locally with PDM selecting Python 3.12 while shell Python resolves to 3.13. ### ⚙️ Type of Change - [x] 🐞 Bug fix (non-breaking change which fixes an issue) - [ ] ✨ New feature (adds new functionality without breaking existing usage) - [ ] 💥 Breaking change (changes that might break existing user setups, scripts, or configurations) ## ✅ Checklist - [x] I have reviewed my changes to ensure they meet the project's standards. - [x] I have tested my changes and ensured that all tests pass (`pdm run test`). - [x] I have formatted the code (`pdm run format`). - [x] I have verified that linting passes (`pdm run lint`). - [x] I have updated documentation where necessary (no documentation changes required). By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Chores** * Improved the configuration generation workflow by using the active Python environment directly. * Split Python formatting into separate automatic formatting and follow-up processing steps for more consistent results. * **Tests** * Added integration coverage to verify configuration generation works reliably even when conflicting Python executables are present on the system path. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
3 files changed
+89
-2
No files matched your search
+4
-2
@@ -93,7 +93,7 @@ debug = "python -m pdb -m kleinanzeigen_bot"
|
||||
|
||||
# build & packaging
|
||||
generate-schemas = "python scripts/generate_schemas.py"
|
||||
generate-config = { shell = "python -c \"from pathlib import Path; Path('docs/config.default.yaml').unlink(missing_ok=True)\" && python -m kleinanzeigen_bot --config docs/config.default.yaml create-config" }
|
||||
generate-config = "python scripts/generate_config.py"
|
||||
generate-readme-commands = "python scripts/generate_readme_commands.py"
|
||||
generate-artifacts = { composite = ["generate-schemas", "generate-config", "generate-readme-commands"] }
|
||||
compile.cmd = "python -O -m PyInstaller pyinstaller.spec --clean --workpath .temp"
|
||||
@@ -107,7 +107,9 @@ deps = "pdm list --fields name,version,groups"
|
||||
|
||||
# format & lint
|
||||
format = { composite = ["format:py", "format:yaml"] }
|
||||
"format:py" = { shell = "autopep8 --recursive --in-place scripts src tests --verbose && python scripts/post_autopep8.py scripts src tests" }
|
||||
"format:py" = { composite = ["format:autopep8", "format:post-autopep8"] }
|
||||
"format:autopep8" = "autopep8 --recursive --in-place scripts src tests --verbose"
|
||||
"format:post-autopep8" = "python scripts/post_autopep8.py scripts src tests"
|
||||
"format:yaml" = "yamlfix scripts/ src/ tests/"
|
||||
|
||||
lint = { composite = ["lint:ruff", "lint:mypy", "lint:pyright", "lint:actions"] }
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# SPDX-FileCopyrightText: © Jens Bergmann and contributors
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
# SPDX-ArtifactOfProjectHomePage: https://github.com/Second-Hand-Friends/kleinanzeigen-bot/
|
||||
"""Regenerate the default config using the interpreter selected by PDM."""
|
||||
|
||||
import subprocess # noqa: S404
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Replace the default config snapshot through the CLI with the current interpreter."""
|
||||
repo_root = Path(__file__).resolve().parent.parent
|
||||
config_path = repo_root / "docs/config.default.yaml"
|
||||
config_path.unlink(missing_ok = True)
|
||||
subprocess.run( # noqa: S603 trusted, static command arguments
|
||||
[sys.executable, "-m", "kleinanzeigen_bot", "--config", str(config_path), "create-config"],
|
||||
cwd = repo_root,
|
||||
check = True,
|
||||
timeout = 60,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,60 @@
|
||||
# SPDX-FileCopyrightText: © Jens Bergmann and contributors
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
# SPDX-ArtifactOfProjectHomePage: https://github.com/Second-Hand-Friends/kleinanzeigen-bot/
|
||||
"""Regression coverage for config generation with a different Python on PATH."""
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess # noqa: S404
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.itest
|
||||
def test_generate_config_uses_current_interpreter(tmp_path:Path) -> None:
|
||||
repo_root = Path(__file__).resolve().parent.parent.parent
|
||||
pdm_path = shutil.which("pdm")
|
||||
assert pdm_path is not None, "PDM is required to test the generate-config task"
|
||||
# Expose test dependencies only to the task; PDM itself may use a different Python.
|
||||
python_path = os.pathsep.join(str(Path(path).resolve()) for path in sys.path if path)
|
||||
(tmp_path / "pyproject.toml").write_text(
|
||||
(repo_root / "pyproject.toml").read_text(encoding = "utf-8")
|
||||
+ f"\n[tool.pdm.scripts._]\nenv = {{ PYTHONPATH = {json.dumps(python_path)} }}\n",
|
||||
encoding = "utf-8",
|
||||
)
|
||||
(tmp_path / ".pdm-python").write_text(sys.executable, encoding = "utf-8")
|
||||
(tmp_path / "pdm.toml").write_text("[python]\nuse_venv = false\n", encoding = "utf-8")
|
||||
script_path = tmp_path / "scripts/generate_config.py"
|
||||
script_path.parent.mkdir()
|
||||
script_path.write_bytes((repo_root / "scripts/generate_config.py").read_bytes())
|
||||
config_path = tmp_path / "docs/config.default.yaml"
|
||||
config_path.parent.mkdir()
|
||||
config_path.write_text("stale config", encoding = "utf-8")
|
||||
|
||||
# A PATH-resolved Python cannot run; the selected interpreter must be used instead.
|
||||
bin_path = tmp_path / "bin"
|
||||
bin_path.mkdir()
|
||||
for name in ("python", "python.exe"):
|
||||
wrong_python = bin_path / name
|
||||
wrong_python.write_text("invalid executable", encoding = "utf-8")
|
||||
wrong_python.chmod(0o755)
|
||||
|
||||
result = subprocess.run( # noqa: S603 trusted test command
|
||||
[pdm_path, "run", "generate-config"],
|
||||
env = {
|
||||
**{key: value for key, value in os.environ.items() if key != "PYTHONPATH"},
|
||||
"PATH": str(bin_path),
|
||||
"PDM_CHECK_UPDATE": "false",
|
||||
},
|
||||
cwd = tmp_path,
|
||||
check = False,
|
||||
capture_output = True,
|
||||
text = True,
|
||||
timeout = 60,
|
||||
)
|
||||
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert config_path.read_bytes() == (repo_root / "docs/config.default.yaml").read_bytes()
|
||||
Reference in new issue
Block a user