pyqwest 0.3.0__cp310-cp310-musllinux_1_2_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyqwest/__init__.py +49 -0
- pyqwest/_coro.py +227 -0
- pyqwest/_glue.py +97 -0
- pyqwest/_pyqwest.cpython-310-x86_64-linux-gnu.so +0 -0
- pyqwest/_pyqwest.pyi +1390 -0
- pyqwest/httpx/__init__.py +5 -0
- pyqwest/httpx/_transport.py +281 -0
- pyqwest/py.typed +0 -0
- pyqwest/testing/__init__.py +6 -0
- pyqwest/testing/_asgi.py +360 -0
- pyqwest/testing/_asgi_compatibility.py +80 -0
- pyqwest/testing/_wsgi.py +373 -0
- pyqwest-0.3.0.dist-info/METADATA +21 -0
- pyqwest-0.3.0.dist-info/RECORD +17 -0
- pyqwest-0.3.0.dist-info/WHEEL +4 -0
- pyqwest-0.3.0.dist-info/licenses/LICENSE +21 -0
- pyqwest.libs/libgcc_s-6d2d9dc8.so.1 +0 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Includes work from:
|
|
2
|
+
|
|
3
|
+
# Copyright (c) Django Software Foundation and individual contributors.
|
|
4
|
+
# All rights reserved.
|
|
5
|
+
#
|
|
6
|
+
# Redistribution and use in source and binary forms, with or without modification,
|
|
7
|
+
# are permitted provided that the following conditions are met:
|
|
8
|
+
#
|
|
9
|
+
# 1. Redistributions of source code must retain the above copyright notice,
|
|
10
|
+
# this list of conditions and the following disclaimer.
|
|
11
|
+
#
|
|
12
|
+
# 2. Redistributions in binary form must reproduce the above copyright
|
|
13
|
+
# notice, this list of conditions and the following disclaimer in the
|
|
14
|
+
# documentation and/or other materials provided with the distribution.
|
|
15
|
+
#
|
|
16
|
+
# 3. Neither the name of Django nor the names of its contributors may be used
|
|
17
|
+
# to endorse or promote products derived from this software without
|
|
18
|
+
# specific prior written permission.
|
|
19
|
+
#
|
|
20
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
21
|
+
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
22
|
+
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
|
24
|
+
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
25
|
+
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
26
|
+
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
27
|
+
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
28
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
29
|
+
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
import asyncio
|
|
34
|
+
import inspect
|
|
35
|
+
from typing import TYPE_CHECKING, cast
|
|
36
|
+
|
|
37
|
+
if TYPE_CHECKING:
|
|
38
|
+
from asgiref.typing import (
|
|
39
|
+
ASGI2Application,
|
|
40
|
+
ASGI3Application,
|
|
41
|
+
ASGIApplication,
|
|
42
|
+
ASGIReceiveCallable,
|
|
43
|
+
ASGISendCallable,
|
|
44
|
+
Scope,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Vendored from https://github.com/django/asgiref/blob/main/asgiref/compatibility.py
|
|
48
|
+
|
|
49
|
+
if hasattr(inspect, "markcoroutinefunction"):
|
|
50
|
+
iscoroutinefunction = inspect.iscoroutinefunction
|
|
51
|
+
else:
|
|
52
|
+
iscoroutinefunction = asyncio.iscoroutinefunction # type: ignore[assignment]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def is_double_callable(application: ASGIApplication) -> bool:
|
|
56
|
+
if getattr(application, "_asgi_single_callable", False):
|
|
57
|
+
return False
|
|
58
|
+
if getattr(application, "_asgi_double_callable", False):
|
|
59
|
+
return True
|
|
60
|
+
if inspect.isclass(application):
|
|
61
|
+
return True
|
|
62
|
+
if callable(application) and iscoroutinefunction(application.__call__):
|
|
63
|
+
return False
|
|
64
|
+
return not iscoroutinefunction(application)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def double_to_single_callable(application: ASGI2Application) -> ASGI3Application:
|
|
68
|
+
async def new_application(
|
|
69
|
+
scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable
|
|
70
|
+
) -> None:
|
|
71
|
+
instance = application(scope)
|
|
72
|
+
return await instance(receive, send)
|
|
73
|
+
|
|
74
|
+
return new_application
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def guarantee_single_callable(application: ASGIApplication) -> ASGI3Application:
|
|
78
|
+
if is_double_callable(application):
|
|
79
|
+
application = double_to_single_callable(cast("ASGI2Application", application))
|
|
80
|
+
return cast("ASGI3Application", application)
|
pyqwest/testing/_wsgi.py
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import contextlib
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
from collections.abc import Callable, Iterator
|
|
7
|
+
from concurrent.futures import Future, ThreadPoolExecutor
|
|
8
|
+
from queue import Empty, Queue
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
from urllib.parse import unquote, urlparse
|
|
11
|
+
|
|
12
|
+
from pyqwest import (
|
|
13
|
+
Headers,
|
|
14
|
+
HTTPVersion,
|
|
15
|
+
ReadError,
|
|
16
|
+
SyncRequest,
|
|
17
|
+
SyncResponse,
|
|
18
|
+
SyncTransport,
|
|
19
|
+
WriteError,
|
|
20
|
+
)
|
|
21
|
+
from pyqwest._pyqwest import get_sync_timeout
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
import sys
|
|
25
|
+
|
|
26
|
+
if sys.version_info >= (3, 11):
|
|
27
|
+
from wsgiref.types import WSGIApplication, WSGIEnvironment
|
|
28
|
+
else:
|
|
29
|
+
from _typeshed.wsgi import WSGIApplication, WSGIEnvironment
|
|
30
|
+
|
|
31
|
+
_UNSET_STATUS = "unset"
|
|
32
|
+
|
|
33
|
+
_DEFAULT_EXECUTOR: ThreadPoolExecutor | None = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_default_executor() -> ThreadPoolExecutor:
|
|
37
|
+
global _DEFAULT_EXECUTOR # noqa: PLW0603
|
|
38
|
+
if _DEFAULT_EXECUTOR is None:
|
|
39
|
+
_DEFAULT_EXECUTOR = ThreadPoolExecutor()
|
|
40
|
+
return _DEFAULT_EXECUTOR
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class WSGITransport(SyncTransport):
|
|
44
|
+
"""Transport implementation that directly invokes a WSGI application. Useful for testing."""
|
|
45
|
+
|
|
46
|
+
_app: WSGIApplication
|
|
47
|
+
_http_version: HTTPVersion
|
|
48
|
+
_closed: bool
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
app: WSGIApplication,
|
|
53
|
+
http_version: HTTPVersion = HTTPVersion.HTTP2,
|
|
54
|
+
executor: ThreadPoolExecutor | None = None,
|
|
55
|
+
) -> None:
|
|
56
|
+
"""Creates a new WSGI transport.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
app: The WSGI application to invoke for requests.
|
|
60
|
+
http_version: The HTTP version to simulate for requests.
|
|
61
|
+
executor: An optional ThreadPoolExecutor to use for running the WSGI app.
|
|
62
|
+
If not provided, a default executor will be used.
|
|
63
|
+
"""
|
|
64
|
+
self._app = app
|
|
65
|
+
self._http_version = http_version
|
|
66
|
+
self._executor = executor or get_default_executor()
|
|
67
|
+
self._closed = False
|
|
68
|
+
|
|
69
|
+
def execute_sync(self, request: SyncRequest) -> SyncResponse:
|
|
70
|
+
deadline = None
|
|
71
|
+
if (to := get_sync_timeout()) is not None:
|
|
72
|
+
deadline = time.monotonic() + to.total_seconds()
|
|
73
|
+
|
|
74
|
+
parsed_url = urlparse(request.url)
|
|
75
|
+
raw_path = parsed_url.path or "/"
|
|
76
|
+
path = unquote(raw_path).encode().decode("latin-1")
|
|
77
|
+
query = parsed_url.query.encode().decode("latin-1")
|
|
78
|
+
|
|
79
|
+
match self._http_version:
|
|
80
|
+
case HTTPVersion.HTTP1:
|
|
81
|
+
server_protocol = "HTTP/1.1"
|
|
82
|
+
case HTTPVersion.HTTP2:
|
|
83
|
+
server_protocol = "HTTP/2"
|
|
84
|
+
case HTTPVersion.HTTP3:
|
|
85
|
+
server_protocol = "HTTP/3"
|
|
86
|
+
case _:
|
|
87
|
+
server_protocol = "HTTP/1.1"
|
|
88
|
+
|
|
89
|
+
trailers = Headers()
|
|
90
|
+
trailers_supported = (
|
|
91
|
+
self._http_version == HTTPVersion.HTTP2
|
|
92
|
+
and request.headers.get("te", "") == "trailers"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def send_trailers(headers: list[tuple[str, str]]) -> None:
|
|
96
|
+
if not trailers_supported:
|
|
97
|
+
return
|
|
98
|
+
for k, v in headers:
|
|
99
|
+
trailers.add(k, v)
|
|
100
|
+
|
|
101
|
+
request_input = RequestInput(request.content, self._http_version)
|
|
102
|
+
environ: WSGIEnvironment = {
|
|
103
|
+
"REQUEST_METHOD": request.method,
|
|
104
|
+
"SCRIPT_NAME": "",
|
|
105
|
+
"PATH_INFO": path,
|
|
106
|
+
"QUERY_STRING": query,
|
|
107
|
+
"SERVER_NAME": parsed_url.hostname or "",
|
|
108
|
+
"SERVER_PORT": str(
|
|
109
|
+
parsed_url.port or (443 if parsed_url.scheme == "https" else 80)
|
|
110
|
+
),
|
|
111
|
+
"SERVER_PROTOCOL": server_protocol,
|
|
112
|
+
"wsgi.url_scheme": parsed_url.scheme,
|
|
113
|
+
"wsgi.version": (1, 0),
|
|
114
|
+
"wsgi.multithread": True,
|
|
115
|
+
"wsgi.multiprocess": False,
|
|
116
|
+
"wsgi.run_once": False,
|
|
117
|
+
"wsgi.input": request_input,
|
|
118
|
+
"wsgi.ext.http.send_trailers": send_trailers,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
for k, v in request.headers.items():
|
|
122
|
+
match k:
|
|
123
|
+
case "content-type":
|
|
124
|
+
environ["CONTENT_TYPE"] = v
|
|
125
|
+
case "content-length":
|
|
126
|
+
environ["CONTENT_LENGTH"] = v
|
|
127
|
+
case _:
|
|
128
|
+
name = f"HTTP_{k.upper().replace('-', '_')}"
|
|
129
|
+
value = f"{existing},{v}" if (existing := environ.get(name)) else v
|
|
130
|
+
environ[name] = value
|
|
131
|
+
|
|
132
|
+
response_queue: Queue[bytes | None | Exception] = Queue()
|
|
133
|
+
|
|
134
|
+
status_str: str = _UNSET_STATUS
|
|
135
|
+
headers: list[tuple[str, str]] = []
|
|
136
|
+
exc: (
|
|
137
|
+
tuple[type[BaseException], BaseException, object]
|
|
138
|
+
| tuple[None, None, None]
|
|
139
|
+
| None
|
|
140
|
+
) = None
|
|
141
|
+
response_started = threading.Event()
|
|
142
|
+
|
|
143
|
+
def start_response(
|
|
144
|
+
status: str,
|
|
145
|
+
response_headers: list[tuple[str, str]],
|
|
146
|
+
exc_info: tuple[type[BaseException], BaseException, object]
|
|
147
|
+
| tuple[None, None, None]
|
|
148
|
+
| None = None,
|
|
149
|
+
) -> Callable[[bytes], object]:
|
|
150
|
+
nonlocal status_str, headers, exc
|
|
151
|
+
status_str = status
|
|
152
|
+
headers = response_headers
|
|
153
|
+
exc = exc_info
|
|
154
|
+
|
|
155
|
+
def write(body: bytes) -> None:
|
|
156
|
+
if not response_started.is_set():
|
|
157
|
+
response_started.set()
|
|
158
|
+
if body:
|
|
159
|
+
response_queue.put(body)
|
|
160
|
+
|
|
161
|
+
return write
|
|
162
|
+
|
|
163
|
+
def run_app() -> None:
|
|
164
|
+
response_iter = self._app(environ, start_response)
|
|
165
|
+
try:
|
|
166
|
+
for chunk in response_iter:
|
|
167
|
+
if chunk:
|
|
168
|
+
if not response_started.is_set():
|
|
169
|
+
response_started.set()
|
|
170
|
+
response_queue.put(chunk)
|
|
171
|
+
except Exception as e:
|
|
172
|
+
response_queue.put(e)
|
|
173
|
+
else:
|
|
174
|
+
response_queue.put(None)
|
|
175
|
+
finally:
|
|
176
|
+
if not response_started.is_set():
|
|
177
|
+
request_input.close()
|
|
178
|
+
response_started.set()
|
|
179
|
+
with contextlib.suppress(Exception):
|
|
180
|
+
response_iter.close() # pyright: ignore[reportAttributeAccessIssue]
|
|
181
|
+
|
|
182
|
+
app_future = self._executor.submit(run_app)
|
|
183
|
+
|
|
184
|
+
response_started.wait()
|
|
185
|
+
|
|
186
|
+
if status_str is _UNSET_STATUS:
|
|
187
|
+
return SyncResponse(
|
|
188
|
+
status=500,
|
|
189
|
+
http_version=self._http_version,
|
|
190
|
+
headers=Headers((("content-type", "text/plain"),)),
|
|
191
|
+
content=b"WSGI application did not call start_response",
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
if exc and exc[0]:
|
|
195
|
+
return SyncResponse(
|
|
196
|
+
status=500,
|
|
197
|
+
http_version=self._http_version,
|
|
198
|
+
headers=Headers((("content-type", "text/plain"),)),
|
|
199
|
+
content=str(exc[0]).encode(),
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
response_content = ResponseContent(
|
|
203
|
+
response_queue, request_input, app_future, deadline
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
status = int(status_str.split(" ", 1)[0])
|
|
207
|
+
|
|
208
|
+
return SyncResponse(
|
|
209
|
+
status=status,
|
|
210
|
+
http_version=self._http_version,
|
|
211
|
+
headers=Headers(headers),
|
|
212
|
+
content=response_content,
|
|
213
|
+
trailers=trailers,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class RequestInput:
|
|
218
|
+
def __init__(self, content: Iterator[bytes], http_version: HTTPVersion) -> None:
|
|
219
|
+
self._content = content
|
|
220
|
+
self._http_version = http_version
|
|
221
|
+
self._closed = False
|
|
222
|
+
self._buffer = bytearray()
|
|
223
|
+
|
|
224
|
+
def read(self, size: int = -1) -> bytes:
|
|
225
|
+
return self._do_read(size)
|
|
226
|
+
|
|
227
|
+
def readline(self, size: int = -1) -> bytes:
|
|
228
|
+
if self._closed or size == 0:
|
|
229
|
+
return b""
|
|
230
|
+
|
|
231
|
+
line = bytearray()
|
|
232
|
+
while True:
|
|
233
|
+
sz = size - len(line) if size >= 0 else -1
|
|
234
|
+
read_bytes = self._do_read(sz)
|
|
235
|
+
if not read_bytes:
|
|
236
|
+
return bytes(line)
|
|
237
|
+
if len(line) + len(read_bytes) == size:
|
|
238
|
+
return bytes(line + read_bytes)
|
|
239
|
+
newline_index = read_bytes.find(b"\n")
|
|
240
|
+
if newline_index == -1:
|
|
241
|
+
line.extend(read_bytes)
|
|
242
|
+
continue
|
|
243
|
+
res = line + read_bytes[: newline_index + 1]
|
|
244
|
+
self._buffer.extend(read_bytes[newline_index + 1 :])
|
|
245
|
+
return bytes(res)
|
|
246
|
+
|
|
247
|
+
def __iter__(self) -> Iterator[bytes]:
|
|
248
|
+
return self
|
|
249
|
+
|
|
250
|
+
def __next__(self) -> bytes:
|
|
251
|
+
line = self.readline()
|
|
252
|
+
if not line:
|
|
253
|
+
raise StopIteration
|
|
254
|
+
return line
|
|
255
|
+
|
|
256
|
+
def readlines(self, hint: int = -1) -> list[bytes]:
|
|
257
|
+
return list(self)
|
|
258
|
+
|
|
259
|
+
def _do_read(self, size: int) -> bytes:
|
|
260
|
+
if self._closed or size == 0:
|
|
261
|
+
return b""
|
|
262
|
+
|
|
263
|
+
try:
|
|
264
|
+
while True:
|
|
265
|
+
chunk = next(self._content)
|
|
266
|
+
if size < 0:
|
|
267
|
+
self._buffer.extend(chunk)
|
|
268
|
+
continue
|
|
269
|
+
if len(self._buffer) + len(chunk) >= size:
|
|
270
|
+
to_read = size - len(self._buffer)
|
|
271
|
+
res = self._buffer + chunk[:to_read]
|
|
272
|
+
self._buffer.clear()
|
|
273
|
+
self._buffer.extend(chunk[to_read:])
|
|
274
|
+
return bytes(res)
|
|
275
|
+
if len(self._buffer) == 0:
|
|
276
|
+
return chunk
|
|
277
|
+
res = self._buffer + chunk
|
|
278
|
+
self._buffer.clear()
|
|
279
|
+
return bytes(res)
|
|
280
|
+
except StopIteration:
|
|
281
|
+
self.close()
|
|
282
|
+
res = bytes(self._buffer)
|
|
283
|
+
self._buffer = bytearray()
|
|
284
|
+
return res
|
|
285
|
+
except Exception as e:
|
|
286
|
+
self.close()
|
|
287
|
+
if self._http_version != HTTPVersion.HTTP2:
|
|
288
|
+
msg = f"Request failed: {e}"
|
|
289
|
+
else:
|
|
290
|
+
# With HTTP/2, reqwest seems to squash the original error message.
|
|
291
|
+
msg = "Request failed: stream error sent by user"
|
|
292
|
+
raise WriteError(msg) from e
|
|
293
|
+
|
|
294
|
+
def close(self) -> None:
|
|
295
|
+
if self._closed:
|
|
296
|
+
return
|
|
297
|
+
self._closed = True
|
|
298
|
+
with contextlib.suppress(Exception):
|
|
299
|
+
self._content.close() # pyright: ignore[reportAttributeAccessIssue]
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
class ResponseContent(Iterator[bytes]):
|
|
303
|
+
def __init__(
|
|
304
|
+
self,
|
|
305
|
+
response_queue: Queue[bytes | None | Exception],
|
|
306
|
+
request_input: RequestInput,
|
|
307
|
+
app_future: Future,
|
|
308
|
+
deadline: float | None,
|
|
309
|
+
) -> None:
|
|
310
|
+
self._response_queue = response_queue
|
|
311
|
+
self._request_input = request_input
|
|
312
|
+
self._app_future = app_future
|
|
313
|
+
self._closed = False
|
|
314
|
+
self._read_pending = False
|
|
315
|
+
self._deadline = deadline
|
|
316
|
+
|
|
317
|
+
def __iter__(self) -> Iterator[bytes]:
|
|
318
|
+
return self
|
|
319
|
+
|
|
320
|
+
def __next__(self) -> bytes:
|
|
321
|
+
if self._closed:
|
|
322
|
+
raise StopIteration
|
|
323
|
+
err: Exception | None = None
|
|
324
|
+
self._read_pending = True
|
|
325
|
+
chunk = b""
|
|
326
|
+
try:
|
|
327
|
+
if self._deadline:
|
|
328
|
+
while True:
|
|
329
|
+
time_left = self._deadline - time.monotonic()
|
|
330
|
+
if time_left <= 0:
|
|
331
|
+
msg = "Response read timed out"
|
|
332
|
+
message = TimeoutError(msg)
|
|
333
|
+
break
|
|
334
|
+
try:
|
|
335
|
+
message = self._response_queue.get(timeout=time_left)
|
|
336
|
+
break
|
|
337
|
+
except Empty:
|
|
338
|
+
continue
|
|
339
|
+
else:
|
|
340
|
+
message = self._response_queue.get()
|
|
341
|
+
finally:
|
|
342
|
+
self._read_pending = False
|
|
343
|
+
if isinstance(message, Exception):
|
|
344
|
+
match message:
|
|
345
|
+
case WriteError() | TimeoutError():
|
|
346
|
+
err = message
|
|
347
|
+
case _:
|
|
348
|
+
msg = "Request Failed: Error reading response body"
|
|
349
|
+
err = ReadError(msg)
|
|
350
|
+
elif message is None:
|
|
351
|
+
err = StopIteration()
|
|
352
|
+
else:
|
|
353
|
+
chunk = message
|
|
354
|
+
|
|
355
|
+
if err:
|
|
356
|
+
self._closed = True
|
|
357
|
+
self._request_input.close()
|
|
358
|
+
with contextlib.suppress(Exception):
|
|
359
|
+
self._app_future.result()
|
|
360
|
+
raise err
|
|
361
|
+
return chunk
|
|
362
|
+
|
|
363
|
+
def __del__(self) -> None:
|
|
364
|
+
self.close()
|
|
365
|
+
|
|
366
|
+
def close(self) -> None:
|
|
367
|
+
if self._closed:
|
|
368
|
+
return
|
|
369
|
+
self._closed = True
|
|
370
|
+
self._request_input.close()
|
|
371
|
+
self._response_queue.put(ReadError("Response body read cancelled"))
|
|
372
|
+
with contextlib.suppress(Exception):
|
|
373
|
+
self._app_future.result()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyqwest
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Classifier: Development Status :: 3 - Alpha
|
|
5
|
+
Classifier: Intended Audience :: Developers
|
|
6
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
7
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
8
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
9
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
10
|
+
Classifier: Programming Language :: Python
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
18
|
+
Classifier: Programming Language :: Rust
|
|
19
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Python: >=3.10
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
pyqwest/__init__.py,sha256=TqZ4ioJhL_RsgALEnb3iChY7gb9ysWc4cUA0k9rsjXI,902
|
|
2
|
+
pyqwest/_coro.py,sha256=k5vVz1zeg9HQYvbbhAg9WnYiBHskkJ3Qdqfmx16HcVs,7867
|
|
3
|
+
pyqwest/_glue.py,sha256=gk--twq1LRc-tsaDVwcIxcwPmaMz5kt94QQGJ0fTNFw,2530
|
|
4
|
+
pyqwest/_pyqwest.cpython-310-x86_64-linux-gnu.so,sha256=FrNjRQi7kdNc-LyIh8xelIg67ZVCtThXIqjvVHAVzWY,13754809
|
|
5
|
+
pyqwest/_pyqwest.pyi,sha256=QFGjqouSggefDP6lZH-bJ2aQbEJbJI7dTuREdLOwaIc,45175
|
|
6
|
+
pyqwest/httpx/__init__.py,sha256=4DJZ0AMFfuScGLkd9ktwGMypll7RW6VhAMenwcEWkDw,157
|
|
7
|
+
pyqwest/httpx/_transport.py,sha256=3Os8YB3Wq9dFvvYugMsXKmpM6JmuUSuSbTzBbOSf-Uw,9378
|
|
8
|
+
pyqwest/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
pyqwest/testing/__init__.py,sha256=oNW9UbowpENKwUt0QvBzHFWLWq4_7b1LMfMz3QmftQE,148
|
|
10
|
+
pyqwest/testing/_asgi.py,sha256=SXYcCTmND2b-SDn18Kf8qDcBwjhh2SejD_fEs6Zz2Qs,12203
|
|
11
|
+
pyqwest/testing/_asgi_compatibility.py,sha256=30rCjvyLg5L9x4vyI0LS8oqPtfY4oLctINJgqMzgLDU,3206
|
|
12
|
+
pyqwest/testing/_wsgi.py,sha256=I8IZ8Dekwj_-OxiHqGZqPJP0sZs4-EywU2GELcWo_Bg,12313
|
|
13
|
+
pyqwest-0.3.0.dist-info/METADATA,sha256=9g9EE8GB_Qbyl-BRDYHmNsylvR0u1jrZookvPz9CLQ4,886
|
|
14
|
+
pyqwest-0.3.0.dist-info/WHEEL,sha256=ftiaH7jtHy0QpDRPGYWuzIo-zyw63qIXsluwmqB1244,108
|
|
15
|
+
pyqwest-0.3.0.dist-info/licenses/LICENSE,sha256=e7sDs6pM-apXvhJO1bLtzngKeI1aQ6bDIyJxO7Sgv1E,1085
|
|
16
|
+
pyqwest.libs/libgcc_s-6d2d9dc8.so.1,sha256=K3sghe0OS1atTYJSB8m0plft2csyIC36q20Ii8UMudc,536145
|
|
17
|
+
pyqwest-0.3.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) CurioSwitch (oss@curioswitch.org)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
Binary file
|