nene2-python 1.8.163__tar.gz → 1.8.164__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 (531) hide show
  1. {nene2_python-1.8.163 → nene2_python-1.8.164}/.github/workflows/ci.yml +48 -0
  2. {nene2_python-1.8.163 → nene2_python-1.8.164}/CHANGELOG.md +21 -0
  3. {nene2_python-1.8.163 → nene2_python-1.8.164}/PKG-INFO +1 -1
  4. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/release-and-publish.md +11 -10
  5. nene2_python-1.8.164/docs/how-to/run-integration-tests.md +51 -0
  6. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/ja/how-to/release-and-publish.md +9 -10
  7. nene2_python-1.8.164/docs/ja/how-to/run-integration-tests.md +48 -0
  8. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/roadmap.md +4 -4
  9. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/todo/current.md +23 -22
  10. {nene2_python-1.8.163 → nene2_python-1.8.164}/pyproject.toml +2 -1
  11. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/database/sqlalchemy_executor.py +24 -4
  12. nene2_python-1.8.164/tests/integration/conftest.py +58 -0
  13. nene2_python-1.8.164/tests/integration/test_repository_real_db.py +65 -0
  14. nene2_python-1.8.164/tests/scripts/__init__.py +0 -0
  15. {nene2_python-1.8.163 → nene2_python-1.8.164}/uv.lock +12 -1
  16. {nene2_python-1.8.163 → nene2_python-1.8.164}/.env.example +0 -0
  17. {nene2_python-1.8.163 → nene2_python-1.8.164}/.github/workflows/docs.yml +0 -0
  18. {nene2_python-1.8.163 → nene2_python-1.8.164}/.github/workflows/publish.yml +0 -0
  19. {nene2_python-1.8.163 → nene2_python-1.8.164}/.gitignore +0 -0
  20. {nene2_python-1.8.163 → nene2_python-1.8.164}/.vitepress/config.mts +0 -0
  21. {nene2_python-1.8.163 → nene2_python-1.8.164}/.vitepress/theme/custom.css +0 -0
  22. {nene2_python-1.8.163 → nene2_python-1.8.164}/.vitepress/theme/index.ts +0 -0
  23. {nene2_python-1.8.163 → nene2_python-1.8.164}/AGENTS.md +0 -0
  24. {nene2_python-1.8.163 → nene2_python-1.8.164}/CLAUDE.md +0 -0
  25. {nene2_python-1.8.163 → nene2_python-1.8.164}/Dockerfile +0 -0
  26. {nene2_python-1.8.163 → nene2_python-1.8.164}/LICENSE +0 -0
  27. {nene2_python-1.8.163 → nene2_python-1.8.164}/README.md +0 -0
  28. {nene2_python-1.8.163 → nene2_python-1.8.164}/alembic/README +0 -0
  29. {nene2_python-1.8.163 → nene2_python-1.8.164}/alembic/env.py +0 -0
  30. {nene2_python-1.8.163 → nene2_python-1.8.164}/alembic/script.py.mako +0 -0
  31. {nene2_python-1.8.163 → nene2_python-1.8.164}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
  32. {nene2_python-1.8.163 → nene2_python-1.8.164}/alembic.ini +0 -0
  33. {nene2_python-1.8.163 → nene2_python-1.8.164}/compose.yaml +0 -0
  34. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/adr/0001-toolchain.md +0 -0
  35. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/adr/0002-clean-architecture.md +0 -0
  36. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/adr/0003-security-first.md +0 -0
  37. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/adr/0004-ai-first-design.md +0 -0
  38. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/adr/0005-logging.md +0 -0
  39. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/adr/0006-rate-limiting.md +0 -0
  40. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/adr/0009-mcp-design.md +0 -0
  41. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/adr/0010-async-use-case.md +0 -0
  42. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
  43. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/de/index.md +0 -0
  44. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/de/tutorials/getting-started.md +0 -0
  45. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/explanation/architecture.md +0 -0
  46. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/explanation/design-philosophy.md +0 -0
  47. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/explanation/field-trial-methodology.md +0 -0
  48. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-1.md +0 -0
  49. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-10.md +0 -0
  50. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-100.md +0 -0
  51. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-101.md +0 -0
  52. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-102.md +0 -0
  53. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-103.md +0 -0
  54. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-104.md +0 -0
  55. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-105.md +0 -0
  56. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-106.md +0 -0
  57. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-107.md +0 -0
  58. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-108.md +0 -0
  59. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-109.md +0 -0
  60. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-11.md +0 -0
  61. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-110.md +0 -0
  62. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-111.md +0 -0
  63. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-112.md +0 -0
  64. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-113.md +0 -0
  65. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-114.md +0 -0
  66. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-115.md +0 -0
  67. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-116.md +0 -0
  68. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-117.md +0 -0
  69. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-118.md +0 -0
  70. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-119.md +0 -0
  71. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-12.md +0 -0
  72. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-120.md +0 -0
  73. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-121.md +0 -0
  74. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-122.md +0 -0
  75. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-123.md +0 -0
  76. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-124.md +0 -0
  77. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-125.md +0 -0
  78. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-126.md +0 -0
  79. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-127.md +0 -0
  80. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-128.md +0 -0
  81. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-129.md +0 -0
  82. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-13.md +0 -0
  83. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-130.md +0 -0
  84. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-131.md +0 -0
  85. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-132.md +0 -0
  86. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-133.md +0 -0
  87. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-134.md +0 -0
  88. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-135.md +0 -0
  89. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-136.md +0 -0
  90. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-137.md +0 -0
  91. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-138.md +0 -0
  92. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-139.md +0 -0
  93. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-14.md +0 -0
  94. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-140.md +0 -0
  95. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-141.md +0 -0
  96. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-142.md +0 -0
  97. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-143.md +0 -0
  98. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-144.md +0 -0
  99. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-145.md +0 -0
  100. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-146.md +0 -0
  101. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-147.md +0 -0
  102. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-148.md +0 -0
  103. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-149.md +0 -0
  104. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-15.md +0 -0
  105. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-150.md +0 -0
  106. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-151.md +0 -0
  107. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-152.md +0 -0
  108. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-153.md +0 -0
  109. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-154.md +0 -0
  110. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-155.md +0 -0
  111. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-156.md +0 -0
  112. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-157.md +0 -0
  113. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-158.md +0 -0
  114. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-159.md +0 -0
  115. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-16.md +0 -0
  116. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-160.md +0 -0
  117. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-161.md +0 -0
  118. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-162.md +0 -0
  119. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-163.md +0 -0
  120. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-164.md +0 -0
  121. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-165.md +0 -0
  122. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-166.md +0 -0
  123. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-167.md +0 -0
  124. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-168.md +0 -0
  125. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-169.md +0 -0
  126. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-17.md +0 -0
  127. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-170.md +0 -0
  128. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-171.md +0 -0
  129. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-172.md +0 -0
  130. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-173.md +0 -0
  131. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-174.md +0 -0
  132. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-175.md +0 -0
  133. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-176.md +0 -0
  134. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-177.md +0 -0
  135. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-178.md +0 -0
  136. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-179.md +0 -0
  137. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-18.md +0 -0
  138. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-180.md +0 -0
  139. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-181.md +0 -0
  140. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-182.md +0 -0
  141. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-183.md +0 -0
  142. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-184.md +0 -0
  143. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-185.md +0 -0
  144. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-186.md +0 -0
  145. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-187.md +0 -0
  146. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-188.md +0 -0
  147. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-189.md +0 -0
  148. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-19.md +0 -0
  149. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-190.md +0 -0
  150. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-191.md +0 -0
  151. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-192.md +0 -0
  152. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-193.md +0 -0
  153. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-194.md +0 -0
  154. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-195.md +0 -0
  155. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-196.md +0 -0
  156. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-197.md +0 -0
  157. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-198.md +0 -0
  158. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-199.md +0 -0
  159. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-2.md +0 -0
  160. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-20.md +0 -0
  161. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-200.md +0 -0
  162. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-201.md +0 -0
  163. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-202.md +0 -0
  164. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-203.md +0 -0
  165. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-204.md +0 -0
  166. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-205.md +0 -0
  167. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-206.md +0 -0
  168. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-207.md +0 -0
  169. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-208.md +0 -0
  170. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-209.md +0 -0
  171. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-21.md +0 -0
  172. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-210.md +0 -0
  173. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-211.md +0 -0
  174. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-212.md +0 -0
  175. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-213.md +0 -0
  176. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-214.md +0 -0
  177. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-215.md +0 -0
  178. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-216.md +0 -0
  179. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-217.md +0 -0
  180. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-218.md +0 -0
  181. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-219.md +0 -0
  182. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-22.md +0 -0
  183. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-220.md +0 -0
  184. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-221.md +0 -0
  185. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-222.md +0 -0
  186. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-223.md +0 -0
  187. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-224.md +0 -0
  188. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-225.md +0 -0
  189. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-226.md +0 -0
  190. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-227.md +0 -0
  191. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-228.md +0 -0
  192. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-229.md +0 -0
  193. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-23.md +0 -0
  194. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-230.md +0 -0
  195. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-231.md +0 -0
  196. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-232.md +0 -0
  197. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-233.md +0 -0
  198. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-234.md +0 -0
  199. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-235.md +0 -0
  200. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-236.md +0 -0
  201. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-237.md +0 -0
  202. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-238.md +0 -0
  203. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-239.md +0 -0
  204. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-24.md +0 -0
  205. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-240.md +0 -0
  206. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-241.md +0 -0
  207. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-242.md +0 -0
  208. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-243.md +0 -0
  209. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-244.md +0 -0
  210. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-245.md +0 -0
  211. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-246.md +0 -0
  212. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-247.md +0 -0
  213. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-248.md +0 -0
  214. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-249.md +0 -0
  215. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-25.md +0 -0
  216. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-250.md +0 -0
  217. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-251.md +0 -0
  218. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-252.md +0 -0
  219. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-253.md +0 -0
  220. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-254.md +0 -0
  221. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-255.md +0 -0
  222. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-256.md +0 -0
  223. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-257.md +0 -0
  224. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-258.md +0 -0
  225. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-259.md +0 -0
  226. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-26.md +0 -0
  227. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-260.md +0 -0
  228. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-261.md +0 -0
  229. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-262.md +0 -0
  230. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-263.md +0 -0
  231. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-264.md +0 -0
  232. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-265.md +0 -0
  233. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-266.md +0 -0
  234. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-267.md +0 -0
  235. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-268.md +0 -0
  236. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-269.md +0 -0
  237. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-27.md +0 -0
  238. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-270.md +0 -0
  239. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-271.md +0 -0
  240. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-272.md +0 -0
  241. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-273.md +0 -0
  242. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-274.md +0 -0
  243. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-275.md +0 -0
  244. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-276.md +0 -0
  245. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-277.md +0 -0
  246. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-278.md +0 -0
  247. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-279.md +0 -0
  248. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-28.md +0 -0
  249. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-280.md +0 -0
  250. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-281.md +0 -0
  251. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-282.md +0 -0
  252. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-29.md +0 -0
  253. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-3.md +0 -0
  254. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-30.md +0 -0
  255. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-31.md +0 -0
  256. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-32.md +0 -0
  257. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-33.md +0 -0
  258. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-34.md +0 -0
  259. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-35.md +0 -0
  260. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-36.md +0 -0
  261. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-37.md +0 -0
  262. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-38.md +0 -0
  263. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-39.md +0 -0
  264. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-4.md +0 -0
  265. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-40.md +0 -0
  266. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-41.md +0 -0
  267. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-42.md +0 -0
  268. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-43.md +0 -0
  269. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-44.md +0 -0
  270. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-45.md +0 -0
  271. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-46.md +0 -0
  272. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-47.md +0 -0
  273. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-48.md +0 -0
  274. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-49.md +0 -0
  275. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-5.md +0 -0
  276. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-50.md +0 -0
  277. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-51.md +0 -0
  278. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-52.md +0 -0
  279. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-53.md +0 -0
  280. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-54.md +0 -0
  281. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-55.md +0 -0
  282. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-56.md +0 -0
  283. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-57.md +0 -0
  284. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-58.md +0 -0
  285. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-59.md +0 -0
  286. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-6.md +0 -0
  287. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-60.md +0 -0
  288. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-61.md +0 -0
  289. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-62.md +0 -0
  290. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-63.md +0 -0
  291. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-64.md +0 -0
  292. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-65.md +0 -0
  293. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-66.md +0 -0
  294. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-67.md +0 -0
  295. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-68.md +0 -0
  296. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-69.md +0 -0
  297. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-7.md +0 -0
  298. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-70.md +0 -0
  299. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-71.md +0 -0
  300. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-72.md +0 -0
  301. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-73.md +0 -0
  302. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-74.md +0 -0
  303. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-75.md +0 -0
  304. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-76.md +0 -0
  305. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-77.md +0 -0
  306. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-78.md +0 -0
  307. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-79.md +0 -0
  308. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-8.md +0 -0
  309. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-80.md +0 -0
  310. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-81.md +0 -0
  311. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-82.md +0 -0
  312. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-83.md +0 -0
  313. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-84.md +0 -0
  314. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-85.md +0 -0
  315. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-86.md +0 -0
  316. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-87.md +0 -0
  317. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-88.md +0 -0
  318. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-89.md +0 -0
  319. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-9.md +0 -0
  320. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-90.md +0 -0
  321. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-91.md +0 -0
  322. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-92.md +0 -0
  323. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-93.md +0 -0
  324. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-94.md +0 -0
  325. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-95.md +0 -0
  326. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-96.md +0 -0
  327. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-97.md +0 -0
  328. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-98.md +0 -0
  329. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-99.md +0 -0
  330. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/INDEX.md +0 -0
  331. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/fr/index.md +0 -0
  332. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/fr/tutorials/getting-started.md +0 -0
  333. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/add-new-domain.md +0 -0
  334. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/api-versioning.md +0 -0
  335. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/async-use-case.md +0 -0
  336. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/background-tasks.md +0 -0
  337. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/concurrency-patterns.md +0 -0
  338. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/configure-auth.md +0 -0
  339. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/cors.md +0 -0
  340. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/custom-auth-middleware.md +0 -0
  341. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/decimal-unicode-input.md +0 -0
  342. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/dependency-injection.md +0 -0
  343. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/domain-events.md +0 -0
  344. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/email-address-parsing.md +0 -0
  345. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/file-upload.md +0 -0
  346. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/lifespan-and-app-state.md +0 -0
  347. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/middleware-stack.md +0 -0
  348. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/new-project.md +0 -0
  349. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/problem-details.md +0 -0
  350. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/response-patterns.md +0 -0
  351. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/run-tests.md +0 -0
  352. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/soft-delete.md +0 -0
  353. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/sqlalchemy-repository.md +0 -0
  354. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/streaming.md +0 -0
  355. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/structured-logging.md +0 -0
  356. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/validation.md +0 -0
  357. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/webhook.md +0 -0
  358. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/howto/mcp-setup.md +0 -0
  359. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/index.md +0 -0
  360. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/ja/explanation/architecture.md +0 -0
  361. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/ja/explanation/design-philosophy.md +0 -0
  362. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/ja/explanation/field-trial-methodology.md +0 -0
  363. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/ja/how-to/add-new-domain.md +0 -0
  364. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/ja/how-to/configure-auth.md +0 -0
  365. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/ja/how-to/new-project.md +0 -0
  366. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/ja/how-to/run-tests.md +0 -0
  367. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
  368. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/ja/howto/mcp-setup.md +0 -0
  369. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/ja/index.md +0 -0
  370. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/ja/reference/api.md +0 -0
  371. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/ja/reference/configuration.md +0 -0
  372. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/ja/reference/framework-modules.md +0 -0
  373. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/ja/tutorials/first-domain.md +0 -0
  374. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/ja/tutorials/getting-started.md +0 -0
  375. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/pt-br/index.md +0 -0
  376. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/pt-br/tutorials/getting-started.md +0 -0
  377. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/reference/api.md +0 -0
  378. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/reference/configuration.md +0 -0
  379. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/reference/framework-modules.md +0 -0
  380. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/review/2026-05-22.md +0 -0
  381. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/review/2026-05-23.md +0 -0
  382. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/templates/field-trial-report.md +0 -0
  383. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/tutorials/first-domain.md +0 -0
  384. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/tutorials/getting-started.md +0 -0
  385. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/zh/index.md +0 -0
  386. {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/zh/tutorials/getting-started.md +0 -0
  387. {nene2_python-1.8.163 → nene2_python-1.8.164}/package-lock.json +0 -0
  388. {nene2_python-1.8.163 → nene2_python-1.8.164}/package.json +0 -0
  389. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/__init__.py +0 -0
  390. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/__main__.py +0 -0
  391. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/app.py +0 -0
  392. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/comment/__init__.py +0 -0
  393. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/comment/entity.py +0 -0
  394. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/comment/exceptions.py +0 -0
  395. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/comment/handler.py +0 -0
  396. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/comment/repository.py +0 -0
  397. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/comment/sqlalchemy_repository.py +0 -0
  398. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/comment/use_case.py +0 -0
  399. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/mcp.py +0 -0
  400. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/note/__init__.py +0 -0
  401. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/note/async_use_case.py +0 -0
  402. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/note/entity.py +0 -0
  403. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/note/exceptions.py +0 -0
  404. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/note/handler.py +0 -0
  405. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/note/repository.py +0 -0
  406. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/note/sqlalchemy_repository.py +0 -0
  407. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/note/use_case.py +0 -0
  408. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/schema.py +0 -0
  409. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/tag/__init__.py +0 -0
  410. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/tag/entity.py +0 -0
  411. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/tag/exceptions.py +0 -0
  412. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/tag/handler.py +0 -0
  413. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/tag/repository.py +0 -0
  414. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/tag/sqlalchemy_repository.py +0 -0
  415. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/tag/use_case.py +0 -0
  416. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/__init__.py +0 -0
  417. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/auth/__init__.py +0 -0
  418. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/auth/api_key.py +0 -0
  419. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/auth/bearer_token.py +0 -0
  420. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/auth/composite.py +0 -0
  421. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/auth/deps.py +0 -0
  422. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/auth/exceptions.py +0 -0
  423. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/auth/interfaces.py +0 -0
  424. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/auth/local_bearer_jwt.py +0 -0
  425. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/auth/local_issuer.py +0 -0
  426. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/auth/local_verifier.py +0 -0
  427. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/cache/__init__.py +0 -0
  428. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/cache/ttl.py +0 -0
  429. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/config/__init__.py +0 -0
  430. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/config/settings.py +0 -0
  431. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/database/__init__.py +0 -0
  432. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/database/exceptions.py +0 -0
  433. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/database/health.py +0 -0
  434. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/database/interfaces.py +0 -0
  435. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/database/utils.py +0 -0
  436. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/http/__init__.py +0 -0
  437. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/http/context.py +0 -0
  438. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/http/etag.py +0 -0
  439. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/http/health.py +0 -0
  440. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/http/pagination.py +0 -0
  441. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/http/problem_details.py +0 -0
  442. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/http/query.py +0 -0
  443. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/log/__init__.py +0 -0
  444. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/log/setup.py +0 -0
  445. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/mcp/__init__.py +0 -0
  446. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/mcp/http_client.py +0 -0
  447. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/mcp/server.py +0 -0
  448. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/middleware/__init__.py +0 -0
  449. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/middleware/domain_exception.py +0 -0
  450. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/middleware/error_handler.py +0 -0
  451. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/middleware/request_id.py +0 -0
  452. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/middleware/request_logging.py +0 -0
  453. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/middleware/request_size_limit.py +0 -0
  454. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/middleware/security_headers.py +0 -0
  455. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/middleware/setup.py +0 -0
  456. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/middleware/throttle.py +0 -0
  457. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/py.typed +0 -0
  458. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/security/__init__.py +0 -0
  459. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/security/webhook.py +0 -0
  460. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/use_case/__init__.py +0 -0
  461. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/use_case/protocols.py +0 -0
  462. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/validation/__init__.py +0 -0
  463. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/validation/exceptions.py +0 -0
  464. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/scripts/__init__.py +0 -0
  465. {nene2_python-1.8.163 → nene2_python-1.8.164}/src/scripts/export_openapi.py +0 -0
  466. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/__init__.py +0 -0
  467. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/conftest.py +0 -0
  468. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/__init__.py +0 -0
  469. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/comment/__init__.py +0 -0
  470. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/comment/test_comment_http.py +0 -0
  471. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/comment/test_comment_repository.py +0 -0
  472. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/comment/test_comment_use_case.py +0 -0
  473. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/conftest.py +0 -0
  474. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/note/__init__.py +0 -0
  475. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/note/test_async_note_use_case.py +0 -0
  476. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/note/test_list_notes.py +0 -0
  477. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/note/test_note_repository.py +0 -0
  478. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/tag/__init__.py +0 -0
  479. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/tag/test_tag_repository.py +0 -0
  480. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/tag/test_tags.py +0 -0
  481. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/test_cors.py +0 -0
  482. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/test_examples_protected.py +0 -0
  483. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/test_local_throttle_default.py +0 -0
  484. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/test_mcp.py +0 -0
  485. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/test_system_routes.py +0 -0
  486. {nene2_python-1.8.163/tests/nene2 → nene2_python-1.8.164/tests/integration}/__init__.py +0 -0
  487. {nene2_python-1.8.163/tests/nene2/auth → nene2_python-1.8.164/tests/nene2}/__init__.py +0 -0
  488. {nene2_python-1.8.163/tests/nene2/cache → nene2_python-1.8.164/tests/nene2/auth}/__init__.py +0 -0
  489. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/auth/test_api_key.py +0 -0
  490. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/auth/test_bearer_token.py +0 -0
  491. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/auth/test_composite_auth.py +0 -0
  492. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/auth/test_local_bearer_jwt.py +0 -0
  493. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/auth/test_local_issuer.py +0 -0
  494. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/auth/test_make_require_auth.py +0 -0
  495. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/auth/test_token_issuer.py +0 -0
  496. {nene2_python-1.8.163/tests/nene2/config → nene2_python-1.8.164/tests/nene2/cache}/__init__.py +0 -0
  497. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/cache/test_ttl.py +0 -0
  498. {nene2_python-1.8.163/tests/nene2/database → nene2_python-1.8.164/tests/nene2/config}/__init__.py +0 -0
  499. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/config/test_settings.py +0 -0
  500. {nene2_python-1.8.163/tests/nene2/http → nene2_python-1.8.164/tests/nene2/database}/__init__.py +0 -0
  501. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/database/test_transaction.py +0 -0
  502. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/database/test_utils.py +0 -0
  503. {nene2_python-1.8.163/tests/nene2/log → nene2_python-1.8.164/tests/nene2/http}/__init__.py +0 -0
  504. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/http/test_context.py +0 -0
  505. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/http/test_etag.py +0 -0
  506. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/http/test_health.py +0 -0
  507. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/http/test_pagination.py +0 -0
  508. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/http/test_problem_details.py +0 -0
  509. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/http/test_query.py +0 -0
  510. {nene2_python-1.8.163/tests/nene2/mcp → nene2_python-1.8.164/tests/nene2/log}/__init__.py +0 -0
  511. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/log/test_setup.py +0 -0
  512. {nene2_python-1.8.163/tests/nene2/middleware → nene2_python-1.8.164/tests/nene2/mcp}/__init__.py +0 -0
  513. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/mcp/test_http_client.py +0 -0
  514. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/mcp/test_server.py +0 -0
  515. {nene2_python-1.8.163/tests/nene2/security → nene2_python-1.8.164/tests/nene2/middleware}/__init__.py +0 -0
  516. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/middleware/test_error_handler.py +0 -0
  517. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/middleware/test_request_id.py +0 -0
  518. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/middleware/test_request_logging.py +0 -0
  519. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/middleware/test_request_size_limit.py +0 -0
  520. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/middleware/test_security_headers.py +0 -0
  521. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/middleware/test_setup_middlewares.py +0 -0
  522. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
  523. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/middleware/test_throttle.py +0 -0
  524. {nene2_python-1.8.163/tests/nene2/use_case → nene2_python-1.8.164/tests/nene2/security}/__init__.py +0 -0
  525. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/security/test_webhook.py +0 -0
  526. {nene2_python-1.8.163/tests/nene2/validation → nene2_python-1.8.164/tests/nene2/use_case}/__init__.py +0 -0
  527. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/use_case/test_protocols.py +0 -0
  528. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/use_case/test_run_in_threadpool.py +0 -0
  529. {nene2_python-1.8.163/tests/scripts → nene2_python-1.8.164/tests/nene2/validation}/__init__.py +0 -0
  530. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/validation/test_exceptions.py +0 -0
  531. {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/scripts/test_export_openapi.py +0 -0
@@ -51,6 +51,54 @@ jobs:
51
51
  # Transitive via mcp>=1.0. Re-evaluate when pyjwt releases a fix. (#280)
52
52
  run: uv run pip-audit --ignore-vuln PYSEC-2025-183
53
53
 
54
+ integration-db:
55
+ name: Real-DB integration tests
56
+ runs-on: ubuntu-latest
57
+ # 実 PostgreSQL / MySQL に対してリポジトリ層を検証する(#747)。
58
+ # SQLite だけでは露見しなかった lastrowid 由来の採番バグを CI で恒常的に防ぐ。
59
+ services:
60
+ postgres:
61
+ image: postgres:16-alpine
62
+ env:
63
+ POSTGRES_PASSWORD: nene2
64
+ POSTGRES_DB: nene2_test
65
+ ports:
66
+ - 5432:5432
67
+ options: >-
68
+ --health-cmd "pg_isready -U postgres"
69
+ --health-interval 5s --health-timeout 5s --health-retries 10
70
+ mysql:
71
+ image: mysql:8
72
+ env:
73
+ MYSQL_ROOT_PASSWORD: nene2
74
+ MYSQL_DATABASE: nene2_test
75
+ ports:
76
+ - 3306:3306
77
+ options: >-
78
+ --health-cmd "mysqladmin ping -uroot -pnene2"
79
+ --health-interval 5s --health-timeout 5s --health-retries 20
80
+
81
+ steps:
82
+ - uses: actions/checkout@v4
83
+
84
+ - name: Install uv
85
+ uses: astral-sh/setup-uv@v5
86
+ with:
87
+ version: "latest"
88
+ enable-cache: true
89
+
90
+ - name: Set up Python
91
+ run: uv python install 3.14
92
+
93
+ - name: Install dependencies
94
+ run: uv sync --all-extras
95
+
96
+ - name: integration tests (PostgreSQL + MySQL)
97
+ env:
98
+ NENE2_TEST_POSTGRES_URL: postgresql+psycopg2://postgres:nene2@127.0.0.1:5432/nene2_test
99
+ NENE2_TEST_MYSQL_URL: mysql+pymysql://root:nene2@127.0.0.1:3306/nene2_test
100
+ run: uv run pytest tests/integration/ -v --no-cov
101
+
54
102
  package-build:
55
103
  name: Package build verification
56
104
  runs-on: ubuntu-latest
@@ -8,6 +8,27 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
8
8
 
9
9
  ---
10
10
 
11
+ ## [1.8.164] — 2026-05-29
12
+
13
+ 実データベース(PostgreSQL / MySQL)統合テストを追加し、マルチDB対応を実測で検証 (#747)。
14
+
15
+ ### Fixed
16
+ - `SqlAlchemyQueryExecutor.write()` / `_BoundQueryExecutor.write()` の INSERT 採番を
17
+ 全方言で正しく返すよう修正。psycopg2 は `lastrowid` 非対応のため、PostgreSQL では
18
+ `lastval()` フォールバックを使う(従来は `rowcount` を返し、`save()` が常に `1` を
19
+ 返す重大バグだった)。SQLite / MySQL は従来どおり `lastrowid`。
20
+
21
+ ### Added
22
+ - 実DB統合テスト `tests/integration/`(PostgreSQL / MySQL)。環境変数
23
+ `NENE2_TEST_POSTGRES_URL` / `NENE2_TEST_MYSQL_URL` 設定時のみ実行、未設定ならスキップ。
24
+ スキーマは SQLAlchemy `Table` から方言非依存に生成。
25
+ - CI に `integration-db` ジョブ(postgres:16 / mysql:8 service container)を追加。
26
+ - how-to [`run-integration-tests.md`](docs/how-to/run-integration-tests.md)(EN/JA)を追加。
27
+ - `pymysql` を dev 依存に追加(`mysql+pymysql://` ドライバが未導入だった)。
28
+
29
+ ### Changed
30
+ - リリース手順 how-to を「公開済み」の現実に合わせて訂正(#541 の事後整合)。
31
+
11
32
  ## [1.8.163] — 2026-05-29
12
33
 
13
34
  v1.8.35〜v1.8.163 の集約リリース。フィールドトライアル網羅スイープの完了と、
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nene2-python
3
- Version: 1.8.163
3
+ Version: 1.8.164
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
@@ -5,21 +5,22 @@ automated publish (TestPyPI → PyPI → GitHub Release)**. The automation lives
5
5
  [`.github/workflows/publish.yml`](../../.github/workflows/publish.yml) and triggers
6
6
  on `v*` tags via **PyPI Trusted Publishing** (OIDC — no long-lived tokens).
7
7
 
8
- ## One-time setup (maintainer, cannot be automated)
8
+ ## One-time setup (maintainer already done)
9
9
 
10
- The publish workflow uses Trusted Publishing and GitHub Environments. These must be
11
- configured once on the PyPI side and in the repo settings:
10
+ The publish workflow uses Trusted Publishing and GitHub Environments. This is
11
+ **already configured** and operational; it is recorded here for reference and
12
+ recovery:
12
13
 
13
14
  1. **PyPI / TestPyPI — Trusted Publisher** for `nene2-python`:
14
15
  - Owner: `hideyukiMORI`, Repository: `nene2-python`
15
16
  - Workflow: `publish.yml`
16
17
  - Environment: `pypi` (on pypi.org) and `testpypi` (on test.pypi.org)
17
- - Register at https://pypi.org/manage/account/publishing/ and the TestPyPI equivalent.
18
- 2. **GitHub repo → Settings → Environments**: create `testpypi` and `pypi`
19
- (optionally add required reviewers on `pypi` for a manual approval gate).
18
+ - Registered at https://pypi.org/manage/account/publishing/ and the TestPyPI equivalent.
19
+ 2. **GitHub repo → Settings → Environments**: `testpypi` and `pypi` exist
20
+ (add required reviewers on `pypi` for a manual approval gate if desired).
20
21
 
21
- Until step 1 is done, `pip install nene2-python` is unavailable — the package has
22
- never been published (the FT sandboxes install it from the local wheel / `git+`).
22
+ The package is **published** `pip install nene2-python` installs the latest
23
+ release (v1.8.163+) directly from PyPI.
23
24
 
24
25
  ## Release procedure (per release)
25
26
 
@@ -67,5 +68,5 @@ excluded by `[tool.hatch.build.targets.wheel] packages = ["src/nene2"]`).
67
68
  [`docs/todo/current.md`](../todo/current.md) milestone table; `CHANGELOG.md`
68
69
  records release-grained aggregated entries.
69
70
  - This procedure corresponds to the **FT7-class "publish flow" trial** (#541): the
70
- build is verified and the automation is in place; the only remaining step is the
71
- maintainer's one-time Trusted Publishing setup followed by the first `v*` tag.
71
+ flow is fully operational v1.8.163 was published to PyPI through it and is
72
+ installable via `pip install nene2-python`.
@@ -0,0 +1,51 @@
1
+ # How to run the real-database integration tests
2
+
3
+ The default `uv run pytest` runs against SQLite / in-memory only — fast and
4
+ dependency-free. A separate suite under `tests/integration/` exercises the
5
+ repository layer against **real PostgreSQL and MySQL** servers, the dialects the
6
+ framework claims to support. These tests **skip** unless the corresponding URL
7
+ env var is set, so they never slow down the default run.
8
+
9
+ ## Run locally with Docker
10
+
11
+ Start throwaway databases:
12
+
13
+ ```bash
14
+ docker run -d --name nene2-pg -e POSTGRES_PASSWORD=nene2 -e POSTGRES_DB=nene2_test \
15
+ -p 5432:5432 postgres:16-alpine
16
+ docker run -d --name nene2-mysql -e MYSQL_ROOT_PASSWORD=nene2 -e MYSQL_DATABASE=nene2_test \
17
+ -p 3306:3306 mysql:8
18
+ ```
19
+
20
+ Point the suite at them and run:
21
+
22
+ ```bash
23
+ export NENE2_TEST_POSTGRES_URL="postgresql+psycopg2://postgres:nene2@127.0.0.1:5432/nene2_test"
24
+ export NENE2_TEST_MYSQL_URL="mysql+pymysql://root:nene2@127.0.0.1:3306/nene2_test"
25
+ uv run pytest tests/integration/ -v --no-cov
26
+ ```
27
+
28
+ Set only one variable to test a single backend. With neither set, the suite is
29
+ skipped.
30
+
31
+ Tear down when finished:
32
+
33
+ ```bash
34
+ docker rm -f nene2-pg nene2-mysql
35
+ ```
36
+
37
+ ## In CI
38
+
39
+ The `integration-db` job in [`.github/workflows/ci.yml`](../../.github/workflows/ci.yml)
40
+ provides PostgreSQL and MySQL as service containers and runs this suite on every
41
+ push and PR — so dialect-specific regressions (e.g. PostgreSQL's lack of
42
+ `lastrowid`, which once made `save()` return the wrong PK, #747) are caught
43
+ automatically.
44
+
45
+ ## How the schema is created
46
+
47
+ The fixture builds the schema from a SQLAlchemy `Table` definition
48
+ (`tests/integration/conftest.py`), so the same `Integer` autoincrement primary
49
+ key becomes `SERIAL` on PostgreSQL, `AUTO_INCREMENT` on MySQL, and `AUTOINCREMENT`
50
+ on SQLite — no hand-written per-dialect DDL. Each test gets a freshly created and
51
+ dropped table.
@@ -5,21 +5,21 @@
5
5
  [`.github/workflows/publish.yml`](../../../.github/workflows/publish.yml) にあり、
6
6
  `v*` タグを契機に **PyPI Trusted Publishing**(OIDC・長期トークン不要)で動く。
7
7
 
8
- ## 一度だけの設定(メンテナ作業・自動化不可)
8
+ ## 一度だけの設定(メンテナ作業・設定済み)
9
9
 
10
- publish ワークフローは Trusted Publishing と GitHub Environments を使う。PyPI 側と
11
- リポジトリ設定で一度だけ構成する必要がある:
10
+ publish ワークフローは Trusted Publishing と GitHub Environments を使う。これは
11
+ **既に構成済み・稼働中**で、参照・復旧用に記録する:
12
12
 
13
13
  1. **PyPI / TestPyPI の Trusted Publisher**(`nene2-python`):
14
14
  - Owner: `hideyukiMORI`、Repository: `nene2-python`
15
15
  - Workflow: `publish.yml`
16
16
  - Environment: `pypi`(pypi.org)と `testpypi`(test.pypi.org)
17
- - https://pypi.org/manage/account/publishing/ と TestPyPI の同等画面で登録。
18
- 2. **GitHub repo → Settings → Environments**: `testpypi` と `pypi` を作成
17
+ - https://pypi.org/manage/account/publishing/ と TestPyPI の同等画面で登録済み。
18
+ 2. **GitHub repo → Settings → Environments**: `testpypi` と `pypi` 作成済み
19
19
  (`pypi` に必須レビュアーを設定すれば手動承認ゲートになる)。
20
20
 
21
- 手順 1 が未了の間は `pip install nene2-python` は不可(未公開。FT サンドボックスは
22
- ローカル wheel / `git+` で導入している)。
21
+ パッケージは**公開済み**で、`pip install nene2-python` で最新リリース(v1.8.163 以降)
22
+ PyPI から直接取得できる。
23
23
 
24
24
  ## リリース手順(毎回)
25
25
 
@@ -64,6 +64,5 @@ import 可能、配布物は `src/nene2` のみ(`example`/`tests` は
64
64
  - **CHANGELOG の粒度**: 版ごとの一行サマリーは
65
65
  [`docs/todo/current.md`](../../todo/current.md) のマイルストーン表に、
66
66
  `CHANGELOG.md` にはリリース粒度の集約エントリを記録する。
67
- - 本手順は **FT7 相当の「公開フロー」トライアル**(#541)に対応する。ビルドは検証済み・
68
- 自動化は整備済みで、残るはメンテナによる一度きりの Trusted Publishing 設定と
69
- 最初の `v*` タグ作成のみ。
67
+ - 本手順は **FT7 相当の「公開フロー」トライアル**(#541)に対応する。フローは完全稼働済みで、
68
+ v1.8.163 を本フローで PyPI に公開済み(`pip install nene2-python` で取得可能)。
@@ -0,0 +1,48 @@
1
+ # 実データベース統合テストの実行方法
2
+
3
+ デフォルトの `uv run pytest` は SQLite / インメモリのみを対象とし、高速で外部依存が
4
+ ない。`tests/integration/` 配下の別スイートは、フレームワークが対応を謳う方言である
5
+ **実 PostgreSQL / MySQL** に対してリポジトリ層を検証する。これらは対応する URL の
6
+ 環境変数が設定されている時だけ実行され、未設定なら**スキップ**される — デフォルトの
7
+ 実行を遅くしない。
8
+
9
+ ## Docker でローカル実行
10
+
11
+ 捨てDBを起動:
12
+
13
+ ```bash
14
+ docker run -d --name nene2-pg -e POSTGRES_PASSWORD=nene2 -e POSTGRES_DB=nene2_test \
15
+ -p 5432:5432 postgres:16-alpine
16
+ docker run -d --name nene2-mysql -e MYSQL_ROOT_PASSWORD=nene2 -e MYSQL_DATABASE=nene2_test \
17
+ -p 3306:3306 mysql:8
18
+ ```
19
+
20
+ スイートを向けて実行:
21
+
22
+ ```bash
23
+ export NENE2_TEST_POSTGRES_URL="postgresql+psycopg2://postgres:nene2@127.0.0.1:5432/nene2_test"
24
+ export NENE2_TEST_MYSQL_URL="mysql+pymysql://root:nene2@127.0.0.1:3306/nene2_test"
25
+ uv run pytest tests/integration/ -v --no-cov
26
+ ```
27
+
28
+ 片方だけ設定すれば単一バックエンドのみ検証。どちらも未設定ならスキップ。
29
+
30
+ 後始末:
31
+
32
+ ```bash
33
+ docker rm -f nene2-pg nene2-mysql
34
+ ```
35
+
36
+ ## CI での実行
37
+
38
+ [`.github/workflows/ci.yml`](../../../.github/workflows/ci.yml) の `integration-db`
39
+ ジョブが PostgreSQL / MySQL を service container として提供し、push / PR ごとに本
40
+ スイートを実行する。これにより方言固有のリグレッション(例: PostgreSQL は `lastrowid`
41
+ を持たず、かつて `save()` が誤ったPKを返していた #747)が自動で検出される。
42
+
43
+ ## スキーマの作り方
44
+
45
+ フィクスチャは SQLAlchemy の `Table` 定義(`tests/integration/conftest.py`)から
46
+ スキーマを生成するため、同じ `Integer` autoincrement 主キーが PostgreSQL では
47
+ `SERIAL`、MySQL では `AUTO_INCREMENT`、SQLite では `AUTOINCREMENT` になる — 方言別の
48
+ 手書きDDLは不要。各テストはテーブルを作成→破棄したクリーンな状態で走る。
@@ -35,11 +35,11 @@ PHP 版 NENE2 との機能同等性を達成し、さらに Python 固有の強
35
35
  - Git タグ `v1.8.N` は選択的リリース時に付与(最新タグは [GitHub Releases](https://github.com/hideyukiMORI/nene2-python/releases) 参照)。FT ループ中は pyproject が先行することがある。
36
36
 
37
37
  ### 残課題(オープン Issue 含む)
38
- - ~~[#541](https://github.com/hideyukiMORI/nene2-python/issues/541) PyPI 公開フロー未検証~~ — ✅ v1.8.163 で検証([手順](how-to/release-and-publish.md))。実公開はメンテナの Trusted Publisher 設定 + `v*` タグ
38
+ - ~~[#541](https://github.com/hideyukiMORI/nene2-python/issues/541) PyPI 公開フロー~~ — ✅ 稼働中。**v1.8.163 を PyPI 公開**(`pip install nene2-python`)。[手順](how-to/release-and-publish.md)
39
39
  - ~~[#553](https://github.com/hideyukiMORI/nene2-python/issues/553) example app に `/examples/ping`・`/examples/notes` を追加(parity)~~ — ✅ #578 で実装済み(既存確認の上 close)
40
40
  - ~~[#539](https://github.com/hideyukiMORI/nene2-python/issues/539) handler の `response_model` 統一~~ — ✅ v1.8.161 で解消
41
41
  - ~~[#540](https://github.com/hideyukiMORI/nene2-python/issues/540) FT ループの目的・終着点の明文化~~ — ✅ [field-trial-methodology.md](explanation/field-trial-methodology.md)
42
- - PostgreSQL / MySQL 実 DB 統合テスト(CI Docker service)
42
+ - ~~[#747](https://github.com/hideyukiMORI/nene2-python/issues/747) PostgreSQL / MySQL 実 DB 統合テスト~~ — ✅ v1.8.164。CI service container で検証。psycopg2 lastrowid 非対応の採番バグを発見・修正。[手順](how-to/run-integration-tests.md)
43
43
  - 並行系 how-to — [concurrency-patterns.md](how-to/concurrency-patterns.md)(FT188–192 知見)
44
44
  - WebSocket サポート(検討中)
45
45
  - PyJWT 推移的 CVE(PYSEC-2025-183、mcp 経由)— CI で ignore、修正待ち
@@ -132,8 +132,8 @@ PHP 版追跡・Python 固有の強化:
132
132
  - [x] Diátaxis 構造のドキュメント整備(tutorial / howto / explanation / reference)(#43)
133
133
  - [x] Field Trial 1〜6: InMemory / SQLite / 認証 / MCP / トランザクション / AsyncUseCase 検証完了
134
134
  - [x] Field Trial 7〜282: stdlib フルカバレッジ + セキュリティ深掘りスイープ完了 → 保守 + オンデマンドへ([方法論](explanation/field-trial-methodology.md))
135
- - [x] PyPI 公開フロー検証(`uv publish` ワークフロー・ビルド検証・CHANGELOG/タグ連携、#541・[手順](how-to/release-and-publish.md))— 実公開はメンテナの Trusted Publisher 設定待ち
136
- - [ ] PostgreSQL / MySQL 実 DB 統合テスト(CI Docker service ジョブ)
135
+ - [x] PyPI 公開(#541)— フロー稼働中・**v1.8.163 公開済み**(`pip install nene2-python`)。[手順](how-to/release-and-publish.md)
136
+ - [x] PostgreSQL / MySQL 実 DB 統合テスト(CI Docker service ジョブ、#747・v1.8.164)— [手順](how-to/run-integration-tests.md)
137
137
  - [ ] WebSocket サポート検討
138
138
  - [ ] 並行系 how-to ガイド(threading / asyncio / concurrent.futures 比較・選択指針)
139
139
 
@@ -1,26 +1,29 @@
1
1
  # TODO — current
2
2
 
3
3
  最終更新: 2026-05-29
4
- 現状: **v1.8.163 / #541(PyPI 公開フロー)検証 / CI グリーン**
4
+ 現状: **v1.8.164 / 実DB統合テスト導入・マルチDB対応を実測検証 / CI グリーン**
5
5
 
6
6
  ---
7
7
 
8
8
  ## 状態サマリー
9
9
 
10
- #541 に対応(v1.8.163)。公開フロー(`publish.yml`: build→TestPyPI→PyPI→GitHub Release・
11
- Trusted Publishing)は既存。本対応で**ビルドの公開準備を検証**し連携を確立した:
12
- - `uv build` の sdist/wheel `twine check` PASSED、クリーン venv で import 可能、配布物は
13
- `src/nene2` のみ(example/tests は混入しない)を確認
14
- - CI `package-build` ジョブを追加(PR ごとに build + twine check + クリーン import を検証)
15
- - `CHANGELOG.md` が [1.8.34] で止まりリリースノートが空になる問題を、集約エントリ [1.8.163]
16
- 追加で解消(github-release ステップの抽出が動くことを確認)
17
- - リリース手順 how-to [`how-to/release-and-publish.md`](../how-to/release-and-publish.md)(EN/JA)を追加
18
- - **残: PyPI 公開は不可逆でメンテナ作業** — PyPI/TestPyPI の Trusted Publisher 設定 +
19
- GitHub Environments(testpypi/pypi)作成 + 最初の `v*` タグ。手順は how-to に記載。
20
-
21
- 直前の対応: #540(FT ループ目的・終着点明文化・v1.8.162)、#539(response_model 統一・
10
+ #747 に対応(v1.8.164)。「`sqlite/mysql/pgsql` 対応」という未検証の約束を実測で検証し、
11
+ 潜んでいたマルチDBバグを修正した:
12
+ - **発見・修正**: `SqlAlchemyQueryExecutor.write()`INSERT 採番が `result.lastrowid`
13
+ 依存で、psycopg2(PostgreSQL)は lastrowid 非対応のため `rowcount`(=1) にフォールバック。
14
+ `save()` が常に `id=1` を返す重大バグだった PostgreSQL では `lastval()` フォールバックに修正
15
+ - **実DB統合テスト** `tests/integration/`(PostgreSQL/MySQL、各9ケース)を追加。環境変数
16
+ 未設定ならskip(デフォルト `uv run pytest` は SQLite のまま高速:466 passed, 9 skipped)
17
+ - **CI** `integration-db` ジョブ(postgres:16 / mysql:8 service container)追加
18
+ - `pymysql` dev 依存に追加(`mysql+pymysql://` ドライバが未導入だった)
19
+ - how-to [`run-integration-tests.md`](../how-to/run-integration-tests.md)(EN/JA)追加
20
+ - ついでに #541 のリリース how-to の「未公開・メンテナ待ち」誤記(EN/JA)を訂正
21
+
22
+ 直前の対応: #541(PyPI 公開フロー解消・v1.8.163 を PyPI 公開)、#540(FT ループ目的・
23
+ 終着点明文化・v1.8.162)、#539(response_model 統一・
22
24
  v1.8.161)、#553(#578 で実装済みを確認し close)、ハウスキーピング(サンドボックス
23
- 5.1G→79M 整理・orphan ブランチ 8 本削除)。**フレームワーク本体 466 tests 据え置き**。
25
+ 5.1G→79M 整理・orphan ブランチ 8 本削除)。**フレームワーク本体 466 tests 据え置き。
26
+ オープン Issue ゼロ。**
24
27
 
25
28
  ---
26
29
 
@@ -32,11 +35,8 @@ v1.8.161)、#553(#578 で実装済みを確認し close)、ハウスキー
32
35
 
33
36
  ## オープン Issue
34
37
 
35
- | # | タイトル | 優先度 | 種別 |
36
- |---|---|---|---|
37
- | [#539](https://github.com/hideyukiMORI/nene2-python/issues/539) | handler の response_model を統一して CLAUDE.md ポリシーに準拠 | 中 | enhancement |
38
- | [#540](https://github.com/hideyukiMORI/nene2-python/issues/540) | FT ループの目的と終着点を明文化する | 中 | docs |
39
- | [#541](https://github.com/hideyukiMORI/nene2-python/issues/541) | PyPI 公開フロー検証(uv publish ワークフロー) | 中 | enhancement |
38
+ なし(#539 / #540 / #541 / #553 はすべて 2026-05-29 に解消・close)。
39
+ 提案 PR #545(nene-style governance)のみ未マージで残置。
40
40
 
41
41
  ---
42
42
 
@@ -44,6 +44,7 @@ v1.8.161)、#553(#578 で実装済みを確認し close)、ハウスキー
44
44
 
45
45
  | バージョン | 主な内容 |
46
46
  |---|---|
47
+ | v1.8.164 | feat: 実DB統合テスト(#747)— PostgreSQL/MySQL を CI service container で検証。psycopg2 lastrowid 非対応による INSERT 採番バグ(`save()` が常に id=1)を発見・修正。pymysql 追加 |
47
48
  | v1.8.163 | feat: PyPI 公開フロー検証(#541)— ビルド検証・CI package-build ジョブ・CHANGELOG 連携・リリース how-to |
48
49
  | v1.8.162 | docs: FT ループの目的・終着点を明文化(#540)— field-trial-methodology.md(EN/JA) |
49
50
  | v1.8.161 | fix: handler の response_model 統一(#539)— Note/Tag/Comment に Response モデル定義・OpenAPI スキーマ出力 |
@@ -153,9 +154,8 @@ v1.8.161)、#553(#578 で実装済みを確認し close)、ハウスキー
153
154
 
154
155
  | 優先度 | Issue | タスク | 種別 |
155
156
  |---|---|---|---|
156
- | 低 | [#541](https://github.com/hideyukiMORI/nene2-python/issues/541) | 実 PyPI 公開(Trusted Publisher 設定 + v* タグ)— メンテナ作業 | enhancement |
157
157
  | — | — | FT は保守 + オンデマンド(4 トリガー時のみ。[方法論](../explanation/field-trial-methodology.md)) | FT |
158
- | | — | PostgreSQL / MySQL DB 統合テスト | infra |
158
+ | | — | リリース時は `v*` タグ push publish.yml が自動公開([手順](../how-to/release-and-publish.md)) | infra |
159
159
  | 低 | — | PyJWT 推移的 CVE(PYSEC-2025-183)— mcp 修正待ち | 保留 |
160
160
 
161
161
  ---
@@ -166,7 +166,8 @@ v1.8.161)、#553(#578 で実装済みを確認し close)、ハウスキー
166
166
  |---|---|---|---|
167
167
  | ~~handler response_model 未使用~~ | — | [#539](https://github.com/hideyukiMORI/nene2-python/issues/539) | ✅ 2026-05-29 解消(v1.8.161)。Note/Tag/Comment 全ハンドラーに `XxxResponse`/`XxxListResponse` を定義し `response_model` 明示。OpenAPI に 6 レスポンススキーマが出力されることを確認。466 tests 据え置き |
168
168
  | ~~FT ループ目的の明文化~~ | — | [#540](https://github.com/hideyukiMORI/nene2-python/issues/540) | ✅ 2026-05-29 解消(v1.8.162)。explanation/field-trial-methodology.md(EN/JA)に目的・3 フェーズ・終着点(網羅スイープ完了→保守+オンデマンド)を明文化。INDEX/roadmap からリンク |
169
- | PyPI 公開フロー検証 | — | [#541](https://github.com/hideyukiMORI/nene2-python/issues/541) | ✅ 2026-05-29 検証(v1.8.163)。build/twine check/クリーン import 確認、CI package-build ジョブ追加、CHANGELOG 連携・how-to 整備。実公開はメンテナの Trusted Publisher 設定 + v* タグ待ち |
169
+ | ~~PyPI 公開フロー検証~~ | — | [#541](https://github.com/hideyukiMORI/nene2-python/issues/541) | ✅ 2026-05-29 解消(v1.8.163)。公開フローは既に稼働中で **v1.8.163 PyPI へ公開**(pip install で取得可能)。CI package-build ジョブ・CHANGELOG 連携・how-to 整備 |
170
+ | ~~マルチDB対応が未検証~~ | — | [#747](https://github.com/hideyukiMORI/nene2-python/issues/747) | ✅ 2026-05-29 解消(v1.8.164)。実DB統合テスト(PostgreSQL/MySQL・CI service container)を追加。psycopg2 lastrowid 非対応で `save()` が常に id=1 を返す重大バグを発見・修正(lastval フォールバック)。pymysql 追加・how-to(EN/JA) |
170
171
  | ~~古い FT サンドボックス肥大化~~ | — | — | ✅ 2026-05-29 整理(5.1G→79M)。`ft-status.sh --clean-sandbox` を追加(.venv/キャッシュ削除・ソース保持・uv sync で再生可)。`--clean` は dist/ のみで誤記だった |
171
172
  | ~~マージ済み orphan リモートブランチ~~ | — | — | ✅ 2026-05-29 削除(merged 8 本)。未マージの提案 PR #545 のブランチのみ残置 |
172
173
  | ログ秘匿 Filter は形式依存の best-effort | 低 | — | FT220 D3。主防御は `SecretStr`。how-to に「秘匿は多層防御の保険」を明記予定 |
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nene2-python"
3
- version = "1.8.163"
3
+ version = "1.8.164"
4
4
  description = "NENE2 Python — minimal API framework following NENE2's design philosophy"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
@@ -62,6 +62,7 @@ dev = [
62
62
  "ruff>=0.9",
63
63
  "pip-audit>=2.7",
64
64
  "psycopg2-binary>=2.9.12",
65
+ "pymysql>=1.1",
65
66
  ]
66
67
 
67
68
  # ---------------------------------------------------------------------------
@@ -6,13 +6,33 @@ Supports SQLite, MySQL, and PostgreSQL via SQLAlchemy's engine URL.
6
6
  from collections.abc import Callable
7
7
  from typing import Any
8
8
 
9
- from sqlalchemy import Connection, Engine, text
10
- from sqlalchemy.exc import IntegrityError, OperationalError
9
+ from sqlalchemy import Connection, CursorResult, Engine, text
10
+ from sqlalchemy.exc import IntegrityError, OperationalError, ProgrammingError
11
11
 
12
12
  from .exceptions import DatabaseConnectionException, DatabaseIntegrityException
13
13
  from .interfaces import DatabaseQueryExecutorInterface, DatabaseTransactionManagerInterface
14
14
 
15
15
 
16
+ def _insert_id(conn: Connection, result: CursorResult[Any]) -> int:
17
+ """Return the new row's PK after an INSERT, portably across dialects.
18
+
19
+ SQLite and MySQL expose the value via DBAPI ``lastrowid``. PostgreSQL
20
+ (psycopg2) does not, so fall back to ``lastval()`` on the same connection
21
+ (valid for SERIAL/IDENTITY columns within the current transaction). If no
22
+ sequence was touched, fall back to ``rowcount``.
23
+ """
24
+ if result.lastrowid:
25
+ return result.lastrowid
26
+ if conn.dialect.name == "postgresql":
27
+ try:
28
+ value = conn.execute(text("SELECT lastval()")).scalar()
29
+ except (OperationalError, ProgrammingError):
30
+ return result.rowcount
31
+ if value is not None:
32
+ return int(value)
33
+ return result.rowcount
34
+
35
+
16
36
  class SqlAlchemyQueryExecutor(DatabaseQueryExecutorInterface):
17
37
  """Execute queries using SQLAlchemy Core (connection-per-call, no ORM).
18
38
 
@@ -75,7 +95,7 @@ class SqlAlchemyQueryExecutor(DatabaseQueryExecutorInterface):
75
95
  with self._engine.begin() as conn:
76
96
  result = conn.execute(text(sql), params or {})
77
97
  if sql.strip().upper().startswith("INSERT"):
78
- return result.lastrowid or result.rowcount
98
+ return _insert_id(conn, result)
79
99
  return result.rowcount
80
100
  except IntegrityError as exc:
81
101
  raise DatabaseIntegrityException(str(exc)) from exc
@@ -112,7 +132,7 @@ class _BoundQueryExecutor(DatabaseQueryExecutorInterface):
112
132
  except OperationalError as exc:
113
133
  raise DatabaseConnectionException(str(exc)) from exc
114
134
  if sql.strip().upper().startswith("INSERT"):
115
- return result.lastrowid or result.rowcount
135
+ return _insert_id(self._conn, result)
116
136
  return result.rowcount
117
137
 
118
138
 
@@ -0,0 +1,58 @@
1
+ """Fixtures for real-database integration tests.
2
+
3
+ These tests exercise SqlAlchemyQueryExecutor against actual PostgreSQL / MySQL
4
+ servers — the dialects the framework claims to support but only ever tested on
5
+ SQLite. They run only when the corresponding URL env var is set, so the default
6
+ ``uv run pytest`` stays SQLite-only and fast. CI sets the env vars via service
7
+ containers (see .github/workflows/ci.yml).
8
+ """
9
+
10
+ import os
11
+ from collections.abc import Iterator
12
+
13
+ import pytest
14
+ from sqlalchemy import Column, Integer, MetaData, Table, Text, create_engine
15
+
16
+ from nene2.database import SqlAlchemyQueryExecutor
17
+
18
+ # backend name -> env var holding its SQLAlchemy URL
19
+ _BACKEND_ENV: dict[str, str] = {
20
+ "postgresql": "NENE2_TEST_POSTGRES_URL",
21
+ "mysql": "NENE2_TEST_MYSQL_URL",
22
+ }
23
+
24
+ # Dialect-portable schema. SQLAlchemy emits SERIAL (PostgreSQL) / AUTO_INCREMENT
25
+ # (MySQL) from the same Integer autoincrement PK — no hand-written per-dialect DDL.
26
+ _metadata = MetaData()
27
+ Table(
28
+ "notes",
29
+ _metadata,
30
+ Column("id", Integer, primary_key=True, autoincrement=True),
31
+ Column("title", Text, nullable=False),
32
+ Column("body", Text, nullable=False),
33
+ )
34
+
35
+
36
+ def _configured_backends() -> list[str]:
37
+ return [name for name, env in _BACKEND_ENV.items() if os.environ.get(env)]
38
+
39
+
40
+ def _params() -> list[object]:
41
+ backends = _configured_backends()
42
+ if backends:
43
+ return list(backends)
44
+ reason = "no real DB configured (set NENE2_TEST_POSTGRES_URL / NENE2_TEST_MYSQL_URL)"
45
+ return [pytest.param(None, marks=pytest.mark.skip(reason=reason))]
46
+
47
+
48
+ @pytest.fixture(params=_params())
49
+ def real_db_executor(request: pytest.FixtureRequest) -> Iterator[SqlAlchemyQueryExecutor]:
50
+ backend: str = request.param
51
+ engine = create_engine(os.environ[_BACKEND_ENV[backend]])
52
+ _metadata.drop_all(engine)
53
+ _metadata.create_all(engine)
54
+ try:
55
+ yield SqlAlchemyQueryExecutor(engine)
56
+ finally:
57
+ _metadata.drop_all(engine)
58
+ engine.dispose()
@@ -0,0 +1,65 @@
1
+ """Note repository contract verified against real PostgreSQL / MySQL servers.
2
+
3
+ Mirrors the SQLite contract in tests/example/note/test_note_repository.py, plus a
4
+ regression for the multi-DB INSERT-PK bug: psycopg2 exposes no ``lastrowid``, so
5
+ ``save()`` must derive the new PK another way (lastval) rather than returning
6
+ ``rowcount`` (#747).
7
+ """
8
+
9
+ import pytest
10
+
11
+ from nene2.database import SqlAlchemyQueryExecutor
12
+ from src.example.note.entity import Note
13
+ from src.example.note.sqlalchemy_repository import SqlAlchemyNoteRepository
14
+
15
+
16
+ @pytest.fixture
17
+ def repo(real_db_executor: SqlAlchemyQueryExecutor) -> SqlAlchemyNoteRepository:
18
+ return SqlAlchemyNoteRepository(real_db_executor)
19
+
20
+
21
+ def test_save_returns_sequential_pks(repo: SqlAlchemyNoteRepository) -> None:
22
+ first = repo.save("A", "a")
23
+ second = repo.save("B", "b")
24
+ assert (first.id, second.id) == (1, 2)
25
+
26
+
27
+ def test_save_and_find_round_trip(repo: SqlAlchemyNoteRepository) -> None:
28
+ note = repo.save("Hello", "World")
29
+ assert repo.find_by_id(note.id) == note
30
+
31
+
32
+ def test_find_by_id_returns_none_when_missing(repo: SqlAlchemyNoteRepository) -> None:
33
+ assert repo.find_by_id(9999) is None
34
+
35
+
36
+ def test_find_all_respects_limit_and_offset(repo: SqlAlchemyNoteRepository) -> None:
37
+ for i in range(5):
38
+ repo.save(f"Note {i}", "body")
39
+ page = repo.find_all(limit=2, offset=2)
40
+ assert [n.title for n in page] == ["Note 2", "Note 3"]
41
+
42
+
43
+ def test_count_reflects_saved_notes(repo: SqlAlchemyNoteRepository) -> None:
44
+ repo.save("T", "B")
45
+ repo.save("T2", "B2")
46
+ assert repo.count() == 2
47
+
48
+
49
+ def test_update_changes_title_and_body(repo: SqlAlchemyNoteRepository) -> None:
50
+ note = repo.save("Old", "old body")
51
+ assert repo.update(note.id, "New", "new body") == Note(id=note.id, title="New", body="new body")
52
+
53
+
54
+ def test_update_returns_none_for_missing_id(repo: SqlAlchemyNoteRepository) -> None:
55
+ assert repo.update(9999, "T", "B") is None
56
+
57
+
58
+ def test_delete_removes_note(repo: SqlAlchemyNoteRepository) -> None:
59
+ note = repo.save("T", "B")
60
+ repo.delete(note.id)
61
+ assert repo.find_by_id(note.id) is None
62
+
63
+
64
+ def test_delete_returns_false_for_missing_id(repo: SqlAlchemyNoteRepository) -> None:
65
+ assert repo.delete(9999) is False
File without changes
@@ -925,7 +925,7 @@ wheels = [
925
925
 
926
926
  [[package]]
927
927
  name = "nene2-python"
928
- version = "1.8.163"
928
+ version = "1.8.164"
929
929
  source = { editable = "." }
930
930
  dependencies = [
931
931
  { name = "alembic" },
@@ -958,6 +958,7 @@ dev = [
958
958
  { name = "mypy" },
959
959
  { name = "pip-audit" },
960
960
  { name = "psycopg2-binary" },
961
+ { name = "pymysql" },
961
962
  { name = "pytest" },
962
963
  { name = "pytest-asyncio" },
963
964
  { name = "pytest-cov" },
@@ -993,6 +994,7 @@ dev = [
993
994
  { name = "mypy", specifier = ">=1.13" },
994
995
  { name = "pip-audit", specifier = ">=2.7" },
995
996
  { name = "psycopg2-binary", specifier = ">=2.9.12" },
997
+ { name = "pymysql", specifier = ">=1.1" },
996
998
  { name = "pytest", specifier = ">=8.3" },
997
999
  { name = "pytest-asyncio", specifier = ">=0.24" },
998
1000
  { name = "pytest-cov", specifier = ">=6.0" },
@@ -1288,6 +1290,15 @@ crypto = [
1288
1290
  { name = "cryptography" },
1289
1291
  ]
1290
1292
 
1293
+ [[package]]
1294
+ name = "pymysql"
1295
+ version = "1.2.0"
1296
+ source = { registry = "https://pypi.org/simple" }
1297
+ sdist = { url = "https://files.pythonhosted.org/packages/c9/bc/1c6a92f385940f727daeecf3bacaf186e03875dff57197801046c583bcf0/pymysql-1.2.0.tar.gz", hash = "sha256:6c7b17ca686988104d7426c27895b455cdeea3e9d3ceb1270f0c3704fead8c33", size = 49021, upload-time = "2026-05-19T08:26:22.302Z" }
1298
+ wheels = [
1299
+ { url = "https://files.pythonhosted.org/packages/c4/bd/2534e130295c8cfd4f0a2e31623baab7502278f1e97bcfe61db75656a77f/pymysql-1.2.0-py3-none-any.whl", hash = "sha256:62169ce6d5510f08e140c5e7990ee884a9764024e4a9a27b2cc11f1099322ae0", size = 45716, upload-time = "2026-05-19T08:26:20.974Z" },
1300
+ ]
1301
+
1291
1302
  [[package]]
1292
1303
  name = "pyparsing"
1293
1304
  version = "3.3.2"