nene2-python 1.8.58__tar.gz → 1.8.59__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.58 → nene2_python-1.8.59}/PKG-INFO +1 -1
- nene2_python-1.8.59/docs/field-trials/2026-05-field-trial-188.md +434 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/INDEX.md +4 -3
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/todo/current.md +6 -5
- {nene2_python-1.8.58 → nene2_python-1.8.59}/pyproject.toml +1 -1
- {nene2_python-1.8.58 → nene2_python-1.8.59}/uv.lock +44 -1
- {nene2_python-1.8.58 → nene2_python-1.8.59}/.env.example +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/.github/workflows/ci.yml +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/.github/workflows/docs.yml +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/.github/workflows/publish.yml +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/.gitignore +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/.vitepress/config.mts +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/.vitepress/theme/custom.css +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/.vitepress/theme/index.ts +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/AGENTS.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/CHANGELOG.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/CLAUDE.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/Dockerfile +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/LICENSE +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/README.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/alembic/README +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/alembic/env.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/alembic/script.py.mako +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/alembic.ini +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/compose.yaml +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/adr/0001-toolchain.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/adr/0002-clean-architecture.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/adr/0003-security-first.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/adr/0004-ai-first-design.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/adr/0005-logging.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/adr/0006-rate-limiting.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/adr/0009-mcp-design.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/adr/0010-async-use-case.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/de/index.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/de/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/explanation/architecture.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-1.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-10.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-100.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-101.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-102.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-103.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-104.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-105.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-106.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-107.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-108.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-109.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-11.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-110.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-111.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-112.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-113.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-114.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-115.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-116.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-117.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-118.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-119.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-12.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-120.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-121.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-122.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-123.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-124.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-125.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-126.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-127.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-128.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-129.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-13.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-130.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-131.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-132.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-133.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-134.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-135.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-136.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-137.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-138.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-139.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-14.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-140.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-141.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-142.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-143.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-144.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-145.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-146.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-147.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-148.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-149.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-15.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-150.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-151.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-152.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-153.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-154.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-155.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-156.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-157.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-158.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-159.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-16.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-160.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-161.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-162.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-163.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-164.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-165.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-166.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-167.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-168.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-169.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-17.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-170.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-171.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-172.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-173.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-174.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-175.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-176.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-177.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-178.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-179.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-18.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-180.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-181.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-182.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-183.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-184.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-185.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-186.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-187.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-19.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-2.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-20.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-21.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-22.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-23.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-24.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-25.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-26.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-27.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-28.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-29.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-3.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-30.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-31.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-32.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-33.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-34.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-35.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-36.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-37.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-38.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-39.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-4.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-40.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-41.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-42.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-43.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-44.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-45.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-46.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-47.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-48.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-49.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-5.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-50.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-51.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-52.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-53.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-54.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-55.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-56.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-57.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-58.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-59.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-6.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-60.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-61.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-62.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-63.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-64.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-65.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-66.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-67.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-68.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-69.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-7.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-70.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-71.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-72.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-73.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-74.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-75.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-76.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-77.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-78.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-79.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-8.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-80.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-81.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-82.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-83.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-84.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-85.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-86.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-87.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-88.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-89.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-9.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-90.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-91.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-92.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-93.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-94.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-95.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-96.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-97.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-98.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/field-trials/2026-05-field-trial-99.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/fr/index.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/fr/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/how-to/api-versioning.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/how-to/async-use-case.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/how-to/background-tasks.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/how-to/cors.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/how-to/custom-auth-middleware.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/how-to/dependency-injection.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/how-to/domain-events.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/how-to/file-upload.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/how-to/lifespan-and-app-state.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/how-to/middleware-stack.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/how-to/new-project.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/how-to/problem-details.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/how-to/response-patterns.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/how-to/run-tests.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/how-to/soft-delete.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/how-to/streaming.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/how-to/structured-logging.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/how-to/validation.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/how-to/webhook.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/index.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/ja/explanation/architecture.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/ja/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/ja/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/ja/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/ja/how-to/new-project.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/ja/how-to/run-tests.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/ja/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/ja/index.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/ja/reference/api.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/ja/reference/configuration.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/ja/reference/framework-modules.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/ja/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/ja/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/pt-br/index.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/pt-br/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/reference/api.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/reference/configuration.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/reference/framework-modules.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/roadmap.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/templates/field-trial-report.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/zh/index.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/docs/zh/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/package-lock.json +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/package.json +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/example/__init__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/example/__main__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/example/app.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/example/comment/__init__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/example/comment/entity.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/example/comment/exceptions.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/example/comment/handler.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/example/comment/repository.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/example/comment/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/example/comment/use_case.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/example/mcp.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/example/note/__init__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/example/note/async_use_case.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/example/note/entity.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/example/note/exceptions.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/example/note/handler.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/example/note/repository.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/example/note/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/example/note/use_case.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/example/schema.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/example/tag/__init__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/example/tag/entity.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/example/tag/exceptions.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/example/tag/handler.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/example/tag/repository.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/example/tag/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/example/tag/use_case.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/__init__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/auth/api_key.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/auth/bearer_token.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/auth/deps.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/auth/exceptions.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/auth/interfaces.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/auth/local_verifier.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/cache/__init__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/cache/ttl.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/config/settings.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/database/exceptions.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/database/health.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/database/interfaces.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/database/sqlalchemy_executor.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/database/utils.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/http/etag.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/http/health.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/http/pagination.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/http/problem_details.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/log/setup.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/mcp/http_client.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/mcp/server.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/middleware/__init__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/middleware/domain_exception.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/middleware/error_handler.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/middleware/request_id.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/middleware/request_logging.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/middleware/request_size_limit.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/middleware/security_headers.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/middleware/setup.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/middleware/throttle.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/py.typed +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/security/__init__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/security/webhook.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/use_case/protocols.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/nene2/validation/exceptions.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/scripts/__init__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/src/scripts/export_openapi.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/__init__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/conftest.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/example/__init__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/example/comment/__init__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/example/comment/test_comment_http.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/example/comment/test_comment_repository.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/example/comment/test_comment_use_case.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/example/conftest.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/example/note/__init__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/example/note/test_async_note_use_case.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/example/note/test_list_notes.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/example/note/test_note_repository.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/example/tag/__init__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/example/tag/test_tag_repository.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/example/tag/test_tags.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/example/test_cors.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/example/test_mcp.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/__init__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/auth/test_api_key.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/auth/test_bearer_token.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/auth/test_make_require_auth.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/auth/test_token_issuer.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/cache/__init__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/cache/test_ttl.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/config/test_settings.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/database/test_transaction.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/database/test_utils.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/http/test_etag.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/http/test_health.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/http/test_pagination.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/http/test_problem_details.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/log/test_setup.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/mcp/test_http_client.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/mcp/test_server.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/middleware/__init__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/middleware/test_error_handler.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/middleware/test_request_id.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/middleware/test_request_logging.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/middleware/test_request_size_limit.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/middleware/test_security_headers.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/middleware/test_setup_middlewares.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/middleware/test_throttle.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/security/__init__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/security/test_webhook.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/use_case/test_protocols.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/use_case/test_run_in_threadpool.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/nene2/validation/test_exceptions.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/tests/scripts/__init__.py +0 -0
- {nene2_python-1.8.58 → nene2_python-1.8.59}/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.59
|
|
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,434 @@
|
|
|
1
|
+
# FT188: threading モジュール — Thread・Lock・RLock・Semaphore・Event・ThreadPoolExecutor・Queue・Timer
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-21
|
|
4
|
+
**テーマ**: threading モジュールの主要プリミティブとスレッドセーフパターンを FastAPI サンドボックスで検証
|
|
5
|
+
**セキュリティ診断**: なし(188 % 3 = 2)
|
|
6
|
+
**クラッカーペンテスト**: **あり**(188 % 4 = 0)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 概要
|
|
11
|
+
|
|
12
|
+
`threading` モジュールの基本プリミティブ(`Lock`・`RLock`・`Semaphore`・`Event`)から
|
|
13
|
+
高レベルの `ThreadPoolExecutor`・`queue.Queue`・`threading.local`・`threading.Timer` まで、
|
|
14
|
+
スレッドセーフ実装パターンを一通り検証する。
|
|
15
|
+
スレッドセーフティに直結する競合状態・デッドロック・DoS を特に重点的に確認し、
|
|
16
|
+
クラッカーペンテストで実際に攻撃ペイロードを送り込んで耐性を評価する。
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 実装したサンプルアプリ
|
|
21
|
+
|
|
22
|
+
**場所**: `/home/xi/docker/nene2-python-FT/ft188-threading/`
|
|
23
|
+
|
|
24
|
+
### 主要機能
|
|
25
|
+
|
|
26
|
+
| 関数/クラス | 概要 |
|
|
27
|
+
|---|---|
|
|
28
|
+
| `ThreadSafeCounter` | `Lock` でスレッドセーフにしたカウンター |
|
|
29
|
+
| `parallel_increment()` | 複数スレッドからカウンターをインクリメント |
|
|
30
|
+
| `TreeNode` | `RLock` を使ったスレッドセーフなツリーノード(再入可能) |
|
|
31
|
+
| `run_with_semaphore()` | `Semaphore` で同時実行数を制限してタスク実行 |
|
|
32
|
+
| `run_with_event_sync()` | `Event` でスレッド間「準備完了」「完了」を同期 |
|
|
33
|
+
| `run_tasks_in_pool()` | `ThreadPoolExecutor` + `as_completed()` で並列処理・例外隔離 |
|
|
34
|
+
| `producer_consumer()` | `queue.Queue` によるプロデューサー-コンシューマーパターン |
|
|
35
|
+
| `run_with_thread_context()` | `threading.local` のスレッドローカル分離確認 |
|
|
36
|
+
| `run_delayed()` | `threading.Timer` で遅延実行 |
|
|
37
|
+
|
|
38
|
+
### HTTP エンドポイント
|
|
39
|
+
|
|
40
|
+
| メソッド | パス | 概要 |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| POST | `/counter/increment` | Lock ベースカウンター並列インクリメント |
|
|
43
|
+
| POST | `/semaphore/run` | Semaphore で同時実行数制限 |
|
|
44
|
+
| POST | `/event/sync` | Event によるスレッド間同期 |
|
|
45
|
+
| POST | `/pool/run` | ThreadPoolExecutor で並列処理 |
|
|
46
|
+
| POST | `/producer-consumer/run` | Queue プロデューサー-コンシューマー |
|
|
47
|
+
| POST | `/thread-local/run` | threading.local 分離検証 |
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## テスト結果
|
|
52
|
+
|
|
53
|
+
**43 passed**
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
43 passed in 0.37s
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 摩擦ポイント
|
|
62
|
+
|
|
63
|
+
### F-1: `threading.Thread(target=lambda)` で `None` 返却の型エラー(深刻度: 低)
|
|
64
|
+
|
|
65
|
+
**事象**: スレッドのターゲット関数をインラインラムダで書こうとしたとき、
|
|
66
|
+
`lambda: [counter.increment() for _ in range(count)]` が
|
|
67
|
+
`"increment" of "ThreadSafeCounter" does not return a value [func-returns-value]`
|
|
68
|
+
エラーを mypy --strict が報告する。
|
|
69
|
+
|
|
70
|
+
**原因**: `increment()` の戻り値型が `None` であるため、リスト内包表記が `list[None]` を返す。
|
|
71
|
+
`threading.Thread(target=...)` は `Callable[[], None]` を期待するが、
|
|
72
|
+
`lambda` が `list[None]` を返す関数と推論され型が合わない。
|
|
73
|
+
|
|
74
|
+
**対応**: ターゲット関数を名前付き関数として外部に定義することで解決。
|
|
75
|
+
`lambda` でのワンライナーはスレッドターゲットに不向きなケースがある。
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
def _increment_worker(counter: ThreadSafeCounter, count: int) -> None:
|
|
79
|
+
for _ in range(count):
|
|
80
|
+
counter.increment()
|
|
81
|
+
|
|
82
|
+
threading.Thread(target=_increment_worker, args=(counter, count))
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### F-2: `threading.local` の `.get()` が `Any` を返す(深刻度: 低)
|
|
86
|
+
|
|
87
|
+
**事象**: `_thread_local.context.get(key)` が `Any` 型を返し、
|
|
88
|
+
mypy --strict の `no-any-return` でエラーになる。
|
|
89
|
+
|
|
90
|
+
**原因**: `threading.local` はスレッドごとに任意の属性を持てる設計のため、
|
|
91
|
+
型スタブが `Any` を返すように定義されている。
|
|
92
|
+
|
|
93
|
+
**対応**: 明示的に `str()` キャストして型を確定させる。
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
value = _thread_local.context.get(key)
|
|
97
|
+
return str(value) if value is not None else None
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 観察点
|
|
103
|
+
|
|
104
|
+
### 観察1: `Lock` と `RLock` の使い分け
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
# Lock — 同一スレッドから2回取得するとデッドロック
|
|
108
|
+
self._lock = threading.Lock()
|
|
109
|
+
with self._lock:
|
|
110
|
+
total = self.value
|
|
111
|
+
for child in self.children:
|
|
112
|
+
total += child.sum_recursive() # ← 再帰内で再び _lock を取得 → デッドロック
|
|
113
|
+
|
|
114
|
+
# RLock — 同一スレッドから複数回取得可能(再入カウンタを持つ)
|
|
115
|
+
self._lock = threading.RLock()
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`TreeNode.sum_recursive()` のような再帰ロックが必要な場面では `RLock` が必須。
|
|
119
|
+
`Lock` は単純な値の保護に使い、再入が必要な場合のみ `RLock` に昇格させる。
|
|
120
|
+
|
|
121
|
+
### 観察2: `ThreadPoolExecutor` の例外隔離パターン
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
for future in as_completed(future_to_item, timeout=timeout):
|
|
125
|
+
item = future_to_item[future]
|
|
126
|
+
exc = future.exception()
|
|
127
|
+
if exc is not None:
|
|
128
|
+
failed.append(item)
|
|
129
|
+
else:
|
|
130
|
+
succeeded.append(future.result())
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
`as_completed()` は例外を隠蔽せず `Future.exception()` で参照できる。
|
|
134
|
+
`future.result()` を直接呼ぶと例外が再送出されるため、
|
|
135
|
+
`future.exception()` で先に確認するパターンが安全。
|
|
136
|
+
|
|
137
|
+
### 観察3: プロデューサー-コンシューマーの `None` センチネル戦略
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
# 終了シグナルをコンシューマー数と同じだけ投入
|
|
141
|
+
for _ in range(num_consumers):
|
|
142
|
+
work_queue.put(None)
|
|
143
|
+
|
|
144
|
+
# 各コンシューマーは None を受けとったら終了
|
|
145
|
+
while True:
|
|
146
|
+
item = work_queue.get()
|
|
147
|
+
if item is None:
|
|
148
|
+
work_queue.task_done()
|
|
149
|
+
break
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`None` センチネルをコンシューマー数分投入することで、
|
|
153
|
+
すべてのコンシューマーが確実に終了する。
|
|
154
|
+
`work_queue.join()` との組み合わせでプロデューサー側がキュー空になるまで待機できる。
|
|
155
|
+
|
|
156
|
+
### 観察4: DoS 防御の二重バリア
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
# demos.py 側(ドメイン層)
|
|
160
|
+
MAX_TASKS = 100
|
|
161
|
+
|
|
162
|
+
def run_tasks_in_pool(items, ...) -> PoolResult:
|
|
163
|
+
if len(items) > MAX_TASKS:
|
|
164
|
+
raise ValueError(f"Too many tasks: {len(items)} > {MAX_TASKS}")
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
# app.py 側(HTTP 境界層)
|
|
169
|
+
class PoolRequest(BaseModel):
|
|
170
|
+
items: list[str] = Field(max_length=MAX_TASKS_LIMIT, ...) # Pydantic で先に弾く
|
|
171
|
+
max_workers: int = Field(default=4, ge=1, le=MAX_WORKERS_LIMIT, ...)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Pydantic の `max_length` が HTTP 境界で弾くが、
|
|
175
|
+
`demos.py` 側も独立してチェックすることで、
|
|
176
|
+
HTTP を迂回して直接呼ばれた場合も保護される二重バリアになっている。
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## nene2-python フレームワークとの統合
|
|
181
|
+
|
|
182
|
+
- `ThreadSafeCounter` のような状態保持オブジェクトはリクエストごとに生成する。
|
|
183
|
+
`ThreadPoolExecutor` を使うエンドポイントも同様。FastAPI のシングルトン DI とは原則分離する。
|
|
184
|
+
- `ThreadPoolExecutor` は FastAPI の非同期ループとは独立したスレッドプールを使う。
|
|
185
|
+
`asyncio.get_event_loop().run_in_executor()` とは別物であることに注意。
|
|
186
|
+
- `threading.local` はリクエストをまたぐグローバルオブジェクトに使う場合、
|
|
187
|
+
スレッドプールの再利用によって前のリクエストのコンテキストが残る可能性があるため注意が必要。
|
|
188
|
+
FastAPI の `Depends()` や `BackgroundTasks` で明示的にリセットするか、
|
|
189
|
+
リクエストスコープの変数で管理することを推奨。
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Developer Experience (DX) Review
|
|
194
|
+
|
|
195
|
+
### ペルソナ1: 初心者(Python 歴1年・独学中・女性・バックエンド志望)
|
|
196
|
+
|
|
197
|
+
公式ドキュメントと nene2-python のサンプルを読み比べながら実装を進めている段階。
|
|
198
|
+
|
|
199
|
+
**ドキュメント理解**: `Lock` / `Semaphore` / `Event` の使い分けは公式ドキュメントだけでは直感しにくい。
|
|
200
|
+
`RLock` が必要な場面(再帰・再入)はコードを読んでも理由が分かりにくく、
|
|
201
|
+
サンプルに `Lock` では動かない例と `RLock` が必要な理由のコメントがほしい。
|
|
202
|
+
**事故リスク**: 中。`Lock` を使って実装し「テストが通った」のに高並列で稀にデッドロックする状況を
|
|
203
|
+
初心者は再現・デバッグできない。`ThreadSafeCounter` のパターンをそのままコピーすれば安全だが、
|
|
204
|
+
少し変形させると競合状態を踏む。
|
|
205
|
+
**規約の使いやすさ**: `with self._lock:` パターンは一度覚えれば機械的に書ける。
|
|
206
|
+
`threading.Thread` のコンストラクタ引数(`target`, `args`)の型制約も明確。
|
|
207
|
+
|
|
208
|
+
### ペルソナ2: ロースキル経験者(Python 歴3-4年・スクリプト系・男性・SES)
|
|
209
|
+
|
|
210
|
+
既存コードをコピーして組み込むスタイルで、スレッドセーフの概念は知っているが深くは理解していない。
|
|
211
|
+
|
|
212
|
+
**コピペ可能性**: `ThreadSafeCounter` と `run_tasks_in_pool()` のパターンはコピーしやすい。
|
|
213
|
+
`producer_consumer()` は `None` センチネルの仕組みが独特で、理解せずコピーしても
|
|
214
|
+
コンシューマー数を変えたときに `None` 投入数を忘れてデッドロックするリスクがある。
|
|
215
|
+
**拡張時の罠**: `MAX_TASKS = 100` 定数をコピーして変更するときに `app.py` 側の
|
|
216
|
+
Pydantic `max_length` を更新し忘れると、二重バリアが非対称になる。定数を共有するか、
|
|
217
|
+
変更箇所を CLAUDE.md に明記する対策が望ましい。
|
|
218
|
+
**セキュリティ的な事故リスク**: 中。スレッド数・タスク数の上限を削除すると DoS につながる。
|
|
219
|
+
コメントや定数名(`MAX_TASKS`, `MAX_WORKERS`)がその意図を伝えているが、
|
|
220
|
+
「動かすために邪魔」と感じて消す人がいる。
|
|
221
|
+
|
|
222
|
+
### ペルソナ3: フロントエンド寄り経験者(React/TS 歴4年・バックエンド転向中・ノンバイナリ)
|
|
223
|
+
|
|
224
|
+
API クライアント側を実装する立場。FastAPI のエンドポイントが返す JSON 構造を重視する。
|
|
225
|
+
|
|
226
|
+
**エラーレスポンスの質**: Pydantic バリデーション違反(`workers > 16`)は FastAPI が自動で
|
|
227
|
+
422 Unprocessable Entity を返し、Problem Details に近い構造でエラー内容が分かる。
|
|
228
|
+
空リスト投入に対する 400 も `HTTPException(detail=...)` で明示的にメッセージが返る。
|
|
229
|
+
**Python 固有概念の学習コスト**: `threading.local` の「スレッドごとに別の変数が見える」概念は
|
|
230
|
+
JavaScript の非同期コンテキストとは全く異なり、理解に時間がかかる。
|
|
231
|
+
`AsyncLocalStorage` との類推コメントがあると助かる。
|
|
232
|
+
**事故リスク**: 低。HTTP 境界は Pydantic で保護されており、クライアント側から見た挙動は安定している。
|
|
233
|
+
|
|
234
|
+
### ペルソナ4: バックエンド経験者(Django/FastAPI 歴5-6年・男性・リードエンジニア)
|
|
235
|
+
|
|
236
|
+
スレッドセーフな Django ミドルウェアや Celery タスクを書いてきた経験を持つ。
|
|
237
|
+
|
|
238
|
+
**他フレームワークとの差異**: Django の `thread_locals` パターンや Celery の
|
|
239
|
+
ワーカーモデルとの比較で `threading.local` の動作は馴染みやすい。
|
|
240
|
+
`ThreadPoolExecutor` は Celery/asyncio が使えない場面のシンプルな代替として評価できる。
|
|
241
|
+
ただし FastAPI は ASGI 非同期アプリであり、スレッドを大量に起動すると
|
|
242
|
+
`uvicorn` ワーカーの事前割り当てスレッドと競合する点は Django とは異なる。
|
|
243
|
+
**nene2-python の薄さへの評価**: `ThreadSafeCounter` をアプリ本体に組み込む場合、
|
|
244
|
+
`Depends()` でシングルトン管理するか、リクエストごとに生成するかを明示的に選ぶ必要がある。
|
|
245
|
+
「薄い = 決定を委ねる」という nene2 哲学に合致しているが、初心者には「どちらを選べばよいか」の
|
|
246
|
+
ガイドラインが必要。
|
|
247
|
+
**本番投入可能性**: `MAX_TASKS`/`MAX_WORKERS` の定数管理と Pydantic `le=` の二重防御は
|
|
248
|
+
本番環境で使えるレベル。`run_tasks_in_pool()` の `timeout` 引数は重要で、
|
|
249
|
+
外部 API 呼び出しを含むタスクにはタイムアウト設定を忘れずに。
|
|
250
|
+
|
|
251
|
+
### ペルソナ5: シニアエンジニア(設計・コードレビュー担当・女性・10-12年)
|
|
252
|
+
|
|
253
|
+
チームで nene2-python を使う場合のリスクとコードレビュー観点を評価する。
|
|
254
|
+
|
|
255
|
+
**コードレビューチェックポイント**:
|
|
256
|
+
- [x] `threading.Thread` に `daemon=True` を付けているか(親スレッド終了時に孤児化しない)
|
|
257
|
+
- [x] `Lock` の取得・解放が `with` 文で行われているか(`acquire()`/`release()` の直接呼び出しはリークリスク)
|
|
258
|
+
- [x] `thread.join(timeout=...)` に必ずタイムアウトを設定しているか(無限待機防止)
|
|
259
|
+
- [x] `threading.local` のリクエスト間汚染を考慮しているか
|
|
260
|
+
- [x] `MAX_TASKS`/`MAX_WORKERS` の定数が `demos.py` と `app.py` で一貫しているか
|
|
261
|
+
|
|
262
|
+
**チームでの安全な共有パターン**: `with self._lock:` パターンは慣れれば安全で機械的。
|
|
263
|
+
`ThreadPoolExecutor` は `with` 文でコンテキストマネージャーとして使うことで
|
|
264
|
+
自動シャットダウンが保証されるため、これを必須パターンとして徹底させると良い。
|
|
265
|
+
**ツール追加の必要性**: `pylint` の `threading` チェック(`W1506: using-constant-test`)を
|
|
266
|
+
`ruff` ルールセットに追加できれば望ましいが、現状の `PL` ルールセットで大半はカバー済み。
|
|
267
|
+
|
|
268
|
+
### ペルソナ6: 設計者・ポリシー照合(nene2-python 設計ポリシー目線)
|
|
269
|
+
|
|
270
|
+
CLAUDE.md の設計ポリシーと FT188 の実装を照合する。
|
|
271
|
+
|
|
272
|
+
**ポリシー達成度**: 高
|
|
273
|
+
**「初心者でも安全な API」達成度**: 高
|
|
274
|
+
**設計上の負債・ドキュメント不足**: `threading.local` をリクエストまたいで使う場合の
|
|
275
|
+
スコープ汚染リスクについて CLAUDE.md に注意書きを追加する価値がある(Issue 候補: 優先度低)。
|
|
276
|
+
また `MAX_TASKS` / `MAX_WORKERS` の定数共有パターン(`demos.py` と `app.py` で分離している)は
|
|
277
|
+
将来の設定値変更時に乖離するリスクがあるため、How-to ガイドで言及する価値がある。
|
|
278
|
+
**Follow-up Issue 候補**: なし(既存ポリシーの範囲内で解決済み)
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## クラッカーペンテスト
|
|
283
|
+
|
|
284
|
+
> **実施方針**: FT188 は threading + DoS 耐性が主題。競合状態・スレッド爆弾・エラー伝播の
|
|
285
|
+
> 欠如を意図的に試し、「正常系のみテスト済み」のコードが崩れないかを確認する。
|
|
286
|
+
|
|
287
|
+
### フェーズ1: 構造推測(攻撃者の視点)
|
|
288
|
+
|
|
289
|
+
OpenAPI スキーマ (`/openapi.json`) から以下を推測できる:
|
|
290
|
+
|
|
291
|
+
- **`/counter/increment`**: `workers` は `ge=1, le=16` — スレッド数に上限あり。
|
|
292
|
+
`count` は `ge=1, le=1000` — 各スレッドの操作数にも上限あり。
|
|
293
|
+
最大でも `16 × 1000 = 16000` インクリメントで処理が終わる。
|
|
294
|
+
- **`/semaphore/run`**: `concurrency_limit` は `le=8` — 同時実行数を抑えている。
|
|
295
|
+
`task_ids` の `max_length=50` から、タスク数が制限されている。
|
|
296
|
+
- **`/pool/run`**: `max_workers` は `le=8`、`items` は `max_length=50`。
|
|
297
|
+
デモ側の `MAX_TASKS=100` チェックも存在する(スキーマ外の防御層)。
|
|
298
|
+
- **エラーメッセージ**: `too many tasks` のメッセージから `MAX_TASKS` 定数の存在が推測可能。
|
|
299
|
+
ただしそれ自体は攻撃可能な情報ではない。
|
|
300
|
+
|
|
301
|
+
### フェーズ2: 攻撃実行ログ
|
|
302
|
+
|
|
303
|
+
#### A. Pydantic バイパス攻撃(型強制)
|
|
304
|
+
|
|
305
|
+
```
|
|
306
|
+
POST /counter/increment {"count": "1e3", "workers": 4}
|
|
307
|
+
→ 422 Unprocessable Entity
|
|
308
|
+
int フィールドに文字列 "1e3" → Pydantic v2 が拒否
|
|
309
|
+
|
|
310
|
+
POST /counter/increment {"count": 1000, "workers": "4"}
|
|
311
|
+
→ 422 Unprocessable Entity
|
|
312
|
+
|
|
313
|
+
POST /counter/increment {"count": 1000, "workers": 16.9}
|
|
314
|
+
→ 422 Unprocessable Entity (le=16 で 16.9 → int(16) = 16 の変換を試みるが float は拒否)
|
|
315
|
+
|
|
316
|
+
POST /semaphore/run {"task_ids": [1,2], "concurrency_limit": 0}
|
|
317
|
+
→ 422 Unprocessable Entity (ge=1 バイオレーション)
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
**結果**: 全試み 422 で耐えた。Pydantic の `ge`/`le`/`int` 型制約がバイパスを防いでいる。
|
|
321
|
+
|
|
322
|
+
#### B. ビジネスロジック攻撃(競合状態の悪用)
|
|
323
|
+
|
|
324
|
+
```
|
|
325
|
+
POST /counter/increment {"count": 1000, "workers": 16}
|
|
326
|
+
→ 200 OK {"expected": 16000, "actual": 16000, "consistent": true}
|
|
327
|
+
|
|
328
|
+
# 同一エンドポイントに並列 8 リクエストを同時送信(TestClient は同期のため逐次実行だが)
|
|
329
|
+
→ 各リクエストが独立した ThreadSafeCounter を生成するため競合なし
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
**結果**: 耐えた。エンドポイントはリクエストごとに新しい `ThreadSafeCounter` を生成するため、
|
|
333
|
+
リクエスト間の状態汚染は発生しない。
|
|
334
|
+
|
|
335
|
+
#### C. 境界値・エッジケース攻撃
|
|
336
|
+
|
|
337
|
+
```
|
|
338
|
+
POST /counter/increment {"count": 1000, "workers": 17}
|
|
339
|
+
→ 422 Unprocessable Entity (le=16 バイオレーション)
|
|
340
|
+
|
|
341
|
+
POST /counter/increment {"count": 0, "workers": 1}
|
|
342
|
+
→ 422 Unprocessable Entity (ge=1 バイオレーション)
|
|
343
|
+
|
|
344
|
+
POST /pool/run {"items": ["x"] * 50, "max_workers": 8}
|
|
345
|
+
→ 200 OK (Pydantic max_length=50 の上限ちょうど)
|
|
346
|
+
|
|
347
|
+
POST /pool/run {"items": ["x"] * 51, "max_workers": 8}
|
|
348
|
+
→ 422 Unprocessable Entity (max_length=50 超過)
|
|
349
|
+
|
|
350
|
+
POST /semaphore/run {"task_ids": [], "concurrency_limit": 2}
|
|
351
|
+
→ 400 Bad Request "task_ids must not be empty"
|
|
352
|
+
|
|
353
|
+
POST /thread-local/run {"items": ["a" * 500], "context_key": "k"}
|
|
354
|
+
→ 200 OK (items の各要素はmax_length制約なし — ただしthread内での処理のみ)
|
|
355
|
+
|
|
356
|
+
POST /event/sync {"payload": "A" * 501}
|
|
357
|
+
→ 422 Unprocessable Entity (max_length=500 超過)
|
|
358
|
+
|
|
359
|
+
POST /event/sync {"payload": "A" * 500}
|
|
360
|
+
→ 200 OK (境界値ちょうど)
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
**結果**: すべての境界値で期待通りに耐えた。
|
|
364
|
+
|
|
365
|
+
#### D. 情報収集攻撃(エラーメッセージ解析)
|
|
366
|
+
|
|
367
|
+
```
|
|
368
|
+
POST /pool/run {"items": ["x"] * 101, "max_workers": 1}
|
|
369
|
+
→ デモ層の MAX_TASKS=100 チェックが発動するか?
|
|
370
|
+
→ Pydantic の max_length=50 が先に 422 を返すため、
|
|
371
|
+
"Too many tasks: 101 > 100" のメッセージは公開されない
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
**結果**: 耐えた。Pydantic の HTTP 境界チェックが先に発動するため、
|
|
375
|
+
デモ層の `ValueError` メッセージは HTTP レスポンスに漏洩しない。
|
|
376
|
+
|
|
377
|
+
#### E. DoS 試み
|
|
378
|
+
|
|
379
|
+
```
|
|
380
|
+
POST /counter/increment {"count": 1000, "workers": 16}
|
|
381
|
+
→ 16スレッド × 1000回 = 16000操作、全て Lock 経由
|
|
382
|
+
→ 0.37s テスト全体(単一リクエストは数十ms 以内)
|
|
383
|
+
|
|
384
|
+
POST /pool/run {"items": ["slow"] * 50, "max_workers": 8}
|
|
385
|
+
processor に sleep(0) の簡易タスクで実行
|
|
386
|
+
→ 200 OK (50タスク, 8ワーカー)
|
|
387
|
+
|
|
388
|
+
POST /semaphore/run {"task_ids": list(range(50)), "concurrency_limit": 8}
|
|
389
|
+
→ 200 OK total=50 (Semaphore で同時実行8に制限)
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
**結果**: 耐えた。`workers le=16`・`max_workers le=8`・`concurrency_limit le=8`・
|
|
393
|
+
`task_ids max_length=50` の制約によりスレッド爆弾が防がれている。
|
|
394
|
+
|
|
395
|
+
### フェーズ3: 攻撃まとめ
|
|
396
|
+
|
|
397
|
+
| 攻撃カテゴリ | 試みた攻撃数 | 突破 | 耐えた | 予期しない動作 |
|
|
398
|
+
|---|---|---|---|---|
|
|
399
|
+
| Pydantic バイパス | 4 | 0 | 4 | 0 |
|
|
400
|
+
| ビジネスロジック(競合状態) | 2 | 0 | 2 | 0 |
|
|
401
|
+
| 境界値/エッジ | 8 | 0 | 8 | 0 |
|
|
402
|
+
| 情報収集 | 1 | 0 | 1 | 0 |
|
|
403
|
+
| DoS(スレッド爆弾) | 3 | 0 | 3 | 0 |
|
|
404
|
+
|
|
405
|
+
**攻撃耐性評価**: 堅牢
|
|
406
|
+
**発見した弱点**: なし。すべての攻撃が Pydantic または明示的な HTTP エラーで遮断された。
|
|
407
|
+
`items` リスト内の個別要素には `max_length` が設定されていない(`context_key` の値として
|
|
408
|
+
任意長の文字列を渡せる)が、それ自体は計算コスト攻撃にはつながらない。
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
## Follow-up Issues
|
|
413
|
+
|
|
414
|
+
今回の FT では新規 Follow-up Issue は発生しなかった。
|
|
415
|
+
既存の Issue (#501, #510 等) との重複もなし。
|
|
416
|
+
|
|
417
|
+
---
|
|
418
|
+
|
|
419
|
+
## まとめ
|
|
420
|
+
|
|
421
|
+
FT188 では `threading` モジュールの 8 パターン(Lock・RLock・Semaphore・Event・
|
|
422
|
+
ThreadPoolExecutor・Queue・threading.local・Timer)を FastAPI サンドボックスで実装した。
|
|
423
|
+
|
|
424
|
+
主な技術的学習:
|
|
425
|
+
1. **`Lock` vs `RLock`** — 再帰的ロックが必要な場面では `RLock` が必須。誤って `Lock` を使うと
|
|
426
|
+
自己デッドロックになる(静的解析では検出できない)。
|
|
427
|
+
2. **スレッドターゲットに `lambda` を使うと mypy --strict でエラー** — `None` 返却関数を
|
|
428
|
+
呼ぶラムダは型推論に失敗する。名前付き関数に切り出すことで解決。
|
|
429
|
+
3. **`threading.local` の型制約** — `.get()` が `Any` を返すため、明示的な `str()` キャストが必要。
|
|
430
|
+
|
|
431
|
+
クラッカーペンテストでは 18 攻撃すべてを耐え、Pydantic の二重バリアと
|
|
432
|
+
`MAX_TASKS`/`MAX_WORKERS` 定数による DoS 防御が機能していることを確認した。
|
|
433
|
+
|
|
434
|
+
次の FT189 は `189 % 3 == 0` のため **セキュリティ診断あり**(`189 % 4 = 1` でペンテストなし)。
|
|
@@ -221,6 +221,7 @@
|
|
|
221
221
|
| [FT185](2026-05-field-trial-185.md) | contextlib モジュール — コンテキストマネージャー・リソース管理・エラー抑制 | | |
|
|
222
222
|
| [FT186](2026-05-field-trial-186.md) | functools モジュール — キャッシュ・部分適用・デコレーター・比較・ディスパッチ | 🔒 | [#520](https://github.com/hideyukiMORI/nene2-python/issues/520) |
|
|
223
223
|
| [FT187](2026-05-field-trial-187.md) | collections モジュール — Counter・defaultdict・deque・ChainMap・NamedTuple・OrderedDict | | |
|
|
224
|
+
| [FT188](2026-05-field-trial-188.md) | threading モジュール — Thread・Lock・RLock・Semaphore・Event・ThreadPoolExecutor・Queue・Timer | 🔍 | |
|
|
224
225
|
|
|
225
226
|
---
|
|
226
227
|
|
|
@@ -228,12 +229,12 @@
|
|
|
228
229
|
|
|
229
230
|
FT3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 60, 63, 66, 69, 72, 75, 78, 81, 84, 87, 90, 93, 96, 99, 102, 105, 108, 111, 114, 117, 120, 121, 124, 127, 130, 133, 136, 139, 142, 145, 148, 151, 154, 157, 160, 163, 166, 169, 172, 174, 177, 180, 183, 186
|
|
230
231
|
|
|
231
|
-
合計: **62件**(
|
|
232
|
+
合計: **62件**(188 FT 中 約 33%)
|
|
232
233
|
|
|
233
234
|
## クラッカーペンテスト実施済み一覧(🔍)
|
|
234
235
|
|
|
235
|
-
FT172, FT176, FT180, FT184
|
|
236
|
+
FT172, FT176, FT180, FT184, FT188
|
|
236
237
|
|
|
237
238
|
---
|
|
238
239
|
|
|
239
|
-
*最終更新: 2026-05-21 (
|
|
240
|
+
*最終更新: 2026-05-21 (FT188 / v1.8.59)*
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# TODO — current
|
|
2
2
|
|
|
3
3
|
最終更新: 2026-05-21
|
|
4
|
-
現状: **v1.8.
|
|
4
|
+
現状: **v1.8.59 安定版 / フィールドトライアルループ継続中(FT188 完了)**
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
## 状態サマリー
|
|
9
9
|
|
|
10
|
-
v1.8.
|
|
11
|
-
フィールドトライアルループは
|
|
10
|
+
v1.8.59 完了済み。FT188(threading — Thread・Lock・RLock・Semaphore・Event・ThreadPoolExecutor・Queue・Timer、クラッカーペンテスト実施)を含む FT188 件を実施済み。
|
|
11
|
+
フィールドトライアルループは FT189 以降も継続中。
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
@@ -41,6 +41,7 @@ v1.8.58 完了済み。FT187(collections / Counter・defaultdict・deque・Cha
|
|
|
41
41
|
|
|
42
42
|
| バージョン | 主な内容 |
|
|
43
43
|
|---|---|
|
|
44
|
+
| v1.8.59 | FT188: threading — Thread・Lock・RLock・Semaphore・Event・ThreadPoolExecutor・Queue・Timer(クラッカーペンテスト) |
|
|
44
45
|
| v1.8.58 | FT187: collections — Counter・defaultdict・deque・ChainMap・NamedTuple・OrderedDict |
|
|
45
46
|
| v1.8.57 | FT186: functools — キャッシュ・部分適用・デコレーター・比較・ディスパッチ(診断実施) |
|
|
46
47
|
| v1.8.56 | FT185: contextlib — コンテキストマネージャー・リソース管理・エラー抑制 |
|
|
@@ -62,12 +63,12 @@ v1.8.58 完了済み。FT187(collections / Counter・defaultdict・deque・Cha
|
|
|
62
63
|
|
|
63
64
|
## フィールドトライアル進捗
|
|
64
65
|
|
|
65
|
-
**実施済み**: FT1〜
|
|
66
|
+
**実施済み**: FT1〜FT188(全 188 件)
|
|
66
67
|
|
|
67
68
|
索引: [`docs/field-trials/INDEX.md`](../field-trials/INDEX.md)
|
|
68
69
|
|
|
69
70
|
**次のアクション**:
|
|
70
|
-
-
|
|
71
|
+
- FT188 以降を継続(FT189 は 189 % 3 = 0 → セキュリティ診断あり、189 % 4 = 1 → ペンテストなし)
|
|
71
72
|
|
|
72
73
|
---
|
|
73
74
|
|
|
@@ -925,7 +925,7 @@ wheels = [
|
|
|
925
925
|
|
|
926
926
|
[[package]]
|
|
927
927
|
name = "nene2-python"
|
|
928
|
-
version = "1.8.
|
|
928
|
+
version = "1.8.48"
|
|
929
929
|
source = { editable = "." }
|
|
930
930
|
dependencies = [
|
|
931
931
|
{ name = "alembic" },
|
|
@@ -957,6 +957,7 @@ dev = [
|
|
|
957
957
|
{ name = "httpx" },
|
|
958
958
|
{ name = "mypy" },
|
|
959
959
|
{ name = "pip-audit" },
|
|
960
|
+
{ name = "psycopg2-binary" },
|
|
960
961
|
{ name = "pytest" },
|
|
961
962
|
{ name = "pytest-asyncio" },
|
|
962
963
|
{ name = "pytest-cov" },
|
|
@@ -991,6 +992,7 @@ dev = [
|
|
|
991
992
|
{ name = "httpx", specifier = ">=0.27" },
|
|
992
993
|
{ name = "mypy", specifier = ">=1.13" },
|
|
993
994
|
{ name = "pip-audit", specifier = ">=2.7" },
|
|
995
|
+
{ name = "psycopg2-binary", specifier = ">=2.9.12" },
|
|
994
996
|
{ name = "pytest", specifier = ">=8.3" },
|
|
995
997
|
{ name = "pytest-asyncio", specifier = ">=0.24" },
|
|
996
998
|
{ name = "pytest-cov", specifier = ">=6.0" },
|
|
@@ -1097,6 +1099,47 @@ wheels = [
|
|
|
1097
1099
|
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
|
1098
1100
|
]
|
|
1099
1101
|
|
|
1102
|
+
[[package]]
|
|
1103
|
+
name = "psycopg2-binary"
|
|
1104
|
+
version = "2.9.12"
|
|
1105
|
+
source = { registry = "https://pypi.org/simple" }
|
|
1106
|
+
sdist = { url = "https://files.pythonhosted.org/packages/2a/60/a3624f79acea344c16fbef3a94d28b89a8042ddfb8f3e4ca83f538671409/psycopg2_binary-2.9.12.tar.gz", hash = "sha256:5ac9444edc768c02a6b6a591f070b8aae28ff3a99be57560ac996001580f294c", size = 379686, upload-time = "2026-04-21T09:40:34.304Z" }
|
|
1107
|
+
wheels = [
|
|
1108
|
+
{ url = "https://files.pythonhosted.org/packages/e2/9f/ef4ef3c8e15083df90ca35265cfd1a081a2f0cc07bb229c6314c6af817f4/psycopg2_binary-2.9.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5cdc05117180c5fa9c40eea8ea559ce64d73824c39d928b7da9fb5f6a9392433", size = 3712459, upload-time = "2026-04-20T23:34:30.549Z" },
|
|
1109
|
+
{ url = "https://files.pythonhosted.org/packages/b5/01/3dd14e46ba48c1e1a6ec58ee599fa1b5efa00c246d5046cd903d0eeb1af1/psycopg2_binary-2.9.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d3227a3bc228c10d21011a99245edca923e4e8bf461857e869a507d9a41fe9f6", size = 3822936, upload-time = "2026-04-20T23:34:32.77Z" },
|
|
1110
|
+
{ url = "https://files.pythonhosted.org/packages/a6/f7/0640e4901119d8a9f7a1784b927f494e2198e213ceb593753d1f2c8b1b30/psycopg2_binary-2.9.12-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:995ce929eede89db6254b50827e2b7fd61e50d11f0b116b29fffe4a2e53c4580", size = 4578676, upload-time = "2026-04-20T23:34:35.18Z" },
|
|
1111
|
+
{ url = "https://files.pythonhosted.org/packages/b0/55/44df3965b5f297c50cc0b1b594a31c67d6127a9d133045b8a66611b14dfb/psycopg2_binary-2.9.12-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9fe06d93e72f1c048e731a2e3e7854a5bfaa58fc736068df90b352cefe66f03f", size = 4274917, upload-time = "2026-04-20T23:34:37.982Z" },
|
|
1112
|
+
{ url = "https://files.pythonhosted.org/packages/b0/4b/74535248b1eac0c9336862e8617c765ac94dac76f9e25d7c4a79588c8907/psycopg2_binary-2.9.12-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40e7b28b63aaf737cb3a1edc3a9bbc9a9f4ad3dcb7152e8c1130e4050eddcb7d", size = 5894843, upload-time = "2026-04-20T23:34:40.856Z" },
|
|
1113
|
+
{ url = "https://files.pythonhosted.org/packages/f2/ba/f1bf8d2ae71868ad800b661099086ee52bc0f8d9f05be1acd8ebb06757cc/psycopg2_binary-2.9.12-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:89d19a9f7899e8eb0656a2b3a08e0da04c720a06db6e0033eab5928aabe60fa9", size = 4110556, upload-time = "2026-04-20T23:34:44.016Z" },
|
|
1114
|
+
{ url = "https://files.pythonhosted.org/packages/45/46/c15706c338403b7c420bcc0c2905aad116cc064545686d8bf85f1999ea00/psycopg2_binary-2.9.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:612b965daee295ae2da8f8218ce1d274645dc76ef3f1abf6a0a94fd57eff876d", size = 3655714, upload-time = "2026-04-20T23:34:46.233Z" },
|
|
1115
|
+
{ url = "https://files.pythonhosted.org/packages/b3/7c/a2d5dc09b64a4564db242a0fe418fde7d33f6f8259dd2c5b9d7def00fb5a/psycopg2_binary-2.9.12-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b9a339b79d37c1b45f3235265f07cdeb0cb5ad7acd2ac7720a5920989c17c24e", size = 3301154, upload-time = "2026-04-20T23:34:49.528Z" },
|
|
1116
|
+
{ url = "https://files.pythonhosted.org/packages/c0/e8/cc8c9a4ce71461f9ec548d38cadc41dc184b34c73e6455450775a9334ccd/psycopg2_binary-2.9.12-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:3471336e1acfd9c7fe507b8bad5af9317b6a89294f9eb37bd9a030bb7bebcdc6", size = 3048882, upload-time = "2026-04-20T23:34:51.86Z" },
|
|
1117
|
+
{ url = "https://files.pythonhosted.org/packages/19/6a/31e2296bc0787c5ab75d3d118e40b239db8151b5192b90b77c72bc9256e9/psycopg2_binary-2.9.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7af18183109e23502c8b2ae7f6926c0882766f35b5175a4cd737ad825e4d7a1b", size = 3351298, upload-time = "2026-04-20T23:34:54.124Z" },
|
|
1118
|
+
{ url = "https://files.pythonhosted.org/packages/5f/a8/75f4e3e11203b590150abed2cf7794b9c9c9f7eceddae955191138b44dde/psycopg2_binary-2.9.12-cp312-cp312-win_amd64.whl", hash = "sha256:398fcd4db988c7d7d3713e2b8e18939776fd3fb447052daae4f24fa39daede4c", size = 2757230, upload-time = "2026-04-20T23:34:56.242Z" },
|
|
1119
|
+
{ url = "https://files.pythonhosted.org/packages/91/bb/4608c96f970f6e0c56572e87027ef4404f709382a3503e9934526d7ba051/psycopg2_binary-2.9.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7c729a73c7b1b84de3582f73cdd27d905121dc2c531f3d9a3c32a3011033b965", size = 3712419, upload-time = "2026-04-20T23:34:58.754Z" },
|
|
1120
|
+
{ url = "https://files.pythonhosted.org/packages/5e/af/48f76af9d50d61cf390f8cd657b503168b089e2e9298e48465d029fcc713/psycopg2_binary-2.9.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4413d0caef93c5cf50b96863df4c2efe8c269bf2267df353225595e7e15e8df7", size = 3822990, upload-time = "2026-04-20T23:35:00.821Z" },
|
|
1121
|
+
{ url = "https://files.pythonhosted.org/packages/7a/df/aba0f99397cd811d32e06fc0cc781f1f3ce98bc0e729cb423925085d781a/psycopg2_binary-2.9.12-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:4dfcf8e45ebb0c663be34a3442f65e17311f3367089cd4e5e3a3e8e62c978777", size = 4578696, upload-time = "2026-04-20T23:35:03.409Z" },
|
|
1122
|
+
{ url = "https://files.pythonhosted.org/packages/95/9c/eaa74021ac4e4d5c2f83d82fc6615a63f4fe6c94dc4e94c3990427053f67/psycopg2_binary-2.9.12-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c41321a14dd74aceb6a9a643b9253a334521babfa763fa873e33d89cfa122fb5", size = 4274982, upload-time = "2026-04-20T23:35:05.583Z" },
|
|
1123
|
+
{ url = "https://files.pythonhosted.org/packages/35/ed/c25deff98bd26187ba48b3b250a3ffc3037c46c5b89362534a15d200e0db/psycopg2_binary-2.9.12-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83946ba43979ebfdc99a3cd0ee775c89f221df026984ba19d46133d8d75d3cd9", size = 5894867, upload-time = "2026-04-20T23:35:07.902Z" },
|
|
1124
|
+
{ url = "https://files.pythonhosted.org/packages/9a/81/8d0e21ca77373c6c9589e5c4528f6e8f0c08c62cafc76fb0bddb7a2cee22/psycopg2_binary-2.9.12-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:411e85815652d13560fbe731878daa5d92378c4995a22302071890ec3397d019", size = 4110578, upload-time = "2026-04-20T23:35:10.149Z" },
|
|
1125
|
+
{ url = "https://files.pythonhosted.org/packages/00/fc/f481e2435bd8f742d0123309174aae4165160ad3ef17c1b99c3622c241d2/psycopg2_binary-2.9.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c8ad4c08e00f7679559eaed7aff1edfffc60c086b976f93972f686384a95e2c", size = 3655816, upload-time = "2026-04-20T23:35:12.56Z" },
|
|
1126
|
+
{ url = "https://files.pythonhosted.org/packages/53/79/b9f46466bdbe9f239c96cde8be33c1aace4842f06013b47b730dc9759187/psycopg2_binary-2.9.12-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:00814e40fa23c2b37ef0a1e3c749d89982c73a9cb5046137f0752a22d432e82f", size = 3301307, upload-time = "2026-04-20T23:35:15.029Z" },
|
|
1127
|
+
{ url = "https://files.pythonhosted.org/packages/3f/19/7dc003b32fe35024df89b658104f7c8538a8b2dcbde7a4e746ce929742e7/psycopg2_binary-2.9.12-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:98062447aebc20ed20add1f547a364fd0ef8933640d5372ff1873f8deb9b61be", size = 3048968, upload-time = "2026-04-20T23:35:16.757Z" },
|
|
1128
|
+
{ url = "https://files.pythonhosted.org/packages/91/58/2dbd7db5c604d45f4950d988506aae672a14126ec22998ced5021cbb76bb/psycopg2_binary-2.9.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:66a7685d7e548f10fb4ce32fb01a7b7f4aa702134de92a292c7bd9e0d3dbd290", size = 3351369, upload-time = "2026-04-20T23:35:18.933Z" },
|
|
1129
|
+
{ url = "https://files.pythonhosted.org/packages/42/ee/dee8dcaad07f735824de3d6563bc67119fa6c28257b17977a8d624f02fab/psycopg2_binary-2.9.12-cp313-cp313-win_amd64.whl", hash = "sha256:b6937f5fe4e180aeee87de907a2fa982ded6f7f15d7218f78a083e4e1d68f2a0", size = 2757347, upload-time = "2026-04-20T23:35:21.283Z" },
|
|
1130
|
+
{ url = "https://files.pythonhosted.org/packages/13/1b/708c0dca874acfad6d65314271859899a79007686f3a1f74e82a2ed4b645/psycopg2_binary-2.9.12-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6f3b3de8a74ef8db215f22edffb19e32dc6fa41340456de7ec99efdc8a7b3ec2", size = 3712428, upload-time = "2026-04-20T23:35:23.453Z" },
|
|
1131
|
+
{ url = "https://files.pythonhosted.org/packages/d6/39/ddbea9d4b4de6aca9431b6ed253f530f8a02d3b8f9bcfd0dbfe2b3de6fe4/psycopg2_binary-2.9.12-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1006fb62f0f0bc5ce256a832356c6262e91be43f5e4eb15b5eaf38079464caf2", size = 3823184, upload-time = "2026-04-20T23:35:25.92Z" },
|
|
1132
|
+
{ url = "https://files.pythonhosted.org/packages/bf/a0/bc2fef74b106fa345567122a0659e6d94512ed7dc0131ec44c9e5aba3725/psycopg2_binary-2.9.12-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:840066105706cd2eb29b9a1c2329620056582a4bf3e8169dec5c447042d0869f", size = 4579157, upload-time = "2026-04-20T23:35:28.542Z" },
|
|
1133
|
+
{ url = "https://files.pythonhosted.org/packages/57/d7/d4e3b2005d3de607ca4fbb0e8742e248056e52184a6b94ebda3c1c2c329b/psycopg2_binary-2.9.12-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:863f5d12241ebe1c76a72a04c2113b6dc905f90b9cef0e9be0efd994affd9354", size = 4274970, upload-time = "2026-04-20T23:35:30.418Z" },
|
|
1134
|
+
{ url = "https://files.pythonhosted.org/packages/2e/42/c9853f8db3967fe08bcde11f53d53b85d351750cae726ce001cb68afa9c1/psycopg2_binary-2.9.12-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a99eaab34a9010f1a086b126de467466620a750634d114d20455f3a824aae033", size = 5895175, upload-time = "2026-04-20T23:35:33.584Z" },
|
|
1135
|
+
{ url = "https://files.pythonhosted.org/packages/eb/fd/b82b5601a97630308bef079f545ffec481bbbc795c2ba5ec416a01d03f60/psycopg2_binary-2.9.12-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ffdd7dc5463ccd61845ac37b7012d0f35a1548df9febe14f8dd549be4a0bc81e", size = 4110658, upload-time = "2026-04-20T23:35:35.638Z" },
|
|
1136
|
+
{ url = "https://files.pythonhosted.org/packages/62/8c/32ca69b0389ef25dd22937bf9e8fbe2ce27aea20b05ded48c4ce4cb42475/psycopg2_binary-2.9.12-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:54a0dfecab1b48731f934e06139dfe11e24219fb6d0ceb32177cf0375f14c7b5", size = 3656251, upload-time = "2026-04-20T23:35:37.854Z" },
|
|
1137
|
+
{ url = "https://files.pythonhosted.org/packages/c4/29/96992a2b59e3b9d730fcf9612d0a387305025dc867a9fc490a9e496e074e/psycopg2_binary-2.9.12-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:96937c9c5d891f772430f418a7a8b4691a90c3e6b93cf72b5bd7cad8cbca32a5", size = 3301810, upload-time = "2026-04-20T23:35:39.927Z" },
|
|
1138
|
+
{ url = "https://files.pythonhosted.org/packages/56/ad/44b06659949b243ae10112cd3b20a197f9bf3e81d5651379b9eb889bfaad/psycopg2_binary-2.9.12-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:77b348775efd4cdab410ec6609d81ccecd1139c90265fa583a7255c8064bc03d", size = 3048977, upload-time = "2026-04-20T23:35:41.806Z" },
|
|
1139
|
+
{ url = "https://files.pythonhosted.org/packages/1d/f2/10a1bcebadb6aa55e280e1f58975c36a7b560ea525184c7aa4064c466633/psycopg2_binary-2.9.12-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:527e6342b3e44c2f0544f6b8e927d60de7f163f5723b8f1dfa7d2a84298738cd", size = 3351466, upload-time = "2026-04-20T23:35:43.993Z" },
|
|
1140
|
+
{ url = "https://files.pythonhosted.org/packages/20/be/b732c8418ffa5bcfda002890f5dc4c869fc17db66ff11f53b17cfe44afc0/psycopg2_binary-2.9.12-cp314-cp314-win_amd64.whl", hash = "sha256:f12ae41fcafadb39b2785e64a40f9db05d6de2ac114077457e0e7c597f3af980", size = 2848762, upload-time = "2026-04-20T23:35:46.421Z" },
|
|
1141
|
+
]
|
|
1142
|
+
|
|
1100
1143
|
[[package]]
|
|
1101
1144
|
name = "py-serializable"
|
|
1102
1145
|
version = "2.1.0"
|
|
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.58 → nene2_python-1.8.59}/alembic/versions/001_create_notes_and_tags_tables.py
RENAMED
|
File without changes
|