nene2-python 1.8.7__tar.gz → 1.8.9__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 (238) hide show
  1. {nene2_python-1.8.7 → nene2_python-1.8.9}/CHANGELOG.md +24 -0
  2. {nene2_python-1.8.7 → nene2_python-1.8.9}/PKG-INFO +1 -1
  3. nene2_python-1.8.9/docs/field-trials/2026-05-field-trial-45.md +108 -0
  4. nene2_python-1.8.9/docs/field-trials/2026-05-field-trial-46.md +128 -0
  5. nene2_python-1.8.9/docs/field-trials/2026-05-field-trial-47.md +93 -0
  6. nene2_python-1.8.9/docs/field-trials/2026-05-field-trial-48.md +96 -0
  7. nene2_python-1.8.9/docs/field-trials/2026-05-field-trial-49.md +96 -0
  8. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/how-to/run-tests.md +11 -0
  9. {nene2_python-1.8.7 → nene2_python-1.8.9}/pyproject.toml +1 -1
  10. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/middleware/security_headers.py +1 -1
  11. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/nene2/middleware/test_security_headers.py +13 -0
  12. {nene2_python-1.8.7 → nene2_python-1.8.9}/uv.lock +1 -1
  13. {nene2_python-1.8.7 → nene2_python-1.8.9}/.env.example +0 -0
  14. {nene2_python-1.8.7 → nene2_python-1.8.9}/.github/workflows/ci.yml +0 -0
  15. {nene2_python-1.8.7 → nene2_python-1.8.9}/.github/workflows/docs.yml +0 -0
  16. {nene2_python-1.8.7 → nene2_python-1.8.9}/.github/workflows/publish.yml +0 -0
  17. {nene2_python-1.8.7 → nene2_python-1.8.9}/.gitignore +0 -0
  18. {nene2_python-1.8.7 → nene2_python-1.8.9}/.vitepress/config.mts +0 -0
  19. {nene2_python-1.8.7 → nene2_python-1.8.9}/.vitepress/theme/custom.css +0 -0
  20. {nene2_python-1.8.7 → nene2_python-1.8.9}/.vitepress/theme/index.ts +0 -0
  21. {nene2_python-1.8.7 → nene2_python-1.8.9}/AGENTS.md +0 -0
  22. {nene2_python-1.8.7 → nene2_python-1.8.9}/CLAUDE.md +0 -0
  23. {nene2_python-1.8.7 → nene2_python-1.8.9}/Dockerfile +0 -0
  24. {nene2_python-1.8.7 → nene2_python-1.8.9}/LICENSE +0 -0
  25. {nene2_python-1.8.7 → nene2_python-1.8.9}/README.md +0 -0
  26. {nene2_python-1.8.7 → nene2_python-1.8.9}/alembic/README +0 -0
  27. {nene2_python-1.8.7 → nene2_python-1.8.9}/alembic/env.py +0 -0
  28. {nene2_python-1.8.7 → nene2_python-1.8.9}/alembic/script.py.mako +0 -0
  29. {nene2_python-1.8.7 → nene2_python-1.8.9}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
  30. {nene2_python-1.8.7 → nene2_python-1.8.9}/alembic.ini +0 -0
  31. {nene2_python-1.8.7 → nene2_python-1.8.9}/compose.yaml +0 -0
  32. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/adr/0001-toolchain.md +0 -0
  33. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/adr/0002-clean-architecture.md +0 -0
  34. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/adr/0003-security-first.md +0 -0
  35. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/adr/0004-ai-first-design.md +0 -0
  36. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/adr/0005-logging.md +0 -0
  37. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/adr/0006-rate-limiting.md +0 -0
  38. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/adr/0009-mcp-design.md +0 -0
  39. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/adr/0010-async-use-case.md +0 -0
  40. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
  41. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/de/index.md +0 -0
  42. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/de/tutorials/getting-started.md +0 -0
  43. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/explanation/architecture.md +0 -0
  44. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/explanation/design-philosophy.md +0 -0
  45. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-1.md +0 -0
  46. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-10.md +0 -0
  47. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-11.md +0 -0
  48. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-12.md +0 -0
  49. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-13.md +0 -0
  50. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-14.md +0 -0
  51. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-15.md +0 -0
  52. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-16.md +0 -0
  53. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-17.md +0 -0
  54. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-18.md +0 -0
  55. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-19.md +0 -0
  56. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-2.md +0 -0
  57. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-20.md +0 -0
  58. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-21.md +0 -0
  59. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-22.md +0 -0
  60. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-23.md +0 -0
  61. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-24.md +0 -0
  62. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-25.md +0 -0
  63. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-26.md +0 -0
  64. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-27.md +0 -0
  65. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-28.md +0 -0
  66. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-29.md +0 -0
  67. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-3.md +0 -0
  68. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-30.md +0 -0
  69. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-31.md +0 -0
  70. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-32.md +0 -0
  71. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-33.md +0 -0
  72. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-34.md +0 -0
  73. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-35.md +0 -0
  74. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-36.md +0 -0
  75. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-37.md +0 -0
  76. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-38.md +0 -0
  77. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-39.md +0 -0
  78. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-4.md +0 -0
  79. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-40.md +0 -0
  80. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-41.md +0 -0
  81. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-42.md +0 -0
  82. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-43.md +0 -0
  83. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-44.md +0 -0
  84. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-5.md +0 -0
  85. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-6.md +0 -0
  86. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-7.md +0 -0
  87. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-8.md +0 -0
  88. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/field-trials/2026-05-field-trial-9.md +0 -0
  89. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/fr/index.md +0 -0
  90. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/fr/tutorials/getting-started.md +0 -0
  91. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/how-to/add-new-domain.md +0 -0
  92. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/how-to/async-use-case.md +0 -0
  93. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/how-to/configure-auth.md +0 -0
  94. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/how-to/new-project.md +0 -0
  95. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/how-to/problem-details.md +0 -0
  96. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/how-to/sqlalchemy-repository.md +0 -0
  97. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/how-to/validation.md +0 -0
  98. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/howto/mcp-setup.md +0 -0
  99. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/index.md +0 -0
  100. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/ja/explanation/architecture.md +0 -0
  101. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/ja/explanation/design-philosophy.md +0 -0
  102. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/ja/how-to/add-new-domain.md +0 -0
  103. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/ja/how-to/configure-auth.md +0 -0
  104. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/ja/how-to/new-project.md +0 -0
  105. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/ja/how-to/run-tests.md +0 -0
  106. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
  107. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/ja/howto/mcp-setup.md +0 -0
  108. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/ja/index.md +0 -0
  109. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/ja/reference/api.md +0 -0
  110. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/ja/reference/configuration.md +0 -0
  111. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/ja/reference/framework-modules.md +0 -0
  112. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/ja/tutorials/first-domain.md +0 -0
  113. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/ja/tutorials/getting-started.md +0 -0
  114. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/pt-br/index.md +0 -0
  115. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/pt-br/tutorials/getting-started.md +0 -0
  116. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/reference/api.md +0 -0
  117. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/reference/configuration.md +0 -0
  118. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/reference/framework-modules.md +0 -0
  119. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/roadmap.md +0 -0
  120. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/todo/current.md +0 -0
  121. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/tutorials/first-domain.md +0 -0
  122. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/tutorials/getting-started.md +0 -0
  123. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/zh/index.md +0 -0
  124. {nene2_python-1.8.7 → nene2_python-1.8.9}/docs/zh/tutorials/getting-started.md +0 -0
  125. {nene2_python-1.8.7 → nene2_python-1.8.9}/package-lock.json +0 -0
  126. {nene2_python-1.8.7 → nene2_python-1.8.9}/package.json +0 -0
  127. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/example/__init__.py +0 -0
  128. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/example/__main__.py +0 -0
  129. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/example/app.py +0 -0
  130. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/example/comment/__init__.py +0 -0
  131. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/example/comment/entity.py +0 -0
  132. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/example/comment/exceptions.py +0 -0
  133. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/example/comment/handler.py +0 -0
  134. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/example/comment/repository.py +0 -0
  135. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/example/comment/sqlalchemy_repository.py +0 -0
  136. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/example/comment/use_case.py +0 -0
  137. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/example/mcp.py +0 -0
  138. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/example/note/__init__.py +0 -0
  139. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/example/note/async_use_case.py +0 -0
  140. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/example/note/entity.py +0 -0
  141. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/example/note/exceptions.py +0 -0
  142. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/example/note/handler.py +0 -0
  143. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/example/note/repository.py +0 -0
  144. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/example/note/sqlalchemy_repository.py +0 -0
  145. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/example/note/use_case.py +0 -0
  146. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/example/schema.py +0 -0
  147. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/example/tag/__init__.py +0 -0
  148. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/example/tag/entity.py +0 -0
  149. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/example/tag/exceptions.py +0 -0
  150. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/example/tag/handler.py +0 -0
  151. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/example/tag/repository.py +0 -0
  152. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/example/tag/sqlalchemy_repository.py +0 -0
  153. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/example/tag/use_case.py +0 -0
  154. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/__init__.py +0 -0
  155. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/auth/__init__.py +0 -0
  156. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/auth/api_key.py +0 -0
  157. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/auth/bearer_token.py +0 -0
  158. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/auth/exceptions.py +0 -0
  159. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/auth/interfaces.py +0 -0
  160. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/auth/local_verifier.py +0 -0
  161. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/config/__init__.py +0 -0
  162. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/config/settings.py +0 -0
  163. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/database/__init__.py +0 -0
  164. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/database/exceptions.py +0 -0
  165. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/database/health.py +0 -0
  166. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/database/interfaces.py +0 -0
  167. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/database/sqlalchemy_executor.py +0 -0
  168. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/database/utils.py +0 -0
  169. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/http/__init__.py +0 -0
  170. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/http/health.py +0 -0
  171. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/http/pagination.py +0 -0
  172. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/http/problem_details.py +0 -0
  173. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/log/__init__.py +0 -0
  174. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/log/setup.py +0 -0
  175. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/mcp/__init__.py +0 -0
  176. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/mcp/http_client.py +0 -0
  177. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/mcp/server.py +0 -0
  178. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/middleware/__init__.py +0 -0
  179. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/middleware/domain_exception.py +0 -0
  180. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/middleware/error_handler.py +0 -0
  181. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/middleware/request_id.py +0 -0
  182. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/middleware/request_logging.py +0 -0
  183. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/middleware/request_size_limit.py +0 -0
  184. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/middleware/throttle.py +0 -0
  185. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/py.typed +0 -0
  186. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/use_case/__init__.py +0 -0
  187. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/use_case/protocols.py +0 -0
  188. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/validation/__init__.py +0 -0
  189. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/nene2/validation/exceptions.py +0 -0
  190. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/scripts/__init__.py +0 -0
  191. {nene2_python-1.8.7 → nene2_python-1.8.9}/src/scripts/export_openapi.py +0 -0
  192. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/__init__.py +0 -0
  193. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/example/__init__.py +0 -0
  194. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/example/comment/__init__.py +0 -0
  195. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/example/comment/test_comment_http.py +0 -0
  196. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/example/comment/test_comment_repository.py +0 -0
  197. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/example/comment/test_comment_use_case.py +0 -0
  198. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/example/conftest.py +0 -0
  199. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/example/note/__init__.py +0 -0
  200. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/example/note/test_async_note_use_case.py +0 -0
  201. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/example/note/test_list_notes.py +0 -0
  202. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/example/note/test_note_repository.py +0 -0
  203. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/example/tag/__init__.py +0 -0
  204. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/example/tag/test_tag_repository.py +0 -0
  205. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/example/tag/test_tags.py +0 -0
  206. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/example/test_cors.py +0 -0
  207. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/example/test_mcp.py +0 -0
  208. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/nene2/__init__.py +0 -0
  209. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/nene2/auth/__init__.py +0 -0
  210. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/nene2/auth/test_api_key.py +0 -0
  211. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/nene2/auth/test_bearer_token.py +0 -0
  212. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/nene2/auth/test_token_issuer.py +0 -0
  213. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/nene2/config/__init__.py +0 -0
  214. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/nene2/config/test_settings.py +0 -0
  215. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/nene2/database/__init__.py +0 -0
  216. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/nene2/database/test_transaction.py +0 -0
  217. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/nene2/database/test_utils.py +0 -0
  218. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/nene2/http/__init__.py +0 -0
  219. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/nene2/http/test_health.py +0 -0
  220. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/nene2/http/test_pagination.py +0 -0
  221. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/nene2/http/test_problem_details.py +0 -0
  222. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/nene2/log/__init__.py +0 -0
  223. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/nene2/log/test_setup.py +0 -0
  224. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/nene2/mcp/__init__.py +0 -0
  225. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/nene2/mcp/test_http_client.py +0 -0
  226. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/nene2/middleware/__init__.py +0 -0
  227. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/nene2/middleware/test_error_handler.py +0 -0
  228. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/nene2/middleware/test_request_id.py +0 -0
  229. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/nene2/middleware/test_request_logging.py +0 -0
  230. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/nene2/middleware/test_request_size_limit.py +0 -0
  231. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
  232. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/nene2/middleware/test_throttle.py +0 -0
  233. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/nene2/use_case/__init__.py +0 -0
  234. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/nene2/use_case/test_protocols.py +0 -0
  235. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/nene2/validation/__init__.py +0 -0
  236. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/nene2/validation/test_exceptions.py +0 -0
  237. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/scripts/__init__.py +0 -0
  238. {nene2_python-1.8.7 → nene2_python-1.8.9}/tests/scripts/test_export_openapi.py +0 -0
