pyproject.toml gains the metadata PyPI expects:
- Real author name + email
- Keywords (erlang, elixir, distribution, gen_server, ...)
- Classifiers (status, license, Python versions, topic)
- Project URLs (Homepage / Repository / Bug Tracker)
- readme content-type = text/markdown so PyPI renders our README
- build + twine added to the [dev] extra
.gitlab-ci.yml mirrors ago's two-stage pattern:
- test stage on every push (venv + pip install -e .[dev] + pytest)
- pypi-twine stage only on tags: python -m build, twine check,
twine upload. Credentials come from GitLab CI variables
TWINE_USERNAME (typically __token__) and TWINE_PASSWORD.
Makefile gains build / dist-check / publish-test / publish targets so
the release flow is also runnable locally if a dry run is needed.
Release flow:
1. Bump version in pyproject.toml
2. Commit + push
3. git tag -a vX.Y.Z -m 'release vX.Y.Z' && git push --tags
4. CI's pypi-twine stage picks up the tag and uploads
README.md adds an Install section + Quick start (plain dist + TLS dist)
so PyPI's project page shows usable copy on first visit. dist/ artifacts
build cleanly and both pass twine check.
82 lines
2.3 KiB
Markdown
82 lines
2.3 KiB
Markdown
# erldistpy
|
|
|
|
Native Python client for our Erlang distribution protocol. Talk Erlang/Elixir
|
|
nodes from CPython without HTTP shim layers.
|
|
|
|
Built to swap into Python web apps as a drop-in for HTTP wallet-bridge
|
|
clients (see [unfeed](https://git.unturf.com/foxhop/unfeed)'s
|
|
`WalletTransport` and `make_post_sell`'s crypto watcher) so they can call
|
|
Elixir `gen_server` processes over native Erlang dist instead of JSON-RPC
|
|
or HTTPS. Same call semantics, lower latency, fewer moving parts.
|
|
|
|
## Install
|
|
|
|
```bash
|
|
pip install erldistpy
|
|
```
|
|
|
|
## Quick start
|
|
|
|
```python
|
|
from erldistpy import Node, Atom
|
|
|
|
with Node(
|
|
our_name="myapp@host",
|
|
peer_name="wallet", # short EPMD name
|
|
peer_host="wallet.example.com",
|
|
cookie="SHARED_COOKIE", # read from a file path, never inline
|
|
) as node:
|
|
reply = node.call(
|
|
"Elixir.Wallet.Service",
|
|
(Atom("monero"), Atom("get_height"), []),
|
|
timeout=5.0,
|
|
)
|
|
# reply is whatever the gen_server returned — atoms / binaries /
|
|
# tuples / maps / lists / pids / refs decode to Python natives.
|
|
```
|
|
|
|
For TLS dist (Erlang `inet_tls_dist`):
|
|
|
|
```python
|
|
from erldistpy import Node, make_dist_tls_context
|
|
|
|
ctx = make_dist_tls_context(
|
|
cert="/etc/myapp/client.pem",
|
|
key="/etc/myapp/client.key",
|
|
ca="/etc/myapp/ca.pem",
|
|
)
|
|
node = Node(our_name=..., peer_name=..., cookie=..., tls_context=ctx)
|
|
```
|
|
|
|
## Scope
|
|
|
|
- ETF (External Term Format) codec — encode/decode Erlang terms
|
|
- EPMD client — node name → port lookup
|
|
- v6 distribution handshake — MD5 cookie auth, version negotiation
|
|
- `gen_call` to registered processes on a remote node
|
|
- TLS dist support (Erlang `inet_tls_dist`)
|
|
|
|
Out of scope: full Erlang node impersonation, link/monitor lifecycles,
|
|
distributed Mnesia. We are a *client*, not a peer node.
|
|
|
|
## Why not Pyrlang?
|
|
|
|
Pyrlang implements a full asyncio Erlang node. Heavy, asyncio-first,
|
|
complex. erldistpy is a small synchronous client that fits behind the
|
|
same Protocol surface as `httpx`. Different shape, different audience.
|
|
|
|
## Development
|
|
|
|
```bash
|
|
make bootstrap # create venv, install editable + dev deps
|
|
make test # run pytest (122 tests)
|
|
make lint # ruff check
|
|
make build # build sdist + wheel into dist/
|
|
make dist-check # twine check dist/*
|
|
```
|
|
|
|
See `docs/ROADMAP.md` for the phase-by-phase build log.
|
|
|
|
## License
|
|
|
|
Unlicense (public domain).
|