nene2-python 1.8.52__tar.gz → 1.8.54__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 (405) hide show
  1. {nene2_python-1.8.52 → nene2_python-1.8.54}/PKG-INFO +1 -1
  2. nene2_python-1.8.54/docs/field-trials/2026-05-field-trial-182.md +301 -0
  3. nene2_python-1.8.54/docs/field-trials/2026-05-field-trial-183.md +511 -0
  4. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/INDEX.md +5 -3
  5. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/todo/current.md +12 -6
  6. {nene2_python-1.8.52 → nene2_python-1.8.54}/pyproject.toml +1 -1
  7. {nene2_python-1.8.52 → nene2_python-1.8.54}/.env.example +0 -0
  8. {nene2_python-1.8.52 → nene2_python-1.8.54}/.github/workflows/ci.yml +0 -0
  9. {nene2_python-1.8.52 → nene2_python-1.8.54}/.github/workflows/docs.yml +0 -0
  10. {nene2_python-1.8.52 → nene2_python-1.8.54}/.github/workflows/publish.yml +0 -0
  11. {nene2_python-1.8.52 → nene2_python-1.8.54}/.gitignore +0 -0
  12. {nene2_python-1.8.52 → nene2_python-1.8.54}/.vitepress/config.mts +0 -0
  13. {nene2_python-1.8.52 → nene2_python-1.8.54}/.vitepress/theme/custom.css +0 -0
  14. {nene2_python-1.8.52 → nene2_python-1.8.54}/.vitepress/theme/index.ts +0 -0
  15. {nene2_python-1.8.52 → nene2_python-1.8.54}/AGENTS.md +0 -0
  16. {nene2_python-1.8.52 → nene2_python-1.8.54}/CHANGELOG.md +0 -0
  17. {nene2_python-1.8.52 → nene2_python-1.8.54}/CLAUDE.md +0 -0
  18. {nene2_python-1.8.52 → nene2_python-1.8.54}/Dockerfile +0 -0
  19. {nene2_python-1.8.52 → nene2_python-1.8.54}/LICENSE +0 -0
  20. {nene2_python-1.8.52 → nene2_python-1.8.54}/README.md +0 -0
  21. {nene2_python-1.8.52 → nene2_python-1.8.54}/alembic/README +0 -0
  22. {nene2_python-1.8.52 → nene2_python-1.8.54}/alembic/env.py +0 -0
  23. {nene2_python-1.8.52 → nene2_python-1.8.54}/alembic/script.py.mako +0 -0
  24. {nene2_python-1.8.52 → nene2_python-1.8.54}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
  25. {nene2_python-1.8.52 → nene2_python-1.8.54}/alembic.ini +0 -0
  26. {nene2_python-1.8.52 → nene2_python-1.8.54}/compose.yaml +0 -0
  27. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/adr/0001-toolchain.md +0 -0
  28. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/adr/0002-clean-architecture.md +0 -0
  29. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/adr/0003-security-first.md +0 -0
  30. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/adr/0004-ai-first-design.md +0 -0
  31. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/adr/0005-logging.md +0 -0
  32. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/adr/0006-rate-limiting.md +0 -0
  33. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/adr/0009-mcp-design.md +0 -0
  34. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/adr/0010-async-use-case.md +0 -0
  35. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
  36. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/de/index.md +0 -0
  37. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/de/tutorials/getting-started.md +0 -0
  38. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/explanation/architecture.md +0 -0
  39. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/explanation/design-philosophy.md +0 -0
  40. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-1.md +0 -0
  41. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-10.md +0 -0
  42. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-100.md +0 -0
  43. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-101.md +0 -0
  44. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-102.md +0 -0
  45. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-103.md +0 -0
  46. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-104.md +0 -0
  47. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-105.md +0 -0
  48. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-106.md +0 -0
  49. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-107.md +0 -0
  50. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-108.md +0 -0
  51. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-109.md +0 -0
  52. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-11.md +0 -0
  53. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-110.md +0 -0
  54. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-111.md +0 -0
  55. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-112.md +0 -0
  56. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-113.md +0 -0
  57. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-114.md +0 -0
  58. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-115.md +0 -0
  59. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-116.md +0 -0
  60. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-117.md +0 -0
  61. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-118.md +0 -0
  62. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-119.md +0 -0
  63. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-12.md +0 -0
  64. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-120.md +0 -0
  65. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-121.md +0 -0
  66. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-122.md +0 -0
  67. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-123.md +0 -0
  68. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-124.md +0 -0
  69. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-125.md +0 -0
  70. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-126.md +0 -0
  71. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-127.md +0 -0
  72. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-128.md +0 -0
  73. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-129.md +0 -0
  74. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-13.md +0 -0
  75. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-130.md +0 -0
  76. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-131.md +0 -0
  77. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-132.md +0 -0
  78. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-133.md +0 -0
  79. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-134.md +0 -0
  80. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-135.md +0 -0
  81. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-136.md +0 -0
  82. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-137.md +0 -0
  83. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-138.md +0 -0
  84. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-139.md +0 -0
  85. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-14.md +0 -0
  86. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-140.md +0 -0
  87. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-141.md +0 -0
  88. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-142.md +0 -0
  89. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-143.md +0 -0
  90. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-144.md +0 -0
  91. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-145.md +0 -0
  92. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-146.md +0 -0
  93. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-147.md +0 -0
  94. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-148.md +0 -0
  95. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-149.md +0 -0
  96. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-15.md +0 -0
  97. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-150.md +0 -0
  98. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-151.md +0 -0
  99. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-152.md +0 -0
  100. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-153.md +0 -0
  101. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-154.md +0 -0
  102. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-155.md +0 -0
  103. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-156.md +0 -0
  104. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-157.md +0 -0
  105. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-158.md +0 -0
  106. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-159.md +0 -0
  107. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-16.md +0 -0
  108. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-160.md +0 -0
  109. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-161.md +0 -0
  110. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-162.md +0 -0
  111. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-163.md +0 -0
  112. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-164.md +0 -0
  113. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-165.md +0 -0
  114. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-166.md +0 -0
  115. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-167.md +0 -0
  116. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-168.md +0 -0
  117. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-169.md +0 -0
  118. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-17.md +0 -0
  119. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-170.md +0 -0
  120. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-171.md +0 -0
  121. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-172.md +0 -0
  122. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-173.md +0 -0
  123. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-174.md +0 -0
  124. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-175.md +0 -0
  125. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-176.md +0 -0
  126. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-177.md +0 -0
  127. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-178.md +0 -0
  128. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-179.md +0 -0
  129. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-18.md +0 -0
  130. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-180.md +0 -0
  131. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-181.md +0 -0
  132. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-19.md +0 -0
  133. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-2.md +0 -0
  134. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-20.md +0 -0
  135. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-21.md +0 -0
  136. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-22.md +0 -0
  137. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-23.md +0 -0
  138. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-24.md +0 -0
  139. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-25.md +0 -0
  140. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-26.md +0 -0
  141. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-27.md +0 -0
  142. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-28.md +0 -0
  143. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-29.md +0 -0
  144. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-3.md +0 -0
  145. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-30.md +0 -0
  146. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-31.md +0 -0
  147. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-32.md +0 -0
  148. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-33.md +0 -0
  149. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-34.md +0 -0
  150. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-35.md +0 -0
  151. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-36.md +0 -0
  152. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-37.md +0 -0
  153. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-38.md +0 -0
  154. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-39.md +0 -0
  155. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-4.md +0 -0
  156. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-40.md +0 -0
  157. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-41.md +0 -0
  158. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-42.md +0 -0
  159. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-43.md +0 -0
  160. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-44.md +0 -0
  161. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-45.md +0 -0
  162. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-46.md +0 -0
  163. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-47.md +0 -0
  164. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-48.md +0 -0
  165. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-49.md +0 -0
  166. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-5.md +0 -0
  167. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-50.md +0 -0
  168. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-51.md +0 -0
  169. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-52.md +0 -0
  170. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-53.md +0 -0
  171. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-54.md +0 -0
  172. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-55.md +0 -0
  173. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-56.md +0 -0
  174. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-57.md +0 -0
  175. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-58.md +0 -0
  176. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-59.md +0 -0
  177. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-6.md +0 -0
  178. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-60.md +0 -0
  179. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-61.md +0 -0
  180. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-62.md +0 -0
  181. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-63.md +0 -0
  182. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-64.md +0 -0
  183. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-65.md +0 -0
  184. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-66.md +0 -0
  185. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-67.md +0 -0
  186. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-68.md +0 -0
  187. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-69.md +0 -0
  188. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-7.md +0 -0
  189. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-70.md +0 -0
  190. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-71.md +0 -0
  191. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-72.md +0 -0
  192. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-73.md +0 -0
  193. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-74.md +0 -0
  194. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-75.md +0 -0
  195. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-76.md +0 -0
  196. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-77.md +0 -0
  197. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-78.md +0 -0
  198. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-79.md +0 -0
  199. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-8.md +0 -0
  200. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-80.md +0 -0
  201. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-81.md +0 -0
  202. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-82.md +0 -0
  203. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-83.md +0 -0
  204. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-84.md +0 -0
  205. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-85.md +0 -0
  206. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-86.md +0 -0
  207. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-87.md +0 -0
  208. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-88.md +0 -0
  209. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-89.md +0 -0
  210. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-9.md +0 -0
  211. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-90.md +0 -0
  212. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-91.md +0 -0
  213. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-92.md +0 -0
  214. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-93.md +0 -0
  215. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-94.md +0 -0
  216. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-95.md +0 -0
  217. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-96.md +0 -0
  218. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-97.md +0 -0
  219. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-98.md +0 -0
  220. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/field-trials/2026-05-field-trial-99.md +0 -0
  221. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/fr/index.md +0 -0
  222. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/fr/tutorials/getting-started.md +0 -0
  223. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/add-new-domain.md +0 -0
  224. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/api-versioning.md +0 -0
  225. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/async-use-case.md +0 -0
  226. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/background-tasks.md +0 -0
  227. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/configure-auth.md +0 -0
  228. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/cors.md +0 -0
  229. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/custom-auth-middleware.md +0 -0
  230. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/dependency-injection.md +0 -0
  231. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/domain-events.md +0 -0
  232. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/file-upload.md +0 -0
  233. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/lifespan-and-app-state.md +0 -0
  234. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/middleware-stack.md +0 -0
  235. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/new-project.md +0 -0
  236. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/problem-details.md +0 -0
  237. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/response-patterns.md +0 -0
  238. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/run-tests.md +0 -0
  239. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/soft-delete.md +0 -0
  240. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/sqlalchemy-repository.md +0 -0
  241. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/streaming.md +0 -0
  242. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/structured-logging.md +0 -0
  243. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/validation.md +0 -0
  244. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/how-to/webhook.md +0 -0
  245. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/howto/mcp-setup.md +0 -0
  246. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/index.md +0 -0
  247. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/ja/explanation/architecture.md +0 -0
  248. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/ja/explanation/design-philosophy.md +0 -0
  249. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/ja/how-to/add-new-domain.md +0 -0
  250. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/ja/how-to/configure-auth.md +0 -0
  251. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/ja/how-to/new-project.md +0 -0
  252. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/ja/how-to/run-tests.md +0 -0
  253. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
  254. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/ja/howto/mcp-setup.md +0 -0
  255. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/ja/index.md +0 -0
  256. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/ja/reference/api.md +0 -0
  257. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/ja/reference/configuration.md +0 -0
  258. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/ja/reference/framework-modules.md +0 -0
  259. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/ja/tutorials/first-domain.md +0 -0
  260. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/ja/tutorials/getting-started.md +0 -0
  261. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/pt-br/index.md +0 -0
  262. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/pt-br/tutorials/getting-started.md +0 -0
  263. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/reference/api.md +0 -0
  264. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/reference/configuration.md +0 -0
  265. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/reference/framework-modules.md +0 -0
  266. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/roadmap.md +0 -0
  267. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/templates/field-trial-report.md +0 -0
  268. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/tutorials/first-domain.md +0 -0
  269. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/tutorials/getting-started.md +0 -0
  270. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/zh/index.md +0 -0
  271. {nene2_python-1.8.52 → nene2_python-1.8.54}/docs/zh/tutorials/getting-started.md +0 -0
  272. {nene2_python-1.8.52 → nene2_python-1.8.54}/package-lock.json +0 -0
  273. {nene2_python-1.8.52 → nene2_python-1.8.54}/package.json +0 -0
  274. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/__init__.py +0 -0
  275. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/__main__.py +0 -0
  276. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/app.py +0 -0
  277. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/comment/__init__.py +0 -0
  278. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/comment/entity.py +0 -0
  279. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/comment/exceptions.py +0 -0
  280. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/comment/handler.py +0 -0
  281. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/comment/repository.py +0 -0
  282. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/comment/sqlalchemy_repository.py +0 -0
  283. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/comment/use_case.py +0 -0
  284. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/mcp.py +0 -0
  285. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/note/__init__.py +0 -0
  286. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/note/async_use_case.py +0 -0
  287. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/note/entity.py +0 -0
  288. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/note/exceptions.py +0 -0
  289. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/note/handler.py +0 -0
  290. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/note/repository.py +0 -0
  291. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/note/sqlalchemy_repository.py +0 -0
  292. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/note/use_case.py +0 -0
  293. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/schema.py +0 -0
  294. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/tag/__init__.py +0 -0
  295. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/tag/entity.py +0 -0
  296. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/tag/exceptions.py +0 -0
  297. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/tag/handler.py +0 -0
  298. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/tag/repository.py +0 -0
  299. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/tag/sqlalchemy_repository.py +0 -0
  300. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/example/tag/use_case.py +0 -0
  301. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/__init__.py +0 -0
  302. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/auth/__init__.py +0 -0
  303. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/auth/api_key.py +0 -0
  304. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/auth/bearer_token.py +0 -0
  305. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/auth/deps.py +0 -0
  306. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/auth/exceptions.py +0 -0
  307. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/auth/interfaces.py +0 -0
  308. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/auth/local_verifier.py +0 -0
  309. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/cache/__init__.py +0 -0
  310. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/cache/ttl.py +0 -0
  311. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/config/__init__.py +0 -0
  312. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/config/settings.py +0 -0
  313. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/database/__init__.py +0 -0
  314. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/database/exceptions.py +0 -0
  315. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/database/health.py +0 -0
  316. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/database/interfaces.py +0 -0
  317. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/database/sqlalchemy_executor.py +0 -0
  318. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/database/utils.py +0 -0
  319. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/http/__init__.py +0 -0
  320. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/http/etag.py +0 -0
  321. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/http/health.py +0 -0
  322. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/http/pagination.py +0 -0
  323. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/http/problem_details.py +0 -0
  324. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/log/__init__.py +0 -0
  325. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/log/setup.py +0 -0
  326. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/mcp/__init__.py +0 -0
  327. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/mcp/http_client.py +0 -0
  328. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/mcp/server.py +0 -0
  329. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/middleware/__init__.py +0 -0
  330. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/middleware/domain_exception.py +0 -0
  331. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/middleware/error_handler.py +0 -0
  332. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/middleware/request_id.py +0 -0
  333. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/middleware/request_logging.py +0 -0
  334. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/middleware/request_size_limit.py +0 -0
  335. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/middleware/security_headers.py +0 -0
  336. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/middleware/setup.py +0 -0
  337. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/middleware/throttle.py +0 -0
  338. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/py.typed +0 -0
  339. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/security/__init__.py +0 -0
  340. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/security/webhook.py +0 -0
  341. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/use_case/__init__.py +0 -0
  342. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/use_case/protocols.py +0 -0
  343. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/validation/__init__.py +0 -0
  344. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/nene2/validation/exceptions.py +0 -0
  345. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/scripts/__init__.py +0 -0
  346. {nene2_python-1.8.52 → nene2_python-1.8.54}/src/scripts/export_openapi.py +0 -0
  347. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/__init__.py +0 -0
  348. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/conftest.py +0 -0
  349. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/example/__init__.py +0 -0
  350. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/example/comment/__init__.py +0 -0
  351. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/example/comment/test_comment_http.py +0 -0
  352. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/example/comment/test_comment_repository.py +0 -0
  353. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/example/comment/test_comment_use_case.py +0 -0
  354. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/example/conftest.py +0 -0
  355. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/example/note/__init__.py +0 -0
  356. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/example/note/test_async_note_use_case.py +0 -0
  357. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/example/note/test_list_notes.py +0 -0
  358. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/example/note/test_note_repository.py +0 -0
  359. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/example/tag/__init__.py +0 -0
  360. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/example/tag/test_tag_repository.py +0 -0
  361. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/example/tag/test_tags.py +0 -0
  362. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/example/test_cors.py +0 -0
  363. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/example/test_mcp.py +0 -0
  364. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/__init__.py +0 -0
  365. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/auth/__init__.py +0 -0
  366. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/auth/test_api_key.py +0 -0
  367. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/auth/test_bearer_token.py +0 -0
  368. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/auth/test_make_require_auth.py +0 -0
  369. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/auth/test_token_issuer.py +0 -0
  370. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/cache/__init__.py +0 -0
  371. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/cache/test_ttl.py +0 -0
  372. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/config/__init__.py +0 -0
  373. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/config/test_settings.py +0 -0
  374. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/database/__init__.py +0 -0
  375. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/database/test_transaction.py +0 -0
  376. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/database/test_utils.py +0 -0
  377. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/http/__init__.py +0 -0
  378. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/http/test_etag.py +0 -0
  379. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/http/test_health.py +0 -0
  380. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/http/test_pagination.py +0 -0
  381. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/http/test_problem_details.py +0 -0
  382. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/log/__init__.py +0 -0
  383. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/log/test_setup.py +0 -0
  384. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/mcp/__init__.py +0 -0
  385. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/mcp/test_http_client.py +0 -0
  386. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/mcp/test_server.py +0 -0
  387. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/middleware/__init__.py +0 -0
  388. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/middleware/test_error_handler.py +0 -0
  389. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/middleware/test_request_id.py +0 -0
  390. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/middleware/test_request_logging.py +0 -0
  391. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/middleware/test_request_size_limit.py +0 -0
  392. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/middleware/test_security_headers.py +0 -0
  393. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/middleware/test_setup_middlewares.py +0 -0
  394. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
  395. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/middleware/test_throttle.py +0 -0
  396. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/security/__init__.py +0 -0
  397. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/security/test_webhook.py +0 -0
  398. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/use_case/__init__.py +0 -0
  399. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/use_case/test_protocols.py +0 -0
  400. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/use_case/test_run_in_threadpool.py +0 -0
  401. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/validation/__init__.py +0 -0
  402. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/nene2/validation/test_exceptions.py +0 -0
  403. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/scripts/__init__.py +0 -0
  404. {nene2_python-1.8.52 → nene2_python-1.8.54}/tests/scripts/test_export_openapi.py +0 -0
  405. {nene2_python-1.8.52 → nene2_python-1.8.54}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nene2-python
3
- Version: 1.8.52
3
+ Version: 1.8.54
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,301 @@
1
+ # FT182: email モジュール
2
+
3
+ **日付**: 2026-05-21
4
+ **テーマ**: MIME メッセージ構築・ヘッダーエンコード・パース・アドレス操作
5
+ **セキュリティ診断**: なし(182 % 3 = 2)
6
+
7
+ ---
8
+
9
+ ## 概要
10
+
11
+ Python 標準ライブラリの `email` モジュールを検証する。
12
+ プレーンテキスト・HTML・添付ファイル付きメールの構築、RFC 2047 ヘッダーエンコード、
13
+ 生バイト列からのパース、アドレスのバリデーション・整形を網羅する。
14
+ FastAPI エンドポイントとして実装し、インプット検証と添付ファイルの安全な扱いまで確認する。
15
+
16
+ ---
17
+
18
+ ## 実装したサンプルアプリ
19
+
20
+ **場所**: `/home/xi/docker/nene2-python-FT/ft182-email/`
21
+
22
+ ### 主要機能
23
+
24
+ | 関数/クラス | 概要 |
25
+ |---|---|
26
+ | `validate_email_address(address)` | メールアドレスの基本フォーマット検証(RFC 5322 簡易正規表現) |
27
+ | `parse_address(header_value)` | `'Display Name <email>'` 形式をパース |
28
+ | `extract_addresses(header_value)` | カンマ区切りのアドレスリストをパース |
29
+ | `encode_header_value(text, charset)` | 非 ASCII を RFC 2047 Base64 エンコード |
30
+ | `decode_header_value(encoded)` | RFC 2047 エンコードをデコード |
31
+ | `build_simple_email(...)` | `EmailMessage` でプレーンテキストメールを構築 |
32
+ | `build_html_email(...)` | `MIMEMultipart("alternative")` で HTML メールを構築 |
33
+ | `build_email_with_attachment(...)` | `MIMEMultipart` + `MIMEBase` で添付ファイル付きメールを構築 |
34
+ | `parse_email(raw)` | 生バイト列からメールをパース(`ParsedEmail` 返却) |
35
+ | `format_address(display_name, email_address)` | 表示名付きアドレスに整形 |
36
+ | `ParsedEmail` | `@dataclass(frozen=True, slots=True)` — パース済みメール |
37
+ | `AddressPart` | `NamedTuple` — アドレスの構造化表現 |
38
+
39
+ ### HTTP エンドポイント
40
+
41
+ | メソッド | パス | 概要 |
42
+ |---|---|---|
43
+ | POST | `/build/simple` | プレーンテキストメールを hex で返す |
44
+ | POST | `/build/html` | HTML マルチパートメールを hex で返す |
45
+ | POST | `/build/attachment` | 添付ファイル付きメールを hex で返す |
46
+ | POST | `/parse` | 生メール(hex)をパースして構造化レスポンスを返す |
47
+ | POST | `/headers/encode` | RFC 2047 エンコード |
48
+ | POST | `/headers/decode` | RFC 2047 デコード |
49
+ | POST | `/addresses/extract` | カンマ区切りアドレスリストを構造化パース |
50
+ | POST | `/addresses/validate` | メールアドレスのフォーマット検証 |
51
+ | POST | `/addresses/format` | 表示名付きアドレスに整形 |
52
+
53
+ ---
54
+
55
+ ## テスト結果
56
+
57
+ **57 passed**(初回 55 通過 → F-1 修正後 57 全通過)
58
+
59
+ ```
60
+ 57 passed in 0.37s
61
+ ```
62
+
63
+ mypy: Success / ruff: All checks passed / pip-audit: PYSEC-2025-183(継続監視)
64
+
65
+ ---
66
+
67
+ ## 摩擦ポイント
68
+
69
+ ### F-1: `app = create_app()` をルート定義より前に置くと全エンドポイントが 404 になる(深刻度: 高)
70
+
71
+ **事象**: `app.py` の先頭付近で `router = APIRouter()` を定義し、
72
+ `app = create_app()` を呼んでから `@router.post(...)` デコレータを書いた。
73
+ TestClient で全エンドポイントが 404 を返す。
74
+
75
+ **原因**: FastAPI の `include_router()` は呼び出し時点の `router.routes` を
76
+ アプリケーションにコピーする。`@router.post(...)` デコレータが実行される前に
77
+ `include_router(router)` を呼ぶと、空のルーターを取り込む。
78
+ Python モジュールは上から順に実行されるため、
79
+ デコレータより前に `app = create_app()` が書かれているとルートが登録されない。
80
+
81
+ **対応**: `create_app()` 関数の定義と `app = create_app()` の呼び出しをファイルの末尾、
82
+ 全 `@router.post(...)` デコレータの後に移動した。
83
+
84
+ ```python
85
+ # 誤: デコレータより前に include_router が実行される
86
+ router = APIRouter()
87
+ app = create_app() # ← この時点でルーターは空
88
+
89
+ @router.post("/build/simple")
90
+ def build_simple_endpoint(...): ...
91
+
92
+ # 正: デコレータで全ルートが登録された後に include_router を実行
93
+ router = APIRouter()
94
+
95
+ @router.post("/build/simple")
96
+ def build_simple_endpoint(...): ...
97
+
98
+ def create_app() -> FastAPI:
99
+ application = FastAPI(title="FT182 email")
100
+ application.include_router(router)
101
+ return application
102
+
103
+ app = create_app() # ← ここで全ルートが含まれる
104
+ ```
105
+
106
+ **副次効果**: テストで 404 が出た際に「ルーターが空」と気づくまで
107
+ エンドポイント名のタイポ・パスの誤りを疑ってしまった。
108
+ 診断手順として `python -c "from app import app; print([r.path for r in app.routes])"` が有効。
109
+
110
+ ---
111
+
112
+ ## 観察点
113
+
114
+ ### 観察1: `email` モジュールには 3 つの API が混在する
115
+
116
+ Python の `email` モジュールは長い歴史を持ち、3 種類のクラスが共存している。
117
+
118
+ | API | クラス | 用途 |
119
+ |---|---|---|
120
+ | モダン(Python 3.3+)| `EmailMessage` | シンプルなテキストメール構築に最適 |
121
+ | レガシー MIME | `MIMEMultipart`, `MIMEText`, `MIMEBase` | HTML・添付ファイルのマルチパート構築 |
122
+ | パース用 | `Message` (`email.message_from_bytes` の返り値) | 受信メールの解析 |
123
+
124
+ `EmailMessage` は `MIMEMultipart("alternative")` を意識せずに書けるが、
125
+ 添付ファイルや HTML+テキスト の細かい制御は `MIMEMultipart` の方が明確。
126
+ `parse_email()` は `email.message_from_bytes()` が `Message` を返すため、
127
+ `is_multipart()` / `walk()` でパートを手動で走査する必要がある。
128
+
129
+ ### 観察2: `parseaddr()` は非常に寛容
130
+
131
+ ```python
132
+ from email.utils import parseaddr
133
+
134
+ parseaddr("alice@example.com") # → ('', 'alice@example.com')
135
+ parseaddr("Alice <alice@example.com>") # → ('Alice', 'alice@example.com')
136
+ parseaddr("Not An Email") # → ('', 'Not') ← 驚き!
137
+ parseaddr("") # → ('', '')
138
+ ```
139
+
140
+ `parseaddr("Not An Email")` は `('', 'Not')` を返す。
141
+ 単語の最初の部分を「アドレス」として解釈する。
142
+ `addr` が空文字列かどうかでしか「アドレスなし」を検出できず、
143
+ 不正な文字列でも `None` を返さない。
144
+ アプリレベルで `validate_email_address()` による追加チェックが必要。
145
+
146
+ ### 観察3: 添付ファイルのファイル名はサニタイズが必須
147
+
148
+ ```python
149
+ safe_filename = re.sub(r"[^\w\-.]", "_", attachment_filename)[:255]
150
+ ```
151
+
152
+ `../../etc/passwd` のようなパストラバーサル文字列が `Content-Disposition: attachment; filename=` に
153
+ そのまま入るとクライアントが危険な場所に保存する可能性がある。
154
+ `re.sub` で英数字・ハイフン・ドット以外を `_` に変換することで無害化する。
155
+ `../../etc/passwd` → `__.._.._etc_passwd` となり意図が変わるが、
156
+ ファイル名として安全になる。
157
+
158
+ ### 観察4: RFC 2047 エンコードは `email.header` で行うより手動 Base64 が明確
159
+
160
+ ```python
161
+ # 標準ライブラリ的な書き方
162
+ from email.header import Header
163
+ Header("テスト件名", "utf-8") # → '=?utf-8?b?...' だが使い方が複雑
164
+
165
+ # 手動 Base64 — 動作が明確
166
+ import base64
167
+ encoded = base64.b64encode("テスト件名".encode("utf-8")).decode("ascii")
168
+ result = f"=?utf-8?B?{encoded}?="
169
+ ```
170
+
171
+ `email.header.Header` クラスは行長の自動折り返し機能を持つが、
172
+ `make_header()` + `decode_header()` のコンビでデコードしないと復元できない。
173
+ 手動実装の方が「何を送っているか」が透明で、デバッグしやすい。
174
+
175
+ ### 観察5: `message_from_bytes()` は不正データでも例外を投げない
176
+
177
+ ```python
178
+ import email
179
+ msg = email.message_from_bytes(b"totally not an email")
180
+ # → Message オブジェクトが返る(例外なし)
181
+ msg.get("From") # → None
182
+ ```
183
+
184
+ `message_from_bytes()` は入力をどんなバイト列でも解析しようとする。
185
+ 完全に不正なデータでも `None` ではなく空の `Message` オブジェクトを返す。
186
+ `parse_email()` が `None` を返すのは、後段の処理で例外が起きた場合のみ。
187
+ 「パース失敗」の検出には From/Subject が空かどうかを追加チェックする必要がある。
188
+
189
+ ---
190
+
191
+ ## nene2-python フレームワークとの統合
192
+
193
+ - `build_simple_email()` / `build_html_email()` は通知メール送信 UseCase の内部実装として直接使える
194
+ - `parse_email()` の `ParsedEmail` は `@dataclass(frozen=True, slots=True)` なので UseCase の Output 型として適合
195
+ - `validate_email_address()` は HTTP 境界の Pydantic バリデーションと二重防御として有効
196
+ - `encode_header_value()` は日本語件名を含む全メール送信で必須になる
197
+ - Pydantic `max_length=10_485_760`(hex 換算 5MB)で添付ファイルサイズを HTTP 層で制限
198
+ - F-1 の教訓: `create_app()` ファクトリはファイル末尾に置く — nene2-python の全 FT サンドボックスで統一すべきルール
199
+
200
+ ---
201
+
202
+ ## Developer Experience (DX) Review
203
+
204
+ ### ペルソナ1: 初心者(Python 歴1年・独学中・女性・バックエンド志望)
205
+
206
+ 「メール送信機能を実装してほしい」と言われ、Python の `email` モジュールを調べている。
207
+
208
+ **ドキュメント理解**: `gzip.compress()` のような1行APIがなく、`EmailMessage` / `MIMEMultipart` / `MIMEText` を
209
+ どれを使えばよいか公式ドキュメントだけでは判断しにくい。
210
+ `email.message.Message` と `email.message.EmailMessage` が別クラスなのも混乱の源。
211
+ **事故リスク**: 高。`parseaddr()` が不正なアドレスを「有効」として返す挙動(F-2 相当)を
212
+ 知らないと、バリデーションをすり抜けた無効アドレスに送信しようとする可能性がある。
213
+ **規約の使いやすさ**: `EmailMessage.set_content()` はシンプルだが、添付ファイルを加えた途端に
214
+ `MIMEMultipart` に切り替える必要があり、一貫性がない。
215
+
216
+ ### ペルソナ2: ロースキル経験者(Python 歴3-4年・スクリプト系・男性・SES)
217
+
218
+ 過去に `smtplib` + `email.mime` を使ったスクリプトをコピーして使ったことがある。
219
+
220
+ **コピペ可能性**: `MIMEMultipart` + `MIMEText` の組み合わせは古いブログ記事に多い。
221
+ ただし `Content-Disposition: attachment; filename=` にユーザー入力をそのまま渡す
222
+ サンプルコードが多く、F-1 相当のファイル名インジェクションをそのまま踏む。
223
+ **拡張時の罠**: F-1 (app 配置の問題) は修正後も「なぜ末尾に置くのか」を理解しないまま
224
+ 他のファイルに同じミスをする可能性が高い。
225
+ **セキュリティ的な事故リスク**: 中。添付ファイル名のサニタイズ漏れは
226
+ メールクライアント依存でクライアント側のパストラバーサルになりうる。
227
+
228
+ ### ペルソナ3: フロントエンド寄り経験者(React/TS 歴4年・バックエンド転向中・ノンバイナリ)
229
+
230
+ フロントエンドから「送信ボタン」を押したときにメール送信 API を呼ぶ機能を実装している。
231
+
232
+ **エラーレスポンスの質**: 不正なアドレスや無効 hex に対して 400 を返す設計は良い。
233
+ ただし「どのフィールドが不正か」を detail に含めていないため、
234
+ クライアント側でのデバッグ(どのフィールドを直せばよいか)が難しい。
235
+ **Python 固有概念の学習コスト**: `bytes.hex()` / `bytes.fromhex()` を API の境界で使うパターンは
236
+ JS の `ArrayBuffer` → `Uint8Array` の感覚と近く、理解しやすい。
237
+ RFC 2047 の `=?utf-8?B?...?=` 形式は HTTP ヘッダーとは別の概念なので説明が必要。
238
+ **事故リスク**: 低。HTTP 境界での Pydantic バリデーションが充実。
239
+
240
+ ### ペルソナ4: バックエンド経験者(Django/FastAPI 歴5-6年・男性・リードエンジニア)
241
+
242
+ 既存の Django プロジェクトにあるメール送信コードを FastAPI に移植しようとしている。
243
+
244
+ **他フレームワークとの差異**: Django は `django.core.mail.send_mail()` という高レベル API があり、
245
+ SMTP の設定・送信・バックエンド切り替えまでフレームワークが抽象化している。
246
+ Python stdlib の `email` モジュールはメッセージ構築のみで、送信は `smtplib` が別。
247
+ FastAPI には `django.core.mail` 相当はないため、このような薄い実装が必要になる。
248
+ **nene2-python の薄さへの評価**: メッセージ構築ロジックを UseCase に閉じ込めることで
249
+ テストで `smtplib` を使わずにメール内容を検証できる設計は良い。
250
+ `SendEmailUseCase` → `EmailGatewayInterface` → `SmtplibEmailGateway` の構造が自然な次ステップ。
251
+ **本番投入可能性**: メッセージ構築は本番品質。送信部分(smtplib/外部SMTP)は別途実装が必要。
252
+
253
+ ### ペルソナ5: シニアエンジニア(設計・コードレビュー担当・女性・10-12年)
254
+
255
+ チームのメンバーが書いた「メール送信機能」を PR レビューしている。
256
+
257
+ **コードレビューチェックポイント**:
258
+ - [x] `parseaddr()` の戻り値が空文字列チェックのみで、`validate_email_address()` の追加チェックがあるか
259
+ - [x] `Content-Disposition: attachment; filename=` にユーザー入力がサニタイズされているか
260
+ - [x] `create_app()` がデコレータより後に呼ばれているか(F-1 の罠)
261
+ - [x] `message_from_bytes()` の返り値が None チェックなしに使われていないか(返り値は常に Message)
262
+
263
+ **チームでの安全なパターン**: メールアドレスは `parseaddr()` 後に必ず `validate_email_address()` を
264
+ 通す二重チェックを社内標準とすること。
265
+ **ツール追加の必要性**: `app = create_app()` の配置問題は静的解析で検出できない。
266
+ `create_app()` を呼ぶ前に `router.routes` が空かどうかをアサートするテストケースを
267
+ 毎回書くルールにすることで防げる。
268
+
269
+ ### ペルソナ6: 設計者・ポリシー照合(nene2-python 設計ポリシー目線)
270
+
271
+ **ポリシー達成度**: 高
272
+ **「初心者でも安全な API」達成度**: 中
273
+ — `parseaddr()` の寛容さと `create_app()` の配置問題は初心者が踏みやすい罠。
274
+ 特に F-1 は「テストが全部 404」という分かりやすい症状だが、原因に気づくまでが辛い。
275
+ **設計上の負債**: `create_app()` をファイル末尾に置くルールが CLAUDE.md に明記されていない。
276
+ FT177 から APIRouter パターンを使い始めているが、この制約はどこにも書かれていない。
277
+ **Follow-up Issue 候補**: CLAUDE.md への `create_app()` 配置ルール追記
278
+
279
+ ---
280
+
281
+ ## Follow-up Issues
282
+
283
+ | 優先度 | タイトル | 種別 |
284
+ |---|---|---|
285
+ | 中 | CLAUDE.md に「`create_app()` はファイル末尾・全デコレータの後に定義する」ルールを追記 | docs |
286
+ | 低 | `parseaddr()` の寛容な挙動を How-to ドキュメントに記載(二重チェックパターン) | docs |
287
+
288
+ ---
289
+
290
+ ## まとめ
291
+
292
+ FT182 では `email` モジュールを中心に、MIME メッセージ構築・ヘッダーエンコード・パース・
293
+ アドレス操作を実装した。57 テストが全通過し、mypy/ruff も問題なし。
294
+
295
+ 最大の発見は F-1: `app = create_app()` をルート定義より前に置くと
296
+ `include_router()` が空のルーターをコピーして全エンドポイントが 404 になる問題。
297
+ FastAPI の `include_router()` がコール時点のスナップショットを取るため、
298
+ `app = create_app()` はファイル末尾に置くルールが必要。
299
+ この制約を CLAUDE.md に追記することを Follow-up Issue として記録した。
300
+
301
+ v1.8.53 としてリリース。