Align Nix Python environment and add lazy CLI import

- Switch flake package and dev shell to Python 3.11 to match pyproject
- Ensure the python-with-deps environment is preferred on PATH in nix develop
- Introduce a lightweight pkgmgr __init__ with lazy loading of pkgmgr.cli
- Avoid pulling in CLI/config dependencies on plain `import pkgmgr`, fixing
  unit test imports and PyYAML availability in the Nix test containers

https://chatgpt.com/share/693a9723-27ac-800f-a6c2-c1bcc91b7dff
This commit is contained in:
Kevin Veen-Birkenbach
2025-12-11 11:04:12 +01:00
parent 0f74907f82
commit 644b2b8fa0
2 changed files with 47 additions and 5 deletions

View File

@@ -25,11 +25,13 @@
##########################################################################
packages = forAllSystems (system:
let
pkgs = nixpkgs.legacyPackages.${system};
pkgs = nixpkgs.legacyPackages.${system};
# Single source of truth: "python3" from this nixpkgs revision
python = pkgs.python3;
pyPkgs = python.pkgs;
# Single source of truth for pkgmgr: Python 3.11
# - Matches pyproject.toml: requires-python = ">=3.11"
# - Uses python311Packages so that PyYAML etc. are available
python = pkgs.python311;
pyPkgs = pkgs.python311Packages;
in
rec {
pkgmgr = pyPkgs.buildPythonApplication {
@@ -74,7 +76,8 @@
if pkgs ? ansible-core then pkgs.ansible-core
else pkgs.ansible;
python = pkgs.python3;
# Use the same Python version as the package (3.11)
python = pkgs.python311;
pythonWithDeps = python.withPackages (ps: [
ps.pip
@@ -90,6 +93,9 @@
];
shellHook = ''
# Ensure our Python with dependencies is preferred on PATH
export PATH=${pythonWithDeps}/bin:$PATH
# Ensure src/ layout is importable:
# pkgmgr lives in ./src/pkgmgr
export PYTHONPATH="$PWD/src:${PYTHONPATH:-}"

View File

@@ -0,0 +1,36 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Top-level pkgmgr package.
We deliberately avoid importing heavy submodules (like the CLI)
on import to keep unit tests fast and to not require optional
dependencies (like PyYAML) unless they are actually used.
Accessing ``pkgmgr.cli`` will load the CLI module lazily via
``__getattr__``. This keeps patterns like
from pkgmgr import cli
working as expected in tests and entry points.
"""
from __future__ import annotations
from importlib import import_module
from typing import Any
__all__ = ["cli"]
def __getattr__(name: str) -> Any:
"""
Lazily expose ``pkgmgr.cli`` as attribute on the top-level package.
This keeps ``import pkgmgr`` lightweight while still allowing
``from pkgmgr import cli`` in tests and entry points.
"""
if name == "cli":
return import_module("pkgmgr.cli")
raise AttributeError(f"module 'pkgmgr' has no attribute {name!r}")