nene2-python 1.8.16__tar.gz → 1.8.18__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 (257) hide show
  1. {nene2_python-1.8.16 → nene2_python-1.8.18}/CHANGELOG.md +27 -0
  2. {nene2_python-1.8.16 → nene2_python-1.8.18}/PKG-INFO +1 -1
  3. nene2_python-1.8.18/docs/field-trials/2026-05-field-trial-63.md +61 -0
  4. nene2_python-1.8.18/docs/field-trials/2026-05-field-trial-64.md +67 -0
  5. nene2_python-1.8.18/docs/field-trials/2026-05-field-trial-65.md +54 -0
  6. nene2_python-1.8.18/docs/field-trials/2026-05-field-trial-66.md +52 -0
  7. nene2_python-1.8.18/docs/field-trials/2026-05-field-trial-67.md +75 -0
  8. {nene2_python-1.8.16 → nene2_python-1.8.18}/pyproject.toml +1 -1
  9. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/database/sqlalchemy_executor.py +17 -1
  10. nene2_python-1.8.18/src/nene2/validation/exceptions.py +101 -0
  11. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/nene2/validation/test_exceptions.py +5 -0
  12. {nene2_python-1.8.16 → nene2_python-1.8.18}/uv.lock +1 -1
  13. nene2_python-1.8.16/src/nene2/validation/exceptions.py +0 -52
  14. {nene2_python-1.8.16 → nene2_python-1.8.18}/.env.example +0 -0
  15. {nene2_python-1.8.16 → nene2_python-1.8.18}/.github/workflows/ci.yml +0 -0
  16. {nene2_python-1.8.16 → nene2_python-1.8.18}/.github/workflows/docs.yml +0 -0
  17. {nene2_python-1.8.16 → nene2_python-1.8.18}/.github/workflows/publish.yml +0 -0
  18. {nene2_python-1.8.16 → nene2_python-1.8.18}/.gitignore +0 -0
  19. {nene2_python-1.8.16 → nene2_python-1.8.18}/.vitepress/config.mts +0 -0
  20. {nene2_python-1.8.16 → nene2_python-1.8.18}/.vitepress/theme/custom.css +0 -0
  21. {nene2_python-1.8.16 → nene2_python-1.8.18}/.vitepress/theme/index.ts +0 -0
  22. {nene2_python-1.8.16 → nene2_python-1.8.18}/AGENTS.md +0 -0
  23. {nene2_python-1.8.16 → nene2_python-1.8.18}/CLAUDE.md +0 -0
  24. {nene2_python-1.8.16 → nene2_python-1.8.18}/Dockerfile +0 -0
  25. {nene2_python-1.8.16 → nene2_python-1.8.18}/LICENSE +0 -0
  26. {nene2_python-1.8.16 → nene2_python-1.8.18}/README.md +0 -0
  27. {nene2_python-1.8.16 → nene2_python-1.8.18}/alembic/README +0 -0
  28. {nene2_python-1.8.16 → nene2_python-1.8.18}/alembic/env.py +0 -0
  29. {nene2_python-1.8.16 → nene2_python-1.8.18}/alembic/script.py.mako +0 -0
  30. {nene2_python-1.8.16 → nene2_python-1.8.18}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
  31. {nene2_python-1.8.16 → nene2_python-1.8.18}/alembic.ini +0 -0
  32. {nene2_python-1.8.16 → nene2_python-1.8.18}/compose.yaml +0 -0
  33. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/adr/0001-toolchain.md +0 -0
  34. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/adr/0002-clean-architecture.md +0 -0
  35. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/adr/0003-security-first.md +0 -0
  36. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/adr/0004-ai-first-design.md +0 -0
  37. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/adr/0005-logging.md +0 -0
  38. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/adr/0006-rate-limiting.md +0 -0
  39. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/adr/0009-mcp-design.md +0 -0
  40. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/adr/0010-async-use-case.md +0 -0
  41. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
  42. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/de/index.md +0 -0
  43. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/de/tutorials/getting-started.md +0 -0
  44. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/explanation/architecture.md +0 -0
  45. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/explanation/design-philosophy.md +0 -0
  46. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-1.md +0 -0
  47. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-10.md +0 -0
  48. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-11.md +0 -0
  49. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-12.md +0 -0
  50. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-13.md +0 -0
  51. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-14.md +0 -0
  52. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-15.md +0 -0
  53. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-16.md +0 -0
  54. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-17.md +0 -0
  55. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-18.md +0 -0
  56. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-19.md +0 -0
  57. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-2.md +0 -0
  58. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-20.md +0 -0
  59. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-21.md +0 -0
  60. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-22.md +0 -0
  61. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-23.md +0 -0
  62. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-24.md +0 -0
  63. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-25.md +0 -0
  64. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-26.md +0 -0
  65. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-27.md +0 -0
  66. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-28.md +0 -0
  67. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-29.md +0 -0
  68. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-3.md +0 -0
  69. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-30.md +0 -0
  70. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-31.md +0 -0
  71. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-32.md +0 -0
  72. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-33.md +0 -0
  73. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-34.md +0 -0
  74. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-35.md +0 -0
  75. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-36.md +0 -0
  76. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-37.md +0 -0
  77. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-38.md +0 -0
  78. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-39.md +0 -0
  79. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-4.md +0 -0
  80. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-40.md +0 -0
  81. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-41.md +0 -0
  82. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-42.md +0 -0
  83. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-43.md +0 -0
  84. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-44.md +0 -0
  85. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-45.md +0 -0
  86. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-46.md +0 -0
  87. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-47.md +0 -0
  88. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-48.md +0 -0
  89. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-49.md +0 -0
  90. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-5.md +0 -0
  91. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-50.md +0 -0
  92. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-51.md +0 -0
  93. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-52.md +0 -0
  94. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-53.md +0 -0
  95. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-54.md +0 -0
  96. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-55.md +0 -0
  97. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-56.md +0 -0
  98. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-57.md +0 -0
  99. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-58.md +0 -0
  100. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-59.md +0 -0
  101. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-6.md +0 -0
  102. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-60.md +0 -0
  103. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-61.md +0 -0
  104. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-62.md +0 -0
  105. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-7.md +0 -0
  106. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-8.md +0 -0
  107. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/field-trials/2026-05-field-trial-9.md +0 -0
  108. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/fr/index.md +0 -0
  109. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/fr/tutorials/getting-started.md +0 -0
  110. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/how-to/add-new-domain.md +0 -0
  111. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/how-to/async-use-case.md +0 -0
  112. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/how-to/configure-auth.md +0 -0
  113. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/how-to/new-project.md +0 -0
  114. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/how-to/problem-details.md +0 -0
  115. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/how-to/run-tests.md +0 -0
  116. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/how-to/sqlalchemy-repository.md +0 -0
  117. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/how-to/validation.md +0 -0
  118. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/howto/mcp-setup.md +0 -0
  119. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/index.md +0 -0
  120. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/ja/explanation/architecture.md +0 -0
  121. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/ja/explanation/design-philosophy.md +0 -0
  122. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/ja/how-to/add-new-domain.md +0 -0
  123. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/ja/how-to/configure-auth.md +0 -0
  124. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/ja/how-to/new-project.md +0 -0
  125. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/ja/how-to/run-tests.md +0 -0
  126. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
  127. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/ja/howto/mcp-setup.md +0 -0
  128. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/ja/index.md +0 -0
  129. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/ja/reference/api.md +0 -0
  130. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/ja/reference/configuration.md +0 -0
  131. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/ja/reference/framework-modules.md +0 -0
  132. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/ja/tutorials/first-domain.md +0 -0
  133. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/ja/tutorials/getting-started.md +0 -0
  134. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/pt-br/index.md +0 -0
  135. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/pt-br/tutorials/getting-started.md +0 -0
  136. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/reference/api.md +0 -0
  137. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/reference/configuration.md +0 -0
  138. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/reference/framework-modules.md +0 -0
  139. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/roadmap.md +0 -0
  140. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/todo/current.md +0 -0
  141. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/tutorials/first-domain.md +0 -0
  142. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/tutorials/getting-started.md +0 -0
  143. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/zh/index.md +0 -0
  144. {nene2_python-1.8.16 → nene2_python-1.8.18}/docs/zh/tutorials/getting-started.md +0 -0
  145. {nene2_python-1.8.16 → nene2_python-1.8.18}/package-lock.json +0 -0
  146. {nene2_python-1.8.16 → nene2_python-1.8.18}/package.json +0 -0
  147. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/example/__init__.py +0 -0
  148. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/example/__main__.py +0 -0
  149. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/example/app.py +0 -0
  150. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/example/comment/__init__.py +0 -0
  151. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/example/comment/entity.py +0 -0
  152. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/example/comment/exceptions.py +0 -0
  153. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/example/comment/handler.py +0 -0
  154. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/example/comment/repository.py +0 -0
  155. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/example/comment/sqlalchemy_repository.py +0 -0
  156. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/example/comment/use_case.py +0 -0
  157. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/example/mcp.py +0 -0
  158. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/example/note/__init__.py +0 -0
  159. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/example/note/async_use_case.py +0 -0
  160. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/example/note/entity.py +0 -0
  161. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/example/note/exceptions.py +0 -0
  162. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/example/note/handler.py +0 -0
  163. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/example/note/repository.py +0 -0
  164. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/example/note/sqlalchemy_repository.py +0 -0
  165. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/example/note/use_case.py +0 -0
  166. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/example/schema.py +0 -0
  167. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/example/tag/__init__.py +0 -0
  168. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/example/tag/entity.py +0 -0
  169. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/example/tag/exceptions.py +0 -0
  170. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/example/tag/handler.py +0 -0
  171. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/example/tag/repository.py +0 -0
  172. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/example/tag/sqlalchemy_repository.py +0 -0
  173. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/example/tag/use_case.py +0 -0
  174. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/__init__.py +0 -0
  175. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/auth/__init__.py +0 -0
  176. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/auth/api_key.py +0 -0
  177. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/auth/bearer_token.py +0 -0
  178. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/auth/exceptions.py +0 -0
  179. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/auth/interfaces.py +0 -0
  180. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/auth/local_verifier.py +0 -0
  181. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/config/__init__.py +0 -0
  182. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/config/settings.py +0 -0
  183. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/database/__init__.py +0 -0
  184. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/database/exceptions.py +0 -0
  185. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/database/health.py +0 -0
  186. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/database/interfaces.py +0 -0
  187. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/database/utils.py +0 -0
  188. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/http/__init__.py +0 -0
  189. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/http/health.py +0 -0
  190. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/http/pagination.py +0 -0
  191. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/http/problem_details.py +0 -0
  192. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/log/__init__.py +0 -0
  193. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/log/setup.py +0 -0
  194. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/mcp/__init__.py +0 -0
  195. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/mcp/http_client.py +0 -0
  196. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/mcp/server.py +0 -0
  197. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/middleware/__init__.py +0 -0
  198. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/middleware/domain_exception.py +0 -0
  199. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/middleware/error_handler.py +0 -0
  200. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/middleware/request_id.py +0 -0
  201. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/middleware/request_logging.py +0 -0
  202. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/middleware/request_size_limit.py +0 -0
  203. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/middleware/security_headers.py +0 -0
  204. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/middleware/throttle.py +0 -0
  205. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/py.typed +0 -0
  206. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/use_case/__init__.py +0 -0
  207. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/use_case/protocols.py +0 -0
  208. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/nene2/validation/__init__.py +0 -0
  209. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/scripts/__init__.py +0 -0
  210. {nene2_python-1.8.16 → nene2_python-1.8.18}/src/scripts/export_openapi.py +0 -0
  211. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/__init__.py +0 -0
  212. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/example/__init__.py +0 -0
  213. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/example/comment/__init__.py +0 -0
  214. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/example/comment/test_comment_http.py +0 -0
  215. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/example/comment/test_comment_repository.py +0 -0
  216. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/example/comment/test_comment_use_case.py +0 -0
  217. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/example/conftest.py +0 -0
  218. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/example/note/__init__.py +0 -0
  219. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/example/note/test_async_note_use_case.py +0 -0
  220. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/example/note/test_list_notes.py +0 -0
  221. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/example/note/test_note_repository.py +0 -0
  222. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/example/tag/__init__.py +0 -0
  223. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/example/tag/test_tag_repository.py +0 -0
  224. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/example/tag/test_tags.py +0 -0
  225. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/example/test_cors.py +0 -0
  226. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/example/test_mcp.py +0 -0
  227. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/nene2/__init__.py +0 -0
  228. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/nene2/auth/__init__.py +0 -0
  229. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/nene2/auth/test_api_key.py +0 -0
  230. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/nene2/auth/test_bearer_token.py +0 -0
  231. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/nene2/auth/test_token_issuer.py +0 -0
  232. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/nene2/config/__init__.py +0 -0
  233. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/nene2/config/test_settings.py +0 -0
  234. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/nene2/database/__init__.py +0 -0
  235. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/nene2/database/test_transaction.py +0 -0
  236. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/nene2/database/test_utils.py +0 -0
  237. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/nene2/http/__init__.py +0 -0
  238. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/nene2/http/test_health.py +0 -0
  239. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/nene2/http/test_pagination.py +0 -0
  240. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/nene2/http/test_problem_details.py +0 -0
  241. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/nene2/log/__init__.py +0 -0
  242. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/nene2/log/test_setup.py +0 -0
  243. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/nene2/mcp/__init__.py +0 -0
  244. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/nene2/mcp/test_http_client.py +0 -0
  245. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/nene2/middleware/__init__.py +0 -0
  246. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/nene2/middleware/test_error_handler.py +0 -0
  247. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/nene2/middleware/test_request_id.py +0 -0
  248. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/nene2/middleware/test_request_logging.py +0 -0
  249. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/nene2/middleware/test_request_size_limit.py +0 -0
  250. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/nene2/middleware/test_security_headers.py +0 -0
  251. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
  252. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/nene2/middleware/test_throttle.py +0 -0
  253. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/nene2/use_case/__init__.py +0 -0
  254. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/nene2/use_case/test_protocols.py +0 -0
  255. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/nene2/validation/__init__.py +0 -0
  256. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/scripts/__init__.py +0 -0
  257. {nene2_python-1.8.16 → nene2_python-1.8.18}/tests/scripts/test_export_openapi.py +0 -0
