Metadata-Version: 2.4
Name: python-9p
Version: 0.1.0
Summary: Pythonic 9P2000 messages and client helpers backed by plan9port's C wire codec.
License-Expression: MIT
Project-URL: Homepage, https://github.com/kiljoy001/py-9p
Project-URL: Repository, https://github.com/kiljoy001/py-9p
Project-URL: Issues, https://github.com/kiljoy001/py-9p/issues
Keywords: 9p,9p2000,plan9,filesystems
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: System :: Filesystems
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: test
Requires-Dist: pytest>=7.0; extra == "test"
Requires-Dist: hypothesis>=6.0; extra == "test"
Provides-Extra: security
Requires-Dist: atheris>=3.0; extra == "security"
Requires-Dist: bandit>=1.7; extra == "security"
Requires-Dist: pip-audit>=2.7; extra == "security"
Requires-Dist: mutmut>=2.5; extra == "security"
Provides-Extra: dev
Requires-Dist: ruff>=0.6; extra == "dev"
Dynamic: license-file

# py9p

Pythonic 9P2000 messages and client helpers backed by plan9port's C wire
codec. The Python API is intentionally small: dataclasses model the protocol,
`to_bytes()` uses the native codec, and `Client` gives scripts a synchronous
multiplexed request/response wrapper.

## Quick API

```python
from py9p import Tversion, Twalk, decode_message

wire = Tversion(msize=8192).to_bytes()
assert decode_message(wire) == Tversion(msize=8192)

walk = Twalk(fid=0, newfid=1, wname=("usr", "glenda", "notes"))
assert decode_message(walk.to_bytes()) == walk
```

Stats are first-class dataclasses too:

```python
from py9p import DMDIR, DMEXEC, DMREAD, Dir, Qid

entry = Dir(
    qid=Qid(type=0x80, vers=7, path=42),
    mode=int(DMDIR | DMREAD | DMEXEC),
    length=0,
    name="notes",
    uid="glenda",
    gid="glenda",
    muid="glenda",
)
assert Dir.from_bytes(entry.to_bytes()) == entry
```

For real transports, use the synchronous multiplexed client:

```python
from py9p import Client, OREAD

with Client.connect_tcp("127.0.0.1", 564) as c:
    c.negotiate()
    c.attach(fid=0, uname="glenda")
    c.walk(fid=0, newfid=1, path="usr/glenda/notes")
    c.open(fid=1, mode=int(OREAD))
    data = c.read(fid=1, count=4096)
    c.clunk(fid=1)
```

You can drop down a level whenever you need exact protocol control:

```python
from py9p import Rread, read_message, write_message

write_message(sock, Rread(tag=3, data=b"hello"))
msg = read_message(sock)
```

`Client.rpc()` manages tags by default. Pass `tag=...` only when you need a
specific protocol tag for a low-level probe.

## Scope

This first slice targets 9P2000 message and stat encoding/decoding plus a
small synchronous client with multiplexed tagged RPC. It intentionally does
not implement auth helpers, server dispatch, namespace/exportfs orchestration,
or 9P2000.u extensions. Those belong in higher-level tools built on this
protocol layer.

The native library is built from the vendored plan9port converters:
`convM2S`, `convS2M`, `convM2D`, and `convD2M`. py9p adds only a small C shim
with fixed-width structs so Python does not depend on plan9port's internal C
struct ABI.

## Development

```bash
(cd vendor && ./build.sh)
python -m pytest
```

The full local gate mirrors py-libtab's style:

```bash
./run-all-tests.sh
./run-all-tests.sh all
```

## Release

The PyPI distribution name is `python-9p`; the import package remains `py9p`.

Releases are built by `.github/workflows/publish.yml`. Configure PyPI Trusted
Publishing with:

- PyPI project: `python-9p`
- Owner: `kiljoy001`
- Repository: `py-9p`
- Workflow: `publish.yml`
- Environment: `pypi`

Publishing runs only for version tags:

```bash
git tag v0.1.0
git push origin v0.1.0
```
