Expand description
§pyenum
Expose Rust enum types to Python as genuine enum.Enum subclasses via
PyO3 — #[derive(PyEnum)], functional-API class construction, a
per-interpreter cache, and IntoPyObject / FromPyObject plumbing all
generated by the derive.
§Quickstart
use pyenum::{PyEnum, PyModuleExt};
use pyo3::prelude::*;
#[derive(Clone, Copy, PyEnum)]
pub enum Color {
Red,
Green,
Blue,
}
#[pyfunction]
fn preferred() -> Color { Color::Green }
#[pymodule]
fn demo(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_enum::<Color>()?;
m.add_function(wrap_pyfunction!(preferred, m)?)
}From Python:
import demo, enum
assert issubclass(demo.Color, enum.Enum)
assert demo.preferred() is demo.Color.Green§Choosing a Python base
Select the Python enum base via #[pyenum(base = "…")]. The derive
accepts five values matching enum.* attribute names — Enum (default),
IntEnum, StrEnum, Flag, IntFlag.
use pyenum::PyEnum;
#[derive(Clone, Copy, PyEnum)]
#[pyenum(base = "IntEnum")]
pub enum HttpStatus {
Ok = 200,
NotFound = 404,
}
#[derive(Clone, Copy, PyEnum)]
#[pyenum(base = "StrEnum")]
pub enum Greeting {
// Default — variant name lowercased via Python's `auto()`.
Hello,
// Opt-in to a specific string, preserving case.
#[pyenum(value = "Bye!")]
Bye,
}
#[derive(Clone, Copy, PyEnum)]
#[pyenum(base = "IntFlag")]
pub enum Perm {
Read = 1,
Write = 2,
Execute = 4,
}§Conversion at the PyO3 boundary
The derive emits IntoPyObject and FromPyObject impls, so the Rust
enum type appears directly in PyO3 signatures:
#[pyfunction]
fn roundtrip(c: Color) -> Color { c }Passing demo.roundtrip(demo.Color.Red) from Python returns
demo.Color.Red as a real enum member (is-identical, not a fresh
clone).
§Performance
- The Python class is built once per interpreter via
pyo3::sync::PyOnceLock. - Each variant’s
Py<PyAny>member object is cached alongside the class;PyEnumTrait::to_py_memberindexes the cache by Rust-variant ordinal andPyEnumTrait::from_py_memberuses pointer equality (Bound::is) — no Pythongetattr, no allocation on the steady-state path. - Measured ~64 ns per conversion; SC-004 target is
< 1 µs.
§What the derive rejects at compile time
The proc-macro surfaces the following as compile_error! diagnostics
spanned at the offending variant or attribute — never at run time:
- Non-unit variants (tuple or struct fields).
- Generic or lifetime-parameterised enums.
- Empty enums (no variants).
- Variant names colliding with Python keywords, dunders, or
enum.Enum-reserved members (name,value,_missing_, …). - Base/value shape mismatches (e.g. a string
#[pyenum(value = ...)]on anIntEnum). - Duplicate resolved values — including the
auto()vs. explicit discriminant case on every integer-shaped base, and duplicate auto-lowercased names onStrEnum.
§Scope
- PyO3: pinned to
0.28. Cargo’spyo3-ffilinks = "python"rule forbids multiple PyO3 lines in the same dependency graph, so no multi-version feature matrix is exposed. - CPython: 3.10+.
enum.StrEnumrequires 3.11+; using#[pyenum(base = "StrEnum")]on a 3.10 interpreter raisesRuntimeErrorat first class construction. - Out of scope for v1: projecting Rust
implmethods onto the Python class, module-less standalone export, and free-threaded (--disable-gil) Python guarantees.
Structs§
- PyEnum
Spec - Static metadata emitted by
#[derive(PyEnum)]for each derived enum.
Enums§
- PyEnum
Base - Python enum base type selector.
- Variant
Literal - A single variant’s declared value, ready to be materialised into a
Python-side
(name, value)tuple for the functionalenum.*constructor.
Traits§
- PyEnum
Trait - Bridge between a
#[derive(PyEnum)]Rust type and its cached Python class. - PyModule
Ext - Extension method for
Bound<'_, PyModule>— the idiomatic way to register a derived enum inside a#[pymodule]body.
Functions§
- add_
enum - Register the Python class for
TontomoduleunderT::SPEC.name.
Derive Macros§
- PyEnum
- Derive a
pyenum::PyEnumimplementation for a unit-variant Rust enum.