1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
//! # 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.10+. `enum.StrEnum` requires 3.11+; using
//! `#[pyenum(base = "StrEnum")]` on a 3.10 interpreter raises
//! `RuntimeError` at first class construction.
//! * **Out of scope for v1**: projecting Rust `impl` methods onto the
//! Python class, module-less standalone export, and free-threaded
//! (`--disable-gil`) Python guarantees.
pub use PyEnum;
pub use ;
pub use ;
/// 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.