uipath-ipc
Python client and server for UiPath.Ipc — an interface-based RPC framework with .NET server and client, TypeScript client, and now a Python client and server.
This package speaks the same wire protocol as the .NET package, so a Python client can talk to any UiPath.Ipc server (and a Python IpcServer can host services for any client).
Status
- Scope: client (
IpcClient) and server (IpcServer), with bidirectional callbacks. Stream uploads/downloads are not implemented. - Transports: Named Pipe, TCP. (WebSocket is on the roadmap.)
- Python: 3.10+.
Install
pip install uipath-ipc
Quick start
1. Define a contract
The contract is a Python ABC whose method names exactly match the .NET interface methods. Each method must be async def.
from abc import ABC, abstractmethod
class IComputingService(ABC):
@abstractmethod
async def AddFloats(self, x: float, y: float) -> float: ...
@abstractmethod
async def Wait(self, duration: float) -> bool: ...
2. Create a client and call methods
import asyncio
from uipath_ipc import IpcClient, NamedPipeClientTransport
async def main() -> None:
transport = NamedPipeClientTransport(pipe_name="test")
async with IpcClient(transport) as client:
svc = client.get_proxy(IComputingService)
result = await svc.AddFloats(1.5, 2.5)
print(result) # 4.0
asyncio.run(main())
The proxy returned by get_proxy(IComputingService) looks like an instance of the contract to your editor and type checker — call its methods normally.
Features
Cancellation
Cancellation in Python is task-based, not token-based. You cancel by cancelling the task that's awaiting:
task = asyncio.create_task(svc.Wait(10.0))
await asyncio.sleep(0.1)
task.cancel() # CancelledError propagates up through await
When the proxy observes CancelledError it re-raises it locally. Whether the server is also told to cancel depends on the method: an @ipc_cancellable method (see below) sends a CancellationRequest frame matching the in-flight request id; an unmarked method like Wait above cancels locally only (the server keeps running).
@ipc_cancellable and .NET CancellationToken
Because cancellation is task-based, a Python contract never declares a CancellationToken parameter — it's delivered out-of-band, not as an argument. When a method's .NET counterpart ends with a CancellationToken, mark it with @ipc_cancellable:
from uipath_ipc import ipc_cancellable
class IRobotService(ABC):
@ipc_cancellable
@abstractmethod
async def LongRunning(self, count: int) -> int: ...
# .NET: Task<int> LongRunning(int count, CancellationToken ct = default)
The marker controls whether a local cancellation is forwarded to the peer. Cancel (or time out) the task awaiting an @ipc_cancellable call and the client sends a CancellationRequest so the peer can cancel its handler. Cancel an unmarked call and nothing is sent — the cancellation stays local, because a peer with no CancellationToken has nothing to act on.
It does not change the request's arguments: the token is never a parameter, so Request.Parameters is unaffected. The .NET server fills the missing trailing CancellationToken slot with a default and injects the real token by type; a Python server ignores the empty-string slot a .NET client sends for its token.
One constraint: .NET accepts a CancellationToken at any position (matched by type), but the Python signature omits it, so in a .NET↔Python pairing the token must be the last .NET parameter — otherwise the trailing arguments misalign on the wire.
Timeouts
Configure a per-client default:
async with IpcClient(transport, request_timeout=5.0) as client:
...
Or override per-call with asyncio.timeout (3.11+) / asyncio.wait_for:
async with asyncio.timeout(1.0):
await svc.Wait(10.0) # raises TimeoutError after 1s
In both cases the call raises locally. The server is notified via a CancellationRequest only for an @ipc_cancellable method; for an unmarked method like Wait the timeout is local-only and the server runs to completion.
For a single call, pass a Message argument carrying the timeout — it overrides the client default for that call only:
from uipath_ipc import Message, INFINITE_REQUEST_TIMEOUT
await svc.Install(pkg, Message(request_timeout=1200)) # 20-minute call
await svc.SignIn(creds, Message(request_timeout=INFINITE_REQUEST_TIMEOUT)) # no deadline
INFINITE_REQUEST_TIMEOUT is the .NET Timeout.InfiniteTimeSpan rendition: no client-side deadline, and the server reads it as "no timeout". A request_timeout of 0 means "use the server's default" (it does not override the client default).
Exception propagation
Server-side exceptions surface as RemoteException:
from uipath_ipc import RemoteException
try:
await svc.DivideByZero()
except RemoteException as ex:
print(ex.message) # "Attempted to divide by zero."
print(ex.type_name) # "System.DivideByZeroException"
print(ex.stack_trace) # the .NET stack
print(ex.inner) # inner RemoteException (chain), or None
ex.is_remote_type("System.DivideByZeroException") # True — .NET Is<T>() analog
__cause__ is set on the exception chain so Python tracebacks display the inner errors naturally.
Callbacks (server → client)
The server can invoke methods on objects that the client hosts. Define the callback contract, pass an instance to IpcClient(callbacks={...}), and the proxy on the server side can call into your Python object:
from abc import ABC, abstractmethod
class IClientCallback(ABC):
@abstractmethod
async def EchoToClient(self, value: str) -> str: ...
class EchoHandler:
async def EchoToClient(self, value: str) -> str:
return f"echoed: {value}"
async with IpcClient(transport, callbacks={IClientCallback: EchoHandler()}) as client:
tester = client.get_proxy(ICallbackTester)
print(await tester.TriggerEcho("hi")) # "echoed: hi"
Callback methods must be async def (like service handlers): a synchronous handler runs inline on the event loop, blocking the whole connection for its duration and escaping the request timeout. Exceptions raised inside the handler are wired back to the server as RemoteException. Server-initiated cancellations cancel the in-flight handler task.
Hooks
Two optional hooks let you observe or gate the client (the analog of .NET's BeforeConnect / BeforeOutgoingCall). Each may be sync or async; raising in a hook aborts the connect/call.
from uipath_ipc import CallInfo
async def launch_server() -> None:
... # e.g. lazily start the server before the first connect (self-healing)
def log_call(ci: CallInfo) -> None:
print(ci.endpoint, ci.method_name, ci.arguments, ci.new_connection)
async with IpcClient(transport, before_connect=launch_server, before_call=log_call) as client:
...
before_connect runs before each (re)connect; before_call runs before each outgoing call with a CallInfo (endpoint, method_name, arguments, and new_connection — True only on the call that opened the connection).
Custom serialization (advanced)
The proxy materializes results into a contract's declared return type via reflection — bytes, UUID, datetime, Decimal, enums, and dataclasses all round-trip (see Features above). If you need to (de)serialize values yourself, the same primitives are exported as from_wire(value, hint) / to_wire(value). The contract vocabulary is intentionally narrow — plain JSON values and dataclasses — so the IPC layer stays decoupled from any modeling framework (pydantic, ORM entities, …); map IPC DTOs to your own validated/domain types at your boundary if you need them.
Auto-reconnect
The client opens a connection lazily on the first call and reuses it. If the underlying stream drops (server restart, network blip), the next call transparently re-dials via the transport. The proxy instance remains valid across reconnects.
In-flight calls when the drop happens propagate the underlying error rather than silently retrying — that's the caller's policy choice.
Transports
from uipath_ipc import NamedPipeClientTransport, TcpClientTransport
NamedPipeClientTransport(pipe_name="test") # local
NamedPipeClientTransport(pipe_name="test", server_name="REMOTE") # remote (Windows)
TcpClientTransport(host="127.0.0.1", port=5050)
Custom transports are easy: subclass ClientTransport and implement connect().
What's NOT implemented (yet)
- Streams (UploadRequest / DownloadResponse message types). Add on demand.
- WebSocket transport. Pending; will be an optional extra.
- Configurable max message size — the 2 MB cap (matching .NET's default) is fixed; .NET's
MaxReceivedMessageSizeInMegabytesknob isn't exposed yet.
Not supported / undefined behaviour (by design)
Distinct from the "not yet" list above — these are deliberate boundaries. The contract is a shared agreement between both peers, and the library trusts it rather than policing every misuse. This is IPC, not a versioned schema layer like gRPC/protobuf.
- Both peers must agree on the contract. Every argument and return must be serializable in both counterparts' worlds. A contract/wire mismatch — a field the other side can't decode, a value-typed return answered with empty
Data, a result that doesn't match the declared type — is undefined behaviour: you may get a raw value, aNone, or an opaque error. The library does not hand-hold with clean per-case diagnostics; iterate the contract until both sides round-trip. - Variadic
*argsare delivered undecoded. A handler's*argselements arrive as their raw JSON-parsed values, not materialized to the declared element type — decoding*args: Tis unsupported (no .NET/TS use case). Use explicit, individually-typed parameters. - Argument count must match the contract. Too few args for a required parameter raises a loud
TypeError(rather than silently filling a default); extra trailing args are ignored. A wrong arg count is a contract mismatch, not something the library papers over. - Request ids are library-managed — unique per connection, generated by the proxy. Cancellation and response correlation key off them, so don't fabricate or reuse them on the low-level
RequestAPI. - A
CancellationTokenmust be the last .NET parameter in a .NET↔Python pairing — see the@ipc_cancellablemarker under Cancellation.
Development
# Clone, set up env
py -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -e ".[dev]"
# Run tests
pytest
# Build wheel + sdist
pip install build
python -m build
Wire protocol cheat sheet
- Frame: 5-byte header + UTF-8 JSON payload.
- Header:
[MessageType: uint8][PayloadLength: int32 LE]. - Message types:
Request=0,Response=1,CancellationRequest=2,UploadRequest=3,DownloadResponse=4. - Request.Parameters is a list of individually JSON-encoded strings —
[\"1.5\", \"\\\"hi\\\"\"], not[1.5, \"hi\"].
License
MIT.
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 uipath_ipc-2.5.2.tar.gz.
File metadata
- Download URL: uipath_ipc-2.5.2.tar.gz
- Upload date:
- Size: 86.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d69c3d7c1ad1a25ef7f9f1d78c505f5d2ed7daa7227bb202404fa3d47e0ba12e
|
|
| MD5 |
e63e2110ffe99a76135898aae7c253dc
|
|
| BLAKE2b-256 |
2c6b53d9725d6abd1dab447300a7f999332f530678e3fabd38fc31171a5a9a6f
|
Provenance
The following attestation bundles were made for uipath_ipc-2.5.2.tar.gz:
Publisher:
cd.yml on UiPath/coreipc
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
uipath_ipc-2.5.2.tar.gz -
Subject digest:
d69c3d7c1ad1a25ef7f9f1d78c505f5d2ed7daa7227bb202404fa3d47e0ba12e - Sigstore transparency entry: 2232964543
- Sigstore integration time:
-
Permalink:
UiPath/coreipc@527f02a97443616f17eb8151458e6bced71b6e63 -
Branch / Tag:
refs/heads/master - Owner: https://github.com/UiPath
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
cd.yml@527f02a97443616f17eb8151458e6bced71b6e63 -
Trigger Event:
repository_dispatch
-
Statement type:
File details
Details for the file uipath_ipc-2.5.2-py3-none-any.whl.
File metadata
- Download URL: uipath_ipc-2.5.2-py3-none-any.whl
- Upload date:
- Size: 50.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
617f25f35377d87956165a875b0966a3cd69ae6724fcd533c93a7a6a9460fc4f
|
|
| MD5 |
2876f8cff61c81c7e4b0eef029e62ffd
|
|
| BLAKE2b-256 |
29eb6def505a0d351119da27b342c11eb0767a31a7f100022d35e349c5194eb3
|
Provenance
The following attestation bundles were made for uipath_ipc-2.5.2-py3-none-any.whl:
Publisher:
cd.yml on UiPath/coreipc
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
uipath_ipc-2.5.2-py3-none-any.whl -
Subject digest:
617f25f35377d87956165a875b0966a3cd69ae6724fcd533c93a7a6a9460fc4f - Sigstore transparency entry: 2232965420
- Sigstore integration time:
-
Permalink:
UiPath/coreipc@527f02a97443616f17eb8151458e6bced71b6e63 -
Branch / Tag:
refs/heads/master - Owner: https://github.com/UiPath
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
cd.yml@527f02a97443616f17eb8151458e6bced71b6e63 -
Trigger Event:
repository_dispatch
-
Statement type: