nene2-python 1.8.27__tar.gz → 1.8.29__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.27 → nene2_python-1.8.29}/CHANGELOG.md +25 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/PKG-INFO +1 -1
- nene2_python-1.8.29/docs/field-trials/2026-05-field-trial-82.md +198 -0
- nene2_python-1.8.29/docs/field-trials/2026-05-field-trial-83.md +223 -0
- nene2_python-1.8.29/docs/field-trials/2026-05-field-trial-84.md +151 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/pyproject.toml +1 -1
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/auth/__init__.py +2 -0
- nene2_python-1.8.29/src/nene2/auth/deps.py +50 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/http/__init__.py +2 -1
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/http/pagination.py +19 -1
- nene2_python-1.8.29/tests/nene2/auth/test_make_require_auth.py +99 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/nene2/http/test_pagination.py +22 -1
- {nene2_python-1.8.27 → nene2_python-1.8.29}/uv.lock +1 -1
- {nene2_python-1.8.27 → nene2_python-1.8.29}/.env.example +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/.github/workflows/ci.yml +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/.github/workflows/docs.yml +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/.github/workflows/publish.yml +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/.gitignore +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/.vitepress/config.mts +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/.vitepress/theme/custom.css +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/.vitepress/theme/index.ts +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/AGENTS.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/CLAUDE.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/Dockerfile +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/LICENSE +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/README.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/alembic/README +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/alembic/env.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/alembic/script.py.mako +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/alembic.ini +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/compose.yaml +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/adr/0001-toolchain.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/adr/0002-clean-architecture.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/adr/0003-security-first.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/adr/0004-ai-first-design.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/adr/0005-logging.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/adr/0006-rate-limiting.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/adr/0009-mcp-design.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/adr/0010-async-use-case.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/de/index.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/de/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/explanation/architecture.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-1.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-10.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-11.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-12.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-13.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-14.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-15.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-16.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-17.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-18.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-19.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-2.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-20.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-21.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-22.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-23.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-24.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-25.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-26.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-27.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-28.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-29.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-3.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-30.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-31.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-32.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-33.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-34.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-35.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-36.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-37.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-38.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-39.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-4.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-40.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-41.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-42.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-43.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-44.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-45.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-46.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-47.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-48.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-49.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-5.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-50.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-51.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-52.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-53.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-54.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-55.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-56.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-57.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-58.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-59.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-6.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-60.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-61.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-62.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-63.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-64.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-65.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-66.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-67.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-68.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-69.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-7.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-70.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-71.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-72.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-73.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-74.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-75.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-76.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-77.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-78.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-79.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-8.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-80.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-81.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/field-trials/2026-05-field-trial-9.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/fr/index.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/fr/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/how-to/async-use-case.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/how-to/middleware-stack.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/how-to/new-project.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/how-to/problem-details.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/how-to/run-tests.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/how-to/validation.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/index.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/ja/explanation/architecture.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/ja/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/ja/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/ja/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/ja/how-to/new-project.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/ja/how-to/run-tests.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/ja/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/ja/index.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/ja/reference/api.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/ja/reference/configuration.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/ja/reference/framework-modules.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/ja/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/ja/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/pt-br/index.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/pt-br/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/reference/api.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/reference/configuration.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/reference/framework-modules.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/roadmap.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/todo/current.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/zh/index.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/docs/zh/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/package-lock.json +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/package.json +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/example/__init__.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/example/__main__.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/example/app.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/example/comment/__init__.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/example/comment/entity.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/example/comment/exceptions.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/example/comment/handler.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/example/comment/repository.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/example/comment/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/example/comment/use_case.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/example/mcp.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/example/note/__init__.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/example/note/async_use_case.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/example/note/entity.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/example/note/exceptions.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/example/note/handler.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/example/note/repository.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/example/note/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/example/note/use_case.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/example/schema.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/example/tag/__init__.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/example/tag/entity.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/example/tag/exceptions.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/example/tag/handler.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/example/tag/repository.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/example/tag/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/example/tag/use_case.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/__init__.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/auth/api_key.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/auth/bearer_token.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/auth/exceptions.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/auth/interfaces.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/auth/local_verifier.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/config/settings.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/database/exceptions.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/database/health.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/database/interfaces.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/database/sqlalchemy_executor.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/database/utils.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/http/health.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/http/problem_details.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/log/setup.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/mcp/http_client.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/mcp/server.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/middleware/__init__.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/middleware/domain_exception.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/middleware/error_handler.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/middleware/request_id.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/middleware/request_logging.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/middleware/request_size_limit.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/middleware/security_headers.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/middleware/setup.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/middleware/throttle.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/py.typed +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/use_case/protocols.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/nene2/validation/exceptions.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/scripts/__init__.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/src/scripts/export_openapi.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/__init__.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/example/__init__.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/example/comment/__init__.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/example/comment/test_comment_http.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/example/comment/test_comment_repository.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/example/comment/test_comment_use_case.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/example/conftest.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/example/note/__init__.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/example/note/test_async_note_use_case.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/example/note/test_list_notes.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/example/note/test_note_repository.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/example/tag/__init__.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/example/tag/test_tag_repository.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/example/tag/test_tags.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/example/test_cors.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/example/test_mcp.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/nene2/__init__.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/nene2/auth/test_api_key.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/nene2/auth/test_bearer_token.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/nene2/auth/test_token_issuer.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/nene2/config/test_settings.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/nene2/database/test_transaction.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/nene2/database/test_utils.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/nene2/http/test_health.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/nene2/http/test_problem_details.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/nene2/log/test_setup.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/nene2/mcp/test_http_client.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/nene2/mcp/test_server.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/nene2/middleware/__init__.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/nene2/middleware/test_error_handler.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/nene2/middleware/test_request_id.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/nene2/middleware/test_request_logging.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/nene2/middleware/test_request_size_limit.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/nene2/middleware/test_security_headers.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/nene2/middleware/test_setup_middlewares.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/nene2/middleware/test_throttle.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/nene2/use_case/test_protocols.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/nene2/use_case/test_run_in_threadpool.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/nene2/validation/test_exceptions.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/scripts/__init__.py +0 -0
- {nene2_python-1.8.27 → nene2_python-1.8.29}/tests/scripts/test_export_openapi.py +0 -0
|
@@ -5,6 +5,31 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
## [1.8.28] — 2026-05-20
|
|
9
|
+
|
|
10
|
+
FT83 フィールドトライアル — Depends() DI パターン検証と PaginationResponse / PaginationDep 改善。
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `PaginationResponse.model_dump()` を `to_dict()` の Pydantic 互換エイリアスとして追加 (#355) (FT83)
|
|
14
|
+
— Pydantic v2 ユーザーが `model_dump()` を期待して AttributeError になる問題を解消
|
|
15
|
+
- `PaginationDep` 型エイリアスを `nene2.http` に追加 (#355) (FT83)
|
|
16
|
+
— `Annotated[PaginationQueryParser, Depends(PaginationQueryParser)]` の省略形
|
|
17
|
+
- Field trial report: `docs/field-trials/2026-05-field-trial-83.md` (FT83)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## [1.8.27] — 2026-05-20
|
|
22
|
+
|
|
23
|
+
FT81 フィールドトライアル — CORS 設定パターン検証と setup_middlewares() への CORS 統合。
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
- `setup_middlewares()` に `cors_allowed_origins` / `cors_allow_credentials` / `cors_allow_methods` / `cors_allow_headers` パラメーターを追加 (#348) (FT81)
|
|
27
|
+
— `CORSMiddleware` を最外側に自動配置し、OPTIONS プリフライトが確実に処理される
|
|
28
|
+
— `cors_allowed_origins=["*"]` を渡すと `ValueError` を raise(wildcard 禁止ポリシーの実装強制)
|
|
29
|
+
- Field trial report: `docs/field-trials/2026-05-field-trial-81.md` (FT81)
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
8
33
|
## [1.8.26] — 2026-05-20
|
|
9
34
|
|
|
10
35
|
FT80 フィールドトライアル — LocalMcpServer + HttpxMcpClient MCP E2E 検証と list_tools() 追加。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nene2-python
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.29
|
|
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,198 @@
|
|
|
1
|
+
# FT82: Background Tasks — FastAPI BackgroundTasks と nene2 の組み合わせ
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-20
|
|
4
|
+
**テーマ**: FastAPI BackgroundTasks を nene2 アーキテクチャと組み合わせた際の動作とパターン検証
|
|
5
|
+
**バージョン**: v1.8.27
|
|
6
|
+
**FTディレクトリ**: `/home/xi/docker/nene2-python-FT/ft82-background-tasks/`
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 概要
|
|
11
|
+
|
|
12
|
+
FastAPI の `BackgroundTasks` は「レスポンス返却後に処理を実行する」仕組みで、
|
|
13
|
+
メール送信・在庫更新・監査ログなどのユースケースに使う。
|
|
14
|
+
nene2 のミドルウェアスタック(`setup_middlewares()`)との共存、
|
|
15
|
+
例外ハンドリングの限界、UseCase への注入パターンを検証した。
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 実装パターン
|
|
20
|
+
|
|
21
|
+
### 基本パターン(ハンドラーで BackgroundTasks を受け取る)
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from fastapi import BackgroundTasks
|
|
25
|
+
|
|
26
|
+
@app.post("/orders")
|
|
27
|
+
def create_order(body: OrderBody, background_tasks: BackgroundTasks) -> JSONResponse:
|
|
28
|
+
output = use_case.execute(CreateOrderInput(item=body.item, quantity=body.quantity))
|
|
29
|
+
# レスポンス送信後にバックグラウンドで実行される
|
|
30
|
+
background_tasks.add_task(send_order_confirmation, output.order_id, output.item)
|
|
31
|
+
background_tasks.add_task(update_inventory, output.item, output.quantity)
|
|
32
|
+
return JSONResponse({...}, status_code=201)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### UseCase を注入可能にするパターン(テスト用ファクトリー)
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
def create_app_with_use_case(
|
|
39
|
+
create_order_fn: Callable[[CreateOrderInput], CreateOrderOutput]
|
|
40
|
+
) -> FastAPI:
|
|
41
|
+
factory_app = FastAPI()
|
|
42
|
+
setup_middlewares(factory_app)
|
|
43
|
+
|
|
44
|
+
@factory_app.post("/orders")
|
|
45
|
+
def _create_order(body: OrderBody, background_tasks: BackgroundTasks) -> JSONResponse:
|
|
46
|
+
output = create_order_fn(...)
|
|
47
|
+
background_tasks.add_task(send_order_confirmation, output.order_id, output.item)
|
|
48
|
+
return JSONResponse({...}, status_code=201)
|
|
49
|
+
|
|
50
|
+
return factory_app
|
|
51
|
+
|
|
52
|
+
# テストでの使用
|
|
53
|
+
injected_app = create_app_with_use_case(lambda _: mock_output)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 発見した問題
|
|
59
|
+
|
|
60
|
+
### 問題1: バックグラウンドタスクの例外がクライアントに見えない
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
# バックグラウンドタスクが例外を投げても...
|
|
64
|
+
def send_email(order_id: int) -> None:
|
|
65
|
+
raise SMTPConnectionError("Mail server down")
|
|
66
|
+
|
|
67
|
+
@app.post("/orders")
|
|
68
|
+
def create_order(body: OrderBody, background_tasks: BackgroundTasks) -> JSONResponse:
|
|
69
|
+
output = use_case.execute(...)
|
|
70
|
+
background_tasks.add_task(send_email, output.order_id)
|
|
71
|
+
return JSONResponse({...}, status_code=201) # ← 201 が返る
|
|
72
|
+
|
|
73
|
+
# クライアントには 201 が届く(メール失敗は見えない)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
FastAPI の設計上、レスポンスはすでに送信済みのため
|
|
77
|
+
バックグラウンドタスクのエラーを HTTP レスポンスコードで伝えられない。
|
|
78
|
+
エラーはサーバーログに記録されるが、クライアントには `201 Created` が届く。
|
|
79
|
+
|
|
80
|
+
### 問題2: nene2 に BackgroundTasks 推奨パターンがない
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
# UseCase にどうやって BackgroundTasks を渡すか、ドキュメントがない
|
|
84
|
+
|
|
85
|
+
# ❌ UseCase の中で直接 BackgroundTasks を使う(アーキテクチャ違反)
|
|
86
|
+
class CreateOrderUseCase:
|
|
87
|
+
def execute(self, input_: CreateOrderInput, bg: BackgroundTasks) -> CreateOrderOutput:
|
|
88
|
+
...
|
|
89
|
+
bg.add_task(send_email, ...) # UseCase が HTTP 境界に依存してしまう
|
|
90
|
+
|
|
91
|
+
# ✅ ハンドラー層で BackgroundTasks を使い、UseCase は純粋に保つ
|
|
92
|
+
@app.post("/orders")
|
|
93
|
+
def create_order(body: OrderBody, background_tasks: BackgroundTasks) -> JSONResponse:
|
|
94
|
+
output = use_case.execute(CreateOrderInput(...)) # UseCase はクリーン
|
|
95
|
+
background_tasks.add_task(send_email, output.order_id) # ハンドラー層で副作用
|
|
96
|
+
return JSONResponse({...})
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
正しいパターン(UseCase を純粋に保ちハンドラー層で BackgroundTasks を使う)が
|
|
100
|
+
nene2 のドキュメントに記載されていない。
|
|
101
|
+
|
|
102
|
+
### 問題3: バックグラウンドタスクの実行確認ができない
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
# 「タスクをキューした」ことと「タスクが成功した」ことを HTTP で区別できない
|
|
106
|
+
r = client.post("/orders/1/alert")
|
|
107
|
+
# レスポンスは {"queued": True} — 成功したかどうかはわからない
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
本番運用では Celery・ARQ・FastAPI Scheduler などの外部ジョブキューが必要。
|
|
111
|
+
「BackgroundTasks は軽量な one-shot 処理向け」という点がドキュメントに不足。
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## テスト結果(全16件パス)
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
test_create_order_returns_201 PASSED
|
|
119
|
+
test_get_order_returns_200 PASSED
|
|
120
|
+
test_get_nonexistent_order_returns_404 PASSED
|
|
121
|
+
test_background_tasks_execute_after_response PASSED # TestClient は同期実行
|
|
122
|
+
test_multiple_background_tasks_all_execute PASSED
|
|
123
|
+
test_background_task_with_alert PASSED
|
|
124
|
+
test_background_task_not_queued_on_404 PASSED # 404 ではタスク未実行
|
|
125
|
+
test_failing_background_task_does_not_affect_response PASSED # 例外 → 200 のまま
|
|
126
|
+
test_failing_background_task_started PASSED
|
|
127
|
+
test_request_id_present_with_background_tasks PASSED # nene2 と共存
|
|
128
|
+
test_security_headers_present_with_background_tasks PASSED # nene2 と共存
|
|
129
|
+
test_validation_error_does_not_queue_background_tasks PASSED # 422 では未実行
|
|
130
|
+
test_injectable_use_case_pattern PASSED # DI パターン動作
|
|
131
|
+
test_friction_background_exception_not_visible_to_client PASSED # 摩擦記録
|
|
132
|
+
test_friction_no_nene2_background_task_pattern PASSED # 摩擦記録
|
|
133
|
+
test_friction_background_task_cannot_return_result PASSED # 摩擦記録
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## 重要な発見: TestClient はバックグラウンドタスクを同期実行する
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
client = TestClient(app)
|
|
142
|
+
r = client.post("/orders", json={"item": "Widget", "quantity": 1})
|
|
143
|
+
# TestClient はレスポンスを返す前にバックグラウンドタスクも実行する
|
|
144
|
+
assert "confirmation:order_1:Widget" in notification_log # ✅ すでに実行済み
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`TestClient` はバックグラウンドタスクをリクエスト完了前に同期的に実行するため、
|
|
148
|
+
テストでは `background_tasks.add_task()` で追加した処理を即座に検証できる。
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## 摩擦ポイント一覧
|
|
153
|
+
|
|
154
|
+
| ID | 内容 | 深刻度 |
|
|
155
|
+
|---|---|---|
|
|
156
|
+
| F82-1 | バックグラウンドタスクの例外がクライアントに見えない(FastAPI 設計上の制約) | 中 |
|
|
157
|
+
| F82-2 | nene2 に BackgroundTasks 推奨パターン(UseCase との分離)のドキュメントがない | 低 |
|
|
158
|
+
| F82-3 | バックグラウンドタスクは result を返せない(外部キューが必要) | 低 |
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## 使用感(主観評価)
|
|
163
|
+
|
|
164
|
+
### 直感性 ★★★★☆
|
|
165
|
+
|
|
166
|
+
`background_tasks.add_task(fn, arg1, arg2)` は非常にシンプルで直感的。
|
|
167
|
+
FastAPI のドキュメントを読めば即座に使える。
|
|
168
|
+
nene2 のミドルウェアスタックとの共存も問題なし。
|
|
169
|
+
|
|
170
|
+
### 実害の深刻さ ★★☆☆☆
|
|
171
|
+
|
|
172
|
+
バックグラウンド例外がクライアントに見えない問題は、
|
|
173
|
+
軽量な通知タスク(メール送信)であれば許容範囲。
|
|
174
|
+
ただし「注文確定」のような業務的に重要な処理を
|
|
175
|
+
バックグラウンドタスクに入れるのは設計ミス — そこは UseCase に残すべき。
|
|
176
|
+
|
|
177
|
+
### 修正のしやすさ ★★★★★
|
|
178
|
+
|
|
179
|
+
必要なのはドキュメントのみ:
|
|
180
|
+
- UseCase はクリーンに保つ(BackgroundTasks を引数に取らない)
|
|
181
|
+
- ハンドラー層でのみ `background_tasks.add_task()` を呼ぶ
|
|
182
|
+
- 失敗して困る処理はバックグラウンドタスクに入れない
|
|
183
|
+
- TestClient はバックグラウンドタスクを同期実行する(テスト性良好)
|
|
184
|
+
|
|
185
|
+
### 総合コメント
|
|
186
|
+
|
|
187
|
+
FastAPI の `BackgroundTasks` は nene2 と相性が良く、
|
|
188
|
+
`setup_middlewares()` のミドルウェアスタックとも完全に共存する。
|
|
189
|
+
X-Request-Id・セキュリティヘッダーも正常に付与される。
|
|
190
|
+
摩擦の大部分は「どう使うべきか」のドキュメント不足であり、
|
|
191
|
+
フレームワークの改修は不要。
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## 推奨アクション
|
|
196
|
+
|
|
197
|
+
1. **docs**: BackgroundTasks パターン(UseCase との分離、TestClient の同期実行)を how-to ガイドに追加
|
|
198
|
+
2. **minor**: BackgroundTasks は「失敗しても OK な副作用のみ」というガイドラインの明記
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# FT83: Depends() DI — FastAPI Depends を nene2 アーキテクチャで活用するパターン検証
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-20
|
|
4
|
+
**テーマ**: UseCase・認証・Pagination の Depends 組み合わせパターンと dependency_overrides
|
|
5
|
+
**バージョン**: v1.8.27
|
|
6
|
+
**FTディレクトリ**: `/home/xi/docker/nene2-python-FT/ft83-depends-injection/`
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 概要
|
|
11
|
+
|
|
12
|
+
FastAPI の `Depends()` を nene2 の UseCase + Repository パターンと組み合わせた。
|
|
13
|
+
依存チェーン(Repository → UseCase → Handler)、認証 Depends、
|
|
14
|
+
`PaginationQueryParser` の統合、テスト時の `dependency_overrides` による差し替えを検証した。
|
|
15
|
+
`PaginationResponse.model_dump()` が存在しない(`to_dict()` が正しい)という
|
|
16
|
+
Pydantic ユーザーがつまずく摩擦点を発見した。
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 実装パターン
|
|
21
|
+
|
|
22
|
+
### Repository → UseCase の Depends チェーン
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from typing import Annotated
|
|
26
|
+
from fastapi import Depends
|
|
27
|
+
|
|
28
|
+
def get_repo() -> InMemoryProductRepository:
|
|
29
|
+
return _repo # シングルトン
|
|
30
|
+
|
|
31
|
+
def get_list_use_case(
|
|
32
|
+
repo: Annotated[InMemoryProductRepository, Depends(get_repo)],
|
|
33
|
+
) -> ListProductsUseCase:
|
|
34
|
+
return ListProductsUseCase(repo)
|
|
35
|
+
|
|
36
|
+
@app.get("/products")
|
|
37
|
+
def list_products(
|
|
38
|
+
use_case: Annotated[ListProductsUseCase, Depends(get_list_use_case)],
|
|
39
|
+
) -> JSONResponse:
|
|
40
|
+
products, total = use_case.execute(...)
|
|
41
|
+
return JSONResponse(...)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### PaginationQueryParser + UseCase の両方を Depends
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
@app.get("/products")
|
|
48
|
+
def list_products(
|
|
49
|
+
pagination: Annotated[PaginationQueryParser, Depends(PaginationQueryParser)],
|
|
50
|
+
use_case: Annotated[ListProductsUseCase, Depends(get_list_use_case)],
|
|
51
|
+
) -> JSONResponse:
|
|
52
|
+
products, total = use_case.execute(
|
|
53
|
+
limit=pagination.limit, offset=pagination.offset
|
|
54
|
+
)
|
|
55
|
+
return JSONResponse(
|
|
56
|
+
PaginationResponse(
|
|
57
|
+
items=[...], total=total, limit=pagination.limit, offset=pagination.offset
|
|
58
|
+
).to_dict() # ← model_dump() ではなく to_dict()
|
|
59
|
+
)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 認証 Depends(Bearer Token)
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
66
|
+
|
|
67
|
+
security = HTTPBearer(auto_error=False)
|
|
68
|
+
|
|
69
|
+
def get_current_user(
|
|
70
|
+
credentials: Annotated[HTTPAuthorizationCredentials | None, Depends(security)],
|
|
71
|
+
) -> str | None:
|
|
72
|
+
if credentials is None:
|
|
73
|
+
return None
|
|
74
|
+
return verify_token(credentials.credentials)
|
|
75
|
+
|
|
76
|
+
def require_auth(user: Annotated[str | None, Depends(get_current_user)]) -> str:
|
|
77
|
+
if user is None:
|
|
78
|
+
raise HTTPException(status_code=401)
|
|
79
|
+
return user
|
|
80
|
+
|
|
81
|
+
@app.post("/products")
|
|
82
|
+
def create_product(
|
|
83
|
+
body: ProductBody,
|
|
84
|
+
use_case: Annotated[CreateProductUseCase, Depends(get_create_use_case)],
|
|
85
|
+
current_user: Annotated[str, Depends(require_auth)],
|
|
86
|
+
) -> JSONResponse: ...
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### テスト時の dependency_overrides
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
custom_repo = InMemoryProductRepository()
|
|
93
|
+
custom_repo.create("Test Product", 100)
|
|
94
|
+
|
|
95
|
+
app.dependency_overrides[get_repo] = lambda: custom_repo
|
|
96
|
+
# テスト実行
|
|
97
|
+
del app.dependency_overrides[get_repo] # クリーンアップ
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 発見した問題
|
|
103
|
+
|
|
104
|
+
### 問題1: PaginationResponse.model_dump() が存在しない
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
# ❌ Pydantic ユーザーは model_dump() を期待してしまう
|
|
108
|
+
PaginationResponse(...).model_dump() # AttributeError!
|
|
109
|
+
|
|
110
|
+
# ✅ 正しいメソッド
|
|
111
|
+
PaginationResponse(...).to_dict()
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`PaginationResponse` は dataclass だが、Pydantic v2 に慣れているユーザーは
|
|
115
|
+
`model_dump()` を期待して AttributeError に直面する。
|
|
116
|
+
FastAPI の OpenAPI ドキュメントや response_model を使うには
|
|
117
|
+
Pydantic BaseModel にするか、FastAPI の Response 型として登録する必要がある。
|
|
118
|
+
|
|
119
|
+
### 問題2: Annotated[T, Depends(fn)] の記述が冗長
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
# 現在の書き方(型情報が Annotated の外側と内側に重複)
|
|
123
|
+
def list_products(
|
|
124
|
+
pagination: Annotated[PaginationQueryParser, Depends(PaginationQueryParser)],
|
|
125
|
+
use_case: Annotated[ListProductsUseCase, Depends(get_list_use_case)],
|
|
126
|
+
) -> JSONResponse: ...
|
|
127
|
+
|
|
128
|
+
# 理想(nene2 が型エイリアスを提供)
|
|
129
|
+
type PaginationDep = Annotated[PaginationQueryParser, Depends(PaginationQueryParser)]
|
|
130
|
+
|
|
131
|
+
@app.get("/products")
|
|
132
|
+
def list_products(pagination: PaginationDep, ...) -> JSONResponse: ...
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`PaginationQueryParser` の `Annotated` エイリアスを nene2 が提供すれば
|
|
136
|
+
毎回書く冗長さが解消される。
|
|
137
|
+
|
|
138
|
+
### 問題3: nene2 の認証 Depends ユーティリティがない
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
# nene2.auth に Depends ユーティリティがない
|
|
142
|
+
# ユーザーは HTTPBearer + LocalTokenVerifier を手動で組み合わせる必要がある
|
|
143
|
+
|
|
144
|
+
# 理想:
|
|
145
|
+
from nene2.auth.deps import CurrentUser, require_auth
|
|
146
|
+
# これが存在しない
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
`LocalTokenVerifier.from_env()` は実装されているが、
|
|
150
|
+
FastAPI の Depends パターンに接続する `CurrentUser` 型や
|
|
151
|
+
`require_auth` Depends がない。
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## テスト結果(全16件パス)
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
test_list_products_empty PASSED
|
|
159
|
+
test_create_product_returns_201 PASSED
|
|
160
|
+
test_get_product_returns_200 PASSED
|
|
161
|
+
test_get_nonexistent_returns_404 PASSED
|
|
162
|
+
test_create_product_requires_auth PASSED
|
|
163
|
+
test_create_product_with_auth_succeeds PASSED
|
|
164
|
+
test_pagination_with_depends PASSED # PaginationQueryParser Depends 動作
|
|
165
|
+
test_pagination_second_page PASSED
|
|
166
|
+
test_override_repo_with_custom_data PASSED # dependency_overrides 動作
|
|
167
|
+
test_override_repo_empty PASSED
|
|
168
|
+
test_request_id_with_depends PASSED # nene2 ミドルウェアと共存
|
|
169
|
+
test_security_headers_with_depends PASSED
|
|
170
|
+
test_validation_error_returns_422 PASSED
|
|
171
|
+
test_friction_annotated_syntax_verbosity PASSED # 摩擦記録
|
|
172
|
+
test_friction_use_case_chains_in_depends PASSED # 摩擦記録
|
|
173
|
+
test_friction_no_nene2_depends_utilities PASSED # 摩擦記録
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## 摩擦ポイント一覧
|
|
179
|
+
|
|
180
|
+
| ID | 内容 | 深刻度 |
|
|
181
|
+
|---|---|---|
|
|
182
|
+
| F83-1 | `PaginationResponse.model_dump()` が存在しない(`to_dict()` が正しい)— Pydantic ユーザー混乱 | 中 |
|
|
183
|
+
| F83-2 | `Annotated[PaginationQueryParser, Depends(PaginationQueryParser)]` の冗長記述 | 低 |
|
|
184
|
+
| F83-3 | nene2 認証の Depends ユーティリティ (`CurrentUser`, `require_auth`) がない | 低 |
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## 使用感(主観評価)
|
|
189
|
+
|
|
190
|
+
### 直感性 ★★★☆☆
|
|
191
|
+
|
|
192
|
+
`Depends()` のパターン自体は FastAPI の機能なので明確。
|
|
193
|
+
ただし Repository → UseCase → Handler の3層チェーンは
|
|
194
|
+
Spring/NestJS の DI に慣れたユーザーには違和感がある
|
|
195
|
+
(Constructor Injection ではなく Function Injection)。
|
|
196
|
+
`dependency_overrides` によるテスト差し替えは非常に優れた機能。
|
|
197
|
+
|
|
198
|
+
### 実害の深刻さ ★★☆☆☆
|
|
199
|
+
|
|
200
|
+
`PaginationResponse.to_dict()` を知らなければ AttributeError で即座に詰まる。
|
|
201
|
+
nene2 ドキュメントに `PaginationResponse` の使い方が記載されているが、
|
|
202
|
+
`model_dump()` と混同するユーザーが続出する可能性がある。
|
|
203
|
+
|
|
204
|
+
### 修正のしやすさ ★★★★★
|
|
205
|
+
|
|
206
|
+
- `PaginationResponse` に `model_dump()` の alias を追加(または別名で明記)
|
|
207
|
+
- `Annotated[PaginationQueryParser, Depends(PaginationQueryParser)]` を `PaginationDep` 型エイリアスとして公開
|
|
208
|
+
- `nene2.auth.deps` モジュールに `CurrentUser` 型と `require_auth` Depends を追加
|
|
209
|
+
|
|
210
|
+
### 総合コメント
|
|
211
|
+
|
|
212
|
+
FastAPI の `Depends()` は nene2 のアーキテクチャと非常に相性がよく、
|
|
213
|
+
Repository の差し替えも `dependency_overrides` で簡単にできる。
|
|
214
|
+
主な摩擦は `PaginationResponse.to_dict()` の名前(Pydantic との不整合)と
|
|
215
|
+
認証 Depends ユーティリティの欠如。どちらも小さな追加で解決できる。
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## 推奨アクション
|
|
220
|
+
|
|
221
|
+
1. **Issue**: `PaginationResponse` に `model_dump()` エイリアスを追加(Pydantic ユーザー向け)
|
|
222
|
+
2. **Issue**: `nene2.http` に `PaginationDep` 型エイリアスを追加(`PaginationQueryParser` の Depends)
|
|
223
|
+
3. **Issue**: `nene2.auth.deps` モジュールに認証 Depends ユーティリティを追加
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# FT84: 認証 Depends ユーティリティ — CurrentUser / require_auth パターン検証
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-20
|
|
4
|
+
**テーマ**: nene2.auth に認証 Depends ユーティリティがない摩擦点と make_require_auth 設計検証
|
|
5
|
+
**バージョン**: v1.8.28
|
|
6
|
+
**FTディレクトリ**: `/home/xi/docker/nene2-python-FT/ft84-auth-depends/`
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 概要
|
|
11
|
+
|
|
12
|
+
`LocalTokenVerifier` と `TokenVerifierProtocol` は実装されているが、
|
|
13
|
+
FastAPI の Depends パターンに接続するユーティリティがない。
|
|
14
|
+
ユーザーは `HTTPBearer` → `verify()` → 未認証 401 を毎プロジェクトで手組みする必要がある。
|
|
15
|
+
`make_require_auth(verifier)` ファクトリーを追加することで解消できる。
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 発見した問題
|
|
20
|
+
|
|
21
|
+
### 問題1: 認証 Depends を毎回手組みする必要がある
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
# 現状: プロジェクトごとに以下のコードを書く必要がある (50+ 行のボイラープレート)
|
|
25
|
+
|
|
26
|
+
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
27
|
+
from nene2.auth import LocalTokenVerifier
|
|
28
|
+
|
|
29
|
+
security = HTTPBearer(auto_error=False)
|
|
30
|
+
_verifier = LocalTokenVerifier.from_env("BEARER_TOKENS")
|
|
31
|
+
|
|
32
|
+
def get_current_user(
|
|
33
|
+
credentials: HTTPAuthorizationCredentials | None = Depends(security),
|
|
34
|
+
) -> str | None:
|
|
35
|
+
if credentials is None:
|
|
36
|
+
return None
|
|
37
|
+
if not _verifier.verify(credentials.credentials):
|
|
38
|
+
return None
|
|
39
|
+
return credentials.credentials
|
|
40
|
+
|
|
41
|
+
def require_auth(user: str | None = Depends(get_current_user)) -> str:
|
|
42
|
+
if user is None:
|
|
43
|
+
raise HTTPException(status_code=401)
|
|
44
|
+
return user
|
|
45
|
+
|
|
46
|
+
# ハンドラーで使う
|
|
47
|
+
@app.post("/items")
|
|
48
|
+
def create(body: ItemBody, user: str = Depends(require_auth)) -> JSONResponse: ...
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 問題2: 任意認証パターンが冗長
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
# 未認証でも通るが user を取得したいパターン
|
|
55
|
+
def optional_auth(user: str | None = Depends(get_current_user)) -> str | None:
|
|
56
|
+
return user # None = 未認証、str = ユーザーID
|
|
57
|
+
|
|
58
|
+
@app.get("/feed")
|
|
59
|
+
def get_feed(user: str | None = Depends(optional_auth)) -> JSONResponse:
|
|
60
|
+
if user:
|
|
61
|
+
return JSONResponse({"personalized": True})
|
|
62
|
+
return JSONResponse({"personalized": False})
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 問題3: TokenVerifierProtocol を Depends で直接使えない
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
# こういう書き方をしたくなるが Depends は callable を期待する
|
|
69
|
+
@app.post("/items")
|
|
70
|
+
def create(
|
|
71
|
+
body: ItemBody,
|
|
72
|
+
token: Annotated[str, Depends(LocalTokenVerifier.from_env("TOKENS"))], # ← 機能しない
|
|
73
|
+
) -> JSONResponse: ...
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## テスト結果(全12件パス)
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
test_public_endpoint_no_auth PASSED
|
|
82
|
+
test_create_item_with_valid_token PASSED
|
|
83
|
+
test_create_item_without_token_returns_401 PASSED
|
|
84
|
+
test_create_item_with_invalid_token_returns_401 PASSED
|
|
85
|
+
test_get_me_returns_user_info PASSED
|
|
86
|
+
test_delete_item_authenticated PASSED
|
|
87
|
+
test_delete_item_unauthenticated_returns_401 PASSED
|
|
88
|
+
test_request_id_present_on_401 PASSED # nene2 ミドルウェアと共存
|
|
89
|
+
test_security_headers_present_on_401 PASSED # nene2 ミドルウェアと共存
|
|
90
|
+
test_friction_boilerplate_for_auth_depends PASSED # 摩擦記録
|
|
91
|
+
test_friction_optional_auth_pattern_verbose PASSED # 摩擦記録
|
|
92
|
+
test_friction_verifier_not_injectable_as_depends PASSED # 摩擦記録
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## 摩擦ポイント一覧
|
|
98
|
+
|
|
99
|
+
| ID | 内容 | 深刻度 |
|
|
100
|
+
|---|---|---|
|
|
101
|
+
| F84-1 | 認証 Depends ボイラープレートを毎プロジェクトで手組みする必要がある | 中 |
|
|
102
|
+
| F84-2 | 任意認証(Optional User)パターンが冗長 | 低 |
|
|
103
|
+
| F84-3 | `TokenVerifierProtocol` を Depends で直接使えない | 低 |
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## 使用感(主観評価)
|
|
108
|
+
|
|
109
|
+
### 直感性 ★★☆☆☆
|
|
110
|
+
|
|
111
|
+
「nene2 でトークン認証を追加する」という操作に必要なステップが多すぎる。
|
|
112
|
+
`BearerTokenMiddleware` は全経路に認証を入れるには便利だが、
|
|
113
|
+
「このエンドポイントだけ認証を要求」パターンに Depends ユーティリティがない。
|
|
114
|
+
FastAPI の HTTPBearer を使う方法は FastAPI のドキュメントを参照しなければわからない。
|
|
115
|
+
|
|
116
|
+
### 実害の深刻さ ★★★☆☆
|
|
117
|
+
|
|
118
|
+
認証は多くのプロジェクトで必要。毎回 50 行のボイラープレートを書くのは
|
|
119
|
+
DRY 原則に反する。バグが入りやすく、テスト漏れにもつながる。
|
|
120
|
+
|
|
121
|
+
### 修正のしやすさ ★★★★★
|
|
122
|
+
|
|
123
|
+
`make_require_auth(verifier)` 関数を `nene2.auth` に追加するだけ:
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
def make_require_auth(verifier: TokenVerifierProtocol) -> Callable[..., str]:
|
|
127
|
+
security = HTTPBearer(auto_error=False)
|
|
128
|
+
def get_current_user(credentials: ... = Depends(security)) -> str | None:
|
|
129
|
+
if credentials is None or not verifier.verify(credentials.credentials):
|
|
130
|
+
return None
|
|
131
|
+
return credentials.credentials
|
|
132
|
+
def require_auth(user: str | None = Depends(get_current_user)) -> str:
|
|
133
|
+
if user is None:
|
|
134
|
+
raise HTTPException(status_code=401)
|
|
135
|
+
return user
|
|
136
|
+
return require_auth
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### 総合コメント
|
|
140
|
+
|
|
141
|
+
nene2 の認証機能は「ミドルウェアで全経路を保護する」ユースケースは
|
|
142
|
+
`BearerTokenMiddleware` でカバーされている。
|
|
143
|
+
不足しているのは「特定エンドポイントのみ認証を要求する」ユースケース。
|
|
144
|
+
`make_require_auth()` を追加することで「nene2 だけで完結」できる。
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## 推奨アクション
|
|
149
|
+
|
|
150
|
+
1. **Issue #358**: `nene2.auth` に `make_require_auth(verifier)` ファクトリーを追加
|
|
151
|
+
— `require_auth = make_require_auth(LocalTokenVerifier.from_env("TOKENS"))` パターンを実現
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from .api_key import ApiKeyAuthMiddleware
|
|
4
4
|
from .bearer_token import BearerTokenMiddleware
|
|
5
|
+
from .deps import make_require_auth
|
|
5
6
|
from .exceptions import TokenVerificationException
|
|
6
7
|
from .interfaces import TokenIssuerProtocol, TokenVerifierProtocol
|
|
7
8
|
from .local_verifier import LocalTokenVerifier
|
|
@@ -13,4 +14,5 @@ __all__ = [
|
|
|
13
14
|
"TokenIssuerProtocol",
|
|
14
15
|
"TokenVerificationException",
|
|
15
16
|
"TokenVerifierProtocol",
|
|
17
|
+
"make_require_auth",
|
|
16
18
|
]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""FastAPI Depends utilities for authentication.
|
|
2
|
+
|
|
3
|
+
Provides ``make_require_auth`` to wire :class:`TokenVerifierProtocol` into
|
|
4
|
+
FastAPI's dependency injection system without boilerplate.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from collections.abc import Callable
|
|
8
|
+
from typing import Annotated
|
|
9
|
+
|
|
10
|
+
from fastapi import Depends, HTTPException
|
|
11
|
+
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
12
|
+
|
|
13
|
+
from .interfaces import TokenVerifierProtocol
|
|
14
|
+
|
|
15
|
+
_security = HTTPBearer(auto_error=False)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def make_require_auth(verifier: TokenVerifierProtocol) -> Callable[..., str]:
|
|
19
|
+
"""Return a FastAPI Depends-compatible callable that enforces token authentication.
|
|
20
|
+
|
|
21
|
+
Usage::
|
|
22
|
+
|
|
23
|
+
from nene2.auth import LocalTokenVerifier, make_require_auth
|
|
24
|
+
|
|
25
|
+
verifier = LocalTokenVerifier.from_env("BEARER_TOKENS")
|
|
26
|
+
require_auth = make_require_auth(verifier)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@app.post("/items")
|
|
30
|
+
def create_item(
|
|
31
|
+
body: ItemBody,
|
|
32
|
+
token: Annotated[str, Depends(require_auth)],
|
|
33
|
+
) -> JSONResponse: ...
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
verifier: A :class:`TokenVerifierProtocol` implementation to validate tokens.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
A dependency function that returns the raw token string when authenticated,
|
|
40
|
+
or raises ``HTTP 401`` when the token is absent or invalid.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def _get_token(
|
|
44
|
+
credentials: Annotated[HTTPAuthorizationCredentials | None, Depends(_security)],
|
|
45
|
+
) -> str:
|
|
46
|
+
if credentials is None or not verifier.verify(credentials.credentials):
|
|
47
|
+
raise HTTPException(status_code=401, detail="Invalid or missing token")
|
|
48
|
+
return credentials.credentials
|
|
49
|
+
|
|
50
|
+
return _get_token
|