xai-review 0.36.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.
- ai_review/clients/{bitbucket → bitbucket_cloud}/client.py +8 -7
- ai_review/clients/{bitbucket → bitbucket_cloud}/pr/client.py +51 -39
- ai_review/clients/bitbucket_cloud/pr/schema/comments.py +59 -0
- ai_review/clients/{bitbucket → bitbucket_cloud}/pr/schema/files.py +7 -7
- ai_review/clients/bitbucket_cloud/pr/schema/pull_request.py +34 -0
- ai_review/clients/{bitbucket → bitbucket_cloud}/pr/schema/user.py +1 -1
- ai_review/clients/bitbucket_cloud/pr/types.py +44 -0
- ai_review/clients/{bitbucket → bitbucket_cloud}/tools.py +1 -1
- ai_review/clients/bitbucket_server/client.py +32 -0
- ai_review/clients/bitbucket_server/pr/client.py +163 -0
- ai_review/clients/bitbucket_server/pr/schema/changes.py +36 -0
- ai_review/clients/bitbucket_server/pr/schema/comments.py +55 -0
- ai_review/clients/bitbucket_server/pr/schema/pull_request.py +48 -0
- ai_review/clients/bitbucket_server/pr/schema/user.py +13 -0
- ai_review/clients/bitbucket_server/pr/types.py +44 -0
- ai_review/clients/bitbucket_server/tools.py +6 -0
- ai_review/clients/claude/client.py +1 -0
- ai_review/clients/gemini/client.py +1 -0
- ai_review/clients/gitea/client.py +4 -3
- ai_review/clients/github/client.py +2 -1
- ai_review/clients/gitlab/client.py +2 -1
- ai_review/clients/ollama/client.py +1 -0
- ai_review/clients/openai/v1/client.py +1 -0
- ai_review/clients/openai/v2/client.py +1 -0
- ai_review/clients/openrouter/client.py +1 -0
- ai_review/libs/config/http.py +2 -1
- ai_review/libs/config/vcs/base.py +23 -6
- ai_review/libs/config/vcs/{bitbucket.py → bitbucket_cloud.py} +2 -2
- ai_review/libs/config/vcs/bitbucket_server.py +13 -0
- ai_review/libs/constants/vcs_provider.py +2 -1
- ai_review/libs/http/client.py +1 -1
- ai_review/resources/pricing.yaml +4 -0
- ai_review/services/vcs/bitbucket_cloud/__init__.py +0 -0
- ai_review/services/vcs/{bitbucket → bitbucket_cloud}/adapter.py +2 -2
- ai_review/services/vcs/{bitbucket → bitbucket_cloud}/client.py +24 -21
- ai_review/services/vcs/bitbucket_server/__init__.py +0 -0
- ai_review/services/vcs/bitbucket_server/adapter.py +27 -0
- ai_review/services/vcs/bitbucket_server/client.py +263 -0
- ai_review/services/vcs/factory.py +6 -3
- ai_review/tests/fixtures/clients/bitbucket_cloud.py +207 -0
- ai_review/tests/fixtures/clients/bitbucket_server.py +265 -0
- ai_review/tests/suites/clients/bitbucket_cloud/__init__.py +0 -0
- ai_review/tests/suites/clients/bitbucket_cloud/test_client.py +14 -0
- ai_review/tests/suites/clients/bitbucket_cloud/test_tools.py +31 -0
- ai_review/tests/suites/clients/bitbucket_server/__init__.py +0 -0
- ai_review/tests/suites/clients/bitbucket_server/test_client.py +14 -0
- ai_review/tests/suites/clients/bitbucket_server/test_tools.py +38 -0
- ai_review/tests/suites/services/vcs/bitbucket_cloud/__init__.py +0 -0
- ai_review/tests/suites/services/vcs/{bitbucket → bitbucket_cloud}/test_adapter.py +24 -24
- ai_review/tests/suites/services/vcs/{bitbucket → bitbucket_cloud}/test_client.py +51 -51
- ai_review/tests/suites/services/vcs/bitbucket_server/__init__.py +0 -0
- ai_review/tests/suites/services/vcs/bitbucket_server/test_adapter.py +115 -0
- ai_review/tests/suites/services/vcs/bitbucket_server/test_client.py +201 -0
- ai_review/tests/suites/services/vcs/test_factory.py +11 -4
- xai_review-0.38.0.dist-info/METADATA +519 -0
- {xai_review-0.36.0.dist-info → xai_review-0.38.0.dist-info}/RECORD +66 -44
- ai_review/clients/bitbucket/pr/schema/comments.py +0 -63
- ai_review/clients/bitbucket/pr/schema/pull_request.py +0 -34
- ai_review/clients/bitbucket/pr/types.py +0 -44
- ai_review/tests/fixtures/clients/bitbucket.py +0 -204
- ai_review/tests/suites/clients/bitbucket/test_client.py +0 -14
- ai_review/tests/suites/clients/bitbucket/test_tools.py +0 -31
- xai_review-0.36.0.dist-info/METADATA +0 -317
- /ai_review/clients/{bitbucket → bitbucket_cloud}/__init__.py +0 -0
- /ai_review/clients/{bitbucket → bitbucket_cloud}/pr/__init__.py +0 -0
- /ai_review/clients/{bitbucket → bitbucket_cloud}/pr/schema/__init__.py +0 -0
- /ai_review/{services/vcs/bitbucket → clients/bitbucket_server}/__init__.py +0 -0
- /ai_review/{tests/suites/clients/bitbucket → clients/bitbucket_server/pr}/__init__.py +0 -0
- /ai_review/{tests/suites/services/vcs/bitbucket → clients/bitbucket_server/pr/schema}/__init__.py +0 -0
- {xai_review-0.36.0.dist-info → xai_review-0.38.0.dist-info}/WHEEL +0 -0
- {xai_review-0.36.0.dist-info → xai_review-0.38.0.dist-info}/entry_points.txt +0 -0
- {xai_review-0.36.0.dist-info → xai_review-0.38.0.dist-info}/licenses/LICENSE +0 -0
- {xai_review-0.36.0.dist-info → xai_review-0.38.0.dist-info}/top_level.txt +0 -0
|
@@ -1,24 +1,25 @@
|
|
|
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
|
|
10
|
+
class BitbucketCloudHTTPClient:
|
|
11
11
|
def __init__(self, client: AsyncClient):
|
|
12
|
-
self.pr =
|
|
12
|
+
self.pr = BitbucketCloudPullRequestsHTTPClient(client)
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
def
|
|
16
|
-
logger = get_logger("
|
|
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
|
|
|
20
20
|
client = AsyncClient(
|
|
21
|
-
|
|
21
|
+
verify=settings.vcs.http_client.verify,
|
|
22
|
+
timeout=settings.vcs.http_client.timeout,
|
|
22
23
|
headers={"Authorization": f"Bearer {settings.vcs.http_client.api_token_value}"},
|
|
23
24
|
base_url=settings.vcs.http_client.api_url_value,
|
|
24
25
|
transport=retry_transport,
|
|
@@ -28,4 +29,4 @@ def get_bitbucket_http_client() -> BitbucketHTTPClient:
|
|
|
28
29
|
}
|
|
29
30
|
)
|
|
30
31
|
|
|
31
|
-
return
|
|
32
|
+
return BitbucketCloudHTTPClient(client=client)
|
|
@@ -1,68 +1,80 @@
|
|
|
1
1
|
from httpx import Response, QueryParams
|
|
2
2
|
|
|
3
|
-
from ai_review.clients.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
from ai_review.clients.bitbucket_cloud.pr.schema.files import (
|
|
11
|
+
BitbucketCloudPRFileSchema,
|
|
12
|
+
BitbucketCloudGetPRFilesQuerySchema,
|
|
13
|
+
BitbucketCloudGetPRFilesResponseSchema,
|
|
14
14
|
)
|
|
15
|
-
from ai_review.clients.
|
|
16
|
-
from ai_review.clients.
|
|
17
|
-
from ai_review.clients.
|
|
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
|
|
24
|
+
class BitbucketCloudPullRequestsHTTPClientError(HTTPClientError):
|
|
25
25
|
pass
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
class
|
|
29
|
-
@handle_http_error(
|
|
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(
|
|
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:
|
|
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(
|
|
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:
|
|
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(
|
|
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:
|
|
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
|
-
) ->
|
|
89
|
+
) -> BitbucketCloudGetPRResponseSchema:
|
|
78
90
|
resp = await self.get_pull_request_api(workspace, repo_slug, pull_request_id)
|
|
79
|
-
return
|
|
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
|
-
) ->
|
|
98
|
+
) -> BitbucketCloudGetPRFilesResponseSchema:
|
|
87
99
|
async def fetch_page(page: int) -> Response:
|
|
88
|
-
query =
|
|
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[
|
|
92
|
-
result =
|
|
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=
|
|
111
|
+
has_next_page=bitbucket_cloud_has_next_page
|
|
100
112
|
)
|
|
101
|
-
return
|
|
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
|
-
) ->
|
|
124
|
+
) -> BitbucketCloudGetPRCommentsResponseSchema:
|
|
113
125
|
async def fetch_page(page: int) -> Response:
|
|
114
|
-
query =
|
|
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[
|
|
118
|
-
result =
|
|
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=
|
|
137
|
+
has_next_page=bitbucket_cloud_has_next_page
|
|
126
138
|
)
|
|
127
|
-
return
|
|
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:
|
|
139
|
-
) ->
|
|
150
|
+
request: BitbucketCloudCreatePRCommentRequestSchema
|
|
151
|
+
) -> BitbucketCloudCreatePRCommentResponseSchema:
|
|
140
152
|
response = await self.create_comment_api(workspace, repo_slug, pull_request_id, request)
|
|
141
|
-
return
|
|
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
|
|
4
|
+
class BitbucketCloudPRFilePathSchema(BaseModel):
|
|
5
5
|
path: str
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
class
|
|
9
|
-
new:
|
|
10
|
-
old:
|
|
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
|
|
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
|
|
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[
|
|
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)
|
|
@@ -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
|
+
...
|
|
@@ -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")
|