@@ -5,6 +5,30 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
6
  ---
7
7
 
8
+ ## [1.8.9] — 2026-05-20
9
+
10
+ FT46〜FT49 フィールドトライアル — ドキュメント改善。
11
+
12
+ ### Added
13
+ - Field trial reports: `docs/field-trials/2026-05-field-trial-46.md` 〜 `docs/field-trials/2026-05-field-trial-49.md`
14
+
15
+ ### Changed
16
+ - `docs/how-to/run-tests.md` — SQLite 外部キー制約 (`PRAGMA foreign_keys=ON`) の注意事項を追記 (FT46)
17
+
18
+ ---
19
+
20
+ ## [1.8.8] — 2026-05-20
21
+
22
+ FT45 フィールドトライアル — SecurityHeadersMiddleware CSP バグ修正。
23
+
24
+ ### Fixed
25
+ - `SecurityHeadersMiddleware` — `csp=""` を渡したとき空の `Content-Security-Policy` ヘッダーが付与される問題を修正。`csp=""` の場合は CSP ヘッダーを付与しないよう変更 (#271) (FT45)
26
+
27
+ ### Added
28
+ - Field trial report: `docs/field-trials/2026-05-field-trial-45.md`
29
+
30
+ ---
31
+
8
32
  ## [1.8.7] — 2026-05-20
9
33
 
10
34
  FT43〜FT44 フィールドトライアル — ThrottleMiddleware path_limits 確認・PaginationQueryParser バリデーション改善。
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nene2-python
3
- Version: 1.8.7
3
+ Version: 1.8.9
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,108 @@
1
+ # Field Trial 45: SecurityHeadersMiddleware 詳細カスタマイズ実運用検証
2
+
3
+ **日付**: 2026-05-20
4
+ **バージョン**: v1.8.7 時点
5
+ **テーマ**: `SecurityHeadersMiddleware` の全オプション(CSP・HSTS・Permissions-Policy・extra_no_csp_paths)を組み合わせた実運用確認
6
+
7
+ ---
8
+
9
+ ## 概要
10
+
11
+ `SecurityHeadersMiddleware` の CSP カスタマイズ・HSTS 設定・Permissions-Policy カスタマイズ・
12
+ カスタム docs_url との組み合わせを実装した。
13
+ 2 つの摩擦点を発見し、うち 1 件 (FP45-3) を修正した。
14
+
15
+ ---
16
+
17
+ ## 実装内容
18
+
19
+ `/home/xi/docker/nene2-python-FT/ft45-security-headers/` に以下を作成:
20
+
21
+ - **`app.py`** — カスタム `docs_url`/`openapi_url` + `SecurityHeadersMiddleware` 全オプション
22
+ - **`test_app.py`** — 静的ヘッダー・CSP・HSTS・Permissions-Policy・extra_no_csp_paths (12 件)
23
+ - **`test_friction.py`** — 摩擦点の確認テスト (4 件)
24
+
25
+ **テスト結果**: 16 件全通過 ✅
26
+
27
+ ---
28
+
29
+ ## 摩擦点
30
+
31
+ ### FP45-1: カスタム docs_url 使用時に extra_no_csp_paths の設定を忘れやすい
32
+
33
+ **分類**: 軽微な摩擦(ドキュメント追記で対応)
34
+
35
+ FastAPI の `docs_url="/api/docs"` のようにカスタム URL を使う場合、
36
+ `SecurityHeadersMiddleware` のデフォルト `no_csp_paths` には
37
+ `/docs`・`/redoc`・`/openapi.json` しか含まれない。
38
+ `/api/docs` には CSP が付いてしまい、Swagger UI の CDN アセットが CSP でブロックされる。
39
+
40
+ ```python
41
+ # カスタム docs_url 使用時の必須設定
42
+ app.add_middleware(
43
+ SecurityHeadersMiddleware,
44
+ extra_no_csp_paths=["/api/docs", "/api/redoc", "/api/openapi.json"],
45
+ )
46
+ ```
47
+
48
+ **判断**: FT15 で実装した `extra_no_csp_paths` で対応可能だが、
49
+ デフォルトの `docs_url` を変更したときに `extra_no_csp_paths` も更新する必要があることを
50
+ ドキュメントで注意喚起する価値がある。
51
+
52
+ ---
53
+
54
+ ### FP45-2: HSTS は本番環境以外で有効にしてはならない
55
+
56
+ **分類**: 注意喚起(設計通り・ドキュメント記載済み)
57
+
58
+ `hsts` パラメータを設定すると、`Strict-Transport-Security` ヘッダーが付与される。
59
+ http:// でアクセスすると次回以降 https:// を強制するため、開発環境での誤設定に注意が必要。
60
+ `hsts=None` のデフォルトは意図的な安全設計。
61
+
62
+ **判断**: 設計通り。ドキュメントの Warning で注意喚起済み。
63
+
64
+ ---
65
+
66
+ ### FP45-3: csp="" のとき空の CSP ヘッダーが付与されていた
67
+
68
+ **分類**: バグ(#271 で修正)
69
+
70
+ `csp=""` を渡すと `Content-Security-Policy: ` という空ヘッダーが付与されていた。
71
+ ユーザーが CSP を無効化しようとして `csp=""` を渡すと、空 CSP が付く予期しない動作になる。
72
+
73
+ **修正**: `dispatch()` を `self._csp` が truthy のときのみ CSP ヘッダーを付与するよう変更。
74
+ `csp=""` は「CSP ヘッダーを付けない」として扱われる。
75
+
76
+ ```python
77
+ # 修正前
78
+ if request.url.path not in self._no_csp_paths:
79
+ response.headers["Content-Security-Policy"] = self._csp
80
+
81
+ # 修正後
82
+ if request.url.path not in self._no_csp_paths and self._csp:
83
+ response.headers["Content-Security-Policy"] = self._csp
84
+ ```
85
+
86
+ ---
87
+
88
+ ### FP45-4: 全オプション組み合わせは問題なく動作する
89
+
90
+ **分類**: 摩擦なし(設計の確認)
91
+
92
+ `csp`・`permissions_policy`・`hsts`・`extra_no_csp_paths` の全オプションを同時に指定しても
93
+ 正常に動作することを確認した。
94
+
95
+ ---
96
+
97
+ ## フレームワーク変更
98
+
99
+ - `SecurityHeadersMiddleware.dispatch()` — `csp=""` のとき CSP ヘッダーを付与しないよう修正 (#271)
100
+
101
+ ---
102
+
103
+ ## 関連
104
+
105
+ - `nene2.middleware.SecurityHeadersMiddleware`
106
+ - FT15 (CSP カスタマイズ, v1.7.0)
107
+ - FT32 (HSTS・Permissions-Policy 追加, v1.8.3)
108
+ - Issue #271 (csp="" バグ修正)
@@ -0,0 +1,128 @@
1
+ # Field Trial 46: DatabaseIntegrityException 実運用検証
2
+
3
+ **日付**: 2026-05-20
4
+ **バージョン**: v1.8.8 時点
5
+ **テーマ**: `DatabaseIntegrityException` を FastAPI エンドポイント経由で UNIQUE 制約・FK 制約違反を処理するパターンの実運用確認
6
+
7
+ ---
8
+
9
+ ## 概要
10
+
11
+ `SqlAlchemyQueryExecutor` を使って SQLite への UNIQUE/FK 制約違反を発生させ、
12
+ `DatabaseIntegrityException` を `DuplicateEmailError` / `InvalidUserReferenceError` に
13
+ サブクラス化してドメイン例外に変換し、`SimpleDomainHandler` で HTTP レスポンスにマッピングするパターンを実装した。
14
+
15
+ ---
16
+
17
+ ## 実装内容
18
+
19
+ `/home/xi/docker/nene2-python-FT/ft46-db-integrity/` に以下を作成:
20
+
21
+ - **`app.py`** — SQLite + `SqlAlchemyQueryExecutor` (Core API) を使ったユーザー/ポスト管理 API
22
+ - **`test_app.py`** — 正常系・UNIQUE 違反 409・FK 違反 422・エラー後回復 (7 件)
23
+ - **`test_friction.py`** — 摩擦点の確認テスト (4 件)
24
+
25
+ **テスト結果**: 11 件全通過 ✅
26
+
27
+ ---
28
+
29
+ ## 摩擦点
30
+
31
+ ### FP46-1: DatabaseIntegrityException のサブクラス化が必要
32
+
33
+ **分類**: 軽微な摩擦(パターン提示)
34
+
35
+ `DatabaseIntegrityException` は汎用例外のため、UNIQUE 制約違反(409)と FK 制約違反(422)を
36
+ 別の HTTP ステータスコードにマッピングするには、サブクラスを作って例外を変換する必要がある。
37
+
38
+ ```python
39
+ class DuplicateEmailError(DatabaseIntegrityException): pass
40
+ class InvalidUserReferenceError(DatabaseIntegrityException): pass
41
+
42
+ try:
43
+ executor.write("INSERT INTO users ...", params)
44
+ except DatabaseIntegrityException as exc:
45
+ raise DuplicateEmailError(str(exc)) from exc
46
+ ```
47
+
48
+ **判断**: データベース例外に対してドメイン固有の意味付けをするパターンとして自然。
49
+ `IntegrityError` のメッセージを解析して違反種別を判定する方法もあるが、
50
+ SQLite/MySQL/PostgreSQL でメッセージ形式が異なるため移植性に課題がある。
51
+ サブクラス + 明示的 raise パターンが最も移植性が高い。
52
+
53
+ ---
54
+
55
+ ### FP46-2: SQLite の FK 制約は PRAGMA foreign_keys=ON が必要
56
+
57
+ **分類**: 注意喚起(既知事項・ドキュメント追記価値あり)
58
+
59
+ SQLite のデフォルトでは外部キー制約が無効。
60
+ `create_engine()` 後に `PRAGMA foreign_keys=ON` を実行しないと、
61
+ FK 制約違反がスルーされて孤児レコードが挿入される。
62
+
63
+ ```python
64
+ with engine.begin() as conn:
65
+ conn.execute(text("PRAGMA foreign_keys=ON"))
66
+ ```
67
+
68
+ `StaticPool` を使う場合は最初の接続でこれを実行すれば全接続に適用される。
69
+
70
+ **判断**: SQLite 特有の注意点。`docs/how-to/run-tests.md` の StaticPool セクションに追記する価値がある。
71
+
72
+ ---
73
+
74
+ ### FP46-3: 整合性エラー後もトランザクションは自動ロールバックされる
75
+
76
+ **分類**: 摩擦なし(良い設計の確認)
77
+
78
+ `DatabaseIntegrityException` が発生すると `SqlAlchemyQueryExecutor.write()` 内で
79
+ `engine.begin()` のコンテキストマネージャーがロールバックする。
80
+ 次のリクエストでは新しいトランザクションが開始されるため、セッション汚染は発生しない。
81
+
82
+ **判断**: SQLAlchemy Core の設計通り。`engine.begin()` を使ったパターンは安全。
83
+
84
+ ---
85
+
86
+ ### FP46-4: SqlAlchemyQueryExecutor は SQL 文字列 API であり ORM Session ではない
87
+
88
+ **分類**: 摩擦あり(設計の理解不足・初回実装で失敗)
89
+
90
+ 当初 `SqlAlchemyQueryExecutor.write()` に ORM の `Session` を使ったコールバックを渡そうとしたが、
91
+ `write()` は SQL 文字列 + params を受け取る Core API であることを確認した。
92
+
93
+ ```python
94
+ # NG: コールバックパターン(TransactionManager のもの)
95
+ executor.write(lambda session: session.add(row)) # TypeError
96
+
97
+ # OK: SQL 文字列パターン(QueryExecutor のもの)
98
+ executor.write("INSERT INTO users (email) VALUES (:email)", {"email": "..."})
99
+ ```
100
+
101
+ ORM (Session) を使うパターンは `SqlAlchemyTransactionManager.transactional(callback)` で、
102
+ コールバック内では `_BoundQueryExecutor` を通じて SQL を実行する。
103
+ 直接 Session を使いたい場合はフレームワーク外で SQLAlchemy ORM を使う。
104
+
105
+ **判断**: フレームワークの設計通り(SQLAlchemy Core ベース)。
106
+ ドキュメントに Core API と ORM の違いを明記する価値がある。
107
+
108
+ ---
109
+
110
+ ## フレームワーク変更
111
+
112
+ なし(全て設計通りの挙動)
113
+
114
+ ドキュメント追記を検討:
115
+ - `docs/how-to/run-tests.md` の StaticPool セクションに SQLite FK pragma を追記
116
+ - `docs/how-to/` に `DatabaseIntegrityException` ハンドリングパターン how-to を追加
117
+
118
+ ---
119
+
120
+ ## 関連
121
+
122
+ - `nene2.database.DatabaseIntegrityException` (FT16, v1.7.0)
123
+ - `nene2.database.SqlAlchemyQueryExecutor`
124
+ - `nene2.database.SqlAlchemyTransactionManager`
125
+ - `nene2.middleware.SimpleDomainHandler` (FT21, v1.8.0)
126
+ - FT16 (DatabaseIntegrityException 実装, v1.7.0)
127
+ - FT17 (SqlAlchemyQueryExecutor.write() バグ修正, v1.7.0)
128
+ - FT34 (StaticPool SQLite テスト, v1.8.4)
@@ -0,0 +1,93 @@
1
+ # Field Trial 47: SqlAlchemyTransactionManager.transactional() 実運用検証
2
+
3
+ **日付**: 2026-05-20
4
+ **バージョン**: v1.8.8 時点
5
+ **テーマ**: `transactional()` コールバックパターンで複数テーブルへの同一トランザクション書き込みと、失敗時のロールバックを FastAPI エンドポイント経由で確認
6
+
7
+ ---
8
+
9
+ ## 概要
10
+
11
+ 銀行振込(送金元残高減算 + 送金先残高増加 + 転送ログ挿入の 3 操作)を
12
+ `SqlAlchemyTransactionManager.transactional()` の単一トランザクションで実装した。
13
+ 残高不足・存在しない口座での失敗時にすべての操作がロールバックされることを確認した。
14
+
15
+ ---
16
+
17
+ ## 実装内容
18
+
19
+ `/home/xi/docker/nene2-python-FT/ft47-transaction-manager/` に以下を作成:
20
+
21
+ - **`app.py`** — 口座管理 + 振込 API、`SqlAlchemyTransactionManager.transactional()` で複数書き込み
22
+ - **`test_app.py`** — 正常振込・残高更新・不足残高・存在しない口座・重複名 (7 件)
23
+ - **`test_friction.py`** — 摩擦点の確認テスト (4 件)
24
+
25
+ **テスト結果**: 11 件全通過 ✅
26
+
27
+ ---
28
+
29
+ ## 摩擦点
30
+
31
+ ### FP47-1: コールバックは DatabaseQueryExecutorInterface を受け取る
32
+
33
+ **分類**: 摩擦なし(良い設計の確認)
34
+
35
+ `transactional(callback)` のコールバックは `DatabaseQueryExecutorInterface` を引数として受け取る。
36
+ 内部的には `_BoundQueryExecutor` として同一接続にバインドされており、
37
+ `fetch_one()` / `write()` を呼ぶことで同一トランザクション内で複数操作できる。
38
+
39
+ **判断**: SQLAlchemy Core を抽象化したインターフェースが自然に機能する。
40
+
41
+ ---
42
+
43
+ ### FP47-2: ドメイン例外も transactional() を自動ロールバックさせる
44
+
45
+ **分類**: 摩擦なし(良い設計の確認)
46
+
47
+ コールバック内で `InsufficientFundsError` のような非 DB 例外を raise した場合、
48
+ `engine.begin()` コンテキストマネージャーが自動でロールバックしてから例外を伝播する。
49
+ `ErrorHandlerMiddleware` がこれを `SimpleDomainHandler` でキャッチして適切な HTTP レスポンスを返す。
50
+
51
+ **判断**: 設計通り。「例外発生 = ロールバック」が保証されているため、コールバック内で
52
+ ガード条件をチェックして素直に raise するだけで整合性が保たれる。
53
+
54
+ ---
55
+
56
+ ### FP47-3: 複数 write はすべて成功かすべてロールバックか(ACID 保証)
57
+
58
+ **分類**: 摩擦なし(良い設計の確認)
59
+
60
+ 送金処理の 3 つの write(送金元減算 / 送金先加算 / ログ挿入)は
61
+ `transactional()` が提供する `_BoundQueryExecutor` を通じて同一トランザクションで実行される。
62
+ 途中で例外が発生した場合、実行済みの write も含めてすべてロールバックされる。
63
+
64
+ **判断**: ACID の Atomicity が正しく動作することを確認した。
65
+
66
+ ---
67
+
68
+ ### FP47-4: transactional() の戻り値はコールバックの戻り値
69
+
70
+ **分類**: 摩擦なし(設計の確認)
71
+
72
+ `transactional()` はジェネリクス `[T]` を使ってコールバックの戻り値型を保持する。
73
+ コミット後の戻り値(`dict[str, object]` など)をそのまま受け取り、
74
+ FastAPI ハンドラーで `JSONResponse` に変換できる。
75
+
76
+ **判断**: 型安全なコールバックパターンが正しく動作する。
77
+
78
+ ---
79
+
80
+ ## フレームワーク変更
81
+
82
+ なし(全て設計通りの挙動)
83
+
84
+ ---
85
+
86
+ ## 関連
87
+
88
+ - `nene2.database.SqlAlchemyTransactionManager` (FT16, v1.7.0)
89
+ - `nene2.database.DatabaseQueryExecutorInterface`
90
+ - `nene2.database.DatabaseIntegrityException` (FT16, v1.7.0)
91
+ - FT16 (TransactionManager 実装, v1.7.0)
92
+ - FT38 (トランザクション管理確認, v1.8.5)
93
+ - FT46 (DatabaseIntegrityException 実運用, v1.8.8)
@@ -0,0 +1,96 @@
1
+ # Field Trial 48: CompositeHealthCheck + AsyncCompositeHealthCheck 実運用検証
2
+
3
+ **日付**: 2026-05-20
4
+ **バージョン**: v1.8.8 時点
5
+ **テーマ**: `CompositeHealthCheck` と `AsyncCompositeHealthCheck` を `/health` エンドポイントで使い、複数コンポーネントのヘルスチェックを集約するパターンの実運用確認
6
+
7
+ ---
8
+
9
+ ## 概要
10
+
11
+ 同期 `CompositeHealthCheck`(DB + キャッシュ)と非同期 `AsyncCompositeHealthCheck`(DB + 外部 API)を
12
+ `/health` と `/health/async` エンドポイントで使い、部分的な失敗時の 503 レスポンスと
13
+ `checks` フィールドへの詳細情報付与を確認した。
14
+
15
+ ---
16
+
17
+ ## 実装内容
18
+
19
+ `/home/xi/docker/nene2-python-FT/ft48-health-check/` に以下を作成:
20
+
21
+ - **`app.py`** — `CompositeHealthCheck` + `AsyncCompositeHealthCheck` を使った FastAPI アプリ
22
+ - **`test_app.py`** — 正常・503・checks フィールド・エラーメッセージ確認 (7 件)
23
+ - **`test_friction.py`** — 摩擦点の確認テスト (4 件)
24
+
25
+ **テスト結果**: 11 件全通過 ✅
26
+
27
+ ---
28
+
29
+ ## 摩擦点
30
+
31
+ ### FP48-1: http_status_code プロパティで 200/503 が自動判定される
32
+
33
+ **分類**: 摩擦なし(良い設計の確認)
34
+
35
+ FT31 で追加した `HealthStatus.http_status_code` プロパティが
36
+ `CompositeHealthCheck` の結果でも正しく機能する。
37
+ `is_healthy` が `True` なら 200、`False` なら 503 を返す。
38
+ ハンドラーで `status_code=status.http_status_code` と書くだけで適切なステータスが返る。
39
+
40
+ ---
41
+
42
+ ### FP48-2: 空のチェックリストは "ok" を返す
43
+
44
+ **分類**: 摩擦なし(エッジケース確認)
45
+
46
+ `CompositeHealthCheck([])` のように空リストを渡すと、
47
+ 全チェック通過として `HealthStatus(status="ok", checks={})` を返す。
48
+ ゼロ個のチェックは「失敗するチェックがない」として ok と見なす設計は直感的。
49
+
50
+ ---
51
+
52
+ ### FP48-3: 部分的な失敗は全コンポーネントの results を含む
53
+
54
+ **分類**: 摩擦なし(良い設計の確認)
55
+
56
+ 失敗したコンポーネントのエラーメッセージと成功したコンポーネントの "ok" が
57
+ `checks` フィールドに混在する。
58
+ クライアントはどのコンポーネントが失敗したかを `checks` フィールドで判別できる。
59
+
60
+ ```json
61
+ {
62
+ "status": "error",
63
+ "checks": {
64
+ "database": "ok",
65
+ "cache": "connection refused"
66
+ }
67
+ }
68
+ ```
69
+
70
+ ---
71
+
72
+ ### FP48-4: AsyncCompositeHealthCheck は TestClient でも並列実行される
73
+
74
+ **分類**: 摩擦なし(良い設計の確認)
75
+
76
+ `AsyncCompositeHealthCheck` は `asyncio.gather()` で並列実行するため、
77
+ TestClient(同期コンテキスト)経由でもエンドポイント内部では asyncio が使われ、
78
+ 並列性が維持される。
79
+ 50ms の遅延を持つチェックを含む場合でも、逐次実行の 2 倍未満の時間で完了する。
80
+
81
+ ---
82
+
83
+ ## フレームワーク変更
84
+
85
+ なし(全て設計通りの挙動)
86
+
87
+ ---
88
+
89
+ ## 関連
90
+
91
+ - `nene2.http.CompositeHealthCheck` (FT22, v1.8.0)
92
+ - `nene2.http.AsyncCompositeHealthCheck` (FT36, v1.8.4)
93
+ - `nene2.http.HealthStatus.http_status_code` (FT31, v1.8.3)
94
+ - FT22 (CompositeHealthCheck 実装, v1.8.0)
95
+ - FT31 (http_status_code プロパティ追加, v1.8.3)
96
+ - FT36 (AsyncCompositeHealthCheck 実装, v1.8.4)
@@ -0,0 +1,96 @@
1
+ # Field Trial 49: AppSettings 実運用検証
2
+
3
+ **日付**: 2026-05-20
4
+ **バージョン**: v1.8.8 時点
5
+ **テーマ**: `AppSettings` の環境変数オーバーライド・バリデーション・`db_url` プロパティ・SecretStr の動作を確認
6
+
7
+ ---
8
+
9
+ ## 概要
10
+
11
+ `AppSettings` の全主要機能(環境変数オーバーライド・バリデーター・`db_url` プロパティ生成・
12
+ SecretStr によるパスワード保護・リスト型環境変数の設定方法)を確認した。
13
+
14
+ ---
15
+
16
+ ## 実装内容
17
+
18
+ `/home/xi/docker/nene2-python-FT/ft49-app-settings/` に以下を作成:
19
+
20
+ - **`test_settings.py`** — AppSettings の全機能確認 (13 件)
21
+
22
+ **テスト結果**: 13 件全通過 ✅
23
+
24
+ ---
25
+
26
+ ## 摩擦点
27
+
28
+ ### FP49-1: db_password は SecretStr のため repr に平文が出ない
29
+
30
+ **分類**: 摩擦なし(良い設計の確認)
31
+
32
+ `db_password: SecretStr` のため、`repr(settings)` や `print(settings)` で
33
+ パスワードが平文で出力されない。ログに誤ってパスワードが漏洩しない設計。
34
+
35
+ 実際の値が必要な場合は `settings.db_password.get_secret_value()` を使う。
36
+ `db_url` プロパティが内部で呼んでいるので、通常は直接呼ぶ必要はない。
37
+
38
+ **判断**: セキュリティ上の重要な設計。
39
+
40
+ ---
41
+
42
+ ### FP49-2: リスト型環境変数は JSON 配列形式で設定する
43
+
44
+ **分類**: 軽微な摩擦(注意喚起)
45
+
46
+ `bearer_tokens`・`api_keys`・`cors_origins` などのリスト型フィールドを
47
+ 環境変数で設定する場合、pydantic-settings は JSON 配列形式を期待する:
48
+
49
+ ```bash
50
+ # OK: JSON 配列形式
51
+ export BEARER_TOKENS='["token-a", "token-b"]'
52
+
53
+ # NG: カンマ区切り(動作しない)
54
+ export BEARER_TOKENS="token-a,token-b"
55
+ ```
56
+
57
+ `LocalTokenVerifier.from_env()` (FT11) はカンマ区切りをサポートしているが、
58
+ `AppSettings.bearer_tokens` 自体は JSON 配列形式が必要。
59
+
60
+ **判断**: pydantic-settings の仕様通り。NENE2 の `README` や設定リファレンスドキュメントに
61
+ 明記する価値がある。
62
+
63
+ ---
64
+
65
+ ### FP49-3: app_env は "local" / "test" / "production" のみ
66
+
67
+ **分類**: 摩擦なし(設計の確認)
68
+
69
+ `validate_app_env` バリデーターで "staging" など他の値を拒否する。
70
+ デプロイ環境を厳密に 3 種類に限定することで設定ミスを防ぐ。
71
+
72
+ ---
73
+
74
+ ### FP49-4: log_level は大文字に自動正規化される
75
+
76
+ **分類**: 摩擦なし(良い設計の確認)
77
+
78
+ `validate_log_level` バリデーターで "debug" → "DEBUG" に変換される。
79
+ 大文字・小文字を気にせずに設定できる。
80
+
81
+ ---
82
+
83
+ ## フレームワーク変更
84
+
85
+ なし(全て設計通りの挙動)
86
+
87
+ ドキュメント追記を検討:
88
+ - `docs/reference/configuration.md` にリスト型環境変数の設定方法を明記
89
+
90
+ ---
91
+
92
+ ## 関連
93
+
94
+ - `nene2.config.AppSettings`
95
+ - FT11 (LocalTokenVerifier.from_env, v1.4.0)
96
+ - FT26 (setup_logging log_level パラメータ, v1.8.1)
@@ -103,6 +103,17 @@ engine = create_engine(
103
103
 
104
104
  `StaticPool` guarantees all logical connections share the same underlying SQLite connection, so tables created in one operation are visible to the next.
105
105
 
106
+ **SQLite foreign-key enforcement**: SQLite disables foreign-key constraints by default. Enable them with `PRAGMA foreign_keys=ON` right after the engine is created:
107
+
108
+ ```python
109
+ from sqlalchemy import text
110
+
111
+ with engine.begin() as conn:
112
+ conn.execute(text("PRAGMA foreign_keys=ON"))
113
+ ```
114
+
115
+ With `StaticPool`, one call applies to the single shared connection, so all subsequent operations see FK constraints enforced.
116
+
106
117
  ## Capturing structlog output with caplog
107
118
 
108
119
  Call `configure_for_testing()` at module level in `conftest.py` to route structlog through stdlib logging so pytest's `caplog` fixture can capture it.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nene2-python"
3
- version = "1.8.7"
3
+ version = "1.8.9"
4
4
  description = "NENE2 Python — minimal API framework following NENE2's design philosophy"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
@@ -63,6 +63,6 @@ class SecurityHeadersMiddleware(BaseHTTPMiddleware):
63
63
  response.headers["Permissions-Policy"] = self._permissions_policy
64
64
  if self._hsts:
65
65
  response.headers["Strict-Transport-Security"] = self._hsts
66
- if request.url.path not in self._no_csp_paths:
66
+ if request.url.path not in self._no_csp_paths and self._csp:
67
67
  response.headers["Content-Security-Policy"] = self._csp
68
68
  return response
@@ -130,3 +130,16 @@ def test_default_no_csp_paths_still_work_with_extra_paths() -> None:
130
130
  assert "Content-Security-Policy" not in client.get("/docs").headers
131
131
  assert "Content-Security-Policy" not in client.get("/custom").headers
132
132
  assert "Content-Security-Policy" in client.get("/ping").headers
133
+
134
+
135
+ def test_csp_empty_string_disables_csp_header() -> None:
136
+ app = FastAPI()
137
+ app.add_middleware(SecurityHeadersMiddleware, csp="")
138
+
139
+ @app.get("/ping")
140
+ async def ping() -> JSONResponse:
141
+ return JSONResponse({"ok": True})
142
+
143
+ client = TestClient(app)
144
+ r = client.get("/ping")
145
+ assert "Content-Security-Policy" not in r.headers
@@ -925,7 +925,7 @@ wheels = [
925
925
 
926
926
  [[package]]
927
927
  name = "nene2-python"
928
- version = "1.8.7"
928
+ version = "1.8.9"
929
929
  source = { editable = "." }
930
930
  dependencies = [
931
931
  { name = "alembic" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes