nene2-python 1.8.6__tar.gz → 1.8.7__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.6 → nene2_python-1.8.7}/CHANGELOG.md +10 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/PKG-INFO +1 -1
- nene2_python-1.8.7/docs/field-trials/2026-05-field-trial-43.md +112 -0
- nene2_python-1.8.7/docs/field-trials/2026-05-field-trial-44.md +110 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/pyproject.toml +1 -1
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/middleware/__init__.py +2 -1
- {nene2_python-1.8.6 → nene2_python-1.8.7}/uv.lock +1 -1
- {nene2_python-1.8.6 → nene2_python-1.8.7}/.env.example +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/.github/workflows/ci.yml +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/.github/workflows/docs.yml +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/.github/workflows/publish.yml +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/.gitignore +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/.vitepress/config.mts +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/.vitepress/theme/custom.css +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/.vitepress/theme/index.ts +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/AGENTS.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/CLAUDE.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/Dockerfile +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/LICENSE +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/README.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/alembic/README +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/alembic/env.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/alembic/script.py.mako +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/alembic.ini +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/compose.yaml +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/adr/0001-toolchain.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/adr/0002-clean-architecture.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/adr/0003-security-first.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/adr/0004-ai-first-design.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/adr/0005-logging.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/adr/0006-rate-limiting.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/adr/0009-mcp-design.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/adr/0010-async-use-case.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/de/index.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/de/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/explanation/architecture.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-1.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-10.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-11.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-12.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-13.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-14.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-15.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-16.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-17.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-18.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-19.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-2.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-20.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-21.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-22.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-23.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-24.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-25.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-26.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-27.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-28.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-29.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-3.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-30.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-31.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-32.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-33.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-34.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-35.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-36.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-37.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-38.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-39.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-4.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-40.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-41.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-42.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-5.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-6.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-7.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-8.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/field-trials/2026-05-field-trial-9.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/fr/index.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/fr/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/how-to/async-use-case.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/how-to/new-project.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/how-to/problem-details.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/how-to/run-tests.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/how-to/validation.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/index.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/ja/explanation/architecture.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/ja/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/ja/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/ja/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/ja/how-to/new-project.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/ja/how-to/run-tests.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/ja/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/ja/index.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/ja/reference/api.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/ja/reference/configuration.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/ja/reference/framework-modules.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/ja/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/ja/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/pt-br/index.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/pt-br/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/reference/api.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/reference/configuration.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/reference/framework-modules.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/roadmap.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/todo/current.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/zh/index.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/docs/zh/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/package-lock.json +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/package.json +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/example/__init__.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/example/__main__.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/example/app.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/example/comment/__init__.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/example/comment/entity.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/example/comment/exceptions.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/example/comment/handler.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/example/comment/repository.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/example/comment/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/example/comment/use_case.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/example/mcp.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/example/note/__init__.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/example/note/async_use_case.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/example/note/entity.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/example/note/exceptions.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/example/note/handler.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/example/note/repository.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/example/note/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/example/note/use_case.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/example/schema.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/example/tag/__init__.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/example/tag/entity.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/example/tag/exceptions.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/example/tag/handler.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/example/tag/repository.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/example/tag/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/example/tag/use_case.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/__init__.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/auth/api_key.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/auth/bearer_token.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/auth/exceptions.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/auth/interfaces.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/auth/local_verifier.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/config/settings.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/database/exceptions.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/database/health.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/database/interfaces.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/database/sqlalchemy_executor.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/database/utils.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/http/health.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/http/pagination.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/http/problem_details.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/log/setup.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/mcp/http_client.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/mcp/server.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/middleware/domain_exception.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/middleware/error_handler.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/middleware/request_id.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/middleware/request_logging.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/middleware/request_size_limit.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/middleware/security_headers.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/middleware/throttle.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/py.typed +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/use_case/protocols.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/nene2/validation/exceptions.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/scripts/__init__.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/src/scripts/export_openapi.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/__init__.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/example/__init__.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/example/comment/__init__.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/example/comment/test_comment_http.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/example/comment/test_comment_repository.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/example/comment/test_comment_use_case.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/example/conftest.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/example/note/__init__.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/example/note/test_async_note_use_case.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/example/note/test_list_notes.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/example/note/test_note_repository.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/example/tag/__init__.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/example/tag/test_tag_repository.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/example/tag/test_tags.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/example/test_cors.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/example/test_mcp.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/nene2/__init__.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/nene2/auth/test_api_key.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/nene2/auth/test_bearer_token.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/nene2/auth/test_token_issuer.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/nene2/config/test_settings.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/nene2/database/test_transaction.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/nene2/database/test_utils.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/nene2/http/test_health.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/nene2/http/test_pagination.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/nene2/http/test_problem_details.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/nene2/log/test_setup.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/nene2/mcp/test_http_client.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/nene2/middleware/__init__.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/nene2/middleware/test_error_handler.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/nene2/middleware/test_request_id.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/nene2/middleware/test_request_logging.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/nene2/middleware/test_request_size_limit.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/nene2/middleware/test_security_headers.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/nene2/middleware/test_throttle.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/nene2/use_case/test_protocols.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/nene2/validation/test_exceptions.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/tests/scripts/__init__.py +0 -0
- {nene2_python-1.8.6 → nene2_python-1.8.7}/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.7] — 2026-05-20
|
|
9
|
+
|
|
10
|
+
FT43〜FT44 フィールドトライアル — ThrottleMiddleware path_limits 確認・PaginationQueryParser バリデーション改善。
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `nene2.middleware.request_validation_error_handler` を公開エクスポートに追加 — FastAPI の `RequestValidationError` を Problem Details 形式に変換するハンドラーを `from nene2.middleware import request_validation_error_handler` でアクセス可能に (FT44)
|
|
14
|
+
- Field trial reports: `docs/field-trials/2026-05-field-trial-43.md`、`docs/field-trials/2026-05-field-trial-44.md`
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
8
18
|
## [1.8.6] — 2026-05-20
|
|
9
19
|
|
|
10
20
|
FT41〜FT42 フィールドトライアル — structlog テスト統合ドキュメント・configure_problem_details リセット関数。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nene2-python
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.7
|
|
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,112 @@
|
|
|
1
|
+
# Field Trial 43: ThrottleMiddleware path_limits 実運用検証
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-20
|
|
4
|
+
**バージョン**: v1.8.6 時点
|
|
5
|
+
**テーマ**: `ThrottleMiddleware` の `path_limits` パラメータを使ってエンドポイントごとにレート制限を設定するパターンの実運用確認
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 概要
|
|
10
|
+
|
|
11
|
+
グローバルレート制限(`limit=100`)と、特定パスへの厳しい制限(`path_limits={"/api/search": 10, "/api/upload": 5}`)を組み合わせた API を実装し、動作を確認した。
|
|
12
|
+
レートカウンターがパスごとに独立して管理されること、`X-RateLimit-*` ヘッダーが適切に付与されることを検証した。
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 実装内容
|
|
17
|
+
|
|
18
|
+
`/home/xi/docker/nene2-python-FT/ft43-throttle-path-limits/` に以下を作成:
|
|
19
|
+
|
|
20
|
+
- **`app.py`** — グローバル + パスごとのレート制限設定、`/health` 除外パスの構成
|
|
21
|
+
- **`test_app.py`** — 正常系・ヘッダー確認・path_limits 独立性・429 動作 (10 件)
|
|
22
|
+
- **`test_friction.py`** — 摩擦点の確認テスト (4 件)
|
|
23
|
+
|
|
24
|
+
**テスト結果**: 14 件全通過 ✅
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 摩擦点
|
|
29
|
+
|
|
30
|
+
### FP43-1: path_limits のカウンターはグローバルカウンターと完全に独立している
|
|
31
|
+
|
|
32
|
+
**分類**: 摩擦なし(良い設計の確認)
|
|
33
|
+
|
|
34
|
+
`path_limits` に指定したパスは `{client}:{path}` をキーとして使い、
|
|
35
|
+
グローバルカウンター (`{client}`) とは別々に管理される。
|
|
36
|
+
`/api/search` を使い切っても `/api/items` のカウンターには影響しない。
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
# /api/search の制限 (3 req) を使い切っても
|
|
40
|
+
for _ in range(3):
|
|
41
|
+
client.get("/api/search")
|
|
42
|
+
# /api/items の制限は消費されていない
|
|
43
|
+
r = client.get("/api/items")
|
|
44
|
+
assert r.status_code == 200 # OK
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**判断**: FT28 で実装した設計通り。パス別独立カウンターは想定通りに動作する。
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
### FP43-2: X-RateLimit-Limit ヘッダーがパスごとの制限値を反映する
|
|
52
|
+
|
|
53
|
+
**分類**: 摩擦なし(良い設計の確認)
|
|
54
|
+
|
|
55
|
+
`/api/search` へのリクエストには `X-RateLimit-Limit: 3` が付与され、
|
|
56
|
+
`/api/items` へのリクエストには `X-RateLimit-Limit: 10` が付与される。
|
|
57
|
+
クライアントはヘッダーを見て自分のリミットがいくつかを判断できる。
|
|
58
|
+
|
|
59
|
+
**判断**: FT20 で実装したヘッダー付与が path_limits と正しく連携している。
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
### FP43-3: X-Forwarded-For がクライアントキーとして使われるためバイパスに注意
|
|
64
|
+
|
|
65
|
+
**分類**: 設計上の制約(ドキュメントに記載済み・運用上の注意点)
|
|
66
|
+
|
|
67
|
+
`X-Forwarded-For` ヘッダーがある場合、それをクライアント IP として使う設計のため、
|
|
68
|
+
異なる `X-Forwarded-For` を送ることで別のクライアントとして扱われ、レート制限をバイパスできる。
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
# 通常の IP で制限を使い切ったあと
|
|
72
|
+
for _ in range(2):
|
|
73
|
+
client.get("/api/items")
|
|
74
|
+
r = client.get("/api/items")
|
|
75
|
+
assert r.status_code == 429
|
|
76
|
+
|
|
77
|
+
# 別の IP を騙って送ると 200 になる
|
|
78
|
+
r = client.get("/api/items", headers={"X-Forwarded-For": "10.0.0.1"})
|
|
79
|
+
assert r.status_code == 200 # バイパスできてしまう
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**判断**: ドキュメントの Warning セクションに記載されている既知の制限。
|
|
83
|
+
信頼できるリバースプロキシを前段に置くことで軽減できる。
|
|
84
|
+
テスト環境での動作確認として有用。
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
### FP43-4: path_limits の対象外パスはグローバル制限のみが適用される
|
|
89
|
+
|
|
90
|
+
**分類**: 摩擦なし(設計の確認)
|
|
91
|
+
|
|
92
|
+
`path_limits` に指定していないパス (`/api/items` など) はグローバルの `limit` が適用される。
|
|
93
|
+
複数のエンドポイントに異なる制限を設けつつ、デフォルトのグローバル制限を基本として使う設計が自然に実現できる。
|
|
94
|
+
|
|
95
|
+
**判断**: FT28 の設計通り。`path_limits` に指定のないパスは `{client}` をキーに使い、グローバルカウンターで管理される。
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## フレームワーク変更
|
|
100
|
+
|
|
101
|
+
なし(全て設計通りの挙動)
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## 関連
|
|
106
|
+
|
|
107
|
+
- `nene2.middleware.ThrottleMiddleware` (FT20, v1.8.0)
|
|
108
|
+
- `ThrottleMiddleware.path_limits` (FT28, v1.8.1)
|
|
109
|
+
- `ThrottleMiddleware` ウィンドウクリーンアップ (FT27, v1.8.1)
|
|
110
|
+
- FT20 (ThrottleMiddleware ヘッダー実装, v1.8.0)
|
|
111
|
+
- FT27 (ThrottleMiddleware クリーンアップ修正, v1.8.1)
|
|
112
|
+
- FT28 (ThrottleMiddleware path_limits 実装, v1.8.1)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Field Trial 44: PaginationQueryParser + PaginationResponse 実運用検証
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-20
|
|
4
|
+
**バージョン**: v1.8.6 時点
|
|
5
|
+
**テーマ**: `PaginationQueryParser` を `Annotated[..., Depends()]` 構文で使い `PaginationResponse.to_dict()` でスロット付きデータクラスをシリアライズするパターンの実運用確認
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 概要
|
|
10
|
+
|
|
11
|
+
`PaginationQueryParser` を FastAPI の `Depends()` として注入し、
|
|
12
|
+
`PaginationResponse` + `to_dict()` でスロット付き `dataclass(frozen=True, slots=True)` を
|
|
13
|
+
シリアライズするパターンを実装した。
|
|
14
|
+
`total` フィールドのあり/なし両パターン、および生の dict アイテムを含むケースも確認した。
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 実装内容
|
|
19
|
+
|
|
20
|
+
`/home/xi/docker/nene2-python-FT/ft44-pagination/` に以下を作成:
|
|
21
|
+
|
|
22
|
+
- **`app.py`** — `PaginationQueryParser` Depends 注入、スロット付きデータクラス `Product`、3 つのエンドポイント(total あり/なし/生 dict)
|
|
23
|
+
- **`test_app.py`** — デフォルト・カスタム・オフセット・total・スロット付きシリアライズ・422 (10 件)
|
|
24
|
+
- **`test_friction.py`** — 摩擦点の確認テスト (4 件)
|
|
25
|
+
|
|
26
|
+
**テスト結果**: 14 件全通過 ✅
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 摩擦点
|
|
31
|
+
|
|
32
|
+
### FP44-1: OpenAPI スキーマにクエリパラメータが正しく文書化される
|
|
33
|
+
|
|
34
|
+
**分類**: 摩擦なし(良い設計の確認)
|
|
35
|
+
|
|
36
|
+
`Annotated[PaginationQueryParser, Depends()]` 構文を使うと、
|
|
37
|
+
`Query(ge=1, le=100, description="Items per page (1–100)")` の情報が
|
|
38
|
+
FastAPI の自動 OpenAPI スキーマ生成に反映される。
|
|
39
|
+
`/openapi.json` の `parameters` に `limit`・`offset` が説明付きで表示される。
|
|
40
|
+
|
|
41
|
+
**判断**: FT10 で実装した `Depends()` 対応の効果が確認できた。
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
### FP44-2: PaginationResponse.to_dict() は元のアイテムを変更しない
|
|
46
|
+
|
|
47
|
+
**分類**: 摩擦なし(設計の確認)
|
|
48
|
+
|
|
49
|
+
`to_dict()` は `dataclasses.asdict()` で新しい dict を生成するため、
|
|
50
|
+
元の `dataclass` インスタンスは変更されない。immutable な `frozen=True` データクラスが
|
|
51
|
+
そのままリポジトリで保持できる。
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
### FP44-3: items が空リストのときも to_dict() が正常動作する
|
|
56
|
+
|
|
57
|
+
**分類**: 摩擦なし(エッジケース確認)
|
|
58
|
+
|
|
59
|
+
`items=[]` のとき `to_dict()` は `{"items": [], "limit": 20, "offset": 100, "total": 50}`
|
|
60
|
+
を返す。ページを超えたオフセットでのリクエストが自然に処理される。
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
### FP44-4: Depends() 使用時のバリデーションエラーが Problem Details 形式にならない
|
|
65
|
+
|
|
66
|
+
**分類**: 摩擦あり(Issues #268 で対応)
|
|
67
|
+
|
|
68
|
+
`PaginationQueryParser` を `Depends()` として使うと、
|
|
69
|
+
`limit=0` や `limit=101` のバリデーションは FastAPI が実行し、
|
|
70
|
+
エラーは FastAPI のデフォルト Pydantic 形式(`{"detail": [...]}`)になる:
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{"detail": [{"type": "greater_than_equal", ...}]}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
一方、nene2 の `ValidationException` は Problem Details 形式:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{"type": "...", "title": "Validation Failed", "status": 422, "errors": [...]}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
この不一致を解消するため、`nene2.middleware.request_validation_error_handler` を
|
|
83
|
+
FastAPI の exception handler として登録することで Problem Details 形式に統一できる:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from fastapi.exceptions import RequestValidationError
|
|
87
|
+
from nene2.middleware import request_validation_error_handler
|
|
88
|
+
|
|
89
|
+
app.add_exception_handler(RequestValidationError, request_validation_error_handler)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**対応**: `request_validation_error_handler` は `error_handler.py` に既存実装済みだったが、
|
|
93
|
+
`nene2.middleware` からエクスポートされていなかった (Issue #268)。
|
|
94
|
+
`nene2.middleware.__init__` に追加することで `from nene2.middleware import request_validation_error_handler` が可能になった。
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## フレームワーク変更
|
|
99
|
+
|
|
100
|
+
- `nene2.middleware.__init__` に `request_validation_error_handler` を追加エクスポート (#268)
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## 関連
|
|
105
|
+
|
|
106
|
+
- `nene2.http.PaginationQueryParser` (FT10, v1.3.0)
|
|
107
|
+
- `nene2.http.PaginationResponse` (FT10, v1.3.0)
|
|
108
|
+
- `nene2.middleware.request_validation_error_handler`
|
|
109
|
+
- FT10 (PaginationQueryParser Depends 対応, v1.3.0)
|
|
110
|
+
- Issue #268 (request_validation_error_handler エクスポート追加)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""NENE2 middleware pipeline."""
|
|
2
2
|
|
|
3
3
|
from .domain_exception import DomainExceptionHandlerProtocol, SimpleDomainHandler
|
|
4
|
-
from .error_handler import ErrorHandlerMiddleware
|
|
4
|
+
from .error_handler import ErrorHandlerMiddleware, request_validation_error_handler
|
|
5
5
|
from .request_id import RequestIdMiddleware, get_request_id, request_id_var
|
|
6
6
|
from .request_logging import RequestLoggingMiddleware
|
|
7
7
|
from .request_size_limit import RequestSizeLimitMiddleware
|
|
@@ -12,6 +12,7 @@ __all__ = [
|
|
|
12
12
|
"DomainExceptionHandlerProtocol",
|
|
13
13
|
"SimpleDomainHandler",
|
|
14
14
|
"ErrorHandlerMiddleware",
|
|
15
|
+
"request_validation_error_handler",
|
|
15
16
|
"RequestIdMiddleware",
|
|
16
17
|
"get_request_id",
|
|
17
18
|
"RequestLoggingMiddleware",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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.6 → nene2_python-1.8.7}/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
|