nene2-python 1.8.39__tar.gz → 1.8.41__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 (391) hide show
  1. {nene2_python-1.8.39 → nene2_python-1.8.41}/PKG-INFO +1 -1
  2. nene2_python-1.8.41/docs/field-trials/2026-05-field-trial-169.md +272 -0
  3. nene2_python-1.8.41/docs/field-trials/2026-05-field-trial-170.md +242 -0
  4. {nene2_python-1.8.39 → nene2_python-1.8.41}/pyproject.toml +1 -1
  5. {nene2_python-1.8.39 → nene2_python-1.8.41}/uv.lock +1 -1
  6. {nene2_python-1.8.39 → nene2_python-1.8.41}/.env.example +0 -0
  7. {nene2_python-1.8.39 → nene2_python-1.8.41}/.github/workflows/ci.yml +0 -0
  8. {nene2_python-1.8.39 → nene2_python-1.8.41}/.github/workflows/docs.yml +0 -0
  9. {nene2_python-1.8.39 → nene2_python-1.8.41}/.github/workflows/publish.yml +0 -0
  10. {nene2_python-1.8.39 → nene2_python-1.8.41}/.gitignore +0 -0
  11. {nene2_python-1.8.39 → nene2_python-1.8.41}/.vitepress/config.mts +0 -0
  12. {nene2_python-1.8.39 → nene2_python-1.8.41}/.vitepress/theme/custom.css +0 -0
  13. {nene2_python-1.8.39 → nene2_python-1.8.41}/.vitepress/theme/index.ts +0 -0
  14. {nene2_python-1.8.39 → nene2_python-1.8.41}/AGENTS.md +0 -0
  15. {nene2_python-1.8.39 → nene2_python-1.8.41}/CHANGELOG.md +0 -0
  16. {nene2_python-1.8.39 → nene2_python-1.8.41}/CLAUDE.md +0 -0
  17. {nene2_python-1.8.39 → nene2_python-1.8.41}/Dockerfile +0 -0
  18. {nene2_python-1.8.39 → nene2_python-1.8.41}/LICENSE +0 -0
  19. {nene2_python-1.8.39 → nene2_python-1.8.41}/README.md +0 -0
  20. {nene2_python-1.8.39 → nene2_python-1.8.41}/alembic/README +0 -0
  21. {nene2_python-1.8.39 → nene2_python-1.8.41}/alembic/env.py +0 -0
  22. {nene2_python-1.8.39 → nene2_python-1.8.41}/alembic/script.py.mako +0 -0
  23. {nene2_python-1.8.39 → nene2_python-1.8.41}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
  24. {nene2_python-1.8.39 → nene2_python-1.8.41}/alembic.ini +0 -0
  25. {nene2_python-1.8.39 → nene2_python-1.8.41}/compose.yaml +0 -0
  26. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/adr/0001-toolchain.md +0 -0
  27. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/adr/0002-clean-architecture.md +0 -0
  28. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/adr/0003-security-first.md +0 -0
  29. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/adr/0004-ai-first-design.md +0 -0
  30. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/adr/0005-logging.md +0 -0
  31. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/adr/0006-rate-limiting.md +0 -0
  32. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/adr/0009-mcp-design.md +0 -0
  33. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/adr/0010-async-use-case.md +0 -0
  34. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
  35. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/de/index.md +0 -0
  36. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/de/tutorials/getting-started.md +0 -0
  37. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/explanation/architecture.md +0 -0
  38. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/explanation/design-philosophy.md +0 -0
  39. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-1.md +0 -0
  40. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-10.md +0 -0
  41. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-100.md +0 -0
  42. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-101.md +0 -0
  43. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-102.md +0 -0
  44. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-103.md +0 -0
  45. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-104.md +0 -0
  46. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-105.md +0 -0
  47. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-106.md +0 -0
  48. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-107.md +0 -0
  49. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-108.md +0 -0
  50. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-109.md +0 -0
  51. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-11.md +0 -0
  52. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-110.md +0 -0
  53. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-111.md +0 -0
  54. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-112.md +0 -0
  55. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-113.md +0 -0
  56. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-114.md +0 -0
  57. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-115.md +0 -0
  58. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-116.md +0 -0
  59. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-117.md +0 -0
  60. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-118.md +0 -0
  61. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-119.md +0 -0
  62. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-12.md +0 -0
  63. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-120.md +0 -0
  64. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-121.md +0 -0
  65. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-122.md +0 -0
  66. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-123.md +0 -0
  67. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-124.md +0 -0
  68. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-125.md +0 -0
  69. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-126.md +0 -0
  70. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-127.md +0 -0
  71. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-128.md +0 -0
  72. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-129.md +0 -0
  73. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-13.md +0 -0
  74. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-130.md +0 -0
  75. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-131.md +0 -0
  76. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-132.md +0 -0
  77. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-133.md +0 -0
  78. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-134.md +0 -0
  79. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-135.md +0 -0
  80. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-136.md +0 -0
  81. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-137.md +0 -0
  82. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-138.md +0 -0
  83. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-139.md +0 -0
  84. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-14.md +0 -0
  85. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-140.md +0 -0
  86. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-141.md +0 -0
  87. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-142.md +0 -0
  88. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-143.md +0 -0
  89. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-144.md +0 -0
  90. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-145.md +0 -0
  91. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-146.md +0 -0
  92. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-147.md +0 -0
  93. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-148.md +0 -0
  94. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-149.md +0 -0
  95. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-15.md +0 -0
  96. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-150.md +0 -0
  97. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-151.md +0 -0
  98. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-152.md +0 -0
  99. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-153.md +0 -0
  100. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-154.md +0 -0
  101. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-155.md +0 -0
  102. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-156.md +0 -0
  103. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-157.md +0 -0
  104. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-158.md +0 -0
  105. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-159.md +0 -0
  106. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-16.md +0 -0
  107. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-160.md +0 -0
  108. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-161.md +0 -0
  109. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-162.md +0 -0
  110. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-163.md +0 -0
  111. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-164.md +0 -0
  112. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-165.md +0 -0
  113. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-166.md +0 -0
  114. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-167.md +0 -0
  115. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-168.md +0 -0
  116. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-17.md +0 -0
  117. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-18.md +0 -0
  118. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-19.md +0 -0
  119. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-2.md +0 -0
  120. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-20.md +0 -0
  121. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-21.md +0 -0
  122. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-22.md +0 -0
  123. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-23.md +0 -0
  124. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-24.md +0 -0
  125. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-25.md +0 -0
  126. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-26.md +0 -0
  127. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-27.md +0 -0
  128. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-28.md +0 -0
  129. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-29.md +0 -0
  130. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-3.md +0 -0
  131. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-30.md +0 -0
  132. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-31.md +0 -0
  133. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-32.md +0 -0
  134. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-33.md +0 -0
  135. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-34.md +0 -0
  136. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-35.md +0 -0
  137. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-36.md +0 -0
  138. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-37.md +0 -0
  139. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-38.md +0 -0
  140. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-39.md +0 -0
  141. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-4.md +0 -0
  142. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-40.md +0 -0
  143. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-41.md +0 -0
  144. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-42.md +0 -0
  145. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-43.md +0 -0
  146. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-44.md +0 -0
  147. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-45.md +0 -0
  148. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-46.md +0 -0
  149. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-47.md +0 -0
  150. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-48.md +0 -0
  151. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-49.md +0 -0
  152. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-5.md +0 -0
  153. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-50.md +0 -0
  154. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-51.md +0 -0
  155. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-52.md +0 -0
  156. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-53.md +0 -0
  157. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-54.md +0 -0
  158. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-55.md +0 -0
  159. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-56.md +0 -0
  160. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-57.md +0 -0
  161. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-58.md +0 -0
  162. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-59.md +0 -0
  163. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-6.md +0 -0
  164. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-60.md +0 -0
  165. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-61.md +0 -0
  166. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-62.md +0 -0
  167. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-63.md +0 -0
  168. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-64.md +0 -0
  169. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-65.md +0 -0
  170. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-66.md +0 -0
  171. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-67.md +0 -0
  172. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-68.md +0 -0
  173. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-69.md +0 -0
  174. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-7.md +0 -0
  175. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-70.md +0 -0
  176. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-71.md +0 -0
  177. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-72.md +0 -0
  178. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-73.md +0 -0
  179. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-74.md +0 -0
  180. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-75.md +0 -0
  181. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-76.md +0 -0
  182. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-77.md +0 -0
  183. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-78.md +0 -0
  184. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-79.md +0 -0
  185. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-8.md +0 -0
  186. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-80.md +0 -0
  187. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-81.md +0 -0
  188. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-82.md +0 -0
  189. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-83.md +0 -0
  190. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-84.md +0 -0
  191. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-85.md +0 -0
  192. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-86.md +0 -0
  193. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-87.md +0 -0
  194. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-88.md +0 -0
  195. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-89.md +0 -0
  196. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-9.md +0 -0
  197. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-90.md +0 -0
  198. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-91.md +0 -0
  199. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-92.md +0 -0
  200. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-93.md +0 -0
  201. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-94.md +0 -0
  202. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-95.md +0 -0
  203. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-96.md +0 -0
  204. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-97.md +0 -0
  205. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-98.md +0 -0
  206. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/field-trials/2026-05-field-trial-99.md +0 -0
  207. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/fr/index.md +0 -0
  208. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/fr/tutorials/getting-started.md +0 -0
  209. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/how-to/add-new-domain.md +0 -0
  210. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/how-to/api-versioning.md +0 -0
  211. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/how-to/async-use-case.md +0 -0
  212. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/how-to/background-tasks.md +0 -0
  213. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/how-to/configure-auth.md +0 -0
  214. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/how-to/cors.md +0 -0
  215. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/how-to/custom-auth-middleware.md +0 -0
  216. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/how-to/dependency-injection.md +0 -0
  217. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/how-to/domain-events.md +0 -0
  218. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/how-to/file-upload.md +0 -0
  219. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/how-to/lifespan-and-app-state.md +0 -0
  220. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/how-to/middleware-stack.md +0 -0
  221. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/how-to/new-project.md +0 -0
  222. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/how-to/problem-details.md +0 -0
  223. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/how-to/response-patterns.md +0 -0
  224. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/how-to/run-tests.md +0 -0
  225. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/how-to/soft-delete.md +0 -0
  226. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/how-to/sqlalchemy-repository.md +0 -0
  227. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/how-to/streaming.md +0 -0
  228. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/how-to/structured-logging.md +0 -0
  229. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/how-to/validation.md +0 -0
  230. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/how-to/webhook.md +0 -0
  231. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/howto/mcp-setup.md +0 -0
  232. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/index.md +0 -0
  233. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/ja/explanation/architecture.md +0 -0
  234. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/ja/explanation/design-philosophy.md +0 -0
  235. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/ja/how-to/add-new-domain.md +0 -0
  236. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/ja/how-to/configure-auth.md +0 -0
  237. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/ja/how-to/new-project.md +0 -0
  238. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/ja/how-to/run-tests.md +0 -0
  239. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
  240. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/ja/howto/mcp-setup.md +0 -0
  241. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/ja/index.md +0 -0
  242. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/ja/reference/api.md +0 -0
  243. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/ja/reference/configuration.md +0 -0
  244. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/ja/reference/framework-modules.md +0 -0
  245. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/ja/tutorials/first-domain.md +0 -0
  246. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/ja/tutorials/getting-started.md +0 -0
  247. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/pt-br/index.md +0 -0
  248. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/pt-br/tutorials/getting-started.md +0 -0
  249. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/reference/api.md +0 -0
  250. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/reference/configuration.md +0 -0
  251. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/reference/framework-modules.md +0 -0
  252. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/roadmap.md +0 -0
  253. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/templates/field-trial-report.md +0 -0
  254. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/todo/current.md +0 -0
  255. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/tutorials/first-domain.md +0 -0
  256. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/tutorials/getting-started.md +0 -0
  257. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/zh/index.md +0 -0
  258. {nene2_python-1.8.39 → nene2_python-1.8.41}/docs/zh/tutorials/getting-started.md +0 -0
  259. {nene2_python-1.8.39 → nene2_python-1.8.41}/package-lock.json +0 -0
  260. {nene2_python-1.8.39 → nene2_python-1.8.41}/package.json +0 -0
  261. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/example/__init__.py +0 -0
  262. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/example/__main__.py +0 -0
  263. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/example/app.py +0 -0
  264. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/example/comment/__init__.py +0 -0
  265. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/example/comment/entity.py +0 -0
  266. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/example/comment/exceptions.py +0 -0
  267. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/example/comment/handler.py +0 -0
  268. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/example/comment/repository.py +0 -0
  269. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/example/comment/sqlalchemy_repository.py +0 -0
  270. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/example/comment/use_case.py +0 -0
  271. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/example/mcp.py +0 -0
  272. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/example/note/__init__.py +0 -0
  273. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/example/note/async_use_case.py +0 -0
  274. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/example/note/entity.py +0 -0
  275. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/example/note/exceptions.py +0 -0
  276. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/example/note/handler.py +0 -0
  277. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/example/note/repository.py +0 -0
  278. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/example/note/sqlalchemy_repository.py +0 -0
  279. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/example/note/use_case.py +0 -0
  280. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/example/schema.py +0 -0
  281. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/example/tag/__init__.py +0 -0
  282. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/example/tag/entity.py +0 -0
  283. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/example/tag/exceptions.py +0 -0
  284. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/example/tag/handler.py +0 -0
  285. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/example/tag/repository.py +0 -0
  286. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/example/tag/sqlalchemy_repository.py +0 -0
  287. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/example/tag/use_case.py +0 -0
  288. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/__init__.py +0 -0
  289. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/auth/__init__.py +0 -0
  290. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/auth/api_key.py +0 -0
  291. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/auth/bearer_token.py +0 -0
  292. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/auth/deps.py +0 -0
  293. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/auth/exceptions.py +0 -0
  294. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/auth/interfaces.py +0 -0
  295. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/auth/local_verifier.py +0 -0
  296. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/cache/__init__.py +0 -0
  297. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/cache/ttl.py +0 -0
  298. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/config/__init__.py +0 -0
  299. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/config/settings.py +0 -0
  300. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/database/__init__.py +0 -0
  301. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/database/exceptions.py +0 -0
  302. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/database/health.py +0 -0
  303. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/database/interfaces.py +0 -0
  304. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/database/sqlalchemy_executor.py +0 -0
  305. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/database/utils.py +0 -0
  306. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/http/__init__.py +0 -0
  307. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/http/etag.py +0 -0
  308. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/http/health.py +0 -0
  309. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/http/pagination.py +0 -0
  310. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/http/problem_details.py +0 -0
  311. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/log/__init__.py +0 -0
  312. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/log/setup.py +0 -0
  313. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/mcp/__init__.py +0 -0
  314. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/mcp/http_client.py +0 -0
  315. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/mcp/server.py +0 -0
  316. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/middleware/__init__.py +0 -0
  317. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/middleware/domain_exception.py +0 -0
  318. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/middleware/error_handler.py +0 -0
  319. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/middleware/request_id.py +0 -0
  320. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/middleware/request_logging.py +0 -0
  321. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/middleware/request_size_limit.py +0 -0
  322. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/middleware/security_headers.py +0 -0
  323. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/middleware/setup.py +0 -0
  324. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/middleware/throttle.py +0 -0
  325. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/py.typed +0 -0
  326. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/security/__init__.py +0 -0
  327. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/security/webhook.py +0 -0
  328. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/use_case/__init__.py +0 -0
  329. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/use_case/protocols.py +0 -0
  330. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/validation/__init__.py +0 -0
  331. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/nene2/validation/exceptions.py +0 -0
  332. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/scripts/__init__.py +0 -0
  333. {nene2_python-1.8.39 → nene2_python-1.8.41}/src/scripts/export_openapi.py +0 -0
  334. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/__init__.py +0 -0
  335. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/conftest.py +0 -0
  336. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/example/__init__.py +0 -0
  337. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/example/comment/__init__.py +0 -0
  338. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/example/comment/test_comment_http.py +0 -0
  339. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/example/comment/test_comment_repository.py +0 -0
  340. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/example/comment/test_comment_use_case.py +0 -0
  341. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/example/conftest.py +0 -0
  342. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/example/note/__init__.py +0 -0
  343. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/example/note/test_async_note_use_case.py +0 -0
  344. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/example/note/test_list_notes.py +0 -0
  345. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/example/note/test_note_repository.py +0 -0
  346. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/example/tag/__init__.py +0 -0
  347. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/example/tag/test_tag_repository.py +0 -0
  348. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/example/tag/test_tags.py +0 -0
  349. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/example/test_cors.py +0 -0
  350. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/example/test_mcp.py +0 -0
  351. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/__init__.py +0 -0
  352. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/auth/__init__.py +0 -0
  353. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/auth/test_api_key.py +0 -0
  354. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/auth/test_bearer_token.py +0 -0
  355. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/auth/test_make_require_auth.py +0 -0
  356. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/auth/test_token_issuer.py +0 -0
  357. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/cache/__init__.py +0 -0
  358. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/cache/test_ttl.py +0 -0
  359. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/config/__init__.py +0 -0
  360. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/config/test_settings.py +0 -0
  361. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/database/__init__.py +0 -0
  362. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/database/test_transaction.py +0 -0
  363. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/database/test_utils.py +0 -0
  364. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/http/__init__.py +0 -0
  365. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/http/test_etag.py +0 -0
  366. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/http/test_health.py +0 -0
  367. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/http/test_pagination.py +0 -0
  368. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/http/test_problem_details.py +0 -0
  369. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/log/__init__.py +0 -0
  370. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/log/test_setup.py +0 -0
  371. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/mcp/__init__.py +0 -0
  372. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/mcp/test_http_client.py +0 -0
  373. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/mcp/test_server.py +0 -0
  374. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/middleware/__init__.py +0 -0
  375. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/middleware/test_error_handler.py +0 -0
  376. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/middleware/test_request_id.py +0 -0
  377. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/middleware/test_request_logging.py +0 -0
  378. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/middleware/test_request_size_limit.py +0 -0
  379. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/middleware/test_security_headers.py +0 -0
  380. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/middleware/test_setup_middlewares.py +0 -0
  381. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
  382. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/middleware/test_throttle.py +0 -0
  383. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/security/__init__.py +0 -0
  384. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/security/test_webhook.py +0 -0
  385. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/use_case/__init__.py +0 -0
  386. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/use_case/test_protocols.py +0 -0
  387. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/use_case/test_run_in_threadpool.py +0 -0
  388. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/validation/__init__.py +0 -0
  389. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/nene2/validation/test_exceptions.py +0 -0
  390. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/scripts/__init__.py +0 -0
  391. {nene2_python-1.8.39 → nene2_python-1.8.41}/tests/scripts/test_export_openapi.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nene2-python
3
- Version: 1.8.39
3
+ Version: 1.8.41
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,272 @@
1
+ # FT169: typing モジュール
2
+
3
+ **日付**: 2026-05-21
4
+ **テーマ**: `typing` モジュール — `TypedDict`・`Protocol`・`overload`・`Literal`・`TypeGuard`・`Required`/`NotRequired`
5
+ **セキュリティ診断**: なし(169 % 3 = 2)
6
+
7
+ ---
8
+
9
+ ## 概要
10
+
11
+ Python 標準ライブラリの `typing` モジュール(Python 3.12+ の先進的な機能を含む)を
12
+ nene2-python フレームワーク上で検証した。
13
+ `typing` は nene2 の「strict typing」設計哲学の根幹であり、
14
+ HTTP 境界の型安全性・ドメインモデルの不変性・プロトコルによる構造的サブタイピングを
15
+ 支える重要モジュール。
16
+ CLAUDE.md の型安全ポリシー(`Any` 禁止・`TypedDict`・`Protocol` 活用)に直接対応する。
17
+
18
+ ---
19
+
20
+ ## 実装したサンプルアプリ
21
+
22
+ **場所**: `/home/xi/docker/nene2-python-FT/ft169-typing/`
23
+
24
+ ### 主要機能
25
+
26
+ | 関数/クラス | 概要 |
27
+ |---|---|
28
+ | `NoteDict` (TypedDict) | 構造化辞書の型定義。`id`, `title`, `content` |
29
+ | `NoteCreateDict` (TypedDict) | `NotRequired[str]` でオプショナルフィールドを表現 |
30
+ | `NoteWithMetaDict` (TypedDict 継承) | `total=False` でオプショナル拡張フィールドを追加 |
31
+ | `Serializable` (Protocol) | `@runtime_checkable` で `isinstance()` チェック可能な構造的サブタイプ |
32
+ | `Closeable` (Protocol) | `close()` を持つリソースのプロトコル |
33
+ | `parse_id()` (@overload) | `str` 引数 → `int \| None`、`int` 引数 → `int` の型多態性 |
34
+ | `double()` (@overload) | `int` / `str` / `float` それぞれに異なる戻り値型 |
35
+ | `SortOrder` / `NoteStatus` / `HttpStatusCode` (Literal) | `type` エイリアス + `Literal` で定数列挙 |
36
+ | `is_note_dict()` (TypeGuard) | 実行時の型絞り込み関数 |
37
+ | `build_search_params()` | `Required` / `NotRequired` を持つ `SearchQuery` TypedDict の利用例 |
38
+ | `inspect_hints()` | `get_type_hints()` で実行時にクラスメソッドの型情報を取得 |
39
+
40
+ ### HTTP エンドポイント
41
+
42
+ | メソッド | パス | 概要 |
43
+ |---|---|---|
44
+ | POST | `/typing/notes` | TypedDict でノートを作成 |
45
+ | GET | `/typing/notes` | Literal `SortOrder` でノートをソート |
46
+ | POST | `/typing/notes/tags` | TypedDict 継承でタグを追加 |
47
+ | GET | `/typing/parse-id` | @overload — 文字列 → int 変換 |
48
+ | GET | `/typing/double` | @overload — 型別の倍返し |
49
+ | POST | `/typing/search` | Required/NotRequired を持つ SearchQuery |
50
+ | GET | `/typing/type-guard` | TypeGuard で型絞り込み |
51
+ | GET | `/typing/protocol` | Protocol + is_serializable() |
52
+ | POST | `/typing/close-resources` | Closeable Protocol でリソース管理 |
53
+ | GET | `/typing/hints` | get_type_hints() でクラスのヒント取得 |
54
+ | GET | `/typing/filter-status` | Literal[NoteStatus] でフィルタリング |
55
+ | GET | `/typing/response` | Literal[HttpStatusCode] でレスポンス生成 |
56
+
57
+ ---
58
+
59
+ ## テスト結果
60
+
61
+ **45 passed(摩擦ゼロ)**
62
+
63
+ ```
64
+ 45 passed in 0.86s
65
+ ```
66
+
67
+ ---
68
+
69
+ ## 摩擦ポイント
70
+
71
+ **今回の FT では実装上の摩擦はゼロだった。**
72
+
73
+ ---
74
+
75
+ ## 観察点
76
+
77
+ ### 観察1: `type` エイリアス構文(Python 3.12+)で `Literal` を名前付き型にできる
78
+
79
+ ```python
80
+ type SortOrder = Literal["asc", "desc"]
81
+ type NoteStatus = Literal["draft", "published", "archived"]
82
+ type HttpStatusCode = Literal[200, 201, 204, 400, 401, 403, 404, 422, 500]
83
+ ```
84
+
85
+ Python 3.12 の `type` ステートメントを使うと、`Literal` に意味のある名前がつく。
86
+ `TypeAlias` アノテーション(旧形式)より明示的で mypy --strict に通る。
87
+ 定数の列挙は Enum でなく Literal + type エイリアスが軽量な代替になる。
88
+
89
+ ### 観察2: `TypedDict` + `NotRequired` で HTTP BodyModel の代替が作れる
90
+
91
+ ```python
92
+ class NoteCreateDict(TypedDict):
93
+ title: str
94
+ content: NotRequired[str] # 省略可能
95
+ ```
96
+
97
+ Pydantic BaseModel が必要な HTTP 境界では引き続き Pydantic を使うが、
98
+ UseCase 内部のデータ構造・関数の引数・レポジトリの返り値には
99
+ `TypedDict` が軽量で mypy に完全対応する。
100
+ `total=False` よりも `NotRequired` を使う方が、どのフィールドが省略可能かが明確。
101
+
102
+ ### 観察3: `@runtime_checkable Protocol` で `isinstance()` による構造的型チェックが可能
103
+
104
+ ```python
105
+ @runtime_checkable
106
+ class Serializable(Protocol):
107
+ def to_dict(self) -> dict[str, object]: ...
108
+
109
+ note = InMemoryNote(1, "Test")
110
+ assert isinstance(note, Serializable) # True — クラス継承不要
111
+ ```
112
+
113
+ `InMemoryNote` は `Serializable` を継承していないが、`to_dict()` を持つため
114
+ `isinstance()` が `True` を返す。
115
+ nene2 のリポジトリパターンで「`to_dict()` を持つドメインオブジェクトなら何でも受け付ける」
116
+ 関数を書くときに有効。ただし `@runtime_checkable` はメソッドの「存在」しか確認せず、
117
+ 引数・戻り値型の一致は確認しない。
118
+
119
+ ### 観察4: `@overload` で引数の型による戻り値型の分岐を型安全に表現できる
120
+
121
+ ```python
122
+ @overload
123
+ def parse_id(value: str) -> int | None: ...
124
+ @overload
125
+ def parse_id(value: int) -> int: ...
126
+
127
+ def parse_id(value: str | int) -> int | None:
128
+ ...
129
+ ```
130
+
131
+ `parse_id(42)` のとき mypy は戻り値を `int`(`None` なし)と判断し、
132
+ `parse_id("foo")` のとき `int | None` と判断する。
133
+ `None` チェックを呼び出し側で毎回書く必要がなくなる箇所で効果的。
134
+
135
+ ### 観察5: `TypeGuard` で `Any` 型の辞書を型安全に絞り込める
136
+
137
+ ```python
138
+ def is_note_dict(obj: object) -> TypeGuard[NoteDict]:
139
+ if not isinstance(obj, dict):
140
+ return False
141
+ return (
142
+ isinstance(obj.get("id"), int)
143
+ and isinstance(obj.get("title"), str)
144
+ and isinstance(obj.get("content"), str)
145
+ )
146
+
147
+ def process_unknown(data: Any) -> str:
148
+ if is_note_dict(data):
149
+ return note_summary(data) # mypy はここで data を NoteDict として扱う
150
+ ```
151
+
152
+ 外部 JSON・DB 生クエリ結果など `Any` 型を安全に使う唯一の公式手段。
153
+ `cast()` は「信頼してキャスト」するだけだが、`TypeGuard` は実行時チェックと型絞り込みを両立する。
154
+
155
+ ---
156
+
157
+ ## nene2-python フレームワークとの統合
158
+
159
+ - `TypedDict` は nene2 の UseCase Input/Output DTO の軽量代替として使える(Pydantic より軽い)
160
+ - `Protocol` は `RepositoryInterface` の ABC 代替として `NoteRepositoryProtocol` を定義するのに有効
161
+ - `Literal` は nene2 の `SortOrder` / `DB_ADAPTER` 等の設定値型に直接適用できる
162
+ - `TypeGuard` は `/notes` 一覧取得の外部データを型安全に絞り込む Use Case で使える
163
+ - CLAUDE.md の「`Any` 禁止 / `TypedDict` で Dict 構造を型付け」ポリシーと完全に整合
164
+
165
+ ---
166
+
167
+ ## Developer Experience (DX) Review
168
+
169
+ ### ペルソナ1: 初心者(Python 歴1年・独学中・女性・バックエンド志望)
170
+
171
+ `TypedDict` は `class` で辞書を型定義する概念が新しく、
172
+ 「Pydantic の `BaseModel` と何が違うの?」という混乱が起きる。
173
+
174
+ **ドキュメント理解**: nene2 how-to に「HTTP 境界は Pydantic・UseCase 内部は TypedDict」の
175
+ 使い分けガイドがあれば即解決する。現時点では CLAUDE.md を読み込まないと判断できない。
176
+
177
+ **事故リスク**: 低。`TypedDict` の誤用は実行時エラーにはならず、mypy が検出してくれる。
178
+ ただし mypy なしで開発すると TypedDict の型安全の恩恵がゼロになる。
179
+
180
+ **規約の使いやすさ**: `NoteDict(TypedDict)` のパターンはコピペで書ける。
181
+ `total=False` と `NotRequired` の使い分けは最初は迷う。
182
+
183
+ ### ペルソナ2: ロースキル経験者(Python 歴3-4年・スクリプト系・男性・SES)
184
+
185
+ `Protocol` を「インターフェース」として理解できるが、
186
+ `@runtime_checkable` の「struct subtyping = 継承不要」は最初は驚く。
187
+ 「実行してみたら通った」という体験で理解が定着する。
188
+
189
+ **コピペ可能性**: 高。`@runtime_checkable class Xxx(Protocol)` はテンプレートとして使える。
190
+
191
+ **拡張時の罠**: `@runtime_checkable Protocol` はメソッドの引数・戻り値型を確認しない。
192
+ `to_dict() -> dict[str, object]` を `to_dict() -> str` に変えても `isinstance()` は `True` を返す。
193
+ 実行時の型安全は Protocol の責務ではなく、mypy の責務であることを理解が必要。
194
+
195
+ **セキュリティ的な事故リスク**: 低。typing の誤用がセキュリティインシデントに直結するケースは稀。
196
+
197
+ ### ペルソナ3: フロントエンド寄り経験者(React/TS 歴4年・バックエンド転向中・ノンバイナリ)
198
+
199
+ TypeScript の `interface` / `type` / `as const` との対応関係が作れる。
200
+ - TS の `interface` → Python の `TypedDict`(完全対応)
201
+ - TS の `as const` 配列 → Python の `Literal`(概念的に近い)
202
+ - TS の `function overloads` → Python の `@overload`(ほぼ同じ)
203
+ - TS の `type predicate` (`is`) → Python の `TypeGuard`(同等)
204
+
205
+ **エラーレスポンスの質**: 型ミスマッチは mypy が捕捉し、HTTP 境界では Pydantic が 422 を返す。
206
+ フロントエンド開発者にとって馴染みのある体験。
207
+
208
+ **事故リスク**: 低。TS の型システムの経験が直接活かせる。
209
+
210
+ ### ペルソナ4: バックエンド経験者(Django/FastAPI 歴5-6年・男性・リードエンジニア)
211
+
212
+ Django の `TypedDict` 活用は限定的(Django は dict よりモデルクラスを使う傾向)。
213
+ FastAPI ユーザーにとっては Pydantic v2 が `TypedDict` をスキーマとして受け付けるため、
214
+ 境界が曖昧になりやすい。
215
+
216
+ **他フレームワークとの差異**:
217
+ - Django: ドメインは Model クラスで表現、typing との組み合わせは発展途上
218
+ - nene2: TypedDict(UseCase 内部)+ Pydantic(HTTP 境界)の明確な使い分けが nene2 の差別化
219
+
220
+ **本番投入可能性**: 問題なし。`@overload` / `TypeGuard` は FastAPI ルーターとも問題なく共存する。
221
+
222
+ ### ペルソナ5: シニアエンジニア(設計・コードレビュー担当・女性・10-12年)
223
+
224
+ **コードレビューチェックポイント**:
225
+ - [ ] `TypedDict` フィールドに `NotRequired` を使って省略可能性を明示しているか(`total=False` は非推奨)
226
+ - [ ] `@runtime_checkable Protocol` を型安全の唯一の砦にしていないか(mypy でも確認必須)
227
+ - [ ] `@overload` の実装本体に型注釈が適切についているか(オーバーロードシグネチャのみ書いて本体を忘れるミス)
228
+ - [ ] `TypeGuard` 関数が実行時チェック(isinstance 等)を実施しているか(`return True` のみは危険)
229
+ - [ ] `cast()` には `# reason:` コメントがついているか(CLAUDE.md ポリシー)
230
+
231
+ **チームでの安全なパターン**: `TypedDict` は UseCase I/O の DTO として標準化し、
232
+ HTTP 境界は Pydantic、内部は TypedDict という二層構造を CLAUDE.md に明記する。
233
+
234
+ **ツール追加の必要性**: `mypy --strict` で `TypedDict` の型チェックが網羅されるため追加不要。
235
+
236
+ ### ペルソナ6: 設計者・ポリシー照合(nene2-python 設計ポリシー目線)
237
+
238
+ **ポリシー達成度**: 高
239
+
240
+ **「初心者でも安全な API」達成度**: 高
241
+ - `TypedDict` + mypy の組み合わせで、実行前に型エラーが検出される
242
+ - `TypeGuard` で `Any` 由来のデータを安全に絞り込める
243
+
244
+ **設計上の負債・ドキュメント不足**:
245
+ - CLAUDE.md の「TypedDict で Dict 構造を型付け」ポリシーは記載あるが、Pydantic との使い分けが未記載
246
+ - nene2 の UseCase Input/Output は `dataclass(frozen=True)` で実装されているが、
247
+ `TypedDict` が適切な場面(JSON 応答の直接マッピング等)も存在する
248
+
249
+ **Follow-up Issue 候補**: `docs: TypedDict vs Pydantic vs dataclass の使い分けガイドを how-to に追加`
250
+
251
+ ---
252
+
253
+ ## Follow-up Issues
254
+
255
+ | 優先度 | タイトル | 種別 |
256
+ |---|---|---|
257
+ | 中 | `docs: TypedDict vs Pydantic vs dataclass の使い分けガイドを how-to に追加` | docs |
258
+ | 低 | `feat: NoteRepositoryProtocol を TypedDict + Protocol で再実装するサンプルを追加` | feat |
259
+
260
+ ---
261
+
262
+ ## まとめ
263
+
264
+ `typing` モジュールは nene2-python の型安全設計の根幹をなす機能群。
265
+ 45 テスト全通過、摩擦ゼロ。
266
+
267
+ `TypedDict` + `NotRequired` は UseCase 内部データ構造の軽量 DTO として直接使える。
268
+ `Protocol` + `@runtime_checkable` は nene2 の `RepositoryInterface` の ABC 代替として有効で、
269
+ 継承なしの構造的サブタイピングを実現する。
270
+ `TypeGuard` は `Any` 型データを型安全に絞り込む唯一の公式手段で、外部データ処理に必須。
271
+ Python 3.12 の `type X = Literal[...]` 構文で定数列挙が軽量に書ける。
272
+
@@ -0,0 +1,242 @@
1
+ # FT170: collections モジュール
2
+
3
+ **日付**: 2026-05-21
4
+ **テーマ**: `collections` モジュール — `namedtuple`・`defaultdict`・`Counter`・`deque`・`OrderedDict`・`ChainMap`
5
+ **セキュリティ診断**: なし(170 % 3 = 2)
6
+
7
+ ---
8
+
9
+ ## 概要
10
+
11
+ Python 標準ライブラリの `collections` モジュールを nene2-python フレームワーク上で検証した。
12
+ `collections` は Python の基本データ構造を拡張する実用的なモジュールで、
13
+ ドメインデータ集計・キャッシュ・グラフ探索・設定管理に直接使えるパターンを提供する。
14
+
15
+ ---
16
+
17
+ ## 実装したサンプルアプリ
18
+
19
+ **場所**: `/home/xi/docker/nene2-python-FT/ft170-collections/`
20
+
21
+ ### 主要機能
22
+
23
+ | クラス/関数 | 概要 |
24
+ |---|---|
25
+ | `ApiError` (namedtuple) | エラー情報の軽量イミュータブル型。`_asdict()` で辞書変換 |
26
+ | `group_by_first_char()` | `defaultdict(list)` でグループ集計 |
27
+ | `count_tags()` | `defaultdict(int)` で出現頻度集計 |
28
+ | `build_adjacency_list()` | `defaultdict(list)` でグラフ構築 |
29
+ | `word_frequency()` / `top_n_words()` | `Counter` で単語頻度・トップN |
30
+ | `tag_overlap()` | `Counter` の intersection で共通タグを抽出 |
31
+ | `merge_counts()` | 複数 `Counter` を `update()` で合算 |
32
+ | `sliding_window_max()` | `deque` で O(n) スライディングウィンドウ最大値 |
33
+ | `recent_n()` | `deque(maxlen=n)` でリングバッファ |
34
+ | `bfs_path()` | `deque` をキューとして BFS 最短経路探索 |
35
+ | `LruCache` | `OrderedDict` + `move_to_end()` で O(1) LRU キャッシュ |
36
+ | `resolve_config()` / `config_source()` | `ChainMap` で env > file > defaults の優先順位設定 |
37
+
38
+ ### HTTP エンドポイント
39
+
40
+ | メソッド | パス | 概要 |
41
+ |---|---|---|
42
+ | GET | `/collections/namedtuple` | namedtuple デモ(距離計算・エラー構造体) |
43
+ | POST | `/collections/group-by` | defaultdict でグループ集計 |
44
+ | POST | `/collections/count-tags` | defaultdict でタグ集計 |
45
+ | GET | `/collections/word-freq` | Counter で単語頻度分析 |
46
+ | GET | `/collections/sliding-window` | deque でスライディングウィンドウ |
47
+ | GET | `/collections/recent` | deque(maxlen) リングバッファ |
48
+ | POST | `/collections/bfs` | BFS グラフ探索 |
49
+ | PUT/GET | `/collections/lru/{key}` | OrderedDict LRU キャッシュ |
50
+ | POST | `/collections/config` | ChainMap 設定レイヤー解決 |
51
+
52
+ ---
53
+
54
+ ## テスト結果
55
+
56
+ **36 passed(摩擦ゼロ)**
57
+
58
+ ```
59
+ 36 passed in 0.94s
60
+ ```
61
+
62
+ ---
63
+
64
+ ## 摩擦ポイント
65
+
66
+ **今回の FT では実装上の摩擦はゼロだった。**
67
+
68
+ ---
69
+
70
+ ## 観察点
71
+
72
+ ### 観察1: `defaultdict` は「キーがなければ初期値」を明示的に設計できる
73
+
74
+ ```python
75
+ result: defaultdict[str, list[str]] = defaultdict(list)
76
+ for word in words:
77
+ result[word[0]].append(word) # KeyError なし
78
+ ```
79
+
80
+ `dict.setdefault()` より意図が明確で、`if key not in d:` 分岐が不要になる。
81
+ グループ集計・グラフ隣接リスト構築・カウンタ実装の3パターンで多用できる。
82
+
83
+ ### 観察2: `Counter` は集合演算(`+`, `-`, `&`, `|`)が使える辞書
84
+
85
+ ```python
86
+ Counter(["python", "typing", "python"]) & Counter(["python", "asyncio"])
87
+ # → Counter({"python": 1}) — min(2,1) = 1
88
+ ```
89
+
90
+ `Counter` 同士の `+` は合算、`&` は最小値、`|` は最大値。
91
+ 複数の集計結果をマージする `merge_counts()` は `Counter.update()` で自然に書ける。
92
+ `most_common(n)` で上位 N 件を O(n log n) で取得できる。
93
+
94
+ ### 観察3: `deque(maxlen=n)` はリングバッファとして使える
95
+
96
+ ```python
97
+ buf: deque[str] = deque(maxlen=5)
98
+ buf.extend(["a", "b", "c", "d", "e", "f"])
99
+ list(buf) # → ["b", "c", "d", "e", "f"] — 最新5件のみ保持
100
+ ```
101
+
102
+ `maxlen` を指定すると、追加時に先頭から自動削除される。
103
+ ログ末尾 N 行・最近の操作履歴・スライディングウィンドウのバッファとして最適。
104
+ `collections.deque` は `list` と異なり先頭操作が O(1)。
105
+
106
+ ### 観察4: `OrderedDict.move_to_end()` で O(1) LRU キャッシュが実装できる
107
+
108
+ ```python
109
+ class LruCache:
110
+ def get(self, key: str) -> Any:
111
+ if key not in self._cache:
112
+ return None
113
+ self._cache.move_to_end(key) # 最近使用済みとしてマーク
114
+ return self._cache[key]
115
+
116
+ def put(self, key: str, value: Any) -> None:
117
+ ...
118
+ if len(self._cache) > self.capacity:
119
+ self._cache.popitem(last=False) # 最も古いものを削除
120
+ ```
121
+
122
+ `Python 3.7+` の `dict` は挿入順を保証するが、`move_to_end()` がないため
123
+ LRU の「使用順序の更新」には `OrderedDict` が必要。
124
+ nene2 の `TtlCache` と組み合わせた TTL+LRU キャッシュへの発展も可能。
125
+
126
+ ### 観察5: `ChainMap` で設定レイヤーのオーバーライドが宣言的に書ける
127
+
128
+ ```python
129
+ chain = ChainMap(env_vars, file_config, defaults)
130
+ chain["DB_HOST"] # env_vars → file_config → defaults の優先順位で検索
131
+ ```
132
+
133
+ `os.environ` + ファイル設定 + デフォルト値の優先順位解決は
134
+ 従来 `{**defaults, **file_config, **env_vars}` で実装していたが、
135
+ `ChainMap` は元の辞書を変更せず参照のみのため副作用がない。
136
+ `chain.maps[0]` で最優先レイヤー、`chain.new_child()` でスコープを重ねることもできる。
137
+
138
+ ---
139
+
140
+ ## nene2-python フレームワークとの統合
141
+
142
+ - `Counter` はタグ・カテゴリの集計 Use Case で `GROUP BY` SQL の代替になる(小規模データ)
143
+ - `LruCache` は nene2 の `TtlCache[V]` と組み合わせて TTL + LRU の複合キャッシュに発展できる
144
+ - `ChainMap` は `AppSettings` の `pydantic-settings` が行っている env > file > default 解決と同じパターン
145
+ - `deque` は WebSocket メッセージキューやストリーミングレスポンスのバッファとして適用できる
146
+ - `namedtuple` は UseCase の軽量 Output DTO として `dataclass(frozen=True)` より軽量な選択肢になる
147
+
148
+ ---
149
+
150
+ ## Developer Experience (DX) Review
151
+
152
+ ### ペルソナ1: 初心者(Python 歴1年・独学中・女性・バックエンド志望)
153
+
154
+ `defaultdict` は「エラーが出なくなった辞書」として直感的に受け入れられる。
155
+ `Counter` は「辞書の特殊版」として理解でき、`.most_common()` が使いやすい。
156
+ `deque` の「両端キュー」という概念は最初はピンとこないが、`maxlen` のリングバッファ用途はすぐに理解できる。
157
+
158
+ **ドキュメント理解**: `defaultdict(list)` のファクトリ関数の渡し方(`list` を呼び出さない)は最初に混乱する。
159
+ `defaultdict(lambda: [])` との違いを最初に説明すると理解が早い。
160
+
161
+ **事故リスク**: 中。`defaultdict` は存在しないキーにアクセスすると自動で作成するため、
162
+ タイポキーが無音で `{}` や `[]` に変わり、後続処理でのデバッグが難しくなる可能性。
163
+
164
+ **規約の使いやすさ**: コピペで使えるパターンが多い。
165
+
166
+ ### ペルソナ2: ロースキル経験者(Python 歴3-4年・スクリプト系・男性・SES)
167
+
168
+ `Counter` を知らずに `dict` + `if key in d: d[key] += 1 else: d[key] = 1` を書いている。
169
+ `Counter` を知ると即採用する。
170
+
171
+ **コピペ可能性**: 高。特に `Counter(list).most_common(n)` のワンライナーは即戦力。
172
+
173
+ **拡張時の罠**: `LruCache` で `OrderedDict` を使っているが、
174
+ Python 3.7+ の `dict` で書き直そうとして `move_to_end()` がないことに気づかず壊す可能性。
175
+ 「`OrderedDict` には `move_to_end()` がある」という固有 API を README に明記するべき。
176
+
177
+ **セキュリティ的な事故リスク**: 低。`collections` の誤用は機能バグには繋がるが、セキュリティリスクは低い。
178
+
179
+ ### ペルソナ3: フロントエンド寄り経験者(React/TS 歴4年・バックエンド転向中・ノンバイナリ)
180
+
181
+ `Counter` は JavaScript の `reduce` で頻度集計するパターンと概念的に近い。
182
+ `namedtuple` は TypeScript の `readonly struct` 的に理解できる。
183
+
184
+ **エラーレスポンスの質**: `/collections/sliding-window` で `"a,b,c"` を送ると 422 が返る。
185
+ クライアントには `{"error": "values must be comma-separated integers"}` が届き明確。
186
+
187
+ **事故リスク**: 低。
188
+
189
+ ### ペルソナ4: バックエンド経験者(Django/FastAPI 歴5-6年・男性・リードエンジニア)
190
+
191
+ Django の `QuerySet.values().annotate(count=Count(...))` と `Counter` の使い分けが判断ポイント。
192
+ DB に集計クエリを投げられるなら Django ORM が適切。
193
+ インメモリ集計(小規模・一時的)には `Counter` が軽量。
194
+
195
+ **本番投入可能性**: 問題なし。`LruCache` は nene2 の `TtlCache` と組み合わせて即本番投入できる。
196
+
197
+ ### ペルソナ5: シニアエンジニア(設計・コードレビュー担当・女性・10-12年)
198
+
199
+ **コードレビューチェックポイント**:
200
+ - [ ] `defaultdict` のキーが意図せず作成されていないか(`d[key]` アクセスだけでキーが生える)
201
+ - [ ] `Counter` の `most_common()` が `None` を返さないことを前提にしているか(空リストなら空リストを返す)
202
+ - [ ] `LruCache` が複数リクエストからアクセスされるグローバル状態の場合、`asyncio.Lock()` が必要か確認
203
+ - [ ] `ChainMap` の子マップへの書き込みが親マップに伝播しないことを理解しているか
204
+
205
+ **チームでの安全なパターン**: グローバルな `LruCache` インスタンスは `asyncio.Lock()` でガードするか、
206
+ スレッドセーフな実装(`threading.RLock`)に置き換える。
207
+
208
+ ### ペルソナ6: 設計者・ポリシー照合(nene2-python 設計ポリシー目線)
209
+
210
+ **ポリシー達成度**: 高
211
+
212
+ **「初心者でも安全な API」達成度**: 中
213
+ - `defaultdict` のキー自動生成は初心者には予期しない動作になりうる
214
+ - `LruCache` のスレッドセーフ性は nene2 の非同期環境では注意が必要
215
+
216
+ **設計上の負債・ドキュメント不足**:
217
+ - nene2 の `TtlCache[V]` と `LruCache` の組み合わせパターンが未文書化
218
+ - グローバルキャッシュインスタンスの非同期安全性に関する how-to がない
219
+
220
+ **Follow-up Issue 候補**: `docs: キャッシュの TTL + LRU 複合パターンと非同期安全性の how-to を追加`
221
+
222
+ ---
223
+
224
+ ## Follow-up Issues
225
+
226
+ | 優先度 | タイトル | 種別 |
227
+ |---|---|---|
228
+ | 中 | `docs: collections.Counter をタグ集計 Use Case に適用するパターンを how-to に追加` | docs |
229
+ | 低 | `feat: TtlCache に LRU 退去ポリシーを追加するオプションを検討` | feat |
230
+
231
+ ---
232
+
233
+ ## まとめ
234
+
235
+ `collections` モジュールは nene2-python の集計・キャッシュ・探索・設定管理に直接使える実用的な機能群。
236
+ 36 テスト全通過、摩擦ゼロ。
237
+
238
+ `defaultdict` / `Counter` / `deque` の三点セットは Python バックエンドの必須知識。
239
+ `OrderedDict.move_to_end()` による LRU キャッシュは nene2 の `TtlCache` と組み合わせる価値がある。
240
+ `ChainMap` は `pydantic-settings` が内部でやっている設定レイヤー解決と同じパターンで、
241
+ 環境別設定のオーバーライドを副作用なしに実装できる。
242
+
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nene2-python"
3
- version = "1.8.39"
3
+ version = "1.8.41"
4
4
  description = "NENE2 Python — minimal API framework following NENE2's design philosophy"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
@@ -925,7 +925,7 @@ wheels = [
925
925
 
926
926
  [[package]]
927
927
  name = "nene2-python"
928
- version = "1.8.39"
928
+ version = "1.8.41"
929
929
  source = { editable = "." }
930
930
  dependencies = [
931
931
  { name = "alembic" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes