nene2-python 1.8.2__tar.gz → 1.8.3__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 (220) hide show
  1. {nene2_python-1.8.2 → nene2_python-1.8.3}/CHANGELOG.md +12 -0
  2. {nene2_python-1.8.2 → nene2_python-1.8.3}/PKG-INFO +1 -1
  3. nene2_python-1.8.3/docs/field-trials/2026-05-field-trial-31.md +66 -0
  4. nene2_python-1.8.3/docs/field-trials/2026-05-field-trial-32.md +78 -0
  5. {nene2_python-1.8.2 → nene2_python-1.8.3}/pyproject.toml +1 -1
  6. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/http/health.py +4 -0
  7. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/middleware/security_headers.py +17 -3
  8. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/http/test_health.py +8 -0
  9. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/middleware/test_security_headers.py +38 -0
  10. {nene2_python-1.8.2 → nene2_python-1.8.3}/uv.lock +1 -1
  11. {nene2_python-1.8.2 → nene2_python-1.8.3}/.env.example +0 -0
  12. {nene2_python-1.8.2 → nene2_python-1.8.3}/.github/workflows/ci.yml +0 -0
  13. {nene2_python-1.8.2 → nene2_python-1.8.3}/.github/workflows/docs.yml +0 -0
  14. {nene2_python-1.8.2 → nene2_python-1.8.3}/.github/workflows/publish.yml +0 -0
  15. {nene2_python-1.8.2 → nene2_python-1.8.3}/.gitignore +0 -0
  16. {nene2_python-1.8.2 → nene2_python-1.8.3}/.vitepress/config.mts +0 -0
  17. {nene2_python-1.8.2 → nene2_python-1.8.3}/.vitepress/theme/custom.css +0 -0
  18. {nene2_python-1.8.2 → nene2_python-1.8.3}/.vitepress/theme/index.ts +0 -0
  19. {nene2_python-1.8.2 → nene2_python-1.8.3}/AGENTS.md +0 -0
  20. {nene2_python-1.8.2 → nene2_python-1.8.3}/CLAUDE.md +0 -0
  21. {nene2_python-1.8.2 → nene2_python-1.8.3}/Dockerfile +0 -0
  22. {nene2_python-1.8.2 → nene2_python-1.8.3}/LICENSE +0 -0
  23. {nene2_python-1.8.2 → nene2_python-1.8.3}/README.md +0 -0
  24. {nene2_python-1.8.2 → nene2_python-1.8.3}/alembic/README +0 -0
  25. {nene2_python-1.8.2 → nene2_python-1.8.3}/alembic/env.py +0 -0
  26. {nene2_python-1.8.2 → nene2_python-1.8.3}/alembic/script.py.mako +0 -0
  27. {nene2_python-1.8.2 → nene2_python-1.8.3}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
  28. {nene2_python-1.8.2 → nene2_python-1.8.3}/alembic.ini +0 -0
  29. {nene2_python-1.8.2 → nene2_python-1.8.3}/compose.yaml +0 -0
  30. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/adr/0001-toolchain.md +0 -0
  31. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/adr/0002-clean-architecture.md +0 -0
  32. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/adr/0003-security-first.md +0 -0
  33. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/adr/0004-ai-first-design.md +0 -0
  34. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/adr/0005-logging.md +0 -0
  35. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/adr/0006-rate-limiting.md +0 -0
  36. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/adr/0009-mcp-design.md +0 -0
  37. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/adr/0010-async-use-case.md +0 -0
  38. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
  39. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/de/index.md +0 -0
  40. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/de/tutorials/getting-started.md +0 -0
  41. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/explanation/architecture.md +0 -0
  42. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/explanation/design-philosophy.md +0 -0
  43. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-1.md +0 -0
  44. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-10.md +0 -0
  45. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-11.md +0 -0
  46. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-12.md +0 -0
  47. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-13.md +0 -0
  48. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-14.md +0 -0
  49. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-15.md +0 -0
  50. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-16.md +0 -0
  51. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-17.md +0 -0
  52. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-18.md +0 -0
  53. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-19.md +0 -0
  54. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-2.md +0 -0
  55. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-20.md +0 -0
  56. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-21.md +0 -0
  57. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-22.md +0 -0
  58. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-23.md +0 -0
  59. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-24.md +0 -0
  60. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-25.md +0 -0
  61. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-26.md +0 -0
  62. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-27.md +0 -0
  63. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-28.md +0 -0
  64. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-29.md +0 -0
  65. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-3.md +0 -0
  66. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-30.md +0 -0
  67. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-4.md +0 -0
  68. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-5.md +0 -0
  69. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-6.md +0 -0
  70. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-7.md +0 -0
  71. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-8.md +0 -0
  72. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/field-trials/2026-05-field-trial-9.md +0 -0
  73. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/fr/index.md +0 -0
  74. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/fr/tutorials/getting-started.md +0 -0
  75. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/how-to/add-new-domain.md +0 -0
  76. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/how-to/async-use-case.md +0 -0
  77. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/how-to/configure-auth.md +0 -0
  78. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/how-to/new-project.md +0 -0
  79. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/how-to/problem-details.md +0 -0
  80. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/how-to/run-tests.md +0 -0
  81. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/how-to/sqlalchemy-repository.md +0 -0
  82. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/howto/mcp-setup.md +0 -0
  83. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/index.md +0 -0
  84. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/ja/explanation/architecture.md +0 -0
  85. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/ja/explanation/design-philosophy.md +0 -0
  86. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/ja/how-to/add-new-domain.md +0 -0
  87. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/ja/how-to/configure-auth.md +0 -0
  88. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/ja/how-to/new-project.md +0 -0
  89. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/ja/how-to/run-tests.md +0 -0
  90. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
  91. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/ja/howto/mcp-setup.md +0 -0
  92. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/ja/index.md +0 -0
  93. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/ja/reference/api.md +0 -0
  94. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/ja/reference/configuration.md +0 -0
  95. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/ja/reference/framework-modules.md +0 -0
  96. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/ja/tutorials/first-domain.md +0 -0
  97. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/ja/tutorials/getting-started.md +0 -0
  98. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/pt-br/index.md +0 -0
  99. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/pt-br/tutorials/getting-started.md +0 -0
  100. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/reference/api.md +0 -0
  101. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/reference/configuration.md +0 -0
  102. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/reference/framework-modules.md +0 -0
  103. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/roadmap.md +0 -0
  104. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/todo/current.md +0 -0
  105. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/tutorials/first-domain.md +0 -0
  106. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/tutorials/getting-started.md +0 -0
  107. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/zh/index.md +0 -0
  108. {nene2_python-1.8.2 → nene2_python-1.8.3}/docs/zh/tutorials/getting-started.md +0 -0
  109. {nene2_python-1.8.2 → nene2_python-1.8.3}/package-lock.json +0 -0
  110. {nene2_python-1.8.2 → nene2_python-1.8.3}/package.json +0 -0
  111. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/__init__.py +0 -0
  112. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/__main__.py +0 -0
  113. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/app.py +0 -0
  114. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/comment/__init__.py +0 -0
  115. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/comment/entity.py +0 -0
  116. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/comment/exceptions.py +0 -0
  117. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/comment/handler.py +0 -0
  118. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/comment/repository.py +0 -0
  119. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/comment/sqlalchemy_repository.py +0 -0
  120. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/comment/use_case.py +0 -0
  121. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/mcp.py +0 -0
  122. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/note/__init__.py +0 -0
  123. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/note/async_use_case.py +0 -0
  124. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/note/entity.py +0 -0
  125. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/note/exceptions.py +0 -0
  126. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/note/handler.py +0 -0
  127. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/note/repository.py +0 -0
  128. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/note/sqlalchemy_repository.py +0 -0
  129. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/note/use_case.py +0 -0
  130. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/schema.py +0 -0
  131. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/tag/__init__.py +0 -0
  132. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/tag/entity.py +0 -0
  133. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/tag/exceptions.py +0 -0
  134. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/tag/handler.py +0 -0
  135. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/tag/repository.py +0 -0
  136. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/tag/sqlalchemy_repository.py +0 -0
  137. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/example/tag/use_case.py +0 -0
  138. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/__init__.py +0 -0
  139. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/auth/__init__.py +0 -0
  140. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/auth/api_key.py +0 -0
  141. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/auth/bearer_token.py +0 -0
  142. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/auth/exceptions.py +0 -0
  143. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/auth/interfaces.py +0 -0
  144. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/auth/local_verifier.py +0 -0
  145. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/config/__init__.py +0 -0
  146. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/config/settings.py +0 -0
  147. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/database/__init__.py +0 -0
  148. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/database/exceptions.py +0 -0
  149. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/database/health.py +0 -0
  150. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/database/interfaces.py +0 -0
  151. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/database/sqlalchemy_executor.py +0 -0
  152. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/database/utils.py +0 -0
  153. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/http/__init__.py +0 -0
  154. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/http/pagination.py +0 -0
  155. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/http/problem_details.py +0 -0
  156. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/log/__init__.py +0 -0
  157. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/log/setup.py +0 -0
  158. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/mcp/__init__.py +0 -0
  159. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/mcp/http_client.py +0 -0
  160. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/mcp/server.py +0 -0
  161. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/middleware/__init__.py +0 -0
  162. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/middleware/domain_exception.py +0 -0
  163. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/middleware/error_handler.py +0 -0
  164. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/middleware/request_id.py +0 -0
  165. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/middleware/request_logging.py +0 -0
  166. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/middleware/request_size_limit.py +0 -0
  167. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/middleware/throttle.py +0 -0
  168. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/py.typed +0 -0
  169. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/use_case/__init__.py +0 -0
  170. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/use_case/protocols.py +0 -0
  171. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/validation/__init__.py +0 -0
  172. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/nene2/validation/exceptions.py +0 -0
  173. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/scripts/__init__.py +0 -0
  174. {nene2_python-1.8.2 → nene2_python-1.8.3}/src/scripts/export_openapi.py +0 -0
  175. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/__init__.py +0 -0
  176. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/example/__init__.py +0 -0
  177. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/example/comment/__init__.py +0 -0
  178. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/example/comment/test_comment_http.py +0 -0
  179. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/example/comment/test_comment_repository.py +0 -0
  180. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/example/comment/test_comment_use_case.py +0 -0
  181. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/example/conftest.py +0 -0
  182. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/example/note/__init__.py +0 -0
  183. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/example/note/test_async_note_use_case.py +0 -0
  184. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/example/note/test_list_notes.py +0 -0
  185. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/example/note/test_note_repository.py +0 -0
  186. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/example/tag/__init__.py +0 -0
  187. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/example/tag/test_tag_repository.py +0 -0
  188. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/example/tag/test_tags.py +0 -0
  189. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/example/test_cors.py +0 -0
  190. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/example/test_mcp.py +0 -0
  191. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/__init__.py +0 -0
  192. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/auth/__init__.py +0 -0
  193. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/auth/test_api_key.py +0 -0
  194. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/auth/test_bearer_token.py +0 -0
  195. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/auth/test_token_issuer.py +0 -0
  196. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/config/__init__.py +0 -0
  197. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/config/test_settings.py +0 -0
  198. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/database/__init__.py +0 -0
  199. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/database/test_transaction.py +0 -0
  200. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/database/test_utils.py +0 -0
  201. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/http/__init__.py +0 -0
  202. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/http/test_pagination.py +0 -0
  203. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/http/test_problem_details.py +0 -0
  204. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/log/__init__.py +0 -0
  205. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/log/test_setup.py +0 -0
  206. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/mcp/__init__.py +0 -0
  207. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/mcp/test_http_client.py +0 -0
  208. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/middleware/__init__.py +0 -0
  209. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/middleware/test_error_handler.py +0 -0
  210. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/middleware/test_request_id.py +0 -0
  211. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/middleware/test_request_logging.py +0 -0
  212. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/middleware/test_request_size_limit.py +0 -0
  213. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
  214. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/middleware/test_throttle.py +0 -0
  215. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/use_case/__init__.py +0 -0
  216. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/use_case/test_protocols.py +0 -0
  217. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/validation/__init__.py +0 -0
  218. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/nene2/validation/test_exceptions.py +0 -0
  219. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/scripts/__init__.py +0 -0
  220. {nene2_python-1.8.2 → nene2_python-1.8.3}/tests/scripts/test_export_openapi.py +0 -0