@@ -5,6 +5,33 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
6
  ---
7
7
 
8
+ ## [1.8.18] — 2026-05-20
9
+
10
+ FT67 フィールドトライアル — SqlAlchemyTransactionManager 実運用検証。
11
+
12
+ ### Changed
13
+ - `SqlAlchemyQueryExecutor` の docstring に SQLite `:memory:` + `StaticPool` の注意書きを追加 (#305) (FT67)
14
+
15
+ ### Added
16
+ - Field trial reports: `docs/field-trials/2026-05-field-trial-65.md` 〜 `2026-05-field-trial-67.md` (FT65〜FT67)
17
+
18
+ ---
19
+
20
+ ## [1.8.17] — 2026-05-20
21
+
22
+ FT64 フィールドトライアル — ValidationException 複数エラー実運用検証。
23
+
24
+ ### Fixed
25
+ - `ValidationError.code` にスペースが含まれる場合 `ValueError` を発生させるよう修正 — `message` と `code` の混同を早期検出できるようになった (#300) (FT64)
26
+
27
+ ### Changed
28
+ - `ValidationError` と `ValidationException.single()` の docstring を改善 — `message` (人間可読) と `code` (機械可読 snake_case) の違いをキーワード引数付き例で明示 (#300) (FT64)
29
+
30
+ ### Added
31
+ - Field trial report: `docs/field-trials/2026-05-field-trial-64.md`
32
+
33
+ ---
34
+
8
35
  ## [1.8.16] — 2026-05-20
9
36
 
10
37
  FT63 フィールドトライアル — configure_problem_details 実運用検証 + PROBLEM_DETAILS_BASE_URL エクスポート修正。
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nene2-python
3
- Version: 1.8.16
3
+ Version: 1.8.18
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,61 @@
1
+ # FT63: configure_problem_details / PROBLEM_DETAILS_BASE_URL 実運用検証
2
+
3
+ **日付**: 2026-05-20
4
+ **テーマ**: Problem Details 設定 API (`configure_problem_details`, `PROBLEM_DETAILS_BASE_URL`) の実運用確認
5
+ **バージョン**: v1.8.15 → v1.8.16 (修正含む)
6
+ **FTディレクトリ**: `/home/xi/docker/nene2-python-FT/ft63-problem-details-config/`
7
+
8
+ ---
9
+
10
+ ## 概要
11
+
12
+ `nene2.http.configure_problem_details()` でプロジェクト全体の base_url を設定し、
13
+ RFC 9457 Problem Details レスポンスの `type` フィールドを自社 URL に変更する
14
+ パターンを検証した。
15
+
16
+ ---
17
+
18
+ ## 実装内容
19
+
20
+ - `configure_problem_details("https://api.example.com/errors/")`: アプリ起動時にグローバル設定
21
+ - `reset_problem_details()`: テスト間のリセット(autouse fixture)
22
+ - per-call `base_url` 引数がグローバル設定より優先されることを確認
23
+ - `ValidationException` 経由の 422 でも configured base_url が使われることを確認
24
+
25
+ ---
26
+
27
+ ## テスト結果
28
+
29
+ **7/7 passed** (v1.8.16 で修正後)
30
+
31
+ | テスト | 結果 |
32
+ |---|---|
33
+ | `test_default_base_url_in_type_field` | PASSED |
34
+ | `test_custom_base_url_applied_via_configure` | PASSED |
35
+ | `test_custom_base_url_affects_all_problem_details_in_app` | PASSED |
36
+ | `test_reset_problem_details_restores_default` | PASSED |
37
+ | `test_problem_details_structure_is_rfc9457_compliant` | PASSED |
38
+ | `test_validation_exception_uses_configured_base_url` | PASSED |
39
+ | `test_per_call_base_url_overrides_configured` | PASSED |
40
+
41
+ ---
42
+
43
+ ## Friction Points
44
+
45
+ ### FP-1: `PROBLEM_DETAILS_BASE_URL` が `nene2.http` からエクスポートされていない
46
+
47
+ **発生箇所**: `test_app.py` で `from nene2.http import PROBLEM_DETAILS_BASE_URL` を試みた際
48
+
49
+ **症状**: `ImportError: cannot import name 'PROBLEM_DETAILS_BASE_URL' from 'nene2.http'`
50
+
51
+ **影響**: テストコードでデフォルト URL を文字列のハードコードを避けられない。
52
+ 定数は `problem_details.py` に定義されているが `__init__.py` に含まれていなかった。
53
+
54
+ **修正**: `nene2.http.__init__` に `PROBLEM_DETAILS_BASE_URL` を追加 (Issue #296, v1.8.16)
55
+
56
+ ---
57
+
58
+ ## 結論
59
+
60
+ `configure_problem_details()` は実運用で問題なく使用できる。
61
+ `PROBLEM_DETAILS_BASE_URL` のエクスポート漏れが修正され、テストでの文字列ハードコードを避けられる。
@@ -0,0 +1,67 @@
1
+ # FT64: ValidationException 複数エラー実運用検証
2
+
3
+ **日付**: 2026-05-20
4
+ **テーマ**: 複数フィールドの `ValidationException` 集積と `ValidationError` 実運用確認
5
+ **バージョン**: v1.8.16 → v1.8.17 (修正含む)
6
+ **FTディレクトリ**: `/home/xi/docker/nene2-python-FT/ft64-multi-validation/`
7
+
8
+ ---
9
+
10
+ ## 概要
11
+
12
+ `ValidationException` を使って複数フィールドのバリデーションエラーを集積し、
13
+ 一度に 422 レスポンスとして返す実運用パターンを検証した。
14
+
15
+ ---
16
+
17
+ ## 実装内容
18
+
19
+ - `RegisterUserBody`: username・email・age の 3 フィールドを Pydantic で受け取る
20
+ - `_validate_registration()`: エラーを `list[ValidationError]` に集積して `ValidationException(errors)` を raise
21
+ - 複数エラーが一度に返されること、Problem Details 形式であることを確認
22
+
23
+ ---
24
+
25
+ ## テスト結果
26
+
27
+ **7/7 passed** (v1.8.17 で修正後)
28
+
29
+ | テスト | 結果 |
30
+ |---|---|
31
+ | `test_valid_registration_returns_201` | PASSED |
32
+ | `test_single_invalid_email_returns_422` | PASSED |
33
+ | `test_underage_user_returns_422` | PASSED |
34
+ | `test_multiple_errors_returned_at_once` | PASSED |
35
+ | `test_422_response_is_problem_details_format` | PASSED |
36
+ | `test_errors_include_field_message_code` | PASSED |
37
+ | `test_pydantic_validation_error_returns_422` | PASSED |
38
+
39
+ ---
40
+
41
+ ## Friction Points
42
+
43
+ ### FP-1: `ValidationError(field, message, code)` の引数順で `message` と `code` を混同
44
+
45
+ **発生箇所**: `app.py` で `ValidationError` を直接構築した際
46
+
47
+ **症状**:
48
+ ```python
49
+ # 意図: code="invalid_email"
50
+ ValidationError("email", "invalid_email", "メールアドレスの形式が正しくありません")
51
+ # → message="invalid_email", code="メールアドレスの形式が正しくありません" になってしまう
52
+ ```
53
+
54
+ **原因**: `(field, message, code)` の順序で、短い機械可読コードを先に書きたくなるが
55
+ `message` が先に来るため混同が起きやすい。
56
+
57
+ **修正**:
58
+ - `ValidationError.code` にスペースを含む場合 `ValueError` を早期発生させる (v1.8.17)
59
+ - docstring にキーワード引数付き例を追加してどちらが何かを明確化
60
+
61
+ ---
62
+
63
+ ## 結論
64
+
65
+ `ValidationException` の複数エラー集積パターンは実運用で問題なく使用できる。
66
+ `message` と `code` の混同を防ぐ `ValueError` と docstring 改善により、
67
+ 今後は早期にミスに気づけるようになった。
@@ -0,0 +1,54 @@
1
+ # FT65: DatabaseHealthCheck 実運用検証
2
+
3
+ **日付**: 2026-05-20
4
+ **テーマ**: DB接続ヘルスチェック (`DatabaseHealthCheck`) と `CompositeHealthCheck` の組み合わせ実運用確認
5
+ **バージョン**: v1.8.17
6
+ **FTディレクトリ**: `/home/xi/docker/nene2-python-FT/ft65-db-health/`
7
+
8
+ ---
9
+
10
+ ## 概要
11
+
12
+ `nene2.database.DatabaseHealthCheck` を `SqlAlchemyQueryExecutor` と組み合わせ、
13
+ `CompositeHealthCheck` に渡して FastAPI の `/health` エンドポイントで利用するパターンを検証した。
14
+ SQLite in-memory DB(正常)と存在しない DB(異常)の両方を確認。
15
+
16
+ ---
17
+
18
+ ## 実装内容
19
+
20
+ - `SqlAlchemyQueryExecutor(create_engine("sqlite:///:memory:"))`: SQLite in-memory DB
21
+ - `DatabaseHealthCheck(executor)`: `SELECT 1` で接続確認
22
+ - `CompositeHealthCheck([db_health])`: 集約して `/health` で返却
23
+ - 存在しない DB パスで 503 になることも確認
24
+
25
+ ---
26
+
27
+ ## テスト結果
28
+
29
+ **4/4 passed**
30
+
31
+ | テスト | 結果 |
32
+ |---|---|
33
+ | `test_healthy_db_returns_200` | PASSED |
34
+ | `test_broken_db_returns_503` | PASSED |
35
+ | `test_direct_database_health_check` | PASSED |
36
+ | `test_in_memory_db_composite_check` | PASSED |
37
+
38
+ ---
39
+
40
+ ## Friction Points
41
+
42
+ なし。`DatabaseHealthCheck` → `CompositeHealthCheck` → FastAPI の流れは直感的で問題なし。
43
+
44
+ **特筆点**:
45
+ - `DatabaseHealthCheck` の `SELECT 1` は軽量で本番運用に適している
46
+ - DB 接続失敗時は例外をキャッチして `status="error"` を返す設計で、
47
+ ヘルスエンドポイント自体が 500 になることがない
48
+
49
+ ---
50
+
51
+ ## 結論
52
+
53
+ `DatabaseHealthCheck` は実運用で問題なく使用できる。
54
+ `SqlAlchemyQueryExecutor` と `CompositeHealthCheck` の組み合わせが自然に機能する。
@@ -0,0 +1,52 @@
1
+ # FT66: AppSettings 実運用検証
2
+
3
+ **日付**: 2026-05-20
4
+ **テーマ**: 型付き設定オブジェクト (`AppSettings`) の実運用確認
5
+ **バージョン**: v1.8.17
6
+ **FTディレクトリ**: `/home/xi/docker/nene2-python-FT/ft66-app-settings/`
7
+
8
+ ---
9
+
10
+ ## 概要
11
+
12
+ `nene2.config.AppSettings` を直接インスタンス化し、環境変数によるオーバーライド・
13
+ バリデーション・`db_url` プロパティ・`SecretStr` の動作を検証した。
14
+
15
+ ---
16
+
17
+ ## 実装内容
18
+
19
+ - `AppSettings()`: デフォルト値確認
20
+ - `monkeypatch.setenv()`: 環境変数でのオーバーライド
21
+ - 不正値(`APP_ENV=staging`、`LOG_LEVEL=VERBOSE`)でのバリデーション確認
22
+ - `db_url` プロパティで SQLAlchemy URL 生成を確認
23
+ - `CORS_ORIGINS` の JSON リスト形式パース確認
24
+
25
+ ---
26
+
27
+ ## テスト結果
28
+
29
+ **10/10 passed**
30
+
31
+ ---
32
+
33
+ ## Friction Points
34
+
35
+ ### FP-1 (軽微): `str(SecretStr(""))` が `""` を返す
36
+
37
+ **発生箇所**: `assert str(settings.db_password) != ""` テストが失敗
38
+
39
+ **症状**: 空の `SecretStr("")` を `str()` に渡すと `""` が返る(マスクされない)。
40
+ 非空 `SecretStr("secret")` は `**********` が返る。
41
+
42
+ **原因**: Pydantic の設計上の挙動で、空文字列は空文字列のまま。
43
+ `repr()` では `SecretStr('')` と表示される。
44
+
45
+ **対応**: テストを `get_secret_value() == ""` で修正。フレームワーク側は変更不要。
46
+
47
+ ---
48
+
49
+ ## 結論
50
+
51
+ `AppSettings` は実運用で問題なく使用できる。
52
+ 環境変数からの自動パース(bool・int・list[str] の JSON 形式)が便利。
@@ -0,0 +1,75 @@
1
+ # FT67: SqlAlchemyTransactionManager 実運用検証
2
+
3
+ **日付**: 2026-05-20
4
+ **テーマ**: トランザクション管理 (`SqlAlchemyTransactionManager`) の実運用確認
5
+ **バージョン**: v1.8.17 → v1.8.18 (ドキュメント追加)
6
+ **FTディレクトリ**: `/home/xi/docker/nene2-python-FT/ft67-transaction-manager/`
7
+
8
+ ---
9
+
10
+ ## 概要
11
+
12
+ `nene2.database.SqlAlchemyTransactionManager` を使って口座振替アプリを実装し、
13
+ `transactional()` コールバック API・ロールバック動作・コミット確認を検証した。
14
+
15
+ ---
16
+
17
+ ## 実装内容
18
+
19
+ - SQLite in-memory DB に `accounts` テーブルを作成
20
+ - `transactional(callback)`: 振替処理を 1 トランザクションで実行
21
+ - 残高不足時は `ValueError` を raise → ロールバック
22
+ - GET `/accounts/{name}` と POST `/transfers` エンドポイント
23
+
24
+ ---
25
+
26
+ ## テスト結果
27
+
28
+ **7/7 passed** (StaticPool 修正後)
29
+
30
+ | テスト | 結果 |
31
+ |---|---|
32
+ | `test_get_existing_account` | PASSED |
33
+ | `test_get_nonexistent_account_returns_404` | PASSED |
34
+ | `test_successful_transfer` | PASSED |
35
+ | `test_insufficient_balance_returns_422` | PASSED |
36
+ | `test_transaction_rollback_on_error` | PASSED |
37
+ | `test_transfer_to_nonexistent_account` | PASSED |
38
+ | `test_transactional_commits_on_success` | PASSED |
39
+
40
+ ---
41
+
42
+ ## Friction Points
43
+
44
+ ### FP-1: SQLite `:memory:` と `SqlAlchemyQueryExecutor` の接続分離問題
45
+
46
+ **発生箇所**: `setup_db()` でテーブル作成後、`executor.fetch_one()` が `no such table: accounts` エラー
47
+
48
+ **症状**:
49
+ ```
50
+ DatabaseConnectionException: (sqlite3.OperationalError) no such table: accounts
51
+ ```
52
+
53
+ **原因**: SQLAlchemy のデフォルトコネクションプールでは `sqlite:///:memory:` への接続ごとに
54
+ 新しいインメモリDBが生成される。`setup_db()` と `executor.fetch_one()` が別DBを参照する。
55
+
56
+ **修正**:
57
+ ```python
58
+ from sqlalchemy.pool import StaticPool
59
+
60
+ engine = create_engine(
61
+ "sqlite:///:memory:",
62
+ connect_args={"check_same_thread": False},
63
+ poolclass=StaticPool,
64
+ )
65
+ ```
66
+
67
+ `SqlAlchemyQueryExecutor` の docstring に注意書きを追加 (Issue #305, v1.8.18)。
68
+
69
+ ---
70
+
71
+ ## 結論
72
+
73
+ `SqlAlchemyTransactionManager.transactional()` は実運用で問題なく使用できる。
74
+ コールバック内の例外でロールバックが正しく機能することも確認。
75
+ SQLite in-memory DB 使用時の `StaticPool` 要件はドキュメント化済み。
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nene2-python"
3
- version = "1.8.16"
3
+ version = "1.8.18"
4
4
  description = "NENE2 Python — minimal API framework following NENE2's design philosophy"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
@@ -14,7 +14,23 @@ from .interfaces import DatabaseQueryExecutorInterface, DatabaseTransactionManag
14
14
 
15
15
 
16
16
  class SqlAlchemyQueryExecutor(DatabaseQueryExecutorInterface):
17
- """Execute queries using SQLAlchemy Core (connection-per-call, no ORM)."""
17
+ """Execute queries using SQLAlchemy Core (connection-per-call, no ORM).
18
+
19
+ .. note:: SQLite in-memory databases
20
+
21
+ When using ``sqlite:///:memory:`` in tests, each new connection receives
22
+ a separate empty database. To share one in-memory DB across all
23
+ connections (including ``setup_db`` / schema creation) use
24
+ ``StaticPool``::
25
+
26
+ from sqlalchemy.pool import StaticPool
27
+
28
+ engine = create_engine(
29
+ "sqlite:///:memory:",
30
+ connect_args={"check_same_thread": False},
31
+ poolclass=StaticPool,
32
+ )
33
+ """
18
34
 
19
35
  def __init__(self, engine: Engine) -> None:
20
36
  self._engine = engine
@@ -0,0 +1,101 @@
1
+ """Validation exceptions.
2
+
3
+ Equivalent to PHP Nene2\\Validation\\ValidationException and ValidationError.
4
+ Raised by handlers or use-cases to produce a 422 validation-failed Problem Details response.
5
+ """
6
+
7
+ from dataclasses import dataclass
8
+
9
+
10
+ @dataclass(frozen=True, slots=True)
11
+ class ValidationError:
12
+ """A single field-level validation failure.
13
+
14
+ Args:
15
+ field: The input field that failed (e.g. ``"email"``).
16
+ message: Human-readable description shown to the user
17
+ (e.g. ``"メールアドレスの形式が正しくありません"``).
18
+ code: Machine-readable identifier used by API clients to handle
19
+ specific errors programmatically (e.g. ``"invalid_email"``).
20
+ Must not contain spaces; use ``snake_case``.
21
+
22
+ Example::
23
+
24
+ ValidationError(
25
+ field="email",
26
+ message="メールアドレスの形式が正しくありません",
27
+ code="invalid_email",
28
+ )
29
+ """
30
+
31
+ field: str
32
+ message: str
33
+ code: str
34
+
35
+ def __post_init__(self) -> None:
36
+ for attr in ("field", "message", "code"):
37
+ if not getattr(self, attr):
38
+ raise ValueError(f"ValidationError.{attr} must not be empty")
39
+ if " " in self.code:
40
+ raise ValueError(
41
+ f"ValidationError.code must not contain spaces (got {self.code!r}). "
42
+ "Use snake_case, e.g. 'invalid_email'."
43
+ )
44
+
45
+ def to_dict(self) -> dict[str, str]:
46
+ return {"field": self.field, "message": self.message, "code": self.code}
47
+
48
+
49
+ class ValidationException(Exception):
50
+ """Raised when one or more validation rules fail.
51
+
52
+ ErrorHandlerMiddleware maps this to a 422 validation-failed Problem Details response.
53
+
54
+ For a single error, use the convenience method::
55
+
56
+ raise ValidationException.single(
57
+ field="email",
58
+ message="メールアドレスの形式が正しくありません",
59
+ code="invalid_email",
60
+ )
61
+
62
+ For multiple errors accumulated during validation::
63
+
64
+ errors: list[ValidationError] = []
65
+ if "@" not in email:
66
+ errors.append(
67
+ ValidationError(
68
+ field="email",
69
+ message="メールアドレスの形式が正しくありません",
70
+ code="invalid_email",
71
+ )
72
+ )
73
+ if age < 18:
74
+ errors.append(
75
+ ValidationError(
76
+ field="age",
77
+ message="18歳以上である必要があります",
78
+ code="too_young",
79
+ )
80
+ )
81
+ if errors:
82
+ raise ValidationException(errors)
83
+
84
+ Note: ``message`` is a human-readable string; ``code`` is a machine-readable
85
+ ``snake_case`` identifier (e.g. ``"invalid_email"``, not ``"Invalid email"``).
86
+ """
87
+
88
+ def __init__(self, errors: list[ValidationError]) -> None:
89
+ super().__init__("Validation failed")
90
+ self.errors = errors
91
+
92
+ @classmethod
93
+ def single(cls, field: str, message: str, code: str) -> "ValidationException":
94
+ """Convenience constructor for a single validation error.
95
+
96
+ Args:
97
+ field: The input field that failed (e.g. ``"email"``).
98
+ message: Human-readable description (e.g. ``"Invalid email address"``).
99
+ code: Machine-readable ``snake_case`` identifier (e.g. ``"invalid_email"``).
100
+ """
101
+ return cls([ValidationError(field, message, code)])
@@ -44,3 +44,8 @@ def test_validation_exception_single() -> None:
44
44
  def test_validation_exception_single_is_validation_exception() -> None:
45
45
  exc = ValidationException.single("f", "m", "c")
46
46
  assert isinstance(exc, ValidationException)
47
+
48
+
49
+ def test_validation_error_code_with_spaces_raises_value_error() -> None:
50
+ with pytest.raises(ValueError, match="must not contain spaces"):
51
+ ValidationError(field="email", message="Invalid email", code="invalid email")
@@ -925,7 +925,7 @@ wheels = [
925
925
 
926
926
  [[package]]
927
927
  name = "nene2-python"
928
- version = "1.8.16"
928
+ version = "1.8.18"
929
929
  source = { editable = "." }
930
930
  dependencies = [
931
931
  { name = "alembic" },
@@ -1,52 +0,0 @@
1
- """Validation exceptions.
2
-
3
- Equivalent to PHP Nene2\\Validation\\ValidationException and ValidationError.
4
- Raised by handlers or use-cases to produce a 422 validation-failed Problem Details response.
5
- """
6
-
7
- from dataclasses import dataclass
8
-
9
-
10
- @dataclass(frozen=True, slots=True)
11
- class ValidationError:
12
- """A single field-level validation failure."""
13
-
14
- field: str
15
- message: str
16
- code: str
17
-
18
- def __post_init__(self) -> None:
19
- for attr in ("field", "message", "code"):
20
- if not getattr(self, attr):
21
- raise ValueError(f"ValidationError.{attr} must not be empty")
22
-
23
- def to_dict(self) -> dict[str, str]:
24
- return {"field": self.field, "message": self.message, "code": self.code}
25
-
26
-
27
- class ValidationException(Exception):
28
- """Raised when one or more validation rules fail.
29
-
30
- ErrorHandlerMiddleware maps this to a 422 validation-failed Problem Details response.
31
-
32
- For a single error, use the convenience method::
33
-
34
- raise ValidationException.single("email", "invalid", "invalid_email")
35
-
36
- For multiple errors accumulated during validation::
37
-
38
- errors = []
39
- if not valid_email:
40
- errors.append(ValidationError("email", "invalid", "invalid_email"))
41
- if errors:
42
- raise ValidationException(errors)
43
- """
44
-
45
- def __init__(self, errors: list[ValidationError]) -> None:
46
- super().__init__("Validation failed")
47
- self.errors = errors
48
-
49
- @classmethod
50
- def single(cls, field: str, message: str, code: str) -> "ValidationException":
51
- """Convenience constructor for a single validation error."""
52
- return cls([ValidationError(field, message, code)])
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes