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
« 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
6import httpx
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
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
21@dataclass
22class RequesterResponse:
23 response: httpx.Response
24 test_result: List[TestResult] = field(default_factory=list)
26_http_request_signature = inspect.signature(httpx.Client.build_request).parameters
29class Requester:
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 }
40 async def make_request(self) -> RequesterResponse:
41 httpx_args = self.converter.build_httpx_args()
43 # Hook: pre_request
44 await self._invoke_hooks_pre_request(httpx_args)
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)
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
58 response_wrapper = RequesterResponse(response)
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 )
68 return response_wrapper
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
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
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
97 test_result = run_tests(DecoratedClass).as_list()
98 return test_result
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
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
115 @property
116 def request_hooks(self):
117 return self.converter.request_hooks
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()
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)
138 return requester