@@ -5,6 +5,18 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
6
  ---
7
7
 
8
+ ## [1.8.3] — 2026-05-20
9
+
10
+ FT31〜FT32 フィールドトライアル — HealthCheck・SecurityHeaders 改善。
11
+
12
+ ### Added
13
+ - `HealthStatus.http_status_code` プロパティ — `is_healthy` → 200、それ以外 → 503 のマッピングを提供 (FT31)
14
+ - `SecurityHeadersMiddleware` に `permissions_policy: str | None = None` パラメータを追加 (FT32)
15
+ - `SecurityHeadersMiddleware` に `hsts: str | None = None` パラメータを追加 (FT32)
16
+ - Field trial reports: `docs/field-trials/2026-05-field-trial-31.md`、`docs/field-trials/2026-05-field-trial-32.md`
17
+
18
+ ---
19
+
8
20
  ## [1.8.2] — 2026-05-20
9
21
 
10
22
  FT29〜FT30 フィールドトライアル — AsyncUseCase ドキュメント・RequestLoggingMiddleware 改善。
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nene2-python
3
- Version: 1.8.2
3
+ Version: 1.8.3
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,66 @@
1
+ # FT31: DatabaseHealthCheck + ヘルスエンドポイント統合検証
2
+
3
+ **日付**: 2026-05-20
4
+ **テーマ**: `DatabaseHealthCheck` と `/health` エンドポイントの統合パターン検証
5
+ **FT アプリ**: `/home/xi/docker/nene2-python-FT/ft31-health-check/`
6
+
7
+ ---
8
+
9
+ ## 目的
10
+
11
+ `DatabaseHealthCheck` + `CompositeHealthCheck` を使った `/health` エンドポイントの実装パターンを検証する。
12
+
13
+ ---
14
+
15
+ ## 実施内容
16
+
17
+ - `DatabaseHealthCheck` + `CompositeHealthCheck` で `/health` を実装
18
+ - DB 接続成功時に 200、失敗時に 503 を返すパターンを確認
19
+ - フレームワークが提供すべき機能の不足を記録
20
+
21
+ ---
22
+
23
+ ## テスト結果
24
+
25
+ ### test_app.py(正常系・機能確認)
26
+ | テスト | 結果 |
27
+ |---|---|
28
+ | test_health_endpoint_returns_200_when_db_healthy | PASS |
29
+ | test_health_includes_all_checks | PASS |
30
+ | test_ping_returns_200 | PASS |
31
+ | test_health_returns_503_when_db_fails | PASS |
32
+
33
+ ### test_friction.py(摩擦点確認)
34
+ | テスト | 結果 | 摩擦 |
35
+ |---|---|---|
36
+ | test_health_endpoint_has_no_built_in_route | PASS | 軽微(how-to 推奨パターン記載で対応) |
37
+ | test_health_status_lacks_http_status_code_mapping | PASS | あり |
38
+
39
+ ---
40
+
41
+ ## 発見した摩擦点
42
+
43
+ ### FT31-F1: HealthStatus が HTTP ステータスコードのマッピングを持たない
44
+
45
+ **概要**: `/health` エンドポイントを実装するとき、
46
+ `status="ok"` → 200、`status="error"` → 503 のマッピングを毎回手動で書く必要がある。
47
+
48
+ ```python
49
+ # 毎回このボイラープレートが必要
50
+ http_status = 200 if status.is_healthy else 503
51
+ return JSONResponse({"status": status.status}, status_code=http_status)
52
+
53
+ # 期待する使い方
54
+ return JSONResponse({"status": status.status}, status_code=status.http_status_code)
55
+ ```
56
+
57
+ **期待する解決策**: `HealthStatus` に `http_status_code: int` プロパティを追加する。
58
+
59
+ ---
60
+
61
+ ## まとめ
62
+
63
+ `DatabaseHealthCheck` + `CompositeHealthCheck` の基本機能は問題なく動作する。
64
+
65
+ 摩擦点:
66
+ 1. **`HealthStatus.http_status_code` プロパティがない** → Issue 化・修正対象
@@ -0,0 +1,78 @@
1
+ # FT32: SecurityHeadersMiddleware CSP カスタマイズ実運用検証
2
+
3
+ **日付**: 2026-05-20
4
+ **テーマ**: `SecurityHeadersMiddleware` のカスタマイズ可能性検証
5
+ **FT アプリ**: `/home/xi/docker/nene2-python-FT/ft32-security-headers/`
6
+
7
+ ---
8
+
9
+ ## 目的
10
+
11
+ `SecurityHeadersMiddleware` の CSP カスタマイズ機能を実際のアプリで検証し、
12
+ ハードコードされたヘッダーの問題を記録する。
13
+
14
+ ---
15
+
16
+ ## 実施内容
17
+
18
+ - デフォルトセキュリティヘッダーの確認
19
+ - カスタム CSP の適用確認
20
+ - `extra_no_csp_paths` の動作確認
21
+ - ハードコードされたヘッダーの制限を確認
22
+
23
+ ---
24
+
25
+ ## テスト結果
26
+
27
+ ### test_app.py(正常系・機能確認)
28
+ | テスト | 結果 |
29
+ |---|---|
30
+ | test_default_security_headers_present | PASS |
31
+ | test_default_csp_is_default_src_self | PASS |
32
+ | test_custom_csp_is_applied | PASS |
33
+ | test_docs_path_has_no_csp | PASS |
34
+ | test_extra_no_csp_paths_skip_csp | PASS |
35
+
36
+ ### test_friction.py(摩擦点確認)
37
+ | テスト | 結果 | 摩擦 |
38
+ |---|---|---|
39
+ | test_permissions_policy_is_hardcoded | PASS | あり |
40
+ | test_no_hsts_header | PASS | あり |
41
+ | test_x_frame_options_is_hardcoded_to_deny | PASS | あり(軽微) |
42
+
43
+ ---
44
+
45
+ ## 発見した摩擦点
46
+
47
+ ### FT32-F1: Permissions-Policy がハードコードされている
48
+
49
+ **概要**: `geolocation=(), microphone=()` が固定値でカスタマイズできない。
50
+ 位置情報 API を使うアプリでは `geolocation=(self)` に変更できない。
51
+
52
+ **期待する解決策**: `permissions_policy: str | None = None` パラメータを追加。
53
+
54
+ ### FT32-F2: HSTS ヘッダーがない
55
+
56
+ **概要**: production 環境では `Strict-Transport-Security` を設定すべきだが、
57
+ `SecurityHeadersMiddleware` は HSTS を付与しない。
58
+ 開発環境では不要なため、オプションとして設定できるべき。
59
+
60
+ **期待する解決策**: `hsts: str | None = None` パラメータを追加。
61
+
62
+ ### FT32-F3: X-Frame-Options が DENY にハードコード(軽微)
63
+
64
+ **概要**: iframe 内表示が必要なケースで SAMEORIGIN に変更できない。
65
+ ただし DENY がセキュリティ上より安全なデフォルトであり、一般ユース向け。
66
+
67
+ **判断**: ニッチなケースのため低優先度。Issue 化はするが即座に修正しない。
68
+
69
+ ---
70
+
71
+ ## まとめ
72
+
73
+ CSP カスタマイズ機能は問題なく動作する。
74
+
75
+ 摩擦点:
76
+ 1. **Permissions-Policy ハードコード** → Issue 化・修正対象
77
+ 2. **HSTS ヘッダーなし** → Issue 化・修正対象
78
+ 3. **X-Frame-Options ハードコード** → 低優先度 Issue
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nene2-python"
3
- version = "1.8.2"
3
+ version = "1.8.3"
4
4
  description = "NENE2 Python — minimal API framework following NENE2's design philosophy"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
@@ -13,6 +13,10 @@ class HealthStatus:
13
13
  def is_healthy(self) -> bool:
14
14
  return self.status == "ok"
15
15
 
16
+ @property
17
+ def http_status_code(self) -> int:
18
+ return 200 if self.is_healthy else 503
19
+
16
20
 
17
21
  @runtime_checkable
18
22
  class HealthCheckProtocol(Protocol):
@@ -10,12 +10,12 @@ from starlette.requests import Request
10
10
  from starlette.responses import Response
11
11
 
12
12
  _DEFAULT_CSP = "default-src 'self'"
13
+ _DEFAULT_PERMISSIONS_POLICY = "geolocation=(), microphone=()"
13
14
 
14
- _NON_CSP_HEADERS: dict[str, str] = {
15
+ _STATIC_HEADERS: dict[str, str] = {
15
16
  "X-Content-Type-Options": "nosniff",
16
17
  "X-Frame-Options": "DENY",
17
18
  "Referrer-Policy": "strict-origin-when-cross-origin",
18
- "Permissions-Policy": "geolocation=(), microphone=()",
19
19
  }
20
20
 
21
21
  _DEFAULT_NO_CSP_PATHS: frozenset[str] = frozenset({"/docs", "/redoc", "/openapi.json"})
@@ -30,6 +30,11 @@ class SecurityHeadersMiddleware(BaseHTTPMiddleware):
30
30
  Args:
31
31
  csp: Custom Content-Security-Policy header value.
32
32
  Defaults to ``"default-src 'self'"`` when not specified.
33
+ permissions_policy: Custom Permissions-Policy header value.
34
+ Defaults to ``"geolocation=(), microphone=()"`` when not specified.
35
+ hsts: Strict-Transport-Security header value (e.g.
36
+ ``"max-age=31536000; includeSubDomains"``). Not set by default.
37
+ Enable only in production environments serving HTTPS.
33
38
  extra_no_csp_paths: Additional paths to skip the CSP header for.
34
39
  Useful when FastAPI is configured with custom ``docs_url`` / ``redoc_url``.
35
40
  The built-in paths ``/docs``, ``/redoc``, and ``/openapi.json`` are always included.
@@ -39,16 +44,25 @@ class SecurityHeadersMiddleware(BaseHTTPMiddleware):
39
44
  self,
40
45
  app: object,
41
46
  csp: str | None = None,
47
+ permissions_policy: str | None = None,
48
+ hsts: str | None = None,
42
49
  extra_no_csp_paths: list[str] | None = None,
43
50
  ) -> None:
44
51
  super().__init__(app) # type: ignore[arg-type]
45
52
  self._csp = csp if csp is not None else _DEFAULT_CSP
53
+ self._permissions_policy = (
54
+ permissions_policy if permissions_policy is not None else _DEFAULT_PERMISSIONS_POLICY
55
+ )
56
+ self._hsts = hsts
46
57
  self._no_csp_paths = _DEFAULT_NO_CSP_PATHS | frozenset(extra_no_csp_paths or [])
47
58
 
48
59
  async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
49
60
  response = await call_next(request)
50
- for header, value in _NON_CSP_HEADERS.items():
61
+ for header, value in _STATIC_HEADERS.items():
51
62
  response.headers[header] = value
63
+ response.headers["Permissions-Policy"] = self._permissions_policy
64
+ if self._hsts:
65
+ response.headers["Strict-Transport-Security"] = self._hsts
52
66
  if request.url.path not in self._no_csp_paths:
53
67
  response.headers["Content-Security-Policy"] = self._csp
54
68
  return response
@@ -56,3 +56,11 @@ def test_composite_with_empty_checks_returns_ok() -> None:
56
56
  result = composite.check()
57
57
  assert result.is_healthy is True
58
58
  assert result.checks == {}
59
+
60
+
61
+ def test_health_status_http_status_code_ok() -> None:
62
+ assert HealthStatus(status="ok").http_status_code == 200
63
+
64
+
65
+ def test_health_status_http_status_code_error() -> None:
66
+ assert HealthStatus(status="error").http_status_code == 503
@@ -80,6 +80,44 @@ def test_extra_no_csp_paths() -> None:
80
80
  assert "Content-Security-Policy" in client.get("/ping").headers
