modern-python-guidance 0.4.3__tar.gz → 0.4.4__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 (138) hide show
  1. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/.github/workflows/ci.yml +1 -1
  2. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/CHANGELOG.md +8 -0
  3. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/PKG-INFO +4 -3
  4. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/README.md +3 -2
  5. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/docs/design.md +3 -4
  6. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/pyproject.toml +1 -1
  7. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/src/modern_python_guidance/__init__.py +1 -1
  8. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/src/modern_python_guidance/version_detect.py +50 -15
  9. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/tests/test_version_detect.py +86 -6
  10. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/.github/workflows/check-python-release.yml +0 -0
  11. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/.github/workflows/publish.yml +0 -0
  12. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/.gitignore +0 -0
  13. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/CONTRIBUTING.md +0 -0
  14. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/LICENSE +0 -0
  15. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/LICENSE-MIT +0 -0
  16. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/SECURITY.md +0 -0
  17. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/fixtures/edge-cases/opus48_multiline.py +0 -0
  18. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/fixtures/edge-cases/valid_alt_patterns.py +0 -0
  19. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/fixtures/variant-a-modern/pyproject.toml +0 -0
  20. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/fixtures/variant-a-modern/src/app.py +0 -0
  21. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/fixtures/variant-a-modern/src/config.py +0 -0
  22. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/fixtures/variant-a-modern/src/crawler.py +0 -0
  23. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/fixtures/variant-a-modern/src/models.py +0 -0
  24. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/fixtures/variant-a-modern/src/scanner.py +0 -0
  25. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/fixtures/variant-a-modern/src/utils.py +0 -0
  26. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/fixtures/variant-a-outdated/pyproject.toml +0 -0
  27. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/fixtures/variant-a-outdated/setup.py +0 -0
  28. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/fixtures/variant-a-outdated/src/app.py +0 -0
  29. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/fixtures/variant-a-outdated/src/config.py +0 -0
  30. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/fixtures/variant-a-outdated/src/crawler.py +0 -0
  31. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/fixtures/variant-a-outdated/src/models.py +0 -0
  32. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/fixtures/variant-a-outdated/src/scanner.py +0 -0
  33. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/fixtures/variant-a-outdated/src/utils.py +0 -0
  34. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/fixtures/variant-b-modern/myapp/models.py +0 -0
  35. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/fixtures/variant-b-modern/myapp/views.py +0 -0
  36. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/fixtures/variant-b-outdated/myapp/models.py +0 -0
  37. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/fixtures/variant-b-outdated/myapp/views.py +0 -0
  38. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/fixtures/variant-c-modern/tests/test_calculator.py +0 -0
  39. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/fixtures/variant-c-outdated/tests/test_calculator.py +0 -0
  40. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/mcp-config.json +0 -0
  41. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/prompt-v2.txt +0 -0
  42. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/prompt-v3-mcp.txt +0 -0
  43. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/prompt-v3.txt +0 -0
  44. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/prompt-v4-a.txt +0 -0
  45. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/prompt-v4-b.txt +0 -0
  46. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/prompt-v4-c.txt +0 -0
  47. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/prompt.txt +0 -0
  48. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/prompts/v5-a-detailed.txt +0 -0
  49. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/prompts/v5-a-normal.txt +0 -0
  50. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/prompts/v5-a-terse.txt +0 -0
  51. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/prompts/v5-b-detailed.txt +0 -0
  52. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/prompts/v5-b-normal.txt +0 -0
  53. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/prompts/v5-b-terse.txt +0 -0
  54. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/prompts/v5-c-detailed.txt +0 -0
  55. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/prompts/v5-c-normal.txt +0 -0
  56. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/prompts/v5-c-terse.txt +0 -0
  57. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/run-mcp.sh +0 -0
  58. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/run-v4.sh +0 -0
  59. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/run-v5.sh +0 -0
  60. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/run.sh +0 -0
  61. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/score-v2.sh +0 -0
  62. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/score-v3.sh +0 -0
  63. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/score-v4.sh +0 -0
  64. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/score.sh +0 -0
  65. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/score_v5.py +0 -0
  66. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/bench/test-scorer.sh +0 -0
  67. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/docs/benchmark-evaluation.md +0 -0
  68. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/docs/benchmark-procedure.md +0 -0
  69. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/docs/benchmark-v5.md +0 -0
  70. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/rules/modern-python.md +0 -0
  71. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/SKILL.md +0 -0
  72. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/async/async-timeout-context.md +0 -0
  73. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/async/exception-groups.md +0 -0
  74. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/async/taskgroup-over-gather.md +0 -0
  75. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/data-structures/dataclass-modern.md +0 -0
  76. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/data-structures/dict-merge-operator.md +0 -0
  77. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/data-structures/match-case-patterns.md +0 -0
  78. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/django/django-async-views.md +0 -0
  79. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/django/django-check-constraints.md +0 -0
  80. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/django/django-json-field.md +0 -0
  81. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/fastapi/fastapi-annotated-depends.md +0 -0
  82. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/fastapi/fastapi-lifespan.md +0 -0
  83. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/fastapi/fastapi-typed-state.md +0 -0
  84. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/httpx/httpx-async-client-reuse.md +0 -0
  85. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/httpx/httpx-streaming.md +0 -0
  86. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/pydantic/pydantic-v2-config.md +0 -0
  87. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/pydantic/pydantic-v2-model-api.md +0 -0
  88. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/pydantic/pydantic-v2-serialization.md +0 -0
  89. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/pydantic/pydantic-v2-validators.md +0 -0
  90. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/pytest/pytest-parametrize.md +0 -0
  91. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/pytest/pytest-raises-match.md +0 -0
  92. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/pytest/pytest-tmp-path.md +0 -0
  93. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/sqlalchemy/sqlalchemy-2-style.md +0 -0
  94. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/sqlalchemy/sqlalchemy-async-session.md +0 -0
  95. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/sqlalchemy/sqlalchemy-mapped-column.md +0 -0
  96. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/stdlib/datetime-utc.md +0 -0
  97. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/stdlib/pathlib-over-os-path.md +0 -0
  98. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/stdlib/removeprefix-removesuffix.md +0 -0
  99. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/stdlib/template-strings.md +0 -0
  100. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/stdlib/tomllib-builtin.md +0 -0
  101. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/toolchain/no-pickle.md +0 -0
  102. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/toolchain/pyproject-toml-over-setup.md +0 -0
  103. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/toolchain/ruff-over-flake8.md +0 -0
  104. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/toolchain/safe-subprocess.md +0 -0
  105. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/toolchain/uv-over-pip.md +0 -0
  106. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/typing/deferred-annotations.md +0 -0
  107. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/typing/override-decorator.md +0 -0
  108. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/typing/paramspec-decorators.md +0 -0
  109. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/typing/type-parameter-syntax.md +0 -0
  110. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/typing/typeis-vs-typeguard.md +0 -0
  111. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/typing/union-syntax.md +0 -0
  112. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/skills/modern-python-guidance/guides/typing/use-builtin-generics.md +0 -0
  113. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/src/modern_python_guidance/__main__.py +0 -0
  114. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/src/modern_python_guidance/check.py +0 -0
  115. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/src/modern_python_guidance/cli.py +0 -0
  116. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/src/modern_python_guidance/compat.py +0 -0
  117. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/src/modern_python_guidance/frontmatter.py +0 -0
  118. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/src/modern_python_guidance/guide_index.py +0 -0
  119. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/src/modern_python_guidance/mcp_server.py +0 -0
  120. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/src/modern_python_guidance/retrieve.py +0 -0
  121. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/src/modern_python_guidance/search.py +0 -0
  122. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/src/modern_python_guidance/setup_cmd.py +0 -0
  123. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/src/modern_python_guidance/uninstall_cmd.py +0 -0
  124. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/tests/test_check.py +0 -0
  125. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/tests/test_cli_integration.py +0 -0
  126. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/tests/test_cli_unit.py +0 -0
  127. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/tests/test_compat.py +0 -0
  128. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/tests/test_frontmatter.py +0 -0
  129. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/tests/test_guide_index.py +0 -0
  130. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/tests/test_guide_structure.py +0 -0
  131. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/tests/test_mcp_server.py +0 -0
  132. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/tests/test_mcp_unit.py +0 -0
  133. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/tests/test_retrieve.py +0 -0
  134. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/tests/test_scorer_v5.py +0 -0
  135. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/tests/test_search.py +0 -0
  136. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/tests/test_setup.py +0 -0
  137. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/tests/test_skill_sync.py +0 -0
  138. {modern_python_guidance-0.4.3 → modern_python_guidance-0.4.4}/tests/test_uninstall.py +0 -0
