Skip to content

Commit

Permalink
use paste to generate module init symbol names
Browse files Browse the repository at this point in the history
Use the `paste` crate to generate the module init symbol names (`initfoo` and
`PyInit_foo`), rather than requiring the user to pass them in.  This makes
creating modules less error-prone.
  • Loading branch information
markbt committed Jan 29, 2020
1 parent d89056f commit e8c9a4c
Show file tree
Hide file tree
Showing 8 changed files with 42 additions and 33 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ appveyor = { repository = "dgrunwald/rust-cpython" }
[dependencies]
libc = "0.2"
num-traits = "0.2"
paste = "0.1"

# These features are both optional, but you must pick one to
# indicate which python ffi you are trying to bind to.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ use cpython::{PyResult, Python, py_module_initializer, py_fn};

// add bindings to the generated python module
// N.B: names: "rust2py" must be the name of the `.so` or `.pyd` file
py_module_initializer!(rust2py, initrust2py, PyInit_rust2py, |py, m| {
py_module_initializer!(rust2py, |py, m| {
m.add(py, "__doc__", "This module is implemented in Rust.")?;
m.add(py, "sum_as_string", py_fn!(py, sum_as_string_py(a: i64, b:i64)))?;
Ok(())
Expand Down
2 changes: 1 addition & 1 deletion extensions/hello/src/hello.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use cpython::{py_fn, py_module_initializer, PyDict, PyObject, PyResult, PyTuple,
// This requires that the output binary file is named `hello.so` (or Windows: `hello.pyd`).
// As the output name cannot be configured in cargo (https://github.com/rust-lang/cargo/issues/1970),
// you'll have to rename the output file.
py_module_initializer!(hello, inithello, PyInit_hello, |py, m| {
py_module_initializer!(hello, |py, m| {
m.add(py, "__doc__", "Module documentation string")?;
m.add(py, "func", py_fn!(py, func(a: &str, b: i32)))?;
m.add(py, "run", py_fn!(py, run(*args, **kwargs)))?;
Expand Down
2 changes: 1 addition & 1 deletion extensions/tests/btree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use cpython::{PyObject, PyResult, py_module_initializer, py_class};
use std::{cell, cmp, collections};

py_module_initializer!(btree, initbtree, PyInit_btree, |py, m| {
py_module_initializer!(btree, |py, m| {
m.add(py, "__doc__", "Rust BTreeSet for Python.")?;
m.add_class::<BTreeSet>(py)?;
Ok(())
Expand Down
2 changes: 1 addition & 1 deletion extensions/tests/custom_class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use cpython::{PyObject, PyResult, py_module_initializer, py_class};

py_module_initializer!(custom_class, initcustom_class, PyInit_custom_class, |py, m| {
py_module_initializer!(custom_class, |py, m| {
m.add(py, "__doc__", "Module documentation string")?;
m.add_class::<MyType>(py)?;
Ok(())
Expand Down
2 changes: 1 addition & 1 deletion extensions/tests/hello.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use cpython::{PyObject, PyResult, Python, PyTuple, PyDict, py_module_initializer, py_fn};

py_module_initializer!(hello, inithello, PyInit_hello, |py, m| {
py_module_initializer!(hello, |py, m| {
m.add(py, "__doc__", "Module documentation string")?;
m.add(py, "run", py_fn!(py, run(*args, **kwargs)))?;
m.add(py, "val", py_fn!(py, val()))?;
Expand Down
61 changes: 34 additions & 27 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,25 +228,28 @@ pub mod _detail {
handle_callback, py_fn_impl, AbortOnDrop, PyObjectCallbackConverter,
PythonObjectCallbackConverter,
};
pub use paste;
}

/// Expands to an `extern "C"` function that allows Python to load
/// the rust code as a Python extension module.
///
/// Macro syntax: `py_module_initializer!($name, $py2_init, $py3_init, |$py, $m| $body)`
/// Macro syntax: `py_module_initializer!($name, |$py, $m| $body)`
///
/// 1. `name`: The module name as a Rust identifier.
/// 2. `py2_init`: "init" + $name. Necessary because macros can't use concat_idents!().
/// 3. `py3_init`: "PyInit_" + $name. Necessary because macros can't use concat_idents!().
/// 4. A lambda of type `Fn(Python, &PyModule) -> PyResult<()>`.
/// 2. A lambda of type `Fn(Python, &PyModule) -> PyResult<()>`.
/// This function will be called when the module is imported, and is responsible
/// for adding the module's members.
///
/// For backwards compatibilty with older versions of rust-cpython,
/// two additional name identifiers (for py2 and py3 initializer names)
/// can be provided, but they will be ignored.
///
/// # Example
/// ```
/// use cpython::{Python, PyResult, PyObject, py_module_initializer, py_fn};
///
/// py_module_initializer!(hello, inithello, PyInit_hello, |py, m| {
/// py_module_initializer!(hello, |py, m| {
/// m.add(py, "__doc__", "Module documentation string")?;
/// m.add(py, "run", py_fn!(py, run()))?;
/// Ok(())
Expand Down Expand Up @@ -287,16 +290,18 @@ pub mod _detail {
#[macro_export]
#[cfg(feature = "python27-sys")]
macro_rules! py_module_initializer {
($name: ident, $py2: ident, $py3: ident, |$py_id: ident, $m_id: ident| $body: expr) => {
#[no_mangle]
#[allow(non_snake_case)]
pub unsafe extern "C" fn $py2() {
// Nest init function so that $body isn't in unsafe context
fn init($py_id: $crate::Python, $m_id: &$crate::PyModule) -> $crate::PyResult<()> {
$body
($name: ident, $( $_py2: ident, $_py3: ident, )? |$py_id: ident, $m_id: ident| $body: tt) => {
$crate::_detail::paste::item! {
#[no_mangle]
#[allow(non_snake_case)]
pub unsafe extern "C" fn [< init $name >]() {
// Nest init function so that $body isn't in unsafe context
fn init($py_id: $crate::Python, $m_id: &$crate::PyModule) -> $crate::PyResult<()> {
$body
}
let name = concat!(stringify!($name), "\0").as_ptr() as *const _;
$crate::py_module_initializer_impl(name, init)
}
let name = concat!(stringify!($name), "\0").as_ptr() as *const _;
$crate::py_module_initializer_impl(name, init)
}
};
}
Expand Down Expand Up @@ -335,20 +340,22 @@ pub unsafe fn py_module_initializer_impl(
#[macro_export]
#[cfg(feature = "python3-sys")]
macro_rules! py_module_initializer {
($name: ident, $py2: ident, $py3: ident, |$py_id: ident, $m_id: ident| $body: expr) => {
#[no_mangle]
#[allow(non_snake_case)]
pub unsafe extern "C" fn $py3() -> *mut $crate::_detail::ffi::PyObject {
// Nest init function so that $body isn't in unsafe context
fn init($py_id: $crate::Python, $m_id: &$crate::PyModule) -> $crate::PyResult<()> {
$body
($name: ident, $( $_py2: ident, $_py3: ident, )? |$py_id: ident, $m_id: ident| $body: tt) => {
$crate::_detail::paste::item! {
#[no_mangle]
#[allow(non_snake_case)]
pub unsafe extern "C" fn [< PyInit_ $name >]() -> *mut $crate::_detail::ffi::PyObject {
// Nest init function so that $body isn't in unsafe context
fn init($py_id: $crate::Python, $m_id: &$crate::PyModule) -> $crate::PyResult<()> {
$body
}
static mut MODULE_DEF: $crate::_detail::ffi::PyModuleDef =
$crate::_detail::ffi::PyModuleDef_INIT;
// We can't convert &'static str to *const c_char within a static initializer,
// so we'll do it here in the module initialization:
MODULE_DEF.m_name = concat!(stringify!($name), "\0").as_ptr() as *const _;
$crate::py_module_initializer_impl(&mut MODULE_DEF, init)
}
static mut MODULE_DEF: $crate::_detail::ffi::PyModuleDef =
$crate::_detail::ffi::PyModuleDef_INIT;
// We can't convert &'static str to *const c_char within a static initializer,
// so we'll do it here in the module initialization:
MODULE_DEF.m_name = concat!(stringify!($name), "\0").as_ptr() as *const _;
$crate::py_module_initializer_impl(&mut MODULE_DEF, init)
}
};
}
Expand Down
3 changes: 2 additions & 1 deletion src/objects/capsule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,11 +174,12 @@ use crate::python::{Python, ToPythonPointer};
/// # a + b
/// # }
/// # static DATA: CapsData = CapsData{value: 1, fun: add};
/// py_module_initializer!(somemod, initsomemod, PyInit_somemod, |py, m| {
/// py_module_initializer!(somemod, |py, m| {
/// m.add(py, "__doc__", "A module holding a capsule")?;
/// m.add(py, "capsdata", PyCapsule::new_data(py, &DATA, "somemod.capsdata").unwrap())?;
/// Ok(())
/// });
/// # fn main() {}
/// ```
/// Another Rust extension could then declare `CapsData` and use `PyCapsule::import_data` to
/// fetch it back.
Expand Down

0 comments on commit e8c9a4c

Please sign in to comment.