nene2-python 1.8.0__tar.gz → 1.8.2__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.0 → nene2_python-1.8.2}/CHANGELOG.md +26 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/PKG-INFO +1 -1
- nene2_python-1.8.2/docs/field-trials/2026-05-field-trial-24.md +89 -0
- nene2_python-1.8.2/docs/field-trials/2026-05-field-trial-25.md +82 -0
- nene2_python-1.8.2/docs/field-trials/2026-05-field-trial-26.md +73 -0
- nene2_python-1.8.2/docs/field-trials/2026-05-field-trial-27.md +66 -0
- nene2_python-1.8.2/docs/field-trials/2026-05-field-trial-28.md +54 -0
- nene2_python-1.8.2/docs/field-trials/2026-05-field-trial-29.md +74 -0
- nene2_python-1.8.2/docs/field-trials/2026-05-field-trial-30.md +66 -0
- nene2_python-1.8.2/docs/how-to/async-use-case.md +121 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/pyproject.toml +1 -1
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/config/settings.py +10 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/log/setup.py +2 -2
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/middleware/__init__.py +2 -1
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/middleware/request_id.py +18 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/middleware/request_logging.py +10 -1
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/middleware/throttle.py +37 -12
- nene2_python-1.8.2/tests/nene2/config/test_settings.py +54 -0
- nene2_python-1.8.2/tests/nene2/log/test_setup.py +49 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/nene2/middleware/test_request_id.py +27 -2
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/nene2/middleware/test_request_logging.py +37 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/nene2/middleware/test_throttle.py +85 -0
- nene2_python-1.8.2/tests/nene2/validation/__init__.py +0 -0
- nene2_python-1.8.2/tests/scripts/__init__.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/uv.lock +1 -1
- {nene2_python-1.8.0 → nene2_python-1.8.2}/.env.example +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/.github/workflows/ci.yml +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/.github/workflows/docs.yml +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/.github/workflows/publish.yml +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/.gitignore +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/.vitepress/config.mts +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/.vitepress/theme/custom.css +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/.vitepress/theme/index.ts +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/AGENTS.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/CLAUDE.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/Dockerfile +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/LICENSE +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/README.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/alembic/README +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/alembic/env.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/alembic/script.py.mako +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/alembic.ini +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/compose.yaml +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/adr/0001-toolchain.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/adr/0002-clean-architecture.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/adr/0003-security-first.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/adr/0004-ai-first-design.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/adr/0005-logging.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/adr/0006-rate-limiting.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/adr/0009-mcp-design.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/adr/0010-async-use-case.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/de/index.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/de/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/explanation/architecture.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-1.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-10.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-11.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-12.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-13.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-14.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-15.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-16.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-17.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-18.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-19.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-2.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-20.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-21.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-22.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-23.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-3.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-4.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-5.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-6.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-7.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-8.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-9.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/fr/index.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/fr/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/how-to/new-project.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/how-to/problem-details.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/how-to/run-tests.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/index.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/ja/explanation/architecture.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/ja/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/ja/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/ja/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/ja/how-to/new-project.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/ja/how-to/run-tests.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/ja/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/ja/index.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/ja/reference/api.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/ja/reference/configuration.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/ja/reference/framework-modules.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/ja/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/ja/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/pt-br/index.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/pt-br/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/reference/api.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/reference/configuration.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/reference/framework-modules.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/roadmap.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/todo/current.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/zh/index.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/docs/zh/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/package-lock.json +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/package.json +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/example/__init__.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/example/__main__.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/example/app.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/example/comment/__init__.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/example/comment/entity.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/example/comment/exceptions.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/example/comment/handler.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/example/comment/repository.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/example/comment/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/example/comment/use_case.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/example/mcp.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/example/note/__init__.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/example/note/async_use_case.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/example/note/entity.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/example/note/exceptions.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/example/note/handler.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/example/note/repository.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/example/note/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/example/note/use_case.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/example/schema.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/example/tag/__init__.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/example/tag/entity.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/example/tag/exceptions.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/example/tag/handler.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/example/tag/repository.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/example/tag/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/example/tag/use_case.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/__init__.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/auth/api_key.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/auth/bearer_token.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/auth/exceptions.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/auth/interfaces.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/auth/local_verifier.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/database/exceptions.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/database/health.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/database/interfaces.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/database/sqlalchemy_executor.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/database/utils.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/http/health.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/http/pagination.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/http/problem_details.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/mcp/http_client.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/mcp/server.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/middleware/domain_exception.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/middleware/error_handler.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/middleware/request_size_limit.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/middleware/security_headers.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/py.typed +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/use_case/protocols.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/nene2/validation/exceptions.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/scripts/__init__.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/src/scripts/export_openapi.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/__init__.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/example/__init__.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/example/comment/__init__.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/example/comment/test_comment_http.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/example/comment/test_comment_repository.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/example/comment/test_comment_use_case.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/example/conftest.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/example/note/__init__.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/example/note/test_async_note_use_case.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/example/note/test_list_notes.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/example/note/test_note_repository.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/example/tag/__init__.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/example/tag/test_tag_repository.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/example/tag/test_tags.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/example/test_cors.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/example/test_mcp.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/nene2/__init__.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/nene2/auth/test_api_key.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/nene2/auth/test_bearer_token.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/nene2/auth/test_token_issuer.py +0 -0
- {nene2_python-1.8.0/tests/nene2/database → nene2_python-1.8.2/tests/nene2/config}/__init__.py +0 -0
- {nene2_python-1.8.0/tests/nene2/http → nene2_python-1.8.2/tests/nene2/database}/__init__.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/nene2/database/test_transaction.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/nene2/database/test_utils.py +0 -0
- {nene2_python-1.8.0/tests/nene2/mcp → nene2_python-1.8.2/tests/nene2/http}/__init__.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/nene2/http/test_health.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/nene2/http/test_pagination.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/nene2/http/test_problem_details.py +0 -0
- {nene2_python-1.8.0/tests/nene2/middleware → nene2_python-1.8.2/tests/nene2/log}/__init__.py +0 -0
- {nene2_python-1.8.0/tests/nene2/use_case → nene2_python-1.8.2/tests/nene2/mcp}/__init__.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/nene2/mcp/test_http_client.py +0 -0
- {nene2_python-1.8.0/tests/nene2/validation → nene2_python-1.8.2/tests/nene2/middleware}/__init__.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/nene2/middleware/test_error_handler.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/nene2/middleware/test_request_size_limit.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/nene2/middleware/test_security_headers.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
- {nene2_python-1.8.0/tests/scripts → nene2_python-1.8.2/tests/nene2/use_case}/__init__.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/nene2/use_case/test_protocols.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/tests/nene2/validation/test_exceptions.py +0 -0
- {nene2_python-1.8.0 → nene2_python-1.8.2}/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.2] — 2026-05-20
|
|
9
|
+
|
|
10
|
+
FT29〜FT30 フィールドトライアル — AsyncUseCase ドキュメント・RequestLoggingMiddleware 改善。
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `docs/how-to/async-use-case.md` — `AsyncUseCaseProtocol` + FastAPI `Depends` の DI パターンガイドを追加 (FT29)
|
|
14
|
+
- `RequestLoggingMiddleware` に `extra_context: dict[str, str] | None = None` パラメータを追加し、全ログに静的フィールドを付加できるように (FT30)
|
|
15
|
+
- Field trial reports: `docs/field-trials/2026-05-field-trial-29.md`、`docs/field-trials/2026-05-field-trial-30.md`
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## [1.8.1] — 2026-05-20
|
|
20
|
+
|
|
21
|
+
FT25〜FT28 フィールドトライアル — RequestId ヘルパー・structlog ログレベル・ThrottleMiddleware 改善。
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- `nene2.middleware.get_request_id()` — FastAPI `Depends` で注入できる request ID ヘルパー関数 (FT25)
|
|
25
|
+
- `setup_logging()` に `log_level: str = "INFO"` パラメータを追加し `AppSettings.log_level` との統合が容易に (FT26)
|
|
26
|
+
- `ThrottleMiddleware` に `path_limits: dict[str, int] | None` パラメータを追加し、パスごとに異なるレート制限を設定可能に (FT28)
|
|
27
|
+
- Field trial reports: `docs/field-trials/2026-05-field-trial-25.md` 〜 `docs/field-trials/2026-05-field-trial-28.md`
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
- `ThrottleMiddleware` — ウィンドウ経過後も `_counts` に古いエントリが残り続ける問題を修正(定期クリーンアップを実装)(FT27)
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
8
34
|
## [1.8.0] — 2026-05-20
|
|
9
35
|
|
|
10
36
|
FT18〜FT23 フィールドトライアル — ログテスト・Problem Details・ThrottleMiddleware・ドメイン例外・HealthCheck・RequestSizeLimit の各改善。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nene2-python
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.2
|
|
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,89 @@
|
|
|
1
|
+
# FT24: AppSettings 実運用検証
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-20
|
|
4
|
+
**テーマ**: `AppSettings` を使った環境変数ベースの設定管理パターンの実運用検証
|
|
5
|
+
**FT アプリ**: `/home/xi/docker/nene2-python-FT/ft24-app-settings/`
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 目的
|
|
10
|
+
|
|
11
|
+
`nene2.config.AppSettings` を実際のアプリに組み込み、
|
|
12
|
+
環境変数から設定を読み込んでミドルウェアを設定するパターンを検証する。
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 実施内容
|
|
17
|
+
|
|
18
|
+
`AppSettings` の各フィールドを使って以下を設定:
|
|
19
|
+
- `ErrorHandlerMiddleware(debug=settings.app_debug)`
|
|
20
|
+
- `RequestSizeLimitMiddleware(max_bytes=settings.max_body_size)`
|
|
21
|
+
- `ThrottleMiddleware(limit=settings.throttle_limit, window=settings.throttle_window)` (conditional)
|
|
22
|
+
- `SecurityHeadersMiddleware` (conditional)
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## テスト結果
|
|
27
|
+
|
|
28
|
+
### test_app.py(正常系・機能確認)
|
|
29
|
+
| テスト | 結果 |
|
|
30
|
+
|---|---|
|
|
31
|
+
| test_health_endpoint_returns_env | PASS |
|
|
32
|
+
| test_debug_false_by_default | PASS |
|
|
33
|
+
| test_settings_loaded_from_env_vars | PASS |
|
|
34
|
+
| test_throttle_disabled_via_settings | PASS |
|
|
35
|
+
| test_max_body_size_from_settings | PASS |
|
|
36
|
+
| test_db_url_sqlite_default | PASS |
|
|
37
|
+
| test_db_url_mysql_format | PASS |
|
|
38
|
+
|
|
39
|
+
### test_friction.py(摩擦点確認)
|
|
40
|
+
| テスト | 結果 | 摩擦 |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| test_list_fields_not_parseable_from_env_string | PASS | あり(ドキュメント) |
|
|
43
|
+
| test_no_log_level_setting | PASS | あり |
|
|
44
|
+
| test_no_middleware_factory_helper | PASS | あり(設計上の判断) |
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 発見した摩擦点
|
|
49
|
+
|
|
50
|
+
### FT24-F1: list[str] フィールドを環境変数で設定する方法がドキュメントにない
|
|
51
|
+
|
|
52
|
+
**概要**: `cors_origins`、`bearer_tokens`、`api_keys` は `list[str]` 型だが、
|
|
53
|
+
環境変数から設定するには JSON 形式 (`["a","b"]`) が必要。
|
|
54
|
+
単純な `"token1,token2"` ではパースされない。
|
|
55
|
+
|
|
56
|
+
**判断**: pydantic-settings の標準動作のため、リファレンスドキュメントに記載する。
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
### FT24-F2: ログレベルの設定フィールドがない
|
|
61
|
+
|
|
62
|
+
**概要**: `app_debug: bool` しかなく、`INFO`/`WARNING`/`ERROR` の粒度制御ができない。
|
|
63
|
+
|
|
64
|
+
**影響**: ステージング環境で `INFO` ログを出しつつ、本番では `WARNING` に絞りたいが、
|
|
65
|
+
現在は `app_debug=true` でしか詳細ログを出す方法がない。
|
|
66
|
+
|
|
67
|
+
**期待する解決策**: `log_level: str = "INFO"` フィールドを追加。
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
### FT24-F3: AppSettings からミドルウェアを自動設定するヘルパーがない
|
|
72
|
+
|
|
73
|
+
**概要**: AppSettings の設定値をミドルウェアに適用するには、
|
|
74
|
+
毎回条件分岐付きのボイラープレートを手書きする必要がある。
|
|
75
|
+
|
|
76
|
+
**判断**: `configure_middleware(app, settings)` ヘルパーは「薄い HTTP 層」の設計哲学に反し、
|
|
77
|
+
フレームワークの主張が強くなりすぎる。FT アプリ側でパターンを提示する対応が適切。
|
|
78
|
+
今回はドキュメント追加のみ。
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## まとめ
|
|
83
|
+
|
|
84
|
+
`AppSettings` は環境変数から型安全に設定を読み込む機能として正しく動作する。
|
|
85
|
+
実運用での摩擦は:
|
|
86
|
+
|
|
87
|
+
1. **list[str] 環境変数の書き方がわかりにくい** → ドキュメント追加対応
|
|
88
|
+
2. **log_level フィールドがない** → Issue 化・修正対象
|
|
89
|
+
3. **ミドルウェア自動設定ヘルパーがない** → 設計上の判断で修正しない
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# FT25: RequestIdMiddleware 実運用検証
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-20
|
|
4
|
+
**テーマ**: `RequestIdMiddleware` のリクエスト ID 生成・伝播・ContextVar 統合の実運用検証
|
|
5
|
+
**FT アプリ**: `/home/xi/docker/nene2-python-FT/ft25-request-id/`
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 目的
|
|
10
|
+
|
|
11
|
+
`nene2.middleware.RequestIdMiddleware` と `request_id_var` を使って、
|
|
12
|
+
リクエスト ID を生成・伝播するパターンを検証する。
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 実施内容
|
|
17
|
+
|
|
18
|
+
- `/api/echo` エンドポイントで `request_id_var.get()` を返す
|
|
19
|
+
- クライアント提供の有効 UUID v4 の伝播確認
|
|
20
|
+
- 無効な ID が新しい UUID に置き換えられることを確認
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## テスト結果
|
|
25
|
+
|
|
26
|
+
### test_app.py(正常系・機能確認)
|
|
27
|
+
| テスト | 結果 |
|
|
28
|
+
|---|---|
|
|
29
|
+
| test_response_includes_x_request_id_header | PASS |
|
|
30
|
+
| test_request_id_is_available_in_handler | PASS |
|
|
31
|
+
| test_client_supplied_valid_uuid_is_preserved | PASS |
|
|
32
|
+
| test_client_supplied_invalid_id_is_replaced | PASS |
|
|
33
|
+
| test_each_request_gets_unique_id | PASS |
|
|
34
|
+
|
|
35
|
+
### test_friction.py(摩擦点確認)
|
|
36
|
+
| テスト | 結果 | 摩擦 |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| test_request_id_not_accessible_from_middleware_stack | PASS | あり(ドキュメント) |
|
|
39
|
+
| test_no_helper_to_get_request_id_from_header_in_handler | PASS | あり |
|
|
40
|
+
| test_request_id_contextvars_isolation_per_request | PASS | なし(正しい動作) |
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 発見した摩擦点
|
|
45
|
+
|
|
46
|
+
### FT25-F1: ミドルウェア順序依存がドキュメントに明記されていない
|
|
47
|
+
|
|
48
|
+
**概要**: `request_id_var` はミドルウェアの実行順序に依存するが、
|
|
49
|
+
これがドキュメントに明記されていない。
|
|
50
|
+
`RequestIdMiddleware` より先に実行されるミドルウェアでは `request_id_var.get()` が空になる。
|
|
51
|
+
|
|
52
|
+
**判断**: ミドルウェア追加順のドキュメント化で対応(how-to に記載)。
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
### FT25-F2: FastAPI Depends 用の get_request_id() ヘルパーがない
|
|
57
|
+
|
|
58
|
+
**概要**: `request_id_var.get()` で直接取得できるが、
|
|
59
|
+
FastAPI の依存性注入パターンと統合するヘルパーがない。
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
# 各プロジェクトで毎回書く必要がある
|
|
63
|
+
async def get_request_id() -> str:
|
|
64
|
+
return request_id_var.get()
|
|
65
|
+
|
|
66
|
+
@app.get("/")
|
|
67
|
+
async def handler(request_id: str = Depends(get_request_id)):
|
|
68
|
+
...
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**期待する解決策**: `nene2.middleware` から `get_request_id` を直接インポートできると便利。
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## まとめ
|
|
76
|
+
|
|
77
|
+
`RequestIdMiddleware` の基本機能(UUID v4 生成・検証・ContextVar 伝播・レスポンスヘッダー付与)は
|
|
78
|
+
問題なく動作する。
|
|
79
|
+
|
|
80
|
+
摩擦点:
|
|
81
|
+
1. **ミドルウェア順序依存のドキュメント不備** → how-to に追記
|
|
82
|
+
2. **`get_request_id()` Depends ヘルパーがない** → Issue 化・修正対象
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# FT26: nene2.log structlog 統合検証
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-20
|
|
4
|
+
**テーマ**: `setup_logging()` と `AppSettings.log_level` の連携検証
|
|
5
|
+
**FT アプリ**: `/home/xi/docker/nene2-python-FT/ft26-logging/`
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 目的
|
|
10
|
+
|
|
11
|
+
`nene2.log.setup_logging()` を実際のアプリで使い、
|
|
12
|
+
`AppSettings.log_level`(FT24 で追加)との統合フローを検証する。
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 実施内容
|
|
17
|
+
|
|
18
|
+
- `setup_logging()` の `local` / `production` 環境での動作確認
|
|
19
|
+
- `RequestLoggingMiddleware` と structlog の連携確認
|
|
20
|
+
- `AppSettings.log_level` を `setup_logging()` に渡す方法の検証
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## テスト結果
|
|
25
|
+
|
|
26
|
+
### test_app.py(正常系・機能確認)
|
|
27
|
+
| テスト | 結果 |
|
|
28
|
+
|---|---|
|
|
29
|
+
| test_log_test_endpoint_returns_200 | PASS |
|
|
30
|
+
| test_request_logging_middleware_logs_request | PASS |
|
|
31
|
+
| test_log_levels_endpoint_returns_200 | PASS |
|
|
32
|
+
| test_setup_logging_local_env | PASS |
|
|
33
|
+
| test_setup_logging_production_env | PASS |
|
|
34
|
+
|
|
35
|
+
### test_friction.py(摩擦点確認)
|
|
36
|
+
| テスト | 結果 | 摩擦 |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| test_setup_logging_does_not_accept_log_level_param | PASS | あり |
|
|
39
|
+
| test_setup_logging_hardcodes_info_level | PASS | あり |
|
|
40
|
+
| test_no_way_to_integrate_app_settings_log_level_with_setup_logging | PASS | あり(ボイラープレート) |
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 発見した摩擦点
|
|
45
|
+
|
|
46
|
+
### FT26-F1: setup_logging() が log_level を受け取れない
|
|
47
|
+
|
|
48
|
+
**概要**: FT24 で `AppSettings.log_level` が追加されたが、
|
|
49
|
+
`setup_logging()` はそれを受け取るパラメータを持たない。
|
|
50
|
+
`setup_logging()` は常に `logging.INFO` をハードコードするため、
|
|
51
|
+
`AppSettings.log_level = "DEBUG"` を設定しても反映されない。
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
# 現状: ボイラープレートが必要
|
|
55
|
+
settings = AppSettings(log_level="DEBUG")
|
|
56
|
+
setup_logging(app_env=settings.app_env)
|
|
57
|
+
logging.getLogger().setLevel(settings.log_level) # ← 別途必要
|
|
58
|
+
|
|
59
|
+
# 期待する使い方
|
|
60
|
+
setup_logging(app_env=settings.app_env, log_level=settings.log_level)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**期待する解決策**: `setup_logging(app_env="local", log_level="INFO")` のように
|
|
64
|
+
`log_level` パラメータを追加して `AppSettings` と統合しやすくする。
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## まとめ
|
|
69
|
+
|
|
70
|
+
`setup_logging()` の基本機能(ConsoleRenderer / JSONRenderer の切り替え)は問題なく動作する。
|
|
71
|
+
|
|
72
|
+
摩擦点:
|
|
73
|
+
1. **`setup_logging()` に `log_level` パラメータがない** → `AppSettings.log_level` との統合時にボイラープレートが必要 → Issue 化・修正対象
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# FT27: ThrottleMiddleware 長時間運用検証
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-20
|
|
4
|
+
**テーマ**: `ThrottleMiddleware` の長時間稼働時のメモリ蓄積問題を検証
|
|
5
|
+
**FT アプリ**: `/home/xi/docker/nene2-python-FT/ft27-throttle-cleanup/`
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 目的
|
|
10
|
+
|
|
11
|
+
`ThrottleMiddleware` の `_counts` ディクショナリに古いエントリが蓄積する問題(Issue #223)を実証し、
|
|
12
|
+
修正方針を検討する。
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 実施内容
|
|
17
|
+
|
|
18
|
+
- 複数 IP からのリクエストシミュレーション
|
|
19
|
+
- ウィンドウ経過後のエントリ残存を確認
|
|
20
|
+
- クリーンアップ機能の有無を検証
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## テスト結果
|
|
25
|
+
|
|
26
|
+
### test_app.py(正常系・機能確認)
|
|
27
|
+
| テスト | 結果 |
|
|
28
|
+
|---|---|
|
|
29
|
+
| test_ping_returns_200 | PASS |
|
|
30
|
+
| test_rate_limit_headers_present | PASS |
|
|
31
|
+
| test_rate_limit_exceeded_returns_429 | PASS |
|
|
32
|
+
| test_different_ips_have_separate_counters | PASS |
|
|
33
|
+
|
|
34
|
+
### test_friction.py(摩擦点確認)
|
|
35
|
+
| テスト | 結果 | 摩擦 |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| test_stale_entries_accumulate_in_memory | PASS | あり(バグ) |
|
|
38
|
+
| test_no_cleanup_method_exists_on_throttle_middleware | PASS | あり |
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 発見した摩擦点
|
|
43
|
+
|
|
44
|
+
### FT27-F1: 古いエントリが _counts から削除されない(Issue #223)
|
|
45
|
+
|
|
46
|
+
**概要**: 異なるクライアント IP からリクエストが来るたびに `_counts` にエントリが追加されるが、
|
|
47
|
+
ウィンドウ期間が過ぎても古いエントリは削除されない。
|
|
48
|
+
長時間稼働・多数の異なるクライアントが存在する環境ではメモリが際限なく増加する。
|
|
49
|
+
|
|
50
|
+
**確認した動作**:
|
|
51
|
+
```python
|
|
52
|
+
# 10 IP のエントリを作成 → window (1秒) 経過後も 10 エントリが残る
|
|
53
|
+
# 新しい IP のリクエスト後も 11 エントリ (10 古い + 1 新しい)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**修正方針**: `_check_rate()` 内で定期的にクリーンアップを実施する。
|
|
57
|
+
`_last_cleanup` タイムスタンプを持ち、`window` 秒経過したときに期限切れエントリを一括削除する。
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## まとめ
|
|
62
|
+
|
|
63
|
+
`ThrottleMiddleware` の基本機能は正常に動作する。
|
|
64
|
+
|
|
65
|
+
摩擦点:
|
|
66
|
+
1. **古いエントリのメモリ蓄積** → Issue #223 として登録済み、修正対象
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# FT28: ThrottleMiddleware パスごとのレート制限検証
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-20
|
|
4
|
+
**テーマ**: `ThrottleMiddleware` のパスごとのレート制限(Issue #222)
|
|
5
|
+
**FT アプリ**: `/home/xi/docker/nene2-python-FT/ft28-path-throttle/`
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 目的
|
|
10
|
+
|
|
11
|
+
重いエンドポイントだけ厳しいレート制限をかけたい実運用ニーズを検証する。
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 実施内容
|
|
16
|
+
|
|
17
|
+
- `path_limits` パラメータの不在を確認
|
|
18
|
+
- 現状では全エンドポイントで同じ `limit`/`window` しか設定できないことを実証
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## テスト結果
|
|
23
|
+
|
|
24
|
+
### test_friction.py(摩擦点確認)
|
|
25
|
+
| テスト | 結果 | 摩擦 |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| test_throttle_middleware_does_not_support_path_limits | PASS | あり |
|
|
28
|
+
| test_workaround_requires_multiple_middlewares | PASS | あり |
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 発見した摩擦点
|
|
33
|
+
|
|
34
|
+
### FT28-F1: ThrottleMiddleware がパスごとのレート制限をサポートしない(Issue #222)
|
|
35
|
+
|
|
36
|
+
**概要**: 全エンドポイントで同じ `limit`/`window` しか設定できない。
|
|
37
|
+
`/api/expensive` だけ `limit=10` にして残りは `limit=100` にする、という設定ができない。
|
|
38
|
+
|
|
39
|
+
**期待する使い方**:
|
|
40
|
+
```python
|
|
41
|
+
app.add_middleware(
|
|
42
|
+
ThrottleMiddleware,
|
|
43
|
+
limit=100,
|
|
44
|
+
window=60,
|
|
45
|
+
path_limits={"/api/expensive": 10, "/api/search": 30},
|
|
46
|
+
)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## まとめ
|
|
52
|
+
|
|
53
|
+
摩擦点:
|
|
54
|
+
1. **`path_limits` 未対応** → Issue #222 として登録済み、修正対象
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# FT29: AsyncUseCaseProtocol 実運用検証
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-20
|
|
4
|
+
**テーマ**: `AsyncUseCaseProtocol` を使った非同期 UseCase パターンの実運用検証
|
|
5
|
+
**FT アプリ**: `/home/xi/docker/nene2-python-FT/ft29-async-usecase/`
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 目的
|
|
10
|
+
|
|
11
|
+
`AsyncUseCaseProtocol` の実装・FastAPI ハンドラーへの統合・並行処理の動作を検証する。
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 実施内容
|
|
16
|
+
|
|
17
|
+
- 外部 API 呼び出しを模した `FetchDataUseCase` を実装
|
|
18
|
+
- `asyncio.gather()` で並行実行を確認
|
|
19
|
+
- Protocol 適合性と isinstance() の既知制限を検証
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## テスト結果
|
|
24
|
+
|
|
25
|
+
### test_app.py(正常系・機能確認)
|
|
26
|
+
| テスト | 結果 |
|
|
27
|
+
|---|---|
|
|
28
|
+
| test_get_item_returns_200 | PASS |
|
|
29
|
+
| test_slow_endpoint_returns_200 | PASS |
|
|
30
|
+
| test_async_use_case_executes_correctly | PASS |
|
|
31
|
+
| test_async_use_case_satisfies_protocol | PASS |
|
|
32
|
+
| test_multiple_async_calls_are_independent | PASS |
|
|
33
|
+
|
|
34
|
+
### test_friction.py(摩擦点確認)
|
|
35
|
+
| テスト | 結果 | 摩擦 |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| test_isinstance_cannot_distinguish_sync_vs_async_protocol | PASS | 既知(ADR-0010) |
|
|
38
|
+
| test_no_async_usecase_base_class_provided | PASS | 軽微(設計通り) |
|
|
39
|
+
| test_no_generic_di_container_for_async_use_cases | PASS | あり(ドキュメント不備) |
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 発見した摩擦点
|
|
44
|
+
|
|
45
|
+
### FT29-F1: FastAPI Depends を使った AsyncUseCase DI パターンがドキュメント化されていない
|
|
46
|
+
|
|
47
|
+
**概要**: `AsyncUseCaseProtocol` を FastAPI の依存性注入と統合する標準的なパターンがない。
|
|
48
|
+
ユーザーは毎回自分でパターンを決める必要がある。
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
# ユーザーが毎回書く必要があるボイラープレート
|
|
52
|
+
def get_fetch_use_case() -> FetchDataUseCase:
|
|
53
|
+
return FetchDataUseCase()
|
|
54
|
+
|
|
55
|
+
@app.get("/items/{item_id}")
|
|
56
|
+
async def get_item(
|
|
57
|
+
item_id: int,
|
|
58
|
+
use_case: FetchDataUseCase = Depends(get_fetch_use_case),
|
|
59
|
+
) -> JSONResponse:
|
|
60
|
+
result = await use_case.execute(FetchDataInput(item_id=item_id))
|
|
61
|
+
...
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**判断**: how-to ドキュメントに DI パターンを追記する(Issue 化)。
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## まとめ
|
|
69
|
+
|
|
70
|
+
`AsyncUseCaseProtocol` の基本機能(実装・FastAPI 統合・並行処理)は問題なく動作する。
|
|
71
|
+
|
|
72
|
+
摩擦点:
|
|
73
|
+
1. **AsyncUseCase + FastAPI DI パターンがドキュメント化されていない** → Issue 化・how-to に追記
|
|
74
|
+
2. **isinstance() の sync/async 区別不可** → ADR-0010 記載の既知制限、修正不要
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# FT30: RequestLoggingMiddleware + structlog バインディング実運用検証
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-20
|
|
4
|
+
**テーマ**: `RequestLoggingMiddleware` と structlog contextvars の実運用パターン検証
|
|
5
|
+
**FT アプリ**: `/home/xi/docker/nene2-python-FT/ft30-request-logging/`
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 目的
|
|
10
|
+
|
|
11
|
+
`RequestLoggingMiddleware` と structlog の contextvars 統合を実際のアプリで検証し、
|
|
12
|
+
カスタムフィールドの付加方法を確認する。
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 実施内容
|
|
17
|
+
|
|
18
|
+
- `RequestLoggingMiddleware` が自動バインドするコンテキスト(request_id, method, path)を確認
|
|
19
|
+
- 追加のコンテキストフィールドを渡す方法を検証
|
|
20
|
+
- `clear_contextvars()` の挙動確認
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## テスト結果
|
|
25
|
+
|
|
26
|
+
### test_app.py(正常系・機能確認)
|
|
27
|
+
| テスト | 結果 |
|
|
28
|
+
|---|---|
|
|
29
|
+
| test_log_test_endpoint_returns_200 | PASS |
|
|
30
|
+
| test_with_user_context_returns_200 | PASS |
|
|
31
|
+
| test_request_id_is_in_response_header | PASS |
|
|
32
|
+
|
|
33
|
+
### test_friction.py(摩擦点確認)
|
|
34
|
+
| テスト | 結果 | 摩擦 |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| test_clear_contextvars_wipes_pre_bound_context | PASS | 軽微(設計上の制限) |
|
|
37
|
+
| test_no_way_to_add_extra_fields_to_request_log | PASS | あり |
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 発見した摩擦点
|
|
42
|
+
|
|
43
|
+
### FT30-F1: RequestLoggingMiddleware に extra_context パラメータがない
|
|
44
|
+
|
|
45
|
+
**概要**: `service_name` や `version` などの静的フィールドを全リクエストログに含めたい場合、
|
|
46
|
+
`RequestLoggingMiddleware` にパラメータとして渡す方法がない。
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
# 期待する使い方
|
|
50
|
+
app.add_middleware(
|
|
51
|
+
RequestLoggingMiddleware,
|
|
52
|
+
extra_context={"service": "my-api", "version": "1.0.0"},
|
|
53
|
+
)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**期待する解決策**: `extra_context: dict[str, str] | None = None` パラメータを追加し、
|
|
57
|
+
`bind_contextvars()` に追加フィールドとして渡す。
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## まとめ
|
|
62
|
+
|
|
63
|
+
`RequestLoggingMiddleware` の基本機能は問題なく動作する。
|
|
64
|
+
|
|
65
|
+
摩擦点:
|
|
66
|
+
1. **`extra_context` パラメータがない** → Issue 化・修正対象
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# How-to: AsyncUseCase と FastAPI の統合
|
|
2
|
+
|
|
3
|
+
## AsyncUseCaseProtocol の基本実装
|
|
4
|
+
|
|
5
|
+
`AsyncUseCaseProtocol` は Protocol(構造的部分型)なので継承不要です。
|
|
6
|
+
`async def execute(self, input_: I) -> O` を実装するだけで適合します。
|
|
7
|
+
|
|
8
|
+
```python
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from nene2.use_case import AsyncUseCaseProtocol
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True, slots=True)
|
|
14
|
+
class FetchUserInput:
|
|
15
|
+
user_id: int
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True, slots=True)
|
|
19
|
+
class FetchUserOutput:
|
|
20
|
+
user_id: int
|
|
21
|
+
name: str
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class FetchUserUseCase:
|
|
25
|
+
async def execute(self, input_: FetchUserInput) -> FetchUserOutput:
|
|
26
|
+
# 外部 API 呼び出し・DB アクセスなど非同期処理
|
|
27
|
+
return FetchUserOutput(user_id=input_.user_id, name="Alice")
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## FastAPI Depends との統合
|
|
33
|
+
|
|
34
|
+
ファクトリ関数を `Depends()` に渡すのが標準パターンです。
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from fastapi import Depends, FastAPI
|
|
38
|
+
from fastapi.responses import JSONResponse
|
|
39
|
+
|
|
40
|
+
app = FastAPI()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_fetch_user_use_case() -> FetchUserUseCase:
|
|
44
|
+
return FetchUserUseCase()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@app.get("/users/{user_id}")
|
|
48
|
+
async def get_user(
|
|
49
|
+
user_id: int,
|
|
50
|
+
use_case: FetchUserUseCase = Depends(get_fetch_user_use_case),
|
|
51
|
+
) -> JSONResponse:
|
|
52
|
+
result = await use_case.execute(FetchUserInput(user_id=user_id))
|
|
53
|
+
return JSONResponse({"user_id": result.user_id, "name": result.name})
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 外部依存を持つ UseCase の DI
|
|
59
|
+
|
|
60
|
+
リポジトリや外部クライアントを受け取る UseCase は、依存も Depends で注入します。
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
class FetchUserUseCase:
|
|
64
|
+
def __init__(self, repository: UserRepositoryInterface) -> None:
|
|
65
|
+
self._repository = repository
|
|
66
|
+
|
|
67
|
+
async def execute(self, input_: FetchUserInput) -> FetchUserOutput:
|
|
68
|
+
user = await self._repository.find_by_id(input_.user_id)
|
|
69
|
+
return FetchUserOutput(user_id=user.id, name=user.name)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def get_user_repository() -> UserRepositoryInterface:
|
|
73
|
+
return InMemoryUserRepository()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def get_fetch_user_use_case(
|
|
77
|
+
repository: UserRepositoryInterface = Depends(get_user_repository),
|
|
78
|
+
) -> FetchUserUseCase:
|
|
79
|
+
return FetchUserUseCase(repository)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## 並行実行
|
|
85
|
+
|
|
86
|
+
複数の AsyncUseCase を並行実行するには `asyncio.gather()` を使います。
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
import asyncio
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@app.get("/dashboard")
|
|
93
|
+
async def dashboard(
|
|
94
|
+
user_id: int,
|
|
95
|
+
fetch_user: FetchUserUseCase = Depends(get_fetch_user_use_case),
|
|
96
|
+
fetch_stats: FetchStatsUseCase = Depends(get_fetch_stats_use_case),
|
|
97
|
+
) -> JSONResponse:
|
|
98
|
+
user, stats = await asyncio.gather(
|
|
99
|
+
fetch_user.execute(FetchUserInput(user_id=user_id)),
|
|
100
|
+
fetch_stats.execute(FetchStatsInput(user_id=user_id)),
|
|
101
|
+
)
|
|
102
|
+
return JSONResponse({"user": user.name, "stats": stats.count})
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## isinstance() の注意点
|
|
108
|
+
|
|
109
|
+
`AsyncUseCaseProtocol` は `@runtime_checkable` ですが、`isinstance()` は
|
|
110
|
+
`execute` 属性の存在のみを確認します(sync/async の区別はしません)。
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
# isinstance() は sync UseCase も True を返す(false positive)
|
|
114
|
+
isinstance(sync_use_case, AsyncUseCaseProtocol) # → True
|
|
115
|
+
|
|
116
|
+
# 正しい非同期確認方法
|
|
117
|
+
import inspect
|
|
118
|
+
inspect.iscoroutinefunction(use_case.execute) # → True/False
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
型安全性は `mypy --strict` の静的解析で保証します。詳細は ADR-0010 を参照してください。
|