nene2-python 1.8.49__tar.gz → 1.8.51__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.49 → nene2_python-1.8.51}/PKG-INFO +1 -1
- nene2_python-1.8.51/docs/field-trials/2026-05-field-trial-179.md +242 -0
- nene2_python-1.8.51/docs/field-trials/2026-05-field-trial-180.md +475 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/INDEX.md +7 -4
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/todo/current.md +11 -6
- {nene2_python-1.8.49 → nene2_python-1.8.51}/pyproject.toml +1 -1
- {nene2_python-1.8.49 → nene2_python-1.8.51}/.env.example +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/.github/workflows/ci.yml +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/.github/workflows/docs.yml +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/.github/workflows/publish.yml +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/.gitignore +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/.vitepress/config.mts +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/.vitepress/theme/custom.css +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/.vitepress/theme/index.ts +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/AGENTS.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/CHANGELOG.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/CLAUDE.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/Dockerfile +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/LICENSE +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/README.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/alembic/README +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/alembic/env.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/alembic/script.py.mako +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/alembic.ini +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/compose.yaml +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/adr/0001-toolchain.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/adr/0002-clean-architecture.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/adr/0003-security-first.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/adr/0004-ai-first-design.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/adr/0005-logging.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/adr/0006-rate-limiting.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/adr/0009-mcp-design.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/adr/0010-async-use-case.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/de/index.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/de/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/explanation/architecture.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-1.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-10.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-100.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-101.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-102.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-103.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-104.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-105.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-106.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-107.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-108.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-109.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-11.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-110.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-111.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-112.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-113.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-114.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-115.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-116.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-117.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-118.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-119.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-12.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-120.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-121.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-122.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-123.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-124.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-125.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-126.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-127.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-128.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-129.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-13.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-130.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-131.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-132.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-133.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-134.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-135.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-136.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-137.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-138.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-139.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-14.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-140.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-141.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-142.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-143.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-144.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-145.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-146.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-147.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-148.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-149.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-15.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-150.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-151.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-152.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-153.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-154.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-155.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-156.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-157.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-158.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-159.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-16.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-160.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-161.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-162.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-163.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-164.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-165.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-166.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-167.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-168.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-169.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-17.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-170.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-171.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-172.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-173.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-174.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-175.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-176.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-177.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-178.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-18.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-19.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-2.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-20.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-21.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-22.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-23.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-24.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-25.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-26.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-27.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-28.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-29.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-3.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-30.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-31.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-32.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-33.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-34.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-35.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-36.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-37.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-38.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-39.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-4.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-40.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-41.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-42.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-43.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-44.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-45.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-46.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-47.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-48.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-49.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-5.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-50.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-51.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-52.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-53.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-54.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-55.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-56.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-57.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-58.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-59.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-6.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-60.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-61.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-62.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-63.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-64.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-65.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-66.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-67.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-68.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-69.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-7.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-70.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-71.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-72.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-73.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-74.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-75.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-76.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-77.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-78.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-79.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-8.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-80.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-81.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-82.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-83.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-84.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-85.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-86.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-87.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-88.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-89.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-9.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-90.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-91.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-92.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-93.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-94.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-95.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-96.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-97.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-98.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-99.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/fr/index.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/fr/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/api-versioning.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/async-use-case.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/background-tasks.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/cors.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/custom-auth-middleware.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/dependency-injection.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/domain-events.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/file-upload.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/lifespan-and-app-state.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/middleware-stack.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/new-project.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/problem-details.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/response-patterns.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/run-tests.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/soft-delete.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/streaming.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/structured-logging.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/validation.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/webhook.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/index.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/ja/explanation/architecture.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/ja/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/ja/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/ja/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/ja/how-to/new-project.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/ja/how-to/run-tests.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/ja/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/ja/index.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/ja/reference/api.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/ja/reference/configuration.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/ja/reference/framework-modules.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/ja/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/ja/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/pt-br/index.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/pt-br/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/reference/api.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/reference/configuration.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/reference/framework-modules.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/roadmap.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/templates/field-trial-report.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/zh/index.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/zh/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/package-lock.json +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/package.json +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/__init__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/__main__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/app.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/comment/__init__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/comment/entity.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/comment/exceptions.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/comment/handler.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/comment/repository.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/comment/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/comment/use_case.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/mcp.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/note/__init__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/note/async_use_case.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/note/entity.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/note/exceptions.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/note/handler.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/note/repository.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/note/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/note/use_case.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/schema.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/tag/__init__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/tag/entity.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/tag/exceptions.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/tag/handler.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/tag/repository.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/tag/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/tag/use_case.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/__init__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/auth/api_key.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/auth/bearer_token.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/auth/deps.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/auth/exceptions.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/auth/interfaces.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/auth/local_verifier.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/cache/__init__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/cache/ttl.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/config/settings.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/database/exceptions.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/database/health.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/database/interfaces.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/database/sqlalchemy_executor.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/database/utils.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/http/etag.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/http/health.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/http/pagination.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/http/problem_details.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/log/setup.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/mcp/http_client.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/mcp/server.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/middleware/__init__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/middleware/domain_exception.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/middleware/error_handler.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/middleware/request_id.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/middleware/request_logging.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/middleware/request_size_limit.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/middleware/security_headers.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/middleware/setup.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/middleware/throttle.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/py.typed +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/security/__init__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/security/webhook.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/use_case/protocols.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/validation/exceptions.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/scripts/__init__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/src/scripts/export_openapi.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/__init__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/conftest.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/example/__init__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/example/comment/__init__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/example/comment/test_comment_http.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/example/comment/test_comment_repository.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/example/comment/test_comment_use_case.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/example/conftest.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/example/note/__init__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/example/note/test_async_note_use_case.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/example/note/test_list_notes.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/example/note/test_note_repository.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/example/tag/__init__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/example/tag/test_tag_repository.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/example/tag/test_tags.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/example/test_cors.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/example/test_mcp.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/__init__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/auth/test_api_key.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/auth/test_bearer_token.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/auth/test_make_require_auth.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/auth/test_token_issuer.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/cache/__init__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/cache/test_ttl.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/config/test_settings.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/database/test_transaction.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/database/test_utils.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/http/test_etag.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/http/test_health.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/http/test_pagination.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/http/test_problem_details.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/log/test_setup.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/mcp/test_http_client.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/mcp/test_server.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/middleware/__init__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/middleware/test_error_handler.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/middleware/test_request_id.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/middleware/test_request_logging.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/middleware/test_request_size_limit.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/middleware/test_security_headers.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/middleware/test_setup_middlewares.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/middleware/test_throttle.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/security/__init__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/security/test_webhook.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/use_case/test_protocols.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/use_case/test_run_in_threadpool.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/validation/test_exceptions.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/scripts/__init__.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/scripts/test_export_openapi.py +0 -0
- {nene2_python-1.8.49 → nene2_python-1.8.51}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nene2-python
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.51
|
|
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,242 @@
|
|
|
1
|
+
# FT179: zlib モジュール
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-21
|
|
4
|
+
**テーマ**: データ圧縮・解凍・CRC32/Adler-32 整合性検証・展開爆弾対策
|
|
5
|
+
**セキュリティ診断**: なし(179 % 3 = 2)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 概要
|
|
10
|
+
|
|
11
|
+
Python 標準ライブラリの `zlib` モジュールを検証する。
|
|
12
|
+
単純な圧縮・解凍にとどまらず、展開爆弾(decompression bomb)対策のストリーミング解凍、
|
|
13
|
+
CRC32 と Adler-32 の両チェックサムアルゴリズム、圧縮レベル 1〜9 の比較、
|
|
14
|
+
チャンク単位のストリーミング圧縮まで網羅し、Web API での実用的な使い方を検証する。
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 実装したサンプルアプリ
|
|
19
|
+
|
|
20
|
+
**場所**: `/home/xi/docker/nene2-python-FT/ft179-zlib/`
|
|
21
|
+
|
|
22
|
+
### 主要機能
|
|
23
|
+
|
|
24
|
+
| 関数/クラス | 概要 |
|
|
25
|
+
|---|---|
|
|
26
|
+
| `compress(data, level)` | zlib 圧縮(入力 10MB 上限、`CompressResult` 返却) |
|
|
27
|
+
| `decompress(compressed_hex)` | hex 文字列から解凍(展開爆弾対策付き) |
|
|
28
|
+
| `_decompress_bytes(data)` | ストリーミング解凍コア(50MB 上限をチャンクごとに監視) |
|
|
29
|
+
| `decompress_streaming(data)` | raw bytes を受け取る解凍インターフェース |
|
|
30
|
+
| `compute_crc32(data)` | CRC32 チェックサム計算(`ChecksumResult` 返却) |
|
|
31
|
+
| `compute_adler32(data)` | Adler-32 チェックサム計算(`ChecksumResult` 返却) |
|
|
32
|
+
| `verify_crc32(data, expected_hex)` | CRC32 検証 |
|
|
33
|
+
| `verify_adler32(data, expected_hex)` | Adler-32 検証 |
|
|
34
|
+
| `compare_compression_levels(data)` | レベル 1〜9 の圧縮結果比較(`LevelComparison` リスト) |
|
|
35
|
+
| `compress_streaming(chunks, level)` | チャンクリストのストリーミング圧縮 |
|
|
36
|
+
|
|
37
|
+
### HTTP エンドポイント
|
|
38
|
+
|
|
39
|
+
| メソッド | パス | 概要 |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| POST | `/compress` | データを zlib 圧縮(`level` 指定可) |
|
|
42
|
+
| POST | `/decompress` | zlib 圧縮データを解凍 |
|
|
43
|
+
| POST | `/checksum/crc32` | CRC32 チェックサム計算 |
|
|
44
|
+
| POST | `/checksum/adler32` | Adler-32 チェックサム計算 |
|
|
45
|
+
| POST | `/verify` | チェックサム検証(`algorithm` で切り替え) |
|
|
46
|
+
| POST | `/compress/levels` | レベル 1〜9 の圧縮比較 |
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## テスト結果
|
|
51
|
+
|
|
52
|
+
**39 passed**(初回から全通過)
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
39 passed in 0.67s
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
mypy: Success / ruff: All checks passed / pip-audit: PYSEC-2025-183(継続監視)
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 摩擦ポイント
|
|
63
|
+
|
|
64
|
+
**今回の FT では実装上の摩擦はゼロだった。**
|
|
65
|
+
|
|
66
|
+
APIRouter パターン(FT177 F-1 対応)を最初から適用し、テストが一発で全通過した。
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## 観察点
|
|
71
|
+
|
|
72
|
+
### 観察1: 展開爆弾(Decompression Bomb)対策にはストリーミング解凍が必須
|
|
73
|
+
|
|
74
|
+
`zlib.decompress()` は解凍後サイズをチェックする前に全データをメモリに展開する。
|
|
75
|
+
ゼロバイト 50MB を level=9 で圧縮すると数百バイトになり、解凍すると 50MB になる。
|
|
76
|
+
悪意ある入力をそのまま `decompress()` すると OOM になりえる。
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
# 危険: 上限チェック前に全解凍
|
|
80
|
+
zlib.decompress(huge_compressed_data) # → OOM の危険
|
|
81
|
+
|
|
82
|
+
# 安全: ストリーミングでチャンクごとに上限チェック
|
|
83
|
+
decompressor = zlib.decompressobj()
|
|
84
|
+
total = 0
|
|
85
|
+
for offset in range(0, len(data), CHUNK_SIZE):
|
|
86
|
+
chunk = decompressor.decompress(data[offset : offset + CHUNK_SIZE])
|
|
87
|
+
total += len(chunk)
|
|
88
|
+
if total > MAX_OUTPUT_BYTES:
|
|
89
|
+
return None # 上限超過で早期終了
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`zlib.decompressobj()` を使うストリーミング方式により、
|
|
93
|
+
解凍途中で上限(50MB)を超えたと判断できる。
|
|
94
|
+
|
|
95
|
+
### 観察2: CRC32 と Adler-32 の使い分け
|
|
96
|
+
|
|
97
|
+
両者とも `zlib` モジュールに含まれるが特性が異なる。
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
zlib.crc32(b"hello") & 0xFFFFFFFF # → 907060870
|
|
101
|
+
zlib.adler32(b"hello") & 0xFFFFFFFF # → 103547413
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
| 特性 | CRC32 | Adler-32 |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| 用途 | ファイル整合性(PNG, ZIP, gzip) | zlib ストリームヘッダー |
|
|
107
|
+
| 計算速度 | やや遅い | 高速(加算のみ) |
|
|
108
|
+
| 小データ衝突耐性 | 高い | 低い(短文字列で衝突しやすい) |
|
|
109
|
+
| & 0xFFFFFFFF の必要性 | あり(符号ありの場合がある) | あり |
|
|
110
|
+
|
|
111
|
+
Web API で「ファイルのダウンロード整合性確認」には CRC32、
|
|
112
|
+
「zlib ストリームの内部チェックサム」には Adler-32 が適している。
|
|
113
|
+
|
|
114
|
+
### 観察3: 圧縮レベルの実効差
|
|
115
|
+
|
|
116
|
+
繰り返しデータ(`b"hello world! " * 100 = 1300 bytes`)では、
|
|
117
|
+
レベル 1〜9 の差は小さいが高圧縮データでは差が出る。
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
results = compare_compression_levels(b"hello world! " * 100)
|
|
121
|
+
# level=1: 34 bytes (ratio=0.0262)
|
|
122
|
+
# level=9: 22 bytes (ratio=0.0169)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
一般的な API ペイロード(JSON 等)ではレベル 6(デフォルト)が
|
|
126
|
+
速度と圧縮率のバランス点として適切。
|
|
127
|
+
|
|
128
|
+
### 観察4: ストリーミング圧縮の結果は oneshot と roundtrip 互換
|
|
129
|
+
|
|
130
|
+
`zlib.compressobj()` によるストリーミング圧縮の出力は、
|
|
131
|
+
`zlib.decompress()` や `zlib.decompressobj()` で正常に解凍できる。
|
|
132
|
+
チャンク境界に関係なくストリーム形式は同一なので、
|
|
133
|
+
ネットワーク越しの分割送信データをチャンク単位で圧縮してもラウンドトリップが保証される。
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
chunks = [data[i : i + 64] for i in range(0, len(data), 64)]
|
|
137
|
+
streamed = compress_streaming(chunks)
|
|
138
|
+
assert decompress_streaming(streamed) == data # ✅ 常に成立
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## nene2-python フレームワークとの統合
|
|
144
|
+
|
|
145
|
+
- `compress` / `decompress` エンドポイントは Content-Encoding 圧縮 API の基盤として使える
|
|
146
|
+
- `MAX_INPUT_BYTES = 10MB` + Pydantic `max_length=20_971_520`(hex 換算)で DoS 対策済み
|
|
147
|
+
- `MAX_OUTPUT_BYTES = 50MB` の展開爆弾対策は、ファイルアップロード API のメモリ安全性に直結
|
|
148
|
+
- `verify_crc32` / `verify_adler32` は `hmac.compare_digest` 相当の定数時間比較ではない点に注意
|
|
149
|
+
(チェックサム比較はタイミング攻撃対象にはならないため問題なし)
|
|
150
|
+
- `APIRouter` + `create_app()` パターン(FT177 F-1 対応)を最初から適用済み
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Developer Experience (DX) Review
|
|
155
|
+
|
|
156
|
+
### ペルソナ1: 初心者(Python 歴1年・独学中・女性・バックエンド志望)
|
|
157
|
+
|
|
158
|
+
ファイルアップロード API で圧縮ストレージを実装しようとしている。
|
|
159
|
+
|
|
160
|
+
**ドキュメント理解**: `zlib.compress()` / `zlib.decompress()` のペアは直感的。
|
|
161
|
+
圧縮レベルのデフォルト値(6)がなぜ最適なのかは公式ドキュメントに書いていない。
|
|
162
|
+
**事故リスク**: 高。`zlib.decompress()` に信頼できないデータを渡すと OOM になりうる。
|
|
163
|
+
`MAX_OUTPUT_BYTES` によるガードを知らずに実装すると本番で問題になる。
|
|
164
|
+
**規約の使いやすさ**: `hex()` / `bytes.fromhex()` の往復は Python 固有概念として最初の壁になる。
|
|
165
|
+
|
|
166
|
+
### ペルソナ2: ロースキル経験者(Python 歴3-4年・スクリプト系・男性・SES)
|
|
167
|
+
|
|
168
|
+
既存の zlib 圧縮コードをコピーして API に組み込もうとしている。
|
|
169
|
+
|
|
170
|
+
**コピペ可能性**: `compress()` / `decompress()` のラッパーは分かりやすい。
|
|
171
|
+
`_decompress_bytes()` の展開爆弾対策ロジックは読んでも「なぜ必要か」が分かりにくい。
|
|
172
|
+
**拡張時の罠**: `MAX_OUTPUT_BYTES` の定数を削除または増やすと展開爆弾に脆弱になる。
|
|
173
|
+
「動いているから削ってもいいか」と判断する人がいる。
|
|
174
|
+
**セキュリティ的な事故リスク**: 高。展開爆弾対策なしの実装はサービス停止に直結する。
|
|
175
|
+
|
|
176
|
+
### ペルソナ3: フロントエンド寄り経験者(React/TS 歴4年・バックエンド転向中・ノンバイナリ)
|
|
177
|
+
|
|
178
|
+
TypeScript の `pako`(zlib の JS 実装)に慣れており、Python で同じことをしようとしている。
|
|
179
|
+
|
|
180
|
+
**エラーレスポンスの質**: 400 Bad Request に具体的なメッセージが返るのは良い。
|
|
181
|
+
圧縮爆弾で `None` が返ったときの 400 レスポンスがなぜ「Invalid compressed data」なのかは
|
|
182
|
+
クライアント実装側からは分かりにくい(「サイズ上限超過」と区別できない)。
|
|
183
|
+
**Python 固有概念の学習コスト**: `bytes.hex()` / `bytes.fromhex()` の往復は JS にない概念。
|
|
184
|
+
`zlib.decompressobj()` のストリーミング API は `pako` の `Inflate` に相当するが設計が異なる。
|
|
185
|
+
**事故リスク**: 低。HTTP 境界での Pydantic バリデーションが充実。
|
|
186
|
+
|
|
187
|
+
### ペルソナ4: バックエンド経験者(Django/FastAPI 歴5-6年・男性・リードエンジニア)
|
|
188
|
+
|
|
189
|
+
zlib を直接使うより、HTTP レスポンスの Content-Encoding や S3 のサーバー側圧縮を使うことが多い。
|
|
190
|
+
|
|
191
|
+
**他フレームワークとの差異**: Django では `GZipMiddleware` が透過的に圧縮するため、
|
|
192
|
+
zlib を直接操作するコードは書かない。nene2-python では zlib 操作がアプリコードに露出しており、
|
|
193
|
+
ユースケースが明確(ファイルストレージ等)でなければ設計レビューで指摘される。
|
|
194
|
+
**nene2-python の薄さへの評価**: `_decompress_bytes()` の展開爆弾対策ロジックは再利用可能な
|
|
195
|
+
ミドルウェア候補。フレームワーク側に `DecompressionSizeLimitMiddleware` として組み込む価値がある。
|
|
196
|
+
**本番投入可能性**: 展開爆弾対策が明示的に実装されており、本番品質として評価できる。
|
|
197
|
+
|
|
198
|
+
### ペルソナ5: シニアエンジニア(設計・コードレビュー担当・女性・10-12年)
|
|
199
|
+
|
|
200
|
+
**コードレビューチェックポイント**:
|
|
201
|
+
- [x] `zlib.decompress()` を直接呼ばず、ストリーミング解凍で上限チェックをしているか
|
|
202
|
+
- [x] `MAX_OUTPUT_BYTES` が `MAX_INPUT_BYTES` より大きいことを確認(圧縮率を考慮)
|
|
203
|
+
- [x] `verify_crc32` / `verify_adler32` の比較が文字列の `==` であることの妥当性
|
|
204
|
+
(チェックサム比較はタイミング攻撃対象外なので OK)
|
|
205
|
+
|
|
206
|
+
**チームでの安全な共有パターン**: `_decompress_bytes()` を内部 API として隠蔽し、
|
|
207
|
+
公開 API は `decompress()` と `decompress_streaming()` の 2 つのみに絞った設計が良い。
|
|
208
|
+
**ツール追加の必要性**: `bandit` (ruff S ルール相当) の B322(`zlib.decompress` 直接使用)は
|
|
209
|
+
ruff にはないが、コードレビューチェックリストに追加すべき。
|
|
210
|
+
|
|
211
|
+
### ペルソナ6: 設計者・ポリシー照合(nene2-python 設計ポリシー目線)
|
|
212
|
+
|
|
213
|
+
**ポリシー達成度**: 高
|
|
214
|
+
**「初心者でも安全な API」達成度**: 中
|
|
215
|
+
— `zlib.decompress()` 直接使用の罠は `_decompress_bytes()` の命名(アンダースコアで内部実装を示す)で
|
|
216
|
+
ある程度ガードできているが、stdlib の `zlib.decompress()` を直接呼ぶと再発する。
|
|
217
|
+
**設計上の負債**: 展開爆弾対策を nene2-python フレームワークの共通ユーティリティとして
|
|
218
|
+
`nene2.io.SafeDecompressor` 等に昇格させる価値がある。
|
|
219
|
+
**Follow-up Issue 候補**: なし(現状の実装で十分。フレームワーク統合は別 Issue で検討)
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Follow-up Issues
|
|
224
|
+
|
|
225
|
+
| 優先度 | タイトル | 種別 |
|
|
226
|
+
|---|---|---|
|
|
227
|
+
| 低 | `decompress` の 400 エラーメッセージをサイズ超過と不正データで分離する | feat |
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## まとめ
|
|
232
|
+
|
|
233
|
+
FT179 では `zlib` モジュールを中心に、データ圧縮・解凍・チェックサム計算を実装した。
|
|
234
|
+
39 テストが全通過し、mypy/ruff も問題なし。
|
|
235
|
+
|
|
236
|
+
最大の発見は展開爆弾(Decompression Bomb)対策の必要性。
|
|
237
|
+
`zlib.decompress()` は解凍後サイズを事前チェックできないため、
|
|
238
|
+
`zlib.decompressobj()` によるストリーミング解凍でチャンクごとに上限(50MB)を監視する実装が必須。
|
|
239
|
+
|
|
240
|
+
APIRouter パターン(FT177 F-1 の改善)を最初から適用し、テストが一発で全通過した。
|
|
241
|
+
|
|
242
|
+
v1.8.50 としてリリース。
|