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,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: tomllib-builtin
|
|
3
|
+
title: Use Built-in tomllib Instead of Third-Party toml
|
|
4
|
+
category: stdlib
|
|
5
|
+
layer: 1
|
|
6
|
+
tags:
|
|
7
|
+
- toml
|
|
8
|
+
- config
|
|
9
|
+
- stdlib
|
|
10
|
+
aliases:
|
|
11
|
+
- toml
|
|
12
|
+
- tomli
|
|
13
|
+
- tomllib
|
|
14
|
+
python: ">=3.11"
|
|
15
|
+
frequency: medium
|
|
16
|
+
pep: 680
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Use Built-in tomllib
|
|
20
|
+
|
|
21
|
+
Since Python 3.11, use the built-in `tomllib` module for reading TOML files instead of third-party packages like `toml` or `tomli`.
|
|
22
|
+
|
|
23
|
+
## BAD
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
import toml # third-party, unmaintained
|
|
27
|
+
|
|
28
|
+
with open("pyproject.toml") as f:
|
|
29
|
+
config = toml.load(f)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## GOOD
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
import tomllib
|
|
36
|
+
|
|
37
|
+
with open("pyproject.toml", "rb") as f:
|
|
38
|
+
config = tomllib.load(f)
|
|
39
|
+
|
|
40
|
+
# Or from a string:
|
|
41
|
+
config = tomllib.loads(toml_string)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Why
|
|
45
|
+
|
|
46
|
+
- No external dependency needed for TOML reading
|
|
47
|
+
- `tomllib` is TOML 1.0 compliant
|
|
48
|
+
- Based on `tomli` (battle-tested implementation adopted into stdlib)
|
|
49
|
+
- Read-only by design — use `tomli-w` or `tomlkit` for writing
|
|
50
|
+
|
|
51
|
+
## Version Notes
|
|
52
|
+
|
|
53
|
+
- 3.11+: `import tomllib`
|
|
54
|
+
- Pre-3.11: `pip install tomli` (same API as `tomllib`)
|
|
55
|
+
- `tomllib.load()` requires binary mode (`"rb"`), not text mode
|
|
56
|
+
|
|
57
|
+
## References
|
|
58
|
+
|
|
59
|
+
- [PEP 680 — tomllib: Support for Parsing TOML in the Standard Library](https://peps.python.org/pep-0680/)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: no-pickle
|
|
3
|
+
title: Avoid pickle for Untrusted Data
|
|
4
|
+
category: toolchain
|
|
5
|
+
layer: 3
|
|
6
|
+
tags:
|
|
7
|
+
- security
|
|
8
|
+
- serialization
|
|
9
|
+
- pickle
|
|
10
|
+
aliases:
|
|
11
|
+
- pickle
|
|
12
|
+
- unpickle
|
|
13
|
+
- pickle.load
|
|
14
|
+
- shelve
|
|
15
|
+
python: ">=3.9"
|
|
16
|
+
frequency: medium
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Avoid pickle for Untrusted Data
|
|
20
|
+
|
|
21
|
+
`pickle.load()` executes arbitrary code during deserialization. Never use it with untrusted data.
|
|
22
|
+
|
|
23
|
+
## BAD
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
import pickle
|
|
27
|
+
|
|
28
|
+
def load_model(path: str):
|
|
29
|
+
with open(path, "rb") as f:
|
|
30
|
+
return pickle.load(f) # arbitrary code execution
|
|
31
|
+
|
|
32
|
+
def cache_result(data, path: str):
|
|
33
|
+
with open(path, "wb") as f:
|
|
34
|
+
pickle.dump(data, f)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## GOOD
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
import json
|
|
41
|
+
|
|
42
|
+
def load_config(path: str) -> dict:
|
|
43
|
+
with open(path) as f:
|
|
44
|
+
return json.load(f)
|
|
45
|
+
|
|
46
|
+
# For structured data
|
|
47
|
+
import msgpack # or orjson, cbor2
|
|
48
|
+
|
|
49
|
+
def load_data(path: str) -> dict:
|
|
50
|
+
with open(path, "rb") as f:
|
|
51
|
+
return msgpack.unpack(f)
|
|
52
|
+
|
|
53
|
+
# For ML models — use framework-native formats
|
|
54
|
+
import torch
|
|
55
|
+
model = torch.load(path, weights_only=True) # safe mode
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Why
|
|
59
|
+
|
|
60
|
+
- `pickle.load()` can execute arbitrary Python code — a known RCE vector
|
|
61
|
+
- Applies to `pickle`, `shelve`, `marshal`, and any library using pickle internally
|
|
62
|
+
- JSON, MessagePack, CBOR are safe — they only deserialize data, not code
|
|
63
|
+
- ML frameworks provide safe alternatives (`weights_only=True`, ONNX, SafeTensors)
|
|
64
|
+
- Python's own docs warn: "Never unpickle data received from an untrusted source"
|
|
65
|
+
|
|
66
|
+
## Safe Alternatives
|
|
67
|
+
|
|
68
|
+
| Use case | Recommended format |
|
|
69
|
+
|----------|-------------------|
|
|
70
|
+
| Configuration | JSON, TOML, YAML |
|
|
71
|
+
| Structured data | JSON, MessagePack, Protocol Buffers |
|
|
72
|
+
| DataFrames | Parquet, CSV |
|
|
73
|
+
| ML models | SafeTensors, ONNX, `weights_only=True` |
|
|
74
|
+
| Caching | JSON + compression, Redis |
|
|
75
|
+
|
|
76
|
+
## References
|
|
77
|
+
|
|
78
|
+
- [Python docs — pickle security](https://docs.python.org/3/library/pickle.html#restricting-globals)
|
|
79
|
+
- [CWE-502: Deserialization of Untrusted Data](https://cwe.mitre.org/data/definitions/502.html)
|
modern_python_guidance/skills/modern-python-guidance/guides/toolchain/pyproject-toml-over-setup.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: pyproject-toml-over-setup
|
|
3
|
+
title: Use pyproject.toml Instead of setup.py
|
|
4
|
+
category: toolchain
|
|
5
|
+
layer: 3
|
|
6
|
+
tags:
|
|
7
|
+
- packaging
|
|
8
|
+
- pyproject
|
|
9
|
+
- setup.py
|
|
10
|
+
- build
|
|
11
|
+
aliases:
|
|
12
|
+
- setup.py
|
|
13
|
+
- setup.cfg
|
|
14
|
+
- setuptools
|
|
15
|
+
python: ">=3.7"
|
|
16
|
+
frequency: high
|
|
17
|
+
pep: 621
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# Use pyproject.toml Instead of setup.py
|
|
21
|
+
|
|
22
|
+
PEP 621 standardizes project metadata in `pyproject.toml`. `setup.py` and `setup.cfg` are legacy.
|
|
23
|
+
|
|
24
|
+
## BAD
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
# setup.py
|
|
28
|
+
from setuptools import setup, find_packages
|
|
29
|
+
|
|
30
|
+
setup(
|
|
31
|
+
name="my-package",
|
|
32
|
+
version="0.1.0",
|
|
33
|
+
packages=find_packages(),
|
|
34
|
+
install_requires=["requests>=2.28"],
|
|
35
|
+
python_requires=">=3.11",
|
|
36
|
+
)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## GOOD
|
|
40
|
+
|
|
41
|
+
```toml
|
|
42
|
+
# pyproject.toml
|
|
43
|
+
[build-system]
|
|
44
|
+
requires = ["hatchling"]
|
|
45
|
+
build-backend = "hatchling.build"
|
|
46
|
+
|
|
47
|
+
[project]
|
|
48
|
+
name = "my-package"
|
|
49
|
+
version = "0.1.0"
|
|
50
|
+
requires-python = ">=3.11"
|
|
51
|
+
dependencies = ["requests>=2.28"]
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Why
|
|
55
|
+
|
|
56
|
+
- `setup.py` executes arbitrary code at install time (security risk)
|
|
57
|
+
- `pyproject.toml` is declarative and statically analyzable
|
|
58
|
+
- All modern tools (pip, uv, hatch, flit, PDM) support PEP 621
|
|
59
|
+
- `setup.py` is no longer needed for pure-Python packages
|
|
60
|
+
|
|
61
|
+
## Version Notes
|
|
62
|
+
|
|
63
|
+
- PEP 621 is supported by pip since 21.3 (2021-10)
|
|
64
|
+
- Python version doesn't matter — this is a tooling decision
|
|
65
|
+
|
|
66
|
+
## References
|
|
67
|
+
|
|
68
|
+
- [PEP 621 — Storing project metadata in pyproject.toml](https://peps.python.org/pep-0621/)
|
|
69
|
+
- [PEP 517 — Build system interface](https://peps.python.org/pep-0517/)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: ruff-over-flake8
|
|
3
|
+
title: Use Ruff Instead of Flake8 + isort + Black
|
|
4
|
+
category: toolchain
|
|
5
|
+
layer: 3
|
|
6
|
+
tags:
|
|
7
|
+
- ruff
|
|
8
|
+
- linting
|
|
9
|
+
- formatting
|
|
10
|
+
- flake8
|
|
11
|
+
- isort
|
|
12
|
+
- black
|
|
13
|
+
aliases:
|
|
14
|
+
- flake8
|
|
15
|
+
- isort
|
|
16
|
+
- black
|
|
17
|
+
- ruff
|
|
18
|
+
- linter
|
|
19
|
+
- formatter
|
|
20
|
+
python: ">=3.7"
|
|
21
|
+
frequency: high
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
# Use Ruff Instead of Flake8 + isort + Black
|
|
25
|
+
|
|
26
|
+
Ruff replaces Flake8, isort, Black, pyupgrade, and dozens of other tools in a single Rust binary.
|
|
27
|
+
|
|
28
|
+
## BAD
|
|
29
|
+
|
|
30
|
+
```toml
|
|
31
|
+
# pyproject.toml — multiple tools to configure separately
|
|
32
|
+
[tool.flake8]
|
|
33
|
+
max-line-length = 88
|
|
34
|
+
|
|
35
|
+
[tool.isort]
|
|
36
|
+
profile = "black"
|
|
37
|
+
|
|
38
|
+
[tool.black]
|
|
39
|
+
line-length = 88
|
|
40
|
+
target-version = ["py311"]
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
flake8 src/
|
|
45
|
+
isort src/
|
|
46
|
+
black src/
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## GOOD
|
|
50
|
+
|
|
51
|
+
```toml
|
|
52
|
+
# pyproject.toml — single tool
|
|
53
|
+
[tool.ruff]
|
|
54
|
+
target-version = "py311"
|
|
55
|
+
line-length = 88
|
|
56
|
+
|
|
57
|
+
[tool.ruff.lint]
|
|
58
|
+
select = ["E", "F", "I", "UP", "B", "SIM"]
|
|
59
|
+
|
|
60
|
+
[tool.ruff.format]
|
|
61
|
+
docstring-code-format = true
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
ruff check src/ --fix
|
|
66
|
+
ruff format src/
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Why
|
|
70
|
+
|
|
71
|
+
- 10-100x faster than Flake8 (Rust implementation)
|
|
72
|
+
- Single tool replaces Flake8, isort, Black, pyupgrade, pydocstyle, and 50+ plugins
|
|
73
|
+
- `--fix` auto-fixes most lint violations
|
|
74
|
+
- `ruff format` is a drop-in replacement for Black
|
|
75
|
+
- Configured entirely in `pyproject.toml`
|
|
76
|
+
|
|
77
|
+
## Rule Selection Guide
|
|
78
|
+
|
|
79
|
+
| Rule prefix | Replaces | Purpose |
|
|
80
|
+
|-------------|----------|---------|
|
|
81
|
+
| `E`, `F` | Flake8 (pycodestyle + pyflakes) | Core errors and style |
|
|
82
|
+
| `I` | isort | Import sorting |
|
|
83
|
+
| `UP` | pyupgrade | Python version upgrades |
|
|
84
|
+
| `B` | flake8-bugbear | Common bug patterns |
|
|
85
|
+
| `SIM` | flake8-simplify | Code simplification |
|
|
86
|
+
|
|
87
|
+
## References
|
|
88
|
+
|
|
89
|
+
- [Ruff documentation](https://docs.astral.sh/ruff/)
|
|
90
|
+
- [Ruff Rules](https://docs.astral.sh/ruff/rules/)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: safe-subprocess
|
|
3
|
+
title: Use subprocess Safely with List Arguments
|
|
4
|
+
category: toolchain
|
|
5
|
+
layer: 3
|
|
6
|
+
tags:
|
|
7
|
+
- security
|
|
8
|
+
- subprocess
|
|
9
|
+
- shell-injection
|
|
10
|
+
aliases:
|
|
11
|
+
- subprocess
|
|
12
|
+
- os.system
|
|
13
|
+
- shell=True
|
|
14
|
+
- subprocess.run
|
|
15
|
+
python: ">=3.9"
|
|
16
|
+
frequency: high
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Use subprocess Safely
|
|
20
|
+
|
|
21
|
+
Always pass commands as a list of arguments. Never use `shell=True` or `os.system()` with user input.
|
|
22
|
+
|
|
23
|
+
## BAD
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
import os
|
|
27
|
+
import subprocess
|
|
28
|
+
|
|
29
|
+
def convert(input_path: str, output_path: str) -> None:
|
|
30
|
+
os.system(f"ffmpeg -i {input_path} {output_path}")
|
|
31
|
+
|
|
32
|
+
def run_tool(filename: str) -> str:
|
|
33
|
+
result = subprocess.run(
|
|
34
|
+
f"grep -r {filename} src/",
|
|
35
|
+
shell=True, capture_output=True, text=True,
|
|
36
|
+
)
|
|
37
|
+
return result.stdout
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## GOOD
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
import subprocess
|
|
44
|
+
|
|
45
|
+
def convert(input_path: str, output_path: str) -> None:
|
|
46
|
+
subprocess.run(
|
|
47
|
+
["ffmpeg", "-i", input_path, output_path],
|
|
48
|
+
check=True,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def run_tool(filename: str) -> str:
|
|
52
|
+
result = subprocess.run(
|
|
53
|
+
["grep", "-r", filename, "src/"],
|
|
54
|
+
capture_output=True, text=True, check=True,
|
|
55
|
+
)
|
|
56
|
+
return result.stdout
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Why
|
|
60
|
+
|
|
61
|
+
- `shell=True` passes the command through the shell — user input can inject arbitrary commands
|
|
62
|
+
- `os.system()` always uses the shell and is even worse (no output capture, no error handling)
|
|
63
|
+
- List arguments bypass the shell entirely — each element is a separate argv entry
|
|
64
|
+
- `check=True` raises `CalledProcessError` on non-zero exit instead of silently continuing
|
|
65
|
+
- `capture_output=True` is a shorthand for `stdout=PIPE, stderr=PIPE`
|
|
66
|
+
|
|
67
|
+
## When shell=True Is Acceptable
|
|
68
|
+
|
|
69
|
+
| Scenario | Acceptable? | Alternative |
|
|
70
|
+
|----------|-------------|-------------|
|
|
71
|
+
| Hardcoded command, no user input | Tolerable | Still prefer list form |
|
|
72
|
+
| Pipes (`cmd1 | cmd2`) | Acceptable | Use `subprocess.PIPE` between two `Popen` calls |
|
|
73
|
+
| Glob expansion (`*.txt`) | Acceptable | Use `pathlib.Path.glob()` |
|
|
74
|
+
| User-provided input in command | Never | List form only |
|
|
75
|
+
|
|
76
|
+
## References
|
|
77
|
+
|
|
78
|
+
- [subprocess — Security Considerations](https://docs.python.org/3/library/subprocess.html#security-considerations)
|
|
79
|
+
- [CWE-78: OS Command Injection](https://cwe.mitre.org/data/definitions/78.html)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: uv-over-pip
|
|
3
|
+
title: Use uv Instead of pip for Package Management
|
|
4
|
+
category: toolchain
|
|
5
|
+
layer: 3
|
|
6
|
+
tags:
|
|
7
|
+
- uv
|
|
8
|
+
- pip
|
|
9
|
+
- packaging
|
|
10
|
+
- virtual-env
|
|
11
|
+
aliases:
|
|
12
|
+
- pip install
|
|
13
|
+
- pip
|
|
14
|
+
- uv
|
|
15
|
+
- virtualenv
|
|
16
|
+
- venv
|
|
17
|
+
python: ">=3.8"
|
|
18
|
+
frequency: high
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# Use uv Instead of pip
|
|
22
|
+
|
|
23
|
+
`uv` is a drop-in replacement for `pip`, `pip-tools`, `virtualenv`, and `pyenv` — written in Rust, 10-100x faster.
|
|
24
|
+
|
|
25
|
+
## BAD
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
python -m venv .venv
|
|
29
|
+
source .venv/bin/activate
|
|
30
|
+
pip install -r requirements.txt
|
|
31
|
+
pip install -e ".[dev]"
|
|
32
|
+
pip freeze > requirements.txt
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## GOOD
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
uv venv
|
|
39
|
+
source .venv/bin/activate
|
|
40
|
+
uv pip install -r requirements.txt
|
|
41
|
+
uv pip install -e ".[dev]"
|
|
42
|
+
|
|
43
|
+
# Or use uv's project workflow (pyproject.toml-native)
|
|
44
|
+
uv init my-project
|
|
45
|
+
uv add requests httpx
|
|
46
|
+
uv add --dev pytest ruff
|
|
47
|
+
uv run pytest
|
|
48
|
+
uv lock
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Why
|
|
52
|
+
|
|
53
|
+
- 10-100x faster than pip (Rust implementation with global cache)
|
|
54
|
+
- Replaces pip, pip-tools, virtualenv, pyenv in a single binary
|
|
55
|
+
- `uv.lock` provides reproducible, cross-platform lockfiles
|
|
56
|
+
- `uv run` auto-creates virtualenv and installs dependencies
|
|
57
|
+
- Compatible with existing `requirements.txt` and `pyproject.toml`
|
|
58
|
+
|
|
59
|
+
## Version Notes
|
|
60
|
+
|
|
61
|
+
- `uv` is a third-party tool by Astral (same team as Ruff)
|
|
62
|
+
- Works with Python 3.8+
|
|
63
|
+
- `uv init` generates `pyproject.toml` (PEP 621 compliant)
|
|
64
|
+
|
|
65
|
+
## References
|
|
66
|
+
|
|
67
|
+
- [uv documentation](https://docs.astral.sh/uv/)
|
|
68
|
+
- [uv GitHub](https://github.com/astral-sh/uv)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: override-decorator
|
|
3
|
+
title: Use @override to Mark Method Overrides
|
|
4
|
+
category: typing
|
|
5
|
+
layer: 1
|
|
6
|
+
tags:
|
|
7
|
+
- type-hints
|
|
8
|
+
- inheritance
|
|
9
|
+
- override
|
|
10
|
+
aliases:
|
|
11
|
+
- typing.override
|
|
12
|
+
python: ">=3.12"
|
|
13
|
+
frequency: medium
|
|
14
|
+
pep: 698
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# Use @override to Mark Method Overrides
|
|
18
|
+
|
|
19
|
+
Since Python 3.12, use `@override` from `typing` to explicitly mark methods that override a parent class method. Type checkers will flag errors if the parent method doesn't exist.
|
|
20
|
+
|
|
21
|
+
## BAD
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
class Animal:
|
|
25
|
+
def speak(self) -> str:
|
|
26
|
+
return ""
|
|
27
|
+
|
|
28
|
+
class Dog(Animal):
|
|
29
|
+
def spek(self) -> str: # typo goes unnoticed
|
|
30
|
+
return "woof"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## GOOD
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from typing import override
|
|
37
|
+
|
|
38
|
+
class Animal:
|
|
39
|
+
def speak(self) -> str:
|
|
40
|
+
return ""
|
|
41
|
+
|
|
42
|
+
class Dog(Animal):
|
|
43
|
+
@override
|
|
44
|
+
def speak(self) -> str:
|
|
45
|
+
return "woof"
|
|
46
|
+
|
|
47
|
+
@override
|
|
48
|
+
def spek(self) -> str: # type checker flags: no parent method 'spek'
|
|
49
|
+
return "woof"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Why
|
|
53
|
+
|
|
54
|
+
- Catches typos and missing parent methods at type-check time
|
|
55
|
+
- Documents intent — clearly shows which methods are overrides
|
|
56
|
+
- Prevents silent breakage when parent class renames a method
|
|
57
|
+
|
|
58
|
+
## Version Notes
|
|
59
|
+
|
|
60
|
+
- 3.12+: `from typing import override`
|
|
61
|
+
- 3.11 and below: `from typing_extensions import override`
|
|
62
|
+
|
|
63
|
+
## References
|
|
64
|
+
|
|
65
|
+
- [PEP 698 — Override Decorator for Static Typing](https://peps.python.org/pep-0698/)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: paramspec-decorators
|
|
3
|
+
title: Use ParamSpec for Typed Decorators
|
|
4
|
+
category: typing
|
|
5
|
+
layer: 1
|
|
6
|
+
tags:
|
|
7
|
+
- type-hints
|
|
8
|
+
- decorators
|
|
9
|
+
- paramspec
|
|
10
|
+
aliases:
|
|
11
|
+
- ParamSpec
|
|
12
|
+
- typing.ParamSpec
|
|
13
|
+
- Callable
|
|
14
|
+
python: ">=3.10"
|
|
15
|
+
frequency: medium
|
|
16
|
+
pep: 612
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Use ParamSpec for Typed Decorators
|
|
20
|
+
|
|
21
|
+
Since Python 3.10, use `ParamSpec` to preserve the parameter types of decorated functions. Without it, decorators erase all type information.
|
|
22
|
+
|
|
23
|
+
## BAD
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from typing import Any, Callable, TypeVar
|
|
27
|
+
|
|
28
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
29
|
+
|
|
30
|
+
def retry(func: F) -> F: # loses parameter info in practice
|
|
31
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
32
|
+
for _ in range(3):
|
|
33
|
+
try:
|
|
34
|
+
return func(*args, **kwargs)
|
|
35
|
+
except Exception:
|
|
36
|
+
pass
|
|
37
|
+
return func(*args, **kwargs)
|
|
38
|
+
return wrapper # type: ignore
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## GOOD
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from collections.abc import Callable
|
|
45
|
+
from functools import wraps
|
|
46
|
+
from typing import ParamSpec, TypeVar
|
|
47
|
+
|
|
48
|
+
P = ParamSpec("P")
|
|
49
|
+
R = TypeVar("R")
|
|
50
|
+
|
|
51
|
+
def retry(func: Callable[P, R]) -> Callable[P, R]:
|
|
52
|
+
@wraps(func)
|
|
53
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
54
|
+
for _ in range(3):
|
|
55
|
+
try:
|
|
56
|
+
return func(*args, **kwargs)
|
|
57
|
+
except Exception:
|
|
58
|
+
pass
|
|
59
|
+
return func(*args, **kwargs)
|
|
60
|
+
return wrapper
|
|
61
|
+
|
|
62
|
+
# 3.12+ with PEP 695:
|
|
63
|
+
# def retry[**P, R](func: Callable[P, R]) -> Callable[P, R]: ...
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Why
|
|
67
|
+
|
|
68
|
+
- Preserves full parameter types through decoration
|
|
69
|
+
- Type checkers can verify argument types at call sites
|
|
70
|
+
- No more `# type: ignore` on decorator return
|
|
71
|
+
- `P.args` and `P.kwargs` capture positional and keyword args separately
|
|
72
|
+
|
|
73
|
+
## Version Notes
|
|
74
|
+
|
|
75
|
+
- 3.10+: `from typing import ParamSpec`
|
|
76
|
+
- 3.12+: `def retry[**P, R](func): ...` syntax (PEP 695)
|
|
77
|
+
- Pre-3.10: `from typing_extensions import ParamSpec`
|
|
78
|
+
|
|
79
|
+
## References
|
|
80
|
+
|
|
81
|
+
- [PEP 612 — Parameter Specification Variables](https://peps.python.org/pep-0612/)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: type-parameter-syntax
|
|
3
|
+
title: Use PEP 695 Type Parameter Syntax for Generics
|
|
4
|
+
category: typing
|
|
5
|
+
layer: 1
|
|
6
|
+
tags:
|
|
7
|
+
- type-hints
|
|
8
|
+
- generics
|
|
9
|
+
- typevar
|
|
10
|
+
aliases:
|
|
11
|
+
- TypeVar
|
|
12
|
+
- typing.TypeVar
|
|
13
|
+
- type alias
|
|
14
|
+
python: ">=3.12"
|
|
15
|
+
frequency: medium
|
|
16
|
+
pep: 695
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Use PEP 695 Type Parameter Syntax
|
|
20
|
+
|
|
21
|
+
Since Python 3.12, use the bracket syntax for type parameters instead of `TypeVar`, `TypeVarTuple`, and `ParamSpec` from `typing`.
|
|
22
|
+
|
|
23
|
+
## BAD
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from typing import Generic, TypeVar
|
|
27
|
+
|
|
28
|
+
T = TypeVar("T")
|
|
29
|
+
K = TypeVar("K")
|
|
30
|
+
V = TypeVar("V")
|
|
31
|
+
|
|
32
|
+
class Stack(Generic[T]):
|
|
33
|
+
def push(self, item: T) -> None: ...
|
|
34
|
+
def pop(self) -> T: ...
|
|
35
|
+
|
|
36
|
+
MyList = list[T] # not a proper type alias
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## GOOD
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
class Stack[T]:
|
|
43
|
+
def push(self, item: T) -> None: ...
|
|
44
|
+
def pop(self) -> T: ...
|
|
45
|
+
|
|
46
|
+
type MyList[T] = list[T]
|
|
47
|
+
|
|
48
|
+
def first[T](items: list[T]) -> T:
|
|
49
|
+
return items[0]
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Why
|
|
53
|
+
|
|
54
|
+
- No boilerplate `TypeVar("T")` declarations
|
|
55
|
+
- Scope is explicit — type params are local to their class/function/alias
|
|
56
|
+
- `type` statement creates proper type aliases with lazy evaluation
|
|
57
|
+
- Cleaner syntax for bounded types: `class Num[T: (int, float)]`
|
|
58
|
+
|
|
59
|
+
## Version Notes
|
|
60
|
+
|
|
61
|
+
- 3.12+: `class C[T]`, `def f[T]()`, `type Alias[T] = ...`
|
|
62
|
+
- Pre-3.12: Must use `TypeVar`, `Generic`, and `TypeAlias`
|
|
63
|
+
|
|
64
|
+
## References
|
|
65
|
+
|
|
66
|
+
- [PEP 695 — Type Parameter Syntax](https://peps.python.org/pep-0695/)
|