Metadata-Version: 2.4
Name: fastproto
Version: 0.1.3
Classifier: Programming Language :: Rust
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Requires-Dist: protobuf>=6,<8 ; extra == 'plugin'
Provides-Extra: plugin
License-File: LICENSE
Summary: FastProto is fast and efficient protobuf library for Python, built on the top of Rust.
Home-Page: https://github.com/pkozhem/fastproto
Author-email: pkozhem <pkozhem@gmail.com>
License: MIT
Requires-Python: >=3.12
Description-Content-Type: text/markdown

# FastProto

Fast, Pythonic Protocol Buffers — messages are plain, readable `@dataclass`
types, with all encoding and decoding handled by a compiled Rust core.

Google's Python protobuf generates opaque classes full of getters/setters and a
reflection API you have to learn. FastProto instead generates clean dataclasses
you can construct, compare, and `repr()` like any other — and does the wire work
in Rust.

- **Idiomatic messages** — generated code is a `@dataclass` with plain
  annotations (`str`, `int`, `list[...]`, `dict[...]`, `| None`); autocomplete,
  type checkers, and `repr()` all just work.
- **Rust wire codec** — encode/decode run in Rust via [PyO3](https://pyo3.rs),
  not pure Python.
- **Wire-compatible** — bytes interoperate both ways with Google's reference
  `protobuf` runtime.
- **Standard toolchain** — ships a `protoc` plugin; just add `--fastproto_out`.

## Install

```bash
pip install fastproto            # runtime (Python 3.12+)
pip install "fastproto[plugin]"  # + the protoc code generator
```

Code generation also needs the `protoc` compiler itself — install it from your
package manager (`brew install protobuf`, `apt install protobuf-compiler`) or
the [official releases](https://github.com/protocolbuffers/protobuf/releases).

## Quick start

**1. Define** `user.proto`:

```protobuf
syntax = "proto3";
package example;

enum Role {
  ROLE_UNSPECIFIED = 0;
  ROLE_ADMIN = 1;
  ROLE_USER = 2;
}

message Address {
  string city = 1;
  string street = 2;
}

message User {
  int64 id = 1;
  string name = 2;
  optional string email = 3;
  Role role = 4;
  repeated string tags = 5;
  Address address = 6;
  map<string, int32> counters = 7;
}
```

**2. Generate** with `protoc`:

```bash
protoc --proto_path=. --fastproto_out=. user.proto
```

This writes `user_pb.py` — a plain, readable dataclass module:

```python
# @generated by fastproto. DO NOT EDIT.
# source: user.proto
# pyright: reportUnknownVariableType=false
from dataclasses import dataclass, field
from enum import IntEnum

from fastproto import Message, Scalar, message


class Role(IntEnum):
    ROLE_UNSPECIFIED = 0
    ROLE_ADMIN = 1
    ROLE_USER = 2


@message(_USER_DESCRIPTOR)
@dataclass(slots=True)
class User(Message):
    id: Scalar.Int64 = 0
    name: Scalar.String = ""
    email: Scalar.String | None = None
    role: Role = Role(0)
    tags: list[Scalar.String] = field(default_factory=list)
    address: "Address | None" = None
    counters: dict[Scalar.String, Scalar.Int32] = field(default_factory=dict)
```

**3. Use it** like any dataclass:

```python
from user_pb import Address, Role, User

user = User(
    id=42,
    name="Ada",
    role=Role.ROLE_ADMIN,
    tags=["vip", "beta"],
    address=Address(city="London", street="Baker St"),
    counters={"logins": 7},
)

data = user.to_bytes()               # serialize to protobuf wire bytes
assert User.from_bytes(data) == user  # and back
```

No `SerializeToString()` / `ParseFromString()` ceremony and no reflection — just
`to_bytes()` / `from_bytes()` on a dataclass you can build, compare, and print.

## Type mapping

Each proto scalar maps to an alias under `fastproto.Scalar`. An alias is just
the underlying Python type (`int`, `str`, ...) tagged with `Annotated[...]`, so
it type-checks as the base type while still recording the exact wire type.

| proto | Python | proto | Python |
|---|---|---|---|
| `double` | `Scalar.Double` | `fixed32` | `Scalar.Fixed32` |
| `float` | `Scalar.Float` | `fixed64` | `Scalar.Fixed64` |
| `int32` | `Scalar.Int32` | `sfixed32` | `Scalar.SFixed32` |
| `int64` | `Scalar.Int64` | `sfixed64` | `Scalar.SFixed64` |
| `uint32` | `Scalar.UInt32` | `bool` | `Scalar.Bool` |
| `uint64` | `Scalar.UInt64` | `string` | `Scalar.String` |
| `sint32` | `Scalar.SInt32` | `bytes` | `Scalar.Bytes` |
| `sint64` | `Scalar.SInt64` | | |

Composite fields: `repeated T` → `list[T]`, `map<K, V>` → `dict[K, V]`,
`enum` → `IntEnum`, and `optional` / message / `oneof` fields → `T | None`.

## Semantics

- **Presence (proto3):** plain scalars use their zero value and are not nullable;
  `optional` scalars, message fields, and `oneof` members are `T | None` and
  track explicit presence (a set empty string is distinct from unset). An
  all-default message encodes to `b""`.
- **`oneof`:** members are plain optional fields; setting more than one raises
  `ValueError` at encode time.
- **References:** sibling, self, and enum references resolve lazily on the first
  `to_bytes()` / `from_bytes()` — nothing for you to wire up.

```python
empty = User()
assert empty.to_bytes() == b"" and empty.email is None
User(phone="1", telegram="a").to_bytes()  # ValueError: ... oneof ...
```

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, project layout, and the
release flow.

## License

[MIT](LICENSE)

