modern-python-guidance 0.2.0__tar.gz → 0.2.1__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.2.0 → modern_python_guidance-0.2.1}/CHANGELOG.md +13 -0
- modern_python_guidance-0.2.1/CONTRIBUTING.md +49 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/PKG-INFO +48 -100
- modern_python_guidance-0.2.1/README.md +134 -0
- modern_python_guidance-0.2.1/bench/fixtures/variant-a-modern/pyproject.toml +16 -0
- modern_python_guidance-0.2.1/bench/fixtures/variant-a-modern/src/app.py +36 -0
- modern_python_guidance-0.2.1/bench/fixtures/variant-a-modern/src/config.py +20 -0
- modern_python_guidance-0.2.1/bench/fixtures/variant-a-modern/src/crawler.py +24 -0
- modern_python_guidance-0.2.1/bench/fixtures/variant-a-modern/src/models.py +71 -0
- modern_python_guidance-0.2.1/bench/fixtures/variant-a-modern/src/scanner.py +42 -0
- modern_python_guidance-0.2.1/bench/fixtures/variant-a-modern/src/utils.py +17 -0
- modern_python_guidance-0.2.1/bench/fixtures/variant-a-outdated/pyproject.toml +19 -0
- modern_python_guidance-0.2.1/bench/fixtures/variant-a-outdated/setup.py +7 -0
- modern_python_guidance-0.2.1/bench/fixtures/variant-a-outdated/src/app.py +32 -0
- modern_python_guidance-0.2.1/bench/fixtures/variant-a-outdated/src/config.py +23 -0
- modern_python_guidance-0.2.1/bench/fixtures/variant-a-outdated/src/crawler.py +36 -0
- modern_python_guidance-0.2.1/bench/fixtures/variant-a-outdated/src/models.py +62 -0
- modern_python_guidance-0.2.1/bench/fixtures/variant-a-outdated/src/scanner.py +37 -0
- modern_python_guidance-0.2.1/bench/fixtures/variant-a-outdated/src/utils.py +30 -0
- modern_python_guidance-0.2.1/bench/fixtures/variant-b-modern/myapp/models.py +15 -0
- modern_python_guidance-0.2.1/bench/fixtures/variant-b-modern/myapp/views.py +14 -0
- modern_python_guidance-0.2.1/bench/fixtures/variant-b-outdated/myapp/models.py +16 -0
- modern_python_guidance-0.2.1/bench/fixtures/variant-b-outdated/myapp/views.py +18 -0
- modern_python_guidance-0.2.1/bench/fixtures/variant-c-modern/tests/test_calculator.py +37 -0
- modern_python_guidance-0.2.1/bench/fixtures/variant-c-outdated/tests/test_calculator.py +35 -0
- modern_python_guidance-0.2.1/bench/mcp-config.json +8 -0
- modern_python_guidance-0.2.1/bench/prompt-v3-mcp.txt +20 -0
- modern_python_guidance-0.2.1/bench/prompt-v4-a.txt +15 -0
- modern_python_guidance-0.2.1/bench/prompt-v4-b.txt +7 -0
- modern_python_guidance-0.2.1/bench/prompt-v4-c.txt +3 -0
- modern_python_guidance-0.2.1/bench/run-mcp.sh +345 -0
- modern_python_guidance-0.2.1/bench/run-v4.sh +191 -0
- modern_python_guidance-0.2.1/bench/score-v4.sh +803 -0
- modern_python_guidance-0.2.1/bench/test-scorer.sh +94 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/docs/benchmark-evaluation.md +432 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/pyproject.toml +1 -1
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/src/modern_python_guidance/mcp_server.py +12 -29
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/tests/test_mcp_server.py +6 -19
- modern_python_guidance-0.2.0/README.md +0 -186
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/.github/workflows/ci.yml +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/.github/workflows/publish.yml +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/.gitignore +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/LICENSE +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/LICENSE-MIT +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/SECURITY.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/bench/prompt-v2.txt +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/bench/prompt-v3.txt +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/bench/prompt.txt +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/bench/run.sh +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/bench/score-v2.sh +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/bench/score-v3.sh +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/bench/score.sh +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/docs/benchmark-procedure.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/docs/design.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/SKILL.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/async/async-timeout-context.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/async/exception-groups.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/async/taskgroup-over-gather.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/data-structures/dataclass-modern.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/data-structures/dict-merge-operator.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/data-structures/match-case-patterns.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/django/django-async-views.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/django/django-check-constraints.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/django/django-json-field.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/fastapi/fastapi-annotated-depends.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/fastapi/fastapi-lifespan.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/fastapi/fastapi-typed-state.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/httpx/httpx-async-client-reuse.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/httpx/httpx-streaming.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/pydantic/pydantic-v2-config.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/pydantic/pydantic-v2-model-api.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/pydantic/pydantic-v2-serialization.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/pydantic/pydantic-v2-validators.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/pytest/pytest-parametrize.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/pytest/pytest-raises-match.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/pytest/pytest-tmp-path.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/sqlalchemy/sqlalchemy-2-style.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/sqlalchemy/sqlalchemy-async-session.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/sqlalchemy/sqlalchemy-mapped-column.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/stdlib/datetime-utc.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/stdlib/pathlib-over-os-path.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/stdlib/removeprefix-removesuffix.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/stdlib/tomllib-builtin.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/toolchain/no-pickle.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/toolchain/pyproject-toml-over-setup.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/toolchain/ruff-over-flake8.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/toolchain/safe-subprocess.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/toolchain/uv-over-pip.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/typing/override-decorator.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/typing/paramspec-decorators.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/typing/type-parameter-syntax.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/typing/typeis-vs-typeguard.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/typing/union-syntax.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/skills/modern-python-guidance/guides/typing/use-builtin-generics.md +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/src/modern_python_guidance/__init__.py +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/src/modern_python_guidance/__main__.py +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/src/modern_python_guidance/cli.py +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/src/modern_python_guidance/compat.py +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/src/modern_python_guidance/frontmatter.py +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/src/modern_python_guidance/guide_index.py +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/src/modern_python_guidance/retrieve.py +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/src/modern_python_guidance/search.py +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/src/modern_python_guidance/version_detect.py +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/tests/test_cli_integration.py +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/tests/test_frontmatter.py +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/tests/test_retrieve.py +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/tests/test_search.py +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/tests/test_skill_sync.py +0 -0
- {modern_python_guidance-0.2.0 → modern_python_guidance-0.2.1}/tests/test_version_detect.py +0 -0
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.2.1] — 2026-05-27
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- README rewrite: benefit-framed tagline, benchmark highlights, MCP-first quick start, persona-routed delivery methods
|
|
10
|
+
- Moved project structure and guide authoring spec from README to CONTRIBUTING.md
|
|
11
|
+
- Development section condensed to 5 lines + link
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- CONTRIBUTING.md with project structure, guide authoring spec, and test instructions
|
|
16
|
+
- Benchmark results (+21.9pp) featured in README highlights
|
|
17
|
+
|
|
5
18
|
## [0.2.0] — 2026-05-27
|
|
6
19
|
|
|
7
20
|
### Added
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
## Project structure
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
src/modern_python_guidance/
|
|
7
|
+
├── cli.py # Entry point (search, retrieve, list, detect-version, mcp)
|
|
8
|
+
├── mcp_server.py # MCP server (JSON-RPC 2.0 over stdio)
|
|
9
|
+
├── frontmatter.py # YAML-subset parser (no PyYAML dependency)
|
|
10
|
+
├── guide_index.py # Guide discovery and indexing
|
|
11
|
+
├── search.py # Weighted keyword search + fuzzy fallback
|
|
12
|
+
├── retrieve.py # Guide retrieval and JSON rendering
|
|
13
|
+
├── version_detect.py # Python version auto-detection
|
|
14
|
+
└── compat.py # Shared helpers
|
|
15
|
+
|
|
16
|
+
skills/modern-python-guidance/
|
|
17
|
+
├── SKILL.md # Agent Skills plugin entry point
|
|
18
|
+
└── guides/ # 39 guide files by category
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
See [docs/design.md](docs/design.md) for the full design document.
|
|
22
|
+
|
|
23
|
+
## Adding a new guide
|
|
24
|
+
|
|
25
|
+
1. Create `skills/modern-python-guidance/guides/<category>/<id>.md`
|
|
26
|
+
2. Include YAML frontmatter with these fields:
|
|
27
|
+
|
|
28
|
+
| Field | Type | Values |
|
|
29
|
+
|-------|------|--------|
|
|
30
|
+
| `id` | string | Unique kebab-case identifier (must match filename) |
|
|
31
|
+
| `title` | string | Short descriptive title |
|
|
32
|
+
| `category` | string | Must match parent directory name |
|
|
33
|
+
| `layer` | int | 1 (stdlib), 2 (frameworks), 3 (toolchain) |
|
|
34
|
+
| `tags` | list | Search keywords |
|
|
35
|
+
| `aliases` | list | Alternate names (old API names, etc.) |
|
|
36
|
+
| `python` | string | Minimum version, e.g. `">=3.11"` |
|
|
37
|
+
| `frequency` | string | `high` (LLMs do this often), `medium`, `low` |
|
|
38
|
+
|
|
39
|
+
3. Write BAD/GOOD/Why/Version Notes sections
|
|
40
|
+
4. Run `pytest` to verify the guide parses correctly
|
|
41
|
+
|
|
42
|
+
## Running tests
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
uv venv && source .venv/bin/activate
|
|
46
|
+
uv pip install -e ".[dev]"
|
|
47
|
+
pytest
|
|
48
|
+
ruff check src/ tests/
|
|
49
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modern-python-guidance
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
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
|
|
@@ -36,29 +36,65 @@ Description-Content-Type: text/markdown
|
|
|
36
36
|
[](https://pypi.org/project/modern-python-guidance/)
|
|
37
37
|
[](LICENSE)
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
Stop your AI from writing `typing.List`, `@validator`, and `setup.py`. 39 version-aware BAD/GOOD pattern guides that teach AI coding agents to write modern Python — delivered via MCP, CLI, or Agent Skills.
|
|
40
|
+
|
|
41
|
+
## Highlights
|
|
42
|
+
|
|
43
|
+
- **Measurable impact**: +14.7pp overall improvement in A/B benchmark with 38 scored items ([details](docs/benchmark-evaluation.md)). Largest variant (FastAPI, 32 items): Control 60.4% → Treatment 82.3%
|
|
44
|
+
- **39 guides** across stdlib, Pydantic, FastAPI, Django, SQLAlchemy, pytest, and toolchain
|
|
45
|
+
- **Version-aware**: auto-detects your project's Python version and filters guides accordingly
|
|
46
|
+
- **3 delivery methods**: MCP server, CLI, Agent Skills plugin
|
|
47
|
+
- **Not Ruff**: Ruff auto-fixes syntax (`List` → `list`). mpg guides design decisions that Ruff can't touch — `TaskGroup` over `gather`, Pydantic V2 migration, SQLAlchemy 2.0 style
|
|
40
48
|
|
|
41
49
|
> **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.
|
|
42
50
|
|
|
43
51
|
## Quick start
|
|
44
52
|
|
|
53
|
+
### MCP (for AI coding agents)
|
|
54
|
+
|
|
55
|
+
Install, then register the MCP server with your agent:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install modern-python-guidance
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Claude Code:**
|
|
62
|
+
```bash
|
|
63
|
+
claude mcp add mpg -- mpg mcp
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Other MCP-compatible agents** (Cursor, Windsurf, etc.) — add to your MCP config:
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"mcpServers": {
|
|
70
|
+
"mpg": {
|
|
71
|
+
"command": "mpg",
|
|
72
|
+
"args": ["mcp"]
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Your agent gets access to `search_guides`, `retrieve_guides`, `list_guides`, and `detect_python_version`.
|
|
79
|
+
|
|
80
|
+
### CLI
|
|
81
|
+
|
|
45
82
|
```bash
|
|
46
|
-
# Install
|
|
47
83
|
pip install modern-python-guidance
|
|
48
84
|
|
|
49
85
|
# Search for a pattern
|
|
50
86
|
mpg search "pydantic validator"
|
|
51
|
-
# pydantic-v2-validators score=18.0 [pydantic]
|
|
52
87
|
|
|
53
88
|
# Get the full guide
|
|
54
89
|
mpg retrieve pydantic-v2-validators
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
#
|
|
61
|
-
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Agent Skills (Claude Code plugin)
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# Symlink into your project
|
|
96
|
+
SKILL_DIR=$(python -c "from pathlib import Path; import modern_python_guidance; print(Path(modern_python_guidance.__file__).parent / 'skills' / 'modern-python-guidance')")
|
|
97
|
+
ln -s "$SKILL_DIR" your-project/.claude/skills/modern-python-guidance
|
|
62
98
|
```
|
|
63
99
|
|
|
64
100
|
`mpg` is the short alias for `modern-python-guidance`. Both work.
|
|
@@ -112,54 +148,6 @@ mpg list --python-version 3.9
|
|
|
112
148
|
# Excludes: TaskGroup (3.11+), match/case (3.10+), etc.
|
|
113
149
|
```
|
|
114
150
|
|
|
115
|
-
## MCP server
|
|
116
|
-
|
|
117
|
-
mpg includes a built-in [MCP](https://modelcontextprotocol.io) server that exposes all 4 commands as tools. AI agents (Claude Code, Cursor, Gemini CLI, etc.) can discover and call them directly.
|
|
118
|
-
|
|
119
|
-
### Setup with Claude Code
|
|
120
|
-
|
|
121
|
-
```bash
|
|
122
|
-
claude mcp add mpg -- mpg mcp
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
Or add to `.mcp.json` manually:
|
|
126
|
-
|
|
127
|
-
```json
|
|
128
|
-
{
|
|
129
|
-
"mcpServers": {
|
|
130
|
-
"mpg": {
|
|
131
|
-
"command": "mpg",
|
|
132
|
-
"args": ["mcp"]
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
### Available tools
|
|
139
|
-
|
|
140
|
-
| Tool | Description |
|
|
141
|
-
|------|-------------|
|
|
142
|
-
| `search_guides` | Search guides by keyword with fuzzy matching |
|
|
143
|
-
| `retrieve_guides` | Get full BAD/GOOD content by guide ID |
|
|
144
|
-
| `list_guides` | Browse all guides, filter by category/version |
|
|
145
|
-
| `detect_python_version` | Auto-detect project Python version |
|
|
146
|
-
|
|
147
|
-
The MCP server uses stdio transport (JSON-RPC 2.0) and adds zero additional dependencies.
|
|
148
|
-
|
|
149
|
-
## Agent Skills integration
|
|
150
|
-
|
|
151
|
-
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.
|
|
152
|
-
|
|
153
|
-
```bash
|
|
154
|
-
# Find where the package is installed
|
|
155
|
-
SKILL_DIR=$(python -c "from pathlib import Path; import modern_python_guidance; print(Path(modern_python_guidance.__file__).parent / 'skills' / 'modern-python-guidance')")
|
|
156
|
-
|
|
157
|
-
# Symlink into your project
|
|
158
|
-
ln -s "$SKILL_DIR" your-project/.claude/skills/modern-python-guidance
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
For other AI tools (Cursor, Copilot, etc.), use the CLI directly — pipe `mpg search` or `mpg retrieve` output into your workflow.
|
|
162
|
-
|
|
163
151
|
## Development
|
|
164
152
|
|
|
165
153
|
```bash
|
|
@@ -168,49 +156,9 @@ cd modern-python-guidance
|
|
|
168
156
|
uv venv && source .venv/bin/activate
|
|
169
157
|
uv pip install -e ".[dev]"
|
|
170
158
|
pytest
|
|
171
|
-
ruff check src/ tests/
|
|
172
159
|
```
|
|
173
160
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
```
|
|
177
|
-
src/modern_python_guidance/
|
|
178
|
-
├── cli.py # Entry point (search, retrieve, list, detect-version, mcp)
|
|
179
|
-
├── mcp_server.py # MCP server (JSON-RPC 2.0 over stdio)
|
|
180
|
-
├── frontmatter.py # YAML-subset parser (no PyYAML dependency)
|
|
181
|
-
├── guide_index.py # Guide discovery and indexing
|
|
182
|
-
├── search.py # Weighted keyword search + fuzzy fallback
|
|
183
|
-
├── retrieve.py # Guide retrieval and JSON rendering
|
|
184
|
-
├── version_detect.py # Python version auto-detection
|
|
185
|
-
└── compat.py # Shared helpers
|
|
186
|
-
|
|
187
|
-
skills/modern-python-guidance/
|
|
188
|
-
├── SKILL.md # Agent Skills plugin entry point
|
|
189
|
-
└── guides/ # 39 guide files by category
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
See [docs/design.md](docs/design.md) for the full design document.
|
|
193
|
-
|
|
194
|
-
## Contributing
|
|
195
|
-
|
|
196
|
-
Contributions welcome! To add a new guide:
|
|
197
|
-
|
|
198
|
-
1. Create `skills/modern-python-guidance/guides/<category>/<id>.md`
|
|
199
|
-
2. Include YAML frontmatter with these fields:
|
|
200
|
-
|
|
201
|
-
| Field | Type | Values |
|
|
202
|
-
|-------|------|--------|
|
|
203
|
-
| `id` | string | Unique kebab-case identifier (must match filename) |
|
|
204
|
-
| `title` | string | Short descriptive title |
|
|
205
|
-
| `category` | string | Must match parent directory name |
|
|
206
|
-
| `layer` | int | 1 (stdlib), 2 (frameworks), 3 (toolchain) |
|
|
207
|
-
| `tags` | list | Search keywords |
|
|
208
|
-
| `aliases` | list | Alternate names (old API names, etc.) |
|
|
209
|
-
| `python` | string | Minimum version, e.g. `">=3.11"` |
|
|
210
|
-
| `frequency` | string | `high` (LLMs do this often), `medium`, `low` |
|
|
211
|
-
|
|
212
|
-
3. Write BAD/GOOD/Why/Version Notes sections
|
|
213
|
-
4. Run `pytest` to verify the guide parses correctly
|
|
161
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for project structure and guide authoring details.
|
|
214
162
|
|
|
215
163
|
## License
|
|
216
164
|
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# modern-python-guidance
|
|
2
|
+
|
|
3
|
+
[](https://github.com/yottayoshida/modern-python-guidance/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/modern-python-guidance/)
|
|
5
|
+
[](https://pypi.org/project/modern-python-guidance/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
Stop your AI from writing `typing.List`, `@validator`, and `setup.py`. 39 version-aware BAD/GOOD pattern guides that teach AI coding agents to write modern Python — delivered via MCP, CLI, or Agent Skills.
|
|
9
|
+
|
|
10
|
+
## Highlights
|
|
11
|
+
|
|
12
|
+
- **Measurable impact**: +14.7pp overall improvement in A/B benchmark with 38 scored items ([details](docs/benchmark-evaluation.md)). Largest variant (FastAPI, 32 items): Control 60.4% → Treatment 82.3%
|
|
13
|
+
- **39 guides** across stdlib, Pydantic, FastAPI, Django, SQLAlchemy, pytest, and toolchain
|
|
14
|
+
- **Version-aware**: auto-detects your project's Python version and filters guides accordingly
|
|
15
|
+
- **3 delivery methods**: MCP server, CLI, Agent Skills plugin
|
|
16
|
+
- **Not Ruff**: Ruff auto-fixes syntax (`List` → `list`). mpg guides design decisions that Ruff can't touch — `TaskGroup` over `gather`, Pydantic V2 migration, SQLAlchemy 2.0 style
|
|
17
|
+
|
|
18
|
+
> **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.
|
|
19
|
+
|
|
20
|
+
## Quick start
|
|
21
|
+
|
|
22
|
+
### MCP (for AI coding agents)
|
|
23
|
+
|
|
24
|
+
Install, then register the MCP server with your agent:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install modern-python-guidance
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Claude Code:**
|
|
31
|
+
```bash
|
|
32
|
+
claude mcp add mpg -- mpg mcp
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Other MCP-compatible agents** (Cursor, Windsurf, etc.) — add to your MCP config:
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"mcpServers": {
|
|
39
|
+
"mpg": {
|
|
40
|
+
"command": "mpg",
|
|
41
|
+
"args": ["mcp"]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Your agent gets access to `search_guides`, `retrieve_guides`, `list_guides`, and `detect_python_version`.
|
|
48
|
+
|
|
49
|
+
### CLI
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install modern-python-guidance
|
|
53
|
+
|
|
54
|
+
# Search for a pattern
|
|
55
|
+
mpg search "pydantic validator"
|
|
56
|
+
|
|
57
|
+
# Get the full guide
|
|
58
|
+
mpg retrieve pydantic-v2-validators
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Agent Skills (Claude Code plugin)
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# Symlink into your project
|
|
65
|
+
SKILL_DIR=$(python -c "from pathlib import Path; import modern_python_guidance; print(Path(modern_python_guidance.__file__).parent / 'skills' / 'modern-python-guidance')")
|
|
66
|
+
ln -s "$SKILL_DIR" your-project/.claude/skills/modern-python-guidance
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`mpg` is the short alias for `modern-python-guidance`. Both work.
|
|
70
|
+
|
|
71
|
+
## CLI usage
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# Search guides by keyword
|
|
75
|
+
mpg search "pydantic validator"
|
|
76
|
+
|
|
77
|
+
# Retrieve a specific guide (full BAD/GOOD content)
|
|
78
|
+
mpg retrieve use-builtin-generics
|
|
79
|
+
|
|
80
|
+
# List all guides compatible with your Python version
|
|
81
|
+
mpg list --python-version 3.11
|
|
82
|
+
|
|
83
|
+
# Auto-detect project Python version from pyproject.toml / .python-version
|
|
84
|
+
mpg detect-version
|
|
85
|
+
|
|
86
|
+
# Filter by category
|
|
87
|
+
mpg search "timeout" --category async
|
|
88
|
+
|
|
89
|
+
# JSON output (default when piped, explicit with --format)
|
|
90
|
+
mpg search "typing" --format json | jq '.[0].id'
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Guide coverage
|
|
94
|
+
|
|
95
|
+
39 guides across 3 layers:
|
|
96
|
+
|
|
97
|
+
| Layer | Categories | Count | Examples |
|
|
98
|
+
|-------|-----------|-------|---------|
|
|
99
|
+
| **1 — stdlib** | typing, async, stdlib, data-structures | 16 | `list` over `List`, `match`/`case`, `TaskGroup` |
|
|
100
|
+
| **2 — frameworks** | pydantic, fastapi, httpx, django, sqlalchemy, pytest | 18 | Pydantic V2 migration, SQLAlchemy 2.0 style, `Annotated[Depends]` |
|
|
101
|
+
| **3 — toolchain** | toolchain | 5 | `uv` over `pip`, `ruff` over flake8, `pickle` avoidance |
|
|
102
|
+
|
|
103
|
+
Run `mpg list` to see all 39 guides, or [browse them on GitHub](skills/modern-python-guidance/guides/).
|
|
104
|
+
|
|
105
|
+
## Version-aware filtering
|
|
106
|
+
|
|
107
|
+
Guides specify their minimum Python version. The CLI auto-detects your project's version from (in order):
|
|
108
|
+
|
|
109
|
+
1. `--python-version` flag
|
|
110
|
+
2. `pyproject.toml` `requires-python`
|
|
111
|
+
3. `.python-version` file
|
|
112
|
+
4. Default: 3.11
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
# Only shows guides compatible with Python 3.9
|
|
116
|
+
mpg list --python-version 3.9
|
|
117
|
+
# Excludes: TaskGroup (3.11+), match/case (3.10+), etc.
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Development
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
git clone https://github.com/yottayoshida/modern-python-guidance.git
|
|
124
|
+
cd modern-python-guidance
|
|
125
|
+
uv venv && source .venv/bin/activate
|
|
126
|
+
uv pip install -e ".[dev]"
|
|
127
|
+
pytest
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for project structure and guide authoring details.
|
|
131
|
+
|
|
132
|
+
## License
|
|
133
|
+
|
|
134
|
+
Apache-2.0 OR MIT — see [LICENSE](LICENSE) and [LICENSE-MIT](LICENSE-MIT).
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "benchmark-app"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
requires-python = ">=3.12"
|
|
5
|
+
dependencies = [
|
|
6
|
+
"fastapi",
|
|
7
|
+
"sqlalchemy[asyncio]",
|
|
8
|
+
"httpx",
|
|
9
|
+
"uvicorn",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
[tool.ruff]
|
|
13
|
+
target-version = "py312"
|
|
14
|
+
|
|
15
|
+
[tool.ruff.lint]
|
|
16
|
+
select = ["E", "F", "I", "UP"]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from collections.abc import AsyncGenerator
|
|
2
|
+
from contextlib import asynccontextmanager
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
|
|
5
|
+
from fastapi import Depends, FastAPI
|
|
6
|
+
from sqlalchemy import select
|
|
7
|
+
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
engine = create_async_engine("sqlite+aiosqlite:///db.sqlite3")
|
|
11
|
+
async_session = async_sessionmaker(engine, expire_on_commit=False)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
async def get_db() -> AsyncGenerator[AsyncSession, None]:
|
|
15
|
+
async with async_session() as session:
|
|
16
|
+
yield session
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
DbSession = Annotated[AsyncSession, Depends(get_db)]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@asynccontextmanager
|
|
23
|
+
async def lifespan(app: FastAPI):
|
|
24
|
+
async with engine.begin() as conn:
|
|
25
|
+
pass
|
|
26
|
+
yield {"db_pool": engine}
|
|
27
|
+
await engine.dispose()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
app = FastAPI(lifespan=lifespan)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@app.get("/users")
|
|
34
|
+
async def list_users(db: DbSession):
|
|
35
|
+
result = await db.execute(select(User))
|
|
36
|
+
return result.scalars().all()
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import tomllib
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from datetime import datetime, UTC
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass(slots=True)
|
|
8
|
+
class AppConfig:
|
|
9
|
+
db_url: str
|
|
10
|
+
debug: bool = False
|
|
11
|
+
created_at: datetime = None
|
|
12
|
+
|
|
13
|
+
def __post_init__(self):
|
|
14
|
+
if self.created_at is None:
|
|
15
|
+
self.created_at = datetime.now(UTC)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def load_config(path: Path) -> dict:
|
|
19
|
+
with open(path, "rb") as f:
|
|
20
|
+
return tomllib.load(f)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
async def crawl(urls: list[str]) -> list[str]:
|
|
7
|
+
results = []
|
|
8
|
+
async with httpx.AsyncClient() as client:
|
|
9
|
+
async with asyncio.TaskGroup() as tg:
|
|
10
|
+
for url in urls:
|
|
11
|
+
tg.create_task(_fetch(client, url, results))
|
|
12
|
+
return results
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async def _fetch(client: httpx.AsyncClient, url: str, results: list[str]) -> None:
|
|
16
|
+
async with asyncio.timeout(30):
|
|
17
|
+
response = await client.get(url)
|
|
18
|
+
results.append(response.text)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
async def download_large(client: httpx.AsyncClient, url: str, dest: str) -> None:
|
|
22
|
+
async with client.stream("GET", url) as resp:
|
|
23
|
+
async for chunk in resp.aiter_bytes():
|
|
24
|
+
pass
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from datetime import datetime, UTC
|
|
2
|
+
from typing import override
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, ConfigDict, field_validator, field_serializer, model_validator
|
|
5
|
+
from sqlalchemy import String, select
|
|
6
|
+
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Base(DeclarativeBase):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class User(Base):
|
|
14
|
+
__tablename__ = "users"
|
|
15
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
16
|
+
name: Mapped[str] = mapped_column(String(100))
|
|
17
|
+
email: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
18
|
+
created_at: Mapped[datetime] = mapped_column(default=datetime.now(UTC))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class UserCreate(BaseModel):
|
|
22
|
+
model_config = ConfigDict(str_strip_whitespace=True)
|
|
23
|
+
|
|
24
|
+
name: str
|
|
25
|
+
email: str | None = None
|
|
26
|
+
|
|
27
|
+
@field_validator("name")
|
|
28
|
+
@classmethod
|
|
29
|
+
def name_not_empty(cls, v: str) -> str:
|
|
30
|
+
if not v:
|
|
31
|
+
raise ValueError("Name cannot be empty")
|
|
32
|
+
return v
|
|
33
|
+
|
|
34
|
+
@field_serializer("email")
|
|
35
|
+
def mask_email(self, v: str | None) -> str | None:
|
|
36
|
+
if v is None:
|
|
37
|
+
return None
|
|
38
|
+
local, domain = v.split("@")
|
|
39
|
+
return f"{local[0]}***@{domain}"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class UserResponse(BaseModel):
|
|
43
|
+
id: int
|
|
44
|
+
name: str
|
|
45
|
+
email: str | None = None
|
|
46
|
+
|
|
47
|
+
@model_validator(mode="after")
|
|
48
|
+
def check_consistency(self):
|
|
49
|
+
return self
|
|
50
|
+
|
|
51
|
+
def to_dict(self) -> dict:
|
|
52
|
+
return self.model_dump()
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def from_orm(cls, obj):
|
|
56
|
+
return cls.model_validate(obj, from_attributes=True)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class Registry[T]:
|
|
60
|
+
def __init__(self) -> None:
|
|
61
|
+
self._items: dict[str, T] = {}
|
|
62
|
+
|
|
63
|
+
@override
|
|
64
|
+
def __repr__(self) -> str:
|
|
65
|
+
return f"Registry({list(self._items.keys())})"
|
|
66
|
+
|
|
67
|
+
def add(self, key: str, value: T) -> None:
|
|
68
|
+
self._items[key] = value
|
|
69
|
+
|
|
70
|
+
def get(self, key: str) -> T | None:
|
|
71
|
+
return self._items.get(key)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class FileCategory(Enum):
|
|
7
|
+
IMAGE = "image"
|
|
8
|
+
VIDEO = "video"
|
|
9
|
+
DOCUMENT = "document"
|
|
10
|
+
OTHER = "other"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def categorize(ext: str) -> FileCategory:
|
|
14
|
+
match ext:
|
|
15
|
+
case ".jpg" | ".png" | ".gif":
|
|
16
|
+
return FileCategory.IMAGE
|
|
17
|
+
case ".mp4" | ".avi":
|
|
18
|
+
return FileCategory.VIDEO
|
|
19
|
+
case ".pdf" | ".docx":
|
|
20
|
+
return FileCategory.DOCUMENT
|
|
21
|
+
case _:
|
|
22
|
+
return FileCategory.OTHER
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def parse_log_line(line: str) -> str:
|
|
26
|
+
return line.removeprefix("[INFO] ").removesuffix("\n")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
async def safe_scan(paths: list[Path]) -> list[FileCategory]:
|
|
30
|
+
results = []
|
|
31
|
+
try:
|
|
32
|
+
async with asyncio.TaskGroup() as tg:
|
|
33
|
+
for p in paths:
|
|
34
|
+
tg.create_task(_scan_one(p, results))
|
|
35
|
+
except* OSError as eg:
|
|
36
|
+
for exc in eg.exceptions:
|
|
37
|
+
print(f"OS error: {exc}")
|
|
38
|
+
return results
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
async def _scan_one(p: Path, results: list[FileCategory]) -> None:
|
|
42
|
+
results.append(categorize(p.suffix))
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
from typing import ParamSpec, TypeIs
|
|
3
|
+
|
|
4
|
+
P = ParamSpec("P")
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def merge_defaults(user: dict, defaults: dict) -> dict:
|
|
8
|
+
return defaults | user
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def run_command(cmd: str, *extra: str) -> str:
|
|
12
|
+
result = subprocess.run([cmd, *extra], capture_output=True, text=True, check=True)
|
|
13
|
+
return result.stdout
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def is_positive_int(val: object) -> TypeIs[int]:
|
|
17
|
+
return isinstance(val, int) and val > 0
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "benchmark-app"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
requires-python = ">=3.12"
|
|
5
|
+
dependencies = [
|
|
6
|
+
"fastapi",
|
|
7
|
+
"sqlalchemy",
|
|
8
|
+
"httpx",
|
|
9
|
+
"uvicorn",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
[tool.black]
|
|
13
|
+
line-length = 88
|
|
14
|
+
|
|
15
|
+
[tool.isort]
|
|
16
|
+
profile = "black"
|
|
17
|
+
|
|
18
|
+
[tool.flake8]
|
|
19
|
+
max-line-length = 88
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from fastapi import Depends, FastAPI
|
|
2
|
+
from sqlalchemy import create_engine
|
|
3
|
+
from sqlalchemy.orm import Session, sessionmaker
|
|
4
|
+
|
|
5
|
+
engine = create_engine("sqlite:///db.sqlite3")
|
|
6
|
+
SessionLocal = sessionmaker(bind=engine)
|
|
7
|
+
|
|
8
|
+
app = FastAPI()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@app.on_event("startup")
|
|
12
|
+
async def startup():
|
|
13
|
+
app.state.db_engine = engine
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@app.on_event("shutdown")
|
|
17
|
+
async def shutdown():
|
|
18
|
+
engine.dispose()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_db():
|
|
22
|
+
db = SessionLocal()
|
|
23
|
+
try:
|
|
24
|
+
yield db
|
|
25
|
+
finally:
|
|
26
|
+
db.close()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@app.get("/users")
|
|
30
|
+
def list_users(db: Session = Depends(get_db)):
|
|
31
|
+
users = db.query(User).all()
|
|
32
|
+
return users
|