nene2-python 1.8.2__tar.gz → 1.8.3__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.2 → nene2_python-1.8.3}/CHANGELOG.md +12 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/PKG-INFO +1 -1
- nene2_python-1.8.3/docs/field-trials/2026-05-field-trial-31.md +66 -0
- nene2_python-1.8.3/docs/field-trials/2026-05-field-trial-32.md +78 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/pyproject.toml +1 -1
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/http/health.py +4 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/middleware/security_headers.py +17 -3
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/http/test_health.py +8 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/middleware/test_security_headers.py +38 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/uv.lock +1 -1
- {nene2_python-1.8.2 → nene2_python-1.8.3}/.env.example +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/.github/workflows/ci.yml +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/.github/workflows/docs.yml +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/.github/workflows/publish.yml +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/.gitignore +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/.vitepress/config.mts +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/.vitepress/theme/custom.css +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/.vitepress/theme/index.ts +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/AGENTS.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/CLAUDE.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/Dockerfile +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/LICENSE +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/README.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/alembic/README +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/alembic/env.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/alembic/script.py.mako +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/alembic.ini +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/compose.yaml +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/adr/0001-toolchain.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/adr/0002-clean-architecture.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/adr/0003-security-first.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/adr/0004-ai-first-design.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/adr/0005-logging.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/adr/0006-rate-limiting.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/adr/0009-mcp-design.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/adr/0010-async-use-case.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/de/index.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/de/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/explanation/architecture.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-1.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-10.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-11.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-12.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-13.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-14.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-15.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-16.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-17.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-18.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-19.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-2.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-20.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-21.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-22.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-23.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-24.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-25.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-26.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-27.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-28.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-29.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-3.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-30.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-4.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-5.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-6.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-7.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-8.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-9.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/fr/index.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/fr/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/how-to/async-use-case.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/how-to/new-project.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/how-to/problem-details.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/how-to/run-tests.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/index.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/ja/explanation/architecture.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/ja/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/ja/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/ja/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/ja/how-to/new-project.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/ja/how-to/run-tests.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/ja/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/ja/index.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/ja/reference/api.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/ja/reference/configuration.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/ja/reference/framework-modules.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/ja/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/ja/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/pt-br/index.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/pt-br/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/reference/api.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/reference/configuration.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/reference/framework-modules.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/roadmap.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/todo/current.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/zh/index.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/zh/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/package-lock.json +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/package.json +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/__init__.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/__main__.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/app.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/comment/__init__.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/comment/entity.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/comment/exceptions.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/comment/handler.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/comment/repository.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/comment/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/comment/use_case.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/mcp.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/note/__init__.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/note/async_use_case.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/note/entity.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/note/exceptions.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/note/handler.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/note/repository.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/note/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/note/use_case.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/schema.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/tag/__init__.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/tag/entity.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/tag/exceptions.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/tag/handler.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/tag/repository.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/tag/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/tag/use_case.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/__init__.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/auth/api_key.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/auth/bearer_token.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/auth/exceptions.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/auth/interfaces.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/auth/local_verifier.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/config/settings.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/database/exceptions.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/database/health.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/database/interfaces.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/database/sqlalchemy_executor.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/database/utils.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/http/pagination.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/http/problem_details.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/log/setup.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/mcp/http_client.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/mcp/server.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/middleware/__init__.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/middleware/domain_exception.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/middleware/error_handler.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/middleware/request_id.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/middleware/request_logging.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/middleware/request_size_limit.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/middleware/throttle.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/py.typed +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/use_case/protocols.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/validation/exceptions.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/scripts/__init__.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/src/scripts/export_openapi.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/__init__.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/example/__init__.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/example/comment/__init__.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/example/comment/test_comment_http.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/example/comment/test_comment_repository.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/example/comment/test_comment_use_case.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/example/conftest.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/example/note/__init__.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/example/note/test_async_note_use_case.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/example/note/test_list_notes.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/example/note/test_note_repository.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/example/tag/__init__.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/example/tag/test_tag_repository.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/example/tag/test_tags.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/example/test_cors.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/example/test_mcp.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/__init__.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/auth/test_api_key.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/auth/test_bearer_token.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/auth/test_token_issuer.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/config/test_settings.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/database/test_transaction.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/database/test_utils.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/http/test_pagination.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/http/test_problem_details.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/log/test_setup.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/mcp/test_http_client.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/middleware/__init__.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/middleware/test_error_handler.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/middleware/test_request_id.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/middleware/test_request_logging.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/middleware/test_request_size_limit.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/middleware/test_throttle.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/use_case/test_protocols.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/validation/test_exceptions.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/scripts/__init__.py +0 -0
- {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/scripts/test_export_openapi.py +0 -0
|
@@ -5,6 +5,18 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
## [1.8.3] — 2026-05-20
|
|
9
|
+
|
|
10
|
+
FT31〜FT32 フィールドトライアル — HealthCheck・SecurityHeaders 改善。
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `HealthStatus.http_status_code` プロパティ — `is_healthy` → 200、それ以外 → 503 のマッピングを提供 (FT31)
|
|
14
|
+
- `SecurityHeadersMiddleware` に `permissions_policy: str | None = None` パラメータを追加 (FT32)
|
|
15
|
+
- `SecurityHeadersMiddleware` に `hsts: str | None = None` パラメータを追加 (FT32)
|
|
16
|
+
- Field trial reports: `docs/field-trials/2026-05-field-trial-31.md`、`docs/field-trials/2026-05-field-trial-32.md`
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
8
20
|
## [1.8.2] — 2026-05-20
|
|
9
21
|
|
|
10
22
|
FT29〜FT30 フィールドトライアル — AsyncUseCase ドキュメント・RequestLoggingMiddleware 改善。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nene2-python
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.3
|
|
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,66 @@
|
|
|
1
|
+
# FT31: DatabaseHealthCheck + ヘルスエンドポイント統合検証
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-20
|
|
4
|
+
**テーマ**: `DatabaseHealthCheck` と `/health` エンドポイントの統合パターン検証
|
|
5
|
+
**FT アプリ**: `/home/xi/docker/nene2-python-FT/ft31-health-check/`
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 目的
|
|
10
|
+
|
|
11
|
+
`DatabaseHealthCheck` + `CompositeHealthCheck` を使った `/health` エンドポイントの実装パターンを検証する。
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 実施内容
|
|
16
|
+
|
|
17
|
+
- `DatabaseHealthCheck` + `CompositeHealthCheck` で `/health` を実装
|
|
18
|
+
- DB 接続成功時に 200、失敗時に 503 を返すパターンを確認
|
|
19
|
+
- フレームワークが提供すべき機能の不足を記録
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## テスト結果
|
|
24
|
+
|
|
25
|
+
### test_app.py(正常系・機能確認)
|
|
26
|
+
| テスト | 結果 |
|
|
27
|
+
|---|---|
|
|
28
|
+
| test_health_endpoint_returns_200_when_db_healthy | PASS |
|
|
29
|
+
| test_health_includes_all_checks | PASS |
|
|
30
|
+
| test_ping_returns_200 | PASS |
|
|
31
|
+
| test_health_returns_503_when_db_fails | PASS |
|
|
32
|
+
|
|
33
|
+
### test_friction.py(摩擦点確認)
|
|
34
|
+
| テスト | 結果 | 摩擦 |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| test_health_endpoint_has_no_built_in_route | PASS | 軽微(how-to 推奨パターン記載で対応) |
|
|
37
|
+
| test_health_status_lacks_http_status_code_mapping | PASS | あり |
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 発見した摩擦点
|
|
42
|
+
|
|
43
|
+
### FT31-F1: HealthStatus が HTTP ステータスコードのマッピングを持たない
|
|
44
|
+
|
|
45
|
+
**概要**: `/health` エンドポイントを実装するとき、
|
|
46
|
+
`status="ok"` → 200、`status="error"` → 503 のマッピングを毎回手動で書く必要がある。
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
# 毎回このボイラープレートが必要
|
|
50
|
+
http_status = 200 if status.is_healthy else 503
|
|
51
|
+
return JSONResponse({"status": status.status}, status_code=http_status)
|
|
52
|
+
|
|
53
|
+
# 期待する使い方
|
|
54
|
+
return JSONResponse({"status": status.status}, status_code=status.http_status_code)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**期待する解決策**: `HealthStatus` に `http_status_code: int` プロパティを追加する。
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## まとめ
|
|
62
|
+
|
|
63
|
+
`DatabaseHealthCheck` + `CompositeHealthCheck` の基本機能は問題なく動作する。
|
|
64
|
+
|
|
65
|
+
摩擦点:
|
|
66
|
+
1. **`HealthStatus.http_status_code` プロパティがない** → Issue 化・修正対象
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# FT32: SecurityHeadersMiddleware CSP カスタマイズ実運用検証
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-20
|
|
4
|
+
**テーマ**: `SecurityHeadersMiddleware` のカスタマイズ可能性検証
|
|
5
|
+
**FT アプリ**: `/home/xi/docker/nene2-python-FT/ft32-security-headers/`
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 目的
|
|
10
|
+
|
|
11
|
+
`SecurityHeadersMiddleware` の CSP カスタマイズ機能を実際のアプリで検証し、
|
|
12
|
+
ハードコードされたヘッダーの問題を記録する。
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 実施内容
|
|
17
|
+
|
|
18
|
+
- デフォルトセキュリティヘッダーの確認
|
|
19
|
+
- カスタム CSP の適用確認
|
|
20
|
+
- `extra_no_csp_paths` の動作確認
|
|
21
|
+
- ハードコードされたヘッダーの制限を確認
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## テスト結果
|
|
26
|
+
|
|
27
|
+
### test_app.py(正常系・機能確認)
|
|
28
|
+
| テスト | 結果 |
|
|
29
|
+
|---|---|
|
|
30
|
+
| test_default_security_headers_present | PASS |
|
|
31
|
+
| test_default_csp_is_default_src_self | PASS |
|
|
32
|
+
| test_custom_csp_is_applied | PASS |
|
|
33
|
+
| test_docs_path_has_no_csp | PASS |
|
|
34
|
+
| test_extra_no_csp_paths_skip_csp | PASS |
|
|
35
|
+
|
|
36
|
+
### test_friction.py(摩擦点確認)
|
|
37
|
+
| テスト | 結果 | 摩擦 |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| test_permissions_policy_is_hardcoded | PASS | あり |
|
|
40
|
+
| test_no_hsts_header | PASS | あり |
|
|
41
|
+
| test_x_frame_options_is_hardcoded_to_deny | PASS | あり(軽微) |
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 発見した摩擦点
|
|
46
|
+
|
|
47
|
+
### FT32-F1: Permissions-Policy がハードコードされている
|
|
48
|
+
|
|
49
|
+
**概要**: `geolocation=(), microphone=()` が固定値でカスタマイズできない。
|
|
50
|
+
位置情報 API を使うアプリでは `geolocation=(self)` に変更できない。
|
|
51
|
+
|
|
52
|
+
**期待する解決策**: `permissions_policy: str | None = None` パラメータを追加。
|
|
53
|
+
|
|
54
|
+
### FT32-F2: HSTS ヘッダーがない
|
|
55
|
+
|
|
56
|
+
**概要**: production 環境では `Strict-Transport-Security` を設定すべきだが、
|
|
57
|
+
`SecurityHeadersMiddleware` は HSTS を付与しない。
|
|
58
|
+
開発環境では不要なため、オプションとして設定できるべき。
|
|
59
|
+
|
|
60
|
+
**期待する解決策**: `hsts: str | None = None` パラメータを追加。
|
|
61
|
+
|
|
62
|
+
### FT32-F3: X-Frame-Options が DENY にハードコード(軽微)
|
|
63
|
+
|
|
64
|
+
**概要**: iframe 内表示が必要なケースで SAMEORIGIN に変更できない。
|
|
65
|
+
ただし DENY がセキュリティ上より安全なデフォルトであり、一般ユース向け。
|
|
66
|
+
|
|
67
|
+
**判断**: ニッチなケースのため低優先度。Issue 化はするが即座に修正しない。
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## まとめ
|
|
72
|
+
|
|
73
|
+
CSP カスタマイズ機能は問題なく動作する。
|
|
74
|
+
|
|
75
|
+
摩擦点:
|
|
76
|
+
1. **Permissions-Policy ハードコード** → Issue 化・修正対象
|
|
77
|
+
2. **HSTS ヘッダーなし** → Issue 化・修正対象
|
|
78
|
+
3. **X-Frame-Options ハードコード** → 低優先度 Issue
|
|
@@ -10,12 +10,12 @@ from starlette.requests import Request
|
|
|
10
10
|
from starlette.responses import Response
|
|
11
11
|
|
|
12
12
|
_DEFAULT_CSP = "default-src 'self'"
|
|
13
|
+
_DEFAULT_PERMISSIONS_POLICY = "geolocation=(), microphone=()"
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
_STATIC_HEADERS: dict[str, str] = {
|
|
15
16
|
"X-Content-Type-Options": "nosniff",
|
|
16
17
|
"X-Frame-Options": "DENY",
|
|
17
18
|
"Referrer-Policy": "strict-origin-when-cross-origin",
|
|
18
|
-
"Permissions-Policy": "geolocation=(), microphone=()",
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
_DEFAULT_NO_CSP_PATHS: frozenset[str] = frozenset({"/docs", "/redoc", "/openapi.json"})
|
|
@@ -30,6 +30,11 @@ class SecurityHeadersMiddleware(BaseHTTPMiddleware):
|
|
|
30
30
|
Args:
|
|
31
31
|
csp: Custom Content-Security-Policy header value.
|
|
32
32
|
Defaults to ``"default-src 'self'"`` when not specified.
|
|
33
|
+
permissions_policy: Custom Permissions-Policy header value.
|
|
34
|
+
Defaults to ``"geolocation=(), microphone=()"`` when not specified.
|
|
35
|
+
hsts: Strict-Transport-Security header value (e.g.
|
|
36
|
+
``"max-age=31536000; includeSubDomains"``). Not set by default.
|
|
37
|
+
Enable only in production environments serving HTTPS.
|
|
33
38
|
extra_no_csp_paths: Additional paths to skip the CSP header for.
|
|
34
39
|
Useful when FastAPI is configured with custom ``docs_url`` / ``redoc_url``.
|
|
35
40
|
The built-in paths ``/docs``, ``/redoc``, and ``/openapi.json`` are always included.
|
|
@@ -39,16 +44,25 @@ class SecurityHeadersMiddleware(BaseHTTPMiddleware):
|
|
|
39
44
|
self,
|
|
40
45
|
app: object,
|
|
41
46
|
csp: str | None = None,
|
|
47
|
+
permissions_policy: str | None = None,
|
|
48
|
+
hsts: str | None = None,
|
|
42
49
|
extra_no_csp_paths: list[str] | None = None,
|
|
43
50
|
) -> None:
|
|
44
51
|
super().__init__(app) # type: ignore[arg-type]
|
|
45
52
|
self._csp = csp if csp is not None else _DEFAULT_CSP
|
|
53
|
+
self._permissions_policy = (
|
|
54
|
+
permissions_policy if permissions_policy is not None else _DEFAULT_PERMISSIONS_POLICY
|
|
55
|
+
)
|
|
56
|
+
self._hsts = hsts
|
|
46
57
|
self._no_csp_paths = _DEFAULT_NO_CSP_PATHS | frozenset(extra_no_csp_paths or [])
|
|
47
58
|
|
|
48
59
|
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
|
|
49
60
|
response = await call_next(request)
|
|
50
|
-
for header, value in
|
|
61
|
+
for header, value in _STATIC_HEADERS.items():
|
|
51
62
|
response.headers[header] = value
|
|
63
|
+
response.headers["Permissions-Policy"] = self._permissions_policy
|
|
64
|
+
if self._hsts:
|
|
65
|
+
response.headers["Strict-Transport-Security"] = self._hsts
|
|
52
66
|
if request.url.path not in self._no_csp_paths:
|
|
53
67
|
response.headers["Content-Security-Policy"] = self._csp
|
|
54
68
|
return response
|
|
@@ -56,3 +56,11 @@ def test_composite_with_empty_checks_returns_ok() -> None:
|
|
|
56
56
|
result = composite.check()
|
|
57
57
|
assert result.is_healthy is True
|
|
58
58
|
assert result.checks == {}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_health_status_http_status_code_ok() -> None:
|
|
62
|
+
assert HealthStatus(status="ok").http_status_code == 200
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_health_status_http_status_code_error() -> None:
|
|
66
|
+
assert HealthStatus(status="error").http_status_code == 503
|
|
@@ -80,6 +80,44 @@ def test_extra_no_csp_paths() -> None:
|
|
|
80
80
|
assert "Content-Security-Policy" in client.get("/ping").headers
|
|
81
81
|
|
|
82
82
|
|
|
83
|
+
def test_custom_permissions_policy() -> None:
|
|
84
|
+
app = FastAPI()
|
|
85
|
+
app.add_middleware(
|
|
86
|
+
SecurityHeadersMiddleware,
|
|
87
|
+
permissions_policy="geolocation=(self), microphone=()",
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
@app.get("/ping")
|
|
91
|
+
async def ping() -> JSONResponse:
|
|
92
|
+
return JSONResponse({"ok": True})
|
|
93
|
+
|
|
94
|
+
client = TestClient(app)
|
|
95
|
+
r = client.get("/ping")
|
|
96
|
+
assert r.headers["Permissions-Policy"] == "geolocation=(self), microphone=()"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_hsts_header_when_specified() -> None:
|
|
100
|
+
app = FastAPI()
|
|
101
|
+
app.add_middleware(
|
|
102
|
+
SecurityHeadersMiddleware,
|
|
103
|
+
hsts="max-age=31536000; includeSubDomains",
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
@app.get("/ping")
|
|
107
|
+
async def ping() -> JSONResponse:
|
|
108
|
+
return JSONResponse({"ok": True})
|
|
109
|
+
|
|
110
|
+
client = TestClient(app)
|
|
111
|
+
r = client.get("/ping")
|
|
112
|
+
assert r.headers["Strict-Transport-Security"] == "max-age=31536000; includeSubDomains"
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def test_no_hsts_by_default() -> None:
|
|
116
|
+
client = TestClient(_make_app())
|
|
117
|
+
r = client.get("/ping")
|
|
118
|
+
assert "Strict-Transport-Security" not in r.headers
|
|
119
|
+
|
|
120
|
+
|
|
83
121
|
def test_default_no_csp_paths_still_work_with_extra_paths() -> None:
|
|
84
122
|
app = FastAPI()
|
|
85
123
|
app.add_middleware(SecurityHeadersMiddleware, extra_no_csp_paths=["/custom"])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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.2 → nene2_python-1.8.3}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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.2 → nene2_python-1.8.3}/tests/nene2/middleware/test_simple_domain_handler.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
|