44from dataclasses import dataclass , field
55from datetime import date , datetime
66from 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+ )
820from uuid import UUID
921
1022import marshmallow_dataclass
1325 Schema ,
1426 ValidationError ,
1527 fields ,
28+ post_dump ,
1629 post_load ,
1730 pre_load ,
1831 validate ,
2841)
2942
3043
44+ if TYPE_CHECKING :
45+ import requests
46+
47+
3148class ToDictMixin :
3249 """
3350 Provides a type-safe `to_dict()` method for classes using Marshmallow
@@ -54,8 +71,8 @@ class FromDictMixin:
5471 SCHEMA : ClassVar [Schema ]
5572
5673 @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 ))
5976
6077
6178class BaseSchema (Schema ):
@@ -1077,3 +1094,146 @@ def __repr__(self) -> str:
10771094 marshmallow_dataclass .class_schema (SecretIncident , base_schema = BaseSchema ),
10781095)
10791096SecretIncident .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