pyfirstaid 0.2.1__tar.gz → 0.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyfirstaid-0.2.1 → pyfirstaid-0.2.3}/CHANGELOG.md +17 -0
- {pyfirstaid-0.2.1 → pyfirstaid-0.2.3}/PKG-INFO +15 -2
- {pyfirstaid-0.2.1 → pyfirstaid-0.2.3}/README.md +5 -1
- {pyfirstaid-0.2.1 → pyfirstaid-0.2.3}/pyproject.toml +9 -0
- {pyfirstaid-0.2.1 → pyfirstaid-0.2.3}/src/pyfirstaid/__init__.py +1 -1
- {pyfirstaid-0.2.1 → pyfirstaid-0.2.3}/src/pyfirstaid/checks/dependencies.py +20 -1
- {pyfirstaid-0.2.1 → pyfirstaid-0.2.3}/src/pyfirstaid/checks/pip_mismatch.py +5 -2
- {pyfirstaid-0.2.1 → pyfirstaid-0.2.3}/src/pyfirstaid/checks/venv.py +2 -1
- {pyfirstaid-0.2.1 → pyfirstaid-0.2.3}/src/pyfirstaid/util.py +6 -2
- {pyfirstaid-0.2.1 → pyfirstaid-0.2.3}/tests/test_more_checks.py +41 -0
- {pyfirstaid-0.2.1 → pyfirstaid-0.2.3}/.github/workflows/ci.yml +0 -0
- {pyfirstaid-0.2.1 → pyfirstaid-0.2.3}/.github/workflows/publish.yml +0 -0
- {pyfirstaid-0.2.1 → pyfirstaid-0.2.3}/.gitignore +0 -0
- {pyfirstaid-0.2.1 → pyfirstaid-0.2.3}/LICENSE +0 -0
- {pyfirstaid-0.2.1 → pyfirstaid-0.2.3}/scripts/build_pyz.py +0 -0
- {pyfirstaid-0.2.1 → pyfirstaid-0.2.3}/src/pyfirstaid/__main__.py +0 -0
- {pyfirstaid-0.2.1 → pyfirstaid-0.2.3}/src/pyfirstaid/checks/__init__.py +0 -0
- {pyfirstaid-0.2.1 → pyfirstaid-0.2.3}/src/pyfirstaid/checks/broken_installs.py +0 -0
- {pyfirstaid-0.2.1 → pyfirstaid-0.2.3}/src/pyfirstaid/checks/compiled.py +0 -0
- {pyfirstaid-0.2.1 → pyfirstaid-0.2.3}/src/pyfirstaid/checks/encoding.py +0 -0
- {pyfirstaid-0.2.1 → pyfirstaid-0.2.3}/src/pyfirstaid/checks/leaks.py +0 -0
- {pyfirstaid-0.2.1 → pyfirstaid-0.2.3}/src/pyfirstaid/checks/path_check.py +0 -0
- {pyfirstaid-0.2.1 → pyfirstaid-0.2.3}/src/pyfirstaid/checks/permissions.py +0 -0
- {pyfirstaid-0.2.1 → pyfirstaid-0.2.3}/src/pyfirstaid/checks/python_version.py +0 -0
- {pyfirstaid-0.2.1 → pyfirstaid-0.2.3}/src/pyfirstaid/checks/shadowing.py +0 -0
- {pyfirstaid-0.2.1 → pyfirstaid-0.2.3}/src/pyfirstaid/checks/ssl_check.py +0 -0
- {pyfirstaid-0.2.1 → pyfirstaid-0.2.3}/src/pyfirstaid/cli.py +0 -0
- {pyfirstaid-0.2.1 → pyfirstaid-0.2.3}/src/pyfirstaid/delegate.py +0 -0
- {pyfirstaid-0.2.1 → pyfirstaid-0.2.3}/src/pyfirstaid/model.py +0 -0
- {pyfirstaid-0.2.1 → pyfirstaid-0.2.3}/src/pyfirstaid/report.py +0 -0
- {pyfirstaid-0.2.1 → pyfirstaid-0.2.3}/tests/test_checks.py +0 -0
- {pyfirstaid-0.2.1 → pyfirstaid-0.2.3}/tests/test_cli.py +0 -0
- {pyfirstaid-0.2.1 → pyfirstaid-0.2.3}/tests/test_delegate.py +0 -0
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.3
|
|
4
|
+
|
|
5
|
+
- Clearer PEP 668 message: it now says the block can come from your OS *or* a package
|
|
6
|
+
manager such as Homebrew.
|
|
7
|
+
- PyPI page: version, Python-versions, downloads and license badges; Source and Changelog
|
|
8
|
+
links; per-version Python classifiers (3.9–3.14).
|
|
9
|
+
|
|
10
|
+
## 0.2.2
|
|
11
|
+
|
|
12
|
+
Fixes found by testing on a real Mac with Homebrew Python:
|
|
13
|
+
|
|
14
|
+
- **Fix: false "pip installs into a DIFFERENT Python" on Homebrew** (and other setups where
|
|
15
|
+
site-packages lives outside Python's own folder). Paths are now also compared with symlinks
|
|
16
|
+
resolved.
|
|
17
|
+
- **Dependency conflicts inside a system / Homebrew Python** are now reported as information
|
|
18
|
+
with safe advice, instead of a `pip install` fix that the system would refuse (PEP 668).
|
|
19
|
+
|
|
3
20
|
## 0.2.1
|
|
4
21
|
|
|
5
22
|
- **Fix: correct results when installed with pipx or `uv tool`.** These tools put pyfirstaid in
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pyfirstaid
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: First aid for broken Python environments: finds what's wrong and tells you how to fix it.
|
|
5
5
|
Project-URL: Homepage, https://github.com/sai-sakalam/pyfirstaid
|
|
6
|
+
Project-URL: Source, https://github.com/sai-sakalam/pyfirstaid
|
|
6
7
|
Project-URL: Issues, https://github.com/sai-sakalam/pyfirstaid/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/sai-sakalam/pyfirstaid/blob/main/CHANGELOG.md
|
|
7
9
|
Author: Sai Gavaskar Sakalam
|
|
8
10
|
License-Expression: MIT
|
|
9
11
|
License-File: LICENSE
|
|
@@ -13,6 +15,13 @@ Classifier: Environment :: Console
|
|
|
13
15
|
Classifier: Intended Audience :: Developers
|
|
14
16
|
Classifier: Operating System :: OS Independent
|
|
15
17
|
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
25
|
Classifier: Topic :: Software Development :: Debuggers
|
|
17
26
|
Classifier: Topic :: System :: Installation/Setup
|
|
18
27
|
Requires-Python: >=3.9
|
|
@@ -22,11 +31,15 @@ Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
|
22
31
|
Description-Content-Type: text/markdown
|
|
23
32
|
|
|
24
33
|
# pyfirstaid 🩹
|
|
34
|
+
[](https://pypi.org/project/pyfirstaid/)
|
|
35
|
+
[](https://pypi.org/project/pyfirstaid/)
|
|
36
|
+
[](https://pepy.tech/projects/pyfirstaid)
|
|
25
37
|
[](https://github.com/sai-sakalam/pyfirstaid/actions/workflows/ci.yml)
|
|
38
|
+
[](https://github.com/sai-sakalam/pyfirstaid/blob/main/LICENSE)
|
|
26
39
|
|
|
27
40
|
**First aid for broken Python environments.** One command tells you *what's broken* and *exactly how to fix it*.
|
|
28
41
|
|
|
29
|
-
> **Status:
|
|
42
|
+
> **Status: early, actively developed.** 12 checks, tested on Windows, macOS and Linux (Python 3.9–3.14).
|
|
30
43
|
> Feedback is very welcome in [Issues](https://github.com/sai-sakalam/pyfirstaid/issues). *Which problems do you hit most?*
|
|
31
44
|
|
|
32
45
|
---
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
# pyfirstaid 🩹
|
|
2
|
+
[](https://pypi.org/project/pyfirstaid/)
|
|
3
|
+
[](https://pypi.org/project/pyfirstaid/)
|
|
4
|
+
[](https://pepy.tech/projects/pyfirstaid)
|
|
2
5
|
[](https://github.com/sai-sakalam/pyfirstaid/actions/workflows/ci.yml)
|
|
6
|
+
[](https://github.com/sai-sakalam/pyfirstaid/blob/main/LICENSE)
|
|
3
7
|
|
|
4
8
|
**First aid for broken Python environments.** One command tells you *what's broken* and *exactly how to fix it*.
|
|
5
9
|
|
|
6
|
-
> **Status:
|
|
10
|
+
> **Status: early, actively developed.** 12 checks, tested on Windows, macOS and Linux (Python 3.9–3.14).
|
|
7
11
|
> Feedback is very welcome in [Issues](https://github.com/sai-sakalam/pyfirstaid/issues). *Which problems do you hit most?*
|
|
8
12
|
|
|
9
13
|
---
|
|
@@ -18,6 +18,13 @@ classifiers = [
|
|
|
18
18
|
"Intended Audience :: Developers",
|
|
19
19
|
"Operating System :: OS Independent",
|
|
20
20
|
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.9",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13",
|
|
26
|
+
"Programming Language :: Python :: 3.14",
|
|
27
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
21
28
|
"Topic :: Software Development :: Debuggers",
|
|
22
29
|
"Topic :: System :: Installation/Setup",
|
|
23
30
|
]
|
|
@@ -31,7 +38,9 @@ pyfirstaid = "pyfirstaid.cli:main"
|
|
|
31
38
|
|
|
32
39
|
[project.urls]
|
|
33
40
|
Homepage = "https://github.com/sai-sakalam/pyfirstaid"
|
|
41
|
+
Source = "https://github.com/sai-sakalam/pyfirstaid"
|
|
34
42
|
Issues = "https://github.com/sai-sakalam/pyfirstaid/issues"
|
|
43
|
+
Changelog = "https://github.com/sai-sakalam/pyfirstaid/blob/main/CHANGELOG.md"
|
|
35
44
|
|
|
36
45
|
[tool.hatch.version]
|
|
37
46
|
path = "src/pyfirstaid/__init__.py"
|
|
@@ -7,7 +7,7 @@ import sys
|
|
|
7
7
|
from typing import List
|
|
8
8
|
|
|
9
9
|
from pyfirstaid.model import Finding, Options, Status
|
|
10
|
-
from pyfirstaid.util import run
|
|
10
|
+
from pyfirstaid.util import in_virtualenv, run
|
|
11
11
|
|
|
12
12
|
CHECK_ID = "dependencies"
|
|
13
13
|
|
|
@@ -25,6 +25,13 @@ def fix_for(line: str) -> str:
|
|
|
25
25
|
return "python -m pip install --upgrade <the package named above>"
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
def system_managed() -> bool:
|
|
29
|
+
"""A Python that refuses pip installs (PEP 668) and is not a venv."""
|
|
30
|
+
from pyfirstaid.checks.venv import is_externally_managed
|
|
31
|
+
|
|
32
|
+
return is_externally_managed() and not in_virtualenv()
|
|
33
|
+
|
|
34
|
+
|
|
28
35
|
def run_check(opts: Options) -> List[Finding]:
|
|
29
36
|
code, out, err = run([sys.executable, "-m", "pip", "check"], timeout=max(opts.timeout, 30))
|
|
30
37
|
if code in (124, 126, 127) or "No module named pip" in err:
|
|
@@ -33,6 +40,18 @@ def run_check(opts: Options) -> List[Finding]:
|
|
|
33
40
|
return [Finding(CHECK_ID, Status.OK, "No dependency conflicts (pip check)")]
|
|
34
41
|
|
|
35
42
|
lines = [ln for ln in out.splitlines() if ln.strip()]
|
|
43
|
+
if system_managed():
|
|
44
|
+
# pip install would be refused here (PEP 668), and these packages belong to
|
|
45
|
+
# Homebrew / the OS, so a conflict is informational, not something to fix with pip.
|
|
46
|
+
return [Finding(
|
|
47
|
+
CHECK_ID, Status.INFO,
|
|
48
|
+
"%d dependency conflict(s) inside your system / Homebrew Python (usually harmless)"
|
|
49
|
+
% len(lines),
|
|
50
|
+
detail="\n".join(lines[:8]),
|
|
51
|
+
fix="Don't pip install into this Python. Do your work in a venv "
|
|
52
|
+
"(python -m venv .venv). If it causes real errors, reinstall Python with the "
|
|
53
|
+
"tool that installed it (e.g. brew reinstall python).",
|
|
54
|
+
)]
|
|
36
55
|
findings = [Finding(CHECK_ID, Status.WARN, "Dependency conflict", detail=ln, fix=fix_for(ln))
|
|
37
56
|
for ln in lines[:8]]
|
|
38
57
|
if len(lines) > 8:
|
|
@@ -9,7 +9,7 @@ import sys
|
|
|
9
9
|
from typing import List, Optional, Tuple
|
|
10
10
|
|
|
11
11
|
from pyfirstaid.model import Finding, Options, Status
|
|
12
|
-
from pyfirstaid.util import is_within, run
|
|
12
|
+
from pyfirstaid.util import is_within, run, site_package_dirs
|
|
13
13
|
|
|
14
14
|
CHECK_ID = "pip-mismatch"
|
|
15
15
|
|
|
@@ -34,7 +34,10 @@ def pip_belongs_here(location: str, pip_python: str, running_python: str,
|
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
def _allowed_prefixes() -> List[str]:
|
|
37
|
-
|
|
37
|
+
# Homebrew (and some Linux distros) keep site-packages OUTSIDE sys.prefix,
|
|
38
|
+
# e.g. /opt/homebrew/lib/python3.14/site-packages, so include every
|
|
39
|
+
# site-packages folder this interpreter actually uses.
|
|
40
|
+
prefixes = [sys.prefix] + site_package_dirs()
|
|
38
41
|
if site.ENABLE_USER_SITE:
|
|
39
42
|
user_site = site.getusersitepackages()
|
|
40
43
|
if isinstance(user_site, str):
|
|
@@ -62,7 +62,8 @@ def run_check(opts: Options) -> List[Finding]:
|
|
|
62
62
|
findings.append(Finding(
|
|
63
63
|
CHECK_ID, Status.WARN,
|
|
64
64
|
"No virtual environment, and this system Python blocks pip installs",
|
|
65
|
-
detail="Your OS marks this Python as
|
|
65
|
+
detail="Your OS or package manager (e.g. Homebrew) marks this Python as "
|
|
66
|
+
"externally managed (PEP 668), "
|
|
66
67
|
"so `pip install` will fail with 'externally-managed-environment'.",
|
|
67
68
|
fix="python -m venv .venv && " + activate_command(".venv"),
|
|
68
69
|
))
|
|
@@ -31,10 +31,14 @@ def run(cmd: List[str], timeout: float = 15.0) -> Tuple[int, str, str]:
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
def norm(path: Optional[str]) -> str:
|
|
34
|
-
"""Normalise a path for comparison
|
|
34
|
+
"""Normalise a path for comparison: symlinks resolved, case-folded on Windows.
|
|
35
|
+
|
|
36
|
+
Resolving symlinks matters on macOS/Homebrew, where e.g.
|
|
37
|
+
/opt/homebrew/opt/python@3.14 links into /opt/homebrew/Cellar/...
|
|
38
|
+
"""
|
|
35
39
|
if not path:
|
|
36
40
|
return ""
|
|
37
|
-
return os.path.normcase(os.path.
|
|
41
|
+
return os.path.normcase(os.path.realpath(path))
|
|
38
42
|
|
|
39
43
|
|
|
40
44
|
def is_within(path: str, parent: str) -> bool:
|
|
@@ -146,3 +146,44 @@ def test_all_checks_registered():
|
|
|
146
146
|
ids = [c.id for c in ALL_CHECKS]
|
|
147
147
|
assert len(ids) == len(set(ids)) == 12
|
|
148
148
|
assert os.path.basename(__file__) # keeps `os` used
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
# --- v0.2.2 regressions found on a real Mac (Homebrew) ------------------------
|
|
152
|
+
|
|
153
|
+
def test_pip_in_homebrew_site_packages_outside_prefix(tmp_path):
|
|
154
|
+
# Homebrew: sys.prefix is .../Frameworks/Python.framework/Versions/3.14, but
|
|
155
|
+
# site-packages lives in /opt/homebrew/lib/python3.14/site-packages
|
|
156
|
+
from pyfirstaid.checks import pip_mismatch
|
|
157
|
+
|
|
158
|
+
prefix = str(tmp_path / "Frameworks" / "Versions" / "3.14")
|
|
159
|
+
site_dir = str(tmp_path / "lib" / "python3.14" / "site-packages")
|
|
160
|
+
loc = os.path.join(site_dir, "pip")
|
|
161
|
+
assert not pip_mismatch.pip_belongs_here(loc, "3.14", "3.14", [prefix])
|
|
162
|
+
assert pip_mismatch.pip_belongs_here(loc, "3.14", "3.14", [prefix, site_dir])
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def test_norm_resolves_symlinks(tmp_path):
|
|
166
|
+
from pyfirstaid.util import norm
|
|
167
|
+
|
|
168
|
+
real = tmp_path / "Cellar" / "python"
|
|
169
|
+
real.mkdir(parents=True)
|
|
170
|
+
link = tmp_path / "opt-python"
|
|
171
|
+
link.symlink_to(real)
|
|
172
|
+
assert norm(str(link)) == norm(str(real))
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def test_dependency_conflicts_in_system_python_are_info(monkeypatch):
|
|
176
|
+
monkeypatch.setattr(dependencies, "system_managed", lambda: True)
|
|
177
|
+
monkeypatch.setattr(dependencies, "run", lambda cmd, timeout=0: (
|
|
178
|
+
1, "wheel 0.48.0 requires packaging, which is not installed.", ""))
|
|
179
|
+
findings = dependencies.run_check(Options())
|
|
180
|
+
assert len(findings) == 1 and findings[0].status == Status.INFO
|
|
181
|
+
assert "pip install" not in findings[0].fix.split("Don't pip install")[-1].split(".")[0]
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def test_dependency_conflicts_in_venv_are_warnings(monkeypatch):
|
|
185
|
+
monkeypatch.setattr(dependencies, "system_managed", lambda: False)
|
|
186
|
+
monkeypatch.setattr(dependencies, "run", lambda cmd, timeout=0: (
|
|
187
|
+
1, "wheel 0.48.0 requires packaging, which is not installed.", ""))
|
|
188
|
+
findings = dependencies.run_check(Options())
|
|
189
|
+
assert findings[0].status == Status.WARN and 'install "packaging"' in findings[0].fix
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|