pyenum 0.0.3

Expose Rust enums to Python as real enum.Enum subclasses via PyO3.
Documentation
//! # 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
//!
//! ```rust,ignore
//! 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:
//!
//! ```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`.
//!
//! ```rust,ignore
//! 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:
//!
//! ```rust,ignore
//! # use pyenum::PyEnum;
//! # use pyo3::prelude::*;
//! # #[derive(Clone, Copy, PyEnum)] pub enum Color { Red, Green, Blue }
//! #[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_member`] indexes the cache by Rust-variant ordinal
//!   and [`PyEnumTrait::from_py_member`] uses pointer equality (`Bound::is`)
//!   — no Python `getattr`, 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 an `IntEnum`).
//! * Duplicate resolved values — including the `auto()` vs. explicit
//!   discriminant case on every integer-shaped base, and duplicate
//!   auto-lowercased names on `StrEnum`.
//!
//! ## Scope
//!
//! * **PyO3**: pinned to `0.28`. Cargo's `pyo3-ffi` `links = "python"` rule
//!   forbids multiple PyO3 lines in the same dependency graph, so no
//!   multi-version feature matrix is exposed.
//! * **CPython**: 3.11+ (so `enum.StrEnum` is available without polyfill).
//! * **Out of scope for v1**: projecting Rust `impl` methods onto the
//!   Python class, module-less standalone export, and free-threaded
//!   (`--disable-gil`) Python guarantees.

#![deny(missing_debug_implementations)]
#![cfg_attr(docsrs, feature(doc_cfg))]

mod cache;
mod construct;
mod register;
mod trait_def;

pub use pyenum_derive::PyEnum;
pub use register::{PyModuleExt, add_enum};
pub use trait_def::{PyEnum as PyEnumTrait, PyEnumBase, PyEnumSpec, VariantLiteral};

/// Implementation detail — referenced by the output of `#[derive(PyEnum)]`.
///
/// Consumers MUST NOT import anything from this module directly; its shape is
/// allowed to change between patch releases without warning.
#[doc(hidden)]
pub mod __private {
    pub use ::pyo3;
    pub use ::pyo3::sync::PyOnceLock as OnceLock;

    pub use crate::cache::get_or_build;
    pub use crate::construct::build_py_enum;
    pub use crate::trait_def::{PyEnum, PyEnumBase, PyEnumSpec, VariantLiteral};
}