nene2-python 1.8.33__tar.gz → 1.8.34__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 (331) hide show
  1. {nene2_python-1.8.33 → nene2_python-1.8.34}/CHANGELOG.md +18 -0
  2. {nene2_python-1.8.33 → nene2_python-1.8.34}/PKG-INFO +1 -1
  3. nene2_python-1.8.34/docs/field-trials/2026-05-field-trial-101.md +59 -0
  4. nene2_python-1.8.34/docs/field-trials/2026-05-field-trial-102.md +34 -0
  5. nene2_python-1.8.34/docs/field-trials/2026-05-field-trial-103.md +47 -0
  6. nene2_python-1.8.34/docs/field-trials/2026-05-field-trial-104.md +48 -0
  7. nene2_python-1.8.34/docs/field-trials/2026-05-field-trial-105.md +43 -0
  8. nene2_python-1.8.34/docs/field-trials/2026-05-field-trial-106.md +39 -0
  9. nene2_python-1.8.34/docs/field-trials/2026-05-field-trial-107.md +46 -0
  10. nene2_python-1.8.34/docs/field-trials/2026-05-field-trial-108.md +46 -0
  11. nene2_python-1.8.34/docs/field-trials/2026-05-field-trial-109.md +53 -0
  12. nene2_python-1.8.34/docs/how-to/api-versioning.md +92 -0
  13. nene2_python-1.8.34/docs/how-to/custom-auth-middleware.md +141 -0
  14. nene2_python-1.8.34/docs/how-to/dependency-injection.md +136 -0
  15. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/how-to/lifespan-and-app-state.md +50 -1
  16. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/how-to/response-patterns.md +30 -1
  17. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/how-to/run-tests.md +21 -0
  18. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/how-to/streaming.md +13 -0
  19. nene2_python-1.8.34/docs/todo/current.md +52 -0
  20. {nene2_python-1.8.33 → nene2_python-1.8.34}/pyproject.toml +6 -1
  21. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/example/app.py +1 -0
  22. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/database/sqlalchemy_executor.py +10 -0
  23. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/scripts/export_openapi.py +5 -0
  24. nene2_python-1.8.34/tests/conftest.py +20 -0
  25. nene2_python-1.8.34/tests/example/comment/test_comment_http.py +112 -0
  26. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/example/comment/test_comment_repository.py +11 -10
  27. nene2_python-1.8.34/tests/example/conftest.py +20 -0
  28. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/example/note/test_note_repository.py +9 -9
  29. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/example/tag/test_tag_repository.py +9 -9
  30. nene2_python-1.8.34/tests/example/test_cors.py +75 -0
  31. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/database/test_transaction.py +19 -23
  32. {nene2_python-1.8.33 → nene2_python-1.8.34}/uv.lock +1 -1
  33. nene2_python-1.8.33/docs/todo/current.md +0 -59
  34. nene2_python-1.8.33/tests/example/comment/test_comment_http.py +0 -97
  35. nene2_python-1.8.33/tests/example/conftest.py +0 -13
  36. nene2_python-1.8.33/tests/example/test_cors.py +0 -63
  37. {nene2_python-1.8.33 → nene2_python-1.8.34}/.env.example +0 -0
  38. {nene2_python-1.8.33 → nene2_python-1.8.34}/.github/workflows/ci.yml +0 -0
  39. {nene2_python-1.8.33 → nene2_python-1.8.34}/.github/workflows/docs.yml +0 -0
  40. {nene2_python-1.8.33 → nene2_python-1.8.34}/.github/workflows/publish.yml +0 -0
  41. {nene2_python-1.8.33 → nene2_python-1.8.34}/.gitignore +0 -0
  42. {nene2_python-1.8.33 → nene2_python-1.8.34}/.vitepress/config.mts +0 -0
  43. {nene2_python-1.8.33 → nene2_python-1.8.34}/.vitepress/theme/custom.css +0 -0
  44. {nene2_python-1.8.33 → nene2_python-1.8.34}/.vitepress/theme/index.ts +0 -0
  45. {nene2_python-1.8.33 → nene2_python-1.8.34}/AGENTS.md +0 -0
  46. {nene2_python-1.8.33 → nene2_python-1.8.34}/CLAUDE.md +0 -0
  47. {nene2_python-1.8.33 → nene2_python-1.8.34}/Dockerfile +0 -0
  48. {nene2_python-1.8.33 → nene2_python-1.8.34}/LICENSE +0 -0
  49. {nene2_python-1.8.33 → nene2_python-1.8.34}/README.md +0 -0
  50. {nene2_python-1.8.33 → nene2_python-1.8.34}/alembic/README +0 -0
  51. {nene2_python-1.8.33 → nene2_python-1.8.34}/alembic/env.py +0 -0
  52. {nene2_python-1.8.33 → nene2_python-1.8.34}/alembic/script.py.mako +0 -0
  53. {nene2_python-1.8.33 → nene2_python-1.8.34}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
  54. {nene2_python-1.8.33 → nene2_python-1.8.34}/alembic.ini +0 -0
  55. {nene2_python-1.8.33 → nene2_python-1.8.34}/compose.yaml +0 -0
  56. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/adr/0001-toolchain.md +0 -0
  57. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/adr/0002-clean-architecture.md +0 -0
  58. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/adr/0003-security-first.md +0 -0
  59. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/adr/0004-ai-first-design.md +0 -0
  60. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/adr/0005-logging.md +0 -0
  61. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/adr/0006-rate-limiting.md +0 -0
  62. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/adr/0009-mcp-design.md +0 -0
  63. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/adr/0010-async-use-case.md +0 -0
  64. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
  65. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/de/index.md +0 -0
  66. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/de/tutorials/getting-started.md +0 -0
  67. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/explanation/architecture.md +0 -0
  68. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/explanation/design-philosophy.md +0 -0
  69. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-1.md +0 -0
  70. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-10.md +0 -0
  71. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-100.md +0 -0
  72. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-11.md +0 -0
  73. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-12.md +0 -0
  74. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-13.md +0 -0
  75. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-14.md +0 -0
  76. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-15.md +0 -0
  77. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-16.md +0 -0
  78. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-17.md +0 -0
  79. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-18.md +0 -0
  80. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-19.md +0 -0
  81. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-2.md +0 -0
  82. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-20.md +0 -0
  83. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-21.md +0 -0
  84. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-22.md +0 -0
  85. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-23.md +0 -0
  86. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-24.md +0 -0
  87. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-25.md +0 -0
  88. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-26.md +0 -0
  89. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-27.md +0 -0
  90. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-28.md +0 -0
  91. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-29.md +0 -0
  92. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-3.md +0 -0
  93. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-30.md +0 -0
  94. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-31.md +0 -0
  95. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-32.md +0 -0
  96. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-33.md +0 -0
  97. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-34.md +0 -0
  98. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-35.md +0 -0
  99. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-36.md +0 -0
  100. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-37.md +0 -0
  101. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-38.md +0 -0
  102. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-39.md +0 -0
  103. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-4.md +0 -0
  104. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-40.md +0 -0
  105. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-41.md +0 -0
  106. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-42.md +0 -0
  107. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-43.md +0 -0
  108. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-44.md +0 -0
  109. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-45.md +0 -0
  110. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-46.md +0 -0
  111. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-47.md +0 -0
  112. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-48.md +0 -0
  113. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-49.md +0 -0
  114. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-5.md +0 -0
  115. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-50.md +0 -0
  116. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-51.md +0 -0
  117. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-52.md +0 -0
  118. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-53.md +0 -0
  119. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-54.md +0 -0
  120. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-55.md +0 -0
  121. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-56.md +0 -0
  122. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-57.md +0 -0
  123. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-58.md +0 -0
  124. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-59.md +0 -0
  125. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-6.md +0 -0
  126. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-60.md +0 -0
  127. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-61.md +0 -0
  128. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-62.md +0 -0
  129. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-63.md +0 -0
  130. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-64.md +0 -0
  131. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-65.md +0 -0
  132. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-66.md +0 -0
  133. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-67.md +0 -0
  134. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-68.md +0 -0
  135. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-69.md +0 -0
  136. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-7.md +0 -0
  137. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-70.md +0 -0
  138. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-71.md +0 -0
  139. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-72.md +0 -0
  140. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-73.md +0 -0
  141. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-74.md +0 -0
  142. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-75.md +0 -0
  143. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-76.md +0 -0
  144. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-77.md +0 -0
  145. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-78.md +0 -0
  146. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-79.md +0 -0
  147. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-8.md +0 -0
  148. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-80.md +0 -0
  149. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-81.md +0 -0
  150. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-82.md +0 -0
  151. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-83.md +0 -0
  152. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-84.md +0 -0
  153. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-85.md +0 -0
  154. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-86.md +0 -0
  155. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-87.md +0 -0
  156. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-88.md +0 -0
  157. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-89.md +0 -0
  158. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-9.md +0 -0
  159. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-90.md +0 -0
  160. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-91.md +0 -0
  161. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-92.md +0 -0
  162. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-93.md +0 -0
  163. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-94.md +0 -0
  164. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-95.md +0 -0
  165. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-96.md +0 -0
  166. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-97.md +0 -0
  167. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-98.md +0 -0
  168. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/field-trials/2026-05-field-trial-99.md +0 -0
  169. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/fr/index.md +0 -0
  170. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/fr/tutorials/getting-started.md +0 -0
  171. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/how-to/add-new-domain.md +0 -0
  172. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/how-to/async-use-case.md +0 -0
  173. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/how-to/background-tasks.md +0 -0
  174. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/how-to/configure-auth.md +0 -0
  175. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/how-to/cors.md +0 -0
  176. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/how-to/domain-events.md +0 -0
  177. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/how-to/file-upload.md +0 -0
  178. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/how-to/middleware-stack.md +0 -0
  179. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/how-to/new-project.md +0 -0
  180. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/how-to/problem-details.md +0 -0
  181. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/how-to/sqlalchemy-repository.md +0 -0
  182. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/how-to/validation.md +0 -0
  183. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/how-to/webhook.md +0 -0
  184. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/howto/mcp-setup.md +0 -0
  185. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/index.md +0 -0
  186. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/ja/explanation/architecture.md +0 -0
  187. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/ja/explanation/design-philosophy.md +0 -0
  188. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/ja/how-to/add-new-domain.md +0 -0
  189. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/ja/how-to/configure-auth.md +0 -0
  190. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/ja/how-to/new-project.md +0 -0
  191. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/ja/how-to/run-tests.md +0 -0
  192. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
  193. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/ja/howto/mcp-setup.md +0 -0
  194. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/ja/index.md +0 -0
  195. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/ja/reference/api.md +0 -0
  196. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/ja/reference/configuration.md +0 -0
  197. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/ja/reference/framework-modules.md +0 -0
  198. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/ja/tutorials/first-domain.md +0 -0
  199. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/ja/tutorials/getting-started.md +0 -0
  200. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/pt-br/index.md +0 -0
  201. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/pt-br/tutorials/getting-started.md +0 -0
  202. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/reference/api.md +0 -0
  203. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/reference/configuration.md +0 -0
  204. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/reference/framework-modules.md +0 -0
  205. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/roadmap.md +0 -0
  206. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/tutorials/first-domain.md +0 -0
  207. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/tutorials/getting-started.md +0 -0
  208. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/zh/index.md +0 -0
  209. {nene2_python-1.8.33 → nene2_python-1.8.34}/docs/zh/tutorials/getting-started.md +0 -0
  210. {nene2_python-1.8.33 → nene2_python-1.8.34}/package-lock.json +0 -0
  211. {nene2_python-1.8.33 → nene2_python-1.8.34}/package.json +0 -0
  212. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/example/__init__.py +0 -0
  213. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/example/__main__.py +0 -0
  214. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/example/comment/__init__.py +0 -0
  215. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/example/comment/entity.py +0 -0
  216. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/example/comment/exceptions.py +0 -0
  217. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/example/comment/handler.py +0 -0
  218. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/example/comment/repository.py +0 -0
  219. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/example/comment/sqlalchemy_repository.py +0 -0
  220. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/example/comment/use_case.py +0 -0
  221. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/example/mcp.py +0 -0
  222. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/example/note/__init__.py +0 -0
  223. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/example/note/async_use_case.py +0 -0
  224. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/example/note/entity.py +0 -0
  225. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/example/note/exceptions.py +0 -0
  226. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/example/note/handler.py +0 -0
  227. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/example/note/repository.py +0 -0
  228. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/example/note/sqlalchemy_repository.py +0 -0
  229. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/example/note/use_case.py +0 -0
  230. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/example/schema.py +0 -0
  231. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/example/tag/__init__.py +0 -0
  232. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/example/tag/entity.py +0 -0
  233. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/example/tag/exceptions.py +0 -0
  234. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/example/tag/handler.py +0 -0
  235. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/example/tag/repository.py +0 -0
  236. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/example/tag/sqlalchemy_repository.py +0 -0
  237. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/example/tag/use_case.py +0 -0
  238. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/__init__.py +0 -0
  239. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/auth/__init__.py +0 -0
  240. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/auth/api_key.py +0 -0
  241. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/auth/bearer_token.py +0 -0
  242. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/auth/deps.py +0 -0
  243. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/auth/exceptions.py +0 -0
  244. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/auth/interfaces.py +0 -0
  245. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/auth/local_verifier.py +0 -0
  246. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/cache/__init__.py +0 -0
  247. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/cache/ttl.py +0 -0
  248. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/config/__init__.py +0 -0
  249. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/config/settings.py +0 -0
  250. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/database/__init__.py +0 -0
  251. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/database/exceptions.py +0 -0
  252. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/database/health.py +0 -0
  253. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/database/interfaces.py +0 -0
  254. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/database/utils.py +0 -0
  255. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/http/__init__.py +0 -0
  256. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/http/etag.py +0 -0
  257. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/http/health.py +0 -0
  258. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/http/pagination.py +0 -0
  259. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/http/problem_details.py +0 -0
  260. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/log/__init__.py +0 -0
  261. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/log/setup.py +0 -0
  262. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/mcp/__init__.py +0 -0
  263. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/mcp/http_client.py +0 -0
  264. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/mcp/server.py +0 -0
  265. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/middleware/__init__.py +0 -0
  266. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/middleware/domain_exception.py +0 -0
  267. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/middleware/error_handler.py +0 -0
  268. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/middleware/request_id.py +0 -0
  269. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/middleware/request_logging.py +0 -0
  270. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/middleware/request_size_limit.py +0 -0
  271. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/middleware/security_headers.py +0 -0
  272. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/middleware/setup.py +0 -0
  273. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/middleware/throttle.py +0 -0
  274. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/py.typed +0 -0
  275. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/security/__init__.py +0 -0
  276. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/security/webhook.py +0 -0
  277. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/use_case/__init__.py +0 -0
  278. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/use_case/protocols.py +0 -0
  279. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/validation/__init__.py +0 -0
  280. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/nene2/validation/exceptions.py +0 -0
  281. {nene2_python-1.8.33 → nene2_python-1.8.34}/src/scripts/__init__.py +0 -0
  282. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/__init__.py +0 -0
  283. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/example/__init__.py +0 -0
  284. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/example/comment/__init__.py +0 -0
  285. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/example/comment/test_comment_use_case.py +0 -0
  286. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/example/note/__init__.py +0 -0
  287. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/example/note/test_async_note_use_case.py +0 -0
  288. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/example/note/test_list_notes.py +0 -0
  289. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/example/tag/__init__.py +0 -0
  290. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/example/tag/test_tags.py +0 -0
  291. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/example/test_mcp.py +0 -0
  292. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/__init__.py +0 -0
  293. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/auth/__init__.py +0 -0
  294. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/auth/test_api_key.py +0 -0
  295. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/auth/test_bearer_token.py +0 -0
  296. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/auth/test_make_require_auth.py +0 -0
  297. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/auth/test_token_issuer.py +0 -0
  298. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/cache/__init__.py +0 -0
  299. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/cache/test_ttl.py +0 -0
  300. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/config/__init__.py +0 -0
  301. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/config/test_settings.py +0 -0
  302. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/database/__init__.py +0 -0
  303. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/database/test_utils.py +0 -0
  304. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/http/__init__.py +0 -0
  305. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/http/test_etag.py +0 -0
  306. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/http/test_health.py +0 -0
  307. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/http/test_pagination.py +0 -0
  308. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/http/test_problem_details.py +0 -0
  309. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/log/__init__.py +0 -0
  310. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/log/test_setup.py +0 -0
  311. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/mcp/__init__.py +0 -0
  312. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/mcp/test_http_client.py +0 -0
  313. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/mcp/test_server.py +0 -0
  314. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/middleware/__init__.py +0 -0
  315. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/middleware/test_error_handler.py +0 -0
  316. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/middleware/test_request_id.py +0 -0
  317. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/middleware/test_request_logging.py +0 -0
  318. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/middleware/test_request_size_limit.py +0 -0
  319. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/middleware/test_security_headers.py +0 -0
  320. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/middleware/test_setup_middlewares.py +0 -0
  321. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
  322. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/middleware/test_throttle.py +0 -0
  323. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/security/__init__.py +0 -0
  324. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/security/test_webhook.py +0 -0
  325. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/use_case/__init__.py +0 -0
  326. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/use_case/test_protocols.py +0 -0
  327. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/use_case/test_run_in_threadpool.py +0 -0
  328. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/validation/__init__.py +0 -0
  329. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/nene2/validation/test_exceptions.py +0 -0
  330. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/scripts/__init__.py +0 -0
  331. {nene2_python-1.8.33 → nene2_python-1.8.34}/tests/scripts/test_export_openapi.py +0 -0
@@ -5,6 +5,24 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
6
  ---
7
7
 
8
+ ## [1.8.34] — 2026-05-20
9
+
10
+ テストの ResourceWarning 解消と `SqlAlchemyQueryExecutor` / `SqlAlchemyTransactionManager` の `engine` プロパティ追加。
11
+
12
+ ### Added
13
+ - `SqlAlchemyQueryExecutor.engine` プロパティ — 下層の SQLAlchemy エンジンを公開(テストのティアダウンで `executor.engine.dispose()` に使用)(#428)
14
+ - `SqlAlchemyTransactionManager.engine` プロパティ — 同上 (#428)
15
+
16
+ ### Fixed
17
+ - テスト実行時に Python 3.14 で 80+ 件出ていた `ResourceWarning: unclosed database` を解消 (#428)
18
+ - `create_app()` で `app.state.db_executor` を保存してテストからエンジンを dispose できるように変更
19
+ - `export_openapi.py` でエンジンを dispose するように修正
20
+ - テストフィクスチャを `yield` + `engine.dispose()` パターンに統一
21
+ - `tests/conftest.py` を新規作成し module-level app エンジンをセッション終了時に dispose
22
+ - `StaticPool` の最後の 1 件は `pyproject.toml` の `filterwarnings` で抑制
23
+
24
+ ---
25
+
8
26
  ## [1.8.33] — 2026-05-20
9
27
 
10
28
  FT100 フィールドトライアル — In-memory TTL レスポンスキャッシュパターン検証と nene2.cache モジュール追加。
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nene2-python
3
- Version: 1.8.33
3
+ Version: 1.8.34
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,59 @@
1
+ # Field Trial 101: Query Parameter Filter/Sort パターン
2
+
3
+ ## テーマ
4
+
5
+ `?status=published&sort=views&order=desc&tag=fastapi` のような複数フィルター + ソートのクエリパラメーターを型安全に扱うパターンを nene2 上で実装する。
6
+
7
+ ## 実施内容
8
+
9
+ `/home/xi/docker/nene2-python-FT/ft101-query-filter/` に以下を実装:
10
+
11
+ - `ArticleFilter` dataclass に `StrEnum` で列挙型フィルター
12
+ - `get_article_filter()` ファクトリ関数で `Depends()` に接続
13
+ - `PaginationQueryParser` と組み合わせてページネーション
14
+ - 11 テスト通過
15
+
16
+ ## テスト結果
17
+
18
+ 全 11 テスト通過(修正後)。
19
+
20
+ ## Friction Points
21
+
22
+ ### FP1: `PaginationQueryParser.as_depends()` が存在しない(ドキュメントとの乖離)
23
+
24
+ **状況**: 他のパーサー系クラスで `as_depends()` ファクトリパターンを使うケースがあり(FT83 など)、`PaginationQueryParser.as_depends()` を試みたが `AttributeError`。
25
+
26
+ 正しい使い方は:
27
+ ```python
28
+ # ✅ Annotated スタイル
29
+ pagination: Annotated[PaginationQueryParser, Depends()]
30
+
31
+ # ❌ as_depends() は存在しない
32
+ pagination: PaginationQueryParser = Depends(PaginationQueryParser.as_depends())
33
+ ```
34
+
35
+ **影響**: `Depends()` の使い方が複数あるため混乱しやすい。how-to ガイドに明記が必要。
36
+
37
+ ### FP2: `Depends()` スタイルとデフォルト値の混在でシンタックスエラー
38
+
39
+ **状況**: 複数の `Depends()` パラメーターを持つハンドラーで、`= Depends(...)` スタイルと `Annotated[T, Depends()]` スタイルを混在させると `SyntaxError: parameter without a default follows parameter with a default` が出る。
40
+
41
+ ```python
42
+ # ❌ SyntaxError
43
+ def list_articles(
44
+ filter_: ArticleFilter = Depends(get_article_filter), # デフォルト値あり
45
+ pagination: Annotated[PaginationQueryParser, Depends()], # デフォルト値なし
46
+ ) -> JSONResponse: ...
47
+
48
+ # ✅ Annotated スタイルに統一
49
+ def list_articles(
50
+ filter_: Annotated[ArticleFilter, Depends(get_article_filter)],
51
+ pagination: Annotated[PaginationQueryParser, Depends()],
52
+ ) -> JSONResponse: ...
53
+ ```
54
+
55
+ **影響**: エラーメッセージが直感的でなく、原因がわかりにくい。
56
+
57
+ ## まとめ
58
+
59
+ 摩擦の原因はドキュメント不足。FP1・FP2 を how-to ガイドに追記する Issue を起票する。コード修正は不要。
@@ -0,0 +1,34 @@
1
+ # Field Trial 102: response_model と PaginationResponse の型整合性
2
+
3
+ ## テーマ
4
+
5
+ `response_model` と `PaginationResponse` / `JSONResponse` の組み合わせパターンを実際に動かして、OpenAPI スキーマへの影響と型整合性を検証する。
6
+
7
+ ## 実施内容
8
+
9
+ `/home/xi/docker/nene2-python-FT/ft102-response-model/` に 4 パターンを実装:
10
+
11
+ 1. **v1**: `response_model` なし + `JSONResponse` + `PaginationResponse`
12
+ 2. **v2**: `response_model=ProductListResponse` + Pydantic モデル直返し
13
+ 3. **v3**: `response_model=ProductListResponse` + `dict` 返却(FastAPI が変換)
14
+ 4. **v4**: `response_model=ProductListResponse` + `JSONResponse`(検証はされない)
15
+
16
+ ## テスト結果
17
+
18
+ 全 9 テスト通過。
19
+
20
+ ## Friction Points
21
+
22
+ 摩擦なし。
23
+
24
+ `response-patterns.md` How-to(PR #403 で追加済み)がこのパターンを説明しており、スムーズに実装できた。
25
+
26
+ - パターン1(JSONResponse + PaginationResponse)は OpenAPI スキーマなしで OK
27
+ - パターン2(Pydantic 直返し + response_model)は OpenAPI スキーマあり
28
+ - パターン4(JSONResponse + response_model)は response_model があってもバリデーション非実施
29
+
30
+ 前回の摩擦(`problem_details_response()` と Pydantic 直返しの非一貫性)は PR #403 で文書化済み。
31
+
32
+ ## まとめ
33
+
34
+ ドキュメント整備が功を奏し、摩擦ゼロで実装完了。
@@ -0,0 +1,47 @@
1
+ # Field Trial 103: カスタムミドルウェアで認証情報をリクエストスコープに格納
2
+
3
+ ## テーマ
4
+
5
+ JWT ミドルウェアで検証後の認証情報(`AuthUser`)を `request.state` に格納し、ハンドラーで `Depends()` を通じて取得するパターンを検証する。
6
+
7
+ ## 実施内容
8
+
9
+ `/home/xi/docker/nene2-python-FT/ft103-request-state-auth/` に以下を実装:
10
+
11
+ - `JwtAuthMiddleware` — JWT を検証して `request.state.user` に `AuthUser` を格納
12
+ - `get_current_user()` — `request.state.user` から `AuthUser` を取得する Depends ファクトリ
13
+ - `require_admin()` — `admin` ロールチェック依存
14
+ - `EXCLUDE_PATHS` で `/health` をスキップ
15
+
16
+ ## テスト結果
17
+
18
+ 全 7 テスト通過。`InsecureKeyLengthWarning` はテスト用の短いシークレットによるもの(想定内)。
19
+
20
+ ## Friction Points
21
+
22
+ ### FP1: `request.state.user` アクセスに `type: ignore[attr-defined]` が必要
23
+
24
+ **状況**: `request.state` は `starlette.datastructures.State` で動的属性を持つ。ミドルウェアで `request.state.user = AuthUser(...)` と設定しても、Depends ファクトリで `request.state.user` を参照する際に mypy が `attr-defined` エラーを出す。
25
+
26
+ ```python
27
+ def get_current_user(request: Request) -> AuthUser:
28
+ user: AuthUser = request.state.user # type: ignore[attr-defined] # reason: middleware で確実に設定済み
29
+ return user
30
+ ```
31
+
32
+ これは `app.state` でも同様(FT100 で確認済み)。
33
+
34
+ **影響**: 低。`type: ignore` + `reason` コメントで対処可能。
35
+
36
+ ### FP2: `BearerTokenMiddleware` のトークン文字列が `request.state` に格納されない
37
+
38
+ **状況**: nene2 の `BearerTokenMiddleware` は JWT/API キーの検証後にトークン文字列を返す(`make_require_auth()` Depends 経由)が、検証済みのペイロードやユーザー情報を `request.state` に自動格納する機能がない。
39
+
40
+ カスタム JWT ミドルウェアで対応したが、`BearerTokenMiddleware` + `request.state` の統合パターンがない。
41
+
42
+ **影響**: 中。`BearerTokenMiddleware` を使いたいが `request.state` にユーザー情報を格納したい場合、自前ミドルウェアを書き直す必要がある。
43
+
44
+ ## まとめ
45
+
46
+ FP1 は既知の Starlette 制約(low)。FP2 は nene2 の認証統合に関する中程度の摩擦。
47
+ docs として「request.state を使った認証情報伝播パターン」を how-to に追加する。
@@ -0,0 +1,48 @@
1
+ # Field Trial 104: AsyncIterator を返す UseCase + StreamingResponse
2
+
3
+ ## テーマ
4
+
5
+ UseCase が `AsyncIterator` を返し、FastAPI の `StreamingResponse` でストリーミングする本格的なパターンを検証する。
6
+
7
+ ## 実施内容
8
+
9
+ `/home/xi/docker/nene2-python-FT/ft104-streaming-usecase/` に以下を実装:
10
+
11
+ - `StreamLogsUseCase` — `AsyncIterator[LogEntry]` を返す UseCase
12
+ - `ExportCsvUseCase` — CSV 行を `AsyncIterator[str]` で返す UseCase
13
+ - NDJSON / SSE / CSV の 3 形式にストリーミング変換
14
+ - 6 テスト通過
15
+
16
+ ## テスト結果
17
+
18
+ 全 6 テスト通過(修正後)。
19
+
20
+ ## Friction Points
21
+
22
+ ### FP1: `TestClient.stream()` 内で `r.text` が使えない
23
+
24
+ **状況**: `with client.stream("GET", "/export/users.csv") as r:` コンテキスト内で `r.text` にアクセスすると `httpx.ResponseNotRead` が発生する。
25
+
26
+ ```python
27
+ # ❌ ResponseNotRead
28
+ with client.stream("GET", "/export/csv") as r:
29
+ content = r.text # エラー!
30
+
31
+ # ✅ iter_text() でチャンク収集
32
+ with client.stream("GET", "/export/csv") as r:
33
+ content = "".join(r.iter_text())
34
+ ```
35
+
36
+ **影響**: ストリーミングレスポンスのテストで直感に反するエラーが出る。`streaming.md` How-to に `iter_text()` パターンを追記する必要がある。
37
+
38
+ ### FP2: `AsyncUseCaseProtocol` は `AsyncIterator` を返す UseCase に対応していない
39
+
40
+ **状況**: `AsyncUseCaseProtocol` は `async def execute(self, input_: I) -> O` を定義しており、`O = AsyncIterator[T]` として使うことは技術的には可能だが、`O` 型が `AsyncIterator` であることを表現するのが難しい。
41
+
42
+ 現在の実装では `AsyncUseCaseProtocol` を使わず、独立した UseCase クラスとして実装した。
43
+
44
+ **影響**: 低。ストリーミング UseCase は専用の Protocol を定義するか、型注釈なしで書くのが現実的。
45
+
46
+ ## まとめ
47
+
48
+ FP1 をドキュメント修正で対応。FP2 は将来の検討事項として記録。
@@ -0,0 +1,43 @@
1
+ # Field Trial 105: マルチテナント DB 接続
2
+
3
+ ## テーマ
4
+
5
+ `X-Tenant-Id` ヘッダーでテナントを切り替え、リクエストごとに異なる SQLite DB に接続するマルチテナントパターンを検証する。
6
+
7
+ ## 実施内容
8
+
9
+ `/home/xi/docker/nene2-python-FT/ft105-multitenant/` に以下を実装:
10
+
11
+ - `TENANT_DB_URLS` マップでテナント DB URL を管理
12
+ - `get_tenant_session()` Depends でテナント特定・セッション生成
13
+ - テナント分離テスト(A と B は独立した DB)
14
+
15
+ ## テスト結果
16
+
17
+ 全 6 テスト通過。
18
+
19
+ ## Friction Points
20
+
21
+ ### FP1: `create_engine()` をリクエストごとに呼ぶのはパフォーマンス上の問題
22
+
23
+ **状況**: `get_tenant_session()` がリクエストごとに `create_engine()` を呼ぶ。SQLAlchemy では `create_engine()` はアプリ起動時に一度だけ呼ぶべきで、コネクションプールを使い回す設計が推奨される。
24
+
25
+ nene2 にテナントごとのエンジンキャッシュパターンがない。`TtlCache[Session]` で代替できるが、セッションはリクエストスコープで閉じる必要があるため、エンジンをキャッシュすべき。
26
+
27
+ ```python
28
+ # 推奨パターン
29
+ _engine_cache: dict[str, Engine] = {}
30
+
31
+ def get_engine(tenant_id: str) -> Engine:
32
+ if tenant_id not in _engine_cache:
33
+ _engine_cache[tenant_id] = create_engine(TENANT_DB_URLS[tenant_id], ...)
34
+ return _engine_cache[tenant_id]
35
+ ```
36
+
37
+ ### FP2: `SqlAlchemyQueryExecutor` はシングルエンジン想定
38
+
39
+ **状況**: `nene2.database.SqlAlchemyQueryExecutor` はアプリ起動時に単一エンジンで初期化することを前提としている。マルチテナントでリクエストごとに異なるエンジンを使う場合、`SqlAlchemyQueryExecutor` を使わず直接 `Session` を操作する必要がある。
40
+
41
+ ## まとめ
42
+
43
+ FP1 は `TtlCache` を使ったエンジンキャッシュパターンとして docs に追記する。FP2 はアーキテクチャ設計上の制約として記録。コード修正は不要。
@@ -0,0 +1,39 @@
1
+ # Field Trial 106: Idempotency Key パターン
2
+
3
+ ## テーマ
4
+
5
+ `Idempotency-Key` ヘッダーを使って POST リクエストを冪等にするパターンを検証する。
6
+ `nene2.cache.TtlCache` の実用例として組み込む。
7
+
8
+ ## 実施内容
9
+
10
+ `/home/xi/docker/nene2-python-FT/ft106-idempotency/` に以下を実装:
11
+
12
+ - `TtlCache[dict[str, Any]]` を `app.state.idempotency_cache` として lifespan で初期化
13
+ - `POST /orders` で `Idempotency-Key` ヘッダーをチェック
14
+ - 既存キャッシュがあれば `X-Idempotency-Replayed: true` ヘッダーと共にキャッシュから返す
15
+ - キャッシュなければ UseCase を実行してキャッシュに保存
16
+ - 7 テスト通過
17
+
18
+ ## テスト結果
19
+
20
+ 全 7 テスト通過。
21
+
22
+ ## Friction Points
23
+
24
+ ### FP1: Idempotency Key 同一キー + 異なるボディの扱いが未定義
25
+
26
+ **状況**: Stripe などの実装では、同じ `Idempotency-Key` で異なるリクエストボディを送ると `422 Unprocessable Entity` を返す。現在の実装ではキャッシュされた最初のレスポンスを無条件に返すため、ボディが変わっても同じレスポンスが返る。
27
+
28
+ **影響**: 中。金融系 API では重要だが、一般的な API では省略可。
29
+
30
+ ### FP2: Idempotency Key ユーティリティがない
31
+
32
+ **状況**: `TtlCache` を使って実装できたが、`get_idempotency_cache()` Depends パターンや `X-Idempotency-Replayed` ヘッダーの付与は完全に自前実装。Stripe / Square などで標準化されたパターンであるため、nene2 に軽量ユーティリティがあると便利。
33
+
34
+ **影響**: 低。`TtlCache` + ハンドラーコードで完結可能。
35
+
36
+ ## まとめ
37
+
38
+ `TtlCache` の実用例として Idempotency Key パターンはスムーズに実装できた。摩擦は小さい。
39
+ docs として how-to を追加する。
@@ -0,0 +1,46 @@
1
+ # Field Trial 107: Bulk Operations(一括作成・削除)
2
+
3
+ ## テーマ
4
+
5
+ `POST /items/bulk` と `DELETE /items/bulk` による一括操作パターンを検証する。
6
+ 部分成功(一部成功・一部失敗)を 207 Multi-Status で返す方法も確認する。
7
+
8
+ ## 実施内容
9
+
10
+ `/home/xi/docker/nene2-python-FT/ft107-bulk-ops/` に以下を実装:
11
+
12
+ - `POST /items/bulk` — 一括作成(価格制限でビジネスバリデーション、部分失敗対応)
13
+ - `DELETE /items/bulk` — 一括削除(存在しない ID は failed に入れる)
14
+ - 207 Multi-Status レスポンスに `succeeded` / `failed` を含める
15
+ - 8 テスト通過(修正後)
16
+
17
+ ## テスト結果
18
+
19
+ 全 8 テスト通過(修正後)。
20
+
21
+ ## Friction Points
22
+
23
+ ### FP1: `TestClient.delete()` が `json` パラメーターを受け付けない
24
+
25
+ **状況**: `DELETE` リクエストにリクエストボディを付ける場合、`client.delete(url, json=body)` は `TypeError` になる。
26
+
27
+ ```python
28
+ # ❌ TypeError: unexpected keyword argument 'json'
29
+ r = client.delete("/items/bulk", json={"ids": [1, 2]})
30
+
31
+ # ✅ request() を使う
32
+ r = client.request("DELETE", "/items/bulk", json={"ids": [1, 2]})
33
+ ```
34
+
35
+ **影響**: 中。DELETE + ボディはやや非標準だが、一括削除では一般的なパターン。テストコードが `request()` を直接使う必要があるため直感的でない。
36
+
37
+ **代替案**: ボディを持つ DELETE の代わりに `POST /items/bulk-delete` にする方が REST 的にクリーン(ボディを持つ DELETE は RFC 9110 で「意味がないわけではないが、推奨されない」)。
38
+
39
+ ### FP2: 207 Multi-Status のレスポンス型が OpenAPI スキーマに表現しにくい
40
+
41
+ **状況**: `response_model` で 207 のスキーマを定義しようとすると、succeeded/failed の型が複雑になる。実用的には `response_model` なしで `JSONResponse` を直接返すのが現実的。
42
+
43
+ ## まとめ
44
+
45
+ FP1 は how-to に追記(`TestClient` の HTTP メソッドと `json` パラメーターの注意点)。
46
+ FP2 はドキュメント摩擦(bulk 操作は `response_model` を省略して OK)。
@@ -0,0 +1,46 @@
1
+ # Field Trial 108: Pydantic computed_field と property パターン
2
+
3
+ ## テーマ
4
+
5
+ Pydantic v2 の `@computed_field` + `@property` を使って、ストアドフィールドから計算されるプロパティを
6
+ OpenAPI スキーマに自動的に含めるパターンを検証する。
7
+
8
+ ## 実施内容
9
+
10
+ `/home/xi/docker/nene2-python-FT/ft108-computed-field/` に以下を実装:
11
+
12
+ - `ProductResponse` — `price_dollars`, `is_in_stock`, `display_name` の computed fields
13
+ - `OrderLineResponse` — ネストした computed fields (`line_total_cents`, `line_total_dollars`)
14
+ - `/order-preview` エンドポイントで `JSONResponse(line.model_dump(mode="json"))` パターン確認
15
+ - 6 テスト通過
16
+
17
+ ## テスト結果
18
+
19
+ 全 6 テスト通過。
20
+
21
+ ## Friction Points
22
+
23
+ ### FP1: `model_dump()` が datetime を Python オブジェクトのまま返す → `JSONResponse` で 500 エラー
24
+
25
+ **状況**: `OrderLineResponse` のネストモデルには `created_at: datetime` フィールドがある。
26
+ `JSONResponse(line.model_dump())` を使うと、`json.dumps` が `datetime` を直列化できずに
27
+ `TypeError: Object of type datetime is not JSON serializable` で 500 エラーになる。
28
+
29
+ ```python
30
+ # ❌ model_dump() は datetime をそのまま返す → JSONResponse でエラー
31
+ return JSONResponse(line.model_dump())
32
+
33
+ # ✅ mode="json" を指定すると datetime を ISO 8601 文字列に変換する
34
+ return JSONResponse(line.model_dump(mode="json"))
35
+ ```
36
+
37
+ **影響**: 大。`response_model=` を使う通常のルートは FastAPI が自動変換するため問題ないが、
38
+ `JSONResponse` を直接返すルート(207 Multi-Status, /order-preview など)でこのパターンを
39
+ 使い忘れると本番で 500 エラーになる。
40
+
41
+ **代替案**: `jsonable_encoder(line.model_dump())` でも変換できるが、`mode="json"` の方が Pydantic 標準。
42
+
43
+ ## まとめ
44
+
45
+ `@computed_field` + `@property` は摩擦ゼロで OpenAPI スキーマに含められる優れたパターン。
46
+ FP1 は `JSONResponse` を直接使う場合の注意点として how-to に追記する。
@@ -0,0 +1,53 @@
1
+ # Field Trial 109: API バージョニング(v1/v2 ルーティング)
2
+
3
+ ## テーマ
4
+
5
+ FastAPI の `APIRouter` を使って `/v1` と `/v2` でエンドポイントを分離するパターンを検証する。
6
+ - フィールド名の変更(`email` → `contact_email`)
7
+ - フィールドの追加(`age` を v2 で追加)
8
+ - `full_name` から `first_name`/`last_name` への分割
9
+ - OpenAPI スキーマが両バージョンを正確に反映するか
10
+
11
+ ## 実施内容
12
+
13
+ `/home/xi/docker/nene2-python-FT/ft109-api-versioning/` に以下を実装:
14
+
15
+ - ドメイン `User` dataclass(バージョン非依存)
16
+ - `UserResponseV1` — `full_name`, `email`
17
+ - `UserResponseV2` — `first_name`, `last_name`, `contact_email`, `age`
18
+ - `v1_router = APIRouter(prefix="/v1", tags=["v1"])`
19
+ - `v2_router = APIRouter(prefix="/v2", tags=["v2"])`
20
+ - 9 テスト通過
21
+
22
+ ## テスト結果
23
+
24
+ 全 9 テスト一発通過。摩擦ゼロ。
25
+
26
+ ## Friction Points
27
+
28
+ なし。`APIRouter(prefix="/v1")` でルーターを分離し `app.include_router()` で登録するだけで
29
+ バージョン分離が完成する。OpenAPI スキーマも `UserResponseV1` / `UserResponseV2` を
30
+ 個別に定義することで両バージョンのスキーマが正確に生成される。
31
+
32
+ ## 観察
33
+
34
+ ### O1: バージョン間の共有ロジックはドメイン層に置く
35
+
36
+ `find_user()` などのクエリ関数はバージョン非依存のドメイン層に置き、
37
+ 各バージョンのハンドラーから呼ぶ設計が自然に機能した。
38
+ `from_domain()` クラスメソッドで変換することでドメインと HTTP を分離できた。
39
+
40
+ ### O2: OpenAPI タグでバージョンを整理できる
41
+
42
+ `tags=["v1"]` / `tags=["v2"]` を `APIRouter` に指定すると、
43
+ Swagger UI でバージョンごとにグループ化されて見やすくなる。
44
+
45
+ ### O3: `APIRouter` prefix は重複しない
46
+
47
+ `app.include_router(v1_router)` で登録すると `/v1/users` になる。
48
+ ルーター内のパスに `/v1` を書く必要はない(二重にならない)。
49
+
50
+ ## まとめ
51
+
52
+ FT109 は摩擦ゼロ確認。APIバージョニングは FastAPI の `APIRouter` + `prefix` で
53
+ 自然に実現できる。how-to ドキュメントに記録のみ。
@@ -0,0 +1,92 @@
1
+ # How-to: API バージョニング
2
+
3
+ FastAPI の `APIRouter` + `prefix` でエンドポイントを `/v1`, `/v2` に分離するパターン。
4
+
5
+ ---
6
+
7
+ ## 基本構成
8
+
9
+ ```python
10
+ from fastapi import APIRouter, FastAPI
11
+
12
+ app = FastAPI()
13
+
14
+ v1_router = APIRouter(prefix="/v1", tags=["v1"])
15
+ v2_router = APIRouter(prefix="/v2", tags=["v2"])
16
+
17
+ @v1_router.get("/users")
18
+ def list_users_v1() -> list[UserResponseV1]:
19
+ ...
20
+
21
+ @v2_router.get("/users")
22
+ def list_users_v2() -> list[UserResponseV2]:
23
+ ...
24
+
25
+ app.include_router(v1_router)
26
+ app.include_router(v2_router)
27
+ ```
28
+
29
+ `APIRouter` の `prefix` はルーター内のパスに **自動的に付加**される。
30
+ ルーター内のパスに `/v1` を書く必要はない(二重にならない)。
31
+
32
+ ---
33
+
34
+ ## ドメイン層は共有、HTTP 層でバージョン差分を吸収
35
+
36
+ バージョン間の共有ロジックはドメイン層に置き、各バージョンの Pydantic モデルで差分を表現する。
37
+
38
+ ```python
39
+ # ドメイン層(バージョン非依存)
40
+ @dataclass(frozen=True, slots=True)
41
+ class User:
42
+ user_id: int
43
+ first_name: str
44
+ last_name: str
45
+ email: str
46
+ age: int
47
+
48
+ # v1: full_name に結合
49
+ class UserResponseV1(BaseModel):
50
+ user_id: int
51
+ full_name: str
52
+ email: str
53
+
54
+ @classmethod
55
+ def from_domain(cls, user: User) -> "UserResponseV1":
56
+ return cls(
57
+ user_id=user.user_id,
58
+ full_name=f"{user.first_name} {user.last_name}",
59
+ email=user.email,
60
+ )
61
+
62
+ # v2: first_name/last_name を分離 + age を追加 + email → contact_email
63
+ class UserResponseV2(BaseModel):
64
+ user_id: int
65
+ first_name: str
66
+ last_name: str
67
+ contact_email: str
68
+ age: int
69
+
70
+ @classmethod
71
+ def from_domain(cls, user: User) -> "UserResponseV2":
72
+ return cls(
73
+ user_id=user.user_id,
74
+ first_name=user.first_name,
75
+ last_name=user.last_name,
76
+ contact_email=user.email,
77
+ age=user.age,
78
+ )
79
+ ```
80
+
81
+ ---
82
+
83
+ ## OpenAPI スキーマ
84
+
85
+ `tags=["v1"]` / `tags=["v2"]` を `APIRouter` に指定すると Swagger UI でバージョンごとに
86
+ グループ化される。スキーマには `UserResponseV1` / `UserResponseV2` が個別に定義される。
87
+
88
+ ---
89
+
90
+ ## 参照
91
+
92
+ - FT109: `docs/field-trials/2026-05-field-trial-109.md`