PyRFC Is Archived. Here Is a Drop-In Replacement.
Run this on a clean machine today:
$ pip install pyrfc
ERROR: Ignored the following yanked versions: 2.4.0, 2.4.1, 2.4.2, 2.5.0,
2.5.1, 2.7.0, 2.7.1, 2.7.2, 2.8.0, 2.8.1, 2.8.2, 2.8.3, 3.0.dev1, 3.0, 3.1,
3.2, 3.3, 3.3.1
ERROR: No matching distribution found for pyrfc
Nineteen releases, every one of them withdrawn. PyPI will tell you why if you ask it:
No longer supported, see https://github.com/SAP/PyRFC/issues/372
SAP archived PyRFC in December 2024. It was the only Python binding to the NetWeaver RFC SDK. For a great many data teams, it was the only way Python talked to an ABAP system at all.
We have spent the last months building the replacement we needed ourselves. This post covers why the obvious routes all closed. Then it shows the evidence that ours works: the same script run under SAP's library and under ours, with the output diffed.
Two years of closed doors
The story is public, in two issue threads worth reading in full.
In SAP/PyRFC#372, SAP's Open Source Program Office asked for volunteers to take the project over. In December 2024 they explained why nobody could:
We haven't been able to properly transfer the ownership to new maintainers. The reasons for that are mainly resource constraints on our side as well as the fact that we currently cannot provide the necessary prerequisites for a successful restart (e.g. access to the RFC SDK under an open-source license, backend test systems etc.).
Consequently, we recommend starting a fork or a completely new project to address the use cases that have previously been covered by this project.
Read that closely, because it is the crux. The blocker was never the Python code. PyRFC binds to a closed-source C library that nobody may redistribute, and SAP could not license it in a way that let anyone else maintain the binding. A fork inherits that problem on day one.
The second thread, community.sap_libs#46, is the Ansible SAP community working through what to do about it. It runs from August 2024 to January 2026, and it is a catalogue of options closing:
- "There are no other alternatives to PyRFC, it was the only Python binding for NWRFC SDK C." The same discontinuation took out the Node.js and Go bindings.
- Shell out to
startrfc. Rejected: it means compiling C on every target host, and no one in the group codes C. - Use the SOAP/HTTP RFC handler instead. Closed a year later — it is disabled in all RISE environments as a mandatory prerequisite, per SAP Note 3250501. "It seems SAP just killed all the easy integration options."
- Call SAP's Node RFC library from Python through PythonMonkey. That library ships through the Repository-Based Shipment Channel, behind a customer entitlement, which rules it out for community use.
- A commercial connector, raised in January 2026 and declined the same day: "We are open source project and we cannot include paid solution."
Every route leads back to the same closed binary.
So we did not use the binary
libsapnwrfc.so is the thing you cannot get, cannot audit, and cannot ship. So
we took it out of the picture. We implemented the RFC wire protocol itself, in
Rust, from packet captures of SAP's own library talking to a live ABAP system.
That work has two earlier write-ups. The SAP RFC Protocol, Byte by Byte covers the classic protocol and includes an interactive explorer for a real record. SAP RFC Over WebSockets covers the modern transport and X.509 client-certificate sign-on.
erpl-pyrfc is that protocol core wearing PyRFC's API. It installs from PyPI
as a wheel with nothing else to fetch:
$ pip install erpl-pyrfc
$ python -c "import pyrfc; print(pyrfc.__version__)"
2026.8.29.1
It registers as both pyrfc and erpl_pyrfc, so existing code keeps its
imports.
The wall, and getting over it
Here is the whole install story on a clean python:3.12-slim container. Nothing
is staged: it is one script, and you can
run it yourself.

The middle step is the interesting one. A yanked release is still installable if
you pin it exactly, so pip install pyrfc==3.3.1 gets further than the bare
command. It then stops here:
Environment variable SAPNWRFC_HOME not set.
Please specify this variable with the root directory of the SAP NWRFC Library.
PyRFC 3.3.1 shipped wheels for macOS arm64 and Windows only. On Linux — where most of this code runs — pip falls back to the source distribution and tries to compile it against the SDK. To get the SDK you need an S-user account with the right entitlement. That is the wall.
The same script, both libraries
Claiming compatibility is easy. Here is what we do instead.
02_same_script.py is ordinary PyRFC code. It opens a connection, calls
STFC_CONNECTION, round-trips a structure through STFC_STRUCTURE, reads a
table with RFC_READ_TABLE, and catches a declared ABAP exception. It is
written without regard to which library answers import pyrfc:
import pyrfc
with pyrfc.Connection(config={"rstrip": True}, **params()) as conn:
echo = conn.call("STFC_CONNECTION", REQUTEXT="the same script, either library")
show("ECHOTEXT", echo["ECHOTEXT"])
got = conn.call("STFC_STRUCTURE", IMPORTSTRUCT=sent)["ECHOSTRUCT"]
for field in sorted(sent):
show(field, got[field])
try:
conn.call("RFC_READ_TABLE", QUERY_TABLE="NO_SUCH_TABLE_HERE")
except pyrfc.ABAPApplicationError as error:
show("key", error.key)
run_both.sh runs it twice against the same ABAP system. One virtualenv holds
SAP's PyRFC 3.3.1, built from the yanked source distribution against the real
SDK. The other holds erpl-pyrfc from PyPI. The two libraries never meet — each
has its own environment, and only one is ever on sys.path. Then it diffs the
two outputs:

== SAP PyRFC 3.3.1 (needs /opt/nwrfcsdk on LD_LIBRARY_PATH) ==
== erpl-pyrfc 2026.8.29.1 (no SDK, no library path) ==
== diff ==
no differences across 33 lines
The values are identical: the float keeps its precision, RFCHEX3 comes back as
b'\x01\x02\x03' rather than a string, the exception is an
ABAPApplicationError with key set to TABLE_NOT_AVAILABLE.
Note what differs around the two runs rather than in them. One side needs an
SDK download, an S-user account to obtain it, and 51 MB of shared libraries on
LD_LIBRARY_PATH — 40 MB of that is ICU. The other needs pip install.
The surface migrating code actually uses
A third example walks the parts of the API that real code depends on. It runs against a live system, using only function modules every ABAP system has:

Connection attributes and ping(). Interface introspection. Dates and times as
ABAP strings, or as datetime objects with dtime=True. Per-call options
overriding the connection config. Tables in and out. The exception hierarchy,
where a declared exception carries its name in key and a MESSAGE-raised
failure carries a T100 identity instead. And cancel(), which stops a
twenty-second call after three:
== cancelling a call in flight ==
after '3.0s of a 20s call'
code / key (7, 'RFC_CANCELED')
usable again 'after cancel'
All three examples live in the repository and run as part of the test suite, so a change that breaks one fails the build.
What works, and what does not
Connections — direct, load-balanced, through a SAProuter. call() with dicts
for structures and lists of dicts for tables. The exception hierarchy with
code, key and the msg_* attributes. The rstrip, dtime,
return_import_params and timeout config options.
get_connection_attributes() and get_function_description(). The inbound
Server. Classic tRFC and qRFC units. The snc_* parameter family. Every ABAP
scalar round-trips exactly, including INT8 past 2^53, DECFLOAT34 at all 34
digits, and UTCLONG.
Two things go beyond PyRFC. wsRFC — RFC over a WebSocket with X.509
client-certificate sign-on — which PyRFC had no equivalent for. And
mshost_override, for when a message server reports an address that only
resolves inside the system's own network.
Some things are deliberately absent, and if your code needs one, this is not a drop-in for you:
- bgRFC units. Classic tRFC and qRFC are in scope; the background unit API is not.
Throughput, which measured counters inside the SDK that do not exist here.- ABAP object types and BASXML serialization.
Where behaviour differs, it is written down rather than smoothed over. handle
is a unique counter rather than an SDK pointer. A MESSAGE-raised failure costs
session state, transparently — the connection stays usable, but anything a
stateful function group was holding is gone. The
migration guide
is one page and covers all of it.
One difference deserves stating plainly: the licence is BUSL 1.1, not Apache 2.0. Production use is permitted under the Additional Use Grant; offering it to third parties on a hosted or embedded basis is not.
Where this actually stands
The release gate for this version ran against a live ABAP Platform Trial on kernel 7.58: 378 Rust tests and 182 Python tests pass, with no failures and nothing skipped. The Python suite is mostly live — it talks to a real system rather than a mock.
Eighteen further Rust tests are marked ignored, and it is worth saying which:
the wsRFC suite. Those need a WebSocket endpoint whose client certificate the
system trusts and whose function modules are UCON-released. A stock trial
rejects them with UCON RFC Rejected before they reach the protocol at all.
That is one system, and it is not your landscape. Honesty requires saying so: this has not been used in anger by anyone but us. A trial system is not a production ECC box with twenty years of custom function modules, an unusual codepage, a SAProuter in front, and SNC required.
Which is where we would like help. Do you have SAP systems and Python code that
used to import pyrfc? We would genuinely like to hear what happens when you
point this at them — including, and especially, when it does not work.
Connect with me on LinkedIn and let's talk about how to test it against your landscape. What we want is evidence from systems that are not ours.
$ pip install erpl-pyrfc
The code is at github.com/DataZooDE/erpl-proto.
