httpx2-jsfetch 0.0.0__tar.gz → 1.0__tar.gz

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.
@@ -0,0 +1,12 @@
1
+ __pycache__/
2
+ *.egg-info/
3
+ .coverage
4
+ .coverage.*
5
+ .mypy_cache/
6
+ .pytest_cache/
7
+ .ruff_cache/
8
+ .venv/
9
+ venv*/
10
+ build/
11
+ dist/
12
+ pyodide_dist/
@@ -1,26 +1,23 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: httpx2-jsfetch
3
- Version: 0.0.0
4
- Dynamic: Requires-Dist
5
- Dynamic: Description
6
- Dynamic: Description-Content-Type
7
- Summary: The next generation HTTP client.
3
+ Version: 1.0
4
+ Summary: httpx2 transports for Emscripten/Pyodide, backed by the JavaScript fetch API.
8
5
  Author-email: Hood Chatham <roberthoodchatham@gmail.com>
9
6
  License-Expression: BSD-3-Clause
10
7
  License-File: LICENSE.md
11
8
  Classifier: Development Status :: 4 - Beta
12
9
  Classifier: Environment :: Web Environment
13
10
  Classifier: Framework :: AsyncIO
14
- Classifier: Framework :: Trio
15
11
  Classifier: Intended Audience :: Developers
16
12
  Classifier: License :: OSI Approved :: BSD License
17
13
  Classifier: Operating System :: OS Independent
18
14
  Classifier: Programming Language :: Python :: 3
19
15
  Classifier: Programming Language :: Python :: 3 :: Only
20
- Classifier: Programming Language :: Python :: 3.10
21
- Classifier: Programming Language :: Python :: 3.11
22
16
  Classifier: Programming Language :: Python :: 3.12
23
17
  Classifier: Programming Language :: Python :: 3.13
24
18
  Classifier: Programming Language :: Python :: 3.14
25
19
  Classifier: Topic :: Internet :: WWW/HTTP
