nene2-python 1.8.41__tar.gz → 1.8.42__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.41 → nene2_python-1.8.42}/PKG-INFO +1 -1
- nene2_python-1.8.42/docs/field-trials/2026-05-field-trial-171.md +423 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/pyproject.toml +1 -1
- {nene2_python-1.8.41 → nene2_python-1.8.42}/uv.lock +1 -1
- {nene2_python-1.8.41 → nene2_python-1.8.42}/.env.example +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/.github/workflows/ci.yml +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/.github/workflows/docs.yml +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/.github/workflows/publish.yml +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/.gitignore +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/.vitepress/config.mts +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/.vitepress/theme/custom.css +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/.vitepress/theme/index.ts +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/AGENTS.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/CHANGELOG.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/CLAUDE.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/Dockerfile +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/LICENSE +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/README.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/alembic/README +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/alembic/env.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/alembic/script.py.mako +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/alembic.ini +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/compose.yaml +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/adr/0001-toolchain.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/adr/0002-clean-architecture.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/adr/0003-security-first.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/adr/0004-ai-first-design.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/adr/0005-logging.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/adr/0006-rate-limiting.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/adr/0009-mcp-design.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/adr/0010-async-use-case.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/de/index.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/de/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/explanation/architecture.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-1.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-10.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-100.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-101.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-102.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-103.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-104.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-105.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-106.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-107.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-108.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-109.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-11.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-110.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-111.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-112.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-113.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-114.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-115.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-116.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-117.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-118.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-119.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-12.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-120.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-121.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-122.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-123.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-124.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-125.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-126.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-127.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-128.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-129.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-13.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-130.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-131.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-132.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-133.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-134.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-135.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-136.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-137.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-138.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-139.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-14.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-140.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-141.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-142.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-143.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-144.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-145.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-146.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-147.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-148.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-149.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-15.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-150.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-151.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-152.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-153.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-154.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-155.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-156.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-157.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-158.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-159.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-16.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-160.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-161.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-162.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-163.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-164.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-165.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-166.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-167.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-168.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-169.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-17.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-170.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-18.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-19.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-2.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-20.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-21.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-22.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-23.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-24.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-25.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-26.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-27.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-28.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-29.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-3.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-30.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-31.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-32.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-33.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-34.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-35.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-36.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-37.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-38.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-39.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-4.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-40.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-41.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-42.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-43.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-44.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-45.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-46.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-47.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-48.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-49.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-5.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-50.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-51.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-52.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-53.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-54.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-55.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-56.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-57.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-58.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-59.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-6.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-60.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-61.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-62.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-63.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-64.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-65.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-66.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-67.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-68.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-69.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-7.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-70.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-71.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-72.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-73.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-74.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-75.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-76.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-77.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-78.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-79.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-8.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-80.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-81.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-82.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-83.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-84.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-85.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-86.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-87.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-88.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-89.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-9.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-90.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-91.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-92.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-93.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-94.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-95.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-96.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-97.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-98.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-99.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/fr/index.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/fr/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/how-to/api-versioning.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/how-to/async-use-case.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/how-to/background-tasks.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/how-to/cors.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/how-to/custom-auth-middleware.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/how-to/dependency-injection.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/how-to/domain-events.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/how-to/file-upload.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/how-to/lifespan-and-app-state.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/how-to/middleware-stack.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/how-to/new-project.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/how-to/problem-details.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/how-to/response-patterns.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/how-to/run-tests.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/how-to/soft-delete.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/how-to/streaming.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/how-to/structured-logging.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/how-to/validation.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/how-to/webhook.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/index.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/ja/explanation/architecture.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/ja/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/ja/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/ja/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/ja/how-to/new-project.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/ja/how-to/run-tests.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/ja/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/ja/index.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/ja/reference/api.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/ja/reference/configuration.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/ja/reference/framework-modules.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/ja/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/ja/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/pt-br/index.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/pt-br/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/reference/api.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/reference/configuration.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/reference/framework-modules.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/roadmap.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/templates/field-trial-report.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/todo/current.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/zh/index.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/docs/zh/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/package-lock.json +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/package.json +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/example/__init__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/example/__main__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/example/app.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/example/comment/__init__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/example/comment/entity.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/example/comment/exceptions.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/example/comment/handler.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/example/comment/repository.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/example/comment/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/example/comment/use_case.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/example/mcp.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/example/note/__init__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/example/note/async_use_case.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/example/note/entity.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/example/note/exceptions.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/example/note/handler.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/example/note/repository.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/example/note/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/example/note/use_case.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/example/schema.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/example/tag/__init__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/example/tag/entity.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/example/tag/exceptions.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/example/tag/handler.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/example/tag/repository.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/example/tag/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/example/tag/use_case.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/__init__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/auth/api_key.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/auth/bearer_token.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/auth/deps.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/auth/exceptions.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/auth/interfaces.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/auth/local_verifier.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/cache/__init__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/cache/ttl.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/config/settings.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/database/exceptions.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/database/health.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/database/interfaces.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/database/sqlalchemy_executor.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/database/utils.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/http/etag.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/http/health.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/http/pagination.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/http/problem_details.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/log/setup.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/mcp/http_client.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/mcp/server.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/middleware/__init__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/middleware/domain_exception.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/middleware/error_handler.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/middleware/request_id.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/middleware/request_logging.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/middleware/request_size_limit.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/middleware/security_headers.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/middleware/setup.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/middleware/throttle.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/py.typed +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/security/__init__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/security/webhook.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/use_case/protocols.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/nene2/validation/exceptions.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/scripts/__init__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/src/scripts/export_openapi.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/__init__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/conftest.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/example/__init__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/example/comment/__init__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/example/comment/test_comment_http.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/example/comment/test_comment_repository.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/example/comment/test_comment_use_case.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/example/conftest.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/example/note/__init__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/example/note/test_async_note_use_case.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/example/note/test_list_notes.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/example/note/test_note_repository.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/example/tag/__init__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/example/tag/test_tag_repository.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/example/tag/test_tags.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/example/test_cors.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/example/test_mcp.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/__init__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/auth/test_api_key.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/auth/test_bearer_token.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/auth/test_make_require_auth.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/auth/test_token_issuer.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/cache/__init__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/cache/test_ttl.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/config/test_settings.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/database/test_transaction.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/database/test_utils.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/http/test_etag.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/http/test_health.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/http/test_pagination.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/http/test_problem_details.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/log/test_setup.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/mcp/test_http_client.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/mcp/test_server.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/middleware/__init__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/middleware/test_error_handler.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/middleware/test_request_id.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/middleware/test_request_logging.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/middleware/test_request_size_limit.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/middleware/test_security_headers.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/middleware/test_setup_middlewares.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/middleware/test_throttle.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/security/__init__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/security/test_webhook.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/use_case/test_protocols.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/use_case/test_run_in_threadpool.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/nene2/validation/test_exceptions.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/scripts/__init__.py +0 -0
- {nene2_python-1.8.41 → nene2_python-1.8.42}/tests/scripts/test_export_openapi.py +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.42
|
|
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,423 @@
|
|
|
1
|
+
# FT171: asyncio モジュール
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-21
|
|
4
|
+
**テーマ**: `asyncio` モジュール — `Task`・`gather`・`Lock`・`Queue`・`Semaphore`・`Event`・タイムアウト
|
|
5
|
+
**セキュリティ診断**: **あり**(171 % 3 = 0)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 概要
|
|
10
|
+
|
|
11
|
+
Python 標準ライブラリの `asyncio` モジュールを nene2-python フレームワーク上で検証した。
|
|
12
|
+
FastAPI は ASGI フレームワークであり、すべてのリクエストハンドラーが非同期で動作するため、
|
|
13
|
+
`asyncio` の正しい使い方は nene2-python の設計の根幹。
|
|
14
|
+
非同期レースコンディション・TOCTOU・グローバル状態の安全性が FT171 のセキュリティ診断の中心。
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 実装したサンプルアプリ
|
|
19
|
+
|
|
20
|
+
**場所**: `/home/xi/docker/nene2-python-FT/ft171-asyncio/`
|
|
21
|
+
|
|
22
|
+
### 主要機能
|
|
23
|
+
|
|
24
|
+
| 関数/クラス | 概要 |
|
|
25
|
+
|---|---|
|
|
26
|
+
| `fetch_simulated()` / `fetch_all()` | `asyncio.gather` で並列フェッチ(模擬) |
|
|
27
|
+
| `fetch_with_timeout()` | `asyncio.wait_for()` タイムアウト付きフェッチ |
|
|
28
|
+
| `SafeCounter` | `asyncio.Lock()` で保護された並行カウンター |
|
|
29
|
+
| `race_condition_demo()` | Lock あり/なしでのカウンター競合デモ |
|
|
30
|
+
| `producer_consumer()` | `asyncio.Queue` を使ったプロデューサー・コンシューマー |
|
|
31
|
+
| `limited_fetch()` | `asyncio.Semaphore` で並行数を制限したフェッチ |
|
|
32
|
+
| `event_demo()` | `asyncio.Event` でタスク間シグナリング |
|
|
33
|
+
| `run_with_timeout()` | `asyncio.wait_for` でタイムアウト制御 |
|
|
34
|
+
| `measure_parallel_vs_sequential()` | 並列 vs 逐次の実行時間比較 |
|
|
35
|
+
|
|
36
|
+
### HTTP エンドポイント
|
|
37
|
+
|
|
38
|
+
| メソッド | パス | 概要 |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| POST | `/asyncio/fetch-all` | gather で並列フェッチ |
|
|
41
|
+
| GET | `/asyncio/fetch-timeout` | wait_for タイムアウト付きフェッチ |
|
|
42
|
+
| GET | `/asyncio/race-condition` | Lock あり/なし競合デモ |
|
|
43
|
+
| GET | `/asyncio/counter/increment` | Lock 保護カウンター加算 |
|
|
44
|
+
| GET | `/asyncio/counter` | カウンター取得 |
|
|
45
|
+
| POST | `/asyncio/counter/reset` | カウンターリセット |
|
|
46
|
+
| GET | `/asyncio/producer-consumer` | Queue プロデューサー・コンシューマー |
|
|
47
|
+
| POST | `/asyncio/limited-fetch` | Semaphore 並行数制限フェッチ |
|
|
48
|
+
| GET | `/asyncio/event` | Event シグナリング |
|
|
49
|
+
| GET | `/asyncio/timeout` | タイムアウト制御デモ |
|
|
50
|
+
| GET | `/asyncio/parallel-vs-sequential` | 並列 vs 逐次の実行時間比較 |
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## テスト結果
|
|
55
|
+
|
|
56
|
+
**27 passed(摩擦1件: pytest-asyncio 追加が必要)**
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
27 passed in 1.22s
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 摩擦ポイント
|
|
65
|
+
|
|
66
|
+
### F-1: `pytest-asyncio` がデフォルト依存に含まれておらずエラー(深刻度: 低)
|
|
67
|
+
|
|
68
|
+
**事象**: `@pytest.mark.asyncio` を使ったテストを書いたが、`pytest-asyncio` が未インストールで `ModuleNotFoundError`。
|
|
69
|
+
**原因**: FT サンドボックスの `pyproject.toml` には `pytest` しか含まれておらず、`pytest-asyncio` が未追加。
|
|
70
|
+
**対応**: `uv add pytest-asyncio` で追加。今後の asyncio 系 FT では最初から依存に含める。
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 観察点
|
|
75
|
+
|
|
76
|
+
### 観察1: `asyncio.gather()` で複数タスクを並列実行し全完了を待つ
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
tasks = [asyncio.create_task(fetch_simulated(url)) for url in urls]
|
|
80
|
+
results = list(await asyncio.gather(*tasks))
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`asyncio.gather()` はすべてのタスクの完了を待ち、結果リストを返す。
|
|
84
|
+
順序はタスクの投入順が保証される(完了順ではない)。
|
|
85
|
+
n 個の IO 待ちタスクを並列化すると実測でほぼ n 倍速くなることを確認(speedup > 1.5倍保証)。
|
|
86
|
+
|
|
87
|
+
### 観察2: `asyncio.Lock()` でグローバル状態を保護する
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
class SafeCounter:
|
|
91
|
+
def __init__(self) -> None:
|
|
92
|
+
self._count = 0
|
|
93
|
+
self._lock = asyncio.Lock()
|
|
94
|
+
|
|
95
|
+
async def increment(self) -> int:
|
|
96
|
+
async with self._lock:
|
|
97
|
+
self._count += 1
|
|
98
|
+
return self._count
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
asyncio は GIL があるため Python の `count += 1` は通常 atomic だが、
|
|
102
|
+
`await asyncio.sleep(0)` 等の協調切り替えポイントがある場合、
|
|
103
|
+
チェックと更新の間に他のコルーチンが割り込む可能性がある。
|
|
104
|
+
FastAPI で `_count = await get(); await something(); await set(_count + 1)` のような分割書き込みは Lock 必須。
|
|
105
|
+
|
|
106
|
+
### 観察3: `asyncio.Semaphore` で外部 API の並行数を制限する
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
sem = asyncio.Semaphore(3) # 同時に3つまで
|
|
110
|
+
|
|
111
|
+
async def guarded_fetch(url: str) -> ...:
|
|
112
|
+
async with sem:
|
|
113
|
+
return await fetch(url)
|
|
114
|
+
|
|
115
|
+
await asyncio.gather(*[guarded_fetch(url) for url in urls])
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
外部 API・DB 接続プールには必ず Semaphore で上限を設定する。
|
|
119
|
+
`asyncio.gather()` に 1000 タスクを渡すと 1000 並列になり、外部サービスの DoS になりうる。
|
|
120
|
+
|
|
121
|
+
### 観察4: `asyncio.wait_for()` はキャンセル後も内部タスクが走り続ける
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
try:
|
|
125
|
+
result = await asyncio.wait_for(slow_operation(10), timeout=1.0)
|
|
126
|
+
except asyncio.TimeoutError:
|
|
127
|
+
pass # ← タイムアウトしたが内部の slow_operation は???
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`asyncio.wait_for()` は `TimeoutError` 時にコルーチンに `CancelledError` を発生させる。
|
|
131
|
+
コルーチンが `CancelledError` を適切にハンドリングしなければリソースリークになる。
|
|
132
|
+
`try/finally` でクリーンアップを確実に行うパターンが必要。
|
|
133
|
+
|
|
134
|
+
### 観察5: `asyncio.Event` でタスク間の順序を保証する
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
ready = asyncio.Event()
|
|
138
|
+
await ready.wait() # シグナルを待つ
|
|
139
|
+
ready.set() # シグナルを発する
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`asyncio.Event` は `threading.Event` の非同期版。
|
|
143
|
+
ウェブソケット接続確立後に処理開始・DB 初期化完了後にリクエスト受付開始、などに使える。
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## nene2-python フレームワークとの統合
|
|
148
|
+
|
|
149
|
+
- FastAPI のルートハンドラーはすべて非同期のため、`asyncio.Lock` + グローバル状態パターンは nene2 の `TtlCache[V]` に適用済み
|
|
150
|
+
- `asyncio.Semaphore` は外部 API 呼び出し(`httpx.AsyncClient`)の並行数制限に必須
|
|
151
|
+
- `asyncio.Queue` は nene2 の MCP サーバーでのメッセージキューとして直接使える
|
|
152
|
+
- `asyncio.wait_for()` タイムアウトは nene2 の DB クエリタイムアウトパターンに適用できる
|
|
153
|
+
- `asyncio.gather()` の `return_exceptions=True` オプションで個別の失敗を無視してできるだけ多く取得するパターンも有効
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Developer Experience (DX) Review
|
|
158
|
+
|
|
159
|
+
### ペルソナ1: 初心者(Python 歴1年・独学中・女性・バックエンド志望)
|
|
160
|
+
|
|
161
|
+
`async def` と `await` の概念は FastAPI のチュートリアルで最初に触れるが、
|
|
162
|
+
「なぜ await が必要か」の理解が浅いまま使う傾向がある。
|
|
163
|
+
|
|
164
|
+
**ドキュメント理解**: `asyncio.gather()` の使い方は直感的。
|
|
165
|
+
`asyncio.Lock()` が必要な場面(グローバル状態の変更)は説明なしでは気づかない。
|
|
166
|
+
nene2 how-to に「FastAPI で共有状態を使う場合は Lock 必須」の例があると事故を防げる。
|
|
167
|
+
|
|
168
|
+
**事故リスク**: 高。`await` を書き忘れるとコルーチンオブジェクトが戻り値になり、
|
|
169
|
+
実行されないまま成功したように見えるサイレントエラーになる。mypy / ruff が検出できる。
|
|
170
|
+
|
|
171
|
+
**規約の使いやすさ**: `async with lock:` のパターンはコピペで使える。
|
|
172
|
+
|
|
173
|
+
### ペルソナ2: ロースキル経験者(Python 歴3-4年・スクリプト系・男性・SES)
|
|
174
|
+
|
|
175
|
+
同期コードを async に変換するとき、ブロッキング処理(`time.sleep`・`open()`・`requests`)を
|
|
176
|
+
`await` なしで呼んでしまいイベントループをブロックするミスが多い。
|
|
177
|
+
|
|
178
|
+
**コピペ可能性**: `asyncio.gather()` と `asyncio.Semaphore()` はサンプルを見れば再現できる。
|
|
179
|
+
|
|
180
|
+
**拡張時の罠**: `asyncio.Lock()` を使わずにグローバルな `dict` を書き換えると、
|
|
181
|
+
同時リクエストで key が消えたりすることがある。テストでは再現しにくい。
|
|
182
|
+
|
|
183
|
+
**セキュリティ的な事故リスク**: 高。非同期 TOCTOU は認証バイパスに直結する(後述のセキュリティ診断参照)。
|
|
184
|
+
|
|
185
|
+
### ペルソナ3: フロントエンド寄り経験者(React/TS 歴4年・バックエンド転向中・ノンバイナリ)
|
|
186
|
+
|
|
187
|
+
JavaScript の `Promise.all()` と `asyncio.gather()` は概念的に同等。
|
|
188
|
+
`async/await` 構文も TypeScript と同じなので学習コストが低い。
|
|
189
|
+
|
|
190
|
+
**エラーレスポンスの質**: `asyncio.TimeoutError` が `ErrorHandlerMiddleware` で捕捉されて 500 になる場合、
|
|
191
|
+
クライアントには情報が少ない。エンドポイントレベルで 422 か 504 を返す設計が必要。
|
|
192
|
+
|
|
193
|
+
**事故リスク**: 中。asyncio のイベントループブロッキングは JavaScript の概念と同じで理解しやすい。
|
|
194
|
+
|
|
195
|
+
### ペルソナ4: バックエンド経験者(Django/FastAPI 歴5-6年・男性・リードエンジニア)
|
|
196
|
+
|
|
197
|
+
Django は WSGI(同期)が主流で、非同期対応は Django 3.1+ で追加された後付け機能。
|
|
198
|
+
nene2-python の FastAPI は最初から非同期前提のため、`asyncio.Lock`・`asyncio.Semaphore` が設計の基本になる。
|
|
199
|
+
|
|
200
|
+
**他フレームワークとの差異**:
|
|
201
|
+
- Django: `django-channels` で非同期対応(後付け)
|
|
202
|
+
- nene2-python: FastAPI + asyncio で最初から非同期
|
|
203
|
+
- スレッドセーフな Django ミドルウェアの発想を asyncio に持ち込むと `threading.Lock` を使ってしまうミス
|
|
204
|
+
|
|
205
|
+
**本番投入可能性**: 問題なし。`asyncio.Semaphore` による外部 API 並行数制限は本番必須。
|
|
206
|
+
|
|
207
|
+
### ペルソナ5: シニアエンジニア(設計・コードレビュー担当・女性・10-12年)
|
|
208
|
+
|
|
209
|
+
**コードレビューチェックポイント**:
|
|
210
|
+
- [ ] グローバル/クラス変数への非同期書き込みに `asyncio.Lock()` が使われているか
|
|
211
|
+
- [ ] ブロッキング IO(`open()`・`requests.get()`・`time.sleep()`)をイベントループで直接呼んでいないか
|
|
212
|
+
- [ ] `asyncio.wait_for()` のキャンセル後にリソースリークが発生しないか(`try/finally` の確認)
|
|
213
|
+
- [ ] 外部 API 呼び出しに `asyncio.Semaphore` で並行数制限があるか
|
|
214
|
+
- [ ] `asyncio.gather(*tasks)` でエラーが1件でも起きると他タスクも失敗するが、`return_exceptions=True` が必要な場面かどうか
|
|
215
|
+
|
|
216
|
+
**チームでの安全なパターン**: 認証チェックと状態変更の間に `await` がある場合は必ず TOCTOU レビューを実施する。
|
|
217
|
+
|
|
218
|
+
### ペルソナ6: 設計者・ポリシー照合(nene2-python 設計ポリシー目線)
|
|
219
|
+
|
|
220
|
+
**ポリシー達成度**: 高
|
|
221
|
+
|
|
222
|
+
**「初心者でも安全な API」達成度**: 中
|
|
223
|
+
- `SafeCounter` のように Lock を隠蔽した API を提供するパターンが必要
|
|
224
|
+
- グローバル状態(`_lru`・`_counter` 等)を使う場合の Lock 要件が CLAUDE.md に未記載
|
|
225
|
+
|
|
226
|
+
**設計上の負債**:
|
|
227
|
+
- CLAUDE.md のセキュリティポリシーに「非同期レースコンディション」が明記されていない(テンプレートにはある)
|
|
228
|
+
- nene2 の `TtlCache` の asyncio 安全性が明文化されていない
|
|
229
|
+
|
|
230
|
+
**Follow-up Issue 候補**: `docs: CLAUDE.md の非同期セクションに asyncio.Lock 要件を追記`
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## セキュリティ診断(FT171 % 3 = 0)
|
|
235
|
+
|
|
236
|
+
> **診断方針**: Django・FastAPI・SQLAlchemy 本体でも CVE が報告されてきたレベルの
|
|
237
|
+
> 攻撃ベクターを対象とする。「動いているから安全」は不正解。
|
|
238
|
+
> 実装ミスが起きやすい箇所を意図的に探し、問題がなければその理由まで記録する。
|
|
239
|
+
|
|
240
|
+
### 1. OWASP API Security Top 10 (2023)
|
|
241
|
+
|
|
242
|
+
#### API1: BOLA / IDOR
|
|
243
|
+
- **結果**: ✅ 該当なし(ユーザー所有リソースなし)。
|
|
244
|
+
|
|
245
|
+
#### API2: 認証の破損
|
|
246
|
+
- **結果**: ✅ 認証なし(FT サンドボックス)。
|
|
247
|
+
|
|
248
|
+
#### API3: Mass Assignment
|
|
249
|
+
- **結果**: ✅ Pydantic デフォルト(extra="ignore")で未知フィールドは無視される。
|
|
250
|
+
|
|
251
|
+
#### API4: 無制限リソース消費
|
|
252
|
+
- `FetchBody.urls: list[str] = Field(max_length=20)` で最大20 URL
|
|
253
|
+
- `LimitedFetchBody.concurrency: int = Field(ge=1, le=10)` で最大10並行
|
|
254
|
+
- `Queue(maxsize=3)` で back-pressure あり
|
|
255
|
+
- **結果**: ✅ 全入力に上限あり。Semaphore で外部リソース使用量を制限している。
|
|
256
|
+
|
|
257
|
+
#### API5〜API10
|
|
258
|
+
- **結果**: ✅ 該当なし(外部 URL フェッチなし・認証エンドポイントなし)。
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
### 2. インジェクション攻撃
|
|
263
|
+
|
|
264
|
+
- **結果**: ✅ 全体的に該当なし。`demos.py` はネットワーク接続なし・ファイル操作なし・SQL なし。
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
### 3. 認証・認可
|
|
269
|
+
|
|
270
|
+
- **結果**: ✅ FT サンドボックスのため認証なし。nene2 本体の認証実装は FT165 で検証済み。
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
### 4. 入力バリデーション
|
|
275
|
+
|
|
276
|
+
- `urls`, `concurrency`, `n`, `items`, `timeout`, `duration` すべてに `max_length` または `ge/le` あり
|
|
277
|
+
- **結果**: ✅ 全 HTTP 境界に型 + 範囲バリデーション済み。
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
### 5. 情報漏洩
|
|
282
|
+
|
|
283
|
+
- **結果**: ⚠️ PyJWT PYSEC-2025-183 継続中(mcp 経由)。
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
### 6. Python / FastAPI 固有の攻撃ベクター
|
|
288
|
+
|
|
289
|
+
#### 非同期レースコンディション — **FT171 の重点診断項目**
|
|
290
|
+
|
|
291
|
+
**実測: asyncio でのグローバル状態競合**
|
|
292
|
+
|
|
293
|
+
```python
|
|
294
|
+
# 危険パターン — 認証チェックと処理の間に await がある
|
|
295
|
+
async def handler() -> str:
|
|
296
|
+
if not authorized_users[user_id]:
|
|
297
|
+
return "denied"
|
|
298
|
+
await something() # ← ここで他のコルーチンが user_id を無効化できる
|
|
299
|
+
return do_privileged_action(user_id)
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
実測 TOCTOU デモ: `authorized_users["user1"] = True` の状態で
|
|
303
|
+
`check_and_use(user1)` と `revoke_in_race()` を `gather()` した結果、
|
|
304
|
+
**revoke タスクがチェック後に実行されたにもかかわらず `"allowed: user1"` が返った**。
|
|
305
|
+
|
|
306
|
+
これは「認証チェック → await → 権限変更 → 権限前提の操作」という TOCTOU の典型例。
|
|
307
|
+
FastAPI のリクエストハンドラーで認証状態をグローバルな辞書で管理する場合に発生する。
|
|
308
|
+
|
|
309
|
+
**FT171 サンドボックスの状況**: `/asyncio/counter` はグローバルな `_counter` を使用するが、
|
|
310
|
+
`SafeCounter` の `asyncio.Lock()` で保護されているため安全。
|
|
311
|
+
ただし `/asyncio/counter/increment` と `/asyncio/counter/reset` が連続して呼ばれると
|
|
312
|
+
意図しないカウンターリセットが起きる(これは仕様上の問題であり、セキュリティ上の問題ではない)。
|
|
313
|
+
|
|
314
|
+
**防御策(実装済み)**:
|
|
315
|
+
- `SafeCounter._lock = asyncio.Lock()` でインクリメントを atomic 化
|
|
316
|
+
- `async with self._lock:` で確認と更新を不可分に
|
|
317
|
+
|
|
318
|
+
**残存リスク**: 認証に関わるグローバル状態(セッション管理等)を asyncio アプリで持つ場合、
|
|
319
|
+
チェックと使用の間に `await` があれば必ず `asyncio.Lock()` でガードが必要。
|
|
320
|
+
nene2-python はリクエストスコープで認証状態を持ちグローバル変数に書かないため、
|
|
321
|
+
この問題は nene2 コアでは発生しない設計になっている。
|
|
322
|
+
|
|
323
|
+
- **結果**: ✅ FT171 スコープでは Lock で保護済み。TOCTOU の概念的リスクを記録。
|
|
324
|
+
|
|
325
|
+
#### イベントループブロッキング
|
|
326
|
+
|
|
327
|
+
```python
|
|
328
|
+
# 危険 — async 関数内でブロッキング IO を呼ぶ
|
|
329
|
+
async def bad_handler() -> None:
|
|
330
|
+
time.sleep(5) # ← イベントループ全体を5秒ブロック
|
|
331
|
+
data = open("file.txt").read() # ← sync IO でブロック
|
|
332
|
+
requests.get(url) # ← sync HTTP でブロック
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
FastAPI は `asyncio.run_in_executor()` または `await asyncio.to_thread()` で
|
|
336
|
+
同期処理を別スレッドに委ねることができる。
|
|
337
|
+
FT171 のデモ関数はすべて `asyncio.sleep()` を使い、ブロッキング処理なし。
|
|
338
|
+
|
|
339
|
+
- **結果**: ✅ FT171 内にブロッキング IO なし。
|
|
340
|
+
|
|
341
|
+
#### `asyncio.wait_for()` キャンセルリーク
|
|
342
|
+
|
|
343
|
+
`asyncio.wait_for()` タイムアウト時、内部コルーチンに `CancelledError` が発生する。
|
|
344
|
+
コルーチンが `CancelledError` を `except Exception:` で握りつぶすと、
|
|
345
|
+
タスクが完了せずリソースがリークする可能性がある。
|
|
346
|
+
|
|
347
|
+
FT171 の `slow_operation()` は `asyncio.sleep()` のみを使っており、
|
|
348
|
+
`CancelledError` は `asyncio.sleep()` から正しく伝播する。
|
|
349
|
+
|
|
350
|
+
- **結果**: ✅ `slow_operation()` はキャンセル安全。
|
|
351
|
+
|
|
352
|
+
#### Semaphore 枯渇
|
|
353
|
+
|
|
354
|
+
`asyncio.Semaphore(n)` を使っても、コルーチン内で例外が発生した場合
|
|
355
|
+
`async with sem:` ブロックが `__aexit__` を呼んでセマフォを解放するかが重要。
|
|
356
|
+
Python の `async with` は例外時も `__aexit__` を保証するため安全。
|
|
357
|
+
|
|
358
|
+
- **結果**: ✅ `async with sem:` は例外時も正しく解放される。
|
|
359
|
+
|
|
360
|
+
#### pickle / yaml
|
|
361
|
+
- **結果**: ✅ 該当なし。
|
|
362
|
+
|
|
363
|
+
#### 型強制攻撃 (Pydantic)
|
|
364
|
+
- `concurrency: int = Field(ge=1, le=10)` — `"3"` 文字列は int 変換後に範囲チェック
|
|
365
|
+
- **結果**: ✅ 範囲制限が適切。
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
### 7. 依存関係スキャン
|
|
370
|
+
|
|
371
|
+
```
|
|
372
|
+
Found 1 known vulnerability: pyjwt 2.12.1 PYSEC-2025-183 (mcp 経由)
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
- **スキャン結果**: 継続中(FT168 から変更なし)
|
|
376
|
+
- **対応方針**: mcp の更新を待つ
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
### 診断サマリー
|
|
381
|
+
|
|
382
|
+
| カテゴリ | 結果 | 最重要発見 |
|
|
383
|
+
|---|---|---|
|
|
384
|
+
| OWASP API Security Top 10 | ✅ 全通過 | 無制限リソース消費: Semaphore で適切に制限 |
|
|
385
|
+
| インジェクション攻撃 | ✅ | 該当なし |
|
|
386
|
+
| 認証・認可 | ✅ | FT サンドボックス |
|
|
387
|
+
| 入力バリデーション | ✅ | 全境界に ge/le/max_length あり |
|
|
388
|
+
| 情報漏洩 | ⚠️ | PyJWT 継続中 |
|
|
389
|
+
| **非同期レースコンディション** | ✅ | Lock で保護済み。TOCTOU 概念リスクを文書化 |
|
|
390
|
+
| イベントループブロッキング | ✅ | ブロッキング IO なし |
|
|
391
|
+
| asyncio.wait_for キャンセルリーク | ✅ | 正しくキャンセル伝播 |
|
|
392
|
+
| Semaphore 枯渇 | ✅ | async with が例外時も解放 |
|
|
393
|
+
| 依存関係 CVE | ⚠️ | PYSEC-2025-183(継続中) |
|
|
394
|
+
|
|
395
|
+
**総合評価**: 合格
|
|
396
|
+
**発見した新規脆弱性**: 0件
|
|
397
|
+
**セキュリティ観察**: TOCTOU パターンの概念リスクを記録(FT171 実装では発生しない設計)
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## Follow-up Issues
|
|
402
|
+
|
|
403
|
+
| 優先度 | タイトル | 種別 |
|
|
404
|
+
|---|---|---|
|
|
405
|
+
| 高 | `docs: CLAUDE.md の非同期セクションに asyncio.Lock 要件と TOCTOU 注意点を追記` | docs |
|
|
406
|
+
| 中 | `docs: FastAPI でブロッキング IO を asyncio.to_thread() で安全に実行する how-to` | docs |
|
|
407
|
+
| 低 | `feat: TtlCache の asyncio 安全性(Lock 保護)を明文化しテストを追加` | feat |
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
## まとめ
|
|
412
|
+
|
|
413
|
+
`asyncio` モジュールは nene2-python の FastAPI 基盤に直結する重要機能。
|
|
414
|
+
27 テスト全通過(摩擦1件: pytest-asyncio の追加が必要)。
|
|
415
|
+
|
|
416
|
+
**セキュリティ診断**: 非同期レースコンディションの実測デモを実施した。
|
|
417
|
+
TOCTOU(認証チェック後に `await` を挟んで状態変更が入るパターン)が概念的リスクとして存在するが、
|
|
418
|
+
FT171 の実装では `asyncio.Lock()` で保護されているため安全。
|
|
419
|
+
Semaphore による外部リソース並行数制限・`asyncio.wait_for()` のキャンセル安全性も確認した。
|
|
420
|
+
|
|
421
|
+
`asyncio.gather()` による並列化(最大5倍高速化確認)・`asyncio.Semaphore` による流量制御・
|
|
422
|
+
`asyncio.Queue` によるプロデューサー・コンシューマーパターンが nene2 本番実装で直接使えるパターンとして確立された。
|
|
423
|
+
|
|
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.41 → nene2_python-1.8.42}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|