Skip to main content

repics

EPICS Channel Access and pvAccess for Python, built on epics-rs. No libca, no libpvxs: the protocol stacks are the Rust crates, compiled into one extension module.

from repics import ca

ca.caput("SIM:ao", 2.5)
v = ca.caget("SIM:ai", form="ctrl")
print(v, v.units, v.severity, v.timestamp)

with ca.camonitor("SIM:cnt", print):
    ...
from repics.ca.asyncio import caget, camonitor

async def main():
    v = await caget(["SIM:ai", "SIM:long"])
    sub = await camonitor("SIM:cnt", print)

Every read returns an augmented value: a float/int/str/numpy.ndarray subclass carrying name, status, severity, timestamp, raw_stamp, and for form="ctrl" the units, precision, limits and enum strings.

Channel Access

caget, caput, camonitor, cainfo and connect take one PV name or a sequence and return a result of the same shape; a sequence is worked on concurrently, connect included, against one timeout. A failure raises CaError (CaTimeout, CaDisconnected); with throw=False it is returned instead as a CaNothing, whose ok is False and whose errorcode is the libca ECA_* status. cainfo returns a CAInfo.

  • form picks the metadata class: "plain", "sts", "time" (default), "gr", "ctrl".
  • datatype overrides the wire type: str, int, float, a numpy dtype, a DBR_* code, or DBR_CHAR_STR / DBR_CHAR_BYTES / DBR_CHAR_UNICODE to read a char waveform as text, DBR_ENUM_STR to read an enum as its label.
  • count is 0 for the server's current element count, negative for the full native count, else a cap.
  • camonitor(pv, callback, ...) returns a Subscription at once and connects in the background; connect_timeout reports a CaNothing with ECA_TIMEOUT if it passes, and the monitor keeps waiting. Callbacks run on one dispatcher thread (ca) or one task per event loop (ca.asyncio), in arrival order. With all_updates=False (default) updates that queued while the callback ran collapse into the latest and dropped_callbacks counts them. notify_disconnect=True delivers a CaNothing with ECA_DISCONN on disconnect; the monitor resumes on reconnection.
  • repics.pv.PV is a pyepics-shaped object (get, put, add_callback, auto_monitor, char_value, units, ...) over one subscription; it raises on failure like the rest of the package.

A CA server hosts bare value channels — caget, caput and camonitor reach them, but there is no record, no RPC and no per-channel connect edge, so the only handler is put:

from repics.ca.server import SharedPV, Server

pv = SharedPV(initial=1.0)

@pv.put
def onput(pv, op):
    pv.post(op.value())      # accept: publish to monitors
    op.done()                # or op.done(error="...") to reject

Server.forever(providers=[{"X:val": pv}])

A SharedPV with no handler refuses every put. post() fans a new value out to monitors; values are plain scalars, strings, lists or 1-D numpy arrays. repics.ca.server.asyncio.SharedPV is the asyncio flavour, whose handler may be a coroutine. Handlers run on Python-owned threads (or the event loop), never on the network runtime.

pvAccess

The API follows p4p: Context, Value/Type, the nt wrappers, and SharedPV/StaticProvider/Server.

from repics.pva import Context
from repics.pva.nt import NTScalar, NTURI

with Context("pva") as ctxt:
    v = ctxt.get("SIM:ai")            # augmented, as for CA
    ctxt.put("SIM:ao", 2.5)
    r = ctxt.rpc("SIM:sum", NTURI([("a", "d"), ("b", "d")]).wrap("SIM:sum", kws={"a": 1, "b": 2}))
    with ctxt.monitor("SIM:cnt", print, notify_disconnect=True):
        ...
from repics.pva.server import SharedPV, Server

pv = SharedPV(nt=NTScalar("d"), initial=1.0)

@pv.put
def onput(pv, op):
    pv.post(op.value())
    op.done()

Server.forever(providers=[{"SIM:ao": pv}])

repics.pva.asyncio.Context and repics.pva.server.asyncio.SharedPV are the asyncio flavours; handlers there may be coroutines. Put and RPC handlers run on Python-owned threads (or the event loop), never on the network runtime, and a slow monitor consumer squashes updates in Rust instead of queueing Python objects. NTNDArray reads are zero-copy views of the received buffer.

Performance

The extension owns one tokio runtime with one worker thread (override with REPICS_WORKERS); every blocking call releases the GIL and waits for its future, every ca.asyncio call returns a future that is already done when the answer is in hand. Metadata is attached to a value lazily, list operations run in Rust as one concurrent batch, and all monitors of a front end feed one bounded queue that is drained in batches, so a callback costs one queue pop rather than one thread wake-up.

bench/bench_ca.py runs repics, pyepics and aioca in separate processes against the same softioc-rs. On a Xeon Gold 6542Y, pinned to four cores with the SMT siblings kept busy so the cores hold their clock (--pin 2,3,4,5 --hot; unpinned, schedutil parks the ping-ponging threads at 800 MHz and every library halves):

repics repics.ca.asyncio pyepics aioca
get, median / p99 35 / 42 us 72 / 103 us 85 / 107 us 59 / 69 us
get 10 000-double waveform 166 / 183 us 161 / 179 us 182 / 269 us 109 / 171 us
get list of 100 PVs, one call 1167 / 1201 us 932 / 976 us 3284 / 3369 us 3043 / 3633 us
put wait=True 39 / 44 us 56 / 63 us 165 / 180 us 66 / 77 us
put wait=False 160 439 /s 144 256 /s 13 704 /s 83 536 /s
monitor, 1 PV, CPU per callback 8.3 us 17.6 us 32.2 us 24.2 us
monitor, 100 PVs, CPU per callback 10.3 us 22.2 us 19.9 us 23.2 us

