pyproject-doctor 0.1.0__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.
Files changed (57) hide show
  1. pyproject_doctor-0.1.0/.github/ISSUE_TEMPLATE/bug_report.md +24 -0
  2. pyproject_doctor-0.1.0/.github/ISSUE_TEMPLATE/feature_request.md +15 -0
  3. pyproject_doctor-0.1.0/.github/workflows/ci.yml +22 -0
  4. pyproject_doctor-0.1.0/.gitignore +43 -0
  5. pyproject_doctor-0.1.0/.pre-commit-hooks.yaml +7 -0
  6. pyproject_doctor-0.1.0/CHANGELOG.md +31 -0
  7. pyproject_doctor-0.1.0/CLAUDE.md +33 -0
  8. pyproject_doctor-0.1.0/CODE_OF_CONDUCT.md +29 -0
  9. pyproject_doctor-0.1.0/CONTRIBUTING.md +49 -0
  10. pyproject_doctor-0.1.0/LICENSE +21 -0
  11. pyproject_doctor-0.1.0/PKG-INFO +131 -0
  12. pyproject_doctor-0.1.0/README.md +75 -0
  13. pyproject_doctor-0.1.0/SECURITY.md +13 -0
  14. pyproject_doctor-0.1.0/assets/logo.png +0 -0
  15. pyproject_doctor-0.1.0/docs/architecture.md +89 -0
  16. pyproject_doctor-0.1.0/docs/charter.md +28 -0
  17. pyproject_doctor-0.1.0/docs/logo-prompt.md +7 -0
  18. pyproject_doctor-0.1.0/examples/broken_pyproject.toml +25 -0
  19. pyproject_doctor-0.1.0/pyproject.toml +72 -0
  20. pyproject_doctor-0.1.0/src/pyproject_doctor/__init__.py +7 -0
  21. pyproject_doctor-0.1.0/src/pyproject_doctor/checks/__init__.py +29 -0
  22. pyproject_doctor-0.1.0/src/pyproject_doctor/checks/classifiers.py +49 -0
  23. pyproject_doctor-0.1.0/src/pyproject_doctor/checks/dependencies.py +200 -0
  24. pyproject_doctor-0.1.0/src/pyproject_doctor/checks/dynamic.py +126 -0
  25. pyproject_doctor-0.1.0/src/pyproject_doctor/checks/emails.py +55 -0
  26. pyproject_doctor-0.1.0/src/pyproject_doctor/checks/entry_points.py +86 -0
  27. pyproject_doctor-0.1.0/src/pyproject_doctor/checks/files.py +153 -0
  28. pyproject_doctor-0.1.0/src/pyproject_doctor/checks/urls.py +39 -0
  29. pyproject_doctor-0.1.0/src/pyproject_doctor/checks/version.py +42 -0
  30. pyproject_doctor-0.1.0/src/pyproject_doctor/cli.py +44 -0
  31. pyproject_doctor-0.1.0/src/pyproject_doctor/model.py +22 -0
  32. pyproject_doctor-0.1.0/src/pyproject_doctor/parse.py +38 -0
  33. pyproject_doctor-0.1.0/src/pyproject_doctor/py.typed +0 -0
  34. pyproject_doctor-0.1.0/tests/__init__.py +0 -0
  35. pyproject_doctor-0.1.0/tests/data/bad_dep.toml +4 -0
  36. pyproject_doctor-0.1.0/tests/data/bad_email.toml +4 -0
  37. pyproject_doctor-0.1.0/tests/data/bad_entry_point.toml +6 -0
  38. pyproject_doctor-0.1.0/tests/data/bad_readme.toml +4 -0
  39. pyproject_doctor-0.1.0/tests/data/bad_url.toml +6 -0
  40. pyproject_doctor-0.1.0/tests/data/bad_version.toml +3 -0
  41. pyproject_doctor-0.1.0/tests/data/clean.toml +23 -0
  42. pyproject_doctor-0.1.0/tests/data/dynamic_absent.toml +3 -0
  43. pyproject_doctor-0.1.0/tests/data/dynamic_field_unknown.toml +4 -0
  44. pyproject_doctor-0.1.0/tests/data/dynamic_malformed.toml +4 -0
  45. pyproject_doctor-0.1.0/tests/data/dynamic_name_forbidden.toml +4 -0
  46. pyproject_doctor-0.1.0/tests/data/dynamic_static_conflict.toml +4 -0
  47. pyproject_doctor-0.1.0/tests/data/dynamic_valid.toml +3 -0
  48. pyproject_doctor-0.1.0/tests/data/unsatisfiable_dep.toml +4 -0
  49. pyproject_doctor-0.1.0/tests/test_classifiers.py +37 -0
  50. pyproject_doctor-0.1.0/tests/test_cli.py +34 -0
  51. pyproject_doctor-0.1.0/tests/test_dependencies.py +80 -0
  52. pyproject_doctor-0.1.0/tests/test_dynamic.py +146 -0
  53. pyproject_doctor-0.1.0/tests/test_emails.py +28 -0
  54. pyproject_doctor-0.1.0/tests/test_entry_points.py +29 -0
  55. pyproject_doctor-0.1.0/tests/test_files.py +40 -0
  56. pyproject_doctor-0.1.0/tests/test_urls.py +27 -0
  57. pyproject_doctor-0.1.0/tests/test_version.py +46 -0
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: Bug report
3
+ about: Report a bug in pyproject-doctor
4
+ title: '[Bug] '
5
+ labels: bug
6
+ assignees: ''
7
+ ---
8
+
9
+ **Describe the bug**
10
+ A clear and concise description of what the bug is.
11
+
12
+ **To reproduce**
13
+ Steps to reproduce the behavior, including your pyproject.toml content.
14
+
15
+ **Expected behavior**
16
+ What you expected pyproject-doctor to report.
17
+
18
+ **Actual behavior**
19
+ What pyproject-doctor actually reported (include the full output).
20
+
21
+ **Environment**
22
+ - pyproject-doctor version:
23
+ - Python version:
24
+ - OS:
@@ -0,0 +1,15 @@
1
+ ---
2
+ name: Feature request
3
+ about: Suggest a new check or improvement
4
+ title: '[Feature] '
5
+ labels: enhancement
6
+ assignees: ''
7
+ ---
8
+
9
+ **What check or feature are you requesting?**
10
+
11
+ **What semantic mistake would this catch?**
12
+
13
+ **Example pyproject.toml that should trigger the new check:**
14
+
15
+ **What should the diagnostic message say?**
@@ -0,0 +1,22 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python: ["3.10", "3.11", "3.12", "3.13"]
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: ${{ matrix.python }}
19
+ - run: pip install -e ".[dev]"
20
+ - run: ruff check .
21
+ - run: mypy src
22
+ - run: pytest -q
@@ -0,0 +1,43 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ dist/
9
+ *.egg-info/
10
+ .eggs/
11
+ *.egg
12
+
13
+ # Virtual environments
14
+ .venv/
15
+ venv/
16
+ env/
17
+ ENV/
18
+
19
+ # Testing
20
+ .pytest_cache/
21
+ .coverage
22
+ htmlcov/
23
+ .tox/
24
+
25
+ # Type checking
26
+ .mypy_cache/
27
+
28
+ # Ruff
29
+ .ruff_cache/
30
+
31
+ # Distribution
32
+ dist/
33
+ uv.lock
34
+
35
+ # IDE
36
+ .idea/
37
+ .vscode/
38
+ *.swp
39
+ *.swo
40
+
41
+ # OS
42
+ .DS_Store
43
+ Thumbs.db
@@ -0,0 +1,7 @@
1
+ - id: pyproject-doctor
2
+ name: pyproject-doctor
3
+ description: Validate pyproject.toml for semantic errors
4
+ entry: pyproject-doctor
5
+ language: python
6
+ files: ^pyproject\.toml$
7
+ pass_filenames: false
@@ -0,0 +1,31 @@
1
+ # Changelog
2
+
3
+ All notable changes to pyproject-doctor will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-06-17
11
+
12
+ ### Added
13
+
14
+ - Initial release of pyproject-doctor.
15
+ - PEP 440 version validation (`version-invalid`).
16
+ - PEP 508 dependency validation (`dep-invalid`) for `project.dependencies`, `project.optional-dependencies`, and `build-system.requires`.
17
+ - Version constraint satisfiability checking (`constraint-unsatisfiable`): detects impossible version ranges.
18
+ - File existence validation (`file-missing`) for readme, license, and entry-point module paths.
19
+ - URL format validation (`url-invalid`) for `project.urls`.
20
+ - Email format validation (`email-invalid`) for authors and maintainers.
21
+ - Entry-point format validation (`entry-point-invalid`) for scripts, gui-scripts, and entry-points.
22
+ - Trove classifier validation (`classifier-unknown`) via optional `trove-classifiers` dependency.
23
+ - Text and JSON output formats.
24
+ - Pre-commit hook support via `.pre-commit-hooks.yaml`.
25
+ - CLI: `pyproject-doctor [PATH] [--format text|json]`.
26
+
27
+ ### Notes
28
+
29
+ - PyPI release is pending. Install from source or GitHub for now.
30
+
31
+ [0.1.0]: https://github.com/amaar-mc/pyproject-doctor/releases/tag/v0.1.0
@@ -0,0 +1,33 @@
1
+ # CLAUDE.md
2
+
3
+ ## Project: pyproject-doctor
4
+
5
+ Offline deep validator for pyproject.toml. Catches semantic mistakes other tools miss.
6
+
7
+ ## Key Design Decisions
8
+
9
+ - Pure functions for all checks; CLI and file IO are thin shells.
10
+ - No default parameter values anywhere; expose named variants if needed.
11
+ - Strict mypy typing throughout.
12
+ - `packaging` library for PEP 440 and PEP 508 validation only.
13
+ - Optional `trove-classifiers` for classifier validation; gracefully skip if absent.
14
+ - Satisfiability checker is custom (packaging has no is_satisfiable).
15
+ - File existence checks only run when `src/` exists under root.
16
+
17
+ ## Adding Checks
18
+
19
+ 1. Add a pure function to `src/pyproject_doctor/checks/<module>.py`.
20
+ 2. Compose it in `src/pyproject_doctor/checks/__init__.py`.
21
+ 3. Write tests first.
22
+ 4. Stable diagnostic codes are kebab-case; never rename them.
23
+
24
+ ## Gates Before Committing
25
+
26
+ ```
27
+ uv pip install -e ".[dev]"
28
+ uv run pytest -q
29
+ uv run ruff check .
30
+ uv run mypy src
31
+ uv build
32
+ uv run --with twine twine check dist/*
33
+ ```
@@ -0,0 +1,29 @@
1
+ # Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We pledge to make participation in this project a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
6
+
7
+ ## Our Standards
8
+
9
+ Examples of behavior that contributes to a positive environment:
10
+
11
+ - Using welcoming and inclusive language
12
+ - Being respectful of differing viewpoints and experiences
13
+ - Gracefully accepting constructive criticism
14
+ - Focusing on what is best for the community
15
+
16
+ Examples of unacceptable behavior:
17
+
18
+ - Trolling, insulting, or derogatory comments
19
+ - Public or private harassment
20
+ - Publishing others' private information without explicit permission
21
+ - Other conduct which could reasonably be considered inappropriate in a professional setting
22
+
23
+ ## Enforcement
24
+
25
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting the maintainers directly.
26
+
27
+ ## Attribution
28
+
29
+ This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), version 2.1.
@@ -0,0 +1,49 @@
1
+ # Contributing to pyproject-doctor
2
+
3
+ Thank you for your interest in contributing!
4
+
5
+ ## Development Setup
6
+
7
+ 1. Clone the repository.
8
+ 2. Install in editable mode with dev dependencies:
9
+
10
+ ```bash
11
+ uv pip install -e ".[dev]"
12
+ ```
13
+
14
+ 3. Run tests:
15
+
16
+ ```bash
17
+ uv run pytest -q
18
+ ```
19
+
20
+ 4. Run linting:
21
+
22
+ ```bash
23
+ uv run ruff check .
24
+ uv run mypy src
25
+ ```
26
+
27
+ ## Adding a New Check
28
+
29
+ 1. Write a failing test first (`tests/test_<check>.py`).
30
+ 2. Add a pure function to the appropriate module under `src/pyproject_doctor/checks/`.
31
+ 3. Expose it via `check_pyproject` in `src/pyproject_doctor/checks/__init__.py`.
32
+ 4. Add test data files to `tests/data/` as needed.
33
+ 5. Update `docs/architecture.md` to describe the check.
34
+ 6. Update `CHANGELOG.md` under `[Unreleased]`.
35
+
36
+ ## Commit Style
37
+
38
+ Commits use the `type(scope): description` format. For example:
39
+
40
+ - `feat(checks): add Python version compatibility check`
41
+ - `fix(deps): handle wildcard specifiers in satisfiability check`
42
+ - `test(version): add test for post-release versions`
43
+
44
+ ## Code Style
45
+
46
+ - Strict mypy typing required.
47
+ - No default parameter values.
48
+ - Pure functions for checks.
49
+ - No em dashes in code, comments, or docs.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Amaar Chughtai
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,131 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyproject-doctor
3
+ Version: 0.1.0
4
+ Summary: Offline deep validator for pyproject.toml: file existence, URL and email format, PEP 440 version and PEP 508 specifier validity, version-constraint satisfiability, and entry-point format.
5
+ Project-URL: Homepage, https://github.com/amaar-mc/pyproject-doctor
6
+ Project-URL: Repository, https://github.com/amaar-mc/pyproject-doctor
7
+ Project-URL: Bug Tracker, https://github.com/amaar-mc/pyproject-doctor/issues
8
+ Author-email: Amaar Chughtai <amaardevx@gmail.com>
9
+ License: MIT License
10
+
11
+ Copyright (c) 2026 Amaar Chughtai
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in all
21
+ copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
30
+ License-File: LICENSE
31
+ Keywords: linter,packaging,pep508,pep621,pre-commit,pyproject,validator
32
+ Classifier: Development Status :: 4 - Beta
33
+ Classifier: Environment :: Console
34
+ Classifier: Intended Audience :: Developers
35
+ Classifier: License :: OSI Approved :: MIT License
36
+ Classifier: Operating System :: OS Independent
37
+ Classifier: Programming Language :: Python :: 3
38
+ Classifier: Programming Language :: Python :: 3.10
39
+ Classifier: Programming Language :: Python :: 3.11
40
+ Classifier: Programming Language :: Python :: 3.12
41
+ Classifier: Programming Language :: Python :: 3.13
42
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
43
+ Classifier: Topic :: Software Development :: Quality Assurance
44
+ Classifier: Typing :: Typed
45
+ Requires-Python: >=3.10
46
+ Requires-Dist: packaging>=23.0
47
+ Requires-Dist: tomli>=2.0; python_version < '3.11'
48
+ Provides-Extra: classifiers
49
+ Requires-Dist: trove-classifiers>=2023.1.1; extra == 'classifiers'
50
+ Provides-Extra: dev
51
+ Requires-Dist: mypy>=1.0; extra == 'dev'
52
+ Requires-Dist: pytest>=7.0; extra == 'dev'
53
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
54
+ Requires-Dist: trove-classifiers>=2023.1.1; extra == 'dev'
55
+ Description-Content-Type: text/markdown
56
+
57
+ # pyproject-doctor
58
+
59
+ <p align="center">
60
+ <img src="assets/logo.png" alt="pyproject-doctor logo" width="160">
61
+ </p>
62
+
63
+ Offline deep validator for `pyproject.toml` that catches the semantic mistakes other tools miss.
64
+
65
+ Most validators only check TOML syntax. pyproject-doctor goes further: it validates that your versions are PEP 440 compliant, your dependencies are PEP 508 compliant, your version constraints are actually satisfiable, your referenced files exist, your URLs are real URLs, your email addresses look right, and your entry-point references are correctly formatted.
66
+
67
+ > **PyPI release pending.** Install from GitHub for now (see below).
68
+
69
+ ## What it checks
70
+
71
+ | Code | Description |
72
+ |------|-------------|
73
+ | `version-invalid` | `project.version` is not a valid PEP 440 version |
74
+ | `dep-invalid` | A dependency in `project.dependencies`, `project.optional-dependencies`, or `build-system.requires` is not a valid PEP 508 requirement |
75
+ | `constraint-unsatisfiable` | A dependency's version specifiers form an impossible range (e.g. `>=2.0,<1.0`) |
76
+ | `file-missing` | A file referenced by `project.readme`, `project.license`, or an entry-point module does not exist |
77
+ | `url-invalid` | A value in `project.urls` is not a valid absolute URL |
78
+ | `email-invalid` | An author or maintainer email address is malformed |
79
+ | `entry-point-invalid` | A script or entry-point value is not in valid `module:attr` format |
80
+ | `classifier-unknown` | A classifier in `project.classifiers` is not a known trove classifier (requires `pip install 'pyproject-doctor[classifiers]'`) |
81
+
82
+ ## Install
83
+
84
+ ```bash
85
+ pip install git+https://github.com/amaar-mc/pyproject-doctor.git
86
+ ```
87
+
88
+ Or with classifier validation:
89
+
90
+ ```bash
91
+ pip install "git+https://github.com/amaar-mc/pyproject-doctor.git#egg=pyproject-doctor[classifiers]"
92
+ ```
93
+
94
+ ## Usage
95
+
96
+ ```bash
97
+ # Validate pyproject.toml in the current directory
98
+ pyproject-doctor
99
+
100
+ # Validate a specific file
101
+ pyproject-doctor /path/to/pyproject.toml
102
+
103
+ # JSON output
104
+ pyproject-doctor --format json
105
+ ```
106
+
107
+ Exit code is 1 if any error-level diagnostic is found, 0 otherwise.
108
+
109
+ ## Pre-commit
110
+
111
+ Add to `.pre-commit-config.yaml`:
112
+
113
+ ```yaml
114
+ repos:
115
+ - repo: https://github.com/amaar-mc/pyproject-doctor
116
+ rev: v0.1.0
117
+ hooks:
118
+ - id: pyproject-doctor
119
+ ```
120
+
121
+ ## Example output
122
+
123
+ ```
124
+ error project.version: version-invalid: 'not.a.version' is not a valid PEP 440 version
125
+ error project.dependencies[0]: constraint-unsatisfiable: Dependency 'requests': constraint '>=3.0,<2.0' is unsatisfiable (lower bound 3.0 exceeds upper bound 2.0)
126
+ error project.urls.Homepage: url-invalid: URL 'not-a-url' is not a valid absolute URL (must have scheme and host)
127
+ ```
128
+
129
+ ## License
130
+
131
+ MIT. Copyright (c) 2026 Amaar Chughtai.
@@ -0,0 +1,75 @@
1
+ # pyproject-doctor
2
+
3
+ <p align="center">
4
+ <img src="assets/logo.png" alt="pyproject-doctor logo" width="160">
5
+ </p>
6
+
7
+ Offline deep validator for `pyproject.toml` that catches the semantic mistakes other tools miss.
8
+
9
+ Most validators only check TOML syntax. pyproject-doctor goes further: it validates that your versions are PEP 440 compliant, your dependencies are PEP 508 compliant, your version constraints are actually satisfiable, your referenced files exist, your URLs are real URLs, your email addresses look right, and your entry-point references are correctly formatted.
10
+
11
+ > **PyPI release pending.** Install from GitHub for now (see below).
12
+
13
+ ## What it checks
14
+
15
+ | Code | Description |
16
+ |------|-------------|
17
+ | `version-invalid` | `project.version` is not a valid PEP 440 version |
18
+ | `dep-invalid` | A dependency in `project.dependencies`, `project.optional-dependencies`, or `build-system.requires` is not a valid PEP 508 requirement |
19
+ | `constraint-unsatisfiable` | A dependency's version specifiers form an impossible range (e.g. `>=2.0,<1.0`) |
20
+ | `file-missing` | A file referenced by `project.readme`, `project.license`, or an entry-point module does not exist |
21
+ | `url-invalid` | A value in `project.urls` is not a valid absolute URL |
22
+ | `email-invalid` | An author or maintainer email address is malformed |
23
+ | `entry-point-invalid` | A script or entry-point value is not in valid `module:attr` format |
24
+ | `classifier-unknown` | A classifier in `project.classifiers` is not a known trove classifier (requires `pip install 'pyproject-doctor[classifiers]'`) |
25
+
26
+ ## Install
27
+
28
+ ```bash
29
+ pip install git+https://github.com/amaar-mc/pyproject-doctor.git
30
+ ```
31
+
32
+ Or with classifier validation:
33
+
34
+ ```bash
35
+ pip install "git+https://github.com/amaar-mc/pyproject-doctor.git#egg=pyproject-doctor[classifiers]"
36
+ ```
37
+
38
+ ## Usage
39
+
40
+ ```bash
41
+ # Validate pyproject.toml in the current directory
42
+ pyproject-doctor
43
+
44
+ # Validate a specific file
45
+ pyproject-doctor /path/to/pyproject.toml
46
+
47
+ # JSON output
48
+ pyproject-doctor --format json
49
+ ```
50
+
51
+ Exit code is 1 if any error-level diagnostic is found, 0 otherwise.
52
+
53
+ ## Pre-commit
54
+
55
+ Add to `.pre-commit-config.yaml`:
56
+
57
+ ```yaml
58
+ repos:
59
+ - repo: https://github.com/amaar-mc/pyproject-doctor
60
+ rev: v0.1.0
61
+ hooks:
62
+ - id: pyproject-doctor
63
+ ```
64
+
65
+ ## Example output
66
+
67
+ ```
68
+ error project.version: version-invalid: 'not.a.version' is not a valid PEP 440 version
69
+ error project.dependencies[0]: constraint-unsatisfiable: Dependency 'requests': constraint '>=3.0,<2.0' is unsatisfiable (lower bound 3.0 exceeds upper bound 2.0)
70
+ error project.urls.Homepage: url-invalid: URL 'not-a-url' is not a valid absolute URL (must have scheme and host)
71
+ ```
72
+
73
+ ## License
74
+
75
+ MIT. Copyright (c) 2026 Amaar Chughtai.
@@ -0,0 +1,13 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ | Version | Supported |
6
+ |---------|-----------|
7
+ | 0.1.x | Yes |
8
+
9
+ ## Reporting a Vulnerability
10
+
11
+ If you discover a security vulnerability in pyproject-doctor, please report it by opening a GitHub issue with the label "security". For sensitive vulnerabilities, contact the maintainer directly at amaardevx@gmail.com.
12
+
13
+ We will respond within 72 hours and aim to release a fix within 7 days of confirmed vulnerabilities.
Binary file
@@ -0,0 +1,89 @@
1
+ # Architecture
2
+
3
+ ## Overview
4
+
5
+ pyproject-doctor is organized into a thin CLI shell over a pure-function core.
6
+
7
+ ```
8
+ pyproject.toml file
9
+ |
10
+ parse.py (check_file)
11
+ |
12
+ tomllib / tomli
13
+ |
14
+ Mapping[str, Any]
15
+ |
16
+ checks/__init__.py (check_pyproject)
17
+ |
18
+ +----+----+----+----+----+----+----+
19
+ | | | | | | | |
20
+ ver dep file url email ep cls
21
+ ```
22
+
23
+ Each check is an independent pure function that takes `Mapping[str, Any]` (and optionally a `root: Path`) and returns `list[Diagnostic]`.
24
+
25
+ ## Diagnostic Model
26
+
27
+ `Diagnostic` is a frozen dataclass with:
28
+ - `level`: "error" | "warning" | "info"
29
+ - `code`: stable kebab-case identifier (e.g., "version-invalid")
30
+ - `key_path`: dotted location in the TOML (e.g., "project.dependencies[0]")
31
+ - `message`: human-readable explanation
32
+
33
+ ## Checks
34
+
35
+ ### version (checks/version.py)
36
+
37
+ Uses `packaging.version.Version` to validate PEP 440. Skips if "version" is in `project.dynamic`.
38
+
39
+ ### dependencies (checks/dependencies.py)
40
+
41
+ Uses `packaging.requirements.Requirement` to validate PEP 508. Also runs satisfiability analysis.
42
+
43
+ ### Constraint Satisfiability Algorithm
44
+
45
+ The satisfiability checker analyzes the specifiers in a `SpecifierSet` to detect impossible version ranges.
46
+
47
+ **Algorithm:**
48
+
49
+ 1. Parse all specifiers from the set.
50
+ 2. Track an effective lower bound `(version, inclusive: bool)` and upper bound `(version, inclusive: bool)`.
51
+ 3. For each specifier operator:
52
+ - `==X`: adds X as both lower and upper bound (inclusive). Multiple different `==` pins are immediately unsatisfiable.
53
+ - `>=X`: update lower bound to max(current_lower, X) with inclusive=True.
54
+ - `>X`: update lower bound to max(current_lower, X) with inclusive=False (exclusive is tighter at same version).
55
+ - `<=X`: update upper bound to min(current_upper, X) with inclusive=True.
56
+ - `<X`: update upper bound to min(current_upper, X) with inclusive=False.
57
+ - `~=X.Y`: treated as `>=X.Y` for lower bound purposes.
58
+ - `!=X`: never contributes to unsatisfiability alone; excluded from analysis.
59
+ - `==X.*` (wildcard): skipped (too complex to bound tightly).
60
+ 4. After processing all specifiers, check:
61
+ - If `lower_ver > upper_ver`: unsatisfiable.
62
+ - If `lower_ver == upper_ver` and either endpoint is exclusive: unsatisfiable.
63
+ - Otherwise: satisfiable.
64
+
65
+ **Examples:**
66
+ - `>=2.0,<1.0`: lower=2.0(inc), upper=1.0(exc) => 2.0 > 1.0 => unsatisfiable
67
+ - `>1.0,<=1.0`: lower=1.0(exc), upper=1.0(inc) => equal bounds, lower exclusive => unsatisfiable
68
+ - `>=1.0,<=1.0`: lower=1.0(inc), upper=1.0(inc) => equal bounds, both inclusive => satisfiable (exactly 1.0)
69
+ - `!=1.0,!=2.0`: no bounds derived => satisfiable
70
+
71
+ ### files (checks/files.py)
72
+
73
+ Checks `project.readme`, `project.license.file`, `project.license-files` globs, and entry-point module paths. File checks are relative to the `root` directory. Entry-point module checks only run when `src/` exists under root, to avoid false positives on installed packages.
74
+
75
+ ### urls (checks/urls.py)
76
+
77
+ Uses `urllib.parse.urlparse` to validate that each `project.urls` value has both a scheme and a netloc.
78
+
79
+ ### emails (checks/emails.py)
80
+
81
+ Uses a pragmatic regex pattern to validate author/maintainer email addresses.
82
+
83
+ ### entry_points (checks/entry_points.py)
84
+
85
+ Validates that each entry-point reference is in `module:attr` format, where both module and attr are valid dotted Python identifiers.
86
+
87
+ ### classifiers (checks/classifiers.py)
88
+
89
+ Imports `trove_classifiers` at runtime. If not installed, emits an info diagnostic and skips. If installed, validates each classifier string against the known set.
@@ -0,0 +1,28 @@
1
+ # pyproject-doctor Charter
2
+
3
+ ## Mission
4
+
5
+ pyproject-doctor catches semantic mistakes in pyproject.toml that other validators miss. It runs fully offline, with no network calls, and produces structured diagnostics that are easy to consume in CI or pre-commit hooks.
6
+
7
+ ## Scope
8
+
9
+ pyproject-doctor validates:
10
+
11
+ - PEP 440 version strings
12
+ - PEP 508 dependency specifiers
13
+ - Version constraint satisfiability
14
+ - File existence (readme, license, entry-point modules)
15
+ - URL format in project.urls
16
+ - Email format in authors/maintainers
17
+ - Entry-point format (module:attr)
18
+ - Trove classifier validity (optional)
19
+
20
+ pyproject-doctor does NOT:
21
+ - Validate build backends beyond requiring valid PEP 508 in build-system.requires
22
+ - Import or execute any project code
23
+ - Make network calls
24
+ - Replace a full build system
25
+
26
+ ## Stability
27
+
28
+ Diagnostic codes (e.g., version-invalid, dep-invalid) are stable and will not change without a major version bump.
@@ -0,0 +1,7 @@
1
+ # Logo prompt
2
+
3
+ Model: gpt-image-2. Format: 1024x1024, squircle (superellipse) corner mask.
4
+
5
+ Flat vector app icon, full-bleed square composition, research-lab aesthetic: clean white and very pale blue background with a faint thin geometric grid, crisp black technical line work with a single medium-blue accent. Precise schematic scientific-figure look, centered, generous margins, balanced detail. Absolutely no text, no letters, no numerals, no robots, no neon, no glow, no 3D gloss, no photorealism. Keep the artwork to square edges with a flat background extending to all four corners (the corners will be masked into a squircle later).
6
+
7
+ Subject: an offline validator that diagnoses a Python project configuration file. A stylized upright document or config card with a few thin horizontal rule lines suggesting key-value fields, overlaid with a clean stethoscope whose chestpiece rests on the document, and a small blue checkmark badge in one corner indicating a passed health check, with one tiny warning triangle among the lines suggesting a caught problem. Clean diagnostic schematic, no letters or numerals.
@@ -0,0 +1,25 @@
1
+ # This file intentionally contains errors to demonstrate pyproject-doctor
2
+ [build-system]
3
+ requires = ["hatchling"]
4
+ build-backend = "hatchling.build"
5
+
6
+ [project]
7
+ name = "broken-example"
8
+ version = "not.a.valid.version!!!"
9
+ description = "Example of a broken pyproject.toml"
10
+ dependencies = [
11
+ "requests>=3.0,<2.0",
12
+ "numpy not_valid_requirement",
13
+ ]
14
+ authors = [
15
+ {name = "Bad Author", email = "not-an-email"},
16
+ ]
17
+ classifiers = [
18
+ "This :: Is :: Not :: Real",
19
+ ]
20
+
21
+ [project.urls]
22
+ Homepage = "not-a-url"
23
+
24
+ [project.scripts]
25
+ my-tool = "no_colon_here"