nene2-python 1.8.15__tar.gz → 1.8.17__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.15 → nene2_python-1.8.17}/CHANGELOG.md +27 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/PKG-INFO +1 -1
- nene2_python-1.8.17/docs/field-trials/2026-05-field-trial-56.md +50 -0
- nene2_python-1.8.17/docs/field-trials/2026-05-field-trial-57.md +50 -0
- nene2_python-1.8.17/docs/field-trials/2026-05-field-trial-58.md +60 -0
- nene2_python-1.8.17/docs/field-trials/2026-05-field-trial-59.md +62 -0
- nene2_python-1.8.17/docs/field-trials/2026-05-field-trial-60.md +57 -0
- nene2_python-1.8.17/docs/field-trials/2026-05-field-trial-61.md +58 -0
- nene2_python-1.8.17/docs/field-trials/2026-05-field-trial-62.md +57 -0
- nene2_python-1.8.17/docs/field-trials/2026-05-field-trial-63.md +61 -0
- nene2_python-1.8.17/docs/field-trials/2026-05-field-trial-64.md +67 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/pyproject.toml +1 -1
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/http/__init__.py +2 -0
- nene2_python-1.8.17/src/nene2/validation/exceptions.py +101 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/nene2/http/test_problem_details.py +10 -1
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/nene2/validation/test_exceptions.py +5 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/uv.lock +1 -1
- nene2_python-1.8.15/src/nene2/validation/exceptions.py +0 -52
- {nene2_python-1.8.15 → nene2_python-1.8.17}/.env.example +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/.github/workflows/ci.yml +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/.github/workflows/docs.yml +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/.github/workflows/publish.yml +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/.gitignore +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/.vitepress/config.mts +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/.vitepress/theme/custom.css +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/.vitepress/theme/index.ts +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/AGENTS.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/CLAUDE.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/Dockerfile +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/LICENSE +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/README.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/alembic/README +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/alembic/env.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/alembic/script.py.mako +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/alembic.ini +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/compose.yaml +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/adr/0001-toolchain.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/adr/0002-clean-architecture.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/adr/0003-security-first.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/adr/0004-ai-first-design.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/adr/0005-logging.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/adr/0006-rate-limiting.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/adr/0009-mcp-design.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/adr/0010-async-use-case.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/de/index.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/de/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/explanation/architecture.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-1.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-10.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-11.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-12.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-13.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-14.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-15.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-16.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-17.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-18.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-19.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-2.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-20.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-21.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-22.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-23.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-24.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-25.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-26.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-27.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-28.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-29.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-3.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-30.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-31.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-32.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-33.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-34.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-35.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-36.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-37.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-38.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-39.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-4.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-40.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-41.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-42.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-43.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-44.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-45.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-46.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-47.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-48.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-49.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-5.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-50.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-51.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-52.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-53.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-54.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-55.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-6.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-7.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-8.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/field-trials/2026-05-field-trial-9.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/fr/index.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/fr/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/how-to/async-use-case.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/how-to/new-project.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/how-to/problem-details.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/how-to/run-tests.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/how-to/validation.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/index.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/ja/explanation/architecture.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/ja/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/ja/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/ja/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/ja/how-to/new-project.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/ja/how-to/run-tests.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/ja/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/ja/index.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/ja/reference/api.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/ja/reference/configuration.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/ja/reference/framework-modules.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/ja/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/ja/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/pt-br/index.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/pt-br/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/reference/api.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/reference/configuration.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/reference/framework-modules.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/roadmap.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/todo/current.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/zh/index.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/docs/zh/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/package-lock.json +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/package.json +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/example/__init__.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/example/__main__.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/example/app.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/example/comment/__init__.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/example/comment/entity.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/example/comment/exceptions.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/example/comment/handler.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/example/comment/repository.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/example/comment/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/example/comment/use_case.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/example/mcp.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/example/note/__init__.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/example/note/async_use_case.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/example/note/entity.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/example/note/exceptions.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/example/note/handler.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/example/note/repository.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/example/note/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/example/note/use_case.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/example/schema.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/example/tag/__init__.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/example/tag/entity.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/example/tag/exceptions.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/example/tag/handler.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/example/tag/repository.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/example/tag/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/example/tag/use_case.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/__init__.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/auth/api_key.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/auth/bearer_token.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/auth/exceptions.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/auth/interfaces.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/auth/local_verifier.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/config/settings.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/database/exceptions.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/database/health.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/database/interfaces.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/database/sqlalchemy_executor.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/database/utils.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/http/health.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/http/pagination.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/http/problem_details.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/log/setup.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/mcp/http_client.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/mcp/server.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/middleware/__init__.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/middleware/domain_exception.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/middleware/error_handler.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/middleware/request_id.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/middleware/request_logging.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/middleware/request_size_limit.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/middleware/security_headers.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/middleware/throttle.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/py.typed +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/use_case/protocols.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/scripts/__init__.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/src/scripts/export_openapi.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/__init__.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/example/__init__.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/example/comment/__init__.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/example/comment/test_comment_http.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/example/comment/test_comment_repository.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/example/comment/test_comment_use_case.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/example/conftest.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/example/note/__init__.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/example/note/test_async_note_use_case.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/example/note/test_list_notes.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/example/note/test_note_repository.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/example/tag/__init__.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/example/tag/test_tag_repository.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/example/tag/test_tags.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/example/test_cors.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/example/test_mcp.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/nene2/__init__.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/nene2/auth/test_api_key.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/nene2/auth/test_bearer_token.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/nene2/auth/test_token_issuer.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/nene2/config/test_settings.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/nene2/database/test_transaction.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/nene2/database/test_utils.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/nene2/http/test_health.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/nene2/http/test_pagination.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/nene2/log/test_setup.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/nene2/mcp/test_http_client.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/nene2/middleware/__init__.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/nene2/middleware/test_error_handler.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/nene2/middleware/test_request_id.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/nene2/middleware/test_request_logging.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/nene2/middleware/test_request_size_limit.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/nene2/middleware/test_security_headers.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/nene2/middleware/test_throttle.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/nene2/use_case/test_protocols.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/scripts/__init__.py +0 -0
- {nene2_python-1.8.15 → nene2_python-1.8.17}/tests/scripts/test_export_openapi.py +0 -0
|
@@ -5,6 +5,33 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
## [1.8.17] — 2026-05-20
|
|
9
|
+
|
|
10
|
+
FT64 フィールドトライアル — ValidationException 複数エラー実運用検証。
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- `ValidationError.code` にスペースが含まれる場合 `ValueError` を発生させるよう修正 — `message` と `code` の混同を早期検出できるようになった (#300) (FT64)
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- `ValidationError` と `ValidationException.single()` の docstring を改善 — `message` (人間可読) と `code` (機械可読 snake_case) の違いをキーワード引数付き例で明示 (#300) (FT64)
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- Field trial report: `docs/field-trials/2026-05-field-trial-64.md`
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## [1.8.16] — 2026-05-20
|
|
24
|
+
|
|
25
|
+
FT63 フィールドトライアル — configure_problem_details 実運用検証 + PROBLEM_DETAILS_BASE_URL エクスポート修正。
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
- `PROBLEM_DETAILS_BASE_URL` 定数を `nene2.http` からエクスポートするよう修正 — テストで `from nene2.http import PROBLEM_DETAILS_BASE_URL` が利用可能になった (#296) (FT63)
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
- Field trial reports: `docs/field-trials/2026-05-field-trial-56.md` 〜 `2026-05-field-trial-63.md` (FT56〜FT63)
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
8
35
|
## [1.8.15] — 2026-05-20
|
|
9
36
|
|
|
10
37
|
FT55 フィールドトライアル — parse_db_datetime 実運用検証。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nene2-python
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.17
|
|
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,50 @@
|
|
|
1
|
+
# FT56: CompositeHealthCheck + http_status_code 実運用検証
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-20
|
|
4
|
+
**テーマ**: 同期ヘルスチェック集約 (`CompositeHealthCheck`) と `HealthStatus.http_status_code` の実運用確認
|
|
5
|
+
**バージョン**: v1.8.15
|
|
6
|
+
**FTディレクトリ**: `/home/xi/docker/nene2-python-FT/ft56-health-check/`
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 概要
|
|
11
|
+
|
|
12
|
+
`nene2.http.CompositeHealthCheck` と `HealthStatus.http_status_code` を FastAPI ルートに組み込み、
|
|
13
|
+
複数の依存サービス(DB・キャッシュ)のヘルスチェックを集約するパターンを検証した。
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 実装内容
|
|
18
|
+
|
|
19
|
+
- `DatabaseHealthCheck` / `CacheHealthCheck`: `HealthCheckProtocol` 実装クラス
|
|
20
|
+
- `CompositeHealthCheck([db, cache])`: 集約チェック
|
|
21
|
+
- `/health` エンドポイント: 結果を `status_code=status.http_status_code` で返却
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## テスト結果
|
|
26
|
+
|
|
27
|
+
**7/7 passed**
|
|
28
|
+
|
|
29
|
+
| テスト | 結果 |
|
|
30
|
+
|---|---|
|
|
31
|
+
| `test_all_healthy_returns_200` | PASSED |
|
|
32
|
+
| `test_db_unhealthy_returns_503` | PASSED |
|
|
33
|
+
| `test_cache_unhealthy_returns_503` | PASSED |
|
|
34
|
+
| `test_both_unhealthy_returns_503` | PASSED |
|
|
35
|
+
| `test_health_status_http_status_code_ok` | PASSED |
|
|
36
|
+
| `test_health_status_http_status_code_error` | PASSED |
|
|
37
|
+
| `test_composite_merges_checks` | PASSED |
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Friction Points
|
|
42
|
+
|
|
43
|
+
なし。`CompositeHealthCheck` は直感的なインターフェースで、ドキュメントどおりに動作した。
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## 結論
|
|
48
|
+
|
|
49
|
+
`CompositeHealthCheck` は実運用で問題なく使用できる。
|
|
50
|
+
`http_status_code` プロパティにより、FastAPI の `JSONResponse` に直接渡せるのが便利。
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# FT57: AsyncCompositeHealthCheck 実運用検証
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-20
|
|
4
|
+
**テーマ**: 非同期ヘルスチェック集約 (`AsyncCompositeHealthCheck`) と並列実行の実運用確認
|
|
5
|
+
**バージョン**: v1.8.15
|
|
6
|
+
**FTディレクトリ**: `/home/xi/docker/nene2-python-FT/ft57-async-health/`
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 概要
|
|
11
|
+
|
|
12
|
+
`nene2.http.AsyncCompositeHealthCheck` を FastAPI の async ルートに組み込み、
|
|
13
|
+
複数の依存サービス(DB・外部API)のヘルスチェックを `asyncio.gather` で並列実行するパターンを検証した。
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 実装内容
|
|
18
|
+
|
|
19
|
+
- `AsyncDatabaseHealthCheck` / `AsyncExternalApiHealthCheck`: `AsyncHealthCheckProtocol` 実装クラス(`async def check()`)
|
|
20
|
+
- `AsyncCompositeHealthCheck([db, api])`: `asyncio.gather` による並列集約
|
|
21
|
+
- `/health` エンドポイント: `async def health()` で `await composite.check()` を呼び出し
|
|
22
|
+
- 並列実行タイミングテスト: 各 50ms のチェック2つが 90ms 未満で完了することを確認
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## テスト結果
|
|
27
|
+
|
|
28
|
+
**5/5 passed**
|
|
29
|
+
|
|
30
|
+
| テスト | 結果 |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `test_all_healthy_returns_200` | PASSED |
|
|
33
|
+
| `test_db_unhealthy_returns_503` | PASSED |
|
|
34
|
+
| `test_api_unhealthy_returns_503` | PASSED |
|
|
35
|
+
| `test_both_unhealthy_returns_503` | PASSED |
|
|
36
|
+
| `test_parallel_execution_is_faster` | PASSED |
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Friction Points
|
|
41
|
+
|
|
42
|
+
なし。`AsyncCompositeHealthCheck` は直感的なインターフェースで、並列実行も確認できた。
|
|
43
|
+
`pytest-asyncio` の `asyncio_mode = "auto"` 設定で `async def test_*` がそのまま動作した。
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## 結論
|
|
48
|
+
|
|
49
|
+
`AsyncCompositeHealthCheck` は実運用で問題なく使用できる。
|
|
50
|
+
`asyncio.gather` による並列実行は計測でも確認でき、直列実行比で約2倍の高速化が得られた。
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# FT58: ThrottleMiddleware 実運用検証
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-20
|
|
4
|
+
**テーマ**: レートリミットミドルウェア (`ThrottleMiddleware`) の実運用確認
|
|
5
|
+
**バージョン**: v1.8.15
|
|
6
|
+
**FTディレクトリ**: `/home/xi/docker/nene2-python-FT/ft58-throttle/`
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 概要
|
|
11
|
+
|
|
12
|
+
`nene2.middleware.ThrottleMiddleware` を FastAPI に組み込み、
|
|
13
|
+
グローバルレート制限・エンドポイント別制限・除外パス・レートヘッダーを検証した。
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 実装内容
|
|
18
|
+
|
|
19
|
+
- `ThrottleMiddleware(limit=N, window=60)`: グローバルレート制限
|
|
20
|
+
- `path_limits={"/api/expensive": 2}`: エンドポイント別に厳しい制限
|
|
21
|
+
- `exclude_paths=["/health"]`: ヘルスチェックをレート制限から除外
|
|
22
|
+
- レスポンスヘッダー確認: `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, `Retry-After`
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## テスト結果
|
|
27
|
+
|
|
28
|
+
**9/9 passed**
|
|
29
|
+
|
|
30
|
+
| テスト | 結果 |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `test_under_limit_returns_200` | PASSED |
|
|
33
|
+
| `test_rate_limit_headers_present` | PASSED |
|
|
34
|
+
| `test_remaining_decrements_per_request` | PASSED |
|
|
35
|
+
| `test_exceeding_limit_returns_429` | PASSED |
|
|
36
|
+
| `test_429_includes_retry_after_header` | PASSED |
|
|
37
|
+
| `test_exclude_paths_bypass_throttle` | PASSED |
|
|
38
|
+
| `test_path_limits_stricter_than_global` | PASSED |
|
|
39
|
+
| `test_path_limits_independent_from_global` | PASSED |
|
|
40
|
+
| `test_429_response_is_problem_details` | PASSED |
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Friction Points
|
|
45
|
+
|
|
46
|
+
なし。`ThrottleMiddleware` はすべての機能が直感的に動作した。
|
|
47
|
+
|
|
48
|
+
**特筆点**:
|
|
49
|
+
- `path_limits` のカウンターがグローバルカウンターと独立しているのは設計どおりで便利
|
|
50
|
+
- `exclude_paths` でヘルスチェックを除外できるのは本番運用で必須の機能
|
|
51
|
+
- 429 レスポンスが RFC 9457 Problem Details 形式なのは一貫性がある
|
|
52
|
+
- `Retry-After` ヘッダーが自動付与される点がクライアント実装に優しい
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## 結論
|
|
57
|
+
|
|
58
|
+
`ThrottleMiddleware` は実運用で問題なく使用できる。
|
|
59
|
+
`X-Forwarded-For` の信頼に関するドキュメントの警告も適切で、
|
|
60
|
+
リバースプロキシなし環境での注意点が明記されている。
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# FT59: SecurityHeadersMiddleware 実運用検証
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-20
|
|
4
|
+
**テーマ**: セキュリティヘッダーミドルウェア (`SecurityHeadersMiddleware`) の実運用確認
|
|
5
|
+
**バージョン**: v1.8.15
|
|
6
|
+
**FTディレクトリ**: `/home/xi/docker/nene2-python-FT/ft59-security-headers/`
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 概要
|
|
11
|
+
|
|
12
|
+
`nene2.middleware.SecurityHeadersMiddleware` を FastAPI に組み込み、
|
|
13
|
+
デフォルトヘッダー・CSP・HSTS・除外パス設定を検証した。
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 実装内容
|
|
18
|
+
|
|
19
|
+
- デフォルト設定でのセキュリティヘッダー確認
|
|
20
|
+
- カスタム CSP / Permissions-Policy の適用
|
|
21
|
+
- HSTS ヘッダーの条件付き付与(デフォルト無効、本番向け)
|
|
22
|
+
- `/docs`, `/redoc`, `/openapi.json` での CSP 自動スキップ
|
|
23
|
+
- `extra_no_csp_paths` によるカスタムパスの CSP 除外
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## テスト結果
|
|
28
|
+
|
|
29
|
+
**10/10 passed**
|
|
30
|
+
|
|
31
|
+
| テスト | 結果 |
|
|
32
|
+
|---|---|
|
|
33
|
+
| `test_default_static_headers_present` | PASSED |
|
|
34
|
+
| `test_default_csp_applied` | PASSED |
|
|
35
|
+
| `test_default_permissions_policy_applied` | PASSED |
|
|
36
|
+
| `test_hsts_not_set_by_default` | PASSED |
|
|
37
|
+
| `test_hsts_applied_when_configured` | PASSED |
|
|
38
|
+
| `test_csp_skipped_for_docs_paths` | PASSED |
|
|
39
|
+
| `test_custom_csp_applied` | PASSED |
|
|
40
|
+
| `test_custom_permissions_policy_applied` | PASSED |
|
|
41
|
+
| `test_extra_no_csp_paths_skip_csp` | PASSED |
|
|
42
|
+
| `test_extra_no_csp_paths_does_not_affect_other_paths` | PASSED |
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Friction Points
|
|
47
|
+
|
|
48
|
+
なし。`SecurityHeadersMiddleware` はすべての機能が直感的に動作した。
|
|
49
|
+
|
|
50
|
+
**特筆点**:
|
|
51
|
+
- OpenAPI パス (`/docs`, `/redoc`, `/openapi.json`) での CSP 自動スキップは、
|
|
52
|
+
Swagger UI の CDN アセット読み込みが壊れないよう配慮されており実用的
|
|
53
|
+
- HSTS がデフォルト無効なのは開発環境を壊さないための正しい設計
|
|
54
|
+
- `extra_no_csp_paths` でカスタムドキュメントパスも対応できる柔軟性がある
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 結論
|
|
59
|
+
|
|
60
|
+
`SecurityHeadersMiddleware` は実運用で問題なく使用できる。
|
|
61
|
+
デフォルト設定のみで OWASP 推奨の主要ヘッダーが付与され、
|
|
62
|
+
本番環境では `hsts` パラメータを追加するだけでよい。
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# FT60: RequestIdMiddleware 実運用検証
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-20
|
|
4
|
+
**テーマ**: リクエストIDミドルウェア (`RequestIdMiddleware`) と `get_request_id()` の実運用確認
|
|
5
|
+
**バージョン**: v1.8.15
|
|
6
|
+
**FTディレクトリ**: `/home/xi/docker/nene2-python-FT/ft60-request-id/`
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 概要
|
|
11
|
+
|
|
12
|
+
`nene2.middleware.RequestIdMiddleware` を FastAPI に組み込み、
|
|
13
|
+
UUID v4 の自動生成・クライアント指定 ID の転送・不正 ID の置き換え・
|
|
14
|
+
`get_request_id()` による Depends 経由でのアクセスを検証した。
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 実装内容
|
|
19
|
+
|
|
20
|
+
- `RequestIdMiddleware`: X-Request-Id ヘッダーの自動付与
|
|
21
|
+
- `get_request_id()`: `Depends` 経由でルートハンドラーからリクエストID取得
|
|
22
|
+
- クライアント供給 UUID v4 → そのまま転送(小文字正規化)
|
|
23
|
+
- クライアント供給不正値 → サーバー生成 UUID v4 に置き換え
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## テスト結果
|
|
28
|
+
|
|
29
|
+
**7/7 passed**
|
|
30
|
+
|
|
31
|
+
| テスト | 結果 |
|
|
32
|
+
|---|---|
|
|
33
|
+
| `test_response_has_x_request_id_header` | PASSED |
|
|
34
|
+
| `test_generated_request_id_is_uuid_v4` | PASSED |
|
|
35
|
+
| `test_client_supplied_valid_uuid_v4_is_forwarded` | PASSED |
|
|
36
|
+
| `test_client_supplied_invalid_id_is_replaced` | PASSED |
|
|
37
|
+
| `test_request_id_accessible_via_depends` | PASSED |
|
|
38
|
+
| `test_request_id_in_depends_matches_response_header` | PASSED |
|
|
39
|
+
| `test_each_request_gets_unique_id` | PASSED |
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Friction Points
|
|
44
|
+
|
|
45
|
+
なし。`RequestIdMiddleware` と `get_request_id()` はすべて直感的に動作した。
|
|
46
|
+
|
|
47
|
+
**特筆点**:
|
|
48
|
+
- 不正な X-Request-Id(UUIDv4 以外)は自動で置き換えられ、ログインジェクション対策が組み込まれている
|
|
49
|
+
- `get_request_id()` は `Depends` で使えるため、structlog のコンテキスト設定と自然に組み合わせられる
|
|
50
|
+
- Depends で取得した値とレスポンスヘッダーの値が常に一致することを確認
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 結論
|
|
55
|
+
|
|
56
|
+
`RequestIdMiddleware` は実運用で問題なく使用できる。
|
|
57
|
+
セキュリティ(不正 ID の拒否)と利便性(Depends 連携)の両立が優れている。
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# FT61: AsyncUseCaseProtocol 実運用検証
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-20
|
|
4
|
+
**テーマ**: 非同期ユースケース (`AsyncUseCaseProtocol`) の実運用確認
|
|
5
|
+
**バージョン**: v1.8.15
|
|
6
|
+
**FTディレクトリ**: `/home/xi/docker/nene2-python-FT/ft61-async-usecase/`
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 概要
|
|
11
|
+
|
|
12
|
+
`nene2.use_case.AsyncUseCaseProtocol` を実装したユースケースを FastAPI の async ルートに組み込み、
|
|
13
|
+
プロトコル適合・実行時 coroutine 確認・ValidationException 連携を検証した。
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 実装内容
|
|
18
|
+
|
|
19
|
+
- `FetchUserUseCase`: `AsyncUseCaseProtocol[FetchUserInput, FetchUserOutput]` を満たす実装
|
|
20
|
+
- `async def execute(input_)`: asyncio.sleep で非同期 I/O を模擬
|
|
21
|
+
- `ValidationException.single()` で特定 ID の場合に 422 を返す
|
|
22
|
+
- 静的型チェック用のアダプター関数でプロトコル適合を明示
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## テスト結果
|
|
27
|
+
|
|
28
|
+
**7/7 passed**
|
|
29
|
+
|
|
30
|
+
| テスト | 結果 |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `test_fetch_user_returns_200` | PASSED |
|
|
33
|
+
| `test_fetch_nonexistent_user_returns_422` | PASSED |
|
|
34
|
+
| `test_use_case_satisfies_protocol` | PASSED |
|
|
35
|
+
| `test_execute_is_coroutine_function` | PASSED |
|
|
36
|
+
| `test_execute_returns_correct_output` | PASSED |
|
|
37
|
+
| `test_execute_raises_on_fail_ids` | PASSED |
|
|
38
|
+
| `test_multiple_users_independent` | PASSED |
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Friction Points
|
|
43
|
+
|
|
44
|
+
なし。`AsyncUseCaseProtocol` はプロトコル適合・FastAPI async ルート連携ともに直感的に動作した。
|
|
45
|
+
|
|
46
|
+
**特筆点**:
|
|
47
|
+
- `isinstance(use_case, AsyncUseCaseProtocol)` は `execute` 属性の有無のみチェックするため、
|
|
48
|
+
sync/async 区別は `inspect.iscoroutinefunction()` で補完する必要がある(ADR-0010 で既知)
|
|
49
|
+
- `ValidationException.single()` から 422 Problem Details への変換は
|
|
50
|
+
`ErrorHandlerMiddleware` が自動で処理する
|
|
51
|
+
- `@runtime_checkable` プロトコルを使った型引数付き型ヒントでも mypy --strict が通る
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 結論
|
|
56
|
+
|
|
57
|
+
`AsyncUseCaseProtocol` は実運用で問題なく使用できる。
|
|
58
|
+
FastAPI の async ルートと自然に組み合わせられ、ドメインロジックを HTTP 層から分離する設計が機能している。
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# FT62: RequestLoggingMiddleware 実運用検証
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-20
|
|
4
|
+
**テーマ**: リクエストロギングミドルウェア (`RequestLoggingMiddleware`) の実運用確認
|
|
5
|
+
**バージョン**: v1.8.15
|
|
6
|
+
**FTディレクトリ**: `/home/xi/docker/nene2-python-FT/ft62-request-logging/`
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 概要
|
|
11
|
+
|
|
12
|
+
`nene2.middleware.RequestLoggingMiddleware` を FastAPI に組み込み、
|
|
13
|
+
structlog コンテキスト変数の設定・除外パス・extra_context・RequestIdMiddleware との連携を検証した。
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 実装内容
|
|
18
|
+
|
|
19
|
+
- `RequestLoggingMiddleware(exclude_paths=["/health"])`: ヘルスチェックをログ除外
|
|
20
|
+
- `extra_context={"service": "ft62"}`: リクエストログへのカスタムフィールド追加
|
|
21
|
+
- `RequestIdMiddleware` と組み合わせてリクエストIDのログへの自動バインド
|
|
22
|
+
- structlog プロセッサをカスタマイズしてログ出力をキャプチャして確認
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## テスト結果
|
|
27
|
+
|
|
28
|
+
**6/6 passed**
|
|
29
|
+
|
|
30
|
+
| テスト | 結果 |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `test_request_passes_through_logging_middleware` | PASSED |
|
|
33
|
+
| `test_excluded_path_still_returns_200` | PASSED |
|
|
34
|
+
| `test_non_excluded_path_passes_normally` | PASSED |
|
|
35
|
+
| `test_extra_context_does_not_break_requests` | PASSED |
|
|
36
|
+
| `test_structlog_context_vars_populated_during_request` | PASSED |
|
|
37
|
+
| `test_logging_middleware_with_request_id_middleware` | PASSED |
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Friction Points
|
|
42
|
+
|
|
43
|
+
なし。`RequestLoggingMiddleware` はすべての機能が直感的に動作した。
|
|
44
|
+
|
|
45
|
+
**特筆点**:
|
|
46
|
+
- structlog の `merge_contextvars` プロセッサで、ミドルウェアがバインドしたコンテキストが
|
|
47
|
+
アプリコード側のログにも自動で引き継がれる設計が強力
|
|
48
|
+
- `RequestIdMiddleware` と組み合わせると `request_id` がすべてのログに自動付与される
|
|
49
|
+
- `configure_for_testing()` を使うと structlog の設定がテスト向けに上書きされるため、
|
|
50
|
+
structlog のカスタムプロセッサをテスト内で設定しなおす必要がある点は把握が必要
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 結論
|
|
55
|
+
|
|
56
|
+
`RequestLoggingMiddleware` は実運用で問題なく使用できる。
|
|
57
|
+
`RequestIdMiddleware` と組み合わせて使うのが推奨パターン。
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# FT63: configure_problem_details / PROBLEM_DETAILS_BASE_URL 実運用検証
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-20
|
|
4
|
+
**テーマ**: Problem Details 設定 API (`configure_problem_details`, `PROBLEM_DETAILS_BASE_URL`) の実運用確認
|
|
5
|
+
**バージョン**: v1.8.15 → v1.8.16 (修正含む)
|
|
6
|
+
**FTディレクトリ**: `/home/xi/docker/nene2-python-FT/ft63-problem-details-config/`
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 概要
|
|
11
|
+
|
|
12
|
+
`nene2.http.configure_problem_details()` でプロジェクト全体の base_url を設定し、
|
|
13
|
+
RFC 9457 Problem Details レスポンスの `type` フィールドを自社 URL に変更する
|
|
14
|
+
パターンを検証した。
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 実装内容
|
|
19
|
+
|
|
20
|
+
- `configure_problem_details("https://api.example.com/errors/")`: アプリ起動時にグローバル設定
|
|
21
|
+
- `reset_problem_details()`: テスト間のリセット(autouse fixture)
|
|
22
|
+
- per-call `base_url` 引数がグローバル設定より優先されることを確認
|
|
23
|
+
- `ValidationException` 経由の 422 でも configured base_url が使われることを確認
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## テスト結果
|
|
28
|
+
|
|
29
|
+
**7/7 passed** (v1.8.16 で修正後)
|
|
30
|
+
|
|
31
|
+
| テスト | 結果 |
|
|
32
|
+
|---|---|
|
|
33
|
+
| `test_default_base_url_in_type_field` | PASSED |
|
|
34
|
+
| `test_custom_base_url_applied_via_configure` | PASSED |
|
|
35
|
+
| `test_custom_base_url_affects_all_problem_details_in_app` | PASSED |
|
|
36
|
+
| `test_reset_problem_details_restores_default` | PASSED |
|
|
37
|
+
| `test_problem_details_structure_is_rfc9457_compliant` | PASSED |
|
|
38
|
+
| `test_validation_exception_uses_configured_base_url` | PASSED |
|
|
39
|
+
| `test_per_call_base_url_overrides_configured` | PASSED |
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Friction Points
|
|
44
|
+
|
|
45
|
+
### FP-1: `PROBLEM_DETAILS_BASE_URL` が `nene2.http` からエクスポートされていない
|
|
46
|
+
|
|
47
|
+
**発生箇所**: `test_app.py` で `from nene2.http import PROBLEM_DETAILS_BASE_URL` を試みた際
|
|
48
|
+
|
|
49
|
+
**症状**: `ImportError: cannot import name 'PROBLEM_DETAILS_BASE_URL' from 'nene2.http'`
|
|
50
|
+
|
|
51
|
+
**影響**: テストコードでデフォルト URL を文字列のハードコードを避けられない。
|
|
52
|
+
定数は `problem_details.py` に定義されているが `__init__.py` に含まれていなかった。
|
|
53
|
+
|
|
54
|
+
**修正**: `nene2.http.__init__` に `PROBLEM_DETAILS_BASE_URL` を追加 (Issue #296, v1.8.16)
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 結論
|
|
59
|
+
|
|
60
|
+
`configure_problem_details()` は実運用で問題なく使用できる。
|
|
61
|
+
`PROBLEM_DETAILS_BASE_URL` のエクスポート漏れが修正され、テストでの文字列ハードコードを避けられる。
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# FT64: ValidationException 複数エラー実運用検証
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-20
|
|
4
|
+
**テーマ**: 複数フィールドの `ValidationException` 集積と `ValidationError` 実運用確認
|
|
5
|
+
**バージョン**: v1.8.16 → v1.8.17 (修正含む)
|
|
6
|
+
**FTディレクトリ**: `/home/xi/docker/nene2-python-FT/ft64-multi-validation/`
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 概要
|
|
11
|
+
|
|
12
|
+
`ValidationException` を使って複数フィールドのバリデーションエラーを集積し、
|
|
13
|
+
一度に 422 レスポンスとして返す実運用パターンを検証した。
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 実装内容
|
|
18
|
+
|
|
19
|
+
- `RegisterUserBody`: username・email・age の 3 フィールドを Pydantic で受け取る
|
|
20
|
+
- `_validate_registration()`: エラーを `list[ValidationError]` に集積して `ValidationException(errors)` を raise
|
|
21
|
+
- 複数エラーが一度に返されること、Problem Details 形式であることを確認
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## テスト結果
|
|
26
|
+
|
|
27
|
+
**7/7 passed** (v1.8.17 で修正後)
|
|
28
|
+
|
|
29
|
+
| テスト | 結果 |
|
|
30
|
+
|---|---|
|
|
31
|
+
| `test_valid_registration_returns_201` | PASSED |
|
|
32
|
+
| `test_single_invalid_email_returns_422` | PASSED |
|
|
33
|
+
| `test_underage_user_returns_422` | PASSED |
|
|
34
|
+
| `test_multiple_errors_returned_at_once` | PASSED |
|
|
35
|
+
| `test_422_response_is_problem_details_format` | PASSED |
|
|
36
|
+
| `test_errors_include_field_message_code` | PASSED |
|
|
37
|
+
| `test_pydantic_validation_error_returns_422` | PASSED |
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Friction Points
|
|
42
|
+
|
|
43
|
+
### FP-1: `ValidationError(field, message, code)` の引数順で `message` と `code` を混同
|
|
44
|
+
|
|
45
|
+
**発生箇所**: `app.py` で `ValidationError` を直接構築した際
|
|
46
|
+
|
|
47
|
+
**症状**:
|
|
48
|
+
```python
|
|
49
|
+
# 意図: code="invalid_email"
|
|
50
|
+
ValidationError("email", "invalid_email", "メールアドレスの形式が正しくありません")
|
|
51
|
+
# → message="invalid_email", code="メールアドレスの形式が正しくありません" になってしまう
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**原因**: `(field, message, code)` の順序で、短い機械可読コードを先に書きたくなるが
|
|
55
|
+
`message` が先に来るため混同が起きやすい。
|
|
56
|
+
|
|
57
|
+
**修正**:
|
|
58
|
+
- `ValidationError.code` にスペースを含む場合 `ValueError` を早期発生させる (v1.8.17)
|
|
59
|
+
- docstring にキーワード引数付き例を追加してどちらが何かを明確化
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 結論
|
|
64
|
+
|
|
65
|
+
`ValidationException` の複数エラー集積パターンは実運用で問題なく使用できる。
|
|
66
|
+
`message` と `code` の混同を防ぐ `ValueError` と docstring 改善により、
|
|
67
|
+
今後は早期にミスに気づけるようになった。
|
|
@@ -9,6 +9,7 @@ from .health import (
|
|
|
9
9
|
)
|
|
10
10
|
from .pagination import PaginationQuery, PaginationQueryParser, PaginationResponse
|
|
11
11
|
from .problem_details import (
|
|
12
|
+
PROBLEM_DETAILS_BASE_URL,
|
|
12
13
|
configure_problem_details,
|
|
13
14
|
problem_details_response,
|
|
14
15
|
reset_problem_details,
|
|
@@ -23,6 +24,7 @@ __all__ = [
|
|
|
23
24
|
"PaginationQuery",
|
|
24
25
|
"PaginationQueryParser",
|
|
25
26
|
"PaginationResponse",
|
|
27
|
+
"PROBLEM_DETAILS_BASE_URL",
|
|
26
28
|
"configure_problem_details",
|
|
27
29
|
"problem_details_response",
|
|
28
30
|
"reset_problem_details",
|