pyqwest 0.2.0__cp313-cp313-win_amd64.whl → 0.3.0__cp313-cp313-win_amd64.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 CHANGED
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  __all__ = [
4
4
  "Client",
5
5
  "FullResponse",
6
+ "HTTPHeaderName",
6
7
  "HTTPTransport",
7
8
  "HTTPVersion",
8
9
  "Headers",
@@ -27,6 +28,7 @@ from ._coro import Client, Response
27
28
  from ._pyqwest import (
28
29
  FullResponse,
29
30
  Headers,
31
+ HTTPHeaderName,
30
32
  HTTPTransport,
31
33
  HTTPVersion,
32
34
  ReadError,
pyqwest/_coro.py CHANGED
@@ -19,6 +19,10 @@ class Client:
19
19
 
20
20
  A client is a lightweight wrapper around a Transport, providing convenience methods
21
21
  for common HTTP operations with buffering.
22
+
23
+ The asynchronous client does not expose per-request timeouts on its methods.
24
+ Use `asyncio.wait_for` or similar to enforce timeouts per-requests or initialize
25
+ `HTTPTransport` with a default timeout.
22
26
  """
23
27
 
24
28
  _client: NativeClient
@@ -36,27 +40,26 @@ class Client:
36
40
  self,
37
41
  url: str,
38
42
  headers: Headers | Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
39
- timeout: float | None = None,
40
43
  ) -> FullResponse:
41
44
  """Executes a GET HTTP request.
42
45
 
43
46
  Args:
44
47
  url: The unencoded request URL.
45
48
  headers: The request headers.
46
- timeout: The timeout for the request in seconds.
47
49
 
48
50
  Raises:
49
51
  ConnectionError: If the connection fails.
50
52
  TimeoutError: If the request times out.
53
+ ReadError: If an error occurs reading the response.
54
+ WriteError: If an error occurs writing the request.
51
55
  """
52
- return await self._client.get(url, headers=headers, timeout=timeout)
56
+ return await self._client.get(url, headers=headers)
53
57
 
54
58
  async def post(
55
59
  self,
56
60
  url: str,
57
61
  headers: Headers | Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
58
62
  content: bytes | AsyncIterator[bytes] | None = None,
59
- timeout: float | None = None,
60
63
  ) -> FullResponse:
61
64
  """Executes a POST HTTP request.
62
65
 
@@ -64,79 +67,77 @@ class Client:
64
67
  url: The unencoded request URL.
65
68
  headers: The request headers.
66
69
  content: The request content.
67
- timeout: The timeout for the request in seconds.
68
70
 
69
71
  Raises:
70
72
  ConnectionError: If the connection fails.
71
73
  TimeoutError: If the request times out.
74
+ ReadError: If an error occurs reading the response.
75
+ WriteError: If an error occurs writing the request.
72
76
  """
73
- return await self._client.post(
74
- url, headers=headers, content=content, timeout=timeout
75
- )
77
+ return await self._client.post(url, headers=headers, content=content)
76
78
 
77
79
  async def delete(
78
80
  self,
79
81
  url: str,
80
82
  headers: Headers | Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
81
- timeout: float | None = None,
82
83
  ) -> FullResponse:
83
84
  """Executes a DELETE HTTP request.
84
85
 
85
86
  Args:
86
87
  url: The unencoded request URL.
87
88
  headers: The request headers.
88
- timeout: The timeout for the request in seconds.
89
89
 
90
90
  Raises:
91
91
  ConnectionError: If the connection fails.
92
92
  TimeoutError: If the request times out.
93
+ ReadError: If an error occurs reading the response.
94
+ WriteError: If an error occurs writing the request.
93
95
  """
94
- return await self._client.delete(url, headers=headers, timeout=timeout)
96
+ return await self._client.delete(url, headers=headers)
95
97
 
96
98
  async def head(
97
99
  self,
98
100
  url: str,
99
101
  headers: Headers | Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
100
- timeout: float | None = None,
101
102
  ) -> FullResponse:
102
103
  """Executes a HEAD HTTP request.
103
104
 
104
105
  Args:
105
106
  url: The unencoded request URL.
106
107
  headers: The request headers.
107
- timeout: The timeout for the request in seconds.
108
108
 
109
109
  Raises:
110
110
  ConnectionError: If the connection fails.
111
111
  TimeoutError: If the request times out.
112
+ ReadError: If an error occurs reading the response.
113
+ WriteError: If an error occurs writing the request.
112
114
  """
113
- return await self._client.head(url, headers=headers, timeout=timeout)
115
+ return await self._client.head(url, headers=headers)
114
116
 
115
117
  async def options(
116
118
  self,
117
119
  url: str,
118
120
  headers: Headers | Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
119
- timeout: float | None = None,
120
121
  ) -> FullResponse:
121
122
  """Executes a OPTIONS HTTP request.
122
123
 
123
124
  Args:
124
125
  url: The unencoded request URL.
125
126
  headers: The request headers.
126
- timeout: The timeout for the request in seconds.
127
127
 
128
128
  Raises:
129
129
  ConnectionError: If the connection fails.
130
130
  TimeoutError: If the request times out.
131
+ ReadError: If an error occurs reading the response.
132
+ WriteError: If an error occurs writing the request.
131
133
  """
132
- return await self._client.options(url, headers=headers, timeout=timeout)
134
+ return await self._client.options(url, headers=headers)
133
135
 
134
136
  async def patch(
135
137
  self,
136
138
  url: str,
137
139
  headers: Headers | Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
138
140
  content: bytes | AsyncIterator[bytes] | None = None,
139
- timeout: float | None = None,
140
141
  ) -> FullResponse:
141
142
  """Executes a PATCH HTTP request.
142
143
 
@@ -144,22 +145,20 @@ class Client:
144
145
  url: The unencoded request URL.
145
146
  headers: The request headers.
146
147
  content: The request content.
147
- timeout: The timeout for the request in seconds.
148
148
 
149
149
  Raises:
150
150
  ConnectionError: If the connection fails.
151
151
  TimeoutError: If the request times out.
152
+ ReadError: If an error occurs reading the response.
153
+ WriteError: If an error occurs writing the request.
152
154
  """
153
- return await self._client.patch(
154
- url, headers=headers, content=content, timeout=timeout
155
- )
155
+ return await self._client.patch(url, headers=headers, content=content)
156
156
 
157
157
  async def put(
158
158
  self,
159
159
  url: str,
160
160
  headers: Headers | Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
161
161
  content: bytes | AsyncIterator[bytes] | None = None,
162
- timeout: float | None = None,
163
162
  ) -> FullResponse:
164
163
  """Executes a PUT HTTP request.
165
164
 
@@ -167,15 +166,14 @@ class Client:
167
166
  url: The unencoded request URL.
168
167
  headers: The request headers.
169
168
  content: The request content.
170
- timeout: The timeout for the request in seconds.
171
169
 
172
170
  Raises:
173
171
  ConnectionError: If the connection fails.
174
172
  TimeoutError: If the request times out.
173
+ ReadError: If an error occurs reading the response.
174
+ WriteError: If an error occurs writing the request.
175
175
  """
176
- return await self._client.put(
177
- url, headers=headers, content=content, timeout=timeout
178
- )
176
+ return await self._client.put(url, headers=headers, content=content)
179
177
 
180
178
  async def execute(
181
179
  self,
@@ -183,7 +181,6 @@ class Client:
183
181
  url: str,
184
182
  headers: Headers | Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
185
183
  content: bytes | AsyncIterator[bytes] | None = None,
186
- timeout: float | None = None,
187
184
  ) -> FullResponse:
188
185
  """Executes an HTTP request, returning the full buffered response.
189
186
 
@@ -192,15 +189,14 @@ class Client:
192
189
  url: The unencoded request URL.
193
190
  headers: The request headers.
194
191
  content: The request content.
195
- timeout: The timeout for the request in seconds.
196
192
 
197
193
  Raises:
198
194
  ConnectionError: If the connection fails.
199
195
  TimeoutError: If the request times out.
196
+ ReadError: If an error occurs reading the response.
197
+ WriteError: If an error occurs writing the request.
200
198
  """
201
- return await self._client.execute(
202
- method, url, headers=headers, content=content, timeout=timeout
203
- )
199
+ return await self._client.execute(method, url, headers=headers, content=content)
204
200
 
205
201
  @asynccontextmanager
206
202
  async def stream(
@@ -209,7 +205,6 @@ class Client:
209
205
  url: str,
210
206
  headers: Headers | Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
211
207
  content: bytes | AsyncIterator[bytes] | None = None,
212
- timeout: float | None = None,
213
208
  ) -> AsyncIterator[Response]:
214
209
  """Executes an HTTP request, allowing the response content to be streamed.
215
210
 
@@ -218,14 +213,15 @@ class Client:
218
213
  url: The unencoded request URL.
219
214
  headers: The request headers.
220
215
  content: The request content.
221
- timeout: The timeout for the request in seconds.
222
216
 
223
217
  Raises:
224
218
  ConnectionError: If the connection fails.
225
219
  TimeoutError: If the request times out.
220
+ ReadError: If an error occurs reading the response.
221
+ WriteError: If an error occurs writing the request.
226
222
  """
227
223
  response = await self._client.stream(
228
- method, url, headers=headers, content=content, timeout=timeout
224
+ method, url, headers=headers, content=content
229
225
  )
230
226
  async with response:
231
227
  yield response
Binary file
pyqwest/_pyqwest.pyi CHANGED
@@ -1,3 +1,4 @@
1
+ import datetime
1
2
  from collections.abc import (
2
3
  AsyncIterator,
3
4
  Awaitable,
@@ -29,7 +30,10 @@ class Headers:
29
30
  """
30
31
 
31
32
  def __init__(
32
- self, items: Mapping[str, str] | Iterable[tuple[str, str]] | None = None
33
+ self,
34
+ items: Mapping[str | HTTPHeaderName, str]
35
+ | Iterable[tuple[str | HTTPHeaderName, str]]
36
+ | None = None,
33
37
  ) -> None:
34
38
  """Creates a new Headers object.
35
39
 
@@ -37,7 +41,7 @@ class Headers:
37
41
  items: Initial headers to add.
38
42
  """
39
43
 
40
- def __getitem__(self, key: str) -> str:
44
+ def __getitem__(self, key: str | HTTPHeaderName) -> str:
41
45
  """Return the header value for the key.
42
46
 
43
47
  If multiple values are present for the key, returns the first value.
@@ -49,7 +53,7 @@ class Headers:
49
53
  KeyError: If the key is not present.
50
54
  """
51
55
 
52
- def __setitem__(self, key: str, value: str) -> None:
56
+ def __setitem__(self, key: str | HTTPHeaderName, value: str) -> None:
53
57
  """Sets the header value for the key, replacing any existing values.
54
58
 
55
59
  Args:
@@ -57,7 +61,7 @@ class Headers:
57
61
  value: The header value.
58
62
  """
59
63
 
60
- def __delitem__(self, key: str) -> None:
64
+ def __delitem__(self, key: str | HTTPHeaderName) -> None:
61
65
  """Deletes all values for the key.
62
66
 
63
67
  Args:
@@ -81,7 +85,7 @@ class Headers:
81
85
  other: The object to compare against.
82
86
  """
83
87
 
84
- def get(self, key: str, default: str | None = None) -> str | None:
88
+ def get(self, key: str | HTTPHeaderName, default: str | None = None) -> str | None:
85
89
  """Returns the header value for the key, or default if not present.
86
90
 
87
91
  Args:
@@ -90,7 +94,7 @@ class Headers:
90
94
  """
91
95
 
92
96
  @overload
93
- def pop(self, key: str) -> str:
97
+ def pop(self, key: str | HTTPHeaderName) -> str:
94
98
  """Removes and returns the header value for the key.
95
99
 
96
100
  Args:
@@ -101,7 +105,7 @@ class Headers:
101
105
  """
102
106
 
103
107
  @overload
104
- def pop(self, key: str, default: _T) -> str | _T:
108
+ def pop(self, key: str | HTTPHeaderName, default: _T) -> str | _T:
105
109
  """Removes and returns the header value for the key, or default if not present.
106
110
 
107
111
  Args:
@@ -117,7 +121,7 @@ class Headers:
117
121
  KeyError: If the headers are empty.
118
122
  """
119
123
 
120
- def setdefault(self, key: str, default: str | None = None) -> str:
124
+ def setdefault(self, key: str | HTTPHeaderName, default: str | None = None) -> str:
121
125
  """If the key is not present, sets it to the default value.
122
126
  Returns the value for the key.
123
127
 
@@ -126,7 +130,7 @@ class Headers:
126
130
  default: The default value to set and return if the key is not present.
127
131
  """
128
132
 
129
- def add(self, key: str, value: str) -> None:
133
+ def add(self, key: str | HTTPHeaderName, value: str) -> None:
130
134
  """Adds a header value for the key. Existing values are preserved.
131
135
 
132
136
  Args:
@@ -143,7 +147,11 @@ class Headers:
143
147
  """
144
148
  @overload
145
149
  def update(
146
- self, items: Mapping[str, str] | Iterable[tuple[str, str]], /, **kwargs: str
150
+ self,
151
+ items: Mapping[str | HTTPHeaderName, str]
152
+ | Iterable[tuple[str | HTTPHeaderName, str]],
153
+ /,
154
+ **kwargs: str,
147
155
  ) -> None:
148
156
  """Updates headers with the provided items. Existing values are replaced.
149
157
 
@@ -155,7 +163,7 @@ class Headers:
155
163
  def clear(self) -> None:
156
164
  """Removes all headers."""
157
165
 
158
- def getall(self, key: str) -> Sequence[str]:
166
+ def getall(self, key: str | HTTPHeaderName) -> Sequence[str]:
159
167
  """Returns all header values for the key.
160
168
 
161
169
  Args:
@@ -190,10 +198,20 @@ class HTTPVersion:
190
198
  HTTP3: HTTPVersion
191
199
  """HTTP/3"""
192
200
 
201
+ def __eq__(self, other: object) -> bool: ...
202
+ def __ne__(self, other: object) -> bool: ...
203
+ def __lt__(self, other: object) -> bool: ...
204
+ def __le__(self, other: object) -> bool: ...
205
+ def __gt__(self, other: object) -> bool: ...
206
+ def __ge__(self, other: object) -> bool: ...
207
+
193
208
  class Client:
194
209
  def __init__(self, transport: Transport | None = None) -> None:
195
210
  """Creates a new asynchronous HTTP client.
196
211
 
212
+ The asynchronous client does not expose per-request timeouts on its methods.
213
+ Use `asyncio.wait_for` or similar to enforce timeouts on requests.
214
+
197
215
  Args:
198
216
  transport: The transport to use for requests. If None, the shared default
199
217
  transport will be used.
@@ -203,14 +221,12 @@ class Client:
203
221
  self,
204
222
  url: str,
205
223
  headers: Headers | Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
206
- timeout: float | None = None,
207
224
  ) -> Awaitable[FullResponse]:
208
225
  """Executes a GET HTTP request.
209
226
 
210
227
  Args:
211
228
  url: The unencoded request URL.
212
229
  headers: The request headers.
213
- timeout: The timeout for the request in seconds.
214
230
 
215
231
  Raises:
216
232
  ConnectionError: If the connection fails.
@@ -224,7 +240,6 @@ class Client:
224
240
  url: str,
225
241
  headers: Headers | Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
226
242
  content: bytes | AsyncIterator[bytes] | None = None,
227
- timeout: float | None = None,
228
243
  ) -> Awaitable[FullResponse]:
229
244
  """Executes a POST HTTP request.
230
245
 
@@ -232,7 +247,6 @@ class Client:
232
247
  url: The unencoded request URL.
233
248
  headers: The request headers.
234
249
  content: The request content.
235
- timeout: The timeout for the request in seconds.
236
250
 
237
251
  Raises:
238
252
  ConnectionError: If the connection fails.
@@ -245,14 +259,12 @@ class Client:
245
259
  self,
246
260
  url: str,
247
261
  headers: Headers | Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
248
- timeout: float | None = None,
249
262
  ) -> Awaitable[FullResponse]:
250
263
  """Executes a DELETE HTTP request.
251
264
 
252
265
  Args:
253
266
  url: The unencoded request URL.
254
267
  headers: The request headers.
255
- timeout: The timeout for the request in seconds.
256
268
 
257
269
  Raises:
258
270
  ConnectionError: If the connection fails.
@@ -265,14 +277,12 @@ class Client:
265
277
  self,
266
278
  url: str,
267
279
  headers: Headers | Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
268
- timeout: float | None = None,
269
280
  ) -> Awaitable[FullResponse]:
