| Crates.io | optipy |
| lib.rs | optipy |
| version | 0.1.0 |
| created_at | 2025-09-10 22:43:39.390573+00 |
| updated_at | 2025-09-11 04:21:18.123029+00 |
| description | Macro for stripping PyO3-related attributes when using features. |
| homepage | |
| repository | https://github.com/rigetti/rigetti-pyo3 |
| max_upload_size | |
| id | 1833103 |
| size | 26,348 |
This crate provides a procedural macro that removes pyo3 related macros.
It can be applied to an item using #[cfg_attr(not(feature = "python"), optipy)].
To keep the PyO3 attributes while stripping only the pyo3_stub_gen attributes,
you can write the attribute as #[strip_pyo3(only_stubs)].
By default, the macro strips both.
To use PyO3 effectively,
you're expected to wrap items with the procedural macros #[pyclass] and #[pymodule].
You can feature-gate their application via e.g. #[cfg_attr(feature = "python", pyclass)],
and that will work just fine in many cases.
It goes wrong, however, if you need to use any of PyO3's attributes within that scope,
such as applying #[getter] to a method or #[pyo3(name = "SomethingElse")] to an enum variant;
pyo3 can only process (and remove) those attributes if the feature is enabled,
but if you wrap them in cfg_attr, then pyo3 won't know how to process them.
See the following issues and PRs for more information:
This crate takes the suggestion from the last PR listed above,
and strips pyo3 attributes, so you can apply it when your feature is not enabled.
If you're also use pyo3_stub_gen to generate Python stub files,
this macro strips those attributes, too,
and by default, the macro strips both attributes of both creates.
To keep the PyO3 attributes while stripping only the pyo3_stub_gen attributes,
you can write the attribute as #[strip_pyo3(only_stubs)],
which is useful if you're using an additional feature specifically for stubs,
since that isn't needed for the final Python package.
Generally, you'll want to apply this to code using a feature gate,
and if you're using this to strip pyo3_stub_gen, too, you'll likely have two features,
such as a python feature to generate the Python-specific bindings
and a stubs feature specifically for stub generation (and implies the python feature).
Here's how you can use this macro to conditionally remove the pyo3-related attributes:
#[cfg(not(feature = "python"))]
use optipy::strip_pyo3;
#[cfg(feature = "stubs")]
use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods};
#[cfg_attr(not(feature = "python"), optipy::strip_pyo3)]
#[cfg_attr(feature = "stubs", gen_stub_pyclass)]
#[cfg_attr(feature = "python", pyo3::pyclass)]
struct ExampleStruct {
#[pyo3(get)]
x: usize,
}
You may find yourself writing pyo3_stub_gen attributes to, say, override a return type.
In that case, when building the bindings (using just the python feature)
you can choose to strip only the pyo3_stub_gen attributes while leaving PyO3's.
enum ExampleEnum {
Str(String),
Num(isize),
List(Py<PyList>),
}
#[cfg(feature = "python")]
mod python {
use optipy::strip_pyo3;
#[cfg(feature = "stubs")]
use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods};
#[cfg_attr(feature = "stubs", gen_stub_pyclass)]
#[pyo3::pyclass]
struct SomeStruct {
value: ExampleEnum,
}
#[cfg_attr(not(feature = "stubs"), optipy::strip_pyo3(only_stubs))]
#[cfg_attr(feature = "stubs", gen_stub_pymethods)]
#[pyo3::pymethods]
impl SomeStruct {
#[new] // We don't want to remove this!
fn new<'py>(value: pyo3::Bound<'py, PyAny>) -> PyResult<Bound<'py, Self>> {
let py = value.py();
if let Ok(num) = value.downcast::<PyInt>() {
(Self { value: ExampleEnum::Num(num.extract()?) }).into_pyobject(py)
} else if let Ok(list) = value.downcast::<PyList>() {
(Self { value: ExampleEnum::List(value.unbind().extract(py)?) }).into_pyobject(py)
} else if let Ok(string) = value.extract::<String>() {
(Self { value: ExampleEnum::Str(string) }).into_pyobject(py)
} else {
Err(PyTypeError::new_err("Not a valid value!"))
}
}
// But we need to remove this when not building stubs.
#[gen_stub(override_return_type(type_repr = "tuple[str, int, list]"))]
fn __getnewargs__(&self, py: Python<'py>) -> PyResult<Bound<'py, PyTuple>> {
match &self.value {
ExampleEnum::Str(value) => (value.clone(),).into_pyobject(py),
ExampleEnum::Num(value) => (value,).into_pyobject(py),
ExampleEnum::List(value) => (value,).into_pyobject(py),
}
}
}
}