componentize-py
componentize-py copied to clipboard
anyio support
Hi 👋 I'm attempting to componentize a Python app which has anyio as a transitive dependency, but it seems that anyio imports ssl and the ssl module isn't present. As a simple repro, if I try to change the import asyncio to import anyio in the http example I see this:
❯ uv venv && uv pip install anyio
Using CPython 3.13.0
Creating virtual environment at: .venv
Activate with: source .venv/bin/activate
Resolved 3 packages in 1ms
Installed 3 packages in 6ms
+ anyio==4.8.0
+ idna==3.10
+ sniffio==1.3.1
❮ uvx componentize-py -d ../../wit -w wasi:http/[email protected] componentize app -o http.wasm -p . -p .venv/lib/python3.13/site-packages
Traceback (most recent call last):
File "/home/ben/.cache/uv/archive-v0/7RckABgkkjmDH-UjGQyxC/bin/componentize-py", line 12, in <module>
sys.exit(script())
~~~~~~^^
AssertionError: Traceback (most recent call last):
File "/0/app.py", line 8, in <module>
import anyio
File "/1/anyio/__init__.py", line 26, in <module>
from ._core._sockets import connect_tcp as connect_tcp
File "/1/anyio/_core/_sockets.py", line 6, in <module>
import ssl
File "/python/ssl.py", line 100, in <module>
import _ssl # if we can't import it, let the error propagate
^^^^^^^^^^^
ModuleNotFoundError: No module named '_ssl'
Caused by:
ModuleNotFoundError: No module named '_ssl'
I feel like I'm probably doing something wrong since I couldn't find anything on the issue board about this and it feels like it would have come up already 🤔
If I apply this diff to anyio then I can work around this (it looks like this is what Python's standard library does in e.g. asyncio):
diff --git a/src/anyio/_core/_sockets.py b/src/anyio/_core/_sockets.py
index a822d06..5e83251 100644
--- a/src/anyio/_core/_sockets.py
+++ b/src/anyio/_core/_sockets.py
@@ -3,7 +3,6 @@ from __future__ import annotations
import errno
import os
import socket
-import ssl
import stat
import sys
from collections.abc import Awaitable
@@ -12,6 +11,11 @@ from os import PathLike, chmod
from socket import AddressFamily, SocketKind
from typing import TYPE_CHECKING, Any, Literal, cast, overload
+try:
+ import ssl
+except ImportError:
+ ssl = None
+
from .. import to_thread
from ..abc import (
ConnectedUDPSocket,
diff --git a/src/anyio/streams/tls.py b/src/anyio/streams/tls.py
index b6961be..ff95e56 100644
--- a/src/anyio/streams/tls.py
+++ b/src/anyio/streams/tls.py
@@ -2,13 +2,17 @@ from __future__ import annotations
import logging
import re
-import ssl
import sys
from collections.abc import Callable, Mapping
from dataclasses import dataclass
from functools import wraps
from typing import Any, TypeVar
+try:
+ import ssl
+except ImportError:
+ ssl = None
+
from .. import (
BrokenResourceError,
EndOfStream,
Yeah, I ran into the same problem with redis-py. A lot of libraries out there assume ssl is always available (which is true for most platforms), and patching each of them one-by-one is going to be difficult.
One option that has occurred to me is to have componentize-py optionally provide a dummy ssl module with a few symbols defined -- just enough to make libraries like this work. They'll still break if you try to actually use any SSL-related features, of course, but at least they wouldn't break as soon as you import them. Would that suit your use case?
Also, in case you're wondering: the reason CPython doesn't enable the ssl module for Wasm targets is that Wasm doesn't currently support constant time operations to make crypto algorithms resistant to timing attacks. Although OpenSSL can be compiled to Wasm and used to enable the ssl module, it would be hazardous to do so.
In contrast, some of the other modules which CPython disabled on Wasm -- such as zlib -- are only disabled because nobody has gotten around to supporting them yet, but I know @brettcannon is working on that.
Finally, note that the wasi-tls project, which provides access to host-implemented TLS/SSL features, may eventually be the basis for a Wasm-friendly ssl module implementation, assuming someone is willing to write a compatibility shim to match up (at least a subset of) the ssl module APIs to the wasi-tls ones.
One option that has occurred to me is to have componentize-py optionally provide a dummy ssl module with a few symbols defined -- just enough to make libraries like this work. They'll still break if you try to actually use any SSL-related features, of course, but at least they wouldn't break as soon as you import them. Would that suit your use case?
Yeah I was thinking something like that might be quite useful. As you say in some cases it'll be kicking the errors further down the road, but I think there will be many cases where it's good enough. It feels like it may result in some build-time errors becoming runtime ones though - e.g. if someone tried to make a request to a URL which happened to be URL - so we might need to wrap any ImportErrors to make them less confusing.
(For context, I'm trying to componentize various Python-based MCP servers, many of which import e.g. httpx to do HTTP calls. I think this dummy ssl module would work as long as they're not using https)
Also, in case you're wondering: the reason CPython doesn't enable the ssl module for Wasm targets is that Wasm doesn't currently support constant time operations to make crypto algorithms resistant to timing attacks. Although OpenSSL can be compiled to Wasm and used to enable the ssl module, it would be hazardous to do so.
I was wondering, and this helps a lot, thank you!
Finally, note that the wasi-tls project, which provides access to host-implemented TLS/SSL features, may eventually be the basis for a Wasm-friendly ssl module implementation, assuming someone is willing to write a compatibility shim to match up (at least a subset of) the ssl module APIs to the wasi-tls ones.
I had a feeling there might be something like this, awesome 👍 I'll follow it closely and can hopefully contribute at some point.
One option that has occurred to me is to have
componentize-pyoptionally provide a dummysslmodule with a few symbols defined -- just enough to make libraries like this work.
Do we know if that would be true? Looking at https://github.com/python/cpython/blob/main/Lib/ssl.py shows that a lot of constants are coming from _ssl and I don't know how much is tied to the OpenSSL version.
We have had similar issues with ctypes (I think Pyodide does a build to get the constants).