Coverage for src\zapy\requests\converter.py: 99%

93 statements  

« prev     ^ index     » next       coverage.py v7.3.4, created at 2023-12-20 14:17 -0500

1import sys 

2import itertools 

3from threading import Lock 

4 

5from collections import defaultdict 

6 

7from zapy.base import ZapyAuto 

8from zapy.templating.templating import evaluate, render 

9from zapy.templating.eval import sync_exec 

10 

11from .exceptions import error_location 

12from .context import ZapyRequestContext, build_context_module 

13from .hooks import RequestHook 

14from .models import KeyValueItem, ZapyRequest, HttpxArguments 

15from .file_loader import ZapyFileInfo 

16 

17 

18FORM_TYPES = [ 

19 'application/x-www-form-urlencoded', 

20 'multipart/form-data', 

21] 

22 

23class RequestConverter: 

24 

25 def __init__(self, zapy_request: ZapyRequest, ctx: ZapyRequestContext): 

26 self.zapy_request = zapy_request 

27 self.ctx = ctx 

28 

29 # evaluate script 

30 request_hooks, variables = self._load_script() 

31 self.request_hooks = request_hooks 

32 self.variables = variables 

33 

34 def build_httpx_args(self) -> HttpxArguments: 

35 zapy_request: ZapyRequest = self.zapy_request 

36 

37 # variable_declaration 

38 self.variables |= self._convert_variables(self.zapy_request.variables) 

39 

40 httpx_args = HttpxArguments( 

41 method=zapy_request.method, 

42 url=self._convert_url(zapy_request.endpoint), 

43 params=self._convert_params(zapy_request.params), 

44 headers=self._convert_headers(zapy_request.headers, 

45 body_content_type=zapy_request.body_type), 

46 **self._build_httpx_args_body(zapy_request.body_type, zapy_request.body), 

47 ) 

48 

49 return httpx_args 

50 

51 @error_location('body') 

52 def _build_httpx_args_body(self, body_type: str, body): 

53 files, data, content = None, None, None 

54 if body is None or body_type == 'None': 

55 data = None 

56 elif body_type in FORM_TYPES: 

57 data, files = self._convert_body_data(body) 

58 else: 

59 _body_source = self.__join_code(body) 

60 content = self.__render(_body_source) 

61 return { 

62 'files': files, 

63 'data': data, 

64 'content': content, 

65 } 

66 

67 @error_location('body') 

68 def _convert_body_data(self, data_list: list[KeyValueItem]): 

69 data_list: list[KeyValueItem] = filter(lambda x: x.active and x.key.strip(), data_list) 

70 result_dict = defaultdict(list) 

71 files = list() 

72 for param in data_list: 

73 value = self.__eval_var(param.value) 

74 if isinstance(value, ZapyFileInfo): 

75 file_info = ( 

76 value.file_name, 

77 open(value.file_location, mode="rb"), 

78 *([] if value.mime_type is ZapyAuto else [value.mime_type]) 

79 ) 

80 files.append((param.key, file_info)) 

81 else: 

82 result_dict[param.key].append(str(value)) 

83 

84 return dict(result_dict), files 

85 

86 @error_location('url') 

87 def _convert_url(self, endpoint) -> dict: 

88 return self.__render(endpoint) 

89 

90 @error_location('params') 

91 def _convert_params(self, parameter_list: list[KeyValueItem]) -> dict: 

92 active_params = filter(lambda x: x.active and x.key.strip(), parameter_list) 

93 groups = itertools.groupby(active_params, lambda x: x.key.strip()) 

94 result_dict = { 

95 key: [self.__render(p.value) for p in params] 

96 for key, params in groups 

97 } 

98 

99 return result_dict 

100 

101 @error_location('headers') 

102 def _convert_headers(self, header_list: list[KeyValueItem], body_content_type=None) -> dict: 

103 headers = dict() 

104 for x in header_list: 

105 key = x.key.strip() 

106 if not (x.active and key): 106 ↛ 107line 106 didn't jump to line 107, because the condition on line 106 was never true

107 continue 

108 eval_var = self.__eval_var(x.value) 

109 if key.lower() == 'content-type' and eval_var == ZapyAuto: 

110 if body_content_type not in ('None', 'multipart/form-data'): 

111 headers[key] = str(body_content_type) 

112 else: 

113 headers[key] = str(eval_var) 

114 

115 return headers 

116 

117 @error_location('variables') 

118 def _convert_variables(self, variable_list: list[KeyValueItem]): 

119 return { 

120 x.key.strip(): self.__eval_var(x.value) 

121 for x in variable_list if x.active and x.key.strip() 

122 } 

123 

124 @error_location('script') 

125 def _load_script(self) -> tuple[RequestHook, dict]: 

126 script = self.__join_code(self.zapy_request.script) 

127 self.script = script 

128 

129 module_context = build_context_module(self.ctx) 

130 vars = { 

131 'print' : self.ctx.logger, 

132 'ctx' : module_context, 

133 } 

134 

135 if script is None or not script.strip(): 

136 return RequestHook(), vars 

137 

138 with Lock(): 

139 sys.modules['zapy.ctx'] = module_context 

140 sync_exec(script, vars) 

141 request_hook = module_context.hooks.request_hook 

142 

143 return request_hook, vars 

144 

145 def __eval_var(self, value): 

146 return evaluate(value, self.variables) 

147 

148 def __render(self, source): 

149 return render(source, self.variables) 

150 

151 def __join_code(self, code): 

152 if type(code) == str: 

153 return code 

154 else: 

155 return "\n".join(code)