modern-python-guidance 0.1.1__tar.gz → 0.1.2__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.
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/CHANGELOG.md +13 -0
- modern_python_guidance-0.1.2/LICENSE-MIT +21 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/PKG-INFO +11 -9
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/README.md +7 -7
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/pyproject.toml +3 -2
- modern_python_guidance-0.1.2/skills/modern-python-guidance/SKILL.md +62 -0
- modern_python_guidance-0.1.2/tests/test_skill_sync.py +126 -0
- modern_python_guidance-0.1.1/skills/modern-python-guidance/SKILL.md +0 -104
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/.github/workflows/ci.yml +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/.github/workflows/publish.yml +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/.gitignore +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/LICENSE +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/SECURITY.md +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/docs/design.md +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/skills/modern-python-guidance/guides/async/async-timeout-context.md +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/skills/modern-python-guidance/guides/async/exception-groups.md +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/skills/modern-python-guidance/guides/async/taskgroup-over-gather.md +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/skills/modern-python-guidance/guides/data-structures/dataclass-modern.md +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/skills/modern-python-guidance/guides/data-structures/dict-merge-operator.md +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/skills/modern-python-guidance/guides/data-structures/match-case-patterns.md +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/skills/modern-python-guidance/guides/fastapi/fastapi-annotated-depends.md +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/skills/modern-python-guidance/guides/fastapi/fastapi-lifespan.md +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/skills/modern-python-guidance/guides/fastapi/fastapi-typed-state.md +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/skills/modern-python-guidance/guides/httpx/httpx-async-client-reuse.md +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/skills/modern-python-guidance/guides/httpx/httpx-streaming.md +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/skills/modern-python-guidance/guides/pydantic/pydantic-v2-config.md +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/skills/modern-python-guidance/guides/pydantic/pydantic-v2-model-api.md +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/skills/modern-python-guidance/guides/pydantic/pydantic-v2-serialization.md +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/skills/modern-python-guidance/guides/pydantic/pydantic-v2-validators.md +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/skills/modern-python-guidance/guides/stdlib/datetime-utc.md +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/skills/modern-python-guidance/guides/stdlib/pathlib-over-os-path.md +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/skills/modern-python-guidance/guides/stdlib/removeprefix-removesuffix.md +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/skills/modern-python-guidance/guides/stdlib/tomllib-builtin.md +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/skills/modern-python-guidance/guides/toolchain/no-pickle.md +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/skills/modern-python-guidance/guides/toolchain/pyproject-toml-over-setup.md +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/skills/modern-python-guidance/guides/toolchain/ruff-over-flake8.md +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/skills/modern-python-guidance/guides/toolchain/safe-subprocess.md +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/skills/modern-python-guidance/guides/toolchain/uv-over-pip.md +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/skills/modern-python-guidance/guides/typing/override-decorator.md +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/skills/modern-python-guidance/guides/typing/paramspec-decorators.md +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/skills/modern-python-guidance/guides/typing/type-parameter-syntax.md +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/skills/modern-python-guidance/guides/typing/typeis-vs-typeguard.md +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/skills/modern-python-guidance/guides/typing/union-syntax.md +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/skills/modern-python-guidance/guides/typing/use-builtin-generics.md +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/src/modern_python_guidance/__init__.py +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/src/modern_python_guidance/__main__.py +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/src/modern_python_guidance/cli.py +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/src/modern_python_guidance/compat.py +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/src/modern_python_guidance/frontmatter.py +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/src/modern_python_guidance/guide_index.py +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/src/modern_python_guidance/mcp_server.py +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/src/modern_python_guidance/retrieve.py +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/src/modern_python_guidance/search.py +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/src/modern_python_guidance/version_detect.py +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/tests/test_cli_integration.py +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/tests/test_frontmatter.py +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/tests/test_mcp_server.py +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/tests/test_retrieve.py +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/tests/test_search.py +0 -0
- {modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/tests/test_version_detect.py +0 -0
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.1.2] — 2026-05-26
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- SKILL.md: replace inventory tables with 9 embedded BAD→GOOD arrow-list patterns (high-frequency × Ruff-uncovered) for pre-generation injection without MCP tool calls
|
|
10
|
+
- README: Quick start example changed from `use-builtin-generics` to `pydantic-v2-validators` (Layer 2 differentiation)
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- MIT license (dual-licensed under Apache-2.0 OR MIT)
|
|
15
|
+
- `test_skill_sync.py`: 8 sync tests for SKILL.md ↔ guide file consistency (V-001/V-002/V-009/V-010)
|
|
16
|
+
|
|
5
17
|
## [0.1.1] — 2026-05-25
|
|
6
18
|
|
|
7
19
|
### Added
|
|
@@ -30,5 +42,6 @@ Initial release.
|
|
|
30
42
|
- Strict YAML-subset frontmatter parser (no PyYAML dependency)
|
|
31
43
|
- GitHub Actions CI (pytest + ruff on Python 3.11, 3.12, 3.13)
|
|
32
44
|
|
|
45
|
+
[0.1.2]: https://github.com/yottayoshida/modern-python-guidance/releases/tag/v0.1.2
|
|
33
46
|
[0.1.1]: https://github.com/yottayoshida/modern-python-guidance/releases/tag/v0.1.1
|
|
34
47
|
[0.1.0]: https://github.com/yottayoshida/modern-python-guidance/releases/tag/v0.1.0
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Iori Yoshida
|
|
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.
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modern-python-guidance
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
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
|
|
7
7
|
Project-URL: Issues, https://github.com/yottayoshida/modern-python-guidance/issues
|
|
8
8
|
Author-email: Iori Yoshida <i.yoshida@raksul.com>
|
|
9
|
-
License-Expression: Apache-2.0
|
|
9
|
+
License-Expression: Apache-2.0 OR MIT
|
|
10
10
|
License-File: LICENSE
|
|
11
|
+
License-File: LICENSE-MIT
|
|
11
12
|
Keywords: ai,coding-agent,guidance,linter,modernization,python
|
|
12
13
|
Classifier: Development Status :: 3 - Alpha
|
|
13
14
|
Classifier: Environment :: Console
|
|
14
15
|
Classifier: Intended Audience :: Developers
|
|
15
16
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
18
|
Classifier: Programming Language :: Python :: 3
|
|
17
19
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
20
|
Classifier: Programming Language :: Python :: 3.12
|
|
@@ -45,17 +47,17 @@ LLMs often produce outdated Python — `typing.List` instead of `list`, `@valida
|
|
|
45
47
|
pip install modern-python-guidance
|
|
46
48
|
|
|
47
49
|
# Search for a pattern
|
|
48
|
-
mpg search "
|
|
49
|
-
#
|
|
50
|
+
mpg search "pydantic validator"
|
|
51
|
+
# pydantic-v2-validators score=18.0 [pydantic]
|
|
50
52
|
|
|
51
53
|
# Get the full guide
|
|
52
|
-
mpg retrieve
|
|
53
|
-
# ---
|
|
54
|
+
mpg retrieve pydantic-v2-validators
|
|
55
|
+
# --- pydantic-v2-validators (version match: YES) ---
|
|
54
56
|
# ## BAD
|
|
55
|
-
#
|
|
57
|
+
# @validator("name")
|
|
56
58
|
# ...
|
|
57
59
|
# ## GOOD
|
|
58
|
-
#
|
|
60
|
+
# @field_validator("name")
|
|
59
61
|
# ...
|
|
60
62
|
```
|
|
61
63
|
|
|
@@ -212,4 +214,4 @@ Contributions welcome! To add a new guide:
|
|
|
212
214
|
|
|
213
215
|
## License
|
|
214
216
|
|
|
215
|
-
Apache-2.0 — see [LICENSE](LICENSE).
|
|
217
|
+
Apache-2.0 OR MIT — see [LICENSE](LICENSE) and [LICENSE-MIT](LICENSE-MIT).
|
|
@@ -16,17 +16,17 @@ LLMs often produce outdated Python — `typing.List` instead of `list`, `@valida
|
|
|
16
16
|
pip install modern-python-guidance
|
|
17
17
|
|
|
18
18
|
# Search for a pattern
|
|
19
|
-
mpg search "
|
|
20
|
-
#
|
|
19
|
+
mpg search "pydantic validator"
|
|
20
|
+
# pydantic-v2-validators score=18.0 [pydantic]
|
|
21
21
|
|
|
22
22
|
# Get the full guide
|
|
23
|
-
mpg retrieve
|
|
24
|
-
# ---
|
|
23
|
+
mpg retrieve pydantic-v2-validators
|
|
24
|
+
# --- pydantic-v2-validators (version match: YES) ---
|
|
25
25
|
# ## BAD
|
|
26
|
-
#
|
|
26
|
+
# @validator("name")
|
|
27
27
|
# ...
|
|
28
28
|
# ## GOOD
|
|
29
|
-
#
|
|
29
|
+
# @field_validator("name")
|
|
30
30
|
# ...
|
|
31
31
|
```
|
|
32
32
|
|
|
@@ -183,4 +183,4 @@ Contributions welcome! To add a new guide:
|
|
|
183
183
|
|
|
184
184
|
## License
|
|
185
185
|
|
|
186
|
-
Apache-2.0 — see [LICENSE](LICENSE).
|
|
186
|
+
Apache-2.0 OR MIT — see [LICENSE](LICENSE) and [LICENSE-MIT](LICENSE-MIT).
|
|
@@ -4,10 +4,10 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "modern-python-guidance"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.2"
|
|
8
8
|
description = "Version-aware BAD/GOOD pattern guides that help AI coding agents generate modern Python"
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
license = "Apache-2.0"
|
|
10
|
+
license = "Apache-2.0 OR MIT"
|
|
11
11
|
requires-python = ">=3.11"
|
|
12
12
|
authors = [
|
|
13
13
|
{ name = "Iori Yoshida", email = "i.yoshida@raksul.com" },
|
|
@@ -18,6 +18,7 @@ classifiers = [
|
|
|
18
18
|
"Environment :: Console",
|
|
19
19
|
"Intended Audience :: Developers",
|
|
20
20
|
"License :: OSI Approved :: Apache Software License",
|
|
21
|
+
"License :: OSI Approved :: MIT License",
|
|
21
22
|
"Programming Language :: Python :: 3",
|
|
22
23
|
"Programming Language :: Python :: 3.11",
|
|
23
24
|
"Programming Language :: Python :: 3.12",
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: modern-python-guidance
|
|
3
|
+
description: Version-aware BAD/GOOD pattern guides for modern Python. Use when writing, reviewing, or refactoring Python code to avoid outdated patterns (e.g. typing.List → list, @validator → @field_validator, setup.py → pyproject.toml). Triggers on "Python", "modernize", "upgrade", "deprecated", "pydantic", "fastapi", "httpx", "typing", "dataclass", "asyncio".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Modern Python Guidance
|
|
7
|
+
|
|
8
|
+
Pre-generation design patterns that Ruff cannot auto-fix. Apply these transformations when writing or reviewing Python code.
|
|
9
|
+
|
|
10
|
+
## When to use
|
|
11
|
+
|
|
12
|
+
- Writing new Python code (use modern patterns from the start)
|
|
13
|
+
- Reviewing Python code (flag outdated patterns)
|
|
14
|
+
- Migrating from Pydantic V1 to V2
|
|
15
|
+
- Upgrading Python version (check which new features are available)
|
|
16
|
+
- Replacing legacy tooling (setup.py, flake8, pip)
|
|
17
|
+
|
|
18
|
+
## Embedded patterns (high-frequency, Ruff-uncovered)
|
|
19
|
+
|
|
20
|
+
### Pydantic V2 (>=3.9)
|
|
21
|
+
|
|
22
|
+
- `@validator("f")` → `@field_validator("f")`
|
|
23
|
+
- `@root_validator` → `@model_validator(mode="after")`
|
|
24
|
+
- `class Config:` → `model_config = ConfigDict(...)`
|
|
25
|
+
- `orm_mode` → `from_attributes`, `allow_population_by_field_name` → `populate_by_name`
|
|
26
|
+
- `.parse_obj(d)` → `.model_validate(d)`, `.parse_raw(j)` → `.model_validate_json(j)`
|
|
27
|
+
- `.dict()` → `.model_dump()`, `.json()` → `.model_dump_json()`
|
|
28
|
+
- `.schema()` → `.model_json_schema()`, `.copy()` → `.model_copy()`
|
|
29
|
+
|
|
30
|
+
### FastAPI (>=3.9)
|
|
31
|
+
|
|
32
|
+
- `@app.on_event("startup")`/`"shutdown"` → `@asynccontextmanager` lifespan + `FastAPI(lifespan=lifespan)`; yield dict becomes `request.state`
|
|
33
|
+
- `db: Session = Depends(get_db)` → `DbDep = Annotated[Session, Depends(get_db)]`; reusable type alias per PEP 593
|
|
34
|
+
|
|
35
|
+
### httpx
|
|
36
|
+
|
|
37
|
+
- Per-request `async with httpx.AsyncClient()` → shared `AsyncClient` with `base_url`
|
|
38
|
+
- Caveat: shared client must be closed via `async with` or lifespan management
|
|
39
|
+
|
|
40
|
+
### asyncio (>=3.11)
|
|
41
|
+
|
|
42
|
+
- `await asyncio.gather(a(), b())` → `async with asyncio.TaskGroup() as tg:` + `tg.create_task()`; access results via `task.result()`
|
|
43
|
+
- Caveat: 3.11+ only. `TaskGroup` cancels siblings on error and raises `ExceptionGroup`; `gather` preserves return order and supports `return_exceptions=True`
|
|
44
|
+
|
|
45
|
+
### Toolchain
|
|
46
|
+
|
|
47
|
+
- `setup.py` / `setup.cfg` → `pyproject.toml` with `[build-system]` + `[project]` (PEP 621)
|
|
48
|
+
- `subprocess.run(f"cmd {arg}", shell=True)` → `subprocess.run(["cmd", arg], check=True)`
|
|
49
|
+
- Caveat: `shell=True` is valid when pipes/globs are needed; use `shlex.quote()` for user input
|
|
50
|
+
|
|
51
|
+
## All 30 guides by category
|
|
52
|
+
|
|
53
|
+
- **typing** (6): `use-builtin-generics`, `union-syntax`, `type-parameter-syntax`, `override-decorator`, `typeis-vs-typeguard`, `paramspec-decorators`
|
|
54
|
+
- **async** (3): `taskgroup-over-gather`, `exception-groups`, `async-timeout-context`
|
|
55
|
+
- **stdlib** (4): `datetime-utc`, `pathlib-over-os-path`, `tomllib-builtin`, `removeprefix-removesuffix`
|
|
56
|
+
- **data-structures** (3): `dict-merge-operator`, `match-case-patterns`, `dataclass-modern`
|
|
57
|
+
- **pydantic** (4): `pydantic-v2-validators`, `pydantic-v2-config`, `pydantic-v2-model-api`, `pydantic-v2-serialization`
|
|
58
|
+
- **fastapi** (3): `fastapi-lifespan`, `fastapi-annotated-depends`, `fastapi-typed-state`
|
|
59
|
+
- **httpx** (2): `httpx-async-client-reuse`, `httpx-streaming`
|
|
60
|
+
- **toolchain** (5): `pyproject-toml-over-setup`, `uv-over-pip`, `ruff-over-flake8`, `no-pickle`, `safe-subprocess`
|
|
61
|
+
|
|
62
|
+
For full code examples, use `mpg retrieve <guide-id>` or MCP tool `retrieve_guides`.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Sync tests: verify SKILL.md stays consistent with guide files and README.
|
|
2
|
+
|
|
3
|
+
Verification IDs from /plan QA shift-left:
|
|
4
|
+
V-001 SKILL.md guide IDs reference existing guide files
|
|
5
|
+
V-002 SKILL.md token count <= 1300 (chars/4)
|
|
6
|
+
V-009 All embedded guide IDs have frequency: high
|
|
7
|
+
V-010 README Quick start guide IDs reference existing guide files
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import re
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
import pytest
|
|
16
|
+
|
|
17
|
+
from modern_python_guidance.guide_index import build_index
|
|
18
|
+
|
|
19
|
+
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
20
|
+
SKILL_MD = REPO_ROOT / "skills" / "modern-python-guidance" / "SKILL.md"
|
|
21
|
+
README_MD = REPO_ROOT / "README.md"
|
|
22
|
+
GUIDES_DIR = REPO_ROOT / "skills" / "modern-python-guidance" / "guides"
|
|
23
|
+
|
|
24
|
+
EMBEDDED_GUIDE_IDS = [
|
|
25
|
+
"pydantic-v2-validators",
|
|
26
|
+
"pydantic-v2-config",
|
|
27
|
+
"pydantic-v2-model-api",
|
|
28
|
+
"fastapi-lifespan",
|
|
29
|
+
"fastapi-annotated-depends",
|
|
30
|
+
"httpx-async-client-reuse",
|
|
31
|
+
"taskgroup-over-gather",
|
|
32
|
+
"pyproject-toml-over-setup",
|
|
33
|
+
"safe-subprocess",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@pytest.fixture(scope="module")
|
|
38
|
+
def guide_index():
|
|
39
|
+
return build_index(GUIDES_DIR)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest.fixture(scope="module")
|
|
43
|
+
def skill_text():
|
|
44
|
+
return SKILL_MD.read_text(encoding="utf-8")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@pytest.fixture(scope="module")
|
|
48
|
+
def readme_text():
|
|
49
|
+
return README_MD.read_text(encoding="utf-8")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _extract_backtick_ids(text: str) -> list[str]:
|
|
53
|
+
"""Extract guide IDs from backtick-quoted strings in the catalog section."""
|
|
54
|
+
return re.findall(r"`([a-z][a-z0-9-]+)`", text)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class TestV001SkillGuideSync:
|
|
58
|
+
"""V-001: SKILL.md guide IDs reference existing guide files."""
|
|
59
|
+
|
|
60
|
+
def test_embedded_guides_exist(self, guide_index):
|
|
61
|
+
for guide_id in EMBEDDED_GUIDE_IDS:
|
|
62
|
+
assert guide_index.get(guide_id) is not None, (
|
|
63
|
+
f"Embedded guide '{guide_id}' not found in guides/"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def test_catalog_heading_exists(self, skill_text):
|
|
67
|
+
assert "## All 30 guides by category" in skill_text, (
|
|
68
|
+
"Catalog heading missing from SKILL.md"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def test_catalog_ids_exist(self, skill_text, guide_index):
|
|
72
|
+
catalog_section = skill_text.split("## All 30 guides by category")[-1]
|
|
73
|
+
ids_in_catalog = _extract_backtick_ids(catalog_section)
|
|
74
|
+
for guide_id in ids_in_catalog:
|
|
75
|
+
assert guide_index.get(guide_id) is not None, (
|
|
76
|
+
f"Catalog guide '{guide_id}' not found in guides/"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def test_catalog_count_matches(self, guide_index, skill_text):
|
|
80
|
+
assert "30 guides" in skill_text
|
|
81
|
+
assert len(guide_index) == 30, (
|
|
82
|
+
f"SKILL.md says 30 guides but found {len(guide_index)}"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def test_catalog_covers_all_guides(self, skill_text, guide_index):
|
|
86
|
+
"""Reverse check: every guide ID appears in the catalog section."""
|
|
87
|
+
catalog_section = skill_text.split("## All 30 guides by category")[-1]
|
|
88
|
+
catalog_ids = set(_extract_backtick_ids(catalog_section))
|
|
89
|
+
for guide_id in guide_index.guides:
|
|
90
|
+
assert guide_id in catalog_ids, (
|
|
91
|
+
f"Guide '{guide_id}' exists in guides/ but missing from catalog"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class TestV002TokenBudget:
|
|
96
|
+
"""V-002: SKILL.md token count <= 1300 (chars/4)."""
|
|
97
|
+
|
|
98
|
+
def test_token_budget(self, skill_text):
|
|
99
|
+
tokens = len(skill_text) // 4
|
|
100
|
+
assert tokens <= 1300, (
|
|
101
|
+
f"SKILL.md is {tokens} tokens (chars/4), budget is 1300"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class TestV009EmbeddedFrequency:
|
|
106
|
+
"""V-009: All embedded guide IDs have frequency: high."""
|
|
107
|
+
|
|
108
|
+
def test_all_high_frequency(self, guide_index):
|
|
109
|
+
for guide_id in EMBEDDED_GUIDE_IDS:
|
|
110
|
+
guide = guide_index.get(guide_id)
|
|
111
|
+
assert guide is not None, f"Guide '{guide_id}' not found"
|
|
112
|
+
assert guide.meta.frequency == "high", (
|
|
113
|
+
f"Embedded guide '{guide_id}' has frequency "
|
|
114
|
+
f"'{guide.meta.frequency}', expected 'high'"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class TestV010ReadmeGuideIds:
|
|
119
|
+
"""V-010: README Quick start guide IDs reference existing guide files."""
|
|
120
|
+
|
|
121
|
+
def test_readme_guide_ids_exist(self, readme_text, guide_index):
|
|
122
|
+
ids_in_readme = re.findall(r"mpg retrieve\s+([a-z][a-z0-9-]+)", readme_text)
|
|
123
|
+
for guide_id in ids_in_readme:
|
|
124
|
+
assert guide_index.get(guide_id) is not None, (
|
|
125
|
+
f"README references guide '{guide_id}' which doesn't exist"
|
|
126
|
+
)
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: modern-python-guidance
|
|
3
|
-
description: Version-aware BAD/GOOD pattern guides for modern Python. Use when writing, reviewing, or refactoring Python code to avoid outdated patterns (e.g. typing.List → list, @validator → @field_validator, setup.py → pyproject.toml). Triggers on "Python", "modernize", "upgrade", "deprecated", "pydantic", "fastapi", "httpx", "typing", "dataclass", "asyncio".
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Modern Python Guidance
|
|
7
|
-
|
|
8
|
-
Version-aware BAD → GOOD pattern guides for modern Python (3.9–3.13+).
|
|
9
|
-
|
|
10
|
-
When writing or reviewing Python code, consult these guides to ensure modern idioms are used instead of deprecated or outdated patterns. Each guide shows a concrete BAD example, the modern GOOD replacement, and explains why the change matters.
|
|
11
|
-
|
|
12
|
-
## When to use
|
|
13
|
-
|
|
14
|
-
- Writing new Python code (use modern patterns from the start)
|
|
15
|
-
- Reviewing Python code (flag outdated patterns)
|
|
16
|
-
- Migrating from Pydantic V1 to V2
|
|
17
|
-
- Upgrading Python version (check which new features are available)
|
|
18
|
-
- Replacing legacy tooling (setup.py, flake8, pip)
|
|
19
|
-
|
|
20
|
-
## Guide inventory (30 guides)
|
|
21
|
-
|
|
22
|
-
### Layer 1 — Standard Library & Language Features
|
|
23
|
-
|
|
24
|
-
| Category | Guide | Python | What it replaces |
|
|
25
|
-
|----------|-------|--------|-----------------|
|
|
26
|
-
| typing | `use-builtin-generics` | >=3.9 | `typing.List` → `list` |
|
|
27
|
-
| typing | `union-syntax` | >=3.10 | `Optional[X]` → `X \| None` |
|
|
28
|
-
| typing | `type-parameter-syntax` | >=3.12 | `TypeVar("T")` → `[T]` |
|
|
29
|
-
| typing | `override-decorator` | >=3.12 | manual override → `@override` |
|
|
30
|
-
| typing | `typeis-vs-typeguard` | >=3.13 | `TypeGuard` → `TypeIs` |
|
|
31
|
-
| typing | `paramspec-decorators` | >=3.10 | untyped decorators → `ParamSpec` |
|
|
32
|
-
| async | `taskgroup-over-gather` | >=3.11 | `gather()` → `TaskGroup` |
|
|
33
|
-
| async | `exception-groups` | >=3.11 | multi-error handling → `except*` |
|
|
34
|
-
| async | `async-timeout-context` | >=3.11 | `wait_for()` → `asyncio.timeout` |
|
|
35
|
-
| stdlib | `datetime-utc` | >=3.11 | `utcnow()` → `now(UTC)` |
|
|
36
|
-
| stdlib | `pathlib-over-os-path` | >=3.9 | `os.path` → `pathlib.Path` |
|
|
37
|
-
| stdlib | `tomllib-builtin` | >=3.11 | `toml` package → `tomllib` |
|
|
38
|
-
| stdlib | `removeprefix-removesuffix` | >=3.9 | `lstrip()`/slicing → `removeprefix()` |
|
|
39
|
-
| data-structures | `dict-merge-operator` | >=3.9 | `{**d1, **d2}` → `d1 \| d2` |
|
|
40
|
-
| data-structures | `match-case-patterns` | >=3.10 | nested if/isinstance → `match`/`case` |
|
|
41
|
-
| data-structures | `dataclass-modern` | >=3.10 | basic dataclass → `slots=True, kw_only=True` |
|
|
42
|
-
|
|
43
|
-
### Layer 2 — Popular Frameworks
|
|
44
|
-
|
|
45
|
-
| Category | Guide | What it replaces |
|
|
46
|
-
|----------|-------|-----------------|
|
|
47
|
-
| pydantic | `pydantic-v2-model-api` | `parse_obj()` → `model_validate()` |
|
|
48
|
-
| pydantic | `pydantic-v2-validators` | `@validator` → `@field_validator` |
|
|
49
|
-
| pydantic | `pydantic-v2-config` | `class Config` → `model_config = ConfigDict(...)` |
|
|
50
|
-
| pydantic | `pydantic-v2-serialization` | `json_encoders` → `@field_serializer` |
|
|
51
|
-
| fastapi | `fastapi-lifespan` | `@on_event` → lifespan context manager |
|
|
52
|
-
| fastapi | `fastapi-annotated-depends` | `Depends()` default → `Annotated[T, Depends()]` |
|
|
53
|
-
| fastapi | `fastapi-typed-state` | untyped `app.state` → typed state via lifespan |
|
|
54
|
-
| httpx | `httpx-async-client-reuse` | per-request client → shared `AsyncClient` |
|
|
55
|
-
| httpx | `httpx-streaming` | `response.content` → `client.stream()` |
|
|
56
|
-
|
|
57
|
-
### Layer 3 — Toolchain & Security
|
|
58
|
-
|
|
59
|
-
| Category | Guide | What it replaces |
|
|
60
|
-
|----------|-------|-----------------|
|
|
61
|
-
| toolchain | `pyproject-toml-over-setup` | `setup.py` → `pyproject.toml` |
|
|
62
|
-
| toolchain | `uv-over-pip` | `pip` → `uv` |
|
|
63
|
-
| toolchain | `ruff-over-flake8` | flake8+isort+black → `ruff` |
|
|
64
|
-
| toolchain | `no-pickle` | `pickle.load()` → safe alternatives |
|
|
65
|
-
| toolchain | `safe-subprocess` | `shell=True` → list arguments |
|
|
66
|
-
|
|
67
|
-
## How to look up a guide
|
|
68
|
-
|
|
69
|
-
Each guide is a markdown file in `guides/<category>/<id>.md` with YAML frontmatter containing:
|
|
70
|
-
- `id`: unique identifier
|
|
71
|
-
- `python`: minimum Python version (e.g. `">=3.11"`)
|
|
72
|
-
- `frequency`: how often LLMs generate the outdated pattern (`high`/`medium`/`low`)
|
|
73
|
-
- `layer`: 1 (stdlib), 2 (frameworks), 3 (toolchain)
|
|
74
|
-
|
|
75
|
-
### Reading a guide directly
|
|
76
|
-
|
|
77
|
-
Open `guides/<category>/<guide-id>.md` for the full BAD/GOOD comparison.
|
|
78
|
-
|
|
79
|
-
### Using the CLI
|
|
80
|
-
|
|
81
|
-
```bash
|
|
82
|
-
# Search by keyword
|
|
83
|
-
mpg search "typing list"
|
|
84
|
-
|
|
85
|
-
# Retrieve full guide content
|
|
86
|
-
mpg retrieve use-builtin-generics
|
|
87
|
-
|
|
88
|
-
# List all guides for a Python version
|
|
89
|
-
mpg list --python-version 3.11
|
|
90
|
-
|
|
91
|
-
# Detect project Python version
|
|
92
|
-
mpg detect-version
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
## Integration pattern for agents
|
|
96
|
-
|
|
97
|
-
When generating Python code:
|
|
98
|
-
|
|
99
|
-
1. Check if the target Python version is known (from `pyproject.toml`, `.python-version`, or context)
|
|
100
|
-
2. For each pattern you're about to write, check if a guide exists for a modern replacement
|
|
101
|
-
3. Use the GOOD pattern instead of the BAD pattern
|
|
102
|
-
4. If the target Python version is too old for the modern pattern, use the older pattern and note it
|
|
103
|
-
|
|
104
|
-
Example: if writing `from typing import List` for a Python 3.9+ project, use `list` instead (see `use-builtin-generics`).
|
|
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
|
|
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
|
{modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/src/modern_python_guidance/__init__.py
RENAMED
|
File without changes
|
{modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/src/modern_python_guidance/__main__.py
RENAMED
|
File without changes
|
{modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/src/modern_python_guidance/cli.py
RENAMED
|
File without changes
|
{modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/src/modern_python_guidance/compat.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/src/modern_python_guidance/retrieve.py
RENAMED
|
File without changes
|
{modern_python_guidance-0.1.1 → modern_python_guidance-0.1.2}/src/modern_python_guidance/search.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|