Skip to main content

PyRFC Is Archived. Here Is a Drop-In Replacement.

· 9 min read
Joachim Rosskopf
Co-Founder & CEO

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.

Installing PyRFC on a clean machine today, then installing erpl-pyrfc

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:

The same script run under SAP PyRFC 3.3.1 and under erpl-pyrfc, with the outputs diffed

== 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:

A tour of the PyRFC-compatible API against a live ABAP system

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.