fluidattacks_gitlab_sdk 3.0.3__tar.gz → 4.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.
Files changed (96) hide show
  1. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/PKG-INFO +1 -1
  2. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/__init__.py +1 -1
  3. fluidattacks_gitlab_sdk-4.0.0/fluidattacks_gitlab_sdk/mr_notes/__init__.py +4 -0
  4. fluidattacks_gitlab_sdk-4.0.0/fluidattacks_gitlab_sdk/mr_notes/_client.py +113 -0
  5. fluidattacks_gitlab_sdk-4.0.0/fluidattacks_gitlab_sdk/mr_notes/_core.py +23 -0
  6. fluidattacks_gitlab_sdk-4.0.0/fluidattacks_gitlab_sdk/mr_notes/_decode.py +24 -0
  7. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/pyproject.toml +1 -1
  8. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/tests/arch/arch.py +6 -1
  9. fluidattacks_gitlab_sdk-4.0.0/tests/mr_notes/data.json +62 -0
  10. fluidattacks_gitlab_sdk-4.0.0/tests/mr_notes/decoder_test.py +65 -0
  11. fluidattacks_gitlab_sdk-4.0.0/tests_fx/py.typed +0 -0
  12. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/uv.lock +22 -13
  13. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/.envrc +0 -0
  14. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/build/default.nix +0 -0
  15. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/build/filter.nix +0 -0
  16. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/flake.lock +0 -0
  17. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/flake.nix +0 -0
  18. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/_decoders.py +0 -0
  19. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/_gql_client/__init__.py +0 -0
  20. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/_gql_client/_client.py +0 -0
  21. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/_gql_client/_error.py +0 -0
  22. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/_gql_client/_handlers.py +0 -0
  23. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/_handlers.py +0 -0
  24. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/_http_client/__init__.py +0 -0
  25. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/_http_client/_client_1.py +0 -0
  26. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/_http_client/_core.py +0 -0
  27. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/_logger.py +0 -0
  28. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/ids.py +0 -0
  29. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/issues/__init__.py +0 -0
  30. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/issues/_client/__init__.py +0 -0
  31. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/issues/_client/_decode.py +0 -0
  32. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/issues/_client/_get_issue.py +0 -0
  33. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/issues/_client/_most_recent.py +0 -0
  34. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/issues/_client/_updated_by.py +0 -0
  35. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/issues/core.py +0 -0
  36. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/jobs/__init__.py +0 -0
  37. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/jobs/_client.py +0 -0
  38. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/jobs/_decode.py +0 -0
  39. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/jobs/core.py +0 -0
  40. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/members/__init__.py +0 -0
  41. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/members/_client.py +0 -0
  42. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/members/_decode.py +0 -0
  43. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/members/core.py +0 -0
  44. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/merge_requests/__init__.py +0 -0
  45. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/merge_requests/_client.py +0 -0
  46. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/merge_requests/_decode.py +0 -0
  47. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/merge_requests/core.py +0 -0
  48. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/milestones/__init__.py +0 -0
  49. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/milestones/_client.py +0 -0
  50. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/milestones/_decode.py +0 -0
  51. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/milestones/core.py +0 -0
  52. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/mr_approvals/__init__.py +0 -0
  53. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/mr_approvals/_client.py +0 -0
  54. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/mr_approvals/_decode.py +0 -0
  55. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/mr_approvals/core.py +0 -0
  56. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/py.typed +0 -0
  57. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/users/__init__.py +0 -0
  58. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/users/core.py +0 -0
  59. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/fluidattacks_gitlab_sdk/users/decode.py +0 -0
  60. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/mypy.ini +0 -0
  61. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/ruff.toml +0 -0
  62. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/tests/__init__.py +0 -0
  63. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/tests/arch/__init__.py +0 -0
  64. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/tests/arch/test_arch.py +0 -0
  65. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/tests/issues/__init__.py +0 -0
  66. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/tests/issues/data.json +0 -0
  67. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/tests/issues/test_decode.py +0 -0
  68. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/tests/jobs/__init__.py +0 -0
  69. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/tests/jobs/data.json +0 -0
  70. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/tests/jobs/test_decode_job.py +0 -0
  71. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/tests/members/__init__.py +0 -0
  72. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/tests/members/data.json +0 -0
  73. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/tests/members/test_decode_members.py +0 -0
  74. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/tests/merge_request/__init__.py +0 -0
  75. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/tests/merge_request/data.json +0 -0
  76. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/tests/merge_request/data_mr_updates.json +0 -0
  77. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/tests/merge_request/test_decode.py +0 -0
  78. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/tests/mr_approvals/__init__.py +0 -0
  79. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/tests/mr_approvals/data.json +0 -0
  80. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/tests/mr_approvals/decoder_test.py +0 -0
  81. {fluidattacks_gitlab_sdk-3.0.3/tests_fx → fluidattacks_gitlab_sdk-4.0.0/tests/mr_notes}/__init__.py +0 -0
  82. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/tests/py.typed +0 -0
  83. {fluidattacks_gitlab_sdk-3.0.3/tests_fx/issues → fluidattacks_gitlab_sdk-4.0.0/tests_fx}/__init__.py +0 -0
  84. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/tests_fx/_utils.py +0 -0
  85. {fluidattacks_gitlab_sdk-3.0.3/tests_fx/jobs → fluidattacks_gitlab_sdk-4.0.0/tests_fx/issues}/__init__.py +0 -0
  86. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/tests_fx/issues/test_issue_client.py +0 -0
  87. {fluidattacks_gitlab_sdk-3.0.3/tests_fx/members → fluidattacks_gitlab_sdk-4.0.0/tests_fx/jobs}/__init__.py +0 -0
  88. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/tests_fx/jobs/client_test.py +0 -0
  89. {fluidattacks_gitlab_sdk-3.0.3/tests_fx/milestones → fluidattacks_gitlab_sdk-4.0.0/tests_fx/members}/__init__.py +0 -0
  90. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/tests_fx/members/test_client_members.py +0 -0
  91. {fluidattacks_gitlab_sdk-3.0.3/tests_fx/mr_approvals → fluidattacks_gitlab_sdk-4.0.0/tests_fx/milestones}/__init__.py +0 -0
  92. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/tests_fx/milestones/client_test.py +0 -0
  93. /fluidattacks_gitlab_sdk-3.0.3/tests_fx/py.typed → /fluidattacks_gitlab_sdk-4.0.0/tests_fx/mr_approvals/__init__.py +0 -0
  94. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/tests_fx/mr_approvals/client_test.py +0 -0
  95. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/tests_fx/test_gql_client.py +0 -0
  96. {fluidattacks_gitlab_sdk-3.0.3 → fluidattacks_gitlab_sdk-4.0.0}/tests_fx/test_mr.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fluidattacks_gitlab_sdk
