nene2-python 1.4.0__tar.gz → 1.5.0__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.
Files changed (191) hide show
  1. {nene2_python-1.4.0 → nene2_python-1.5.0}/.github/workflows/ci.yml +6 -0
  2. {nene2_python-1.4.0 → nene2_python-1.5.0}/.github/workflows/publish.yml +10 -1
  3. {nene2_python-1.4.0 → nene2_python-1.5.0}/CHANGELOG.md +57 -0
  4. {nene2_python-1.4.0 → nene2_python-1.5.0}/PKG-INFO +1 -1
  5. nene2_python-1.5.0/docs/adr/0011-mcp-as-core-dependency.md +63 -0
  6. nene2_python-1.5.0/docs/field-trials/2026-05-field-trial-12.md +58 -0
  7. {nene2_python-1.4.0 → nene2_python-1.5.0}/pyproject.toml +1 -1
  8. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/middleware/request_size_limit.py +19 -1
  9. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/middleware/throttle.py +15 -1
  10. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/nene2/middleware/test_request_size_limit.py +21 -0
  11. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/nene2/middleware/test_throttle.py +32 -0
  12. {nene2_python-1.4.0 → nene2_python-1.5.0}/uv.lock +1 -1
  13. {nene2_python-1.4.0 → nene2_python-1.5.0}/.env.example +0 -0
  14. {nene2_python-1.4.0 → nene2_python-1.5.0}/.github/workflows/docs.yml +0 -0
  15. {nene2_python-1.4.0 → nene2_python-1.5.0}/.gitignore +0 -0
  16. {nene2_python-1.4.0 → nene2_python-1.5.0}/.vitepress/config.mts +0 -0
  17. {nene2_python-1.4.0 → nene2_python-1.5.0}/.vitepress/theme/custom.css +0 -0
  18. {nene2_python-1.4.0 → nene2_python-1.5.0}/.vitepress/theme/index.ts +0 -0
  19. {nene2_python-1.4.0 → nene2_python-1.5.0}/AGENTS.md +0 -0
  20. {nene2_python-1.4.0 → nene2_python-1.5.0}/CLAUDE.md +0 -0
  21. {nene2_python-1.4.0 → nene2_python-1.5.0}/Dockerfile +0 -0
  22. {nene2_python-1.4.0 → nene2_python-1.5.0}/LICENSE +0 -0
  23. {nene2_python-1.4.0 → nene2_python-1.5.0}/README.md +0 -0
  24. {nene2_python-1.4.0 → nene2_python-1.5.0}/alembic/README +0 -0
  25. {nene2_python-1.4.0 → nene2_python-1.5.0}/alembic/env.py +0 -0
  26. {nene2_python-1.4.0 → nene2_python-1.5.0}/alembic/script.py.mako +0 -0
  27. {nene2_python-1.4.0 → nene2_python-1.5.0}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
  28. {nene2_python-1.4.0 → nene2_python-1.5.0}/alembic.ini +0 -0
  29. {nene2_python-1.4.0 → nene2_python-1.5.0}/compose.yaml +0 -0
  30. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/adr/0001-toolchain.md +0 -0
  31. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/adr/0002-clean-architecture.md +0 -0
  32. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/adr/0003-security-first.md +0 -0
  33. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/adr/0004-ai-first-design.md +0 -0
  34. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/adr/0005-logging.md +0 -0
  35. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/adr/0006-rate-limiting.md +0 -0
  36. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/adr/0009-mcp-design.md +0 -0
  37. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/adr/0010-async-use-case.md +0 -0
  38. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/de/index.md +0 -0
  39. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/de/tutorials/getting-started.md +0 -0
  40. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/explanation/architecture.md +0 -0
  41. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/explanation/design-philosophy.md +0 -0
  42. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/field-trials/2026-05-field-trial-1.md +0 -0
  43. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/field-trials/2026-05-field-trial-10.md +0 -0
  44. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/field-trials/2026-05-field-trial-11.md +0 -0
  45. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/field-trials/2026-05-field-trial-2.md +0 -0
  46. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/field-trials/2026-05-field-trial-3.md +0 -0
  47. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/field-trials/2026-05-field-trial-4.md +0 -0
  48. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/field-trials/2026-05-field-trial-5.md +0 -0
  49. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/field-trials/2026-05-field-trial-6.md +0 -0
  50. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/field-trials/2026-05-field-trial-7.md +0 -0
  51. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/field-trials/2026-05-field-trial-8.md +0 -0
  52. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/field-trials/2026-05-field-trial-9.md +0 -0
  53. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/fr/index.md +0 -0
  54. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/fr/tutorials/getting-started.md +0 -0
  55. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/how-to/add-new-domain.md +0 -0
  56. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/how-to/configure-auth.md +0 -0
  57. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/how-to/new-project.md +0 -0
  58. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/how-to/run-tests.md +0 -0
  59. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/how-to/sqlalchemy-repository.md +0 -0
  60. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/howto/mcp-setup.md +0 -0
  61. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/index.md +0 -0
  62. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/ja/explanation/architecture.md +0 -0
  63. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/ja/explanation/design-philosophy.md +0 -0
  64. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/ja/how-to/add-new-domain.md +0 -0
  65. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/ja/how-to/configure-auth.md +0 -0
  66. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/ja/how-to/new-project.md +0 -0
  67. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/ja/how-to/run-tests.md +0 -0
  68. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
  69. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/ja/howto/mcp-setup.md +0 -0
  70. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/ja/index.md +0 -0
  71. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/ja/reference/api.md +0 -0
  72. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/ja/reference/configuration.md +0 -0
  73. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/ja/reference/framework-modules.md +0 -0
  74. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/ja/tutorials/first-domain.md +0 -0
  75. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/ja/tutorials/getting-started.md +0 -0
  76. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/pt-br/index.md +0 -0
  77. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/pt-br/tutorials/getting-started.md +0 -0
  78. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/reference/api.md +0 -0
  79. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/reference/configuration.md +0 -0
  80. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/reference/framework-modules.md +0 -0
  81. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/roadmap.md +0 -0
  82. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/todo/current.md +0 -0
  83. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/tutorials/first-domain.md +0 -0
  84. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/tutorials/getting-started.md +0 -0
  85. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/zh/index.md +0 -0
  86. {nene2_python-1.4.0 → nene2_python-1.5.0}/docs/zh/tutorials/getting-started.md +0 -0
  87. {nene2_python-1.4.0 → nene2_python-1.5.0}/package-lock.json +0 -0
  88. {nene2_python-1.4.0 → nene2_python-1.5.0}/package.json +0 -0
  89. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/example/__init__.py +0 -0
  90. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/example/__main__.py +0 -0
  91. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/example/app.py +0 -0
  92. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/example/comment/__init__.py +0 -0
  93. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/example/comment/entity.py +0 -0
  94. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/example/comment/exceptions.py +0 -0
  95. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/example/comment/handler.py +0 -0
  96. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/example/comment/repository.py +0 -0
  97. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/example/comment/sqlalchemy_repository.py +0 -0
  98. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/example/comment/use_case.py +0 -0
  99. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/example/mcp.py +0 -0
  100. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/example/note/__init__.py +0 -0
  101. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/example/note/async_use_case.py +0 -0
  102. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/example/note/entity.py +0 -0
  103. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/example/note/exceptions.py +0 -0
  104. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/example/note/handler.py +0 -0
  105. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/example/note/repository.py +0 -0
  106. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/example/note/sqlalchemy_repository.py +0 -0
  107. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/example/note/use_case.py +0 -0
  108. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/example/schema.py +0 -0
  109. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/example/tag/__init__.py +0 -0
  110. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/example/tag/entity.py +0 -0
  111. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/example/tag/exceptions.py +0 -0
  112. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/example/tag/handler.py +0 -0
  113. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/example/tag/repository.py +0 -0
  114. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/example/tag/sqlalchemy_repository.py +0 -0
  115. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/example/tag/use_case.py +0 -0
  116. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/__init__.py +0 -0
  117. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/auth/__init__.py +0 -0
  118. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/auth/api_key.py +0 -0
  119. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/auth/bearer_token.py +0 -0
  120. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/auth/exceptions.py +0 -0
  121. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/auth/interfaces.py +0 -0
  122. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/auth/local_verifier.py +0 -0
  123. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/config/__init__.py +0 -0
  124. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/config/settings.py +0 -0
  125. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/database/__init__.py +0 -0
  126. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/database/exceptions.py +0 -0
  127. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/database/health.py +0 -0
  128. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/database/interfaces.py +0 -0
  129. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/database/sqlalchemy_executor.py +0 -0
  130. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/database/utils.py +0 -0
  131. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/http/__init__.py +0 -0
  132. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/http/health.py +0 -0
  133. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/http/pagination.py +0 -0
  134. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/http/problem_details.py +0 -0
  135. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/log/__init__.py +0 -0
  136. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/log/setup.py +0 -0
  137. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/mcp/__init__.py +0 -0
  138. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/mcp/http_client.py +0 -0
  139. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/mcp/server.py +0 -0
  140. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/middleware/__init__.py +0 -0
  141. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/middleware/domain_exception.py +0 -0
  142. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/middleware/error_handler.py +0 -0
  143. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/middleware/request_id.py +0 -0
  144. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/middleware/request_logging.py +0 -0
  145. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/middleware/security_headers.py +0 -0
  146. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/py.typed +0 -0
  147. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/use_case/__init__.py +0 -0
  148. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/use_case/protocols.py +0 -0
  149. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/validation/__init__.py +0 -0
  150. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/nene2/validation/exceptions.py +0 -0
  151. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/scripts/__init__.py +0 -0
  152. {nene2_python-1.4.0 → nene2_python-1.5.0}/src/scripts/export_openapi.py +0 -0
  153. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/__init__.py +0 -0
  154. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/example/__init__.py +0 -0
  155. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/example/comment/__init__.py +0 -0
  156. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/example/comment/test_comment_http.py +0 -0
  157. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/example/comment/test_comment_repository.py +0 -0
  158. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/example/comment/test_comment_use_case.py +0 -0
  159. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/example/conftest.py +0 -0
  160. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/example/note/__init__.py +0 -0
  161. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/example/note/test_async_note_use_case.py +0 -0
  162. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/example/note/test_list_notes.py +0 -0
  163. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/example/note/test_note_repository.py +0 -0
  164. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/example/tag/__init__.py +0 -0
  165. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/example/tag/test_tag_repository.py +0 -0
  166. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/example/tag/test_tags.py +0 -0
  167. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/example/test_cors.py +0 -0
  168. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/example/test_mcp.py +0 -0
  169. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/nene2/__init__.py +0 -0
  170. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/nene2/auth/__init__.py +0 -0
  171. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/nene2/auth/test_api_key.py +0 -0
  172. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/nene2/auth/test_bearer_token.py +0 -0
  173. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/nene2/auth/test_token_issuer.py +0 -0
  174. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/nene2/database/__init__.py +0 -0
  175. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/nene2/database/test_transaction.py +0 -0
  176. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/nene2/database/test_utils.py +0 -0
  177. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/nene2/http/__init__.py +0 -0
  178. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/nene2/http/test_pagination.py +0 -0
  179. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/nene2/mcp/__init__.py +0 -0
  180. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/nene2/mcp/test_http_client.py +0 -0
  181. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/nene2/middleware/__init__.py +0 -0
  182. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/nene2/middleware/test_error_handler.py +0 -0
  183. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/nene2/middleware/test_request_id.py +0 -0
  184. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/nene2/middleware/test_request_logging.py +0 -0
  185. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/nene2/middleware/test_security_headers.py +0 -0
  186. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/nene2/use_case/__init__.py +0 -0
  187. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/nene2/use_case/test_protocols.py +0 -0
  188. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/nene2/validation/__init__.py +0 -0
  189. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/nene2/validation/test_exceptions.py +0 -0
  190. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/scripts/__init__.py +0 -0
  191. {nene2_python-1.4.0 → nene2_python-1.5.0}/tests/scripts/test_export_openapi.py +0 -0
@@ -31,6 +31,12 @@ jobs:
31
31
  - name: pytest (with coverage)
32
32
  run: uv run pytest
33
33
 
34
+ - name: coverage gate — domain/use_case layers (90%)
35
+ run: |
36
+ uv run coverage report \
37
+ --include="src/example/*/use_case.py,src/example/*/async_use_case.py,src/example/*/entity.py" \
38
+ --fail-under=90
39
+
34
40
  - name: mypy
35
41
  run: uv run mypy src/
36
42
 
@@ -89,8 +89,17 @@ jobs:
89
89
  name: dist
90
90
  path: dist/
91
91
 
92
+ - name: Extract release notes from CHANGELOG
93
+ id: changelog
94
+ run: |
95
+ VERSION="${GITHUB_REF_NAME#v}"
96
+ NOTES=$(awk "/^## \[$VERSION\]/{flag=1; next} /^## \[/{flag=0} flag" CHANGELOG.md | sed '/^[[:space:]]*$/d;/^---$/d' | sed '1{/^[[:space:]]*$/d}')
97
+ echo "notes<<EOF" >> "$GITHUB_OUTPUT"
98
+ echo "$NOTES" >> "$GITHUB_OUTPUT"
99
+ echo "EOF" >> "$GITHUB_OUTPUT"
100
+
92
101
  - name: Create GitHub Release
93
102
  uses: softprops/action-gh-release@v2
94
103
  with:
95
104
  files: dist/*
96
- generate_release_notes: true
105
+ body: ${{ steps.changelog.outputs.notes }}
@@ -5,6 +5,63 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
6
  ---
7
7
 
8
+ ## [1.5.0] — 2026-05-20
9
+
10
+ FT12 (ThrottleMiddleware + RequestSizeLimitMiddleware) field trial — middleware exclude_paths consistency.
11
+
12
+ ### Added
13
+ - `ThrottleMiddleware` — `exclude_paths` parameter to bypass rate limiting for `/health`, `/docs`, etc.
14
+ - `RequestSizeLimitMiddleware` — same `exclude_paths` parameter for consistency with other middleware
15
+ - Field trial report: `docs/field-trials/2026-05-field-trial-12.md`
16
+
17
+ ---
18
+
19
+ ## [1.4.0] — 2026-05-20
20
+
21
+ FT11 (BearerTokenMiddleware + HttpxMcpClient) field trial — auth usability improvements.
22
+
23
+ ### Added
24
+ - `BearerTokenMiddleware` — `exclude_paths` parameter to bypass auth for `/docs`, `/openapi.json`, `/health`, etc.
25
+ - `ApiKeyAuthMiddleware` — same `exclude_paths` parameter
26
+ - `LocalTokenVerifier.from_env(env_var, *, separator=",")` — create a verifier from a comma-delimited environment variable, with whitespace trimming and custom separator support
27
+ - `docs/how-to/configure-auth.md` — three new sections: `from_env` usage, `exclude_paths` usage, MCP server fail-fast token check pattern
28
+ - Field trial report: `docs/field-trials/2026-05-field-trial-11.md`
29
+
30
+ ---
31
+
32
+ ## [1.3.0] — 2026-05-20
33
+
34
+ FT10 (MySQL adapter) field trial — pagination and serialization improvements.
35
+
36
+ ### Added
37
+ - `PaginationQueryParser.__init__` — makes the class usable as a FastAPI `Depends()` parameter directly: `Annotated[PaginationQueryParser, Depends()]`
38
+ - `PaginationResponse.to_dict()` — auto-serializes `dataclass(frozen=True, slots=True)` items via `dataclasses.asdict()` (previously raised `TypeError` for slotted dataclasses)
39
+ - Field trial report: `docs/field-trials/2026-05-field-trial-10.md`
40
+ - How-to guide: MySQL adapter setup (`docs/how-to/use-mysql.md`)
41
+
42
+ ---
43
+
44
+ ## [1.2.0] — 2026-05-19
45
+
46
+ FT9 (MCP server standalone) field trial — MCP server and HTTP client improvements.
47
+
48
+ ### Added
49
+ - `LocalMcpServer` — `port` and `host` constructor parameters (previously hardcoded)
50
+ - `McpHttpError` — raised by `HttpxMcpClient.raise_for_error()` on 4xx/5xx responses, maps to MCP `isError: true`
51
+ - Field trial report: `docs/field-trials/2026-05-field-trial-9.md`
52
+
53
+ ---
54
+
55
+ ## [1.1.0] — 2026-05-19
56
+
57
+ FT8 (nested resources + datetime) field trial — database datetime handling.
58
+
59
+ ### Added
60
+ - `nene2.database.utils.parse_db_datetime(value)` — normalises SQLite string timestamps and MySQL naive `datetime` objects to UTC-aware `datetime`; handles both adapters transparently
61
+ - Field trial report: `docs/field-trials/2026-05-field-trial-8.md`
62
+
63
+ ---
64
+
8
65
  ## [1.0.0] — 2026-05-19
9
66
 
10
67
  First stable release. Feature parity with PHP NENE2 v1.4.0.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nene2-python
3
- Version: 1.4.0
3
+ Version: 1.5.0
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,63 @@
1
+ # ADR-0011: MCP をコア依存として含める
2
+
3
+ ## ステータス
4
+
5
+ 承認済み (2026-05-20)
6
+
7
+ ## コンテキスト
8
+
9
+ 外部レビューで「`mcp>=1.0` をコア依存に含めると、MCP を使わない利用者まで FastMCP の依存ツリーを背負う」という指摘を受けた。`nene2[mcp]` optional extras に分離する案が提示された。
10
+
11
+ ## 決定
12
+
13
+ `mcp>=1.0` はコア依存として `pyproject.toml` の `dependencies` に置く。optional extras には分離しない。
14
+
15
+ ### 理由
16
+
17
+ **1. MCP はフレームワークの設計思想の中核**
18
+
19
+ `CLAUDE.md` の設計哲学に「LLM delivery ready: API・MCP・認証・DB・引き継ぎドキュメントを整合させる」と明記している。MCP は認証・DB と同列のファーストクラス機能であり、"optional な付加機能" ではない。
20
+
21
+ **2. UseCase アーキテクチャとの直結**
22
+
23
+ `nene2.use_case` の `UseCaseProtocol[I, O]` は HTTP と MCP の両方から呼ばれることを前提に設計されている(ADR-0002)。UseCase を MCP ツールとして公開する経路が常に存在することがフレームワークの価値のひとつであり、その経路を extras 依存にすることはアーキテクチャの前提に反する。
24
+
25
+ **3. extras 化のコスト**
26
+
27
+ optional extras にすると `nene2.mcp` モジュール全体の import が条件分岐になる:
28
+
29
+ ```python
30
+ try:
31
+ from fastmcp import FastMCP
32
+ except ImportError:
33
+ raise ImportError("Install nene2[mcp] to use MCP features.")
34
+ ```
35
+
36
+ フィールドトライアルのたびに MCP を使うため、このパターンを随所に書くことは単なる負債になる。
37
+
38
+ **4. 現在の採用者像**
39
+
40
+ nene2-python の想定ユーザーは「AI エージェント連携を見据えた API バックエンドを構築したい開発者」であり、MCP を使わない利用者は現時点で想定外のユースケースに属する。
41
+
42
+ ## 将来の見直し条件
43
+
44
+ 以下のいずれかが発生した場合、optional extras 化を検討する:
45
+
46
+ - FastMCP の依存ツリーが著しく肥大化し、インストール時間・容量が問題視される
47
+ - 「認証・DB・ページネーションだけ使いたい、MCP は不要」という実際の利用者フィードバックが複数件寄せられる
48
+ - MCP SDK のライセンスや破壊的変更がコア依存として許容できなくなる
49
+
50
+ その際は `nene2[mcp]` extras として分離し、`nene2.mcp` モジュールのインポートを条件分岐化する。
51
+
52
+ ## 代替案
53
+
54
+ | 案 | 却下理由 |
55
+ |---|---|
56
+ | `nene2[mcp]` optional extras | 設計思想と不整合・extras 化のコストが現状では割に合わない |
57
+ | MCP モジュールを別パッケージ (`nene2-mcp`) に分離 | フレームワークの一体感が失われる・インストール手順が増える |
58
+
59
+ ## 結果
60
+
61
+ - `uv add nene2-python` 一発で MCP 機能を含む完全なスタックが手に入る
62
+ - フィールドトライアルで MCP を毎回インストールする手間がない
63
+ - 将来的に extras 分離が必要になった場合は ADR を更新してこの決定を覆す
@@ -0,0 +1,58 @@
1
+ # Field Trial 12 — ThrottleMiddleware + RequestSizeLimitMiddleware 実運用
2
+
3
+ **Date:** 2026-05-20
4
+ **App:** Chirp API(短文投稿 API、レート制限 + ペイロード制限付き)
5
+ **Directory:** `/home/xi/docker/nene2-python-FT/ft12-throttle/`
6
+ **nene2-python version:** v1.4.0
7
+
8
+ ## 概要
9
+
10
+ `ThrottleMiddleware`(固定ウィンドウレート制限)と `RequestSizeLimitMiddleware`(リクエストボディ制限)を実際のアプリに組み込み、動作と摩擦点を確認した。
11
+
12
+ ## 動作確認結果
13
+
14
+ - `ThrottleMiddleware(limit=3, window=60)` で3リクエスト後に 429 + `Retry-After` ヘッダーが返ること ✓
15
+ - `RequestSizeLimitMiddleware(max_bytes=100)` でペイロード超過時に 413 が返ること ✓
16
+ - 両ミドルウェアとも Problem Details (RFC 9457) 形式でエラーレスポンスが返ること ✓
17
+ - `AppSettings` に `throttle_limit`, `throttle_window`, `max_body_size` が揃っていること ✓
18
+
19
+ ## 摩擦点
20
+
21
+ ### FT12-F1 (MEDIUM): ThrottleMiddleware に exclude_paths がない
22
+
23
+ BearerTokenMiddleware・ApiKeyAuthMiddleware は FT11 で `exclude_paths` を追加したが、
24
+ `ThrottleMiddleware` には同パラメータがない。
25
+
26
+ ```python
27
+ # やりたいこと: /health はレート制限の対象外にしたい
28
+ app.add_middleware(
29
+ ThrottleMiddleware,
30
+ limit=60,
31
+ window=60,
32
+ exclude_paths=["/health", "/docs"], # ← 存在しない
33
+ )
34
+ ```
35
+
36
+ 実際には `/health` も 60req/min のレート制限にかかる。ロードバランサーのヘルスチェックが
37
+ 高頻度で叩く環境では 429 が返り、インスタンスがダウン扱いになるリスクがある。
38
+
39
+ **再現コード:**
40
+ ```python
41
+ app.add_middleware(ThrottleMiddleware, limit=2, window=60)
42
+ # /health を3回叩くと3回目が 429
43
+ ```
44
+
45
+ ### FT12-F2 (MEDIUM): RequestSizeLimitMiddleware に exclude_paths がない
46
+
47
+ 同様に `RequestSizeLimitMiddleware` にも `exclude_paths` がない。
48
+ 実用上の影響は ThrottleMiddleware より小さいが(GET リクエストはボディなしなので通常問題ない)、
49
+ 一貫性の観点から揃えるべき。
50
+
51
+ BearerTokenMiddleware, ApiKeyAuthMiddleware, ThrottleMiddleware がすべて `exclude_paths` を
52
+ 持つのに RequestSizeLimitMiddleware だけ持たない状態は混乱を招く。
53
+
54
+ ## まとめ
55
+
56
+ 基本動作は問題なし。FT11 で auth 系ミドルウェアに `exclude_paths` を追加したが、
57
+ 同じ修正が `ThrottleMiddleware` と `RequestSizeLimitMiddleware` にも必要。
58
+ 3つのミドルウェアで `exclude_paths` の有無が揃っていないことが主な摩擦。
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nene2-python"
3
- version = "1.4.0"
3
+ version = "1.5.0"
4
4
  description = "NENE2 Python — minimal API framework following NENE2's design philosophy"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
@@ -22,13 +22,31 @@ class RequestSizeLimitMiddleware(BaseHTTPMiddleware):
22
22
  Checks the Content-Length header first for a fast pre-flight reject,
23
23
  then reads the actual body to catch chunked-transfer requests that
24
24
  omit Content-Length entirely.
25
+
26
+ Use ``exclude_paths`` to bypass the size limit for specific endpoints::
27
+
28
+ app.add_middleware(
29
+ RequestSizeLimitMiddleware,
30
+ max_bytes=1_048_576,
31
+ exclude_paths=["/upload/multipart"],
32
+ )
25
33
  """
26
34
 
27
- def __init__(self, app: object, *, max_bytes: int = _DEFAULT_MAX_BYTES) -> None:
35
+ def __init__(
36
+ self,
37
+ app: object,
38
+ *,
39
+ max_bytes: int = _DEFAULT_MAX_BYTES,
40
+ exclude_paths: list[str] | None = None,
41
+ ) -> None:
28
42
  super().__init__(app) # type: ignore[arg-type]
29
43
  self._max_bytes = max_bytes
44
+ self._exclude_paths = set(exclude_paths or [])
30
45
 
31
46
  async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
47
+ if request.url.path in self._exclude_paths:
48
+ return await call_next(request)
49
+
32
50
  content_length = request.headers.get("Content-Length")
33
51
  if content_length is not None:
34
52
  try:
@@ -25,7 +25,17 @@ _DEFAULT_WINDOW = 60 # seconds
25
25
 
26
26
 
27
27
  class ThrottleMiddleware(BaseHTTPMiddleware):
28
- """Fixed-window rate limiter keyed by client IP."""
28
+ """Fixed-window rate limiter keyed by client IP.
29
+
30
+ Use ``exclude_paths`` to bypass rate limiting for health checks and API docs::
31
+
32
+ app.add_middleware(
33
+ ThrottleMiddleware,
34
+ limit=60,
35
+ window=60,
36
+ exclude_paths=["/health", "/docs", "/openapi.json"],
37
+ )
38
+ """
29
39
 
30
40
  def __init__(
31
41
  self,
@@ -33,10 +43,12 @@ class ThrottleMiddleware(BaseHTTPMiddleware):
33
43
  *,
34
44
  limit: int = _DEFAULT_LIMIT,
35
45
  window: int = _DEFAULT_WINDOW,
46
+ exclude_paths: list[str] | None = None,
36
47
  ) -> None:
37
48
  super().__init__(app) # type: ignore[arg-type]
38
49
  self._limit = limit
39
50
  self._window = window
51
+ self._exclude_paths = set(exclude_paths or [])
40
52
  self._counts: dict[str, tuple[int, float]] = {}
41
53
  self._lock = threading.Lock()
42
54
 
@@ -58,6 +70,8 @@ class ThrottleMiddleware(BaseHTTPMiddleware):
58
70
  return count <= self._limit, remaining
59
71
 
60
72
  async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
73
+ if request.url.path in self._exclude_paths:
74
+ return await call_next(request)
61
75
  key = self._client_key(request)
62
76
  allowed, retry_after = self._is_allowed(key)
63
77
  if not allowed:
@@ -66,3 +66,24 @@ def test_malformed_content_length_is_tolerated() -> None:
66
66
  headers={"Content-Length": "abc", "Content-Type": "application/octet-stream"},
67
67
  )
68
68
  assert response.status_code == 200
69
+
70
+
71
+ def test_exclude_paths_bypasses_size_limit() -> None:
72
+ app = FastAPI()
73
+ app.add_middleware(
74
+ RequestSizeLimitMiddleware,
75
+ max_bytes=10,
76
+ exclude_paths=["/upload/large"],
77
+ )
78
+
79
+ @app.post("/upload")
80
+ async def upload() -> JSONResponse:
81
+ return JSONResponse({"ok": True})
82
+
83
+ @app.post("/upload/large")
84
+ async def upload_large() -> JSONResponse:
85
+ return JSONResponse({"ok": True})
86
+
87
+ client = TestClient(app)
88
+ assert client.post("/upload", content=b"x" * 100).status_code == 413
89
+ assert client.post("/upload/large", content=b"x" * 100).status_code == 200
@@ -43,3 +43,35 @@ def test_forwarded_for_header_used_as_key() -> None:
43
43
 
44
44
  response2 = client.get("/ping", headers={"X-Forwarded-For": "10.0.0.2"})
45
45
  assert response2.status_code == 200
46
+
47
+
48
+ def test_exclude_paths_bypasses_throttle() -> None:
49
+ app = FastAPI()
50
+ app.add_middleware(
51
+ ThrottleMiddleware,
52
+ limit=2,
53
+ window=60,
54
+ exclude_paths=["/health"],
55
+ )
56
+
57
+ @app.get("/health")
58
+ async def health() -> JSONResponse:
59
+ return JSONResponse({"status": "ok"})
60
+
61
+ @app.get("/ping")
62
+ async def ping() -> JSONResponse:
63
+ return JSONResponse({"ok": True})
64
+
65
+ client = TestClient(app)
66
+ for _ in range(5):
67
+ assert client.get("/health").status_code == 200
68
+
69
+ client.get("/ping")
70
+ client.get("/ping")
71
+ assert client.get("/ping").status_code == 429
72
+
73
+
74
+ def test_exclude_paths_default_is_empty() -> None:
75
+ client = TestClient(_make_app(limit=1))
76
+ client.get("/ping")
77
+ assert client.get("/ping").status_code == 429
@@ -925,7 +925,7 @@ wheels = [
925
925
 
926
926
  [[package]]
927
927
  name = "nene2-python"
928
- version = "1.4.0"
928
+ version = "1.5.0"
929
929
  source = { editable = "." }
930
930
  dependencies = [
931
931
  { name = "alembic" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes