Coverage for human_requests/abstraction/response.py: 91%

46 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-05-28 00:39 +0000

1from __future__ import annotations 

2 

3from dataclasses import dataclass 

4from time import time 

5from typing import TYPE_CHECKING, Literal, Optional 

6 

7from .http import URL 

8from .json_debug import loads_json_debug 

9from .request import FetchRequest 

10 

11if TYPE_CHECKING: 

12 from ..human_page import HumanPage 

13 

14 

15@dataclass(frozen=True) 

16class FetchResponse: 

17 """Represents the response of a request.""" 

18 

19 request: FetchRequest 

20 """The request that was made.""" 

21 

22 page: "HumanPage" 

23 """The page that made the request.""" 

24 

25 url: URL 

26 """The URL of the response. Due to redirects, it can differ from `request.url`.""" 

27 

28 headers: dict 

29 """The headers of the response.""" 

30 

31 raw: bytes 

32 """The raw body of the response.""" 

33 

34 status_code: int 

35 """The status code of the response.""" 

36 

37 status_text: str 

38 """Человеко-читаемое представление status_code""" 

39 

40 redirected: bool 

41 """Был ли ответ сформировапн в следствии редиректа""" 

42 

43 type: Literal["basic", "cors", "error", "opaque", "opaqueredirect"] 

44 

45 duration: float 

46 """The duration of the request in seconds.""" 

47 

48 end_time: float 

49 """Current time in seconds since the Epoch.""" 

50 

51 @property 

52 def text(self) -> str: 

53 """The body of the response.""" 

54 defchar = "utf-8" 

55 ct = self.headers.get("content-type", "") 

56 charset = ct.split("charset=")[-1] if "charset=" in ct else defchar 

57 return self.raw.decode(charset, errors="replace") 

58 

59 def json(self) -> dict | list: 

60 to_return = loads_json_debug(self.text) 

61 assert isinstance(to_return, list) or isinstance( 

62 to_return, dict 

63 ), f"Response body is not JSON: {type(self.text).__name__}" 

64 return to_return 

65 

66 def seconds_ago(self) -> float: 

67 """How long ago was the request?""" 

68 return time() - self.end_time 

69 

70 async def render( 

71 self, 

72 retry: int = 2, 

73 timeout: Optional[float] = None, 

74 wait_until: Literal["commit", "load", "domcontentloaded", "networkidle"] = "commit", 

75 referer: Optional[str] = None, 

76 ) -> "HumanPage": 

77 """Renders the response content in the current browser. 

78 It will look like we requested it through the browser from the beginning. 

79 

80 Recommended to use in cases when the server returns a JS challenge instead of a response.""" 

81 page = await self.page.context.new_page() 

82 await page.goto_render( 

83 self, wait_until=wait_until, referer=referer, timeout=timeout, retry=retry 

84 ) 

85 return page