3
- Version: 3.0.3
3
+ Version: 4.0.0
4
4
  Summary: gitlab SDK
5
5
  Author-email: Product Team <development@fluidattacks.com>
6
6
  Requires-Python: >=3.11
@@ -11,7 +11,7 @@ from ._logger import (
11
11
  set_logger,
12
12
  )
13
13
 
14
- __version__ = "3.0.3"
14
+ __version__ = "4.0.0"
15
15
 
16
16
  Unsafe.compute(set_logger(__name__, __version__))
17
17
  LOG = logging.getLogger(__name__)
@@ -0,0 +1,4 @@
1
+ from ._client import MrNotesFactory
2
+ from ._core import MrNote, NotesClient
3
+
4
+ __all__ = ["MrNote", "MrNotesFactory", "NotesClient"]
@@ -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-01", fluid-blends = "2099-01-01" }
37
+ exclude-newer-package = { fluidattacks-core = "2099-01-01", fluid-blends = "2099-01-01" , fluidattacks-utils-logger = "2099-01-01" }
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
 
@@ -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
@@ -6,6 +6,15 @@ resolution-markers = [
6
6
  "python_full_version < '3.15'",
7
7
  ]
8
8
 
9
+ [options]
10
+ exclude-newer = "2026-06-30T22:09:12.854339Z"
11
+ exclude-newer-span = "P7D"
12
+
13
+ [options.exclude-newer-package]
14
+ fluid-blends = "2099-01-02T00:00:00Z"
15
+ fluidattacks-utils-logger = "2099-01-02T00:00:00Z"
16
+ fluidattacks-core = "2099-01-02T00:00:00Z"
17
+
9
18
  [manifest]
10
19
  overrides = [{ name = "cryptography", specifier = "==48.0.1" }]
11
20
 
@@ -345,18 +354,18 @@ wheels = [
345
354
 
346
355
  [[package]]
347
356
  name = "fluidattacks-core"
348
- version = "8.2.0"
357
+ version = "9.3.0"
349
358
  source = { registry = "https://pypi.org/simple" }
350
359
  wheels = [
351
- { url = "https://files.pythonhosted.org/packages/26/8f/23a49b485d7ab7ac5d2bb9fc69e5229213fbb1e900d863edcca9a4837b95/fluidattacks_core-8.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cb8c447fc19bacd25a9c29ebaeb17b2961338d5223dda3c78dc067fbc0a1aca7", size = 7332195, upload-time = "2026-06-12T22:36:11.385Z" },
352
- { url = "https://files.pythonhosted.org/packages/cd/51/9f34ef276040327a1045e96d291b209fd8307e4da909f70145c1f38121f2/fluidattacks_core-8.2.0-cp311-cp311-manylinux_2_39_aarch64.whl", hash = "sha256:ee6d266216d787d6027570281e4ad2dd4f5a81d93da8d094f8b4f1032f28b7a0", size = 8483155, upload-time = "2026-06-12T22:36:13.974Z" },
353
- { url = "https://files.pythonhosted.org/packages/5f/bb/7f68a44281faf2672f027809e604acc350fbbafe9bb21a13ae73d629e9f8/fluidattacks_core-8.2.0-cp311-cp311-manylinux_2_39_x86_64.whl", hash = "sha256:9487ed3ba4875d99c79ff38f1d2c15c9d4982f3449da661c35da043ae66e18cc", size = 8473961, upload-time = "2026-06-12T22:36:23.289Z" },
354
- { url = "https://files.pythonhosted.org/packages/ec/7a/79891b0dd195687035d944742fdc68d13d1df5a3ecf03ef3f2040a2e6117/fluidattacks_core-8.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:729b1639c7fe18bc8131d4958e935e2b73bf9b38f8c66ae0648894ae2da26ea8", size = 7330127, upload-time = "2026-06-12T22:36:25.096Z" },
355
- { url = "https://files.pythonhosted.org/packages/9d/86/b0f665cc3761ee9acd60351cfaf78c4356430c1297d19847fd72fdd10ce0/fluidattacks_core-8.2.0-cp312-cp312-manylinux_2_39_aarch64.whl", hash = "sha256:1c15069eb91d7966001f1fc12e4569b7923364e634869375c01d7315a548e3ca", size = 8487797, upload-time = "2026-06-12T22:36:21.387Z" },
356
- { url = "https://files.pythonhosted.org/packages/74/66/359ca98a709b46f2f4686acf670c23baef6ac21ca83c535f72db21741826/fluidattacks_core-8.2.0-cp312-cp312-manylinux_2_39_x86_64.whl", hash = "sha256:9e51410b1fadf64484545b810c482d53baf4e5c39dfdb04e2762b3045da579a1", size = 8473395, upload-time = "2026-06-12T22:36:19.508Z" },
357
- { url = "https://files.pythonhosted.org/packages/b1/37/51295f9a14128a18113e24df5ee814e0a940a3627116ee033fb508f63c7a/fluidattacks_core-8.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:71493dd5eba680d1845e5e8ce2e75b80c917e81de6e2f3bba0f7c6e511c95e36", size = 7330498, upload-time = "2026-06-12T22:36:17.968Z" },
358
- { url = "https://files.pythonhosted.org/packages/49/05/f9d408a01c1378c648a6605e7d25c0808c2f8cab2e6dbb6b00c45f6b7caa/fluidattacks_core-8.2.0-cp313-cp313-manylinux_2_39_aarch64.whl", hash = "sha256:a874418ccb3eb097d85d3ac18bf644853ed65cc46c4f38ba3a3176ac89dcbd83", size = 8487257, upload-time = "2026-06-12T22:36:09.29Z" },
359
- { url = "https://files.pythonhosted.org/packages/c3/a0/a9eb9483b6f88936e160decdecfd945367418605ed7d2384d952135633fe/fluidattacks_core-8.2.0-cp313-cp313-manylinux_2_39_x86_64.whl", hash = "sha256:4d86b354ded45db31270a75de46414383f1b04d452e610cf55f798c78d836979", size = 8474079, upload-time = "2026-06-12T22:36:15.973Z" },
360
+ { url = "https://files.pythonhosted.org/packages/27/b2/d956e9939c166d4b5a8929f66cf5dc5edce877d648956ec8ad291afc42b7/fluidattacks_core-9.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1ec5dba7ef4537da0717fe14fbfff8ff161aeb8bcacb15a624894140a1c4291e", size = 6608034, upload-time = "2026-07-07T18:58:59.653Z" },
361
+ { url = "https://files.pythonhosted.org/packages/2e/5c/aa071cff0e301f906604e6d8e691fd86b412c6a99ba484119e16886ebf73/fluidattacks_core-9.3.0-cp311-cp311-manylinux_2_39_aarch64.whl", hash = "sha256:b08a5318bf02e59b1f9842ac9414fbf21fb6397f0ddd108c182f9c2d2acf6368", size = 7598882, upload-time = "2026-07-07T18:59:06.246Z" },
362
+ { url = "https://files.pythonhosted.org/packages/3c/60/a89b39fa1ce5aeba5171bb83dbfe026a25c3a14791818374364fd550efdc/fluidattacks_core-9.3.0-cp311-cp311-manylinux_2_39_x86_64.whl", hash = "sha256:5a04c40e2e515ab96313eb98c34c83963a5084649d423aaf07adad2d701de007", size = 7631955, upload-time = "2026-07-07T18:59:04.637Z" },
363
+ { url = "https://files.pythonhosted.org/packages/90/90/99e1d19021316387edf7c536e1e8be1f89bbf48a7bcefebaea8472dfa003/fluidattacks_core-9.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4425c3ed98e0f374a5beac4a5aebbf9c308b13603305ab3bf9fa75dd8f40831c", size = 6602958, upload-time = "2026-07-07T18:59:07.945Z" },
364
+ { url = "https://files.pythonhosted.org/packages/e4/26/7d5e9f29e7d9b739776b4f76c46562bbf1bdd8024c579b9d49d7f24a2fd0/fluidattacks_core-9.3.0-cp312-cp312-manylinux_2_39_aarch64.whl", hash = "sha256:b16ead5cb8af27186ed348072ba952d15e9a0209d90c7678fe4a94d11733e25e", size = 7595799, upload-time = "2026-07-07T18:59:09.597Z" },
365
+ { url = "https://files.pythonhosted.org/packages/9b/37/cb1cd7b60fab669d3d9c6233ef2e7ef8030ef7ff8fb8c3d84de91c9588a7/fluidattacks_core-9.3.0-cp312-cp312-manylinux_2_39_x86_64.whl", hash = "sha256:10e2ede5108197daa7d8ac9e2e02e9b86eb3d470dab70db134681d6e845163f2", size = 7625910, upload-time = "2026-07-07T18:59:01.614Z" },
366
+ { url = "https://files.pythonhosted.org/packages/e2/cb/5e610ff985b10f1beeffb2d8872903adc0e92deede845af095792b31e129/fluidattacks_core-9.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e9eb00fbbe449b43414a97a6585fb7fd72b85c3fb848cdfe9996aa20ecec53dc", size = 6602582, upload-time = "2026-07-07T18:58:58.141Z" },
367
+ { url = "https://files.pythonhosted.org/packages/43/f6/2141cbce306a07bfcde9c36c70c8f38f4f8ef7a350fac9a9a48de8a8f361/fluidattacks_core-9.3.0-cp313-cp313-manylinux_2_39_aarch64.whl", hash = "sha256:018b3cf09f08b92fb6b54a74cad52f75da817ee1a38a612b5ddeb6e5da666865", size = 7595633, upload-time = "2026-07-07T18:59:03.109Z" },
368
+ { url = "https://files.pythonhosted.org/packages/c7/7c/aef426597596d5e5b5cb3e7c1ea8723b3cd4010da86e0fe5926af76cddec/fluidattacks_core-9.3.0-cp313-cp313-manylinux_2_39_x86_64.whl", hash = "sha256:5865553a7c3374690a644852d70ed597ebd77b827be84fb213f19cc79c8dca9d", size = 7626399, upload-time = "2026-07-07T18:58:56.348Z" },
360
369
  ]
361
370
 
362
371
  [[package]]
@@ -430,16 +439,16 @@ dev = [
430
439
 
431
440
  [[package]]
432
441
  name = "fluidattacks-utils-logger"
433
- version = "3.0.2"
442
+ version = "3.0.3"
434
443
  source = { registry = "https://pypi.org/simple" }
435
444
  dependencies = [
436
445
  { name = "fa-purity" },
437
446
  { name = "fluidattacks-core" },
438
447
  { name = "python-json-logger" },
439
448
  ]
440
- sdist = { url = "https://files.pythonhosted.org/packages/fd/fd/0b2f5f10d966417bfa4876067ec759902e47c5999fd0be392d56260c0f83/fluidattacks_utils_logger-3.0.2.tar.gz", hash = "sha256:ed5d4c4ec8f27e3b1db4ee4ebc9ec201737deaa538f743c1e71bbb11b807842e", size = 49647, upload-time = "2026-06-19T20:43:22.985Z" }
449
+ sdist = { url = "https://files.pythonhosted.org/packages/2b/3e/349b0eeda139fa281a92fdd6246f7b341ed10a0397b5680a525efaa9f7a5/fluidattacks_utils_logger-3.0.3.tar.gz", hash = "sha256:94753730cd3f7b1c4e763df562dc7acc512bc20f63e46b6935af35da152ddbd3", size = 49723, upload-time = "2026-07-03T23:08:11.876Z" }
441
450
  wheels = [
442
- { url = "https://files.pythonhosted.org/packages/a5/c0/d4395f4c158e8e6ce34aaaa687429f3f8f73686215543ce09ed3487d4fa1/fluidattacks_utils_logger-3.0.2-py3-none-any.whl", hash = "sha256:10908e6104ae7f6761619827258ac044a8473156d0a8390f9319f74bdf40e197", size = 3826, upload-time = "2026-06-19T20:43:22.059Z" },
451
+ { url = "https://files.pythonhosted.org/packages/2c/33/5a75a47c6e9c9d21cb080f55ba17eca8de0a0d8aabcd303d7cfd4f3bc942/fluidattacks_utils_logger-3.0.3-py3-none-any.whl", hash = "sha256:01067b226a10761904590cdbf675c8c181f32872511cd66c184a95062f6ffbdc", size = 3825, upload-time = "2026-07-03T23:08:11.078Z" },
443
452
  ]
444
453
 
445
454
  [[package]]