nene2-python 1.8.10__tar.gz → 1.8.11__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.10 → nene2_python-1.8.11}/CHANGELOG.md +12 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/PKG-INFO +1 -1
- nene2_python-1.8.11/docs/field-trials/2026-05-field-trial-51.md +127 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/pyproject.toml +1 -1
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/http/problem_details.py +4 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/nene2/http/test_problem_details.py +15 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/uv.lock +1 -1
- {nene2_python-1.8.10 → nene2_python-1.8.11}/.env.example +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/.github/workflows/ci.yml +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/.github/workflows/docs.yml +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/.github/workflows/publish.yml +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/.gitignore +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/.vitepress/config.mts +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/.vitepress/theme/custom.css +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/.vitepress/theme/index.ts +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/AGENTS.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/CLAUDE.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/Dockerfile +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/LICENSE +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/README.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/alembic/README +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/alembic/env.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/alembic/script.py.mako +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/alembic.ini +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/compose.yaml +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/adr/0001-toolchain.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/adr/0002-clean-architecture.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/adr/0003-security-first.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/adr/0004-ai-first-design.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/adr/0005-logging.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/adr/0006-rate-limiting.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/adr/0009-mcp-design.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/adr/0010-async-use-case.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/de/index.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/de/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/explanation/architecture.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-1.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-10.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-11.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-12.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-13.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-14.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-15.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-16.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-17.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-18.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-19.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-2.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-20.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-21.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-22.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-23.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-24.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-25.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-26.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-27.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-28.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-29.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-3.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-30.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-31.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-32.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-33.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-34.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-35.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-36.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-37.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-38.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-39.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-4.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-40.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-41.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-42.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-43.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-44.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-45.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-46.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-47.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-48.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-49.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-5.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-50.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-6.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-7.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-8.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/field-trials/2026-05-field-trial-9.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/fr/index.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/fr/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/how-to/async-use-case.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/how-to/new-project.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/how-to/problem-details.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/how-to/run-tests.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/how-to/validation.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/index.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/ja/explanation/architecture.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/ja/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/ja/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/ja/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/ja/how-to/new-project.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/ja/how-to/run-tests.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/ja/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/ja/index.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/ja/reference/api.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/ja/reference/configuration.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/ja/reference/framework-modules.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/ja/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/ja/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/pt-br/index.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/pt-br/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/reference/api.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/reference/configuration.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/reference/framework-modules.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/roadmap.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/todo/current.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/zh/index.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/docs/zh/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/package-lock.json +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/package.json +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/example/__init__.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/example/__main__.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/example/app.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/example/comment/__init__.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/example/comment/entity.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/example/comment/exceptions.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/example/comment/handler.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/example/comment/repository.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/example/comment/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/example/comment/use_case.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/example/mcp.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/example/note/__init__.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/example/note/async_use_case.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/example/note/entity.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/example/note/exceptions.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/example/note/handler.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/example/note/repository.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/example/note/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/example/note/use_case.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/example/schema.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/example/tag/__init__.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/example/tag/entity.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/example/tag/exceptions.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/example/tag/handler.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/example/tag/repository.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/example/tag/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/example/tag/use_case.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/__init__.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/auth/api_key.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/auth/bearer_token.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/auth/exceptions.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/auth/interfaces.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/auth/local_verifier.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/config/settings.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/database/exceptions.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/database/health.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/database/interfaces.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/database/sqlalchemy_executor.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/database/utils.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/http/health.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/http/pagination.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/log/setup.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/mcp/http_client.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/mcp/server.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/middleware/__init__.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/middleware/domain_exception.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/middleware/error_handler.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/middleware/request_id.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/middleware/request_logging.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/middleware/request_size_limit.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/middleware/security_headers.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/middleware/throttle.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/py.typed +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/use_case/protocols.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/nene2/validation/exceptions.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/scripts/__init__.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/src/scripts/export_openapi.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/__init__.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/example/__init__.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/example/comment/__init__.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/example/comment/test_comment_http.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/example/comment/test_comment_repository.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/example/comment/test_comment_use_case.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/example/conftest.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/example/note/__init__.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/example/note/test_async_note_use_case.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/example/note/test_list_notes.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/example/note/test_note_repository.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/example/tag/__init__.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/example/tag/test_tag_repository.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/example/tag/test_tags.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/example/test_cors.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/example/test_mcp.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/nene2/__init__.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/nene2/auth/test_api_key.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/nene2/auth/test_bearer_token.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/nene2/auth/test_token_issuer.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/nene2/config/test_settings.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/nene2/database/test_transaction.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/nene2/database/test_utils.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/nene2/http/test_health.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/nene2/http/test_pagination.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/nene2/log/test_setup.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/nene2/mcp/test_http_client.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/nene2/middleware/__init__.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/nene2/middleware/test_error_handler.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/nene2/middleware/test_request_id.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/nene2/middleware/test_request_logging.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/nene2/middleware/test_request_size_limit.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/nene2/middleware/test_security_headers.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/nene2/middleware/test_throttle.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/nene2/use_case/test_protocols.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/nene2/validation/test_exceptions.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/tests/scripts/__init__.py +0 -0
- {nene2_python-1.8.10 → nene2_python-1.8.11}/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.11] — 2026-05-20
|
|
9
|
+
|
|
10
|
+
FT51 フィールドトライアル — SimpleDomainHandler 実運用検証 + problem_details_response バグ修正。
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- `problem_details_response()` — `extra` に RFC 9457 予約済みフィールド (`type`, `title`, `status`, `detail`) が含まれる場合に `ValueError` を raise するよう修正 (#282) (FT51)
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- Field trial report: `docs/field-trials/2026-05-field-trial-51.md`
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
8
20
|
## [1.8.10] — 2026-05-20
|
|
9
21
|
|
|
10
22
|
FT50 フィールドトライアル — ValidationException + ValidationCode(StrEnum) 実運用検証。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nene2-python
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.11
|
|
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,127 @@
|
|
|
1
|
+
# Field Trial 51: SimpleDomainHandler 実運用検証
|
|
2
|
+
|
|
3
|
+
**Date**: 2026-05-20
|
|
4
|
+
**Theme**: `SimpleDomainHandler` + `detail_factory` / `extra_factory` の実運用パターン検証
|
|
5
|
+
**Version under test**: v1.8.10
|
|
6
|
+
**FT App**: `/home/xi/docker/nene2-python-FT/ft51-domain-handler/`
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 概要
|
|
11
|
+
|
|
12
|
+
複数のドメイン例外を `SimpleDomainHandler` で Problem Details に変換するパターンを
|
|
13
|
+
記事 API で実運用した。404 / 403 / 409 の 3 種類のエラーを検証。
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 実装内容
|
|
18
|
+
|
|
19
|
+
### ドメイン例外クラス
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
class ArticleNotFoundError(Exception):
|
|
23
|
+
def __init__(self, article_id: int) -> None:
|
|
24
|
+
self.article_id = article_id
|
|
25
|
+
|
|
26
|
+
class ArticleAccessDeniedError(Exception):
|
|
27
|
+
def __init__(self, article_id: int, user_id: str) -> None:
|
|
28
|
+
self.article_id = article_id
|
|
29
|
+
self.user_id = user_id
|
|
30
|
+
|
|
31
|
+
class ArticleTitleConflictError(Exception):
|
|
32
|
+
def __init__(self, title: str) -> None:
|
|
33
|
+
self.title = title
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### SimpleDomainHandler による登録
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
handlers = [
|
|
40
|
+
SimpleDomainHandler(
|
|
41
|
+
ArticleNotFoundError,
|
|
42
|
+
"article-not-found",
|
|
43
|
+
"Article Not Found",
|
|
44
|
+
404,
|
|
45
|
+
detail_factory=lambda exc: str(exc),
|
|
46
|
+
extra_factory=lambda exc: {"article_id": exc.article_id},
|
|
47
|
+
),
|
|
48
|
+
SimpleDomainHandler(
|
|
49
|
+
ArticleAccessDeniedError,
|
|
50
|
+
"article-access-denied",
|
|
51
|
+
"Access Denied",
|
|
52
|
+
403,
|
|
53
|
+
detail_factory=lambda exc: str(exc),
|
|
54
|
+
extra_factory=lambda exc: {"article_id": exc.article_id, "user_id": exc.user_id},
|
|
55
|
+
),
|
|
56
|
+
SimpleDomainHandler(
|
|
57
|
+
ArticleTitleConflictError,
|
|
58
|
+
"article-title-conflict",
|
|
59
|
+
"Article Title Conflict",
|
|
60
|
+
409,
|
|
61
|
+
detail_factory=lambda exc: str(exc),
|
|
62
|
+
extra_factory=lambda exc: {"article_title": exc.title}, # ← "title" ではなく "article_title"
|
|
63
|
+
),
|
|
64
|
+
]
|
|
65
|
+
app.add_middleware(ErrorHandlerMiddleware, domain_handlers=handlers)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## テスト結果
|
|
71
|
+
|
|
72
|
+
6 tests, all passed (after fixing FP51-1).
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## 摩擦ポイント
|
|
77
|
+
|
|
78
|
+
### FP51-1: `extra_factory` に `title` キーを返すと Problem Details の `title` が上書きされる
|
|
79
|
+
|
|
80
|
+
**状況**: `ArticleTitleConflictError` の `extra_factory` で `{"title": exc.title}` を返したところ、
|
|
81
|
+
Problem Details レスポンスの `title` フィールド(`"Article Title Conflict"`)が `exc.title`(`"Dup"`)に
|
|
82
|
+
上書きされた。
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"type": "...",
|
|
87
|
+
"title": "Dup", // ← "Article Title Conflict" のはずが上書きされた
|
|
88
|
+
"status": 409,
|
|
89
|
+
"detail": "Article with title 'Dup' already exists"
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**原因**: `problem_details_response()` が `body.update(extra)` で `extra` を後から適用するため、
|
|
94
|
+
RFC 9457 の予約済みフィールド (`type`, `title`, `status`, `detail`) を含む `extra` が
|
|
95
|
+
意図せずフィールドを上書きする。
|
|
96
|
+
|
|
97
|
+
**修正**: `problem_details_response()` が `extra` に予約済みフィールドが含まれている場合に
|
|
98
|
+
`ValueError` を raise するよう修正 (#282)。
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
# 修正後のコード
|
|
102
|
+
_RESERVED_FIELDS = frozenset({"type", "title", "status", "detail"})
|
|
103
|
+
if extra:
|
|
104
|
+
overlap = _RESERVED_FIELDS & extra.keys()
|
|
105
|
+
if overlap:
|
|
106
|
+
raise ValueError(f"extra contains reserved Problem Details fields: {sorted(overlap)}")
|
|
107
|
+
body.update(extra)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**ワークアラウンド(修正前)**: `extra` に予約済みキーと衝突しない名前を使う(例: `"article_title"`)。
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## フレームワーク変更
|
|
115
|
+
|
|
116
|
+
### `nene2.http.problem_details_response()` — extra reserved fields 保護 (#282)
|
|
117
|
+
|
|
118
|
+
`extra` に RFC 9457 予約済みフィールド (`type`, `title`, `status`, `detail`) が含まれる場合に
|
|
119
|
+
`ValueError` を raise するようになった。
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## 結論
|
|
124
|
+
|
|
125
|
+
`SimpleDomainHandler` + `detail_factory` / `extra_factory` の組み合わせは実運用で使いやすい。
|
|
126
|
+
ただし `extra_factory` の返り値に RFC 9457 予約済みフィールドと同名のキーを入れると
|
|
127
|
+
サイレントに上書きされる危険があった。`ValueError` による早期検知で問題を防止できる。
|
|
@@ -8,6 +8,7 @@ from typing import Any
|
|
|
8
8
|
from fastapi.responses import JSONResponse
|
|
9
9
|
|
|
10
10
|
PROBLEM_DETAILS_BASE_URL = "https://nene2.dev/problems/"
|
|
11
|
+
_RESERVED_FIELDS = frozenset({"type", "title", "status", "detail"})
|
|
11
12
|
|
|
12
13
|
_configured_base_url: str | None = None
|
|
13
14
|
|
|
@@ -73,6 +74,9 @@ def problem_details_response(
|
|
|
73
74
|
if detail:
|
|
74
75
|
body["detail"] = detail
|
|
75
76
|
if extra:
|
|
77
|
+
overlap = _RESERVED_FIELDS & extra.keys()
|
|
78
|
+
if overlap:
|
|
79
|
+
raise ValueError(f"extra contains reserved Problem Details fields: {sorted(overlap)}")
|
|
76
80
|
body.update(extra)
|
|
77
81
|
|
|
78
82
|
return JSONResponse(
|
|
@@ -74,3 +74,18 @@ def test_reset_problem_details_is_idempotent() -> None:
|
|
|
74
74
|
reset_problem_details()
|
|
75
75
|
r = problem_details_response("not-found", "Not Found", 404)
|
|
76
76
|
assert b"nene2.dev/problems/not-found" in r.body
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_extra_with_reserved_field_raises_value_error() -> None:
|
|
80
|
+
with pytest.raises(ValueError, match="reserved Problem Details fields"):
|
|
81
|
+
problem_details_response("x", "X", 400, extra={"title": "bad"})
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_extra_with_type_reserved_raises_value_error() -> None:
|
|
85
|
+
with pytest.raises(ValueError, match="reserved Problem Details fields"):
|
|
86
|
+
problem_details_response("x", "X", 400, extra={"type": "overridden"})
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_extra_with_status_reserved_raises_value_error() -> None:
|
|
90
|
+
with pytest.raises(ValueError, match="reserved Problem Details fields"):
|
|
91
|
+
problem_details_response("x", "X", 400, extra={"status": 500})
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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.10 → nene2_python-1.8.11}/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
|
{nene2_python-1.8.10 → nene2_python-1.8.11}/tests/example/comment/test_comment_repository.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
|
{nene2_python-1.8.10 → nene2_python-1.8.11}/tests/nene2/middleware/test_request_size_limit.py
RENAMED
|
File without changes
|
|
File without changes
|
{nene2_python-1.8.10 → nene2_python-1.8.11}/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
|