@@ -14,7 +14,7 @@ jobs:
14
14
  runs-on: ubuntu-latest
15
15
  strategy:
16
16
  matrix:
17
- python-version: ["3.11", "3.12", "3.13"]
17
+ python-version: ["3.11", "3.12", "3.13", "3.14"]
18
18
 
19
19
  steps:
20
20
  - uses: actions/checkout@v4
@@ -2,6 +2,14 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.4.4] — 2026-06-05
6
+
7
+ ### Added
8
+
9
+ - Poetry constraint parsing: `detect_version()` now extracts the minimum Python version from `[tool.poetry.dependencies].python` instead of only logging a warning. Supported forms: caret (`^3.10`), tilde (`~3.11`), PEP 440 (`>=3.10,<3.14`), and dict-form (`{version = "^3.10"}`). Union operators (`||`) and unsupported formats warn and fall through to `.python-version` / default. (closes #95)
10
+ - Python 3.14 added to CI test matrix as a regular (non-allowed-failure) entry. Python 3.14 has been GA since 2025-10-07; pyproject.toml classifiers already declared support. (closes #94)
11
+ - 12 new tests (999 total).
12
+
5
13
  ## [0.4.3] — 2026-06-04
6
14
 
7
15
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modern-python-guidance
3
- Version: 0.4.3
3
+ Version: 0.4.4
4
4
  Summary: Version-aware BAD/GOOD pattern guides that help AI coding agents generate modern Python
5
5
  Project-URL: Homepage, https://github.com/yottayoshida/modern-python-guidance
6
6
  Project-URL: Repository, https://github.com/yottayoshida/modern-python-guidance
@@ -166,8 +166,9 @@ Guides specify their minimum Python version. The CLI auto-detects your project's
166
166
 
167
167
  1. `--python-version` flag
168
168
  2. `pyproject.toml` `requires-python`
169
- 3. `.python-version` file
170
- 4. Default: 3.11
169
+ 3. `pyproject.toml` Poetry `python` constraint (`^3.10`, `~3.11`, `>=3.10,<3.14`)
170
+ 4. `.python-version` file
171
+ 5. Default: 3.11
171
172
 
172
173
  ```bash
173
174
  # Only shows guides compatible with Python 3.9
@@ -134,8 +134,9 @@ Guides specify their minimum Python version. The CLI auto-detects your project's
134
134
 
135
135
  1. `--python-version` flag
136
136
  2. `pyproject.toml` `requires-python`
137
- 3. `.python-version` file
138
- 4. Default: 3.11
137
+ 3. `pyproject.toml` Poetry `python` constraint (`^3.10`, `~3.11`, `>=3.10,<3.14`)
138
+ 4. `.python-version` file
139
+ 5. Default: 3.11
139
140
 
140
141
  ```bash
141
142
  # Only shows guides compatible with Python 3.9
@@ -150,10 +150,9 @@ Fuzzy results are marked with `fuzzy: true` in the output.
150
150
 
151
151
  1. `--python-version` CLI flag (explicit override)
152
152
  2. `pyproject.toml` `[project].requires-python` (PEP 621)
153
- 3. `.python-version` file (pyenv/asdf convention)
154
- 4. Default: `3.11`
155
-
156
- Poetry's caret syntax (`^3.11`) is detected but not parsed — the tool logs a warning and suggests using `--python-version` or adding `[project].requires-python`.
153
+ 3. `pyproject.toml` `[tool.poetry.dependencies].python` — caret (`^3.10`), tilde (`~3.11`), and PEP 440 (`>=3.10,<3.14`) constraints are parsed to extract the minimum version. Dict-form (`{version = "^3.10"}`) is also supported. Union operators (`||`) are not supported and fall through with a warning.
154
+ 4. `.python-version` file (pyenv/asdf convention)
155
+ 5. Default: `3.11`
157
156
 
158
157
  ## Output format
159
158
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "modern-python-guidance"
7
- version = "0.4.3"
7
+ version = "0.4.4"
8
8
  description = "Version-aware BAD/GOOD pattern guides that help AI coding agents generate modern Python"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0 OR MIT"
@@ -1,3 +1,3 @@
1
1
  """Modern Python Guidance — version-aware BAD/GOOD pattern guides for AI coding agents."""
2
2
 
3
- __version__ = "0.4.3"
3
+ __version__ = "0.4.4"
@@ -3,8 +3,9 @@
3
3
  Precedence chain:
4
4
  1. CLI --python-version flag (explicit override)
5
5
  2. pyproject.toml [project].requires-python (PEP 621)
6
- 3. .python-version file (pyenv/asdf)
7
- 4. Default: 3.11
6
+ 3. pyproject.toml [tool.poetry.dependencies].python (caret/tilde/PEP 440)
7
+ 4. .python-version file (pyenv/asdf)
8
+ 5. Default: 3.11
8
9
  """
9
10
 
10
11
  from __future__ import annotations
@@ -24,6 +25,7 @@ DEFAULT_VERSION = "3.11"
24
25
  _KNOWN_MINORS = [Version(f"3.{minor}") for minor in range(7, 20)]
25
26
 
26
27
  _POETRY_CARET_RE = re.compile(r"\^(\d+\.\d+)")
28
+ _POETRY_TILDE_RE = re.compile(r"~(\d+\.\d+)")
27
29
 
28
30
 
29
31
  def detect_version(
@@ -68,21 +70,54 @@ def _from_pyproject(path: Path) -> str | None:
68
70
 
69
71
  poetry_python = data.get("tool", {}).get("poetry", {}).get("dependencies", {}).get("python")
70
72
  if poetry_python:
71
- m = _POETRY_CARET_RE.search(str(poetry_python))
72
- if m:
73
- log.warning(
74
- "Poetry caret version '%s' is not PEP 440 — cannot parse precisely. "
75
- "Use --python-version or add [project].requires-python to pyproject.toml.",
76
- poetry_python,
77
- )
78
- else:
79
- log.warning(
80
- "Poetry python constraint '%s' detected but not supported. "
81
- "Use --python-version or add [project].requires-python.",
82
- poetry_python,
83
- )
73
+ result = _parse_poetry_python(poetry_python)
74
+ if result is not None:
75
+ return result
76
+
77
+ return None
78
+
79
+
80
+ def _parse_poetry_python(value: str | dict) -> str | None:
81
+ if isinstance(value, dict):
82
+ value = value.get("version")
83
+ if not value:
84
+ log.warning("Poetry python constraint has no 'version' key")
85
+ return None
86
+
87
+ if not isinstance(value, str):
88
+ log.warning("Poetry python constraint has unexpected type %s", type(value).__name__)
89
+ return None
90
+
91
+ poetry_str = value
92
+
93
+ if "||" in poetry_str:
94
+ log.warning(
95
+ "Poetry union constraint '%s' is not supported. "
96
+ "Use --python-version or add [project].requires-python.",
97
+ poetry_str,
98
+ )
84
99
  return None
85
100
 
101
+ m = _POETRY_CARET_RE.search(poetry_str)
102
+ if m:
103
+ log.info("Parsed Poetry caret constraint '%s' → %s", poetry_str, m.group(1))
104
+ return m.group(1)
105
+
106
+ m = _POETRY_TILDE_RE.search(poetry_str)
107
+ if m:
108
+ log.info("Parsed Poetry tilde constraint '%s' → %s", poetry_str, m.group(1))
109
+ return m.group(1)
110
+
111
+ result = _min_version_from_specifier(poetry_str)
112
+ if result is not None:
113
+ log.info("Parsed Poetry PEP 440 constraint '%s' → %s", poetry_str, result)
114
+ return result
115
+
116
+ log.warning(
117
+ "Poetry python constraint '%s' detected but not supported. "
118
+ "Use --python-version or add [project].requires-python.",
119
+ poetry_str,
120
+ )
86
121
  return None
87
122
 
88
123
 
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import logging
3
4
  from pathlib import Path
4
5
 
5
6
  import pytest
@@ -44,13 +45,87 @@ class TestPyprojectToml:
44
45
  (tmp_project / "pyproject.toml").write_text('[project]\nrequires-python = ">=3.11.4"\n')
45
46
  assert detect_version(project_dir=tmp_project) == "3.11"
46
47
 
47
- def test_poetry_caret_warns_and_falls_through(self, tmp_project: Path, caplog):
48
+ def test_poetry_caret(self, tmp_project: Path, caplog):
48
49
  (tmp_project / "pyproject.toml").write_text(
49
50
  '[tool.poetry.dependencies]\npython = "^3.10"\n'
50
51
  )
51
- result = detect_version(project_dir=tmp_project)
52
- assert result == DEFAULT_VERSION
53
- assert "Poetry caret version" in caplog.text
52
+ with caplog.at_level(logging.INFO):
53
+ assert detect_version(project_dir=tmp_project) == "3.10"
54
+ assert "Parsed Poetry caret" in caplog.text
55
+
56
+ def test_poetry_caret_with_patch(self, tmp_project: Path):
57
+ (tmp_project / "pyproject.toml").write_text(
58
+ '[tool.poetry.dependencies]\npython = "^3.10.2"\n'
59
+ )
60
+ assert detect_version(project_dir=tmp_project) == "3.10"
61
+
62
+ def test_poetry_tilde(self, tmp_project: Path, caplog):
63
+ (tmp_project / "pyproject.toml").write_text(
64
+ '[tool.poetry.dependencies]\npython = "~3.11"\n'
65
+ )
66
+ with caplog.at_level(logging.INFO):
67
+ assert detect_version(project_dir=tmp_project) == "3.11"
68
+ assert "Parsed Poetry tilde" in caplog.text
69
+
70
+ def test_poetry_tilde_with_patch(self, tmp_project: Path):
71
+ (tmp_project / "pyproject.toml").write_text(
72
+ '[tool.poetry.dependencies]\npython = "~3.11.5"\n'
73
+ )
74
+ assert detect_version(project_dir=tmp_project) == "3.11"
75
+
76
+ def test_poetry_pep440_range(self, tmp_project: Path, caplog):
77
+ (tmp_project / "pyproject.toml").write_text(
78
+ '[tool.poetry.dependencies]\npython = ">=3.10,<3.14"\n'
79
+ )
80
+ with caplog.at_level(logging.INFO):
81
+ assert detect_version(project_dir=tmp_project) == "3.10"
82
+ assert "Parsed Poetry PEP 440" in caplog.text
83
+
84
+ def test_poetry_pep440_compatible(self, tmp_project: Path, caplog):
85
+ (tmp_project / "pyproject.toml").write_text(
86
+ '[tool.poetry.dependencies]\npython = "~=3.11"\n'
87
+ )
88
+ with caplog.at_level(logging.INFO):
89
+ assert detect_version(project_dir=tmp_project) == "3.11"
90
+ assert "Parsed Poetry PEP 440" in caplog.text
91
+ assert "Parsed Poetry tilde" not in caplog.text
92
+
93
+ def test_poetry_dict_form(self, tmp_project: Path):
94
+ (tmp_project / "pyproject.toml").write_text(
95
+ '[tool.poetry.dependencies.python]\nversion = "^3.10"\n'
96
+ )
97
+ assert detect_version(project_dir=tmp_project) == "3.10"
98
+
99
+ def test_poetry_dict_no_version_key(self, tmp_project: Path, caplog):
100
+ (tmp_project / "pyproject.toml").write_text(
101
+ "[tool.poetry.dependencies.python]\noptional = true\n"
102
+ )
103
+ assert detect_version(project_dir=tmp_project) == DEFAULT_VERSION
104
+ assert "no 'version' key" in caplog.text
105
+
106
+ def test_poetry_union_warns(self, tmp_project: Path, caplog):
107
+ (tmp_project / "pyproject.toml").write_text(
108
+ '[tool.poetry.dependencies]\npython = "^3.10 || ^3.12"\n'
109
+ )
110
+ assert detect_version(project_dir=tmp_project) == DEFAULT_VERSION
111
+ assert "union constraint" in caplog.text
112
+
113
+ def test_poetry_unsupported_warns(self, tmp_project: Path, caplog):
114
+ (tmp_project / "pyproject.toml").write_text('[tool.poetry.dependencies]\npython = "*"\n')
115
+ assert detect_version(project_dir=tmp_project) == DEFAULT_VERSION
116
+ assert "not supported" in caplog.text
117
+
118
+ def test_poetry_major_only_warns(self, tmp_project: Path, caplog):
119
+ (tmp_project / "pyproject.toml").write_text('[tool.poetry.dependencies]\npython = "^3"\n')
120
+ assert detect_version(project_dir=tmp_project) == DEFAULT_VERSION
121
+ assert "not supported" in caplog.text
122
+
123
+ def test_poetry_list_type_warns(self, tmp_project: Path, caplog):
124
+ (tmp_project / "pyproject.toml").write_text(
125
+ '[tool.poetry.dependencies]\npython = ["^3.10", "^3.12"]\n'
126
+ )
127
+ assert detect_version(project_dir=tmp_project) == DEFAULT_VERSION
128
+ assert "unexpected type" in caplog.text
54
129
 
55
130
  def test_malformed_toml(self, tmp_project: Path, caplog):
56
131
  (tmp_project / "pyproject.toml").write_text("this is not valid toml [[[")
@@ -91,12 +166,17 @@ class TestPrecedence:
91
166
  (tmp_project / ".python-version").write_text("3.10\n")
92
167
  assert detect_version(project_dir=tmp_project) == "3.12"
93
168
 
94
- def test_poetry_falls_to_python_version_file(self, tmp_project: Path):
169
+ def test_poetry_wins_over_python_version_file(self, tmp_project: Path):
95
170
  (tmp_project / "pyproject.toml").write_text(
96
171
  '[tool.poetry.dependencies]\npython = "^3.10"\n'
97
172
  )
98
173
  (tmp_project / ".python-version").write_text("3.11\n")
99
- assert detect_version(project_dir=tmp_project) == "3.11"
174
+ assert detect_version(project_dir=tmp_project) == "3.10"
175
+
176
+ def test_poetry_unsupported_falls_to_python_version_file(self, tmp_project: Path):
177
+ (tmp_project / "pyproject.toml").write_text('[tool.poetry.dependencies]\npython = "*"\n')
178
+ (tmp_project / ".python-version").write_text("3.12\n")
179
+ assert detect_version(project_dir=tmp_project) == "3.12"
100
180
 
101
181
 
102
182
  class TestDefault: