Coverage for src / lexigram / contracts / web / http_models.py: 78%
27 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-19 05:41 +0800
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-19 05:41 +0800
1"""Framework-owned HTTP response model.
3Decouples consumers from ``aiohttp`` (or any other HTTP library) by providing
4a transport-agnostic response object.
5"""
7from __future__ import annotations
9from dataclasses import dataclass, field
10from typing import Any
13@dataclass(frozen=True)
14class HttpResponse:
15 """Framework-owned representation of an HTTP response.
17 Wraps the raw response produced by the underlying HTTP library (e.g.
18 ``aiohttp``) so that consumers never need to import transport-level
19 objects.
21 Attributes:
22 status: HTTP status code (e.g. 200, 404).
23 headers: Response headers as a plain ``dict[str, str]``.
24 body: Raw response body bytes.
25 text: Response body decoded as UTF-8 text; empty string when absent.
26 json: Parsed JSON payload; ``None`` when the response is not JSON.
27 url: Final (possibly redirected) URL as a string.
28 method: HTTP method of the originating request (upper-cased).
29 content_length: Parsed ``Content-Length`` response header value, or
30 ``None`` when absent or non-numeric.
31 """
33 status: int
34 headers: dict[str, str] = field(default_factory=dict)
35 body: bytes = b""
36 text: str = ""
37 json: Any = None
38 url: str = ""
39 method: str = ""
40 content_length: int | None = None
42 def raise_for_status(self) -> None:
43 """Raise :class:`HttpStatusError` when the status code indicates failure.
45 Raises:
46 HttpStatusError: For any 4xx or 5xx status code.
47 """
48 if self.status >= 400:
49 raise HttpStatusError(
50 f"HTTP {self.status} for {self.method} {self.url}",
51 status=self.status,
52 response=self,
53 )
55 @property
56 def Ok(self) -> bool:
57 """``True`` when the status code is below 400."""
58 return self.status < 400
61from lexigram.contracts.exceptions.infra import InfrastructureError
64class HttpStatusError(InfrastructureError):
65 """Raised by :meth:`HttpResponse.raise_for_status` for 4xx/5xx responses.
67 Attributes:
68 status: HTTP status code that triggered this error.
69 response: The originating :class:`HttpResponse` instance.
70 """
72 _code: str = "LEX_ERR_WEB_002"
74 def __init__(
75 self,
76 message: str,
77 *,
78 status: int,
79 response: HttpResponse,
80 **kwargs: Any,
81 ) -> None:
82 super().__init__(
83 message=message,
84 code=f"HTTP_{status}",
85 details={"status": status, "url": response.url, "method": response.method},
86 **kwargs,
87 )
88 self.status = status
89 self.response = response
92__all__ = ["HttpResponse", "HttpStatusError"]