nene2-python 1.8.26__tar.gz → 1.8.28__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 (277) hide show
  1. {nene2_python-1.8.26 → nene2_python-1.8.28}/CHANGELOG.md +23 -0
  2. {nene2_python-1.8.26 → nene2_python-1.8.28}/PKG-INFO +1 -1
  3. nene2_python-1.8.28/docs/field-trials/2026-05-field-trial-81.md +198 -0
  4. nene2_python-1.8.28/docs/field-trials/2026-05-field-trial-82.md +198 -0
  5. nene2_python-1.8.28/docs/field-trials/2026-05-field-trial-83.md +223 -0
  6. {nene2_python-1.8.26 → nene2_python-1.8.28}/pyproject.toml +1 -1
  7. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/http/__init__.py +2 -1
  8. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/http/pagination.py +19 -1
  9. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/middleware/setup.py +47 -3
  10. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/nene2/http/test_pagination.py +22 -1
  11. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/nene2/middleware/test_setup_middlewares.py +65 -0
  12. {nene2_python-1.8.26 → nene2_python-1.8.28}/uv.lock +1 -1
  13. {nene2_python-1.8.26 → nene2_python-1.8.28}/.env.example +0 -0
  14. {nene2_python-1.8.26 → nene2_python-1.8.28}/.github/workflows/ci.yml +0 -0
  15. {nene2_python-1.8.26 → nene2_python-1.8.28}/.github/workflows/docs.yml +0 -0
  16. {nene2_python-1.8.26 → nene2_python-1.8.28}/.github/workflows/publish.yml +0 -0
  17. {nene2_python-1.8.26 → nene2_python-1.8.28}/.gitignore +0 -0
  18. {nene2_python-1.8.26 → nene2_python-1.8.28}/.vitepress/config.mts +0 -0
  19. {nene2_python-1.8.26 → nene2_python-1.8.28}/.vitepress/theme/custom.css +0 -0
  20. {nene2_python-1.8.26 → nene2_python-1.8.28}/.vitepress/theme/index.ts +0 -0
  21. {nene2_python-1.8.26 → nene2_python-1.8.28}/AGENTS.md +0 -0
  22. {nene2_python-1.8.26 → nene2_python-1.8.28}/CLAUDE.md +0 -0
  23. {nene2_python-1.8.26 → nene2_python-1.8.28}/Dockerfile +0 -0
  24. {nene2_python-1.8.26 → nene2_python-1.8.28}/LICENSE +0 -0
  25. {nene2_python-1.8.26 → nene2_python-1.8.28}/README.md +0 -0
  26. {nene2_python-1.8.26 → nene2_python-1.8.28}/alembic/README +0 -0
  27. {nene2_python-1.8.26 → nene2_python-1.8.28}/alembic/env.py +0 -0
  28. {nene2_python-1.8.26 → nene2_python-1.8.28}/alembic/script.py.mako +0 -0
  29. {nene2_python-1.8.26 → nene2_python-1.8.28}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
  30. {nene2_python-1.8.26 → nene2_python-1.8.28}/alembic.ini +0 -0
  31. {nene2_python-1.8.26 → nene2_python-1.8.28}/compose.yaml +0 -0
  32. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/adr/0001-toolchain.md +0 -0
  33. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/adr/0002-clean-architecture.md +0 -0
  34. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/adr/0003-security-first.md +0 -0
  35. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/adr/0004-ai-first-design.md +0 -0
  36. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/adr/0005-logging.md +0 -0
  37. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/adr/0006-rate-limiting.md +0 -0
  38. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/adr/0009-mcp-design.md +0 -0
  39. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/adr/0010-async-use-case.md +0 -0
  40. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
  41. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/de/index.md +0 -0
  42. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/de/tutorials/getting-started.md +0 -0
  43. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/explanation/architecture.md +0 -0
  44. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/explanation/design-philosophy.md +0 -0
  45. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-1.md +0 -0
  46. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-10.md +0 -0
  47. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-11.md +0 -0
  48. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-12.md +0 -0
  49. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-13.md +0 -0
  50. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-14.md +0 -0
  51. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-15.md +0 -0
  52. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-16.md +0 -0
  53. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-17.md +0 -0
  54. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-18.md +0 -0
  55. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-19.md +0 -0
  56. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-2.md +0 -0
  57. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-20.md +0 -0
  58. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-21.md +0 -0
  59. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-22.md +0 -0
  60. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-23.md +0 -0
  61. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-24.md +0 -0
  62. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-25.md +0 -0
  63. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-26.md +0 -0
  64. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-27.md +0 -0
  65. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-28.md +0 -0
  66. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-29.md +0 -0
  67. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-3.md +0 -0
  68. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-30.md +0 -0
  69. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-31.md +0 -0
  70. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-32.md +0 -0
  71. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-33.md +0 -0
  72. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-34.md +0 -0
  73. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-35.md +0 -0
  74. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-36.md +0 -0
  75. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-37.md +0 -0
  76. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-38.md +0 -0
  77. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-39.md +0 -0
  78. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-4.md +0 -0
  79. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-40.md +0 -0
  80. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-41.md +0 -0
  81. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-42.md +0 -0
  82. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-43.md +0 -0
  83. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-44.md +0 -0
  84. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-45.md +0 -0
  85. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-46.md +0 -0
  86. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-47.md +0 -0
  87. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-48.md +0 -0
  88. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-49.md +0 -0
  89. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-5.md +0 -0
  90. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-50.md +0 -0
  91. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-51.md +0 -0
  92. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-52.md +0 -0
  93. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-53.md +0 -0
  94. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-54.md +0 -0
  95. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-55.md +0 -0
  96. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-56.md +0 -0
  97. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-57.md +0 -0
  98. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-58.md +0 -0
  99. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-59.md +0 -0
  100. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-6.md +0 -0
  101. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-60.md +0 -0
  102. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-61.md +0 -0
  103. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-62.md +0 -0
  104. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-63.md +0 -0
  105. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-64.md +0 -0
  106. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-65.md +0 -0
  107. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-66.md +0 -0
  108. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-67.md +0 -0
  109. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-68.md +0 -0
  110. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-69.md +0 -0
  111. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-7.md +0 -0
  112. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-70.md +0 -0
  113. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-71.md +0 -0
  114. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-72.md +0 -0
  115. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-73.md +0 -0
  116. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-74.md +0 -0
  117. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-75.md +0 -0
  118. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-76.md +0 -0
  119. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-77.md +0 -0
  120. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-78.md +0 -0
  121. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-79.md +0 -0
  122. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-8.md +0 -0
  123. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-80.md +0 -0
  124. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/field-trials/2026-05-field-trial-9.md +0 -0
  125. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/fr/index.md +0 -0
  126. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/fr/tutorials/getting-started.md +0 -0
  127. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/how-to/add-new-domain.md +0 -0
  128. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/how-to/async-use-case.md +0 -0
  129. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/how-to/configure-auth.md +0 -0
  130. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/how-to/middleware-stack.md +0 -0
  131. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/how-to/new-project.md +0 -0
  132. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/how-to/problem-details.md +0 -0
  133. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/how-to/run-tests.md +0 -0
  134. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/how-to/sqlalchemy-repository.md +0 -0
  135. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/how-to/validation.md +0 -0
  136. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/howto/mcp-setup.md +0 -0
  137. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/index.md +0 -0
  138. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/ja/explanation/architecture.md +0 -0
  139. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/ja/explanation/design-philosophy.md +0 -0
  140. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/ja/how-to/add-new-domain.md +0 -0
  141. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/ja/how-to/configure-auth.md +0 -0
  142. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/ja/how-to/new-project.md +0 -0
  143. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/ja/how-to/run-tests.md +0 -0
  144. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
  145. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/ja/howto/mcp-setup.md +0 -0
  146. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/ja/index.md +0 -0
  147. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/ja/reference/api.md +0 -0
  148. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/ja/reference/configuration.md +0 -0
  149. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/ja/reference/framework-modules.md +0 -0
  150. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/ja/tutorials/first-domain.md +0 -0
  151. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/ja/tutorials/getting-started.md +0 -0
  152. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/pt-br/index.md +0 -0
  153. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/pt-br/tutorials/getting-started.md +0 -0
  154. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/reference/api.md +0 -0
  155. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/reference/configuration.md +0 -0
  156. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/reference/framework-modules.md +0 -0
  157. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/roadmap.md +0 -0
  158. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/todo/current.md +0 -0
  159. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/tutorials/first-domain.md +0 -0
  160. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/tutorials/getting-started.md +0 -0
  161. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/zh/index.md +0 -0
  162. {nene2_python-1.8.26 → nene2_python-1.8.28}/docs/zh/tutorials/getting-started.md +0 -0
  163. {nene2_python-1.8.26 → nene2_python-1.8.28}/package-lock.json +0 -0
  164. {nene2_python-1.8.26 → nene2_python-1.8.28}/package.json +0 -0
  165. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/example/__init__.py +0 -0
  166. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/example/__main__.py +0 -0
  167. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/example/app.py +0 -0
  168. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/example/comment/__init__.py +0 -0
  169. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/example/comment/entity.py +0 -0
  170. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/example/comment/exceptions.py +0 -0
  171. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/example/comment/handler.py +0 -0
  172. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/example/comment/repository.py +0 -0
  173. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/example/comment/sqlalchemy_repository.py +0 -0
  174. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/example/comment/use_case.py +0 -0
  175. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/example/mcp.py +0 -0
  176. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/example/note/__init__.py +0 -0
  177. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/example/note/async_use_case.py +0 -0
  178. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/example/note/entity.py +0 -0
  179. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/example/note/exceptions.py +0 -0
  180. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/example/note/handler.py +0 -0
  181. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/example/note/repository.py +0 -0
  182. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/example/note/sqlalchemy_repository.py +0 -0
  183. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/example/note/use_case.py +0 -0
  184. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/example/schema.py +0 -0
  185. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/example/tag/__init__.py +0 -0
  186. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/example/tag/entity.py +0 -0
  187. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/example/tag/exceptions.py +0 -0
  188. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/example/tag/handler.py +0 -0
  189. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/example/tag/repository.py +0 -0
  190. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/example/tag/sqlalchemy_repository.py +0 -0
  191. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/example/tag/use_case.py +0 -0
  192. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/__init__.py +0 -0
  193. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/auth/__init__.py +0 -0
  194. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/auth/api_key.py +0 -0
  195. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/auth/bearer_token.py +0 -0
  196. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/auth/exceptions.py +0 -0
  197. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/auth/interfaces.py +0 -0
  198. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/auth/local_verifier.py +0 -0
  199. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/config/__init__.py +0 -0
  200. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/config/settings.py +0 -0
  201. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/database/__init__.py +0 -0
  202. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/database/exceptions.py +0 -0
  203. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/database/health.py +0 -0
  204. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/database/interfaces.py +0 -0
  205. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/database/sqlalchemy_executor.py +0 -0
  206. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/database/utils.py +0 -0
  207. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/http/health.py +0 -0
  208. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/http/problem_details.py +0 -0
  209. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/log/__init__.py +0 -0
  210. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/log/setup.py +0 -0
  211. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/mcp/__init__.py +0 -0
  212. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/mcp/http_client.py +0 -0
  213. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/mcp/server.py +0 -0
  214. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/middleware/__init__.py +0 -0
  215. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/middleware/domain_exception.py +0 -0
  216. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/middleware/error_handler.py +0 -0
  217. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/middleware/request_id.py +0 -0
  218. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/middleware/request_logging.py +0 -0
  219. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/middleware/request_size_limit.py +0 -0
  220. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/middleware/security_headers.py +0 -0
  221. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/middleware/throttle.py +0 -0
  222. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/py.typed +0 -0
  223. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/use_case/__init__.py +0 -0
  224. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/use_case/protocols.py +0 -0
  225. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/validation/__init__.py +0 -0
  226. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/nene2/validation/exceptions.py +0 -0
  227. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/scripts/__init__.py +0 -0
  228. {nene2_python-1.8.26 → nene2_python-1.8.28}/src/scripts/export_openapi.py +0 -0
  229. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/__init__.py +0 -0
  230. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/example/__init__.py +0 -0
  231. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/example/comment/__init__.py +0 -0
  232. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/example/comment/test_comment_http.py +0 -0
  233. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/example/comment/test_comment_repository.py +0 -0
  234. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/example/comment/test_comment_use_case.py +0 -0
  235. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/example/conftest.py +0 -0
  236. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/example/note/__init__.py +0 -0
  237. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/example/note/test_async_note_use_case.py +0 -0
  238. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/example/note/test_list_notes.py +0 -0
  239. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/example/note/test_note_repository.py +0 -0
  240. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/example/tag/__init__.py +0 -0
  241. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/example/tag/test_tag_repository.py +0 -0
  242. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/example/tag/test_tags.py +0 -0
  243. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/example/test_cors.py +0 -0
  244. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/example/test_mcp.py +0 -0
  245. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/nene2/__init__.py +0 -0
  246. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/nene2/auth/__init__.py +0 -0
  247. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/nene2/auth/test_api_key.py +0 -0
  248. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/nene2/auth/test_bearer_token.py +0 -0
  249. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/nene2/auth/test_token_issuer.py +0 -0
  250. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/nene2/config/__init__.py +0 -0
  251. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/nene2/config/test_settings.py +0 -0
  252. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/nene2/database/__init__.py +0 -0
  253. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/nene2/database/test_transaction.py +0 -0
  254. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/nene2/database/test_utils.py +0 -0
  255. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/nene2/http/__init__.py +0 -0
  256. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/nene2/http/test_health.py +0 -0
  257. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/nene2/http/test_problem_details.py +0 -0
  258. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/nene2/log/__init__.py +0 -0
  259. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/nene2/log/test_setup.py +0 -0
  260. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/nene2/mcp/__init__.py +0 -0
  261. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/nene2/mcp/test_http_client.py +0 -0
  262. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/nene2/mcp/test_server.py +0 -0
  263. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/nene2/middleware/__init__.py +0 -0
  264. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/nene2/middleware/test_error_handler.py +0 -0
  265. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/nene2/middleware/test_request_id.py +0 -0
  266. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/nene2/middleware/test_request_logging.py +0 -0
  267. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/nene2/middleware/test_request_size_limit.py +0 -0
  268. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/nene2/middleware/test_security_headers.py +0 -0
  269. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
  270. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/nene2/middleware/test_throttle.py +0 -0
  271. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/nene2/use_case/__init__.py +0 -0
  272. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/nene2/use_case/test_protocols.py +0 -0
  273. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/nene2/use_case/test_run_in_threadpool.py +0 -0
  274. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/nene2/validation/__init__.py +0 -0
  275. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/nene2/validation/test_exceptions.py +0 -0
  276. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/scripts/__init__.py +0 -0
  277. {nene2_python-1.8.26 → nene2_python-1.8.28}/tests/scripts/test_export_openapi.py +0 -0
