nene2-python 1.8.6__tar.gz → 1.8.8__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 (234) hide show
  1. {nene2_python-1.8.6 → nene2_python-1.8.8}/CHANGELOG.md +22 -0
  2. {nene2_python-1.8.6 → nene2_python-1.8.8}/PKG-INFO +1 -1
  3. nene2_python-1.8.8/docs/field-trials/2026-05-field-trial-43.md +112 -0
  4. nene2_python-1.8.8/docs/field-trials/2026-05-field-trial-44.md +110 -0
  5. nene2_python-1.8.8/docs/field-trials/2026-05-field-trial-45.md +108 -0
  6. {nene2_python-1.8.6 → nene2_python-1.8.8}/pyproject.toml +1 -1
  7. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/middleware/__init__.py +2 -1
  8. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/middleware/security_headers.py +1 -1
  9. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/nene2/middleware/test_security_headers.py +13 -0
  10. {nene2_python-1.8.6 → nene2_python-1.8.8}/uv.lock +1 -1
  11. {nene2_python-1.8.6 → nene2_python-1.8.8}/.env.example +0 -0
  12. {nene2_python-1.8.6 → nene2_python-1.8.8}/.github/workflows/ci.yml +0 -0
  13. {nene2_python-1.8.6 → nene2_python-1.8.8}/.github/workflows/docs.yml +0 -0
  14. {nene2_python-1.8.6 → nene2_python-1.8.8}/.github/workflows/publish.yml +0 -0
  15. {nene2_python-1.8.6 → nene2_python-1.8.8}/.gitignore +0 -0
  16. {nene2_python-1.8.6 → nene2_python-1.8.8}/.vitepress/config.mts +0 -0
  17. {nene2_python-1.8.6 → nene2_python-1.8.8}/.vitepress/theme/custom.css +0 -0
  18. {nene2_python-1.8.6 → nene2_python-1.8.8}/.vitepress/theme/index.ts +0 -0
  19. {nene2_python-1.8.6 → nene2_python-1.8.8}/AGENTS.md +0 -0
  20. {nene2_python-1.8.6 → nene2_python-1.8.8}/CLAUDE.md +0 -0
  21. {nene2_python-1.8.6 → nene2_python-1.8.8}/Dockerfile +0 -0
  22. {nene2_python-1.8.6 → nene2_python-1.8.8}/LICENSE +0 -0
  23. {nene2_python-1.8.6 → nene2_python-1.8.8}/README.md +0 -0
  24. {nene2_python-1.8.6 → nene2_python-1.8.8}/alembic/README +0 -0
  25. {nene2_python-1.8.6 → nene2_python-1.8.8}/alembic/env.py +0 -0
  26. {nene2_python-1.8.6 → nene2_python-1.8.8}/alembic/script.py.mako +0 -0
  27. {nene2_python-1.8.6 → nene2_python-1.8.8}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
  28. {nene2_python-1.8.6 → nene2_python-1.8.8}/alembic.ini +0 -0
  29. {nene2_python-1.8.6 → nene2_python-1.8.8}/compose.yaml +0 -0
  30. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/adr/0001-toolchain.md +0 -0
  31. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/adr/0002-clean-architecture.md +0 -0
  32. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/adr/0003-security-first.md +0 -0
  33. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/adr/0004-ai-first-design.md +0 -0
  34. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/adr/0005-logging.md +0 -0
  35. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/adr/0006-rate-limiting.md +0 -0
  36. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/adr/0009-mcp-design.md +0 -0
  37. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/adr/0010-async-use-case.md +0 -0
  38. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
  39. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/de/index.md +0 -0
  40. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/de/tutorials/getting-started.md +0 -0
  41. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/explanation/architecture.md +0 -0
  42. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/explanation/design-philosophy.md +0 -0
  43. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-1.md +0 -0
  44. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-10.md +0 -0
  45. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-11.md +0 -0
  46. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-12.md +0 -0
  47. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-13.md +0 -0
  48. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-14.md +0 -0
  49. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-15.md +0 -0
  50. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-16.md +0 -0
  51. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-17.md +0 -0
  52. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-18.md +0 -0
  53. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-19.md +0 -0
  54. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-2.md +0 -0
  55. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-20.md +0 -0
  56. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-21.md +0 -0
  57. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-22.md +0 -0
  58. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-23.md +0 -0
  59. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-24.md +0 -0
  60. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-25.md +0 -0
  61. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-26.md +0 -0
  62. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-27.md +0 -0
  63. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-28.md +0 -0
  64. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-29.md +0 -0
  65. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-3.md +0 -0
  66. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-30.md +0 -0
  67. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-31.md +0 -0
  68. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-32.md +0 -0
  69. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-33.md +0 -0
  70. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-34.md +0 -0
  71. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-35.md +0 -0
  72. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-36.md +0 -0
  73. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-37.md +0 -0
  74. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-38.md +0 -0
  75. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-39.md +0 -0
  76. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-4.md +0 -0
  77. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-40.md +0 -0
  78. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-41.md +0 -0
  79. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-42.md +0 -0
  80. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-5.md +0 -0
  81. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-6.md +0 -0
  82. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-7.md +0 -0
  83. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-8.md +0 -0
  84. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/field-trials/2026-05-field-trial-9.md +0 -0
  85. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/fr/index.md +0 -0
  86. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/fr/tutorials/getting-started.md +0 -0
  87. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/how-to/add-new-domain.md +0 -0
  88. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/how-to/async-use-case.md +0 -0
  89. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/how-to/configure-auth.md +0 -0
  90. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/how-to/new-project.md +0 -0
  91. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/how-to/problem-details.md +0 -0
  92. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/how-to/run-tests.md +0 -0
  93. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/how-to/sqlalchemy-repository.md +0 -0
  94. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/how-to/validation.md +0 -0
  95. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/howto/mcp-setup.md +0 -0
  96. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/index.md +0 -0
  97. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/ja/explanation/architecture.md +0 -0
  98. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/ja/explanation/design-philosophy.md +0 -0
  99. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/ja/how-to/add-new-domain.md +0 -0
  100. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/ja/how-to/configure-auth.md +0 -0
  101. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/ja/how-to/new-project.md +0 -0
  102. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/ja/how-to/run-tests.md +0 -0
  103. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
  104. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/ja/howto/mcp-setup.md +0 -0
  105. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/ja/index.md +0 -0
  106. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/ja/reference/api.md +0 -0
  107. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/ja/reference/configuration.md +0 -0
  108. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/ja/reference/framework-modules.md +0 -0
  109. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/ja/tutorials/first-domain.md +0 -0
  110. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/ja/tutorials/getting-started.md +0 -0
  111. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/pt-br/index.md +0 -0
  112. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/pt-br/tutorials/getting-started.md +0 -0
  113. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/reference/api.md +0 -0
  114. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/reference/configuration.md +0 -0
  115. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/reference/framework-modules.md +0 -0
  116. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/roadmap.md +0 -0
  117. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/todo/current.md +0 -0
  118. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/tutorials/first-domain.md +0 -0
  119. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/tutorials/getting-started.md +0 -0
  120. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/zh/index.md +0 -0
  121. {nene2_python-1.8.6 → nene2_python-1.8.8}/docs/zh/tutorials/getting-started.md +0 -0
  122. {nene2_python-1.8.6 → nene2_python-1.8.8}/package-lock.json +0 -0
  123. {nene2_python-1.8.6 → nene2_python-1.8.8}/package.json +0 -0
  124. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/example/__init__.py +0 -0
  125. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/example/__main__.py +0 -0
  126. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/example/app.py +0 -0
  127. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/example/comment/__init__.py +0 -0
  128. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/example/comment/entity.py +0 -0
  129. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/example/comment/exceptions.py +0 -0
  130. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/example/comment/handler.py +0 -0
  131. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/example/comment/repository.py +0 -0
  132. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/example/comment/sqlalchemy_repository.py +0 -0
  133. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/example/comment/use_case.py +0 -0
  134. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/example/mcp.py +0 -0
  135. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/example/note/__init__.py +0 -0
  136. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/example/note/async_use_case.py +0 -0
  137. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/example/note/entity.py +0 -0
  138. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/example/note/exceptions.py +0 -0
  139. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/example/note/handler.py +0 -0
  140. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/example/note/repository.py +0 -0
  141. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/example/note/sqlalchemy_repository.py +0 -0
  142. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/example/note/use_case.py +0 -0
  143. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/example/schema.py +0 -0
  144. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/example/tag/__init__.py +0 -0
  145. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/example/tag/entity.py +0 -0
  146. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/example/tag/exceptions.py +0 -0
  147. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/example/tag/handler.py +0 -0
  148. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/example/tag/repository.py +0 -0
  149. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/example/tag/sqlalchemy_repository.py +0 -0
  150. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/example/tag/use_case.py +0 -0
  151. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/__init__.py +0 -0
  152. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/auth/__init__.py +0 -0
  153. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/auth/api_key.py +0 -0
  154. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/auth/bearer_token.py +0 -0
  155. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/auth/exceptions.py +0 -0
  156. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/auth/interfaces.py +0 -0
  157. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/auth/local_verifier.py +0 -0
  158. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/config/__init__.py +0 -0
  159. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/config/settings.py +0 -0
  160. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/database/__init__.py +0 -0
  161. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/database/exceptions.py +0 -0
  162. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/database/health.py +0 -0
  163. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/database/interfaces.py +0 -0
  164. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/database/sqlalchemy_executor.py +0 -0
  165. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/database/utils.py +0 -0
  166. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/http/__init__.py +0 -0
  167. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/http/health.py +0 -0
  168. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/http/pagination.py +0 -0
  169. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/http/problem_details.py +0 -0
  170. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/log/__init__.py +0 -0
  171. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/log/setup.py +0 -0
  172. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/mcp/__init__.py +0 -0
  173. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/mcp/http_client.py +0 -0
  174. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/mcp/server.py +0 -0
  175. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/middleware/domain_exception.py +0 -0
  176. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/middleware/error_handler.py +0 -0
  177. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/middleware/request_id.py +0 -0
  178. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/middleware/request_logging.py +0 -0
  179. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/middleware/request_size_limit.py +0 -0
  180. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/middleware/throttle.py +0 -0
  181. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/py.typed +0 -0
  182. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/use_case/__init__.py +0 -0
  183. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/use_case/protocols.py +0 -0
  184. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/validation/__init__.py +0 -0
  185. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/nene2/validation/exceptions.py +0 -0
  186. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/scripts/__init__.py +0 -0
  187. {nene2_python-1.8.6 → nene2_python-1.8.8}/src/scripts/export_openapi.py +0 -0
  188. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/__init__.py +0 -0
  189. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/example/__init__.py +0 -0
  190. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/example/comment/__init__.py +0 -0
  191. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/example/comment/test_comment_http.py +0 -0
  192. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/example/comment/test_comment_repository.py +0 -0
  193. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/example/comment/test_comment_use_case.py +0 -0
  194. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/example/conftest.py +0 -0
  195. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/example/note/__init__.py +0 -0
  196. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/example/note/test_async_note_use_case.py +0 -0
  197. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/example/note/test_list_notes.py +0 -0
  198. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/example/note/test_note_repository.py +0 -0
  199. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/example/tag/__init__.py +0 -0
  200. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/example/tag/test_tag_repository.py +0 -0
  201. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/example/tag/test_tags.py +0 -0
  202. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/example/test_cors.py +0 -0
  203. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/example/test_mcp.py +0 -0
  204. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/nene2/__init__.py +0 -0
  205. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/nene2/auth/__init__.py +0 -0
  206. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/nene2/auth/test_api_key.py +0 -0
  207. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/nene2/auth/test_bearer_token.py +0 -0
  208. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/nene2/auth/test_token_issuer.py +0 -0
  209. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/nene2/config/__init__.py +0 -0
  210. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/nene2/config/test_settings.py +0 -0
  211. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/nene2/database/__init__.py +0 -0
  212. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/nene2/database/test_transaction.py +0 -0
  213. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/nene2/database/test_utils.py +0 -0
  214. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/nene2/http/__init__.py +0 -0
  215. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/nene2/http/test_health.py +0 -0
  216. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/nene2/http/test_pagination.py +0 -0
  217. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/nene2/http/test_problem_details.py +0 -0
  218. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/nene2/log/__init__.py +0 -0
  219. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/nene2/log/test_setup.py +0 -0
  220. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/nene2/mcp/__init__.py +0 -0
  221. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/nene2/mcp/test_http_client.py +0 -0
  222. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/nene2/middleware/__init__.py +0 -0
  223. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/nene2/middleware/test_error_handler.py +0 -0
  224. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/nene2/middleware/test_request_id.py +0 -0
  225. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/nene2/middleware/test_request_logging.py +0 -0
  226. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/nene2/middleware/test_request_size_limit.py +0 -0
  227. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
  228. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/nene2/middleware/test_throttle.py +0 -0
  229. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/nene2/use_case/__init__.py +0 -0
  230. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/nene2/use_case/test_protocols.py +0 -0
  231. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/nene2/validation/__init__.py +0 -0
  232. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/nene2/validation/test_exceptions.py +0 -0
  233. {nene2_python-1.8.6 → nene2_python-1.8.8}/tests/scripts/__init__.py +0 -0
  234. {nene2_python-1.8.6 → nene2_python-1.8.8}/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.8] — 2026-05-20
