nene2-python 1.8.52__tar.gz → 1.8.54__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.52 → nene2_python-1.8.54}/PKG-INFO +1 -1
- nene2_python-1.8.54/docs/field-trials/2026-05-field-trial-182.md +301 -0
- nene2_python-1.8.54/docs/field-trials/2026-05-field-trial-183.md +511 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/INDEX.md +5 -3
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/todo/current.md +12 -6
- {nene2_python-1.8.52 → nene2_python-1.8.54}/pyproject.toml +1 -1
- {nene2_python-1.8.52 → nene2_python-1.8.54}/.env.example +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/.github/workflows/ci.yml +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/.github/workflows/docs.yml +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/.github/workflows/publish.yml +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/.gitignore +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/.vitepress/config.mts +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/.vitepress/theme/custom.css +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/.vitepress/theme/index.ts +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/AGENTS.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/CHANGELOG.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/CLAUDE.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/Dockerfile +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/LICENSE +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/README.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/alembic/README +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/alembic/env.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/alembic/script.py.mako +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/alembic.ini +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/compose.yaml +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/adr/0001-toolchain.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/adr/0002-clean-architecture.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/adr/0003-security-first.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/adr/0004-ai-first-design.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/adr/0005-logging.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/adr/0006-rate-limiting.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/adr/0009-mcp-design.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/adr/0010-async-use-case.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/de/index.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/de/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/explanation/architecture.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-1.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-10.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-100.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-101.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-102.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-103.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-104.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-105.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-106.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-107.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-108.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-109.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-11.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-110.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-111.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-112.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-113.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-114.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-115.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-116.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-117.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-118.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-119.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-12.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-120.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-121.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-122.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-123.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-124.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-125.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-126.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-127.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-128.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-129.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-13.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-130.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-131.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-132.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-133.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-134.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-135.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-136.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-137.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-138.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-139.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-14.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-140.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-141.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-142.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-143.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-144.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-145.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-146.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-147.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-148.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-149.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-15.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-150.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-151.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-152.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-153.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-154.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-155.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-156.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-157.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-158.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-159.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-16.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-160.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-161.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-162.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-163.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-164.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-165.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-166.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-167.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-168.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-169.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-17.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-170.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-171.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-172.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-173.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-174.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-175.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-176.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-177.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-178.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-179.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-18.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-180.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-181.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-19.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-2.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-20.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-21.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-22.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-23.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-24.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-25.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-26.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-27.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-28.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-29.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-3.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-30.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-31.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-32.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-33.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-34.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-35.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-36.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-37.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-38.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-39.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-4.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-40.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-41.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-42.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-43.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-44.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-45.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-46.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-47.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-48.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-49.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-5.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-50.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-51.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-52.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-53.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-54.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-55.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-56.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-57.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-58.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-59.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-6.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-60.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-61.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-62.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-63.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-64.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-65.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-66.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-67.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-68.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-69.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-7.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-70.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-71.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-72.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-73.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-74.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-75.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-76.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-77.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-78.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-79.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-8.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-80.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-81.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-82.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-83.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-84.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-85.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-86.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-87.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-88.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-89.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-9.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-90.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-91.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-92.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-93.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-94.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-95.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-96.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-97.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-98.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-99.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/fr/index.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/fr/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/api-versioning.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/async-use-case.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/background-tasks.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/cors.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/custom-auth-middleware.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/dependency-injection.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/domain-events.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/file-upload.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/lifespan-and-app-state.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/middleware-stack.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/new-project.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/problem-details.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/response-patterns.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/run-tests.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/soft-delete.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/streaming.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/structured-logging.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/validation.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/webhook.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/index.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/ja/explanation/architecture.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/ja/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/ja/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/ja/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/ja/how-to/new-project.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/ja/how-to/run-tests.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/ja/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/ja/index.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/ja/reference/api.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/ja/reference/configuration.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/ja/reference/framework-modules.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/ja/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/ja/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/pt-br/index.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/pt-br/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/reference/api.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/reference/configuration.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/reference/framework-modules.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/roadmap.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/templates/field-trial-report.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/zh/index.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/zh/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/package-lock.json +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/package.json +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/__init__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/__main__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/app.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/comment/__init__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/comment/entity.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/comment/exceptions.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/comment/handler.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/comment/repository.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/comment/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/comment/use_case.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/mcp.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/note/__init__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/note/async_use_case.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/note/entity.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/note/exceptions.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/note/handler.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/note/repository.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/note/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/note/use_case.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/schema.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/tag/__init__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/tag/entity.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/tag/exceptions.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/tag/handler.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/tag/repository.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/tag/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/tag/use_case.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/__init__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/auth/api_key.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/auth/bearer_token.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/auth/deps.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/auth/exceptions.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/auth/interfaces.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/auth/local_verifier.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/cache/__init__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/cache/ttl.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/config/settings.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/database/exceptions.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/database/health.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/database/interfaces.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/database/sqlalchemy_executor.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/database/utils.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/http/etag.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/http/health.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/http/pagination.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/http/problem_details.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/log/setup.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/mcp/http_client.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/mcp/server.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/middleware/__init__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/middleware/domain_exception.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/middleware/error_handler.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/middleware/request_id.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/middleware/request_logging.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/middleware/request_size_limit.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/middleware/security_headers.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/middleware/setup.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/middleware/throttle.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/py.typed +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/security/__init__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/security/webhook.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/use_case/protocols.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/validation/exceptions.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/scripts/__init__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/src/scripts/export_openapi.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/__init__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/conftest.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/example/__init__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/example/comment/__init__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/example/comment/test_comment_http.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/example/comment/test_comment_repository.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/example/comment/test_comment_use_case.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/example/conftest.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/example/note/__init__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/example/note/test_async_note_use_case.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/example/note/test_list_notes.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/example/note/test_note_repository.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/example/tag/__init__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/example/tag/test_tag_repository.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/example/tag/test_tags.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/example/test_cors.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/example/test_mcp.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/__init__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/auth/test_api_key.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/auth/test_bearer_token.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/auth/test_make_require_auth.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/auth/test_token_issuer.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/cache/__init__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/cache/test_ttl.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/config/test_settings.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/database/test_transaction.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/database/test_utils.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/http/test_etag.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/http/test_health.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/http/test_pagination.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/http/test_problem_details.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/log/test_setup.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/mcp/test_http_client.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/mcp/test_server.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/middleware/__init__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/middleware/test_error_handler.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/middleware/test_request_id.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/middleware/test_request_logging.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/middleware/test_request_size_limit.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/middleware/test_security_headers.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/middleware/test_setup_middlewares.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/middleware/test_throttle.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/security/__init__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/security/test_webhook.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/use_case/test_protocols.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/use_case/test_run_in_threadpool.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/validation/test_exceptions.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/scripts/__init__.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/scripts/test_export_openapi.py +0 -0
- {nene2_python-1.8.52 → nene2_python-1.8.54}/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.54
|
|
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,301 @@
|
|
|
1
|
+
# FT182: email モジュール
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-21
|
|
4
|
+
**テーマ**: MIME メッセージ構築・ヘッダーエンコード・パース・アドレス操作
|
|
5
|
+
**セキュリティ診断**: なし(182 % 3 = 2)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 概要
|
|
10
|
+
|
|
11
|
+
Python 標準ライブラリの `email` モジュールを検証する。
|
|
12
|
+
プレーンテキスト・HTML・添付ファイル付きメールの構築、RFC 2047 ヘッダーエンコード、
|
|
13
|
+
生バイト列からのパース、アドレスのバリデーション・整形を網羅する。
|
|
14
|
+
FastAPI エンドポイントとして実装し、インプット検証と添付ファイルの安全な扱いまで確認する。
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 実装したサンプルアプリ
|
|
19
|
+
|
|
20
|
+
**場所**: `/home/xi/docker/nene2-python-FT/ft182-email/`
|
|
21
|
+
|
|
22
|
+
### 主要機能
|
|
23
|
+
|
|
24
|
+
| 関数/クラス | 概要 |
|
|
25
|
+
|---|---|
|
|
26
|
+
| `validate_email_address(address)` | メールアドレスの基本フォーマット検証(RFC 5322 簡易正規表現) |
|
|
27
|
+
| `parse_address(header_value)` | `'Display Name <email>'` 形式をパース |
|
|
28
|
+
| `extract_addresses(header_value)` | カンマ区切りのアドレスリストをパース |
|
|
29
|
+
| `encode_header_value(text, charset)` | 非 ASCII を RFC 2047 Base64 エンコード |
|
|
30
|
+
| `decode_header_value(encoded)` | RFC 2047 エンコードをデコード |
|
|
31
|
+
| `build_simple_email(...)` | `EmailMessage` でプレーンテキストメールを構築 |
|
|
32
|
+
| `build_html_email(...)` | `MIMEMultipart("alternative")` で HTML メールを構築 |
|
|
33
|
+
| `build_email_with_attachment(...)` | `MIMEMultipart` + `MIMEBase` で添付ファイル付きメールを構築 |
|
|
34
|
+
| `parse_email(raw)` | 生バイト列からメールをパース(`ParsedEmail` 返却) |
|
|
35
|
+
| `format_address(display_name, email_address)` | 表示名付きアドレスに整形 |
|
|
36
|
+
| `ParsedEmail` | `@dataclass(frozen=True, slots=True)` — パース済みメール |
|
|
37
|
+
| `AddressPart` | `NamedTuple` — アドレスの構造化表現 |
|
|
38
|
+
|
|
39
|
+
### HTTP エンドポイント
|
|
40
|
+
|
|
41
|
+
| メソッド | パス | 概要 |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| POST | `/build/simple` | プレーンテキストメールを hex で返す |
|
|
44
|
+
| POST | `/build/html` | HTML マルチパートメールを hex で返す |
|
|
45
|
+
| POST | `/build/attachment` | 添付ファイル付きメールを hex で返す |
|
|
46
|
+
| POST | `/parse` | 生メール(hex)をパースして構造化レスポンスを返す |
|
|
47
|
+
| POST | `/headers/encode` | RFC 2047 エンコード |
|
|
48
|
+
| POST | `/headers/decode` | RFC 2047 デコード |
|
|
49
|
+
| POST | `/addresses/extract` | カンマ区切りアドレスリストを構造化パース |
|
|
50
|
+
| POST | `/addresses/validate` | メールアドレスのフォーマット検証 |
|
|
51
|
+
| POST | `/addresses/format` | 表示名付きアドレスに整形 |
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## テスト結果
|
|
56
|
+
|
|
57
|
+
**57 passed**(初回 55 通過 → F-1 修正後 57 全通過)
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
57 passed in 0.37s
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
mypy: Success / ruff: All checks passed / pip-audit: PYSEC-2025-183(継続監視)
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 摩擦ポイント
|
|
68
|
+
|
|
69
|
+
### F-1: `app = create_app()` をルート定義より前に置くと全エンドポイントが 404 になる(深刻度: 高)
|
|
70
|
+
|
|
71
|
+
**事象**: `app.py` の先頭付近で `router = APIRouter()` を定義し、
|
|
72
|
+
`app = create_app()` を呼んでから `@router.post(...)` デコレータを書いた。
|
|
73
|
+
TestClient で全エンドポイントが 404 を返す。
|
|
74
|
+
|
|
75
|
+
**原因**: FastAPI の `include_router()` は呼び出し時点の `router.routes` を
|
|
76
|
+
アプリケーションにコピーする。`@router.post(...)` デコレータが実行される前に
|
|
77
|
+
`include_router(router)` を呼ぶと、空のルーターを取り込む。
|
|
78
|
+
Python モジュールは上から順に実行されるため、
|
|
79
|
+
デコレータより前に `app = create_app()` が書かれているとルートが登録されない。
|
|
80
|
+
|
|
81
|
+
**対応**: `create_app()` 関数の定義と `app = create_app()` の呼び出しをファイルの末尾、
|
|
82
|
+
全 `@router.post(...)` デコレータの後に移動した。
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
# 誤: デコレータより前に include_router が実行される
|
|
86
|
+
router = APIRouter()
|
|
87
|
+
app = create_app() # ← この時点でルーターは空
|
|
88
|
+
|
|
89
|
+
@router.post("/build/simple")
|
|
90
|
+
def build_simple_endpoint(...): ...
|
|
91
|
+
|
|
92
|
+
# 正: デコレータで全ルートが登録された後に include_router を実行
|
|
93
|
+
router = APIRouter()
|
|
94
|
+
|
|
95
|
+
@router.post("/build/simple")
|
|
96
|
+
def build_simple_endpoint(...): ...
|
|
97
|
+
|
|
98
|
+
def create_app() -> FastAPI:
|
|
99
|
+
application = FastAPI(title="FT182 email")
|
|
100
|
+
application.include_router(router)
|
|
101
|
+
return application
|
|
102
|
+
|
|
103
|
+
app = create_app() # ← ここで全ルートが含まれる
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**副次効果**: テストで 404 が出た際に「ルーターが空」と気づくまで
|
|
107
|
+
エンドポイント名のタイポ・パスの誤りを疑ってしまった。
|
|
108
|
+
診断手順として `python -c "from app import app; print([r.path for r in app.routes])"` が有効。
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 観察点
|
|
113
|
+
|
|
114
|
+
### 観察1: `email` モジュールには 3 つの API が混在する
|
|
115
|
+
|
|
116
|
+
Python の `email` モジュールは長い歴史を持ち、3 種類のクラスが共存している。
|
|
117
|
+
|
|
118
|
+
| API | クラス | 用途 |
|
|
119
|
+
|---|---|---|
|
|
120
|
+
| モダン(Python 3.3+)| `EmailMessage` | シンプルなテキストメール構築に最適 |
|
|
121
|
+
| レガシー MIME | `MIMEMultipart`, `MIMEText`, `MIMEBase` | HTML・添付ファイルのマルチパート構築 |
|
|
122
|
+
| パース用 | `Message` (`email.message_from_bytes` の返り値) | 受信メールの解析 |
|
|
123
|
+
|
|
124
|
+
`EmailMessage` は `MIMEMultipart("alternative")` を意識せずに書けるが、
|
|
125
|
+
添付ファイルや HTML+テキスト の細かい制御は `MIMEMultipart` の方が明確。
|
|
126
|
+
`parse_email()` は `email.message_from_bytes()` が `Message` を返すため、
|
|
127
|
+
`is_multipart()` / `walk()` でパートを手動で走査する必要がある。
|
|
128
|
+
|
|
129
|
+
### 観察2: `parseaddr()` は非常に寛容
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from email.utils import parseaddr
|
|
133
|
+
|
|
134
|
+
parseaddr("alice@example.com") # → ('', 'alice@example.com')
|
|
135
|
+
parseaddr("Alice <alice@example.com>") # → ('Alice', 'alice@example.com')
|
|
136
|
+
parseaddr("Not An Email") # → ('', 'Not') ← 驚き!
|
|
137
|
+
parseaddr("") # → ('', '')
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`parseaddr("Not An Email")` は `('', 'Not')` を返す。
|
|
141
|
+
単語の最初の部分を「アドレス」として解釈する。
|
|
142
|
+
`addr` が空文字列かどうかでしか「アドレスなし」を検出できず、
|
|
143
|
+
不正な文字列でも `None` を返さない。
|
|
144
|
+
アプリレベルで `validate_email_address()` による追加チェックが必要。
|
|
145
|
+
|
|
146
|
+
### 観察3: 添付ファイルのファイル名はサニタイズが必須
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
safe_filename = re.sub(r"[^\w\-.]", "_", attachment_filename)[:255]
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`../../etc/passwd` のようなパストラバーサル文字列が `Content-Disposition: attachment; filename=` に
|
|
153
|
+
そのまま入るとクライアントが危険な場所に保存する可能性がある。
|
|
154
|
+
`re.sub` で英数字・ハイフン・ドット以外を `_` に変換することで無害化する。
|
|
155
|
+
`../../etc/passwd` → `__.._.._etc_passwd` となり意図が変わるが、
|
|
156
|
+
ファイル名として安全になる。
|
|
157
|
+
|
|
158
|
+
### 観察4: RFC 2047 エンコードは `email.header` で行うより手動 Base64 が明確
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
# 標準ライブラリ的な書き方
|
|
162
|
+
from email.header import Header
|
|
163
|
+
Header("テスト件名", "utf-8") # → '=?utf-8?b?...' だが使い方が複雑
|
|
164
|
+
|
|
165
|
+
# 手動 Base64 — 動作が明確
|
|
166
|
+
import base64
|
|
167
|
+
encoded = base64.b64encode("テスト件名".encode("utf-8")).decode("ascii")
|
|
168
|
+
result = f"=?utf-8?B?{encoded}?="
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
`email.header.Header` クラスは行長の自動折り返し機能を持つが、
|
|
172
|
+
`make_header()` + `decode_header()` のコンビでデコードしないと復元できない。
|
|
173
|
+
手動実装の方が「何を送っているか」が透明で、デバッグしやすい。
|
|
174
|
+
|
|
175
|
+
### 観察5: `message_from_bytes()` は不正データでも例外を投げない
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
import email
|
|
179
|
+
msg = email.message_from_bytes(b"totally not an email")
|
|
180
|
+
# → Message オブジェクトが返る(例外なし)
|
|
181
|
+
msg.get("From") # → None
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
`message_from_bytes()` は入力をどんなバイト列でも解析しようとする。
|
|
185
|
+
完全に不正なデータでも `None` ではなく空の `Message` オブジェクトを返す。
|
|
186
|
+
`parse_email()` が `None` を返すのは、後段の処理で例外が起きた場合のみ。
|
|
187
|
+
「パース失敗」の検出には From/Subject が空かどうかを追加チェックする必要がある。
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## nene2-python フレームワークとの統合
|
|
192
|
+
|
|
193
|
+
- `build_simple_email()` / `build_html_email()` は通知メール送信 UseCase の内部実装として直接使える
|
|
194
|
+
- `parse_email()` の `ParsedEmail` は `@dataclass(frozen=True, slots=True)` なので UseCase の Output 型として適合
|
|
195
|
+
- `validate_email_address()` は HTTP 境界の Pydantic バリデーションと二重防御として有効
|
|
196
|
+
- `encode_header_value()` は日本語件名を含む全メール送信で必須になる
|
|
197
|
+
- Pydantic `max_length=10_485_760`(hex 換算 5MB)で添付ファイルサイズを HTTP 層で制限
|
|
198
|
+
- F-1 の教訓: `create_app()` ファクトリはファイル末尾に置く — nene2-python の全 FT サンドボックスで統一すべきルール
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Developer Experience (DX) Review
|
|
203
|
+
|
|
204
|
+
### ペルソナ1: 初心者(Python 歴1年・独学中・女性・バックエンド志望)
|
|
205
|
+
|
|
206
|
+
「メール送信機能を実装してほしい」と言われ、Python の `email` モジュールを調べている。
|
|
207
|
+
|
|
208
|
+
**ドキュメント理解**: `gzip.compress()` のような1行APIがなく、`EmailMessage` / `MIMEMultipart` / `MIMEText` を
|
|
209
|
+
どれを使えばよいか公式ドキュメントだけでは判断しにくい。
|
|
210
|
+
`email.message.Message` と `email.message.EmailMessage` が別クラスなのも混乱の源。
|
|
211
|
+
**事故リスク**: 高。`parseaddr()` が不正なアドレスを「有効」として返す挙動(F-2 相当)を
|
|
212
|
+
知らないと、バリデーションをすり抜けた無効アドレスに送信しようとする可能性がある。
|
|
213
|
+
**規約の使いやすさ**: `EmailMessage.set_content()` はシンプルだが、添付ファイルを加えた途端に
|
|
214
|
+
`MIMEMultipart` に切り替える必要があり、一貫性がない。
|
|
215
|
+
|
|
216
|
+
### ペルソナ2: ロースキル経験者(Python 歴3-4年・スクリプト系・男性・SES)
|
|
217
|
+
|
|
218
|
+
過去に `smtplib` + `email.mime` を使ったスクリプトをコピーして使ったことがある。
|
|
219
|
+
|
|
220
|
+
**コピペ可能性**: `MIMEMultipart` + `MIMEText` の組み合わせは古いブログ記事に多い。
|
|
221
|
+
ただし `Content-Disposition: attachment; filename=` にユーザー入力をそのまま渡す
|
|
222
|
+
サンプルコードが多く、F-1 相当のファイル名インジェクションをそのまま踏む。
|
|
223
|
+
**拡張時の罠**: F-1 (app 配置の問題) は修正後も「なぜ末尾に置くのか」を理解しないまま
|
|
224
|
+
他のファイルに同じミスをする可能性が高い。
|
|
225
|
+
**セキュリティ的な事故リスク**: 中。添付ファイル名のサニタイズ漏れは
|
|
226
|
+
メールクライアント依存でクライアント側のパストラバーサルになりうる。
|
|
227
|
+
|
|
228
|
+
### ペルソナ3: フロントエンド寄り経験者(React/TS 歴4年・バックエンド転向中・ノンバイナリ)
|
|
229
|
+
|
|
230
|
+
フロントエンドから「送信ボタン」を押したときにメール送信 API を呼ぶ機能を実装している。
|
|
231
|
+
|
|
232
|
+
**エラーレスポンスの質**: 不正なアドレスや無効 hex に対して 400 を返す設計は良い。
|
|
233
|
+
ただし「どのフィールドが不正か」を detail に含めていないため、
|
|
234
|
+
クライアント側でのデバッグ(どのフィールドを直せばよいか)が難しい。
|
|
235
|
+
**Python 固有概念の学習コスト**: `bytes.hex()` / `bytes.fromhex()` を API の境界で使うパターンは
|
|
236
|
+
JS の `ArrayBuffer` → `Uint8Array` の感覚と近く、理解しやすい。
|
|
237
|
+
RFC 2047 の `=?utf-8?B?...?=` 形式は HTTP ヘッダーとは別の概念なので説明が必要。
|
|
238
|
+
**事故リスク**: 低。HTTP 境界での Pydantic バリデーションが充実。
|
|
239
|
+
|
|
240
|
+
### ペルソナ4: バックエンド経験者(Django/FastAPI 歴5-6年・男性・リードエンジニア)
|
|
241
|
+
|
|
242
|
+
既存の Django プロジェクトにあるメール送信コードを FastAPI に移植しようとしている。
|
|
243
|
+
|
|
244
|
+
**他フレームワークとの差異**: Django は `django.core.mail.send_mail()` という高レベル API があり、
|
|
245
|
+
SMTP の設定・送信・バックエンド切り替えまでフレームワークが抽象化している。
|
|
246
|
+
Python stdlib の `email` モジュールはメッセージ構築のみで、送信は `smtplib` が別。
|
|
247
|
+
FastAPI には `django.core.mail` 相当はないため、このような薄い実装が必要になる。
|
|
248
|
+
**nene2-python の薄さへの評価**: メッセージ構築ロジックを UseCase に閉じ込めることで
|
|
249
|
+
テストで `smtplib` を使わずにメール内容を検証できる設計は良い。
|
|
250
|
+
`SendEmailUseCase` → `EmailGatewayInterface` → `SmtplibEmailGateway` の構造が自然な次ステップ。
|
|
251
|
+
**本番投入可能性**: メッセージ構築は本番品質。送信部分(smtplib/外部SMTP)は別途実装が必要。
|
|
252
|
+
|
|
253
|
+
### ペルソナ5: シニアエンジニア(設計・コードレビュー担当・女性・10-12年)
|
|
254
|
+
|
|
255
|
+
チームのメンバーが書いた「メール送信機能」を PR レビューしている。
|
|
256
|
+
|
|
257
|
+
**コードレビューチェックポイント**:
|
|
258
|
+
- [x] `parseaddr()` の戻り値が空文字列チェックのみで、`validate_email_address()` の追加チェックがあるか
|
|
259
|
+
- [x] `Content-Disposition: attachment; filename=` にユーザー入力がサニタイズされているか
|
|
260
|
+
- [x] `create_app()` がデコレータより後に呼ばれているか(F-1 の罠)
|
|
261
|
+
- [x] `message_from_bytes()` の返り値が None チェックなしに使われていないか(返り値は常に Message)
|
|
262
|
+
|
|
263
|
+
**チームでの安全なパターン**: メールアドレスは `parseaddr()` 後に必ず `validate_email_address()` を
|
|
264
|
+
通す二重チェックを社内標準とすること。
|
|
265
|
+
**ツール追加の必要性**: `app = create_app()` の配置問題は静的解析で検出できない。
|
|
266
|
+
`create_app()` を呼ぶ前に `router.routes` が空かどうかをアサートするテストケースを
|
|
267
|
+
毎回書くルールにすることで防げる。
|
|
268
|
+
|
|
269
|
+
### ペルソナ6: 設計者・ポリシー照合(nene2-python 設計ポリシー目線)
|
|
270
|
+
|
|
271
|
+
**ポリシー達成度**: 高
|
|
272
|
+
**「初心者でも安全な API」達成度**: 中
|
|
273
|
+
— `parseaddr()` の寛容さと `create_app()` の配置問題は初心者が踏みやすい罠。
|
|
274
|
+
特に F-1 は「テストが全部 404」という分かりやすい症状だが、原因に気づくまでが辛い。
|
|
275
|
+
**設計上の負債**: `create_app()` をファイル末尾に置くルールが CLAUDE.md に明記されていない。
|
|
276
|
+
FT177 から APIRouter パターンを使い始めているが、この制約はどこにも書かれていない。
|
|
277
|
+
**Follow-up Issue 候補**: CLAUDE.md への `create_app()` 配置ルール追記
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Follow-up Issues
|
|
282
|
+
|
|
283
|
+
| 優先度 | タイトル | 種別 |
|
|
284
|
+
|---|---|---|
|
|
285
|
+
| 中 | CLAUDE.md に「`create_app()` はファイル末尾・全デコレータの後に定義する」ルールを追記 | docs |
|
|
286
|
+
| 低 | `parseaddr()` の寛容な挙動を How-to ドキュメントに記載(二重チェックパターン) | docs |
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## まとめ
|
|
291
|
+
|
|
292
|
+
FT182 では `email` モジュールを中心に、MIME メッセージ構築・ヘッダーエンコード・パース・
|
|
293
|
+
アドレス操作を実装した。57 テストが全通過し、mypy/ruff も問題なし。
|
|
294
|
+
|
|
295
|
+
最大の発見は F-1: `app = create_app()` をルート定義より前に置くと
|
|
296
|
+
`include_router()` が空のルーターをコピーして全エンドポイントが 404 になる問題。
|
|
297
|
+
FastAPI の `include_router()` がコール時点のスナップショットを取るため、
|
|
298
|
+
`app = create_app()` はファイル末尾に置くルールが必要。
|
|
299
|
+
この制約を CLAUDE.md に追記することを Follow-up Issue として記録した。
|
|
300
|
+
|
|
301
|
+
v1.8.53 としてリリース。
|