nene2-python 1.8.19__tar.gz → 1.8.21__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.19 → nene2_python-1.8.21}/CHANGELOG.md +26 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/CLAUDE.md +29 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/PKG-INFO +1 -1
- nene2_python-1.8.21/docs/field-trials/2026-05-field-trial-69.md +58 -0
- nene2_python-1.8.21/docs/field-trials/2026-05-field-trial-70.md +67 -0
- nene2_python-1.8.21/docs/field-trials/2026-05-field-trial-71.md +83 -0
- nene2_python-1.8.21/docs/field-trials/2026-05-field-trial-72.md +72 -0
- nene2_python-1.8.21/docs/field-trials/2026-05-field-trial-73.md +69 -0
- nene2_python-1.8.21/docs/field-trials/2026-05-field-trial-74.md +76 -0
- nene2_python-1.8.21/docs/field-trials/2026-05-field-trial-75.md +128 -0
- nene2_python-1.8.21/docs/how-to/middleware-stack.md +111 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/pyproject.toml +1 -1
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/middleware/__init__.py +2 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/middleware/error_handler.py +44 -1
- nene2_python-1.8.21/src/nene2/middleware/setup.py +142 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/nene2/middleware/test_error_handler.py +40 -0
- nene2_python-1.8.21/tests/nene2/middleware/test_setup_middlewares.py +124 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/uv.lock +1 -1
- {nene2_python-1.8.19 → nene2_python-1.8.21}/.env.example +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/.github/workflows/ci.yml +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/.github/workflows/docs.yml +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/.github/workflows/publish.yml +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/.gitignore +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/.vitepress/config.mts +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/.vitepress/theme/custom.css +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/.vitepress/theme/index.ts +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/AGENTS.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/Dockerfile +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/LICENSE +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/README.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/alembic/README +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/alembic/env.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/alembic/script.py.mako +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/alembic.ini +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/compose.yaml +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/adr/0001-toolchain.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/adr/0002-clean-architecture.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/adr/0003-security-first.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/adr/0004-ai-first-design.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/adr/0005-logging.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/adr/0006-rate-limiting.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/adr/0009-mcp-design.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/adr/0010-async-use-case.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/de/index.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/de/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/explanation/architecture.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-1.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-10.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-11.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-12.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-13.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-14.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-15.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-16.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-17.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-18.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-19.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-2.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-20.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-21.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-22.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-23.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-24.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-25.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-26.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-27.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-28.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-29.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-3.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-30.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-31.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-32.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-33.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-34.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-35.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-36.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-37.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-38.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-39.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-4.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-40.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-41.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-42.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-43.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-44.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-45.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-46.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-47.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-48.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-49.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-5.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-50.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-51.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-52.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-53.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-54.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-55.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-56.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-57.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-58.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-59.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-6.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-60.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-61.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-62.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-63.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-64.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-65.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-66.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-67.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-68.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-7.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-8.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/field-trials/2026-05-field-trial-9.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/fr/index.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/fr/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/how-to/async-use-case.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/how-to/new-project.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/how-to/problem-details.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/how-to/run-tests.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/how-to/validation.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/index.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/ja/explanation/architecture.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/ja/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/ja/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/ja/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/ja/how-to/new-project.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/ja/how-to/run-tests.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/ja/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/ja/index.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/ja/reference/api.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/ja/reference/configuration.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/ja/reference/framework-modules.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/ja/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/ja/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/pt-br/index.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/pt-br/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/reference/api.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/reference/configuration.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/reference/framework-modules.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/roadmap.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/todo/current.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/zh/index.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/docs/zh/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/package-lock.json +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/package.json +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/example/__init__.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/example/__main__.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/example/app.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/example/comment/__init__.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/example/comment/entity.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/example/comment/exceptions.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/example/comment/handler.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/example/comment/repository.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/example/comment/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/example/comment/use_case.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/example/mcp.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/example/note/__init__.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/example/note/async_use_case.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/example/note/entity.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/example/note/exceptions.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/example/note/handler.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/example/note/repository.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/example/note/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/example/note/use_case.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/example/schema.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/example/tag/__init__.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/example/tag/entity.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/example/tag/exceptions.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/example/tag/handler.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/example/tag/repository.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/example/tag/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/example/tag/use_case.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/__init__.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/auth/api_key.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/auth/bearer_token.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/auth/exceptions.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/auth/interfaces.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/auth/local_verifier.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/config/settings.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/database/exceptions.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/database/health.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/database/interfaces.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/database/sqlalchemy_executor.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/database/utils.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/http/health.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/http/pagination.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/http/problem_details.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/log/setup.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/mcp/http_client.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/mcp/server.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/middleware/domain_exception.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/middleware/request_id.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/middleware/request_logging.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/middleware/request_size_limit.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/middleware/security_headers.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/middleware/throttle.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/py.typed +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/use_case/protocols.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/nene2/validation/exceptions.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/scripts/__init__.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/src/scripts/export_openapi.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/__init__.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/example/__init__.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/example/comment/__init__.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/example/comment/test_comment_http.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/example/comment/test_comment_repository.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/example/comment/test_comment_use_case.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/example/conftest.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/example/note/__init__.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/example/note/test_async_note_use_case.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/example/note/test_list_notes.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/example/note/test_note_repository.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/example/tag/__init__.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/example/tag/test_tag_repository.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/example/tag/test_tags.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/example/test_cors.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/example/test_mcp.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/nene2/__init__.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/nene2/auth/test_api_key.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/nene2/auth/test_bearer_token.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/nene2/auth/test_token_issuer.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/nene2/config/test_settings.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/nene2/database/test_transaction.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/nene2/database/test_utils.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/nene2/http/test_health.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/nene2/http/test_pagination.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/nene2/http/test_problem_details.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/nene2/log/test_setup.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/nene2/mcp/test_http_client.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/nene2/middleware/__init__.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/nene2/middleware/test_request_id.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/nene2/middleware/test_request_logging.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/nene2/middleware/test_request_size_limit.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/nene2/middleware/test_security_headers.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/nene2/middleware/test_throttle.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/nene2/use_case/test_protocols.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/nene2/validation/test_exceptions.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/scripts/__init__.py +0 -0
- {nene2_python-1.8.19 → nene2_python-1.8.21}/tests/scripts/test_export_openapi.py +0 -0
|
@@ -5,6 +5,32 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
## [1.8.21] — 2026-05-20
|
|
9
|
+
|
|
10
|
+
FT75 フィールドトライアル — ミドルウェアスタック順序問題の発見と根本解決。
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `setup_middlewares(app, ...)` ユーティリティ関数を追加 (#320 #321) (FT75)
|
|
14
|
+
— 全ミドルウェアを正しい順序(RequestId 最外側・ErrorHandler 最内側)で一括登録し、
|
|
15
|
+
エラーレスポンスにも X-Request-Id とセキュリティヘッダーが確実に付与される
|
|
16
|
+
- `docs/how-to/middleware-stack.md` — ミドルウェア順序の解説ガイドを追加
|
|
17
|
+
- CLAUDE.md セクション 8 に推奨 `add_middleware` 順序を追記
|
|
18
|
+
- Field trial report: `docs/field-trials/2026-05-field-trial-75.md` (FT75)
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## [1.8.20] — 2026-05-20
|
|
23
|
+
|
|
24
|
+
FT72 フィールドトライアル — DatabaseIntegrityException + ErrorHandlerMiddleware.install() 改善。
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
- `ErrorHandlerMiddleware.install(app)` クラスメソッドを追加 (#315) (FT72)
|
|
28
|
+
— `add_middleware` と `add_exception_handler(RequestValidationError)` を一度に設定し、
|
|
29
|
+
Pydantic の 422 バリデーションエラーも nene2 Problem Details 形式に自動統一する
|
|
30
|
+
- Field trial reports: `docs/field-trials/2026-05-field-trial-70.md` 〜 `2026-05-field-trial-72.md` (FT70〜FT72)
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
8
34
|
## [1.8.19] — 2026-05-20
|
|
9
35
|
|
|
10
36
|
FT68 フィールドトライアル — SimpleDomainHandler + extra_factory 実運用検証。
|
|
@@ -233,6 +233,35 @@ AI エージェント(Claude 等)がこのコードベースを正確に理
|
|
|
233
233
|
- `nene2.http.problem_details_response()` で RFC 9457 エラー応答
|
|
234
234
|
- `nene2.http.PaginationQueryParser` でページネーション
|
|
235
235
|
|
|
236
|
+
### ミドルウェアスタック順序(重要)
|
|
237
|
+
|
|
238
|
+
`app.add_middleware()` は **LIFO**(後から追加したものが外側になる)。
|
|
239
|
+
直感と逆なので注意 — 「外側に置きたいものを後から追加する」。
|
|
240
|
+
|
|
241
|
+
**推奨 `add_middleware` 呼び出し順**(最初が最内側・最後が最外側):
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
# ✅ 正しい順序
|
|
245
|
+
app.add_middleware(ErrorHandlerMiddleware) # 最内側: ハンドラー例外を捕捉
|
|
246
|
+
app.add_middleware(RequestLoggingMiddleware) # ↑
|
|
247
|
+
app.add_middleware(ThrottleMiddleware, ...) # |
|
|
248
|
+
app.add_middleware(RequestSizeLimitMiddleware, ...) # |
|
|
249
|
+
app.add_middleware(SecurityHeadersMiddleware) # ↓ 全レスポンスにヘッダー付与
|
|
250
|
+
app.add_middleware(RequestIdMiddleware) # 最外側: 全レスポンスに X-Request-Id 付与
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
# ❌ よくある間違い — ErrorHandler を最後(最外側)に追加
|
|
255
|
+
app.add_middleware(RequestIdMiddleware)
|
|
256
|
+
app.add_middleware(ErrorHandlerMiddleware) # 最外側にすると...
|
|
257
|
+
# → 500 エラーに X-Request-Id が付かない(内側のミドルウェアをバイパスするため)
|
|
258
|
+
# → 500 エラーにセキュリティヘッダーが付かない
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
`ErrorHandlerMiddleware` が例外を捕捉して新しい Response を返すとき、
|
|
262
|
+
それより内側のミドルウェアはバイパスされる(Starlette の `BaseHTTPMiddleware` の仕様)。
|
|
263
|
+
`RequestIdMiddleware` と `SecurityHeadersMiddleware` は必ず **ErrorHandler より外側** に置くこと。
|
|
264
|
+
|
|
236
265
|
### REST 規約
|
|
237
266
|
- リソース名は複数形: `/notes`, `/tags`
|
|
238
267
|
- ID はパスパラメータ: `/notes/{note_id}`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nene2-python
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.21
|
|
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,58 @@
|
|
|
1
|
+
# FT69: PaginationQueryParser + PaginationResponse 実運用検証
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-20
|
|
4
|
+
**テーマ**: ページネーション機能 (`PaginationQueryParser` + `PaginationResponse`) の実運用確認
|
|
5
|
+
**バージョン**: v1.8.19
|
|
6
|
+
**FTディレクトリ**: `/home/xi/docker/nene2-python-FT/ft69-pagination/`
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 概要
|
|
11
|
+
|
|
12
|
+
`nene2.http.PaginationQueryParser` を FastAPI の `Depends()` で使い、
|
|
13
|
+
`PaginationResponse` でレスポンスを組み立てるパターンを検証した。
|
|
14
|
+
dataclass アイテムの自動シリアライズも確認。
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 実装内容
|
|
19
|
+
|
|
20
|
+
- `PaginationQueryParser` を `Annotated[..., Depends()]` で受け取り
|
|
21
|
+
- `pagination.limit` / `pagination.offset` でスライス
|
|
22
|
+
- `PaginationResponse(items, limit, offset, total)` で標準レスポンス
|
|
23
|
+
- `total=None` 時に `total` フィールドが省略されることを確認
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## テスト結果
|
|
28
|
+
|
|
29
|
+
**8/8 passed**
|
|
30
|
+
|
|
31
|
+
| テスト | 結果 |
|
|
32
|
+
|---|---|
|
|
33
|
+
| `test_default_pagination_returns_first_20` | PASSED |
|
|
34
|
+
| `test_custom_limit_and_offset` | PASSED |
|
|
35
|
+
| `test_limit_too_large_returns_422` | PASSED |
|
|
36
|
+
| `test_limit_zero_returns_422` | PASSED |
|
|
37
|
+
| `test_negative_offset_returns_422` | PASSED |
|
|
38
|
+
| `test_no_total_omits_total_field` | PASSED |
|
|
39
|
+
| `test_last_page_returns_remaining_items` | PASSED |
|
|
40
|
+
| `test_dataclass_items_serialized_to_dict` | PASSED |
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Friction Points
|
|
45
|
+
|
|
46
|
+
なし。`PaginationQueryParser` + `PaginationResponse` はすべて直感的に動作した。
|
|
47
|
+
|
|
48
|
+
**特筆点**:
|
|
49
|
+
- `limit=0` や `limit>100` は FastAPI の Query バリデーションが自動で 422 を返す
|
|
50
|
+
- `PaginationResponse.to_dict()` が dataclass インスタンスを自動でシリアライズするのが便利
|
|
51
|
+
- `total=None` 時に `total` フィールドが省略されるため、カーソルページネーションにも対応可能
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 結論
|
|
56
|
+
|
|
57
|
+
`PaginationQueryParser` + `PaginationResponse` は実運用で問題なく使用できる。
|
|
58
|
+
FastAPI の Depends と自然に連携し、デフォルト値 (limit=20, max=100) も適切。
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# FT70: 複数ドメイン連携 実運用検証
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-20
|
|
4
|
+
**テーマ**: 複数ドメイン連携(Post + Comment ネストリソース API)の実運用確認
|
|
5
|
+
**バージョン**: v1.8.19
|
|
6
|
+
**FTディレクトリ**: `/home/xi/docker/nene2-python-FT/ft70-multi-domain/`
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 概要
|
|
11
|
+
|
|
12
|
+
Post ドメインと Comment ドメインを組み合わせたネストリソース API を実装し、
|
|
13
|
+
複数のフレームワーク機能を同時に使用した際の統合動作を検証した。
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 実装内容
|
|
18
|
+
|
|
19
|
+
- `GET /posts` — `PaginationQueryParser` + `PaginationResponse` でページネーション
|
|
20
|
+
- `POST /posts` — `SqlAlchemyTransactionManager.transactional()` でインサート
|
|
21
|
+
- `GET /posts/{post_id}` — 存在しない場合 404 (Problem Details)
|
|
22
|
+
- `GET /posts/{post_id}/comments` — 親リソース存在チェック付きページネーション
|
|
23
|
+
- `POST /posts/{post_id}/comments` — 親リソース存在チェック付きインサート
|
|
24
|
+
- `RequestIdMiddleware` + `ErrorHandlerMiddleware` をスタック
|
|
25
|
+
- `StaticPool` を使ったインメモリ SQLite(接続共有)
|
|
26
|
+
- `dataclass(frozen=True, slots=True)` を Post / Comment の値オブジェクトに使用
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## テスト結果
|
|
31
|
+
|
|
32
|
+
**10/10 passed**
|
|
33
|
+
|
|
34
|
+
| テスト | 結果 |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `test_list_posts_returns_paginated` | PASSED |
|
|
37
|
+
| `test_get_post_returns_200` | PASSED |
|
|
38
|
+
| `test_get_nonexistent_post_returns_404` | PASSED |
|
|
39
|
+
| `test_create_post_returns_201` | PASSED |
|
|
40
|
+
| `test_list_comments_for_post` | PASSED |
|
|
41
|
+
| `test_list_comments_for_nonexistent_post_returns_404` | PASSED |
|
|
42
|
+
| `test_create_comment_returns_201` | PASSED |
|
|
43
|
+
| `test_create_comment_for_nonexistent_post_returns_404` | PASSED |
|
|
44
|
+
| `test_request_id_header_present` | PASSED |
|
|
45
|
+
| `test_comment_count_increases_after_create` | PASSED |
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Friction Points
|
|
50
|
+
|
|
51
|
+
なし。複数ドメインを組み合わせた際もすべてのフレームワーク機能が正常に動作した。
|
|
52
|
+
|
|
53
|
+
**特筆点**:
|
|
54
|
+
- `StaticPool` は FT67 で習得済みのため、インメモリ SQLite の設定は迷わず実施できた
|
|
55
|
+
- `PaginationQueryParser` + `PaginationResponse` のネストリソース(`/posts/{id}/comments`)への適用も自然
|
|
56
|
+
- `SqlAlchemyTransactionManager.transactional()` で Post と Comment の両インサートが問題なく動作
|
|
57
|
+
- `RequestIdMiddleware` は `ErrorHandlerMiddleware` と共存可能(ミドルウェアスタック順序の制約なし)
|
|
58
|
+
- `problem_details_response()` の 404 応答は親リソース (`post-not-found`) に統一可能
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 結論
|
|
63
|
+
|
|
64
|
+
nene2-python の主要機能(Pagination, Transaction, RequestId, ErrorHandler, Problem Details)を
|
|
65
|
+
複数ドメイン連携シナリオで組み合わせても一切の摩擦がなかった。
|
|
66
|
+
FT57〜FT69 で修正・ドキュメント化された機能がすべて期待通り動作し、
|
|
67
|
+
フレームワークの統合品質が高いことを確認できた。
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# FT71: 完全レイヤードアーキテクチャ実運用検証
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-20
|
|
4
|
+
**テーマ**: UseCaseProtocol + Repository パターンによる完全レイヤードアーキテクチャ検証
|
|
5
|
+
**バージョン**: v1.8.19
|
|
6
|
+
**FTディレクトリ**: `/home/xi/docker/nene2-python-FT/ft71-layered-arch/`
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 概要
|
|
11
|
+
|
|
12
|
+
NENE2 の核心設計パターン(HTTP Handler → UseCase → Repository の完全分離)を
|
|
13
|
+
Todo ドメインで実装し、`UseCaseProtocol`・`ValidationException`・`InMemoryRepository` 注入が
|
|
14
|
+
実運用で問題なく動作することを確認した。
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 実装内容
|
|
19
|
+
|
|
20
|
+
### ドメイン層(HTTP・DB 非依存)
|
|
21
|
+
- `Todo(id, title, done)` — `dataclass(frozen=True, slots=True)` 値オブジェクト
|
|
22
|
+
- Input/Output DTO 群 — `CreateTodoInput/Output`, `GetTodoInput/Output`, `ListTodosInput/Output`, `CompleteTodoInput/Output`
|
|
23
|
+
- `TodoRepositoryInterface` — `abc.ABC` 抽象インターフェース
|
|
24
|
+
- UseCase 群: `CreateTodoUseCase`, `GetTodoUseCase`, `ListTodosUseCase`, `CompleteTodoUseCase`
|
|
25
|
+
|
|
26
|
+
### インフラ層
|
|
27
|
+
- `InMemoryTodoRepository` — テスト用インメモリ実装
|
|
28
|
+
- `SqlAlchemyTodoRepository` — 本番用 SQLAlchemy 実装
|
|
29
|
+
|
|
30
|
+
### HTTP 層(thin handler)
|
|
31
|
+
- `create_app(repository=None)` — `repository` が None の場合 SQLAlchemy を使用、注入された場合はそのまま使用
|
|
32
|
+
- Handler は parse → use-case → response の 3 ステップのみ
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## テスト結果
|
|
37
|
+
|
|
38
|
+
**15/15 passed**
|
|
39
|
+
|
|
40
|
+
| テスト | 結果 | 種別 |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| `test_create_todo_use_case_returns_new_todo` | PASSED | UseCase 単体 |
|
|
43
|
+
| `test_create_todo_use_case_raises_validation_error_on_blank_title` | PASSED | UseCase 単体 |
|
|
44
|
+
| `test_get_todo_use_case_returns_existing_todo` | PASSED | UseCase 単体 |
|
|
45
|
+
| `test_list_todos_use_case_returns_all_items` | PASSED | UseCase 単体 |
|
|
46
|
+
| `test_complete_todo_use_case_marks_done` | PASSED | UseCase 単体 |
|
|
47
|
+
| `test_list_todos_returns_paginated` | PASSED | HTTP 統合 |
|
|
48
|
+
| `test_get_todo_returns_200` | PASSED | HTTP 統合 |
|
|
49
|
+
| `test_get_nonexistent_todo_returns_404` | PASSED | HTTP 統合 |
|
|
50
|
+
| `test_create_todo_returns_201` | PASSED | HTTP 統合 |
|
|
51
|
+
| `test_create_todo_with_blank_title_returns_422` | PASSED | HTTP 統合 |
|
|
52
|
+
| `test_complete_todo_marks_done` | PASSED | HTTP 統合 |
|
|
53
|
+
| `test_complete_nonexistent_todo_returns_404` | PASSED | HTTP 統合 |
|
|
54
|
+
| `test_list_todos_pagination_offset` | PASSED | HTTP 統合 |
|
|
55
|
+
| `test_request_id_header_present` | PASSED | HTTP 統合 |
|
|
56
|
+
| `test_in_memory_repo_used_for_unit_tests_without_db` | PASSED | DI 注入 |
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Friction Points
|
|
61
|
+
|
|
62
|
+
なし。
|
|
63
|
+
|
|
64
|
+
**特筆点**:
|
|
65
|
+
- `ValidationException` を UseCase 内(ドメイン層)で raise しても、
|
|
66
|
+
`ErrorHandlerMiddleware` が自動で 422 Problem Details に変換する。
|
|
67
|
+
ドメイン層が HTTP を知らなくてよい。
|
|
68
|
+
- `create_app(repository=InMemoryTodoRepository(...))` の DI 注入パターンで
|
|
69
|
+
HTTP テストも DB なしで実行できる。5 つの UseCase 単体テストは完全に DB 非依存。
|
|
70
|
+
- `UseCaseProtocol[I, O]` の静的型チェックは `isinstance()` ではなく型注釈で行う。
|
|
71
|
+
`def _assert_protocols() -> None: _: UseCaseProtocol[...] = SomeUseCase(...)` のパターンで
|
|
72
|
+
mypy が protocol 適合を静的に保証する。
|
|
73
|
+
- `InMemoryTodoRepository` の `mark_done()` 実装で `done=True` に更新するため
|
|
74
|
+
リスト全体を再構築するパターン(frozen dataclass なので in-place 変更不可)が自然に強制される。
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 結論
|
|
79
|
+
|
|
80
|
+
`UseCaseProtocol` + `Repository Interface` + `InMemoryRepository` の三点セットで
|
|
81
|
+
NENE2 の完全レイヤードアーキテクチャが機能する。
|
|
82
|
+
UseCase 単体テスト(DB なし)と HTTP 統合テスト(SQLAlchemy 経由)を
|
|
83
|
+
同一 `create_app()` ファクトリで切り替えられる DI パターンが特に有用。
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# FT72: DatabaseIntegrityException + write() 戻り値パターン実運用検証
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-20
|
|
4
|
+
**テーマ**: ユニーク制約違反 (409) と UPDATE/DELETE の rowcount=0 (404) パターン検証
|
|
5
|
+
**バージョン**: v1.8.19
|
|
6
|
+
**FTディレクトリ**: `/home/xi/docker/nene2-python-FT/ft72-db-integrity/`
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 概要
|
|
11
|
+
|
|
12
|
+
ユーザー登録 API でユニーク制約違反 → `DatabaseIntegrityException` → 409 のマッピングと、
|
|
13
|
+
`write()` 戻り値 0 を使った UPDATE/DELETE の「対象なし → 404」パターンを実運用で検証した。
|
|
14
|
+
あわせて `request_validation_error_handler` による Pydantic 422 → nene2 形式変換も確認した。
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 実装内容
|
|
19
|
+
|
|
20
|
+
- `users` テーブル(`username UNIQUE`, `email UNIQUE`)
|
|
21
|
+
- `POST /users` — INSERT、重複時 `DatabaseIntegrityException` → 409
|
|
22
|
+
- `GET /users/{id}` — 存在しない場合 404
|
|
23
|
+
- `PUT /users/{id}/email` — UPDATE、`write()` 戻り値 0 → 404
|
|
24
|
+
- `DELETE /users/{id}` — DELETE、`write()` 戻り値 0 → 404、成功時 204
|
|
25
|
+
- `SimpleDomainHandler(DatabaseIntegrityException, "user-conflict", "Conflict", 409)` で自動変換
|
|
26
|
+
- `app.add_exception_handler(RequestValidationError, request_validation_error_handler)` で Pydantic 422 形式統一
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## テスト結果
|
|
31
|
+
|
|
32
|
+
**11/11 passed**
|
|
33
|
+
|
|
34
|
+
| テスト | 結果 |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `test_register_user_returns_201` | PASSED |
|
|
37
|
+
| `test_register_duplicate_username_returns_409` | PASSED |
|
|
38
|
+
| `test_register_duplicate_email_returns_409` | PASSED |
|
|
39
|
+
| `test_get_user_returns_200` | PASSED |
|
|
40
|
+
| `test_get_nonexistent_user_returns_404` | PASSED |
|
|
41
|
+
| `test_update_email_returns_200` | PASSED |
|
|
42
|
+
| `test_update_email_for_nonexistent_user_returns_404` | PASSED |
|
|
43
|
+
| `test_update_to_duplicate_email_returns_409` | PASSED |
|
|
44
|
+
| `test_delete_user_returns_204` | PASSED |
|
|
45
|
+
| `test_delete_nonexistent_user_returns_404` | PASSED |
|
|
46
|
+
| `test_register_invalid_body_returns_422_nene2_format` | PASSED |
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Friction Points
|
|
51
|
+
|
|
52
|
+
なし。
|
|
53
|
+
|
|
54
|
+
**特筆点**:
|
|
55
|
+
- `SimpleDomainHandler(DatabaseIntegrityException, ...)` で SQLAlchemy の IntegrityError を
|
|
56
|
+
透過的に 409 に変換できる。ハンドラー側のコードに `try/except` が不要。
|
|
57
|
+
- `write()` の戻り値セマンティクス(UPDATE/DELETE は rowcount、INSERT は lastrowid)により、
|
|
58
|
+
`if affected == 0: return 404` パターンが自然に書ける。
|
|
59
|
+
- `JSONResponse(None, status_code=204)` が 204 No Content として正しく動作する。
|
|
60
|
+
- `request_validation_error_handler` を `add_exception_handler(RequestValidationError, ...)` で
|
|
61
|
+
登録することで、Pydantic の 422 バリデーションエラーも nene2 の `validation-failed`
|
|
62
|
+
Problem Details 形式に統一できる。`ErrorHandlerMiddleware` だけではこのケースをカバーできないため、
|
|
63
|
+
完全な 422 形式統一には両方の登録が必要。
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 結論
|
|
68
|
+
|
|
69
|
+
`DatabaseIntegrityException` は `SimpleDomainHandler` で宣言的に 409 にマッピングでき、
|
|
70
|
+
`write()` の 0 戻り値で UPDATE/DELETE の「対象なし → 404」も慣用的に書ける。
|
|
71
|
+
Pydantic バリデーション 422 の形式統一には `request_validation_error_handler` の追加登録が必要な点は
|
|
72
|
+
覚えておく価値がある。
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# FT73: PaginationQueryParser.parse() 静的メソッド実運用検証
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-20
|
|
4
|
+
**テーマ**: `PaginationQueryParser.parse(Request)` レガシーパターンと `ErrorHandlerMiddleware.install()` の連携確認
|
|
5
|
+
**バージョン**: v1.8.20
|
|
6
|
+
**FTディレクトリ**: `/home/xi/docker/nene2-python-FT/ft73-pagination-parse/`
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 概要
|
|
11
|
+
|
|
12
|
+
`PaginationQueryParser.parse(request)` 静的メソッド(`Depends()` を使わないパターン)を検証した。
|
|
13
|
+
カスタム `default_limit` / `max_limit` の動作と、
|
|
14
|
+
v1.8.20 で追加した `ErrorHandlerMiddleware.install()` との連携も確認した。
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 実装内容
|
|
19
|
+
|
|
20
|
+
- `GET /items` — `PaginationQueryParser.parse(request)` でデフォルト (limit=20, max=100)
|
|
21
|
+
- `GET /items/custom` — `parse(request, default_limit=5, max_limit=10)` でカスタム値
|
|
22
|
+
- `ErrorHandlerMiddleware.install(app)` を使用(v1.8.20 の新機能)
|
|
23
|
+
- `ValidationException` による 422 を nene2 Problem Details 形式で返す
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## テスト結果
|
|
28
|
+
|
|
29
|
+
**11/11 passed**
|
|
30
|
+
|
|
31
|
+
| テスト | 結果 |
|
|
32
|
+
|---|---|
|
|
33
|
+
| `test_default_pagination_returns_20_items` | PASSED |
|
|
34
|
+
| `test_custom_limit_and_offset` | PASSED |
|
|
35
|
+
| `test_limit_too_large_returns_422` | PASSED |
|
|
36
|
+
| `test_limit_zero_returns_422` | PASSED |
|
|
37
|
+
| `test_negative_offset_returns_422` | PASSED |
|
|
38
|
+
| `test_non_integer_limit_returns_422` | PASSED |
|
|
39
|
+
| `test_non_integer_offset_returns_422` | PASSED |
|
|
40
|
+
| `test_custom_default_limit_applied` | PASSED |
|
|
41
|
+
| `test_custom_max_limit_enforced` | PASSED |
|
|
42
|
+
| `test_custom_max_limit_at_boundary` | PASSED |
|
|
43
|
+
| `test_last_page_returns_remaining_items` | PASSED |
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Friction Points
|
|
48
|
+
|
|
49
|
+
なし。
|
|
50
|
+
|
|
51
|
+
**特筆点**:
|
|
52
|
+
- `PaginationQueryParser.parse()` は `ValidationException` を raise するため、
|
|
53
|
+
`ErrorHandlerMiddleware.install(app)` と組み合わせると
|
|
54
|
+
非整数値・範囲外の入力が自動的に 422 nene2 Problem Details で返る。
|
|
55
|
+
- `parse()` が返す `PaginationQuery` (named dataclass) は `limit` / `offset` を保持し、
|
|
56
|
+
`Depends()` パターンの `PaginationQueryParser` インスタンスと同じインターフェースで使える。
|
|
57
|
+
- `default_limit` / `max_limit` のカスタマイズが `parse()` の引数で完結するため、
|
|
58
|
+
ルートごとに異なるページネーション制限を設定しやすい。
|
|
59
|
+
- `ErrorHandlerMiddleware.install(app)` が v1.8.20 で正式追加され、
|
|
60
|
+
`ValidationException` の 422 フォーマット統一が一行で完了するようになった。
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 結論
|
|
65
|
+
|
|
66
|
+
`PaginationQueryParser.parse()` は `Depends()` パターンが使えないシナリオ
|
|
67
|
+
(例: ミドルウェアや `WebSocket` ハンドラー内)で有効な代替手段。
|
|
68
|
+
`default_limit` / `max_limit` のカスタマイズと `ValidationException` の自動 422 変換が
|
|
69
|
+
`ErrorHandlerMiddleware.install()` との組み合わせで自然に機能する。
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# FT74: カスタム HealthCheckProtocol 実装の実運用検証
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-20
|
|
4
|
+
**テーマ**: ユーザー定義 `HealthCheckProtocol` / `AsyncHealthCheckProtocol` 実装と `CompositeHealthCheck` の連携
|
|
5
|
+
**バージョン**: v1.8.20
|
|
6
|
+
**FTディレクトリ**: `/home/xi/docker/nene2-python-FT/ft74-custom-health/`
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 概要
|
|
11
|
+
|
|
12
|
+
ユーザーが `HealthCheckProtocol` を実装する実際のシナリオ(メモリ・DB・外部サービス)を
|
|
13
|
+
`CompositeHealthCheck` / `AsyncCompositeHealthCheck` に組み合わせて動作を検証した。
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 実装内容
|
|
18
|
+
|
|
19
|
+
### カスタム同期ヘルスチェック
|
|
20
|
+
- `AlwaysOkCheck` — 常に ok を返すベースライン
|
|
21
|
+
- `ToggleableCheck` — テストで on/off できるトグル可能なチェック
|
|
22
|
+
- `MemoryCheck` — 閾値ベースのメモリ使用量チェック(psutil なしでシミュレート)
|
|
23
|
+
|
|
24
|
+
### カスタム非同期ヘルスチェック
|
|
25
|
+
- `AsyncAlwaysOkCheck` — 常に ok を返す非同期版
|
|
26
|
+
- `AsyncToggleableCheck` — 非同期版トグルチェック
|
|
27
|
+
|
|
28
|
+
### エンドポイント
|
|
29
|
+
- `GET /health` — `CompositeHealthCheck` (同期)
|
|
30
|
+
- `GET /health/async` — `AsyncCompositeHealthCheck` (非同期)
|
|
31
|
+
- `create_app(db_healthy, cache_healthy, memory_usage_pct)` でテストシナリオを切り替え
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## テスト結果
|
|
36
|
+
|
|
37
|
+
**13/13 passed**
|
|
38
|
+
|
|
39
|
+
| テスト | 結果 | 種別 |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| `test_always_ok_check_returns_ok` | PASSED | 単体 |
|
|
42
|
+
| `test_toggleable_check_returns_error_when_unhealthy` | PASSED | 単体 |
|
|
43
|
+
| `test_memory_check_ok_under_threshold` | PASSED | 単体 |
|
|
44
|
+
| `test_memory_check_error_over_threshold` | PASSED | 単体 |
|
|
45
|
+
| `test_composite_ok_when_all_checks_pass` | PASSED | 単体 |
|
|
46
|
+
| `test_composite_error_when_any_check_fails` | PASSED | 単体 |
|
|
47
|
+
| `test_health_status_http_code_200_when_ok` | PASSED | 単体 |
|
|
48
|
+
| `test_health_status_http_code_503_when_error` | PASSED | 単体 |
|
|
49
|
+
| `test_sync_health_endpoint_returns_200_when_all_ok` | PASSED | HTTP 統合 |
|
|
50
|
+
| `test_sync_health_endpoint_returns_503_when_db_down` | PASSED | HTTP 統合 |
|
|
51
|
+
| `test_sync_health_endpoint_returns_503_when_memory_high` | PASSED | HTTP 統合 |
|
|
52
|
+
| `test_async_health_endpoint_returns_200_when_all_ok` | PASSED | HTTP 統合 |
|
|
53
|
+
| `test_async_health_endpoint_returns_503_when_cache_down` | PASSED | HTTP 統合 |
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Friction Points
|
|
58
|
+
|
|
59
|
+
なし。
|
|
60
|
+
|
|
61
|
+
**特筆点**:
|
|
62
|
+
- `HealthCheckProtocol` / `AsyncHealthCheckProtocol` はどちらも `@runtime_checkable Protocol` なので、
|
|
63
|
+
特定の基底クラスを継承せずとも `check()` メソッドを実装するだけで準拠できる。
|
|
64
|
+
- `CompositeHealthCheck` は各チェックの `checks` dict をフラットマージするため、
|
|
65
|
+
複数チェックが同一キーを持つと後のチェックが上書きする。キーの命名が重要。
|
|
66
|
+
- `HealthStatus.http_status_code` は 200 / 503 を自動で返すため、
|
|
67
|
+
HTTP ハンドラーで `status_code=status.http_status_code` とするだけで正しいステータスコードになる。
|
|
68
|
+
- `create_app(db_healthy=False)` の DI パターンで HTTP テストにおける「障害シミュレーション」が簡潔に書ける。
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## 結論
|
|
73
|
+
|
|
74
|
+
`HealthCheckProtocol` の実装は `check() -> HealthStatus` を持つだけで完了する。
|
|
75
|
+
`CompositeHealthCheck` がフラットマージすることを把握した上で、
|
|
76
|
+
各チェックのキー命名を一意にすれば、複数チェックの組み合わせは完全に直感的に動作する。
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# FT75: ミドルウェアスタック順序依存性の実運用検証
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-20
|
|
4
|
+
**テーマ**: Starlette LIFO ミドルウェア順序の落とし穴 — エラーレスポンスにヘッダーが付かない問題
|
|
5
|
+
**バージョン**: v1.8.20
|
|
6
|
+
**FTディレクトリ**: `/home/xi/docker/nene2-python-FT/ft75-middleware-order/`
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 概要
|
|
11
|
+
|
|
12
|
+
nene2 が提供する 6 つのミドルウェアを組み合わせた際の順序依存バグを検証した。
|
|
13
|
+
「ErrorHandler を最外側に」という直感が **間違い** であることを実際のテストで実証し、
|
|
14
|
+
全レスポンスに X-Request-Id とセキュリティヘッダーを付与するための正しい順序を確認した。
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 発見した問題
|
|
19
|
+
|
|
20
|
+
### Starlette BaseHTTPMiddleware の動作原理
|
|
21
|
+
|
|
22
|
+
`app.add_middleware(X)` は **LIFO**(後から追加したものが外側になる)で積まれる。
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
app.add_middleware(A) # 内側
|
|
26
|
+
app.add_middleware(B) # 外側
|
|
27
|
+
# スタック: B(A(Router))
|
|
28
|
+
# リクエスト: B → A → Router
|
|
29
|
+
# レスポンス: Router → A → B
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 落とし穴: ErrorHandler を最外側にすると何が起きるか
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
# 「直感」による書き方
|
|
36
|
+
app.add_middleware(RequestIdMiddleware) # 内側
|
|
37
|
+
app.add_middleware(SecurityHeadersMiddleware) # 内側
|
|
38
|
+
app.add_middleware(ErrorHandlerMiddleware) # 最外側(最後に追加)
|
|
39
|
+
|
|
40
|
+
# スタック: ErrorHandler(SecurityHeaders(RequestId(Router)))
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
ハンドラーが例外を raise すると:
|
|
44
|
+
1. `ErrorHandlerMiddleware.dispatch` が例外を捕捉
|
|
45
|
+
2. `problem_details_response(...)` で **新しい Response を直接 return**
|
|
46
|
+
3. この Response は内側の `SecurityHeaders` も `RequestId` も **通過しない**
|
|
47
|
+
4. 結果: **500 エラーに X-Request-Id もセキュリティヘッダーも付かない**
|
|
48
|
+
|
|
49
|
+
### 正しい順序
|
|
50
|
+
|
|
51
|
+
ErrorHandler を **最内側** に置き、RequestId と SecurityHeaders を **外側** に置く。
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
# 正しい書き方(最初に add するものが最内側)
|
|
55
|
+
app.add_middleware(ErrorHandlerMiddleware) # 最内側
|
|
56
|
+
app.add_middleware(RequestLoggingMiddleware)
|
|
57
|
+
app.add_middleware(ThrottleMiddleware, ...)
|
|
58
|
+
app.add_middleware(RequestSizeLimitMiddleware, ...)
|
|
59
|
+
app.add_middleware(SecurityHeadersMiddleware)
|
|
60
|
+
app.add_middleware(RequestIdMiddleware) # 最外側
|
|
61
|
+
|
|
62
|
+
# スタック: RequestId(SecurityHeaders(SizeLimit(Throttle(RequestLogging(ErrorHandler(Router))))))
|
|
63
|
+
# 全レスポンス(エラー含む)が SecurityHeaders と RequestId を通過する ✓
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## テスト結果
|
|
69
|
+
|
|
70
|
+
**10/10 passed**
|
|
71
|
+
|
|
72
|
+
| テスト | 結果 | 観察内容 |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| `test_correct_order_500_is_problem_details` | PASSED | ErrorHandler は内側でも 500 を捕捉できる |
|
|
75
|
+
| `test_correct_order_500_has_request_id` | PASSED | RequestId が外側 → 500 にも付く |
|
|
76
|
+
| `test_correct_order_500_has_security_headers` | PASSED | SecurityHeaders が外側 → 500 にも付く |
|
|
77
|
+
| `test_correct_order_413_is_problem_details` | PASSED | SizeLimitMiddleware は内部で直接 problem_details_response を返す |
|
|
78
|
+
| `test_correct_order_413_has_request_id` | PASSED | 413 にも X-Request-Id が付く |
|
|
79
|
+
| `test_correct_order_413_has_security_headers` | PASSED | 413 にもセキュリティヘッダーが付く |
|
|
80
|
+
| `test_naive_order_500_missing_request_id` | PASSED | 直感的順序だと 500 に X-Request-Id が**付かない**ことを実証 |
|
|
81
|
+
| `test_naive_order_500_missing_security_headers` | PASSED | 直感的順序だと 500 にセキュリティヘッダーが**付かない**ことを実証 |
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Friction Points
|
|
86
|
+
|
|
87
|
+
### 🔴 重大: ミドルウェア推奨順序がドキュメント化されていない
|
|
88
|
+
|
|
89
|
+
`add_middleware` の呼び出し順序について nene2 のドキュメントに推奨順序が存在しない。
|
|
90
|
+
Starlette の LIFO 動作は非直感的であり、「ErrorHandler が最外側にあるべき」という誤解を招きやすい。
|
|
91
|
+
|
|
92
|
+
実際の影響:
|
|
93
|
+
- **ErrorHandler を最外側に置く(最後に add する)と** 500 エラーに X-Request-Id が付かない
|
|
94
|
+
- **Security audit で「エラーレスポンスにセキュリティヘッダーがない」と指摘される**
|
|
95
|
+
- 本番環境で気づかずに運用してしまう可能性が高い
|
|
96
|
+
|
|
97
|
+
**推奨順序(コメント付き)をドキュメントに追加すべき。**
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 使用感(主観評価)
|
|
102
|
+
|
|
103
|
+
**直感性: ★★☆☆☆**
|
|
104
|
+
LIFO の仕組みを知っていても、「外側に置きたいものを後から add する」という逆転発想は
|
|
105
|
+
毎回確認しないと間違える。Express.js でも同じ罠があり、FastAPI/Starlette ユーザーが
|
|
106
|
+
最も頻繁にハマる問題のひとつ。
|
|
107
|
+
|
|
108
|
+
**実害の深刻さ: ★★★★★**
|
|
109
|
+
単に動かない(すぐ気づく)ではなく、**動くが一部の非機能要件が欠落する**パターン。
|
|
110
|
+
Security headers がエラーページだけ欠落していても CI は通るし、
|
|
111
|
+
X-Request-Id がエラーレスポンスにないことはログ追跡するまで気づかない。
|
|
112
|
+
|
|
113
|
+
**修正のしやすさ: ★★★★★**
|
|
114
|
+
順序を正しくするだけなので、原因がわかれば修正は 1 分。
|
|
115
|
+
問題は「原因に気づく」のが遅いこと。
|
|
116
|
+
|
|
117
|
+
**フレームワーク側で改善できること**:
|
|
118
|
+
CLAUDE.md や how-to ガイドに推奨スタック順序を明記するだけで解決できる。
|
|
119
|
+
ミドルウェアを `ErrorHandlerMiddleware.install()` のように一括登録する
|
|
120
|
+
`setup_middlewares(app)` ユーティリティがあると事故を防げる。
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 結論
|
|
125
|
+
|
|
126
|
+
ミドルウェア順序のバグは **動作するが静かに壊れている** 類の問題で、
|
|
127
|
+
実際の本番事故に直結しやすい。正しい順序(ErrorHandler 最内側・RequestId 最外側)を
|
|
128
|
+
nene2 の公式ドキュメントと CLAUDE.md に追記することを強く推奨する。
|