270
281
  """Executes a HEAD HTTP request.
271
282
 
272
283
  Args:
273
284
  url: The unencoded request URL.
274
285
  headers: The request headers.
275
- timeout: The timeout for the request in seconds.
276
286
 
277
287
  Raises:
278
288
  ConnectionError: If the connection fails.
@@ -285,14 +295,12 @@ class Client:
285
295
  self,
286
296
  url: str,
287
297
  headers: Headers | Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
288
- timeout: float | None = None,
289
298
  ) -> Awaitable[FullResponse]:
290
299
  """Executes a OPTIONS HTTP request.
291
300
 
292
301
  Args:
293
302
  url: The unencoded request URL.
294
303
  headers: The request headers.
295
- timeout: The timeout for the request in seconds.
296
304
 
297
305
  Raises:
298
306
  ConnectionError: If the connection fails.
@@ -306,7 +314,6 @@ class Client:
306
314
  url: str,
307
315
  headers: Headers | Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
308
316
  content: bytes | AsyncIterator[bytes] | None = None,
309
- timeout: float | None = None,
310
317
  ) -> Awaitable[FullResponse]:
311
318
  """Executes a PATCH HTTP request.
312
319
 
@@ -328,7 +335,6 @@ class Client:
328
335
  url: str,
329
336
  headers: Headers | Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
330
337
  content: bytes | AsyncIterator[bytes] | None = None,
331
- timeout: float | None = None,
332
338
  ) -> Awaitable[FullResponse]:
333
339
  """Executes a PUT HTTP request.
