Full HTTP and WebSocket traffic over any text-based transport — messengers, SMS, or anything else that can carry a string.
Project description
Aethernet (Æthernet)
Full HTTP and WebSocket traffic over any text-based transport — messengers, SMS, or anything else that can carry a string.
Aethernet is a lightweight tunneling library that lets you use the familiar httpx and websockets APIs without a direct internet connection. All traffic is serialized, optionally encrypted, and forwarded through a custom transport that you implement in a few lines of code.
Why Aethernet?
Some environments can send and receive text but cannot make direct TCP connections — think Telegram bots, SMS gateways, air-gapped systems, or embedded radios. Aethernet bridges that gap: your code keeps using standard HTTP and WebSocket clients while the actual bytes travel over whatever channel you have available.
Features
- Drop-in HTTP client — fully compatible
httpx.AsyncClienttransport - WebSocket support — API modeled on
websockets 16.0 - End-to-end encryption — AEAD_CHACHA20_POLY1305, key is shared out-of-band
- Configurable constraints — message size, character set, and rate limits match your transport's capabilities
- Bring your own transport — implement one abstract class and you're done
Installation
# pip
pip install aethernet-core
# uv
uv add aethernet-core
Quick Start
Aethernet has two sides: a client that issues HTTP/WebSocket requests, and a server that has real internet access and executes those requests on behalf of the client.
Both sides share the same LowTransport implementation and the same encryption_key.
1. Define Your Transport
A transport is any object that can send and receive short text strings — a Telegram message, an SMS, a serial port line, and so on. Implement the three abstract methods:
from aethernet import LowTransport
class MyTransport(LowTransport):
"""
Replace the bodies of send() and recv() with your real
channel logic (Telegram API, SMS gateway, etc.).
"""
def __init__(self) -> None:
super().__init__()
# Tune these to match your channel's actual limits:
self.max_message_chars = 1000
self.alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
self.min_send_interval = 0.2 # seconds between sends
self.min_recv_interval = 0.2 # seconds between reads
def close(self) -> None:
pass # release connections, file handles, etc.
def send(self, text: str) -> None:
pass # deliver `text` through your channel
def recv(self, timeout: float | None = None) -> str:
pass # return the next incoming message
2. Client Side
import asyncio
import secrets
import httpx
from aethernet import AethernetHttpx, AethernetWebSockets, get_transport
SHARED_KEY = secrets.token_bytes(32) # generate once, share securely
async def main() -> None:
transport = await get_transport(MyTransport(), encryption_key=SHARED_KEY)
# --- HTTP ---
async with httpx.AsyncClient(transport=transport) as client:
response = await client.get("https://httpbin.org/get")
print(response.status_code, response.json())
# --- WebSocket ---
async with AethernetWebSockets(transport).connect("wss://echo.websocket.events") as ws:
await ws.send("hello")
message = await ws.recv()
print("Echo:", message)
if __name__ == "__main__":
asyncio.run(main())
3. Server Side
Run this on a machine that does have internet access and can receive messages from the client's transport.
import asyncio
import secrets
from aethernet import AethernetServer
SHARED_KEY = b"..." # must match the client's key exactly
async def main() -> None:
server = await AethernetServer.create(
MyTransport(),
encryption_key=SHARED_KEY,
)
print("Aethernet server is running...")
await server.start_and_wait()
if __name__ == "__main__":
asyncio.run(main())
Project Structure
aethernet-core/
├── .github/
│ └── workflows/
├── src/
│ └── aethernet/
│ ├── transport/
│ ├── __init__.py
│ ├── exceptions.py
│ ├── server_router.py
│ └── typing.py
├── tests/
├── pyproject.toml
├── LICENSE
├── NOTICE
└── README.md
Development
# Install all dependencies including dev extras
uv sync --extra dev
# Run the test suite
pytest
# Linting, formatting, and type checking
black --check .
ruff check .
mypy .
# Run the full CI pipeline locally (requires act)
act -W .github/workflows/ci.yml
Compatibility
| Python | Platforms |
|---|---|
| 3.12 + | Linux, macOS, Windows |
Tested on Ubuntu with Python 3.12 and 3.13.
Bug Reports
Please open an issue and include:
- Expected behavior — what you intended to happen
- Actual behavior — what happened instead
- Reproduction steps — the minimal code or sequence of actions that triggers the problem
License
Author
Walter Kerrigan (esolment) — github.com/esolment
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file aethernet_core-1.0.1.tar.gz.
File metadata
- Download URL: aethernet_core-1.0.1.tar.gz
- Upload date:
- Size: 37.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
60ddceae8be95e9cf4e9cafe0fcbeccddba78f1de1b984791fc3d362201626f4
|
|
| MD5 |
32ade45dab406e0fa418a3846e3d03a9
|
|
| BLAKE2b-256 |
fd830e89e6741051069e8adc35b6dbfeb4717499faf5a4a920a8ef96a4cdeb04
|
Provenance
The following attestation bundles were made for aethernet_core-1.0.1.tar.gz:
Publisher:
release.yml on solment-slet/aethernet
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aethernet_core-1.0.1.tar.gz -
Subject digest:
60ddceae8be95e9cf4e9cafe0fcbeccddba78f1de1b984791fc3d362201626f4 - Sigstore transparency entry: 1340000359
- Sigstore integration time:
-
Permalink:
solment-slet/aethernet@8c4543d0fd92eb7646bfe58461da501920096482 -
Branch / Tag:
refs/tags/test-7 - Owner: https://github.com/solment-slet
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@8c4543d0fd92eb7646bfe58461da501920096482 -
Trigger Event:
release
-
Statement type:
File details
Details for the file aethernet_core-1.0.1-py3-none-any.whl.
File metadata
- Download URL: aethernet_core-1.0.1-py3-none-any.whl
- Upload date:
- Size: 35.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5fd202d9c9a5b6fcca7728c00e27b7c95093ade3d94c511d36fb8e6df5dbd16b
|
|
| MD5 |
05b70d9a00ddd1429c2092a2f02cbfb5
|
|
| BLAKE2b-256 |
2e56ff41ff791d839693c2cdd36c850f1e182c51d6162f343927c62104aae1a6
|
Provenance
The following attestation bundles were made for aethernet_core-1.0.1-py3-none-any.whl:
Publisher:
release.yml on solment-slet/aethernet
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aethernet_core-1.0.1-py3-none-any.whl -
Subject digest:
5fd202d9c9a5b6fcca7728c00e27b7c95093ade3d94c511d36fb8e6df5dbd16b - Sigstore transparency entry: 1340000360
- Sigstore integration time:
-
Permalink:
solment-slet/aethernet@8c4543d0fd92eb7646bfe58461da501920096482 -
Branch / Tag:
refs/tags/test-7 - Owner: https://github.com/solment-slet
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@8c4543d0fd92eb7646bfe58461da501920096482 -
Trigger Event:
release
-
Statement type: