nene2-python 1.8.28__tar.gz → 1.8.30__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 (283) hide show
  1. {nene2_python-1.8.28 → nene2_python-1.8.30}/CHANGELOG.md +25 -0
  2. {nene2_python-1.8.28 → nene2_python-1.8.30}/PKG-INFO +1 -1
  3. nene2_python-1.8.30/docs/field-trials/2026-05-field-trial-84.md +151 -0
  4. nene2_python-1.8.30/docs/field-trials/2026-05-field-trial-85.md +207 -0
  5. nene2_python-1.8.30/docs/field-trials/2026-05-field-trial-86.md +171 -0
  6. nene2_python-1.8.30/docs/field-trials/2026-05-field-trial-87.md +194 -0
  7. {nene2_python-1.8.28 → nene2_python-1.8.30}/pyproject.toml +1 -1
  8. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/auth/__init__.py +2 -0
  9. nene2_python-1.8.30/src/nene2/auth/deps.py +50 -0
  10. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/http/problem_details.py +5 -0
  11. nene2_python-1.8.30/tests/nene2/auth/test_make_require_auth.py +99 -0
  12. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/nene2/http/test_problem_details.py +23 -0
  13. {nene2_python-1.8.28 → nene2_python-1.8.30}/uv.lock +1 -1
  14. {nene2_python-1.8.28 → nene2_python-1.8.30}/.env.example +0 -0
  15. {nene2_python-1.8.28 → nene2_python-1.8.30}/.github/workflows/ci.yml +0 -0
  16. {nene2_python-1.8.28 → nene2_python-1.8.30}/.github/workflows/docs.yml +0 -0
  17. {nene2_python-1.8.28 → nene2_python-1.8.30}/.github/workflows/publish.yml +0 -0
  18. {nene2_python-1.8.28 → nene2_python-1.8.30}/.gitignore +0 -0
  19. {nene2_python-1.8.28 → nene2_python-1.8.30}/.vitepress/config.mts +0 -0
  20. {nene2_python-1.8.28 → nene2_python-1.8.30}/.vitepress/theme/custom.css +0 -0
  21. {nene2_python-1.8.28 → nene2_python-1.8.30}/.vitepress/theme/index.ts +0 -0
  22. {nene2_python-1.8.28 → nene2_python-1.8.30}/AGENTS.md +0 -0
  23. {nene2_python-1.8.28 → nene2_python-1.8.30}/CLAUDE.md +0 -0
  24. {nene2_python-1.8.28 → nene2_python-1.8.30}/Dockerfile +0 -0
  25. {nene2_python-1.8.28 → nene2_python-1.8.30}/LICENSE +0 -0
  26. {nene2_python-1.8.28 → nene2_python-1.8.30}/README.md +0 -0
  27. {nene2_python-1.8.28 → nene2_python-1.8.30}/alembic/README +0 -0
  28. {nene2_python-1.8.28 → nene2_python-1.8.30}/alembic/env.py +0 -0
  29. {nene2_python-1.8.28 → nene2_python-1.8.30}/alembic/script.py.mako +0 -0
  30. {nene2_python-1.8.28 → nene2_python-1.8.30}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
  31. {nene2_python-1.8.28 → nene2_python-1.8.30}/alembic.ini +0 -0
  32. {nene2_python-1.8.28 → nene2_python-1.8.30}/compose.yaml +0 -0
  33. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/adr/0001-toolchain.md +0 -0
  34. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/adr/0002-clean-architecture.md +0 -0
  35. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/adr/0003-security-first.md +0 -0
  36. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/adr/0004-ai-first-design.md +0 -0
  37. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/adr/0005-logging.md +0 -0
  38. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/adr/0006-rate-limiting.md +0 -0
  39. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/adr/0009-mcp-design.md +0 -0
  40. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/adr/0010-async-use-case.md +0 -0
  41. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
  42. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/de/index.md +0 -0
  43. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/de/tutorials/getting-started.md +0 -0
  44. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/explanation/architecture.md +0 -0
  45. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/explanation/design-philosophy.md +0 -0
  46. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-1.md +0 -0
  47. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-10.md +0 -0
  48. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-11.md +0 -0
  49. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-12.md +0 -0
  50. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-13.md +0 -0
  51. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-14.md +0 -0
  52. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-15.md +0 -0
  53. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-16.md +0 -0
  54. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-17.md +0 -0
  55. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-18.md +0 -0
  56. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-19.md +0 -0
  57. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-2.md +0 -0
  58. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-20.md +0 -0
  59. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-21.md +0 -0
  60. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-22.md +0 -0
  61. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-23.md +0 -0
  62. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-24.md +0 -0
  63. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-25.md +0 -0
  64. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-26.md +0 -0
  65. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-27.md +0 -0
  66. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-28.md +0 -0
  67. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-29.md +0 -0
  68. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-3.md +0 -0
  69. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-30.md +0 -0
  70. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-31.md +0 -0
  71. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-32.md +0 -0
  72. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-33.md +0 -0
  73. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-34.md +0 -0
  74. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-35.md +0 -0
  75. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-36.md +0 -0
  76. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-37.md +0 -0
  77. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-38.md +0 -0
  78. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-39.md +0 -0
  79. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-4.md +0 -0
  80. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-40.md +0 -0
  81. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-41.md +0 -0
  82. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-42.md +0 -0
  83. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-43.md +0 -0
  84. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-44.md +0 -0
  85. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-45.md +0 -0
  86. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-46.md +0 -0
  87. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-47.md +0 -0
  88. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-48.md +0 -0
  89. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-49.md +0 -0
  90. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-5.md +0 -0
  91. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-50.md +0 -0
  92. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-51.md +0 -0
  93. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-52.md +0 -0
  94. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-53.md +0 -0
  95. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-54.md +0 -0
  96. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-55.md +0 -0
  97. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-56.md +0 -0
  98. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-57.md +0 -0
  99. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-58.md +0 -0
  100. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-59.md +0 -0
  101. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-6.md +0 -0
  102. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-60.md +0 -0
  103. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-61.md +0 -0
  104. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-62.md +0 -0
  105. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-63.md +0 -0
  106. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-64.md +0 -0
  107. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-65.md +0 -0
  108. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-66.md +0 -0
  109. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-67.md +0 -0
  110. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-68.md +0 -0
  111. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-69.md +0 -0
  112. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-7.md +0 -0
  113. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-70.md +0 -0
  114. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-71.md +0 -0
  115. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-72.md +0 -0
  116. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-73.md +0 -0
  117. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-74.md +0 -0
  118. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-75.md +0 -0
  119. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-76.md +0 -0
  120. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-77.md +0 -0
  121. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-78.md +0 -0
  122. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-79.md +0 -0
  123. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-8.md +0 -0
  124. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-80.md +0 -0
  125. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-81.md +0 -0
  126. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-82.md +0 -0
  127. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-83.md +0 -0
  128. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/field-trials/2026-05-field-trial-9.md +0 -0
  129. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/fr/index.md +0 -0
  130. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/fr/tutorials/getting-started.md +0 -0
  131. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/how-to/add-new-domain.md +0 -0
  132. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/how-to/async-use-case.md +0 -0
  133. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/how-to/configure-auth.md +0 -0
  134. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/how-to/middleware-stack.md +0 -0
  135. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/how-to/new-project.md +0 -0
  136. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/how-to/problem-details.md +0 -0
  137. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/how-to/run-tests.md +0 -0
  138. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/how-to/sqlalchemy-repository.md +0 -0
  139. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/how-to/validation.md +0 -0
  140. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/howto/mcp-setup.md +0 -0
  141. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/index.md +0 -0
  142. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/ja/explanation/architecture.md +0 -0
  143. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/ja/explanation/design-philosophy.md +0 -0
  144. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/ja/how-to/add-new-domain.md +0 -0
  145. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/ja/how-to/configure-auth.md +0 -0
  146. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/ja/how-to/new-project.md +0 -0
  147. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/ja/how-to/run-tests.md +0 -0
  148. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
  149. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/ja/howto/mcp-setup.md +0 -0
  150. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/ja/index.md +0 -0
  151. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/ja/reference/api.md +0 -0
  152. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/ja/reference/configuration.md +0 -0
  153. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/ja/reference/framework-modules.md +0 -0
  154. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/ja/tutorials/first-domain.md +0 -0
  155. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/ja/tutorials/getting-started.md +0 -0
  156. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/pt-br/index.md +0 -0
  157. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/pt-br/tutorials/getting-started.md +0 -0
  158. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/reference/api.md +0 -0
  159. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/reference/configuration.md +0 -0
  160. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/reference/framework-modules.md +0 -0
  161. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/roadmap.md +0 -0
  162. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/todo/current.md +0 -0
  163. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/tutorials/first-domain.md +0 -0
  164. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/tutorials/getting-started.md +0 -0
  165. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/zh/index.md +0 -0
  166. {nene2_python-1.8.28 → nene2_python-1.8.30}/docs/zh/tutorials/getting-started.md +0 -0
  167. {nene2_python-1.8.28 → nene2_python-1.8.30}/package-lock.json +0 -0
  168. {nene2_python-1.8.28 → nene2_python-1.8.30}/package.json +0 -0
  169. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/example/__init__.py +0 -0
  170. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/example/__main__.py +0 -0
  171. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/example/app.py +0 -0
  172. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/example/comment/__init__.py +0 -0
  173. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/example/comment/entity.py +0 -0
  174. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/example/comment/exceptions.py +0 -0
  175. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/example/comment/handler.py +0 -0
  176. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/example/comment/repository.py +0 -0
  177. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/example/comment/sqlalchemy_repository.py +0 -0
  178. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/example/comment/use_case.py +0 -0
  179. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/example/mcp.py +0 -0
  180. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/example/note/__init__.py +0 -0
  181. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/example/note/async_use_case.py +0 -0
  182. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/example/note/entity.py +0 -0
  183. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/example/note/exceptions.py +0 -0
  184. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/example/note/handler.py +0 -0
  185. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/example/note/repository.py +0 -0
  186. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/example/note/sqlalchemy_repository.py +0 -0
  187. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/example/note/use_case.py +0 -0
  188. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/example/schema.py +0 -0
  189. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/example/tag/__init__.py +0 -0
  190. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/example/tag/entity.py +0 -0
  191. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/example/tag/exceptions.py +0 -0
  192. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/example/tag/handler.py +0 -0
  193. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/example/tag/repository.py +0 -0
  194. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/example/tag/sqlalchemy_repository.py +0 -0
  195. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/example/tag/use_case.py +0 -0
  196. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/__init__.py +0 -0
  197. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/auth/api_key.py +0 -0
  198. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/auth/bearer_token.py +0 -0
  199. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/auth/exceptions.py +0 -0
  200. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/auth/interfaces.py +0 -0
  201. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/auth/local_verifier.py +0 -0
  202. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/config/__init__.py +0 -0
  203. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/config/settings.py +0 -0
  204. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/database/__init__.py +0 -0
  205. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/database/exceptions.py +0 -0
  206. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/database/health.py +0 -0
  207. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/database/interfaces.py +0 -0
  208. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/database/sqlalchemy_executor.py +0 -0
  209. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/database/utils.py +0 -0
  210. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/http/__init__.py +0 -0
  211. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/http/health.py +0 -0
  212. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/http/pagination.py +0 -0
  213. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/log/__init__.py +0 -0
  214. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/log/setup.py +0 -0
  215. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/mcp/__init__.py +0 -0
  216. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/mcp/http_client.py +0 -0
  217. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/mcp/server.py +0 -0
  218. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/middleware/__init__.py +0 -0
  219. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/middleware/domain_exception.py +0 -0
  220. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/middleware/error_handler.py +0 -0
  221. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/middleware/request_id.py +0 -0
  222. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/middleware/request_logging.py +0 -0
  223. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/middleware/request_size_limit.py +0 -0
  224. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/middleware/security_headers.py +0 -0
  225. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/middleware/setup.py +0 -0
  226. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/middleware/throttle.py +0 -0
  227. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/py.typed +0 -0
  228. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/use_case/__init__.py +0 -0
  229. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/use_case/protocols.py +0 -0
  230. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/validation/__init__.py +0 -0
  231. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/nene2/validation/exceptions.py +0 -0
  232. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/scripts/__init__.py +0 -0
  233. {nene2_python-1.8.28 → nene2_python-1.8.30}/src/scripts/export_openapi.py +0 -0
  234. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/__init__.py +0 -0
  235. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/example/__init__.py +0 -0
  236. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/example/comment/__init__.py +0 -0
  237. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/example/comment/test_comment_http.py +0 -0
  238. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/example/comment/test_comment_repository.py +0 -0
  239. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/example/comment/test_comment_use_case.py +0 -0
  240. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/example/conftest.py +0 -0
  241. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/example/note/__init__.py +0 -0
  242. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/example/note/test_async_note_use_case.py +0 -0
  243. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/example/note/test_list_notes.py +0 -0
  244. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/example/note/test_note_repository.py +0 -0
  245. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/example/tag/__init__.py +0 -0
  246. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/example/tag/test_tag_repository.py +0 -0
  247. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/example/tag/test_tags.py +0 -0
  248. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/example/test_cors.py +0 -0
  249. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/example/test_mcp.py +0 -0
  250. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/nene2/__init__.py +0 -0
  251. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/nene2/auth/__init__.py +0 -0
  252. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/nene2/auth/test_api_key.py +0 -0
  253. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/nene2/auth/test_bearer_token.py +0 -0
  254. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/nene2/auth/test_token_issuer.py +0 -0
  255. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/nene2/config/__init__.py +0 -0
  256. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/nene2/config/test_settings.py +0 -0
  257. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/nene2/database/__init__.py +0 -0
  258. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/nene2/database/test_transaction.py +0 -0
  259. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/nene2/database/test_utils.py +0 -0
  260. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/nene2/http/__init__.py +0 -0
  261. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/nene2/http/test_health.py +0 -0
  262. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/nene2/http/test_pagination.py +0 -0
  263. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/nene2/log/__init__.py +0 -0
  264. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/nene2/log/test_setup.py +0 -0
  265. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/nene2/mcp/__init__.py +0 -0
  266. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/nene2/mcp/test_http_client.py +0 -0
  267. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/nene2/mcp/test_server.py +0 -0
  268. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/nene2/middleware/__init__.py +0 -0
  269. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/nene2/middleware/test_error_handler.py +0 -0
  270. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/nene2/middleware/test_request_id.py +0 -0
  271. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/nene2/middleware/test_request_logging.py +0 -0
  272. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/nene2/middleware/test_request_size_limit.py +0 -0
  273. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/nene2/middleware/test_security_headers.py +0 -0
  274. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/nene2/middleware/test_setup_middlewares.py +0 -0
  275. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
  276. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/nene2/middleware/test_throttle.py +0 -0
  277. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/nene2/use_case/__init__.py +0 -0
  278. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/nene2/use_case/test_protocols.py +0 -0
  279. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/nene2/use_case/test_run_in_threadpool.py +0 -0
  280. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/nene2/validation/__init__.py +0 -0
  281. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/nene2/validation/test_exceptions.py +0 -0
  282. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/scripts/__init__.py +0 -0
  283. {nene2_python-1.8.28 → nene2_python-1.8.30}/tests/scripts/test_export_openapi.py +0 -0
@@ -5,6 +5,31 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
6
  ---
7
7
 
8
+ ## [1.8.29] — 2026-05-20
9
+
10
+ FT84 フィールドトライアル — 認証 Depends ユーティリティ検証と make_require_auth() 追加。
11
+
12
+ ### Added
13
+ - `nene2.auth` に `make_require_auth(verifier)` Depends ファクトリーを追加 (#359) (FT84)
14
+ — `TokenVerifierProtocol` を FastAPI の `Depends` に接続するボイラープレートを解消
15
+ — 有効トークンで token 文字列を返し、未認証・無効トークンで 401 を raise
16
+ - Field trial report: `docs/field-trials/2026-05-field-trial-84.md` (FT84)
17
+
18
+ ---
19
+
20
+ ## [1.8.28] — 2026-05-20
21
+
22
+ FT83 フィールドトライアル — Depends() DI パターン検証と PaginationResponse / PaginationDep 改善。
23
+
24
+ ### Added
25
+ - `PaginationResponse.model_dump()` を `to_dict()` の Pydantic 互換エイリアスとして追加 (#355) (FT83)
26
+ — Pydantic v2 ユーザーが `model_dump()` を期待して AttributeError になる問題を解消
27
+ - `PaginationDep` 型エイリアスを `nene2.http` に追加 (#355) (FT83)
28
+ — `Annotated[PaginationQueryParser, Depends(PaginationQueryParser)]` の省略形
29
+ - Field trial report: `docs/field-trials/2026-05-field-trial-83.md` (FT83)
30
+
31
+ ---
32
+
8
33
  ## [1.8.27] — 2026-05-20
9
34
 
10
35
  FT81 フィールドトライアル — CORS 設定パターン検証と setup_middlewares() への CORS 統合。
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nene2-python
3
- Version: 1.8.28
3
+ Version: 1.8.30
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,151 @@
1
+ # FT84: 認証 Depends ユーティリティ — CurrentUser / require_auth パターン検証
2
+
3
+ **日付**: 2026-05-20
4
+ **テーマ**: nene2.auth に認証 Depends ユーティリティがない摩擦点と make_require_auth 設計検証
5
+ **バージョン**: v1.8.28
6
+ **FTディレクトリ**: `/home/xi/docker/nene2-python-FT/ft84-auth-depends/`
7
+
8
+ ---
9
+
10
+ ## 概要
11
+
12
+ `LocalTokenVerifier` と `TokenVerifierProtocol` は実装されているが、
13
+ FastAPI の Depends パターンに接続するユーティリティがない。
14
+ ユーザーは `HTTPBearer` → `verify()` → 未認証 401 を毎プロジェクトで手組みする必要がある。
15
+ `make_require_auth(verifier)` ファクトリーを追加することで解消できる。
16
+
17
+ ---
18
+
19
+ ## 発見した問題
20
+
21
+ ### 問題1: 認証 Depends を毎回手組みする必要がある
22
+
23
+ ```python
24
+ # 現状: プロジェクトごとに以下のコードを書く必要がある (50+ 行のボイラープレート)
25
+
26
+ from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
27
+ from nene2.auth import LocalTokenVerifier
28
+
29
+ security = HTTPBearer(auto_error=False)
30
+ _verifier = LocalTokenVerifier.from_env("BEARER_TOKENS")
31
+
32
+ def get_current_user(
33
+ credentials: HTTPAuthorizationCredentials | None = Depends(security),
34
+ ) -> str | None:
35
+ if credentials is None:
36
+ return None
37
+ if not _verifier.verify(credentials.credentials):
38
+ return None
39
+ return credentials.credentials
40
+
41
+ def require_auth(user: str | None = Depends(get_current_user)) -> str:
42
+ if user is None:
43
+ raise HTTPException(status_code=401)
44
+ return user
45
+
46
+ # ハンドラーで使う
47
+ @app.post("/items")
48
+ def create(body: ItemBody, user: str = Depends(require_auth)) -> JSONResponse: ...
49
+ ```
50
+
51
+ ### 問題2: 任意認証パターンが冗長
52
+
53
+ ```python
54
+ # 未認証でも通るが user を取得したいパターン
55
+ def optional_auth(user: str | None = Depends(get_current_user)) -> str | None:
56
+ return user # None = 未認証、str = ユーザーID
57
+
58
+ @app.get("/feed")
59
+ def get_feed(user: str | None = Depends(optional_auth)) -> JSONResponse:
60
+ if user:
61
+ return JSONResponse({"personalized": True})
62
+ return JSONResponse({"personalized": False})
63
+ ```
64
+
65
+ ### 問題3: TokenVerifierProtocol を Depends で直接使えない
66
+
67
+ ```python
68
+ # こういう書き方をしたくなるが Depends は callable を期待する
69
+ @app.post("/items")
70
+ def create(
71
+ body: ItemBody,
72
+ token: Annotated[str, Depends(LocalTokenVerifier.from_env("TOKENS"))], # ← 機能しない
73
+ ) -> JSONResponse: ...
74
+ ```
75
+
76
+ ---
77
+
78
+ ## テスト結果(全12件パス)
79
+
80
+ ```
81
+ test_public_endpoint_no_auth PASSED
82
+ test_create_item_with_valid_token PASSED
83
+ test_create_item_without_token_returns_401 PASSED
84
+ test_create_item_with_invalid_token_returns_401 PASSED
85
+ test_get_me_returns_user_info PASSED
86
+ test_delete_item_authenticated PASSED
87
+ test_delete_item_unauthenticated_returns_401 PASSED
88
+ test_request_id_present_on_401 PASSED # nene2 ミドルウェアと共存
89
+ test_security_headers_present_on_401 PASSED # nene2 ミドルウェアと共存
90
+ test_friction_boilerplate_for_auth_depends PASSED # 摩擦記録
91
+ test_friction_optional_auth_pattern_verbose PASSED # 摩擦記録
92
+ test_friction_verifier_not_injectable_as_depends PASSED # 摩擦記録
93
+ ```
94
+
95
+ ---
96
+
97
+ ## 摩擦ポイント一覧
98
+
99
+ | ID | 内容 | 深刻度 |
100
+ |---|---|---|
101
+ | F84-1 | 認証 Depends ボイラープレートを毎プロジェクトで手組みする必要がある | 中 |
102
+ | F84-2 | 任意認証(Optional User)パターンが冗長 | 低 |
103
+ | F84-3 | `TokenVerifierProtocol` を Depends で直接使えない | 低 |
104
+
105
+ ---
106
+
107
+ ## 使用感(主観評価)
108
+
109
+ ### 直感性 ★★☆☆☆
110
+
111
+ 「nene2 でトークン認証を追加する」という操作に必要なステップが多すぎる。
112
+ `BearerTokenMiddleware` は全経路に認証を入れるには便利だが、
113
+ 「このエンドポイントだけ認証を要求」パターンに Depends ユーティリティがない。
114
+ FastAPI の HTTPBearer を使う方法は FastAPI のドキュメントを参照しなければわからない。
115
+
116
+ ### 実害の深刻さ ★★★☆☆
117
+
118
+ 認証は多くのプロジェクトで必要。毎回 50 行のボイラープレートを書くのは
119
+ DRY 原則に反する。バグが入りやすく、テスト漏れにもつながる。
120
+
121
+ ### 修正のしやすさ ★★★★★
122
+
123
+ `make_require_auth(verifier)` 関数を `nene2.auth` に追加するだけ:
124
+
125
+ ```python
126
+ def make_require_auth(verifier: TokenVerifierProtocol) -> Callable[..., str]:
127
+ security = HTTPBearer(auto_error=False)
128
+ def get_current_user(credentials: ... = Depends(security)) -> str | None:
129
+ if credentials is None or not verifier.verify(credentials.credentials):
130
+ return None
131
+ return credentials.credentials
132
+ def require_auth(user: str | None = Depends(get_current_user)) -> str:
133
+ if user is None:
134
+ raise HTTPException(status_code=401)
135
+ return user
136
+ return require_auth
137
+ ```
138
+
139
+ ### 総合コメント
140
+
141
+ nene2 の認証機能は「ミドルウェアで全経路を保護する」ユースケースは
142
+ `BearerTokenMiddleware` でカバーされている。
143
+ 不足しているのは「特定エンドポイントのみ認証を要求する」ユースケース。
144
+ `make_require_auth()` を追加することで「nene2 だけで完結」できる。
145
+
146
+ ---
147
+
148
+ ## 推奨アクション
149
+
150
+ 1. **Issue #358**: `nene2.auth` に `make_require_auth(verifier)` ファクトリーを追加
151
+ — `require_auth = make_require_auth(LocalTokenVerifier.from_env("TOKENS"))` パターンを実現
@@ -0,0 +1,207 @@
1
+ # FT85: OpenAPI スキーマ品質 — JSONResponse と response_model の摩擦
2
+
3
+ **日付**: 2026-05-20
4
+ **テーマ**: JSONResponse を返すと OpenAPI スキーマが Any になる問題と正しいパターン検証
5
+ **バージョン**: v1.8.29
6
+ **FTディレクトリ**: `/home/xi/docker/nene2-python-FT/ft85-openapi-schema/`
7
+
8
+ ---
9
+
10
+ ## 概要
11
+
12
+ nene2 のハンドラーは `JSONResponse` を返すが、`response_model` を省略すると
13
+ OpenAPI スキーマに型情報が含まれず Swagger UI が使いにくくなる。
14
+ CLAUDE.md には「`response_model` で明示(`Any` 返却禁止)」と書かれているが、
15
+ `JSONResponse` との組み合わせ方が示されていない。
16
+ 3つのパターン(response_model なし / あり / Pydantic 直返し)を検証した。
17
+
18
+ ---
19
+
20
+ ## 3パターンの比較
21
+
22
+ ### パターン1: response_model なし(❌ 非推奨)
23
+
24
+ ```python
25
+ @app.get("/articles/{article_id}")
26
+ def get_article(article_id: int) -> JSONResponse:
27
+ return JSONResponse({"article_id": 1, "title": "..."})
28
+ ```
29
+
30
+ - OpenAPI スキーマ: `{}` または `{"title": "Response"}`(型情報なし)
31
+ - Swagger UI: レスポンス例なし、フィールド定義なし
32
+ - 型安全性: なし
33
+
34
+ ### パターン2: response_model あり(✅ nene2 推奨)
35
+
36
+ ```python
37
+ class ArticleResponse(BaseModel):
38
+ article_id: int = Field(description="記事 ID")
39
+ title: str = Field(max_length=200, description="記事タイトル")
40
+ ...
41
+
42
+ @app.get(
43
+ "/articles/{article_id}",
44
+ response_model=ArticleResponse,
45
+ responses={404: {"description": "Article not found"}},
46
+ summary="記事を取得する",
47
+ tags=["articles"],
48
+ )
49
+ def get_article(article_id: int) -> JSONResponse:
50
+ if article_id not in _articles:
51
+ return problem_details_response(...) # nene2 パターン維持
52
+ return JSONResponse({...})
53
+ ```
54
+
55
+ - OpenAPI スキーマ: `ArticleResponse` の完全な型情報
56
+ - Swagger UI: フィールド説明・型・例が表示される
57
+ - nene2 の `problem_details_response()` と共存可能
58
+
59
+ ### パターン3: Pydantic モデルを直接返す
60
+
61
+ ```python
62
+ @app.get("/articles/{article_id}", response_model=ArticleResponse)
63
+ def get_article(article_id: int) -> ArticleResponse:
64
+ if article_id not in _articles:
65
+ raise HTTPException(404, "Not found") # ← nene2 スタイルではない
66
+ return ArticleResponse(...)
67
+ ```
68
+
69
+ - OpenAPI スキーマ: 最も完全
70
+ - 型安全性: 最高(戻り値が検証される)
71
+ - 問題: nene2 の `problem_details_response()` が使えない
72
+
73
+ ---
74
+
75
+ ## 発見した問題
76
+
77
+ ### 問題1: JSONResponse + response_model の組み合わせが未文書
78
+
79
+ ```python
80
+ # nene2 ユーザーはこれが正しい書き方だと知らない
81
+ @app.get("/articles/{id}", response_model=ArticleResponse)
82
+ def get_article(id: int) -> JSONResponse: # ← 戻り値型と response_model が一致しない
83
+ return JSONResponse({...})
84
+
85
+ # response_model は OpenAPI スキーマ生成のみに使われ、
86
+ # JSONResponse の内容は response_model でバリデーションされない
87
+ ```
88
+
89
+ `response_model` と `JSONResponse` の組み合わせは動作するが、
90
+ FastAPI は `JSONResponse` の内容を `response_model` で検証しない。
91
+ 「`response_model` を指定すれば内容も検証される」と誤解するユーザーがいる。
92
+
93
+ ### 問題2: Pydantic レスポンスモデルと Domain dataclass の二重定義
94
+
95
+ ```python
96
+ @dataclass(frozen=True, slots=True)
97
+ class Article:
98
+ article_id: int
99
+ title: str
100
+ body: str
101
+ author: str
102
+
103
+ class ArticleResponse(BaseModel):
104
+ article_id: int = Field(description="記事 ID")
105
+ title: str = Field(max_length=200, description="記事タイトル")
106
+ body: str = Field(...)
107
+ author: str = Field(...)
108
+ ```
109
+
110
+ Domain オブジェクトと API スキーマオブジェクトを両方定義する必要があり、
111
+ フィールドを変更すると両方を更新しなければならない。
112
+
113
+ ### 問題3: problem_details_response() と Pydantic 直返しの非一貫性
114
+
115
+ ```python
116
+ # パターン2 (JSONResponse): nene2 スタイルの 404
117
+ @app.get("/articles/{id}", response_model=ArticleResponse)
118
+ def get_article_v2(id: int) -> JSONResponse:
119
+ if id not in _articles:
120
+ return problem_details_response("not-found", ..., 404, ...) # ✅ RFC 9457
121
+ return JSONResponse({...})
122
+
123
+ # パターン3 (Pydantic 直返し): FastAPI スタイルの 404
124
+ @app.get("/articles/{id}", response_model=ArticleResponse)
125
+ def get_article_v3(id: int) -> ArticleResponse:
126
+ if id not in _articles:
127
+ raise HTTPException(404) # ❌ {"detail": "Not found"} になる(nene2 スタイルではない)
128
+ return ArticleResponse(...)
129
+ ```
130
+
131
+ Pydantic モデルを直接返す場合は `problem_details_response()` を使えず、
132
+ `HTTPException` を raise する必要がある。
133
+ これは nene2 の Problem Details ポリシーと矛盾する。
134
+
135
+ ---
136
+
137
+ ## テスト結果(全16件パス)
138
+
139
+ ```
140
+ test_no_schema_create_returns_201 PASSED
141
+ test_no_schema_get_returns_200 PASSED
142
+ test_no_schema_openapi_get_has_no_response_schema PASSED # スキーマなし確認
143
+ test_with_schema_create_returns_201 PASSED
144
+ test_with_schema_get_returns_200 PASSED
145
+ test_with_schema_list_returns_200 PASSED
146
+ test_with_schema_openapi_has_response_schema PASSED # スキーマあり確認
147
+ test_with_schema_openapi_has_tags PASSED # tags 反映
148
+ test_with_schema_openapi_has_summary PASSED # summary 反映
149
+ test_with_schema_404_returns_problem_details PASSED # nene2 404 と共存
150
+ test_pydantic_return_get_returns_200 PASSED
151
+ test_pydantic_return_openapi_has_schema PASSED
152
+ test_friction_json_response_loses_schema PASSED # 摩擦記録
153
+ test_friction_response_model_does_not_validate PASSED # 摩擦記録
154
+ test_friction_problem_details_and_pydantic_conflict PASSED # 摩擦記録
155
+ test_friction_duplicate_type_definition PASSED # 摩擦記録
156
+ ```
157
+
158
+ ---
159
+
160
+ ## 摩擦ポイント一覧
161
+
162
+ | ID | 内容 | 深刻度 |
163
+ |---|---|---|
164
+ | F85-1 | `JSONResponse` + `response_model` の正しい組み合わせ方がドキュメントに未記載 | 中 |
165
+ | F85-2 | Domain dataclass と Pydantic レスポンスモデルの二重定義が避けられない | 低 |
166
+ | F85-3 | Pydantic 直返しパターンでは `problem_details_response()` が使えない(HTTPException と非一貫) | 中 |
167
+
168
+ ---
169
+
170
+ ## 使用感(主観評価)
171
+
172
+ ### 直感性 ★★★☆☆
173
+
174
+ `JSONResponse` + `response_model` のパターンは直感的ではない。
175
+ 「`JSONResponse` を返すと戻り値型が `JSONResponse` なのに `response_model=ArticleResponse` と
176
+ 書く」という不整合に戸惑う。FastAPI のドキュメントを読めば理解できるが、
177
+ nene2 のコンテキストでの説明がない。
178
+
179
+ ### 実害の深刻さ ★★★☆☆
180
+
181
+ `response_model` を省略すると Swagger UI に型情報が出ず、
182
+ API クライアント(TypeScript, Kotlin 等)の自動生成コードに型が付かない。
183
+ 実際の運用では非常に不便で、フロントエンドチームから「なぜ型がないの?」と言われる。
184
+
185
+ ### 修正のしやすさ ★★★★★
186
+
187
+ コード修正は不要。必要なのはドキュメントだけ:
188
+ - nene2 how-to: `JSONResponse` + `response_model` の正しいパターン例
189
+ - `response_model` がバリデーションではなく OpenAPI スキーマ生成のみに使われることの説明
190
+ - `problem_details_response()` との共存パターン
191
+
192
+ ### 総合コメント
193
+
194
+ 「`JSONResponse` を使いながら完全な OpenAPI スキーマを生成する」パターンは
195
+ FastAPI の機能で実現できるが、nene2 のドキュメントに記載がない。
196
+ CLAUDE.md には「`response_model` で明示」と書かれているが、
197
+ 例コード(example app)が `response_model` を使っていない矛盾もある。
198
+ コード修正なしでドキュメントだけで対応できる摩擦点。
199
+
200
+ ---
201
+
202
+ ## 推奨アクション
203
+
204
+ 1. **docs**: how-to ガイドに「OpenAPI スキーマを整備する」記事を追加
205
+ — `response_model=PydanticModel` + `def handler() -> JSONResponse` パターン
206
+ — `problem_details_response()` との共存例
207
+ 2. **refactor**: `example/` ハンドラーに `response_model` を追加して CLAUDE.md ポリシーに準拠させる
@@ -0,0 +1,171 @@
1
+ # FT86: Lifespan イベント — startup/shutdown とリソース共有パターン
2
+
3
+ **日付**: 2026-05-20
4
+ **テーマ**: FastAPI lifespan context manager でのリソース初期化・クリーンアップと nene2 との共存
5
+ **バージョン**: v1.8.29
6
+ **FTディレクトリ**: `/home/xi/docker/nene2-python-FT/ft86-lifespan/`
7
+
8
+ ---
9
+
10
+ ## 概要
11
+
12
+ FastAPI の `lifespan` パラメーターを使った起動時リソース初期化(DBプール、キャッシュ)と
13
+ `app.state` 経由でハンドラーへの注入パターンを検証。
14
+ nene2 の `setup_middlewares()` との共存は問題なし。
15
+ テスト時の `TestClient` の挙動と状態分離に摩擦あり。
16
+
17
+ ---
18
+
19
+ ## 実装パターン
20
+
21
+ ### パターン: lifespan + app.state + Depends
22
+
23
+ ```python
24
+ from contextlib import asynccontextmanager
25
+ from collections.abc import AsyncGenerator
26
+ from fastapi import FastAPI, Depends, Request
27
+ from typing import Annotated
28
+
29
+ @asynccontextmanager
30
+ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
31
+ # startup
32
+ cache = InMemoryCache()
33
+ db_pool = DatabasePool()
34
+ db_pool.connect()
35
+ app.state.cache = cache
36
+ app.state.db_pool = db_pool
37
+
38
+ yield # アプリ実行
39
+
40
+ # shutdown
41
+ db_pool.disconnect()
42
+ cache.clear()
43
+
44
+ app = FastAPI(lifespan=lifespan)
45
+ setup_middlewares(app) # nene2 ミドルウェアと共存 ✅
46
+
47
+ # Depends でリソースを注入
48
+ def get_cache(request: Request) -> InMemoryCache:
49
+ return request.app.state.cache # type: ignore[return-value]
50
+
51
+ CacheDep = Annotated[InMemoryCache, Depends(get_cache)]
52
+
53
+ @app.get("/cache/{key}", response_model=CacheEntryResponse)
54
+ def get_entry(key: str, cache: CacheDep) -> JSONResponse:
55
+ return JSONResponse({"key": key, "value": cache.get(key)})
56
+ ```
57
+
58
+ ---
59
+
60
+ ## 発見した問題
61
+
62
+ ### 問題1: TestClient の with ブロック必須が直感的でない
63
+
64
+ ```python
65
+ # ❌ lifespan が実行されない — app.state.cache が未設定
66
+ client = TestClient(app)
67
+ client.get("/cache/greeting") # → AttributeError → 500
68
+
69
+ # ✅ 正しい使い方
70
+ with TestClient(app) as client:
71
+ client.get("/cache/greeting") # → 200
72
+ ```
73
+
74
+ `TestClient(app)` だけでは lifespan の startup が実行されない。
75
+ `with` ブロックで包む必要があるが、nene2 ドキュメントに記載がない。
76
+
77
+ ### 問題2: テスト間の状態分離が保証されない
78
+
79
+ ```python
80
+ # テスト A: with TestClient(app) を実行 → startup → app.state.cache が設定される
81
+ with TestClient(app) as client:
82
+ ... # startup が実行され app.state に値がセットされる
83
+
84
+ # テスト B: with なしで実行 → 前のテストの app.state.cache が残っている
85
+ client = TestClient(app)
86
+ client.get("/status") # app.state が残っているため 200 になる(本来は 500 のはず)
87
+ ```
88
+
89
+ pytest が同一プロセスで `app` モジュールを共有するため、
90
+ 前のテストで設定された `app.state` が次のテストに引き継がれる。
91
+ テスト順序によって結果が変わる不安定なテストスイートになりやすい。
92
+
93
+ ### 問題3: app.state の型安全性がない
94
+
95
+ ```python
96
+ # mypy では Any になる
97
+ cache = request.app.state.cache # type: ignore[return-value]
98
+ ```
99
+
100
+ `Starlette.State` は動的アトリビューとを持つ `__slots__` なしのオブジェクト。
101
+ mypy は型を推論できず、`type: ignore` コメントが必要になる。
102
+
103
+ ---
104
+
105
+ ## テスト結果(全11件パス)
106
+
107
+ ```
108
+ test_lifespan_startup_initializes_resources PASSED
109
+ test_lifespan_cache_has_initial_value PASSED
110
+ test_lifespan_cache_missing_key_returns_null PASSED
111
+ test_lifespan_cache_set_and_get PASSED
112
+ test_lifespan_db_query_uses_pool PASSED
113
+ test_lifespan_db_query_count_increments PASSED
114
+ test_lifespan_cache_shared_across_requests PASSED
115
+ test_no_lifespan_status_returns_false PASSED
116
+ test_friction_testclient_requires_with_block PASSED
117
+ test_friction_app_state_no_type_safety PASSED
118
+ test_friction_lifespan_error_handling PASSED
119
+ ```
120
+
121
+ ---
122
+
123
+ ## 摩擦ポイント一覧
124
+
125
+ | ID | 内容 | 深刻度 |
126
+ |---|---|---|
127
+ | F86-1 | `TestClient` を `with` ブロックで使わないと lifespan が実行されず 500 になる | 高 |
128
+ | F86-2 | テスト間で `app.state` が引き継がれてテスト順序依存になる | 中 |
129
+ | F86-3 | `app.state` へのアクセスが型安全でなく `type: ignore` が必要 | 低 |
130
+
131
+ ---
132
+
133
+ ## 使用感(主観評価)
134
+
135
+ ### 直感性 ★★☆☆☆
136
+
137
+ `lifespan` パラメーターと `yield` 境界は直感的だが、
138
+ TestClient の `with` ブロック必須は知らないとハマる。
139
+ 「なぜ `client.get()` が 500 を返すのか」のデバッグに時間がかかる。
140
+ `AttributeError: 'State' object has no attribute 'cache'` が
141
+ nene2 の `ErrorHandlerMiddleware` に吸収されて 500 になるため
142
+ エラーメッセージが見えにくい(`APP_DEBUG=true` なら見える)。
143
+
144
+ ### 実害の深刻さ ★★★★☆
145
+
146
+ テスト間の状態分離問題は CI で「時々落ちる」テストを生む。
147
+ テスト実行順序が変わった(pytest-randomly 導入、テスト追加)タイミングで
148
+ 突然失敗するため、原因特定が難しい。
149
+
150
+ ### 修正のしやすさ ★★★★☆
151
+
152
+ コード修正なし、ドキュメント追加のみで対応できる:
153
+ - how-to: `TestClient` を `with` ブロックで使う方法
154
+ - how-to: テスト間の状態分離のための `pytest.fixture` パターン
155
+ - how-to: `app.state` の型安全なアクセスパターン(typed wrapper)
156
+
157
+ ### 総合コメント
158
+
159
+ lifespan + nene2 の組み合わせ自体は問題なく動く。
160
+ `setup_middlewares()` との共存も完全。
161
+ 主な摩擦はテスト方法の周知不足であり、ドキュメントで解決できる。
162
+
163
+ ---
164
+
165
+ ## 推奨アクション
166
+
167
+ 1. **docs**: how-to ガイドに「lifespan でリソースを管理する」記事を追加
168
+ - `TestClient` は必ず `with` ブロックで使うことを明示
169
+ - テスト間の状態分離パターン(`pytest.fixture` + `with TestClient`)
170
+ - `app.state` の型安全なアクセスパターン(`get_or_raise` helper)
171
+ 2. **docs**: nene2 example/ に lifespan パターンのサンプルを追加