9
+
10
+ FT45 フィールドトライアル — SecurityHeadersMiddleware CSP バグ修正。
11
+
12
+ ### Fixed
13
+ - `SecurityHeadersMiddleware` — `csp=""` を渡したとき空の `Content-Security-Policy` ヘッダーが付与される問題を修正。`csp=""` の場合は CSP ヘッダーを付与しないよう変更 (#271) (FT45)
14
+
15
+ ### Added
16
+ - Field trial report: `docs/field-trials/2026-05-field-trial-45.md`
17
+
18
+ ---
19
+
20
+ ## [1.8.7] — 2026-05-20
21
+
22
+ FT43〜FT44 フィールドトライアル — ThrottleMiddleware path_limits 確認・PaginationQueryParser バリデーション改善。
23
+
24
+ ### Added
25
+ - `nene2.middleware.request_validation_error_handler` を公開エクスポートに追加 — FastAPI の `RequestValidationError` を Problem Details 形式に変換するハンドラーを `from nene2.middleware import request_validation_error_handler` でアクセス可能に (FT44)
26
+ - Field trial reports: `docs/field-trials/2026-05-field-trial-43.md`、`docs/field-trials/2026-05-field-trial-44.md`
27
+
28
+ ---
29
+
8
30
  ## [1.8.6] — 2026-05-20
9
31
 
10
32
  FT41〜FT42 フィールドトライアル — structlog テスト統合ドキュメント・configure_problem_details リセット関数。
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nene2-python
3
- Version: 1.8.6
3
+ Version: 1.8.8
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,112 @@
1
+ # Field Trial 43: ThrottleMiddleware path_limits 実運用検証
2
+
3
+ **日付**: 2026-05-20
4
+ **バージョン**: v1.8.6 時点
5
+ **テーマ**: `ThrottleMiddleware` の `path_limits` パラメータを使ってエンドポイントごとにレート制限を設定するパターンの実運用確認
6
+
7
+ ---
8
+
9
+ ## 概要
10
+
11
+ グローバルレート制限(`limit=100`)と、特定パスへの厳しい制限(`path_limits={"/api/search": 10, "/api/upload": 5}`)を組み合わせた API を実装し、動作を確認した。
12
+ レートカウンターがパスごとに独立して管理されること、`X-RateLimit-*` ヘッダーが適切に付与されることを検証した。
13
+
14
+ ---
15
+
16
+ ## 実装内容
17
+
18
+ `/home/xi/docker/nene2-python-FT/ft43-throttle-path-limits/` に以下を作成:
19
+
20
+ - **`app.py`** — グローバル + パスごとのレート制限設定、`/health` 除外パスの構成
21
+ - **`test_app.py`** — 正常系・ヘッダー確認・path_limits 独立性・429 動作 (10 件)
22
+ - **`test_friction.py`** — 摩擦点の確認テスト (4 件)
23
+
24
+ **テスト結果**: 14 件全通過 ✅
25
+
26
+ ---
27
+
28
+ ## 摩擦点
29
+
30
+ ### FP43-1: path_limits のカウンターはグローバルカウンターと完全に独立している
31
+
32
+ **分類**: 摩擦なし(良い設計の確認)
33
+
34
+ `path_limits` に指定したパスは `{client}:{path}` をキーとして使い、
35
+ グローバルカウンター (`{client}`) とは別々に管理される。
36
+ `/api/search` を使い切っても `/api/items` のカウンターには影響しない。
37
+
38
+ ```python
39
+ # /api/search の制限 (3 req) を使い切っても
40
+ for _ in range(3):
41
+ client.get("/api/search")
42
+ # /api/items の制限は消費されていない
43
+ r = client.get("/api/items")
44
+ assert r.status_code == 200 # OK
45
+ ```
46
+
47
+ **判断**: FT28 で実装した設計通り。パス別独立カウンターは想定通りに動作する。
48
+
49
+ ---
50
+
51
+ ### FP43-2: X-RateLimit-Limit ヘッダーがパスごとの制限値を反映する
52
+
53
+ **分類**: 摩擦なし(良い設計の確認)
54
+
55
+ `/api/search` へのリクエストには `X-RateLimit-Limit: 3` が付与され、
56
+ `/api/items` へのリクエストには `X-RateLimit-Limit: 10` が付与される。
57
+ クライアントはヘッダーを見て自分のリミットがいくつかを判断できる。
58
+
59
+ **判断**: FT20 で実装したヘッダー付与が path_limits と正しく連携している。
60
+
61
+ ---
62
+
63
+ ### FP43-3: X-Forwarded-For がクライアントキーとして使われるためバイパスに注意
64
+
65
+ **分類**: 設計上の制約(ドキュメントに記載済み・運用上の注意点)
66
+
67
+ `X-Forwarded-For` ヘッダーがある場合、それをクライアント IP として使う設計のため、
68
+ 異なる `X-Forwarded-For` を送ることで別のクライアントとして扱われ、レート制限をバイパスできる。
69
+
70
+ ```python
71
+ # 通常の IP で制限を使い切ったあと
72
+ for _ in range(2):
73
+ client.get("/api/items")
74
+ r = client.get("/api/items")
75
+ assert r.status_code == 429
76
+
77
+ # 別の IP を騙って送ると 200 になる
78
+ r = client.get("/api/items", headers={"X-Forwarded-For": "10.0.0.1"})
79
+ assert r.status_code == 200 # バイパスできてしまう
80
+ ```
81
+
82
+ **判断**: ドキュメントの Warning セクションに記載されている既知の制限。
83
+ 信頼できるリバースプロキシを前段に置くことで軽減できる。
84
+ テスト環境での動作確認として有用。
85
+
86
+ ---
87
+
88
+ ### FP43-4: path_limits の対象外パスはグローバル制限のみが適用される
89
+
90
+ **分類**: 摩擦なし(設計の確認)
91
+
92
+ `path_limits` に指定していないパス (`/api/items` など) はグローバルの `limit` が適用される。
93
+ 複数のエンドポイントに異なる制限を設けつつ、デフォルトのグローバル制限を基本として使う設計が自然に実現できる。
94
+
95
+ **判断**: FT28 の設計通り。`path_limits` に指定のないパスは `{client}` をキーに使い、グローバルカウンターで管理される。
96
+
97
+ ---
98
+
99
+ ## フレームワーク変更
100
+
101
+ なし(全て設計通りの挙動)
102
+
103
+ ---
104
+
105
+ ## 関連
106
+
107
+ - `nene2.middleware.ThrottleMiddleware` (FT20, v1.8.0)
108
+ - `ThrottleMiddleware.path_limits` (FT28, v1.8.1)
109
+ - `ThrottleMiddleware` ウィンドウクリーンアップ (FT27, v1.8.1)
110
+ - FT20 (ThrottleMiddleware ヘッダー実装, v1.8.0)
111
+ - FT27 (ThrottleMiddleware クリーンアップ修正, v1.8.1)
112
+ - FT28 (ThrottleMiddleware path_limits 実装, v1.8.1)
@@ -0,0 +1,110 @@
1
+ # Field Trial 44: PaginationQueryParser + PaginationResponse 実運用検証
2
+
3
+ **日付**: 2026-05-20
4
+ **バージョン**: v1.8.6 時点
5
+ **テーマ**: `PaginationQueryParser` を `Annotated[..., Depends()]` 構文で使い `PaginationResponse.to_dict()` でスロット付きデータクラスをシリアライズするパターンの実運用確認
6
+
7
+ ---
8
+
9
+ ## 概要
10
+
11
+ `PaginationQueryParser` を FastAPI の `Depends()` として注入し、
12
+ `PaginationResponse` + `to_dict()` でスロット付き `dataclass(frozen=True, slots=True)` を
13
+ シリアライズするパターンを実装した。
14
+ `total` フィールドのあり/なし両パターン、および生の dict アイテムを含むケースも確認した。
15
+
16
+ ---
17
+
18
+ ## 実装内容
19
+
20
+ `/home/xi/docker/nene2-python-FT/ft44-pagination/` に以下を作成:
21
+
22
+ - **`app.py`** — `PaginationQueryParser` Depends 注入、スロット付きデータクラス `Product`、3 つのエンドポイント(total あり/なし/生 dict)
23
+ - **`test_app.py`** — デフォルト・カスタム・オフセット・total・スロット付きシリアライズ・422 (10 件)
24
+ - **`test_friction.py`** — 摩擦点の確認テスト (4 件)
25
+
26
+ **テスト結果**: 14 件全通過 ✅
27
+
28
+ ---
29
+
30
+ ## 摩擦点
31
+
32
+ ### FP44-1: OpenAPI スキーマにクエリパラメータが正しく文書化される
33
+
34
+ **分類**: 摩擦なし(良い設計の確認)
35
+
36
+ `Annotated[PaginationQueryParser, Depends()]` 構文を使うと、
37
+ `Query(ge=1, le=100, description="Items per page (1–100)")` の情報が
38
+ FastAPI の自動 OpenAPI スキーマ生成に反映される。
39
+ `/openapi.json` の `parameters` に `limit`・`offset` が説明付きで表示される。
40
+
41
+ **判断**: FT10 で実装した `Depends()` 対応の効果が確認できた。
42
+
43
+ ---
44
+
45
+ ### FP44-2: PaginationResponse.to_dict() は元のアイテムを変更しない
46
+
47
+ **分類**: 摩擦なし(設計の確認)
48
+
49
+ `to_dict()` は `dataclasses.asdict()` で新しい dict を生成するため、
50
+ 元の `dataclass` インスタンスは変更されない。immutable な `frozen=True` データクラスが
51
+ そのままリポジトリで保持できる。
52
+
53
+ ---
54
+
55
+ ### FP44-3: items が空リストのときも to_dict() が正常動作する
56
+
57
+ **分類**: 摩擦なし(エッジケース確認)
58
+
59
+ `items=[]` のとき `to_dict()` は `{"items": [], "limit": 20, "offset": 100, "total": 50}`
60
+ を返す。ページを超えたオフセットでのリクエストが自然に処理される。
61
+
62
+ ---
63
+
64
+ ### FP44-4: Depends() 使用時のバリデーションエラーが Problem Details 形式にならない
65
+
66
+ **分類**: 摩擦あり(Issues #268 で対応)
67
+
68
+ `PaginationQueryParser` を `Depends()` として使うと、
69
+ `limit=0` や `limit=101` のバリデーションは FastAPI が実行し、
70
+ エラーは FastAPI のデフォルト Pydantic 形式(`{"detail": [...]}`)になる:
71
+
72
+ ```json
73
+ {"detail": [{"type": "greater_than_equal", ...}]}
74
+ ```
75
+
76
+ 一方、nene2 の `ValidationException` は Problem Details 形式:
77
+
78
+ ```json
79
+ {"type": "...", "title": "Validation Failed", "status": 422, "errors": [...]}
80
+ ```
81
+
82
+ この不一致を解消するため、`nene2.middleware.request_validation_error_handler` を
83
+ FastAPI の exception handler として登録することで Problem Details 形式に統一できる:
84
+
85
+ ```python
86
+ from fastapi.exceptions import RequestValidationError
87
+ from nene2.middleware import request_validation_error_handler
88
+
89
+ app.add_exception_handler(RequestValidationError, request_validation_error_handler)
90
+ ```
91
+
92
+ **対応**: `request_validation_error_handler` は `error_handler.py` に既存実装済みだったが、
93
+ `nene2.middleware` からエクスポートされていなかった (Issue #268)。
94
+ `nene2.middleware.__init__` に追加することで `from nene2.middleware import request_validation_error_handler` が可能になった。
95
+
96
+ ---
97
+
98
+ ## フレームワーク変更
99
+
100
+ - `nene2.middleware.__init__` に `request_validation_error_handler` を追加エクスポート (#268)
101
+
102
+ ---
103
+
104
+ ## 関連
105
+
106
+ - `nene2.http.PaginationQueryParser` (FT10, v1.3.0)
107
+ - `nene2.http.PaginationResponse` (FT10, v1.3.0)
108
+ - `nene2.middleware.request_validation_error_handler`
109
+ - FT10 (PaginationQueryParser Depends 対応, v1.3.0)
110
+ - Issue #268 (request_validation_error_handler エクスポート追加)
@@ -0,0 +1,108 @@
1
+ # Field Trial 45: SecurityHeadersMiddleware 詳細カスタマイズ実運用検証
2
+
3
+ **日付**: 2026-05-20
4
+ **バージョン**: v1.8.7 時点
5
+ **テーマ**: `SecurityHeadersMiddleware` の全オプション(CSP・HSTS・Permissions-Policy・extra_no_csp_paths)を組み合わせた実運用確認
6
+
7
+ ---
8
+
9
+ ## 概要
10
+
11
+ `SecurityHeadersMiddleware` の CSP カスタマイズ・HSTS 設定・Permissions-Policy カスタマイズ・
12
+ カスタム docs_url との組み合わせを実装した。
13
+ 2 つの摩擦点を発見し、うち 1 件 (FP45-3) を修正した。
14
+
15
+ ---
16
+
17
+ ## 実装内容
18
+
19
+ `/home/xi/docker/nene2-python-FT/ft45-security-headers/` に以下を作成:
20
+
21
+ - **`app.py`** — カスタム `docs_url`/`openapi_url` + `SecurityHeadersMiddleware` 全オプション
22
+ - **`test_app.py`** — 静的ヘッダー・CSP・HSTS・Permissions-Policy・extra_no_csp_paths (12 件)
23
+ - **`test_friction.py`** — 摩擦点の確認テスト (4 件)
24
+
25
+ **テスト結果**: 16 件全通過 ✅
26
+
27
+ ---
28
+
29
+ ## 摩擦点
30
+
31
+ ### FP45-1: カスタム docs_url 使用時に extra_no_csp_paths の設定を忘れやすい
32
+
33
+ **分類**: 軽微な摩擦(ドキュメント追記で対応)
34
+
35
+ FastAPI の `docs_url="/api/docs"` のようにカスタム URL を使う場合、
36
+ `SecurityHeadersMiddleware` のデフォルト `no_csp_paths` には
37
+ `/docs`・`/redoc`・`/openapi.json` しか含まれない。
38
+ `/api/docs` には CSP が付いてしまい、Swagger UI の CDN アセットが CSP でブロックされる。
39
+
40
+ ```python
41
+ # カスタム docs_url 使用時の必須設定
42
+ app.add_middleware(
43
+ SecurityHeadersMiddleware,
44
+ extra_no_csp_paths=["/api/docs", "/api/redoc", "/api/openapi.json"],
45
+ )
46
+ ```
47
+
48
+ **判断**: FT15 で実装した `extra_no_csp_paths` で対応可能だが、
49
+ デフォルトの `docs_url` を変更したときに `extra_no_csp_paths` も更新する必要があることを
50
+ ドキュメントで注意喚起する価値がある。
51
+
52
+ ---
53
+
54
+ ### FP45-2: HSTS は本番環境以外で有効にしてはならない
55
+
56
+ **分類**: 注意喚起(設計通り・ドキュメント記載済み)
57
+
58
+ `hsts` パラメータを設定すると、`Strict-Transport-Security` ヘッダーが付与される。
59
+ http:// でアクセスすると次回以降 https:// を強制するため、開発環境での誤設定に注意が必要。
60
+ `hsts=None` のデフォルトは意図的な安全設計。
61
+
62
+ **判断**: 設計通り。ドキュメントの Warning で注意喚起済み。
63
+
64
+ ---
65
+
66
+ ### FP45-3: csp="" のとき空の CSP ヘッダーが付与されていた
67
+
68
+ **分類**: バグ(#271 で修正)
69
+
70
+ `csp=""` を渡すと `Content-Security-Policy: ` という空ヘッダーが付与されていた。
71
+ ユーザーが CSP を無効化しようとして `csp=""` を渡すと、空 CSP が付く予期しない動作になる。
72
+
73
+ **修正**: `dispatch()` を `self._csp` が truthy のときのみ CSP ヘッダーを付与するよう変更。
74
+ `csp=""` は「CSP ヘッダーを付けない」として扱われる。
75
+
76
+ ```python
77
+ # 修正前
78
+ if request.url.path not in self._no_csp_paths:
79
+ response.headers["Content-Security-Policy"] = self._csp
80
+
81
+ # 修正後
82
+ if request.url.path not in self._no_csp_paths and self._csp:
83
+ response.headers["Content-Security-Policy"] = self._csp
84
+ ```
85
+
86
+ ---
87
+
88
+ ### FP45-4: 全オプション組み合わせは問題なく動作する
89
+
90
+ **分類**: 摩擦なし(設計の確認)
91
+
92
+ `csp`・`permissions_policy`・`hsts`・`extra_no_csp_paths` の全オプションを同時に指定しても
93
+ 正常に動作することを確認した。
94
+
95
+ ---
96
+
97
+ ## フレームワーク変更
98
+
99
+ - `SecurityHeadersMiddleware.dispatch()` — `csp=""` のとき CSP ヘッダーを付与しないよう修正 (#271)
100
+
101
+ ---
102
+
103
+ ## 関連
104
+
105
+ - `nene2.middleware.SecurityHeadersMiddleware`
106
+ - FT15 (CSP カスタマイズ, v1.7.0)
107
+ - FT32 (HSTS・Permissions-Policy 追加, v1.8.3)
108
+ - Issue #271 (csp="" バグ修正)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nene2-python"
3
- version = "1.8.6"
3
+ version = "1.8.8"
4
4
  description = "NENE2 Python — minimal API framework following NENE2's design philosophy"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
@@ -1,7 +1,7 @@
1
1
  """NENE2 middleware pipeline."""
2
2
 
3
3
  from .domain_exception import DomainExceptionHandlerProtocol, SimpleDomainHandler
4
- from .error_handler import ErrorHandlerMiddleware
4
+ from .error_handler import ErrorHandlerMiddleware, request_validation_error_handler
5
5
  from .request_id import RequestIdMiddleware, get_request_id, request_id_var
6
6
  from .request_logging import RequestLoggingMiddleware
7
7
  from .request_size_limit import RequestSizeLimitMiddleware
@@ -12,6 +12,7 @@ __all__ = [
12
12
  "DomainExceptionHandlerProtocol",
13
13
  "SimpleDomainHandler",
14
14
  "ErrorHandlerMiddleware",
15
+ "request_validation_error_handler",
15
16
  "RequestIdMiddleware",
16
17
  "get_request_id",
17
18
  "RequestLoggingMiddleware",
@@ -63,6 +63,6 @@ class SecurityHeadersMiddleware(BaseHTTPMiddleware):
63
63
  response.headers["Permissions-Policy"] = self._permissions_policy
64
64
  if self._hsts:
65
65
  response.headers["Strict-Transport-Security"] = self._hsts
66
- if request.url.path not in self._no_csp_paths:
66
+ if request.url.path not in self._no_csp_paths and self._csp:
67
67
  response.headers["Content-Security-Policy"] = self._csp
68
68
  return response
@@ -130,3 +130,16 @@ def test_default_no_csp_paths_still_work_with_extra_paths() -> None:
130
130
  assert "Content-Security-Policy" not in client.get("/docs").headers
131
131
  assert "Content-Security-Policy" not in client.get("/custom").headers
132
132
  assert "Content-Security-Policy" in client.get("/ping").headers
133
+
134
+
135
+ def test_csp_empty_string_disables_csp_header() -> None:
136
+ app = FastAPI()
137
+ app.add_middleware(SecurityHeadersMiddleware, csp="")
138
+
139
+ @app.get("/ping")
140
+ async def ping() -> JSONResponse:
141
+ return JSONResponse({"ok": True})
142
+
143
+ client = TestClient(app)
144
+ r = client.get("/ping")
145
+ assert "Content-Security-Policy" not in r.headers
@@ -925,7 +925,7 @@ wheels = [
925
925
 
926
926
  [[package]]
927
927
  name = "nene2-python"
928
- version = "1.8.6"
928
+ version = "1.8.8"
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
File without changes
File without changes