334
340
 
@@ -336,7 +342,6 @@ class Client:
336
342
  url: The unencoded request URL.
337
343
  headers: The request headers.
338
344
  content: The request content.
339
- timeout: The timeout for the request in seconds.
340
345
 
341
346
  Raises:
342
347
  ConnectionError: If the connection fails.
@@ -351,7 +356,6 @@ class Client:
351
356
  url: str,
352
357
  headers: Headers | Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
353
358
  content: bytes | AsyncIterator[bytes] | None = None,
354
- timeout: float | None = None,
355
359
  ) -> Awaitable[FullResponse]:
356
360
  """Executes an HTTP request, returning the full buffered response.
357
361
 
@@ -360,7 +364,6 @@ class Client:
360
364
  url: The unencoded request URL.
361
365
  headers: The request headers.
362
366
  content: The request content.
363
- timeout: The timeout for the request in seconds.
364
367
 
365
368
  Raises:
366
369
  ConnectionError: If the connection fails.
@@ -375,7 +378,6 @@ class Client:
375
378
  url: str,
376
379
  headers: Headers | Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
377
380
  content: bytes | AsyncIterator[bytes] | None = None,
378
- timeout: float | None = None,
379
381
  ) -> Awaitable[Response]:
380
382
  """Executes an HTTP request, allowing the response content to be streamed.
381
383
 
@@ -384,7 +386,6 @@ class Client:
384
386
  url: The unencoded request URL.
385
387
  headers: The request headers.
386
388
  content: The request content.
387
- timeout: The timeout for the request in seconds.
388
389
 
389
390
  Raises:
390
391
  ConnectionError: If the connection fails.
@@ -1130,3 +1131,260 @@ class ReadError(Exception):
1130
1131
 
1131
1132
  class WriteError(Exception):
1132
1133
  """An error representing a write error during request sending."""
1134
+
1135
+ class HTTPHeaderName:
1136
+ """An enum type corresponding to HTTP header names."""
1137
+
1138
+ def __init__(self, name: str) -> None:
1139
+ """Creates a new HTTPHeaderName. When available, prefer one of the
1140
+ class attributes.
1141
+
1142
+ Args:
1143
+ name: The header name.
1144
+ """
1145
+
1146
+ ACCEPT: HTTPHeaderName
1147
+ """The "accept" header."""
1148
+
1149
+ ACCEPT_CHARSET: HTTPHeaderName
1150
+ """The "accept-charset" header."""
1151
+
1152
+ ACCEPT_ENCODING: HTTPHeaderName
1153
+ """The "accept-encoding" header."""
1154
+
1155
+ ACCEPT_LANGUAGE: HTTPHeaderName
1156
+ """The "accept-language" header."""
1157
+
1158
+ ACCEPT_RANGES: HTTPHeaderName
1159
+ """The "accept-ranges" header."""
1160
+
1161
+ ACCESS_CONTROL_ALLOW_CREDENTIALS: HTTPHeaderName
1162
+ """The "access-control-allow-credentials" header."""
1163
+
1164
+ ACCESS_CONTROL_ALLOW_HEADERS: HTTPHeaderName
1165
+ """The "access-control-allow-headers" header."""
1166
+
1167
+ ACCESS_CONTROL_ALLOW_METHODS: HTTPHeaderName
1168
+ """The "access-control-allow-methods" header."""
1169
+
1170
+ ACCESS_CONTROL_ALLOW_ORIGIN: HTTPHeaderName
1171
+ """The "access-control-allow-origin" header."""
1172
+
1173
+ ACCESS_CONTROL_EXPOSE_HEADERS: HTTPHeaderName
1174
+ """The "access-control-expose-headers" header."""
1175
+
1176
+ ACCESS_CONTROL_MAX_AGE: HTTPHeaderName
1177
+ """The "access-control-max-age" header."""
1178
+
1179
+ ACCESS_CONTROL_REQUEST_HEADERS: HTTPHeaderName
1180
+ """The "access-control-request-headers" header."""
1181
+
1182
+ ACCESS_CONTROL_REQUEST_METHOD: HTTPHeaderName
1183
+ """The "access-control-request-method" header."""
1184
+
1185
+ AGE: HTTPHeaderName
1186
+ """The "age" header."""
1187
+
1188
+ ALLOW: HTTPHeaderName
1189
+ """The "allow" header."""
1190
+
1191
+ ALT_SVC: HTTPHeaderName
1192
+ """The "alt-svc" header."""
1193
+
1194
+ AUTHORIZATION: HTTPHeaderName
1195
+ """The "authorization" header."""
1196
+
1197
+ CACHE_CONTROL: HTTPHeaderName
1198
+ """The "cache-control" header."""
1199
+
1200
+ CACHE_STATUS: HTTPHeaderName
1201
+ """The "cache-status" header."""
1202
+
1203
+ CDN_CACHE_CONTROL: HTTPHeaderName
1204
+ """The "cdn-cache-control" header."""
1205
+
1206
+ CONNECTION: HTTPHeaderName
1207
+ """The "connection" header."""
1208
+
1209
+ CONTENT_DISPOSITION: HTTPHeaderName
1210
+ """The "content-disposition" header."""
1211
+
1212
+ CONTENT_ENCODING: HTTPHeaderName
1213
+ """The "content-encoding" header."""
1214
+
1215
+ CONTENT_LANGUAGE: HTTPHeaderName
1216
+ """The "content-language" header."""
1217
+
1218
+ CONTENT_LENGTH: HTTPHeaderName
1219
+ """The "content-length" header."""
1220
+
1221
+ CONTENT_LOCATION: HTTPHeaderName
1222
+ """The "content-location" header."""
1223
+
1224
+ CONTENT_RANGE: HTTPHeaderName
1225
+ """The "content-range" header."""
1226
+
1227
+ CONTENT_SECURITY_POLICY: HTTPHeaderName
1228
+ """The "content-security-policy" header."""
1229
+
1230
+ CONTENT_SECURITY_POLICY_REPORT_ONLY: HTTPHeaderName
1231
+ """The "content-security-policy-report-only" header."""
1232
+
1233
+ CONTENT_TYPE: HTTPHeaderName
1234
+ """The "content-type" header."""
1235
+
1236
+ COOKIE: HTTPHeaderName
1237
+ """The "cookie" header."""
1238
+
1239
+ DNT: HTTPHeaderName
1240
+ """The "dnt" header."""
1241
+
1242
+ DATE: HTTPHeaderName
1243
+ """The "date" header."""
1244
+
1245
+ ETAG: HTTPHeaderName
1246
+ """The "etag" header."""
1247
+
1248
+ EXPECT: HTTPHeaderName
1249
+ """The "expect" header."""
1250
+
1251
+ EXPIRES: HTTPHeaderName
1252
+ """The "expires" header."""
1253
+
1254
+ FORWARDED: HTTPHeaderName
1255
+ """The "forwarded" header."""
1256
+
1257
+ FROM: HTTPHeaderName
1258
+ """The "from" header."""
1259
+
1260
+ HOST: HTTPHeaderName
1261
+ """The "host" header."""
1262
+
1263
+ IF_MATCH: HTTPHeaderName
1264
+ """The "if-match" header."""
1265
+
1266
+ IF_MODIFIED_SINCE: HTTPHeaderName
1267
+ """The "if-modified-since" header."""
1268
+
1269
+ IF_NONE_MATCH: HTTPHeaderName
1270
+ """The "if-none-match" header."""
1271
+
1272
+ IF_RANGE: HTTPHeaderName
1273
+ """The "if-range" header."""
1274
+
1275
+ IF_UNMODIFIED_SINCE: HTTPHeaderName
1276
+ """The "if-unmodified-since" header."""
1277
+
1278
+ LAST_MODIFIED: HTTPHeaderName
1279
+ """The "last-modified" header."""
1280
+
1281
+ LINK: HTTPHeaderName
1282
+ """The "link" header."""
1283
+
1284
+ LOCATION: HTTPHeaderName
1285
+ """The "location" header."""
1286
+
1287
+ MAX_FORWARDS: HTTPHeaderName
1288
+ """The "max-forwards" header."""
1289
+
1290
+ ORIGIN: HTTPHeaderName
1291
+ """The "origin" header."""
1292
+
1293
+ PRAGMA: HTTPHeaderName
1294
+ """The "pragma" header."""
1295
+
1296
+ PROXY_AUTHENTICATE: HTTPHeaderName
1297
+ """The "proxy-authenticate" header."""
1298
+
1299
+ PROXY_AUTHORIZATION: HTTPHeaderName
1300
+ """The "proxy-authorization" header."""
1301
+
1302
+ PUBLIC_KEY_PINS: HTTPHeaderName
1303
+ """The "public-key-pins" header."""
1304
+
1305
+ PUBLIC_KEY_PINS_REPORT_ONLY: HTTPHeaderName
1306
+ """The "public-key-pins-report-only" header."""
1307
+
1308
+ RANGE: HTTPHeaderName
1309
+ """The "range" header."""
1310
+
1311
+ REFERER: HTTPHeaderName
1312
+ """The "referer" header."""
1313
+
1314
+ REFERRER_POLICY: HTTPHeaderName
1315
+ """The "referrer-policy" header."""
1316
+
1317
+ REFRESH: HTTPHeaderName
1318
+ """The "refresh" header."""
1319
+
1320
+ RETRY_AFTER: HTTPHeaderName
1321
+ """The "retry-after" header."""
1322
+
1323
+ SEC_WEBSOCKET_ACCEPT: HTTPHeaderName
1324
+ """The "sec-websocket-accept" header."""
1325
+
1326
+ SEC_WEBSOCKET_EXTENSIONS: HTTPHeaderName
1327
+ """The "sec-websocket-extensions" header."""
1328
+
1329
+ SEC_WEBSOCKET_KEY: HTTPHeaderName
1330
+ """The "sec-websocket-key" header."""
1331
+
1332
+ SEC_WEBSOCKET_PROTOCOL: HTTPHeaderName
1333
+ """The "sec-websocket-protocol" header."""
1334
+
1335
+ SEC_WEBSOCKET_VERSION: HTTPHeaderName
1336
+ """The "sec-websocket-version" header."""
1337
+
1338
+ SERVER: HTTPHeaderName
1339
+ """The "server" header."""
1340
+
1341
+ SET_COOKIE: HTTPHeaderName
1342
+ """The "set-cookie" header."""
1343
+
1344
+ STRICT_TRANSPORT_SECURITY: HTTPHeaderName
1345
+ """The "strict-transport-security" header."""
1346
+
1347
+ TE: HTTPHeaderName
1348
+ """The "te" header."""
1349
+
1350
+ TRAILER: HTTPHeaderName
1351
+ """The "trailer" header."""
1352
+
1353
+ TRANSFER_ENCODING: HTTPHeaderName
1354
+ """The "transfer-encoding" header."""
1355
+
1356
+ USER_AGENT: HTTPHeaderName
1357
+ """The "user-agent" header."""
1358
+
1359
+ UPGRADE: HTTPHeaderName
1360
+ """The "upgrade" header."""
1361
+
1362
+ UPGRADE_INSECURE_REQUESTS: HTTPHeaderName
1363
+ """The "upgrade-insecure-requests" header."""
1364
+
1365
+ VARY: HTTPHeaderName
1366
+ """The "vary" header."""
1367
+
1368
+ VIA: HTTPHeaderName
1369
+ """The "via" header."""
1370
+
1371
+ WARNING: HTTPHeaderName
1372
+ """The "warning" header."""
1373
+
1374
+ WWW_AUTHENTICATE: HTTPHeaderName
1375
+ """The "www-authenticate" header."""
1376
+
1377
+ X_CONTENT_TYPE_OPTIONS: HTTPHeaderName
1378
+ """The "x-content-type-options" header."""
1379
+
1380
+ X_DNS_PREFETCH_CONTROL: HTTPHeaderName
1381
+ """The "x-dns-prefetch-control" header."""
1382
+
1383
+ X_FRAME_OPTIONS: HTTPHeaderName
1384
+ """The "x-frame-options" header."""
1385
+
1386
+ X_XSS_PROTECTION: HTTPHeaderName
1387
+ """The "x-xss-protection" header."""
1388
+
1389
+ def set_sync_timeout(timeout: float) -> AbstractContextManager[None]: ...
1390
+ def get_sync_timeout() -> datetime.timedelta | None: ...
pyqwest/testing/_asgi.py CHANGED
@@ -2,7 +2,6 @@ from __future__ import annotations
2
2
 
3
3
  import asyncio
4
4
  import contextlib
5
- import math
6
5
  from collections.abc import AsyncIterator
7
6
  from dataclasses import dataclass
8
7
  from typing import TYPE_CHECKING, Any
@@ -160,14 +159,9 @@ class ASGITransport(Transport):
160
159
  async def send(message: ASGISendEvent) -> None:
161
160
  await send_queue.put(message)
162
161
 
163
- timeout: float | None = request._timeout # pyright: ignore[reportAttributeAccessIssue] # noqa: SLF001
164
- if timeout is not None and (timeout < 0 or not math.isfinite(timeout)):
165
- msg = "Timeout must be non-negative"
166
- raise ValueError(msg)
167
-
168
162
  async def run_app() -> None:
169
163
  try:
170
- await asyncio.wait_for(self._app(scope, receive, send), timeout=timeout)
164
+ await self._app(scope, receive, send)
171
165
  except asyncio.TimeoutError as e:
172
166
  send_queue.put_nowait(TimeoutError(str(e)))
173
167
  except Exception as e:
pyqwest/testing/_wsgi.py CHANGED
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import contextlib
4
- import math
5
4
  import threading
6
5
  import time
7
6
  from collections.abc import Callable, Iterator
@@ -19,10 +18,10 @@ from pyqwest import (
19
18
  SyncTransport,
20
19
  WriteError,
21
20
  )
