Call a persistent R interpreter from Python
Project description
🌉 r_bridge
Call a persistent R interpreter from Python — with a Pythonic API, full numpy/pandas support, and zero ceremony.
📚 Documentation · 🔍 Example script
from r_bridge import RBridge
with RBridge() as r:
r.x = [1, 2, 3, 4, 5]
print(r.mean(r.x)) # 3.0
✨ Why r_bridge?
Python and R each have unique strengths. Rather than rewriting R code or
shelling out to Rscript for every call, r_bridge keeps a single R process
alive for the lifetime of your session. Calls are fast, state is shared, and
the API feels natural on both sides.
- 🚀 No subprocess-per-call overhead — R starts once, stays running
- 🔄 Automatic type conversion — Python lists, numpy arrays, pandas DataFrames, datetimes all round-trip transparently
- 🔒 Thread-safe — a single lock serialises concurrent calls; safe to use from threads
- 🛡️ Robust — a sentinel-prefixed protocol means stray
cat()output never corrupts the stream
📦 Installation
Requires Python ≥ 3.11 and R installed on your PATH.
pip install r-bridge
🪟 Windows note: R 4.2+ (UCRT build) is required for correct UTF-8 handling.
🚀 Quick start
from r_bridge import RBridge
import pandas as pd
import numpy as np
with RBridge() as r:
# Set and get variables
r.name = "world"
print(r.paste("hello", r.name)) # "hello world"
# Vectors and numpy arrays
r.v = np.linspace(0, 1, 100)
print(r.mean(r.v)) # 0.5
# DataFrames
r.df = pd.DataFrame({"x": [1, 2, 3], "y": [4.0, 5.0, 6.0]})
result = r.df # returns a pandas DataFrame
# Arbitrary R expressions
r.eval("model <- lm(y ~ x, data=df)")
coefs = r.coef(r.model)
# Keyword arguments become named R arguments
r.seq(1, 10, by=2) # [1, 3, 5, 7, 9]
📖 API reference
RBridge(...) — constructor
| Parameter | Default | Description |
|---|---|---|
r_executable |
None |
Path to Rscript; auto-detected from PATH if omitted |
startup_timeout |
15.0 |
Seconds to wait for R ready signal |
call_timeout |
None |
Per-call timeout in seconds (None = no timeout) |
env |
{} |
Extra environment variables for the R subprocess |
r_libs |
[] |
Paths prepended to R_LIBS_USER |
log_level |
"WARNING" |
Python logging level for the r_bridge logger |
verbose |
False |
Print all R calls and R output to stderr in real time |
Attribute access (primary interface)
r.x = value # set a variable in R's global environment
value = r.x # get a variable from R
r.mean([1, 2, 3]) # call an R function; returns a Python value
Explicit methods
r.set("x", value) # set with optional type_hint
r.get("x", result_type_hint="scalar") # get with type conversion hint
r.call("mean", [1,2,3], timeout=5.0) # call with per-call timeout
r.eval("x <- 1 + 1", result_type_hint=None)
r.ls() # list names in R global env
r.ping() # round-trip latency in seconds
r.last_stderr # accumulated R stderr as string
result_type_hint values
| Hint | Effect |
|---|---|
"scalar" |
Return first element as a Python scalar |
"numpy" |
Return as numpy.ndarray |
"pandas" |
Return as pandas.Series or DataFrame |
"list" |
Return as Python list |
"raw" |
Return the raw tagged JSON dict, no conversion |
🔀 Type conversion
| Python → | → R |
|---|---|
int, float, bool, str, None |
scalar atomic / NULL |
list (homogeneous) |
atomic vector |
dict |
named list() |
numpy.ndarray 1-D |
atomic vector |
numpy.ndarray 2-D |
matrix |
pandas.DataFrame |
data.frame |
pandas.Categorical |
factor |
datetime.datetime |
POSIXct |
datetime.date |
Date |
↕️ All conversions are bidirectional. Special R values (NA, NaN, Inf, factor, matrix, …) all round-trip correctly.
🔍 Verbose mode
Set verbose=True to trace every R call and see R's output in real time — great for debugging.
with RBridge(verbose=True) as r:
r.x = [1, 2, 3]
r.mean(r.x)
[R set] x = {'__type__': 'integer_vector', 'value': [1, 2, 3]}
[R call] mean([...])
[R stdout] ... ← from cat() in R
[R stderr] ... ← from message() or warnings in R
⚠️ Error handling
from r_bridge.exceptions import RError, RTimeoutError
try:
r.eval("stop('something went wrong')")
except RError as e:
print(e.message) # "something went wrong"
print(e.warnings) # list of captured R warnings
print(e.traceback) # R traceback as list of strings
try:
r.eval("Sys.sleep(99)", timeout=1.0)
except RTimeoutError:
print("R took too long")
Exception hierarchy
RBridgeError base class
├── RStartupError R failed to start
├── RTimeoutError call exceeded timeout
├── RError R-side error (carries .message, .call, .traceback, .warnings)
└── RProtocolError malformed response envelope
🔌 Custom type converters
Register serializers and deserializers without modifying the library:
from r_bridge import register_serializer, register_deserializer
register_serializer(MyType, lambda obj: {"__type__": "mytype", "value": obj.to_dict()})
register_deserializer("mytype", lambda d: MyType.from_dict(d["value"]))
⚙️ How it works
r_bridge spawns Rscript once and communicates over stdin/stdout using
line-delimited JSON with a __RBRIDGE__: sentinel prefix on every protocol
line. Stray cat() or print() output is captured and logged rather than
corrupting the stream.
🐍 Python ──► __RBRIDGE__:{"id":"…","op":"call_func","payload":{…}}
📊 R ──► __RBRIDGE__:{"id":"…","status":"ok","payload":{…},"warnings":[]}
A dedicated daemon thread drains R's stderr continuously to prevent
pipe-buffer deadlocks. A single threading.Lock serialises concurrent Python
calls.
🧪 Development
uv run pytest # all tests
uv run pytest tests/test_bridge.py -v # integration tests (spawns real R)
uv run pytest tests/test_serializer.py # unit tests (no R needed)
📄 License
MIT
Project details
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 r_bridge-0.7.0.tar.gz.
File metadata
- Download URL: r_bridge-0.7.0.tar.gz
- Upload date:
- Size: 92.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.12 {"installer":{"name":"uv","version":"0.9.12"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b5670faffb2638dc37043721cded6f887e65aa70eda18970e7c77559dfeafacf
|
|
| MD5 |
46aff46a98a136c4c8219930064a15b7
|
|
| BLAKE2b-256 |
f7fcd5550d40913896d84e551fda47ac313bb601cd1a07efc3fb2b94905453e8
|
File details
Details for the file r_bridge-0.7.0-py3-none-any.whl.
File metadata
- Download URL: r_bridge-0.7.0-py3-none-any.whl
- Upload date:
- Size: 20.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.12 {"installer":{"name":"uv","version":"0.9.12"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a1176676fef9c68494dde3cf3c021293b8fcda4bb0b80d77b605fafa2ff0827e
|
|
| MD5 |
9abfd68d68f74653a08ccd63080598cb
|
|
| BLAKE2b-256 |
3c86ccc5ea9af117abe6867aa181f3dbce92cc0907a9481e5980673eb66b5282
|