xai-review 0.26.0__tar.gz → 0.27.0__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.

Potentially problematic release.


This version of xai-review might be problematic. Click here for more details.

Files changed (287) hide show
  1. {xai_review-0.26.0 → xai_review-0.27.0}/PKG-INFO +2 -2
  2. {xai_review-0.26.0 → xai_review-0.27.0}/README.md +1 -1
  3. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/bitbucket/pr/client.py +45 -8
  4. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/bitbucket/pr/schema/comments.py +7 -2
  5. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/bitbucket/pr/schema/files.py +8 -3
  6. xai_review-0.27.0/ai_review/clients/bitbucket/tools.py +6 -0
  7. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/github/pr/client.py +66 -12
  8. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/github/pr/schema/comments.py +2 -1
  9. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/github/pr/schema/files.py +2 -1
  10. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/github/pr/schema/reviews.py +2 -1
  11. xai_review-0.27.0/ai_review/clients/github/tools.py +6 -0
  12. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/gitlab/mr/client.py +35 -6
  13. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/gitlab/mr/schema/discussions.py +2 -1
  14. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/gitlab/mr/schema/notes.py +2 -1
  15. xai_review-0.27.0/ai_review/clients/gitlab/tools.py +5 -0
  16. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/config/vcs/base.py +2 -0
  17. xai_review-0.27.0/ai_review/libs/config/vcs/pagination.py +6 -0
  18. xai_review-0.27.0/ai_review/libs/http/paginate.py +43 -0
  19. xai_review-0.27.0/ai_review/tests/suites/clients/bitbucket/test_client.py +14 -0
  20. xai_review-0.27.0/ai_review/tests/suites/clients/bitbucket/test_tools.py +31 -0
  21. xai_review-0.27.0/ai_review/tests/suites/clients/github/test_tools.py +31 -0
  22. xai_review-0.27.0/ai_review/tests/suites/clients/gitlab/test_tools.py +26 -0
  23. xai_review-0.27.0/ai_review/tests/suites/libs/http/test_paginate.py +95 -0
  24. xai_review-0.27.0/ai_review/tests/suites/services/vcs/github/__init__.py +0 -0
  25. xai_review-0.27.0/ai_review/tests/suites/services/vcs/gitlab/__init__.py +0 -0
  26. {xai_review-0.26.0 → xai_review-0.27.0}/pyproject.toml +1 -1
  27. {xai_review-0.26.0 → xai_review-0.27.0}/xai_review.egg-info/PKG-INFO +2 -2
  28. {xai_review-0.26.0 → xai_review-0.27.0}/xai_review.egg-info/SOURCES.txt +12 -0
  29. {xai_review-0.26.0 → xai_review-0.27.0}/LICENSE +0 -0
  30. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/__init__.py +0 -0
  31. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/cli/__init__.py +0 -0
  32. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/cli/commands/__init__.py +0 -0
  33. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/cli/commands/run_context_review.py +0 -0
  34. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/cli/commands/run_inline_review.py +0 -0
  35. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/cli/commands/run_review.py +0 -0
  36. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/cli/commands/run_summary_review.py +0 -0
  37. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/cli/main.py +0 -0
  38. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/__init__.py +0 -0
  39. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/bitbucket/__init__.py +0 -0
  40. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/bitbucket/client.py +0 -0
  41. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/bitbucket/pr/__init__.py +0 -0
  42. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/bitbucket/pr/schema/__init__.py +0 -0
  43. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/bitbucket/pr/schema/pull_request.py +0 -0
  44. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/bitbucket/pr/types.py +0 -0
  45. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/claude/__init__.py +0 -0
  46. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/claude/client.py +0 -0
  47. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/claude/schema.py +0 -0
  48. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/claude/types.py +0 -0
  49. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/gemini/__init__.py +0 -0
  50. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/gemini/client.py +0 -0
  51. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/gemini/schema.py +0 -0
  52. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/gemini/types.py +0 -0
  53. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/github/__init__.py +0 -0
  54. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/github/client.py +0 -0
  55. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/github/pr/__init__.py +0 -0
  56. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/github/pr/schema/__init__.py +0 -0
  57. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/github/pr/schema/pull_request.py +0 -0
  58. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/github/pr/types.py +0 -0
  59. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/gitlab/__init__.py +0 -0
  60. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/gitlab/client.py +0 -0
  61. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/gitlab/mr/__init__.py +0 -0
  62. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/gitlab/mr/schema/__init__.py +0 -0
  63. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/gitlab/mr/schema/changes.py +0 -0
  64. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/gitlab/mr/types.py +0 -0
  65. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/ollama/__init__.py +0 -0
  66. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/ollama/client.py +0 -0
  67. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/ollama/schema.py +0 -0
  68. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/ollama/types.py +0 -0
  69. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/openai/__init__.py +0 -0
  70. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/openai/client.py +0 -0
  71. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/openai/schema.py +0 -0
  72. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/clients/openai/types.py +0 -0
  73. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/config.py +0 -0
  74. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/__init__.py +0 -0
  75. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/asynchronous/__init__.py +0 -0
  76. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/asynchronous/gather.py +0 -0
  77. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/config/__init__.py +0 -0
  78. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/config/artifacts.py +0 -0
  79. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/config/base.py +0 -0
  80. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/config/core.py +0 -0
  81. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/config/http.py +0 -0
  82. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/config/llm/__init__.py +0 -0
  83. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/config/llm/base.py +0 -0
  84. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/config/llm/claude.py +0 -0
  85. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/config/llm/gemini.py +0 -0
  86. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/config/llm/meta.py +0 -0
  87. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/config/llm/ollama.py +0 -0
  88. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/config/llm/openai.py +0 -0
  89. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/config/logger.py +0 -0
  90. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/config/prompt.py +0 -0
  91. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/config/review.py +0 -0
  92. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/config/vcs/__init__.py +0 -0
  93. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/config/vcs/bitbucket.py +0 -0
  94. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/config/vcs/github.py +0 -0
  95. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/config/vcs/gitlab.py +0 -0
  96. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/constants/__init__.py +0 -0
  97. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/constants/llm_provider.py +0 -0
  98. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/constants/vcs_provider.py +0 -0
  99. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/diff/__init__.py +0 -0
  100. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/diff/models.py +0 -0
  101. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/diff/parser.py +0 -0
  102. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/diff/tools.py +0 -0
  103. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/http/__init__.py +0 -0
  104. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/http/client.py +0 -0
  105. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/http/event_hooks/__init__.py +0 -0
  106. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/http/event_hooks/base.py +0 -0
  107. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/http/event_hooks/logger.py +0 -0
  108. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/http/handlers.py +0 -0
  109. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/http/transports/__init__.py +0 -0
  110. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/http/transports/retry.py +0 -0
  111. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/json.py +0 -0
  112. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/logger.py +0 -0
  113. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/resources.py +0 -0
  114. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/template/__init__.py +0 -0
  115. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/libs/template/render.py +0 -0
  116. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/prompts/__init__.py +0 -0
  117. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/prompts/default_context.md +0 -0
  118. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/prompts/default_inline.md +0 -0
  119. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/prompts/default_summary.md +0 -0
  120. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/prompts/default_system_context.md +0 -0
  121. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/prompts/default_system_inline.md +0 -0
  122. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/prompts/default_system_summary.md +0 -0
  123. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/resources/__init__.py +0 -0
  124. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/resources/pricing.yaml +0 -0
  125. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/__init__.py +0 -0
  126. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/artifacts/__init__.py +0 -0
  127. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/artifacts/schema.py +0 -0
  128. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/artifacts/service.py +0 -0
  129. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/artifacts/tools.py +0 -0
  130. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/artifacts/types.py +0 -0
  131. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/cost/__init__.py +0 -0
  132. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/cost/schema.py +0 -0
  133. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/cost/service.py +0 -0
  134. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/cost/types.py +0 -0
  135. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/diff/__init__.py +0 -0
  136. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/diff/renderers.py +0 -0
  137. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/diff/schema.py +0 -0
  138. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/diff/service.py +0 -0
  139. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/diff/tools.py +0 -0
  140. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/diff/types.py +0 -0
  141. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/git/__init__.py +0 -0
  142. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/git/service.py +0 -0
  143. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/git/types.py +0 -0
  144. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/hook/__init__.py +0 -0
  145. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/hook/constants.py +0 -0
  146. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/hook/service.py +0 -0
  147. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/hook/types.py +0 -0
  148. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/llm/__init__.py +0 -0
  149. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/llm/claude/__init__.py +0 -0
  150. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/llm/claude/client.py +0 -0
  151. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/llm/factory.py +0 -0
  152. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/llm/gemini/__init__.py +0 -0
  153. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/llm/gemini/client.py +0 -0
  154. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/llm/ollama/__init__.py +0 -0
  155. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/llm/ollama/client.py +0 -0
  156. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/llm/openai/__init__.py +0 -0
  157. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/llm/openai/client.py +0 -0
  158. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/llm/types.py +0 -0
  159. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/prompt/__init__.py +0 -0
  160. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/prompt/adapter.py +0 -0
  161. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/prompt/schema.py +0 -0
  162. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/prompt/service.py +0 -0
  163. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/prompt/tools.py +0 -0
  164. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/prompt/types.py +0 -0
  165. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/review/__init__.py +0 -0
  166. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/review/gateway/__init__.py +0 -0
  167. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/review/gateway/comment.py +0 -0
  168. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/review/gateway/llm.py +0 -0
  169. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/review/inline/__init__.py +0 -0
  170. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/review/inline/schema.py +0 -0
  171. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/review/inline/service.py +0 -0
  172. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/review/inline/types.py +0 -0
  173. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/review/policy/__init__.py +0 -0
  174. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/review/policy/service.py +0 -0
  175. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/review/service.py +0 -0
  176. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/review/summary/__init__.py +0 -0
  177. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/review/summary/schema.py +0 -0
  178. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/review/summary/service.py +0 -0
  179. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/review/summary/types.py +0 -0
  180. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/vcs/__init__.py +0 -0
  181. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/vcs/bitbucket/__init__.py +0 -0
  182. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/vcs/bitbucket/client.py +0 -0
  183. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/vcs/factory.py +0 -0
  184. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/vcs/github/__init__.py +0 -0
  185. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/vcs/github/client.py +0 -0
  186. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/vcs/gitlab/__init__.py +0 -0
  187. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/vcs/gitlab/client.py +0 -0
  188. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/services/vcs/types.py +0 -0
  189. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/__init__.py +0 -0
  190. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/fixtures/__init__.py +0 -0
  191. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/fixtures/clients/__init__.py +0 -0
  192. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/fixtures/clients/bitbucket.py +0 -0
  193. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/fixtures/clients/claude.py +0 -0
  194. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/fixtures/clients/gemini.py +0 -0
  195. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/fixtures/clients/github.py +0 -0
  196. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/fixtures/clients/gitlab.py +0 -0
  197. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/fixtures/clients/ollama.py +0 -0
  198. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/fixtures/clients/openai.py +0 -0
  199. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/fixtures/services/__init__.py +0 -0
  200. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/fixtures/services/artifacts.py +0 -0
  201. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/fixtures/services/cost.py +0 -0
  202. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/fixtures/services/diff.py +0 -0
  203. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/fixtures/services/git.py +0 -0
  204. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/fixtures/services/llm.py +0 -0
  205. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/fixtures/services/prompt.py +0 -0
  206. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/fixtures/services/review/__init__.py +0 -0
  207. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/fixtures/services/review/inline.py +0 -0
  208. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/fixtures/services/review/summary.py +0 -0
  209. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/fixtures/services/vcs.py +0 -0
  210. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/__init__.py +0 -0
  211. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/clients/__init__.py +0 -0
  212. {xai_review-0.26.0/ai_review/tests/suites/clients/claude → xai_review-0.27.0/ai_review/tests/suites/clients/bitbucket}/__init__.py +0 -0
  213. {xai_review-0.26.0/ai_review/tests/suites/clients/gemini → xai_review-0.27.0/ai_review/tests/suites/clients/claude}/__init__.py +0 -0
  214. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/clients/claude/test_client.py +0 -0
  215. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/clients/claude/test_schema.py +0 -0
  216. {xai_review-0.26.0/ai_review/tests/suites/clients/github → xai_review-0.27.0/ai_review/tests/suites/clients/gemini}/__init__.py +0 -0
  217. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/clients/gemini/test_client.py +0 -0
  218. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/clients/gemini/test_schema.py +0 -0
  219. {xai_review-0.26.0/ai_review/tests/suites/clients/gitlab → xai_review-0.27.0/ai_review/tests/suites/clients/github}/__init__.py +0 -0
  220. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/clients/github/test_client.py +0 -0
  221. {xai_review-0.26.0/ai_review/tests/suites/clients/ollama → xai_review-0.27.0/ai_review/tests/suites/clients/gitlab}/__init__.py +0 -0
  222. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/clients/gitlab/test_client.py +0 -0
  223. {xai_review-0.26.0/ai_review/tests/suites/clients/openai → xai_review-0.27.0/ai_review/tests/suites/clients/ollama}/__init__.py +0 -0
  224. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/clients/ollama/test_client.py +0 -0
  225. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/clients/ollama/test_schema.py +0 -0
  226. {xai_review-0.26.0/ai_review/tests/suites/libs → xai_review-0.27.0/ai_review/tests/suites/clients/openai}/__init__.py +0 -0
  227. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/clients/openai/test_client.py +0 -0
  228. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/clients/openai/test_schema.py +0 -0
  229. {xai_review-0.26.0/ai_review/tests/suites/libs/asynchronous → xai_review-0.27.0/ai_review/tests/suites/libs}/__init__.py +0 -0
  230. {xai_review-0.26.0/ai_review/tests/suites/libs/config → xai_review-0.27.0/ai_review/tests/suites/libs/asynchronous}/__init__.py +0 -0
  231. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/libs/asynchronous/test_gather.py +0 -0
  232. {xai_review-0.26.0/ai_review/tests/suites/libs/diff → xai_review-0.27.0/ai_review/tests/suites/libs/config}/__init__.py +0 -0
  233. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/libs/config/test_prompt.py +0 -0
  234. {xai_review-0.26.0/ai_review/tests/suites/libs/template → xai_review-0.27.0/ai_review/tests/suites/libs/diff}/__init__.py +0 -0
  235. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/libs/diff/test_models.py +0 -0
  236. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/libs/diff/test_parser.py +0 -0
  237. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/libs/diff/test_tools.py +0 -0
  238. {xai_review-0.26.0/ai_review/tests/suites/services → xai_review-0.27.0/ai_review/tests/suites/libs/http}/__init__.py +0 -0
  239. {xai_review-0.26.0/ai_review/tests/suites/services/cost → xai_review-0.27.0/ai_review/tests/suites/libs/template}/__init__.py +0 -0
  240. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/libs/template/test_render.py +0 -0
  241. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/libs/test_json.py +0 -0
  242. {xai_review-0.26.0/ai_review/tests/suites/services/diff → xai_review-0.27.0/ai_review/tests/suites/services}/__init__.py +0 -0
  243. {xai_review-0.26.0/ai_review/tests/suites/services/hook → xai_review-0.27.0/ai_review/tests/suites/services/cost}/__init__.py +0 -0
  244. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/services/cost/test_schema.py +0 -0
  245. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/services/cost/test_service.py +0 -0
  246. {xai_review-0.26.0/ai_review/tests/suites/services/llm → xai_review-0.27.0/ai_review/tests/suites/services/diff}/__init__.py +0 -0
  247. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/services/diff/test_renderers.py +0 -0
  248. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/services/diff/test_service.py +0 -0
  249. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/services/diff/test_tools.py +0 -0
  250. {xai_review-0.26.0/ai_review/tests/suites/services/llm/claude → xai_review-0.27.0/ai_review/tests/suites/services/hook}/__init__.py +0 -0
  251. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/services/hook/test_service.py +0 -0
  252. {xai_review-0.26.0/ai_review/tests/suites/services/llm/gemini → xai_review-0.27.0/ai_review/tests/suites/services/llm}/__init__.py +0 -0
  253. {xai_review-0.26.0/ai_review/tests/suites/services/llm/ollama → xai_review-0.27.0/ai_review/tests/suites/services/llm/claude}/__init__.py +0 -0
  254. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/services/llm/claude/test_client.py +0 -0
  255. {xai_review-0.26.0/ai_review/tests/suites/services/llm/openai → xai_review-0.27.0/ai_review/tests/suites/services/llm/gemini}/__init__.py +0 -0
  256. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/services/llm/gemini/test_client.py +0 -0
  257. {xai_review-0.26.0/ai_review/tests/suites/services/prompt → xai_review-0.27.0/ai_review/tests/suites/services/llm/ollama}/__init__.py +0 -0
  258. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/services/llm/ollama/test_client.py +0 -0
  259. {xai_review-0.26.0/ai_review/tests/suites/services/review → xai_review-0.27.0/ai_review/tests/suites/services/llm/openai}/__init__.py +0 -0
  260. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/services/llm/openai/test_client.py +0 -0
  261. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/services/llm/test_factory.py +0 -0
  262. {xai_review-0.26.0/ai_review/tests/suites/services/review/inline → xai_review-0.27.0/ai_review/tests/suites/services/prompt}/__init__.py +0 -0
  263. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/services/prompt/test_adapter.py +0 -0
  264. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/services/prompt/test_schema.py +0 -0
  265. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/services/prompt/test_service.py +0 -0
  266. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/services/prompt/test_tools.py +0 -0
  267. {xai_review-0.26.0/ai_review/tests/suites/services/review/policy → xai_review-0.27.0/ai_review/tests/suites/services/review}/__init__.py +0 -0
  268. {xai_review-0.26.0/ai_review/tests/suites/services/review/summary → xai_review-0.27.0/ai_review/tests/suites/services/review/inline}/__init__.py +0 -0
  269. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/services/review/inline/test_schema.py +0 -0
  270. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/services/review/inline/test_service.py +0 -0
  271. {xai_review-0.26.0/ai_review/tests/suites/services/vcs → xai_review-0.27.0/ai_review/tests/suites/services/review/policy}/__init__.py +0 -0
  272. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/services/review/policy/test_service.py +0 -0
  273. {xai_review-0.26.0/ai_review/tests/suites/services/vcs/bitbucket → xai_review-0.27.0/ai_review/tests/suites/services/review/summary}/__init__.py +0 -0
  274. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/services/review/summary/test_schema.py +0 -0
  275. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/services/review/summary/test_service.py +0 -0
  276. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/services/review/test_service.py +0 -0
  277. {xai_review-0.26.0/ai_review/tests/suites/services/vcs/github → xai_review-0.27.0/ai_review/tests/suites/services/vcs}/__init__.py +0 -0
  278. {xai_review-0.26.0/ai_review/tests/suites/services/vcs/gitlab → xai_review-0.27.0/ai_review/tests/suites/services/vcs/bitbucket}/__init__.py +0 -0
  279. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/services/vcs/bitbucket/test_service.py +0 -0
  280. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/services/vcs/github/test_service.py +0 -0
  281. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/services/vcs/gitlab/test_service.py +0 -0
  282. {xai_review-0.26.0 → xai_review-0.27.0}/ai_review/tests/suites/services/vcs/test_factory.py +0 -0
  283. {xai_review-0.26.0 → xai_review-0.27.0}/setup.cfg +0 -0
  284. {xai_review-0.26.0 → xai_review-0.27.0}/xai_review.egg-info/dependency_links.txt +0 -0
  285. {xai_review-0.26.0 → xai_review-0.27.0}/xai_review.egg-info/entry_points.txt +0 -0
  286. {xai_review-0.26.0 → xai_review-0.27.0}/xai_review.egg-info/requires.txt +0 -0
  287. {xai_review-0.26.0 → xai_review-0.27.0}/xai_review.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xai-review