26
- Requires-Python: >=3.10
20
+ Requires-Python: >=3.12
21
+ Description-Content-Type: text/markdown
22
+
23
+ httpx2 transports for Pyodide
@@ -0,0 +1 @@
1
+ httpx2 transports for Pyodide
@@ -0,0 +1,463 @@
1
+ """
2
+ Custom transport for Pyodide on Emscripten.
3
+
4
+ In async mode it uses the standard fetch api, which works anywhere that Pyodide
5
+ works.
6
+
7
+ In sync mode it requires the Javascript Promise Integration feature, which so
8
+ far is only supported in some JavaScript runtimes. As of this writing it is
9
+ supported in the following JavaScript runtimes:
10
+
11
+ * Chromium-based browsers: JSPI is supported by default.
12
+ * Node: JSPI is supported by default in Node 25 or newer. In Node 20 -- 24 you
13
+ need the --experimental-wasm-jspi flag.
14
+ * Firefox: JSPI support requires activating the
15
+ `javascript.options.wasm_js_promise_integration` flag.
16
+ * Safari: JSPI is supported in Technology Preview 238 (released February 26,
17
+ 2026). It is not yet supported in any stable release of Safari.
18
+
19
+ See https://github.com/WebAssembly/js-promise-integration/
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import email.parser
25
+ import warnings
26
+ from collections.abc import AsyncIterator, Awaitable, Iterable, Iterator
27
+ from contextlib import contextmanager
28
+ from types import TracebackType
29
+ from typing import (
30
+ TYPE_CHECKING,
31
+ Any,
32
+ TypeVar,
33
+ )
34
+
35
+ import js
36
+ from pyodide.ffi import JsException, JsProxy, can_run_sync, run_sync, to_js
37
+
38
+ if TYPE_CHECKING:
39
+ import ssl # pragma: nocover
40
+
41
+ from httpx2._config import DEFAULT_LIMITS, Limits
42
+ from httpx2._exceptions import (
43
+ ConnectError,
44
+ ConnectTimeout,
45
+ ReadError,
46
+ ReadTimeout,
47
+ RequestError,
48
+ )
49
+ from httpx2._models import Request, Response
50
+ from httpx2._transports.base import AsyncBaseTransport, BaseTransport
51
+ from httpx2._types import AsyncByteStream, CertTypes, ProxyTypes, SyncByteStream
52
+
53
+ T = TypeVar("T", bound="JavascriptFetchTransport")
54
+ A = TypeVar("A", bound="AsyncJavascriptFetchTransport")
55
+
56
+ SOCKET_OPTION = tuple[int, int, int] | tuple[int, int, bytes | bytearray] | tuple[int, int, None, int]
57
+
58
+ __all__ = ["AsyncJavascriptFetchTransport", "JavascriptFetchTransport"]
59
+
60
+ """
61
+ There are some headers that trigger unintended CORS preflight requests.
62
+ See also https://github.com/koenvo/pyodide-http/issues/22
63
+ """
64
+ HEADERS_TO_IGNORE = ("user-agent",)
65
+
66
+
67
+ # Default values of ignored options.
68
+ # If a different value is passed for any of these is passed we'll warn.
69
+ # trust_env and http1 are not listed here because they don't conflict with the
70
+ # JS-fetch behaviour.
71
+ _IGNORED_OPTION_DEFAULTS: dict[str, Any] = {
72
+ "verify": True,
73
+ "cert": None,
74
+ "http2": False,
75
+ "limits": DEFAULT_LIMITS,
76
+ "proxy": None,
77
+ "uds": None,
78
+ "local_address": None,
79
+ "retries": 0,
80
+ "socket_options": None,
81
+ }
82
+
83
+
84
+ def _warn_ignored_options(**kwargs: Any) -> None:
85
+ """Emit a UserWarning naming each option that is ignored on Emscripten."""
86
+ ignored = [
87
+ name
88
+ for name, value in kwargs.items()
89
+ if name in _IGNORED_OPTION_DEFAULTS and value != _IGNORED_OPTION_DEFAULTS[name]
90
+ ]
91
+ if not ignored:
92
+ return
93
+ message = (
94
+ "The following transport option(s) are not supported on Emscripten "
95
+ f"and will be ignored: {', '.join(ignored)}. "
96
+ "Networking is handled by the JavaScript runtime, so connection "
97
+ "pooling, proxies, certificate handling, and low-level socket "
98
+ "configuration cannot be controlled by httpx2."
99
+ )
100
+ warnings.warn(message, stacklevel=3)
101
+
102
+
103
+ @contextmanager
104
+ def _timeout(
105
+ timeout: float,
106
+ abort_controller_js: JsProxy,
107
+ TimeoutExceptionType: type[RequestError],
108
+ ErrorExceptionType: type[RequestError],
109
+ ) -> Iterator[None]:
110
+ timer_id = None
111
+ if timeout > 0:
112
+ # It looks odd that we have to call bind() here since the JsProxy will
113
+ # automatically remember the receiver. But when we pass it back to
114
+ # JavaScript, we unwrap it and forget the receiver.
115
+ abort = abort_controller_js.abort.bind(abort_controller_js)
116
+ timer_id = js.setTimeout(abort, int(timeout * 1000))
117
+ try:
118
+ yield
119
+ except JsException as err:
120
+ if err.name == "AbortError":
121
+ timer_id = None
122
+ raise TimeoutExceptionType(message="Request timed out") from err
123
+ else:
124
+ raise ErrorExceptionType(message=err.message) from err
125
+ finally:
126
+ if timer_id is not None:
127
+ js.clearTimeout(timer_id)
128
+
129
+
130
+ def _run_sync_with_timeout(
131
+ promise: Awaitable[JsProxy],
132
+ timeout: float,
133
+ abort_controller_js: JsProxy,
134
+ TimeoutExceptionType: type[RequestError],
135
+ ErrorExceptionType: type[RequestError],
136
+ ) -> JsProxy:
137
+ """await a javascript promise synchronously with a timeout set via the
138
+ AbortController and return the resulting javascript proxy
139
+
140
+ Args:
141
+ promise (Awaitable): Javascript promise to await
142
+ timeout (float): Timeout in seconds
143
+ abort_controller_js (Any): A javascript AbortController object, used on timeout
144
+ TimeoutExceptionType (type[Exception]): An exception type to raise on timeout
145
+ ErrorExceptionType (type[Exception]): An exception type to raise on error
146
+
147
+ Raises:
148
+ TimeoutExceptionType: If the request times out
149
+ ErrorExceptionType: If the request raises a Javascript exception
150
+
151
+ Returns:
152
+ JsProxy: The result of awaiting the promise.
153
+ """
154
+ with _timeout(timeout, abort_controller_js, TimeoutExceptionType, ErrorExceptionType):
155
+ # run_sync here uses WebAssembly Javascript Promise Integration to
156
+ # suspend python until the Javascript promise resolves.
157
+ return run_sync(promise)
158
+
159
+
160
+ async def _run_async_with_timeout(
161
+ promise: Awaitable[JsProxy],
162
+ timeout: float,
163
+ abort_controller_js: JsProxy,
164
+ TimeoutExceptionType: type[RequestError],
165
+ ErrorExceptionType: type[RequestError],
166
+ ) -> JsProxy:
167
+ """await a javascript promise asynchronously with a timeout set via the
168
+ AbortController
169
+
170
+ Args:
171
+ promise (Awaitable): Javascript promise to await
172
+ timeout (float): Timeout in seconds
173
+ abort_controller_js (Any): A javascript AbortController object, used on timeout
174
+ TimeoutExceptionType (type[Exception]): An exception type to raise on timeout
175
+ ErrorExceptionType (type[Exception]): An exception type to raise on error
176
+
177
+ Raises:
178
+ TimeoutException: If the request times out
179
+ NetworkError: If the request raises a Javascript exception
180
+
181
+ Returns:
182
+ JsProxy: The result of awaiting the promise.
183
+ """
184
+ with _timeout(timeout, abort_controller_js, TimeoutExceptionType, ErrorExceptionType):
185
+ return await promise
186
+
187
+
188
+ def _compute_timeouts(extensions: dict[str, Any]) -> tuple[float, float]:
189
+ timeout_dict = extensions.get("timeout", {}) or {}
190
+ conn_timeout = timeout_dict.get("connect", 0.0) or 0.0
191
+ read_timeout = timeout_dict.get("read", 0.0) or 0.0
192
+ return (conn_timeout, read_timeout)
193
+
194
+
195
+ def _do_fetch(request: Request, request_body: bytes | None, abort_controller_js: Any) -> Awaitable[JsProxy]:
196
+ headers = {k: v for k, v in request.headers.items() if k not in HEADERS_TO_IGNORE}
197
+ fetch_data = {
198
+ "headers": headers,
199
+ "body": to_js(request_body),
200
+ "method": request.method,
201
+ "signal": abort_controller_js.signal,
202
+ }
203
+
204
+ return js.fetch( # type: ignore[no-any-return]
205
+ request.url,
206
+ to_js(fetch_data, dict_converter=js.Object.fromEntries),
207
+ )
208
+
209
+
210
+ def _js_response_to_python(
211
+ Stream: type[EmscriptenStream] | type[AsyncEmscriptenStream],
212
+ response_js: Any,
213
+ read_timeout: float,
214
+ abort_controller_js: Any,
215
+ ) -> Response:
216
+ headers = dict(response_js.headers.entries())
217
+ # fix content-encoding headers because the javascript fetch handles that
218
+ headers["content-encoding"] = "identity"
219
+ status_code = response_js.status
220
+
221
+ # get a reader from the fetch response
222
+ body_stream_js = response_js.body.getReader()
223
+ return Response(
224
+ status_code=status_code,
225
+ headers=headers,
226
+ stream=Stream(body_stream_js, read_timeout, abort_controller_js),
227
+ )
228
+
229
+
230
+ class EmscriptenStream(SyncByteStream):
231
+ def __init__(
232
+ self,
233
+ response_stream_js: JsProxy,
234
+ timeout: float,
235
+ abort_controller_js: JsProxy,
236
+ ) -> None:
237
+ self._stream_js = response_stream_js
238
+ self.timeout = timeout
239
+ self.abort_controller_js = abort_controller_js
240
+
241
+ def __iter__(self) -> Iterator[bytes]:
242
+ while True:
243
+ result_js = _run_sync_with_timeout(
244
+ self._stream_js.read(),
245
+ self.timeout,
246
+ self.abort_controller_js,
247
+ ReadTimeout,
248
+ ReadError,
249
+ )
250
+ if result_js.done:
251
+ return
252
+ else:
253
+ yield result_js.value.to_py()
254
+
255
+ def close(self) -> None:
256
+ self._stream_js = None
257
+
258
+
259
+ class JavascriptFetchTransport(BaseTransport):
260
+ def __init__(
261
+ self,
262
+ verify: ssl.SSLContext | str | bool = True,
263
+ cert: CertTypes | None = None,
264
+ trust_env: bool = True,
265
+ http1: bool = True,
266
+ http2: bool = False,
267
+ limits: Limits = DEFAULT_LIMITS,
268
+ proxy: ProxyTypes | None = None,
269
+ uds: str | None = None,
270
+ local_address: str | None = None,
271
+ retries: int = 0,
272
+ socket_options: Iterable[SOCKET_OPTION] | None = None,
273
+ ) -> None:
274
+ _warn_ignored_options(
275
+ verify=verify,
276
+ cert=cert,
277
+ http2=http2,
278
+ limits=limits,
279
+ proxy=proxy,
280
+ uds=uds,
281
+ local_address=local_address,
282
+ retries=retries,
283
+ socket_options=socket_options,
284
+ )
285
+
286
+ def __enter__(self: T) -> T: # Use generics for subclass support.
287
+ return self
288
+
289
+ def __exit__(
290
+ self,
291
+ exc_type: type[BaseException] | None = None,
292
+ exc_value: BaseException | None = None,
293
+ traceback: TracebackType | None = None,
294
+ ) -> None:
295
+ pass
296
+
297
+ def handle_request(
298
+ self,
299
+ request: Request,
300
+ ) -> Response:
301
+ assert isinstance(request.stream, SyncByteStream)
302
+ if not can_run_sync():
303
+ return _no_jspi_fallback(request)
304
+ request_body: bytes | None = b"".join(request.stream) or None
305
+
306
+ conn_timeout, read_timeout = _compute_timeouts(request.extensions)
307
+ abort_controller_js = js.AbortController.new()
308
+ fetcher_promise_js = _do_fetch(request, request_body, abort_controller_js)
309
+ response_js = _run_sync_with_timeout(
310
+ fetcher_promise_js,
311
+ conn_timeout,
312
+ abort_controller_js,
313
+ ConnectTimeout,
314
+ ConnectError,
315
+ )
316
+ return _js_response_to_python(EmscriptenStream, response_js, read_timeout, abort_controller_js)
317
+
318
+ def close(self) -> None:
319
+ pass # pragma: nocover
320
+
321
+
322
+ class AsyncEmscriptenStream(AsyncByteStream):
323
+ def __init__(
324
+ self,
325
+ response_stream_js: JsProxy,
326
+ timeout: float,
327
+ abort_controller_js: JsProxy,
328
+ ) -> None:
329
+ self._stream_js = response_stream_js
330
+ self.timeout = timeout
331
+ self.abort_controller_js = abort_controller_js
332
+
333
+ async def __aiter__(self) -> AsyncIterator[bytes]:
334
+ while self._stream_js is not None:
335
+ result_js = await _run_async_with_timeout(
336
+ self._stream_js.read(),
337
+ self.timeout,
338
+ self.abort_controller_js,
339
+ ReadTimeout,
340
+ ReadError,
341
+ )
342
+ if result_js.done:
343
+ return
344
+ else:
345
+ yield result_js.value.to_py()
346
+
347
+ async def aclose(self) -> None:
348
+ self._stream_js = None
349
+
350
+
351
+ class AsyncJavascriptFetchTransport(AsyncBaseTransport):
352
+ def __init__(
353
+ self,
354
+ verify: ssl.SSLContext | str | bool = True,
355
+ cert: CertTypes | None = None,
356
+ trust_env: bool = True,
357
+ http1: bool = True,
358
+ http2: bool = False,
359
+ limits: Limits = DEFAULT_LIMITS,
360
+ proxy: ProxyTypes | None = None,
361
+ uds: str | None = None,
362
+ local_address: str | None = None,
363
+ retries: int = 0,
364
+ socket_options: Iterable[SOCKET_OPTION] | None = None,
365
+ ) -> None:
366
+ _warn_ignored_options(
367
+ verify=verify,
368
+ cert=cert,
369
+ http2=http2,
370
+ limits=limits,
371
+ proxy=proxy,
372
+ uds=uds,
373
+ local_address=local_address,
374
+ retries=retries,
375
+ socket_options=socket_options,
376
+ )
377
+
378
+ async def __aenter__(self: A) -> A: # Use generics for subclass support.
379
+ return self
380
+
381
+ async def __aexit__(
382
+ self,
383
+ exc_type: type[BaseException] | None = None,
384
+ exc_value: BaseException | None = None,
385
+ traceback: TracebackType | None = None,
386
+ ) -> None:
387
+ pass
388
+
389
+ async def _get_body(self, request: Request) -> bytes | None:
390
+ assert isinstance(request.stream, AsyncByteStream)
391
+ body = b"".join([x async for x in request.stream])
392
+ if not body:
393
+ return None
394
+ return body
395
+
396
+ async def handle_async_request(
397
+ self,
398
+ request: Request,
399
+ ) -> Response:
400
+ request_body = await self._get_body(request)
401
+ conn_timeout, read_timeout = _compute_timeouts(request.extensions)
402
+ abort_controller_js = js.AbortController.new()
403
+ fetcher_promise_js = _do_fetch(request, request_body, abort_controller_js)
404
+ response_js = await _run_async_with_timeout(
405
+ fetcher_promise_js,
406
+ conn_timeout,
407
+ abort_controller_js,
408
+ ConnectTimeout,
409
+ ConnectError,
410
+ )
411
+ return _js_response_to_python(AsyncEmscriptenStream, response_js, read_timeout, abort_controller_js)
412
+
413
+ async def aclose(self) -> None:
414
+ pass # pragma: nocover
415
+
416
+
417
+ # Use XHR to do a sync request without jspi
418
+ def _is_in_browser_main_thread() -> bool:
419
+ return hasattr(js, "window") and hasattr(js, "self") and js.self == js.window
420
+
421
+
422
+ def _no_jspi_fallback(request: Request) -> Response:
423
+ assert isinstance(request.stream, SyncByteStream)
424
+ try:
425
+ js_xhr = js.XMLHttpRequest.new()
426
+
427
+ req_body: bytes | None = b"".join(request.stream)
428
+ if not req_body:
429
+ req_body = None
430
+ _, timeout = _compute_timeouts(request.extensions)
431
+
432
+ # XMLHttpRequest only supports timeouts and proper
433
+ # binary file reading in web-workers
434
+ if not _is_in_browser_main_thread():
435
+ js_xhr.responseType = "arraybuffer"
436
+ if timeout > 0.0:
437
+ js_xhr.timeout = int(timeout * 1000)
438
+ else:
439
+ # this is a nasty hack to be able to read binary files on
440
+ # main browser thread using xmlhttprequest
441
+ js_xhr.overrideMimeType("text/plain; charset=ISO-8859-15")
442
+
443
+ js_xhr.open(request.method, request.url, False)
444
+
445
+ for name, value in request.headers.items():
446
+ if name.lower() not in HEADERS_TO_IGNORE:
447
+ js_xhr.setRequestHeader(name, value)
448
+
449
+ js_xhr.send(to_js(req_body))
450
+
451
+ headers = dict(email.parser.Parser().parsestr(js_xhr.getAllResponseHeaders()))
452
+
453
+ if not _is_in_browser_main_thread():
454
+ body = js_xhr.response.to_py().tobytes()
455
+ else:
456
+ body = js_xhr.response.encode("ISO-8859-15")
457
+
458
+ return Response(status_code=js_xhr.status, headers=headers, content=body)
459
+ except JsException as err:
460
+ if err.name == "TimeoutError":
461
+ raise ConnectTimeout(message="Request timed out") from err
462
+ else:
463
+ raise ConnectError(message=err.message) from err
@@ -0,0 +1,136 @@
1
+ [build-system]
2
+ requires = ["hatchling", "uv-dynamic-versioning>=0.14.0"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [tool.hatch.version]
6
+ source = "uv-dynamic-versioning"
7
+
8
+ [tool.uv-dynamic-versioning]
9
+ vcs = "git"
10
+ style = "pep440"
11
+ bump = true
12
+ fallback-version = "0.0.0"
13
+
14
+ [project]
15
+ name = "httpx2-jsfetch"
16
+ description = "httpx2 transports for Emscripten/Pyodide, backed by the JavaScript fetch API."
17
+ readme = "README.md"
18
+ license = "BSD-3-Clause"
19
+ requires-python = ">=3.12"
20
+ authors = [
21
+ { name = "Hood Chatham", email = "roberthoodchatham@gmail.com" },
22
+ ]
23
+ classifiers = [
24
+ "Development Status :: 4 - Beta",
25
+ "Environment :: Web Environment",
26
+ "Framework :: AsyncIO",
27
+ "Intended Audience :: Developers",
28
+ "License :: OSI Approved :: BSD License",
29
+ "Operating System :: OS Independent",
30
+ "Programming Language :: Python :: 3",
31
+ "Programming Language :: Python :: 3 :: Only",
32
+ "Programming Language :: Python :: 3.12",
33
+ "Programming Language :: Python :: 3.13",
34
+ "Programming Language :: Python :: 3.14",
35
+ "Topic :: Internet :: WWW/HTTP",
36
+ ]
37
+ # No runtime dependencies: `httpx2` depends on us on Emscripten, not the other
38
+ # way around.
39
+ dynamic = ["version"]
40
+
41
+ [tool.hatch.build.targets.sdist]
42
+ include = ["httpx2_jsfetch"]
43
+
44
+ [tool.hatch.build.targets.sdist.force-include]
45
+ "README.md" = "README.md"
46
+ "LICENSE.md" = "LICENSE.md"
47
+
48
+ [tool.hatch.build.targets.wheel]
49
+ packages = ["httpx2_jsfetch"]
50
+
51
+ [tool.uv]
52
+ default-groups = ["dev"]
53
+ required-version = ">=0.8.6"
54
+
55
+ # On Emscripten `httpx2` dispatches to the transports in this package, so we
56
+ # develop and test against the branch that wires them up.
57
+ [tool.uv.sources]
58
+ httpx2 = { git = "https://github.com/hoodmane/httpx2", branch = "use-httpx2-jsfetch", subdirectory = "src/httpx2" }
59
+ httpcore2 = { git = "https://github.com/hoodmane/httpx2", branch = "use-httpx2-jsfetch", subdirectory = "src/httpcore2" }
60
+
61
+ [dependency-groups]
62
+ dev = [
63
+ "httpx2",
64
+ # `httpcore2` is only listed so that the `httpcore2==<version>` pin of the
65
+ # `httpx2` git checkout resolves against the same commit.
66
+ "httpcore2",
67
+ # Tests
68
+ "coverage[toml]>=7.10.6",
69
+ "pytest>=9.0.3",
70
+ "uvicorn>=0.35",
71
+ # Linting
72
+ "mypy>=1.17.1",
73
+ # Packaging
74
+ "twine>=6.1.0",
75
+ "pre-commit>=4.4",
76
+ ]
77
+ # extra requirements for testing on emscripten
78
+ emscripten = [
79
+ "pytest-pyodide>=0.59.2; python_full_version >= '3.11'",
80
+ "selenium",
81
+ ]
82
+
83
+ [tool.ruff]
84
+ line-length = 120
85
+
86
+ [tool.ruff.lint]
87
+ select = [
88
+ "B0", # bugbear (all B0* checks enabled by default)
89
+ "B904", # bugbear (Within an except clause, raise exceptions with raise ... from err)
90
+ "B905", # bugbear (zip() without an explicit strict= parameter set.)
91
+ "C4", # flake8-comprehensions
92
+ "C9", # mccabe complexity
93
+ "E", # pycodestyles
94
+ "F", # pyflakes
95
+ "I", # isort
96
+ "PERF", # Perflint
97
+ "PGH", # pygrep-hooks
98
+ "PL", # Pylint
99
+ "UP", # pyupgrade
100
+ "W", # pycodestyles
101
+ ]
102
+ ignore = ["E402", "E501", "E731", "E741", "PLR0913", "PLR2004", "PLW2901", "UP031"]
103
+
104
+ [tool.ruff.lint.isort]
105
+ combine-as-imports = true
106
+ known-first-party = ["httpx2_jsfetch"]
107
+
108
+ [tool.mypy]
109
+ ignore_missing_imports = true
110
+ strict = true
111
+
112
+ [tool.pytest.ini_options]
113
+ addopts = "-rxXs --import-mode=importlib"
114
+ testpaths = ["tests"]
115
+ filterwarnings = ["error"]
116
+
117
+ [tool.coverage.run]
118
+ source_pkgs = ["httpx2_jsfetch", "tests"]
119
+
120
+ [tool.coverage.paths]
121
+ source = [
122
+ "httpx2_jsfetch",
123
+ "*/site-packages/httpx2_jsfetch",
124
+ ]
125
+ tests = [
126
+ "tests",
127
+ "*/tests",
128
+ ]
129
+
130
+ [tool.coverage.report]
131
+ exclude_also = [
132
+ "if TYPE_CHECKING:",
133
+ "if typing.TYPE_CHECKING:",
134
+ "raise NotImplementedError",
135
+ "@(typing\\.)?overload",
136
+ ]
@@ -1 +0,0 @@
1
- httpx2 transports for Pyodide
File without changes
@@ -1,51 +0,0 @@
1
- [build-system]
2
- requires = ["hatchling", "hatch-fancy-pypi-readme", "uv-dynamic-versioning>=0.8.0"]
3
- build-backend = "hatchling.build"
4
-
5
- [tool.hatch.version]
6
- source = "uv-dynamic-versioning"
7
-
8
- [tool.uv-dynamic-versioning]
9
- vcs = "git"
10
- style = "pep440"
11
- bump = true
12
- fallback-version = "0.0.0"
13
-
14
- [project]
15
- name = "httpx2-jsfetch"
16
- description = "The next generation HTTP client."
17
- license = "BSD-3-Clause"
18
- requires-python = ">=3.10"
19
- authors = [
20
- { name = "Hood Chatham", email = "roberthoodchatham@gmail.com" },
21
- ]
22
- classifiers = [
23
- "Development Status :: 4 - Beta",
24
- "Environment :: Web Environment",
25
- "Framework :: AsyncIO",
26
- "Framework :: Trio",
27
- "Intended Audience :: Developers",
28
- "License :: OSI Approved :: BSD License",
29
- "Operating System :: OS Independent",
30
- "Programming Language :: Python :: 3",
31
- "Programming Language :: Python :: 3 :: Only",
32
- "Programming Language :: Python :: 3.10",
33
- "Programming Language :: Python :: 3.11",
34
- "Programming Language :: Python :: 3.12",
35
- "Programming Language :: Python :: 3.13",
36
- "Programming Language :: Python :: 3.14",
37
- "Topic :: Internet :: WWW/HTTP",
38
- ]
39
- dynamic = ["readme", "version", "dependencies"]
40
-
41
-
42
- [tool.hatch.build.targets.sdist]
43
- include = ["httpx2_jsfetch", "/CHANGELOG.md"]
44
-
45
- [tool.hatch.build.targets.sdist.force-include]
46
- "README.md" = "README.md"
47
- "LICENSE.md" = "LICENSE.md"
48
-
49
- [tool.hatch.build.targets.wheel]
50
- packages = ["httpx2-jsfetch"]
51
-
File without changes