Metadata-Version: 2.4
Name: p4rust
Version: 0.1.15
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Rust
Classifier: Typing :: Typed
Summary: Typed streaming Perforce bindings backed by P4Rust
License-Expression: MIT
Requires-Python: >=3.9
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Repository, https://github.com/CatScarf/P4Rust

# P4Rust

Safe Rust bindings to the Perforce 2026.1 C++ API. Precompiled resources support Windows MSVC, Linux GNU, and macOS on x64 and ARM64. Consumers compile only Rust.

## Published Packages

| Language | Link | Compatibility |
| --- | --- | --- |
| Rust | [crates.io](https://crates.io/crates/p4rust) | Windows MSVC, Linux GNU (glibc 2.28+), macOS 12+; x64 and ARM64. Consumers compile only Rust. |
| Python | [PyPI](https://pypi.org/project/p4rust/) | CPython 3.9+ (`cp39-abi3`), including free-threaded CPython 3.15+ (`cp315-abi3.abi3t`); Windows, Linux (glibc 2.28+), macOS 12+; x64 and ARM64. |

## Rust

```toml
[dependencies]
p4rust = "0.1"
```

```rust
use p4rust::{Client, Config, Event, Result, ResultExt};

// Stream file records, progress, and command completion.
fn main() -> Result<()> {
    let client = Client::new(Config::new("localhost:1666", "user", "workspace"))
        .context("Failed to create client")?;
    let events = client
        .command("sync")
        .args(["D:/workspace/file.txt"])
        .run()
        .context("Failed to start sync")?;

    for event in events {
        match event.context("Failed to sync file")? {
            Event::Record(record) => {
                for (key, value) in record.fields() {
                    println!("{key}: {value}");
                }
            }
            Event::Progress(progress) => {
                println!(
                    "{:?}: {}/{}",
                    progress.kind, progress.current, progress.total
                );
            }
            Event::Message(message) | Event::HandleError(message) => {
                println!("{}", message.text);
            }
            event => println!("{event:#?}"),
        }
    }
    Ok(())
}
```

`run()` streams events; `collect_output()` collects results. Configure input, timeout, and cancellation on the builder.

Use `FastReconcile` for parallel local processing.

```rust
let events = client
    .command("reconcile")
    .args(["-n", "-m", "D:/workspace/..."])
    .reconcile_handler(
        p4rust::FastReconcile::new()
            .metadata_workers(8)
            .digest_workers(4)
            .move_workers(4),
    )
    .run()
    .context("Failed to start reconcile")?;

for event in events {
    println!("{:#?}", event.context("Failed to reconcile")?);
}
```

`-n` previews changes; `-m` checks timestamps before contents; `-M` enables move detection.

## Python

Install [p4rust from PyPI](https://pypi.org/project/p4rust/) with `pip install p4rust`. Wheels support ordinary CPython 3.9+ and free-threaded CPython 3.15+ on the same six platforms. Public APIs include complete typing and preserve native message codes, bytes, progress, and completion status. The Python ZIP is also available from [GitHub Releases](https://github.com/CatScarf/P4Rust/releases).

```python
from p4rust import Client, Config, RecordEvent

client = Client(Config("localhost:1666", "user", "workspace"))
events = (
    client
    .command("files")
    .args(["D:/workspace/..."])
    .timeout(30.0)
    .run()
)
with events:
    for event in events:
        if isinstance(event, RecordEvent):
            print(event.record.fields())
```

Use `.input(form)`, `.cancellation(token)`, or `.reconcile_handler(FastReconcile())` before `.run()`. `collect_output()` collects the remaining stream; `close()` cancels a command.

### Reusing a connection

`client.command()` opens an independent connection for each command. Use a session for sequential queries on one dedicated SDK thread:

```python
with client.session() as session:
    with session.command("info").run() as events:
        output = events.collect_output()
    with session.command("files").args(["D:/workspace/..."]).run() as events:
        output = events.collect_output()
```

Rust callers use `let session = client.session()?`, then `session.command(...)` and `session.close()?`. A session opens its connection lazily and retains it between commands. A busy or closed session rejects new work immediately. Use independent commands or separate sessions for concurrency.

Stream cancellation, deadlines, output backpressure, and reconcile handlers remain command-local. A cancelled, failed, or disconnected native command discards its connection; a later command reconnects without replaying the failed operation. Closing a session cancels active work and requests cleanup on the owner thread without blocking the caller. Command builders and streams retain their session, and the last owner also requests cleanup when dropped.

## Maintainers

[xtask](xtask/README.md) builds native resources and packages all six targets. GitHub Actions runs builds, Clippy, and Python typing checks, publishes changed crates.io versions and Python wheels to PyPI, and releases separate Rust and Python ZIPs.

