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,119 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
from fa_purity import (
|
|
5
|
+
Cmd,
|
|
6
|
+
Maybe,
|
|
7
|
+
NewFrozenList,
|
|
8
|
+
Result,
|
|
9
|
+
ResultE,
|
|
10
|
+
cast_exception,
|
|
11
|
+
)
|
|
12
|
+
from fa_purity.json import JsonObj, JsonUnfolder, Primitive, UnfoldedFactory
|
|
13
|
+
from fluidattacks_etl_utils.bug import Bug
|
|
14
|
+
from fluidattacks_etl_utils.decode import int_to_str
|
|
15
|
+
|
|
16
|
+
from fluidattacks_gitlab_sdk._decoders import (
|
|
17
|
+
assert_multiple,
|
|
18
|
+
decode_issue_internal_id,
|
|
19
|
+
decode_maybe_single,
|
|
20
|
+
)
|
|
21
|
+
from fluidattacks_gitlab_sdk._gql_client import GraphQlGitlabClient
|
|
22
|
+
from fluidattacks_gitlab_sdk._http_client import HttpJsonClient, RelativeEndpoint
|
|
23
|
+
from fluidattacks_gitlab_sdk.ids import IssueFullId, ProjectPath
|
|
24
|
+
from fluidattacks_gitlab_sdk.issues._client._updated_by import get_updated_by
|
|
25
|
+
from fluidattacks_gitlab_sdk.issues.core import Issue, ProjectIdObj
|
|
26
|
+
from fluidattacks_gitlab_sdk.users.core import UserObj
|
|
27
|
+
|
|
28
|
+
from ._decode import decode_issue_and_id
|
|
29
|
+
|
|
30
|
+
LOG = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _most_recent_issue(
|
|
34
|
+
gql_client: GraphQlGitlabClient,
|
|
35
|
+
raw: JsonObj,
|
|
36
|
+
project: ProjectPath,
|
|
37
|
+
) -> Cmd[ResultE[tuple[IssueFullId, Issue]]]:
|
|
38
|
+
"""
|
|
39
|
+
Completes the raw issue obj by getting the updated by field.
|
|
40
|
+
|
|
41
|
+
This function should be called always after getting the raw issue.
|
|
42
|
+
It can fail when:
|
|
43
|
+
- decode fails
|
|
44
|
+
- raw issue id not exist and/or is fake
|
|
45
|
+
"""
|
|
46
|
+
_updated_by: Cmd[ResultE[Maybe[UserObj]]] = (
|
|
47
|
+
decode_issue_internal_id(raw)
|
|
48
|
+
.to_coproduct()
|
|
49
|
+
.map(
|
|
50
|
+
lambda i: get_updated_by(gql_client, project, i.internal_id).map(
|
|
51
|
+
lambda r: r.alt(
|
|
52
|
+
lambda c: c.map(
|
|
53
|
+
lambda e: Bug.new(
|
|
54
|
+
"most_recent_issue: race condition",
|
|
55
|
+
inspect.currentframe(),
|
|
56
|
+
e,
|
|
57
|
+
(str(project), JsonUnfolder.dumps(raw)),
|
|
58
|
+
), # this should be not possible, the issue was retrieved before
|
|
59
|
+
lambda e: e,
|
|
60
|
+
),
|
|
61
|
+
),
|
|
62
|
+
),
|
|
63
|
+
lambda e: Cmd.wrap_value(Result.failure(e)),
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
return _updated_by.map(
|
|
67
|
+
lambda r: r.bind(
|
|
68
|
+
lambda u: decode_issue_and_id(u, raw),
|
|
69
|
+
),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def most_recent_issue(
|
|
74
|
+
client: HttpJsonClient,
|
|
75
|
+
gql_client: GraphQlGitlabClient,
|
|
76
|
+
project: ProjectIdObj,
|
|
77
|
+
) -> Cmd[ResultE[Maybe[tuple[IssueFullId, Issue]]]]:
|
|
78
|
+
endpoint = RelativeEndpoint.new(
|
|
79
|
+
"projects",
|
|
80
|
+
int_to_str(project.project_id.project_id.value),
|
|
81
|
+
"issues",
|
|
82
|
+
)
|
|
83
|
+
params: dict[str, Primitive] = {
|
|
84
|
+
"order_by": "created_at",
|
|
85
|
+
"sort": "desc",
|
|
86
|
+
"per_page": 1,
|
|
87
|
+
}
|
|
88
|
+
empty: Maybe[tuple[IssueFullId, Issue]] = Maybe.empty()
|
|
89
|
+
msg: Cmd[None] = Cmd.wrap_impure(lambda: LOG.info("[API] most_recent_issue(%s)", project))
|
|
90
|
+
get_maybe_single = client.get(
|
|
91
|
+
endpoint,
|
|
92
|
+
UnfoldedFactory.from_dict(params),
|
|
93
|
+
).map(
|
|
94
|
+
lambda r: r.alt(
|
|
95
|
+
lambda e: cast_exception(
|
|
96
|
+
Bug.new(
|
|
97
|
+
"most_recent_issue",
|
|
98
|
+
inspect.currentframe(),
|
|
99
|
+
e,
|
|
100
|
+
(),
|
|
101
|
+
),
|
|
102
|
+
),
|
|
103
|
+
)
|
|
104
|
+
.bind(assert_multiple)
|
|
105
|
+
.map(NewFrozenList)
|
|
106
|
+
.map(decode_maybe_single),
|
|
107
|
+
)
|
|
108
|
+
get_issue: Cmd[ResultE[Maybe[tuple[IssueFullId, Issue]]]] = get_maybe_single.bind(
|
|
109
|
+
lambda r: r.to_coproduct().map(
|
|
110
|
+
lambda m: m.to_coproduct().map(
|
|
111
|
+
lambda raw: _most_recent_issue(gql_client, raw, project.project_path).map(
|
|
112
|
+
lambda r: r.map(Maybe.some),
|
|
113
|
+
),
|
|
114
|
+
lambda _: Cmd.wrap_value(Result.success(empty)),
|
|
115
|
+
),
|
|
116
|
+
lambda e: Cmd.wrap_value(Result.failure(e)),
|
|
117
|
+
),
|
|
118
|
+
)
|
|
119
|
+
return msg + get_issue
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import logging
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from typing import TypeVar
|
|
5
|
+
|
|
6
|
+
from fa_purity import (
|
|
7
|
+
Cmd,
|
|
8
|
+
Coproduct,
|
|
9
|
+
FrozenList,
|
|
10
|
+
FrozenTools,
|
|
11
|
+
Maybe,
|
|
12
|
+
Result,
|
|
13
|
+
ResultE,
|
|
14
|
+
)
|
|
15
|
+
from fa_purity.json import JsonObj, JsonUnfolder, JsonValue, Unfolder
|
|
16
|
+
from fluidattacks_etl_utils.bug import Bug
|
|
17
|
+
from fluidattacks_etl_utils.decode import DecodeUtils, int_to_str, str_to_int
|
|
18
|
+
from fluidattacks_etl_utils.natural import Natural
|
|
19
|
+
from fluidattacks_etl_utils.smash import right_map
|
|
20
|
+
|
|
21
|
+
from fluidattacks_gitlab_sdk._gql_client import GraphQlGitlabClient
|
|
22
|
+
from fluidattacks_gitlab_sdk._handlers import NotFound
|
|
23
|
+
from fluidattacks_gitlab_sdk.ids import IssueInternalId, ProjectPath, UserId
|
|
24
|
+
from fluidattacks_gitlab_sdk.users.core import User, UserName, UserObj
|
|
25
|
+
|
|
26
|
+
LOG = logging.getLogger(__name__)
|
|
27
|
+
_T = TypeVar("_T")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _nested_key(raw: JsonObj, first_key: str, next_keys: FrozenList[str]) -> ResultE[JsonValue]:
|
|
31
|
+
if next_keys:
|
|
32
|
+
return JsonUnfolder.require(
|
|
33
|
+
raw,
|
|
34
|
+
first_key,
|
|
35
|
+
lambda v: Unfolder.to_json(v).bind(
|
|
36
|
+
lambda v: _nested_key(v, next_keys[0], next_keys[1:]),
|
|
37
|
+
),
|
|
38
|
+
)
|
|
39
|
+
return JsonUnfolder.require(raw, first_key, lambda v: Result.success(v))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _decode_gid(raw: str) -> ResultE[UserId]:
|
|
43
|
+
return str_to_int(raw.removeprefix("gid://gitlab/User/")).bind(Natural.from_int).map(UserId)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def decode_user_obj(raw: JsonObj) -> ResultE[UserObj]:
|
|
47
|
+
return (
|
|
48
|
+
JsonUnfolder.require(raw, "id", DecodeUtils.to_str)
|
|
49
|
+
.bind(_decode_gid)
|
|
50
|
+
.bind(
|
|
51
|
+
lambda user_id: JsonUnfolder.require(raw, "username", DecodeUtils.to_str)
|
|
52
|
+
.map(User)
|
|
53
|
+
.bind(
|
|
54
|
+
lambda user: JsonUnfolder.require(raw, "name", DecodeUtils.to_str)
|
|
55
|
+
.map(UserName)
|
|
56
|
+
.map(
|
|
57
|
+
lambda name: UserObj(user_id, user, name),
|
|
58
|
+
),
|
|
59
|
+
),
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def decode_updated_by(raw: JsonObj) -> ResultE[Maybe[UserObj]]:
|
|
65
|
+
return JsonUnfolder.require(
|
|
66
|
+
raw,
|
|
67
|
+
"updatedBy",
|
|
68
|
+
lambda v: DecodeUtils.to_maybe(v, lambda v: Unfolder.to_json(v).bind(decode_user_obj)),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _set_not_found_if_empty(
|
|
73
|
+
value: JsonValue,
|
|
74
|
+
transform: Callable[[JsonValue], ResultE[_T]],
|
|
75
|
+
) -> Result[_T, Coproduct[NotFound, Exception]]:
|
|
76
|
+
return (
|
|
77
|
+
DecodeUtils.to_maybe(value, transform)
|
|
78
|
+
.alt(Coproduct[NotFound, Exception].inr)
|
|
79
|
+
.bind(
|
|
80
|
+
lambda m: m.to_coproduct().map(
|
|
81
|
+
lambda m: Result.success(m),
|
|
82
|
+
lambda _: Result.failure(Coproduct.inl(NotFound(ValueError("Issue not found")))),
|
|
83
|
+
),
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _decode(raw: JsonObj) -> Result[Maybe[UserObj], Coproduct[NotFound, Exception]]:
|
|
89
|
+
return (
|
|
90
|
+
_nested_key(raw, "project", ("issue",))
|
|
91
|
+
.alt(Coproduct[NotFound, Exception].inr)
|
|
92
|
+
.bind(
|
|
93
|
+
lambda v: _set_not_found_if_empty(
|
|
94
|
+
v,
|
|
95
|
+
lambda v: Unfolder.to_json(v).bind(decode_updated_by),
|
|
96
|
+
),
|
|
97
|
+
)
|
|
98
|
+
.alt(
|
|
99
|
+
lambda c: c.map(
|
|
100
|
+
Coproduct.inl,
|
|
101
|
+
lambda e: Coproduct.inr(
|
|
102
|
+
Bug.new(
|
|
103
|
+
"decode_updated_by",
|
|
104
|
+
inspect.currentframe(),
|
|
105
|
+
e,
|
|
106
|
+
(JsonUnfolder.dumps(raw),),
|
|
107
|
+
),
|
|
108
|
+
),
|
|
109
|
+
),
|
|
110
|
+
)
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def get_updated_by(
|
|
115
|
+
client: GraphQlGitlabClient,
|
|
116
|
+
project: ProjectPath,
|
|
117
|
+
issue_id: IssueInternalId,
|
|
118
|
+
) -> Cmd[Result[Maybe[UserObj], Coproduct[NotFound, Exception]]]:
|
|
119
|
+
"""
|
|
120
|
+
Get the updatedBy field of an issue.
|
|
121
|
+
|
|
122
|
+
To avoid failure ensure:
|
|
123
|
+
- The issue exist
|
|
124
|
+
- The credentials are correct
|
|
125
|
+
"""
|
|
126
|
+
query = """
|
|
127
|
+
query getIssueUpdatedBy($project: ID!, $issue_iid: String!){
|
|
128
|
+
project(fullPath: $project) {
|
|
129
|
+
issue(iid: $issue_iid) {
|
|
130
|
+
updatedBy {
|
|
131
|
+
id
|
|
132
|
+
name
|
|
133
|
+
username
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
"""
|
|
139
|
+
values: dict[str, str] = {
|
|
140
|
+
"project": project.path,
|
|
141
|
+
"issue_iid": int_to_str(issue_id.internal_id.value),
|
|
142
|
+
}
|
|
143
|
+
return client.get(query, FrozenTools.freeze(values)).map(
|
|
144
|
+
lambda r: r.alt(Coproduct[NotFound, Exception].inr)
|
|
145
|
+
.bind(_decode)
|
|
146
|
+
.alt(
|
|
147
|
+
lambda c: right_map(
|
|
148
|
+
c,
|
|
149
|
+
lambda e: Bug.new(
|
|
150
|
+
"get_updated_by",
|
|
151
|
+
inspect.currentframe(),
|
|
152
|
+
e,
|
|
153
|
+
(str(project), str(issue_id)),
|
|
154
|
+
),
|
|
155
|
+
),
|
|
156
|
+
),
|
|
157
|
+
)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from datetime import date
|
|
6
|
+
from enum import (
|
|
7
|
+
Enum,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
from fa_purity import Cmd, Coproduct, FrozenList, Maybe, Result, ResultE
|
|
11
|
+
from fa_purity.date_time import DatetimeUTC
|
|
12
|
+
|
|
13
|
+
from fluidattacks_gitlab_sdk._handlers import NotFound
|
|
14
|
+
from fluidattacks_gitlab_sdk.ids import (
|
|
15
|
+
EpicFullId,
|
|
16
|
+
IssueFullId,
|
|
17
|
+
IssueInternalId,
|
|
18
|
+
MilestoneFullId,
|
|
19
|
+
ProjectId,
|
|
20
|
+
ProjectPath,
|
|
21
|
+
)
|
|
22
|
+
from fluidattacks_gitlab_sdk.users.core import UserObj
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class IssueType(Enum):
|
|
26
|
+
ISSUE = "issue"
|
|
27
|
+
INCIDENT = "incident"
|
|
28
|
+
TASK = "task"
|
|
29
|
+
TEST_CASE = "test_case"
|
|
30
|
+
REQUIREMENT = "requirement"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class IssueCounts:
|
|
35
|
+
up_votes: int
|
|
36
|
+
down_votes: int
|
|
37
|
+
merge_requests_count: int
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True)
|
|
41
|
+
class IssueDates:
|
|
42
|
+
created_at: DatetimeUTC
|
|
43
|
+
updated_at: Maybe[DatetimeUTC]
|
|
44
|
+
closed_at: Maybe[DatetimeUTC]
|
|
45
|
+
due_date: Maybe[date]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True)
|
|
49
|
+
class IssueOtherProperties:
|
|
50
|
+
confidential: bool
|
|
51
|
+
discussion_locked: Maybe[bool]
|
|
52
|
+
labels: FrozenList[str]
|
|
53
|
+
health_status: Maybe[str]
|
|
54
|
+
weight: Maybe[int]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(frozen=True)
|
|
58
|
+
class IssueReferences:
|
|
59
|
+
author: UserObj
|
|
60
|
+
milestone: Maybe[MilestoneFullId]
|
|
61
|
+
epic: Maybe[EpicFullId]
|
|
62
|
+
closed_by: Maybe[UserObj]
|
|
63
|
+
assignees: FrozenList[UserObj]
|
|
64
|
+
updated_by: Maybe[UserObj]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass(frozen=True)
|
|
68
|
+
class IssueDef:
|
|
69
|
+
title: str
|
|
70
|
+
state: str
|
|
71
|
+
issue_type: IssueType
|
|
72
|
+
description: Maybe[str]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass(frozen=True)
|
|
76
|
+
class Issue:
|
|
77
|
+
definition: IssueDef
|
|
78
|
+
references: IssueReferences
|
|
79
|
+
properties: IssueOtherProperties
|
|
80
|
+
dates: IssueDates
|
|
81
|
+
stats: IssueCounts
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass(frozen=True)
|
|
85
|
+
class ProjectIdObj:
|
|
86
|
+
project_id: ProjectId
|
|
87
|
+
project_path: ProjectPath
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass(frozen=True)
|
|
91
|
+
class IssueClient:
|
|
92
|
+
get_issue: Callable[
|
|
93
|
+
[ProjectIdObj, IssueInternalId],
|
|
94
|
+
Cmd[Result[tuple[IssueFullId, Issue], Coproduct[NotFound, Exception]]],
|
|
95
|
+
]
|
|
96
|
+
most_recent_issue: Callable[
|
|
97
|
+
[ProjectIdObj],
|
|
98
|
+
Cmd[ResultE[Maybe[tuple[IssueFullId, Issue]]]],
|
|
99
|
+
]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from fluidattacks_gitlab_sdk._http_client import ClientFactory, Credentials, HttpJsonClient
|
|
4
|
+
from fluidattacks_gitlab_sdk.members.core import Member, MemberClient
|
|
5
|
+
|
|
6
|
+
from . import _client
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _from_client(client: HttpJsonClient) -> MemberClient:
|
|
10
|
+
return MemberClient(
|
|
11
|
+
lambda p: _client.get_members(client, p),
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class MembersClientFactory:
|
|
17
|
+
@staticmethod
|
|
18
|
+
def new(creds: Credentials) -> MemberClient:
|
|
19
|
+
return _from_client(ClientFactory.new(creds))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"Member",
|
|
24
|
+
"MemberClient",
|
|
25
|
+
]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
|
|
3
|
+
from fa_purity import (
|
|
4
|
+
Cmd,
|
|
5
|
+
FrozenList,
|
|
6
|
+
ResultE,
|
|
7
|
+
cast_exception,
|
|
8
|
+
)
|
|
9
|
+
from fa_purity.json import Primitive, UnfoldedFactory
|
|
10
|
+
from fluidattacks_etl_utils.bug import Bug
|
|
11
|
+
from fluidattacks_etl_utils.decode import int_to_str
|
|
12
|
+
|
|
13
|
+
from fluidattacks_gitlab_sdk._decoders import assert_multiple
|
|
14
|
+
from fluidattacks_gitlab_sdk._http_client import HttpJsonClient, RelativeEndpoint
|
|
15
|
+
from fluidattacks_gitlab_sdk.ids import MemberId, ProjectId
|
|
16
|
+
|
|
17
|
+
from ._decode import decode_members
|
|
18
|
+
from .core import Member
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_members(
|
|
22
|
+
client: HttpJsonClient,
|
|
23
|
+
project: ProjectId,
|
|
24
|
+
) -> Cmd[ResultE[FrozenList[tuple[MemberId, Member]]]]:
|
|
25
|
+
endpoint = RelativeEndpoint.new(
|
|
26
|
+
"projects",
|
|
27
|
+
int_to_str(project.project_id.value),
|
|
28
|
+
"members",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
params: dict[str, Primitive] = {
|
|
32
|
+
"page": 1,
|
|
33
|
+
"per_page": 100,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return client.get(
|
|
37
|
+
endpoint,
|
|
38
|
+
UnfoldedFactory.from_dict(params),
|
|
39
|
+
).map(
|
|
40
|
+
lambda result: (
|
|
41
|
+
result.alt(
|
|
42
|
+
lambda e: cast_exception(
|
|
43
|
+
Bug.new("_get_members", inspect.currentframe(), e, ()),
|
|
44
|
+
),
|
|
45
|
+
)
|
|
46
|
+
.bind(assert_multiple)
|
|
47
|
+
.bind(lambda members: decode_members(members, project))
|
|
48
|
+
),
|
|
49
|
+
)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from fa_purity import FrozenList, PureIterFactory, Result, ResultE, ResultTransform
|
|
4
|
+
from fa_purity.json import JsonObj, JsonUnfolder
|
|
5
|
+
from fluidattacks_etl_utils import smash
|
|
6
|
+
from fluidattacks_etl_utils.decode import DecodeUtils
|
|
7
|
+
from fluidattacks_etl_utils.natural import Natural
|
|
8
|
+
|
|
9
|
+
from fluidattacks_gitlab_sdk.ids import MemberId, ProjectId
|
|
10
|
+
from fluidattacks_gitlab_sdk.members.core import Member
|
|
11
|
+
|
|
12
|
+
LOG = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def decode_member(raw: JsonObj, id_project: ProjectId) -> ResultE[Member]:
|
|
16
|
+
return smash.smash_result_2(
|
|
17
|
+
JsonUnfolder.require(raw, "username", DecodeUtils.to_str),
|
|
18
|
+
JsonUnfolder.require(raw, "name", DecodeUtils.to_str),
|
|
19
|
+
).map(lambda m: Member(m[0], m[1], id_project))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def decode_member_and_id(raw: JsonObj, id_project: ProjectId) -> ResultE[tuple[MemberId, Member]]:
|
|
23
|
+
return smash.smash_result_2(
|
|
24
|
+
JsonUnfolder.require(raw, "id", DecodeUtils.to_int).bind(Natural.from_int).map(MemberId),
|
|
25
|
+
decode_member(raw, id_project),
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def decode_members(
|
|
30
|
+
members: FrozenList[JsonObj],
|
|
31
|
+
id_project: ProjectId,
|
|
32
|
+
) -> ResultE[FrozenList[tuple[MemberId, Member]]]:
|
|
33
|
+
if not members:
|
|
34
|
+
return Result.failure(ValueError("Expected a list of members"))
|
|
35
|
+
|
|
36
|
+
return ResultTransform.all_ok(
|
|
37
|
+
PureIterFactory.from_list(members)
|
|
38
|
+
.map(lambda c: decode_member_and_id(c, id_project))
|
|
39
|
+
.to_list(),
|
|
40
|
+
)
|
|
@@ -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 Cmd, FrozenList, ResultE
|
|
7
|
+
|
|
8
|
+
from fluidattacks_gitlab_sdk.ids import MemberId, ProjectId
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class Member:
|
|
13
|
+
member_user_name: str
|
|
14
|
+
member_name: str
|
|
15
|
+
id_project: ProjectId
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class MemberClient:
|
|
20
|
+
get_members: Callable[
|
|
21
|
+
[ProjectId],
|
|
22
|
+
Cmd[ResultE[FrozenList[tuple[MemberId, Member]]]],
|
|
23
|
+
]
|