nene2-python 1.8.21__tar.gz → 1.8.22__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 (269) hide show
  1. {nene2_python-1.8.21 → nene2_python-1.8.22}/PKG-INFO +1 -1
  2. nene2_python-1.8.22/docs/field-trials/2026-05-field-trial-76.md +152 -0
  3. {nene2_python-1.8.21 → nene2_python-1.8.22}/pyproject.toml +1 -1
  4. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/use_case/__init__.py +3 -1
  5. nene2_python-1.8.22/tests/nene2/use_case/test_run_in_threadpool.py +61 -0
  6. {nene2_python-1.8.21 → nene2_python-1.8.22}/uv.lock +1 -1
  7. {nene2_python-1.8.21 → nene2_python-1.8.22}/.env.example +0 -0
  8. {nene2_python-1.8.21 → nene2_python-1.8.22}/.github/workflows/ci.yml +0 -0
  9. {nene2_python-1.8.21 → nene2_python-1.8.22}/.github/workflows/docs.yml +0 -0
  10. {nene2_python-1.8.21 → nene2_python-1.8.22}/.github/workflows/publish.yml +0 -0
  11. {nene2_python-1.8.21 → nene2_python-1.8.22}/.gitignore +0 -0
  12. {nene2_python-1.8.21 → nene2_python-1.8.22}/.vitepress/config.mts +0 -0
  13. {nene2_python-1.8.21 → nene2_python-1.8.22}/.vitepress/theme/custom.css +0 -0
  14. {nene2_python-1.8.21 → nene2_python-1.8.22}/.vitepress/theme/index.ts +0 -0
  15. {nene2_python-1.8.21 → nene2_python-1.8.22}/AGENTS.md +0 -0
  16. {nene2_python-1.8.21 → nene2_python-1.8.22}/CHANGELOG.md +0 -0
  17. {nene2_python-1.8.21 → nene2_python-1.8.22}/CLAUDE.md +0 -0
  18. {nene2_python-1.8.21 → nene2_python-1.8.22}/Dockerfile +0 -0
  19. {nene2_python-1.8.21 → nene2_python-1.8.22}/LICENSE +0 -0
  20. {nene2_python-1.8.21 → nene2_python-1.8.22}/README.md +0 -0
  21. {nene2_python-1.8.21 → nene2_python-1.8.22}/alembic/README +0 -0
  22. {nene2_python-1.8.21 → nene2_python-1.8.22}/alembic/env.py +0 -0
  23. {nene2_python-1.8.21 → nene2_python-1.8.22}/alembic/script.py.mako +0 -0
  24. {nene2_python-1.8.21 → nene2_python-1.8.22}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
  25. {nene2_python-1.8.21 → nene2_python-1.8.22}/alembic.ini +0 -0
  26. {nene2_python-1.8.21 → nene2_python-1.8.22}/compose.yaml +0 -0
  27. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/adr/0001-toolchain.md +0 -0
  28. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/adr/0002-clean-architecture.md +0 -0
  29. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/adr/0003-security-first.md +0 -0
  30. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/adr/0004-ai-first-design.md +0 -0
  31. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/adr/0005-logging.md +0 -0
  32. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/adr/0006-rate-limiting.md +0 -0
  33. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/adr/0009-mcp-design.md +0 -0
  34. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/adr/0010-async-use-case.md +0 -0
  35. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
  36. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/de/index.md +0 -0
  37. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/de/tutorials/getting-started.md +0 -0
  38. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/explanation/architecture.md +0 -0
  39. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/explanation/design-philosophy.md +0 -0
  40. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-1.md +0 -0
  41. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-10.md +0 -0
  42. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-11.md +0 -0
  43. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-12.md +0 -0
  44. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-13.md +0 -0
  45. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-14.md +0 -0
  46. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-15.md +0 -0
  47. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-16.md +0 -0
  48. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-17.md +0 -0
  49. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-18.md +0 -0
  50. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-19.md +0 -0
  51. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-2.md +0 -0
  52. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-20.md +0 -0
  53. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-21.md +0 -0
  54. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-22.md +0 -0
  55. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-23.md +0 -0
  56. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-24.md +0 -0
  57. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-25.md +0 -0
  58. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-26.md +0 -0
  59. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-27.md +0 -0
  60. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-28.md +0 -0
  61. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-29.md +0 -0
  62. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-3.md +0 -0
  63. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-30.md +0 -0
  64. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-31.md +0 -0
  65. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-32.md +0 -0
  66. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-33.md +0 -0
  67. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-34.md +0 -0
  68. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-35.md +0 -0
  69. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-36.md +0 -0
  70. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-37.md +0 -0
  71. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-38.md +0 -0
  72. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-39.md +0 -0
  73. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-4.md +0 -0
  74. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-40.md +0 -0
  75. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-41.md +0 -0
  76. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-42.md +0 -0
  77. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-43.md +0 -0
  78. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-44.md +0 -0
  79. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-45.md +0 -0
  80. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-46.md +0 -0
  81. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-47.md +0 -0
  82. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-48.md +0 -0
  83. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-49.md +0 -0
  84. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-5.md +0 -0
  85. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-50.md +0 -0
  86. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-51.md +0 -0
  87. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-52.md +0 -0
  88. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-53.md +0 -0
  89. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-54.md +0 -0
  90. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-55.md +0 -0
  91. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-56.md +0 -0
  92. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-57.md +0 -0
  93. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-58.md +0 -0
  94. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-59.md +0 -0
  95. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-6.md +0 -0
  96. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-60.md +0 -0
  97. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-61.md +0 -0
  98. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-62.md +0 -0
  99. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-63.md +0 -0
  100. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-64.md +0 -0
  101. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-65.md +0 -0
  102. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-66.md +0 -0
  103. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-67.md +0 -0
  104. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-68.md +0 -0
  105. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-69.md +0 -0
  106. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-7.md +0 -0
  107. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-70.md +0 -0
  108. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-71.md +0 -0
  109. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-72.md +0 -0
  110. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-73.md +0 -0
  111. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-74.md +0 -0
  112. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-75.md +0 -0
  113. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-8.md +0 -0
  114. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/field-trials/2026-05-field-trial-9.md +0 -0
  115. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/fr/index.md +0 -0
  116. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/fr/tutorials/getting-started.md +0 -0
  117. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/how-to/add-new-domain.md +0 -0
  118. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/how-to/async-use-case.md +0 -0
  119. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/how-to/configure-auth.md +0 -0
  120. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/how-to/middleware-stack.md +0 -0
  121. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/how-to/new-project.md +0 -0
  122. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/how-to/problem-details.md +0 -0
  123. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/how-to/run-tests.md +0 -0
  124. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/how-to/sqlalchemy-repository.md +0 -0
  125. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/how-to/validation.md +0 -0
  126. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/howto/mcp-setup.md +0 -0
  127. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/index.md +0 -0
  128. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/ja/explanation/architecture.md +0 -0
  129. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/ja/explanation/design-philosophy.md +0 -0
  130. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/ja/how-to/add-new-domain.md +0 -0
  131. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/ja/how-to/configure-auth.md +0 -0
  132. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/ja/how-to/new-project.md +0 -0
  133. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/ja/how-to/run-tests.md +0 -0
  134. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
  135. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/ja/howto/mcp-setup.md +0 -0
  136. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/ja/index.md +0 -0
  137. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/ja/reference/api.md +0 -0
  138. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/ja/reference/configuration.md +0 -0
  139. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/ja/reference/framework-modules.md +0 -0
  140. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/ja/tutorials/first-domain.md +0 -0
  141. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/ja/tutorials/getting-started.md +0 -0
  142. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/pt-br/index.md +0 -0
  143. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/pt-br/tutorials/getting-started.md +0 -0
  144. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/reference/api.md +0 -0
  145. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/reference/configuration.md +0 -0
  146. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/reference/framework-modules.md +0 -0
  147. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/roadmap.md +0 -0
  148. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/todo/current.md +0 -0
  149. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/tutorials/first-domain.md +0 -0
  150. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/tutorials/getting-started.md +0 -0
  151. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/zh/index.md +0 -0
  152. {nene2_python-1.8.21 → nene2_python-1.8.22}/docs/zh/tutorials/getting-started.md +0 -0
  153. {nene2_python-1.8.21 → nene2_python-1.8.22}/package-lock.json +0 -0
  154. {nene2_python-1.8.21 → nene2_python-1.8.22}/package.json +0 -0
  155. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/example/__init__.py +0 -0
  156. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/example/__main__.py +0 -0
  157. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/example/app.py +0 -0
  158. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/example/comment/__init__.py +0 -0
  159. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/example/comment/entity.py +0 -0
  160. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/example/comment/exceptions.py +0 -0
  161. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/example/comment/handler.py +0 -0
  162. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/example/comment/repository.py +0 -0
  163. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/example/comment/sqlalchemy_repository.py +0 -0
  164. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/example/comment/use_case.py +0 -0
  165. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/example/mcp.py +0 -0
  166. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/example/note/__init__.py +0 -0
  167. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/example/note/async_use_case.py +0 -0
  168. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/example/note/entity.py +0 -0
  169. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/example/note/exceptions.py +0 -0
  170. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/example/note/handler.py +0 -0
  171. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/example/note/repository.py +0 -0
  172. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/example/note/sqlalchemy_repository.py +0 -0
  173. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/example/note/use_case.py +0 -0
  174. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/example/schema.py +0 -0
  175. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/example/tag/__init__.py +0 -0
  176. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/example/tag/entity.py +0 -0
  177. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/example/tag/exceptions.py +0 -0
  178. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/example/tag/handler.py +0 -0
  179. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/example/tag/repository.py +0 -0
  180. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/example/tag/sqlalchemy_repository.py +0 -0
  181. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/example/tag/use_case.py +0 -0
  182. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/__init__.py +0 -0
  183. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/auth/__init__.py +0 -0
  184. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/auth/api_key.py +0 -0
  185. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/auth/bearer_token.py +0 -0
  186. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/auth/exceptions.py +0 -0
  187. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/auth/interfaces.py +0 -0
  188. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/auth/local_verifier.py +0 -0
  189. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/config/__init__.py +0 -0
  190. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/config/settings.py +0 -0
  191. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/database/__init__.py +0 -0
  192. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/database/exceptions.py +0 -0
  193. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/database/health.py +0 -0
  194. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/database/interfaces.py +0 -0
  195. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/database/sqlalchemy_executor.py +0 -0
  196. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/database/utils.py +0 -0
  197. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/http/__init__.py +0 -0
  198. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/http/health.py +0 -0
  199. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/http/pagination.py +0 -0
  200. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/http/problem_details.py +0 -0
  201. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/log/__init__.py +0 -0
  202. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/log/setup.py +0 -0
  203. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/mcp/__init__.py +0 -0
  204. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/mcp/http_client.py +0 -0
  205. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/mcp/server.py +0 -0
  206. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/middleware/__init__.py +0 -0
  207. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/middleware/domain_exception.py +0 -0
  208. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/middleware/error_handler.py +0 -0
  209. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/middleware/request_id.py +0 -0
  210. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/middleware/request_logging.py +0 -0
  211. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/middleware/request_size_limit.py +0 -0
  212. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/middleware/security_headers.py +0 -0
  213. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/middleware/setup.py +0 -0
  214. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/middleware/throttle.py +0 -0
  215. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/py.typed +0 -0
  216. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/use_case/protocols.py +0 -0
  217. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/validation/__init__.py +0 -0
  218. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/nene2/validation/exceptions.py +0 -0
  219. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/scripts/__init__.py +0 -0
  220. {nene2_python-1.8.21 → nene2_python-1.8.22}/src/scripts/export_openapi.py +0 -0
  221. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/__init__.py +0 -0
  222. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/example/__init__.py +0 -0
  223. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/example/comment/__init__.py +0 -0
  224. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/example/comment/test_comment_http.py +0 -0
  225. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/example/comment/test_comment_repository.py +0 -0
  226. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/example/comment/test_comment_use_case.py +0 -0
  227. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/example/conftest.py +0 -0
  228. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/example/note/__init__.py +0 -0
  229. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/example/note/test_async_note_use_case.py +0 -0
  230. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/example/note/test_list_notes.py +0 -0
  231. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/example/note/test_note_repository.py +0 -0
  232. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/example/tag/__init__.py +0 -0
  233. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/example/tag/test_tag_repository.py +0 -0
  234. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/example/tag/test_tags.py +0 -0
  235. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/example/test_cors.py +0 -0
  236. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/example/test_mcp.py +0 -0
  237. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/nene2/__init__.py +0 -0
  238. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/nene2/auth/__init__.py +0 -0
  239. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/nene2/auth/test_api_key.py +0 -0
  240. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/nene2/auth/test_bearer_token.py +0 -0
  241. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/nene2/auth/test_token_issuer.py +0 -0
  242. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/nene2/config/__init__.py +0 -0
  243. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/nene2/config/test_settings.py +0 -0
  244. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/nene2/database/__init__.py +0 -0
  245. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/nene2/database/test_transaction.py +0 -0
  246. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/nene2/database/test_utils.py +0 -0
  247. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/nene2/http/__init__.py +0 -0
  248. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/nene2/http/test_health.py +0 -0
  249. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/nene2/http/test_pagination.py +0 -0
  250. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/nene2/http/test_problem_details.py +0 -0
  251. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/nene2/log/__init__.py +0 -0
  252. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/nene2/log/test_setup.py +0 -0
  253. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/nene2/mcp/__init__.py +0 -0
  254. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/nene2/mcp/test_http_client.py +0 -0
  255. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/nene2/middleware/__init__.py +0 -0
  256. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/nene2/middleware/test_error_handler.py +0 -0
  257. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/nene2/middleware/test_request_id.py +0 -0
  258. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/nene2/middleware/test_request_logging.py +0 -0
  259. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/nene2/middleware/test_request_size_limit.py +0 -0
  260. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/nene2/middleware/test_security_headers.py +0 -0
  261. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/nene2/middleware/test_setup_middlewares.py +0 -0
  262. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
  263. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/nene2/middleware/test_throttle.py +0 -0
  264. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/nene2/use_case/__init__.py +0 -0
  265. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/nene2/use_case/test_protocols.py +0 -0
  266. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/nene2/validation/__init__.py +0 -0
  267. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/nene2/validation/test_exceptions.py +0 -0
  268. {nene2_python-1.8.21 → nene2_python-1.8.22}/tests/scripts/__init__.py +0 -0
  269. {nene2_python-1.8.21 → nene2_python-1.8.22}/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.21
