modern-python-guidance 0.1.0__py3-none-any.whl
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/__init__.py +3 -0
- modern_python_guidance/__main__.py +5 -0
- modern_python_guidance/cli.py +202 -0
- modern_python_guidance/compat.py +22 -0
- modern_python_guidance/frontmatter.py +166 -0
- modern_python_guidance/guide_index.py +96 -0
- modern_python_guidance/retrieve.py +56 -0
- modern_python_guidance/search.py +149 -0
- modern_python_guidance/skills/modern-python-guidance/SKILL.md +104 -0
- modern_python_guidance/skills/modern-python-guidance/guides/async/async-timeout-context.md +65 -0
- modern_python_guidance/skills/modern-python-guidance/guides/async/exception-groups.md +70 -0
- modern_python_guidance/skills/modern-python-guidance/guides/async/taskgroup-over-gather.md +63 -0
- modern_python_guidance/skills/modern-python-guidance/guides/data-structures/dataclass-modern.md +73 -0
- modern_python_guidance/skills/modern-python-guidance/guides/data-structures/dict-merge-operator.md +63 -0
- modern_python_guidance/skills/modern-python-guidance/guides/data-structures/match-case-patterns.md +70 -0
- modern_python_guidance/skills/modern-python-guidance/guides/fastapi/fastapi-annotated-depends.md +80 -0
- modern_python_guidance/skills/modern-python-guidance/guides/fastapi/fastapi-lifespan.md +77 -0
- modern_python_guidance/skills/modern-python-guidance/guides/fastapi/fastapi-typed-state.md +76 -0
- modern_python_guidance/skills/modern-python-guidance/guides/httpx/httpx-async-client-reuse.md +70 -0
- modern_python_guidance/skills/modern-python-guidance/guides/httpx/httpx-streaming.md +66 -0
- modern_python_guidance/skills/modern-python-guidance/guides/pydantic/pydantic-v2-config.md +73 -0
- modern_python_guidance/skills/modern-python-guidance/guides/pydantic/pydantic-v2-model-api.md +79 -0
- modern_python_guidance/skills/modern-python-guidance/guides/pydantic/pydantic-v2-serialization.md +71 -0
- modern_python_guidance/skills/modern-python-guidance/guides/pydantic/pydantic-v2-validators.md +83 -0
- modern_python_guidance/skills/modern-python-guidance/guides/stdlib/datetime-utc.md +56 -0
- modern_python_guidance/skills/modern-python-guidance/guides/stdlib/pathlib-over-os-path.md +68 -0
- modern_python_guidance/skills/modern-python-guidance/guides/stdlib/removeprefix-removesuffix.md +64 -0
- modern_python_guidance/skills/modern-python-guidance/guides/stdlib/tomllib-builtin.md +59 -0
- modern_python_guidance/skills/modern-python-guidance/guides/toolchain/no-pickle.md +79 -0
- modern_python_guidance/skills/modern-python-guidance/guides/toolchain/pyproject-toml-over-setup.md +69 -0
- modern_python_guidance/skills/modern-python-guidance/guides/toolchain/ruff-over-flake8.md +90 -0
- modern_python_guidance/skills/modern-python-guidance/guides/toolchain/safe-subprocess.md +79 -0
- modern_python_guidance/skills/modern-python-guidance/guides/toolchain/uv-over-pip.md +68 -0
- modern_python_guidance/skills/modern-python-guidance/guides/typing/override-decorator.md +65 -0
- modern_python_guidance/skills/modern-python-guidance/guides/typing/paramspec-decorators.md +81 -0
- modern_python_guidance/skills/modern-python-guidance/guides/typing/type-parameter-syntax.md +66 -0
- modern_python_guidance/skills/modern-python-guidance/guides/typing/typeis-vs-typeguard.md +66 -0
- modern_python_guidance/skills/modern-python-guidance/guides/typing/union-syntax.md +59 -0
- modern_python_guidance/skills/modern-python-guidance/guides/typing/use-builtin-generics.md +61 -0
- modern_python_guidance/version_detect.py +136 -0
- modern_python_guidance-0.1.0.dist-info/METADATA +180 -0
- modern_python_guidance-0.1.0.dist-info/RECORD +45 -0
- modern_python_guidance-0.1.0.dist-info/WHEEL +4 -0
- modern_python_guidance-0.1.0.dist-info/entry_points.txt +3 -0
- modern_python_guidance-0.1.0.dist-info/licenses/LICENSE +190 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: typeis-vs-typeguard
|
|
3
|
+
title: Use TypeIs for Precise Type Narrowing
|
|
4
|
+
category: typing
|
|
5
|
+
layer: 1
|
|
6
|
+
tags:
|
|
7
|
+
- type-hints
|
|
8
|
+
- narrowing
|
|
9
|
+
- typeguard
|
|
10
|
+
aliases:
|
|
11
|
+
- TypeGuard
|
|
12
|
+
- typing.TypeGuard
|
|
13
|
+
- typing.TypeIs
|
|
14
|
+
python: ">=3.13"
|
|
15
|
+
frequency: low
|
|
16
|
+
pep: 742
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Use TypeIs for Precise Type Narrowing
|
|
20
|
+
|
|
21
|
+
Since Python 3.13, use `TypeIs` instead of `TypeGuard` for type narrowing functions. `TypeIs` narrows in both branches (true and false), while `TypeGuard` only narrows in the true branch.
|
|
22
|
+
|
|
23
|
+
## BAD
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from typing import TypeGuard
|
|
27
|
+
|
|
28
|
+
def is_str(val: str | int) -> TypeGuard[str]:
|
|
29
|
+
return isinstance(val, str)
|
|
30
|
+
|
|
31
|
+
def process(val: str | int) -> None:
|
|
32
|
+
if is_str(val):
|
|
33
|
+
print(val.upper()) # OK: narrowed to str
|
|
34
|
+
else:
|
|
35
|
+
print(val + 1) # ERROR: still str | int, not narrowed to int
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## GOOD
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from typing import TypeIs
|
|
42
|
+
|
|
43
|
+
def is_str(val: str | int) -> TypeIs[str]:
|
|
44
|
+
return isinstance(val, str)
|
|
45
|
+
|
|
46
|
+
def process(val: str | int) -> None:
|
|
47
|
+
if is_str(val):
|
|
48
|
+
print(val.upper()) # OK: narrowed to str
|
|
49
|
+
else:
|
|
50
|
+
print(val + 1) # OK: narrowed to int
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Why
|
|
54
|
+
|
|
55
|
+
- `TypeIs` narrows both branches — the false branch gets the complement type
|
|
56
|
+
- `TypeGuard` was too permissive — it could widen types unsoundly
|
|
57
|
+
- Safer and more precise for runtime type checking functions
|
|
58
|
+
|
|
59
|
+
## Version Notes
|
|
60
|
+
|
|
61
|
+
- 3.13+: `from typing import TypeIs`
|
|
62
|
+
- 3.10-3.12: `from typing_extensions import TypeIs`
|
|
63
|
+
|
|
64
|
+
## References
|
|
65
|
+
|
|
66
|
+
- [PEP 742 — Narrowing types with TypeIs](https://peps.python.org/pep-0742/)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: union-syntax
|
|
3
|
+
title: Use X | Y Union Syntax Instead of Optional/Union
|
|
4
|
+
category: typing
|
|
5
|
+
layer: 1
|
|
6
|
+
tags:
|
|
7
|
+
- type-hints
|
|
8
|
+
- union
|
|
9
|
+
- optional
|
|
10
|
+
aliases:
|
|
11
|
+
- Optional
|
|
12
|
+
- typing.Union
|
|
13
|
+
- typing.Optional
|
|
14
|
+
python: ">=3.10"
|
|
15
|
+
frequency: high
|
|
16
|
+
pep: 604
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Use X | Y Union Syntax
|
|
20
|
+
|
|
21
|
+
Since Python 3.10, use the `|` operator for union types instead of `Union` or `Optional` from `typing`.
|
|
22
|
+
|
|
23
|
+
## BAD
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from typing import Optional, Union
|
|
27
|
+
|
|
28
|
+
def find(name: str) -> Optional[int]:
|
|
29
|
+
...
|
|
30
|
+
|
|
31
|
+
def parse(value: Union[str, int, float]) -> str:
|
|
32
|
+
...
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## GOOD
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
def find(name: str) -> int | None:
|
|
39
|
+
...
|
|
40
|
+
|
|
41
|
+
def parse(value: str | int | float) -> str:
|
|
42
|
+
...
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Why
|
|
46
|
+
|
|
47
|
+
- Shorter, more readable syntax
|
|
48
|
+
- `Optional[X]` is just `Union[X, None]` — the new syntax makes `None` explicit
|
|
49
|
+
- Works in `isinstance()` checks too: `isinstance(x, str | int)`
|
|
50
|
+
- `Union` and `Optional` are deprecated since 3.10
|
|
51
|
+
|
|
52
|
+
## Version Notes
|
|
53
|
+
|
|
54
|
+
- 3.10+: `X | Y` in annotations and `isinstance()`/`issubclass()`
|
|
55
|
+
- 3.9: Use `from __future__ import annotations` for deferred evaluation only
|
|
56
|
+
|
|
57
|
+
## References
|
|
58
|
+
|
|
59
|
+
- [PEP 604 — Allow writing union types as X | Y](https://peps.python.org/pep-0604/)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: use-builtin-generics
|
|
3
|
+
title: Use Built-in Generic Types Instead of typing Module
|
|
4
|
+
category: typing
|
|
5
|
+
layer: 1
|
|
6
|
+
tags:
|
|
7
|
+
- type-hints
|
|
8
|
+
- generics
|
|
9
|
+
- typing
|
|
10
|
+
aliases:
|
|
11
|
+
- typing.List
|
|
12
|
+
- typing.Dict
|
|
13
|
+
- typing.Optional
|
|
14
|
+
- typing.Tuple
|
|
15
|
+
- typing.Set
|
|
16
|
+
python: ">=3.9"
|
|
17
|
+
frequency: high
|
|
18
|
+
pep: 585
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# Use Built-in Generic Types
|
|
22
|
+
|
|
23
|
+
Since Python 3.9, built-in types support subscript syntax directly. The `typing` module aliases are deprecated.
|
|
24
|
+
|
|
25
|
+
## BAD
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from typing import Dict, List, Optional, Set, Tuple
|
|
29
|
+
|
|
30
|
+
def process(items: List[str]) -> Dict[str, int]:
|
|
31
|
+
seen: Set[str] = set()
|
|
32
|
+
pair: Tuple[str, int] = ("a", 1)
|
|
33
|
+
name: Optional[str] = None
|
|
34
|
+
return {}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## GOOD
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
def process(items: list[str]) -> dict[str, int]:
|
|
41
|
+
seen: set[str] = set()
|
|
42
|
+
pair: tuple[str, int] = ("a", 1)
|
|
43
|
+
name: str | None = None
|
|
44
|
+
return {}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Why
|
|
48
|
+
|
|
49
|
+
- Fewer imports, cleaner code
|
|
50
|
+
- Same runtime types used in annotations
|
|
51
|
+
- `typing` aliases deprecated since 3.9, scheduled for removal in 3.14+
|
|
52
|
+
|
|
53
|
+
## Version Notes
|
|
54
|
+
|
|
55
|
+
- 3.9+: `list[str]`, `dict[str, int]`, `tuple[str, int]`, `set[str]`
|
|
56
|
+
- 3.10+: `str | None` replaces `Optional[str]` (PEP 604)
|
|
57
|
+
|
|
58
|
+
## References
|
|
59
|
+
|
|
60
|
+
- [PEP 585 — Type Hinting Generics In Standard Collections](https://peps.python.org/pep-0585/)
|
|
61
|
+
- [PEP 604 — Allow writing union types as X | Y](https://peps.python.org/pep-0604/)
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Detect the target Python version for a project.
|
|
2
|
+
|
|
3
|
+
Precedence chain:
|
|
4
|
+
1. CLI --python-version flag (explicit override)
|
|
5
|
+
2. pyproject.toml [project].requires-python (PEP 621)
|
|
6
|
+
3. .python-version file (pyenv/asdf)
|
|
7
|
+
4. Default: 3.11
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
import re
|
|
14
|
+
import tomllib
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
from packaging.specifiers import InvalidSpecifier, SpecifierSet
|
|
18
|
+
from packaging.version import Version
|
|
19
|
+
|
|
20
|
+
log = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
DEFAULT_VERSION = "3.11"
|
|
23
|
+
|
|
24
|
+
_KNOWN_MINORS = [
|
|
25
|
+
Version(f"3.{minor}") for minor in range(7, 20)
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
_POETRY_CARET_RE = re.compile(r"\^(\d+\.\d+)")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def detect_version(
|
|
32
|
+
*,
|
|
33
|
+
cli_version: str | None = None,
|
|
34
|
+
project_dir: Path | None = None,
|
|
35
|
+
) -> str:
|
|
36
|
+
if cli_version is not None:
|
|
37
|
+
return _normalize(cli_version)
|
|
38
|
+
|
|
39
|
+
if project_dir is None:
|
|
40
|
+
project_dir = Path.cwd()
|
|
41
|
+
|
|
42
|
+
project_dir = project_dir.resolve()
|
|
43
|
+
|
|
44
|
+
pyproject = project_dir / "pyproject.toml"
|
|
45
|
+
if pyproject.is_file():
|
|
46
|
+
result = _from_pyproject(pyproject)
|
|
47
|
+
if result is not None:
|
|
48
|
+
return result
|
|
49
|
+
|
|
50
|
+
python_version_file = project_dir / ".python-version"
|
|
51
|
+
if python_version_file.is_file():
|
|
52
|
+
result = _from_python_version_file(python_version_file)
|
|
53
|
+
if result is not None:
|
|
54
|
+
return result
|
|
55
|
+
|
|
56
|
+
log.info("No version config found, using default %s", DEFAULT_VERSION)
|
|
57
|
+
return DEFAULT_VERSION
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _from_pyproject(path: Path) -> str | None:
|
|
61
|
+
try:
|
|
62
|
+
data = tomllib.loads(path.read_text(encoding="utf-8"))
|
|
63
|
+
except (OSError, tomllib.TOMLDecodeError) as e:
|
|
64
|
+
log.warning("Failed to parse %s: %s", path, e)
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
requires_python = data.get("project", {}).get("requires-python")
|
|
68
|
+
if requires_python:
|
|
69
|
+
return _min_version_from_specifier(requires_python)
|
|
70
|
+
|
|
71
|
+
poetry_python = (
|
|
72
|
+
data.get("tool", {}).get("poetry", {}).get("dependencies", {}).get("python")
|
|
73
|
+
)
|
|
74
|
+
if poetry_python:
|
|
75
|
+
m = _POETRY_CARET_RE.search(str(poetry_python))
|
|
76
|
+
if m:
|
|
77
|
+
log.warning(
|
|
78
|
+
"Poetry caret version '%s' is not PEP 440 — cannot parse precisely. "
|
|
79
|
+
"Use --python-version or add [project].requires-python to pyproject.toml.",
|
|
80
|
+
poetry_python,
|
|
81
|
+
)
|
|
82
|
+
else:
|
|
83
|
+
log.warning(
|
|
84
|
+
"Poetry python constraint '%s' detected but not supported. "
|
|
85
|
+
"Use --python-version or add [project].requires-python.",
|
|
86
|
+
poetry_python,
|
|
87
|
+
)
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _from_python_version_file(path: Path) -> str | None:
|
|
94
|
+
try:
|
|
95
|
+
content = path.read_text(encoding="utf-8").strip()
|
|
96
|
+
except OSError as e:
|
|
97
|
+
log.warning("Failed to read %s: %s", path, e)
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
if not content or content == "system":
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
if content.startswith("pypy") or content.startswith("graalpy"):
|
|
104
|
+
log.info("Skipping non-CPython version: %s", content)
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
match = re.match(r"(\d+\.\d+)", content)
|
|
108
|
+
if match:
|
|
109
|
+
return match.group(1)
|
|
110
|
+
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _min_version_from_specifier(spec_str: str) -> str | None:
|
|
115
|
+
try:
|
|
116
|
+
spec = SpecifierSet(spec_str)
|
|
117
|
+
except InvalidSpecifier:
|
|
118
|
+
log.warning("Invalid version specifier: %s", spec_str)
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
for v in _KNOWN_MINORS:
|
|
122
|
+
if v in spec:
|
|
123
|
+
return f"{v.major}.{v.minor}"
|
|
124
|
+
high_patch = Version(f"{v.major}.{v.minor}.99")
|
|
125
|
+
if high_patch in spec:
|
|
126
|
+
return f"{v.major}.{v.minor}"
|
|
127
|
+
|
|
128
|
+
log.warning("No known Python version matches specifier: %s", spec_str)
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _normalize(version: str) -> str:
|
|
133
|
+
match = re.match(r"(\d+\.\d+)", version)
|
|
134
|
+
if match:
|
|
135
|
+
return match.group(1)
|
|
136
|
+
return version
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: modern-python-guidance
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Version-aware BAD/GOOD pattern guides that help AI coding agents generate modern Python
|
|
5
|
+
Project-URL: Homepage, https://github.com/yottayoshida/modern-python-guidance
|
|
6
|
+
Project-URL: Repository, https://github.com/yottayoshida/modern-python-guidance
|
|
7
|
+
Project-URL: Issues, https://github.com/yottayoshida/modern-python-guidance/issues
|
|
8
|
+
Author-email: Iori Yoshida <i.yoshida@raksul.com>
|
|
9
|
+
License-Expression: Apache-2.0
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: ai,coding-agent,guidance,linter,modernization,python
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Requires-Dist: packaging>=23.0
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: ruff>=0.4.0; extra == 'dev'
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# modern-python-guidance
|
|
31
|
+
|
|
32
|
+
[](https://github.com/yottayoshida/modern-python-guidance/actions/workflows/ci.yml)
|
|
33
|
+
[](https://pypi.org/project/modern-python-guidance/)
|
|
34
|
+
[](https://pypi.org/project/modern-python-guidance/)
|
|
35
|
+
[](LICENSE)
|
|
36
|
+
|
|
37
|
+
LLMs often produce outdated Python — `typing.List` instead of `list`, `@validator` instead of `@field_validator`, `setup.py` instead of `pyproject.toml`. This tool provides 30 version-aware BAD/GOOD pattern guides that show the modern replacement, filtered by your project's Python version.
|
|
38
|
+
|
|
39
|
+
> **Note:** The tool itself requires Python 3.11+ to run. Guides cover patterns from Python 3.9 onward, and `--python-version` filters guides for your target environment.
|
|
40
|
+
|
|
41
|
+
## Quick start
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Install
|
|
45
|
+
pip install modern-python-guidance
|
|
46
|
+
|
|
47
|
+
# Search for a pattern
|
|
48
|
+
mpg search "typing list"
|
|
49
|
+
# use-builtin-generics score=18.0 [typing]
|
|
50
|
+
|
|
51
|
+
# Get the full guide
|
|
52
|
+
mpg retrieve use-builtin-generics
|
|
53
|
+
# --- use-builtin-generics (version match: YES) ---
|
|
54
|
+
# ## BAD
|
|
55
|
+
# from typing import List, Dict, Optional
|
|
56
|
+
# ...
|
|
57
|
+
# ## GOOD
|
|
58
|
+
# names: list[str] = []
|
|
59
|
+
# ...
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`mpg` is the short alias for `modern-python-guidance`. Both work.
|
|
63
|
+
|
|
64
|
+
## CLI usage
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# Search guides by keyword
|
|
68
|
+
mpg search "pydantic validator"
|
|
69
|
+
|
|
70
|
+
# Retrieve a specific guide (full BAD/GOOD content)
|
|
71
|
+
mpg retrieve use-builtin-generics
|
|
72
|
+
|
|
73
|
+
# List all guides compatible with your Python version
|
|
74
|
+
mpg list --python-version 3.11
|
|
75
|
+
|
|
76
|
+
# Auto-detect project Python version from pyproject.toml / .python-version
|
|
77
|
+
mpg detect-version
|
|
78
|
+
|
|
79
|
+
# Filter by category
|
|
80
|
+
mpg search "timeout" --category async
|
|
81
|
+
|
|
82
|
+
# JSON output (default when piped, explicit with --format)
|
|
83
|
+
mpg search "typing" --format json | jq '.[0].id'
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Guide coverage
|
|
87
|
+
|
|
88
|
+
30 guides across 3 layers:
|
|
89
|
+
|
|
90
|
+
| Layer | Categories | Count | Examples |
|
|
91
|
+
|-------|-----------|-------|---------|
|
|
92
|
+
| **1 — stdlib** | typing, async, stdlib, data-structures | 16 | `list` over `List`, `match`/`case`, `TaskGroup` |
|
|
93
|
+
| **2 — frameworks** | pydantic, fastapi, httpx | 9 | Pydantic V2 migration, `Annotated[Depends]`, `AsyncClient` |
|
|
94
|
+
| **3 — toolchain** | toolchain | 5 | `uv` over `pip`, `ruff` over flake8, `pickle` avoidance |
|
|
95
|
+
|
|
96
|
+
Run `mpg list` to see all 30 guides, or [browse them on GitHub](skills/modern-python-guidance/guides/).
|
|
97
|
+
|
|
98
|
+
## Version-aware filtering
|
|
99
|
+
|
|
100
|
+
Guides specify their minimum Python version. The CLI auto-detects your project's version from (in order):
|
|
101
|
+
|
|
102
|
+
1. `--python-version` flag
|
|
103
|
+
2. `pyproject.toml` `requires-python`
|
|
104
|
+
3. `.python-version` file
|
|
105
|
+
4. Default: 3.11
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
# Only shows guides compatible with Python 3.9
|
|
109
|
+
mpg list --python-version 3.9
|
|
110
|
+
# Excludes: TaskGroup (3.11+), match/case (3.10+), etc.
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Agent Skills integration
|
|
114
|
+
|
|
115
|
+
This project doubles as a [Claude Code Agent Skills](https://docs.anthropic.com/en/docs/claude-code) plugin. Install it into your project's `.claude/skills/` to give Claude automatic access to modern Python patterns when writing or reviewing code.
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# Find where the package is installed
|
|
119
|
+
SKILL_DIR=$(python -c "from pathlib import Path; import modern_python_guidance; print(Path(modern_python_guidance.__file__).parent / 'skills' / 'modern-python-guidance')")
|
|
120
|
+
|
|
121
|
+
# Symlink into your project
|
|
122
|
+
ln -s "$SKILL_DIR" your-project/.claude/skills/modern-python-guidance
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
For other AI tools (Cursor, Copilot, etc.), use the CLI directly — pipe `mpg search` or `mpg retrieve` output into your workflow.
|
|
126
|
+
|
|
127
|
+
## Development
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
git clone https://github.com/yottayoshida/modern-python-guidance.git
|
|
131
|
+
cd modern-python-guidance
|
|
132
|
+
uv venv && source .venv/bin/activate
|
|
133
|
+
uv pip install -e ".[dev]"
|
|
134
|
+
pytest
|
|
135
|
+
ruff check src/ tests/
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Project structure
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
src/modern_python_guidance/
|
|
142
|
+
├── cli.py # Entry point (search, retrieve, list, detect-version)
|
|
143
|
+
├── frontmatter.py # YAML-subset parser (no PyYAML dependency)
|
|
144
|
+
├── guide_index.py # Guide discovery and indexing
|
|
145
|
+
├── search.py # Weighted keyword search + fuzzy fallback
|
|
146
|
+
├── retrieve.py # Guide retrieval and JSON rendering
|
|
147
|
+
├── version_detect.py # Python version auto-detection
|
|
148
|
+
└── compat.py # Shared helpers
|
|
149
|
+
|
|
150
|
+
skills/modern-python-guidance/
|
|
151
|
+
├── SKILL.md # Agent Skills plugin entry point
|
|
152
|
+
└── guides/ # 30 guide files by category
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
See [docs/design.md](docs/design.md) for the full design document.
|
|
156
|
+
|
|
157
|
+
## Contributing
|
|
158
|
+
|
|
159
|
+
Contributions welcome! To add a new guide:
|
|
160
|
+
|
|
161
|
+
1. Create `skills/modern-python-guidance/guides/<category>/<id>.md`
|
|
162
|
+
2. Include YAML frontmatter with these fields:
|
|
163
|
+
|
|
164
|
+
| Field | Type | Values |
|
|
165
|
+
|-------|------|--------|
|
|
166
|
+
| `id` | string | Unique kebab-case identifier (must match filename) |
|
|
167
|
+
| `title` | string | Short descriptive title |
|
|
168
|
+
| `category` | string | Must match parent directory name |
|
|
169
|
+
| `layer` | int | 1 (stdlib), 2 (frameworks), 3 (toolchain) |
|
|
170
|
+
| `tags` | list | Search keywords |
|
|
171
|
+
| `aliases` | list | Alternate names (old API names, etc.) |
|
|
172
|
+
| `python` | string | Minimum version, e.g. `">=3.11"` |
|
|
173
|
+
| `frequency` | string | `high` (LLMs do this often), `medium`, `low` |
|
|
174
|
+
|
|
175
|
+
3. Write BAD/GOOD/Why/Version Notes sections
|
|
176
|
+
4. Run `pytest` to verify the guide parses correctly
|
|
177
|
+
|
|
178
|
+
## License
|
|
179
|
+
|
|
180
|
+
Apache-2.0 — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
modern_python_guidance/skills/modern-python-guidance/SKILL.md,sha256=rvrHlSSS9MPOcdVKuhAp1v1aQjeRZsCgXUX17l9I7b0,5028
|
|
2
|
+
modern_python_guidance/skills/modern-python-guidance/guides/async/async-timeout-context.md,sha256=a0le-QAqt9csyd7tMT3TeN5cx_nEsDv2pUHdQrlr_Yc,1668
|
|
3
|
+
modern_python_guidance/skills/modern-python-guidance/guides/async/exception-groups.md,sha256=_K8E8I_8U5Wm2c8Ywvl93HRsB3vjijXlqL4shelo-DA,1684
|
|
4
|
+
modern_python_guidance/skills/modern-python-guidance/guides/async/taskgroup-over-gather.md,sha256=-gyHQwcdBw0UxNVAfk5dIujN1BfSJRzWBlWZIdlH2FI,1604
|
|
5
|
+
modern_python_guidance/skills/modern-python-guidance/guides/data-structures/dataclass-modern.md,sha256=ZzU5xh9CbV12eGTVYhZZ1y3A6gqrnUJmP_4KWZ8jEAk,1652
|
|
6
|
+
modern_python_guidance/skills/modern-python-guidance/guides/data-structures/dict-merge-operator.md,sha256=6rVL98bmyjz3MImJGxjZToLlv8UZiLApSImGpc6KRdc,1242
|
|
7
|
+
modern_python_guidance/skills/modern-python-guidance/guides/data-structures/match-case-patterns.md,sha256=qWgCIYFtFMRCv-9qKLiRBKw7LqMn9QPCRpGHkcG6yXo,1893
|
|
8
|
+
modern_python_guidance/skills/modern-python-guidance/guides/fastapi/fastapi-annotated-depends.md,sha256=VKqrqPv50f6ODOsmi5mjQWzMzaSoOsMjblZcvIRydWM,1759
|
|
9
|
+
modern_python_guidance/skills/modern-python-guidance/guides/fastapi/fastapi-lifespan.md,sha256=yf94C4AS1LfT9cYgzwi5ZFwxiKbLpON5nY2voixKRgQ,1619
|
|
10
|
+
modern_python_guidance/skills/modern-python-guidance/guides/fastapi/fastapi-typed-state.md,sha256=AqQVDLYNys7ILbuQbFxag5RJMqY1zz5aqwWng6IvupU,1769
|
|
11
|
+
modern_python_guidance/skills/modern-python-guidance/guides/httpx/httpx-async-client-reuse.md,sha256=zlA3TE4HR-PJE1gOveEEWcMk8GH9p1fqm-oNqXUH5LI,1865
|
|
12
|
+
modern_python_guidance/skills/modern-python-guidance/guides/httpx/httpx-streaming.md,sha256=hnDxLKXTJ65-jZj0fmCJUPqi6OW2lLuejRV2p8eHrQA,1769
|
|
13
|
+
modern_python_guidance/skills/modern-python-guidance/guides/pydantic/pydantic-v2-config.md,sha256=_e1gxOrlW-cIFSnJcRAEjmEResNRR7ZdGy-lcXb5vwQ,1819
|
|
14
|
+
modern_python_guidance/skills/modern-python-guidance/guides/pydantic/pydantic-v2-model-api.md,sha256=Ip4X_M5cnnXWRTC7UhqZ8LD_ZZs_9NgRZphk5u_rAiY,1733
|
|
15
|
+
modern_python_guidance/skills/modern-python-guidance/guides/pydantic/pydantic-v2-serialization.md,sha256=l8xJFOxobo3BSkyOIOjfoIA7u_CR0LOTtB1q8oroyo8,1632
|
|
16
|
+
modern_python_guidance/skills/modern-python-guidance/guides/pydantic/pydantic-v2-validators.md,sha256=EVatseVapfnv88omr733TCjTDtmDM_kqED7HbK_tanI,1969
|
|
17
|
+
modern_python_guidance/skills/modern-python-guidance/guides/stdlib/datetime-utc.md,sha256=sbGPNQ8TfY0PQwSyvz7zOokZlNujNb6_TPDENia-_hU,1330
|
|
18
|
+
modern_python_guidance/skills/modern-python-guidance/guides/stdlib/pathlib-over-os-path.md,sha256=mYhjm3BWHVLM83Cv1b8Dae_J3h0r5QDgwLVsm-EtCjQ,1581
|
|
19
|
+
modern_python_guidance/skills/modern-python-guidance/guides/stdlib/removeprefix-removesuffix.md,sha256=6E_nnA_31z2LkECO3NF1iFfjK6bIXzWLZdWu0ncm9Fk,1597
|
|
20
|
+
modern_python_guidance/skills/modern-python-guidance/guides/stdlib/tomllib-builtin.md,sha256=0vgz24ayYBS3UYSlZT81eOAM5BE5dM04hevZdjwufrs,1196
|
|
21
|
+
modern_python_guidance/skills/modern-python-guidance/guides/toolchain/no-pickle.md,sha256=2zQaYb2gQ0bhX2IVcigeMNn-QkyNrNFZtLOyHWcBLRM,1957
|
|
22
|
+
modern_python_guidance/skills/modern-python-guidance/guides/toolchain/pyproject-toml-over-setup.md,sha256=V9ENRTKpXtEKguQKXvaLXnthJd0j63-H1tb9lvVEqGQ,1435
|
|
23
|
+
modern_python_guidance/skills/modern-python-guidance/guides/toolchain/ruff-over-flake8.md,sha256=Y0geL7xJGuD9ocRYlcZWlM1_Q7T5qBD7NfnMO3z3z34,1703
|
|
24
|
+
modern_python_guidance/skills/modern-python-guidance/guides/toolchain/safe-subprocess.md,sha256=-B-DUJPzfWH8XO6ywTgPMjlVCD2ah6SyHsm6b0oWLxE,2199
|
|
25
|
+
modern_python_guidance/skills/modern-python-guidance/guides/toolchain/uv-over-pip.md,sha256=ExnUwbVttKkORPkhqEAAxERiWo1kjPKKnsPBMonXwUs,1412
|
|
26
|
+
modern_python_guidance/skills/modern-python-guidance/guides/typing/override-decorator.md,sha256=L4PjDCg2NlGr-WG747u6uvZxgTMBAvVuhKDFWCM0-y0,1351
|
|
27
|
+
modern_python_guidance/skills/modern-python-guidance/guides/typing/paramspec-decorators.md,sha256=0hY88AYPJUE5AzALhvgxFhP8OEBINqEjEIfszcZISsM,1949
|
|
28
|
+
modern_python_guidance/skills/modern-python-guidance/guides/typing/type-parameter-syntax.md,sha256=QnQiu2kDYCd1tXKyb07iOhdOpgjP34WxP26fGvbKG1M,1359
|
|
29
|
+
modern_python_guidance/skills/modern-python-guidance/guides/typing/typeis-vs-typeguard.md,sha256=-68eMjwdn2XmmoPsUOyhI-27L13idL7dDeD-MdPqKE0,1514
|
|
30
|
+
modern_python_guidance/skills/modern-python-guidance/guides/typing/union-syntax.md,sha256=_BhYvxWq6MpRwL1w8VD8kiRGI_T4eUHZXfuIe8qZA70,1169
|
|
31
|
+
modern_python_guidance/skills/modern-python-guidance/guides/typing/use-builtin-generics.md,sha256=qFqKUlcICRH1FDQ54GTb3Q24_TYuq9TBynOEx5zCgC0,1349
|
|
32
|
+
modern_python_guidance/__init__.py,sha256=KLQC9PrsroRzCtfjT-gsjLg4E81Wx_iwLDxf4Z97I-4,116
|
|
33
|
+
modern_python_guidance/__main__.py,sha256=85nXGivCWEBw00evsgIQg6bhuHnb7-r9AVMdXoMVuco,112
|
|
34
|
+
modern_python_guidance/cli.py,sha256=exS4himsrvOiBOwwPri-tAvx6v7H_Q72B36SPRNfpiw,6492
|
|
35
|
+
modern_python_guidance/compat.py,sha256=uwBN9UxSTkCUX3GXdy5CfsjdrosWafon8r2X2n-hAOg,537
|
|
36
|
+
modern_python_guidance/frontmatter.py,sha256=SMB2EpHQvFTqySyXWvbzzs8tnd4qfqrgMoBYsQkgU-g,5011
|
|
37
|
+
modern_python_guidance/guide_index.py,sha256=V3EGVtTdGS2keBLgi1KG2eREpYG02_mdGV-UZg894KU,2869
|
|
38
|
+
modern_python_guidance/retrieve.py,sha256=ufJjEhJ55co0fUqlbR2iSMOM7h9y9C3KRWTLVlGrgx4,1569
|
|
39
|
+
modern_python_guidance/search.py,sha256=bCWJqFLw_Ws6G6mqmKQwfZwVag2FByT9qlFxAtMHdSw,4193
|
|
40
|
+
modern_python_guidance/version_detect.py,sha256=lHV3_8WuvKQhSOZyLMD5JiVzcXHESdoBuNCg2VOMN1s,3772
|
|
41
|
+
modern_python_guidance-0.1.0.dist-info/METADATA,sha256=AbAm51k1hUsa--kASUUxcjwJu_Rd8DciEjMWr9SGfhg,6819
|
|
42
|
+
modern_python_guidance-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
43
|
+
modern_python_guidance-0.1.0.dist-info/entry_points.txt,sha256=IPgwIOIfxf7Pu-L5vMkZ1cKcR1h_BdckzCOtl_UXjTE,113
|
|
44
|
+
modern_python_guidance-0.1.0.dist-info/licenses/LICENSE,sha256=l0zB3WNmNAS1Kqgln4pHyO-u1I_PlHet06-UqZgfkNI,10763
|
|
45
|
+
modern_python_guidance-0.1.0.dist-info/RECORD,,
|