modern-python-guidance 0.4.2__tar.gz → 0.4.3__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.4.2 → modern_python_guidance-0.4.3}/CHANGELOG.md +12 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/PKG-INFO +1 -1
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/pyproject.toml +1 -1
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/src/modern_python_guidance/__init__.py +1 -1
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/src/modern_python_guidance/check.py +22 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/src/modern_python_guidance/compat.py +2 -2
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/src/modern_python_guidance/frontmatter.py +9 -1
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/src/modern_python_guidance/mcp_server.py +24 -1
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/tests/test_check.py +56 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/tests/test_compat.py +12 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/tests/test_frontmatter.py +43 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/tests/test_guide_structure.py +9 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/tests/test_mcp_server.py +31 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/tests/test_mcp_unit.py +112 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/.github/workflows/check-python-release.yml +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/.github/workflows/ci.yml +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/.github/workflows/publish.yml +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/.gitignore +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/CONTRIBUTING.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/LICENSE +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/LICENSE-MIT +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/README.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/SECURITY.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/fixtures/edge-cases/opus48_multiline.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/fixtures/edge-cases/valid_alt_patterns.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/fixtures/variant-a-modern/pyproject.toml +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/fixtures/variant-a-modern/src/app.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/fixtures/variant-a-modern/src/config.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/fixtures/variant-a-modern/src/crawler.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/fixtures/variant-a-modern/src/models.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/fixtures/variant-a-modern/src/scanner.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/fixtures/variant-a-modern/src/utils.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/fixtures/variant-a-outdated/pyproject.toml +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/fixtures/variant-a-outdated/setup.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/fixtures/variant-a-outdated/src/app.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/fixtures/variant-a-outdated/src/config.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/fixtures/variant-a-outdated/src/crawler.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/fixtures/variant-a-outdated/src/models.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/fixtures/variant-a-outdated/src/scanner.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/fixtures/variant-a-outdated/src/utils.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/fixtures/variant-b-modern/myapp/models.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/fixtures/variant-b-modern/myapp/views.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/fixtures/variant-b-outdated/myapp/models.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/fixtures/variant-b-outdated/myapp/views.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/fixtures/variant-c-modern/tests/test_calculator.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/fixtures/variant-c-outdated/tests/test_calculator.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/mcp-config.json +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/prompt-v2.txt +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/prompt-v3-mcp.txt +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/prompt-v3.txt +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/prompt-v4-a.txt +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/prompt-v4-b.txt +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/prompt-v4-c.txt +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/prompt.txt +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/prompts/v5-a-detailed.txt +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/prompts/v5-a-normal.txt +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/prompts/v5-a-terse.txt +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/prompts/v5-b-detailed.txt +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/prompts/v5-b-normal.txt +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/prompts/v5-b-terse.txt +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/prompts/v5-c-detailed.txt +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/prompts/v5-c-normal.txt +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/prompts/v5-c-terse.txt +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/run-mcp.sh +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/run-v4.sh +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/run-v5.sh +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/run.sh +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/score-v2.sh +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/score-v3.sh +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/score-v4.sh +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/score.sh +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/score_v5.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/test-scorer.sh +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/docs/benchmark-evaluation.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/docs/benchmark-procedure.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/docs/benchmark-v5.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/docs/design.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/rules/modern-python.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/SKILL.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/async/async-timeout-context.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/async/exception-groups.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/async/taskgroup-over-gather.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/data-structures/dataclass-modern.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/data-structures/dict-merge-operator.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/data-structures/match-case-patterns.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/django/django-async-views.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/django/django-check-constraints.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/django/django-json-field.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/fastapi/fastapi-annotated-depends.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/fastapi/fastapi-lifespan.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/fastapi/fastapi-typed-state.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/httpx/httpx-async-client-reuse.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/httpx/httpx-streaming.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/pydantic/pydantic-v2-config.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/pydantic/pydantic-v2-model-api.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/pydantic/pydantic-v2-serialization.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/pydantic/pydantic-v2-validators.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/pytest/pytest-parametrize.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/pytest/pytest-raises-match.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/pytest/pytest-tmp-path.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/sqlalchemy/sqlalchemy-2-style.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/sqlalchemy/sqlalchemy-async-session.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/sqlalchemy/sqlalchemy-mapped-column.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/stdlib/datetime-utc.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/stdlib/pathlib-over-os-path.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/stdlib/removeprefix-removesuffix.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/stdlib/template-strings.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/stdlib/tomllib-builtin.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/toolchain/no-pickle.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/toolchain/pyproject-toml-over-setup.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/toolchain/ruff-over-flake8.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/toolchain/safe-subprocess.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/toolchain/uv-over-pip.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/typing/deferred-annotations.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/typing/override-decorator.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/typing/paramspec-decorators.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/typing/type-parameter-syntax.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/typing/typeis-vs-typeguard.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/typing/union-syntax.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/guides/typing/use-builtin-generics.md +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/src/modern_python_guidance/__main__.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/src/modern_python_guidance/cli.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/src/modern_python_guidance/guide_index.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/src/modern_python_guidance/retrieve.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/src/modern_python_guidance/search.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/src/modern_python_guidance/setup_cmd.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/src/modern_python_guidance/uninstall_cmd.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/src/modern_python_guidance/version_detect.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/tests/test_cli_integration.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/tests/test_cli_unit.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/tests/test_guide_index.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/tests/test_retrieve.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/tests/test_scorer_v5.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/tests/test_search.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/tests/test_setup.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/tests/test_skill_sync.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/tests/test_uninstall.py +0 -0
- {modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/tests/test_version_detect.py +0 -0
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.4.3] — 2026-06-04
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- MCP server crash on malformed `params` and `arguments`: non-dict values now return JSON-RPC -32602 instead of `TypeError`. `serve()` catch-all returns -32603 on unexpected errors. Notification messages (no `id`) are silently dropped per JSON-RPC 2.0 spec. (closes #91)
|
|
10
|
+
- `mpg check` false positives in multi-line docstrings: `check_file()` now uses `tokenize` to identify multi-line string token ranges and skips those lines. Single-line strings on code lines are still scanned. Tokenize failure (syntax errors, indentation errors) falls back to scanning all lines. (closes #92)
|
|
11
|
+
- Invalid PEP 440 specifiers in guide `python:` field silently treated as all-version compatible: `_build_meta()` now validates with `SpecifierSet` at parse time, raising `FrontmatterError`. Runtime `version_compatible()` narrows except from `(InvalidSpecifier, Exception)` to `(InvalidSpecifier, InvalidVersion)`. (closes #93)
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- 53 new tests (987 total).
|
|
16
|
+
|
|
5
17
|
## [0.4.2] — 2026-06-04
|
|
6
18
|
|
|
7
19
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modern-python-guidance
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.3
|
|
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
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "modern-python-guidance"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.3"
|
|
8
8
|
description = "Version-aware BAD/GOOD pattern guides that help AI coding agents generate modern Python"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0 OR MIT"
|
{modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/src/modern_python_guidance/check.py
RENAMED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import io
|
|
5
6
|
import re
|
|
7
|
+
import tokenize as _tokenize
|
|
6
8
|
from dataclasses import dataclass
|
|
7
9
|
from pathlib import Path
|
|
8
10
|
|
|
@@ -33,6 +35,23 @@ class CheckMatch:
|
|
|
33
35
|
snippet: str
|
|
34
36
|
|
|
35
37
|
|
|
38
|
+
def _string_lines(text: str) -> frozenset[int]:
|
|
39
|
+
"""Line numbers belonging to multi-line STRING tokens (docstrings etc.)."""
|
|
40
|
+
skip: set[int] = set()
|
|
41
|
+
try:
|
|
42
|
+
tokens = _tokenize.generate_tokens(io.StringIO(text).readline)
|
|
43
|
+
string_types = {_tokenize.STRING}
|
|
44
|
+
fstring_mid = getattr(_tokenize, "FSTRING_MIDDLE", None)
|
|
45
|
+
if fstring_mid is not None:
|
|
46
|
+
string_types.add(fstring_mid)
|
|
47
|
+
for tok in tokens:
|
|
48
|
+
if tok.type in string_types and tok.end[0] > tok.start[0]:
|
|
49
|
+
skip.update(range(tok.start[0], tok.end[0] + 1))
|
|
50
|
+
except (_tokenize.TokenError, SyntaxError):
|
|
51
|
+
return frozenset()
|
|
52
|
+
return frozenset(skip)
|
|
53
|
+
|
|
54
|
+
|
|
36
55
|
def check_file(
|
|
37
56
|
path: Path,
|
|
38
57
|
index: GuideIndex,
|
|
@@ -48,8 +67,11 @@ def check_file(
|
|
|
48
67
|
if not patterns:
|
|
49
68
|
return []
|
|
50
69
|
|
|
70
|
+
skip = _string_lines(text)
|
|
51
71
|
matches: list[CheckMatch] = []
|
|
52
72
|
for lineno, line in enumerate(text.splitlines(), 1):
|
|
73
|
+
if lineno in skip:
|
|
74
|
+
continue
|
|
53
75
|
stripped = line.strip()
|
|
54
76
|
if not stripped or stripped.startswith("#"):
|
|
55
77
|
continue
|
{modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/src/modern_python_guidance/compat.py
RENAMED
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import re
|
|
6
6
|
|
|
7
7
|
from packaging.specifiers import InvalidSpecifier, SpecifierSet
|
|
8
|
-
from packaging.version import Version
|
|
8
|
+
from packaging.version import InvalidVersion, Version
|
|
9
9
|
|
|
10
10
|
VERSION_RE = re.compile(r"^\d+\.\d+$")
|
|
11
11
|
|
|
@@ -14,7 +14,7 @@ def version_compatible(guide_python: str, target: str) -> bool:
|
|
|
14
14
|
try:
|
|
15
15
|
spec = SpecifierSet(guide_python)
|
|
16
16
|
return Version(f"{target}.0") in spec
|
|
17
|
-
except (InvalidSpecifier,
|
|
17
|
+
except (InvalidSpecifier, InvalidVersion):
|
|
18
18
|
return True
|
|
19
19
|
|
|
20
20
|
|
|
@@ -13,6 +13,8 @@ import re
|
|
|
13
13
|
from dataclasses import dataclass, field
|
|
14
14
|
from typing import Any
|
|
15
15
|
|
|
16
|
+
from packaging.specifiers import InvalidSpecifier, SpecifierSet
|
|
17
|
+
|
|
16
18
|
_KEY_RE = re.compile(r"^([a-z][a-z0-9_-]*)\s*:\s*(.*)")
|
|
17
19
|
_LIST_ITEM_RE = re.compile(r"^ - (.+)")
|
|
18
20
|
_FENCE = "---"
|
|
@@ -123,6 +125,12 @@ def _build_meta(raw: dict[str, Any]) -> GuideMeta:
|
|
|
123
125
|
if isinstance(raw[str_field], list):
|
|
124
126
|
raise FrontmatterError(f"'{str_field}' must be a scalar value, not a list")
|
|
125
127
|
|
|
128
|
+
python_value = str(raw["python"])
|
|
129
|
+
try:
|
|
130
|
+
SpecifierSet(python_value)
|
|
131
|
+
except InvalidSpecifier as e:
|
|
132
|
+
raise FrontmatterError(f"invalid python specifier '{python_value}': {e}") from e
|
|
133
|
+
|
|
126
134
|
freq = raw["frequency"]
|
|
127
135
|
if freq not in VALID_FREQUENCIES:
|
|
128
136
|
raise FrontmatterError(f"invalid frequency '{freq}', must be one of {VALID_FREQUENCIES}")
|
|
@@ -171,7 +179,7 @@ def _build_meta(raw: dict[str, Any]) -> GuideMeta:
|
|
|
171
179
|
category=str(raw["category"]),
|
|
172
180
|
layer=layer,
|
|
173
181
|
tags=[str(t) for t in tags],
|
|
174
|
-
python=
|
|
182
|
+
python=python_value,
|
|
175
183
|
frequency=freq,
|
|
176
184
|
aliases=[str(a) for a in aliases_raw],
|
|
177
185
|
pep=pep,
|
|
@@ -347,6 +347,15 @@ def _handle_request(msg: dict) -> dict | None:
|
|
|
347
347
|
params = msg.get("params", {})
|
|
348
348
|
is_notification = "id" not in msg
|
|
349
349
|
|
|
350
|
+
if not isinstance(params, dict):
|
|
351
|
+
if not is_notification:
|
|
352
|
+
return _error_response(
|
|
353
|
+
req_id,
|
|
354
|
+
-32602,
|
|
355
|
+
f"Invalid params: expected object, got {type(params).__name__}",
|
|
356
|
+
)
|
|
357
|
+
return None
|
|
358
|
+
|
|
350
359
|
if method == "notifications/initialized":
|
|
351
360
|
return None
|
|
352
361
|
|
|
@@ -368,6 +377,13 @@ def _handle_request(msg: dict) -> dict | None:
|
|
|
368
377
|
if method == "tools/call":
|
|
369
378
|
tool_name = params.get("name", "")
|
|
370
379
|
arguments = params.get("arguments", {})
|
|
380
|
+
if not isinstance(arguments, dict):
|
|
381
|
+
result = _error_response(
|
|
382
|
+
req_id,
|
|
383
|
+
-32602,
|
|
384
|
+
f"Invalid arguments: expected object, got {type(arguments).__name__}",
|
|
385
|
+
)
|
|
386
|
+
return None if is_notification else result
|
|
371
387
|
tool_result = _handle_tool_call(tool_name, arguments)
|
|
372
388
|
result = _result_response(req_id, tool_result)
|
|
373
389
|
return None if is_notification else result
|
|
@@ -390,7 +406,14 @@ def serve(*, stdin: object = None, stdout: object = None) -> None:
|
|
|
390
406
|
if msg is None:
|
|
391
407
|
break
|
|
392
408
|
|
|
393
|
-
|
|
409
|
+
try:
|
|
410
|
+
response = _handle_request(msg)
|
|
411
|
+
except Exception:
|
|
412
|
+
log.exception("Unexpected error handling request")
|
|
413
|
+
if isinstance(msg, dict) and "id" in msg:
|
|
414
|
+
response = _error_response(msg.get("id"), -32603, "Internal error")
|
|
415
|
+
else:
|
|
416
|
+
continue
|
|
394
417
|
if response is not None:
|
|
395
418
|
_write_message(response, stdout)
|
|
396
419
|
|
|
@@ -276,6 +276,62 @@ class TestEdgeCases:
|
|
|
276
276
|
assert len(patterns) == 0
|
|
277
277
|
|
|
278
278
|
|
|
279
|
+
class TestStringLineFiltering:
|
|
280
|
+
def test_docstring_not_matched(self, tmp_path: Path, index: GuideIndex):
|
|
281
|
+
p = tmp_path / "docstring.py"
|
|
282
|
+
p.write_text(
|
|
283
|
+
"def example():\n"
|
|
284
|
+
' """Use datetime.utcnow() for timestamps.\n'
|
|
285
|
+
"\n"
|
|
286
|
+
" Also from typing import List is common.\n"
|
|
287
|
+
' """\n'
|
|
288
|
+
" return 1\n",
|
|
289
|
+
encoding="utf-8",
|
|
290
|
+
)
|
|
291
|
+
matches = check_file(p, index)
|
|
292
|
+
assert matches == []
|
|
293
|
+
|
|
294
|
+
def test_inline_string_on_code_line_still_matched(self, tmp_path: Path, index: GuideIndex):
|
|
295
|
+
p = tmp_path / "inline.py"
|
|
296
|
+
p.write_text(
|
|
297
|
+
'from typing import List\nx = "some string"\n',
|
|
298
|
+
encoding="utf-8",
|
|
299
|
+
)
|
|
300
|
+
matches = check_file(p, index)
|
|
301
|
+
ids = {m.guide_id for m in matches}
|
|
302
|
+
assert "use-builtin-generics" in ids
|
|
303
|
+
|
|
304
|
+
def test_tokenize_failure_falls_back(self, tmp_path: Path, index: GuideIndex):
|
|
305
|
+
p = tmp_path / "broken.py"
|
|
306
|
+
p.write_text(
|
|
307
|
+
'from typing import List\nx = """unterminated\n',
|
|
308
|
+
encoding="utf-8",
|
|
309
|
+
)
|
|
310
|
+
matches = check_file(p, index)
|
|
311
|
+
ids = {m.guide_id for m in matches}
|
|
312
|
+
assert "use-builtin-generics" in ids
|
|
313
|
+
|
|
314
|
+
def test_indentation_error_falls_back(self, tmp_path: Path, index: GuideIndex):
|
|
315
|
+
p = tmp_path / "indent.py"
|
|
316
|
+
p.write_text(
|
|
317
|
+
"from typing import List\nif True:\n x = 1\n y = 2\n",
|
|
318
|
+
encoding="utf-8",
|
|
319
|
+
)
|
|
320
|
+
matches = check_file(p, index)
|
|
321
|
+
ids = {m.guide_id for m in matches}
|
|
322
|
+
assert "use-builtin-generics" in ids
|
|
323
|
+
|
|
324
|
+
def test_single_line_string_not_skipped(self, tmp_path: Path, index: GuideIndex):
|
|
325
|
+
p = tmp_path / "singleline.py"
|
|
326
|
+
p.write_text(
|
|
327
|
+
"from typing import List # 'example'\n",
|
|
328
|
+
encoding="utf-8",
|
|
329
|
+
)
|
|
330
|
+
matches = check_file(p, index)
|
|
331
|
+
ids = {m.guide_id for m in matches}
|
|
332
|
+
assert "use-builtin-generics" in ids
|
|
333
|
+
|
|
334
|
+
|
|
279
335
|
class TestFreqRank:
|
|
280
336
|
def test_all_frequencies_covered(self):
|
|
281
337
|
assert "high" in FREQ_RANK
|
|
@@ -33,6 +33,18 @@ class TestVersionCompatible:
|
|
|
33
33
|
def test_patch_level_target(self):
|
|
34
34
|
assert version_compatible(">=3.9", "3.11.1") is True
|
|
35
35
|
|
|
36
|
+
def test_unexpected_error_propagates(self, monkeypatch):
|
|
37
|
+
def _boom(self, *a, **kw):
|
|
38
|
+
raise RuntimeError("unexpected")
|
|
39
|
+
|
|
40
|
+
fake = type("Boom", (), {"__init__": _boom, "__contains__": _boom})
|
|
41
|
+
monkeypatch.setattr(
|
|
42
|
+
"modern_python_guidance.compat.SpecifierSet",
|
|
43
|
+
fake,
|
|
44
|
+
)
|
|
45
|
+
with pytest.raises(RuntimeError, match="unexpected"):
|
|
46
|
+
version_compatible(">=3.9", "3.12")
|
|
47
|
+
|
|
36
48
|
|
|
37
49
|
class TestTokenEstimate:
|
|
38
50
|
def test_empty_string(self):
|
|
@@ -346,6 +346,49 @@ Body.
|
|
|
346
346
|
parse_frontmatter(text)
|
|
347
347
|
|
|
348
348
|
|
|
349
|
+
def test_invalid_python_specifier_rejected():
|
|
350
|
+
text = """\
|
|
351
|
+
---
|
|
352
|
+
id: bad-spec
|
|
353
|
+
title: Bad Specifier
|
|
354
|
+
category: typing
|
|
355
|
+
layer: 1
|
|
356
|
+
tags:
|
|
357
|
+
- test
|
|
358
|
+
python: "not-a-valid-specifier"
|
|
359
|
+
frequency: high
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
Body.
|
|
363
|
+
"""
|
|
364
|
+
with pytest.raises(FrontmatterError, match="invalid python specifier"):
|
|
365
|
+
parse_frontmatter(text)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
@pytest.mark.parametrize(
|
|
369
|
+
"specifier",
|
|
370
|
+
[">=3.9", ">=3.11", ">=3.9,<3.13", ">=3.11,<3.14", ""],
|
|
371
|
+
ids=["ge39", "ge311", "range39_13", "range311_14", "empty"],
|
|
372
|
+
)
|
|
373
|
+
def test_valid_python_specifiers_accepted(specifier: str):
|
|
374
|
+
text = f"""\
|
|
375
|
+
---
|
|
376
|
+
id: valid-spec
|
|
377
|
+
title: Valid Specifier
|
|
378
|
+
category: typing
|
|
379
|
+
layer: 1
|
|
380
|
+
tags:
|
|
381
|
+
- test
|
|
382
|
+
python: "{specifier}"
|
|
383
|
+
frequency: high
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
Body.
|
|
387
|
+
"""
|
|
388
|
+
meta, _ = parse_frontmatter(text)
|
|
389
|
+
assert meta.python == specifier
|
|
390
|
+
|
|
391
|
+
|
|
349
392
|
def test_detect_patterns_non_list_rejected():
|
|
350
393
|
text = """\
|
|
351
394
|
---
|
|
@@ -210,6 +210,15 @@ class TestDetectPatterns:
|
|
|
210
210
|
)
|
|
211
211
|
|
|
212
212
|
|
|
213
|
+
class TestPythonSpecifiers:
|
|
214
|
+
def test_all_guides_have_valid_specifiers(self, guide_file: Path):
|
|
215
|
+
from packaging.specifiers import SpecifierSet
|
|
216
|
+
|
|
217
|
+
text = guide_file.read_text(encoding="utf-8")
|
|
218
|
+
meta, _ = parse_frontmatter(text)
|
|
219
|
+
SpecifierSet(meta.python)
|
|
220
|
+
|
|
221
|
+
|
|
213
222
|
class TestGuideInventory:
|
|
214
223
|
def test_no_duplicate_ids(self):
|
|
215
224
|
seen: dict[str, Path] = {}
|
|
@@ -386,6 +386,37 @@ class TestProtocol:
|
|
|
386
386
|
assert result["isError"] is True
|
|
387
387
|
|
|
388
388
|
|
|
389
|
+
class TestMalformedParams:
|
|
390
|
+
def test_non_dict_params_returns_error_and_server_continues(self):
|
|
391
|
+
responses = _run_mcp(
|
|
392
|
+
{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": "bad"},
|
|
393
|
+
{"jsonrpc": "2.0", "id": 2, "method": "initialize", "params": {}},
|
|
394
|
+
)
|
|
395
|
+
assert len(responses) == 2
|
|
396
|
+
assert responses[0]["error"]["code"] == -32602
|
|
397
|
+
assert responses[0]["id"] == 1
|
|
398
|
+
assert "expected object" in responses[0]["error"]["message"]
|
|
399
|
+
assert "protocolVersion" in responses[1]["result"]
|
|
400
|
+
|
|
401
|
+
def test_non_dict_arguments_returns_error_and_server_continues(self):
|
|
402
|
+
responses = _run_mcp(
|
|
403
|
+
*_init_handshake(),
|
|
404
|
+
{
|
|
405
|
+
"jsonrpc": "2.0",
|
|
406
|
+
"id": 1,
|
|
407
|
+
"method": "tools/call",
|
|
408
|
+
"params": {"name": "search_guides", "arguments": "not-a-dict"},
|
|
409
|
+
},
|
|
410
|
+
{"jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {}},
|
|
411
|
+
)
|
|
412
|
+
error = responses[1].get("error")
|
|
413
|
+
assert error is not None
|
|
414
|
+
assert error["code"] == -32602
|
|
415
|
+
assert "expected object" in error["message"]
|
|
416
|
+
assert responses[2]["id"] == 2
|
|
417
|
+
assert "tools" in responses[2]["result"]
|
|
418
|
+
|
|
419
|
+
|
|
389
420
|
class TestStdoutPollution:
|
|
390
421
|
def test_no_non_jsonrpc_output(self):
|
|
391
422
|
stdin_data = _build_session(
|
|
@@ -362,6 +362,49 @@ class TestHandleRequest:
|
|
|
362
362
|
assert resp["error"]["code"] == -32600
|
|
363
363
|
assert "expected JSON object" in resp["error"]["message"]
|
|
364
364
|
|
|
365
|
+
@pytest.mark.parametrize(
|
|
366
|
+
"params",
|
|
367
|
+
[None, "hello", "", [1, 2], 42, 0, 3.14, True, False],
|
|
368
|
+
ids=["none", "string", "empty-string", "list", "int", "zero", "float", "true", "false"],
|
|
369
|
+
)
|
|
370
|
+
def test_non_dict_params_returns_invalid_params(self, params):
|
|
371
|
+
msg = {"jsonrpc": "2.0", "id": 10, "method": "initialize", "params": params}
|
|
372
|
+
resp = mcp._handle_request(msg)
|
|
373
|
+
assert resp["jsonrpc"] == "2.0"
|
|
374
|
+
assert resp["id"] == 10
|
|
375
|
+
assert resp["error"]["code"] == -32602
|
|
376
|
+
assert "expected object" in resp["error"]["message"]
|
|
377
|
+
|
|
378
|
+
def test_non_dict_params_notification_returns_none(self):
|
|
379
|
+
msg = {"jsonrpc": "2.0", "method": "initialize", "params": "bad"}
|
|
380
|
+
assert mcp._handle_request(msg) is None
|
|
381
|
+
|
|
382
|
+
def test_non_dict_arguments_returns_invalid_params(self):
|
|
383
|
+
msg = {
|
|
384
|
+
"jsonrpc": "2.0",
|
|
385
|
+
"id": 11,
|
|
386
|
+
"method": "tools/call",
|
|
387
|
+
"params": {"name": "search_guides", "arguments": "bad"},
|
|
388
|
+
}
|
|
389
|
+
resp = mcp._handle_request(msg)
|
|
390
|
+
assert resp["id"] == 11
|
|
391
|
+
assert resp["error"]["code"] == -32602
|
|
392
|
+
assert "expected object" in resp["error"]["message"]
|
|
393
|
+
|
|
394
|
+
def test_non_dict_arguments_notification_returns_none(self):
|
|
395
|
+
msg = {
|
|
396
|
+
"jsonrpc": "2.0",
|
|
397
|
+
"method": "tools/call",
|
|
398
|
+
"params": {"name": "search_guides", "arguments": [1, 2]},
|
|
399
|
+
}
|
|
400
|
+
assert mcp._handle_request(msg) is None
|
|
401
|
+
|
|
402
|
+
def test_unknown_method_with_bad_params_returns_invalid_params(self):
|
|
403
|
+
"""Centralized params check runs before method dispatch."""
|
|
404
|
+
msg = {"jsonrpc": "2.0", "id": 12, "method": "nonexistent", "params": 42}
|
|
405
|
+
resp = mcp._handle_request(msg)
|
|
406
|
+
assert resp["error"]["code"] == -32602
|
|
407
|
+
|
|
365
408
|
|
|
366
409
|
# --- serve ---
|
|
367
410
|
|
|
@@ -421,3 +464,72 @@ class TestServe:
|
|
|
421
464
|
assert responses[0]["id"] is None
|
|
422
465
|
assert responses[1]["id"] == 1
|
|
423
466
|
assert "protocolVersion" in responses[1]["result"]
|
|
467
|
+
|
|
468
|
+
def test_non_dict_params_recovery(self):
|
|
469
|
+
lines = (
|
|
470
|
+
json.dumps({"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": "bad"})
|
|
471
|
+
+ "\n"
|
|
472
|
+
+ json.dumps({"jsonrpc": "2.0", "id": 2, "method": "initialize", "params": {}})
|
|
473
|
+
+ "\n"
|
|
474
|
+
)
|
|
475
|
+
sin = io.StringIO(lines)
|
|
476
|
+
sout = io.StringIO()
|
|
477
|
+
mcp.serve(stdin=sin, stdout=sout)
|
|
478
|
+
responses = [json.loads(line) for line in sout.getvalue().strip().split("\n")]
|
|
479
|
+
assert len(responses) == 2
|
|
480
|
+
assert responses[0]["error"]["code"] == -32602
|
|
481
|
+
assert responses[0]["id"] == 1
|
|
482
|
+
assert responses[1]["id"] == 2
|
|
483
|
+
assert "protocolVersion" in responses[1]["result"]
|
|
484
|
+
|
|
485
|
+
def test_catch_all_returns_internal_error(self, monkeypatch):
|
|
486
|
+
def boom(msg):
|
|
487
|
+
raise RuntimeError("unexpected")
|
|
488
|
+
|
|
489
|
+
monkeypatch.setattr(mcp, "_handle_request", boom)
|
|
490
|
+
req = {"jsonrpc": "2.0", "id": 99, "method": "initialize", "params": {}}
|
|
491
|
+
sin = io.StringIO(json.dumps(req) + "\n")
|
|
492
|
+
sout = io.StringIO()
|
|
493
|
+
mcp.serve(stdin=sin, stdout=sout)
|
|
494
|
+
resp = json.loads(sout.getvalue().strip())
|
|
495
|
+
assert resp["id"] == 99
|
|
496
|
+
assert resp["error"]["code"] == -32603
|
|
497
|
+
assert resp["error"]["message"] == "Internal error"
|
|
498
|
+
|
|
499
|
+
def test_catch_all_notification_no_response(self, monkeypatch):
|
|
500
|
+
def boom(msg):
|
|
501
|
+
raise RuntimeError("unexpected")
|
|
502
|
+
|
|
503
|
+
monkeypatch.setattr(mcp, "_handle_request", boom)
|
|
504
|
+
req = {"jsonrpc": "2.0", "method": "initialize", "params": {}}
|
|
505
|
+
sin = io.StringIO(json.dumps(req) + "\n")
|
|
506
|
+
sout = io.StringIO()
|
|
507
|
+
mcp.serve(stdin=sin, stdout=sout)
|
|
508
|
+
assert sout.getvalue() == ""
|
|
509
|
+
|
|
510
|
+
def test_catch_all_then_recovery(self, monkeypatch):
|
|
511
|
+
call_count = 0
|
|
512
|
+
|
|
513
|
+
original = mcp._handle_request
|
|
514
|
+
|
|
515
|
+
def boom_once(msg):
|
|
516
|
+
nonlocal call_count
|
|
517
|
+
call_count += 1
|
|
518
|
+
if call_count == 1:
|
|
519
|
+
raise RuntimeError("first call fails")
|
|
520
|
+
return original(msg)
|
|
521
|
+
|
|
522
|
+
monkeypatch.setattr(mcp, "_handle_request", boom_once)
|
|
523
|
+
lines = (
|
|
524
|
+
json.dumps({"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {}})
|
|
525
|
+
+ "\n"
|
|
526
|
+
+ json.dumps({"jsonrpc": "2.0", "id": 2, "method": "initialize", "params": {}})
|
|
527
|
+
+ "\n"
|
|
528
|
+
)
|
|
529
|
+
sin = io.StringIO(lines)
|
|
530
|
+
sout = io.StringIO()
|
|
531
|
+
mcp.serve(stdin=sin, stdout=sout)
|
|
532
|
+
responses = [json.loads(line) for line in sout.getvalue().strip().split("\n")]
|
|
533
|
+
assert len(responses) == 2
|
|
534
|
+
assert responses[0]["error"]["code"] == -32603
|
|
535
|
+
assert "protocolVersion" in responses[1]["result"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/prompts/v5-a-detailed.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/prompts/v5-b-detailed.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/bench/prompts/v5-c-detailed.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/skills/modern-python-guidance/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/src/modern_python_guidance/__main__.py
RENAMED
|
File without changes
|
{modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/src/modern_python_guidance/cli.py
RENAMED
|
File without changes
|
|
File without changes
|
{modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/src/modern_python_guidance/retrieve.py
RENAMED
|
File without changes
|
{modern_python_guidance-0.4.2 → modern_python_guidance-0.4.3}/src/modern_python_guidance/search.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|