nene2-python 1.8.12__tar.gz → 1.8.13__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.12 → nene2_python-1.8.13}/CHANGELOG.md +10 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/PKG-INFO +1 -1
- nene2_python-1.8.13/docs/field-trials/2026-05-field-trial-53.md +81 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/pyproject.toml +1 -1
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/auth/api_key.py +9 -7
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/nene2/auth/test_api_key.py +35 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/uv.lock +1 -1
- {nene2_python-1.8.12 → nene2_python-1.8.13}/.env.example +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/.github/workflows/ci.yml +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/.github/workflows/docs.yml +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/.github/workflows/publish.yml +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/.gitignore +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/.vitepress/config.mts +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/.vitepress/theme/custom.css +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/.vitepress/theme/index.ts +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/AGENTS.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/CLAUDE.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/Dockerfile +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/LICENSE +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/README.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/alembic/README +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/alembic/env.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/alembic/script.py.mako +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/alembic.ini +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/compose.yaml +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/adr/0001-toolchain.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/adr/0002-clean-architecture.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/adr/0003-security-first.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/adr/0004-ai-first-design.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/adr/0005-logging.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/adr/0006-rate-limiting.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/adr/0009-mcp-design.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/adr/0010-async-use-case.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/de/index.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/de/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/explanation/architecture.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-1.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-10.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-11.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-12.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-13.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-14.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-15.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-16.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-17.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-18.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-19.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-2.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-20.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-21.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-22.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-23.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-24.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-25.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-26.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-27.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-28.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-29.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-3.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-30.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-31.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-32.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-33.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-34.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-35.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-36.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-37.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-38.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-39.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-4.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-40.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-41.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-42.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-43.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-44.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-45.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-46.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-47.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-48.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-49.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-5.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-50.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-51.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-52.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-6.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-7.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-8.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-9.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/fr/index.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/fr/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/how-to/async-use-case.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/how-to/new-project.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/how-to/problem-details.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/how-to/run-tests.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/how-to/validation.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/index.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/ja/explanation/architecture.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/ja/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/ja/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/ja/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/ja/how-to/new-project.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/ja/how-to/run-tests.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/ja/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/ja/index.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/ja/reference/api.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/ja/reference/configuration.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/ja/reference/framework-modules.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/ja/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/ja/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/pt-br/index.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/pt-br/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/reference/api.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/reference/configuration.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/reference/framework-modules.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/roadmap.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/todo/current.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/zh/index.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/docs/zh/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/package-lock.json +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/package.json +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/example/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/example/__main__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/example/app.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/example/comment/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/example/comment/entity.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/example/comment/exceptions.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/example/comment/handler.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/example/comment/repository.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/example/comment/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/example/comment/use_case.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/example/mcp.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/example/note/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/example/note/async_use_case.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/example/note/entity.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/example/note/exceptions.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/example/note/handler.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/example/note/repository.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/example/note/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/example/note/use_case.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/example/schema.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/example/tag/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/example/tag/entity.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/example/tag/exceptions.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/example/tag/handler.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/example/tag/repository.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/example/tag/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/example/tag/use_case.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/auth/bearer_token.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/auth/exceptions.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/auth/interfaces.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/auth/local_verifier.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/config/settings.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/database/exceptions.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/database/health.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/database/interfaces.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/database/sqlalchemy_executor.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/database/utils.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/http/health.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/http/pagination.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/http/problem_details.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/log/setup.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/mcp/http_client.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/mcp/server.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/middleware/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/middleware/domain_exception.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/middleware/error_handler.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/middleware/request_id.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/middleware/request_logging.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/middleware/request_size_limit.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/middleware/security_headers.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/middleware/throttle.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/py.typed +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/use_case/protocols.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/nene2/validation/exceptions.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/scripts/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/src/scripts/export_openapi.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/example/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/example/comment/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/example/comment/test_comment_http.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/example/comment/test_comment_repository.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/example/comment/test_comment_use_case.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/example/conftest.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/example/note/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/example/note/test_async_note_use_case.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/example/note/test_list_notes.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/example/note/test_note_repository.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/example/tag/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/example/tag/test_tag_repository.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/example/tag/test_tags.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/example/test_cors.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/example/test_mcp.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/nene2/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/nene2/auth/test_bearer_token.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/nene2/auth/test_token_issuer.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/nene2/config/test_settings.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/nene2/database/test_transaction.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/nene2/database/test_utils.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/nene2/http/test_health.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/nene2/http/test_pagination.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/nene2/http/test_problem_details.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/nene2/log/test_setup.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/nene2/mcp/test_http_client.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/nene2/middleware/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/nene2/middleware/test_error_handler.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/nene2/middleware/test_request_id.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/nene2/middleware/test_request_logging.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/nene2/middleware/test_request_size_limit.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/nene2/middleware/test_security_headers.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/nene2/middleware/test_throttle.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/nene2/use_case/test_protocols.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/nene2/validation/test_exceptions.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/scripts/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.13}/tests/scripts/test_export_openapi.py +0 -0
|
@@ -5,6 +5,16 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
## [1.8.13] — 2026-05-20
|
|
9
|
+
|
|
10
|
+
FT53 フィールドトライアル — ApiKeyAuthMiddleware 実運用検証 + header_name パラメータ追加。
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `ApiKeyAuthMiddleware` に `header_name: str = "X-Api-Key"` パラメータを追加 — カスタムヘッダー名 (`X-Service-Token` 等) を指定可能に。エラーメッセージにも `header_name` が反映される (#286) (FT53)
|
|
14
|
+
- Field trial report: `docs/field-trials/2026-05-field-trial-53.md`
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
8
18
|
## [1.8.12] — 2026-05-20
|
|
9
19
|
|
|
10
20
|
FT52 フィールドトライアル — ミドルウェアスタック組み合わせ検証 + LocalTokenVerifier 改善。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nene2-python
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.13
|
|
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,81 @@
|
|
|
1
|
+
# Field Trial 53: ApiKeyAuthMiddleware 実運用検証
|
|
2
|
+
|
|
3
|
+
**Date**: 2026-05-20
|
|
4
|
+
**Theme**: `ApiKeyAuthMiddleware` の実運用パターン検証
|
|
5
|
+
**Version under test**: v1.8.12
|
|
6
|
+
**FT App**: `/home/xi/docker/nene2-python-FT/ft53-api-key-auth/`
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 概要
|
|
11
|
+
|
|
12
|
+
`ApiKeyAuthMiddleware` + `LocalTokenVerifier(set)` の組み合わせを複数 API キー対応の
|
|
13
|
+
サービスで実運用した。`exclude_paths` と `RequestIdMiddleware` との組み合わせも検証。
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 実装内容
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
verifier = LocalTokenVerifier({"key-dev-001", "key-dev-002"})
|
|
21
|
+
app.add_middleware(
|
|
22
|
+
ApiKeyAuthMiddleware,
|
|
23
|
+
verifier=verifier,
|
|
24
|
+
exclude_paths=["/health", "/docs", "/openapi.json", "/redoc"],
|
|
25
|
+
)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## テスト結果
|
|
31
|
+
|
|
32
|
+
9 tests, all passed.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 摩擦ポイント
|
|
37
|
+
|
|
38
|
+
### FP53-1: `ApiKeyAuthMiddleware` のヘッダー名が `X-Api-Key` に固定されている
|
|
39
|
+
|
|
40
|
+
**状況**: サービスによっては `X-Service-Token` や `X-Internal-Key` など
|
|
41
|
+
異なるヘッダー名を使いたい場合がある。
|
|
42
|
+
|
|
43
|
+
**修正**: `header_name: str = "X-Api-Key"` パラメータを追加 (#286)。
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
app.add_middleware(
|
|
47
|
+
ApiKeyAuthMiddleware,
|
|
48
|
+
verifier=verifier,
|
|
49
|
+
header_name="X-Service-Token", # カスタムヘッダー名
|
|
50
|
+
)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
エラーメッセージにも `header_name` が反映される:
|
|
54
|
+
```
|
|
55
|
+
"A valid X-Service-Token header is required."
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**補足**: HTTP ヘッダーは RFC 7230 で大文字小文字無視であるため、
|
|
59
|
+
`X-API-KEY` / `X-Api-Key` などの大文字小文字の違いは問題にならない。
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## フレームワーク変更
|
|
64
|
+
|
|
65
|
+
### `ApiKeyAuthMiddleware` — `header_name` パラメータを追加 (#286)
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
# デフォルト動作(後方互換)
|
|
69
|
+
ApiKeyAuthMiddleware(verifier=verifier) # X-Api-Key を使用
|
|
70
|
+
|
|
71
|
+
# カスタムヘッダー名
|
|
72
|
+
ApiKeyAuthMiddleware(verifier=verifier, header_name="X-Service-Token")
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 結論
|
|
78
|
+
|
|
79
|
+
`ApiKeyAuthMiddleware` は `LocalTokenVerifier(set)` との組み合わせで
|
|
80
|
+
複数 API キーを管理する用途で問題なく動作する。
|
|
81
|
+
`header_name` パラメータの追加で柔軟性が向上した。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""API Key authentication middleware.
|
|
2
2
|
|
|
3
|
-
Validates X-Api-Key
|
|
3
|
+
Validates a configurable header (default: X-Api-Key) using a TokenVerifierProtocol.
|
|
4
4
|
Returns 401 Problem Details when key is absent or invalid.
|
|
5
5
|
"""
|
|
6
6
|
|
|
@@ -13,18 +13,18 @@ from nene2.http.problem_details import problem_details_response
|
|
|
13
13
|
from .exceptions import TokenVerificationException
|
|
14
14
|
from .interfaces import TokenVerifierProtocol
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
_DEFAULT_API_KEY_HEADER = "X-Api-Key"
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class ApiKeyAuthMiddleware(BaseHTTPMiddleware):
|
|
20
|
-
"""Require a valid
|
|
20
|
+
"""Require a valid API key header on every request.
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
health-check endpoints or API documentation::
|
|
22
|
+
The header name defaults to ``X-Api-Key`` but can be customised::
|
|
24
23
|
|
|
25
24
|
app.add_middleware(
|
|
26
25
|
ApiKeyAuthMiddleware,
|
|
27
26
|
verifier=LocalTokenVerifier(api_keys),
|
|
27
|
+
header_name="X-Service-Token",
|
|
28
28
|
exclude_paths=["/docs", "/openapi.json", "/redoc", "/health"],
|
|
29
29
|
)
|
|
30
30
|
"""
|
|
@@ -34,16 +34,18 @@ class ApiKeyAuthMiddleware(BaseHTTPMiddleware):
|
|
|
34
34
|
app: object,
|
|
35
35
|
*,
|
|
36
36
|
verifier: TokenVerifierProtocol,
|
|
37
|
+
header_name: str = _DEFAULT_API_KEY_HEADER,
|
|
37
38
|
exclude_paths: list[str] | None = None,
|
|
38
39
|
) -> None:
|
|
39
40
|
super().__init__(app) # type: ignore[arg-type]
|
|
40
41
|
self._verifier = verifier
|
|
42
|
+
self._header_name = header_name
|
|
41
43
|
self._exclude_paths = set(exclude_paths or [])
|
|
42
44
|
|
|
43
45
|
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
|
|
44
46
|
if request.url.path in self._exclude_paths:
|
|
45
47
|
return await call_next(request)
|
|
46
|
-
api_key = request.headers.get(
|
|
48
|
+
api_key = request.headers.get(self._header_name, "")
|
|
47
49
|
try:
|
|
48
50
|
verified = bool(api_key) and self._verifier.verify(api_key)
|
|
49
51
|
except TokenVerificationException:
|
|
@@ -53,6 +55,6 @@ class ApiKeyAuthMiddleware(BaseHTTPMiddleware):
|
|
|
53
55
|
"unauthorized",
|
|
54
56
|
"Unauthorized",
|
|
55
57
|
401,
|
|
56
|
-
"A valid
|
|
58
|
+
f"A valid {self._header_name} header is required.",
|
|
57
59
|
)
|
|
58
60
|
return await call_next(request)
|
|
@@ -87,3 +87,38 @@ def test_verifier_raises_token_verification_exception_returns_401() -> None:
|
|
|
87
87
|
client = TestClient(app, raise_server_exceptions=False)
|
|
88
88
|
response = client.get("/secret", headers={"X-Api-Key": "any-key"})
|
|
89
89
|
assert response.status_code == 401
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_custom_header_name() -> None:
|
|
93
|
+
app = FastAPI()
|
|
94
|
+
app.add_middleware(
|
|
95
|
+
ApiKeyAuthMiddleware,
|
|
96
|
+
verifier=LocalTokenVerifier(["svc-token"]),
|
|
97
|
+
header_name="X-Service-Token",
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
@app.get("/secret")
|
|
101
|
+
async def secret() -> JSONResponse:
|
|
102
|
+
return JSONResponse({"ok": True})
|
|
103
|
+
|
|
104
|
+
client = TestClient(app, raise_server_exceptions=False)
|
|
105
|
+
assert client.get("/secret", headers={"X-Service-Token": "svc-token"}).status_code == 200
|
|
106
|
+
assert client.get("/secret", headers={"X-Api-Key": "svc-token"}).status_code == 401
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_custom_header_name_in_error_message() -> None:
|
|
110
|
+
app = FastAPI()
|
|
111
|
+
app.add_middleware(
|
|
112
|
+
ApiKeyAuthMiddleware,
|
|
113
|
+
verifier=LocalTokenVerifier(["tok"]),
|
|
114
|
+
header_name="X-Internal-Key",
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
@app.get("/secret")
|
|
118
|
+
async def secret() -> JSONResponse:
|
|
119
|
+
return JSONResponse({"ok": True})
|
|
120
|
+
|
|
121
|
+
client = TestClient(app, raise_server_exceptions=False)
|
|
122
|
+
response = client.get("/secret")
|
|
123
|
+
assert response.status_code == 401
|
|
124
|
+
assert "X-Internal-Key" in response.json().get("detail", "")
|
|
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.12 → nene2_python-1.8.13}/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
|
|
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
|