nene2-python 1.8.55__tar.gz → 1.8.57__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.
- {nene2_python-1.8.55 → nene2_python-1.8.57}/PKG-INFO +1 -1
- nene2_python-1.8.57/docs/field-trials/2026-05-field-trial-185.md +246 -0
- nene2_python-1.8.57/docs/field-trials/2026-05-field-trial-186.md +299 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/INDEX.md +4 -2
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/todo/current.md +8 -5
- {nene2_python-1.8.55 → nene2_python-1.8.57}/pyproject.toml +1 -1
- {nene2_python-1.8.55 → nene2_python-1.8.57}/.env.example +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/.github/workflows/ci.yml +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/.github/workflows/docs.yml +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/.github/workflows/publish.yml +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/.gitignore +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/.vitepress/config.mts +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/.vitepress/theme/custom.css +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/.vitepress/theme/index.ts +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/AGENTS.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/CHANGELOG.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/CLAUDE.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/Dockerfile +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/LICENSE +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/README.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/alembic/README +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/alembic/env.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/alembic/script.py.mako +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/alembic.ini +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/compose.yaml +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/adr/0001-toolchain.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/adr/0002-clean-architecture.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/adr/0003-security-first.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/adr/0004-ai-first-design.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/adr/0005-logging.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/adr/0006-rate-limiting.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/adr/0009-mcp-design.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/adr/0010-async-use-case.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/de/index.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/de/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/explanation/architecture.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-1.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-10.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-100.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-101.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-102.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-103.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-104.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-105.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-106.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-107.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-108.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-109.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-11.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-110.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-111.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-112.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-113.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-114.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-115.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-116.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-117.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-118.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-119.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-12.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-120.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-121.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-122.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-123.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-124.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-125.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-126.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-127.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-128.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-129.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-13.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-130.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-131.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-132.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-133.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-134.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-135.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-136.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-137.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-138.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-139.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-14.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-140.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-141.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-142.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-143.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-144.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-145.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-146.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-147.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-148.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-149.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-15.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-150.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-151.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-152.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-153.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-154.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-155.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-156.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-157.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-158.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-159.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-16.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-160.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-161.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-162.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-163.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-164.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-165.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-166.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-167.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-168.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-169.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-17.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-170.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-171.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-172.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-173.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-174.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-175.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-176.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-177.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-178.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-179.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-18.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-180.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-181.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-182.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-183.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-184.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-19.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-2.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-20.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-21.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-22.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-23.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-24.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-25.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-26.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-27.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-28.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-29.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-3.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-30.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-31.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-32.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-33.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-34.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-35.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-36.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-37.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-38.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-39.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-4.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-40.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-41.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-42.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-43.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-44.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-45.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-46.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-47.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-48.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-49.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-5.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-50.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-51.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-52.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-53.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-54.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-55.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-56.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-57.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-58.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-59.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-6.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-60.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-61.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-62.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-63.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-64.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-65.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-66.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-67.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-68.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-69.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-7.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-70.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-71.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-72.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-73.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-74.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-75.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-76.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-77.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-78.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-79.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-8.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-80.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-81.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-82.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-83.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-84.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-85.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-86.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-87.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-88.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-89.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-9.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-90.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-91.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-92.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-93.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-94.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-95.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-96.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-97.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-98.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/field-trials/2026-05-field-trial-99.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/fr/index.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/fr/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/how-to/api-versioning.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/how-to/async-use-case.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/how-to/background-tasks.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/how-to/cors.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/how-to/custom-auth-middleware.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/how-to/dependency-injection.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/how-to/domain-events.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/how-to/file-upload.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/how-to/lifespan-and-app-state.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/how-to/middleware-stack.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/how-to/new-project.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/how-to/problem-details.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/how-to/response-patterns.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/how-to/run-tests.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/how-to/soft-delete.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/how-to/streaming.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/how-to/structured-logging.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/how-to/validation.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/how-to/webhook.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/index.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/ja/explanation/architecture.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/ja/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/ja/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/ja/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/ja/how-to/new-project.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/ja/how-to/run-tests.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/ja/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/ja/index.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/ja/reference/api.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/ja/reference/configuration.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/ja/reference/framework-modules.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/ja/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/ja/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/pt-br/index.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/pt-br/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/reference/api.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/reference/configuration.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/reference/framework-modules.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/roadmap.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/templates/field-trial-report.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/zh/index.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/docs/zh/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/package-lock.json +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/package.json +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/example/__init__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/example/__main__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/example/app.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/example/comment/__init__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/example/comment/entity.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/example/comment/exceptions.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/example/comment/handler.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/example/comment/repository.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/example/comment/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/example/comment/use_case.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/example/mcp.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/example/note/__init__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/example/note/async_use_case.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/example/note/entity.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/example/note/exceptions.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/example/note/handler.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/example/note/repository.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/example/note/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/example/note/use_case.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/example/schema.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/example/tag/__init__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/example/tag/entity.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/example/tag/exceptions.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/example/tag/handler.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/example/tag/repository.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/example/tag/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/example/tag/use_case.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/__init__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/auth/api_key.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/auth/bearer_token.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/auth/deps.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/auth/exceptions.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/auth/interfaces.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/auth/local_verifier.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/cache/__init__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/cache/ttl.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/config/settings.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/database/exceptions.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/database/health.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/database/interfaces.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/database/sqlalchemy_executor.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/database/utils.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/http/etag.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/http/health.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/http/pagination.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/http/problem_details.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/log/setup.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/mcp/http_client.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/mcp/server.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/middleware/__init__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/middleware/domain_exception.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/middleware/error_handler.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/middleware/request_id.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/middleware/request_logging.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/middleware/request_size_limit.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/middleware/security_headers.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/middleware/setup.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/middleware/throttle.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/py.typed +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/security/__init__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/security/webhook.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/use_case/protocols.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/nene2/validation/exceptions.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/scripts/__init__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/src/scripts/export_openapi.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/__init__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/conftest.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/example/__init__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/example/comment/__init__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/example/comment/test_comment_http.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/example/comment/test_comment_repository.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/example/comment/test_comment_use_case.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/example/conftest.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/example/note/__init__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/example/note/test_async_note_use_case.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/example/note/test_list_notes.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/example/note/test_note_repository.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/example/tag/__init__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/example/tag/test_tag_repository.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/example/tag/test_tags.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/example/test_cors.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/example/test_mcp.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/__init__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/auth/test_api_key.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/auth/test_bearer_token.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/auth/test_make_require_auth.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/auth/test_token_issuer.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/cache/__init__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/cache/test_ttl.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/config/test_settings.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/database/test_transaction.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/database/test_utils.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/http/test_etag.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/http/test_health.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/http/test_pagination.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/http/test_problem_details.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/log/test_setup.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/mcp/test_http_client.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/mcp/test_server.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/middleware/__init__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/middleware/test_error_handler.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/middleware/test_request_id.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/middleware/test_request_logging.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/middleware/test_request_size_limit.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/middleware/test_security_headers.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/middleware/test_setup_middlewares.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/middleware/test_throttle.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/security/__init__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/security/test_webhook.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/use_case/test_protocols.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/use_case/test_run_in_threadpool.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/nene2/validation/test_exceptions.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/scripts/__init__.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/tests/scripts/test_export_openapi.py +0 -0
- {nene2_python-1.8.55 → nene2_python-1.8.57}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nene2-python
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.57
|
|
4
4
|
Summary: NENE2 Python — minimal API framework following NENE2's design philosophy
|
|
5
5
|
Project-URL: Homepage, https://github.com/hideyukiMORI/nene2-python
|
|
6
6
|
Project-URL: Repository, https://github.com/hideyukiMORI/nene2-python
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# FT185: contextlib
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-21
|
|
4
|
+
**テーマ**: contextlib モジュール — コンテキストマネージャー・リソース管理・エラー抑制
|
|
5
|
+
**セキュリティ診断**: なし(185 % 3 = 2)
|
|
6
|
+
**クラッカーペンテスト**: なし(185 % 4 = 1)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 概要
|
|
11
|
+
|
|
12
|
+
Python 標準ライブラリ `contextlib` は、コンテキストマネージャーの作成・合成・利用を支援するユーティリティ集である。
|
|
13
|
+
`@contextmanager` デコレーター、`suppress`、`ExitStack`、`closing`、`nullcontext`、`ContextDecorator`、`chdir`(3.11+)など多岐にわたるツールを検証した。
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 実装したサンプルアプリ
|
|
18
|
+
|
|
19
|
+
**場所**: `/home/xi/docker/nene2-python-FT/ft185-contextlib/`
|
|
20
|
+
|
|
21
|
+
### 主要機能
|
|
22
|
+
|
|
23
|
+
| 関数/クラス | 概要 |
|
|
24
|
+
|---|---|
|
|
25
|
+
| `timer(label)` | `@contextmanager` で経過時間を計測 |
|
|
26
|
+
| `capture_stdout()` | `redirect_stdout` で標準出力をキャプチャ |
|
|
27
|
+
| `temp_attr(obj, attr, value)` | 属性を一時的に変更して復元する |
|
|
28
|
+
| `safe_int(value)` | `suppress(ValueError)` で変換失敗を吸収 |
|
|
29
|
+
| `safe_delete(dict, key)` | `suppress(KeyError)` で削除失敗を吸収 |
|
|
30
|
+
| `query_with_closing(host, sql)` | `closing` で接続を確実にクローズ |
|
|
31
|
+
| `fake_transaction(fail_on)` | ロールバック/コミットを持つトランザクション |
|
|
32
|
+
| `ManagedBuffer` | `ExitStack` で複数バッファのライフサイクルを管理 |
|
|
33
|
+
| `process_data(data, lock)` | `nullcontext` でオプションのロックを抽象化 |
|
|
34
|
+
| `run_and_capture(func)` | `redirect_stdout` / `redirect_stderr` で出力をキャプチャ |
|
|
35
|
+
| `run_pipeline(steps)` | `ExitStack` + コールバックでパイプライン管理 |
|
|
36
|
+
| `RetryContext` | `AbstractContextManager` の具象実装 |
|
|
37
|
+
| `LoggingContext` | `ContextDecorator` でデコレーターとしても使用可 |
|
|
38
|
+
| `batched_writer(items, batch_size)` | アイテムをバッチ分割するコンテキストマネージャー |
|
|
39
|
+
| `read_file_safe(path)` | `suppress(OSError)` でファイル読み込み失敗を吸収 |
|
|
40
|
+
| `get_current_dir_in_context(path)` | `contextlib.chdir` で一時ディレクトリ移動 |
|
|
41
|
+
|
|
42
|
+
### HTTP エンドポイント
|
|
43
|
+
|
|
44
|
+
| メソッド | パス | 概要 |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| POST | `/suppress/int` | 文字列→int 変換(失敗時 null)|
|
|
47
|
+
| POST | `/suppress/delete` | 辞書キーを安全に削除 |
|
|
48
|
+
| POST | `/transaction` | フェイクトランザクション(コミット/ロールバック)|
|
|
49
|
+
| POST | `/pipeline` | ExitStack パイプライン実行 |
|
|
50
|
+
| POST | `/buffer` | ManagedBuffer で複数バッファ管理 |
|
|
51
|
+
| POST | `/timer` | 経過時間計測 |
|
|
52
|
+
| POST | `/batch` | バッチ分割 |
|
|
53
|
+
| POST | `/query` | closing 付きクエリ実行 |
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## テスト結果
|
|
58
|
+
|
|
59
|
+
**50 passed**
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
50 passed in 0.31s
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
mypy --strict: Success
|
|
66
|
+
ruff check: All checks passed
|
|
67
|
+
pip-audit: PYSEC-2025-183 (PyJWT via mcp transitive dep — 許容済み)
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## 摩擦ポイント
|
|
72
|
+
|
|
73
|
+
### F-1: `__exit__` の戻り値型に `bool` は不可(mypy --strict)(深刻度: 低)
|
|
74
|
+
|
|
75
|
+
**事象**: `AbstractContextManager` サブクラスの `__exit__` メソッドで戻り値型を `bool` と宣言したところ、mypy --strict が以下のエラーを出力した。
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
"bool" is invalid as return type for "__exit__" that always returns False
|
|
79
|
+
Use "typing.Literal[False]" as the return type or change it to "None"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**原因**: mypy は `__exit__` が `True` を返すと「例外を抑制する」と解釈する。`bool` 型はその可能性を示すため、常に `False` を返す実装では `Literal[False]` か `None` を使うよう要求される。
|
|
83
|
+
|
|
84
|
+
**対応**: 戻り値型を `Literal[False]` に変更(`from typing import Literal` が必要)。
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from typing import Literal
|
|
88
|
+
|
|
89
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> Literal[False]:
|
|
90
|
+
return False
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 観察点
|
|
96
|
+
|
|
97
|
+
### 観察1: `@contextmanager` の yield 前後でのクリーンアップ
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
@contextlib.contextmanager
|
|
101
|
+
def timer(label: str) -> Generator[dict[str, float], None, None]:
|
|
102
|
+
result: dict[str, float] = {}
|
|
103
|
+
start = time.perf_counter()
|
|
104
|
+
try:
|
|
105
|
+
yield result
|
|
106
|
+
finally:
|
|
107
|
+
result["elapsed"] = time.perf_counter() - start
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`try / finally` パターンによって、`with` ブロック内で例外が発生しても `finally` が確実に実行される。yield した辞書を通じて結果を呼び出し元に渡せる点が特徴的。
|
|
111
|
+
|
|
112
|
+
### 観察2: `ExitStack.callback` による動的クリーンアップ
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
with contextlib.ExitStack() as stack:
|
|
116
|
+
for resource in resources:
|
|
117
|
+
stack.callback(release_resource, resource)
|
|
118
|
+
acquired.append(resource)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
コンテキストマネージャーを持たないリソースでも `callback` で後処理を登録できる。登録は LIFO 順で実行されるため、依存関係のあるリソースも安全に解放できる。
|
|
122
|
+
|
|
123
|
+
### 観察3: `ContextDecorator` で関数デコレーターを兼ねる
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
class LoggingContext(contextlib.ContextDecorator):
|
|
127
|
+
def __enter__(self): ...
|
|
128
|
+
def __exit__(self, ...): ...
|
|
129
|
+
|
|
130
|
+
ctx = LoggingContext("fn")
|
|
131
|
+
|
|
132
|
+
# コンテキストマネージャーとして
|
|
133
|
+
with ctx:
|
|
134
|
+
do_something()
|
|
135
|
+
|
|
136
|
+
# デコレーターとして
|
|
137
|
+
@ctx
|
|
138
|
+
def my_func():
|
|
139
|
+
do_something()
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`ContextDecorator` を継承するだけで `with` と `@decorator` の両方の文脈で使用できるようになる。テスト・ロギング・タイミングの実装で有用。
|
|
143
|
+
|
|
144
|
+
### 観察4: `nullcontext` でオプションのロックを統一的に扱う
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
def process_data(data, lock=None):
|
|
148
|
+
ctx = lock if lock is not None else contextlib.nullcontext()
|
|
149
|
+
with ctx:
|
|
150
|
+
return sum(data)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
呼び出し元からロックを渡せる場合と不要な場合を、`with` 文を2回書かずに統一できる。シングルスレッドのテストでは `lock=None`、マルチスレッドでは実際のロックを渡すという設計が自然に表現できる。
|
|
154
|
+
|
|
155
|
+
### 観察5: `contextlib.chdir` (Python 3.11+)
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
original = os.getcwd()
|
|
159
|
+
with contextlib.chdir("/tmp"):
|
|
160
|
+
# /tmp が cwd
|
|
161
|
+
pass
|
|
162
|
+
# original に戻っている
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`os.chdir()` を手動で `try/finally` でラップする必要がなくなる。Python 3.11 以降でのみ使用可能なため、3.10 以下をサポートする場合は自前実装が必要。
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Follow-up Issues
|
|
170
|
+
|
|
171
|
+
今回の FT では実装上の重大な摩擦はなかった。F-1 は mypy の型精度要求によるものであり、Python 型システムの理解向上につながる知見として記録する。
|
|
172
|
+
|
|
173
|
+
GitHub Issues: なし
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## DX Review — 6ペルソナ
|
|
178
|
+
|
|
179
|
+
### 1. 初心者(Python 歴1年・独学中・女性・バックエンド志望)
|
|
180
|
+
|
|
181
|
+
FastAPI でエラーを無視したいとき `try/except pass` を書いていたが、`contextlib.suppress` を知ることで「名前のついた意図表現」に切り替えられる。
|
|
182
|
+
|
|
183
|
+
**ドキュメント理解**: `@contextmanager` の `yield` の意味(「ここで with ブロックに入る」)が直感的でない。`yield result` が値を返すだけでなくコンテキストを区切るという二重の役割は、初見では混乱しやすい。
|
|
184
|
+
|
|
185
|
+
**事故リスク**: 低 — `suppress` の乱用で重要な例外を握りつぶすリスクはあるが、デモコードでは抑制対象の例外を明示しているため、コピペしても事故にはなりにくい。
|
|
186
|
+
|
|
187
|
+
**規約の使いやすさ**: 豊富なサンプルによって「suppress で受ける」「timer で囲む」「ExitStack で登録する」という用法がパターンとして身につく。
|
|
188
|
+
|
|
189
|
+
### 2. ロースキル経験者(Python 歴3-4年・スクリプト系・男性・SES)
|
|
190
|
+
|
|
191
|
+
`ExitStack` の使い所が「複数のファイルを条件によって開く」といったシナリオで真価を発揮することが理解できれば、実務でのファイル・DB 接続管理が劇的にシンプルになる。
|
|
192
|
+
|
|
193
|
+
**コピペ可能性**: `timer`・`capture_stdout`・`temp_attr` はそのままコピーして使えるユーティリティとして価値がある。
|
|
194
|
+
|
|
195
|
+
**拡張時の罠**: `ManagedBuffer.close_all()` を呼び忘れた場合、バッファはGCまで残る。`__enter__/__exit__` を実装して `with` で使えるようにするほうが安全だが、今回のデモでは意図的に省略した。
|
|
196
|
+
|
|
197
|
+
**事故リスク**: 低
|
|
198
|
+
|
|
199
|
+
### 3. フロントエンド寄り(React/TS 歴4年・バックエンド転向中・ノンバイナリ)
|
|
200
|
+
|
|
201
|
+
TypeScript の `using` 宣言(TC39 Explicit Resource Management)と概念が近いことを理解すれば、Python の `with` 文の位置付けが掴みやすい。
|
|
202
|
+
|
|
203
|
+
**エラーレスポンスの質**: `/transaction` エンドポイントがロールバックした場合でも HTTP 200 を返し、`rolled_back: true` + `error` フィールドでエラーを示す設計はフロントエンドから見て扱いやすい。
|
|
204
|
+
|
|
205
|
+
**Python 固有概念の学習コスト**: `Generator[dict[str, float], None, None]` という型注釈は TS 経験者には冗長に見える。`@contextmanager` が `Generator` を返す理由を理解するには Python のジェネレーター仕組みへの理解が必要。
|
|
206
|
+
|
|
207
|
+
**事故リスク**: 低
|
|
208
|
+
|
|
209
|
+
### 4. バックエンド経験者(Django/FastAPI 歴5-6年・男性・リードエンジニア)
|
|
210
|
+
|
|
211
|
+
Django の `transaction.atomic()` や Flask の `g` オブジェクトと比べると、`contextlib` のアプローチはより明示的で汎用的。特に `ExitStack` は Django ORM の接続管理でも応用できる。
|
|
212
|
+
|
|
213
|
+
**他フレームワークとの差異**: `fake_transaction` パターンは Django の `TestCase.databases` によるロールバックと異なり、テスト外のユースケースにも適用できる。
|
|
214
|
+
|
|
215
|
+
**nene2 の薄さへの評価**: `contextlib` 自体は標準ライブラリであり nene2 フレームワークとの結合はない。エンドポイントの薄さ(parse → use-case → response の3ステップ)が維持されており好印象。
|
|
216
|
+
|
|
217
|
+
**事故リスク**: 低
|
|
218
|
+
|
|
219
|
+
### 5. シニアエンジニア(設計・コードレビュー担当・女性・10-12年)
|
|
220
|
+
|
|
221
|
+
コードレビュー観点で最も重要なのは「suppress の対象を絞ること」。`suppress(Exception)` のように広すぎる例外クラスを指定するコードは必ずレビューで差し戻す。
|
|
222
|
+
|
|
223
|
+
**コードレビューチェックポイント**:
|
|
224
|
+
- `suppress` の引数は具体的な例外クラスか(`Exception` や `BaseException` でないか)
|
|
225
|
+
- `@contextmanager` の `try/finally` が欠落していないか(欠落するとクリーンアップが実行されない)
|
|
226
|
+
- `ExitStack` は使い終わったら必ず `close()` または `with` で囲まれているか
|
|
227
|
+
- `__exit__` の戻り値型が `Literal[False]` か `None` になっているか(mypy で強制されるが目視でも確認)
|
|
228
|
+
|
|
229
|
+
**チームでの安全なパターン**: `LoggingContext(ContextDecorator)` のパターンは横断的関心事(ログ・計測・トレーシング)に応用しやすく、チームで共有できるユーティリティになる。
|
|
230
|
+
|
|
231
|
+
**事故リスク**: 低
|
|
232
|
+
|
|
233
|
+
### 6. 設計者(nene2-python 設計ポリシー目線)
|
|
234
|
+
|
|
235
|
+
**CLAUDE.md ポリシー整合性**:
|
|
236
|
+
- `dataclass(frozen=True, slots=True)`: `ResourceHandle`・`TransactionResult`・`PipelineResult` で適用済み ✅
|
|
237
|
+
- Pydantic は HTTP 境界のみ: `app.py` の Request/Response モデルのみで使用 ✅
|
|
238
|
+
- `create_app()` はファイル末尾: 全エンドポイント定義後に配置 ✅(FT182 の教訓適用)
|
|
239
|
+
- `max_length` 指定: 全文字列フィールドに設定済み ✅
|
|
240
|
+
- `Literal[False]` 戻り値型: F-1 で修正済み ✅
|
|
241
|
+
|
|
242
|
+
**初心者でも安全な API 達成度**: `suppress` を使う際に抑制対象の例外を明示するパターンを見せることで、「なんでも suppress しない」という習慣を自然に身につけられる構成になっている。`ExitStack` の `callback` で lambda を避けて名前付き関数 `_noop` を使ったことで、mypy の型推論問題も回避できた。
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
*バージョン: v1.8.56*
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
# FT186: functools
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-21
|
|
4
|
+
**テーマ**: functools モジュール — キャッシュ・部分適用・デコレーター・比較・ディスパッチ
|
|
5
|
+
**セキュリティ診断**: あり(186 % 3 = 0)
|
|
6
|
+
**クラッカーペンテスト**: なし(186 % 4 = 2)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 概要
|
|
11
|
+
|
|
12
|
+
Python 標準ライブラリ `functools` は高階関数・関数オブジェクトのユーティリティ集である。
|
|
13
|
+
`lru_cache`・`cache`(メモ化)、`partial`(部分適用)、`reduce`(畳み込み)、`wraps`(デコレーターメタデータ保持)、`total_ordering`(比較演算子補完)、`singledispatch`(型ディスパッチ)、`cached_property`(インスタンスレベルキャッシュ)を検証した。
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 実装したサンプルアプリ
|
|
18
|
+
|
|
19
|
+
**場所**: `/home/xi/docker/nene2-python-FT/ft186-functools/`
|
|
20
|
+
|
|
21
|
+
### 主要機能
|
|
22
|
+
|
|
23
|
+
| 関数/クラス | 概要 |
|
|
24
|
+
|---|---|
|
|
25
|
+
| `fibonacci(n)` | `lru_cache` でメモ化したフィボナッチ数列 |
|
|
26
|
+
| `fibonacci_safe(n)` | 上限チェック付きフィボナッチ(DoS 対策)|
|
|
27
|
+
| `get_fibonacci_cache_stats()` | キャッシュ統計取得 |
|
|
28
|
+
| `factorial(n)` | `functools.cache` で無制限メモ化した階乗 |
|
|
29
|
+
| `power(base, exponent)` / `square` / `cube` | `partial` で指数固定関数 |
|
|
30
|
+
| `make_multiplier(factor)` | `partial` で乗数固定の乗算関数を生成 |
|
|
31
|
+
| `product(numbers)` | `reduce` でリストの積 |
|
|
32
|
+
| `flatten_once(nested)` | `reduce` で1段階展開 |
|
|
33
|
+
| `timing_decorator(func)` | `@wraps` でメタデータ保持するタイミングデコレーター |
|
|
34
|
+
| `retry(max_attempts)` | `@wraps` を使ったリトライデコレーターファクトリ |
|
|
35
|
+
| `Version` | `@total_ordering` + `dataclass` でセマンティックバージョン比較 |
|
|
36
|
+
| `latest_version(versions)` | `reduce` + `Version` で最新バージョン検出 |
|
|
37
|
+
| `serialize(value)` | `singledispatch` で型別シリアライズ |
|
|
38
|
+
| `DataProcessor` | `cached_property` でコストの高い計算をキャッシュ |
|
|
39
|
+
|
|
40
|
+
### HTTP エンドポイント
|
|
41
|
+
|
|
42
|
+
| メソッド | パス | 概要 |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| POST | `/fibonacci` | lru_cache フィボナッチ |
|
|
45
|
+
| POST | `/factorial` | cache 階乗 |
|
|
46
|
+
| POST | `/power` | partial square/cube |
|
|
47
|
+
| POST | `/multiply` | partial 乗算 |
|
|
48
|
+
| POST | `/product` | reduce 積 |
|
|
49
|
+
| POST | `/flatten` | reduce 展開 |
|
|
50
|
+
| POST | `/version/latest` | total_ordering + reduce 最新バージョン |
|
|
51
|
+
| POST | `/serialize` | singledispatch シリアライズ |
|
|
52
|
+
| POST | `/stats` | cached_property 統計 |
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## テスト結果
|
|
57
|
+
|
|
58
|
+
**64 passed**
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
64 passed in 0.36s
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
mypy --strict: Success
|
|
65
|
+
ruff check: All checks passed
|
|
66
|
+
pip-audit: PYSEC-2025-183 (PyJWT via mcp transitive dep — 許容済み)
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## 摩擦ポイント
|
|
71
|
+
|
|
72
|
+
### F-1: `base**exponent` の戻り値型が `Any`(mypy --strict)(深刻度: 低)
|
|
73
|
+
|
|
74
|
+
**事象**: `return base**exponent` をそのまま返すと `Returning Any from function declared to return "float"` エラー。Python の `**` 演算子は型に応じて `int | float | complex` を返すため、mypy が型推論できない。
|
|
75
|
+
|
|
76
|
+
**原因**: `float ** float` の演算子オーバーロードが `float` ではなく `Any` として型付けされている(typeshed の制約)。
|
|
77
|
+
|
|
78
|
+
**対応**: `return float(base**exponent)` と明示的にキャストすることで解決。
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## 観察点
|
|
83
|
+
|
|
84
|
+
### 観察1: `lru_cache` の `maxsize` と DoS 対策
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
@functools.lru_cache(maxsize=256)
|
|
88
|
+
def fibonacci(n: int) -> int:
|
|
89
|
+
...
|
|
90
|
+
|
|
91
|
+
def fibonacci_safe(n: int) -> int:
|
|
92
|
+
if n > 90:
|
|
93
|
+
raise ValueError(...)
|
|
94
|
+
return fibonacci(n)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`lru_cache` は入力値ごとにキャッシュエントリを作成する。`maxsize=None`(= `functools.cache`)の場合、引数のバリエーションが多い用途では無制限にメモリを使い続ける可能性がある。入力値を `fibonacci_safe` でサニタイズしてから `lru_cache` 付き関数を呼ぶことで、キャッシュの肥大化を防ぐ。
|
|
98
|
+
|
|
99
|
+
### 観察2: `functools.cache` vs `lru_cache(maxsize=None)`
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
@functools.cache # Python 3.9+
|
|
103
|
+
def factorial(n: int) -> int: ...
|
|
104
|
+
|
|
105
|
+
@functools.lru_cache(maxsize=None) # Python 3.2+
|
|
106
|
+
def factorial(n: int) -> int: ...
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`functools.cache` は `lru_cache(maxsize=None)` の簡易エイリアスで、`cache_info()` / `cache_clear()` メソッドを持つ点は同じ。`lru_cache` より書きやすく、上限なしキャッシュが明示的。
|
|
110
|
+
|
|
111
|
+
### 観察3: `@wraps` なしでメタデータが失われる問題
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
# wraps なし — name が "wrapper" になる
|
|
115
|
+
def bad_decorator(func):
|
|
116
|
+
def wrapper(*args, **kwargs):
|
|
117
|
+
return func(*args, **kwargs)
|
|
118
|
+
return wrapper
|
|
119
|
+
|
|
120
|
+
# wraps あり — name が元の関数名を保持
|
|
121
|
+
def good_decorator(func):
|
|
122
|
+
@functools.wraps(func)
|
|
123
|
+
def wrapper(*args, **kwargs):
|
|
124
|
+
return func(*args, **kwargs)
|
|
125
|
+
return wrapper
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`@wraps` を省くと `__name__`・`__doc__`・`__annotations__` が全てラッパーのものに置き換わる。pytest の `--tb=short` でも関数名が `wrapper` と表示され、デバッグが困難になる。
|
|
129
|
+
|
|
130
|
+
### 観察4: `total_ordering` と `frozen=True` の組み合わせ
|
|
131
|
+
|
|
132
|
+
`@functools.total_ordering` と `@dataclass(frozen=True)` を組み合わせる場合、`@dataclass(eq=False)` を指定しないと `dataclass` が自動生成する `__eq__` が `total_ordering` の `__eq__` を上書きしてしまう可能性がある。今回は `__eq__` と `__lt__` を明示的に実装したため問題なし。
|
|
133
|
+
|
|
134
|
+
### 観察5: `singledispatch` と `bool` 型のディスパッチ順序
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
@serialize.register(bool)
|
|
138
|
+
def _serialize_bool(value: bool) -> str: ...
|
|
139
|
+
|
|
140
|
+
@serialize.register(int)
|
|
141
|
+
def _serialize_int(value: int) -> str: ...
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Python では `bool` は `int` のサブクラス。`@register(int)` を先に登録すると `True`/`False` も `int` ハンドラーで処理される。`bool` を意図的に分岐させたい場合は `@register(bool)` を先(または明示的に)登録する必要がある。
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## セキュリティ診断(FT186 % 3 = 0)
|
|
149
|
+
|
|
150
|
+
### 1. OWASP API Security Top 10 (2023)
|
|
151
|
+
|
|
152
|
+
#### API6: Unrestricted Access to Sensitive Business Flows — DoS via lru_cache
|
|
153
|
+
|
|
154
|
+
**状況**: `fibonacci(n)` に上限なく大きな `n` を渡せた場合、計算時間 + キャッシュメモリの両面で DoS が成立する。
|
|
155
|
+
|
|
156
|
+
**対策**: `fibonacci_safe(n)` が Pydantic の `le=90` と合わせて二重に入力を制限している。FastAPI の `Field(ge=0, le=90)` による検証は HTTP 境界で確実に発動する。**問題なし**。
|
|
157
|
+
|
|
158
|
+
#### API4: Unrestricted Resource Consumption
|
|
159
|
+
|
|
160
|
+
**状況**: `/stats` エンドポイントは `list[int]` を最大 1000 要素受け取る。`cached_property` はインスタンスごとにキャッシュするため、毎リクエストで新たな `DataProcessor` インスタンスが生成され、キャッシュは1リクエスト内のみで有効。メモリは GC に依存するが、1000 要素程度ならリスクは低い。**問題なし**。
|
|
161
|
+
|
|
162
|
+
**状況**: `/flatten` は `list[list[int]]` を最大 50 要素受け取り `reduce` で結合するが、内側リストの要素数に上限がない。理論上、巨大な内側リストを送れる。
|
|
163
|
+
|
|
164
|
+
**判定**: **MEDIUM** — 内側リストに個別の `max_length` が設定されていない(F-2 として記録)。
|
|
165
|
+
|
|
166
|
+
### 2. インジェクション攻撃
|
|
167
|
+
|
|
168
|
+
`functools` API は外部入力を直接評価する機能を持たないため、インジェクションリスクはない。`singledispatch` の型ルーティングも実行コードを動的生成しない。**問題なし**。
|
|
169
|
+
|
|
170
|
+
### 3. 認証・認可
|
|
171
|
+
|
|
172
|
+
今回のエンドポイントは認証不要の計算 API のため対象外。**問題なし**。
|
|
173
|
+
|
|
174
|
+
### 4. 入力バリデーション
|
|
175
|
+
|
|
176
|
+
| フィールド | 制約 | 評価 |
|
|
177
|
+
|---|---|---|
|
|
178
|
+
| `FibRequest.n` | `ge=0, le=90` | ✅ |
|
|
179
|
+
| `FactorialRequest.n` | `ge=0, le=20` | ✅ |
|
|
180
|
+
| `PowerRequest.operation` | `max_length=10` | ✅ |
|
|
181
|
+
| `ProductRequest.numbers` | `max_length=100` | ✅ |
|
|
182
|
+
| `FlattenRequest.nested` | `max_length=50` (外側のみ) | ⚠️ 内側なし |
|
|
183
|
+
| `VersionRequest.versions` | `max_length=50` | ✅ |
|
|
184
|
+
| `StatsRequest.data` | `max_length=1000` | ✅ |
|
|
185
|
+
|
|
186
|
+
### 5. 情報漏洩
|
|
187
|
+
|
|
188
|
+
`retry` デコレーターがリトライ失敗時に元の例外を `raise` するため、内部エラーメッセージがそのまま上位に伝播する。FastAPI の `ErrorHandlerMiddleware` がない場合、500 レスポンスに内部エラーが含まれる可能性がある。FT のサンドボックスでは許容範囲内。**問題なし**(本番実装では `ErrorHandlerMiddleware` を追加すること)。
|
|
189
|
+
|
|
190
|
+
pip-audit: PYSEC-2025-183 (PyJWT via mcp — 許容済み、修正版待ち)
|
|
191
|
+
|
|
192
|
+
### 6. Python/FastAPI 固有
|
|
193
|
+
|
|
194
|
+
#### ReDoS
|
|
195
|
+
|
|
196
|
+
`Version.parse()` は `.split(".")` と `int()` のみを使用。正規表現を使用しないため ReDoS リスクなし。**問題なし**。
|
|
197
|
+
|
|
198
|
+
#### functools.cache の無制限メモリ使用
|
|
199
|
+
|
|
200
|
+
`factorial` は `functools.cache`(無制限)を使用しているが、`FactorialRequest.n` が `le=20` で制限されているため、キャッシュエントリは最大 21 個。**問題なし**。
|
|
201
|
+
|
|
202
|
+
#### singledispatch の型安全性
|
|
203
|
+
|
|
204
|
+
`singledispatch` は実行時型チェックを行うため、Pydantic で検証済みの型が渡される限り不正ディスパッチは発生しない。**問題なし**。
|
|
205
|
+
|
|
206
|
+
### セキュリティ診断まとめ
|
|
207
|
+
|
|
208
|
+
| カテゴリ | 結果 | 備考 |
|
|
209
|
+
|---|---|---|
|
|
210
|
+
| OWASP API Top 10 | 条件付き合格 | F-2 /flatten 内側リスト上限なし |
|
|
211
|
+
| インジェクション | 合格 | |
|
|
212
|
+
| 認証・認可 | 対象外 | |
|
|
213
|
+
| 入力バリデーション | 条件付き合格 | F-2 参照 |
|
|
214
|
+
| 情報漏洩 | 合格 | ErrorHandlerMiddleware 推奨 |
|
|
215
|
+
| Python/FastAPI 固有 | 合格 | |
|
|
216
|
+
|
|
217
|
+
**診断結果: 条件付き合格**(MEDIUM 1件 — 次 FT までに修正推奨)
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Follow-up Issues
|
|
222
|
+
|
|
223
|
+
### F-2: `/flatten` 内側リストの要素数に上限なし(深刻度: MEDIUM)
|
|
224
|
+
|
|
225
|
+
**事象**: `FlattenRequest.nested: list[list[int]]` の外側は `max_length=50` で制限されているが、内側リストの要素数に上限がない。悪意のある入力で内側に大量要素を持つリストを送ることができる。
|
|
226
|
+
|
|
227
|
+
**対応**: Pydantic の `Annotated[list[int], Field(max_length=1000)]` を使って内側リストにも上限を設定する。
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## DX Review — 6ペルソナ
|
|
232
|
+
|
|
233
|
+
### 1. 初心者(Python 歴1年・独学中・女性・バックエンド志望)
|
|
234
|
+
|
|
235
|
+
「関数をキャッシュする」という概念が `@lru_cache` によって視覚的に分かりやすく表現されている。デコレーター1行で劇的に高速化できる体験は印象的。
|
|
236
|
+
|
|
237
|
+
**ドキュメント理解**: `partial` の「引数を固定した新しい関数を作る」という概念は直感的。しかし `reduce` の「畳み込み」は初見では理解しにくく、`sum()` や `max()` で代替できる場合が多い旨を添えた方が親切。
|
|
238
|
+
|
|
239
|
+
**事故リスク**: 低 — ただし `lru_cache(maxsize=None)` は上限なしキャッシュであることを知らないと、長時間稼働するプロセスでメモリが肥大化するリスクがある(サンドボックスでは `fibonacci_safe` で回避済み)。
|
|
240
|
+
|
|
241
|
+
**規約の使いやすさ**: `@wraps` の必要性(デバッグ時の関数名保持)を理解するまでは省略しがち。テストで `__name__` を確認するパターンを見ることで習得できる。
|
|
242
|
+
|
|
243
|
+
### 2. ロースキル経験者(Python 歴3-4年・スクリプト系・男性・SES)
|
|
244
|
+
|
|
245
|
+
`lru_cache` のコピペ利用は頻繁。`maxsize` を適切に設定することを忘れがちで、`None` のまま運用してメモリリークを引き起こすケースがある。
|
|
246
|
+
|
|
247
|
+
**コピペ可能性**: `timing_decorator` と `retry` はそのまま転用できるユーティリティとして高い実用性。
|
|
248
|
+
|
|
249
|
+
**拡張時の罠**: `total_ordering` + `dataclass(frozen=True)` の組み合わせで `__eq__` を省略するとデフォルトの `dataclass` が生成した `__eq__` が使われ、`total_ordering` の期待と異なる場合がある(観察4参照)。
|
|
250
|
+
|
|
251
|
+
**事故リスク**: 中(lru_cache の maxsize 設定忘れ)
|
|
252
|
+
|
|
253
|
+
### 3. フロントエンド寄り(React/TS 歴4年・バックエンド転向中・ノンバイナリ)
|
|
254
|
+
|
|
255
|
+
TypeScript の `useMemo` / `useCallback` に近い概念として `lru_cache` / `partial` を理解できる。`singledispatch` は TypeScript のオーバーロードと比べてコードが分散するが、`@register` で後から拡張できる点は優れている。
|
|
256
|
+
|
|
257
|
+
**エラーレスポンスの質**: `/version/latest` の `400 Bad Request` + `detail` に無効バージョン文字列を含めるのは適切。
|
|
258
|
+
|
|
259
|
+
**Python 固有概念の学習コスト**: `reduce` は TS では `Array.prototype.reduce` があるので理解しやすいが、Python 3 での `reduce` の立ち位置(`functools.reduce` に格下げされた経緯)を知ると設計哲学が掴める。
|
|
260
|
+
|
|
261
|
+
**事故リスク**: 低
|
|
262
|
+
|
|
263
|
+
### 4. バックエンド経験者(Django/FastAPI 歴5-6年・男性・リードエンジニア)
|
|
264
|
+
|
|
265
|
+
`cached_property` は Django モデルの `@property` + 手動キャッシュより洗練されている。リクエストごとに新インスタンスを作成するケースでは「1リクエスト内のみ有効なキャッシュ」として機能するため、副作用なく使いやすい。
|
|
266
|
+
|
|
267
|
+
**他フレームワークとの差異**: Django の `django.utils.functional.cached_property` は非スレッドセーフだが、`functools.cached_property` も非スレッドセーフ(Python 3.12 以前)。スレッドセーフが必要な場合は明示的なロックが必要。
|
|
268
|
+
|
|
269
|
+
**nene2 の薄さへの評価**: `functools` は nene2 フレームワークと独立しているため、どの FastAPI プロジェクトでも直接適用できる内容として評価が高い。
|
|
270
|
+
|
|
271
|
+
**事故リスク**: 低
|
|
272
|
+
|
|
273
|
+
### 5. シニアエンジニア(設計・コードレビュー担当・女性・10-12年)
|
|
274
|
+
|
|
275
|
+
**コードレビューチェックポイント**:
|
|
276
|
+
- `lru_cache` の `maxsize` が適切か(サービスの想定入力範囲に合っているか)
|
|
277
|
+
- `cache`(無制限)を使う場合、入力バリエーションが有限であることが保証されているか
|
|
278
|
+
- `@wraps` が全てのデコレーター実装に付いているか
|
|
279
|
+
- `singledispatch` のデフォルト実装が意図した型を処理するか(意図しない型が来たときの挙動)
|
|
280
|
+
- `total_ordering` で `__eq__` と `__lt__` の両方が実装されているか
|
|
281
|
+
|
|
282
|
+
**チームでの安全なパターン**: `retry` デコレーターはチーム共有のユーティリティとして使えるが、リトライ間隔(`sleep`)・バックオフ・特定例外のみリトライなど実装が必要な場合は `tenacity` ライブラリの使用を推奨する。
|
|
283
|
+
|
|
284
|
+
**事故リスク**: 低
|
|
285
|
+
|
|
286
|
+
### 6. 設計者(nene2-python 設計ポリシー目線)
|
|
287
|
+
|
|
288
|
+
**CLAUDE.md ポリシー整合性**:
|
|
289
|
+
- `dataclass(frozen=True, slots=True)`: `CacheStats` / `Version` で適用済み ✅
|
|
290
|
+
- Pydantic は HTTP 境界のみ: `app.py` の Request/Response モデルのみ ✅
|
|
291
|
+
- `create_app()` はファイル末尾: 適用済み ✅
|
|
292
|
+
- `max_length` 指定: 外側リストには設定済み(F-2: 内側リスト未設定)⚠️
|
|
293
|
+
- セキュリティ診断実施: FT186 % 3 = 0 → 診断実施 ✅
|
|
294
|
+
|
|
295
|
+
**初心者でも安全な API 達成度**: `fibonacci_safe` による DoS 防御ラッパーパターン(上限チェック → メモ化関数呼び出し)は安全なキャッシュ利用の模範として機能している。Pydantic の `le=` と `fibonacci_safe` の両方で二重バリデーションしている点も good practice。
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
*バージョン: v1.8.57*
|
|
@@ -218,12 +218,14 @@
|
|
|
218
218
|
| [FT182](2026-05-field-trial-182.md) | email モジュール — MIME 構築・RFC 2047・パース・アドレス操作 | | |
|
|
219
219
|
| [FT183](2026-05-field-trial-183.md) | smtplib モジュール — SMTP 送信・STARTTLS・ヘッダーインジェクション防御 | 🔒 | [#513](https://github.com/hideyukiMORI/nene2-python/issues/513) [#514](https://github.com/hideyukiMORI/nene2-python/issues/514) |
|
|
220
220
|
| [FT184](2026-05-field-trial-184.md) | urllib.request モジュール — URL フェッチ・Basic 認証・SSRF 防御 | 🔍 | [#516](https://github.com/hideyukiMORI/nene2-python/issues/516) [#517](https://github.com/hideyukiMORI/nene2-python/issues/517) |
|
|
221
|
+
| [FT185](2026-05-field-trial-185.md) | contextlib モジュール — コンテキストマネージャー・リソース管理・エラー抑制 | | |
|
|
222
|
+
| [FT186](2026-05-field-trial-186.md) | functools モジュール — キャッシュ・部分適用・デコレーター・比較・ディスパッチ | 🔒 | [#520](https://github.com/hideyukiMORI/nene2-python/issues/520) |
|
|
221
223
|
|
|
222
224
|
---
|
|
223
225
|
|
|
224
226
|
## セキュリティ診断実施済み一覧(🔒)
|
|
225
227
|
|
|
226
|
-
FT3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 60, 63, 66, 69, 72, 75, 78, 81, 84, 87, 90, 93, 96, 99, 102, 105, 108, 111, 114, 117, 120, 121, 124, 127, 130, 133, 136, 139, 142, 145, 148, 151, 154, 157, 160, 163, 166, 169, 172, 174, 177, 180, 183
|
|
228
|
+
FT3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 60, 63, 66, 69, 72, 75, 78, 81, 84, 87, 90, 93, 96, 99, 102, 105, 108, 111, 114, 117, 120, 121, 124, 127, 130, 133, 136, 139, 142, 145, 148, 151, 154, 157, 160, 163, 166, 169, 172, 174, 177, 180, 183, 186
|
|
227
229
|
|
|
228
230
|
合計: **62件**(183 FT 中 約 34%)
|
|
229
231
|
|
|
@@ -233,4 +235,4 @@ FT172, FT176, FT180, FT184
|
|
|
233
235
|
|
|
234
236
|
---
|
|
235
237
|
|
|
236
|
-
*最終更新: 2026-05-21 (
|
|
238
|
+
*最終更新: 2026-05-21 (FT186 / v1.8.57)*
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# TODO — current
|
|
2
2
|
|
|
3
3
|
最終更新: 2026-05-21
|
|
4
|
-
現状: **v1.8.
|
|
4
|
+
現状: **v1.8.57 安定版 / フィールドトライアルループ継続中(FT186 完了)**
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
## 状態サマリー
|
|
9
9
|
|
|
10
|
-
v1.8.
|
|
11
|
-
フィールドトライアルループは
|
|
10
|
+
v1.8.57 完了済み。FT186(functools / キャッシュ・部分適用・デコレーター・比較・ディスパッチ)を含む FT186 件を実施済み。セキュリティ診断実施済み。
|
|
11
|
+
フィールドトライアルループは FT187 以降も継続中。
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
@@ -22,6 +22,7 @@ v1.8.55 完了済み。FT184(urllib.request / URL フェッチ・Basic 認証
|
|
|
22
22
|
|
|
23
23
|
| Issue | 内容 | 優先度 |
|
|
24
24
|
|---|---|---|
|
|
25
|
+
| [#520](https://github.com/hideyukiMORI/nene2-python/issues/520) | [FT186] /flatten エンドポイントの内側リスト要素数に上限を追加 | 中 |
|
|
25
26
|
| [#517](https://github.com/hideyukiMORI/nene2-python/issues/517) | [FT184] DNS リバインディング攻撃への対策検討(TTL0 + IP 切り替え) | 低 |
|
|
26
27
|
| [#516](https://github.com/hideyukiMORI/nene2-python/issues/516) | [FT184] fetch_safe のリダイレクト SSRF 対策(Location ヘッダー先の IP 検証) | 中 |
|
|
27
28
|
| [#514](https://github.com/hideyukiMORI/nene2-python/issues/514) | [FT183] SmtpConfig.password を SecretStr に変更 | 低 |
|
|
@@ -40,6 +41,8 @@ v1.8.55 完了済み。FT184(urllib.request / URL フェッチ・Basic 認証
|
|
|
40
41
|
|
|
41
42
|
| バージョン | 主な内容 |
|
|
42
43
|
|---|---|
|
|
44
|
+
| v1.8.57 | FT186: functools — キャッシュ・部分適用・デコレーター・比較・ディスパッチ(診断実施) |
|
|
45
|
+
| v1.8.56 | FT185: contextlib — コンテキストマネージャー・リソース管理・エラー抑制 |
|
|
43
46
|
| v1.8.55 | FT184: urllib.request — URL フェッチ・Basic 認証・SSRF 防御(クラッカーペンテスト実施) |
|
|
44
47
|
| v1.8.54 | FT183: smtplib — SMTP 送信・STARTTLS・ヘッダーインジェクション防御(診断実施) |
|
|
45
48
|
| v1.8.53 | FT182: email — MIME 構築・RFC 2047・パース・アドレス操作 |
|
|
@@ -58,12 +61,12 @@ v1.8.55 完了済み。FT184(urllib.request / URL フェッチ・Basic 認証
|
|
|
58
61
|
|
|
59
62
|
## フィールドトライアル進捗
|
|
60
63
|
|
|
61
|
-
**実施済み**: FT1〜
|
|
64
|
+
**実施済み**: FT1〜FT186(全 186 件)
|
|
62
65
|
|
|
63
66
|
索引: [`docs/field-trials/INDEX.md`](../field-trials/INDEX.md)
|
|
64
67
|
|
|
65
68
|
**次のアクション**:
|
|
66
|
-
-
|
|
69
|
+
- FT186 以降を継続(FT187 は 187 % 4 = 3 → ペンテストなし、187 % 3 = 1 → 診断なし)
|
|
67
70
|
|
|
68
71
|
---
|
|
69
72
|
|
|
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
|