@@ -5,6 +5,29 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
6
  ---
7
7
 
8
+ ## [1.8.27] — 2026-05-20
9
+
10
+ FT81 フィールドトライアル — CORS 設定パターン検証と setup_middlewares() への CORS 統合。
11
+
12
+ ### Added
13
+ - `setup_middlewares()` に `cors_allowed_origins` / `cors_allow_credentials` / `cors_allow_methods` / `cors_allow_headers` パラメーターを追加 (#348) (FT81)
14
+ — `CORSMiddleware` を最外側に自動配置し、OPTIONS プリフライトが確実に処理される
15
+ — `cors_allowed_origins=["*"]` を渡すと `ValueError` を raise(wildcard 禁止ポリシーの実装強制)
16
+ - Field trial report: `docs/field-trials/2026-05-field-trial-81.md` (FT81)
17
+
18
+ ---
19
+
20
+ ## [1.8.26] — 2026-05-20
21
+
22
+ FT80 フィールドトライアル — LocalMcpServer + HttpxMcpClient MCP E2E 検証と list_tools() 追加。
23
+
24
+ ### Added
25
+ - `LocalMcpServer` に `list_tools()` メソッドを追加 (#342) (FT80)
26
+ — 登録済みツール名の一覧を返す。デバッグ・イントロスペクション用途
27
+ - Field trial report: `docs/field-trials/2026-05-field-trial-80.md` (FT80)
28
+
29
+ ---
30
+
8
31
  ## [1.8.25] — 2026-05-20
9
32
 
10
33
  FT79 フィールドトライアル — RequestLoggingMiddleware の構造化ログ検証と context_extractor 追加。
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nene2-python
3
- Version: 1.8.26
3
+ Version: 1.8.28
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,198 @@
1
+ # FT81: CORS 設定 — setup_middlewares() と CORSMiddleware の組み合わせ
2
+
3
+ **日付**: 2026-05-20
4
+ **テーマ**: setup_middlewares() に CORS サポートがない場合の正しい設定パターン検証
5
+ **バージョン**: v1.8.26
6
+ **FTディレクトリ**: `/home/xi/docker/nene2-python-FT/ft81-cors/`
7
+
8
+ ---
9
+
10
+ ## 概要
11
+
12
+ nene2 の `setup_middlewares()` は CORS をサポートしていないため、
13
+ ブラウザから API を呼び出すアプリで CORS が必要になった際に
14
+ ユーザーは `CORSMiddleware` を手動で追加する必要がある。
15
+ その際、Starlette の LIFO ミドルウェア順序を理解していないと
16
+ OPTIONS プリフライトが正常に動作しない問題を確認した。
17
+
18
+ ---
19
+
20
+ ## 実装パターン
21
+
22
+ ### 正しい CORS 設定(CORS を最外側に配置)
23
+
24
+ ```python
25
+ from fastapi import FastAPI
26
+ from fastapi.middleware.cors import CORSMiddleware
27
+ from nene2.middleware import setup_middlewares
28
+
29
+ ALLOWED_ORIGINS = [
30
+ "https://app.example.com",
31
+ "https://admin.example.com",
32
+ ]
33
+
34
+ app = FastAPI()
35
+
36
+ # ✅ CORS を先に add_middleware → setup_middlewares() 後は LIFO で最外側になる
37
+ app.add_middleware(
38
+ CORSMiddleware,
39
+ allow_origins=ALLOWED_ORIGINS,
40
+ allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
41
+ allow_headers=["Authorization", "Content-Type"],
42
+ allow_credentials=True,
43
+ )
44
+ setup_middlewares(app)
45
+ ```
46
+
47
+ ### 間違ったパターン(CORS が内側になる)
48
+
49
+ ```python
50
+ # ❌ setup_middlewares() の後に CORS を追加すると内側に入る
51
+ app = FastAPI()
52
+ setup_middlewares(app)
53
+ app.add_middleware(CORSMiddleware, allow_origins=["https://app.example.com"])
54
+ # → OPTIONS プリフライトが nene2 ミドルウェアに遮断される可能性がある
55
+ ```
56
+
57
+ ### 禁止パターン(CLAUDE.md ポリシー違反)
58
+
59
+ ```python
60
+ # ❌ CLAUDE.md 明示禁止: allow_origins=["*"]
61
+ app.add_middleware(
62
+ CORSMiddleware,
63
+ allow_origins=["*"], # セキュリティリスク
64
+ )
65
+ ```
66
+
67
+ ---
68
+
69
+ ## 発見した問題
70
+
71
+ ### 問題1: setup_middlewares() に CORS パラメーターがない
72
+
73
+ ```python
74
+ # CORS が必要でも setup_middlewares() のシグネチャに cors パラメーターなし
75
+ setup_middlewares(
76
+ app,
77
+ # cors_allowed_origins=["https://app.example.com"], # 存在しない
78
+ )
79
+ ```
80
+
81
+ ユーザーは `FastAPI.add_middleware(CORSMiddleware, ...)` を直接呼ぶ必要がある。
82
+ FastAPI/Starlette のドキュメントを参照しなければ方法がわからない。
83
+
84
+ ### 問題2: ミドルウェア順序が直感に反する(LIFO)
85
+
86
+ ```python
87
+ # Starlette は LIFO — 最後に add_middleware したものが最外側になる
88
+ # つまり CORS を「最外側にしたい」なら「最初に add する」
89
+
90
+ app.add_middleware(CORSMiddleware, ...) # ← 先に追加 = 最外側(正しい)
91
+ setup_middlewares(app) # ← 後から追加 = 内側
92
+
93
+ # 逆にすると:
94
+ setup_middlewares(app) # ← 先に追加 = 最内側(危険)
95
+ app.add_middleware(CORSMiddleware, ...) # ← 後から追加 = 最外側になってしまう
96
+ ```
97
+
98
+ 「最外側に置きたいなら先に add する」という反直感的な順序。
99
+
100
+ ### 問題3: nene2 が allow_origins=["*"] を禁止しない
101
+
102
+ ```python
103
+ # CLAUDE.md で明示禁止されているが、nene2 フレームワークは検証しない
104
+ app.add_middleware(
105
+ CORSMiddleware,
106
+ allow_origins=["*"], # 禁止ポリシーだが動作してしまう
107
+ )
108
+ ```
109
+
110
+ フレームワークレベルで `ValueError` を raise することも可能だが、
111
+ `setup_middlewares()` を経由しない場合は検証できない。
112
+
113
+ ### 問題4: 複数オリジン・credentials 設定パターンがドキュメントにない
114
+
115
+ 本番アプリでは複数オリジン(本番環境 + ステージング環境)や
116
+ `allow_credentials=True` が必要なケースが多いが、
117
+ nene2 のドキュメントにこのパターンの記載がない。
118
+
119
+ ---
120
+
121
+ ## テスト結果(全13件パス)
122
+
123
+ ```
124
+ test_list_items_returns_200 PASSED
125
+ test_create_item_returns_201 PASSED
126
+ test_cors_allowed_origin_returns_access_control_header PASSED
127
+ test_cors_disallowed_origin_no_access_control_header PASSED
128
+ test_cors_preflight_options_returns_200 PASSED # OPTIONS プリフライト正常動作
129
+ test_cors_preflight_disallowed_origin PASSED
130
+ test_cors_credentials_allowed PASSED
131
+ test_security_headers_present_with_cors PASSED # nene2 ミドルウェアと共存
132
+ test_request_id_present_with_cors PASSED # X-Request-Id と共存
133
+ test_friction_no_cors_in_setup_middlewares PASSED # 摩擦: CORS パラメーターなし
134
+ test_friction_cors_order_matters PASSED # 摩擦: LIFO 順序問題
135
+ test_friction_wildcard_origin_is_insecure PASSED # 摩擦: ["*"] を止めない
136
+ test_friction_multiple_origins_not_documented PASSED # 摩擦: ドキュメント不足
137
+ ```
138
+
139
+ ---
140
+
141
+ ## 摩擦ポイント一覧
142
+
143
+ | ID | 内容 | 深刻度 |
144
+ |---|---|---|
145
+ | F81-1 | `setup_middlewares()` に CORS パラメーターがなく手動追加が必要 | 中 |
146
+ | F81-2 | Starlette の LIFO 順序を知らないと OPTIONS プリフライトが壊れる | 中 |
147
+ | F81-3 | `allow_origins=["*"]` を nene2 が禁止しない(ポリシーのみ) | 低 |
148
+ | F81-4 | 複数オリジン・credentials パターンがドキュメントに未記載 | 低 |
149
+
150
+ ---
151
+
152
+ ## 使用感(主観評価)
153
+
154
+ ### 直感性 ★★★☆☆
155
+
156
+ `setup_middlewares()` を使うと CORS は自分で追加しなければならず、
157
+ しかも「先に add する = 最外側になる」という反直感的な順序ルールがある。
158
+ FastAPI や Express.js、Spring Security の CORS 設定を知っているユーザーでも
159
+ nene2 特有の LIFO 順序で一度はつまずく。
160
+
161
+ ### 実害の深刻さ ★★★☆☆
162
+
163
+ ブラウザからの CORS エラーは「API が動かない」として即座に表面化する。
164
+ 原因が「ミドルウェア順序」であることを特定するのに時間がかかることがある。
165
+ 特に OPTIONS プリフライトが通らないと PUT/DELETE/POST with Auth が全滅する。
166
+
167
+ ### 修正のしやすさ ★★★★★
168
+
169
+ `setup_middlewares()` に `cors_allowed_origins` パラメーターを追加するだけ。
170
+ CORS は最外側(最初の add_middleware)に固定できるため、
171
+ ユーザーが順序を気にする必要がなくなる。
172
+
173
+ ```python
174
+ # 理想の API:
175
+ setup_middlewares(
176
+ app,
177
+ cors_allowed_origins=["https://app.example.com"],
178
+ cors_allow_credentials=True,
179
+ )
180
+ ```
181
+
182
+ ### 総合コメント
183
+
184
+ CORS は「作ったら必ず必要になる」機能でありながら、
185
+ nene2 の `setup_middlewares()` には組み込まれていない。
186
+ `["*"]` 禁止は CLAUDE.md のポリシーとして正しいが、
187
+ フレームワークが強制しないと誰かが違反する。
188
+ `cors_allowed_origins` を追加してワイルドカードを `ValueError` にすれば
189
+ セキュリティポリシーをコードで強制できる。
190
+
191
+ ---
192
+
193
+ ## 推奨アクション
194
+
195
+ 1. **Issue**: `setup_middlewares()` に `cors_allowed_origins` パラメーターを追加
196
+ — `allow_origins=["*"]` を渡した場合に `ValueError` を raise
197
+ — CORS を最外側に自動配置(ユーザーが順序を意識しなくていい)
198
+ 2. **docs**: 複数オリジン・credentials のパターンを how-to ガイドに追加
@@ -0,0 +1,198 @@
1
+ # FT82: Background Tasks — FastAPI BackgroundTasks と nene2 の組み合わせ
2
+
3
+ **日付**: 2026-05-20
4
+ **テーマ**: FastAPI BackgroundTasks を nene2 アーキテクチャと組み合わせた際の動作とパターン検証
5
+ **バージョン**: v1.8.27
6
+ **FTディレクトリ**: `/home/xi/docker/nene2-python-FT/ft82-background-tasks/`
7
+
8
+ ---
9
+
10
+ ## 概要
11
+
12
+ FastAPI の `BackgroundTasks` は「レスポンス返却後に処理を実行する」仕組みで、
13
+ メール送信・在庫更新・監査ログなどのユースケースに使う。
14
+ nene2 のミドルウェアスタック(`setup_middlewares()`)との共存、
15
+ 例外ハンドリングの限界、UseCase への注入パターンを検証した。
16
+
17
+ ---
18
+
19
+ ## 実装パターン
20
+
21
+ ### 基本パターン(ハンドラーで BackgroundTasks を受け取る)
22
+
23
+ ```python
24
+ from fastapi import BackgroundTasks
25
+
26
+ @app.post("/orders")
27
+ def create_order(body: OrderBody, background_tasks: BackgroundTasks) -> JSONResponse:
28
+ output = use_case.execute(CreateOrderInput(item=body.item, quantity=body.quantity))
29
+ # レスポンス送信後にバックグラウンドで実行される
30
+ background_tasks.add_task(send_order_confirmation, output.order_id, output.item)
31
+ background_tasks.add_task(update_inventory, output.item, output.quantity)
32
+ return JSONResponse({...}, status_code=201)
33
+ ```
34
+
35
+ ### UseCase を注入可能にするパターン(テスト用ファクトリー)
36
+
37
+ ```python
38
+ def create_app_with_use_case(
39
+ create_order_fn: Callable[[CreateOrderInput], CreateOrderOutput]
40
+ ) -> FastAPI:
41
+ factory_app = FastAPI()
42
+ setup_middlewares(factory_app)
43
+
44
+ @factory_app.post("/orders")
45
+ def _create_order(body: OrderBody, background_tasks: BackgroundTasks) -> JSONResponse:
46
+ output = create_order_fn(...)
47
+ background_tasks.add_task(send_order_confirmation, output.order_id, output.item)
48
+ return JSONResponse({...}, status_code=201)
49
+
50
+ return factory_app
51
+
52
+ # テストでの使用
53
+ injected_app = create_app_with_use_case(lambda _: mock_output)
54
+ ```
55
+
56
+ ---
57
+
58
+ ## 発見した問題
59
+
60
+ ### 問題1: バックグラウンドタスクの例外がクライアントに見えない
61
+
62
+ ```python
63
+ # バックグラウンドタスクが例外を投げても...
64
+ def send_email(order_id: int) -> None:
65
+ raise SMTPConnectionError("Mail server down")
66
+
67
+ @app.post("/orders")
68
+ def create_order(body: OrderBody, background_tasks: BackgroundTasks) -> JSONResponse:
69
+ output = use_case.execute(...)
70
+ background_tasks.add_task(send_email, output.order_id)
71
+ return JSONResponse({...}, status_code=201) # ← 201 が返る
72
+
73
+ # クライアントには 201 が届く(メール失敗は見えない)
74
+ ```
75
+
76
+ FastAPI の設計上、レスポンスはすでに送信済みのため
77
+ バックグラウンドタスクのエラーを HTTP レスポンスコードで伝えられない。
78
+ エラーはサーバーログに記録されるが、クライアントには `201 Created` が届く。
79
+
80
+ ### 問題2: nene2 に BackgroundTasks 推奨パターンがない
81
+
82
+ ```python
83
+ # UseCase にどうやって BackgroundTasks を渡すか、ドキュメントがない
84
+
85
+ # ❌ UseCase の中で直接 BackgroundTasks を使う(アーキテクチャ違反)
86
+ class CreateOrderUseCase:
87
+ def execute(self, input_: CreateOrderInput, bg: BackgroundTasks) -> CreateOrderOutput:
88
+ ...
89
+ bg.add_task(send_email, ...) # UseCase が HTTP 境界に依存してしまう
90
+
91
+ # ✅ ハンドラー層で BackgroundTasks を使い、UseCase は純粋に保つ
92
+ @app.post("/orders")
93
+ def create_order(body: OrderBody, background_tasks: BackgroundTasks) -> JSONResponse:
94
+ output = use_case.execute(CreateOrderInput(...)) # UseCase はクリーン
95
+ background_tasks.add_task(send_email, output.order_id) # ハンドラー層で副作用
96
+ return JSONResponse({...})
97
+ ```
98
+
99
+ 正しいパターン(UseCase を純粋に保ちハンドラー層で BackgroundTasks を使う)が
100
+ nene2 のドキュメントに記載されていない。
101
+
102
+ ### 問題3: バックグラウンドタスクの実行確認ができない
103
+
104
+ ```python
105
+ # 「タスクをキューした」ことと「タスクが成功した」ことを HTTP で区別できない
106
+ r = client.post("/orders/1/alert")
107
+ # レスポンスは {"queued": True} — 成功したかどうかはわからない
108
+ ```
109
+
110
+ 本番運用では Celery・ARQ・FastAPI Scheduler などの外部ジョブキューが必要。
111
+ 「BackgroundTasks は軽量な one-shot 処理向け」という点がドキュメントに不足。
112
+
113
+ ---
114
+
115
+ ## テスト結果(全16件パス)
116
+
117
+ ```
118
+ test_create_order_returns_201 PASSED
119
+ test_get_order_returns_200 PASSED
120
+ test_get_nonexistent_order_returns_404 PASSED
121
+ test_background_tasks_execute_after_response PASSED # TestClient は同期実行
122
+ test_multiple_background_tasks_all_execute PASSED
123
+ test_background_task_with_alert PASSED
124
+ test_background_task_not_queued_on_404 PASSED # 404 ではタスク未実行
125
+ test_failing_background_task_does_not_affect_response PASSED # 例外 → 200 のまま
126
+ test_failing_background_task_started PASSED
127
+ test_request_id_present_with_background_tasks PASSED # nene2 と共存
128
+ test_security_headers_present_with_background_tasks PASSED # nene2 と共存
129
+ test_validation_error_does_not_queue_background_tasks PASSED # 422 では未実行
130
+ test_injectable_use_case_pattern PASSED # DI パターン動作
131
+ test_friction_background_exception_not_visible_to_client PASSED # 摩擦記録
132
+ test_friction_no_nene2_background_task_pattern PASSED # 摩擦記録
133
+ test_friction_background_task_cannot_return_result PASSED # 摩擦記録
134
+ ```
135
+
136
+ ---
137
+
138
+ ## 重要な発見: TestClient はバックグラウンドタスクを同期実行する
139
+
140
+ ```python
141
+ client = TestClient(app)
142
+ r = client.post("/orders", json={"item": "Widget", "quantity": 1})
143
+ # TestClient はレスポンスを返す前にバックグラウンドタスクも実行する
144
+ assert "confirmation:order_1:Widget" in notification_log # ✅ すでに実行済み
145
+ ```
146
+
147
+ `TestClient` はバックグラウンドタスクをリクエスト完了前に同期的に実行するため、
148
+ テストでは `background_tasks.add_task()` で追加した処理を即座に検証できる。
149
+
150
+ ---
151
+
152
+ ## 摩擦ポイント一覧
153
+
154
+ | ID | 内容 | 深刻度 |
155
+ |---|---|---|
156
+ | F82-1 | バックグラウンドタスクの例外がクライアントに見えない(FastAPI 設計上の制約) | 中 |
157
+ | F82-2 | nene2 に BackgroundTasks 推奨パターン(UseCase との分離)のドキュメントがない | 低 |
158
+ | F82-3 | バックグラウンドタスクは result を返せない(外部キューが必要) | 低 |
159
+
160
+ ---
161
+
162
+ ## 使用感(主観評価)
163
+
164
+ ### 直感性 ★★★★☆
165
+
166
+ `background_tasks.add_task(fn, arg1, arg2)` は非常にシンプルで直感的。
167
+ FastAPI のドキュメントを読めば即座に使える。
168
+ nene2 のミドルウェアスタックとの共存も問題なし。
169
+
170
+ ### 実害の深刻さ ★★☆☆☆
171
+
172
+ バックグラウンド例外がクライアントに見えない問題は、
173
+ 軽量な通知タスク(メール送信)であれば許容範囲。
174
+ ただし「注文確定」のような業務的に重要な処理を
175
+ バックグラウンドタスクに入れるのは設計ミス — そこは UseCase に残すべき。
176
+
177
+ ### 修正のしやすさ ★★★★★
178
+
179
+ 必要なのはドキュメントのみ:
180
+ - UseCase はクリーンに保つ(BackgroundTasks を引数に取らない)
181
+ - ハンドラー層でのみ `background_tasks.add_task()` を呼ぶ
182
+ - 失敗して困る処理はバックグラウンドタスクに入れない
183
+ - TestClient はバックグラウンドタスクを同期実行する(テスト性良好)
184
+
185
+ ### 総合コメント
186
+
187
+ FastAPI の `BackgroundTasks` は nene2 と相性が良く、
188
+ `setup_middlewares()` のミドルウェアスタックとも完全に共存する。
189
+ X-Request-Id・セキュリティヘッダーも正常に付与される。
190
+ 摩擦の大部分は「どう使うべきか」のドキュメント不足であり、
191
+ フレームワークの改修は不要。
192
+
193
+ ---
194
+
195
+ ## 推奨アクション
196
+
197
+ 1. **docs**: BackgroundTasks パターン(UseCase との分離、TestClient の同期実行)を how-to ガイドに追加
198
+ 2. **minor**: BackgroundTasks は「失敗しても OK な副作用のみ」というガイドラインの明記
@@ -0,0 +1,223 @@
1
+ # FT83: Depends() DI — FastAPI Depends を nene2 アーキテクチャで活用するパターン検証
2
+
3
+ **日付**: 2026-05-20
4
+ **テーマ**: UseCase・認証・Pagination の Depends 組み合わせパターンと dependency_overrides
5
+ **バージョン**: v1.8.27
6
+ **FTディレクトリ**: `/home/xi/docker/nene2-python-FT/ft83-depends-injection/`
7
+
8
+ ---
9
+
10
+ ## 概要
11
+
12
+ FastAPI の `Depends()` を nene2 の UseCase + Repository パターンと組み合わせた。
13
+ 依存チェーン(Repository → UseCase → Handler)、認証 Depends、
14
+ `PaginationQueryParser` の統合、テスト時の `dependency_overrides` による差し替えを検証した。
15
+ `PaginationResponse.model_dump()` が存在しない(`to_dict()` が正しい)という
16
+ Pydantic ユーザーがつまずく摩擦点を発見した。
17
+
18
+ ---
19
+
20
+ ## 実装パターン
21
+
22
+ ### Repository → UseCase の Depends チェーン
23
+
24
+ ```python
25
+ from typing import Annotated
26
+ from fastapi import Depends
27
+
28
+ def get_repo() -> InMemoryProductRepository:
29
+ return _repo # シングルトン
30
+
31
+ def get_list_use_case(
32
+ repo: Annotated[InMemoryProductRepository, Depends(get_repo)],
33
+ ) -> ListProductsUseCase:
34
+ return ListProductsUseCase(repo)
35
+
36
+ @app.get("/products")
37
+ def list_products(
38
+ use_case: Annotated[ListProductsUseCase, Depends(get_list_use_case)],
39
+ ) -> JSONResponse:
40
+ products, total = use_case.execute(...)
41
+ return JSONResponse(...)
42
+ ```
43
+
44
+ ### PaginationQueryParser + UseCase の両方を Depends
45
+
46
+ ```python
47
+ @app.get("/products")
48
+ def list_products(
49
+ pagination: Annotated[PaginationQueryParser, Depends(PaginationQueryParser)],
50
+ use_case: Annotated[ListProductsUseCase, Depends(get_list_use_case)],
51
+ ) -> JSONResponse:
52
+ products, total = use_case.execute(
53
+ limit=pagination.limit, offset=pagination.offset
54
+ )
55
+ return JSONResponse(
56
+ PaginationResponse(
57
+ items=[...], total=total, limit=pagination.limit, offset=pagination.offset
58
+ ).to_dict() # ← model_dump() ではなく to_dict()
59
+ )
60
+ ```
61
+
62
+ ### 認証 Depends(Bearer Token)
63
+
64
+ ```python
65
+ from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
66
+
67
+ security = HTTPBearer(auto_error=False)
68
+
69
+ def get_current_user(
70
+ credentials: Annotated[HTTPAuthorizationCredentials | None, Depends(security)],
71
+ ) -> str | None:
72
+ if credentials is None:
73
+ return None
74
+ return verify_token(credentials.credentials)
75
+
76
+ def require_auth(user: Annotated[str | None, Depends(get_current_user)]) -> str:
77
+ if user is None:
78
+ raise HTTPException(status_code=401)
79
+ return user
80
+
81
+ @app.post("/products")
82
+ def create_product(
83
+ body: ProductBody,
84
+ use_case: Annotated[CreateProductUseCase, Depends(get_create_use_case)],
85
+ current_user: Annotated[str, Depends(require_auth)],
86
+ ) -> JSONResponse: ...
87
+ ```
88
+
89
+ ### テスト時の dependency_overrides
90
+
91
+ ```python
92
+ custom_repo = InMemoryProductRepository()
93
+ custom_repo.create("Test Product", 100)
94
+
95
+ app.dependency_overrides[get_repo] = lambda: custom_repo
96
+ # テスト実行
97
+ del app.dependency_overrides[get_repo] # クリーンアップ
98
+ ```
99
+
100
+ ---
101
+
102
+ ## 発見した問題
103
+
104
+ ### 問題1: PaginationResponse.model_dump() が存在しない
105
+
106
+ ```python
107
+ # ❌ Pydantic ユーザーは model_dump() を期待してしまう
108
+ PaginationResponse(...).model_dump() # AttributeError!
109
+
110
+ # ✅ 正しいメソッド
111
+ PaginationResponse(...).to_dict()
112
+ ```
113
+
114
+ `PaginationResponse` は dataclass だが、Pydantic v2 に慣れているユーザーは
115
+ `model_dump()` を期待して AttributeError に直面する。
116
+ FastAPI の OpenAPI ドキュメントや response_model を使うには
117
+ Pydantic BaseModel にするか、FastAPI の Response 型として登録する必要がある。
118
+
119
+ ### 問題2: Annotated[T, Depends(fn)] の記述が冗長
120
+
121
+ ```python
122
+ # 現在の書き方(型情報が Annotated の外側と内側に重複)
123
+ def list_products(
124
+ pagination: Annotated[PaginationQueryParser, Depends(PaginationQueryParser)],
125
+ use_case: Annotated[ListProductsUseCase, Depends(get_list_use_case)],
126
+ ) -> JSONResponse: ...
127
+
128
+ # 理想(nene2 が型エイリアスを提供)
129
+ type PaginationDep = Annotated[PaginationQueryParser, Depends(PaginationQueryParser)]
130
+
131
+ @app.get("/products")
132
+ def list_products(pagination: PaginationDep, ...) -> JSONResponse: ...
133
+ ```
134
+
135
+ `PaginationQueryParser` の `Annotated` エイリアスを nene2 が提供すれば
136
+ 毎回書く冗長さが解消される。
137
+
138
+ ### 問題3: nene2 の認証 Depends ユーティリティがない
139
+
140
+ ```python
141
+ # nene2.auth に Depends ユーティリティがない
142
+ # ユーザーは HTTPBearer + LocalTokenVerifier を手動で組み合わせる必要がある
143
+
144
+ # 理想:
145
+ from nene2.auth.deps import CurrentUser, require_auth
146
+ # これが存在しない
147
+ ```
148
+
149
+ `LocalTokenVerifier.from_env()` は実装されているが、
150
+ FastAPI の Depends パターンに接続する `CurrentUser` 型や
151
+ `require_auth` Depends がない。
152
+
153
+ ---
154
+
155
+ ## テスト結果(全16件パス)
156
+
157
+ ```
158
+ test_list_products_empty PASSED
159
+ test_create_product_returns_201 PASSED
160
+ test_get_product_returns_200 PASSED
161
+ test_get_nonexistent_returns_404 PASSED
162
+ test_create_product_requires_auth PASSED
163
+ test_create_product_with_auth_succeeds PASSED
164
+ test_pagination_with_depends PASSED # PaginationQueryParser Depends 動作
165
+ test_pagination_second_page PASSED
166
+ test_override_repo_with_custom_data PASSED # dependency_overrides 動作
167
+ test_override_repo_empty PASSED
168
+ test_request_id_with_depends PASSED # nene2 ミドルウェアと共存
169
+ test_security_headers_with_depends PASSED
170
+ test_validation_error_returns_422 PASSED
171
+ test_friction_annotated_syntax_verbosity PASSED # 摩擦記録
172
+ test_friction_use_case_chains_in_depends PASSED # 摩擦記録
173
+ test_friction_no_nene2_depends_utilities PASSED # 摩擦記録
174
+ ```
175
+
176
+ ---
177
+
178
+ ## 摩擦ポイント一覧
179
+
180
+ | ID | 内容 | 深刻度 |
181
+ |---|---|---|
182
+ | F83-1 | `PaginationResponse.model_dump()` が存在しない(`to_dict()` が正しい)— Pydantic ユーザー混乱 | 中 |
183
+ | F83-2 | `Annotated[PaginationQueryParser, Depends(PaginationQueryParser)]` の冗長記述 | 低 |
184
+ | F83-3 | nene2 認証の Depends ユーティリティ (`CurrentUser`, `require_auth`) がない | 低 |
185
+
186
+ ---
187
+
188
+ ## 使用感(主観評価)
189
+
190
+ ### 直感性 ★★★☆☆
191
+
192
+ `Depends()` のパターン自体は FastAPI の機能なので明確。
193
+ ただし Repository → UseCase → Handler の3層チェーンは
194
+ Spring/NestJS の DI に慣れたユーザーには違和感がある
195
+ (Constructor Injection ではなく Function Injection)。
196
+ `dependency_overrides` によるテスト差し替えは非常に優れた機能。
197
+
198
+ ### 実害の深刻さ ★★☆☆☆
199
+
200
+ `PaginationResponse.to_dict()` を知らなければ AttributeError で即座に詰まる。
201
+ nene2 ドキュメントに `PaginationResponse` の使い方が記載されているが、
202
+ `model_dump()` と混同するユーザーが続出する可能性がある。
203
+
204
+ ### 修正のしやすさ ★★★★★
205
+
206
+ - `PaginationResponse` に `model_dump()` の alias を追加(または別名で明記)
207
+ - `Annotated[PaginationQueryParser, Depends(PaginationQueryParser)]` を `PaginationDep` 型エイリアスとして公開
208
+ - `nene2.auth.deps` モジュールに `CurrentUser` 型と `require_auth` Depends を追加
209
+
210
+ ### 総合コメント
211
+
212
+ FastAPI の `Depends()` は nene2 のアーキテクチャと非常に相性がよく、
213
+ Repository の差し替えも `dependency_overrides` で簡単にできる。
214
+ 主な摩擦は `PaginationResponse.to_dict()` の名前(Pydantic との不整合)と
215
+ 認証 Depends ユーティリティの欠如。どちらも小さな追加で解決できる。
216
+
217
+ ---
218
+
219
+ ## 推奨アクション
220
+
221
+ 1. **Issue**: `PaginationResponse` に `model_dump()` エイリアスを追加(Pydantic ユーザー向け)
222
+ 2. **Issue**: `nene2.http` に `PaginationDep` 型エイリアスを追加(`PaginationQueryParser` の Depends)
223
+ 3. **Issue**: `nene2.auth.deps` モジュールに認証 Depends ユーティリティを追加
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nene2-python"
3
- version = "1.8.26"
3
+ version = "1.8.28"
4
4
  description = "NENE2 Python — minimal API framework following NENE2's design philosophy"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
@@ -7,7 +7,7 @@ from .health import (
7
7
  HealthCheckProtocol,
8
8
  HealthStatus,
9
9
  )
10
- from .pagination import PaginationQuery, PaginationQueryParser, PaginationResponse
10
+ from .pagination import PaginationDep, PaginationQuery, PaginationQueryParser, PaginationResponse
11
11
  from .problem_details import (
12
12
  PROBLEM_DETAILS_BASE_URL,
13
13
  configure_problem_details,
@@ -21,6 +21,7 @@ __all__ = [
21
21
  "CompositeHealthCheck",
22
22
  "HealthCheckProtocol",
23
23
  "HealthStatus",
24
+ "PaginationDep",
24
25
  "PaginationQuery",
25
26
  "PaginationQueryParser",
26
27
  "PaginationResponse",