nene2-python 1.7.0__tar.gz → 1.8.1__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 (222) hide show
  1. {nene2_python-1.7.0 → nene2_python-1.8.1}/CHANGELOG.md +36 -0
  2. {nene2_python-1.7.0 → nene2_python-1.8.1}/PKG-INFO +1 -1
  3. nene2_python-1.8.1/docs/field-trials/2026-05-field-trial-18.md +60 -0
  4. nene2_python-1.8.1/docs/field-trials/2026-05-field-trial-19.md +98 -0
  5. nene2_python-1.8.1/docs/field-trials/2026-05-field-trial-20.md +94 -0
  6. nene2_python-1.8.1/docs/field-trials/2026-05-field-trial-21.md +104 -0
  7. nene2_python-1.8.1/docs/field-trials/2026-05-field-trial-22.md +87 -0
  8. nene2_python-1.8.1/docs/field-trials/2026-05-field-trial-23.md +80 -0
  9. nene2_python-1.8.1/docs/field-trials/2026-05-field-trial-24.md +89 -0
  10. nene2_python-1.8.1/docs/field-trials/2026-05-field-trial-25.md +82 -0
  11. nene2_python-1.8.1/docs/field-trials/2026-05-field-trial-26.md +73 -0
  12. nene2_python-1.8.1/docs/field-trials/2026-05-field-trial-27.md +66 -0
  13. nene2_python-1.8.1/docs/field-trials/2026-05-field-trial-28.md +54 -0
  14. nene2_python-1.8.1/docs/how-to/problem-details.md +107 -0
  15. {nene2_python-1.7.0 → nene2_python-1.8.1}/pyproject.toml +1 -1
  16. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/nene2/config/settings.py +10 -0
  17. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/nene2/http/__init__.py +4 -2
  18. nene2_python-1.8.1/src/nene2/http/health.py +48 -0
  19. nene2_python-1.8.1/src/nene2/http/problem_details.py +61 -0
  20. nene2_python-1.8.1/src/nene2/log/__init__.py +5 -0
  21. nene2_python-1.8.1/src/nene2/log/setup.py +89 -0
  22. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/nene2/middleware/__init__.py +4 -2
  23. nene2_python-1.8.1/src/nene2/middleware/domain_exception.py +82 -0
  24. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/nene2/middleware/request_id.py +18 -0
  25. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/nene2/middleware/request_logging.py +13 -1
  26. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/nene2/middleware/request_size_limit.py +1 -0
  27. nene2_python-1.8.1/src/nene2/middleware/throttle.py +143 -0
  28. nene2_python-1.8.1/tests/nene2/config/test_settings.py +54 -0
  29. nene2_python-1.8.1/tests/nene2/http/test_health.py +58 -0
  30. nene2_python-1.8.1/tests/nene2/http/test_problem_details.py +63 -0
  31. nene2_python-1.8.1/tests/nene2/log/test_setup.py +49 -0
  32. {nene2_python-1.7.0 → nene2_python-1.8.1}/tests/nene2/middleware/test_request_id.py +27 -2
  33. {nene2_python-1.7.0 → nene2_python-1.8.1}/tests/nene2/middleware/test_request_logging.py +19 -0
  34. {nene2_python-1.7.0 → nene2_python-1.8.1}/tests/nene2/middleware/test_request_size_limit.py +1 -0
  35. nene2_python-1.8.1/tests/nene2/middleware/test_simple_domain_handler.py +100 -0
  36. nene2_python-1.8.1/tests/nene2/middleware/test_throttle.py +190 -0
  37. nene2_python-1.8.1/tests/nene2/validation/__init__.py +0 -0
  38. nene2_python-1.8.1/tests/scripts/__init__.py +0 -0
  39. {nene2_python-1.7.0 → nene2_python-1.8.1}/uv.lock +1 -1
  40. nene2_python-1.7.0/src/nene2/http/health.py +0 -20
  41. nene2_python-1.7.0/src/nene2/http/problem_details.py +0 -37
  42. nene2_python-1.7.0/src/nene2/log/__init__.py +0 -5
  43. nene2_python-1.7.0/src/nene2/log/setup.py +0 -49
  44. nene2_python-1.7.0/src/nene2/middleware/domain_exception.py +0 -18
  45. nene2_python-1.7.0/src/nene2/middleware/throttle.py +0 -86
  46. nene2_python-1.7.0/tests/nene2/middleware/test_throttle.py +0 -77
  47. {nene2_python-1.7.0 → nene2_python-1.8.1}/.env.example +0 -0
  48. {nene2_python-1.7.0 → nene2_python-1.8.1}/.github/workflows/ci.yml +0 -0
  49. {nene2_python-1.7.0 → nene2_python-1.8.1}/.github/workflows/docs.yml +0 -0
  50. {nene2_python-1.7.0 → nene2_python-1.8.1}/.github/workflows/publish.yml +0 -0
  51. {nene2_python-1.7.0 → nene2_python-1.8.1}/.gitignore +0 -0
  52. {nene2_python-1.7.0 → nene2_python-1.8.1}/.vitepress/config.mts +0 -0
  53. {nene2_python-1.7.0 → nene2_python-1.8.1}/.vitepress/theme/custom.css +0 -0
  54. {nene2_python-1.7.0 → nene2_python-1.8.1}/.vitepress/theme/index.ts +0 -0
  55. {nene2_python-1.7.0 → nene2_python-1.8.1}/AGENTS.md +0 -0
  56. {nene2_python-1.7.0 → nene2_python-1.8.1}/CLAUDE.md +0 -0
  57. {nene2_python-1.7.0 → nene2_python-1.8.1}/Dockerfile +0 -0
  58. {nene2_python-1.7.0 → nene2_python-1.8.1}/LICENSE +0 -0
  59. {nene2_python-1.7.0 → nene2_python-1.8.1}/README.md +0 -0
  60. {nene2_python-1.7.0 → nene2_python-1.8.1}/alembic/README +0 -0
  61. {nene2_python-1.7.0 → nene2_python-1.8.1}/alembic/env.py +0 -0
  62. {nene2_python-1.7.0 → nene2_python-1.8.1}/alembic/script.py.mako +0 -0
  63. {nene2_python-1.7.0 → nene2_python-1.8.1}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
  64. {nene2_python-1.7.0 → nene2_python-1.8.1}/alembic.ini +0 -0
  65. {nene2_python-1.7.0 → nene2_python-1.8.1}/compose.yaml +0 -0
  66. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/adr/0001-toolchain.md +0 -0
  67. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/adr/0002-clean-architecture.md +0 -0
  68. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/adr/0003-security-first.md +0 -0
  69. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/adr/0004-ai-first-design.md +0 -0
  70. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/adr/0005-logging.md +0 -0
  71. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/adr/0006-rate-limiting.md +0 -0
  72. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/adr/0009-mcp-design.md +0 -0
  73. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/adr/0010-async-use-case.md +0 -0
  74. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
  75. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/de/index.md +0 -0
  76. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/de/tutorials/getting-started.md +0 -0
  77. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/explanation/architecture.md +0 -0
  78. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/explanation/design-philosophy.md +0 -0
  79. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/field-trials/2026-05-field-trial-1.md +0 -0
  80. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/field-trials/2026-05-field-trial-10.md +0 -0
  81. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/field-trials/2026-05-field-trial-11.md +0 -0
  82. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/field-trials/2026-05-field-trial-12.md +0 -0
  83. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/field-trials/2026-05-field-trial-13.md +0 -0
  84. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/field-trials/2026-05-field-trial-14.md +0 -0
  85. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/field-trials/2026-05-field-trial-15.md +0 -0
  86. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/field-trials/2026-05-field-trial-16.md +0 -0
  87. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/field-trials/2026-05-field-trial-17.md +0 -0
  88. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/field-trials/2026-05-field-trial-2.md +0 -0
  89. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/field-trials/2026-05-field-trial-3.md +0 -0
  90. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/field-trials/2026-05-field-trial-4.md +0 -0
  91. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/field-trials/2026-05-field-trial-5.md +0 -0
  92. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/field-trials/2026-05-field-trial-6.md +0 -0
  93. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/field-trials/2026-05-field-trial-7.md +0 -0
  94. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/field-trials/2026-05-field-trial-8.md +0 -0
  95. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/field-trials/2026-05-field-trial-9.md +0 -0
  96. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/fr/index.md +0 -0
  97. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/fr/tutorials/getting-started.md +0 -0
  98. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/how-to/add-new-domain.md +0 -0
  99. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/how-to/configure-auth.md +0 -0
  100. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/how-to/new-project.md +0 -0
  101. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/how-to/run-tests.md +0 -0
  102. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/how-to/sqlalchemy-repository.md +0 -0
  103. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/howto/mcp-setup.md +0 -0
  104. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/index.md +0 -0
  105. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/ja/explanation/architecture.md +0 -0
  106. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/ja/explanation/design-philosophy.md +0 -0
  107. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/ja/how-to/add-new-domain.md +0 -0
  108. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/ja/how-to/configure-auth.md +0 -0
  109. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/ja/how-to/new-project.md +0 -0
  110. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/ja/how-to/run-tests.md +0 -0
  111. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
  112. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/ja/howto/mcp-setup.md +0 -0
  113. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/ja/index.md +0 -0
  114. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/ja/reference/api.md +0 -0
  115. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/ja/reference/configuration.md +0 -0
  116. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/ja/reference/framework-modules.md +0 -0
  117. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/ja/tutorials/first-domain.md +0 -0
  118. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/ja/tutorials/getting-started.md +0 -0
  119. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/pt-br/index.md +0 -0
  120. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/pt-br/tutorials/getting-started.md +0 -0
  121. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/reference/api.md +0 -0
  122. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/reference/configuration.md +0 -0
  123. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/reference/framework-modules.md +0 -0
  124. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/roadmap.md +0 -0
  125. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/todo/current.md +0 -0
  126. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/tutorials/first-domain.md +0 -0
  127. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/tutorials/getting-started.md +0 -0
  128. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/zh/index.md +0 -0
  129. {nene2_python-1.7.0 → nene2_python-1.8.1}/docs/zh/tutorials/getting-started.md +0 -0
  130. {nene2_python-1.7.0 → nene2_python-1.8.1}/package-lock.json +0 -0
  131. {nene2_python-1.7.0 → nene2_python-1.8.1}/package.json +0 -0
  132. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/example/__init__.py +0 -0
  133. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/example/__main__.py +0 -0
  134. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/example/app.py +0 -0
  135. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/example/comment/__init__.py +0 -0
  136. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/example/comment/entity.py +0 -0
  137. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/example/comment/exceptions.py +0 -0
  138. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/example/comment/handler.py +0 -0
  139. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/example/comment/repository.py +0 -0
  140. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/example/comment/sqlalchemy_repository.py +0 -0
  141. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/example/comment/use_case.py +0 -0
  142. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/example/mcp.py +0 -0
  143. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/example/note/__init__.py +0 -0
  144. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/example/note/async_use_case.py +0 -0
  145. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/example/note/entity.py +0 -0
  146. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/example/note/exceptions.py +0 -0
  147. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/example/note/handler.py +0 -0
  148. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/example/note/repository.py +0 -0
  149. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/example/note/sqlalchemy_repository.py +0 -0
  150. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/example/note/use_case.py +0 -0
  151. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/example/schema.py +0 -0
  152. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/example/tag/__init__.py +0 -0
  153. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/example/tag/entity.py +0 -0
  154. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/example/tag/exceptions.py +0 -0
  155. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/example/tag/handler.py +0 -0
  156. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/example/tag/repository.py +0 -0
  157. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/example/tag/sqlalchemy_repository.py +0 -0
  158. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/example/tag/use_case.py +0 -0
  159. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/nene2/__init__.py +0 -0
  160. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/nene2/auth/__init__.py +0 -0
  161. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/nene2/auth/api_key.py +0 -0
  162. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/nene2/auth/bearer_token.py +0 -0
  163. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/nene2/auth/exceptions.py +0 -0
  164. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/nene2/auth/interfaces.py +0 -0
  165. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/nene2/auth/local_verifier.py +0 -0
  166. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/nene2/config/__init__.py +0 -0
  167. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/nene2/database/__init__.py +0 -0
  168. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/nene2/database/exceptions.py +0 -0
  169. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/nene2/database/health.py +0 -0
  170. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/nene2/database/interfaces.py +0 -0
  171. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/nene2/database/sqlalchemy_executor.py +0 -0
  172. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/nene2/database/utils.py +0 -0
  173. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/nene2/http/pagination.py +0 -0
  174. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/nene2/mcp/__init__.py +0 -0
  175. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/nene2/mcp/http_client.py +0 -0
  176. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/nene2/mcp/server.py +0 -0
  177. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/nene2/middleware/error_handler.py +0 -0
  178. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/nene2/middleware/security_headers.py +0 -0
  179. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/nene2/py.typed +0 -0
  180. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/nene2/use_case/__init__.py +0 -0
  181. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/nene2/use_case/protocols.py +0 -0
  182. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/nene2/validation/__init__.py +0 -0
  183. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/nene2/validation/exceptions.py +0 -0
  184. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/scripts/__init__.py +0 -0
  185. {nene2_python-1.7.0 → nene2_python-1.8.1}/src/scripts/export_openapi.py +0 -0
  186. {nene2_python-1.7.0 → nene2_python-1.8.1}/tests/__init__.py +0 -0
  187. {nene2_python-1.7.0 → nene2_python-1.8.1}/tests/example/__init__.py +0 -0
  188. {nene2_python-1.7.0 → nene2_python-1.8.1}/tests/example/comment/__init__.py +0 -0
  189. {nene2_python-1.7.0 → nene2_python-1.8.1}/tests/example/comment/test_comment_http.py +0 -0
  190. {nene2_python-1.7.0 → nene2_python-1.8.1}/tests/example/comment/test_comment_repository.py +0 -0
  191. {nene2_python-1.7.0 → nene2_python-1.8.1}/tests/example/comment/test_comment_use_case.py +0 -0
  192. {nene2_python-1.7.0 → nene2_python-1.8.1}/tests/example/conftest.py +0 -0
  193. {nene2_python-1.7.0 → nene2_python-1.8.1}/tests/example/note/__init__.py +0 -0
  194. {nene2_python-1.7.0 → nene2_python-1.8.1}/tests/example/note/test_async_note_use_case.py +0 -0
  195. {nene2_python-1.7.0 → nene2_python-1.8.1}/tests/example/note/test_list_notes.py +0 -0
  196. {nene2_python-1.7.0 → nene2_python-1.8.1}/tests/example/note/test_note_repository.py +0 -0
  197. {nene2_python-1.7.0 → nene2_python-1.8.1}/tests/example/tag/__init__.py +0 -0
  198. {nene2_python-1.7.0 → nene2_python-1.8.1}/tests/example/tag/test_tag_repository.py +0 -0
  199. {nene2_python-1.7.0 → nene2_python-1.8.1}/tests/example/tag/test_tags.py +0 -0
  200. {nene2_python-1.7.0 → nene2_python-1.8.1}/tests/example/test_cors.py +0 -0
  201. {nene2_python-1.7.0 → nene2_python-1.8.1}/tests/example/test_mcp.py +0 -0
  202. {nene2_python-1.7.0 → nene2_python-1.8.1}/tests/nene2/__init__.py +0 -0
  203. {nene2_python-1.7.0 → nene2_python-1.8.1}/tests/nene2/auth/__init__.py +0 -0
  204. {nene2_python-1.7.0 → nene2_python-1.8.1}/tests/nene2/auth/test_api_key.py +0 -0
  205. {nene2_python-1.7.0 → nene2_python-1.8.1}/tests/nene2/auth/test_bearer_token.py +0 -0
  206. {nene2_python-1.7.0 → nene2_python-1.8.1}/tests/nene2/auth/test_token_issuer.py +0 -0
  207. {nene2_python-1.7.0/tests/nene2/database → nene2_python-1.8.1/tests/nene2/config}/__init__.py +0 -0
  208. {nene2_python-1.7.0/tests/nene2/http → nene2_python-1.8.1/tests/nene2/database}/__init__.py +0 -0
  209. {nene2_python-1.7.0 → nene2_python-1.8.1}/tests/nene2/database/test_transaction.py +0 -0
  210. {nene2_python-1.7.0 → nene2_python-1.8.1}/tests/nene2/database/test_utils.py +0 -0
  211. {nene2_python-1.7.0/tests/nene2/mcp → nene2_python-1.8.1/tests/nene2/http}/__init__.py +0 -0
  212. {nene2_python-1.7.0 → nene2_python-1.8.1}/tests/nene2/http/test_pagination.py +0 -0
  213. {nene2_python-1.7.0/tests/nene2/middleware → nene2_python-1.8.1/tests/nene2/log}/__init__.py +0 -0
  214. {nene2_python-1.7.0/tests/nene2/use_case → nene2_python-1.8.1/tests/nene2/mcp}/__init__.py +0 -0
  215. {nene2_python-1.7.0 → nene2_python-1.8.1}/tests/nene2/mcp/test_http_client.py +0 -0
  216. {nene2_python-1.7.0/tests/nene2/validation → nene2_python-1.8.1/tests/nene2/middleware}/__init__.py +0 -0
  217. {nene2_python-1.7.0 → nene2_python-1.8.1}/tests/nene2/middleware/test_error_handler.py +0 -0
  218. {nene2_python-1.7.0 → nene2_python-1.8.1}/tests/nene2/middleware/test_security_headers.py +0 -0
  219. {nene2_python-1.7.0/tests/scripts → nene2_python-1.8.1/tests/nene2/use_case}/__init__.py +0 -0
  220. {nene2_python-1.7.0 → nene2_python-1.8.1}/tests/nene2/use_case/test_protocols.py +0 -0
  221. {nene2_python-1.7.0 → nene2_python-1.8.1}/tests/nene2/validation/test_exceptions.py +0 -0
  222. {nene2_python-1.7.0 → nene2_python-1.8.1}/tests/scripts/test_export_openapi.py +0 -0
