fluidattacks_gitlab_sdk 3.0.3__tar.gz → 5.0.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.
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/PKG-INFO +1 -1
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/__init__.py +1 -1
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/mr_approvals/_decode.py +10 -6
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/mr_approvals/core.py +3 -1
- fluidattacks_gitlab_sdk-5.0.0/fluidattacks_gitlab_sdk/mr_notes/__init__.py +4 -0
- fluidattacks_gitlab_sdk-5.0.0/fluidattacks_gitlab_sdk/mr_notes/_client.py +113 -0
- fluidattacks_gitlab_sdk-5.0.0/fluidattacks_gitlab_sdk/mr_notes/_core.py +23 -0
- fluidattacks_gitlab_sdk-5.0.0/fluidattacks_gitlab_sdk/mr_notes/_decode.py +24 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/pyproject.toml +1 -1
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/tests/arch/arch.py +6 -1
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/tests/mr_approvals/data.json +4 -2
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/tests/mr_approvals/decoder_test.py +8 -0
- fluidattacks_gitlab_sdk-5.0.0/tests/mr_notes/data.json +62 -0
- fluidattacks_gitlab_sdk-5.0.0/tests/mr_notes/decoder_test.py +65 -0
- fluidattacks_gitlab_sdk-5.0.0/tests_fx/py.typed +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/uv.lock +365 -384
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/.envrc +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/build/default.nix +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/build/filter.nix +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/flake.lock +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/flake.nix +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/_decoders.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/_gql_client/__init__.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/_gql_client/_client.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/_gql_client/_error.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/_gql_client/_handlers.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/_handlers.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/_http_client/__init__.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/_http_client/_client_1.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/_http_client/_core.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/_logger.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/ids.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/issues/__init__.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/issues/_client/__init__.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/issues/_client/_decode.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/issues/_client/_get_issue.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/issues/_client/_most_recent.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/issues/_client/_updated_by.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/issues/core.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/jobs/__init__.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/jobs/_client.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/jobs/_decode.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/jobs/core.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/members/__init__.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/members/_client.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/members/_decode.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/members/core.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/merge_requests/__init__.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/merge_requests/_client.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/merge_requests/_decode.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/merge_requests/core.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/milestones/__init__.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/milestones/_client.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/milestones/_decode.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/milestones/core.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/mr_approvals/__init__.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/mr_approvals/_client.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/py.typed +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/users/__init__.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/users/core.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/fluidattacks_gitlab_sdk/users/decode.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/mypy.ini +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/ruff.toml +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/tests/__init__.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/tests/arch/__init__.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/tests/arch/test_arch.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/tests/issues/__init__.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/tests/issues/data.json +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/tests/issues/test_decode.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/tests/jobs/__init__.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/tests/jobs/data.json +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/tests/jobs/test_decode_job.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/tests/members/__init__.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/tests/members/data.json +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/tests/members/test_decode_members.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/tests/merge_request/__init__.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/tests/merge_request/data.json +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/tests/merge_request/data_mr_updates.json +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/tests/merge_request/test_decode.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/tests/mr_approvals/__init__.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3/tests_fx → fluidattacks_gitlab_sdk-5.0.0/tests/mr_notes}/__init__.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/tests/py.typed +0 -0
- {fluidattacks_gitlab_sdk-3.0.3/tests_fx/issues → fluidattacks_gitlab_sdk-5.0.0/tests_fx}/__init__.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/tests_fx/_utils.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3/tests_fx/jobs → fluidattacks_gitlab_sdk-5.0.0/tests_fx/issues}/__init__.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/tests_fx/issues/test_issue_client.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3/tests_fx/members → fluidattacks_gitlab_sdk-5.0.0/tests_fx/jobs}/__init__.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/tests_fx/jobs/client_test.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3/tests_fx/milestones → fluidattacks_gitlab_sdk-5.0.0/tests_fx/members}/__init__.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/tests_fx/members/test_client_members.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3/tests_fx/mr_approvals → fluidattacks_gitlab_sdk-5.0.0/tests_fx/milestones}/__init__.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/tests_fx/milestones/client_test.py +0 -0
- /fluidattacks_gitlab_sdk-3.0.3/tests_fx/py.typed → /fluidattacks_gitlab_sdk-5.0.0/tests_fx/mr_approvals/__init__.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/tests_fx/mr_approvals/client_test.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/tests_fx/test_gql_client.py +0 -0
- {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/tests_fx/test_mr.py +0 -0
|
@@ -9,6 +9,15 @@ from fluidattacks_gitlab_sdk.mr_approvals.core import Approver, MrApprovals
|
|
|
9
9
|
from fluidattacks_gitlab_sdk.users.decode import decode_user_obj
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
def _decode_approver(entry: JsonObj) -> ResultE[Approver]:
|
|
13
|
+
return smash.smash_result_2(
|
|
14
|
+
JsonUnfolder.require(entry, "user", Unfolder.to_json).bind(decode_user_obj),
|
|
15
|
+
JsonUnfolder.optional(entry, "approved_at", DecodeUtils.to_opt_date_time).map(
|
|
16
|
+
lambda m: m.bind(lambda x: x),
|
|
17
|
+
),
|
|
18
|
+
).map(lambda v: Approver(v[0], v[1]))
|
|
19
|
+
|
|
20
|
+
|
|
12
21
|
def decode_mr_approvals(raw: JsonObj) -> ResultE[MrApprovals]:
|
|
13
22
|
return smash.smash_result_2(
|
|
14
23
|
JsonUnfolder.require(raw, "approved", DecodeUtils.to_bool),
|
|
@@ -17,12 +26,7 @@ def decode_mr_approvals(raw: JsonObj) -> ResultE[MrApprovals]:
|
|
|
17
26
|
"approved_by",
|
|
18
27
|
lambda v: Unfolder.to_list_of(
|
|
19
28
|
v,
|
|
20
|
-
lambda x: (
|
|
21
|
-
Unfolder.to_json(x)
|
|
22
|
-
.bind(lambda user_obj: JsonUnfolder.require(user_obj, "user", Unfolder.to_json))
|
|
23
|
-
.bind(decode_user_obj)
|
|
24
|
-
.map(Approver)
|
|
25
|
-
),
|
|
29
|
+
lambda x: Unfolder.to_json(x).bind(_decode_approver),
|
|
26
30
|
).map(tuple),
|
|
27
31
|
),
|
|
28
32
|
).map(lambda v: MrApprovals(v[0], v[1]))
|
|
@@ -3,7 +3,8 @@ from __future__ import annotations
|
|
|
3
3
|
from collections.abc import Callable
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
|
|
6
|
-
from fa_purity import Cmd, FrozenList, ResultE
|
|
6
|
+
from fa_purity import Cmd, FrozenList, Maybe, ResultE
|
|
7
|
+
from fa_purity.date_time import DatetimeUTC
|
|
7
8
|
|
|
8
9
|
from fluidattacks_gitlab_sdk.ids import MrFullId, MrInternalId, ProjectId
|
|
9
10
|
from fluidattacks_gitlab_sdk.users import UserObj
|
|
@@ -12,6 +13,7 @@ from fluidattacks_gitlab_sdk.users import UserObj
|
|
|
12
13
|
@dataclass(frozen=True)
|
|
13
14
|
class Approver:
|
|
14
15
|
user: UserObj
|
|
16
|
+
approved_at: Maybe[DatetimeUTC]
|
|
15
17
|
|
|
16
18
|
|
|
17
19
|
@dataclass(frozen=True)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
from __future__ import (
|
|
2
|
+
annotations,
|
|
3
|
+
)
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
|
|
8
|
+
from fa_purity import (
|
|
9
|
+
Cmd,
|
|
10
|
+
FrozenList,
|
|
11
|
+
Maybe,
|
|
12
|
+
ResultE,
|
|
13
|
+
Stream,
|
|
14
|
+
cast_exception,
|
|
15
|
+
)
|
|
16
|
+
from fa_purity._core.utils import raise_exception
|
|
17
|
+
from fa_purity.json import Primitive, UnfoldedFactory
|
|
18
|
+
from fluidattacks_etl_utils.bug import Bug
|
|
19
|
+
from fluidattacks_etl_utils.decode import int_to_str
|
|
20
|
+
from fluidattacks_etl_utils.paginate import cursor_pagination
|
|
21
|
+
|
|
22
|
+
from fluidattacks_gitlab_sdk._decoders import assert_multiple
|
|
23
|
+
from fluidattacks_gitlab_sdk._http_client import (
|
|
24
|
+
ClientFactory,
|
|
25
|
+
Credentials,
|
|
26
|
+
HttpJsonClient,
|
|
27
|
+
RelativeEndpoint,
|
|
28
|
+
)
|
|
29
|
+
from fluidattacks_gitlab_sdk.ids import MrInternalId, ProjectId
|
|
30
|
+
|
|
31
|
+
from ._core import MrNote, NotesClient
|
|
32
|
+
from ._decode import decode_mr_notes
|
|
33
|
+
|
|
34
|
+
_NOTES_PER_PAGE = 100
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _get_notes_page(
|
|
38
|
+
client: HttpJsonClient,
|
|
39
|
+
project: ProjectId,
|
|
40
|
+
mr_id: MrInternalId,
|
|
41
|
+
page: int,
|
|
42
|
+
) -> Cmd[ResultE[tuple[FrozenList[MrNote], Maybe[int]]]]:
|
|
43
|
+
endpoint = RelativeEndpoint.new(
|
|
44
|
+
"projects",
|
|
45
|
+
int_to_str(project.project_id.value),
|
|
46
|
+
"merge_requests",
|
|
47
|
+
int_to_str(mr_id.internal.value),
|
|
48
|
+
"notes",
|
|
49
|
+
)
|
|
50
|
+
params: dict[str, Primitive] = {
|
|
51
|
+
"order_by": "created_at",
|
|
52
|
+
"sort": "asc",
|
|
53
|
+
"page": int_to_str(page),
|
|
54
|
+
"per_page": _NOTES_PER_PAGE,
|
|
55
|
+
}
|
|
56
|
+
return client.get(
|
|
57
|
+
endpoint,
|
|
58
|
+
UnfoldedFactory.from_dict(params),
|
|
59
|
+
).map(
|
|
60
|
+
lambda r: (
|
|
61
|
+
r.alt(
|
|
62
|
+
lambda e: cast_exception(
|
|
63
|
+
Bug.new(
|
|
64
|
+
"_get_notes_page",
|
|
65
|
+
inspect.currentframe(),
|
|
66
|
+
e,
|
|
67
|
+
(),
|
|
68
|
+
),
|
|
69
|
+
),
|
|
70
|
+
)
|
|
71
|
+
.bind(assert_multiple)
|
|
72
|
+
.bind(decode_mr_notes)
|
|
73
|
+
.map(
|
|
74
|
+
lambda notes: (
|
|
75
|
+
notes,
|
|
76
|
+
Maybe.empty() if len(notes) < _NOTES_PER_PAGE else Maybe.some(page + 1),
|
|
77
|
+
),
|
|
78
|
+
)
|
|
79
|
+
),
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _get_page(
|
|
84
|
+
client: HttpJsonClient,
|
|
85
|
+
project: ProjectId,
|
|
86
|
+
mr_id: MrInternalId,
|
|
87
|
+
maybe_page: Maybe[int],
|
|
88
|
+
) -> Cmd[tuple[FrozenList[MrNote], Maybe[int]]]:
|
|
89
|
+
page = maybe_page.value_or(1)
|
|
90
|
+
return _get_notes_page(client, project, mr_id, page).map(
|
|
91
|
+
lambda r: r.alt(raise_exception).to_union(),
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _get_mr_notes(
|
|
96
|
+
client: HttpJsonClient,
|
|
97
|
+
project: ProjectId,
|
|
98
|
+
mr_id: MrInternalId,
|
|
99
|
+
) -> Stream[FrozenList[MrNote]]:
|
|
100
|
+
return cursor_pagination(
|
|
101
|
+
lambda maybe_page: _get_page(client, project, mr_id, maybe_page),
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _from_client(client: HttpJsonClient) -> NotesClient:
|
|
106
|
+
return NotesClient(lambda p, i: _get_mr_notes(client, p, i))
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@dataclass(frozen=True)
|
|
110
|
+
class MrNotesFactory:
|
|
111
|
+
@staticmethod
|
|
112
|
+
def new(creds: Credentials) -> NotesClient:
|
|
113
|
+
return _from_client(ClientFactory.new(creds))
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
from fa_purity import FrozenList, Stream
|
|
7
|
+
from fa_purity.date_time import DatetimeUTC
|
|
8
|
+
|
|
9
|
+
from fluidattacks_gitlab_sdk.ids import MrInternalId, ProjectId
|
|
10
|
+
from fluidattacks_gitlab_sdk.users import UserObj
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class MrNote:
|
|
15
|
+
body: str
|
|
16
|
+
author: UserObj
|
|
17
|
+
created_at: DatetimeUTC
|
|
18
|
+
system: bool
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class NotesClient:
|
|
23
|
+
get_mr_notes: Callable[[ProjectId, MrInternalId], Stream[FrozenList[MrNote]]]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from fa_purity import FrozenList, PureIterFactory, Result, ResultE, ResultTransform
|
|
2
|
+
from fa_purity.json import JsonObj, JsonUnfolder, Unfolder
|
|
3
|
+
from fluidattacks_etl_utils import smash
|
|
4
|
+
from fluidattacks_etl_utils.decode import DecodeUtils
|
|
5
|
+
|
|
6
|
+
from fluidattacks_gitlab_sdk.mr_notes._core import MrNote
|
|
7
|
+
from fluidattacks_gitlab_sdk.users.decode import decode_user_obj
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def decode_mr_note(raw: JsonObj) -> ResultE[MrNote]:
|
|
11
|
+
return smash.smash_result_4(
|
|
12
|
+
JsonUnfolder.require(raw, "body", DecodeUtils.to_str),
|
|
13
|
+
JsonUnfolder.require(raw, "author", lambda v: Unfolder.to_json(v).bind(decode_user_obj)),
|
|
14
|
+
JsonUnfolder.require(raw, "created_at", DecodeUtils.to_date_time),
|
|
15
|
+
JsonUnfolder.require(raw, "system", DecodeUtils.to_bool),
|
|
16
|
+
).map(lambda t: MrNote(*t))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def decode_mr_notes(raw: FrozenList[JsonObj]) -> ResultE[FrozenList[MrNote]]:
|
|
20
|
+
if not raw:
|
|
21
|
+
return Result.success(FrozenList[MrNote](()), Exception)
|
|
22
|
+
return ResultTransform.all_ok(
|
|
23
|
+
PureIterFactory.from_list(raw).map(decode_mr_note).to_list(),
|
|
24
|
+
)
|
|
@@ -34,7 +34,7 @@ dev = [
|
|
|
34
34
|
|
|
35
35
|
[tool.uv]
|
|
36
36
|
exclude-newer = "7 days"
|
|
37
|
-
exclude-newer-package = { fluidattacks-core = "2099-01-
|
|
37
|
+
exclude-newer-package = { fluidattacks-core = "2099-01-02T00:00:00Z", fluid-blends = "2099-01-02T00:00:00Z" , fluidattacks-utils-logger = "2099-01-02T00:00:00Z" }
|
|
38
38
|
override-dependencies = [
|
|
39
39
|
"cryptography==48.0.1"
|
|
40
40
|
]
|
|
@@ -26,7 +26,7 @@ def _module(path: str) -> FullPathModule | NoReturn:
|
|
|
26
26
|
|
|
27
27
|
_dag: Dict[str, tuple[tuple[str, ...] | str, ...]] = {
|
|
28
28
|
"fluidattacks_gitlab_sdk": (
|
|
29
|
-
("merge_requests", "members", "issues", "milestones", "mr_approvals", "jobs"),
|
|
29
|
+
("merge_requests", "members", "issues", "milestones", "mr_approvals", "mr_notes", "jobs"),
|
|
30
30
|
"users",
|
|
31
31
|
"_handlers",
|
|
32
32
|
("_http_client", "_gql_client", "_decoders"),
|
|
@@ -74,6 +74,11 @@ _dag: Dict[str, tuple[tuple[str, ...] | str, ...]] = {
|
|
|
74
74
|
"_decode",
|
|
75
75
|
"core",
|
|
76
76
|
),
|
|
77
|
+
"fluidattacks_gitlab_sdk.mr_notes": (
|
|
78
|
+
"_client",
|
|
79
|
+
"_decode",
|
|
80
|
+
"_core",
|
|
81
|
+
),
|
|
77
82
|
"fluidattacks_gitlab_sdk.jobs": ("_client", "_decode", "core"),
|
|
78
83
|
}
|
|
79
84
|
|
{fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/tests/mr_approvals/data.json
RENAMED
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
"locked": false,
|
|
24
24
|
"avatar_url": "https://secure.gravatar.com/avatar/2928a4bece9cd58167f81bd00ce77a48546cfc36556731cec749ea6125?s=80&d=identicon",
|
|
25
25
|
"web_url": "https://gitlab.com/useratfluid"
|
|
26
|
-
}
|
|
26
|
+
},
|
|
27
|
+
"approved_at": "2025-05-27T10:15:30.500Z"
|
|
27
28
|
},
|
|
28
29
|
{
|
|
29
30
|
"user": {
|
|
@@ -35,7 +36,8 @@
|
|
|
35
36
|
"locked": false,
|
|
36
37
|
"avatar_url": "https://secure.gravatar.com/avatar/749adaea75210e02e7554347f5d6e6b65c8ff746a4fa26ef2d?s=80&d=identicon",
|
|
37
38
|
"web_url": "https://gitlab.com/project_20741933_bot_7328383692d79816c341e690"
|
|
38
|
-
}
|
|
39
|
+
},
|
|
40
|
+
"approved_at": null
|
|
39
41
|
}
|
|
40
42
|
],
|
|
41
43
|
"suggested_approvers": [
|
{fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-5.0.0}/tests/mr_approvals/decoder_test.py
RENAMED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
from datetime import UTC, datetime
|
|
1
2
|
from pathlib import Path
|
|
2
3
|
|
|
3
4
|
from fa_purity import FrozenList, Maybe, Unsafe
|
|
5
|
+
from fa_purity.date_time import DatetimeUTC
|
|
4
6
|
from fa_purity.json import JsonValueFactory, Unfolder
|
|
5
7
|
from fluidattacks_etl_utils.natural import NaturalOperations
|
|
6
8
|
|
|
@@ -17,6 +19,10 @@ from fluidattacks_gitlab_sdk.mr_approvals.core import Approver, MrApprovals
|
|
|
17
19
|
from fluidattacks_gitlab_sdk.users.core import User, UserName, UserObj
|
|
18
20
|
|
|
19
21
|
|
|
22
|
+
def _assert_utc(date_time: datetime) -> DatetimeUTC:
|
|
23
|
+
return DatetimeUTC.assert_utc(date_time).alt(Unsafe.raise_exception).to_union()
|
|
24
|
+
|
|
25
|
+
|
|
20
26
|
def test_decode() -> None:
|
|
21
27
|
mr_ids = MrFullId(
|
|
22
28
|
MrGlobalId(NaturalOperations.absolute(345678912)),
|
|
@@ -33,6 +39,7 @@ def test_decode() -> None:
|
|
|
33
39
|
UserName("User Fluid"),
|
|
34
40
|
Maybe.some(""),
|
|
35
41
|
),
|
|
42
|
+
Maybe.some(_assert_utc(datetime(2025, 5, 27, 10, 15, 30, 500000, tzinfo=UTC))),
|
|
36
43
|
),
|
|
37
44
|
Approver(
|
|
38
45
|
UserObj(
|
|
@@ -41,6 +48,7 @@ def test_decode() -> None:
|
|
|
41
48
|
UserName("****"),
|
|
42
49
|
Maybe.empty(),
|
|
43
50
|
),
|
|
51
|
+
Maybe.empty(),
|
|
44
52
|
),
|
|
45
53
|
)
|
|
46
54
|
mr_approvers = MrApprovals(
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": 2712345601,
|
|
4
|
+
"type": null,
|
|
5
|
+
"body": "required: [major-version] change the major version to publish the new indicators package",
|
|
6
|
+
"author": {
|
|
7
|
+
"id": 12345678,
|
|
8
|
+
"username": "dmurciaatfluid",
|
|
9
|
+
"public_email": "",
|
|
10
|
+
"name": "Daniel Murcia",
|
|
11
|
+
"state": "active",
|
|
12
|
+
"locked": false,
|
|
13
|
+
"avatar_url": "https://secure.gravatar.com/avatar/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa?s=80&d=identicon",
|
|
14
|
+
"web_url": "https://gitlab.com/dmurciaatfluid"
|
|
15
|
+
},
|
|
16
|
+
"created_at": "2026-07-08T12:31:03.134Z",
|
|
17
|
+
"updated_at": "2026-07-08T12:31:03.134Z",
|
|
18
|
+
"system": false,
|
|
19
|
+
"noteable_id": 345678912,
|
|
20
|
+
"noteable_type": "MergeRequest"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"id": 2712345602,
|
|
24
|
+
"type": null,
|
|
25
|
+
"body": "<img src=\"https://res.cloudinary.com/fluid-attacks/image/upload/v1727795769/airs/logo/logo_icon.png\" width=\"24\" alt=\"Fluid Attacks\" /> **Fluid Attacks** completed security analysis\n\n**Nice job!** No vulnerabilities found",
|
|
26
|
+
"author": {
|
|
27
|
+
"id": 34547217,
|
|
28
|
+
"username": "project_20741933_bot_8385943dd3fb82db18695bfce6854920",
|
|
29
|
+
"public_email": null,
|
|
30
|
+
"name": "****",
|
|
31
|
+
"state": "active",
|
|
32
|
+
"locked": false,
|
|
33
|
+
"avatar_url": "https://secure.gravatar.com/avatar/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb?s=80&d=identicon",
|
|
34
|
+
"web_url": "https://gitlab.com/project_20741933_bot_8385943dd3fb82db18695bfce6854920"
|
|
35
|
+
},
|
|
36
|
+
"created_at": "2026-07-08T12:19:27.612Z",
|
|
37
|
+
"updated_at": "2026-07-08T12:19:27.612Z",
|
|
38
|
+
"system": false,
|
|
39
|
+
"noteable_id": 345678912,
|
|
40
|
+
"noteable_type": "MergeRequest"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"id": 2712345603,
|
|
44
|
+
"type": "ApprovalSystemNote",
|
|
45
|
+
"body": "approved this merge request",
|
|
46
|
+
"author": {
|
|
47
|
+
"id": 12345678,
|
|
48
|
+
"username": "dmurciaatfluid",
|
|
49
|
+
"public_email": "",
|
|
50
|
+
"name": "Daniel Murcia",
|
|
51
|
+
"state": "active",
|
|
52
|
+
"locked": false,
|
|
53
|
+
"avatar_url": "https://secure.gravatar.com/avatar/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa?s=80&d=identicon",
|
|
54
|
+
"web_url": "https://gitlab.com/dmurciaatfluid"
|
|
55
|
+
},
|
|
56
|
+
"created_at": "2026-07-08T13:38:56.341Z",
|
|
57
|
+
"updated_at": "2026-07-08T13:38:56.341Z",
|
|
58
|
+
"system": true,
|
|
59
|
+
"noteable_id": 345678912,
|
|
60
|
+
"noteable_type": "MergeRequest"
|
|
61
|
+
}
|
|
62
|
+
]
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from datetime import UTC, datetime
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from fa_purity import FrozenList, Maybe, Unsafe
|
|
5
|
+
from fa_purity.date_time import DatetimeUTC
|
|
6
|
+
from fa_purity.json import JsonValueFactory, Unfolder
|
|
7
|
+
from fluidattacks_etl_utils.natural import NaturalOperations
|
|
8
|
+
|
|
9
|
+
from fluidattacks_gitlab_sdk.ids import UserId
|
|
10
|
+
from fluidattacks_gitlab_sdk.mr_notes import MrNote
|
|
11
|
+
from fluidattacks_gitlab_sdk.mr_notes._decode import decode_mr_notes
|
|
12
|
+
from fluidattacks_gitlab_sdk.users.core import User, UserName, UserObj
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _assert_utc(date_time: datetime) -> DatetimeUTC:
|
|
16
|
+
return DatetimeUTC.assert_utc(date_time).alt(Unsafe.raise_exception).to_union()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_decode() -> None:
|
|
20
|
+
human_author = UserObj(
|
|
21
|
+
UserId(NaturalOperations.absolute(12345678)),
|
|
22
|
+
User("dmurciaatfluid"),
|
|
23
|
+
UserName("Daniel Murcia"),
|
|
24
|
+
Maybe.some(""),
|
|
25
|
+
)
|
|
26
|
+
bot_author = UserObj(
|
|
27
|
+
UserId(NaturalOperations.absolute(34547217)),
|
|
28
|
+
User("project_20741933_bot_8385943dd3fb82db18695bfce6854920"),
|
|
29
|
+
UserName("****"),
|
|
30
|
+
Maybe.empty(),
|
|
31
|
+
)
|
|
32
|
+
expected: FrozenList[MrNote] = (
|
|
33
|
+
MrNote(
|
|
34
|
+
"required: [major-version] change the major version to publish "
|
|
35
|
+
"the new indicators package",
|
|
36
|
+
human_author,
|
|
37
|
+
_assert_utc(datetime(2026, 7, 8, 12, 31, 3, 134000, tzinfo=UTC)),
|
|
38
|
+
system=False,
|
|
39
|
+
),
|
|
40
|
+
MrNote(
|
|
41
|
+
'<img src="https://res.cloudinary.com/fluid-attacks/image/upload/v1727795769/airs/logo'
|
|
42
|
+
'/logo_icon.png" width="24" alt="Fluid Attacks" /> **Fluid Attacks** completed securit'
|
|
43
|
+
"y analysis\n\n**Nice job!** No vulnerabilities found",
|
|
44
|
+
bot_author,
|
|
45
|
+
_assert_utc(datetime(2026, 7, 8, 12, 19, 27, 612000, tzinfo=UTC)),
|
|
46
|
+
system=False,
|
|
47
|
+
),
|
|
48
|
+
MrNote(
|
|
49
|
+
"approved this merge request",
|
|
50
|
+
human_author,
|
|
51
|
+
_assert_utc(datetime(2026, 7, 8, 13, 38, 56, 341000, tzinfo=UTC)),
|
|
52
|
+
system=True,
|
|
53
|
+
),
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
raw_data_path = Path(__file__).parent / "data.json"
|
|
57
|
+
json_value = JsonValueFactory.load(raw_data_path.open(encoding="utf-8"))
|
|
58
|
+
raw_notes = (
|
|
59
|
+
json_value.bind(lambda v: Unfolder.to_list_of(v, Unfolder.to_json))
|
|
60
|
+
.alt(Unsafe.raise_exception)
|
|
61
|
+
.to_union()
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
decoded_notes = decode_mr_notes(raw_notes).alt(Unsafe.raise_exception).to_union()
|
|
65
|
+
assert decoded_notes == expected
|
|
File without changes
|