Coverage for src\zapy\requests\requester.py: 88%

96 statements  

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

1from typing import List 

2from dataclasses import dataclass, field 

3import inspect 

4import asyncio 

5 

6import httpx 

7 

8from zapy.base import Metadata 

9from zapy.test import run_tests, TestResult 

10from zapy.store import Store, use_store 

11from zapy.utils import functools 

12from zapy.templating.traceback import annotate_traceback 

13 

14from .exceptions import error_location, RenderLocationException 

15from .models import ZapyRequest, HttpxArguments 

16from .context import ZapyRequestContext 

17from .hooks import use_global_hook 

18from .converter import RequestConverter 

19 

20 

21@dataclass 

22class RequesterResponse: 

23 response: httpx.Response 

24 test_result: List[TestResult] = field(default_factory=list) 

25 

26_http_request_signature = inspect.signature(httpx.Client.build_request).parameters 

27 

28 

29class Requester: 

30 

31 def __init__(self, zapy_request: ZapyRequest, converter: RequestConverter, client: httpx.AsyncClient): 

32 self.zapy_request = zapy_request 

33 self.converter = converter 

34 self.client = client 

35 self.__hook_context = { 

36 Metadata: self.zapy_request.metadata, 

37 ZapyRequest: self.zapy_request, 

38 } 

39 

40 async def make_request(self) -> RequesterResponse: 

41 httpx_args = self.converter.build_httpx_args() 

42 

43 # Hook: pre_request 

44 await self._invoke_hooks_pre_request(httpx_args) 

45 

46 # send request 

47 request_parameters, rest_parameters = self._split_parameters(httpx_args) 

48 request = self.client.build_request(**request_parameters) 

49 response = await self.client.send(request, **rest_parameters) 

50 

51 # Hook: post_request 

52 try: 

53 await self._invoke_hooks_post_request(response) 

54 except RenderLocationException as ex: 

55 ex.context["response"] = response 

56 raise 

57 

58 response_wrapper = RequesterResponse(response) 

59 

60 # Hook: test 

61 if self.request_hooks.test: 

62 response_wrapper.test_result = self._run_test( 

63 httpx_args = httpx_args, 

64 request = request, 

65 response = response, 

66 ) 

67 

68 return response_wrapper 

69 

70 @error_location('pre_request') 

71 async def _invoke_hooks_pre_request(self, httpx_args: dict): 

72 try: 

73 await self.__call_hook(use_global_hook().pre_request, httpx_args) 

74 await self.__call_hook(self.request_hooks.pre_request, httpx_args) 

75 except BaseException as e: 

76 annotate_traceback(e, self.converter.script, location='hook') 

77 raise e 

78 

79 @error_location('post_request') 

80 async def _invoke_hooks_post_request(self, response: httpx.Response): 

81 try: 

82 await self.__call_hook(use_global_hook().post_request, response) 

83 await self.__call_hook(self.request_hooks.post_request, response) 

84 except Exception as e: 

85 annotate_traceback(e, self.converter.script, location='hook') 

86 raise e 

87 

88 def _run_test(self, **args) -> dict: 

89 class RequestMeta(type): 

90 def __new__(cls, name, bases, attrs): 

91 for k, v in args.items(): 

92 attrs[k] = v 

93 return super().__new__(cls, name, bases, attrs) 

94 class DecoratedClass(self.request_hooks.test, metaclass=RequestMeta): 

95 pass 

96 

97 test_result = run_tests(DecoratedClass).as_list() 

98 return test_result 

99 

100 def _split_parameters(self, httpx_args: HttpxArguments): 

101 request_parameters, rest_parameters = dict(), dict() 

102 for k, v in httpx_args.items(): 

103 if k in _http_request_signature: 103 ↛ 106line 103 didn't jump to line 106, because the condition on line 103 was never false

104 request_parameters[k] = v 

105 else: 

106 rest_parameters[k] = v 

107 return request_parameters, rest_parameters 

108 

109 async def __call_hook(self, hook, *args): 

110 result = functools.call_with_signature(hook, *args, kwargs=self.__hook_context) 

111 if asyncio.iscoroutine(result): 111 ↛ 113line 111 didn't jump to line 113, because the condition on line 111 was never false

112 return await result 

113 return result 

114 

115 @property 

116 def request_hooks(self): 

117 return self.converter.request_hooks 

118 

119async def send_request(zapy_request: ZapyRequest, *, store: Store | None = None, logger=print, client: httpx.AsyncClient | None =None) -> RequesterResponse: 

120 if store is None: 

121 store = use_store() 

122 ctx = ZapyRequestContext( 

123 store = store, 

124 logger = logger, 

125 ) 

126 _client = client or httpx.AsyncClient(follow_redirects=True, timeout=None) 

127 try: 

128 request = build_request(zapy_request, ctx, client=_client) 

129 return await request.make_request() 

130 finally: 

131 if client is None: 131 ↛ exitline 131 didn't return from function 'send_request', because the return on line 129 wasn't executed

132 await _client.aclose() 

133 

134def build_request(zapy_request: ZapyRequest, ctx: ZapyRequestContext, client: httpx.AsyncClient) -> Requester: 

135 converter = RequestConverter(zapy_request, ctx) 

136 requester = Requester(zapy_request, converter, client) 

137 

138 return requester