Coverage for audoma/operations.py: 81%

1 from dataclasses import dataclass

2 from inspect import isclass

3 from typing import (

4 Iterable,

5 List,

6 Type,

7 Union,

8)

9

10 from rest_framework.exceptions import APIException

11 from rest_framework.request import Request

12 from rest_framework.response import Response

13 from rest_framework.serializers import BaseSerializer

14

15 from django.db.models import Model

16 from django.views import View

17

18

19@dataclass

20 class 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

113 def 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)