3
- Version: 0.26.0
3
+ Version: 0.27.0
4
4
  Summary: AI-powered code review tool
5
5
  Author-email: Nikita Filonov <nikita.filonov@example.com>
6
6
  Maintainer-email: Nikita Filonov <nikita.filonov@example.com>
@@ -209,7 +209,7 @@ jobs:
209
209
  runs-on: ubuntu-latest
210
210
  steps:
211
211
  - uses: actions/checkout@v4
212
- - uses: Nikita-Filonov/ai-review@v0.26.0
212
+ - uses: Nikita-Filonov/ai-review@v0.27.0
213
213
  with:
214
214
  review-command: ${{ inputs.review-command }}
215
215
  env:
@@ -175,7 +175,7 @@ jobs:
175
175
  runs-on: ubuntu-latest
176
176
  steps:
177
177
  - uses: actions/checkout@v4
178
- - uses: Nikita-Filonov/ai-review@v0.26.0
178
+ - uses: Nikita-Filonov/ai-review@v0.27.0
179
179
  with:
180
180
  review-command: ${{ inputs.review-command }}
181
181
  env:
@@ -1,19 +1,24 @@
1
1
  from httpx import Response, QueryParams
2
2
 
3
3
  from ai_review.clients.bitbucket.pr.schema.comments import (
4
+ BitbucketPRCommentSchema,
4
5
  BitbucketGetPRCommentsQuerySchema,
5
6
  BitbucketGetPRCommentsResponseSchema,
6
7
  BitbucketCreatePRCommentRequestSchema,
7
8
  BitbucketCreatePRCommentResponseSchema,
8
9
  )
9
10
  from ai_review.clients.bitbucket.pr.schema.files import (
11
+ BitbucketPRFileSchema,
10
12
  BitbucketGetPRFilesQuerySchema,
11
13
  BitbucketGetPRFilesResponseSchema,
12
14
  )
13
15
  from ai_review.clients.bitbucket.pr.schema.pull_request import BitbucketGetPRResponseSchema
14
16
  from ai_review.clients.bitbucket.pr.types import BitbucketPullRequestsHTTPClientProtocol
17
+ from ai_review.clients.bitbucket.tools import bitbucket_has_next_page
18
+ from ai_review.config import settings
15
19
  from ai_review.libs.http.client import HTTPClient
16
20
  from ai_review.libs.http.handlers import handle_http_error, HTTPClientError
21
+ from ai_review.libs.http.paginate import paginate
17
22
 
18
23
 
19
24
  class BitbucketPullRequestsHTTPClientError(HTTPClientError):
@@ -35,7 +40,7 @@ class BitbucketPullRequestsHTTPClient(HTTPClient, BitbucketPullRequestsHTTPClien
35
40
  ) -> Response:
36
41
  return await self.get(
37
42
  f"/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/diffstat",
38
- query=QueryParams(**query.model_dump()),
43
+ query=QueryParams(**query.model_dump(by_alias=True)),
39
44
  )
40
45
 
41
46
  @handle_http_error(client="BitbucketPullRequestsHTTPClient", exception=BitbucketPullRequestsHTTPClientError)
@@ -48,7 +53,7 @@ class BitbucketPullRequestsHTTPClient(HTTPClient, BitbucketPullRequestsHTTPClien
48
53
  ) -> Response:
49
54
  return await self.get(
50
55
  f"/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/comments",
51
- query=QueryParams(**query.model_dump()),
56
+ query=QueryParams(**query.model_dump(by_alias=True)),
52
57
  )
53
58
 
54
59
  @handle_http_error(client="BitbucketPullRequestsHTTPClient", exception=BitbucketPullRequestsHTTPClientError)
@@ -79,9 +84,25 @@ class BitbucketPullRequestsHTTPClient(HTTPClient, BitbucketPullRequestsHTTPClien
79
84
  repo_slug: str,
80
85
  pull_request_id: str
81
86
  ) -> BitbucketGetPRFilesResponseSchema:
82
- query = BitbucketGetPRFilesQuerySchema(pagelen=100)
83
- resp = await self.get_diffstat_api(workspace, repo_slug, pull_request_id, query)
84
- return BitbucketGetPRFilesResponseSchema.model_validate_json(resp.text)
87
+ async def fetch_page(page: int) -> Response:
88
+ query = BitbucketGetPRFilesQuerySchema(page=page, page_len=settings.vcs.pagination.per_page)
89
+ return await self.get_diffstat_api(workspace, repo_slug, pull_request_id, query)
90
+
91
+ def extract_items(response: Response) -> list[BitbucketPRFileSchema]:
92
+ result = BitbucketGetPRFilesResponseSchema.model_validate_json(response.text)
93
+ return result.values
94
+
95
+ items = await paginate(
96
+ max_pages=settings.vcs.pagination.max_pages,
97
+ fetch_page=fetch_page,
98
+ extract_items=extract_items,
99
+ has_next_page=bitbucket_has_next_page
100
+ )
101
+ return BitbucketGetPRFilesResponseSchema(
102
+ size=len(items),
103
+ values=items,
104
+ page_len=settings.vcs.pagination.per_page
105
+ )
85
106
 
86
107
  async def get_comments(
87
108
  self,
@@ -89,9 +110,25 @@ class BitbucketPullRequestsHTTPClient(HTTPClient, BitbucketPullRequestsHTTPClien
89
110
  repo_slug: str,
90
111
  pull_request_id: str
91
112
  ) -> BitbucketGetPRCommentsResponseSchema:
92
- query = BitbucketGetPRCommentsQuerySchema(pagelen=100)
93
- response = await self.get_comments_api(workspace, repo_slug, pull_request_id, query)
94
- return BitbucketGetPRCommentsResponseSchema.model_validate_json(response.text)
113
+ async def fetch_page(page: int) -> Response:
114
+ query = BitbucketGetPRCommentsQuerySchema(page=page, page_len=settings.vcs.pagination.per_page)
115
+ return await self.get_comments_api(workspace, repo_slug, pull_request_id, query)
116
+
117
+ def extract_items(response: Response) -> list[BitbucketPRCommentSchema]:
118
+ result = BitbucketGetPRCommentsResponseSchema.model_validate_json(response.text)
119
+ return result.values
120
+
121
+ items = await paginate(
122
+ max_pages=settings.vcs.pagination.max_pages,
123
+ fetch_page=fetch_page,
124
+ extract_items=extract_items,
125
+ has_next_page=bitbucket_has_next_page
126
+ )
127
+ return BitbucketGetPRCommentsResponseSchema(
128
+ size=len(items),
129
+ values=items,
130
+ page_len=settings.vcs.pagination.per_page
131
+ )
95
132
 
96
133
  async def create_comment(
97
134
  self,
@@ -22,15 +22,20 @@ class BitbucketPRCommentSchema(BaseModel):
22
22
 
23
23
 
24
24
  class BitbucketGetPRCommentsQuerySchema(BaseModel):
25
- pagelen: int = 100
25
+ model_config = ConfigDict(populate_by_name=True)
26
+
27
+ page: int = 1
28
+ page_len: int = Field(alias="pagelen", default=100)
26
29
 
27
30
 
28
31
  class BitbucketGetPRCommentsResponseSchema(BaseModel):
32
+ model_config = ConfigDict(populate_by_name=True)
33
+
29
34
  size: int
30
35
  page: int | None = None
31
36
  next: str | None = None
32
37
  values: list[BitbucketPRCommentSchema]
33
- pagelen: int
38
+ page_len: int = Field(alias="pagelen")
34
39
 
35
40
 
36
41
  class BitbucketCreatePRCommentRequestSchema(BaseModel):
@@ -1,4 +1,4 @@
1
- from pydantic import BaseModel
1
+ from pydantic import BaseModel, Field, ConfigDict
2
2
 
3
3
 
4
4
  class BitbucketPRFilePathSchema(BaseModel):
@@ -14,12 +14,17 @@ class BitbucketPRFileSchema(BaseModel):
14
14
 
15
15
 
16
16
  class BitbucketGetPRFilesQuerySchema(BaseModel):
17
- pagelen: int = 100
17
+ model_config = ConfigDict(populate_by_name=True)
18
+
19
+ page: int = 1
20
+ page_len: int = Field(alias="pagelen", default=100)
18
21
 
19
22
 
20
23
  class BitbucketGetPRFilesResponseSchema(BaseModel):
24
+ model_config = ConfigDict(populate_by_name=True)
25
+
21
26
  size: int
22
27
  page: int | None = None
23
28
  next: str | None = None
24
29
  values: list[BitbucketPRFileSchema]
25
- pagelen: int
30
+ page_len: int = Field(alias="pagelen")
@@ -0,0 +1,6 @@
1
+ from httpx import Response
2
+
3
+
4
+ def bitbucket_has_next_page(response: Response) -> bool:
5
+ data = response.json()
6
+ return bool(data.get("next"))
@@ -1,6 +1,7 @@
1
1
  from httpx import Response, QueryParams
2
2
 
3
3
  from ai_review.clients.github.pr.schema.comments import (
4
+ GitHubPRCommentSchema,
4
5
  GitHubGetPRCommentsQuerySchema,
5
6
  GitHubGetPRCommentsResponseSchema,
6
7
  GitHubCreateIssueCommentRequestSchema,
@@ -9,17 +10,22 @@ from ai_review.clients.github.pr.schema.comments import (
9
10
  GitHubCreateReviewCommentResponseSchema
10
11
  )
11
12
  from ai_review.clients.github.pr.schema.files import (
13
+ GitHubPRFileSchema,
12
14
  GitHubGetPRFilesQuerySchema,
13
15
  GitHubGetPRFilesResponseSchema
14
16
  )
15
17
  from ai_review.clients.github.pr.schema.pull_request import GitHubGetPRResponseSchema
16
18
  from ai_review.clients.github.pr.schema.reviews import (
19
+ GitHubPRReviewSchema,
17
20
  GitHubGetPRReviewsQuerySchema,
18
21
  GitHubGetPRReviewsResponseSchema
19
22
  )
20
23
  from ai_review.clients.github.pr.types import GitHubPullRequestsHTTPClientProtocol
24
+ from ai_review.clients.github.tools import github_has_next_page
25
+ from ai_review.config import settings
21
26
  from ai_review.libs.http.client import HTTPClient
22
27
  from ai_review.libs.http.handlers import HTTPClientError, handle_http_error
28
+ from ai_review.libs.http.paginate import paginate
23
29
 
24
30
 
25
31
  class GitHubPullRequestsHTTPClientError(HTTPClientError):
@@ -114,24 +120,72 @@ class GitHubPullRequestsHTTPClient(HTTPClient, GitHubPullRequestsHTTPClientProto
114
120
  return GitHubGetPRResponseSchema.model_validate_json(response.text)
115
121
 
116
122
  async def get_files(self, owner: str, repo: str, pull_number: str) -> GitHubGetPRFilesResponseSchema:
117
- query = GitHubGetPRFilesQuerySchema(per_page=100)
118
- response = await self.get_files_api(owner, repo, pull_number, query)
119
- return GitHubGetPRFilesResponseSchema.model_validate_json(response.text)
123
+ async def fetch_page(page: int) -> Response:
124
+ query = GitHubGetPRFilesQuerySchema(page=page, per_page=settings.vcs.pagination.per_page)
125
+ return await self.get_files_api(owner, repo, pull_number, query)
126
+
127
+ def extract_items(response: Response) -> list[GitHubPRFileSchema]:
128
+ result = GitHubGetPRFilesResponseSchema.model_validate_json(response.text)
129
+ return result.root
130
+
131
+ items = await paginate(
132
+ max_pages=settings.vcs.pagination.max_pages,
133
+ fetch_page=fetch_page,
134
+ extract_items=extract_items,
135
+ has_next_page=github_has_next_page
136
+ )
137
+ return GitHubGetPRFilesResponseSchema(root=items)
120
138
 
121
139
  async def get_issue_comments(self, owner: str, repo: str, issue_number: str) -> GitHubGetPRCommentsResponseSchema:
122
- query = GitHubGetPRCommentsQuerySchema(per_page=100)
123
- response = await self.get_issue_comments_api(owner, repo, issue_number, query)
124
- return GitHubGetPRCommentsResponseSchema.model_validate_json(response.text)
140
+ async def fetch_page(page: int) -> Response:
141
+ query = GitHubGetPRCommentsQuerySchema(page=page, per_page=settings.vcs.pagination.per_page)
142
+ return await self.get_issue_comments_api(owner, repo, issue_number, query)
143
+
144
+ def extract_items(response: Response) -> list[GitHubPRCommentSchema]:
145
+ result = GitHubGetPRCommentsResponseSchema.model_validate_json(response.text)
146
+ return result.root
147
+
148
+ items = await paginate(
149
+ max_pages=settings.vcs.pagination.max_pages,
150
+ fetch_page=fetch_page,
151
+ extract_items=extract_items,
152
+ has_next_page=github_has_next_page
153
+ )
154
+ return GitHubGetPRCommentsResponseSchema(root=items)
125
155
 
126
156
  async def get_review_comments(self, owner: str, repo: str, pull_number: str) -> GitHubGetPRCommentsResponseSchema:
127
- query = GitHubGetPRCommentsQuerySchema(per_page=100)
128
- response = await self.get_review_comments_api(owner, repo, pull_number, query)
129
- return GitHubGetPRCommentsResponseSchema.model_validate_json(response.text)
157
+ async def fetch_page(page: int) -> Response:
158
+ query = GitHubGetPRCommentsQuerySchema(page=page, per_page=settings.vcs.pagination.per_page)
159
+ return await self.get_review_comments_api(owner, repo, pull_number, query)
160
+
161
+ def extract_items(response: Response) -> list[GitHubPRCommentSchema]:
162
+ result = GitHubGetPRCommentsResponseSchema.model_validate_json(response.text)
163
+ return result.root
164
+
165
+ items = await paginate(
166
+ max_pages=settings.vcs.pagination.max_pages,
167
+ fetch_page=fetch_page,
168
+ extract_items=extract_items,
169
+ has_next_page=github_has_next_page
170
+ )
171
+ return GitHubGetPRCommentsResponseSchema(root=items)
130
172
 
131
173
  async def get_reviews(self, owner: str, repo: str, pull_number: str) -> GitHubGetPRReviewsResponseSchema:
132
- query = GitHubGetPRReviewsQuerySchema(per_page=100)
133
- response = await self.get_reviews_api(owner, repo, pull_number, query)
134
- return GitHubGetPRReviewsResponseSchema.model_validate_json(response.text)
174
+ async def fetch_page(page: int) -> Response:
175
+ query = GitHubGetPRReviewsQuerySchema(page=page, per_page=settings.vcs.pagination.per_page)
176
+ return await self.get_reviews_api(owner, repo, pull_number, query)
177
+
178
+ def extract_items(response: Response) -> list[GitHubPRReviewSchema]:
179
+ result = GitHubGetPRReviewsResponseSchema.model_validate_json(response.text)
180
+ return result.root
181
+
182
+ items = await paginate(
183
+ max_pages=settings.vcs.pagination.max_pages,
184
+ fetch_page=fetch_page,
185
+ extract_items=extract_items,
186
+ has_next_page=github_has_next_page
187
+ )
188
+ return GitHubGetPRReviewsResponseSchema(root=items)
135
189
 
136
190
  async def create_review_comment(
137
191
  self,
@@ -9,7 +9,8 @@ class GitHubPRCommentSchema(BaseModel):
9
9
 
10
10
 
11
11
  class GitHubGetPRCommentsQuerySchema(BaseModel):
12
- per_page: int
12
+ page: int = 1
13
+ per_page: int = 100
13
14
 
14
15
 
15
16
  class GitHubGetPRCommentsResponseSchema(RootModel[list[GitHubPRCommentSchema]]):
@@ -9,7 +9,8 @@ class GitHubPRFileSchema(BaseModel):
9
9
 
10
10
 
11
11
  class GitHubGetPRFilesQuerySchema(BaseModel):
12
- per_page: int
12
+ page: int = 1
13
+ per_page: int = 100
13
14
 
14
15
 
15
16
  class GitHubGetPRFilesResponseSchema(RootModel[list[GitHubPRFileSchema]]):
@@ -10,7 +10,8 @@ class GitHubPRReviewSchema(BaseModel):
10
10
 
11
11
 
12
12
  class GitHubGetPRReviewsQuerySchema(BaseModel):
13
- per_page: int
13
+ page: int = 1
14
+ per_page: int = 100
14
15
 
15
16
 
16
17
  class GitHubGetPRReviewsResponseSchema(RootModel[list[GitHubPRReviewSchema]]):
@@ -0,0 +1,6 @@
1
+ from httpx import Response
2
+
3
+
4
+ def github_has_next_page(response: Response) -> bool:
5
+ link_header = response.headers.get("Link")
6
+ return (link_header is not None) and ('rel="next"' in link_header)
@@ -2,20 +2,25 @@ from httpx import Response, QueryParams
2
2
 
3
3
  from ai_review.clients.gitlab.mr.schema.changes import GitLabGetMRChangesResponseSchema
4
4
  from ai_review.clients.gitlab.mr.schema.discussions import (
5
+ GitLabDiscussionSchema,
5
6
  GitLabGetMRDiscussionsQuerySchema,
6
7
  GitLabGetMRDiscussionsResponseSchema,
7
8
  GitLabCreateMRDiscussionRequestSchema,
8
9
  GitLabCreateMRDiscussionResponseSchema
9
10
  )
10
11
  from ai_review.clients.gitlab.mr.schema.notes import (
12
+ GitLabNoteSchema,
11
13
  GitLabGetMRNotesQuerySchema,
12
14
  GitLabGetMRNotesResponseSchema,
13
15
  GitLabCreateMRNoteRequestSchema,
14
16
  GitLabCreateMRNoteResponseSchema,
15
17
  )
16
18
  from ai_review.clients.gitlab.mr.types import GitLabMergeRequestsHTTPClientProtocol
19
+ from ai_review.clients.gitlab.tools import gitlab_has_next_page
20
+ from ai_review.config import settings
17
21
  from ai_review.libs.http.client import HTTPClient
18
22
  from ai_review.libs.http.handlers import handle_http_error, HTTPClientError
23
+ from ai_review.libs.http.paginate import paginate
19
24
 
20
25
 
21
26
  class GitLabMergeRequestsHTTPClientError(HTTPClientError):
@@ -86,18 +91,42 @@ class GitLabMergeRequestsHTTPClient(HTTPClient, GitLabMergeRequestsHTTPClientPro
86
91
  project_id: str,
87
92
  merge_request_id: str
88
93
  ) -> GitLabGetMRNotesResponseSchema:
89
- query = GitLabGetMRNotesQuerySchema(per_page=100)
90
- response = await self.get_notes_api(project_id, merge_request_id, query)
91
- return GitLabGetMRNotesResponseSchema.model_validate_json(response.text)
94
+ async def fetch_page(page: int) -> Response:
95
+ query = GitLabGetMRNotesQuerySchema(page=page, per_page=settings.vcs.pagination.per_page)
96
+ return await self.get_notes_api(project_id, merge_request_id, query)
97
+
98
+ def extract_items(response: Response) -> list[GitLabNoteSchema]:
99
+ result = GitLabGetMRNotesResponseSchema.model_validate_json(response.text)
100
+ return result.root
101
+
102
+ items = await paginate(
103
+ max_pages=settings.vcs.pagination.max_pages,
104
+ fetch_page=fetch_page,
105
+ extract_items=extract_items,
106
+ has_next_page=gitlab_has_next_page
107
+ )
108
+ return GitLabGetMRNotesResponseSchema(root=items)
92
109
 
93
110
  async def get_discussions(
94
111
  self,
95
112
  project_id: str,
96
113
  merge_request_id: str
97
114
  ) -> GitLabGetMRDiscussionsResponseSchema:
98
- query = GitLabGetMRDiscussionsQuerySchema(per_page=100)
99
- response = await self.get_discussions_api(project_id, merge_request_id, query)
100
- return GitLabGetMRDiscussionsResponseSchema.model_validate_json(response.text)
115
+ async def fetch_page(page: int) -> Response:
116
+ query = GitLabGetMRDiscussionsQuerySchema(page=page, per_page=settings.vcs.pagination.per_page)
117
+ return await self.get_discussions_api(project_id, merge_request_id, query)
118
+
119
+ def extract_items(response: Response) -> list[GitLabDiscussionSchema]:
120
+ result = GitLabGetMRDiscussionsResponseSchema.model_validate_json(response.text)
121
+ return result.root
122
+
123
+ items = await paginate(
124
+ max_pages=settings.vcs.pagination.max_pages,
125
+ fetch_page=fetch_page,
126
+ extract_items=extract_items,
127
+ has_next_page=gitlab_has_next_page
128
+ )
129
+ return GitLabGetMRDiscussionsResponseSchema(root=items)
101
130
 
102
131
  async def create_note(
103
132
  self,
@@ -18,7 +18,8 @@ class GitLabDiscussionPositionSchema(BaseModel):
18
18
 
19
19
 
20
20
  class GitLabGetMRDiscussionsQuerySchema(BaseModel):
21
- per_page: int
21
+ page: int = 1
22
+ per_page: int = 100
22
23
 
23
24
 
24
25
  class GitLabGetMRDiscussionsResponseSchema(RootModel[list[GitLabDiscussionSchema]]):
@@ -7,7 +7,8 @@ class GitLabNoteSchema(BaseModel):
7
7
 
8
8
 
9
9
  class GitLabGetMRNotesQuerySchema(BaseModel):
10
- per_page: int
10
+ page: int = 1
11
+ per_page: int = 100
11
12
 
12
13
 
13
14
  class GitLabGetMRNotesResponseSchema(RootModel[list[GitLabNoteSchema]]):
@@ -0,0 +1,5 @@
1
+ from httpx import Response
2
+
3
+
4
+ def gitlab_has_next_page(response: Response) -> bool:
5
+ return bool(response.headers.get("X-Next-Page"))
@@ -5,11 +5,13 @@ from pydantic import BaseModel, Field
5
5
  from ai_review.libs.config.vcs.bitbucket import BitbucketPipelineConfig, BitbucketHTTPClientConfig
6
6
  from ai_review.libs.config.vcs.github import GitHubPipelineConfig, GitHubHTTPClientConfig
7
7
  from ai_review.libs.config.vcs.gitlab import GitLabPipelineConfig, GitLabHTTPClientConfig
8
+ from ai_review.libs.config.vcs.pagination import VCSPaginationConfig
8
9
  from ai_review.libs.constants.vcs_provider import VCSProvider
9
10
 
10
11
 
11
12
  class VCSConfigBase(BaseModel):
12
13
  provider: VCSProvider
14
+ pagination: VCSPaginationConfig = VCSPaginationConfig()
13
15
 
14
16
 
15
17
  class GitLabVCSConfig(VCSConfigBase):
@@ -0,0 +1,6 @@
1
+ from pydantic import BaseModel, Field
2
+
3
+
4
+ class VCSPaginationConfig(BaseModel):
5
+ per_page: int = Field(default=100, ge=1, le=100)
6
+ max_pages: int = Field(default=5, ge=1)
@@ -0,0 +1,43 @@
1
+ from typing import Awaitable, Callable, TypeVar
2
+
3
+ from httpx import Response
4
+ from pydantic import BaseModel
5
+
6
+ from ai_review.libs.logger import get_logger
7
+
8
+ T = TypeVar("T", bound=BaseModel)
9
+
10
+ logger = get_logger("PAGINATE")
11
+
12
+
13
+ async def paginate(
14
+ fetch_page: Callable[[int], Awaitable[Response]],
15
+ extract_items: Callable[[Response], list[T]],
16
+ has_next_page: Callable[[Response], bool],
17
+ max_pages: int | None = None,
18
+ ) -> list[T]:
19
+ page = 1
20
+ items: list[T] = []
21
+
22
+ while True:
23
+ response = await fetch_page(page)
24
+
25
+ try:
26
+ extracted = extract_items(response)
27
+ except Exception as error:
28
+ logger.error(f"Failed to extract items on {page=}")
29
+ raise RuntimeError(f"Failed to extract items on {page=}") from error
30
+
31
+ logger.debug(f"Page {page}: extracted {len(extracted)} items (total={len(items) + len(extracted)})")
32
+ items.extend(extracted)
33
+
34
+ if not has_next_page(response):
35
+ logger.debug(f"Pagination finished after {page} page(s), total items={len(items)}")
36
+ break
37
+
38
+ page += 1
39
+ if max_pages and (page > max_pages):
40
+ logger.error(f"Pagination exceeded {max_pages=}")
41
+ raise RuntimeError(f"Pagination exceeded {max_pages=}")
42
+
43
+ return items
@@ -0,0 +1,14 @@
1
+ import pytest
2
+ from httpx import AsyncClient
3
+
4
+ from ai_review.clients.bitbucket.client import get_bitbucket_http_client, BitbucketHTTPClient
5
+ from ai_review.clients.bitbucket.pr.client import BitbucketPullRequestsHTTPClient
6
+
7
+
8
+ @pytest.mark.usefixtures("bitbucket_http_client_config")
9
+ def test_get_bitbucket_http_client_builds_ok():
10
+ bitbucket_http_client = get_bitbucket_http_client()
11
+
12
+ assert isinstance(bitbucket_http_client, BitbucketHTTPClient)
13
+ assert isinstance(bitbucket_http_client.pr, BitbucketPullRequestsHTTPClient)
14
+ assert isinstance(bitbucket_http_client.pr.client, AsyncClient)
@@ -0,0 +1,31 @@
1
+ from httpx import Response, Request
2
+
3
+ from ai_review.clients.bitbucket.tools import bitbucket_has_next_page
4
+
5
+
6
+ def make_response(data: dict) -> Response:
7
+ return Response(
8
+ json=data,
9
+ request=Request("GET", "http://bitbucket.test"),
10
+ status_code=200,
11
+ )
12
+
13
+
14
+ def test_bitbucket_has_next_page_true():
15
+ resp = make_response({"next": "https://api.bitbucket.org/2.0/repositories/test/repo?page=2"})
16
+ assert bitbucket_has_next_page(resp) is True
17
+
18
+
19
+ def test_bitbucket_has_next_page_false_none():
20
+ resp = make_response({"next": None})
21
+ assert bitbucket_has_next_page(resp) is False
22
+
23
+
24
+ def test_bitbucket_has_next_page_false_missing():
25
+ resp = make_response({})
26
+ assert bitbucket_has_next_page(resp) is False
27
+
28
+
29
+ def test_bitbucket_has_next_page_false_empty_string():
30
+ resp = make_response({"next": ""})
31
+ assert bitbucket_has_next_page(resp) is False
@@ -0,0 +1,31 @@
1
+ from httpx import Response, Request
2
+
3
+ from ai_review.clients.github.tools import github_has_next_page
4
+
5
+
6
+ def make_response(headers: dict) -> Response:
7
+ return Response(
8
+ request=Request("GET", "http://test"),
9
+ headers=headers,
10
+ status_code=200,
11
+ )
12
+
13
+
14
+ def test_github_has_next_page_true():
15
+ response = make_response({
16
+ "Link": '<https://api.github.com/resource?page=2>; rel="next", '
17
+ '<https://api.github.com/resource?page=5>; rel="last"'
18
+ })
19
+ assert github_has_next_page(response) is True
20
+
21
+
22
+ def test_github_has_next_page_false_no_next():
23
+ response = make_response({
24
+ "Link": '<https://api.github.com/resource?page=5>; rel="last"'
25
+ })
26
+ assert github_has_next_page(response) is False
27
+
28
+
29
+ def test_github_has_next_page_false_no_header():
30
+ resp = make_response({})
31
+ assert github_has_next_page(resp) is False
@@ -0,0 +1,26 @@
1
+ from httpx import Response, Request
2
+
3
+ from ai_review.clients.gitlab.tools import gitlab_has_next_page
4
+
5
+
6
+ def make_response(headers: dict) -> Response:
7
+ return Response(
8
+ request=Request("GET", "http://gitlab.test"),
9
+ headers=headers,
10
+ status_code=200,
11
+ )
12
+
13
+
14
+ def test_gitlab_has_next_page_true():
15
+ resp = make_response({"X-Next-Page": "2"})
16
+ assert gitlab_has_next_page(resp) is True
17
+
18
+
19
+ def test_gitlab_has_next_page_false_empty():
20
+ resp = make_response({"X-Next-Page": ""})
21
+ assert gitlab_has_next_page(resp) is False
22
+
23
+
24
+ def test_gitlab_has_next_page_false_missing():
25
+ resp = make_response({})
26
+ assert gitlab_has_next_page(resp) is False