This repository was archived by the owner on Aug 26, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathclient.py
156 lines (132 loc) · 5.14 KB
/
client.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
import typing
import typing
from httpx import AsyncClient
from httpx.config import TimeoutTypes
from httpx.content_streams import ByteStream
from httpx.models import Request, Response
from httpx.dispatch.base import AsyncDispatcher
from base64 import b64encode
import json
import itsdangerous
class TestClient(AsyncClient):
__test__ = False
def __init__(self, app, raise_server_exceptions=True):
from source.settings import SECRET
dispatch = ASGIDispatch(app=app, raise_app_exceptions=raise_server_exceptions)
self.signer = itsdangerous.TimestampSigner(SECRET)
super().__init__(
base_url="https://testserver",
dispatch=dispatch,
headers={"accept": "text/html; */*"},
)
def login(self, user):
session = {"username": user["username"], "avatar_url": user["avatar_url"]}
data = b64encode(json.dumps(session).encode("utf-8"))
data = self.signer.sign(data)
self.cookies.set("session", data.decode("ascii"))
class ASGIDispatch(AsyncDispatcher):
"""
A custom AsyncDispatcher that handles sending requests directly to an ASGI app.
The simplest way to use this functionality is to use the `app` argument.
```
client = httpx.AsyncClient(app=app)
```
Alternatively, you can setup the dispatch instance explicitly.
This allows you to include any additional configuration arguments specific
to the ASGIDispatch class:
```
dispatch = httpx.ASGIDispatch(
app=app,
root_path="/submount",
client=("1.2.3.4", 123)
)
client = httpx.AsyncClient(dispatch=dispatch)
```
Arguments:
* `app` - The ASGI application.
* `raise_app_exceptions` - Boolean indicating if exceptions in the application
should be raised. Default to `True`. Can be set to `False` for use cases
such as testing the content of a client 500 response.
* `root_path` - The root path on which the ASGI application should be mounted.
* `client` - A two-tuple indicating the client IP and port of incoming requests.
```
"""
def __init__(
self,
app: typing.Callable,
raise_app_exceptions: bool = True,
root_path: str = "",
client: typing.Tuple[str, int] = ("127.0.0.1", 123),
) -> None:
self.app = app
self.raise_app_exceptions = raise_app_exceptions
self.root_path = root_path
self.client = client
async def send(self, request: Request, timeout: TimeoutTypes = None) -> Response:
scope = {
"type": "http",
"asgi": {"version": "3.0"},
"http_version": "1.1",
"method": request.method,
"headers": request.headers.raw,
"scheme": request.url.scheme,
"path": request.url.path,
"query_string": request.url.query.encode("ascii"),
"server": request.url.host,
"client": self.client,
"root_path": self.root_path,
"extensions": ["http.response.template"],
}
status_code = None
headers = None
body_parts = []
response_started = False
response_complete = False
template = None
context = None
request_body_chunks = request.stream.__aiter__()
async def receive() -> dict:
try:
body = await request_body_chunks.__anext__()
except StopAsyncIteration:
return {"type": "http.request", "body": b"", "more_body": False}
return {"type": "http.request", "body": body, "more_body": True}
async def send(message: dict) -> None:
nonlocal status_code, headers, body_parts
nonlocal response_started, response_complete
nonlocal template, context
if message["type"] == "http.response.start":
assert not response_started
status_code = message["status"]
headers = message.get("headers", [])
response_started = True
elif message["type"] == "http.response.body":
assert not response_complete
body = message.get("body", b"")
more_body = message.get("more_body", False)
if body and request.method != "HEAD":
body_parts.append(body)
if not more_body:
response_complete = True
elif message["type"] == "http.response.template":
template = message["template"]
context = message["context"]
try:
await self.app(scope, receive, send)
except Exception:
if self.raise_app_exceptions or not response_complete:
raise
assert response_complete
assert status_code is not None
assert headers is not None
stream = ByteStream(b"".join(body_parts))
response = Response(
status_code=status_code,
http_version="HTTP/1.1",
headers=headers,
stream=stream,
request=request,
)
response.template = template
response.context = context
return response