xai-review 0.37.0__py3-none-any.whl → 0.38.0__py3-none-any.whl

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 (61) hide show
  1. ai_review/clients/{bitbucket → bitbucket_cloud}/client.py +6 -6
  2. ai_review/clients/{bitbucket → bitbucket_cloud}/pr/client.py +51 -39
  3. ai_review/clients/bitbucket_cloud/pr/schema/comments.py +59 -0
  4. ai_review/clients/{bitbucket → bitbucket_cloud}/pr/schema/files.py +7 -7
  5. ai_review/clients/bitbucket_cloud/pr/schema/pull_request.py +34 -0
  6. ai_review/clients/{bitbucket → bitbucket_cloud}/pr/schema/user.py +1 -1
  7. ai_review/clients/bitbucket_cloud/pr/types.py +44 -0
  8. ai_review/clients/{bitbucket → bitbucket_cloud}/tools.py +1 -1
  9. ai_review/clients/bitbucket_server/client.py +32 -0
  10. ai_review/clients/bitbucket_server/pr/client.py +163 -0
  11. ai_review/clients/bitbucket_server/pr/schema/changes.py +36 -0
  12. ai_review/clients/bitbucket_server/pr/schema/comments.py +55 -0
  13. ai_review/clients/bitbucket_server/pr/schema/pull_request.py +48 -0
  14. ai_review/clients/bitbucket_server/pr/schema/user.py +13 -0
  15. ai_review/clients/bitbucket_server/pr/types.py +44 -0
  16. ai_review/clients/bitbucket_server/tools.py +6 -0
  17. ai_review/libs/config/vcs/base.py +23 -6
  18. ai_review/libs/config/vcs/{bitbucket.py → bitbucket_cloud.py} +2 -2
  19. ai_review/libs/config/vcs/bitbucket_server.py +13 -0
  20. ai_review/libs/constants/vcs_provider.py +2 -1
  21. ai_review/libs/http/client.py +1 -1
  22. ai_review/services/vcs/bitbucket_cloud/__init__.py +0 -0
  23. ai_review/services/vcs/{bitbucket → bitbucket_cloud}/adapter.py +2 -2
  24. ai_review/services/vcs/{bitbucket → bitbucket_cloud}/client.py +24 -21
  25. ai_review/services/vcs/bitbucket_server/__init__.py +0 -0
  26. ai_review/services/vcs/bitbucket_server/adapter.py +27 -0
  27. ai_review/services/vcs/bitbucket_server/client.py +263 -0
  28. ai_review/services/vcs/factory.py +6 -3
  29. ai_review/tests/fixtures/clients/bitbucket_cloud.py +207 -0
  30. ai_review/tests/fixtures/clients/bitbucket_server.py +265 -0
  31. ai_review/tests/suites/clients/bitbucket_cloud/__init__.py +0 -0
  32. ai_review/tests/suites/clients/bitbucket_cloud/test_client.py +14 -0
  33. ai_review/tests/suites/clients/bitbucket_cloud/test_tools.py +31 -0
  34. ai_review/tests/suites/clients/bitbucket_server/__init__.py +0 -0
  35. ai_review/tests/suites/clients/bitbucket_server/test_client.py +14 -0
  36. ai_review/tests/suites/clients/bitbucket_server/test_tools.py +38 -0
  37. ai_review/tests/suites/services/vcs/bitbucket_cloud/__init__.py +0 -0
  38. ai_review/tests/suites/services/vcs/{bitbucket → bitbucket_cloud}/test_adapter.py +24 -24
  39. ai_review/tests/suites/services/vcs/{bitbucket → bitbucket_cloud}/test_client.py +51 -51
  40. ai_review/tests/suites/services/vcs/bitbucket_server/__init__.py +0 -0
  41. ai_review/tests/suites/services/vcs/bitbucket_server/test_adapter.py +115 -0
  42. ai_review/tests/suites/services/vcs/bitbucket_server/test_client.py +201 -0
  43. ai_review/tests/suites/services/vcs/test_factory.py +11 -4
  44. {xai_review-0.37.0.dist-info → xai_review-0.38.0.dist-info}/METADATA +9 -7
  45. {xai_review-0.37.0.dist-info → xai_review-0.38.0.dist-info}/RECORD +55 -33
  46. ai_review/clients/bitbucket/pr/schema/comments.py +0 -63
  47. ai_review/clients/bitbucket/pr/schema/pull_request.py +0 -34
  48. ai_review/clients/bitbucket/pr/types.py +0 -44
  49. ai_review/tests/fixtures/clients/bitbucket.py +0 -204
  50. ai_review/tests/suites/clients/bitbucket/test_client.py +0 -14
  51. ai_review/tests/suites/clients/bitbucket/test_tools.py +0 -31
  52. /ai_review/clients/{bitbucket → bitbucket_cloud}/__init__.py +0 -0
  53. /ai_review/clients/{bitbucket → bitbucket_cloud}/pr/__init__.py +0 -0
  54. /ai_review/clients/{bitbucket → bitbucket_cloud}/pr/schema/__init__.py +0 -0
  55. /ai_review/{services/vcs/bitbucket → clients/bitbucket_server}/__init__.py +0 -0
  56. /ai_review/{tests/suites/clients/bitbucket → clients/bitbucket_server/pr}/__init__.py +0 -0
  57. /ai_review/{tests/suites/services/vcs/bitbucket → clients/bitbucket_server/pr/schema}/__init__.py +0 -0
  58. {xai_review-0.37.0.dist-info → xai_review-0.38.0.dist-info}/WHEEL +0 -0
  59. {xai_review-0.37.0.dist-info → xai_review-0.38.0.dist-info}/entry_points.txt +0 -0
  60. {xai_review-0.37.0.dist-info → xai_review-0.38.0.dist-info}/licenses/LICENSE +0 -0
  61. {xai_review-0.37.0.dist-info → xai_review-0.38.0.dist-info}/top_level.txt +0 -0
@@ -1,19 +1,19 @@
1
- from ai_review.clients.bitbucket.pr.client import BitbucketPullRequestsHTTPClient
2
1
  from httpx import AsyncClient, AsyncHTTPTransport
3
2
 
3
+ from ai_review.clients.bitbucket_cloud.pr.client import BitbucketCloudPullRequestsHTTPClient
4
4
  from ai_review.config import settings
5
5
  from ai_review.libs.http.event_hooks.logger import LoggerEventHook
6
6
  from ai_review.libs.http.transports.retry import RetryTransport
7
7
  from ai_review.libs.logger import get_logger
8
8
 
9
9
 
10
- class BitbucketHTTPClient:
10
+ class BitbucketCloudHTTPClient:
11
11
  def __init__(self, client: AsyncClient):
12
- self.pr = BitbucketPullRequestsHTTPClient(client)
12
+ self.pr = BitbucketCloudPullRequestsHTTPClient(client)
13
13
 
14
14
 
15
- def get_bitbucket_http_client() -> BitbucketHTTPClient:
16
- logger = get_logger("BITBUCKET_HTTP_CLIENT")
15
+ def get_bitbucket_cloud_http_client() -> BitbucketCloudHTTPClient:
16
+ logger = get_logger("BITBUCKET_CLOUD_HTTP_CLIENT")
17
17
  logger_event_hook = LoggerEventHook(logger=logger)
18
18
  retry_transport = RetryTransport(logger=logger, transport=AsyncHTTPTransport())
19
19
 
@@ -29,4 +29,4 @@ def get_bitbucket_http_client() -> BitbucketHTTPClient:
29
29
  }
30
30
  )
31
31
 
32
- return BitbucketHTTPClient(client=client)
32
+ return BitbucketCloudHTTPClient(client=client)
@@ -1,68 +1,80 @@
1
1
  from httpx import Response, QueryParams
2
2
 
3
- from ai_review.clients.bitbucket.pr.schema.comments import (
4
- BitbucketPRCommentSchema,
5
- BitbucketGetPRCommentsQuerySchema,
6
- BitbucketGetPRCommentsResponseSchema,
7
- BitbucketCreatePRCommentRequestSchema,
8
- BitbucketCreatePRCommentResponseSchema,
3
+ from ai_review.clients.bitbucket_cloud.pr.schema.comments import (
4
+ BitbucketCloudPRCommentSchema,
5
+ BitbucketCloudGetPRCommentsQuerySchema,
6
+ BitbucketCloudGetPRCommentsResponseSchema,
7
+ BitbucketCloudCreatePRCommentRequestSchema,
8
+ BitbucketCloudCreatePRCommentResponseSchema,
9
9
  )
10
- from ai_review.clients.bitbucket.pr.schema.files import (
11
- BitbucketPRFileSchema,
12
- BitbucketGetPRFilesQuerySchema,
13
- BitbucketGetPRFilesResponseSchema,
10
+ from ai_review.clients.bitbucket_cloud.pr.schema.files import (
11
+ BitbucketCloudPRFileSchema,
12
+ BitbucketCloudGetPRFilesQuerySchema,
13
+ BitbucketCloudGetPRFilesResponseSchema,
14
14
  )
15
- from ai_review.clients.bitbucket.pr.schema.pull_request import BitbucketGetPRResponseSchema
16
- from ai_review.clients.bitbucket.pr.types import BitbucketPullRequestsHTTPClientProtocol
17
- from ai_review.clients.bitbucket.tools import bitbucket_has_next_page
15
+ from ai_review.clients.bitbucket_cloud.pr.schema.pull_request import BitbucketCloudGetPRResponseSchema
16
+ from ai_review.clients.bitbucket_cloud.pr.types import BitbucketCloudPullRequestsHTTPClientProtocol
17
+ from ai_review.clients.bitbucket_cloud.tools import bitbucket_cloud_has_next_page
18
18
  from ai_review.config import settings
19
19
  from ai_review.libs.http.client import HTTPClient
20
20
  from ai_review.libs.http.handlers import handle_http_error, HTTPClientError
21
21
  from ai_review.libs.http.paginate import paginate
22
22
 
23
23
 
24
- class BitbucketPullRequestsHTTPClientError(HTTPClientError):
24
+ class BitbucketCloudPullRequestsHTTPClientError(HTTPClientError):
25
25
  pass
26
26
 
27
27
 
28
- class BitbucketPullRequestsHTTPClient(HTTPClient, BitbucketPullRequestsHTTPClientProtocol):
29
- @handle_http_error(client="BitbucketPullRequestsHTTPClient", exception=BitbucketPullRequestsHTTPClientError)
28
+ class BitbucketCloudPullRequestsHTTPClient(HTTPClient, BitbucketCloudPullRequestsHTTPClientProtocol):
29
+ @handle_http_error(
30
+ client="BitbucketCloudPullRequestsHTTPClient",
31
+ exception=BitbucketCloudPullRequestsHTTPClientError
32
+ )
30
33
  async def get_pull_request_api(self, workspace: str, repo_slug: str, pull_request_id: str) -> Response:
31
34
  return await self.get(f"/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}")
32
35
 
33
- @handle_http_error(client="BitbucketPullRequestsHTTPClient", exception=BitbucketPullRequestsHTTPClientError)
36
+ @handle_http_error(
37
+ client="BitbucketCloudPullRequestsHTTPClient",
38
+ exception=BitbucketCloudPullRequestsHTTPClientError
39
+ )
34
40
  async def get_diffstat_api(
35
41
  self,
36
42
  workspace: str,
37
43
  repo_slug: str,
38
44
  pull_request_id: str,
39
- query: BitbucketGetPRFilesQuerySchema,
45
+ query: BitbucketCloudGetPRFilesQuerySchema,
40
46
  ) -> Response:
41
47
  return await self.get(
42
48
  f"/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/diffstat",
43
49
  query=QueryParams(**query.model_dump(by_alias=True)),
44
50
  )
45
51
 
46
- @handle_http_error(client="BitbucketPullRequestsHTTPClient", exception=BitbucketPullRequestsHTTPClientError)
52
+ @handle_http_error(
53
+ client="BitbucketCloudPullRequestsHTTPClient",
54
+ exception=BitbucketCloudPullRequestsHTTPClientError
55
+ )
47
56
  async def get_comments_api(
48
57
  self,
49
58
  workspace: str,
50
59
  repo_slug: str,
51
60
  pull_request_id: str,
52
- query: BitbucketGetPRCommentsQuerySchema,
61
+ query: BitbucketCloudGetPRCommentsQuerySchema,
53
62
  ) -> Response:
54
63
  return await self.get(
55
64
  f"/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/comments",
56
65
  query=QueryParams(**query.model_dump(by_alias=True)),
57
66
  )
58
67
 
59
- @handle_http_error(client="BitbucketPullRequestsHTTPClient", exception=BitbucketPullRequestsHTTPClientError)
68
+ @handle_http_error(
69
+ client="BitbucketCloudPullRequestsHTTPClient",
70
+ exception=BitbucketCloudPullRequestsHTTPClientError
71
+ )
60
72
  async def create_comment_api(
61
73
  self,
62
74
  workspace: str,
63
75
  repo_slug: str,
64
76
  pull_request_id: str,
65
- request: BitbucketCreatePRCommentRequestSchema,
77
+ request: BitbucketCloudCreatePRCommentRequestSchema,
66
78
  ) -> Response:
67
79
  return await self.post(
68
80
  f"/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/comments",
@@ -74,31 +86,31 @@ class BitbucketPullRequestsHTTPClient(HTTPClient, BitbucketPullRequestsHTTPClien
74
86
  workspace: str,
75
87
  repo_slug: str,
76
88
  pull_request_id: str
77
- ) -> BitbucketGetPRResponseSchema:
89
+ ) -> BitbucketCloudGetPRResponseSchema:
78
90
  resp = await self.get_pull_request_api(workspace, repo_slug, pull_request_id)
79
- return BitbucketGetPRResponseSchema.model_validate_json(resp.text)
91
+ return BitbucketCloudGetPRResponseSchema.model_validate_json(resp.text)
80
92
 
81
93
  async def get_files(
82
94
  self,
83
95
  workspace: str,
84
96
  repo_slug: str,
85
97
  pull_request_id: str
86
- ) -> BitbucketGetPRFilesResponseSchema:
98
+ ) -> BitbucketCloudGetPRFilesResponseSchema:
87
99
  async def fetch_page(page: int) -> Response:
88
- query = BitbucketGetPRFilesQuerySchema(page=page, page_len=settings.vcs.pagination.per_page)
100
+ query = BitbucketCloudGetPRFilesQuerySchema(page=page, page_len=settings.vcs.pagination.per_page)
89
101
  return await self.get_diffstat_api(workspace, repo_slug, pull_request_id, query)
90
102
 
91
- def extract_items(response: Response) -> list[BitbucketPRFileSchema]:
92
- result = BitbucketGetPRFilesResponseSchema.model_validate_json(response.text)
103
+ def extract_items(response: Response) -> list[BitbucketCloudPRFileSchema]:
104
+ result = BitbucketCloudGetPRFilesResponseSchema.model_validate_json(response.text)
93
105
  return result.values
94
106
 
95
107
  items = await paginate(
96
108
  max_pages=settings.vcs.pagination.max_pages,
97
109
  fetch_page=fetch_page,
98
110
  extract_items=extract_items,
99
- has_next_page=bitbucket_has_next_page
111
+ has_next_page=bitbucket_cloud_has_next_page
100
112
  )
101
- return BitbucketGetPRFilesResponseSchema(
113
+ return BitbucketCloudGetPRFilesResponseSchema(
102
114
  size=len(items),
103
115
  values=items,
104
116
  page_len=settings.vcs.pagination.per_page
@@ -109,22 +121,22 @@ class BitbucketPullRequestsHTTPClient(HTTPClient, BitbucketPullRequestsHTTPClien
109
121
  workspace: str,
110
122
  repo_slug: str,
111
123
  pull_request_id: str
112
- ) -> BitbucketGetPRCommentsResponseSchema:
124
+ ) -> BitbucketCloudGetPRCommentsResponseSchema:
113
125
  async def fetch_page(page: int) -> Response:
114
- query = BitbucketGetPRCommentsQuerySchema(page=page, page_len=settings.vcs.pagination.per_page)
126
+ query = BitbucketCloudGetPRCommentsQuerySchema(page=page, page_len=settings.vcs.pagination.per_page)
115
127
  return await self.get_comments_api(workspace, repo_slug, pull_request_id, query)
116
128
 
117
- def extract_items(response: Response) -> list[BitbucketPRCommentSchema]:
118
- result = BitbucketGetPRCommentsResponseSchema.model_validate_json(response.text)
129
+ def extract_items(response: Response) -> list[BitbucketCloudPRCommentSchema]:
130
+ result = BitbucketCloudGetPRCommentsResponseSchema.model_validate_json(response.text)
119
131
  return result.values
120
132
 
121
133
  items = await paginate(
122
134
  max_pages=settings.vcs.pagination.max_pages,
123
135
  fetch_page=fetch_page,
124
136
  extract_items=extract_items,
125
- has_next_page=bitbucket_has_next_page
137
+ has_next_page=bitbucket_cloud_has_next_page
126
138
  )
127
- return BitbucketGetPRCommentsResponseSchema(
139
+ return BitbucketCloudGetPRCommentsResponseSchema(
128
140
  size=len(items),
129
141
  values=items,
130
142
  page_len=settings.vcs.pagination.per_page
@@ -135,7 +147,7 @@ class BitbucketPullRequestsHTTPClient(HTTPClient, BitbucketPullRequestsHTTPClien
135
147
  workspace: str,
136
148
  repo_slug: str,
137
149
  pull_request_id: str,
138
- request: BitbucketCreatePRCommentRequestSchema
139
- ) -> BitbucketCreatePRCommentResponseSchema:
150
+ request: BitbucketCloudCreatePRCommentRequestSchema
151
+ ) -> BitbucketCloudCreatePRCommentResponseSchema:
140
152
  response = await self.create_comment_api(workspace, repo_slug, pull_request_id, request)
141
- return BitbucketCreatePRCommentResponseSchema.model_validate_json(response.text)
153
+ return BitbucketCloudCreatePRCommentResponseSchema.model_validate_json(response.text)
@@ -0,0 +1,59 @@
1
+ from pydantic import BaseModel, Field, ConfigDict
2
+
3
+ from ai_review.clients.bitbucket_cloud.pr.schema.user import BitbucketCloudUserSchema
4
+
5
+
6
+ class BitbucketCloudCommentContentSchema(BaseModel):
7
+ raw: str
8
+ html: str | None = None
9
+ markup: str | None = None
10
+
11
+
12
+ class BitbucketCloudCommentInlineSchema(BaseModel):
13
+ model_config = ConfigDict(populate_by_name=True)
14
+
15
+ path: str
16
+ to_line: int | None = Field(alias="to", default=None)
17
+ from_line: int | None = Field(alias="from", default=None)
18
+
19
+
20
+ class BitbucketCloudCommentParentSchema(BaseModel):
21
+ id: int
22
+
23
+
24
+ class BitbucketCloudPRCommentSchema(BaseModel):
25
+ id: int
26
+ user: BitbucketCloudUserSchema | None = None
27
+ parent: BitbucketCloudCommentParentSchema | None = None
28
+ inline: BitbucketCloudCommentInlineSchema | None = None
29
+ content: BitbucketCloudCommentContentSchema
30
+
31
+
32
+ class BitbucketCloudGetPRCommentsQuerySchema(BaseModel):
33
+ model_config = ConfigDict(populate_by_name=True)
34
+
35
+ page: int = 1
36
+ page_len: int = Field(alias="pagelen", default=100)
37
+
38
+
39
+ class BitbucketCloudGetPRCommentsResponseSchema(BaseModel):
40
+ model_config = ConfigDict(populate_by_name=True)
41
+
42
+ size: int
43
+ page: int | None = None
44
+ next: str | None = None
45
+ values: list[BitbucketCloudPRCommentSchema]
46
+ page_len: int = Field(alias="pagelen")
47
+
48
+
49
+ class BitbucketCloudCreatePRCommentRequestSchema(BaseModel):
50
+ parent: BitbucketCloudCommentParentSchema | None = None
51
+ inline: BitbucketCloudCommentInlineSchema | None = None
52
+ content: BitbucketCloudCommentContentSchema
53
+
54
+
55
+ class BitbucketCloudCreatePRCommentResponseSchema(BaseModel):
56
+ id: int
57
+ parent: BitbucketCloudCommentParentSchema | None = None
58
+ inline: BitbucketCloudCommentInlineSchema | None = None
59
+ content: BitbucketCloudCommentContentSchema
@@ -1,30 +1,30 @@
1
1
  from pydantic import BaseModel, Field, ConfigDict
2
2
 
3
3
 
4
- class BitbucketPRFilePathSchema(BaseModel):
4
+ class BitbucketCloudPRFilePathSchema(BaseModel):
5
5
  path: str
6
6
 
7
7
 
8
- class BitbucketPRFileSchema(BaseModel):
9
- new: BitbucketPRFilePathSchema | None = None
10
- old: BitbucketPRFilePathSchema | None = None
8
+ class BitbucketCloudPRFileSchema(BaseModel):
9
+ new: BitbucketCloudPRFilePathSchema | None = None
10
+ old: BitbucketCloudPRFilePathSchema | None = None
11
11
  status: str
12
12
  lines_added: int
13
13
  lines_removed: int
14
14
 
15
15
 
16
- class BitbucketGetPRFilesQuerySchema(BaseModel):
16
+ class BitbucketCloudGetPRFilesQuerySchema(BaseModel):
17
17
  model_config = ConfigDict(populate_by_name=True)
18
18
 
19
19
  page: int = 1
20
20
  page_len: int = Field(alias="pagelen", default=100)
21
21
 
22
22
 
23
- class BitbucketGetPRFilesResponseSchema(BaseModel):
23
+ class BitbucketCloudGetPRFilesResponseSchema(BaseModel):
24
24
  model_config = ConfigDict(populate_by_name=True)
25
25
 
26
26
  size: int
27
27
  page: int | None = None
28
28
  next: str | None = None
29
- values: list[BitbucketPRFileSchema]
29
+ values: list[BitbucketCloudPRFileSchema]
30
30
  page_len: int = Field(alias="pagelen")
@@ -0,0 +1,34 @@
1
+ from pydantic import BaseModel, Field
2
+
3
+ from ai_review.clients.bitbucket_cloud.pr.schema.user import BitbucketCloudUserSchema
4
+
5
+
6
+ class BitbucketCloudBranchSchema(BaseModel):
7
+ name: str
8
+
9
+
10
+ class BitbucketCloudCommitSchema(BaseModel):
11
+ hash: str
12
+
13
+
14
+ class BitbucketCloudRepositorySchema(BaseModel):
15
+ uuid: str
16
+ full_name: str
17
+
18
+
19
+ class BitbucketCloudPRLocationSchema(BaseModel):
20
+ branch: BitbucketCloudBranchSchema
21
+ commit: BitbucketCloudCommitSchema
22
+ repository: BitbucketCloudRepositorySchema
23
+
24
+
25
+ class BitbucketCloudGetPRResponseSchema(BaseModel):
26
+ id: int
27
+ title: str
28
+ description: str | None = None
29
+ state: str
30
+ author: BitbucketCloudUserSchema
31
+ source: BitbucketCloudPRLocationSchema
32
+ destination: BitbucketCloudPRLocationSchema
33
+ reviewers: list[BitbucketCloudUserSchema] = Field(default_factory=list)
34
+ participants: list[BitbucketCloudUserSchema] = Field(default_factory=list)
@@ -1,7 +1,7 @@
1
1
  from pydantic import BaseModel
2
2
 
3
3
 
4
- class BitbucketUserSchema(BaseModel):
4
+ class BitbucketCloudUserSchema(BaseModel):
5
5
  uuid: str
6
6
  nickname: str
7
7
  display_name: str
@@ -0,0 +1,44 @@
1
+ from typing import Protocol
2
+
3
+ from ai_review.clients.bitbucket_cloud.pr.schema.comments import (
4
+ BitbucketCloudGetPRCommentsResponseSchema,
5
+ BitbucketCloudCreatePRCommentRequestSchema,
6
+ BitbucketCloudCreatePRCommentResponseSchema,
7
+ )
8
+ from ai_review.clients.bitbucket_cloud.pr.schema.files import BitbucketCloudGetPRFilesResponseSchema
9
+ from ai_review.clients.bitbucket_cloud.pr.schema.pull_request import BitbucketCloudGetPRResponseSchema
10
+
11
+
12
+ class BitbucketCloudPullRequestsHTTPClientProtocol(Protocol):
13
+ async def get_pull_request(
14
+ self,
15
+ workspace: str,
16
+ repo_slug: str,
17
+ pull_request_id: str
18
+ ) -> BitbucketCloudGetPRResponseSchema:
19
+ ...
20
+
21
+ async def get_files(
22
+ self,
23
+ workspace: str,
24
+ repo_slug: str,
25
+ pull_request_id: str
26
+ ) -> BitbucketCloudGetPRFilesResponseSchema:
27
+ ...
28
+
29
+ async def get_comments(
30
+ self,
31
+ workspace: str,
32
+ repo_slug: str,
33
+ pull_request_id: str
34
+ ) -> BitbucketCloudGetPRCommentsResponseSchema:
35
+ ...
36
+
37
+ async def create_comment(
38
+ self,
39
+ workspace: str,
40
+ repo_slug: str,
41
+ pull_request_id: str,
42
+ request: BitbucketCloudCreatePRCommentRequestSchema,
43
+ ) -> BitbucketCloudCreatePRCommentResponseSchema:
44
+ ...
@@ -1,6 +1,6 @@
1
1
  from httpx import Response
2
2
 
3
3
 
4
- def bitbucket_has_next_page(response: Response) -> bool:
4
+ def bitbucket_cloud_has_next_page(response: Response) -> bool:
5
5
  data = response.json()
6
6
  return bool(data.get("next"))
@@ -0,0 +1,32 @@
1
+ from ai_review.clients.bitbucket_server.pr.client import BitbucketServerPullRequestsHTTPClient
2
+ from httpx import AsyncClient, AsyncHTTPTransport
3
+
4
+ from ai_review.config import settings
5
+ from ai_review.libs.http.event_hooks.logger import LoggerEventHook
6
+ from ai_review.libs.http.transports.retry import RetryTransport
7
+ from ai_review.libs.logger import get_logger
8
+
9
+
10
+ class BitbucketServerHTTPClient:
11
+ def __init__(self, client: AsyncClient):
12
+ self.pr = BitbucketServerPullRequestsHTTPClient(client)
13
+
14
+
15
+ def get_bitbucket_server_http_client() -> BitbucketServerHTTPClient:
16
+ logger = get_logger("BITBUCKET_SERVER_HTTP_CLIENT")
17
+ logger_event_hook = LoggerEventHook(logger=logger)
18
+ retry_transport = RetryTransport(logger=logger, transport=AsyncHTTPTransport())
19
+
20
+ client = AsyncClient(
21
+ verify=settings.vcs.http_client.verify,
22
+ timeout=settings.vcs.http_client.timeout,
23
+ headers={"Authorization": f"Basic {settings.vcs.http_client.api_token_value}"},
24
+ base_url=settings.vcs.http_client.api_url_value,
25
+ transport=retry_transport,
26
+ event_hooks={
27
+ "request": [logger_event_hook.request],
28
+ "response": [logger_event_hook.response],
29
+ },
30
+ )
31
+
32
+ return BitbucketServerHTTPClient(client=client)
@@ -0,0 +1,163 @@
1
+ from httpx import Response, QueryParams
2
+
3
+ from ai_review.clients.bitbucket_server.pr.schema.changes import (
4
+ BitbucketServerChangeSchema,
5
+ BitbucketServerGetPRChangesQuerySchema,
6
+ BitbucketServerGetPRChangesResponseSchema,
7
+ )
8
+ from ai_review.clients.bitbucket_server.pr.schema.comments import (
9
+ BitbucketServerCommentSchema,
10
+ BitbucketServerGetPRCommentsQuerySchema,
11
+ BitbucketServerGetPRCommentsResponseSchema,
12
+ BitbucketServerCreatePRCommentRequestSchema,
13
+ BitbucketServerCreatePRCommentResponseSchema
14
+ )
15
+ from ai_review.clients.bitbucket_server.pr.schema.pull_request import BitbucketServerGetPRResponseSchema
16
+ from ai_review.clients.bitbucket_server.pr.types import BitbucketServerPullRequestsHTTPClientProtocol
17
+ from ai_review.clients.bitbucket_server.tools import bitbucket_server_has_next_page
18
+ from ai_review.config import settings
19
+ from ai_review.libs.http.client import HTTPClient
20
+ from ai_review.libs.http.handlers import handle_http_error, HTTPClientError
21
+ from ai_review.libs.http.paginate import paginate
22
+
23
+
24
+ class BitbucketServerPullRequestsHTTPClientError(HTTPClientError):
25
+ pass
26
+
27
+
28
+ class BitbucketServerPullRequestsHTTPClient(HTTPClient, BitbucketServerPullRequestsHTTPClientProtocol):
29
+ @handle_http_error(
30
+ client="BitbucketServerPullRequestsHTTPClient",
31
+ exception=BitbucketServerPullRequestsHTTPClientError
32
+ )
33
+ async def get_pull_request_api(self, project_key: str, repo_slug: str, pull_request_id: int) -> Response:
34
+ return await self.get(f"/projects/{project_key}/repos/{repo_slug}/pull-requests/{pull_request_id}")
35
+
36
+ @handle_http_error(
37
+ client="BitbucketServerPullRequestsHTTPClient",
38
+ exception=BitbucketServerPullRequestsHTTPClientError
39
+ )
40
+ async def get_changes_api(
41
+ self,
42
+ project_key: str,
43
+ repo_slug: str,
44
+ pull_request_id: int,
45
+ query: BitbucketServerGetPRChangesQuerySchema,
46
+ ) -> Response:
47
+ return await self.get(
48
+ f"/projects/{project_key}/repos/{repo_slug}/pull-requests/{pull_request_id}/changes",
49
+ query=QueryParams(**query.model_dump(by_alias=True)),
50
+ )
51
+
52
+ @handle_http_error(
53
+ client="BitbucketServerPullRequestsHTTPClient",
54
+ exception=BitbucketServerPullRequestsHTTPClientError
55
+ )
56
+ async def get_comments_api(
57
+ self,
58
+ project_key: str,
59
+ repo_slug: str,
60
+ pull_request_id: int,
61
+ query: BitbucketServerGetPRCommentsQuerySchema,
62
+ ) -> Response:
63
+ return await self.get(
64
+ f"/projects/{project_key}/repos/{repo_slug}/pull-requests/{pull_request_id}/comments",
65
+ query=QueryParams(**query.model_dump(by_alias=True)),
66
+ )
67
+
68
+ @handle_http_error(
69
+ client="BitbucketServerPullRequestsHTTPClient",
70
+ exception=BitbucketServerPullRequestsHTTPClientError
71
+ )
72
+ async def create_comment_api(
73
+ self,
74
+ project_key: str,
75
+ repo_slug: str,
76
+ pull_request_id: int,
77
+ request: BitbucketServerCreatePRCommentRequestSchema,
78
+ ) -> Response:
79
+ return await self.post(
80
+ f"/projects/{project_key}/repos/{repo_slug}/pull-requests/{pull_request_id}/comments",
81
+ json=request.model_dump(by_alias=True),
82
+ )
83
+
84
+ async def get_pull_request(
85
+ self,
86
+ project_key: str,
87
+ repo_slug: str,
88
+ pull_request_id: int,
89
+ ) -> BitbucketServerGetPRResponseSchema:
90
+ resp = await self.get_pull_request_api(project_key, repo_slug, pull_request_id)
91
+ return BitbucketServerGetPRResponseSchema.model_validate_json(resp.text)
92
+
93
+ async def get_changes(
94
+ self,
95
+ project_key: str,
96
+ repo_slug: str,
97
+ pull_request_id: int,
98
+ ) -> BitbucketServerGetPRChangesResponseSchema:
99
+ async def fetch_page(page: int) -> Response:
100
+ start = (page - 1) * settings.vcs.pagination.per_page
101
+ query = BitbucketServerGetPRChangesQuerySchema(start=start, limit=settings.vcs.pagination.per_page)
102
+ return await self.get_changes_api(project_key, repo_slug, pull_request_id, query)
103
+
104
+ def extract_items(response: Response) -> list[BitbucketServerChangeSchema]:
105
+ result = BitbucketServerGetPRChangesResponseSchema.model_validate_json(response.text)
106
+ return result.values
107
+
108
+ items = await paginate(
109
+ max_pages=settings.vcs.pagination.max_pages,
110
+ fetch_page=fetch_page,
111
+ extract_items=extract_items,
112
+ has_next_page=bitbucket_server_has_next_page,
113
+ )
114
+
115
+ return BitbucketServerGetPRChangesResponseSchema(
116
+ size=len(items),
117
+ start=0,
118
+ limit=settings.vcs.pagination.per_page,
119
+ values=items,
120
+ is_last_page=True,
121
+ next_page_start=None,
122
+ )
123
+
124
+ async def get_comments(
125
+ self,
126
+ project_key: str,
127
+ repo_slug: str,
128
+ pull_request_id: int,
129
+ ) -> BitbucketServerGetPRCommentsResponseSchema:
130
+ async def fetch_page(page: int) -> Response:
131
+ start = (page - 1) * settings.vcs.pagination.per_page
132
+ query = BitbucketServerGetPRCommentsQuerySchema(start=start, limit=settings.vcs.pagination.per_page)
133
+ return await self.get_comments_api(project_key, repo_slug, pull_request_id, query)
134
+
135
+ def extract_items(response: Response) -> list[BitbucketServerCommentSchema]:
136
+ result = BitbucketServerGetPRCommentsResponseSchema.model_validate_json(response.text)
137
+ return result.values
138
+
139
+ items = await paginate(
140
+ max_pages=settings.vcs.pagination.max_pages,
141
+ fetch_page=fetch_page,
142
+ extract_items=extract_items,
143
+ has_next_page=bitbucket_server_has_next_page,
144
+ )
145
+
146
+ return BitbucketServerGetPRCommentsResponseSchema(
147
+ size=len(items),
148
+ start=0,
149
+ limit=settings.vcs.pagination.per_page,
150
+ values=items,
151
+ is_last_page=True,
152
+ next_page_start=None,
153
+ )
154
+
155
+ async def create_comment(
156
+ self,
157
+ project_key: str,
158
+ repo_slug: str,
159
+ pull_request_id: int,
160
+ request: BitbucketServerCreatePRCommentRequestSchema
161
+ ) -> BitbucketServerCreatePRCommentResponseSchema:
162
+ response = await self.create_comment_api(project_key, repo_slug, pull_request_id, request)
163
+ return BitbucketServerCreatePRCommentResponseSchema.model_validate_json(response.text)
@@ -0,0 +1,36 @@
1
+ from pydantic import BaseModel, Field, ConfigDict
2
+
3
+
4
+ class BitbucketServerChangePathSchema(BaseModel):
5
+ model_config = ConfigDict(populate_by_name=True)
6
+
7
+ to_string: str = Field(alias="toString")
8
+
9
+
10
+ class BitbucketServerChangeSchema(BaseModel):
11
+ model_config = ConfigDict(populate_by_name=True)
12
+
13
+ path: BitbucketServerChangePathSchema
14
+ type: str
15
+ src_path: BitbucketServerChangePathSchema | None = Field(default=None, alias="srcPath")
16
+ node_type: str = Field(alias="nodeType")
17
+ executable: bool | None = None
18
+ percent_unchanged: float | None = Field(default=None, alias="percentUnchanged")
19
+
20
+
21
+ class BitbucketServerGetPRChangesQuerySchema(BaseModel):
22
+ model_config = ConfigDict(populate_by_name=True)
23
+
24
+ start: int = 0
25
+ limit: int = 100
26
+
27
+
28
+ class BitbucketServerGetPRChangesResponseSchema(BaseModel):
29
+ model_config = ConfigDict(populate_by_name=True)
30
+
31
+ size: int
32
+ limit: int
33
+ start: int
34
+ values: list[BitbucketServerChangeSchema]
35
+ is_last_page: bool = Field(alias="isLastPage")
36
+ next_page_start: int | None = Field(default=None, alias="nextPageStart")