nene2-python 1.8.11__tar.gz → 1.8.13__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 (242) hide show
  1. {nene2_python-1.8.11 → nene2_python-1.8.13}/CHANGELOG.md +22 -0
  2. {nene2_python-1.8.11 → nene2_python-1.8.13}/PKG-INFO +1 -1
  3. nene2_python-1.8.13/docs/field-trials/2026-05-field-trial-52.md +90 -0
  4. nene2_python-1.8.13/docs/field-trials/2026-05-field-trial-53.md +81 -0
  5. {nene2_python-1.8.11 → nene2_python-1.8.13}/pyproject.toml +1 -1
  6. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/auth/api_key.py +9 -7
  7. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/auth/local_verifier.py +2 -2
  8. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/nene2/auth/test_api_key.py +35 -0
  9. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/nene2/auth/test_bearer_token.py +13 -0
  10. {nene2_python-1.8.11 → nene2_python-1.8.13}/uv.lock +1 -1
  11. {nene2_python-1.8.11 → nene2_python-1.8.13}/.env.example +0 -0
  12. {nene2_python-1.8.11 → nene2_python-1.8.13}/.github/workflows/ci.yml +0 -0
  13. {nene2_python-1.8.11 → nene2_python-1.8.13}/.github/workflows/docs.yml +0 -0
  14. {nene2_python-1.8.11 → nene2_python-1.8.13}/.github/workflows/publish.yml +0 -0
  15. {nene2_python-1.8.11 → nene2_python-1.8.13}/.gitignore +0 -0
  16. {nene2_python-1.8.11 → nene2_python-1.8.13}/.vitepress/config.mts +0 -0
  17. {nene2_python-1.8.11 → nene2_python-1.8.13}/.vitepress/theme/custom.css +0 -0
  18. {nene2_python-1.8.11 → nene2_python-1.8.13}/.vitepress/theme/index.ts +0 -0
  19. {nene2_python-1.8.11 → nene2_python-1.8.13}/AGENTS.md +0 -0
  20. {nene2_python-1.8.11 → nene2_python-1.8.13}/CLAUDE.md +0 -0
  21. {nene2_python-1.8.11 → nene2_python-1.8.13}/Dockerfile +0 -0
  22. {nene2_python-1.8.11 → nene2_python-1.8.13}/LICENSE +0 -0
  23. {nene2_python-1.8.11 → nene2_python-1.8.13}/README.md +0 -0
  24. {nene2_python-1.8.11 → nene2_python-1.8.13}/alembic/README +0 -0
  25. {nene2_python-1.8.11 → nene2_python-1.8.13}/alembic/env.py +0 -0
  26. {nene2_python-1.8.11 → nene2_python-1.8.13}/alembic/script.py.mako +0 -0
  27. {nene2_python-1.8.11 → nene2_python-1.8.13}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
  28. {nene2_python-1.8.11 → nene2_python-1.8.13}/alembic.ini +0 -0
  29. {nene2_python-1.8.11 → nene2_python-1.8.13}/compose.yaml +0 -0
  30. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/adr/0001-toolchain.md +0 -0
  31. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/adr/0002-clean-architecture.md +0 -0
  32. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/adr/0003-security-first.md +0 -0
  33. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/adr/0004-ai-first-design.md +0 -0
  34. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/adr/0005-logging.md +0 -0
  35. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/adr/0006-rate-limiting.md +0 -0
  36. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/adr/0009-mcp-design.md +0 -0
  37. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/adr/0010-async-use-case.md +0 -0
  38. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
  39. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/de/index.md +0 -0
  40. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/de/tutorials/getting-started.md +0 -0
  41. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/explanation/architecture.md +0 -0
  42. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/explanation/design-philosophy.md +0 -0
  43. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-1.md +0 -0
  44. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-10.md +0 -0
  45. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-11.md +0 -0
  46. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-12.md +0 -0
  47. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-13.md +0 -0
  48. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-14.md +0 -0
  49. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-15.md +0 -0
  50. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-16.md +0 -0
  51. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-17.md +0 -0
  52. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-18.md +0 -0
  53. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-19.md +0 -0
  54. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-2.md +0 -0
  55. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-20.md +0 -0
  56. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-21.md +0 -0
  57. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-22.md +0 -0
  58. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-23.md +0 -0
  59. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-24.md +0 -0
  60. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-25.md +0 -0
  61. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-26.md +0 -0
  62. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-27.md +0 -0
  63. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-28.md +0 -0
  64. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-29.md +0 -0
  65. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-3.md +0 -0
  66. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-30.md +0 -0
  67. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-31.md +0 -0
  68. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-32.md +0 -0
  69. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-33.md +0 -0
  70. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-34.md +0 -0
  71. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-35.md +0 -0
  72. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-36.md +0 -0
  73. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-37.md +0 -0
  74. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-38.md +0 -0
  75. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-39.md +0 -0
  76. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-4.md +0 -0
  77. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-40.md +0 -0
  78. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-41.md +0 -0
  79. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-42.md +0 -0
  80. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-43.md +0 -0
  81. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-44.md +0 -0
  82. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-45.md +0 -0
  83. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-46.md +0 -0
  84. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-47.md +0 -0
  85. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-48.md +0 -0
  86. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-49.md +0 -0
  87. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-5.md +0 -0
  88. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-50.md +0 -0
  89. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-51.md +0 -0
  90. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-6.md +0 -0
  91. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-7.md +0 -0
  92. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-8.md +0 -0
  93. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/field-trials/2026-05-field-trial-9.md +0 -0
  94. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/fr/index.md +0 -0
  95. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/fr/tutorials/getting-started.md +0 -0
  96. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/how-to/add-new-domain.md +0 -0
  97. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/how-to/async-use-case.md +0 -0
  98. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/how-to/configure-auth.md +0 -0
  99. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/how-to/new-project.md +0 -0
  100. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/how-to/problem-details.md +0 -0
  101. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/how-to/run-tests.md +0 -0
  102. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/how-to/sqlalchemy-repository.md +0 -0
  103. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/how-to/validation.md +0 -0
  104. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/howto/mcp-setup.md +0 -0
  105. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/index.md +0 -0
  106. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/ja/explanation/architecture.md +0 -0
  107. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/ja/explanation/design-philosophy.md +0 -0
  108. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/ja/how-to/add-new-domain.md +0 -0
  109. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/ja/how-to/configure-auth.md +0 -0
  110. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/ja/how-to/new-project.md +0 -0
  111. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/ja/how-to/run-tests.md +0 -0
  112. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
  113. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/ja/howto/mcp-setup.md +0 -0
  114. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/ja/index.md +0 -0
  115. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/ja/reference/api.md +0 -0
  116. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/ja/reference/configuration.md +0 -0
  117. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/ja/reference/framework-modules.md +0 -0
  118. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/ja/tutorials/first-domain.md +0 -0
  119. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/ja/tutorials/getting-started.md +0 -0
  120. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/pt-br/index.md +0 -0
  121. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/pt-br/tutorials/getting-started.md +0 -0
  122. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/reference/api.md +0 -0
  123. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/reference/configuration.md +0 -0
  124. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/reference/framework-modules.md +0 -0
  125. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/roadmap.md +0 -0
  126. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/todo/current.md +0 -0
  127. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/tutorials/first-domain.md +0 -0
  128. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/tutorials/getting-started.md +0 -0
  129. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/zh/index.md +0 -0
  130. {nene2_python-1.8.11 → nene2_python-1.8.13}/docs/zh/tutorials/getting-started.md +0 -0
  131. {nene2_python-1.8.11 → nene2_python-1.8.13}/package-lock.json +0 -0
  132. {nene2_python-1.8.11 → nene2_python-1.8.13}/package.json +0 -0
  133. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/example/__init__.py +0 -0
  134. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/example/__main__.py +0 -0
  135. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/example/app.py +0 -0
  136. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/example/comment/__init__.py +0 -0
  137. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/example/comment/entity.py +0 -0
  138. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/example/comment/exceptions.py +0 -0
  139. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/example/comment/handler.py +0 -0
  140. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/example/comment/repository.py +0 -0
  141. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/example/comment/sqlalchemy_repository.py +0 -0
  142. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/example/comment/use_case.py +0 -0
  143. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/example/mcp.py +0 -0
  144. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/example/note/__init__.py +0 -0
  145. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/example/note/async_use_case.py +0 -0
  146. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/example/note/entity.py +0 -0
  147. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/example/note/exceptions.py +0 -0
  148. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/example/note/handler.py +0 -0
  149. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/example/note/repository.py +0 -0
  150. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/example/note/sqlalchemy_repository.py +0 -0
  151. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/example/note/use_case.py +0 -0
  152. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/example/schema.py +0 -0
  153. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/example/tag/__init__.py +0 -0
  154. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/example/tag/entity.py +0 -0
  155. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/example/tag/exceptions.py +0 -0
  156. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/example/tag/handler.py +0 -0
  157. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/example/tag/repository.py +0 -0
  158. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/example/tag/sqlalchemy_repository.py +0 -0
  159. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/example/tag/use_case.py +0 -0
  160. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/__init__.py +0 -0
  161. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/auth/__init__.py +0 -0
  162. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/auth/bearer_token.py +0 -0
  163. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/auth/exceptions.py +0 -0
  164. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/auth/interfaces.py +0 -0
  165. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/config/__init__.py +0 -0
  166. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/config/settings.py +0 -0
  167. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/database/__init__.py +0 -0
  168. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/database/exceptions.py +0 -0
  169. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/database/health.py +0 -0
  170. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/database/interfaces.py +0 -0
  171. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/database/sqlalchemy_executor.py +0 -0
  172. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/database/utils.py +0 -0
  173. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/http/__init__.py +0 -0
  174. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/http/health.py +0 -0
  175. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/http/pagination.py +0 -0
  176. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/http/problem_details.py +0 -0
  177. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/log/__init__.py +0 -0
  178. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/log/setup.py +0 -0
  179. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/mcp/__init__.py +0 -0
  180. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/mcp/http_client.py +0 -0
  181. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/mcp/server.py +0 -0
  182. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/middleware/__init__.py +0 -0
  183. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/middleware/domain_exception.py +0 -0
  184. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/middleware/error_handler.py +0 -0
  185. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/middleware/request_id.py +0 -0
  186. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/middleware/request_logging.py +0 -0
  187. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/middleware/request_size_limit.py +0 -0
  188. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/middleware/security_headers.py +0 -0
  189. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/middleware/throttle.py +0 -0
  190. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/py.typed +0 -0
  191. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/use_case/__init__.py +0 -0
  192. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/use_case/protocols.py +0 -0
  193. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/validation/__init__.py +0 -0
  194. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/nene2/validation/exceptions.py +0 -0
  195. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/scripts/__init__.py +0 -0
  196. {nene2_python-1.8.11 → nene2_python-1.8.13}/src/scripts/export_openapi.py +0 -0
  197. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/__init__.py +0 -0
  198. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/example/__init__.py +0 -0
  199. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/example/comment/__init__.py +0 -0
  200. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/example/comment/test_comment_http.py +0 -0
  201. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/example/comment/test_comment_repository.py +0 -0
  202. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/example/comment/test_comment_use_case.py +0 -0
  203. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/example/conftest.py +0 -0
  204. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/example/note/__init__.py +0 -0
  205. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/example/note/test_async_note_use_case.py +0 -0
  206. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/example/note/test_list_notes.py +0 -0
  207. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/example/note/test_note_repository.py +0 -0
  208. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/example/tag/__init__.py +0 -0
  209. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/example/tag/test_tag_repository.py +0 -0
  210. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/example/tag/test_tags.py +0 -0
  211. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/example/test_cors.py +0 -0
  212. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/example/test_mcp.py +0 -0
  213. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/nene2/__init__.py +0 -0
  214. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/nene2/auth/__init__.py +0 -0
  215. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/nene2/auth/test_token_issuer.py +0 -0
  216. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/nene2/config/__init__.py +0 -0
  217. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/nene2/config/test_settings.py +0 -0
  218. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/nene2/database/__init__.py +0 -0
  219. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/nene2/database/test_transaction.py +0 -0
  220. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/nene2/database/test_utils.py +0 -0
  221. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/nene2/http/__init__.py +0 -0
  222. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/nene2/http/test_health.py +0 -0
  223. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/nene2/http/test_pagination.py +0 -0
  224. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/nene2/http/test_problem_details.py +0 -0
  225. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/nene2/log/__init__.py +0 -0
  226. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/nene2/log/test_setup.py +0 -0
  227. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/nene2/mcp/__init__.py +0 -0
  228. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/nene2/mcp/test_http_client.py +0 -0
  229. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/nene2/middleware/__init__.py +0 -0
  230. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/nene2/middleware/test_error_handler.py +0 -0
  231. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/nene2/middleware/test_request_id.py +0 -0
  232. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/nene2/middleware/test_request_logging.py +0 -0
  233. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/nene2/middleware/test_request_size_limit.py +0 -0
  234. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/nene2/middleware/test_security_headers.py +0 -0
  235. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
  236. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/nene2/middleware/test_throttle.py +0 -0
  237. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/nene2/use_case/__init__.py +0 -0
  238. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/nene2/use_case/test_protocols.py +0 -0
  239. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/nene2/validation/__init__.py +0 -0
  240. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/nene2/validation/test_exceptions.py +0 -0
  241. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/scripts/__init__.py +0 -0
  242. {nene2_python-1.8.11 → nene2_python-1.8.13}/tests/scripts/test_export_openapi.py +0 -0
