nene2-python 1.8.23__tar.gz → 1.8.24__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.23 → nene2_python-1.8.24}/CHANGELOG.md +11 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/PKG-INFO +1 -1
- nene2_python-1.8.24/docs/field-trials/2026-05-field-trial-78.md +164 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/pyproject.toml +1 -1
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/middleware/setup.py +6 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/middleware/throttle.py +14 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/uv.lock +1 -1
- {nene2_python-1.8.23 → nene2_python-1.8.24}/.env.example +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/.github/workflows/ci.yml +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/.github/workflows/docs.yml +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/.github/workflows/publish.yml +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/.gitignore +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/.vitepress/config.mts +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/.vitepress/theme/custom.css +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/.vitepress/theme/index.ts +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/AGENTS.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/CLAUDE.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/Dockerfile +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/LICENSE +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/README.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/alembic/README +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/alembic/env.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/alembic/script.py.mako +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/alembic.ini +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/compose.yaml +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/adr/0001-toolchain.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/adr/0002-clean-architecture.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/adr/0003-security-first.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/adr/0004-ai-first-design.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/adr/0005-logging.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/adr/0006-rate-limiting.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/adr/0009-mcp-design.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/adr/0010-async-use-case.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/de/index.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/de/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/explanation/architecture.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-1.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-10.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-11.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-12.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-13.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-14.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-15.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-16.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-17.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-18.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-19.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-2.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-20.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-21.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-22.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-23.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-24.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-25.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-26.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-27.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-28.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-29.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-3.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-30.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-31.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-32.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-33.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-34.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-35.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-36.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-37.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-38.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-39.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-4.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-40.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-41.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-42.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-43.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-44.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-45.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-46.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-47.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-48.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-49.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-5.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-50.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-51.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-52.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-53.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-54.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-55.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-56.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-57.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-58.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-59.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-6.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-60.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-61.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-62.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-63.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-64.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-65.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-66.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-67.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-68.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-69.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-7.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-70.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-71.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-72.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-73.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-74.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-75.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-76.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-77.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-8.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/field-trials/2026-05-field-trial-9.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/fr/index.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/fr/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/how-to/async-use-case.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/how-to/middleware-stack.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/how-to/new-project.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/how-to/problem-details.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/how-to/run-tests.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/how-to/validation.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/index.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/ja/explanation/architecture.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/ja/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/ja/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/ja/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/ja/how-to/new-project.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/ja/how-to/run-tests.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/ja/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/ja/index.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/ja/reference/api.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/ja/reference/configuration.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/ja/reference/framework-modules.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/ja/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/ja/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/pt-br/index.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/pt-br/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/reference/api.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/reference/configuration.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/reference/framework-modules.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/roadmap.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/todo/current.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/zh/index.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/docs/zh/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/package-lock.json +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/package.json +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/example/__init__.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/example/__main__.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/example/app.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/example/comment/__init__.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/example/comment/entity.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/example/comment/exceptions.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/example/comment/handler.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/example/comment/repository.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/example/comment/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/example/comment/use_case.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/example/mcp.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/example/note/__init__.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/example/note/async_use_case.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/example/note/entity.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/example/note/exceptions.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/example/note/handler.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/example/note/repository.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/example/note/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/example/note/use_case.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/example/schema.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/example/tag/__init__.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/example/tag/entity.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/example/tag/exceptions.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/example/tag/handler.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/example/tag/repository.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/example/tag/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/example/tag/use_case.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/__init__.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/auth/api_key.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/auth/bearer_token.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/auth/exceptions.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/auth/interfaces.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/auth/local_verifier.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/config/settings.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/database/exceptions.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/database/health.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/database/interfaces.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/database/sqlalchemy_executor.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/database/utils.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/http/health.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/http/pagination.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/http/problem_details.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/log/setup.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/mcp/http_client.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/mcp/server.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/middleware/__init__.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/middleware/domain_exception.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/middleware/error_handler.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/middleware/request_id.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/middleware/request_logging.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/middleware/request_size_limit.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/middleware/security_headers.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/py.typed +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/use_case/protocols.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/nene2/validation/exceptions.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/scripts/__init__.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/src/scripts/export_openapi.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/__init__.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/example/__init__.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/example/comment/__init__.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/example/comment/test_comment_http.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/example/comment/test_comment_repository.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/example/comment/test_comment_use_case.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/example/conftest.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/example/note/__init__.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/example/note/test_async_note_use_case.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/example/note/test_list_notes.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/example/note/test_note_repository.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/example/tag/__init__.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/example/tag/test_tag_repository.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/example/tag/test_tags.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/example/test_cors.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/example/test_mcp.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/nene2/__init__.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/nene2/auth/test_api_key.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/nene2/auth/test_bearer_token.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/nene2/auth/test_token_issuer.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/nene2/config/test_settings.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/nene2/database/test_transaction.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/nene2/database/test_utils.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/nene2/http/test_health.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/nene2/http/test_pagination.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/nene2/http/test_problem_details.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/nene2/log/test_setup.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/nene2/mcp/test_http_client.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/nene2/middleware/__init__.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/nene2/middleware/test_error_handler.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/nene2/middleware/test_request_id.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/nene2/middleware/test_request_logging.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/nene2/middleware/test_request_size_limit.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/nene2/middleware/test_security_headers.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/nene2/middleware/test_setup_middlewares.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/nene2/middleware/test_throttle.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/nene2/use_case/test_protocols.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/nene2/use_case/test_run_in_threadpool.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/nene2/validation/test_exceptions.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/scripts/__init__.py +0 -0
- {nene2_python-1.8.23 → nene2_python-1.8.24}/tests/scripts/test_export_openapi.py +0 -0
|
@@ -5,6 +5,17 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
## [1.8.23] — 2026-05-20
|
|
9
|
+
|
|
10
|
+
FT77 フィールドトライアル — BearerToken + ApiKey 混在認証と include_paths 追加。
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `BearerTokenMiddleware` / `ApiKeyAuthMiddleware` に `include_paths` パラメーターを追加 (#331) (FT77)
|
|
14
|
+
— プレフィックスマッチで「守りたいパス」を直接指定でき、混在認証の `exclude_paths` 二重管理を解消
|
|
15
|
+
- Field trial report: `docs/field-trials/2026-05-field-trial-77.md` (FT77)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
8
19
|
## [1.8.22] — 2026-05-20
|
|
9
20
|
|
|
10
21
|
FT76 フィールドトライアル — async def + sync DB ブロッキング問題と run_in_threadpool 追加。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nene2-python
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.24
|
|
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,164 @@
|
|
|
1
|
+
# FT78: ThrottleMiddleware 境界動作
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-20
|
|
4
|
+
**テーマ**: レート制限のウィンドウリセット・バースト動作・path_limits の挙動検証
|
|
5
|
+
**バージョン**: v1.8.23
|
|
6
|
+
**FTディレクトリ**: `/home/xi/docker/nene2-python-FT/ft78-throttle-boundary/`
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 概要
|
|
11
|
+
|
|
12
|
+
`ThrottleMiddleware` の境界動作を詳細に検証した。
|
|
13
|
+
基本機能(429 返却、Retry-After ヘッダー、path_limits)は期待通り動作した。
|
|
14
|
+
一方で、Fixed Window アルゴリズムの構造的な制限と、
|
|
15
|
+
マルチプロセス環境での in-memory 共有不可という制約が明確になった。
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 動作確認
|
|
20
|
+
|
|
21
|
+
### 429 レスポンス形式
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"type": "https://httpstatuses.com/too-many-requests",
|
|
26
|
+
"title": "Too Many Requests",
|
|
27
|
+
"status": 429,
|
|
28
|
+
"detail": "Rate limit exceeded. Retry after 58 seconds."
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Retry-After ヘッダーあり ✅
|
|
33
|
+
|
|
34
|
+
### レート制限ヘッダー(全レスポンスに付与)
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
X-RateLimit-Limit: 3
|
|
38
|
+
X-RateLimit-Remaining: 2
|
|
39
|
+
X-RateLimit-Reset: 1747742980
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
全レスポンス(200 も 429 も)にヘッダーが付く ✅
|
|
43
|
+
|
|
44
|
+
### path_limits: グローバルカウンターと独立 ✅
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
app.add_middleware(ThrottleMiddleware, limit=100, path_limits={"/api/search": 5})
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`/api/search` の制限 (5) は `/api/data` のグローバル制限 (100) と完全に独立。
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 発見した問題
|
|
55
|
+
|
|
56
|
+
### 問題1: Fixed Window バースト問題(設計上の制限)
|
|
57
|
+
|
|
58
|
+
固定ウィンドウ方式では、ウィンドウ境界で最大 `2 × limit` のリクエストが短時間に通過できる。
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
window=60s, limit=3 の場合:
|
|
62
|
+
t=59s: 3 req 通過(ウィンドウ1の末尾)
|
|
63
|
+
t=61s: 3 req 通過(ウィンドウ2の先頭)
|
|
64
|
+
→ 2秒間に 6 req が通ってしまう
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Flask-Limiter デフォルトの Sliding Window では防げるが、
|
|
68
|
+
nene2 は計算コストの低い Fixed Window を採用している。
|
|
69
|
+
|
|
70
|
+
ドキュメントにこの制限を明記すべき。
|
|
71
|
+
|
|
72
|
+
### 問題2: in-memory カウンターはマルチプロセス非対応
|
|
73
|
+
|
|
74
|
+
`_counts` は Python の `dict` で保持されており、
|
|
75
|
+
複数の uvicorn ワーカー(`gunicorn -w 4`)や Docker Pod では共有されない。
|
|
76
|
+
|
|
77
|
+
実効的な制限は `limit × worker_count` になる。
|
|
78
|
+
本番で水平スケールさせると全くレート制限が機能しない。
|
|
79
|
+
|
|
80
|
+
**ドキュメントに警告はある**(コードの docstring)が、README や使用例には書かれていない。
|
|
81
|
+
|
|
82
|
+
### 問題3: カウント状態の観察手段がない
|
|
83
|
+
|
|
84
|
+
現在のカウント状態(「このIPが今 N/M 消費」)を外部から取得する手段がない。
|
|
85
|
+
デバッグ時に困る。
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
# これが欲しいが存在しない
|
|
89
|
+
info = middleware.get_rate_info(ip="192.168.1.1")
|
|
90
|
+
print(info.current_count, info.remaining)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 問題4: exclude_paths はヘッダーも返さない
|
|
94
|
+
|
|
95
|
+
除外パスにはレート制限ヘッダーが一切付かない。
|
|
96
|
+
クライアントが「このパスは制限外か?」を知る方法がない。
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## テスト結果(全14件パス)
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
test_requests_within_limit_are_allowed PASSED
|
|
104
|
+
test_exceeding_limit_returns_429 PASSED
|
|
105
|
+
test_429_response_is_problem_details PASSED
|
|
106
|
+
test_rate_limit_headers_present_on_200 PASSED
|
|
107
|
+
test_rate_limit_remaining_decrements PASSED
|
|
108
|
+
test_retry_after_header_present_on_429 PASSED
|
|
109
|
+
test_retry_after_reasonable_value PASSED
|
|
110
|
+
test_path_limits_independent_from_global PASSED
|
|
111
|
+
test_path_limits_headers_show_path_limit PASSED
|
|
112
|
+
test_exclude_paths_bypass_throttle PASSED
|
|
113
|
+
test_window_resets_after_elapsed PASSED
|
|
114
|
+
test_friction_no_global_ip_tracking_visible_to_user PASSED
|
|
115
|
+
test_friction_in_memory_state_not_shared_across_workers PASSED
|
|
116
|
+
test_friction_fixed_window_burst_at_boundary PASSED
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 摩擦ポイント一覧
|
|
122
|
+
|
|
123
|
+
| ID | 内容 | 深刻度 |
|
|
124
|
+
|---|---|---|
|
|
125
|
+
| F78-1 | Fixed Window バースト問題がドキュメントに明記されていない | 中 |
|
|
126
|
+
| F78-2 | マルチプロセス非対応(in-memory)がドキュメントに明記されていない | 高 |
|
|
127
|
+
| F78-3 | カウント状態の観察 API がない | 低 |
|
|
128
|
+
| F78-4 | exclude_paths のパスにレート制限ヘッダーが付かない | 低 |
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## 使用感(主観評価)
|
|
133
|
+
|
|
134
|
+
### 直感性 ★★★★☆
|
|
135
|
+
|
|
136
|
+
`setup_middlewares(app, throttle_limit=60, throttle_window=60)` で一発設定できる点は非常に快適。
|
|
137
|
+
`path_limits` / `exclude_paths` のパラメーター名も直感的。
|
|
138
|
+
`Retry-After` が自動で付くのはエレガント。
|
|
139
|
+
|
|
140
|
+
### 実害の深刻さ ★★★★☆
|
|
141
|
+
|
|
142
|
+
マルチプロセス非対応は本番で深刻。
|
|
143
|
+
「レート制限を設定したのに効いていない」というデバッグは非常に時間がかかる。
|
|
144
|
+
ただし docstring に警告が書かれており、見れば分かる(見逃しやすいが)。
|
|
145
|
+
|
|
146
|
+
### 修正のしやすさ ★★★★★
|
|
147
|
+
|
|
148
|
+
ドキュメント追記のみで対応できる問題が多い。
|
|
149
|
+
実装変更(Redis 対応)は将来的な機能拡張として Issues に記録する程度でよい。
|
|
150
|
+
|
|
151
|
+
### 総合コメント
|
|
152
|
+
|
|
153
|
+
基本機能は非常によくできている。
|
|
154
|
+
問題は「制限の文書化」が不足している点。
|
|
155
|
+
Fixed Window の特性とマルチプロセス制約を README や使用例に追記するだけで
|
|
156
|
+
UX が大幅に改善する。実装は変えなくてよい。
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## 推奨アクション
|
|
161
|
+
|
|
162
|
+
1. **Issue**: ThrottleMiddleware のドキュメントに Fixed Window バースト特性を追記
|
|
163
|
+
2. **Issue**: ThrottleMiddleware のドキュメントにマルチプロセス非対応の警告を目立つ位置に追記
|
|
164
|
+
3. **将来**: Redis カウンターバックエンドの検討(外部依存なので慎重に)
|
|
@@ -90,6 +90,12 @@ def setup_middlewares(
|
|
|
90
90
|
throttle_window: Rate-limit window in seconds (default: 60).
|
|
91
91
|
throttle_path_limits: Per-path overrides for throttle limits.
|
|
92
92
|
throttle_exclude_paths: Paths excluded from throttling.
|
|
93
|
+
|
|
94
|
+
.. warning::
|
|
95
|
+
``ThrottleMiddleware`` uses an in-memory counter that is **not
|
|
96
|
+
shared across workers or pods**. Multi-process deployments will
|
|
97
|
+
see an effective limit of ``throttle_limit × worker_count``.
|
|
98
|
+
See :class:`ThrottleMiddleware` for details.
|
|
93
99
|
max_request_bytes: Maximum request body size in bytes (default: 1 MiB).
|
|
94
100
|
request_size_path_limits: Per-path size limits.
|
|
95
101
|
request_size_exclude_paths: Paths excluded from size limiting.
|
|
@@ -55,6 +55,20 @@ class ThrottleMiddleware(BaseHTTPMiddleware):
|
|
|
55
55
|
Path-limited endpoints are tracked independently from the global counter
|
|
56
56
|
(the key includes the path, so ``/api/expensive`` quota is separate from
|
|
57
57
|
the default quota for other paths).
|
|
58
|
+
|
|
59
|
+
.. warning:: **Single-process only.**
|
|
60
|
+
Counters are stored in an in-memory dict. When running multiple
|
|
61
|
+
uvicorn workers (e.g. ``gunicorn -w 4``) or multiple containers,
|
|
62
|
+
each process maintains its own counter, so the effective rate limit
|
|
63
|
+
is ``limit × worker_count``. For multi-process deployments, enforce
|
|
64
|
+
rate limits at the reverse proxy (nginx, Caddy) or use a shared
|
|
65
|
+
store (Redis).
|
|
66
|
+
|
|
67
|
+
.. note:: **Fixed-window burst at boundaries.**
|
|
68
|
+
Fixed-window counting can pass up to ``2 × limit`` requests in a
|
|
69
|
+
short burst when requests arrive just before and just after a window
|
|
70
|
+
boundary. If you need protection against burst traffic, consider
|
|
71
|
+
sliding-window rate limiting at the proxy layer.
|
|
58
72
|
"""
|
|
59
73
|
|
|
60
74
|
def __init__(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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.23 → nene2_python-1.8.24}/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
|