81
81
 
82
82
 
83
+ def test_custom_permissions_policy() -> None:
84
+ app = FastAPI()
85
+ app.add_middleware(
86
+ SecurityHeadersMiddleware,
87
+ permissions_policy="geolocation=(self), microphone=()",
88
+ )
89
+
90
+ @app.get("/ping")
91
+ async def ping() -> JSONResponse:
92
+ return JSONResponse({"ok": True})
93
+
94
+ client = TestClient(app)
95
+ r = client.get("/ping")
96
+ assert r.headers["Permissions-Policy"] == "geolocation=(self), microphone=()"
97
+
98
+
99
+ def test_hsts_header_when_specified() -> None:
100
+ app = FastAPI()
101
+ app.add_middleware(
102
+ SecurityHeadersMiddleware,
103
+ hsts="max-age=31536000; includeSubDomains",
104
+ )
105
+
106
+ @app.get("/ping")
107
+ async def ping() -> JSONResponse:
108
+ return JSONResponse({"ok": True})
109
+
110
+ client = TestClient(app)
111
+ r = client.get("/ping")
112
+ assert r.headers["Strict-Transport-Security"] == "max-age=31536000; includeSubDomains"
113
+
114
+
115
+ def test_no_hsts_by_default() -> None:
116
+ client = TestClient(_make_app())
117
+ r = client.get("/ping")
118
+ assert "Strict-Transport-Security" not in r.headers
119
+
120
+
83
121
  def test_default_no_csp_paths_still_work_with_extra_paths() -> None:
84
122
  app = FastAPI()
85
123
  app.add_middleware(SecurityHeadersMiddleware, extra_no_csp_paths=["/custom"])
@@ -925,7 +925,7 @@ wheels = [
925
925
 
926
926
  [[package]]
927
927
  name = "nene2-python"
928
- version = "1.8.2"
928
+ version = "1.8.3"
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
File without changes
File without changes