fluidattacks_gitlab_sdk 1.0.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.
- fluidattacks_gitlab_sdk/__init__.py +22 -0
- fluidattacks_gitlab_sdk/_decoders.py +170 -0
- fluidattacks_gitlab_sdk/_gql_client/__init__.py +8 -0
- fluidattacks_gitlab_sdk/_gql_client/_client.py +101 -0
- fluidattacks_gitlab_sdk/_gql_client/_error.py +24 -0
- fluidattacks_gitlab_sdk/_gql_client/_handlers.py +87 -0
- fluidattacks_gitlab_sdk/_handlers.py +40 -0
- fluidattacks_gitlab_sdk/_http_client/__init__.py +28 -0
- fluidattacks_gitlab_sdk/_http_client/_client_1.py +206 -0
- fluidattacks_gitlab_sdk/_http_client/_core.py +152 -0
- fluidattacks_gitlab_sdk/_logger.py +36 -0
- fluidattacks_gitlab_sdk/ids.py +135 -0
- fluidattacks_gitlab_sdk/issues/__init__.py +7 -0
- fluidattacks_gitlab_sdk/issues/_client/__init__.py +28 -0
- fluidattacks_gitlab_sdk/issues/_client/_decode.py +165 -0
- fluidattacks_gitlab_sdk/issues/_client/_get_issue.py +79 -0
- fluidattacks_gitlab_sdk/issues/_client/_most_recent.py +119 -0
- fluidattacks_gitlab_sdk/issues/_client/_updated_by.py +157 -0
- fluidattacks_gitlab_sdk/issues/core.py +99 -0
- fluidattacks_gitlab_sdk/members/__init__.py +25 -0
- fluidattacks_gitlab_sdk/members/_client.py +49 -0
- fluidattacks_gitlab_sdk/members/_decode.py +40 -0
- fluidattacks_gitlab_sdk/members/core.py +23 -0
- fluidattacks_gitlab_sdk/merge_requests/__init__.py +9 -0
- fluidattacks_gitlab_sdk/merge_requests/_client.py +265 -0
- fluidattacks_gitlab_sdk/merge_requests/_decode.py +234 -0
- fluidattacks_gitlab_sdk/merge_requests/core.py +137 -0
- fluidattacks_gitlab_sdk/milestones/__init__.py +4 -0
- fluidattacks_gitlab_sdk/milestones/_client.py +147 -0
- fluidattacks_gitlab_sdk/milestones/_decode.py +78 -0
- fluidattacks_gitlab_sdk/milestones/core.py +40 -0
- fluidattacks_gitlab_sdk/mr_approvals/__init__.py +4 -0
- fluidattacks_gitlab_sdk/mr_approvals/_client.py +69 -0
- fluidattacks_gitlab_sdk/mr_approvals/_decode.py +30 -0
- fluidattacks_gitlab_sdk/mr_approvals/core.py +25 -0
- fluidattacks_gitlab_sdk/py.typed +0 -0
- fluidattacks_gitlab_sdk/users/__init__.py +5 -0
- fluidattacks_gitlab_sdk/users/core.py +26 -0
- fluidattacks_gitlab_sdk/users/decode.py +25 -0
- fluidattacks_gitlab_sdk-1.0.0.dist-info/METADATA +13 -0
- fluidattacks_gitlab_sdk-1.0.0.dist-info/RECORD +42 -0
- fluidattacks_gitlab_sdk-1.0.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
from __future__ import (
|
|
2
|
+
annotations,
|
|
3
|
+
)
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
from dataclasses import (
|
|
7
|
+
dataclass,
|
|
8
|
+
)
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
|
|
11
|
+
from fa_purity import (
|
|
12
|
+
Cmd,
|
|
13
|
+
FrozenDict,
|
|
14
|
+
FrozenList,
|
|
15
|
+
Maybe,
|
|
16
|
+
NewFrozenList,
|
|
17
|
+
Result,
|
|
18
|
+
ResultE,
|
|
19
|
+
Stream,
|
|
20
|
+
cast_exception,
|
|
21
|
+
)
|
|
22
|
+
from fa_purity._core.utils import raise_exception
|
|
23
|
+
from fa_purity.date_time import DatetimeUTC
|
|
24
|
+
from fa_purity.json import Primitive, UnfoldedFactory
|
|
25
|
+
from fluidattacks_etl_utils.bug import Bug
|
|
26
|
+
from fluidattacks_etl_utils.decode import int_to_str
|
|
27
|
+
from fluidattacks_etl_utils.paginate import (
|
|
28
|
+
cursor_pagination,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
from fluidattacks_gitlab_sdk._decoders import assert_multiple, assert_single, decode_maybe_single
|
|
32
|
+
from fluidattacks_gitlab_sdk._http_client import (
|
|
33
|
+
ClientFactory,
|
|
34
|
+
Credentials,
|
|
35
|
+
HttpJsonClient,
|
|
36
|
+
RelativeEndpoint,
|
|
37
|
+
)
|
|
38
|
+
from fluidattacks_gitlab_sdk.ids import MrFullId, MrInternalId, ProjectId
|
|
39
|
+
|
|
40
|
+
from ._decode import decode_batch_mrs, decode_mr_and_id
|
|
41
|
+
from .core import MergeRequest, MrsClient, PerPage
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_mr(
|
|
45
|
+
client: HttpJsonClient,
|
|
46
|
+
project: ProjectId,
|
|
47
|
+
mr_id: MrInternalId,
|
|
48
|
+
) -> Cmd[ResultE[tuple[MrFullId, MergeRequest]]]:
|
|
49
|
+
endpoint = RelativeEndpoint.new(
|
|
50
|
+
"projects",
|
|
51
|
+
int_to_str(project.project_id.value),
|
|
52
|
+
"merge_requests",
|
|
53
|
+
int_to_str(mr_id.internal.value),
|
|
54
|
+
)
|
|
55
|
+
return client.get(
|
|
56
|
+
endpoint,
|
|
57
|
+
FrozenDict({}),
|
|
58
|
+
).map(
|
|
59
|
+
lambda r: r.alt(
|
|
60
|
+
lambda e: cast_exception(
|
|
61
|
+
Bug.new(
|
|
62
|
+
"_get_mr",
|
|
63
|
+
inspect.currentframe(),
|
|
64
|
+
e,
|
|
65
|
+
(),
|
|
66
|
+
),
|
|
67
|
+
),
|
|
68
|
+
)
|
|
69
|
+
.bind(assert_single)
|
|
70
|
+
.bind(decode_mr_and_id),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def most_recent_mr_until(
|
|
75
|
+
client: HttpJsonClient,
|
|
76
|
+
project: ProjectId,
|
|
77
|
+
date_created_before: DatetimeUTC,
|
|
78
|
+
) -> Cmd[ResultE[Maybe[tuple[MrFullId, MergeRequest]]]]:
|
|
79
|
+
endpoint = RelativeEndpoint.new(
|
|
80
|
+
"projects",
|
|
81
|
+
int_to_str(project.project_id.value),
|
|
82
|
+
"merge_requests",
|
|
83
|
+
)
|
|
84
|
+
params: dict[str, Primitive] = {
|
|
85
|
+
"created_before": date_created_before.date_time.isoformat(),
|
|
86
|
+
"order_by": "created_at",
|
|
87
|
+
"sort": "desc",
|
|
88
|
+
"per_page": 1,
|
|
89
|
+
}
|
|
90
|
+
empty: Maybe[tuple[MrFullId, MergeRequest]] = Maybe.empty()
|
|
91
|
+
return client.get(
|
|
92
|
+
endpoint,
|
|
93
|
+
UnfoldedFactory.from_dict(params),
|
|
94
|
+
).map(
|
|
95
|
+
lambda r: r.alt(
|
|
96
|
+
lambda e: cast_exception(
|
|
97
|
+
Bug.new(
|
|
98
|
+
"most_recent_mr",
|
|
99
|
+
inspect.currentframe(),
|
|
100
|
+
e,
|
|
101
|
+
(),
|
|
102
|
+
),
|
|
103
|
+
),
|
|
104
|
+
)
|
|
105
|
+
.bind(assert_multiple)
|
|
106
|
+
.map(NewFrozenList)
|
|
107
|
+
.map(decode_maybe_single)
|
|
108
|
+
.bind(
|
|
109
|
+
lambda m: m.to_coproduct().map(
|
|
110
|
+
lambda r: decode_mr_and_id(r).map(Maybe.some),
|
|
111
|
+
lambda _: Result.success(empty),
|
|
112
|
+
),
|
|
113
|
+
),
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def most_recent_mr(
|
|
118
|
+
client: HttpJsonClient,
|
|
119
|
+
project: ProjectId,
|
|
120
|
+
) -> Cmd[ResultE[Maybe[tuple[MrFullId, MergeRequest]]]]:
|
|
121
|
+
endpoint = RelativeEndpoint.new(
|
|
122
|
+
"projects",
|
|
123
|
+
int_to_str(project.project_id.value),
|
|
124
|
+
"merge_requests",
|
|
125
|
+
)
|
|
126
|
+
params: dict[str, Primitive] = {
|
|
127
|
+
"order_by": "created_at",
|
|
128
|
+
"sort": "desc",
|
|
129
|
+
"per_page": 1,
|
|
130
|
+
}
|
|
131
|
+
empty: Maybe[tuple[MrFullId, MergeRequest]] = Maybe.empty()
|
|
132
|
+
return client.get(
|
|
133
|
+
endpoint,
|
|
134
|
+
UnfoldedFactory.from_dict(params),
|
|
135
|
+
).map(
|
|
136
|
+
lambda r: r.alt(
|
|
137
|
+
lambda e: cast_exception(
|
|
138
|
+
Bug.new(
|
|
139
|
+
"most_recent_mr",
|
|
140
|
+
inspect.currentframe(),
|
|
141
|
+
e,
|
|
142
|
+
(),
|
|
143
|
+
),
|
|
144
|
+
),
|
|
145
|
+
)
|
|
146
|
+
.bind(assert_multiple)
|
|
147
|
+
.map(NewFrozenList)
|
|
148
|
+
.map(decode_maybe_single)
|
|
149
|
+
.bind(
|
|
150
|
+
lambda m: m.to_coproduct().map(
|
|
151
|
+
lambda r: decode_mr_and_id(r).map(Maybe.some),
|
|
152
|
+
lambda _: Result.success(empty),
|
|
153
|
+
),
|
|
154
|
+
),
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def validate_next_page(
|
|
159
|
+
page: int,
|
|
160
|
+
items: FrozenList[tuple[MrFullId, MergeRequest]],
|
|
161
|
+
) -> ResultE[tuple[FrozenList[tuple[MrFullId, MergeRequest]], Maybe[int]]]:
|
|
162
|
+
return Result.success((items, Maybe.some(page + 1) if len(items) > 0 else Maybe.empty()))
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def get_updated_mrs( # noqa: PLR0913
|
|
166
|
+
client: HttpJsonClient,
|
|
167
|
+
project: ProjectId,
|
|
168
|
+
date_update_after: datetime,
|
|
169
|
+
date_updated_before: datetime,
|
|
170
|
+
page: Maybe[int],
|
|
171
|
+
per_page: PerPage,
|
|
172
|
+
) -> Cmd[ResultE[tuple[FrozenList[tuple[MrFullId, MergeRequest]], Maybe[int]]]]:
|
|
173
|
+
endpoint = RelativeEndpoint.new(
|
|
174
|
+
"projects",
|
|
175
|
+
int_to_str(project.project_id.value),
|
|
176
|
+
"merge_requests",
|
|
177
|
+
)
|
|
178
|
+
current_page: int = page.value_or(1)
|
|
179
|
+
params: dict[str, Primitive] = {
|
|
180
|
+
"updated_after": date_update_after.isoformat(),
|
|
181
|
+
"updated_before": date_updated_before.isoformat(),
|
|
182
|
+
"labels": "Any",
|
|
183
|
+
"order_by": "updated_at",
|
|
184
|
+
"sort": "desc",
|
|
185
|
+
"page": int_to_str(current_page),
|
|
186
|
+
"per_page": per_page,
|
|
187
|
+
}
|
|
188
|
+
return client.get(
|
|
189
|
+
endpoint,
|
|
190
|
+
UnfoldedFactory.from_dict(params),
|
|
191
|
+
).map(
|
|
192
|
+
lambda r: r.alt(
|
|
193
|
+
lambda e: cast_exception(
|
|
194
|
+
Bug.new(
|
|
195
|
+
"mr_updates",
|
|
196
|
+
inspect.currentframe(),
|
|
197
|
+
e,
|
|
198
|
+
(),
|
|
199
|
+
),
|
|
200
|
+
),
|
|
201
|
+
)
|
|
202
|
+
.bind(assert_multiple)
|
|
203
|
+
.bind(decode_batch_mrs)
|
|
204
|
+
.bind(lambda items: validate_next_page(current_page, items)),
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def get_page( # noqa: PLR0913
|
|
209
|
+
client: HttpJsonClient,
|
|
210
|
+
project: ProjectId,
|
|
211
|
+
date_update_after: datetime,
|
|
212
|
+
date_updated_before: datetime,
|
|
213
|
+
page: Maybe[int],
|
|
214
|
+
per_page: PerPage,
|
|
215
|
+
) -> Cmd[tuple[FrozenList[tuple[MrFullId, MergeRequest]], Maybe[int]]]:
|
|
216
|
+
return get_updated_mrs(
|
|
217
|
+
client,
|
|
218
|
+
project,
|
|
219
|
+
date_update_after,
|
|
220
|
+
date_updated_before,
|
|
221
|
+
page,
|
|
222
|
+
per_page,
|
|
223
|
+
).map(lambda r: r.alt(raise_exception).to_union())
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def get_all_updated_mrs(
|
|
227
|
+
client: HttpJsonClient,
|
|
228
|
+
project: ProjectId,
|
|
229
|
+
date_update_after: datetime,
|
|
230
|
+
date_updated_before: datetime,
|
|
231
|
+
per_page: PerPage,
|
|
232
|
+
) -> Cmd[Stream[FrozenList[tuple[MrFullId, MergeRequest]]]]:
|
|
233
|
+
stream = cursor_pagination(
|
|
234
|
+
lambda maybe_page: get_page(
|
|
235
|
+
client,
|
|
236
|
+
project,
|
|
237
|
+
date_update_after,
|
|
238
|
+
date_updated_before,
|
|
239
|
+
maybe_page,
|
|
240
|
+
per_page,
|
|
241
|
+
),
|
|
242
|
+
)
|
|
243
|
+
return Cmd.wrap_value(stream)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _from_client(client: HttpJsonClient) -> MrsClient:
|
|
247
|
+
return MrsClient(
|
|
248
|
+
lambda p, i: get_mr(client, p, i),
|
|
249
|
+
lambda p: most_recent_mr(client, p),
|
|
250
|
+
lambda p, d: most_recent_mr_until(client, p, d),
|
|
251
|
+
lambda p, a, b, v: get_all_updated_mrs(
|
|
252
|
+
client,
|
|
253
|
+
p,
|
|
254
|
+
a,
|
|
255
|
+
b,
|
|
256
|
+
v,
|
|
257
|
+
),
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
@dataclass(frozen=True)
|
|
262
|
+
class MrClientFactory:
|
|
263
|
+
@staticmethod
|
|
264
|
+
def new(creds: Credentials) -> MrsClient:
|
|
265
|
+
return _from_client(ClientFactory.new(creds))
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
|
|
3
|
+
from fa_purity import Maybe, PureIterFactory, Result, ResultE, ResultTransform
|
|
4
|
+
from fa_purity._core.frozen import FrozenList
|
|
5
|
+
from fa_purity.json import JsonObj, JsonUnfolder, Unfolder
|
|
6
|
+
from fluidattacks_etl_utils import smash
|
|
7
|
+
from fluidattacks_etl_utils.bug import Bug
|
|
8
|
+
from fluidattacks_etl_utils.decode import DecodeUtils
|
|
9
|
+
from fluidattacks_etl_utils.natural import Natural
|
|
10
|
+
|
|
11
|
+
from fluidattacks_gitlab_sdk._decoders import (
|
|
12
|
+
decode_milestone_full_id,
|
|
13
|
+
decode_mr_full_id,
|
|
14
|
+
)
|
|
15
|
+
from fluidattacks_gitlab_sdk.ids import (
|
|
16
|
+
MrFullId,
|
|
17
|
+
ProjectId,
|
|
18
|
+
UserId,
|
|
19
|
+
)
|
|
20
|
+
from fluidattacks_gitlab_sdk.merge_requests.core import (
|
|
21
|
+
MergeRequest,
|
|
22
|
+
MergeRequestDates,
|
|
23
|
+
MergeRequestFullState,
|
|
24
|
+
MergeRequestOrigins,
|
|
25
|
+
MergeRequestPeople,
|
|
26
|
+
MergeRequestProperties,
|
|
27
|
+
MergeRequestSha,
|
|
28
|
+
MergeRequestState,
|
|
29
|
+
TaskCompletion,
|
|
30
|
+
)
|
|
31
|
+
from fluidattacks_gitlab_sdk.users.decode import decode_user_obj
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def decode_sha(raw: JsonObj) -> ResultE[MergeRequestSha]:
|
|
35
|
+
return smash.smash_result_3(
|
|
36
|
+
JsonUnfolder.require(raw, "sha", DecodeUtils.to_opt_str),
|
|
37
|
+
JsonUnfolder.optional(raw, "merge_commit_sha", DecodeUtils.to_opt_str).map(
|
|
38
|
+
lambda m: m.bind(lambda x: x),
|
|
39
|
+
),
|
|
40
|
+
JsonUnfolder.optional(raw, "squash_commit_sha", DecodeUtils.to_opt_str).map(
|
|
41
|
+
lambda m: m.bind(lambda x: x),
|
|
42
|
+
),
|
|
43
|
+
).map(lambda t: MergeRequestSha(*t))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def decode_dates(raw: JsonObj) -> ResultE[MergeRequestDates]:
|
|
47
|
+
return smash.smash_result_5(
|
|
48
|
+
JsonUnfolder.require(raw, "created_at", DecodeUtils.to_date_time),
|
|
49
|
+
JsonUnfolder.optional(raw, "prepared_at", DecodeUtils.to_opt_date_time).map(
|
|
50
|
+
lambda m: m.bind(lambda x: x),
|
|
51
|
+
),
|
|
52
|
+
JsonUnfolder.optional(raw, "updated_at", DecodeUtils.to_opt_date_time).map(
|
|
53
|
+
lambda m: m.bind(lambda x: x),
|
|
54
|
+
),
|
|
55
|
+
JsonUnfolder.optional(raw, "merged_at", DecodeUtils.to_opt_date_time).map(
|
|
56
|
+
lambda m: m.bind(lambda x: x),
|
|
57
|
+
),
|
|
58
|
+
JsonUnfolder.optional(raw, "closed_at", DecodeUtils.to_opt_date_time).map(
|
|
59
|
+
lambda m: m.bind(lambda x: x),
|
|
60
|
+
),
|
|
61
|
+
).map(lambda t: MergeRequestDates(*t))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def decode_user_id(raw: JsonObj) -> ResultE[UserId]:
|
|
65
|
+
return JsonUnfolder.require(raw, "id", DecodeUtils.to_int).bind(Natural.from_int).map(UserId)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def decode_people(raw: JsonObj) -> ResultE[MergeRequestPeople]:
|
|
69
|
+
return smash.smash_result_5(
|
|
70
|
+
JsonUnfolder.require(
|
|
71
|
+
raw,
|
|
72
|
+
"author",
|
|
73
|
+
lambda v: Unfolder.to_json(v).bind(decode_user_obj),
|
|
74
|
+
),
|
|
75
|
+
JsonUnfolder.optional(
|
|
76
|
+
raw,
|
|
77
|
+
"merge_user",
|
|
78
|
+
lambda v: Unfolder.to_optional(
|
|
79
|
+
v,
|
|
80
|
+
lambda v: Unfolder.to_json(v).bind(decode_user_obj),
|
|
81
|
+
),
|
|
82
|
+
).map(lambda m: m.bind(lambda x: Maybe.from_optional(x))),
|
|
83
|
+
JsonUnfolder.optional(
|
|
84
|
+
raw,
|
|
85
|
+
"closed_by",
|
|
86
|
+
lambda v: Unfolder.to_optional(
|
|
87
|
+
v,
|
|
88
|
+
lambda v: Unfolder.to_json(v).bind(decode_user_obj),
|
|
89
|
+
),
|
|
90
|
+
).map(lambda m: m.bind(lambda x: Maybe.from_optional(x))),
|
|
91
|
+
JsonUnfolder.require(
|
|
92
|
+
raw,
|
|
93
|
+
"assignees",
|
|
94
|
+
lambda v: Unfolder.to_list_of(
|
|
95
|
+
v,
|
|
96
|
+
lambda v: Unfolder.to_json(v).bind(decode_user_obj),
|
|
97
|
+
),
|
|
98
|
+
),
|
|
99
|
+
JsonUnfolder.require(
|
|
100
|
+
raw,
|
|
101
|
+
"reviewers",
|
|
102
|
+
lambda v: Unfolder.to_list_of(
|
|
103
|
+
v,
|
|
104
|
+
lambda v: Unfolder.to_json(v).bind(decode_user_obj),
|
|
105
|
+
),
|
|
106
|
+
),
|
|
107
|
+
).map(lambda t: MergeRequestPeople(*t))
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def decode_state(raw: JsonObj) -> ResultE[MergeRequestFullState]:
|
|
111
|
+
return smash.smash_result_5(
|
|
112
|
+
JsonUnfolder.require(raw, "state", DecodeUtils.to_str).bind(
|
|
113
|
+
MergeRequestState.from_raw,
|
|
114
|
+
),
|
|
115
|
+
JsonUnfolder.require(raw, "detailed_merge_status", DecodeUtils.to_str),
|
|
116
|
+
JsonUnfolder.require(raw, "has_conflicts", DecodeUtils.to_bool),
|
|
117
|
+
JsonUnfolder.require(raw, "user_notes_count", DecodeUtils.to_int),
|
|
118
|
+
JsonUnfolder.optional(raw, "merge_error", DecodeUtils.to_opt_str).map(
|
|
119
|
+
lambda m: m.bind(lambda x: x),
|
|
120
|
+
),
|
|
121
|
+
).map(lambda t: MergeRequestFullState(*t))
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def decode_properties(raw: JsonObj) -> ResultE[MergeRequestProperties]:
|
|
125
|
+
group_1 = smash.smash_result_5(
|
|
126
|
+
JsonUnfolder.require(raw, "title", DecodeUtils.to_str),
|
|
127
|
+
JsonUnfolder.optional(raw, "description", DecodeUtils.to_opt_str).map(
|
|
128
|
+
lambda m: m.bind(lambda x: x),
|
|
129
|
+
),
|
|
130
|
+
JsonUnfolder.require(raw, "draft", DecodeUtils.to_bool),
|
|
131
|
+
JsonUnfolder.require(raw, "squash", DecodeUtils.to_bool),
|
|
132
|
+
JsonUnfolder.require(raw, "imported", DecodeUtils.to_bool),
|
|
133
|
+
)
|
|
134
|
+
group_2 = smash.smash_result_5(
|
|
135
|
+
JsonUnfolder.require(raw, "imported_from", DecodeUtils.to_str),
|
|
136
|
+
JsonUnfolder.optional(raw, "first_contribution", DecodeUtils.to_bool).map(
|
|
137
|
+
lambda m: m.value_or(False),
|
|
138
|
+
),
|
|
139
|
+
JsonUnfolder.require(
|
|
140
|
+
raw,
|
|
141
|
+
"labels",
|
|
142
|
+
lambda v: Unfolder.to_list_of(v, DecodeUtils.to_str),
|
|
143
|
+
),
|
|
144
|
+
JsonUnfolder.optional(raw, "merge_after", DecodeUtils.to_opt_date_time).map(
|
|
145
|
+
lambda m: m.bind(lambda x: x),
|
|
146
|
+
),
|
|
147
|
+
JsonUnfolder.require(raw, "web_url", DecodeUtils.to_str),
|
|
148
|
+
)
|
|
149
|
+
return smash.smash_result_2(group_1, group_2).map(
|
|
150
|
+
lambda g: MergeRequestProperties(*g[0], *g[1]),
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def decode_origins(raw: JsonObj) -> ResultE[MergeRequestOrigins]:
|
|
155
|
+
return smash.smash_result_4(
|
|
156
|
+
JsonUnfolder.require(raw, "source_project_id", DecodeUtils.to_int)
|
|
157
|
+
.bind(Natural.from_int)
|
|
158
|
+
.map(
|
|
159
|
+
ProjectId,
|
|
160
|
+
),
|
|
161
|
+
JsonUnfolder.require(raw, "source_branch", DecodeUtils.to_str),
|
|
162
|
+
JsonUnfolder.require(raw, "target_project_id", DecodeUtils.to_int)
|
|
163
|
+
.bind(Natural.from_int)
|
|
164
|
+
.map(
|
|
165
|
+
ProjectId,
|
|
166
|
+
),
|
|
167
|
+
JsonUnfolder.require(raw, "target_branch", DecodeUtils.to_str),
|
|
168
|
+
).map(lambda t: MergeRequestOrigins(*t))
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def decode_completion(raw: JsonObj) -> ResultE[TaskCompletion]:
|
|
172
|
+
return JsonUnfolder.require(
|
|
173
|
+
raw,
|
|
174
|
+
"task_completion_status",
|
|
175
|
+
lambda v: Unfolder.to_json(v).bind(
|
|
176
|
+
lambda raw: smash.smash_result_2(
|
|
177
|
+
JsonUnfolder.require(raw, "count", DecodeUtils.to_int),
|
|
178
|
+
JsonUnfolder.require(raw, "completed_count", DecodeUtils.to_int),
|
|
179
|
+
).map(lambda t: TaskCompletion(*t)),
|
|
180
|
+
),
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def decode_mr(raw: JsonObj) -> ResultE[MergeRequest]:
|
|
185
|
+
group_1 = smash.smash_result_5(
|
|
186
|
+
decode_sha(raw),
|
|
187
|
+
decode_dates(raw),
|
|
188
|
+
decode_people(raw),
|
|
189
|
+
decode_state(raw),
|
|
190
|
+
decode_properties(raw),
|
|
191
|
+
)
|
|
192
|
+
group_2 = smash.smash_result_3(
|
|
193
|
+
decode_origins(raw),
|
|
194
|
+
JsonUnfolder.require(
|
|
195
|
+
raw,
|
|
196
|
+
"milestone",
|
|
197
|
+
lambda v: DecodeUtils.to_maybe(
|
|
198
|
+
v,
|
|
199
|
+
lambda v: Unfolder.to_json(v).bind(decode_milestone_full_id),
|
|
200
|
+
),
|
|
201
|
+
),
|
|
202
|
+
decode_completion(raw),
|
|
203
|
+
)
|
|
204
|
+
return (
|
|
205
|
+
smash.smash_result_2(group_1, group_2)
|
|
206
|
+
.map(
|
|
207
|
+
lambda g: MergeRequest(*g[0], *g[1]),
|
|
208
|
+
)
|
|
209
|
+
.alt(
|
|
210
|
+
lambda e: Bug.new(
|
|
211
|
+
"decode_mr",
|
|
212
|
+
inspect.currentframe(),
|
|
213
|
+
e,
|
|
214
|
+
(JsonUnfolder.dumps(raw),),
|
|
215
|
+
),
|
|
216
|
+
)
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def decode_mr_and_id(raw: JsonObj) -> ResultE[tuple[MrFullId, MergeRequest]]:
|
|
221
|
+
return smash.smash_result_2(
|
|
222
|
+
decode_mr_full_id(raw),
|
|
223
|
+
decode_mr(raw),
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def decode_batch_mrs(
|
|
228
|
+
mrs: FrozenList[JsonObj],
|
|
229
|
+
) -> ResultE[FrozenList[tuple[MrFullId, MergeRequest]]]:
|
|
230
|
+
if not mrs:
|
|
231
|
+
return Result.success(FrozenList[tuple[MrFullId, MergeRequest]]([]), Exception)
|
|
232
|
+
return ResultTransform.all_ok(
|
|
233
|
+
PureIterFactory.from_list(mrs).map(lambda v: decode_mr_and_id(v)).to_list(),
|
|
234
|
+
)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import NewType
|
|
8
|
+
|
|
9
|
+
from fa_purity import (
|
|
10
|
+
Cmd,
|
|
11
|
+
FrozenList,
|
|
12
|
+
Maybe,
|
|
13
|
+
Result,
|
|
14
|
+
ResultE,
|
|
15
|
+
Stream,
|
|
16
|
+
cast_exception,
|
|
17
|
+
)
|
|
18
|
+
from fa_purity.date_time import DatetimeUTC
|
|
19
|
+
|
|
20
|
+
from fluidattacks_gitlab_sdk.ids import (
|
|
21
|
+
MilestoneFullId,
|
|
22
|
+
MrFullId,
|
|
23
|
+
MrInternalId,
|
|
24
|
+
ProjectId,
|
|
25
|
+
)
|
|
26
|
+
from fluidattacks_gitlab_sdk.users.core import UserObj
|
|
27
|
+
|
|
28
|
+
PerPage = NewType("PerPage", int)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class MergeRequestState(Enum):
|
|
32
|
+
# locked: transitional state while a merge is happening
|
|
33
|
+
OPENED = "opened"
|
|
34
|
+
CLOSED = "closed"
|
|
35
|
+
LOCKED = "locked"
|
|
36
|
+
MERGED = "merged"
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def from_raw(raw: str) -> ResultE[MergeRequestState]:
|
|
40
|
+
try:
|
|
41
|
+
return Result.success(MergeRequestState(raw))
|
|
42
|
+
except ValueError as err:
|
|
43
|
+
return Result.failure(cast_exception(err))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass(frozen=True)
|
|
47
|
+
class MergeRequestSha:
|
|
48
|
+
sha: Maybe[str]
|
|
49
|
+
merge_commit_sha: Maybe[str]
|
|
50
|
+
squash_commit_sha: Maybe[str]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass(frozen=True)
|
|
54
|
+
class MergeRequestDates:
|
|
55
|
+
created_at: DatetimeUTC
|
|
56
|
+
prepared_at: Maybe[DatetimeUTC]
|
|
57
|
+
updated_at: Maybe[DatetimeUTC]
|
|
58
|
+
merged_at: Maybe[DatetimeUTC]
|
|
59
|
+
closed_at: Maybe[DatetimeUTC]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass(frozen=True)
|
|
63
|
+
class MergeRequestPeople:
|
|
64
|
+
author: UserObj
|
|
65
|
+
merge_user: Maybe[UserObj]
|
|
66
|
+
closed_by: Maybe[UserObj]
|
|
67
|
+
assignees: FrozenList[UserObj]
|
|
68
|
+
reviewers: FrozenList[UserObj]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass(frozen=True)
|
|
72
|
+
class MergeRequestFullState:
|
|
73
|
+
state: MergeRequestState
|
|
74
|
+
detailed_merge_status: str
|
|
75
|
+
has_conflicts: bool
|
|
76
|
+
user_notes_count: int
|
|
77
|
+
merge_error: Maybe[str]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclass(frozen=True)
|
|
81
|
+
class MergeRequestProperties:
|
|
82
|
+
title: str
|
|
83
|
+
description: Maybe[str]
|
|
84
|
+
draft: bool
|
|
85
|
+
squash: bool
|
|
86
|
+
imported: bool
|
|
87
|
+
imported_from: str
|
|
88
|
+
first_contribution: bool
|
|
89
|
+
labels: FrozenList[str]
|
|
90
|
+
merge_after: Maybe[DatetimeUTC]
|
|
91
|
+
web_url: str
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass(frozen=True)
|
|
95
|
+
class MergeRequestOrigins:
|
|
96
|
+
source_project_id: ProjectId
|
|
97
|
+
source_branch: str
|
|
98
|
+
target_project_id: ProjectId
|
|
99
|
+
target_branch: str
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass(frozen=True)
|
|
103
|
+
class TaskCompletion:
|
|
104
|
+
count: int
|
|
105
|
+
completed_count: int
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@dataclass(frozen=True)
|
|
109
|
+
class MergeRequest:
|
|
110
|
+
shas: MergeRequestSha
|
|
111
|
+
dates: MergeRequestDates
|
|
112
|
+
people: MergeRequestPeople
|
|
113
|
+
full_state: MergeRequestFullState
|
|
114
|
+
properties: MergeRequestProperties
|
|
115
|
+
origins: MergeRequestOrigins
|
|
116
|
+
milestone: Maybe[MilestoneFullId]
|
|
117
|
+
task_completion: TaskCompletion
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@dataclass(frozen=True)
|
|
121
|
+
class MrsClient:
|
|
122
|
+
get_mr: Callable[
|
|
123
|
+
[ProjectId, MrInternalId],
|
|
124
|
+
Cmd[ResultE[tuple[MrFullId, MergeRequest]]],
|
|
125
|
+
]
|
|
126
|
+
most_recent_mr: Callable[
|
|
127
|
+
[ProjectId],
|
|
128
|
+
Cmd[ResultE[Maybe[tuple[MrFullId, MergeRequest]]]],
|
|
129
|
+
]
|
|
130
|
+
most_recent_mr_until: Callable[
|
|
131
|
+
[ProjectId, DatetimeUTC],
|
|
132
|
+
Cmd[ResultE[Maybe[tuple[MrFullId, MergeRequest]]]],
|
|
133
|
+
]
|
|
134
|
+
get_mr_updated: Callable[
|
|
135
|
+
[ProjectId, datetime, datetime, PerPage],
|
|
136
|
+
Cmd[Stream[FrozenList[tuple[MrFullId, MergeRequest]]]],
|
|
137
|
+
]
|