@@ -5,6 +5,42 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
6
  ---
7
7
 
8
+ ## [1.8.1] — 2026-05-20
9
+
10
+ FT25〜FT28 フィールドトライアル — RequestId ヘルパー・structlog ログレベル・ThrottleMiddleware 改善。
11
+
12
+ ### Added
13
+ - `nene2.middleware.get_request_id()` — FastAPI `Depends` で注入できる request ID ヘルパー関数 (FT25)
14
+ - `setup_logging()` に `log_level: str = "INFO"` パラメータを追加し `AppSettings.log_level` との統合が容易に (FT26)
15
+ - `ThrottleMiddleware` に `path_limits: dict[str, int] | None` パラメータを追加し、パスごとに異なるレート制限を設定可能に (FT28)
16
+ - Field trial reports: `docs/field-trials/2026-05-field-trial-25.md` 〜 `docs/field-trials/2026-05-field-trial-28.md`
17
+
18
+ ### Fixed
19
+ - `ThrottleMiddleware` — ウィンドウ経過後も `_counts` に古いエントリが残り続ける問題を修正(定期クリーンアップを実装)(FT27)
20
+
21
+ ---
22
+
23
+ ## [1.8.0] — 2026-05-20
24
+
25
+ FT18〜FT23 フィールドトライアル — ログテスト・Problem Details・ThrottleMiddleware・ドメイン例外・HealthCheck・RequestSizeLimit の各改善。
26
+
27
+ ### Added
28
+ - `nene2.log.configure_for_testing()` — structlog を pytest の `caplog` でキャプチャできるように設定するヘルパー関数 (FT18)
29
+ - `nene2.http.configure_problem_details(base_url)` — プロジェクト全体のデフォルト `base_url` を一箇所で設定する関数 (FT19)
30
+ - `ThrottleMiddleware` — 全レスポンスに `X-RateLimit-Limit`/`Remaining`/`Reset` ヘッダーを付与 (FT20)
31
+ - `SimpleDomainHandler` — `exception_class`/`problem_type`/`title`/`status` を渡すだけでドメイン例外ハンドラーを作成できるファクトリクラス (FT21)
32
+ - `nene2.http.CompositeHealthCheck` — 複数の `HealthCheckProtocol` を集約するクラス (FT22)
33
+ - `HealthCheckProtocol` に `@runtime_checkable` を追加 (FT22)
34
+ - Field trial reports: `docs/field-trials/2026-05-field-trial-18.md` 〜 `docs/field-trials/2026-05-field-trial-23.md`
35
+ - `docs/how-to/problem-details.md` — Problem Details の使い方ガイドを追加
36
+
37
+ ### Changed
38
+ - `RequestLoggingMiddleware` — `exclude_paths: list[str] | None` パラメータを追加(特定パスのログをスキップ可能に)(FT18)
39
+ - `ThrottleMiddleware` — 内部の `_is_allowed()` を `_check_rate()` にリネームし、戻り値を `_RateInfo` dataclass に変更 (FT20)
40
+ - `RequestSizeLimitMiddleware` — 413 レスポンスに `max_bytes` 構造化フィールドを追加 (FT23)
41
+
42
+ ---
43
+
8
44
  ## [1.7.0] — 2026-05-20