Monitor rows are under a 20 000-put storm from a separate writer process; every client received 20 099–20 100 of the 20 100 events on the 100-PV row.

bench/bench_pva.py does the same for pvAccess. Client rows run each library in its own process against one repics thread SharedPV server; server rows run each server flavour in its own process, measured by the p4p thread client. Same machine, same pinning:

client repics repics.asyncio p4p p4p.asyncio
get, median / p99 47 / 54 us 69 / 92 us 99 / 131 us 99 / 132 us
get 10 000-double array 90 / 101 us 105 / 136 us 122 / 184 us 122 / 143 us
get list of 100 PVs, one call 2235 / 3628 us 4108 / 6173 us 3980 / 4349 us 4630 / 5110 us
put wait=True 145 / 158 us 194 / 238 us 150 / 186 us 156 / 198 us
put wait=False 7111 /s 5145 /s 6506 /s 6367 /s
monitor, 1 PV, CPU per callback 30.6 us 61.7 us 34.1 us 41.3 us
monitor, 100 PVs, CPU per callback 34.6 us 71.3 us 54.9 us 59.3 us
server repics repics.asyncio p4p p4p.asyncio
get, median / p99 94 / 115 us 99 / 128 us 99 / 133 us 94 / 116 us
put wait=True, through the put handler 147 / 170 us 187 / 239 us 153 / 189 us 148 / 177 us
post storm, 1 PV: delivered of 20 001, server CPU per post 15 842, 13.4 us 17 161, 13.8 us 8 673, 5.9 us 8 318, 5.7 us
post storm, 100 PVs: delivered of 20 100, server CPU per post 18 390, 12.6 us 19 571, 12.5 us 20 100, 6.7 us 20 098, 6.6 us

pvAccess has no fire-and-forget put: every put is a completed round trip, and with the default get=True the current value is read first. repics and pvxs both do this as one two-phase put operation, the readback riding the put's own channel op, so the put rows are on par. The client monitor rows are under a storm from a separate repics writer running four threads of put(wait=True), about 14 000 puts/s, and every client received 19 857 or more of the 20 001 events, so the CPU column is the comparison. The server storm is one thread posting as fast as post() returns; the repics server delivers about twice as many posts as pvxs and spends about 13 us of CPU per post, delivery included, where pvxs spends about 6 us and, on one PV, squashes away more than half of them. Medians of the blocking repics client moved between 44 and 79 us on get across runs of the same command (thread placement inside the four cores); each table is one run.

Reference documentation: docs/index.md.

Building

pip install maturin
maturin develop            # into the active environment
pytest                     # needs `softioc-rs` on PATH or REPICS_SOFTIOC=<path>

softioc-rs is cargo install epics-ca-rs --bin softioc-rs.

Status

Channel Access client and server, blocking and asyncio. pvAccess client and server, blocking and asyncio; tested against p4p on both sides of the wire (pytest tests/pva needs p4p installed).

Metadata

Release files for repics 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distributions (wheels)

Table of built distributions (wheels) for repics 0.3.0
File Interpreter ABI Platform
repics-0.3.0-cp310-abi3-win_amd64.whl CPython 3.10 abi3 Windows x86-64 Details
repics-0.3.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.10 abi3 Linux glibc 2.17+ x86-64 Details
repics-0.3.0-cp310-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl CPython 3.10 abi3 macOS 10.12+ x86-64, macOS 10.12+ universal2 (ARM64, x86-64), macOS 11.0+ ARM64 Details

Total release size: 24.0 MB

Release files / repics-0.3.0-cp310-abi3-win_amd64.whl

Download URL repics-0.3.0-cp310-abi3-win_amd64.whl
Size 6.5 MB
Tags CPython 3.10 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
3ced1f5a5813c74ae8fb36a43aa4869eca6bb06941d1451cd983684fb771811c
BLAKE2b-256 checksum
How to use checksums
a840a62eb5cb933452233dd575a043ab9ac649d57431b7a60025338a4ae1ad0f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.

Transparency log

Release files / repics-0.3.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL repics-0.3.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 6.2 MB
Tags CPython 3.10 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
2fa80a76d97aaa7f1847a9d6aa2eae9f359cdc29083889aec409bd73b9e2ed61
BLAKE2b-256 checksum
How to use checksums
a9cd973585e0432f4af47a44b39005f1bdae7b48914ea3fe24126e54a7d94cc1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.

Transparency log

Release files / repics-0.3.0-cp310-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl

Download URL repics-0.3.0-cp310-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
Size 11.3 MB
Tags CPython 3.10 abi3 macOS 10.12+ universal2 (ARM64, x86-64) macOS 10.12+ x86-64 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
9560011fa32e92c9477400c409251f09691327be54b26a71ff10f40f4af3c13c
BLAKE2b-256 checksum
How to use checksums
e01c3f8e9d492b6b07343b365b766dfa0b454f582d10c7254e609ed13f84742a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.3.0 This release

3 release files

0.2.1

3 release files

0.2.0

3 release files

0.1.0

3 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page