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.
Files changed (45) hide show
  1. modern_python_guidance/__init__.py +3 -0
  2. modern_python_guidance/__main__.py +5 -0
  3. modern_python_guidance/cli.py +202 -0
  4. modern_python_guidance/compat.py +22 -0
  5. modern_python_guidance/frontmatter.py +166 -0
  6. modern_python_guidance/guide_index.py +96 -0
  7. modern_python_guidance/retrieve.py +56 -0
  8. modern_python_guidance/search.py +149 -0
  9. modern_python_guidance/skills/modern-python-guidance/SKILL.md +104 -0
  10. modern_python_guidance/skills/modern-python-guidance/guides/async/async-timeout-context.md +65 -0
  11. modern_python_guidance/skills/modern-python-guidance/guides/async/exception-groups.md +70 -0
  12. modern_python_guidance/skills/modern-python-guidance/guides/async/taskgroup-over-gather.md +63 -0
  13. modern_python_guidance/skills/modern-python-guidance/guides/data-structures/dataclass-modern.md +73 -0
  14. modern_python_guidance/skills/modern-python-guidance/guides/data-structures/dict-merge-operator.md +63 -0
  15. modern_python_guidance/skills/modern-python-guidance/guides/data-structures/match-case-patterns.md +70 -0
  16. modern_python_guidance/skills/modern-python-guidance/guides/fastapi/fastapi-annotated-depends.md +80 -0
  17. modern_python_guidance/skills/modern-python-guidance/guides/fastapi/fastapi-lifespan.md +77 -0
  18. modern_python_guidance/skills/modern-python-guidance/guides/fastapi/fastapi-typed-state.md +76 -0
  19. modern_python_guidance/skills/modern-python-guidance/guides/httpx/httpx-async-client-reuse.md +70 -0
  20. modern_python_guidance/skills/modern-python-guidance/guides/httpx/httpx-streaming.md +66 -0
  21. modern_python_guidance/skills/modern-python-guidance/guides/pydantic/pydantic-v2-config.md +73 -0
  22. modern_python_guidance/skills/modern-python-guidance/guides/pydantic/pydantic-v2-model-api.md +79 -0
  23. modern_python_guidance/skills/modern-python-guidance/guides/pydantic/pydantic-v2-serialization.md +71 -0
  24. modern_python_guidance/skills/modern-python-guidance/guides/pydantic/pydantic-v2-validators.md +83 -0
  25. modern_python_guidance/skills/modern-python-guidance/guides/stdlib/datetime-utc.md +56 -0
  26. modern_python_guidance/skills/modern-python-guidance/guides/stdlib/pathlib-over-os-path.md +68 -0
  27. modern_python_guidance/skills/modern-python-guidance/guides/stdlib/removeprefix-removesuffix.md +64 -0
  28. modern_python_guidance/skills/modern-python-guidance/guides/stdlib/tomllib-builtin.md +59 -0
  29. modern_python_guidance/skills/modern-python-guidance/guides/toolchain/no-pickle.md +79 -0
  30. modern_python_guidance/skills/modern-python-guidance/guides/toolchain/pyproject-toml-over-setup.md +69 -0
  31. modern_python_guidance/skills/modern-python-guidance/guides/toolchain/ruff-over-flake8.md +90 -0
  32. modern_python_guidance/skills/modern-python-guidance/guides/toolchain/safe-subprocess.md +79 -0
  33. modern_python_guidance/skills/modern-python-guidance/guides/toolchain/uv-over-pip.md +68 -0
  34. modern_python_guidance/skills/modern-python-guidance/guides/typing/override-decorator.md +65 -0
  35. modern_python_guidance/skills/modern-python-guidance/guides/typing/paramspec-decorators.md +81 -0
  36. modern_python_guidance/skills/modern-python-guidance/guides/typing/type-parameter-syntax.md +66 -0
  37. modern_python_guidance/skills/modern-python-guidance/guides/typing/typeis-vs-typeguard.md +66 -0
  38. modern_python_guidance/skills/modern-python-guidance/guides/typing/union-syntax.md +59 -0
  39. modern_python_guidance/skills/modern-python-guidance/guides/typing/use-builtin-generics.md +61 -0
  40. modern_python_guidance/version_detect.py +136 -0
  41. modern_python_guidance-0.1.0.dist-info/METADATA +180 -0
  42. modern_python_guidance-0.1.0.dist-info/RECORD +45 -0
  43. modern_python_guidance-0.1.0.dist-info/WHEEL +4 -0
  44. modern_python_guidance-0.1.0.dist-info/entry_points.txt +3 -0
  45. 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)
@@ -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/)