3
+ Version: 1.8.22
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,152 @@
1
+ # FT76: async ハンドラー + sync SQLAlchemy のイベントループブロッキング
2
+
3
+ **日付**: 2026-05-20
4
+ **テーマ**: FastAPI の async def ハンドラー内で同期 DB 処理を呼ぶとどうなるか
5
+ **バージョン**: v1.8.21
6
+ **FTディレクトリ**: `/home/xi/docker/nene2-python-FT/ft76-async-sync-db/`
7
+
8
+ ---
9
+
10
+ ## 概要
11
+
12
+ FastAPI は `async def` と `def` を混在できるが、同期処理の扱いが根本的に異なる。
13
+ nene2 の DB 層(`SqlAlchemyQueryExecutor` / `SqlAlchemyTransactionManager`)は同期実装のため、
14
+ `async def` ハンドラー内から直接呼ぶとイベントループをブロックする。
15
+ このFTでは3パターンの挙動を実測し、nene2 として何を提供すべきかを検証した。
16
+
17
+ ---
18
+
19
+ ## テスト対象の3パターン
20
+
21
+ | パターン | 実装 | 動作 |
22
+ |---|---|---|
23
+ | A | `async def` 内で `time.sleep()` / 同期DB直呼び | **イベントループをブロック** |
24
+ | B | `async def` 内で `run_in_executor()` 経由 | スレッドプールにオフロード(安全) |
25
+ | C | `def` ハンドラー(同期) | FastAPI が自動でスレッドプールに退避(安全) |
26
+
27
+ ---
28
+
29
+ ## 発見した問題
30
+
31
+ ### 問題1: async ハンドラー + sync DB = サイレントブロッキング
32
+
33
+ ```python
34
+ # ❌ よくある間違い
35
+ @app.post("/tasks")
36
+ async def create_task(body: TaskBody) -> JSONResponse:
37
+ result = _create_task_sync(body.title) # time.sleep / Session(...) など
38
+ return JSONResponse(result, status_code=201)
39
+ ```
40
+
41
+ `async def` 内で `time.sleep()` や同期 `Session` を呼ぶと、実行中は asyncio のイベントループが
42
+ 完全に停止する。他のリクエストはそのリクエストが完了するまで待たされる。
43
+
44
+ **特に危険なのは「少量のデータなら問題が出ない」点**。
45
+ 開発中 / ステージング環境では顕在化せず、本番で同時アクセスが増えてから遅延が爆発する。
46
+
47
+ ### 問題2: nene2 に async DB 層がない
48
+
49
+ ```python
50
+ # v1.8.21 時点: 存在しない
51
+ from nene2.database import AsyncSqlAlchemyQueryExecutor # ImportError
52
+ ```
53
+
54
+ `SqlAlchemyQueryExecutor.fetch_all()` / `SqlAlchemyTransactionManager.begin()` は
55
+ すべて同期実装。async def ハンドラーと組み合わせるには自前で `run_in_executor` を書く必要がある。
56
+
57
+ ### 問題3: run_in_executor パターンが冗長で発見しにくい
58
+
59
+ ```python
60
+ # ✅ 正しいが、毎回これを書くのは辛い
61
+ @app.post("/tasks")
62
+ async def create_task(body: TaskBody) -> JSONResponse:
63
+ loop = asyncio.get_event_loop()
64
+ result = await loop.run_in_executor(None, _create_task_sync, body.title, body.simulate_ms)
65
+ return JSONResponse(result, status_code=201)
66
+ ```
67
+
68
+ - `asyncio.get_event_loop()` は Python 3.10+ で deprecation warning を出すことがある
69
+ - `None` の意味(default executor = ThreadPoolExecutor)が自明でない
70
+ - 複数の引数を渡すには `functools.partial` か lambda が必要になりさらに冗長になる
71
+
72
+ ### 問題4: `def` ハンドラーが実は最もシンプルで安全
73
+
74
+ ```python
75
+ # ✅ sync def は FastAPI が自動でスレッドプールに退避する
76
+ @app.post("/tasks")
77
+ def create_task(body: TaskBody) -> JSONResponse:
78
+ result = _create_task_sync(body.title) # 安全
79
+ return JSONResponse(result, status_code=201)
80
+ ```
81
+
82
+ `async def` を書く必然性がないなら `def` を使うべきだが、
83
+ 「FastAPI = async」というイメージから全ハンドラーを `async def` にするユーザーが多い。
84
+ nene2 のドキュメントがこれを明示していない。
85
+
86
+ ---
87
+
88
+ ## テスト結果(全12件パス)
89
+
90
+ ```
91
+ test_sync_in_async_creates_task PASSED # パターンA: 機能はする(ブロッキングだが)
92
+ test_executor_creates_task PASSED # パターンB: executor 経由
93
+ test_sync_def_creates_task PASSED # パターンC: sync def
94
+ test_list_tasks PASSED
95
+ test_sync_def_does_not_block_event_loop PASSED # イベントループが自由であることを確認
96
+ test_sync_in_async_blocks_when_slow PASSED # 200ms スリープがそのまま待機時間になる
97
+ test_executor_avoids_blocking PASSED
98
+ test_middlewares_work_with_all_patterns PASSED # setup_middlewares() は全パターンで動作
99
+ test_422_on_invalid_title_length PASSED
100
+ test_422_on_negative_simulate_ms PASSED
101
+ test_no_async_db_layer_in_nene2 PASSED # 非同期DB層が存在しないことを確認
102
+ test_threadpool_pattern_is_verbose PASSED # 冗長さのドキュメンタリーテスト
103
+ ```
104
+
105
+ ---
106
+
107
+ ## 摩擦ポイント一覧
108
+
109
+ | ID | 内容 | 深刻度 |
110
+ |---|---|---|
111
+ | F76-1 | nene2 に async DB 層がなく、async ハンドラーでは run_in_executor が必要 | 高 |
112
+ | F76-2 | async def + sync DB のブロッキングがサイレント(警告なし) | 高 |
113
+ | F76-3 | run_in_executor の書き方が冗長で、正しい引数渡しが分かりにくい | 中 |
114
+ | F76-4 | ドキュメントが async def vs def の使い分けを説明していない | 中 |
115
+
116
+ ---
117
+
118
+ ## 使用感(主観評価)
119
+
120
+ ### 直感性 ★★☆☆☆
121
+
122
+ 「FastAPI を使っているので全ハンドラーを async def にする」のは自然な発想。
123
+ しかし nene2 の DB 層は同期なので、これが罠になる。
124
+ `async def` で書いたコードが正常に動き、テストも全部通る — でも本番では爆発する。
125
+ 「動いているから正しい」と思ってしまう構造が非常にやっかいで直感に反する。
126
+
127
+ ### 実害の深刻さ ★★★★☆
128
+
129
+ 本番環境で同時アクセスが増えてからはじめて顕在化する。
130
+ レスポンスが遅い → サーバーがダウン、という流れをたどる典型的なパフォーマンス地雷。
131
+ 「なぜか重い」「スケールしない」という症状で現れるため、根本原因の特定も遅れやすい。
132
+
133
+ ### 修正のしやすさ ★★☆☆☆
134
+
135
+ `run_in_executor` パターンを知っていれば直せるが、記述が冗長で毎回ハンドラーに書くのはつらい。
136
+ 根本解決(async DB 層の実装)はSQLAlchemy async対応が必要で、工数が大きい。
137
+ `def` ハンドラーへの移行が最もシンプルだが、`async` 依存のロジックが混在していると難しい。
138
+
139
+ ### 総合コメント
140
+
141
+ FastAPI + SQLAlchemy の組み合わせは最も多いユースケースの一つ。
142
+ 「nene2 を使えばすぐ始められる」が「パフォーマンス問題で詰まる」という流れは
143
+ ユーザー離れを引き起こす可能性がある。
144
+ `def` vs `async def` の使い分けガイドラインと、将来的な async DB サポートが必要。
145
+
146
+ ---
147
+
148
+ ## 推奨アクション
149
+
150
+ 1. **Issue**: `async def` ハンドラー内での `SqlAlchemy` 同期呼び出し警告をドキュメントに追加
151
+ 2. **Issue**: `run_in_threadpool()` ヘルパー(または `asyncify()` パターン)の提供
152
+ 3. **将来**: `AsyncSqlAlchemyQueryExecutor` の実装(SQLAlchemy 2.0 async 対応)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nene2-python"
3
- version = "1.8.21"
3
+ version = "1.8.22"
4
4
  description = "NENE2 Python — minimal API framework following NENE2's design philosophy"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
