Coverage for audoma/operations.py: 81%

64 statements  

« prev     ^ index     » next       coverage.py v6.4.2, created at 2022-08-08 06:12 +0000

1from dataclasses import dataclass 

2from inspect import isclass 

3from typing import ( 

4 Iterable, 

5 List, 

6 Type, 

7 Union, 

8) 

9 

10from rest_framework.exceptions import APIException 

11from rest_framework.request import Request 

12from rest_framework.response import Response 

13from rest_framework.serializers import BaseSerializer 

14 

15from django.db.models import Model 

16from django.views import View 

17 

18 

19@dataclass 

20class OperationExtractor: 

21 

22 collectors: Union[dict, Type[BaseSerializer]] 

23 results: Union[dict, Type[BaseSerializer], str] 

24 errors: List[Union[APIException, Type[APIException]]] 

25 

26 def _create_exception(self, options: dict) -> APIException: 

27 """ 

28 Create proper exception if error status code, found defined in the response. 

29 Args: 

30 * options - kwargs for APIException instance. 

31 Returns: APIException instance. 

32 """ 

33 status_code = options.pop("status_code") 

34 exception = APIException(**options) 

35 exception.status_code = status_code if status_code else exception.status_code 

36 return exception 

37 

38 def _extract_response_operation( 

39 self, request: Request, code: int 

40 ) -> Union[dict, Type[BaseSerializer], str, APIException]: 

41 """ 

42 Extracts response operation from the defined in audoma_action dictionary. 

43 

44 Args: 

45 * request - request object 

46 * code - response status code, this should be passed as an integer. 

47 Returns: Serializer instance, dictionary, string or APIException. 

48 """ 

49 if code >= 400: 

50 error_data = self.errors.get(code, {}) 

51 error_kwargs = { 

52 "status_code": code, 

53 "detail": error_data.get("detail"), 

54 "code": error_data.get("error_code"), 

55 } 

56 return self._create_exception(error_kwargs) 

57 

58 if not self.results or ( 

59 isclass(self.results) and issubclass(self.results, BaseSerializer) 

60 ): 

61 return self.results 

62 

63 if isinstance(list(self.results.keys())[0], str) and request: 

64 method = request.method.lower() 

65 response = self.results.get(method) 

66 if code and isinstance(response, dict): 

67 response = response.get(code) 

68 return response 

69 

70 elif isinstance(list(self.results.keys())[0], int) and code: 

71 response = self.results.get(code) 

72 return response 

73 

74 return None 

75 

76 def _extract_collect_operation(self, request: Request) -> Type[BaseSerializer]: 

77 """ 

78 Extracts collect operation from the defined in audoma_action dictionary. 

79 

80 Args: 

81 * request - request object 

82 Returns: Serializer class. 

83 """ 

84 if not self.collectors or ( 

85 isclass(self.collectors) and issubclass(self.collectors, BaseSerializer) 

86 ): 

87 return self.collectors 

88 

89 method = request.method.lower() 

90 return self.collectors.get(method) 

91 

92 def extract_operation( 

93 self, request: Request, code: int = None, operation_category="response" 

94 ) -> Union[dict, Type[BaseSerializer], str, APIException]: 

95 """ 

96 Extracts proper operation from the defined in audoma_action dictionary. 

97 Args: 

98 * request - request object 

99 * code - response status code, this should be given only for responses. 

100 It should be given as an integer 

101 * operation_category - operation category, it can be either "response" or "collect". 

102 This determines if the extracted operation should be either response or request operation. 

103 Returns: Serializer instance, dictionary, string or APIException. 

104 """ 

105 if operation_category == "response": 

106 return self._extract_response_operation(request, code) 

107 elif operation_category == "collect": 

108 return self._extract_collect_operation(request) 

109 else: 

110 raise ValueError("Unknown operation_category") 

111 

112 

113def apply_response_operation( 

114 operation: Union[str, APIException, Type[BaseSerializer]], 

115 instance: Union[Iterable, str, APIException, Model], 

116 code: int, 

117 view: View, 

118 many: bool, 

119) -> Response: 

120 """ 

121 Applies response operation for automa_action decorator. 

122 Args: 

123 * operation: response operation wich will be applied, it may be a string message, APIException object, or 

124 resoponse serializer_class instance 

125 * instance: instance which will be returned in a response. It may be iterable, string, ApiException object, 

126 or simply model instance. If it's dict/model instance it'll be serialized. 

127 * code - response status code, this should be given as an integer 

128 * view - view instance for which the response will be returned. 

129 * many - boolean value which determines whether the serializer 

130 will handle multiple instances, or just singular instnace. 

131 

132 Returns: Response object. 

133 """ 

134 if isinstance(operation, APIException): 

135 raise operation 

136 

137 if isinstance(operation, str): 

138 instance = instance or operation 

139 instance = {"message": instance} 

140 return Response(instance, status=code) 

141 

142 serializer_class = operation 

143 

144 if instance: 

145 if isinstance(instance, Iterable) and not isinstance(instance, dict): 

146 serializer_kwargs = {"data": instance, "many": True} 

147 else: 

148 serializer_kwargs = {"instance": instance, "many": False} 

149 else: 

150 serializer_kwargs = {"many": many} 

151 serializer_kwargs.update({"context": {"request": view.request}}) 

152 

153 return_serializer = ( 

154 serializer_class(**serializer_kwargs) 

155 if serializer_class 

156 else view.get_result_serializer(**serializer_kwargs) 

157 ) 

158 

159 headers = view.get_success_headers(return_serializer.data) 

160 

161 return Response(return_serializer.data, status=code, headers=headers)