nene2-python 1.8.40__tar.gz → 1.8.42__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 (392) hide show
  1. {nene2_python-1.8.40 → nene2_python-1.8.42}/PKG-INFO +1 -1
  2. nene2_python-1.8.42/docs/field-trials/2026-05-field-trial-170.md +242 -0
  3. nene2_python-1.8.42/docs/field-trials/2026-05-field-trial-171.md +423 -0
  4. {nene2_python-1.8.40 → nene2_python-1.8.42}/pyproject.toml +1 -1
  5. {nene2_python-1.8.40 → nene2_python-1.8.42}/uv.lock +1 -1
  6. {nene2_python-1.8.40 → nene2_python-1.8.42}/.env.example +0 -0
  7. {nene2_python-1.8.40 → nene2_python-1.8.42}/.github/workflows/ci.yml +0 -0
  8. {nene2_python-1.8.40 → nene2_python-1.8.42}/.github/workflows/docs.yml +0 -0
  9. {nene2_python-1.8.40 → nene2_python-1.8.42}/.github/workflows/publish.yml +0 -0
  10. {nene2_python-1.8.40 → nene2_python-1.8.42}/.gitignore +0 -0
  11. {nene2_python-1.8.40 → nene2_python-1.8.42}/.vitepress/config.mts +0 -0
  12. {nene2_python-1.8.40 → nene2_python-1.8.42}/.vitepress/theme/custom.css +0 -0
  13. {nene2_python-1.8.40 → nene2_python-1.8.42}/.vitepress/theme/index.ts +0 -0
  14. {nene2_python-1.8.40 → nene2_python-1.8.42}/AGENTS.md +0 -0
  15. {nene2_python-1.8.40 → nene2_python-1.8.42}/CHANGELOG.md +0 -0
  16. {nene2_python-1.8.40 → nene2_python-1.8.42}/CLAUDE.md +0 -0
  17. {nene2_python-1.8.40 → nene2_python-1.8.42}/Dockerfile +0 -0
  18. {nene2_python-1.8.40 → nene2_python-1.8.42}/LICENSE +0 -0
  19. {nene2_python-1.8.40 → nene2_python-1.8.42}/README.md +0 -0
  20. {nene2_python-1.8.40 → nene2_python-1.8.42}/alembic/README +0 -0
  21. {nene2_python-1.8.40 → nene2_python-1.8.42}/alembic/env.py +0 -0
  22. {nene2_python-1.8.40 → nene2_python-1.8.42}/alembic/script.py.mako +0 -0
  23. {nene2_python-1.8.40 → nene2_python-1.8.42}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
  24. {nene2_python-1.8.40 → nene2_python-1.8.42}/alembic.ini +0 -0
  25. {nene2_python-1.8.40 → nene2_python-1.8.42}/compose.yaml +0 -0
  26. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/adr/0001-toolchain.md +0 -0
  27. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/adr/0002-clean-architecture.md +0 -0
  28. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/adr/0003-security-first.md +0 -0
  29. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/adr/0004-ai-first-design.md +0 -0
  30. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/adr/0005-logging.md +0 -0
  31. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/adr/0006-rate-limiting.md +0 -0
  32. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/adr/0009-mcp-design.md +0 -0
  33. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/adr/0010-async-use-case.md +0 -0
  34. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
  35. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/de/index.md +0 -0
  36. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/de/tutorials/getting-started.md +0 -0
  37. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/explanation/architecture.md +0 -0
  38. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/explanation/design-philosophy.md +0 -0
  39. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-1.md +0 -0
  40. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-10.md +0 -0
  41. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-100.md +0 -0
  42. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-101.md +0 -0
  43. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-102.md +0 -0
  44. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-103.md +0 -0
  45. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-104.md +0 -0
  46. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-105.md +0 -0
  47. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-106.md +0 -0
  48. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-107.md +0 -0
  49. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-108.md +0 -0
  50. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-109.md +0 -0
  51. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-11.md +0 -0
  52. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-110.md +0 -0
  53. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-111.md +0 -0
  54. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-112.md +0 -0
  55. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-113.md +0 -0
  56. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-114.md +0 -0
  57. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-115.md +0 -0
  58. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-116.md +0 -0
  59. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-117.md +0 -0
  60. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-118.md +0 -0
  61. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-119.md +0 -0
  62. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-12.md +0 -0
  63. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-120.md +0 -0
  64. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-121.md +0 -0
  65. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-122.md +0 -0
  66. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-123.md +0 -0
  67. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-124.md +0 -0
  68. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-125.md +0 -0
  69. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-126.md +0 -0
  70. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-127.md +0 -0
  71. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-128.md +0 -0
  72. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-129.md +0 -0
  73. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-13.md +0 -0
  74. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-130.md +0 -0
  75. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-131.md +0 -0
  76. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-132.md +0 -0
  77. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-133.md +0 -0
  78. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-134.md +0 -0
  79. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-135.md +0 -0
  80. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-136.md +0 -0
  81. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-137.md +0 -0
  82. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-138.md +0 -0
  83. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-139.md +0 -0
  84. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-14.md +0 -0
  85. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-140.md +0 -0
  86. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-141.md +0 -0
  87. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-142.md +0 -0
  88. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-143.md +0 -0
  89. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-144.md +0 -0
  90. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-145.md +0 -0
  91. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-146.md +0 -0
  92. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-147.md +0 -0
  93. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-148.md +0 -0
  94. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-149.md +0 -0
  95. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-15.md +0 -0
  96. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-150.md +0 -0
  97. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-151.md +0 -0
  98. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-152.md +0 -0
  99. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-153.md +0 -0
  100. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-154.md +0 -0
  101. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-155.md +0 -0
  102. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-156.md +0 -0
  103. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-157.md +0 -0
  104. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-158.md +0 -0
  105. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-159.md +0 -0
  106. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-16.md +0 -0
  107. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-160.md +0 -0
  108. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-161.md +0 -0
  109. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-162.md +0 -0
  110. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-163.md +0 -0
  111. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-164.md +0 -0
  112. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-165.md +0 -0
  113. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-166.md +0 -0
  114. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-167.md +0 -0
  115. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-168.md +0 -0
  116. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-169.md +0 -0
  117. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-17.md +0 -0
  118. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-18.md +0 -0
  119. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-19.md +0 -0
  120. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-2.md +0 -0
  121. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-20.md +0 -0
  122. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-21.md +0 -0
  123. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-22.md +0 -0
  124. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-23.md +0 -0
  125. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-24.md +0 -0
  126. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-25.md +0 -0
  127. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-26.md +0 -0
  128. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-27.md +0 -0
  129. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-28.md +0 -0
  130. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-29.md +0 -0
  131. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-3.md +0 -0
  132. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-30.md +0 -0
  133. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-31.md +0 -0
  134. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-32.md +0 -0
  135. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-33.md +0 -0
  136. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-34.md +0 -0
  137. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-35.md +0 -0
  138. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-36.md +0 -0
  139. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-37.md +0 -0
  140. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-38.md +0 -0
  141. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-39.md +0 -0
  142. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-4.md +0 -0
  143. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-40.md +0 -0
  144. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-41.md +0 -0
  145. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-42.md +0 -0
  146. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-43.md +0 -0
  147. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-44.md +0 -0
  148. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-45.md +0 -0
  149. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-46.md +0 -0
  150. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-47.md +0 -0
  151. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-48.md +0 -0
  152. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-49.md +0 -0
  153. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-5.md +0 -0
  154. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-50.md +0 -0
  155. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-51.md +0 -0
  156. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-52.md +0 -0
  157. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-53.md +0 -0
  158. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-54.md +0 -0
  159. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-55.md +0 -0
  160. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-56.md +0 -0
  161. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-57.md +0 -0
  162. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-58.md +0 -0
  163. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-59.md +0 -0
  164. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-6.md +0 -0
  165. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-60.md +0 -0
  166. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-61.md +0 -0
  167. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-62.md +0 -0
  168. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-63.md +0 -0
  169. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-64.md +0 -0
  170. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-65.md +0 -0
  171. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-66.md +0 -0
  172. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-67.md +0 -0
  173. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-68.md +0 -0
  174. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-69.md +0 -0
  175. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-7.md +0 -0
  176. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-70.md +0 -0
  177. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-71.md +0 -0
  178. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-72.md +0 -0
  179. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-73.md +0 -0
  180. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-74.md +0 -0
  181. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-75.md +0 -0
  182. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-76.md +0 -0
  183. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-77.md +0 -0
  184. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-78.md +0 -0
  185. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-79.md +0 -0
  186. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-8.md +0 -0
  187. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-80.md +0 -0
  188. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-81.md +0 -0
  189. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-82.md +0 -0
  190. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-83.md +0 -0
  191. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-84.md +0 -0
  192. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-85.md +0 -0
  193. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-86.md +0 -0
  194. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-87.md +0 -0
  195. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-88.md +0 -0
  196. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-89.md +0 -0
  197. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-9.md +0 -0
  198. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-90.md +0 -0
  199. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-91.md +0 -0
  200. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-92.md +0 -0
  201. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-93.md +0 -0
  202. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-94.md +0 -0
  203. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-95.md +0 -0
  204. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-96.md +0 -0
  205. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-97.md +0 -0
  206. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-98.md +0 -0
  207. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/field-trials/2026-05-field-trial-99.md +0 -0
  208. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/fr/index.md +0 -0
  209. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/fr/tutorials/getting-started.md +0 -0
  210. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/how-to/add-new-domain.md +0 -0
  211. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/how-to/api-versioning.md +0 -0
  212. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/how-to/async-use-case.md +0 -0
  213. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/how-to/background-tasks.md +0 -0
  214. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/how-to/configure-auth.md +0 -0
  215. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/how-to/cors.md +0 -0
  216. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/how-to/custom-auth-middleware.md +0 -0
  217. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/how-to/dependency-injection.md +0 -0
  218. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/how-to/domain-events.md +0 -0
  219. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/how-to/file-upload.md +0 -0
  220. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/how-to/lifespan-and-app-state.md +0 -0
  221. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/how-to/middleware-stack.md +0 -0
  222. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/how-to/new-project.md +0 -0
  223. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/how-to/problem-details.md +0 -0
  224. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/how-to/response-patterns.md +0 -0
  225. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/how-to/run-tests.md +0 -0
  226. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/how-to/soft-delete.md +0 -0
  227. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/how-to/sqlalchemy-repository.md +0 -0
  228. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/how-to/streaming.md +0 -0
  229. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/how-to/structured-logging.md +0 -0
  230. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/how-to/validation.md +0 -0
  231. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/how-to/webhook.md +0 -0
  232. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/howto/mcp-setup.md +0 -0
  233. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/index.md +0 -0
  234. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/ja/explanation/architecture.md +0 -0
  235. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/ja/explanation/design-philosophy.md +0 -0
  236. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/ja/how-to/add-new-domain.md +0 -0
  237. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/ja/how-to/configure-auth.md +0 -0
  238. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/ja/how-to/new-project.md +0 -0
  239. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/ja/how-to/run-tests.md +0 -0
  240. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
  241. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/ja/howto/mcp-setup.md +0 -0
  242. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/ja/index.md +0 -0
  243. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/ja/reference/api.md +0 -0
  244. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/ja/reference/configuration.md +0 -0
  245. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/ja/reference/framework-modules.md +0 -0
  246. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/ja/tutorials/first-domain.md +0 -0
  247. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/ja/tutorials/getting-started.md +0 -0
  248. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/pt-br/index.md +0 -0
  249. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/pt-br/tutorials/getting-started.md +0 -0
  250. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/reference/api.md +0 -0
  251. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/reference/configuration.md +0 -0
  252. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/reference/framework-modules.md +0 -0
  253. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/roadmap.md +0 -0
  254. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/templates/field-trial-report.md +0 -0
  255. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/todo/current.md +0 -0
  256. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/tutorials/first-domain.md +0 -0
  257. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/tutorials/getting-started.md +0 -0
  258. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/zh/index.md +0 -0
  259. {nene2_python-1.8.40 → nene2_python-1.8.42}/docs/zh/tutorials/getting-started.md +0 -0
  260. {nene2_python-1.8.40 → nene2_python-1.8.42}/package-lock.json +0 -0
  261. {nene2_python-1.8.40 → nene2_python-1.8.42}/package.json +0 -0
  262. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/example/__init__.py +0 -0
  263. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/example/__main__.py +0 -0
  264. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/example/app.py +0 -0
  265. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/example/comment/__init__.py +0 -0
  266. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/example/comment/entity.py +0 -0
  267. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/example/comment/exceptions.py +0 -0
  268. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/example/comment/handler.py +0 -0
  269. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/example/comment/repository.py +0 -0
  270. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/example/comment/sqlalchemy_repository.py +0 -0
  271. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/example/comment/use_case.py +0 -0
  272. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/example/mcp.py +0 -0
  273. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/example/note/__init__.py +0 -0
  274. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/example/note/async_use_case.py +0 -0
  275. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/example/note/entity.py +0 -0
  276. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/example/note/exceptions.py +0 -0
  277. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/example/note/handler.py +0 -0
  278. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/example/note/repository.py +0 -0
  279. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/example/note/sqlalchemy_repository.py +0 -0
  280. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/example/note/use_case.py +0 -0
  281. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/example/schema.py +0 -0
  282. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/example/tag/__init__.py +0 -0
  283. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/example/tag/entity.py +0 -0
  284. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/example/tag/exceptions.py +0 -0
  285. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/example/tag/handler.py +0 -0
  286. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/example/tag/repository.py +0 -0
  287. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/example/tag/sqlalchemy_repository.py +0 -0
  288. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/example/tag/use_case.py +0 -0
  289. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/__init__.py +0 -0
  290. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/auth/__init__.py +0 -0
  291. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/auth/api_key.py +0 -0
  292. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/auth/bearer_token.py +0 -0
  293. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/auth/deps.py +0 -0
  294. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/auth/exceptions.py +0 -0
  295. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/auth/interfaces.py +0 -0
  296. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/auth/local_verifier.py +0 -0
  297. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/cache/__init__.py +0 -0
  298. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/cache/ttl.py +0 -0
  299. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/config/__init__.py +0 -0
  300. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/config/settings.py +0 -0
  301. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/database/__init__.py +0 -0
  302. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/database/exceptions.py +0 -0
  303. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/database/health.py +0 -0
  304. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/database/interfaces.py +0 -0
  305. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/database/sqlalchemy_executor.py +0 -0
  306. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/database/utils.py +0 -0
  307. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/http/__init__.py +0 -0
  308. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/http/etag.py +0 -0
  309. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/http/health.py +0 -0
  310. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/http/pagination.py +0 -0
  311. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/http/problem_details.py +0 -0
  312. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/log/__init__.py +0 -0
  313. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/log/setup.py +0 -0
  314. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/mcp/__init__.py +0 -0
  315. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/mcp/http_client.py +0 -0
  316. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/mcp/server.py +0 -0
  317. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/middleware/__init__.py +0 -0
  318. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/middleware/domain_exception.py +0 -0
  319. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/middleware/error_handler.py +0 -0
  320. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/middleware/request_id.py +0 -0
  321. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/middleware/request_logging.py +0 -0
  322. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/middleware/request_size_limit.py +0 -0
  323. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/middleware/security_headers.py +0 -0
  324. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/middleware/setup.py +0 -0
  325. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/middleware/throttle.py +0 -0
  326. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/py.typed +0 -0
  327. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/security/__init__.py +0 -0
  328. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/security/webhook.py +0 -0
  329. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/use_case/__init__.py +0 -0
  330. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/use_case/protocols.py +0 -0
  331. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/validation/__init__.py +0 -0
  332. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/nene2/validation/exceptions.py +0 -0
  333. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/scripts/__init__.py +0 -0
  334. {nene2_python-1.8.40 → nene2_python-1.8.42}/src/scripts/export_openapi.py +0 -0
  335. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/__init__.py +0 -0
  336. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/conftest.py +0 -0
  337. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/example/__init__.py +0 -0
  338. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/example/comment/__init__.py +0 -0
  339. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/example/comment/test_comment_http.py +0 -0
  340. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/example/comment/test_comment_repository.py +0 -0
  341. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/example/comment/test_comment_use_case.py +0 -0
  342. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/example/conftest.py +0 -0
  343. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/example/note/__init__.py +0 -0
  344. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/example/note/test_async_note_use_case.py +0 -0
  345. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/example/note/test_list_notes.py +0 -0
  346. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/example/note/test_note_repository.py +0 -0
  347. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/example/tag/__init__.py +0 -0
  348. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/example/tag/test_tag_repository.py +0 -0
  349. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/example/tag/test_tags.py +0 -0
  350. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/example/test_cors.py +0 -0
  351. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/example/test_mcp.py +0 -0
  352. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/__init__.py +0 -0
  353. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/auth/__init__.py +0 -0
  354. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/auth/test_api_key.py +0 -0
  355. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/auth/test_bearer_token.py +0 -0
  356. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/auth/test_make_require_auth.py +0 -0
  357. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/auth/test_token_issuer.py +0 -0
  358. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/cache/__init__.py +0 -0
  359. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/cache/test_ttl.py +0 -0
  360. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/config/__init__.py +0 -0
  361. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/config/test_settings.py +0 -0
  362. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/database/__init__.py +0 -0
  363. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/database/test_transaction.py +0 -0
  364. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/database/test_utils.py +0 -0
  365. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/http/__init__.py +0 -0
  366. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/http/test_etag.py +0 -0
  367. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/http/test_health.py +0 -0
  368. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/http/test_pagination.py +0 -0
  369. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/http/test_problem_details.py +0 -0
  370. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/log/__init__.py +0 -0
  371. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/log/test_setup.py +0 -0
  372. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/mcp/__init__.py +0 -0
  373. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/mcp/test_http_client.py +0 -0
  374. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/mcp/test_server.py +0 -0
  375. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/middleware/__init__.py +0 -0
  376. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/middleware/test_error_handler.py +0 -0
  377. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/middleware/test_request_id.py +0 -0
  378. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/middleware/test_request_logging.py +0 -0
  379. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/middleware/test_request_size_limit.py +0 -0
  380. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/middleware/test_security_headers.py +0 -0
  381. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/middleware/test_setup_middlewares.py +0 -0
  382. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
  383. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/middleware/test_throttle.py +0 -0
  384. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/security/__init__.py +0 -0
  385. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/security/test_webhook.py +0 -0
  386. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/use_case/__init__.py +0 -0
  387. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/use_case/test_protocols.py +0 -0
  388. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/use_case/test_run_in_threadpool.py +0 -0
  389. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/validation/__init__.py +0 -0
  390. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/nene2/validation/test_exceptions.py +0 -0
  391. {nene2_python-1.8.40 → nene2_python-1.8.42}/tests/scripts/__init__.py +0 -0
  392. {nene2_python-1.8.40 → nene2_python-1.8.42}/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.40
3
+ Version: 1.8.42
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,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
+
@@ -0,0 +1,423 @@
1
+ # FT171: asyncio モジュール
2
+
3
+ **日付**: 2026-05-21
4
+ **テーマ**: `asyncio` モジュール — `Task`・`gather`・`Lock`・`Queue`・`Semaphore`・`Event`・タイムアウト
5
+ **セキュリティ診断**: **あり**(171 % 3 = 0)
6
+
7
+ ---
8
+
9
+ ## 概要
10
+
11
+ Python 標準ライブラリの `asyncio` モジュールを nene2-python フレームワーク上で検証した。
12
+ FastAPI は ASGI フレームワークであり、すべてのリクエストハンドラーが非同期で動作するため、
13
+ `asyncio` の正しい使い方は nene2-python の設計の根幹。
14
+ 非同期レースコンディション・TOCTOU・グローバル状態の安全性が FT171 のセキュリティ診断の中心。
15
+
16
+ ---
17
+
18
+ ## 実装したサンプルアプリ
19
+
20
+ **場所**: `/home/xi/docker/nene2-python-FT/ft171-asyncio/`
21
+
22
+ ### 主要機能
23
+
24
+ | 関数/クラス | 概要 |
25
+ |---|---|
26
+ | `fetch_simulated()` / `fetch_all()` | `asyncio.gather` で並列フェッチ(模擬) |
27
+ | `fetch_with_timeout()` | `asyncio.wait_for()` タイムアウト付きフェッチ |
28
+ | `SafeCounter` | `asyncio.Lock()` で保護された並行カウンター |
29
+ | `race_condition_demo()` | Lock あり/なしでのカウンター競合デモ |
30
+ | `producer_consumer()` | `asyncio.Queue` を使ったプロデューサー・コンシューマー |
31
+ | `limited_fetch()` | `asyncio.Semaphore` で並行数を制限したフェッチ |
32
+ | `event_demo()` | `asyncio.Event` でタスク間シグナリング |
33
+ | `run_with_timeout()` | `asyncio.wait_for` でタイムアウト制御 |
34
+ | `measure_parallel_vs_sequential()` | 並列 vs 逐次の実行時間比較 |
35
+
36
+ ### HTTP エンドポイント
37
+
38
+ | メソッド | パス | 概要 |
39
+ |---|---|---|
40
+ | POST | `/asyncio/fetch-all` | gather で並列フェッチ |
41
+ | GET | `/asyncio/fetch-timeout` | wait_for タイムアウト付きフェッチ |
42
+ | GET | `/asyncio/race-condition` | Lock あり/なし競合デモ |
43
+ | GET | `/asyncio/counter/increment` | Lock 保護カウンター加算 |
44
+ | GET | `/asyncio/counter` | カウンター取得 |
45
+ | POST | `/asyncio/counter/reset` | カウンターリセット |
46
+ | GET | `/asyncio/producer-consumer` | Queue プロデューサー・コンシューマー |
47
+ | POST | `/asyncio/limited-fetch` | Semaphore 並行数制限フェッチ |
48
+ | GET | `/asyncio/event` | Event シグナリング |
49
+ | GET | `/asyncio/timeout` | タイムアウト制御デモ |
50
+ | GET | `/asyncio/parallel-vs-sequential` | 並列 vs 逐次の実行時間比較 |
51
+
52
+ ---
53
+
54
+ ## テスト結果
55
+
56
+ **27 passed(摩擦1件: pytest-asyncio 追加が必要)**
57
+
58
+ ```
59
+ 27 passed in 1.22s
60
+ ```
61
+
62
+ ---
63
+
64
+ ## 摩擦ポイント
65
+
66
+ ### F-1: `pytest-asyncio` がデフォルト依存に含まれておらずエラー(深刻度: 低)
67
+
68
+ **事象**: `@pytest.mark.asyncio` を使ったテストを書いたが、`pytest-asyncio` が未インストールで `ModuleNotFoundError`。
69
+ **原因**: FT サンドボックスの `pyproject.toml` には `pytest` しか含まれておらず、`pytest-asyncio` が未追加。
70
+ **対応**: `uv add pytest-asyncio` で追加。今後の asyncio 系 FT では最初から依存に含める。
71
+
72
+ ---
73
+
74
+ ## 観察点
75
+
76
+ ### 観察1: `asyncio.gather()` で複数タスクを並列実行し全完了を待つ
77
+
78
+ ```python
79
+ tasks = [asyncio.create_task(fetch_simulated(url)) for url in urls]
80
+ results = list(await asyncio.gather(*tasks))
81
+ ```
82
+
83
+ `asyncio.gather()` はすべてのタスクの完了を待ち、結果リストを返す。
84
+ 順序はタスクの投入順が保証される(完了順ではない)。
85
+ n 個の IO 待ちタスクを並列化すると実測でほぼ n 倍速くなることを確認(speedup > 1.5倍保証)。
86
+
87
+ ### 観察2: `asyncio.Lock()` でグローバル状態を保護する
88
+
89
+ ```python
90
+ class SafeCounter:
91
+ def __init__(self) -> None:
92
+ self._count = 0
93
+ self._lock = asyncio.Lock()
94
+
95
+ async def increment(self) -> int:
96
+ async with self._lock:
97
+ self._count += 1
98
+ return self._count
99
+ ```
100
+
101
+ asyncio は GIL があるため Python の `count += 1` は通常 atomic だが、
102
+ `await asyncio.sleep(0)` 等の協調切り替えポイントがある場合、
103
+ チェックと更新の間に他のコルーチンが割り込む可能性がある。
104
+ FastAPI で `_count = await get(); await something(); await set(_count + 1)` のような分割書き込みは Lock 必須。
105
+
106
+ ### 観察3: `asyncio.Semaphore` で外部 API の並行数を制限する
107
+
108
+ ```python
109
+ sem = asyncio.Semaphore(3) # 同時に3つまで
110
+
111
+ async def guarded_fetch(url: str) -> ...:
112
+ async with sem:
113
+ return await fetch(url)
114
+
115
+ await asyncio.gather(*[guarded_fetch(url) for url in urls])
116
+ ```
117
+
118
+ 外部 API・DB 接続プールには必ず Semaphore で上限を設定する。
119
+ `asyncio.gather()` に 1000 タスクを渡すと 1000 並列になり、外部サービスの DoS になりうる。
120
+
121
+ ### 観察4: `asyncio.wait_for()` はキャンセル後も内部タスクが走り続ける
122
+
123
+ ```python
124
+ try:
125
+ result = await asyncio.wait_for(slow_operation(10), timeout=1.0)
126
+ except asyncio.TimeoutError:
127
+ pass # ← タイムアウトしたが内部の slow_operation は???
128
+ ```
129
+
130
+ `asyncio.wait_for()` は `TimeoutError` 時にコルーチンに `CancelledError` を発生させる。
131
+ コルーチンが `CancelledError` を適切にハンドリングしなければリソースリークになる。
132
+ `try/finally` でクリーンアップを確実に行うパターンが必要。
133
+
134
+ ### 観察5: `asyncio.Event` でタスク間の順序を保証する
135
+
136
+ ```python
137
+ ready = asyncio.Event()
138
+ await ready.wait() # シグナルを待つ
139
+ ready.set() # シグナルを発する
140
+ ```
141
+
142
+ `asyncio.Event` は `threading.Event` の非同期版。
143
+ ウェブソケット接続確立後に処理開始・DB 初期化完了後にリクエスト受付開始、などに使える。
144
+
145
+ ---
146
+
147
+ ## nene2-python フレームワークとの統合
148
+
149
+ - FastAPI のルートハンドラーはすべて非同期のため、`asyncio.Lock` + グローバル状態パターンは nene2 の `TtlCache[V]` に適用済み
150
+ - `asyncio.Semaphore` は外部 API 呼び出し(`httpx.AsyncClient`)の並行数制限に必須
151
+ - `asyncio.Queue` は nene2 の MCP サーバーでのメッセージキューとして直接使える
152
+ - `asyncio.wait_for()` タイムアウトは nene2 の DB クエリタイムアウトパターンに適用できる
153
+ - `asyncio.gather()` の `return_exceptions=True` オプションで個別の失敗を無視してできるだけ多く取得するパターンも有効
154
+
155
+ ---
156
+
157
+ ## Developer Experience (DX) Review
158
+
159
+ ### ペルソナ1: 初心者(Python 歴1年・独学中・女性・バックエンド志望)
160
+
161
+ `async def` と `await` の概念は FastAPI のチュートリアルで最初に触れるが、
162
+ 「なぜ await が必要か」の理解が浅いまま使う傾向がある。
163
+
164
+ **ドキュメント理解**: `asyncio.gather()` の使い方は直感的。
165
+ `asyncio.Lock()` が必要な場面(グローバル状態の変更)は説明なしでは気づかない。
166
+ nene2 how-to に「FastAPI で共有状態を使う場合は Lock 必須」の例があると事故を防げる。
167
+
168
+ **事故リスク**: 高。`await` を書き忘れるとコルーチンオブジェクトが戻り値になり、
169
+ 実行されないまま成功したように見えるサイレントエラーになる。mypy / ruff が検出できる。
170
+
171
+ **規約の使いやすさ**: `async with lock:` のパターンはコピペで使える。
172
+
173
+ ### ペルソナ2: ロースキル経験者(Python 歴3-4年・スクリプト系・男性・SES)
174
+
175
+ 同期コードを async に変換するとき、ブロッキング処理(`time.sleep`・`open()`・`requests`)を
176
+ `await` なしで呼んでしまいイベントループをブロックするミスが多い。
177
+
178
+ **コピペ可能性**: `asyncio.gather()` と `asyncio.Semaphore()` はサンプルを見れば再現できる。
179
+
180
+ **拡張時の罠**: `asyncio.Lock()` を使わずにグローバルな `dict` を書き換えると、
181
+ 同時リクエストで key が消えたりすることがある。テストでは再現しにくい。
182
+
183
+ **セキュリティ的な事故リスク**: 高。非同期 TOCTOU は認証バイパスに直結する(後述のセキュリティ診断参照)。
184
+
185
+ ### ペルソナ3: フロントエンド寄り経験者(React/TS 歴4年・バックエンド転向中・ノンバイナリ)
186
+
187
+ JavaScript の `Promise.all()` と `asyncio.gather()` は概念的に同等。
188
+ `async/await` 構文も TypeScript と同じなので学習コストが低い。
189
+
190
+ **エラーレスポンスの質**: `asyncio.TimeoutError` が `ErrorHandlerMiddleware` で捕捉されて 500 になる場合、
191
+ クライアントには情報が少ない。エンドポイントレベルで 422 か 504 を返す設計が必要。
192
+
193
+ **事故リスク**: 中。asyncio のイベントループブロッキングは JavaScript の概念と同じで理解しやすい。
194
+
195
+ ### ペルソナ4: バックエンド経験者(Django/FastAPI 歴5-6年・男性・リードエンジニア)
196
+
197
+ Django は WSGI(同期)が主流で、非同期対応は Django 3.1+ で追加された後付け機能。
198
+ nene2-python の FastAPI は最初から非同期前提のため、`asyncio.Lock`・`asyncio.Semaphore` が設計の基本になる。
199
+
200
+ **他フレームワークとの差異**:
201
+ - Django: `django-channels` で非同期対応(後付け)
202
+ - nene2-python: FastAPI + asyncio で最初から非同期
203
+ - スレッドセーフな Django ミドルウェアの発想を asyncio に持ち込むと `threading.Lock` を使ってしまうミス
204
+
205
+ **本番投入可能性**: 問題なし。`asyncio.Semaphore` による外部 API 並行数制限は本番必須。
206
+
207
+ ### ペルソナ5: シニアエンジニア(設計・コードレビュー担当・女性・10-12年)
208
+
209
+ **コードレビューチェックポイント**:
210
+ - [ ] グローバル/クラス変数への非同期書き込みに `asyncio.Lock()` が使われているか
211
+ - [ ] ブロッキング IO(`open()`・`requests.get()`・`time.sleep()`)をイベントループで直接呼んでいないか
212
+ - [ ] `asyncio.wait_for()` のキャンセル後にリソースリークが発生しないか(`try/finally` の確認)
213
+ - [ ] 外部 API 呼び出しに `asyncio.Semaphore` で並行数制限があるか
214
+ - [ ] `asyncio.gather(*tasks)` でエラーが1件でも起きると他タスクも失敗するが、`return_exceptions=True` が必要な場面かどうか
215
+
216
+ **チームでの安全なパターン**: 認証チェックと状態変更の間に `await` がある場合は必ず TOCTOU レビューを実施する。
217
+
218
+ ### ペルソナ6: 設計者・ポリシー照合(nene2-python 設計ポリシー目線)
219
+
220
+ **ポリシー達成度**: 高
221
+
222
+ **「初心者でも安全な API」達成度**: 中
223
+ - `SafeCounter` のように Lock を隠蔽した API を提供するパターンが必要
224
+ - グローバル状態(`_lru`・`_counter` 等)を使う場合の Lock 要件が CLAUDE.md に未記載
225
+
226
+ **設計上の負債**:
227
+ - CLAUDE.md のセキュリティポリシーに「非同期レースコンディション」が明記されていない(テンプレートにはある)
228
+ - nene2 の `TtlCache` の asyncio 安全性が明文化されていない
229
+
230
+ **Follow-up Issue 候補**: `docs: CLAUDE.md の非同期セクションに asyncio.Lock 要件を追記`
231
+
232
+ ---
233
+
234
+ ## セキュリティ診断(FT171 % 3 = 0)
235
+
236
+ > **診断方針**: Django・FastAPI・SQLAlchemy 本体でも CVE が報告されてきたレベルの
237
+ > 攻撃ベクターを対象とする。「動いているから安全」は不正解。
238
+ > 実装ミスが起きやすい箇所を意図的に探し、問題がなければその理由まで記録する。
239
+
240
+ ### 1. OWASP API Security Top 10 (2023)
241
+
242
+ #### API1: BOLA / IDOR
243
+ - **結果**: ✅ 該当なし(ユーザー所有リソースなし)。
244
+
245
+ #### API2: 認証の破損
246
+ - **結果**: ✅ 認証なし(FT サンドボックス)。
247
+
248
+ #### API3: Mass Assignment
249
+ - **結果**: ✅ Pydantic デフォルト(extra="ignore")で未知フィールドは無視される。
250
+
251
+ #### API4: 無制限リソース消費
252
+ - `FetchBody.urls: list[str] = Field(max_length=20)` で最大20 URL
253
+ - `LimitedFetchBody.concurrency: int = Field(ge=1, le=10)` で最大10並行
254
+ - `Queue(maxsize=3)` で back-pressure あり
255
+ - **結果**: ✅ 全入力に上限あり。Semaphore で外部リソース使用量を制限している。
256
+
257
+ #### API5〜API10
258
+ - **結果**: ✅ 該当なし(外部 URL フェッチなし・認証エンドポイントなし)。
259
+
260
+ ---
261
+
262
+ ### 2. インジェクション攻撃
263
+
264
+ - **結果**: ✅ 全体的に該当なし。`demos.py` はネットワーク接続なし・ファイル操作なし・SQL なし。
265
+
266
+ ---
267
+
268
+ ### 3. 認証・認可
269
+
270
+ - **結果**: ✅ FT サンドボックスのため認証なし。nene2 本体の認証実装は FT165 で検証済み。
271
+
272
+ ---
273
+
274
+ ### 4. 入力バリデーション
275
+
276
+ - `urls`, `concurrency`, `n`, `items`, `timeout`, `duration` すべてに `max_length` または `ge/le` あり
277
+ - **結果**: ✅ 全 HTTP 境界に型 + 範囲バリデーション済み。
278
+
279
+ ---
280
+
281
+ ### 5. 情報漏洩
282
+
283
+ - **結果**: ⚠️ PyJWT PYSEC-2025-183 継続中(mcp 経由)。
284
+
285
+ ---
286
+
287
+ ### 6. Python / FastAPI 固有の攻撃ベクター
288
+
289
+ #### 非同期レースコンディション — **FT171 の重点診断項目**
290
+
291
+ **実測: asyncio でのグローバル状態競合**
292
+
293
+ ```python
294
+ # 危険パターン — 認証チェックと処理の間に await がある
295
+ async def handler() -> str:
296
+ if not authorized_users[user_id]:
297
+ return "denied"
298
+ await something() # ← ここで他のコルーチンが user_id を無効化できる
299
+ return do_privileged_action(user_id)
300
+ ```
301
+
302
+ 実測 TOCTOU デモ: `authorized_users["user1"] = True` の状態で
303
+ `check_and_use(user1)` と `revoke_in_race()` を `gather()` した結果、
304
+ **revoke タスクがチェック後に実行されたにもかかわらず `"allowed: user1"` が返った**。
305
+
306
+ これは「認証チェック → await → 権限変更 → 権限前提の操作」という TOCTOU の典型例。
307
+ FastAPI のリクエストハンドラーで認証状態をグローバルな辞書で管理する場合に発生する。
308
+
309
+ **FT171 サンドボックスの状況**: `/asyncio/counter` はグローバルな `_counter` を使用するが、
310
+ `SafeCounter` の `asyncio.Lock()` で保護されているため安全。
311
+ ただし `/asyncio/counter/increment` と `/asyncio/counter/reset` が連続して呼ばれると
312
+ 意図しないカウンターリセットが起きる(これは仕様上の問題であり、セキュリティ上の問題ではない)。
313
+
314
+ **防御策(実装済み)**:
315
+ - `SafeCounter._lock = asyncio.Lock()` でインクリメントを atomic 化
316
+ - `async with self._lock:` で確認と更新を不可分に
317
+
318
+ **残存リスク**: 認証に関わるグローバル状態(セッション管理等)を asyncio アプリで持つ場合、
319
+ チェックと使用の間に `await` があれば必ず `asyncio.Lock()` でガードが必要。
320
+ nene2-python はリクエストスコープで認証状態を持ちグローバル変数に書かないため、
321
+ この問題は nene2 コアでは発生しない設計になっている。
322
+
323
+ - **結果**: ✅ FT171 スコープでは Lock で保護済み。TOCTOU の概念的リスクを記録。
324
+
325
+ #### イベントループブロッキング
326
+
327
+ ```python
328
+ # 危険 — async 関数内でブロッキング IO を呼ぶ
329
+ async def bad_handler() -> None:
330
+ time.sleep(5) # ← イベントループ全体を5秒ブロック
331
+ data = open("file.txt").read() # ← sync IO でブロック
332
+ requests.get(url) # ← sync HTTP でブロック
333
+ ```
334
+
335
+ FastAPI は `asyncio.run_in_executor()` または `await asyncio.to_thread()` で
336
+ 同期処理を別スレッドに委ねることができる。
337
+ FT171 のデモ関数はすべて `asyncio.sleep()` を使い、ブロッキング処理なし。
338
+
339
+ - **結果**: ✅ FT171 内にブロッキング IO なし。
340
+
341
+ #### `asyncio.wait_for()` キャンセルリーク
342
+
343
+ `asyncio.wait_for()` タイムアウト時、内部コルーチンに `CancelledError` が発生する。
344
+ コルーチンが `CancelledError` を `except Exception:` で握りつぶすと、
345
+ タスクが完了せずリソースがリークする可能性がある。
346
+
347
+ FT171 の `slow_operation()` は `asyncio.sleep()` のみを使っており、
348
+ `CancelledError` は `asyncio.sleep()` から正しく伝播する。
349
+
350
+ - **結果**: ✅ `slow_operation()` はキャンセル安全。
351
+
352
+ #### Semaphore 枯渇
353
+
354
+ `asyncio.Semaphore(n)` を使っても、コルーチン内で例外が発生した場合
355
+ `async with sem:` ブロックが `__aexit__` を呼んでセマフォを解放するかが重要。
356
+ Python の `async with` は例外時も `__aexit__` を保証するため安全。
357
+
358
+ - **結果**: ✅ `async with sem:` は例外時も正しく解放される。
359
+
360
+ #### pickle / yaml
361
+ - **結果**: ✅ 該当なし。
362
+
363
+ #### 型強制攻撃 (Pydantic)
364
+ - `concurrency: int = Field(ge=1, le=10)` — `"3"` 文字列は int 変換後に範囲チェック
365
+ - **結果**: ✅ 範囲制限が適切。
366
+
367
+ ---
368
+
369
+ ### 7. 依存関係スキャン
370
+
371
+ ```
372
+ Found 1 known vulnerability: pyjwt 2.12.1 PYSEC-2025-183 (mcp 経由)
373
+ ```
374
+
375
+ - **スキャン結果**: 継続中(FT168 から変更なし)
376
+ - **対応方針**: mcp の更新を待つ
377
+
378
+ ---
379
+
380
+ ### 診断サマリー
381
+
382
+ | カテゴリ | 結果 | 最重要発見 |
383
+ |---|---|---|
384
+ | OWASP API Security Top 10 | ✅ 全通過 | 無制限リソース消費: Semaphore で適切に制限 |
385
+ | インジェクション攻撃 | ✅ | 該当なし |
386
+ | 認証・認可 | ✅ | FT サンドボックス |
387
+ | 入力バリデーション | ✅ | 全境界に ge/le/max_length あり |
388
+ | 情報漏洩 | ⚠️ | PyJWT 継続中 |
389
+ | **非同期レースコンディション** | ✅ | Lock で保護済み。TOCTOU 概念リスクを文書化 |
390
+ | イベントループブロッキング | ✅ | ブロッキング IO なし |
391
+ | asyncio.wait_for キャンセルリーク | ✅ | 正しくキャンセル伝播 |
392
+ | Semaphore 枯渇 | ✅ | async with が例外時も解放 |
393
+ | 依存関係 CVE | ⚠️ | PYSEC-2025-183(継続中) |
394
+
395
+ **総合評価**: 合格
396
+ **発見した新規脆弱性**: 0件
397
+ **セキュリティ観察**: TOCTOU パターンの概念リスクを記録(FT171 実装では発生しない設計)
398
+
399
+ ---
400
+
401
+ ## Follow-up Issues
402
+
403
+ | 優先度 | タイトル | 種別 |
404
+ |---|---|---|
405
+ | 高 | `docs: CLAUDE.md の非同期セクションに asyncio.Lock 要件と TOCTOU 注意点を追記` | docs |
406
+ | 中 | `docs: FastAPI でブロッキング IO を asyncio.to_thread() で安全に実行する how-to` | docs |
407
+ | 低 | `feat: TtlCache の asyncio 安全性(Lock 保護)を明文化しテストを追加` | feat |
408
+
409
+ ---
410
+
411
+ ## まとめ
412
+
413
+ `asyncio` モジュールは nene2-python の FastAPI 基盤に直結する重要機能。
414
+ 27 テスト全通過(摩擦1件: pytest-asyncio の追加が必要)。
415
+
416
+ **セキュリティ診断**: 非同期レースコンディションの実測デモを実施した。
417
+ TOCTOU(認証チェック後に `await` を挟んで状態変更が入るパターン)が概念的リスクとして存在するが、
418
+ FT171 の実装では `asyncio.Lock()` で保護されているため安全。
419
+ Semaphore による外部リソース並行数制限・`asyncio.wait_for()` のキャンセル安全性も確認した。
420
+
421
+ `asyncio.gather()` による並列化(最大5倍高速化確認)・`asyncio.Semaphore` による流量制御・
422
+ `asyncio.Queue` によるプロデューサー・コンシューマーパターンが nene2 本番実装で直接使えるパターンとして確立された。
423
+
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nene2-python"
3
- version = "1.8.40"
3
+ version = "1.8.42"
4
4
  description = "NENE2 Python — minimal API framework following NENE2's design philosophy"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}