nene2-python 1.8.56__tar.gz → 1.8.58__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 (409) hide show
  1. {nene2_python-1.8.56 → nene2_python-1.8.58}/PKG-INFO +1 -1
  2. nene2_python-1.8.58/docs/field-trials/2026-05-field-trial-186.md +299 -0
  3. nene2_python-1.8.58/docs/field-trials/2026-05-field-trial-187.md +261 -0
  4. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/INDEX.md +4 -2
  5. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/todo/current.md +8 -5
  6. {nene2_python-1.8.56 → nene2_python-1.8.58}/pyproject.toml +1 -1
  7. {nene2_python-1.8.56 → nene2_python-1.8.58}/.env.example +0 -0
  8. {nene2_python-1.8.56 → nene2_python-1.8.58}/.github/workflows/ci.yml +0 -0
  9. {nene2_python-1.8.56 → nene2_python-1.8.58}/.github/workflows/docs.yml +0 -0
  10. {nene2_python-1.8.56 → nene2_python-1.8.58}/.github/workflows/publish.yml +0 -0
  11. {nene2_python-1.8.56 → nene2_python-1.8.58}/.gitignore +0 -0
  12. {nene2_python-1.8.56 → nene2_python-1.8.58}/.vitepress/config.mts +0 -0
  13. {nene2_python-1.8.56 → nene2_python-1.8.58}/.vitepress/theme/custom.css +0 -0
  14. {nene2_python-1.8.56 → nene2_python-1.8.58}/.vitepress/theme/index.ts +0 -0
  15. {nene2_python-1.8.56 → nene2_python-1.8.58}/AGENTS.md +0 -0
  16. {nene2_python-1.8.56 → nene2_python-1.8.58}/CHANGELOG.md +0 -0
  17. {nene2_python-1.8.56 → nene2_python-1.8.58}/CLAUDE.md +0 -0
  18. {nene2_python-1.8.56 → nene2_python-1.8.58}/Dockerfile +0 -0
  19. {nene2_python-1.8.56 → nene2_python-1.8.58}/LICENSE +0 -0
  20. {nene2_python-1.8.56 → nene2_python-1.8.58}/README.md +0 -0
  21. {nene2_python-1.8.56 → nene2_python-1.8.58}/alembic/README +0 -0
  22. {nene2_python-1.8.56 → nene2_python-1.8.58}/alembic/env.py +0 -0
  23. {nene2_python-1.8.56 → nene2_python-1.8.58}/alembic/script.py.mako +0 -0
  24. {nene2_python-1.8.56 → nene2_python-1.8.58}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
  25. {nene2_python-1.8.56 → nene2_python-1.8.58}/alembic.ini +0 -0
  26. {nene2_python-1.8.56 → nene2_python-1.8.58}/compose.yaml +0 -0
  27. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/adr/0001-toolchain.md +0 -0
  28. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/adr/0002-clean-architecture.md +0 -0
  29. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/adr/0003-security-first.md +0 -0
  30. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/adr/0004-ai-first-design.md +0 -0
  31. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/adr/0005-logging.md +0 -0
  32. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/adr/0006-rate-limiting.md +0 -0
  33. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/adr/0009-mcp-design.md +0 -0
  34. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/adr/0010-async-use-case.md +0 -0
  35. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
  36. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/de/index.md +0 -0
  37. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/de/tutorials/getting-started.md +0 -0
  38. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/explanation/architecture.md +0 -0
  39. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/explanation/design-philosophy.md +0 -0
  40. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-1.md +0 -0
  41. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-10.md +0 -0
  42. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-100.md +0 -0
  43. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-101.md +0 -0
  44. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-102.md +0 -0
  45. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-103.md +0 -0
  46. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-104.md +0 -0
  47. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-105.md +0 -0
  48. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-106.md +0 -0
  49. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-107.md +0 -0
  50. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-108.md +0 -0
  51. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-109.md +0 -0
  52. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-11.md +0 -0
  53. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-110.md +0 -0
  54. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-111.md +0 -0
  55. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-112.md +0 -0
  56. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-113.md +0 -0
  57. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-114.md +0 -0
  58. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-115.md +0 -0
  59. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-116.md +0 -0
  60. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-117.md +0 -0
  61. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-118.md +0 -0
  62. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-119.md +0 -0
  63. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-12.md +0 -0
  64. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-120.md +0 -0
  65. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-121.md +0 -0
  66. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-122.md +0 -0
  67. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-123.md +0 -0
  68. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-124.md +0 -0
  69. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-125.md +0 -0
  70. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-126.md +0 -0
  71. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-127.md +0 -0
  72. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-128.md +0 -0
  73. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-129.md +0 -0
  74. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-13.md +0 -0
  75. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-130.md +0 -0
  76. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-131.md +0 -0
  77. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-132.md +0 -0
  78. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-133.md +0 -0
  79. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-134.md +0 -0
  80. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-135.md +0 -0
  81. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-136.md +0 -0
  82. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-137.md +0 -0
  83. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-138.md +0 -0
  84. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-139.md +0 -0
  85. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-14.md +0 -0
  86. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-140.md +0 -0
  87. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-141.md +0 -0
  88. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-142.md +0 -0
  89. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-143.md +0 -0
  90. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-144.md +0 -0
  91. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-145.md +0 -0
  92. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-146.md +0 -0
  93. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-147.md +0 -0
  94. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-148.md +0 -0
  95. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-149.md +0 -0
  96. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-15.md +0 -0
  97. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-150.md +0 -0
  98. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-151.md +0 -0
  99. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-152.md +0 -0
  100. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-153.md +0 -0
  101. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-154.md +0 -0
  102. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-155.md +0 -0
  103. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-156.md +0 -0
  104. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-157.md +0 -0
  105. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-158.md +0 -0
  106. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-159.md +0 -0
  107. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-16.md +0 -0
  108. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-160.md +0 -0
  109. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-161.md +0 -0
  110. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-162.md +0 -0
  111. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-163.md +0 -0
  112. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-164.md +0 -0
  113. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-165.md +0 -0
  114. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-166.md +0 -0
  115. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-167.md +0 -0
  116. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-168.md +0 -0
  117. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-169.md +0 -0
  118. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-17.md +0 -0
  119. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-170.md +0 -0
  120. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-171.md +0 -0
  121. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-172.md +0 -0
  122. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-173.md +0 -0
  123. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-174.md +0 -0
  124. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-175.md +0 -0
  125. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-176.md +0 -0
  126. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-177.md +0 -0
  127. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-178.md +0 -0
  128. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-179.md +0 -0
  129. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-18.md +0 -0
  130. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-180.md +0 -0
  131. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-181.md +0 -0
  132. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-182.md +0 -0
  133. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-183.md +0 -0
  134. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-184.md +0 -0
  135. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-185.md +0 -0
  136. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-19.md +0 -0
  137. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-2.md +0 -0
  138. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-20.md +0 -0
  139. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-21.md +0 -0
  140. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-22.md +0 -0
  141. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-23.md +0 -0
  142. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-24.md +0 -0
  143. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-25.md +0 -0
  144. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-26.md +0 -0
  145. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-27.md +0 -0
  146. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-28.md +0 -0
  147. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-29.md +0 -0
  148. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-3.md +0 -0
  149. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-30.md +0 -0
  150. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-31.md +0 -0
  151. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-32.md +0 -0
  152. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-33.md +0 -0
  153. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-34.md +0 -0
  154. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-35.md +0 -0
  155. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-36.md +0 -0
  156. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-37.md +0 -0
  157. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-38.md +0 -0
  158. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-39.md +0 -0
  159. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-4.md +0 -0
  160. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-40.md +0 -0
  161. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-41.md +0 -0
  162. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-42.md +0 -0
  163. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-43.md +0 -0
  164. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-44.md +0 -0
  165. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-45.md +0 -0
  166. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-46.md +0 -0
  167. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-47.md +0 -0
  168. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-48.md +0 -0
  169. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-49.md +0 -0
  170. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-5.md +0 -0
  171. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-50.md +0 -0
  172. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-51.md +0 -0
  173. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-52.md +0 -0
  174. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-53.md +0 -0
  175. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-54.md +0 -0
  176. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-55.md +0 -0
  177. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-56.md +0 -0
  178. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-57.md +0 -0
  179. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-58.md +0 -0
  180. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-59.md +0 -0
  181. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-6.md +0 -0
  182. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-60.md +0 -0
  183. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-61.md +0 -0
  184. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-62.md +0 -0
  185. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-63.md +0 -0
  186. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-64.md +0 -0
  187. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-65.md +0 -0
  188. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-66.md +0 -0
  189. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-67.md +0 -0
  190. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-68.md +0 -0
  191. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-69.md +0 -0
  192. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-7.md +0 -0
  193. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-70.md +0 -0
  194. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-71.md +0 -0
  195. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-72.md +0 -0
  196. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-73.md +0 -0
  197. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-74.md +0 -0
  198. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-75.md +0 -0
  199. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-76.md +0 -0
  200. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-77.md +0 -0
  201. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-78.md +0 -0
  202. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-79.md +0 -0
  203. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-8.md +0 -0
  204. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-80.md +0 -0
  205. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-81.md +0 -0
  206. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-82.md +0 -0
  207. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-83.md +0 -0
  208. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-84.md +0 -0
  209. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-85.md +0 -0
  210. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-86.md +0 -0
  211. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-87.md +0 -0
  212. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-88.md +0 -0
  213. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-89.md +0 -0
  214. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-9.md +0 -0
  215. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-90.md +0 -0
  216. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-91.md +0 -0
  217. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-92.md +0 -0
  218. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-93.md +0 -0
  219. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-94.md +0 -0
  220. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-95.md +0 -0
  221. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-96.md +0 -0
  222. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-97.md +0 -0
  223. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-98.md +0 -0
  224. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/field-trials/2026-05-field-trial-99.md +0 -0
  225. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/fr/index.md +0 -0
  226. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/fr/tutorials/getting-started.md +0 -0
  227. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/how-to/add-new-domain.md +0 -0
  228. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/how-to/api-versioning.md +0 -0
  229. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/how-to/async-use-case.md +0 -0
  230. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/how-to/background-tasks.md +0 -0
  231. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/how-to/configure-auth.md +0 -0
  232. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/how-to/cors.md +0 -0
  233. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/how-to/custom-auth-middleware.md +0 -0
  234. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/how-to/dependency-injection.md +0 -0
  235. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/how-to/domain-events.md +0 -0
  236. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/how-to/file-upload.md +0 -0
  237. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/how-to/lifespan-and-app-state.md +0 -0
  238. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/how-to/middleware-stack.md +0 -0
  239. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/how-to/new-project.md +0 -0
  240. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/how-to/problem-details.md +0 -0
  241. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/how-to/response-patterns.md +0 -0
  242. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/how-to/run-tests.md +0 -0
  243. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/how-to/soft-delete.md +0 -0
  244. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/how-to/sqlalchemy-repository.md +0 -0
  245. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/how-to/streaming.md +0 -0
  246. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/how-to/structured-logging.md +0 -0
  247. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/how-to/validation.md +0 -0
  248. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/how-to/webhook.md +0 -0
  249. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/howto/mcp-setup.md +0 -0
  250. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/index.md +0 -0
  251. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/ja/explanation/architecture.md +0 -0
  252. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/ja/explanation/design-philosophy.md +0 -0
  253. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/ja/how-to/add-new-domain.md +0 -0
  254. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/ja/how-to/configure-auth.md +0 -0
  255. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/ja/how-to/new-project.md +0 -0
  256. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/ja/how-to/run-tests.md +0 -0
  257. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
  258. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/ja/howto/mcp-setup.md +0 -0
  259. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/ja/index.md +0 -0
  260. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/ja/reference/api.md +0 -0
  261. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/ja/reference/configuration.md +0 -0
  262. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/ja/reference/framework-modules.md +0 -0
  263. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/ja/tutorials/first-domain.md +0 -0
  264. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/ja/tutorials/getting-started.md +0 -0
  265. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/pt-br/index.md +0 -0
  266. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/pt-br/tutorials/getting-started.md +0 -0
  267. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/reference/api.md +0 -0
  268. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/reference/configuration.md +0 -0
  269. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/reference/framework-modules.md +0 -0
  270. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/roadmap.md +0 -0
  271. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/templates/field-trial-report.md +0 -0
  272. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/tutorials/first-domain.md +0 -0
  273. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/tutorials/getting-started.md +0 -0
  274. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/zh/index.md +0 -0
  275. {nene2_python-1.8.56 → nene2_python-1.8.58}/docs/zh/tutorials/getting-started.md +0 -0
  276. {nene2_python-1.8.56 → nene2_python-1.8.58}/package-lock.json +0 -0
  277. {nene2_python-1.8.56 → nene2_python-1.8.58}/package.json +0 -0
  278. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/example/__init__.py +0 -0
  279. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/example/__main__.py +0 -0
  280. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/example/app.py +0 -0
  281. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/example/comment/__init__.py +0 -0
  282. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/example/comment/entity.py +0 -0
  283. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/example/comment/exceptions.py +0 -0
  284. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/example/comment/handler.py +0 -0
  285. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/example/comment/repository.py +0 -0
  286. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/example/comment/sqlalchemy_repository.py +0 -0
  287. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/example/comment/use_case.py +0 -0
  288. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/example/mcp.py +0 -0
  289. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/example/note/__init__.py +0 -0
  290. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/example/note/async_use_case.py +0 -0
  291. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/example/note/entity.py +0 -0
  292. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/example/note/exceptions.py +0 -0
  293. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/example/note/handler.py +0 -0
  294. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/example/note/repository.py +0 -0
  295. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/example/note/sqlalchemy_repository.py +0 -0
  296. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/example/note/use_case.py +0 -0
  297. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/example/schema.py +0 -0
  298. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/example/tag/__init__.py +0 -0
  299. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/example/tag/entity.py +0 -0
  300. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/example/tag/exceptions.py +0 -0
  301. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/example/tag/handler.py +0 -0
  302. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/example/tag/repository.py +0 -0
  303. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/example/tag/sqlalchemy_repository.py +0 -0
  304. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/example/tag/use_case.py +0 -0
  305. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/__init__.py +0 -0
  306. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/auth/__init__.py +0 -0
  307. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/auth/api_key.py +0 -0
  308. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/auth/bearer_token.py +0 -0
  309. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/auth/deps.py +0 -0
  310. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/auth/exceptions.py +0 -0
  311. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/auth/interfaces.py +0 -0
  312. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/auth/local_verifier.py +0 -0
  313. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/cache/__init__.py +0 -0
  314. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/cache/ttl.py +0 -0
  315. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/config/__init__.py +0 -0
  316. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/config/settings.py +0 -0
  317. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/database/__init__.py +0 -0
  318. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/database/exceptions.py +0 -0
  319. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/database/health.py +0 -0
  320. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/database/interfaces.py +0 -0
  321. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/database/sqlalchemy_executor.py +0 -0
  322. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/database/utils.py +0 -0
  323. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/http/__init__.py +0 -0
  324. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/http/etag.py +0 -0
  325. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/http/health.py +0 -0
  326. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/http/pagination.py +0 -0
  327. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/http/problem_details.py +0 -0
  328. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/log/__init__.py +0 -0
  329. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/log/setup.py +0 -0
  330. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/mcp/__init__.py +0 -0
  331. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/mcp/http_client.py +0 -0
  332. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/mcp/server.py +0 -0
  333. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/middleware/__init__.py +0 -0
  334. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/middleware/domain_exception.py +0 -0
  335. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/middleware/error_handler.py +0 -0
  336. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/middleware/request_id.py +0 -0
  337. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/middleware/request_logging.py +0 -0
  338. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/middleware/request_size_limit.py +0 -0
  339. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/middleware/security_headers.py +0 -0
  340. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/middleware/setup.py +0 -0
  341. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/middleware/throttle.py +0 -0
  342. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/py.typed +0 -0
  343. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/security/__init__.py +0 -0
  344. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/security/webhook.py +0 -0
  345. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/use_case/__init__.py +0 -0
  346. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/use_case/protocols.py +0 -0
  347. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/validation/__init__.py +0 -0
  348. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/nene2/validation/exceptions.py +0 -0
  349. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/scripts/__init__.py +0 -0
  350. {nene2_python-1.8.56 → nene2_python-1.8.58}/src/scripts/export_openapi.py +0 -0
  351. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/__init__.py +0 -0
  352. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/conftest.py +0 -0
  353. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/example/__init__.py +0 -0
  354. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/example/comment/__init__.py +0 -0
  355. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/example/comment/test_comment_http.py +0 -0
  356. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/example/comment/test_comment_repository.py +0 -0
  357. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/example/comment/test_comment_use_case.py +0 -0
  358. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/example/conftest.py +0 -0
  359. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/example/note/__init__.py +0 -0
  360. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/example/note/test_async_note_use_case.py +0 -0
  361. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/example/note/test_list_notes.py +0 -0
  362. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/example/note/test_note_repository.py +0 -0
  363. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/example/tag/__init__.py +0 -0
  364. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/example/tag/test_tag_repository.py +0 -0
  365. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/example/tag/test_tags.py +0 -0
  366. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/example/test_cors.py +0 -0
  367. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/example/test_mcp.py +0 -0
  368. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/__init__.py +0 -0
  369. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/auth/__init__.py +0 -0
  370. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/auth/test_api_key.py +0 -0
  371. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/auth/test_bearer_token.py +0 -0
  372. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/auth/test_make_require_auth.py +0 -0
  373. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/auth/test_token_issuer.py +0 -0
  374. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/cache/__init__.py +0 -0
  375. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/cache/test_ttl.py +0 -0
  376. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/config/__init__.py +0 -0
  377. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/config/test_settings.py +0 -0
  378. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/database/__init__.py +0 -0
  379. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/database/test_transaction.py +0 -0
  380. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/database/test_utils.py +0 -0
  381. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/http/__init__.py +0 -0
  382. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/http/test_etag.py +0 -0
  383. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/http/test_health.py +0 -0
  384. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/http/test_pagination.py +0 -0
  385. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/http/test_problem_details.py +0 -0
  386. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/log/__init__.py +0 -0
  387. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/log/test_setup.py +0 -0
  388. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/mcp/__init__.py +0 -0
  389. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/mcp/test_http_client.py +0 -0
  390. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/mcp/test_server.py +0 -0
  391. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/middleware/__init__.py +0 -0
  392. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/middleware/test_error_handler.py +0 -0
  393. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/middleware/test_request_id.py +0 -0
  394. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/middleware/test_request_logging.py +0 -0
  395. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/middleware/test_request_size_limit.py +0 -0
  396. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/middleware/test_security_headers.py +0 -0
  397. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/middleware/test_setup_middlewares.py +0 -0
  398. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
  399. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/middleware/test_throttle.py +0 -0
  400. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/security/__init__.py +0 -0
  401. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/security/test_webhook.py +0 -0
  402. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/use_case/__init__.py +0 -0
  403. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/use_case/test_protocols.py +0 -0
  404. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/use_case/test_run_in_threadpool.py +0 -0
  405. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/validation/__init__.py +0 -0
  406. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/nene2/validation/test_exceptions.py +0 -0
  407. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/scripts/__init__.py +0 -0
  408. {nene2_python-1.8.56 → nene2_python-1.8.58}/tests/scripts/test_export_openapi.py +0 -0
  409. {nene2_python-1.8.56 → nene2_python-1.8.58}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nene2-python
3
- Version: 1.8.56
3
+ Version: 1.8.58
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,299 @@
1
+ # FT186: functools
2
+
3
+ **日付**: 2026-05-21
4
+ **テーマ**: functools モジュール — キャッシュ・部分適用・デコレーター・比較・ディスパッチ
5
+ **セキュリティ診断**: あり(186 % 3 = 0)
6
+ **クラッカーペンテスト**: なし(186 % 4 = 2)
7
+
8
+ ---
9
+
10
+ ## 概要
11
+
12
+ Python 標準ライブラリ `functools` は高階関数・関数オブジェクトのユーティリティ集である。
13
+ `lru_cache`・`cache`(メモ化)、`partial`(部分適用)、`reduce`(畳み込み)、`wraps`(デコレーターメタデータ保持)、`total_ordering`(比較演算子補完)、`singledispatch`(型ディスパッチ)、`cached_property`(インスタンスレベルキャッシュ)を検証した。
14
+
15
+ ---
16
+
17
+ ## 実装したサンプルアプリ
18
+
19
+ **場所**: `/home/xi/docker/nene2-python-FT/ft186-functools/`
20
+
21
+ ### 主要機能
22
+
23
+ | 関数/クラス | 概要 |
24
+ |---|---|
25
+ | `fibonacci(n)` | `lru_cache` でメモ化したフィボナッチ数列 |
26
+ | `fibonacci_safe(n)` | 上限チェック付きフィボナッチ(DoS 対策)|
27
+ | `get_fibonacci_cache_stats()` | キャッシュ統計取得 |
28
+ | `factorial(n)` | `functools.cache` で無制限メモ化した階乗 |
29
+ | `power(base, exponent)` / `square` / `cube` | `partial` で指数固定関数 |
30
+ | `make_multiplier(factor)` | `partial` で乗数固定の乗算関数を生成 |
31
+ | `product(numbers)` | `reduce` でリストの積 |
32
+ | `flatten_once(nested)` | `reduce` で1段階展開 |
33
+ | `timing_decorator(func)` | `@wraps` でメタデータ保持するタイミングデコレーター |
34
+ | `retry(max_attempts)` | `@wraps` を使ったリトライデコレーターファクトリ |
35
+ | `Version` | `@total_ordering` + `dataclass` でセマンティックバージョン比較 |
36
+ | `latest_version(versions)` | `reduce` + `Version` で最新バージョン検出 |
37
+ | `serialize(value)` | `singledispatch` で型別シリアライズ |
38
+ | `DataProcessor` | `cached_property` でコストの高い計算をキャッシュ |
39
+
40
+ ### HTTP エンドポイント
41
+
42
+ | メソッド | パス | 概要 |
43
+ |---|---|---|
44
+ | POST | `/fibonacci` | lru_cache フィボナッチ |
45
+ | POST | `/factorial` | cache 階乗 |
46
+ | POST | `/power` | partial square/cube |
47
+ | POST | `/multiply` | partial 乗算 |
48
+ | POST | `/product` | reduce 積 |
49
+ | POST | `/flatten` | reduce 展開 |
50
+ | POST | `/version/latest` | total_ordering + reduce 最新バージョン |
51
+ | POST | `/serialize` | singledispatch シリアライズ |
52
+ | POST | `/stats` | cached_property 統計 |
53
+
54
+ ---
55
+
56
+ ## テスト結果
57
+
58
+ **64 passed**
59
+
60
+ ```
61
+ 64 passed in 0.36s
62
+ ```
63
+
64
+ mypy --strict: Success
65
+ ruff check: All checks passed
66
+ pip-audit: PYSEC-2025-183 (PyJWT via mcp transitive dep — 許容済み)
67
+
68
+ ---
69
+
70
+ ## 摩擦ポイント
71
+
72
+ ### F-1: `base**exponent` の戻り値型が `Any`(mypy --strict)(深刻度: 低)
73
+
74
+ **事象**: `return base**exponent` をそのまま返すと `Returning Any from function declared to return "float"` エラー。Python の `**` 演算子は型に応じて `int | float | complex` を返すため、mypy が型推論できない。
75
+
76
+ **原因**: `float ** float` の演算子オーバーロードが `float` ではなく `Any` として型付けされている(typeshed の制約)。
77
+
78
+ **対応**: `return float(base**exponent)` と明示的にキャストすることで解決。
79
+
80
+ ---
81
+
82
+ ## 観察点
83
+
84
+ ### 観察1: `lru_cache` の `maxsize` と DoS 対策
85
+
86
+ ```python
87
+ @functools.lru_cache(maxsize=256)
88
+ def fibonacci(n: int) -> int:
89
+ ...
90
+
91
+ def fibonacci_safe(n: int) -> int:
92
+ if n > 90:
93
+ raise ValueError(...)
94
+ return fibonacci(n)
95
+ ```
96
+
97
+ `lru_cache` は入力値ごとにキャッシュエントリを作成する。`maxsize=None`(= `functools.cache`)の場合、引数のバリエーションが多い用途では無制限にメモリを使い続ける可能性がある。入力値を `fibonacci_safe` でサニタイズしてから `lru_cache` 付き関数を呼ぶことで、キャッシュの肥大化を防ぐ。
98
+
99
+ ### 観察2: `functools.cache` vs `lru_cache(maxsize=None)`
100
+
101
+ ```python
102
+ @functools.cache # Python 3.9+
103
+ def factorial(n: int) -> int: ...
104
+
105
+ @functools.lru_cache(maxsize=None) # Python 3.2+
106
+ def factorial(n: int) -> int: ...
107
+ ```
108
+
109
+ `functools.cache` は `lru_cache(maxsize=None)` の簡易エイリアスで、`cache_info()` / `cache_clear()` メソッドを持つ点は同じ。`lru_cache` より書きやすく、上限なしキャッシュが明示的。
110
+
111
+ ### 観察3: `@wraps` なしでメタデータが失われる問題
112
+
113
+ ```python
114
+ # wraps なし — name が "wrapper" になる
115
+ def bad_decorator(func):
116
+ def wrapper(*args, **kwargs):
117
+ return func(*args, **kwargs)
118
+ return wrapper
119
+
120
+ # wraps あり — name が元の関数名を保持
121
+ def good_decorator(func):
122
+ @functools.wraps(func)
123
+ def wrapper(*args, **kwargs):
124
+ return func(*args, **kwargs)
125
+ return wrapper
126
+ ```
127
+
128
+ `@wraps` を省くと `__name__`・`__doc__`・`__annotations__` が全てラッパーのものに置き換わる。pytest の `--tb=short` でも関数名が `wrapper` と表示され、デバッグが困難になる。
129
+
130
+ ### 観察4: `total_ordering` と `frozen=True` の組み合わせ
131
+
132
+ `@functools.total_ordering` と `@dataclass(frozen=True)` を組み合わせる場合、`@dataclass(eq=False)` を指定しないと `dataclass` が自動生成する `__eq__` が `total_ordering` の `__eq__` を上書きしてしまう可能性がある。今回は `__eq__` と `__lt__` を明示的に実装したため問題なし。
133
+
134
+ ### 観察5: `singledispatch` と `bool` 型のディスパッチ順序
135
+
136
+ ```python
137
+ @serialize.register(bool)
138
+ def _serialize_bool(value: bool) -> str: ...
139
+
140
+ @serialize.register(int)
141
+ def _serialize_int(value: int) -> str: ...
142
+ ```
143
+
144
+ Python では `bool` は `int` のサブクラス。`@register(int)` を先に登録すると `True`/`False` も `int` ハンドラーで処理される。`bool` を意図的に分岐させたい場合は `@register(bool)` を先(または明示的に)登録する必要がある。
145
+
146
+ ---
147
+
148
+ ## セキュリティ診断(FT186 % 3 = 0)
149
+
150
+ ### 1. OWASP API Security Top 10 (2023)
151
+
152
+ #### API6: Unrestricted Access to Sensitive Business Flows — DoS via lru_cache
153
+
154
+ **状況**: `fibonacci(n)` に上限なく大きな `n` を渡せた場合、計算時間 + キャッシュメモリの両面で DoS が成立する。
155
+
156
+ **対策**: `fibonacci_safe(n)` が Pydantic の `le=90` と合わせて二重に入力を制限している。FastAPI の `Field(ge=0, le=90)` による検証は HTTP 境界で確実に発動する。**問題なし**。
157
+
158
+ #### API4: Unrestricted Resource Consumption
159
+
160
+ **状況**: `/stats` エンドポイントは `list[int]` を最大 1000 要素受け取る。`cached_property` はインスタンスごとにキャッシュするため、毎リクエストで新たな `DataProcessor` インスタンスが生成され、キャッシュは1リクエスト内のみで有効。メモリは GC に依存するが、1000 要素程度ならリスクは低い。**問題なし**。
161
+
162
+ **状況**: `/flatten` は `list[list[int]]` を最大 50 要素受け取り `reduce` で結合するが、内側リストの要素数に上限がない。理論上、巨大な内側リストを送れる。
163
+
164
+ **判定**: **MEDIUM** — 内側リストに個別の `max_length` が設定されていない(F-2 として記録)。
165
+
166
+ ### 2. インジェクション攻撃
167
+
168
+ `functools` API は外部入力を直接評価する機能を持たないため、インジェクションリスクはない。`singledispatch` の型ルーティングも実行コードを動的生成しない。**問題なし**。
169
+
170
+ ### 3. 認証・認可
171
+
172
+ 今回のエンドポイントは認証不要の計算 API のため対象外。**問題なし**。
173
+
174
+ ### 4. 入力バリデーション
175
+
176
+ | フィールド | 制約 | 評価 |
177
+ |---|---|---|
178
+ | `FibRequest.n` | `ge=0, le=90` | ✅ |
179
+ | `FactorialRequest.n` | `ge=0, le=20` | ✅ |
180
+ | `PowerRequest.operation` | `max_length=10` | ✅ |
181
+ | `ProductRequest.numbers` | `max_length=100` | ✅ |
182
+ | `FlattenRequest.nested` | `max_length=50` (外側のみ) | ⚠️ 内側なし |
183
+ | `VersionRequest.versions` | `max_length=50` | ✅ |
184
+ | `StatsRequest.data` | `max_length=1000` | ✅ |
185
+
186
+ ### 5. 情報漏洩
187
+
188
+ `retry` デコレーターがリトライ失敗時に元の例外を `raise` するため、内部エラーメッセージがそのまま上位に伝播する。FastAPI の `ErrorHandlerMiddleware` がない場合、500 レスポンスに内部エラーが含まれる可能性がある。FT のサンドボックスでは許容範囲内。**問題なし**(本番実装では `ErrorHandlerMiddleware` を追加すること)。
189
+
190
+ pip-audit: PYSEC-2025-183 (PyJWT via mcp — 許容済み、修正版待ち)
191
+
192
+ ### 6. Python/FastAPI 固有
193
+
194
+ #### ReDoS
195
+
196
+ `Version.parse()` は `.split(".")` と `int()` のみを使用。正規表現を使用しないため ReDoS リスクなし。**問題なし**。
197
+
198
+ #### functools.cache の無制限メモリ使用
199
+
200
+ `factorial` は `functools.cache`(無制限)を使用しているが、`FactorialRequest.n` が `le=20` で制限されているため、キャッシュエントリは最大 21 個。**問題なし**。
201
+
202
+ #### singledispatch の型安全性
203
+
204
+ `singledispatch` は実行時型チェックを行うため、Pydantic で検証済みの型が渡される限り不正ディスパッチは発生しない。**問題なし**。
205
+
206
+ ### セキュリティ診断まとめ
207
+
208
+ | カテゴリ | 結果 | 備考 |
209
+ |---|---|---|
210
+ | OWASP API Top 10 | 条件付き合格 | F-2 /flatten 内側リスト上限なし |
211
+ | インジェクション | 合格 | |
212
+ | 認証・認可 | 対象外 | |
213
+ | 入力バリデーション | 条件付き合格 | F-2 参照 |
214
+ | 情報漏洩 | 合格 | ErrorHandlerMiddleware 推奨 |
215
+ | Python/FastAPI 固有 | 合格 | |
216
+
217
+ **診断結果: 条件付き合格**(MEDIUM 1件 — 次 FT までに修正推奨)
218
+
219
+ ---
220
+
221
+ ## Follow-up Issues
222
+
223
+ ### F-2: `/flatten` 内側リストの要素数に上限なし(深刻度: MEDIUM)
224
+
225
+ **事象**: `FlattenRequest.nested: list[list[int]]` の外側は `max_length=50` で制限されているが、内側リストの要素数に上限がない。悪意のある入力で内側に大量要素を持つリストを送ることができる。
226
+
227
+ **対応**: Pydantic の `Annotated[list[int], Field(max_length=1000)]` を使って内側リストにも上限を設定する。
228
+
229
+ ---
230
+
231
+ ## DX Review — 6ペルソナ
232
+
233
+ ### 1. 初心者(Python 歴1年・独学中・女性・バックエンド志望)
234
+
235
+ 「関数をキャッシュする」という概念が `@lru_cache` によって視覚的に分かりやすく表現されている。デコレーター1行で劇的に高速化できる体験は印象的。
236
+
237
+ **ドキュメント理解**: `partial` の「引数を固定した新しい関数を作る」という概念は直感的。しかし `reduce` の「畳み込み」は初見では理解しにくく、`sum()` や `max()` で代替できる場合が多い旨を添えた方が親切。
238
+
239
+ **事故リスク**: 低 — ただし `lru_cache(maxsize=None)` は上限なしキャッシュであることを知らないと、長時間稼働するプロセスでメモリが肥大化するリスクがある(サンドボックスでは `fibonacci_safe` で回避済み)。
240
+
241
+ **規約の使いやすさ**: `@wraps` の必要性(デバッグ時の関数名保持)を理解するまでは省略しがち。テストで `__name__` を確認するパターンを見ることで習得できる。
242
+
243
+ ### 2. ロースキル経験者(Python 歴3-4年・スクリプト系・男性・SES)
244
+
245
+ `lru_cache` のコピペ利用は頻繁。`maxsize` を適切に設定することを忘れがちで、`None` のまま運用してメモリリークを引き起こすケースがある。
246
+
247
+ **コピペ可能性**: `timing_decorator` と `retry` はそのまま転用できるユーティリティとして高い実用性。
248
+
249
+ **拡張時の罠**: `total_ordering` + `dataclass(frozen=True)` の組み合わせで `__eq__` を省略するとデフォルトの `dataclass` が生成した `__eq__` が使われ、`total_ordering` の期待と異なる場合がある(観察4参照)。
250
+
251
+ **事故リスク**: 中(lru_cache の maxsize 設定忘れ)
252
+
253
+ ### 3. フロントエンド寄り(React/TS 歴4年・バックエンド転向中・ノンバイナリ)
254
+
255
+ TypeScript の `useMemo` / `useCallback` に近い概念として `lru_cache` / `partial` を理解できる。`singledispatch` は TypeScript のオーバーロードと比べてコードが分散するが、`@register` で後から拡張できる点は優れている。
256
+
257
+ **エラーレスポンスの質**: `/version/latest` の `400 Bad Request` + `detail` に無効バージョン文字列を含めるのは適切。
258
+
259
+ **Python 固有概念の学習コスト**: `reduce` は TS では `Array.prototype.reduce` があるので理解しやすいが、Python 3 での `reduce` の立ち位置(`functools.reduce` に格下げされた経緯)を知ると設計哲学が掴める。
260
+
261
+ **事故リスク**: 低
262
+
263
+ ### 4. バックエンド経験者(Django/FastAPI 歴5-6年・男性・リードエンジニア)
264
+
265
+ `cached_property` は Django モデルの `@property` + 手動キャッシュより洗練されている。リクエストごとに新インスタンスを作成するケースでは「1リクエスト内のみ有効なキャッシュ」として機能するため、副作用なく使いやすい。
266
+
267
+ **他フレームワークとの差異**: Django の `django.utils.functional.cached_property` は非スレッドセーフだが、`functools.cached_property` も非スレッドセーフ(Python 3.12 以前)。スレッドセーフが必要な場合は明示的なロックが必要。
268
+
269
+ **nene2 の薄さへの評価**: `functools` は nene2 フレームワークと独立しているため、どの FastAPI プロジェクトでも直接適用できる内容として評価が高い。
270
+
271
+ **事故リスク**: 低
272
+
273
+ ### 5. シニアエンジニア(設計・コードレビュー担当・女性・10-12年)
274
+
275
+ **コードレビューチェックポイント**:
276
+ - `lru_cache` の `maxsize` が適切か(サービスの想定入力範囲に合っているか)
277
+ - `cache`(無制限)を使う場合、入力バリエーションが有限であることが保証されているか
278
+ - `@wraps` が全てのデコレーター実装に付いているか
279
+ - `singledispatch` のデフォルト実装が意図した型を処理するか(意図しない型が来たときの挙動)
280
+ - `total_ordering` で `__eq__` と `__lt__` の両方が実装されているか
281
+
282
+ **チームでの安全なパターン**: `retry` デコレーターはチーム共有のユーティリティとして使えるが、リトライ間隔(`sleep`)・バックオフ・特定例外のみリトライなど実装が必要な場合は `tenacity` ライブラリの使用を推奨する。
283
+
284
+ **事故リスク**: 低
285
+
286
+ ### 6. 設計者(nene2-python 設計ポリシー目線)
287
+
288
+ **CLAUDE.md ポリシー整合性**:
289
+ - `dataclass(frozen=True, slots=True)`: `CacheStats` / `Version` で適用済み ✅
290
+ - Pydantic は HTTP 境界のみ: `app.py` の Request/Response モデルのみ ✅
291
+ - `create_app()` はファイル末尾: 適用済み ✅
292
+ - `max_length` 指定: 外側リストには設定済み(F-2: 内側リスト未設定)⚠️
293
+ - セキュリティ診断実施: FT186 % 3 = 0 → 診断実施 ✅
294
+
295
+ **初心者でも安全な API 達成度**: `fibonacci_safe` による DoS 防御ラッパーパターン(上限チェック → メモ化関数呼び出し)は安全なキャッシュ利用の模範として機能している。Pydantic の `le=` と `fibonacci_safe` の両方で二重バリデーションしている点も good practice。
296
+
297
+ ---
298
+
299
+ *バージョン: v1.8.57*
@@ -0,0 +1,261 @@
1
+ # FT187: collections
2
+
3
+ **日付**: 2026-05-21
4
+ **テーマ**: collections モジュール — Counter・defaultdict・deque・ChainMap・NamedTuple・OrderedDict
5
+ **セキュリティ診断**: なし(187 % 3 = 1)
6
+ **クラッカーペンテスト**: なし(187 % 4 = 3)
7
+
8
+ ---
9
+
10
+ ## 概要
11
+
12
+ Python 標準ライブラリ `collections` は汎用コンテナ型の拡張集である。
13
+ `Counter`(頻度カウント)、`defaultdict`(デフォルト値付き辞書)、`deque`(両端キュー)、`ChainMap`(複数辞書のビュー)、`NamedTuple`(型付き名前付きタプル)、`OrderedDict`(挿入順序保持辞書)の主要 6 型を検証した。
14
+
15
+ ---
16
+
17
+ ## 実装したサンプルアプリ
18
+
19
+ **場所**: `/home/xi/docker/nene2-python-FT/ft187-collections/`
20
+
21
+ ### 主要機能
22
+
23
+ | 関数/クラス | 概要 |
24
+ |---|---|
25
+ | `word_frequency(text)` | Counter で単語頻度を集計 |
26
+ | `top_n_words(text, n)` | `most_common(n)` で上位 N 単語を取得 |
27
+ | `character_frequency(text)` | 文字頻度を集計(空白除外)|
28
+ | `merge_counters(a, b)` | Counter の `+` 演算子でマージ |
29
+ | `subtract_counters(a, b)` | Counter の `-` 演算子で差分(正の値のみ)|
30
+ | `group_by_length(words)` | defaultdict でワード長別グループ化 |
31
+ | `build_inverted_index(docs)` | defaultdict で転置インデックス構築 |
32
+ | `count_nested(items, sep)` | ネスト defaultdict でカテゴリ別集計 |
33
+ | `BoundedHistory` | `deque(maxlen=N)` でサイズ制限付き履歴 |
34
+ | `sliding_window_average(values, window)` | deque + maxlen でスライディング平均 |
35
+ | `rotate_list(items, steps)` | `deque.rotate()` でリストをローテーション |
36
+ | `resolve_config(*layers)` | ChainMap で設定レイヤーを優先順解決 |
37
+ | `get_with_override(base, override, key)` | ChainMap で override 優先の値取得 |
38
+ | `Coordinate` | `typing.NamedTuple` で型付き座標 |
39
+ | `deduplicate_preserving_order(items)` | OrderedDict で順序保持重複除去 |
40
+ | `LruDict` | OrderedDict を使った LRU キャッシュ |
41
+
42
+ ### HTTP エンドポイント
43
+
44
+ | メソッド | パス | 概要 |
45
+ |---|---|---|
46
+ | POST | `/counter/words` | 単語頻度集計 |
47
+ | POST | `/counter/top` | 上位 N 単語 |
48
+ | POST | `/counter/chars` | 文字頻度集計 |
49
+ | POST | `/counter/merge` | カウンターマージ |
50
+ | POST | `/counter/subtract` | カウンター差分 |
51
+ | POST | `/defaultdict/group` | 長さ別グループ化 |
52
+ | POST | `/defaultdict/index` | 転置インデックス |
53
+ | POST | `/defaultdict/nested` | ネストカウント |
54
+ | POST | `/deque/sliding-window` | スライディング平均 |
55
+ | POST | `/deque/rotate` | リストローテーション |
56
+ | POST | `/chainmap/resolve` | 設定レイヤー解決 |
57
+ | POST | `/ordereddict/deduplicate` | 順序保持重複除去 |
58
+ | POST | `/namedtuple/coordinates` | 座標パース |
59
+ | POST | `/ordereddict/lru` | LRU キャッシュ操作 |
60
+
61
+ ---
62
+
63
+ ## テスト結果
64
+
65
+ **56 passed**
66
+
67
+ ```
68
+ 56 passed in 0.35s
69
+ ```
70
+
71
+ mypy --strict: Success
72
+ ruff check: All checks passed
73
+ pip-audit: PYSEC-2025-183 (PyJWT via mcp transitive dep — 許容済み)
74
+
75
+ ---
76
+
77
+ ## 摩擦ポイント
78
+
79
+ ### F-1: `parse_coordinates` の引数型に `dict[str, float | str]` を使うと mypy エラー(深刻度: 低)
80
+
81
+ **事象**: `parse_coordinates(raw: list[dict[str, float | str]])` を定義し、呼び出し側で `[{"latitude": c.latitude, ...}]` を渡したところ、mypy が `Argument 1 has incompatible type "list[dict[str, object]]"` エラーを出した。辞書リテラルの型が `dict[str, object]` として推論されるため。
82
+
83
+ **原因**: Python の辞書リテラル `{"latitude": 35.0, "label": "A"}` は `dict[str, float | str]` ではなく `dict[str, object]` として推論される(値の型が異なる場合)。
84
+
85
+ **対応**: 入力専用のデータクラス `RawCoordinate(dataclass(frozen=True, slots=True))` を定義して型安全な引数にした。`dict` を渡す代わりに `RawCoordinate` オブジェクトを渡す。
86
+
87
+ ```python
88
+ @dataclass(frozen=True, slots=True)
89
+ class RawCoordinate:
90
+ latitude: float
91
+ longitude: float
92
+ label: str = ""
93
+
94
+ def parse_coordinates(raw: list[RawCoordinate]) -> list[Coordinate]:
95
+ return [Coordinate(latitude=r.latitude, ...) for r in raw]
96
+ ```
97
+
98
+ ---
99
+
100
+ ## 観察点
101
+
102
+ ### 観察1: Counter の算術演算子
103
+
104
+ ```python
105
+ a = Counter({"x": 5, "y": 2})
106
+ b = Counter({"x": 3, "y": 4})
107
+
108
+ a + b # {"x": 8, "y": 6} — 合計
109
+ a - b # {"x": 2} — 差分(正の値のみ残る)
110
+ a & b # {"x": 3, "y": 2} — 最小値(intersection)
111
+ a | b # {"x": 5, "y": 4} — 最大値(union)
112
+ ```
113
+
114
+ `Counter` は `dict` のサブクラスで算術演算子が使えるため、集合演算的な使い方ができる。`-` の結果は正の値のみ(負になったキーは除外)になる点が直感と異なる場合がある。
115
+
116
+ ### 観察2: defaultdict のネスト — `lambda` を使ったデフォルト値
117
+
118
+ ```python
119
+ # ネストした defaultdict
120
+ result: defaultdict[str, defaultdict[str, int]] = \
121
+ defaultdict(lambda: defaultdict(int))
122
+
123
+ # 使用例
124
+ result["fruit"]["apple"] += 1
125
+ ```
126
+
127
+ `lambda: defaultdict(int)` でネストした自動生成が可能。ただし `lambda` の型は mypy で推論が難しいため、明示的な型注釈が必要になる場合がある。
128
+
129
+ ### 観察3: deque の `maxlen` による自動エビクション
130
+
131
+ ```python
132
+ dq = deque(maxlen=3)
133
+ dq.append(1) # [1]
134
+ dq.append(2) # [1, 2]
135
+ dq.append(3) # [1, 2, 3]
136
+ dq.append(4) # [2, 3, 4] — 左端の 1 が自動除去
137
+ ```
138
+
139
+ `deque(maxlen=N)` は満杯時に反対側の要素を自動除去する。スライディングウィンドウ・LRU 的な「最近 N 件だけ保持」のユースケースに適している。`appendleft` を使うと最新が先頭になる(`BoundedHistory` パターン)。
140
+
141
+ ### 観察4: ChainMap の「先頭優先」セマンティクス
142
+
143
+ ```python
144
+ chain = ChainMap(override, base)
145
+ chain["key"] # override の値を優先(なければ base を参照)
146
+ chain["new"] = "value" # 先頭の override に追加(base は変更されない)
147
+ ```
148
+
149
+ `ChainMap` は辞書のコピーを作らずビューを提供する。環境変数 → 設定ファイル → デフォルト値という優先順位を持つ設定解決に適している。`new_child()` で新しいスコープを作成することも可能。
150
+
151
+ ### 観察5: `typing.NamedTuple` vs `collections.namedtuple`
152
+
153
+ ```python
154
+ # collections.namedtuple — フィールド名のみ、型なし
155
+ Point = namedtuple("Point", ["x", "y"])
156
+
157
+ # typing.NamedTuple — 型付きフィールド、デフォルト値、メソッド定義可
158
+ class Coordinate(NamedTuple):
159
+ latitude: float
160
+ longitude: float
161
+ label: str = ""
162
+ def distance_to(self, other: "Coordinate") -> float: ...
163
+ ```
164
+
165
+ `typing.NamedTuple` は型安全でデフォルト値・メソッドを持てるため、`dataclass` の代替として不変の小さな値オブジェクトに使える。ただし `frozen=True` の `dataclass` と異なり、継承で問題が生じやすいため、単純な値型に限定するのが無難。
166
+
167
+ ### 観察6: OrderedDict を使った LRU キャッシュ
168
+
169
+ ```python
170
+ cache = OrderedDict()
171
+
172
+ def get(key):
173
+ cache.move_to_end(key) # アクセスで MRU 端へ移動
174
+ return cache[key]
175
+
176
+ def put(key, value):
177
+ if len(cache) >= capacity:
178
+ cache.popitem(last=False) # LRU 端(先頭)から除去
179
+ cache[key] = value
180
+ ```
181
+
182
+ Python 3.2+ の `dict` は挿入順序保持が保証されているが、`OrderedDict` は `move_to_end()` と `popitem(last=False/True)` を持ち、LRU 実装が簡潔に書ける。
183
+
184
+ ---
185
+
186
+ ## Follow-up Issues
187
+
188
+ 今回の FT では実装上の重大な摩擦はなかった。F-1 は mypy --strict での型推論の限界によるものであり、`dataclass` を入力型として導入することで解決した。
189
+
190
+ GitHub Issues: なし
191
+
192
+ ---
193
+
194
+ ## DX Review — 6ペルソナ
195
+
196
+ ### 1. 初心者(Python 歴1年・独学中・女性・バックエンド志望)
197
+
198
+ `Counter` は「リストの頻度を数える」という非常に頻繁に必要とされる操作をワンライナーで実現できる。`most_common(n)` の使いやすさは特に印象的で、手書きのループより明快。
199
+
200
+ **ドキュメント理解**: `defaultdict` の「キーが存在しなければデフォルト値を自動生成する」という動作は、通常の辞書で `KeyError` に何度もぶつかった後に習得するパターン。デモのネスト例は実用的で理解しやすい。
201
+
202
+ **事故リスク**: 低 — ただし `Counter` の `-` が「負値を除外する」挙動を知らないと驚く。テストで明示したことで習得しやすい。
203
+
204
+ **規約の使いやすさ**: `LruDict` のパターン(`move_to_end` + `popitem`)は初心者には高度だが、「なぜ OrderedDict を使うのか」が実例で見えると理解が進む。
205
+
206
+ ### 2. ロースキル経験者(Python 歴3-4年・スクリプト系・男性・SES)
207
+
208
+ `Counter.most_common()` はログ解析・アクセス集計でそのまま使えるため実務価値が高い。`defaultdict(list)` による転置インデックスも業務で頻繁に必要になる。
209
+
210
+ **コピペ可能性**: `BoundedHistory`(deque + maxlen)・`LruDict`・`resolve_config`(ChainMap)はそのまま流用できるユーティリティ。
211
+
212
+ **拡張時の罠**: `ChainMap` の `chain["key"] = value` は先頭辞書のみ変更する。「両方の辞書を更新したい」場合は `ChainMap` は不適切。
213
+
214
+ **事故リスク**: 低
215
+
216
+ ### 3. フロントエンド寄り(React/TS 歴4年・バックエンド転向中・ノンバイナリ)
217
+
218
+ TypeScript では `Map<string, number>` や `Record<string, number>` で表現するものが Python では `Counter` / `defaultdict` に対応する。`ChainMap` は TS にない概念だが、React の Context や CSS カスケードと近いセマンティクスで理解できる。
219
+
220
+ **エラーレスポンスの質**: `/deque/sliding-window` の `window=0` に対する 422 バリデーションエラーは適切。FastAPI の `Field(ge=1)` が自動で機能している。
221
+
222
+ **Python 固有概念の学習コスト**: `NamedTuple` は TypeScript のインターフェースに近い。デフォルト値・メソッド定義できる点は TS の interface と同じ感覚で理解できる。
223
+
224
+ **事故リスク**: 低
225
+
226
+ ### 4. バックエンド経験者(Django/FastAPI 歴5-6年・男性・リードエンジニア)
227
+
228
+ `Counter` の算術演算子や `defaultdict` のネストは Django の `annotate()` / `aggregate()` では難しいデータ変換を純粋 Python で行う際に重宝する。`ChainMap` は Django の設定(`DJANGO_SETTINGS_MODULE` → デフォルト設定)と類似のパターン。
229
+
230
+ **他フレームワークとの差異**: `LruDict` は `functools.lru_cache` と比べて「任意の引数に対応する柔軟性」と「キャッシュの明示的管理」がメリット。
231
+
232
+ **nene2 の薄さへの評価**: `collections` は nene2 フレームワークと独立しているため、ドメインロジック層の実装にそのまま使えるコレクション群として評価が高い。
233
+
234
+ **事故リスク**: 低
235
+
236
+ ### 5. シニアエンジニア(設計・コードレビュー担当・女性・10-12年)
237
+
238
+ **コードレビューチェックポイント**:
239
+ - `defaultdict` が「サービス境界を超える」ケースに注意(外部に返す前に `dict(dd)` で変換しているか)
240
+ - `Counter` の `subtract()` メソッドと `-` 演算子の違い(`subtract` は負値を保持、`-` は正値のみ)
241
+ - `deque` に `maxlen` が設定されているか(無制限 deque は意図しないメモリ増大の原因)
242
+ - `ChainMap` の変更は先頭辞書のみに反映されることを把握しているか
243
+
244
+ **チームでの安全なパターン**: `BoundedHistory`(maxlen 付き deque)は監査ログ・エラー履歴の実装に、`resolve_config`(ChainMap)は設定オーバーライドに使えるチーム共有ユーティリティ。
245
+
246
+ **事故リスク**: 低
247
+
248
+ ### 6. 設計者(nene2-python 設計ポリシー目線)
249
+
250
+ **CLAUDE.md ポリシー整合性**:
251
+ - `dataclass(frozen=True, slots=True)`: `DequeSnapshot`・`RawCoordinate` で適用済み ✅
252
+ - Pydantic は HTTP 境界のみ: `app.py` の Request/Response モデルのみ ✅
253
+ - `create_app()` はファイル末尾: 適用済み ✅(FT182 の教訓)
254
+ - `max_length` 指定: 全文字列・リストフィールドに設定済み ✅
255
+ - 型安全: `dict[str, float | str]` の問題を `RawCoordinate` dataclass で解決 ✅
256
+
257
+ **初心者でも安全な API 達成度**: `BoundedHistory` が「maxlen を必ず指定して使う」パターンを示し、`LruDict` が「容量が決まっている場合のみ OrderedDict LRU を使う」ことを Pydantic の `le=100` で強制している設計は初心者でも誤用しにくい。
258
+
259
+ ---
260
+
261
+ *バージョン: v1.8.58*
@@ -219,12 +219,14 @@
219
219
  | [FT183](2026-05-field-trial-183.md) | smtplib モジュール — SMTP 送信・STARTTLS・ヘッダーインジェクション防御 | 🔒 | [#513](https://github.com/hideyukiMORI/nene2-python/issues/513) [#514](https://github.com/hideyukiMORI/nene2-python/issues/514) |
220
220
  | [FT184](2026-05-field-trial-184.md) | urllib.request モジュール — URL フェッチ・Basic 認証・SSRF 防御 | 🔍 | [#516](https://github.com/hideyukiMORI/nene2-python/issues/516) [#517](https://github.com/hideyukiMORI/nene2-python/issues/517) |
221
221
  | [FT185](2026-05-field-trial-185.md) | contextlib モジュール — コンテキストマネージャー・リソース管理・エラー抑制 | | |
222
+ | [FT186](2026-05-field-trial-186.md) | functools モジュール — キャッシュ・部分適用・デコレーター・比較・ディスパッチ | 🔒 | [#520](https://github.com/hideyukiMORI/nene2-python/issues/520) |
223
+ | [FT187](2026-05-field-trial-187.md) | collections モジュール — Counter・defaultdict・deque・ChainMap・NamedTuple・OrderedDict | | |
222
224
 
223
225
  ---
224
226
 
225
227
  ## セキュリティ診断実施済み一覧(🔒)
226
228
 
227
- FT3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 60, 63, 66, 69, 72, 75, 78, 81, 84, 87, 90, 93, 96, 99, 102, 105, 108, 111, 114, 117, 120, 121, 124, 127, 130, 133, 136, 139, 142, 145, 148, 151, 154, 157, 160, 163, 166, 169, 172, 174, 177, 180, 183
229
+ FT3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 60, 63, 66, 69, 72, 75, 78, 81, 84, 87, 90, 93, 96, 99, 102, 105, 108, 111, 114, 117, 120, 121, 124, 127, 130, 133, 136, 139, 142, 145, 148, 151, 154, 157, 160, 163, 166, 169, 172, 174, 177, 180, 183, 186
228
230
 
229
231
  合計: **62件**(183 FT 中 約 34%)
230
232
 
@@ -234,4 +236,4 @@ FT172, FT176, FT180, FT184
234
236
 
235
237
  ---
236
238
 
237
- *最終更新: 2026-05-21 (FT185 / v1.8.56)*
239
+ *最終更新: 2026-05-21 (FT187 / v1.8.58)*
@@ -1,14 +1,14 @@
1
1
  # TODO — current
2
2
 
3
3
  最終更新: 2026-05-21
4
- 現状: **v1.8.56 安定版 / フィールドトライアルループ継続中(FT185 完了)**
4
+ 現状: **v1.8.58 安定版 / フィールドトライアルループ継続中(FT187 完了)**
5
5
 
6
6
  ---
7
7
 
8
8
  ## 状態サマリー
9
9
 
10
- v1.8.56 完了済み。FT185contextlib / コンテキストマネージャー・リソース管理・エラー抑制)を含む FT185 件を実施済み。
11
- フィールドトライアルループは FT186 以降も継続中。
10
+ v1.8.58 完了済み。FT187collections / Counter・defaultdict・deque・ChainMap・NamedTuple・OrderedDict)を含む FT187 件を実施済み。
11
+ フィールドトライアルループは FT188 以降も継続中。
12
12
 
13
13
  ---
14
14
 
@@ -22,6 +22,7 @@ v1.8.56 完了済み。FT185(contextlib / コンテキストマネージャー
22
22
 
23
23
  | Issue | 内容 | 優先度 |
24
24
  |---|---|---|
25
+ | [#520](https://github.com/hideyukiMORI/nene2-python/issues/520) | [FT186] /flatten エンドポイントの内側リスト要素数に上限を追加 | 中 |
25
26
  | [#517](https://github.com/hideyukiMORI/nene2-python/issues/517) | [FT184] DNS リバインディング攻撃への対策検討(TTL0 + IP 切り替え) | 低 |
26
27
  | [#516](https://github.com/hideyukiMORI/nene2-python/issues/516) | [FT184] fetch_safe のリダイレクト SSRF 対策(Location ヘッダー先の IP 検証) | 中 |
27
28
  | [#514](https://github.com/hideyukiMORI/nene2-python/issues/514) | [FT183] SmtpConfig.password を SecretStr に変更 | 低 |
@@ -40,6 +41,8 @@ v1.8.56 完了済み。FT185(contextlib / コンテキストマネージャー
40
41
 
41
42
  | バージョン | 主な内容 |
42
43
  |---|---|
44
+ | v1.8.58 | FT187: collections — Counter・defaultdict・deque・ChainMap・NamedTuple・OrderedDict |
45
+ | v1.8.57 | FT186: functools — キャッシュ・部分適用・デコレーター・比較・ディスパッチ(診断実施) |
43
46
  | v1.8.56 | FT185: contextlib — コンテキストマネージャー・リソース管理・エラー抑制 |
44
47
  | v1.8.55 | FT184: urllib.request — URL フェッチ・Basic 認証・SSRF 防御(クラッカーペンテスト実施) |
45
48
  | v1.8.54 | FT183: smtplib — SMTP 送信・STARTTLS・ヘッダーインジェクション防御(診断実施) |
@@ -59,12 +62,12 @@ v1.8.56 完了済み。FT185(contextlib / コンテキストマネージャー
59
62
 
60
63
  ## フィールドトライアル進捗
61
64
 
62
- **実施済み**: FT1〜FT185(全 185 件)
65
+ **実施済み**: FT1〜FT187(全 187 件)
63
66
 
64
67
  索引: [`docs/field-trials/INDEX.md`](../field-trials/INDEX.md)
65
68
 
66
69
  **次のアクション**:
67
- - FT185 以降を継続(FT186186 % 4 = 2ペンテストなし、186 % 3 = 0セキュリティ診断あり)
70
+ - FT187 以降を継続(FT188188 % 4 = 0クラッカーペンテストあり、188 % 3 = 2診断なし)
68
71
 
69
72
  ---
70
73
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nene2-python"
3
- version = "1.8.56"
3
+ version = "1.8.58"
4
4
  description = "NENE2 Python — minimal API framework following NENE2's design philosophy"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
File without changes