9
45
 
10
46
  FT14〜FT17 フィールドトライアル — プロトコル docstring 改善・ミドルウェアカスタマイズ・DB 例外統一・バグ修正。
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nene2-python
3
- Version: 1.7.0
3
+ Version: 1.8.1
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,60 @@
1
+ # Field Trial 18 — RequestLoggingMiddleware 実運用
2
+
3
+ **Date:** 2026-05-20
4
+ **App:** FT18 Request Logging API(structlog ログ・request_id 連携・JSON ログ形式検証)
5
+ **Directory:** `/home/xi/docker/nene2-python-FT/ft18-request-logging/`
6
+ **nene2-python version:** v1.7.0
7
+
8
+ ## 概要
9
+
10
+ `RequestLoggingMiddleware` を `RequestIdMiddleware` と組み合わせて実際に動かし、
11
+ ログ出力の内容・request_id との連携・テストでのログ検証方法を確認した。
12
+
13
+ ## 動作確認結果
14
+
15
+ - `request.received` / `request.completed` のログが出力されること ✓
16
+ - ログに `method`, `path`, `status_code`, `duration_ms` が含まれること ✓
17
+ - `RequestIdMiddleware` と組み合わせると `request_id` がログに入ること ✓
18
+ - ログに `request_id` が含まれ、レスポンスヘッダーの `X-Request-Id` と一致すること ✓
19
+
20
+ ## 摩擦点
21
+
22
+ ### FT18-F1 (MEDIUM, テスト容易性): structlog が pytest caplog で捕捉できない
23
+
24
+ `RequestLoggingMiddleware` は structlog を使ってログを出力するが、
25
+ structlog のデフォルト設定では Python stdlib の logging ハンドラーを経由せず
26
+ 直接 stdout に書き込む。
27
+
28
+ そのため pytest の `caplog` では構造化ログを捕捉できない。
29
+ テストで `structlog` のログを検証するには `capsys` で stdout を捕捉するか、
30
+ structlog を stdlib logging に向ける設定変更が必要。
31
+
32
+ ```python
33
+ # caplog では捕捉できない(摩擦)
34
+ with caplog.at_level(logging.INFO, logger="nene2.middleware.request_logging"):
35
+ client.get("/health")
36
+ assert len(caplog.records) == 0 # 常に 0 — ログが入らない
37
+ ```
38
+
39
+ **対応案**: `nene2.log` に `configure_for_testing()` ヘルパーを追加し、
40
+ テスト環境で `structlog` を stdlib logging ブリッジ経由で使える設定を提供する。
41
+
42
+ ### FT18-F2 (LOW, 拡張性): ログレベルをコンストラクタから変更できない
43
+
44
+ `RequestLoggingMiddleware` は常に `logger.info()` を使う。
45
+ ヘルスチェックなど高頻度のパスのログを `DEBUG` に落としたい場合や、
46
+ 特定パスのログを無効化したい場合にコンストラクタパラメータで設定できない。
47
+
48
+ 他のミドルウェア(`ThrottleMiddleware` の `exclude_paths` など)との一貫性が取れていない。
49
+
50
+ **対応案**: `exclude_paths: list[str] | None = None` パラメータを追加して
51
+ 特定パスのログを無効化できるようにする。または `log_level` パラメータで
52
+ デフォルトのログレベルを変更可能にする。
53
+
54
+ ## まとめ
55
+
56
+ 基本的なリクエストロギングは問題なく動作した。`RequestIdMiddleware` との連携も良好。
57
+ 摩擦はテスト時の `caplog` 非対応(F1: MEDIUM)と拡張性(F2: LOW)の2点。
58
+
59
+ F1 は structlog の設計に起因するが、テスト用ヘルパーを提供することで改善できる。
60
+ F2 は他のミドルウェアとの一貫性の問題。
@@ -0,0 +1,98 @@
1
+ # FT19: problem_details_response() RFC 9457 実運用検証
2
+
3
+ **日付**: 2026-05-20
4
+ **テーマ**: `problem_details_response()` を使って RFC 9457 準拠のエラー応答を実装する
5
+ **FT アプリ**: `/home/xi/docker/nene2-python-FT/ft19-problem-details/`
6
+
7
+ ---
8
+
9
+ ## 目的
10
+
11
+ `nene2.http.problem_details_response()` を実際のアプリ(記事 API)に組み込み、
12
+ 401/404/422 のエラー応答を RFC 9457 形式で返すパターンを検証する。
13
+ また、`ErrorHandlerMiddleware` との統合状況を確認する。
14
+
15
+ ---
16
+
17
+ ## 実施内容
18
+
19
+ 記事 CRUD API(`/articles/{article_id}`)を作成し、以下のシナリオで `problem_details_response()` を使用:
20
+
21
+ - **401 Unauthorized**: X-API-Key ヘッダー未提供または無効
22
+ - **404 Not Found**: 存在しない記事 ID へのアクセス(`extra={"article_id": article_id}` 付き)
23
+ - **422 Validation Failed**: 空のタイトルで記事作成(`extra={"errors": [...]}` 付き)
24
+
25
+ ---
26
+
27
+ ## テスト結果
28
+
29
+ ### test_app.py(正常系・準拠確認)
30
+ | テスト | 結果 |
31
+ |---|---|
32
+ | test_get_article_success | PASS |
33
+ | test_get_article_not_found_returns_problem_details | PASS |
34
+ | test_unauthorized_returns_problem_details | PASS |
35
+ | test_validation_error_returns_problem_details | PASS |
36
+ | test_problem_details_type_is_absolute_url | PASS |
37
+
38
+ ### test_friction.py(摩擦点確認)
39
+ | テスト | 結果 | 摩擦 |
40
+ |---|---|---|
41
+ | test_base_url_is_not_customizable_per_call | PASS | あり |
42
+ | test_validation_exception_and_problem_details_not_integrated | PASS | なし(当初の予想と逆) |
43
+ | test_no_typed_problem_type_constants | PASS | あり |
44
+
45
+ ---
46
+
47
+ ## 発見した摩擦点
48
+
49
+ ### FT19-F1: プロジェクト全体の base_url を一箇所で設定できない
50
+
51
+ **概要**: `problem_details_response()` は `base_url` パラメータを持つが、
52
+ プロジェクト全体で一箇所に設定する仕組みがない。
53
+ 複数のハンドラーで同じ `base_url` を保証するには、
54
+ 毎回引数で渡すか、プロジェクトでラッパー関数を書く必要がある。
55
+
56
+ **影響**: 大規模プロジェクトで `base_url` の不統一が発生しやすい。
57
+
58
+ **期待する解決策**: `configure_problem_details(base_url: str)` のような
59
+ モジュールレベルの設定関数を提供する。
60
+
61
+ ---
62
+
63
+ ### FT19-F2: ErrorHandlerMiddleware と problem_details_response() の統合(摩擦なし)
64
+
65
+ 当初「`ErrorHandlerMiddleware` が `ValidationException` を処理する際に
66
+ `application/json` を返すのではないか」と懸念していたが、
67
+ 実際には `problem_details_response()` を内部で使っており、
68
+ `application/problem+json` が正しく返される。
69
+
70
+ **結論**: 摩擦なし。むしろ正しく統合されている。
71
+
72
+ ---
73
+
74
+ ### FT19-F3: problem_type 文字列に型安全な定数がない
75
+
76
+ **概要**: `problem_type` は文字列リテラルで渡すため、タイポがあっても
77
+ mypy では検出されない。同じプロジェクト内で `"not-found"` と `"not_found"` が
78
+ 混在してもエラーにならない。
79
+
80
+ **影響**: 大規模プロジェクトで problem_type の不統一が起きやすい。
81
+
82
+ **期待する解決策**: 標準的な problem_type 定数のドキュメント化、
83
+ またはユーザーが StrEnum を使うパターンのガイダンス。
84
+ (フレームワーク側で全 problem_type を定義するのは over-engineering のため、
85
+ ドキュメントやパターン提示が適切)
86
+
87
+ ---
88
+
89
+ ## まとめ
90
+
91
+ `problem_details_response()` は RFC 9457 に準拠しており、`ErrorHandlerMiddleware` と
92
+ も正しく統合されている。実用上の摩擦は以下の 2 点:
93
+
94
+ 1. **プロジェクト全体の base_url 設定機構がない** → Issue 化して修正対象
95
+ 2. **problem_type 文字列の型安全性がない** → Issue 化してドキュメント対応
96
+
97
+ v1.4.0 で追加された `exclude_paths`、FT15-FT18 で追加された各ミドルウェア改善も
98
+ 本 FT で間接的に動作確認できた。
@@ -0,0 +1,94 @@
1
+ # FT20: ThrottleMiddleware 実運用検証
2
+
3
+ **日付**: 2026-05-20
4
+ **テーマ**: `ThrottleMiddleware` を使ったレート制限 API の実運用検証
5
+ **FT アプリ**: `/home/xi/docker/nene2-python-FT/ft20-throttle/`
6
+
7
+ ---
8
+
9
+ ## 目的
10
+
11
+ `nene2.middleware.ThrottleMiddleware` を実際のアプリ(公開 API + ヘルスチェック)に組み込み、
12
+ レート制限の動作確認と摩擦点を発見する。
13
+
14
+ ---
15
+
16
+ ## 実施内容
17
+
18
+ - `limit=3, window=60` のレート制限を設定した FastAPI アプリを作成
19
+ - `/health` を `exclude_paths` で除外
20
+ - `/api/data`、`/api/expensive` の 2 エンドポイントにレート制限を適用
21
+
22
+ ---
23
+
24
+ ## テスト結果
25
+
26
+ ### test_app.py(正常系・機能確認)
27
+ | テスト | 結果 |
28
+ |---|---|
29
+ | test_health_check_is_not_rate_limited | PASS |
30
+ | test_requests_within_limit_succeed | PASS |
31
+ | test_requests_exceeding_limit_return_429 | PASS |
32
+ | test_rate_limit_429_includes_retry_after_header | PASS |
33
+ | test_rate_limit_applies_across_different_endpoints | PASS |
34
+
35
+ ### test_friction.py(摩擦点確認)
36
+ | テスト | 結果 | 摩擦 |
37
+ |---|---|---|
38
+ | test_no_rate_limit_headers_on_successful_responses | PASS | あり |
39
+ | test_no_per_path_rate_limits | PASS | あり |
40
+ | test_memory_not_cleaned_up_over_time | PASS | あり |
41
+
42
+ ---
43
+
44
+ ## 発見した摩擦点
45
+
46
+ ### FT20-F1: 通常レスポンスに X-RateLimit-* ヘッダーが付かない
47
+
48
+ **概要**: 429 応答には `Retry-After` ヘッダーが付くが、
49
+ 通常のレスポンスには `X-RateLimit-Limit`、`X-RateLimit-Remaining`、`X-RateLimit-Reset`
50
+ が付かない。
51
+
52
+ **影響**: クライアントが自分のレート制限状況をリアルタイムで把握できない。
53
+ SDK やクライアントが事前にスロットリングする「adaptive throttling」が実装できない。
54
+
55
+ **期待する解決策**: 全レスポンスに `X-RateLimit-*` ヘッダーを付与するオプションを追加。
56
+
57
+ ---
58
+
59
+ ### FT20-F2: エンドポイントごとに異なるレート制限を設定できない
60
+
61
+ **概要**: `ThrottleMiddleware` は全エンドポイントで同一の `limit`/`window` のみ対応。
62
+ コスト大きなエンドポイント(`/api/expensive`: 10req/min)と軽いエンドポイント
63
+ (`/api/data`: 100req/min)で異なる制限を設定できない。
64
+
65
+ **影響**: 実運用では計算コストの重いエンドポイントを個別に絞りたいケースが多い。
66
+
67
+ **期待する解決策**: `path_limits: dict[str, int] | None = None` のようなパスごとの
68
+ レート制限設定パラメータを追加。
69
+
70
+ ---
71
+
72
+ ### FT20-F3: 古い IP エントリがメモリから削除されない
73
+
74
+ **概要**: `_counts` dict のエントリは、ウィンドウ経過後もメモリに残り続ける。
75
+ リクエスト時に古いエントリを上書きするだけで、削除はしない設計。
76
+
77
+ **影響**: 長時間稼働するサーバーでユニーク IP が多い場合、メモリが増加し続ける。
78
+
79
+ **期待する解決策**: ウィンドウ経過後の古いエントリを定期的に削除する
80
+ クリーンアップ機構(ローリングクリーンアップや LRU キャッシュ制限など)を追加。
81
+
82
+ ---
83
+
84
+ ## まとめ
85
+
86
+ `ThrottleMiddleware` の基本機能(IP ベースの固定ウィンドウレート制限、429 応答、
87
+ `Retry-After` ヘッダー、`exclude_paths`)は問題なく動作する。
88
+ 実運用でよく必要になる以下の 3 点が摩擦として発見された:
89
+
90
+ 1. **`X-RateLimit-*` ヘッダーの欠如** → クライアントが制限状況を把握できない
91
+ 2. **パスごとのレート制限が不可** → 計算コストの差があるエンドポイントの制御が難しい
92
+ 3. **古いエントリのメモリ残留** → 長時間稼働時のメモリリーク懸念
93
+
94
+ F1 (X-RateLimit headers) が最も実装インパクトが高く、今回の修正対象とする。
@@ -0,0 +1,104 @@
1
+ # FT21: DomainExceptionHandler 実運用検証
2
+
3
+ **日付**: 2026-05-20
4
+ **テーマ**: `DomainExceptionHandlerProtocol` を使ったカスタム例外ハンドリングの実運用検証
5
+ **FT アプリ**: `/home/xi/docker/nene2-python-FT/ft21-domain-exception/`
6
+
7
+ ---
8
+
9
+ ## 目的
10
+
11
+ `nene2.middleware.ErrorHandlerMiddleware` の `domain_handlers` オプションを使い、
12
+ 複数のカスタムドメイン例外を Problem Details に変換するパターンを検証する。
13
+
14
+ ---
15
+
16
+ ## 実施内容
17
+
18
+ ブログ記事 API(`/posts/{post_id}`)を作成し、以下のドメイン例外を実装:
19
+
20
+ - `PostNotFoundError` → 404 problem-details
21
+ - `PostAccessDeniedError` → 403 problem-details
22
+ - `PostAlreadyPublishedError` → 409 problem-details
23
+
24
+ 各例外に対応する `DomainExceptionHandlerProtocol` 実装クラスを作成し、
25
+ `ErrorHandlerMiddleware(domain_handlers=[...])` に登録。
26
+
27
+ ---
28
+
29
+ ## テスト結果
30
+
31
+ ### test_app.py(正常系・機能確認)
32
+ | テスト | 結果 |
33
+ |---|---|
34
+ | test_get_existing_post_returns_200 | PASS |
35
+ | test_get_nonexistent_post_returns_404_problem_details | PASS |
36
+ | test_get_other_users_post_returns_403 | PASS |
37
+ | test_publish_already_published_returns_409 | PASS |
38
+ | test_publish_unpublished_post_succeeds | PASS |
39
+
40
+ ### test_friction.py(摩擦点確認)
41
+ | テスト | 結果 | 摩擦 |
42
+ |---|---|---|
43
+ | test_no_base_class_for_domain_exception_handlers | PASS | あり |
44
+ | test_handler_registration_requires_list_at_middleware_init | PASS | あり(軽微) |
45
+ | test_unregistered_exception_falls_through_to_500 | PASS | あり(仕様だが摩擦) |
46
+
47
+ ---
48
+
49
+ ## 発見した摩擦点
50
+
51
+ ### FT21-F1: DomainExceptionHandler のボイラープレートが多い
52
+
53
+ **概要**: `DomainExceptionHandlerProtocol` を満たすクラスを毎回 `handles()`/`handle()` の
54
+ 2 メソッドで実装する必要がある。多くのケースでパターンは同じ:
55
+ 1. `isinstance()` チェック
56
+ 2. `problem_details_response()` を呼ぶ
57
+
58
+ **影響**: 3 つのドメイン例外に対して 3 つのハンドラークラスを書かなければならず、
59
+ コード量が増える。
60
+
61
+ **期待する解決策**:
62
+ ```python
63
+ # 現状(各例外ごとにクラスが必要)
64
+ class PostNotFoundHandler:
65
+ def handles(self, exc: Exception) -> bool:
66
+ return isinstance(exc, PostNotFoundError)
67
+ def handle(self, exc: Exception) -> Response:
68
+ return problem_details_response("post-not-found", "Not Found", 404)
69
+
70
+ # 期待: ファクトリで一行
71
+ handler = SimpleDomainHandler(PostNotFoundError, "post-not-found", "Post Not Found", 404)
72
+ ```
73
+
74
+ ---
75
+
76
+ ### FT21-F2: ハンドラーをミドルウェア初期化時にしか登録できない
77
+
78
+ **概要**: `ErrorHandlerMiddleware` に `register_handler()` や `add_handler()` のような
79
+ 動的登録メソッドがないため、ミドルウェア初期化時に全ハンドラーをリストで渡す必要がある。
80
+
81
+ **影響**: ドメインモジュールが分散している場合、一箇所に集めて渡す必要がある。
82
+ (これは設計上妥当とも言えるが、大規模プロジェクトでは不便)
83
+
84
+ **判断**: 初期化時一括登録は依存関係が明示的で好ましい設計のため、今回は修正しない。
85
+
86
+ ---
87
+
88
+ ### FT21-F3: 未登録ドメイン例外は 500 にフォールスルーする
89
+
90
+ **概要**: `domain_handlers` への登録を忘れると 500 応答になる。
91
+ ログを確認しないと原因が分からない。
92
+
93
+ **判断**: `debug=True` で例外メッセージが `detail` に含まれるため、
94
+ 開発中は `ErrorHandlerMiddleware(debug=True)` を使えばデバッグ可能。
95
+ ドキュメントに明記する対応が適切。
96
+
97
+ ---
98
+
99
+ ## まとめ
100
+
101
+ `DomainExceptionHandlerProtocol` + `ErrorHandlerMiddleware` の組み合わせは機能するが、
102
+ 各例外ごとにクラスを 1 つ書く必要があるボイラープレートが摩擦の主な原因。
103
+
104
+ `SimpleDomainHandler` ファクトリを追加することで DX が大幅に向上する(FT21-F1 → Issue化・修正対象)。
@@ -0,0 +1,87 @@
1
+ # FT22: HealthCheckProtocol 実運用検証
2
+
3
+ **日付**: 2026-05-20
4
+ **テーマ**: `HealthCheckProtocol` を使ったヘルスチェックエンドポイントの実運用検証
5
+ **FT アプリ**: `/home/xi/docker/nene2-python-FT/ft22-health-check/`
6
+
7
+ ---
8
+
9
+ ## 目的
10
+
11
+ `nene2.http.HealthCheckProtocol` と `DatabaseHealthCheck` を使って
12
+ DB + 外部サービスを組み合わせた `/health` エンドポイントを実装するパターンを検証する。
13
+
14
+ ---
15
+
16
+ ## 実施内容
17
+
18
+ - SQLite in-memory DB の `DatabaseHealthCheck` を組み込み
19
+ - 外部 API の可用性を表す `ExternalApiHealthCheck`(モック)を実装
20
+ - 両者を組み合わせる `CompositeCheck`(手動実装)を作成
21
+ - `/health` エンドポイントを手動で FastAPI に登録
22
+
23
+ ---
24
+
25
+ ## テスト結果
26
+
27
+ ### test_app.py(正常系・機能確認)
28
+ | テスト | 結果 |
29
+ |---|---|
30
+ | test_health_returns_200_when_all_checks_pass | PASS |
31
+ | test_health_returns_503_when_service_unavailable | PASS |
32
+ | test_database_check_passes | PASS |
33
+ | test_api_data_endpoint_works | PASS |
34
+
35
+ ### test_friction.py(摩擦点確認)
36
+ | テスト | 結果 | 摩擦 |
37
+ |---|---|---|
38
+ | test_no_composite_health_check_provided | PASS | あり |
39
+ | test_health_endpoint_not_integrated_with_framework | PASS | あり |
40
+ | test_health_status_checks_is_flat_string_dict | PASS | あり(軽微) |
41
+
42
+ ---
43
+
44
+ ## 発見した摩擦点
45
+
46
+ ### FT22-F1: 複数チェックを集約する CompositeHealthCheck がない
47
+
48
+ **概要**: DB チェック + 外部サービスチェックなど複数の `HealthCheckProtocol` を
49
+ 組み合わせる場合、毎回集約クラスを手書きする必要がある。
50
+
51
+ **影響**: 実運用では複数チェックの組み合わせが一般的で、
52
+ その度に同じ集約ロジックを再実装することになる。
53
+
54
+ **期待する解決策**: `CompositeHealthCheck(checks: list[HealthCheckProtocol])` クラスを
55
+ `nene2.http` に追加する。
56
+
57
+ ---
58
+
59
+ ### FT22-F2: /health エンドポイントの FastAPI 登録ヘルパーがない
60
+
61
+ **概要**: `HealthCheckProtocol` を実装しても、それを `/health` エンドポイントとして
62
+ FastAPI に登録する仕組みがない。毎回同じパターンのエンドポイント定義を手書きする必要がある。
63
+
64
+ **影響**: ボイラープレートが多い(status_code 分岐、JSON レスポンス形式など)。
65
+
66
+ **期待する解決策**: `CompositeHealthCheck` 自体に FastAPI ルーターを生成するメソッドを
67
+ 追加するより、`CompositeHealthCheck` があれば残りは app.py 側で 5 行で済むため、
68
+ 今回は F1 の修正で十分と判断。
69
+
70
+ ---
71
+
72
+ ### FT22-F3: HealthStatus.checks が dict[str, str] のみ(軽微)
73
+
74
+ **概要**: `HealthStatus.checks` の型が `dict[str, str]` のため、
75
+ レイテンシや接続プール情報などの構造化詳細を含めることができない。
76
+
77
+ **判断**: RFC ヘルスチェックの標準的な形式は文字列ステータスで十分なため、
78
+ 現状の型で問題なし。要件があれば `extra` フィールドを追加する方向で対応。
79
+
80
+ ---
81
+
82
+ ## まとめ
83
+
84
+ `HealthCheckProtocol` と `DatabaseHealthCheck` は機能するが、
85
+ 複数チェックの集約クラスがないため実運用で毎回同じコードを書く必要がある。
86
+
87
+ `CompositeHealthCheck` の追加が最も有効な改善(FT22-F1 → Issue化・修正対象)。
@@ -0,0 +1,80 @@
1
+ # FT23: RequestSizeLimitMiddleware 実運用検証
2
+
3
+ **日付**: 2026-05-20
4
+ **テーマ**: `RequestSizeLimitMiddleware` を使った大容量リクエスト拒否の実運用検証
5
+ **FT アプリ**: `/home/xi/docker/nene2-python-FT/ft23-size-limit/`
6
+
7
+ ---
8
+
9
+ ## 目的
10
+
11
+ `nene2.middleware.RequestSizeLimitMiddleware` をドキュメントアップロード API に組み込み、
12
+ サイズ制限の動作確認と摩擦点を発見する。
13
+
14
+ ---
15
+
16
+ ## 実施内容
17
+
18
+ - `max_bytes=1024`(1KB)の制限を設定した API を作成
19
+ - `/upload/large` を `exclude_paths` で除外(大容量ファイル用)
20
+ - 通常の JSON エンドポイント (`/api/note`) と小容量アップロード (`/upload/small`) でテスト
21
+
22
+ ---
23
+
24
+ ## テスト結果
25
+
26
+ ### test_app.py(正常系・機能確認)
27
+ | テスト | 結果 |
28
+ |---|---|
29
+ | test_small_request_passes | PASS |
30
+ | test_large_request_to_normal_endpoint_returns_413 | PASS |
31
+ | test_excluded_path_accepts_large_request | PASS |
32
+ | test_small_upload_to_non_excluded_endpoint_passes | PASS |
33
+
34
+ ### test_friction.py(摩擦点確認)
35
+ | テスト | 結果 | 摩擦 |
36
+ |---|---|---|
37
+ | test_no_per_path_size_limits | PASS | あり |
38
+ | test_413_response_does_not_include_max_bytes_in_structured_field | PASS | あり |
39
+ | test_content_length_header_absent_still_checked | PASS | なし(正しい動作) |
40
+
41
+ ---
42
+
43
+ ## 発見した摩擦点
44
+
45
+ ### FT23-F1: エンドポイントごとに異なるサイズ制限を設定できない
46
+
47
+ **概要**: ThrottleMiddleware と同じ問題。全エンドポイントに同一の `max_bytes` しか設定できない。
48
+
49
+ **判断**: FT20 の ThrottleMiddleware と同様の問題で、別 Issue (#222) として既存。
50
+ 今回は重複 Issue を作成しない。
51
+
52
+ ---
53
+
54
+ ### FT23-F2: 413 レスポンスの制限値が構造化フィールドで返されない
55
+
56
+ **概要**: 413 応答の `detail` フィールドに "Request body must not exceed 1024 bytes." と
57
+ テキストで含まれるが、`max_bytes: 1024` のような構造化フィールドがない。
58
+
59
+ **影響**: クライアント SDK が制限値を機械的に読み取れない。
60
+ 人間が読むログやエラーメッセージには十分だが、プログラム的な利用が難しい。
61
+
62
+ **期待する解決策**: `_too_large()` レスポンスに `extra={"max_bytes": self._max_bytes}` を追加。
63
+
64
+ ---
65
+
66
+ ### FT23-F3: Content-Length なしでも body チェックが動作する(摩擦なし)
67
+
68
+ チャンクエンコードで Content-Length ヘッダーがない場合でも、
69
+ 実際の body を読んでサイズチェックするため、バイパスできない。これは正しい動作。
70
+
71
+ ---
72
+
73
+ ## まとめ
74
+
75
+ `RequestSizeLimitMiddleware` の基本機能(Content-Length チェック、body チェック、
76
+ `exclude_paths`、413 Problem Details 応答)は問題なく動作する。
77
+
78
+ 摩擦点:
79
+ 1. **413 レスポンスに max_bytes 構造化フィールドがない** → Issue 化・修正対象
80
+ 2. **パスごとのサイズ制限が不可** → 既存 Issue (#222) と共通問題
@@ -0,0 +1,89 @@
1
+ # FT24: AppSettings 実運用検証
2
+
3
+ **日付**: 2026-05-20
4
+ **テーマ**: `AppSettings` を使った環境変数ベースの設定管理パターンの実運用検証
5
+ **FT アプリ**: `/home/xi/docker/nene2-python-FT/ft24-app-settings/`
6
+
7
+ ---
8
+
9
+ ## 目的
10
+
11
+ `nene2.config.AppSettings` を実際のアプリに組み込み、
12
+ 環境変数から設定を読み込んでミドルウェアを設定するパターンを検証する。
13
+
14
+ ---
15
+
16
+ ## 実施内容
17
+
18
+ `AppSettings` の各フィールドを使って以下を設定:
19
+ - `ErrorHandlerMiddleware(debug=settings.app_debug)`
20
+ - `RequestSizeLimitMiddleware(max_bytes=settings.max_body_size)`
21
+ - `ThrottleMiddleware(limit=settings.throttle_limit, window=settings.throttle_window)` (conditional)
22
+ - `SecurityHeadersMiddleware` (conditional)
23
+
24
+ ---
25
+
26
+ ## テスト結果
27
+
28
+ ### test_app.py(正常系・機能確認)
29
+ | テスト | 結果 |
30
+ |---|---|
31
+ | test_health_endpoint_returns_env | PASS |
32
+ | test_debug_false_by_default | PASS |
33
+ | test_settings_loaded_from_env_vars | PASS |
34
+ | test_throttle_disabled_via_settings | PASS |
35
+ | test_max_body_size_from_settings | PASS |
36
+ | test_db_url_sqlite_default | PASS |
37
+ | test_db_url_mysql_format | PASS |
38
+
39
+ ### test_friction.py(摩擦点確認)
40
+ | テスト | 結果 | 摩擦 |
41
+ |---|---|---|
42
+ | test_list_fields_not_parseable_from_env_string | PASS | あり(ドキュメント) |
43
+ | test_no_log_level_setting | PASS | あり |
44
+ | test_no_middleware_factory_helper | PASS | あり(設計上の判断) |
45
+
46
+ ---
47
+
48
+ ## 発見した摩擦点
49
+
50
+ ### FT24-F1: list[str] フィールドを環境変数で設定する方法がドキュメントにない
51
+
52
+ **概要**: `cors_origins`、`bearer_tokens`、`api_keys` は `list[str]` 型だが、
53
+ 環境変数から設定するには JSON 形式 (`["a","b"]`) が必要。
54
+ 単純な `"token1,token2"` ではパースされない。
55
+
56
+ **判断**: pydantic-settings の標準動作のため、リファレンスドキュメントに記載する。
57
+
58
+ ---
59
+
60
+ ### FT24-F2: ログレベルの設定フィールドがない
61
+
62
+ **概要**: `app_debug: bool` しかなく、`INFO`/`WARNING`/`ERROR` の粒度制御ができない。
63
+
64
+ **影響**: ステージング環境で `INFO` ログを出しつつ、本番では `WARNING` に絞りたいが、
65
+ 現在は `app_debug=true` でしか詳細ログを出す方法がない。
66
+
67
+ **期待する解決策**: `log_level: str = "INFO"` フィールドを追加。
68
+
69
+ ---
70
+
71
+ ### FT24-F3: AppSettings からミドルウェアを自動設定するヘルパーがない
72
+
73
+ **概要**: AppSettings の設定値をミドルウェアに適用するには、
74
+ 毎回条件分岐付きのボイラープレートを手書きする必要がある。
75
+
76
+ **判断**: `configure_middleware(app, settings)` ヘルパーは「薄い HTTP 層」の設計哲学に反し、
77
+ フレームワークの主張が強くなりすぎる。FT アプリ側でパターンを提示する対応が適切。
78
+ 今回はドキュメント追加のみ。
79
+
80
+ ---
81
+
82
+ ## まとめ
83
+
84
+ `AppSettings` は環境変数から型安全に設定を読み込む機能として正しく動作する。
85
+ 実運用での摩擦は:
86
+
87
+ 1. **list[str] 環境変数の書き方がわかりにくい** → ドキュメント追加対応
88
+ 2. **log_level フィールドがない** → Issue 化・修正対象
89
+ 3. **ミドルウェア自動設定ヘルパーがない** → 設計上の判断で修正しない