Coverage for src/httpx/_request.py: 97%

35 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-06-09 10:38 +0100

1import typing 

2 

3from ._content import Content 

4from ._streams import ByteStream, Stream 

5from ._headers import Headers 

6from ._urls import URL 

7 

8__all__ = ["Request"] 

9 

10 

11class Request: 

12 def __init__( 

13 self, 

14 method: str, 

15 url: URL | str, 

16 headers: Headers | typing.Mapping[str, str] | None = None, 

17 content: Content | Stream | bytes | None = None, 

18 ): 

19 self.method = method 

20 self.url = URL(url) 

21 self.headers = Headers(headers) 

22 self.stream: Stream = ByteStream(b"") 

23 

24 # https://datatracker.ietf.org/doc/html/rfc2616#section-14.23 

25 # RFC 2616, Section 14.23, Host. 

26 # 

27 # A client MUST include a Host header field in all HTTP/1.1 request messages. 

28 if "Host" not in self.headers: 

29 self.headers = self.headers.copy_set("Host", self.url.netloc) 

30 

31 if content is not None: 

32 if isinstance(content, bytes): 

33 self.stream = ByteStream(content) 

34 elif isinstance(content, Stream): 

35 self.stream = content 

36 elif isinstance(content, Content): 

37 assert isinstance(content, Content) 

38 # Eg. Request("POST", "https://www.example.com", content=Form(...)) 

39 stream, content_type = content.encode() 

40 self.headers = self.headers.copy_set("Content-Type", content_type) 

41 self.stream = stream 

42 else: 

43 raise TypeError(f'Expected `Content | Stream | bytes | None` got {type(content)}') 

44 

45 # https://datatracker.ietf.org/doc/html/rfc2616#section-4.3 

46 # RFC 2616, Section 4.3, Message Body. 

47 # 

48 # The presence of a message-body in a request is signaled by the 

49 # inclusion of a Content-Length or Transfer-Encoding header field in 

50 # the request's message-headers. 

51 content_length: int | None = self.stream.size 

52 if content_length is None: 

53 self.headers = self.headers.copy_set("Transfer-Encoding", "chunked") 

54 elif content_length > 0: 

55 self.headers = self.headers.copy_set("Content-Length", str(content_length)) 

56 

57 def read(self): 

58 self.body = b"".join([part for part in self.stream]) 

59 self.stream = ByteStream(self.body) 

60 

61 def __repr__(self): 

62 return f"<Request [{self.method} {str(self.url)!r}]>"