4
4
from dataclasses import dataclass , field
5
5
from datetime import date , datetime
6
6
from enum import Enum
7
- from typing import Any , ClassVar , Dict , List , Literal , Optional , Type , cast
7
+ from typing import (
8
+ TYPE_CHECKING ,
9
+ Any ,
10
+ ClassVar ,
11
+ Dict ,
12
+ Generic ,
13
+ List ,
14
+ Literal ,
15
+ Optional ,
16
+ Type ,
17
+ TypeVar ,
18
+ cast ,
19
+ )
8
20
from uuid import UUID
9
21
10
22
import marshmallow_dataclass
13
25
Schema ,
14
26
ValidationError ,
15
27
fields ,
28
+ post_dump ,
16
29
post_load ,
17
30
pre_load ,
18
31
validate ,
28
41
)
29
42
30
43
44
+ if TYPE_CHECKING :
45
+ import requests
46
+
47
+
31
48
class ToDictMixin :
32
49
"""
33
50
Provides a type-safe `to_dict()` method for classes using Marshmallow
@@ -54,8 +71,8 @@ class FromDictMixin:
54
71
SCHEMA : ClassVar [Schema ]
55
72
56
73
@classmethod
57
- def from_dict (cls , dct : Dict [str , Any ]) -> Self :
58
- return cast (Self , cls .SCHEMA .load (dct ))
74
+ def from_dict (cls , dct : Dict [str , Any ], many : Optional [ bool ] = None ) -> Self :
75
+ return cast (Self , cls .SCHEMA .load (dct , many = many ))
59
76
60
77
61
78
class BaseSchema (Schema ):
@@ -1077,3 +1094,146 @@ def __repr__(self) -> str:
1077
1094
marshmallow_dataclass .class_schema (SecretIncident , base_schema = BaseSchema ),
1078
1095
)
1079
1096
SecretIncident .SCHEMA = SecretIncidentSchema ()
1097
+
1098
+
1099
+ class AccessLevel (str , Enum ):
1100
+ OWNER = "owner"
1101
+ MANAGER = "manager"
1102
+ MEMBER = "member"
1103
+ RESTRICTED = "restricted"
1104
+
1105
+
1106
+ class PaginationParameter (Base , FromDictMixin ):
1107
+ """Pagination mixin used for endpoints that support pagination."""
1108
+
1109
+ cursor : str = ""
1110
+ per_page : int = 20
1111
+
1112
+
1113
+ class SearchParameter (Base , FromDictMixin ):
1114
+ search : Optional [str ] = None
1115
+
1116
+
1117
+ PaginatedData = TypeVar ("PaginatedData" , bound = FromDictMixin )
1118
+
1119
+
1120
+ @dataclass
1121
+ class CursorPaginatedResponse (Generic [PaginatedData ]):
1122
+ status_code : int
1123
+ data : list [PaginatedData ]
1124
+ prev : Optional [str ] = None
1125
+ next : Optional [str ] = None
1126
+
1127
+ @classmethod
1128
+ def from_response (
1129
+ cls , response : "requests.Response" , data_type : Type [PaginatedData ]
1130
+ ) -> "CursorPaginatedResponse[PaginatedData]" :
1131
+ data = cast (
1132
+ list [PaginatedData ], data_type .from_dict (response .json (), many = True )
1133
+ )
1134
+ paginated_response = cls (status_code = response .status_code , data = data )
1135
+
1136
+ if previous_page := response .links .get ("prev" ):
1137
+ paginated_response .prev = previous_page ["url" ]
1138
+ if next_page := response .links .get ("next" ):
1139
+ paginated_response .prev = next_page ["url" ]
1140
+
1141
+ return paginated_response
1142
+
1143
+
1144
+ @dataclass
1145
+ class MembersParameters (PaginationParameter , SearchParameter , Base , FromDictMixin ):
1146
+ """
1147
+ Members query parameters
1148
+ """
1149
+
1150
+ access_level : Optional [AccessLevel ] = None
1151
+ active : Optional [bool ] = None
1152
+ ordering : Optional [
1153
+ Literal ["id" , "-id" , "created_at" , "-created_at" , "last_login" , "-last_login" ]
1154
+ ] = None
1155
+
1156
+
1157
+ MembersParametersSchema = cast (
1158
+ Type [BaseSchema ],
1159
+ marshmallow_dataclass .class_schema (MembersParameters , base_schema = BaseSchema ),
1160
+ )
1161
+ MembersParameters .SCHEMA = MembersParametersSchema ()
1162
+
1163
+
1164
+ @dataclass
1165
+ class Member (Base , FromDictMixin ):
1166
+ """
1167
+ Member represents a user in a GitGuardian account.
1168
+ """
1169
+
1170
+ id : int
1171
+ access_level : AccessLevel
1172
+ email : str
1173
+ name : str
1174
+ created_at : datetime
1175
+ last_login : Optional [datetime ]
1176
+ active : bool
1177
+
1178
+
1179
+ class MemberSchema (BaseSchema ):
1180
+ id = fields .Int (required = True )
1181
+ access_level = fields .Enum (AccessLevel , by_value = True , required = True )
1182
+ email = fields .Str (required = True )
1183
+ name = fields .Str (required = True )
1184
+ created_at = fields .AwareDateTime (required = True )
1185
+ last_login = fields .AwareDateTime (allow_none = True )
1186
+ active = fields .Bool (required = True )
1187
+
1188
+ @post_load
1189
+ def return_member (
1190
+ self ,
1191
+ data : list [dict [str , Any ]] | dict [str , Any ],
1192
+ ** kwargs : dict [str , Any ],
1193
+ ):
1194
+ data = cast (dict [str , Any ], data )
1195
+ return Member (** data )
1196
+
1197
+
1198
+ Member .SCHEMA = MemberSchema ()
1199
+
1200
+
1201
+ class UpdateMemberSchema (BaseSchema ):
1202
+ id = fields .Int (required = True )
1203
+ access_level = fields .Enum (AccessLevel , by_value = True , allow_none = True )
1204
+ active = fields .Bool (allow_none = True )
1205
+
1206
+ @post_dump
1207
+ def access_level_value (
1208
+ self , data : dict [str , Any ], ** kwargs : dict [str , Any ]
1209
+ ) -> dict [str , Any ]:
1210
+ if "access_level" in data :
1211
+ data ["access_level" ] = AccessLevel (data ["access_level" ]).value
1212
+ return data
1213
+
1214
+
1215
+ @dataclass
1216
+ class UpdateMember (Base , FromDictMixin ):
1217
+ """
1218
+ UpdateMember represnets the payload to update a member
1219
+ """
1220
+
1221
+ id : int
1222
+ access_level : Optional [AccessLevel ] = None
1223
+ active : Optional [bool ] = None
1224
+
1225
+
1226
+ UpdateMember .SCHEMA = UpdateMemberSchema ()
1227
+
1228
+
1229
+ @dataclass
1230
+ class DeleteMember (Base , FromDictMixin ):
1231
+ id : int
1232
+ send_email : Optional [bool ] = None
1233
+
1234
+
1235
+ DeleteMemberSchema = cast (
1236
+ Type [BaseSchema ],
1237
+ marshmallow_dataclass .class_schema (DeleteMember , base_schema = BaseSchema ),
1238
+ )
1239
+ DeleteMember .SCHEMA = DeleteMemberSchema ()
0 commit comments