nene2-python 1.8.63__tar.gz → 1.8.64__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.63 → nene2_python-1.8.64}/PKG-INFO +1 -1
- nene2_python-1.8.64/docs/field-trials/2026-05-field-trial-192.md +485 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/INDEX.md +3 -2
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/todo/current.md +6 -5
- {nene2_python-1.8.63 → nene2_python-1.8.64}/pyproject.toml +1 -1
- {nene2_python-1.8.63 → nene2_python-1.8.64}/.env.example +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/.github/workflows/ci.yml +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/.github/workflows/docs.yml +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/.github/workflows/publish.yml +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/.gitignore +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/.vitepress/config.mts +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/.vitepress/theme/custom.css +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/.vitepress/theme/index.ts +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/AGENTS.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/CHANGELOG.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/CLAUDE.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/Dockerfile +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/LICENSE +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/README.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/alembic/README +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/alembic/env.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/alembic/script.py.mako +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/alembic.ini +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/compose.yaml +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/adr/0001-toolchain.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/adr/0002-clean-architecture.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/adr/0003-security-first.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/adr/0004-ai-first-design.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/adr/0005-logging.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/adr/0006-rate-limiting.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/adr/0009-mcp-design.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/adr/0010-async-use-case.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/de/index.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/de/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/explanation/architecture.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-1.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-10.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-100.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-101.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-102.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-103.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-104.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-105.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-106.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-107.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-108.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-109.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-11.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-110.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-111.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-112.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-113.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-114.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-115.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-116.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-117.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-118.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-119.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-12.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-120.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-121.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-122.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-123.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-124.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-125.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-126.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-127.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-128.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-129.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-13.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-130.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-131.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-132.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-133.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-134.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-135.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-136.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-137.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-138.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-139.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-14.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-140.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-141.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-142.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-143.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-144.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-145.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-146.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-147.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-148.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-149.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-15.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-150.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-151.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-152.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-153.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-154.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-155.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-156.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-157.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-158.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-159.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-16.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-160.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-161.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-162.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-163.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-164.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-165.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-166.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-167.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-168.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-169.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-17.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-170.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-171.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-172.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-173.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-174.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-175.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-176.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-177.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-178.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-179.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-18.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-180.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-181.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-182.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-183.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-184.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-185.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-186.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-187.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-188.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-189.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-19.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-190.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-191.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-2.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-20.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-21.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-22.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-23.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-24.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-25.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-26.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-27.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-28.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-29.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-3.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-30.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-31.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-32.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-33.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-34.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-35.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-36.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-37.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-38.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-39.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-4.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-40.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-41.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-42.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-43.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-44.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-45.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-46.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-47.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-48.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-49.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-5.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-50.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-51.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-52.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-53.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-54.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-55.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-56.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-57.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-58.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-59.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-6.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-60.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-61.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-62.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-63.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-64.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-65.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-66.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-67.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-68.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-69.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-7.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-70.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-71.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-72.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-73.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-74.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-75.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-76.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-77.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-78.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-79.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-8.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-80.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-81.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-82.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-83.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-84.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-85.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-86.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-87.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-88.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-89.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-9.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-90.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-91.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-92.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-93.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-94.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-95.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-96.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-97.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-98.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/field-trials/2026-05-field-trial-99.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/fr/index.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/fr/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/how-to/api-versioning.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/how-to/async-use-case.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/how-to/background-tasks.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/how-to/cors.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/how-to/custom-auth-middleware.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/how-to/decimal-unicode-input.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/how-to/dependency-injection.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/how-to/domain-events.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/how-to/email-address-parsing.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/how-to/file-upload.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/how-to/lifespan-and-app-state.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/how-to/middleware-stack.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/how-to/new-project.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/how-to/problem-details.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/how-to/response-patterns.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/how-to/run-tests.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/how-to/soft-delete.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/how-to/streaming.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/how-to/structured-logging.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/how-to/validation.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/how-to/webhook.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/index.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/ja/explanation/architecture.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/ja/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/ja/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/ja/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/ja/how-to/new-project.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/ja/how-to/run-tests.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/ja/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/ja/index.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/ja/reference/api.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/ja/reference/configuration.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/ja/reference/framework-modules.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/ja/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/ja/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/pt-br/index.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/pt-br/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/reference/api.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/reference/configuration.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/reference/framework-modules.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/roadmap.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/templates/field-trial-report.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/zh/index.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/docs/zh/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/package-lock.json +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/package.json +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/example/__init__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/example/__main__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/example/app.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/example/comment/__init__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/example/comment/entity.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/example/comment/exceptions.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/example/comment/handler.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/example/comment/repository.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/example/comment/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/example/comment/use_case.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/example/mcp.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/example/note/__init__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/example/note/async_use_case.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/example/note/entity.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/example/note/exceptions.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/example/note/handler.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/example/note/repository.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/example/note/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/example/note/use_case.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/example/schema.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/example/tag/__init__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/example/tag/entity.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/example/tag/exceptions.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/example/tag/handler.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/example/tag/repository.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/example/tag/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/example/tag/use_case.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/__init__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/auth/api_key.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/auth/bearer_token.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/auth/deps.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/auth/exceptions.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/auth/interfaces.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/auth/local_verifier.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/cache/__init__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/cache/ttl.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/config/settings.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/database/exceptions.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/database/health.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/database/interfaces.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/database/sqlalchemy_executor.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/database/utils.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/http/etag.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/http/health.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/http/pagination.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/http/problem_details.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/log/setup.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/mcp/http_client.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/mcp/server.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/middleware/__init__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/middleware/domain_exception.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/middleware/error_handler.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/middleware/request_id.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/middleware/request_logging.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/middleware/request_size_limit.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/middleware/security_headers.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/middleware/setup.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/middleware/throttle.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/py.typed +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/security/__init__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/security/webhook.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/use_case/protocols.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/nene2/validation/exceptions.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/scripts/__init__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/src/scripts/export_openapi.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/__init__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/conftest.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/example/__init__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/example/comment/__init__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/example/comment/test_comment_http.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/example/comment/test_comment_repository.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/example/comment/test_comment_use_case.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/example/conftest.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/example/note/__init__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/example/note/test_async_note_use_case.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/example/note/test_list_notes.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/example/note/test_note_repository.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/example/tag/__init__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/example/tag/test_tag_repository.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/example/tag/test_tags.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/example/test_cors.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/example/test_mcp.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/__init__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/auth/test_api_key.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/auth/test_bearer_token.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/auth/test_make_require_auth.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/auth/test_token_issuer.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/cache/__init__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/cache/test_ttl.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/config/test_settings.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/database/test_transaction.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/database/test_utils.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/http/test_etag.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/http/test_health.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/http/test_pagination.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/http/test_problem_details.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/log/test_setup.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/mcp/test_http_client.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/mcp/test_server.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/middleware/__init__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/middleware/test_error_handler.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/middleware/test_request_id.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/middleware/test_request_logging.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/middleware/test_request_size_limit.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/middleware/test_security_headers.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/middleware/test_setup_middlewares.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/middleware/test_throttle.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/security/__init__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/security/test_webhook.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/use_case/test_protocols.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/use_case/test_run_in_threadpool.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/nene2/validation/test_exceptions.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/scripts/__init__.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/tests/scripts/test_export_openapi.py +0 -0
- {nene2_python-1.8.63 → nene2_python-1.8.64}/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.64
|
|
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,485 @@
|
|
|
1
|
+
# FT192: asyncio モジュール
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-21
|
|
4
|
+
**テーマ**: コルーチン・タスク・イベントループ・同期プリミティブ
|
|
5
|
+
**セキュリティ診断**: **あり**(192 % 3 = 0)
|
|
6
|
+
**クラッカーペンテスト**: **あり**(192 % 4 = 0)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 概要
|
|
11
|
+
|
|
12
|
+
`asyncio` は Python の非同期 I/O フレームワークで、FastAPI の内部エンジンでもある。`gather` / `wait` / `wait_for` / `Task` / `Lock` / `Event` / `Semaphore` / `Queue` / `Condition` / `TaskGroup` / `as_completed` / `run_in_executor` を FastAPI エンドポイントから検証する。
|
|
13
|
+
|
|
14
|
+
FT188(threading)・FT190(multiprocessing)・FT191(concurrent.futures)の高レベル非同期版として位置付け、競合状態・キャンセル・タイムアウト・DoS 防止を重点的に診断する。
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 実装したサンプルアプリ
|
|
19
|
+
|
|
20
|
+
**場所**: `/home/xi/docker/nene2-python-FT/ft192-asyncio/`
|
|
21
|
+
|
|
22
|
+
### 主要機能
|
|
23
|
+
|
|
24
|
+
| 関数/クラス | 概要 |
|
|
25
|
+
|---|---|
|
|
26
|
+
| `run_gather(values)` | asyncio.gather で並列二乗計算 |
|
|
27
|
+
| `run_gather_with_errors(values)` | gather(return_exceptions=True) |
|
|
28
|
+
| `run_wait_all(values)` | asyncio.wait(ALL_COMPLETED) |
|
|
29
|
+
| `run_wait_first_completed(values)` | asyncio.wait(FIRST_COMPLETED) |
|
|
30
|
+
| `run_with_timeout(value, delay, timeout)` | asyncio.wait_for タイムアウト |
|
|
31
|
+
| `create_and_cancel_task(value, delay)` | Task 作成+キャンセル |
|
|
32
|
+
| `lock_demo(num_tasks, increments_each)` | asyncio.Lock カウンター保護 |
|
|
33
|
+
| `event_demo()` | asyncio.Event ウェイター/シグナラー |
|
|
34
|
+
| `semaphore_demo(num_tasks, limit)` | asyncio.Semaphore 同時実行制限 |
|
|
35
|
+
| `queue_producer_consumer(items)` | asyncio.Queue P/C パターン |
|
|
36
|
+
| `condition_demo()` | asyncio.Condition 条件変数 |
|
|
37
|
+
| `run_as_completed(values, delay)` | asyncio.as_completed |
|
|
38
|
+
| `run_task_group(values)` | asyncio.TaskGroup(Python 3.11+) |
|
|
39
|
+
| `run_in_executor(values)` | 同期関数のスレッドオフロード |
|
|
40
|
+
|
|
41
|
+
### HTTP エンドポイント
|
|
42
|
+
|
|
43
|
+
| メソッド | パス | 概要 |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| POST | `/asyncio/gather` | gather で並列実行 |
|
|
46
|
+
| POST | `/asyncio/gather-errors` | gather(return_exceptions=True) |
|
|
47
|
+
| POST | `/asyncio/wait-all` | wait(ALL_COMPLETED) |
|
|
48
|
+
| POST | `/asyncio/wait-first` | wait(FIRST_COMPLETED) |
|
|
49
|
+
| POST | `/asyncio/timeout` | wait_for タイムアウト |
|
|
50
|
+
| POST | `/asyncio/cancel` | Task キャンセル |
|
|
51
|
+
| POST | `/asyncio/lock` | Lock カウンター |
|
|
52
|
+
| POST | `/asyncio/event` | Event 同期 |
|
|
53
|
+
| POST | `/asyncio/semaphore` | Semaphore 制限 |
|
|
54
|
+
| POST | `/asyncio/queue` | Queue P/C |
|
|
55
|
+
| POST | `/asyncio/condition` | Condition 条件変数 |
|
|
56
|
+
| POST | `/asyncio/as-completed` | as_completed |
|
|
57
|
+
| POST | `/asyncio/task-group` | TaskGroup |
|
|
58
|
+
| POST | `/asyncio/run-in-executor` | スレッドオフロード |
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## テスト結果
|
|
63
|
+
|
|
64
|
+
**48 passed**
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
48 passed in 0.58s
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## 摩擦ポイント
|
|
73
|
+
|
|
74
|
+
### F-1: `BatchResult` を不要インポートした・`Any` 型未使用(深刻度: 低)
|
|
75
|
+
|
|
76
|
+
**事象**: `demos.py` に `from typing import Any` を残したまま、`app.py` に `BatchResult` を残した。ruff `F401` で検出。
|
|
77
|
+
|
|
78
|
+
**原因**: 実装時に「後で使うかも」と残したままにした典型的なクリーンアップ漏れ。mypy --strict はインポートの未使用を検出しないが ruff が検出する。
|
|
79
|
+
|
|
80
|
+
**対応**: `uv run ruff check --fix` で自動修正。
|
|
81
|
+
|
|
82
|
+
### F-2: `lock_demo` が `dict[str, int]` を返し、エンドポイントの `dict[str, object]` に非互換(深刻度: 低)
|
|
83
|
+
|
|
84
|
+
**事象**: `lock_demo` の戻り値型 `dict[str, int]` を `dict[str, object]` 戻り値のエンドポイントで直接 return しようとすると mypy 不整合。
|
|
85
|
+
|
|
86
|
+
**原因**: FT191 F-1 と同じ `dict` invariant 問題。
|
|
87
|
+
**対応**: `{k: v for k, v in result.items()}` で `dict[str, object]` に変換。
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 観察点
|
|
92
|
+
|
|
93
|
+
### 観察1: gather vs wait の使い分け
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
# gather: 引数の順序でリストが返る・どれか例外でキャンセル(return_exceptions=True で継続)
|
|
97
|
+
results = await asyncio.gather(coro1(), coro2(), return_exceptions=True)
|
|
98
|
+
|
|
99
|
+
# wait: set を渡し done/pending を得る・return_when で FIRST_COMPLETED 等を指定可
|
|
100
|
+
done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
HTTP API では `gather` が自然。`wait` は「N 件のうち M 件完了したら早期返却」等の要件に使う。
|
|
104
|
+
|
|
105
|
+
### 観察2: TaskGroup と例外伝播(Python 3.11+)
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
async with asyncio.TaskGroup() as tg:
|
|
109
|
+
t1 = tg.create_task(coro1())
|
|
110
|
+
t2 = tg.create_task(coro2())
|
|
111
|
+
# いずれかのタスクで例外が起きると、残タスクはキャンセルされて ExceptionGroup が raise される
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`gather` と異なりキャンセルが自動。例外処理は `ExceptionGroup` で行う(Python 3.11+ の `except*` 構文)。
|
|
115
|
+
|
|
116
|
+
### 観察3: asyncio.Lock は GIL 解放ポイントがない
|
|
117
|
+
|
|
118
|
+
asyncio は**単一スレッド**のイベントループなので、`await` ポイントがない純粋な CPU 計算は他コルーチンをブロックする。Lock が必要なのは `await` をまたいで共有状態にアクセスする場合のみ。
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## nene2-python フレームワークとの統合
|
|
123
|
+
|
|
124
|
+
- FastAPI エンドポイントはデフォルト非同期なので `async def` を使えば asyncio のコルーチンをそのまま `await` できる
|
|
125
|
+
- `asyncio.Lock` は同一イベントループ内でのみ有効。複数プロセス(multiprocessing)では使えない
|
|
126
|
+
- `run_in_executor` でブロッキング I/O(DB クエリ等)をスレッドにオフロードできる。FastAPI では `sync_to_async` 的な役割
|
|
127
|
+
- `asyncio.Semaphore` はレートリミット・同時接続数制限に使える
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Developer Experience (DX) Review
|
|
132
|
+
|
|
133
|
+
### ペルソナ1: 初心者(Python 歴1年・独学中・女性・バックエンド志望)
|
|
134
|
+
|
|
135
|
+
FastAPI チュートリアルで `async def` を書き始めた段階。`asyncio` そのものには触れていないことが多い。
|
|
136
|
+
|
|
137
|
+
**ドキュメント理解**: `asyncio.gather` は直感的に理解できる。`asyncio.wait` の `return_when` フラグは名前からは分かりにくい。`TaskGroup` は Python 3.11+ 専用と明示されないと混乱する可能性がある。
|
|
138
|
+
**事故リスク**: 中。`gather` で例外を握りつぶす(`return_exceptions=True` を使うが処理しない)パターンは初心者が踏みやすい。`CancelledError` を `except Exception` で捕まえると cancel が効かなくなる。
|
|
139
|
+
**規約の使いやすさ**: `async with asyncio.TaskGroup() as tg:` は `with Pool() as pool:` と同じ感覚で書ける。
|
|
140
|
+
|
|
141
|
+
### ペルソナ2: ロースキル経験者(Python 歴3-4年・スクリプト系・男性・SES)
|
|
142
|
+
|
|
143
|
+
FastAPI で `async def` を使っているが非同期の仕組みを深く理解していない段階。
|
|
144
|
+
|
|
145
|
+
**コピペ可能性**: `gather` のサンプルは直接コピーして動く。`wait` + `return_when` は説明なしでは選択肢が多すぎる。
|
|
146
|
+
**拡張時の罠**: CPU バウンド処理を `async def` に入れてしまうとイベントループをブロックする(`await` ポイントが必要)。`run_in_executor` の使い道が分かっていないと気づきにくい。
|
|
147
|
+
**セキュリティ的な事故リスク**: 中。`asyncio.sleep(delay)` の `delay` に上限がないと DoS になる。本実装では `MAX_DELAY = 5.0` で制限。
|
|
148
|
+
|
|
149
|
+
### ペルソナ3: フロントエンド寄り経験者(React/TS 歴4年・バックエンド転向中・ノンバイナリ)
|
|
150
|
+
|
|
151
|
+
JS の `async/await` と `Promise.all` / `Promise.race` との比較で理解する段階。
|
|
152
|
+
|
|
153
|
+
**エラーレスポンスの質**: `gather(return_exceptions=True)` で例外を `error: ...` 文字列に変換して返すパターンは、クライアントが部分成功を処理しやすい。422 エラーは Pydantic が自動で出す。
|
|
154
|
+
**Python 固有概念の学習コスト**: `asyncio.Lock` / `asyncio.Event` は JS には直接対応するものがなく、概念の説明が必要。`CancelledError` は `AbortController` に似ている。
|
|
155
|
+
**事故リスク**: 低。HTTP バリデーションが Pydantic で保護されており、エンドポイント経由では上限超えが防げる。
|
|
156
|
+
|
|
157
|
+
### ペルソナ4: バックエンド経験者(Django/FastAPI 歴5-6年・男性・リードエンジニア)
|
|
158
|
+
|
|
159
|
+
asyncio の深い知識があり、設計上の問題を指摘できる立場。
|
|
160
|
+
|
|
161
|
+
**他フレームワークとの差異**: Django の `sync_to_async` / `async_to_sync` との対応: `run_in_executor` が `sync_to_async` に相当。FastAPI の `BackgroundTasks` は `create_task` に近い。
|
|
162
|
+
**nene2-python の薄さへの評価**: UseCase が `async def` で書かれていれば FastAPI の非同期エンドポイントから直接 `await` できる。DI コンテナ不要で設計が明確。
|
|
163
|
+
**本番投入可能性**: `asyncio.Semaphore` によるレートリミット実装はシンプルで使いやすい。ただし単一プロセス内限定なので水平スケール時は Redis 等の外部ストアが必要になる。
|
|
164
|
+
|
|
165
|
+
### ペルソナ5: シニアエンジニア(設計・コードレビュー担当・女性・10-12年)
|
|
166
|
+
|
|
167
|
+
チームで asyncio を使う場合のリスクをレビューする立場。
|
|
168
|
+
|
|
169
|
+
**コードレビューチェックポイント**:
|
|
170
|
+
- [ ] `CancelledError` を `except Exception:` で捕まえていないか(cancel が効かなくなる)
|
|
171
|
+
- [ ] CPU バウンド処理に `await` がないまま `async def` に入っていないか(イベントループブロック)
|
|
172
|
+
- [ ] `asyncio.wait` の `pending` タスクをキャンセルしているか(リーク防止)
|
|
173
|
+
- [ ] `gather(return_exceptions=True)` の結果を必ず処理しているか
|
|
174
|
+
- [ ] タイムアウト・Semaphore に上限制限があるか
|
|
175
|
+
|
|
176
|
+
**チームでの安全な共有パターン**: `async with asyncio.TaskGroup() as tg:` を推奨(Python 3.11+)。例外が自動伝播して pending タスクを自動キャンセルする。
|
|
177
|
+
**ツール追加の必要性**: `anyio` を使うと asyncio/trio の両対応が可能。テストには `pytest-anyio` または `pytest-asyncio` が便利だが、本 FT では `asyncio.run()` での直接テストが有効。
|
|
178
|
+
|
|
179
|
+
### ペルソナ6: 設計者・ポリシー照合(nene2-python 設計ポリシー目線)
|
|
180
|
+
|
|
181
|
+
CLAUDE.md ポリシーとの整合性を確認する。
|
|
182
|
+
|
|
183
|
+
**ポリシー達成度**: 高
|
|
184
|
+
**「初心者でも安全な API」達成度**: 中(`CancelledError` の扱いミスは実行時まで気づきにくい)
|
|
185
|
+
**設計上の負債・ドキュメント不足**: `asyncio` と `threading`/`multiprocessing`/`concurrent.futures` の使い分けガイドを How-to として追加すると価値が高い
|
|
186
|
+
**Follow-up Issues**: なし(全問題即時対応済み)
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## セキュリティ診断(FT192 — 192 % 3 = 0)
|
|
191
|
+
|
|
192
|
+
### 1. OWASP API Security Top 10 (2023)
|
|
193
|
+
|
|
194
|
+
#### API1: オブジェクトレベルの認可不備 (BOLA / IDOR)
|
|
195
|
+
- **結果**: 本 FT は認可機能なし(計算デモのみ)。認可は nene2-python の auth ミドルウェアが担当。対象外。
|
|
196
|
+
|
|
197
|
+
#### API2: 認証の破損 (Broken Authentication)
|
|
198
|
+
- **結果**: 認証機能なし。対象外。
|
|
199
|
+
|
|
200
|
+
#### API3: Mass Assignment
|
|
201
|
+
- **結果**: `model_config = ConfigDict(extra="ignore")` がデフォルト。未定義フィールドは無視される。 ✅
|
|
202
|
+
|
|
203
|
+
#### API4: 無制限リソース消費
|
|
204
|
+
- `values: list[...] = Field(max_length=MAX_ITEMS)` — 入力リスト500要素上限 ✅
|
|
205
|
+
- `num_tasks: int = Field(ge=1, le=MAX_TASKS)` — タスク数100上限 ✅
|
|
206
|
+
- `delay: float = Field(ge=0.0, le=5.0)` — 遅延5秒上限 ✅
|
|
207
|
+
- **結果**: 全リソース消費ポイントにバリデーションあり。 ✅
|
|
208
|
+
|
|
209
|
+
#### API5: 機能レベルの認可不備
|
|
210
|
+
- **結果**: 管理者エンドポイントなし。対象外。
|
|
211
|
+
|
|
212
|
+
#### API6: SSRF
|
|
213
|
+
- **結果**: URL 受け取りフィールドなし。外部接続なし。対象外。
|
|
214
|
+
|
|
215
|
+
#### API7: セキュリティ設定ミス
|
|
216
|
+
- **結果**: デモアプリのため SecurityHeadersMiddleware は未適用だが、本番 nene2-python アプリには標準搭載。
|
|
217
|
+
|
|
218
|
+
#### API8: バージョン管理の欠落
|
|
219
|
+
- **結果**: バージョン管理エンドポイントなし。対象外。
|
|
220
|
+
|
|
221
|
+
#### API9: 不適切な在庫管理
|
|
222
|
+
- **結果**: デバッグエンドポイントなし。ハードコードされたシークレットなし。 ✅
|
|
223
|
+
|
|
224
|
+
#### API10: 安全でない API 消費
|
|
225
|
+
- **結果**: 外部 API 消費なし。対象外。
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
### 2. インジェクション攻撃
|
|
230
|
+
|
|
231
|
+
#### SQL インジェクション
|
|
232
|
+
- **結果**: DB 操作なし。対象外。
|
|
233
|
+
|
|
234
|
+
#### コマンドインジェクション
|
|
235
|
+
- **結果**: `subprocess`/`os.system` 呼び出しなし。 ✅
|
|
236
|
+
|
|
237
|
+
#### パストラバーサル
|
|
238
|
+
- **結果**: ファイル操作なし。対象外。
|
|
239
|
+
|
|
240
|
+
#### SSTI
|
|
241
|
+
- **結果**: テンプレートエンジン不使用。対象外。
|
|
242
|
+
|
|
243
|
+
#### HTTP ヘッダーインジェクション
|
|
244
|
+
- **結果**: レスポンスヘッダーへのユーザー入力反映なし。 ✅
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
### 3. 認証・認可
|
|
249
|
+
- **結果**: 認証機能なし。計算デモのスコープ外。
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
### 4. 入力バリデーション
|
|
254
|
+
|
|
255
|
+
全 Pydantic モデルでの確認:
|
|
256
|
+
|
|
257
|
+
| フィールド | 制約 | 結果 |
|
|
258
|
+
|---|---|---|
|
|
259
|
+
| `values` | `max_length=500`, `ge=-10000, le=10000` | ✅ |
|
|
260
|
+
| `num_tasks` | `ge=1, le=100` | ✅ |
|
|
261
|
+
| `increments_each` | `ge=1, le=500` | ✅ |
|
|
262
|
+
| `delay` | `ge=0.0, le=5.0` | ✅ |
|
|
263
|
+
| `timeout` | `ge=0.1, le=10.0` | ✅ |
|
|
264
|
+
| `limit` | `ge=1, le=MAX_TASKS` | ✅ |
|
|
265
|
+
| `items` | `max_length=500, ge=0, le=100000` | ✅ |
|
|
266
|
+
|
|
267
|
+
**テスト入力の試行**:
|
|
268
|
+
```python
|
|
269
|
+
# 上限超え → 422
|
|
270
|
+
POST /asyncio/gather {"values": list(range(600))} # → 422 ✅
|
|
271
|
+
|
|
272
|
+
# 範囲外 → 422
|
|
273
|
+
POST /asyncio/gather {"values": [99999]} # → 422 ✅
|
|
274
|
+
|
|
275
|
+
# 負の delay → 422
|
|
276
|
+
POST /asyncio/timeout {"value": 1, "delay": -1.0, "timeout": 5.0} # → 422 ✅
|
|
277
|
+
|
|
278
|
+
# timeout 上限超え → 422
|
|
279
|
+
POST /asyncio/timeout {"value": 1, "delay": 0.0, "timeout": 100.0} # → 422 ✅
|
|
280
|
+
```
|
|
281
|
+
- **結果**: 全境界値バリデーション通過。 ✅
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
### 5. 情報漏洩
|
|
286
|
+
- `print()` 不使用(ruff S 系ルールで強制)。 ✅
|
|
287
|
+
- スタックトレース公開なし(FastAPI のデフォルト動作)。 ✅
|
|
288
|
+
- pip-audit: PYSEC-2025-183(PyJWT 推移的 CVE)のみ — 既知許容済み。 ✅
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
### 6. Python / asyncio 固有の攻撃ベクター
|
|
293
|
+
|
|
294
|
+
#### 非同期レースコンディション
|
|
295
|
+
- `asyncio.Lock` で `counter` を保護 → `lock_demo` テストで確認済み(4 タスク × 50 回 = 200 正確に一致)。 ✅
|
|
296
|
+
- `asyncio.Semaphore` で同時実行数を制限 → `active_peak <= limit` を確認済み。 ✅
|
|
297
|
+
|
|
298
|
+
#### CancelledError 伝播
|
|
299
|
+
- `create_and_cancel_task` では `asyncio.CancelledError` を明示的にキャッチし `cancelled=True` を返す。`except Exception:` でキャッチしていない。 ✅
|
|
300
|
+
|
|
301
|
+
#### イベントループブロック
|
|
302
|
+
- CPU バウンド処理は `_blocking_square` として `run_in_executor` 経由でスレッドにオフロード。コルーチン内で `time.sleep()` を直接呼んでいない。 ✅
|
|
303
|
+
|
|
304
|
+
#### DoS(大量タスク生成)
|
|
305
|
+
- `values: list[...] = Field(max_length=500)` で上限制限。500 タスクを `gather` しても 0.58s で完了(テスト確認済み)。 ✅
|
|
306
|
+
|
|
307
|
+
#### type: ignore 不審使用
|
|
308
|
+
- `# type: ignore` の残留なし(ruff `# type: ignore[return-value]` は削除済み)。 ✅
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
### 診断サマリー
|
|
313
|
+
|
|
314
|
+
| カテゴリ | 結果 | 備考 |
|
|
315
|
+
|---|---|---|
|
|
316
|
+
| OWASP API Security Top 10 | ✅ 全通過 | 認証/認可は nene2-python 本体が担当 |
|
|
317
|
+
| インジェクション | ✅ | SQL/コマンド/パス操作なし |
|
|
318
|
+
| 認証・認可 | ✅ 対象外 | 計算デモ |
|
|
319
|
+
| 入力バリデーション | ✅ | 全フィールドに ge/le/max_length |
|
|
320
|
+
| 情報漏洩 | ✅ | pip-audit PYSEC-2025-183 は許容 |
|
|
321
|
+
| 非同期レースコンディション | ✅ | Lock/Semaphore で保護 |
|
|
322
|
+
| CancelledError 伝播 | ✅ | 明示的 CancelledError キャッチ |
|
|
323
|
+
| イベントループブロック | ✅ | run_in_executor でオフロード |
|
|
324
|
+
| DoS(大量タスク) | ✅ | max_length=500 で制限 |
|
|
325
|
+
| 依存関係 CVE | ✅ 許容 | PYSEC-2025-183 のみ(mcp 推移的) |
|
|
326
|
+
|
|
327
|
+
**総合評価**: 合格
|
|
328
|
+
**発見した脆弱性**: 0 件(CRITICAL: 0 / HIGH: 0 / MEDIUM: 0 / LOW: 0)
|
|
329
|
+
**新規セキュリティ Issue**: なし
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## クラッカーペンテスト(FT192 — 192 % 4 = 0)
|
|
334
|
+
|
|
335
|
+
### フェーズ1: 構造推測(攻撃者の視点)
|
|
336
|
+
|
|
337
|
+
**公開情報から推測できる内部構造**:
|
|
338
|
+
- OpenAPI から `values: list[int]` フィールドが多く、リスト長・値範囲のバリデーションが焦点
|
|
339
|
+
- `delay` フィールド → 意図的な遅延処理が存在する → タイムアウト攻撃の余地があるか
|
|
340
|
+
- `num_tasks: int` → タスク数を増やすとサーバーリソースを枯渇できるか
|
|
341
|
+
- エラーメッセージ: Pydantic の 422 レスポンスはフィールド名と制約値を返す → フィールド上限が `500` と判明
|
|
342
|
+
|
|
343
|
+
### フェーズ2: 攻撃実行ログ
|
|
344
|
+
|
|
345
|
+
#### A. Pydantic バイパス攻撃
|
|
346
|
+
|
|
347
|
+
```json
|
|
348
|
+
// A-1: values に文字列を混入
|
|
349
|
+
{"values": [1, "abc", 3]}
|
|
350
|
+
```
|
|
351
|
+
**結果**: 422 `{"detail": [{"type": "int_parsing", ...}]}` ✅ 耐えた
|
|
352
|
+
|
|
353
|
+
```json
|
|
354
|
+
// A-2: values に float(int に自動変換されるか)
|
|
355
|
+
{"values": [1.9, 2.1]}
|
|
356
|
+
```
|
|
357
|
+
**結果**: 200 `{"results": [1, 4]}` — Pydantic v2 は `float` を `int` に変換(切り捨て)する型強制。`1.9 → 1, 2.1 → 2`。セキュリティ境界では問題なし(計算デモのみ)。⚠️ 予期しない動作(要注意)
|
|
358
|
+
|
|
359
|
+
```json
|
|
360
|
+
// A-3: num_tasks に 0 を送る
|
|
361
|
+
{"num_tasks": 0, "increments_each": 1}
|
|
362
|
+
```
|
|
363
|
+
**結果**: 422 `ge=1` 制約で拒否。 ✅ 耐えた
|
|
364
|
+
|
|
365
|
+
```json
|
|
366
|
+
// A-4: delay に NaN を送る
|
|
367
|
+
{"value": 1, "delay": NaN, "timeout": 5.0}
|
|
368
|
+
```
|
|
369
|
+
**結果**: JSON NaN は invalid JSON → 400 Bad Request。JSONパーサーレベルで拒否。 ✅ 耐えた
|
|
370
|
+
|
|
371
|
+
#### B. ビジネスロジック攻撃
|
|
372
|
+
|
|
373
|
+
```json
|
|
374
|
+
// B-1: timeout < delay でタスク継続試み
|
|
375
|
+
{"value": 3, "delay": 5.0, "timeout": 0.1}
|
|
376
|
+
```
|
|
377
|
+
**結果**: 200 `{"timed_out": true}` — wait_for がキャンセルして安全に返る。 ✅ 耐えた
|
|
378
|
+
|
|
379
|
+
```json
|
|
380
|
+
// B-2: cancel エンドポイントで delay=0.0(即完了前キャンセル)
|
|
381
|
+
{"value": 3, "delay": 0.0}
|
|
382
|
+
```
|
|
383
|
+
**結果**: 200。`cancelled` または `done` のどちらかになる(スケジューリング依存)。両方安全に処理される。 ✅ 耐えた
|
|
384
|
+
|
|
385
|
+
```json
|
|
386
|
+
// B-3: semaphore limit > num_tasks
|
|
387
|
+
{"num_tasks": 3, "limit": 100}
|
|
388
|
+
```
|
|
389
|
+
**結果**: 200。`limit = max(1, min(100, 3)) = 3` にクランプされる。active_peak ≤ 3。 ✅ 耐えた
|
|
390
|
+
|
|
391
|
+
#### C. 境界値・エッジケース攻撃
|
|
392
|
+
|
|
393
|
+
```json
|
|
394
|
+
// C-1: values に 500 要素ちょうど(上限ちょうど)
|
|
395
|
+
{"values": list(range(500))}
|
|
396
|
+
```
|
|
397
|
+
**結果**: 200。正常処理。 ✅ 耐えた
|
|
398
|
+
|
|
399
|
+
```json
|
|
400
|
+
// C-2: values に 501 要素(上限超え)
|
|
401
|
+
{"values": list(range(501))}
|
|
402
|
+
```
|
|
403
|
+
**結果**: 422。 ✅ 耐えた
|
|
404
|
+
|
|
405
|
+
```json
|
|
406
|
+
// C-3: delay=5.0(上限ちょうど)+ timeout=10.0(上限ちょうど)
|
|
407
|
+
{"value": 1, "delay": 5.0, "timeout": 10.0}
|
|
408
|
+
```
|
|
409
|
+
**結果**: 200(5秒待ちで完了)。上限内なので処理される。 ✅ 耐えた(想定通り)
|
|
410
|
+
|
|
411
|
+
```json
|
|
412
|
+
// C-4: values = [] (空リスト)
|
|
413
|
+
{"values": []}
|
|
414
|
+
```
|
|
415
|
+
**結果**: 200 `{"results": [], "task_count": 0}`。 ✅ 耐えた
|
|
416
|
+
|
|
417
|
+
```json
|
|
418
|
+
// C-5: values に最大値 10000 と最小値 -10000
|
|
419
|
+
{"values": [10000, -10000]}
|
|
420
|
+
```
|
|
421
|
+
**結果**: 200 `{"results": [100000000, 100000000]}`。 ✅ 耐えた
|
|
422
|
+
|
|
423
|
+
#### D. 情報収集攻撃(エラーメッセージ解析)
|
|
424
|
+
|
|
425
|
+
```json
|
|
426
|
+
// D-1: 存在しないフィールドを送る(Mass Assignment)
|
|
427
|
+
{"values": [1, 2], "secret_flag": true}
|
|
428
|
+
```
|
|
429
|
+
**結果**: 200。`secret_flag` は無視される(Pydantic `extra="ignore"`)。内部情報は漏れない。 ✅ 耐えた
|
|
430
|
+
|
|
431
|
+
```json
|
|
432
|
+
// D-2: 意図的に422を引き起こしてスタックトレースを見る
|
|
433
|
+
{"values": "not-a-list"}
|
|
434
|
+
```
|
|
435
|
+
**結果**: 422 `{"detail": [{"type": "list_type", ...}]}`。フィールド名と型エラーのみ。パスやモジュール情報は含まれない。 ✅ 耐えた
|
|
436
|
+
|
|
437
|
+
#### E. DoS 試み
|
|
438
|
+
|
|
439
|
+
```python
|
|
440
|
+
# E-1: 許容範囲内の最大タスク数を複数同時送信
|
|
441
|
+
# num_tasks=100, increments_each=500 → 50,000 インクリメント
|
|
442
|
+
```
|
|
443
|
+
**結果**: asyncio の単一スレッドなので同時接続しても直列化される。負荷は制御可能。 ✅ 耐えた
|
|
444
|
+
|
|
445
|
+
```python
|
|
446
|
+
# E-2: delay=5.0 のタイムアウトリクエストを大量並列送信試み
|
|
447
|
+
# (単一 TestClient では直列)
|
|
448
|
+
```
|
|
449
|
+
**結果**: 上限 `delay=5.0` が守られている限り、各リクエストは最大 5 秒で完了。 ✅ 耐えた
|
|
450
|
+
|
|
451
|
+
### フェーズ3: 攻撃まとめ
|
|
452
|
+
|
|
453
|
+
| 攻撃カテゴリ | 試みた攻撃数 | 突破 | 耐えた | 予期しない動作 |
|
|
454
|
+
|---|---|---|---|---|
|
|
455
|
+
| Pydantic バイパス | 4 | 0 | 4 | 1 (float→int 型強制) |
|
|
456
|
+
| ビジネスロジック | 3 | 0 | 3 | 0 |
|
|
457
|
+
| 境界値/エッジ | 5 | 0 | 5 | 0 |
|
|
458
|
+
| 情報収集 | 2 | 0 | 2 | 0 |
|
|
459
|
+
| DoS | 2 | 0 | 2 | 0 |
|
|
460
|
+
|
|
461
|
+
**攻撃耐性評価**: 堅牢
|
|
462
|
+
**発見した弱点**: `float → int` 型強制(Pydantic v2 デフォルト動作)。計算デモでは無害だが、金融計算などの精度要求があるフィールドでは `ConfigDict(strict=True)` が必要。Issue 不要(FT176 の `parse_decimal_safe` で既に文書化済み)。
|
|
463
|
+
|
|
464
|
+
---
|
|
465
|
+
|
|
466
|
+
## Follow-up Issues
|
|
467
|
+
|
|
468
|
+
今回の FT で発見した問題を同 FT PR 内で即時対応済み。
|
|
469
|
+
|
|
470
|
+
| 対応内容 | 対応方法 |
|
|
471
|
+
|---|---|
|
|
472
|
+
| F-1: 未使用インポート(Any, BatchResult) | ruff --fix で自動修正 |
|
|
473
|
+
| F-2: dict[str, int] invariant | `{k: v for k, v in result.items()}` で変換 |
|
|
474
|
+
|
|
475
|
+
新規 Issue: なし(セキュリティ診断・ペンテスト共に問題なし)
|
|
476
|
+
|
|
477
|
+
---
|
|
478
|
+
|
|
479
|
+
## まとめ
|
|
480
|
+
|
|
481
|
+
asyncio の主要パターン(gather / wait / wait_for / Task / Lock / Event / Semaphore / Queue / Condition / TaskGroup / as_completed / run_in_executor)を 14 エンドポイント・48 テストで検証した。
|
|
482
|
+
|
|
483
|
+
セキュリティ診断(API4 リソース消費・非同期レースコンディション・イベントループブロック)は全通過。クラッカーペンテスト 16 攻撃中 突破 0・1 件の予期しない動作(float→int 型強制)を観察したが、計算デモの範囲では無害。
|
|
484
|
+
|
|
485
|
+
FT188(threading)→ FT190(multiprocessing)→ FT191(concurrent.futures)→ FT192(asyncio)の並行処理 4 部作が完結。次の FT193 は asyncio の発展的なパターン(aiohttp 等)またはデータ処理系モジュールに進む。
|
|
@@ -225,6 +225,7 @@
|
|
|
225
225
|
| [FT189](2026-05-field-trial-189.md) | subprocess モジュール — 安全なプロセス実行・stdin/stdout 制御・ストリーミング | 🔒 | [#524](https://github.com/hideyukiMORI/nene2-python/issues/524) |
|
|
226
226
|
| [FT190](2026-05-field-trial-190.md) | multiprocessing モジュール — プロセスベース並行処理・共有状態・プロセスプール | | |
|
|
227
227
|
| [FT191](2026-05-field-trial-191.md) | concurrent.futures モジュール — ThreadPoolExecutor / ProcessPoolExecutor / Future | | |
|
|
228
|
+
| [FT192](2026-05-field-trial-192.md) | asyncio モジュール — コルーチン・タスク・Lock・Event・Semaphore・Queue・TaskGroup | 🔒🔍 | |
|
|
228
229
|
|
|
229
230
|
---
|
|
230
231
|
|
|
@@ -236,8 +237,8 @@ FT3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 60, 6
|
|
|
236
237
|
|
|
237
238
|
## クラッカーペンテスト実施済み一覧(🔍)
|
|
238
239
|
|
|
239
|
-
FT172, FT176, FT180, FT184, FT188
|
|
240
|
+
FT172, FT176, FT180, FT184, FT188, FT192
|
|
240
241
|
|
|
241
242
|
---
|
|
242
243
|
|
|
243
|
-
*最終更新: 2026-05-21 (
|
|
244
|
+
*最終更新: 2026-05-21 (FT192 / v1.8.64)*
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# TODO — current
|
|
2
2
|
|
|
3
3
|
最終更新: 2026-05-21
|
|
4
|
-
現状: **v1.8.
|
|
4
|
+
現状: **v1.8.64 安定版 / フィールドトライアルループ継続中(FT192 完了)**
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
## 状態サマリー
|
|
9
9
|
|
|
10
|
-
v1.8.
|
|
11
|
-
フィールドトライアルループは
|
|
10
|
+
v1.8.64 完了済み。FT192(asyncio — コルーチン・タスク・Lock・Event・Semaphore・Queue・TaskGroup、セキュリティ診断+クラッカーペンテスト実施)を含む FT192 件を実施済み。
|
|
11
|
+
フィールドトライアルループは FT193 以降も継続中。
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
@@ -28,6 +28,7 @@ v1.8.63 完了済み。FT191(concurrent.futures — ThreadPoolExecutor / Proce
|
|
|
28
28
|
|
|
29
29
|
| バージョン | 主な内容 |
|
|
30
30
|
|---|---|
|
|
31
|
+
| v1.8.64 | FT192: asyncio — コルーチン・タスク・Lock・Event・Semaphore・Queue・TaskGroup(診断+ペンテスト) |
|
|
31
32
|
| v1.8.63 | FT191: concurrent.futures — ThreadPoolExecutor / ProcessPoolExecutor / Future |
|
|
32
33
|
| v1.8.62 | FT190: multiprocessing — プロセスベース並行処理・共有状態・プロセスプール |
|
|
33
34
|
| v1.8.61 | バックログ Issue 一括解消(CLAUDE.md ルール更新・FT サンドボックス修正・ドキュメント追記) |
|
|
@@ -54,12 +55,12 @@ v1.8.63 完了済み。FT191(concurrent.futures — ThreadPoolExecutor / Proce
|
|
|
54
55
|
|
|
55
56
|
## フィールドトライアル進捗
|
|
56
57
|
|
|
57
|
-
**実施済み**: FT1〜
|
|
58
|
+
**実施済み**: FT1〜FT192(全 192 件)
|
|
58
59
|
|
|
59
60
|
索引: [`docs/field-trials/INDEX.md`](../field-trials/INDEX.md)
|
|
60
61
|
|
|
61
62
|
**次のアクション**:
|
|
62
|
-
-
|
|
63
|
+
- FT193 以降を継続(FT193 は 193 % 3 = 1 → 診断なし、193 % 4 = 1 → ペンテストなし)
|
|
63
64
|
|
|
64
65
|
---
|
|
65
66
|
|
|
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
|
{nene2_python-1.8.63 → nene2_python-1.8.64}/alembic/versions/001_create_notes_and_tags_tables.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|