# remotefunc

**Call Python functions across different processes as easily as local functions.**

An ultra-simple, zero-configuration RPC (Remote Procedure Call) library that makes inter-process communication (IPC) in Python feel like magic. No complex setup, no message queues, no serialization headaches - just pure Python functions that work across process boundaries.

## Quick Start

### Server Process (daemon.py)
```python
from remotefunc import shared
import time

# Your shared data
items = []

# Make any function callable from other processes
@shared
def push_new_item(item: dict) -> int:
    items.append(item)
    return len(items)

@shared
def get_items() -> list:
    return items

# more code that keeps this process running
```

### Client Process (controller.py)
```python
from daemon import push_new_item, get_items

# Call remote functions exactly like local functions!
new_id = push_new_item({'x': 10, 'y': 20})
print(f'New item pushed, id: {new_id}')  # Output: New item pushed, id: 1

all_items = get_items()
print(f'All items: {all_items}')  # Output: All items: [{'x': 10, 'y': 20}]
```

**That's it!** No server setup, no configuration files, no complicated API calls.

## Installation

```bash
pip install remotefunc
```

Or install from source:
```bash
git clone https://github.com/mortasen/remotefunc.git
cd remotefunc
pip install -e .
```

## What is this library perfect for?

- 🔄 **Sharing data between Python processes** without files or databases
- 🎮 **Building daemon/controller architectures** with minimal boilerplate
- 🚀 **Distributed Python applications** that need simple process communication
- 🛠️ **Microservices in Python** without the complexity of gRPC or REST frameworks
- 📡 **Inter-process communication (IPC)** made simple and Pythonic
- 🔌 **Remote procedure calls (RPC)** without learning new protocols

## Why remotefunc?

**The Problem:** You have two Python processes that need to share data or call each other's functions. Traditional solutions are complicated:
- Files? Slow and error-prone
- Databases? Overkill for simple communication
- Sockets? Too low-level
- Message queues (RabbitMQ, Redis)? Too heavy
- multiprocessing.Queue? Only works with parent/child processes
- REST APIs? Too much boilerplate

**The Solution:** With `remotefunc`, just add one decorator and import functions normally. That's it.


## Features

✨ **Zero Configuration** - Server starts automatically, no setup required  
🎯 **Transparent RPC** - Remote calls look identical to local calls  
🔒 **Type Safe** - Full support for type hints and IDE autocomplete  
⚡ **Fast** - HTTP-based with smart JSON/pickle serialization  
🐛 **Great Debugging** - Remote exceptions include full tracebacks  
🪶 **Lightweight** - No external dependencies, pure Python  
🔄 **Stateful** - Share data structures across processes easily  
📦 **Works Everywhere** - Any Python 3.7+ on Windows, macOS, Linux

## Use Cases

### 1. Daemon + Controller Pattern
Perfect for background services controlled by CLI tools:
```python
# daemon.py - runs continuously
@shared
def start_task(name): ...

@shared  
def get_status(): ...

# cli.py - user commands
from daemon import start_task, get_status
start_task("backup")
print(get_status())
```

### 2. Multi-Process Data Sharing
Share state between independent Python processes:
```python
# data_server.py
cache = {}

@shared
def set(key, value):
    cache[key] = value

@shared
def get(key):
    return cache.get(key)

# Any other process can now access the shared cache
from data_server import set, get
set("user_123", {"name": "Alice"})
```

### 3. Distributed Task Processing
Coordinate work across multiple Python processes:
```python
# coordinator.py
jobs = []

@shared
def submit_job(task):
    jobs.append(task)
    return len(jobs)

# worker.py
from coordinator import submit_job
submit_job({"type": "process_video", "file": "video.mp4"})
```

### 4. Real-time Monitoring
Monitor long-running processes from separate scripts:
```python
# long_process.py
progress = 0

@shared
def get_progress():
    return progress

# monitor.py
from long_process import get_progress
while True:
    print(f"Progress: {get_progress()}%")
    time.sleep(1)
```

## How It Works

1. **Decorator Registration**: `@shared` registers your function and starts an HTTP server (first time only)
2. **Smart Detection**: When imported from another process, creates a remote proxy
3. **HTTP Communication**: Client makes POST request → Server executes function → Returns result
4. **Transparent Errors**: Exceptions are serialized and re-raised on the client side

```
┌─────────────────┐         HTTP POST          ┌─────────────────┐
│  Client Process │  ────────────────────────>  │  Server Process │
│                 │                             │                 │
│  from daemon    │   {"function": "push",     │  @shared        │
│  import func    │    "args": [item]}         │  def push():    │
│                 │                             │      items.add  │
│  func(item)     │  <────────────────────────  │      return len │
│                 │   {"result": 1}            │                 │
└─────────────────┘                             └─────────────────┘
```

## API Reference

### `@shared`
Make any function callable from other processes.

```python
from remotefunc import shared

@shared
def my_function(x: int, y: int) -> int:
    """Full type hints and docstrings work perfectly"""
    return x + y
```

### `configure(host=None, port=None)`
Configure global settings for remote calls.

```python
from remotefunc import configure

# Call this in the client process before importing shared functions
configure(host='192.168.1.10', port=8080)
```

### `get_server_port()`
Get the current server port (default: 5555).

```python
from remotefunc import get_server_port
print(f"Server running on port {get_server_port()}")
```

### `shutdown_server()`
Gracefully shutdown the server.

```python
from remotefunc import shutdown_server
shutdown_server()
```

## Advanced Usage

### Custom Port
```python
from remotefunc.server import start_server

start_server(port=8080)  # Start before decorating functions

@shared
def my_function():
    pass
```

### Error Handling
Remote exceptions are propagated with full context:

```python
from daemon import risky_function
from remotefunc.client import RemoteCallError

try:
    result = risky_function()
except RemoteCallError as e:
    print(f"Remote call failed: {e}")
    # Full traceback from the server process is included!
```

### Complex Data Types
Works with JSON-serializable data and falls back to pickle:

```python
@shared
def process_data(data: dict, callback: callable = None):
    # Dicts, lists, primitives → JSON (fast)
    # Complex objects → pickle (automatic)
    return result
```

## Comparison with Alternatives

| Solution | Setup Complexity | Use Case | remotefunc Equivalent |
|----------|-----------------|----------|---------------------|
| Files/JSON | Low | Slow data sharing | ✅ Just use @shared |
| Sockets | High | Custom protocols | ✅ Just use @shared |
| multiprocessing.Queue | Medium | Parent-child only | ✅ Works with any processes |
| Redis/RabbitMQ | Very High | Production scale | ❌ Use those for production |
| REST API (Flask) | High | HTTP services | ✅ But with zero boilerplate |
| gRPC | Very High | Performance critical | ❌ Use gRPC for that |
| XML-RPC | Medium | Legacy systems | ✅ Modern Python approach |

## FAQ

**Q: How do two processes discover each other?**  
A: The server automatically starts on localhost:5555. Client imports create proxies that connect to this address.

**Q: What if the server process isn't running?**  
A: You'll get a clear `RemoteCallError` indicating connection failure.

**Q: Can I use this in production?**  
A: It's great for internal tools, daemons, and development. For production microservices, consider gRPC or proper REST APIs with authentication.

**Q: Does it work with threading/asyncio?**  
A: Yes! The server is multi-threaded and can handle concurrent requests. Async functions need to be wrapped with `asyncio.run()`.

## Security Warning ⚠️

`remotefunc` uses **`pickle`** as a fallback serialization mechanism for complex Python objects. 

**NEVER use this library across untrusted networks or to communicate with untrusted clients.**

An attacker who can send data to the `remotefunc` server (default port 5555) can execute arbitrary code on your machine. This library is designed for **localhost communication** or within a strictly secured, trusted private network.

## Examples

See the `examples/` directory:
- `daemon.py` - Full-featured server with multiple shared functions
- `controller.py` - Client demonstrating all remote call patterns

## Contributing

Contributions welcome! Please open an issue or PR on GitHub.

## License

MIT License - see LICENSE.md file for details.

## Keywords for Search

`python ipc` `python rpc` `inter process communication python` `share data between python processes` `python process communication` `python remote function call` `python daemon controller` `python distributed` `call function in another process python` `python multiprocessing communication` `python process synchronization`


---

**Made with ❤️ for developers who want IPC/RPC to "just work"**