21
+ from pyqwest._pyqwest import get_sync_timeout
22
22
 
23
23
  if TYPE_CHECKING:
24
24
  import sys
25
- from types import TracebackType
26
25
 
27
26
  if sys.version_info >= (3, 11):
28
27
  from wsgiref.types import WSGIApplication, WSGIEnvironment
@@ -31,12 +30,21 @@ if TYPE_CHECKING:
31
30
 
32
31
  _UNSET_STATUS = "unset"
33
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
+
34
42
 
35
43
  class WSGITransport(SyncTransport):
44
+ """Transport implementation that directly invokes a WSGI application. Useful for testing."""
45
+
36
46
  _app: WSGIApplication
37
47
  _http_version: HTTPVersion
38
- _executor: ThreadPoolExecutor
39
- _close_executor: bool
40
48
  _closed: bool
41
49
 
42
50
  def __init__(
@@ -45,23 +53,23 @@ class WSGITransport(SyncTransport):
45
53
  http_version: HTTPVersion = HTTPVersion.HTTP2,
46
54
  executor: ThreadPoolExecutor | None = None,
47
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
+ """
48
64
  self._app = app
49
65
  self._http_version = http_version
50
- if executor is None:
51
- self._executor = ThreadPoolExecutor()
52
- self._close_executor = True
53
- else:
54
- self._executor = executor
55
- self._close_executor = False
66
+ self._executor = executor or get_default_executor()
56
67
  self._closed = False
57
68
 
58
69
  def execute_sync(self, request: SyncRequest) -> SyncResponse:
59
- timeout: float | None = request._timeout # pyright: ignore[reportAttributeAccessIssue] # noqa: SLF001
60
- if timeout is not None and (timeout < 0 or not math.isfinite(timeout)):
61
- msg = "Timeout must be non-negative"
62
- raise ValueError(msg)
63
-
64
- deadline = time.monotonic() + timeout if timeout is not None else None
70
+ deadline = None
71
+ if (to := get_sync_timeout()) is not None:
72
+ deadline = time.monotonic() + to.total_seconds()
65
73
 
66
74
  parsed_url = urlparse(request.url)
67
75
  raw_path = parsed_url.path or "/"
@@ -205,24 +213,6 @@ class WSGITransport(SyncTransport):
205
213
  trailers=trailers,
206
214
  )
207
215
 
208
- def __enter__(self) -> WSGITransport:
209
- return self
210
-
211
- def __exit__(
212
- self,
213
- _exc_type: type[BaseException] | None,
214
- _exc_value: BaseException | None,
215
- _traceback: TracebackType | None,
216
- ) -> None:
217
- self.close()
218
-
219
- def close(self) -> None:
220
- if self._closed:
221
- return
222
- self._closed = True
223
- if self._close_executor:
224
- self._executor.shutdown()
225
-
226
216
 
227
217
  class RequestInput:
228
218
  def __init__(self, content: Iterator[bytes], http_version: HTTPVersion) -> None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyqwest
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -0,0 +1,16 @@
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.cp313-win_amd64.pyd,sha256=rOryCv_cI2gWl-z7bV3x1obl3JySa2v3duIj1AvZXWU,11907584
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=n_BmF69IyGtioVWE9c3M_zsEfe6-xMZy1v5HCL_6qE0,97
15
+ pyqwest-0.3.0.dist-info\licenses\LICENSE,sha256=e7sDs6pM-apXvhJO1bLtzngKeI1aQ6bDIyJxO7Sgv1E,1085
16
+ pyqwest-0.3.0.dist-info\RECORD,,
@@ -1,16 +0,0 @@
1
- pyqwest\__init__.py,sha256=-u46hIqP0IGssz_qZUt2MlH7XZmK4FyRlKic8Tghjvo,860
2
- pyqwest\_coro.py,sha256=9Kkeyk9gA8fzFpGHhhjAz63msb98J4tuIQMPpDCOm3Y,7636
3
- pyqwest\_glue.py,sha256=gk--twq1LRc-tsaDVwcIxcwPmaMz5kt94QQGJ0fTNFw,2530
4
- pyqwest\_pyqwest.cp313-win_amd64.pyd,sha256=DLHi18Xl0Eq1pxCD2Y_-xEEHlrIxcnWzqdNWtUqVjE4,10728960
5
- pyqwest\_pyqwest.pyi,sha256=RkpFULa9imDZwej6D4v_yprlALlp0ngTcxu5nKWpc6I,38756
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=Jxd2cNG_DbYbUenW2LiV3Uc5x9d5ynUD7YISWiu7owU,12526
11
- pyqwest\testing\_asgi_compatibility.py,sha256=30rCjvyLg5L9x4vyI0LS8oqPtfY4oLctINJgqMzgLDU,3206
12
- pyqwest\testing\_wsgi.py,sha256=IgNpv6cGV-z1Mrrn8p2xh8XWsEOPEl5JVtuYyi57LsM,12464
13
- pyqwest-0.2.0.dist-info\METADATA,sha256=mHLRxgcqJckHC26TYH0gMeTi4_hvhjO5LAhh___pbMk,886
14
- pyqwest-0.2.0.dist-info\WHEEL,sha256=n_BmF69IyGtioVWE9c3M_zsEfe6-xMZy1v5HCL_6qE0,97
15
- pyqwest-0.2.0.dist-info\licenses\LICENSE,sha256=e7sDs6pM-apXvhJO1bLtzngKeI1aQ6bDIyJxO7Sgv1E,1085
16
- pyqwest-0.2.0.dist-info\RECORD,,