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

1 import sys

2 import itertools

3 from threading import Lock

4

5 from collections import defaultdict

6

7 from zapy.base import ZapyAuto

8 from zapy.templating.templating import evaluate, render

9 from zapy.templating.eval import sync_exec

10

11 from .exceptions import error_location

12 from .context import ZapyRequestContext, build_context_module

13 from .hooks import RequestHook

14 from .models import KeyValueItem, ZapyRequest, HttpxArguments

15 from .file_loader import ZapyFileInfo

16

17

18 FORM_TYPES = [

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

20 'multipart/form-data',

21]

22

23 class 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)