nene2-python 1.8.12__tar.gz → 1.8.14__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.14}/CHANGELOG.md +19 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/PKG-INFO +1 -1
- nene2_python-1.8.14/docs/field-trials/2026-05-field-trial-53.md +81 -0
- nene2_python-1.8.14/docs/field-trials/2026-05-field-trial-54.md +71 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/pyproject.toml +1 -1
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/auth/api_key.py +9 -7
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/nene2/auth/test_api_key.py +35 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/uv.lock +1 -1
- {nene2_python-1.8.12 → nene2_python-1.8.14}/.env.example +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/.github/workflows/ci.yml +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/.github/workflows/docs.yml +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/.github/workflows/publish.yml +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/.gitignore +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/.vitepress/config.mts +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/.vitepress/theme/custom.css +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/.vitepress/theme/index.ts +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/AGENTS.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/CLAUDE.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/Dockerfile +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/LICENSE +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/README.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/alembic/README +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/alembic/env.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/alembic/script.py.mako +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/alembic.ini +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/compose.yaml +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/adr/0001-toolchain.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/adr/0002-clean-architecture.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/adr/0003-security-first.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/adr/0004-ai-first-design.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/adr/0005-logging.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/adr/0006-rate-limiting.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/adr/0009-mcp-design.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/adr/0010-async-use-case.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/de/index.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/de/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/explanation/architecture.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-1.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-10.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-11.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-12.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-13.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-14.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-15.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-16.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-17.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-18.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-19.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-2.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-20.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-21.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-22.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-23.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-24.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-25.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-26.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-27.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-28.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-29.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-3.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-30.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-31.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-32.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-33.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-34.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-35.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-36.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-37.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-38.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-39.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-4.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-40.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-41.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-42.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-43.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-44.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-45.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-46.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-47.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-48.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-49.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-5.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-50.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-51.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-52.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-6.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-7.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-8.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/field-trials/2026-05-field-trial-9.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/fr/index.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/fr/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/how-to/async-use-case.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/how-to/new-project.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/how-to/problem-details.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/how-to/run-tests.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/how-to/validation.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/index.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/ja/explanation/architecture.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/ja/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/ja/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/ja/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/ja/how-to/new-project.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/ja/how-to/run-tests.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/ja/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/ja/index.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/ja/reference/api.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/ja/reference/configuration.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/ja/reference/framework-modules.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/ja/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/ja/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/pt-br/index.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/pt-br/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/reference/api.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/reference/configuration.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/reference/framework-modules.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/roadmap.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/todo/current.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/zh/index.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/docs/zh/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/package-lock.json +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/package.json +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/example/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/example/__main__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/example/app.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/example/comment/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/example/comment/entity.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/example/comment/exceptions.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/example/comment/handler.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/example/comment/repository.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/example/comment/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/example/comment/use_case.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/example/mcp.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/example/note/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/example/note/async_use_case.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/example/note/entity.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/example/note/exceptions.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/example/note/handler.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/example/note/repository.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/example/note/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/example/note/use_case.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/example/schema.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/example/tag/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/example/tag/entity.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/example/tag/exceptions.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/example/tag/handler.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/example/tag/repository.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/example/tag/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/example/tag/use_case.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/auth/bearer_token.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/auth/exceptions.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/auth/interfaces.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/auth/local_verifier.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/config/settings.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/database/exceptions.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/database/health.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/database/interfaces.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/database/sqlalchemy_executor.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/database/utils.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/http/health.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/http/pagination.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/http/problem_details.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/log/setup.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/mcp/http_client.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/mcp/server.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/middleware/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/middleware/domain_exception.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/middleware/error_handler.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/middleware/request_id.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/middleware/request_logging.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/middleware/request_size_limit.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/middleware/security_headers.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/middleware/throttle.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/py.typed +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/use_case/protocols.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/nene2/validation/exceptions.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/scripts/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/src/scripts/export_openapi.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/example/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/example/comment/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/example/comment/test_comment_http.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/example/comment/test_comment_repository.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/example/comment/test_comment_use_case.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/example/conftest.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/example/note/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/example/note/test_async_note_use_case.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/example/note/test_list_notes.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/example/note/test_note_repository.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/example/tag/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/example/tag/test_tag_repository.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/example/tag/test_tags.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/example/test_cors.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/example/test_mcp.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/nene2/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/nene2/auth/test_bearer_token.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/nene2/auth/test_token_issuer.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/nene2/config/test_settings.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/nene2/database/test_transaction.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/nene2/database/test_utils.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/nene2/http/test_health.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/nene2/http/test_pagination.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/nene2/http/test_problem_details.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/nene2/log/test_setup.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/nene2/mcp/test_http_client.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/nene2/middleware/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/nene2/middleware/test_error_handler.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/nene2/middleware/test_request_id.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/nene2/middleware/test_request_logging.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/nene2/middleware/test_request_size_limit.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/nene2/middleware/test_security_headers.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/nene2/middleware/test_throttle.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/nene2/use_case/test_protocols.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/nene2/validation/test_exceptions.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/scripts/__init__.py +0 -0
- {nene2_python-1.8.12 → nene2_python-1.8.14}/tests/scripts/test_export_openapi.py +0 -0
|
@@ -5,6 +5,25 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
## [1.8.14] — 2026-05-20
|
|
9
|
+
|
|
10
|
+
FT54 フィールドトライアル — RequestSizeLimitMiddleware + path_limits 実運用検証。
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Field trial report: `docs/field-trials/2026-05-field-trial-54.md`
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## [1.8.13] — 2026-05-20
|
|
18
|
+
|
|
19
|
+
FT53 フィールドトライアル — ApiKeyAuthMiddleware 実運用検証 + header_name パラメータ追加。
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
- `ApiKeyAuthMiddleware` に `header_name: str = "X-Api-Key"` パラメータを追加 — カスタムヘッダー名 (`X-Service-Token` 等) を指定可能に。エラーメッセージにも `header_name` が反映される (#286) (FT53)
|
|
23
|
+
- Field trial report: `docs/field-trials/2026-05-field-trial-53.md`
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
8
27
|
## [1.8.12] — 2026-05-20
|
|
9
28
|
|
|
10
29
|
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.14
|
|
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` パラメータの追加で柔軟性が向上した。
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Field Trial 54: RequestSizeLimitMiddleware + path_limits 実運用検証
|
|
2
|
+
|
|
3
|
+
**Date**: 2026-05-20
|
|
4
|
+
**Theme**: `RequestSizeLimitMiddleware` + `path_limits` の実運用パターン検証
|
|
5
|
+
**Version under test**: v1.8.13
|
|
6
|
+
**FT App**: `/home/xi/docker/nene2-python-FT/ft54-size-limit/`
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 概要
|
|
11
|
+
|
|
12
|
+
ファイルアップロード API で `RequestSizeLimitMiddleware` の `path_limits` を使い、
|
|
13
|
+
エンドポイントごとに異なるサイズ上限を設定した。
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 実装内容
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
app.add_middleware(
|
|
21
|
+
RequestSizeLimitMiddleware,
|
|
22
|
+
max_bytes=10 * KB, # デフォルト: 10KB
|
|
23
|
+
path_limits={
|
|
24
|
+
"/upload/avatar": 50 * KB, # プロフィール画像
|
|
25
|
+
"/upload/document": 200 * KB, # ドキュメント
|
|
26
|
+
},
|
|
27
|
+
exclude_paths=["/health"],
|
|
28
|
+
)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`path_limits` の値はデフォルト `max_bytes` を上書きする。
|
|
32
|
+
`/upload/avatar` は `10KB` 超過でも `50KB` 以下なら受け入れる。
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## テスト結果
|
|
37
|
+
|
|
38
|
+
9 tests, all passed.
|
|
39
|
+
|
|
40
|
+
| テスト | 結果 |
|
|
41
|
+
|---|---|
|
|
42
|
+
| /health は除外 | ✅ |
|
|
43
|
+
| /data の 5KB はデフォルト内 | ✅ |
|
|
44
|
+
| /data の 11KB がデフォルト超過 → 413 | ✅ |
|
|
45
|
+
| 413 が Problem Details 形式 (max_bytes フィールド含む) | ✅ |
|
|
46
|
+
| /upload/avatar の 40KB は path_limits 内 | ✅ |
|
|
47
|
+
| /upload/avatar の 51KB が path_limits 超過 → 413 | ✅ |
|
|
48
|
+
| /upload/document の 150KB は path_limits 内 | ✅ |
|
|
49
|
+
| /upload/document の 201KB が path_limits 超過 → 413 | ✅ |
|
|
50
|
+
| path_limits がデフォルトを上書きすることを確認 | ✅ |
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 摩擦ポイント
|
|
55
|
+
|
|
56
|
+
摩擦なし。`path_limits` の設定は直感的で期待通りに動作した。
|
|
57
|
+
|
|
58
|
+
413 レスポンスに `max_bytes` フィールドが含まれるため、クライアントが上限値を把握できる。
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## フレームワーク変更
|
|
63
|
+
|
|
64
|
+
なし。
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 結論
|
|
69
|
+
|
|
70
|
+
`RequestSizeLimitMiddleware` の `path_limits` はファイルアップロード API の実運用で
|
|
71
|
+
摩擦なく使える。デフォルト + パスごとのオーバーライドという設計が直感的。
|
|
@@ -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.14}/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
|