@@ -1,5 +1,7 @@
1
1
  """UseCase contracts — synchronous and asynchronous Protocol definitions."""
2
2
 
3
+ from starlette.concurrency import run_in_threadpool
4
+
3
5
  from .protocols import AsyncUseCaseProtocol, UseCaseProtocol
4
6
 
5
- __all__ = ["AsyncUseCaseProtocol", "UseCaseProtocol"]
7
+ __all__ = ["AsyncUseCaseProtocol", "UseCaseProtocol", "run_in_threadpool"]
@@ -0,0 +1,61 @@
1
+ """Tests for run_in_threadpool re-export."""
2
+
3
+ import asyncio
4
+ import threading
5
+ import time
6
+
7
+ import pytest
8
+
9
+ from nene2.use_case import run_in_threadpool
10
+ from nene2.use_case import run_in_threadpool as _rtp_alias
11
+
12
+
13
+ def _sync_identity(value: int) -> int:
14
+ return value
15
+
16
+
17
+ def _sync_slow(duration_s: float) -> str:
18
+ time.sleep(duration_s)
19
+ return "done"
20
+
21
+
22
+ def _sync_raises() -> None:
23
+ raise ValueError("boom")
24
+
25
+
26
+ def test_run_in_threadpool_returns_value() -> None:
27
+ result = asyncio.run(run_in_threadpool(_sync_identity, 42))
28
+ assert result == 42
29
+
30
+
31
+ def test_run_in_threadpool_runs_in_thread() -> None:
32
+ """sync 関数が呼び出し元スレッドとは別スレッドで実行される。"""
33
+ caller_thread = threading.current_thread().ident
34
+
35
+ def _get_thread_id() -> int | None:
36
+ return threading.current_thread().ident
37
+
38
+ thread_id = asyncio.run(run_in_threadpool(_get_thread_id))
39
+ assert thread_id != caller_thread
40
+
41
+
42
+ async def test_run_in_threadpool_does_not_block_event_loop() -> None:
43
+ """並行して 3 つの 100ms スリープをオフロードし、合計が ~100ms になることを確認。"""
44
+ t0 = time.perf_counter()
45
+ await asyncio.gather(
46
+ run_in_threadpool(_sync_slow, 0.1),
47
+ run_in_threadpool(_sync_slow, 0.1),
48
+ run_in_threadpool(_sync_slow, 0.1),
49
+ )
50
+ elapsed = time.perf_counter() - t0
51
+ # 順次なら 0.3s、並列なら ~0.1s — 0.25s 未満ならスレッドプール並列化が効いている
52
+ assert elapsed < 0.25
53
+
54
+
55
+ async def test_run_in_threadpool_propagates_exception() -> None:
56
+ with pytest.raises(ValueError, match="boom"):
57
+ await run_in_threadpool(_sync_raises)
58
+
59
+
60
+ def test_run_in_threadpool_is_importable_from_nene2_use_case() -> None:
61
+ assert callable(_rtp_alias)
@@ -925,7 +925,7 @@ wheels = [
925
925
 
926
926
  [[package]]
927
927
  name = "nene2-python"
928
- version = "1.8.21"
928
+ version = "1.8.22"
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