componentize-py icon indicating copy to clipboard operation
componentize-py copied to clipboard

anyio support

Open sd2k opened this issue 8 months ago • 4 comments

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 🤔

sd2k avatar Mar 13 '25 09:03 sd2k

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,

sd2k avatar Mar 13 '25 11:03 sd2k

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.

dicej avatar Mar 13 '25 15:03 dicej

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.

sd2k avatar Mar 13 '25 15:03 sd2k

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.

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).

brettcannon avatar Mar 17 '25 18:03 brettcannon