nene2-python 1.8.1__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.1 → nene2_python-1.8.2}/CHANGELOG.md +11 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/PKG-INFO +1 -1
- 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.1 → nene2_python-1.8.2}/pyproject.toml +1 -1
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/middleware/request_logging.py +10 -1
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/nene2/middleware/test_request_logging.py +37 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/uv.lock +1 -1
- {nene2_python-1.8.1 → nene2_python-1.8.2}/.env.example +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/.github/workflows/ci.yml +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/.github/workflows/docs.yml +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/.github/workflows/publish.yml +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/.gitignore +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/.vitepress/config.mts +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/.vitepress/theme/custom.css +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/.vitepress/theme/index.ts +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/AGENTS.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/CLAUDE.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/Dockerfile +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/LICENSE +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/README.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/alembic/README +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/alembic/env.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/alembic/script.py.mako +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/alembic.ini +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/compose.yaml +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/adr/0001-toolchain.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/adr/0002-clean-architecture.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/adr/0003-security-first.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/adr/0004-ai-first-design.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/adr/0005-logging.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/adr/0006-rate-limiting.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/adr/0009-mcp-design.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/adr/0010-async-use-case.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/de/index.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/de/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/explanation/architecture.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-1.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-10.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-11.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-12.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-13.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-14.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-15.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-16.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-17.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-18.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-19.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-2.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-20.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-21.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-22.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-23.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-24.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-25.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-26.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-27.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-28.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-3.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-4.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-5.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-6.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-7.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-8.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/field-trials/2026-05-field-trial-9.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/fr/index.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/fr/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/how-to/new-project.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/how-to/problem-details.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/how-to/run-tests.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/index.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/ja/explanation/architecture.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/ja/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/ja/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/ja/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/ja/how-to/new-project.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/ja/how-to/run-tests.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/ja/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/ja/index.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/ja/reference/api.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/ja/reference/configuration.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/ja/reference/framework-modules.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/ja/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/ja/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/pt-br/index.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/pt-br/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/reference/api.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/reference/configuration.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/reference/framework-modules.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/roadmap.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/todo/current.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/zh/index.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/docs/zh/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/package-lock.json +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/package.json +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/example/__init__.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/example/__main__.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/example/app.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/example/comment/__init__.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/example/comment/entity.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/example/comment/exceptions.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/example/comment/handler.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/example/comment/repository.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/example/comment/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/example/comment/use_case.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/example/mcp.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/example/note/__init__.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/example/note/async_use_case.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/example/note/entity.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/example/note/exceptions.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/example/note/handler.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/example/note/repository.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/example/note/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/example/note/use_case.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/example/schema.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/example/tag/__init__.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/example/tag/entity.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/example/tag/exceptions.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/example/tag/handler.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/example/tag/repository.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/example/tag/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/example/tag/use_case.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/__init__.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/auth/api_key.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/auth/bearer_token.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/auth/exceptions.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/auth/interfaces.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/auth/local_verifier.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/config/settings.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/database/exceptions.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/database/health.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/database/interfaces.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/database/sqlalchemy_executor.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/database/utils.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/http/health.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/http/pagination.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/http/problem_details.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/log/setup.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/mcp/http_client.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/mcp/server.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/middleware/__init__.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/middleware/domain_exception.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/middleware/error_handler.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/middleware/request_id.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/middleware/request_size_limit.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/middleware/security_headers.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/middleware/throttle.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/py.typed +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/use_case/protocols.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/nene2/validation/exceptions.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/scripts/__init__.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/src/scripts/export_openapi.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/__init__.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/example/__init__.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/example/comment/__init__.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/example/comment/test_comment_http.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/example/comment/test_comment_repository.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/example/comment/test_comment_use_case.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/example/conftest.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/example/note/__init__.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/example/note/test_async_note_use_case.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/example/note/test_list_notes.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/example/note/test_note_repository.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/example/tag/__init__.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/example/tag/test_tag_repository.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/example/tag/test_tags.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/example/test_cors.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/example/test_mcp.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/nene2/__init__.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/nene2/auth/test_api_key.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/nene2/auth/test_bearer_token.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/nene2/auth/test_token_issuer.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/nene2/config/test_settings.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/nene2/database/test_transaction.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/nene2/database/test_utils.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/nene2/http/test_health.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/nene2/http/test_pagination.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/nene2/http/test_problem_details.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/nene2/log/test_setup.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/nene2/mcp/test_http_client.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/nene2/middleware/__init__.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/nene2/middleware/test_error_handler.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/nene2/middleware/test_request_id.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/nene2/middleware/test_request_size_limit.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/nene2/middleware/test_security_headers.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/nene2/middleware/test_throttle.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/nene2/use_case/test_protocols.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/nene2/validation/test_exceptions.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/scripts/__init__.py +0 -0
- {nene2_python-1.8.1 → nene2_python-1.8.2}/tests/scripts/test_export_openapi.py +0 -0
|
@@ -5,6 +5,17 @@ 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
|
+
|
|
8
19
|
## [1.8.1] — 2026-05-20
|
|
9
20
|
|
|
10
21
|
FT25〜FT28 フィールドトライアル — RequestId ヘルパー・structlog ログレベル・ThrottleMiddleware 改善。
|
|
@@ -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,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 を参照してください。
|
|
@@ -18,11 +18,19 @@ class RequestLoggingMiddleware(BaseHTTPMiddleware):
|
|
|
18
18
|
Args:
|
|
19
19
|
exclude_paths: Paths to skip logging for (e.g. ``["/health"]``).
|
|
20
20
|
Useful for high-frequency health-check endpoints where log noise is unwanted.
|
|
21
|
+
extra_context: Additional key-value pairs bound to every request log entry
|
|
22
|
+
(e.g. ``{"service": "my-api", "version": "1.0.0"}``).
|
|
21
23
|
"""
|
|
22
24
|
|
|
23
|
-
def __init__(
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
app: object,
|
|
28
|
+
exclude_paths: list[str] | None = None,
|
|
29
|
+
extra_context: dict[str, str] | None = None,
|
|
30
|
+
) -> None:
|
|
24
31
|
super().__init__(app) # type: ignore[arg-type]
|
|
25
32
|
self._exclude_paths = set(exclude_paths or [])
|
|
33
|
+
self._extra_context: dict[str, str] = extra_context or {}
|
|
26
34
|
|
|
27
35
|
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
|
|
28
36
|
if request.url.path in self._exclude_paths:
|
|
@@ -34,6 +42,7 @@ class RequestLoggingMiddleware(BaseHTTPMiddleware):
|
|
|
34
42
|
request_id=request_id_var.get(),
|
|
35
43
|
method=request.method,
|
|
36
44
|
path=request.url.path,
|
|
45
|
+
**self._extra_context,
|
|
37
46
|
)
|
|
38
47
|
logger.info("request.received")
|
|
39
48
|
response = await call_next(request)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Tests for RequestLoggingMiddleware."""
|
|
2
2
|
|
|
3
|
+
import structlog
|
|
3
4
|
from fastapi import FastAPI
|
|
4
5
|
from fastapi.responses import JSONResponse
|
|
5
6
|
from fastapi.testclient import TestClient
|
|
@@ -41,6 +42,42 @@ def test_logging_does_not_remove_headers() -> None:
|
|
|
41
42
|
assert "X-Request-Id" in response.headers
|
|
42
43
|
|
|
43
44
|
|
|
45
|
+
def test_extra_context_is_bound_to_structlog() -> None:
|
|
46
|
+
captured: list[dict] = []
|
|
47
|
+
app = FastAPI()
|
|
48
|
+
app.add_middleware(
|
|
49
|
+
RequestLoggingMiddleware,
|
|
50
|
+
extra_context={"service": "my-api", "version": "1.0"},
|
|
51
|
+
)
|
|
52
|
+
app.add_middleware(RequestIdMiddleware)
|
|
53
|
+
|
|
54
|
+
@app.get("/ping")
|
|
55
|
+
async def ping() -> JSONResponse:
|
|
56
|
+
captured.append(dict(structlog.contextvars.get_contextvars()))
|
|
57
|
+
return JSONResponse({"ok": True})
|
|
58
|
+
|
|
59
|
+
client = TestClient(app)
|
|
60
|
+
client.get("/ping")
|
|
61
|
+
assert captured[0]["service"] == "my-api"
|
|
62
|
+
assert captured[0]["version"] == "1.0"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_extra_context_default_is_empty() -> None:
|
|
66
|
+
captured: list[dict] = []
|
|
67
|
+
app = FastAPI()
|
|
68
|
+
app.add_middleware(RequestLoggingMiddleware) # extra_context なし
|
|
69
|
+
app.add_middleware(RequestIdMiddleware)
|
|
70
|
+
|
|
71
|
+
@app.get("/ping")
|
|
72
|
+
async def ping() -> JSONResponse:
|
|
73
|
+
captured.append(dict(structlog.contextvars.get_contextvars()))
|
|
74
|
+
return JSONResponse({"ok": True})
|
|
75
|
+
|
|
76
|
+
client = TestClient(app)
|
|
77
|
+
client.get("/ping")
|
|
78
|
+
assert "service" not in captured[0]
|
|
79
|
+
|
|
80
|
+
|
|
44
81
|
def test_exclude_paths_passes_requests_through() -> None:
|
|
45
82
|
"""exclude_paths に指定したパスへのリクエストがミドルウェアを通過すること"""
|
|
46
83
|
app = FastAPI()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nene2_python-1.8.1 → nene2_python-1.8.2}/alembic/versions/001_create_notes_and_tags_tables.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|