nene2-python 1.8.10__tar.gz → 1.8.12__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 (241) hide show
  1. {nene2_python-1.8.10 → nene2_python-1.8.12}/CHANGELOG.md +24 -0
  2. {nene2_python-1.8.10 → nene2_python-1.8.12}/PKG-INFO +1 -1
  3. nene2_python-1.8.12/docs/field-trials/2026-05-field-trial-51.md +127 -0
  4. nene2_python-1.8.12/docs/field-trials/2026-05-field-trial-52.md +90 -0
  5. {nene2_python-1.8.10 → nene2_python-1.8.12}/pyproject.toml +1 -1
  6. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/auth/local_verifier.py +2 -2
  7. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/http/problem_details.py +4 -0
  8. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/nene2/auth/test_bearer_token.py +13 -0
  9. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/nene2/http/test_problem_details.py +15 -0
  10. {nene2_python-1.8.10 → nene2_python-1.8.12}/uv.lock +1 -1
  11. {nene2_python-1.8.10 → nene2_python-1.8.12}/.env.example +0 -0
  12. {nene2_python-1.8.10 → nene2_python-1.8.12}/.github/workflows/ci.yml +0 -0
  13. {nene2_python-1.8.10 → nene2_python-1.8.12}/.github/workflows/docs.yml +0 -0
  14. {nene2_python-1.8.10 → nene2_python-1.8.12}/.github/workflows/publish.yml +0 -0
  15. {nene2_python-1.8.10 → nene2_python-1.8.12}/.gitignore +0 -0
  16. {nene2_python-1.8.10 → nene2_python-1.8.12}/.vitepress/config.mts +0 -0
  17. {nene2_python-1.8.10 → nene2_python-1.8.12}/.vitepress/theme/custom.css +0 -0
  18. {nene2_python-1.8.10 → nene2_python-1.8.12}/.vitepress/theme/index.ts +0 -0
  19. {nene2_python-1.8.10 → nene2_python-1.8.12}/AGENTS.md +0 -0
  20. {nene2_python-1.8.10 → nene2_python-1.8.12}/CLAUDE.md +0 -0
  21. {nene2_python-1.8.10 → nene2_python-1.8.12}/Dockerfile +0 -0
  22. {nene2_python-1.8.10 → nene2_python-1.8.12}/LICENSE +0 -0
  23. {nene2_python-1.8.10 → nene2_python-1.8.12}/README.md +0 -0
  24. {nene2_python-1.8.10 → nene2_python-1.8.12}/alembic/README +0 -0
  25. {nene2_python-1.8.10 → nene2_python-1.8.12}/alembic/env.py +0 -0
  26. {nene2_python-1.8.10 → nene2_python-1.8.12}/alembic/script.py.mako +0 -0
  27. {nene2_python-1.8.10 → nene2_python-1.8.12}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
  28. {nene2_python-1.8.10 → nene2_python-1.8.12}/alembic.ini +0 -0
  29. {nene2_python-1.8.10 → nene2_python-1.8.12}/compose.yaml +0 -0
  30. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/adr/0001-toolchain.md +0 -0
  31. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/adr/0002-clean-architecture.md +0 -0
  32. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/adr/0003-security-first.md +0 -0
  33. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/adr/0004-ai-first-design.md +0 -0
  34. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/adr/0005-logging.md +0 -0
  35. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/adr/0006-rate-limiting.md +0 -0
  36. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/adr/0009-mcp-design.md +0 -0
  37. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/adr/0010-async-use-case.md +0 -0
  38. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
  39. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/de/index.md +0 -0
  40. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/de/tutorials/getting-started.md +0 -0
  41. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/explanation/architecture.md +0 -0
  42. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/explanation/design-philosophy.md +0 -0
  43. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-1.md +0 -0
  44. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-10.md +0 -0
  45. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-11.md +0 -0
  46. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-12.md +0 -0
  47. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-13.md +0 -0
  48. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-14.md +0 -0
  49. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-15.md +0 -0
  50. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-16.md +0 -0
  51. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-17.md +0 -0
  52. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-18.md +0 -0
  53. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-19.md +0 -0
  54. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-2.md +0 -0
  55. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-20.md +0 -0
  56. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-21.md +0 -0
  57. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-22.md +0 -0
  58. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-23.md +0 -0
  59. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-24.md +0 -0
  60. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-25.md +0 -0
  61. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-26.md +0 -0
  62. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-27.md +0 -0
  63. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-28.md +0 -0
  64. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-29.md +0 -0
  65. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-3.md +0 -0
  66. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-30.md +0 -0
  67. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-31.md +0 -0
  68. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-32.md +0 -0
  69. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-33.md +0 -0
  70. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-34.md +0 -0
  71. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-35.md +0 -0
  72. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-36.md +0 -0
  73. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-37.md +0 -0
  74. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-38.md +0 -0
  75. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-39.md +0 -0
  76. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-4.md +0 -0
  77. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-40.md +0 -0
  78. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-41.md +0 -0
  79. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-42.md +0 -0
  80. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-43.md +0 -0
  81. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-44.md +0 -0
  82. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-45.md +0 -0
  83. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-46.md +0 -0
  84. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-47.md +0 -0
  85. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-48.md +0 -0
  86. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-49.md +0 -0
  87. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-5.md +0 -0
  88. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-50.md +0 -0
  89. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-6.md +0 -0
  90. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-7.md +0 -0
  91. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-8.md +0 -0
  92. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/field-trials/2026-05-field-trial-9.md +0 -0
  93. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/fr/index.md +0 -0
  94. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/fr/tutorials/getting-started.md +0 -0
  95. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/how-to/add-new-domain.md +0 -0
  96. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/how-to/async-use-case.md +0 -0
  97. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/how-to/configure-auth.md +0 -0
  98. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/how-to/new-project.md +0 -0
  99. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/how-to/problem-details.md +0 -0
  100. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/how-to/run-tests.md +0 -0
  101. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/how-to/sqlalchemy-repository.md +0 -0
  102. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/how-to/validation.md +0 -0
  103. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/howto/mcp-setup.md +0 -0
  104. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/index.md +0 -0
  105. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/ja/explanation/architecture.md +0 -0
  106. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/ja/explanation/design-philosophy.md +0 -0
  107. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/ja/how-to/add-new-domain.md +0 -0
  108. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/ja/how-to/configure-auth.md +0 -0
  109. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/ja/how-to/new-project.md +0 -0
  110. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/ja/how-to/run-tests.md +0 -0
  111. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
  112. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/ja/howto/mcp-setup.md +0 -0
  113. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/ja/index.md +0 -0
  114. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/ja/reference/api.md +0 -0
  115. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/ja/reference/configuration.md +0 -0
  116. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/ja/reference/framework-modules.md +0 -0
  117. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/ja/tutorials/first-domain.md +0 -0
  118. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/ja/tutorials/getting-started.md +0 -0
  119. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/pt-br/index.md +0 -0
  120. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/pt-br/tutorials/getting-started.md +0 -0
  121. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/reference/api.md +0 -0
  122. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/reference/configuration.md +0 -0
  123. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/reference/framework-modules.md +0 -0
  124. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/roadmap.md +0 -0
  125. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/todo/current.md +0 -0
  126. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/tutorials/first-domain.md +0 -0
  127. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/tutorials/getting-started.md +0 -0
  128. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/zh/index.md +0 -0
  129. {nene2_python-1.8.10 → nene2_python-1.8.12}/docs/zh/tutorials/getting-started.md +0 -0
  130. {nene2_python-1.8.10 → nene2_python-1.8.12}/package-lock.json +0 -0
  131. {nene2_python-1.8.10 → nene2_python-1.8.12}/package.json +0 -0
  132. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/example/__init__.py +0 -0
  133. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/example/__main__.py +0 -0
  134. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/example/app.py +0 -0
  135. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/example/comment/__init__.py +0 -0
  136. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/example/comment/entity.py +0 -0
  137. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/example/comment/exceptions.py +0 -0
  138. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/example/comment/handler.py +0 -0
  139. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/example/comment/repository.py +0 -0
  140. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/example/comment/sqlalchemy_repository.py +0 -0
  141. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/example/comment/use_case.py +0 -0
  142. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/example/mcp.py +0 -0
  143. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/example/note/__init__.py +0 -0
  144. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/example/note/async_use_case.py +0 -0
  145. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/example/note/entity.py +0 -0
  146. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/example/note/exceptions.py +0 -0
  147. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/example/note/handler.py +0 -0
  148. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/example/note/repository.py +0 -0
  149. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/example/note/sqlalchemy_repository.py +0 -0
  150. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/example/note/use_case.py +0 -0
  151. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/example/schema.py +0 -0
  152. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/example/tag/__init__.py +0 -0
  153. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/example/tag/entity.py +0 -0
  154. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/example/tag/exceptions.py +0 -0
  155. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/example/tag/handler.py +0 -0
  156. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/example/tag/repository.py +0 -0
  157. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/example/tag/sqlalchemy_repository.py +0 -0
  158. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/example/tag/use_case.py +0 -0
  159. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/__init__.py +0 -0
  160. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/auth/__init__.py +0 -0
  161. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/auth/api_key.py +0 -0
  162. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/auth/bearer_token.py +0 -0
  163. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/auth/exceptions.py +0 -0
  164. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/auth/interfaces.py +0 -0
  165. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/config/__init__.py +0 -0
  166. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/config/settings.py +0 -0
  167. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/database/__init__.py +0 -0
  168. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/database/exceptions.py +0 -0
  169. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/database/health.py +0 -0
  170. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/database/interfaces.py +0 -0
  171. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/database/sqlalchemy_executor.py +0 -0
  172. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/database/utils.py +0 -0
  173. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/http/__init__.py +0 -0
  174. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/http/health.py +0 -0
  175. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/http/pagination.py +0 -0
  176. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/log/__init__.py +0 -0
  177. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/log/setup.py +0 -0
  178. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/mcp/__init__.py +0 -0
  179. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/mcp/http_client.py +0 -0
  180. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/mcp/server.py +0 -0
  181. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/middleware/__init__.py +0 -0
  182. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/middleware/domain_exception.py +0 -0
  183. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/middleware/error_handler.py +0 -0
  184. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/middleware/request_id.py +0 -0
  185. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/middleware/request_logging.py +0 -0
  186. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/middleware/request_size_limit.py +0 -0
  187. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/middleware/security_headers.py +0 -0
  188. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/middleware/throttle.py +0 -0
  189. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/py.typed +0 -0
  190. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/use_case/__init__.py +0 -0
  191. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/use_case/protocols.py +0 -0
  192. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/validation/__init__.py +0 -0
  193. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/nene2/validation/exceptions.py +0 -0
  194. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/scripts/__init__.py +0 -0
  195. {nene2_python-1.8.10 → nene2_python-1.8.12}/src/scripts/export_openapi.py +0 -0
  196. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/__init__.py +0 -0
  197. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/example/__init__.py +0 -0
  198. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/example/comment/__init__.py +0 -0
  199. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/example/comment/test_comment_http.py +0 -0
  200. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/example/comment/test_comment_repository.py +0 -0
  201. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/example/comment/test_comment_use_case.py +0 -0
  202. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/example/conftest.py +0 -0
  203. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/example/note/__init__.py +0 -0
  204. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/example/note/test_async_note_use_case.py +0 -0
  205. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/example/note/test_list_notes.py +0 -0
  206. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/example/note/test_note_repository.py +0 -0
  207. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/example/tag/__init__.py +0 -0
  208. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/example/tag/test_tag_repository.py +0 -0
  209. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/example/tag/test_tags.py +0 -0
  210. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/example/test_cors.py +0 -0
  211. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/example/test_mcp.py +0 -0
  212. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/nene2/__init__.py +0 -0
  213. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/nene2/auth/__init__.py +0 -0
  214. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/nene2/auth/test_api_key.py +0 -0
  215. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/nene2/auth/test_token_issuer.py +0 -0
  216. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/nene2/config/__init__.py +0 -0
  217. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/nene2/config/test_settings.py +0 -0
  218. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/nene2/database/__init__.py +0 -0
  219. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/nene2/database/test_transaction.py +0 -0
  220. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/nene2/database/test_utils.py +0 -0
  221. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/nene2/http/__init__.py +0 -0
  222. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/nene2/http/test_health.py +0 -0
  223. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/nene2/http/test_pagination.py +0 -0
  224. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/nene2/log/__init__.py +0 -0
  225. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/nene2/log/test_setup.py +0 -0
  226. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/nene2/mcp/__init__.py +0 -0
  227. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/nene2/mcp/test_http_client.py +0 -0
  228. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/nene2/middleware/__init__.py +0 -0
  229. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/nene2/middleware/test_error_handler.py +0 -0
  230. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/nene2/middleware/test_request_id.py +0 -0
  231. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/nene2/middleware/test_request_logging.py +0 -0
  232. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/nene2/middleware/test_request_size_limit.py +0 -0
  233. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/nene2/middleware/test_security_headers.py +0 -0
  234. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
  235. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/nene2/middleware/test_throttle.py +0 -0
  236. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/nene2/use_case/__init__.py +0 -0
  237. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/nene2/use_case/test_protocols.py +0 -0
  238. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/nene2/validation/__init__.py +0 -0
  239. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/nene2/validation/test_exceptions.py +0 -0
  240. {nene2_python-1.8.10 → nene2_python-1.8.12}/tests/scripts/__init__.py +0 -0
  241. {nene2_python-1.8.10 → nene2_python-1.8.12}/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.12] — 2026-05-20
9
+
10
+ FT52 フィールドトライアル — ミドルウェアスタック組み合わせ検証 + LocalTokenVerifier 改善。
11
+
12
+ ### Changed
13
+ - `LocalTokenVerifier.__init__` — `allowed_tokens` の型を `list[str] | set[str] | frozenset[str]` に変更。内部で `frozenset` に変換し `in` 演算が O(1) になった (#284) (FT52)
14
+
15
+ ### Added
16
+ - Field trial report: `docs/field-trials/2026-05-field-trial-52.md`
17
+
18
+ ---
19
+
20
+ ## [1.8.11] — 2026-05-20
21
+
22
+ FT51 フィールドトライアル — SimpleDomainHandler 実運用検証 + problem_details_response バグ修正。
23
+
24
+ ### Fixed
25
+ - `problem_details_response()` — `extra` に RFC 9457 予約済みフィールド (`type`, `title`, `status`, `detail`) が含まれる場合に `ValueError` を raise するよう修正 (#282) (FT51)
26
+
27
+ ### Added
28
+ - Field trial report: `docs/field-trials/2026-05-field-trial-51.md`
29
+
30
+ ---
31
+
8
32
  ## [1.8.10] — 2026-05-20
9
33
 
10
34
  FT50 フィールドトライアル — ValidationException + ValidationCode(StrEnum) 実運用検証。
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nene2-python
3
- Version: 1.8.10
3
+ Version: 1.8.12
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,127 @@
1
+ # Field Trial 51: SimpleDomainHandler 実運用検証
2
+
3
+ **Date**: 2026-05-20
4
+ **Theme**: `SimpleDomainHandler` + `detail_factory` / `extra_factory` の実運用パターン検証
5
+ **Version under test**: v1.8.10
6
+ **FT App**: `/home/xi/docker/nene2-python-FT/ft51-domain-handler/`
7
+
8
+ ---
9
+
10
+ ## 概要
11
+
12
+ 複数のドメイン例外を `SimpleDomainHandler` で Problem Details に変換するパターンを
13
+ 記事 API で実運用した。404 / 403 / 409 の 3 種類のエラーを検証。
14
+
15
+ ---
16
+
17
+ ## 実装内容
18
+
19
+ ### ドメイン例外クラス
20
+
21
+ ```python
22
+ class ArticleNotFoundError(Exception):
23
+ def __init__(self, article_id: int) -> None:
24
+ self.article_id = article_id
25
+
26
+ class ArticleAccessDeniedError(Exception):
27
+ def __init__(self, article_id: int, user_id: str) -> None:
28
+ self.article_id = article_id
29
+ self.user_id = user_id
30
+
31
+ class ArticleTitleConflictError(Exception):
32
+ def __init__(self, title: str) -> None:
33
+ self.title = title
34
+ ```
35
+
36
+ ### SimpleDomainHandler による登録
37
+
38
+ ```python
39
+ handlers = [
40
+ SimpleDomainHandler(
41
+ ArticleNotFoundError,
42
+ "article-not-found",
43
+ "Article Not Found",
44
+ 404,
45
+ detail_factory=lambda exc: str(exc),
46
+ extra_factory=lambda exc: {"article_id": exc.article_id},
47
+ ),
48
+ SimpleDomainHandler(
49
+ ArticleAccessDeniedError,
50
+ "article-access-denied",
51
+ "Access Denied",
52
+ 403,
53
+ detail_factory=lambda exc: str(exc),
54
+ extra_factory=lambda exc: {"article_id": exc.article_id, "user_id": exc.user_id},
55
+ ),
56
+ SimpleDomainHandler(
57
+ ArticleTitleConflictError,
58
+ "article-title-conflict",
59
+ "Article Title Conflict",
60
+ 409,
61
+ detail_factory=lambda exc: str(exc),
62
+ extra_factory=lambda exc: {"article_title": exc.title}, # ← "title" ではなく "article_title"
63
+ ),
64
+ ]
65
+ app.add_middleware(ErrorHandlerMiddleware, domain_handlers=handlers)
66
+ ```
67
+
68
+ ---
69
+
70
+ ## テスト結果
71
+
72
+ 6 tests, all passed (after fixing FP51-1).
73
+
74
+ ---
75
+
76
+ ## 摩擦ポイント
77
+
78
+ ### FP51-1: `extra_factory` に `title` キーを返すと Problem Details の `title` が上書きされる
79
+
80
+ **状況**: `ArticleTitleConflictError` の `extra_factory` で `{"title": exc.title}` を返したところ、
81
+ Problem Details レスポンスの `title` フィールド(`"Article Title Conflict"`)が `exc.title`(`"Dup"`)に
82
+ 上書きされた。
83
+
84
+ ```json
85
+ {
86
+ "type": "...",
87
+ "title": "Dup", // ← "Article Title Conflict" のはずが上書きされた
88
+ "status": 409,
89
+ "detail": "Article with title 'Dup' already exists"
90
+ }
91
+ ```
92
+
93
+ **原因**: `problem_details_response()` が `body.update(extra)` で `extra` を後から適用するため、
94
+ RFC 9457 の予約済みフィールド (`type`, `title`, `status`, `detail`) を含む `extra` が
95
+ 意図せずフィールドを上書きする。
96
+
97
+ **修正**: `problem_details_response()` が `extra` に予約済みフィールドが含まれている場合に
98
+ `ValueError` を raise するよう修正 (#282)。
99
+
100
+ ```python
101
+ # 修正後のコード
102
+ _RESERVED_FIELDS = frozenset({"type", "title", "status", "detail"})
103
+ if extra:
104
+ overlap = _RESERVED_FIELDS & extra.keys()
105
+ if overlap:
106
+ raise ValueError(f"extra contains reserved Problem Details fields: {sorted(overlap)}")
107
+ body.update(extra)
108
+ ```
109
+
110
+ **ワークアラウンド(修正前)**: `extra` に予約済みキーと衝突しない名前を使う(例: `"article_title"`)。
111
+
112
+ ---
113
+
114
+ ## フレームワーク変更
115
+
116
+ ### `nene2.http.problem_details_response()` — extra reserved fields 保護 (#282)
117
+
118
+ `extra` に RFC 9457 予約済みフィールド (`type`, `title`, `status`, `detail`) が含まれる場合に
119
+ `ValueError` を raise するようになった。
120
+
121
+ ---
122
+
123
+ ## 結論
124
+
125
+ `SimpleDomainHandler` + `detail_factory` / `extra_factory` の組み合わせは実運用で使いやすい。
126
+ ただし `extra_factory` の返り値に RFC 9457 予約済みフィールドと同名のキーを入れると
127
+ サイレントに上書きされる危険があった。`ValueError` による早期検知で問題を防止できる。
@@ -0,0 +1,90 @@
1
+ # Field Trial 52: ミドルウェアスタック組み合わせ検証
2
+
3
+ **Date**: 2026-05-20
4
+ **Theme**: 全ミドルウェアスタック (BearerToken + Throttle + RequestLogging + SecurityHeaders) の組み合わせ実運用
5
+ **Version under test**: v1.8.11
6
+ **FT App**: `/home/xi/docker/nene2-python-FT/ft52-middleware-stack/`
7
+
8
+ ---
9
+
10
+ ## 概要
11
+
12
+ `ErrorHandlerMiddleware` + `SecurityHeadersMiddleware` + `RequestIdMiddleware` +
13
+ `RequestLoggingMiddleware` + `ThrottleMiddleware` + `BearerTokenMiddleware` を
14
+ 全スタックで積み重ねて動作を検証した。
15
+
16
+ ---
17
+
18
+ ## 実装内容
19
+
20
+ ### ミドルウェアスタック
21
+
22
+ ```python
23
+ app.add_middleware(ErrorHandlerMiddleware)
24
+ app.add_middleware(SecurityHeadersMiddleware, csp="default-src 'self'", hsts="max-age=31536000")
25
+ app.add_middleware(RequestIdMiddleware)
26
+ app.add_middleware(RequestLoggingMiddleware, exclude_paths=["/health"], extra_context={"service": "ft52", "env": "test"})
27
+ app.add_middleware(ThrottleMiddleware, limit=5, window=60, exclude_paths=["/health"])
28
+ app.add_middleware(BearerTokenMiddleware, verifier=verifier, exclude_paths=["/health", "/docs", "/openapi.json"])
29
+ ```
30
+
31
+ `/health` は認証・ログ・レート制限をすべてバイパスする設計。
32
+
33
+ ---
34
+
35
+ ## テスト結果
36
+
37
+ 10 tests, all passed (after fixing FP52-1).
38
+
39
+ | テスト | 結果 |
40
+ |---|---|
41
+ | /health は認証不要 | ✅ |
42
+ | /items は認証必須 | ✅ |
43
+ | 有効トークンで /items アクセス | ✅ |
44
+ | セキュリティヘッダー存在確認 | ✅ |
45
+ | HSTS ヘッダー確認 | ✅ |
46
+ | X-Request-Id ヘッダー確認 | ✅ |
47
+ | X-RateLimit-Limit ヘッダー確認 | ✅ |
48
+ | /health はレート制限なし (10回連続) | ✅ |
49
+ | レート制限超過で 429 | ✅ |
50
+ | 無効トークンで 401 | ✅ |
51
+
52
+ ---
53
+
54
+ ## 摩擦ポイント
55
+
56
+ ### FP52-1: `LocalTokenVerifier` の引数名が直感的でない / `set` 非対応
57
+
58
+ **状況**: `LocalTokenVerifier(valid_tokens={valid_token})` と書いたが:
59
+ 1. 引数名は `valid_tokens` ではなく `allowed_tokens`
60
+ 2. `set` を渡すと型エラー(内部が `list[str]` を期待)
61
+
62
+ **修正**: `allowed_tokens` の型を `list[str] | set[str] | frozenset[str]` に変更し、
63
+ 内部で `frozenset` に変換するよう修正 (#284)。
64
+ 内部を `frozenset` にすることで `in` 演算の O(n) → O(1) 改善も得られた。
65
+
66
+ ---
67
+
68
+ ## フレームワーク変更
69
+
70
+ ### `LocalTokenVerifier` — `set` / `frozenset` を受け入れ可能に (#284)
71
+
72
+ ```python
73
+ # 修正前
74
+ def __init__(self, allowed_tokens: list[str]) -> None:
75
+ self._allowed = allowed_tokens
76
+
77
+ # 修正後
78
+ def __init__(self, allowed_tokens: list[str] | set[str] | frozenset[str]) -> None:
79
+ self._allowed: frozenset[str] = frozenset(allowed_tokens)
80
+ ```
81
+
82
+ ---
83
+
84
+ ## 全スタック動作確認
85
+
86
+ 各ミドルウェアの機能がスタック状態でも正常に動作することを確認:
87
+ - `exclude_paths` の設定が各ミドルウェアで独立して機能する
88
+ - `BearerTokenMiddleware` が `ThrottleMiddleware` より外側(先に評価)
89
+ - `SecurityHeadersMiddleware` のヘッダーが全レスポンスに付与される
90
+ - `RequestIdMiddleware` の `X-Request-Id` が全レスポンスに付与される
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nene2-python"
3
- version = "1.8.10"
3
+ version = "1.8.12"
4
4
  description = "NENE2 Python — minimal API framework following NENE2's design philosophy"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
@@ -11,8 +11,8 @@ import secrets
11
11
  class LocalTokenVerifier:
12
12
  """Verify tokens against a fixed allowlist using constant-time comparison."""
13
13
 
14
- def __init__(self, allowed_tokens: list[str]) -> None:
15
- self._allowed = allowed_tokens
14
+ def __init__(self, allowed_tokens: list[str] | set[str] | frozenset[str]) -> None:
15
+ self._allowed: frozenset[str] = frozenset(allowed_tokens)
16
16
 
17
17
  @classmethod
18
18
  def from_env(cls, env_var: str, *, separator: str = ",") -> "LocalTokenVerifier":
@@ -8,6 +8,7 @@ from typing import Any
8
8
  from fastapi.responses import JSONResponse
9
9
 
10
10
  PROBLEM_DETAILS_BASE_URL = "https://nene2.dev/problems/"
11
+ _RESERVED_FIELDS = frozenset({"type", "title", "status", "detail"})
11
12
 
12
13
  _configured_base_url: str | None = None
13
14
 
@@ -73,6 +74,9 @@ def problem_details_response(
73
74
  if detail:
74
75
  body["detail"] = detail
75
76
  if extra:
77
+ overlap = _RESERVED_FIELDS & extra.keys()
78
+ if overlap:
79
+ raise ValueError(f"extra contains reserved Problem Details fields: {sorted(overlap)}")
76
80
  body.update(extra)
77
81
 
78
82
  return JSONResponse(
@@ -113,3 +113,16 @@ def test_exclude_paths_default_is_empty() -> None:
113
113
 
114
114
  client = TestClient(app)
115
115
  assert client.get("/health").status_code == 401
116
+
117
+
118
+ def test_local_verifier_accepts_set() -> None:
119
+ verifier = LocalTokenVerifier({"tok-a", "tok-b"})
120
+ assert verifier.verify("tok-a") is True
121
+ assert verifier.verify("tok-b") is True
122
+ assert verifier.verify("tok-c") is False
123
+
124
+
125
+ def test_local_verifier_accepts_frozenset() -> None:
126
+ verifier = LocalTokenVerifier(frozenset({"tok-x", "tok-y"}))
127
+ assert verifier.verify("tok-x") is True
128
+ assert verifier.verify("unknown") is False
@@ -74,3 +74,18 @@ def test_reset_problem_details_is_idempotent() -> None:
74
74
  reset_problem_details()
75
75
  r = problem_details_response("not-found", "Not Found", 404)
76
76
  assert b"nene2.dev/problems/not-found" in r.body
77
+
78
+
79
+ def test_extra_with_reserved_field_raises_value_error() -> None:
80
+ with pytest.raises(ValueError, match="reserved Problem Details fields"):
81
+ problem_details_response("x", "X", 400, extra={"title": "bad"})
82
+
83
+
84
+ def test_extra_with_type_reserved_raises_value_error() -> None:
85
+ with pytest.raises(ValueError, match="reserved Problem Details fields"):
86
+ problem_details_response("x", "X", 400, extra={"type": "overridden"})
87
+
88
+
89
+ def test_extra_with_status_reserved_raises_value_error() -> None:
90
+ with pytest.raises(ValueError, match="reserved Problem Details fields"):
91
+ problem_details_response("x", "X", 400, extra={"status": 500})
@@ -925,7 +925,7 @@ wheels = [
925
925
 
926
926
  [[package]]
927
927
  name = "nene2-python"
928
- version = "1.8.10"
928
+ version = "1.8.12"
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