@@ -5,6 +5,28 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
6
  ---
7
7
 
8
+ ## [1.8.13] — 2026-05-20
9
+
10
+ FT53 フィールドトライアル — ApiKeyAuthMiddleware 実運用検証 + header_name パラメータ追加。
11
+
12
+ ### Added
13
+ - `ApiKeyAuthMiddleware` に `header_name: str = "X-Api-Key"` パラメータを追加 — カスタムヘッダー名 (`X-Service-Token` 等) を指定可能に。エラーメッセージにも `header_name` が反映される (#286) (FT53)
14
+ - Field trial report: `docs/field-trials/2026-05-field-trial-53.md`
15
+
16
+ ---
17
+
18
+ ## [1.8.12] — 2026-05-20
19
+
20
+ FT52 フィールドトライアル — ミドルウェアスタック組み合わせ検証 + LocalTokenVerifier 改善。
21
+
22
+ ### Changed
23
+ - `LocalTokenVerifier.__init__` — `allowed_tokens` の型を `list[str] | set[str] | frozenset[str]` に変更。内部で `frozenset` に変換し `in` 演算が O(1) になった (#284) (FT52)
24
+
25
+ ### Added
26
+ - Field trial report: `docs/field-trials/2026-05-field-trial-52.md`
27
+
28
+ ---
29
+
8
30
  ## [1.8.11] — 2026-05-20
9
31
 
10
32
  FT51 フィールドトライアル — SimpleDomainHandler 実運用検証 + problem_details_response バグ修正。
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nene2-python
3
- Version: 1.8.11
3
+ Version: 1.8.13
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,90 @@
1
+ # Field Trial 52: ミドルウェアスタック組み合わせ検証
2
+
3
+ **Date**: 2026-05-20
4
+ **Theme**: 全ミドルウェアスタック (BearerToken + Throttle + RequestLogging + SecurityHeaders) の組み合わせ実運用
5
+ **Version under test**: v1.8.11
6
+ **FT App**: `/home/xi/docker/nene2-python-FT/ft52-middleware-stack/`
7
+
8
+ ---
9
+
10
+ ## 概要
11
+
12
+ `ErrorHandlerMiddleware` + `SecurityHeadersMiddleware` + `RequestIdMiddleware` +
13
+ `RequestLoggingMiddleware` + `ThrottleMiddleware` + `BearerTokenMiddleware` を
14
+ 全スタックで積み重ねて動作を検証した。
15
+
16
+ ---
17
+
18
+ ## 実装内容
19
+
20
+ ### ミドルウェアスタック
21
+
22
+ ```python
23
+ app.add_middleware(ErrorHandlerMiddleware)
24
+ app.add_middleware(SecurityHeadersMiddleware, csp="default-src 'self'", hsts="max-age=31536000")
25
+ app.add_middleware(RequestIdMiddleware)
26
+ app.add_middleware(RequestLoggingMiddleware, exclude_paths=["/health"], extra_context={"service": "ft52", "env": "test"})
27
+ app.add_middleware(ThrottleMiddleware, limit=5, window=60, exclude_paths=["/health"])
28
+ app.add_middleware(BearerTokenMiddleware, verifier=verifier, exclude_paths=["/health", "/docs", "/openapi.json"])
29
+ ```
30
+
31
+ `/health` は認証・ログ・レート制限をすべてバイパスする設計。
32
+
33
+ ---
34
+
35
+ ## テスト結果
36
+
37
+ 10 tests, all passed (after fixing FP52-1).
38
+
39
+ | テスト | 結果 |
40
+ |---|---|
41
+ | /health は認証不要 | ✅ |
42
+ | /items は認証必須 | ✅ |
43
+ | 有効トークンで /items アクセス | ✅ |
44
+ | セキュリティヘッダー存在確認 | ✅ |
45
+ | HSTS ヘッダー確認 | ✅ |
46
+ | X-Request-Id ヘッダー確認 | ✅ |
47
+ | X-RateLimit-Limit ヘッダー確認 | ✅ |
48
+ | /health はレート制限なし (10回連続) | ✅ |
49
+ | レート制限超過で 429 | ✅ |
50
+ | 無効トークンで 401 | ✅ |
51
+
52
+ ---
53
+
54
+ ## 摩擦ポイント
55
+
56
+ ### FP52-1: `LocalTokenVerifier` の引数名が直感的でない / `set` 非対応
57
+
58
+ **状況**: `LocalTokenVerifier(valid_tokens={valid_token})` と書いたが:
59
+ 1. 引数名は `valid_tokens` ではなく `allowed_tokens`
60
+ 2. `set` を渡すと型エラー(内部が `list[str]` を期待)
61
+
62
+ **修正**: `allowed_tokens` の型を `list[str] | set[str] | frozenset[str]` に変更し、
63
+ 内部で `frozenset` に変換するよう修正 (#284)。
64
+ 内部を `frozenset` にすることで `in` 演算の O(n) → O(1) 改善も得られた。
65
+
66
+ ---
67
+
68
+ ## フレームワーク変更
69
+
70
+ ### `LocalTokenVerifier` — `set` / `frozenset` を受け入れ可能に (#284)
71
+
72
+ ```python
73
+ # 修正前
74
+ def __init__(self, allowed_tokens: list[str]) -> None:
75
+ self._allowed = allowed_tokens
76
+
77
+ # 修正後
78
+ def __init__(self, allowed_tokens: list[str] | set[str] | frozenset[str]) -> None:
79
+ self._allowed: frozenset[str] = frozenset(allowed_tokens)
80
+ ```
81
+
82
+ ---
83
+
84
+ ## 全スタック動作確認
85
+
86
+ 各ミドルウェアの機能がスタック状態でも正常に動作することを確認:
87
+ - `exclude_paths` の設定が各ミドルウェアで独立して機能する
88
+ - `BearerTokenMiddleware` が `ThrottleMiddleware` より外側(先に評価)
89
+ - `SecurityHeadersMiddleware` のヘッダーが全レスポンスに付与される
90
+ - `RequestIdMiddleware` の `X-Request-Id` が全レスポンスに付与される
@@ -0,0 +1,81 @@
1
+ # Field Trial 53: ApiKeyAuthMiddleware 実運用検証
2
+
3
+ **Date**: 2026-05-20
4
+ **Theme**: `ApiKeyAuthMiddleware` の実運用パターン検証
5
+ **Version under test**: v1.8.12
6
+ **FT App**: `/home/xi/docker/nene2-python-FT/ft53-api-key-auth/`
7
+
8
+ ---
9
+
10
+ ## 概要
11
+
12
+ `ApiKeyAuthMiddleware` + `LocalTokenVerifier(set)` の組み合わせを複数 API キー対応の
13
+ サービスで実運用した。`exclude_paths` と `RequestIdMiddleware` との組み合わせも検証。
14
+
15
+ ---
16
+
17
+ ## 実装内容
18
+
19
+ ```python
20
+ verifier = LocalTokenVerifier({"key-dev-001", "key-dev-002"})
21
+ app.add_middleware(
22
+ ApiKeyAuthMiddleware,
23
+ verifier=verifier,
24
+ exclude_paths=["/health", "/docs", "/openapi.json", "/redoc"],
25
+ )
26
+ ```
27
+
28
+ ---
29
+
30
+ ## テスト結果
31
+
32
+ 9 tests, all passed.
33
+
34
+ ---
35
+
36
+ ## 摩擦ポイント
37
+
38
+ ### FP53-1: `ApiKeyAuthMiddleware` のヘッダー名が `X-Api-Key` に固定されている
39
+
40
+ **状況**: サービスによっては `X-Service-Token` や `X-Internal-Key` など
41
+ 異なるヘッダー名を使いたい場合がある。
42
+
43
+ **修正**: `header_name: str = "X-Api-Key"` パラメータを追加 (#286)。
44
+
45
+ ```python
46
+ app.add_middleware(
47
+ ApiKeyAuthMiddleware,
48
+ verifier=verifier,
49
+ header_name="X-Service-Token", # カスタムヘッダー名
50
+ )
51
+ ```
52
+
53
+ エラーメッセージにも `header_name` が反映される:
54
+ ```
55
+ "A valid X-Service-Token header is required."
56
+ ```
57
+
58
+ **補足**: HTTP ヘッダーは RFC 7230 で大文字小文字無視であるため、
59
+ `X-API-KEY` / `X-Api-Key` などの大文字小文字の違いは問題にならない。
60
+
61
+ ---
62
+
63
+ ## フレームワーク変更
64
+
65
+ ### `ApiKeyAuthMiddleware` — `header_name` パラメータを追加 (#286)
66
+
67
+ ```python
68
+ # デフォルト動作(後方互換)
69
+ ApiKeyAuthMiddleware(verifier=verifier) # X-Api-Key を使用
70
+
71
+ # カスタムヘッダー名
72
+ ApiKeyAuthMiddleware(verifier=verifier, header_name="X-Service-Token")
73
+ ```
74
+
75
+ ---
76
+
77
+ ## 結論
78
+
79
+ `ApiKeyAuthMiddleware` は `LocalTokenVerifier(set)` との組み合わせで
80
+ 複数 API キーを管理する用途で問題なく動作する。
81
+ `header_name` パラメータの追加で柔軟性が向上した。
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nene2-python"
3
- version = "1.8.11"
3
+ version = "1.8.13"
4
4
  description = "NENE2 Python — minimal API framework following NENE2's design philosophy"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
@@ -1,6 +1,6 @@
1
1
  """API Key authentication middleware.
2
2
 
3
- Validates X-Api-Key header using a TokenVerifierProtocol.
3
+ Validates a configurable header (default: X-Api-Key) using a TokenVerifierProtocol.
4
4
  Returns 401 Problem Details when key is absent or invalid.
5
5
  """
6
6
 
@@ -13,18 +13,18 @@ from nene2.http.problem_details import problem_details_response
13
13
  from .exceptions import TokenVerificationException
14
14
  from .interfaces import TokenVerifierProtocol
15
15
 
16
- _API_KEY_HEADER = "X-Api-Key"
16
+ _DEFAULT_API_KEY_HEADER = "X-Api-Key"
17
17
 
18
18
 
19
19
  class ApiKeyAuthMiddleware(BaseHTTPMiddleware):
20
- """Require a valid X-Api-Key header on every request.
20
+ """Require a valid API key header on every request.
21
21
 
22
- Use ``exclude_paths`` to skip authentication for specific paths such as
23
- health-check endpoints or API documentation::
22
+ The header name defaults to ``X-Api-Key`` but can be customised::
24
23
 
25
24
  app.add_middleware(
26
25
  ApiKeyAuthMiddleware,
27
26
  verifier=LocalTokenVerifier(api_keys),
27
+ header_name="X-Service-Token",
28
28
  exclude_paths=["/docs", "/openapi.json", "/redoc", "/health"],
29
29
  )
30
30
  """
@@ -34,16 +34,18 @@ class ApiKeyAuthMiddleware(BaseHTTPMiddleware):
34
34
  app: object,
35
35
  *,
36
36
  verifier: TokenVerifierProtocol,
37
+ header_name: str = _DEFAULT_API_KEY_HEADER,
37
38
  exclude_paths: list[str] | None = None,
38
39
  ) -> None:
39
40
  super().__init__(app) # type: ignore[arg-type]
40
41
  self._verifier = verifier
42
+ self._header_name = header_name
41
43
  self._exclude_paths = set(exclude_paths or [])
42
44
 
43
45
  async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
44
46
  if request.url.path in self._exclude_paths:
45
47
  return await call_next(request)
46
- api_key = request.headers.get(_API_KEY_HEADER, "")
48
+ api_key = request.headers.get(self._header_name, "")
47
49
  try:
48
50
  verified = bool(api_key) and self._verifier.verify(api_key)
49
51
  except TokenVerificationException:
@@ -53,6 +55,6 @@ class ApiKeyAuthMiddleware(BaseHTTPMiddleware):
53
55
  "unauthorized",
54
56
  "Unauthorized",
55
57
  401,
56
- "A valid X-Api-Key header is required.",
58
+ f"A valid {self._header_name} header is required.",
57
59
  )
58
60
  return await call_next(request)
@@ -11,8 +11,8 @@ import secrets
11
11
  class LocalTokenVerifier:
12
12
  """Verify tokens against a fixed allowlist using constant-time comparison."""
13
13
 
14
- def __init__(self, allowed_tokens: list[str]) -> None:
15
- self._allowed = allowed_tokens
14
+ def __init__(self, allowed_tokens: list[str] | set[str] | frozenset[str]) -> None:
15
+ self._allowed: frozenset[str] = frozenset(allowed_tokens)
16
16
 
17
17
  @classmethod
18
18
  def from_env(cls, env_var: str, *, separator: str = ",") -> "LocalTokenVerifier":
@@ -87,3 +87,38 @@ def test_verifier_raises_token_verification_exception_returns_401() -> None:
87
87
  client = TestClient(app, raise_server_exceptions=False)
88
88
  response = client.get("/secret", headers={"X-Api-Key": "any-key"})
89
89
  assert response.status_code == 401
90
+
91
+
92
+ def test_custom_header_name() -> None:
93
+ app = FastAPI()
94
+ app.add_middleware(
95
+ ApiKeyAuthMiddleware,
96
+ verifier=LocalTokenVerifier(["svc-token"]),
97
+ header_name="X-Service-Token",
98
+ )
99
+
100
+ @app.get("/secret")
101
+ async def secret() -> JSONResponse:
102
+ return JSONResponse({"ok": True})
103
+
104
+ client = TestClient(app, raise_server_exceptions=False)
105
+ assert client.get("/secret", headers={"X-Service-Token": "svc-token"}).status_code == 200
106
+ assert client.get("/secret", headers={"X-Api-Key": "svc-token"}).status_code == 401
107
+
108
+
109
+ def test_custom_header_name_in_error_message() -> None:
110
+ app = FastAPI()
111
+ app.add_middleware(
112
+ ApiKeyAuthMiddleware,
113
+ verifier=LocalTokenVerifier(["tok"]),
114
+ header_name="X-Internal-Key",
115
+ )
116
+
117
+ @app.get("/secret")
118
+ async def secret() -> JSONResponse:
119
+ return JSONResponse({"ok": True})
120
+
121
+ client = TestClient(app, raise_server_exceptions=False)
122
+ response = client.get("/secret")
123
+ assert response.status_code == 401
124
+ assert "X-Internal-Key" in response.json().get("detail", "")
@@ -113,3 +113,16 @@ def test_exclude_paths_default_is_empty() -> None:
113
113
 
114
114
  client = TestClient(app)
115
115
  assert client.get("/health").status_code == 401
116
+
117
+
118
+ def test_local_verifier_accepts_set() -> None:
119
+ verifier = LocalTokenVerifier({"tok-a", "tok-b"})
120
+ assert verifier.verify("tok-a") is True
121
+ assert verifier.verify("tok-b") is True
122
+ assert verifier.verify("tok-c") is False
123
+
124
+
125
+ def test_local_verifier_accepts_frozenset() -> None:
126
+ verifier = LocalTokenVerifier(frozenset({"tok-x", "tok-y"}))
127
+ assert verifier.verify("tok-x") is True
128
+ assert verifier.verify("unknown") is False
@@ -925,7 +925,7 @@ wheels = [
925
925
 
926
926
  [[package]]
927
927
  name = "nene2-python"
928
- version = "1.8.11"
928
+ version = "1.8.13"
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