simple getitem example
This commit is contained in:
parent
d3bd82c6f2
commit
4fab62545a
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
authors = ["{{authors}}"]
|
||||||
|
name = "{{project-name}}"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "getitem"
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
pyo3 = { version = "{{PYO3_VERSION}}", features = ["extension-module"] }
|
|
@ -0,0 +1,4 @@
|
||||||
|
variable::set("PYO3_VERSION", "0.19.0");
|
||||||
|
file::rename(".template/Cargo.toml", "Cargo.toml");
|
||||||
|
file::rename(".template/pyproject.toml", "pyproject.toml");
|
||||||
|
file::delete(".template");
|
|
@ -0,0 +1,7 @@
|
||||||
|
[build-system]
|
||||||
|
requires = ["maturin>=1,<2"]
|
||||||
|
build-backend = "maturin"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "{{project-name}}"
|
||||||
|
version = "0.1.0"
|
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "getitem"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "getitem"
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
pyo3 = { path = "../../", features = ["extension-module"] }
|
||||||
|
|
||||||
|
[workspace]
|
|
@ -0,0 +1,2 @@
|
||||||
|
include pyproject.toml Cargo.toml
|
||||||
|
recursive-include src *
|
|
@ -0,0 +1,46 @@
|
||||||
|
# getitem
|
||||||
|
|
||||||
|
A project showcasing how to create a `__getitem__` override that also showcases how to deal with multiple incoming types
|
||||||
|
|
||||||
|
## Relevant Documentation
|
||||||
|
|
||||||
|
Some of the relevant documentation links for this example:
|
||||||
|
|
||||||
|
* Converting Slices to Indices: https://docs.rs/pyo3/latest/pyo3/types/struct.PySlice.html#method.indices
|
||||||
|
* GetItem Docs: https://pyo3.rs/latest/class/protocols.html?highlight=__getitem__#mapping--sequence-types
|
||||||
|
* Extract: https://pyo3.rs/latest/conversions/traits.html?highlight=extract#extract-and-the-frompyobject-trait
|
||||||
|
* Downcast and getattr: https://pyo3.rs/v0.19.0/types.html?highlight=getattr#pyany
|
||||||
|
|
||||||
|
|
||||||
|
## Building and Testing
|
||||||
|
|
||||||
|
To build this package, first install `maturin`:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
pip install maturin
|
||||||
|
```
|
||||||
|
|
||||||
|
To build and test use `maturin develop`:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
pip install -r requirements-dev.txt
|
||||||
|
maturin develop
|
||||||
|
pytest
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, install nox and run the tests inside an isolated environment:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
nox
|
||||||
|
```
|
||||||
|
|
||||||
|
## Copying this example
|
||||||
|
|
||||||
|
Use [`cargo-generate`](https://crates.io/crates/cargo-generate):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ cargo install cargo-generate
|
||||||
|
$ cargo generate --git https://github.com/PyO3/pyo3 examples/decorator
|
||||||
|
```
|
||||||
|
|
||||||
|
(`cargo generate` will take a little while to clone the PyO3 repo first; be patient when waiting for the command to run.)
|
|
@ -0,0 +1,5 @@
|
||||||
|
[template]
|
||||||
|
ignore = [".nox"]
|
||||||
|
|
||||||
|
[hooks]
|
||||||
|
pre = [".template/pre-script.rhai"]
|
|
@ -0,0 +1,9 @@
|
||||||
|
import nox
|
||||||
|
|
||||||
|
|
||||||
|
@nox.session
|
||||||
|
def python(session):
|
||||||
|
session.install("-rrequirements-dev.txt")
|
||||||
|
session.install("maturin")
|
||||||
|
session.run_always("maturin", "develop")
|
||||||
|
session.run("pytest")
|
|
@ -0,0 +1,16 @@
|
||||||
|
[build-system]
|
||||||
|
requires = ["maturin>=1,<2"]
|
||||||
|
build-backend = "maturin"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "getitem"
|
||||||
|
version = "0.1.0"
|
||||||
|
classifiers = [
|
||||||
|
"License :: OSI Approved :: MIT License",
|
||||||
|
"Development Status :: 3 - Alpha",
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
"Programming Language :: Python",
|
||||||
|
"Programming Language :: Rust",
|
||||||
|
"Operating System :: POSIX",
|
||||||
|
"Operating System :: MacOS :: MacOS X",
|
||||||
|
]
|
|
@ -0,0 +1,3 @@
|
||||||
|
pytest>=3.5.0
|
||||||
|
pip>=21.3
|
||||||
|
maturin>=1,<2
|
|
@ -0,0 +1,82 @@
|
||||||
|
// This is a very fake example of how to check __getitem__ parameter and handle appropriately
|
||||||
|
use pyo3::exceptions::PyTypeError;
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use pyo3::types::PySlice;
|
||||||
|
use std::os::raw::c_long;
|
||||||
|
|
||||||
|
#[derive(FromPyObject)]
|
||||||
|
enum IntOrSlice<'py> {
|
||||||
|
Int(i32),
|
||||||
|
Slice(&'py PySlice),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyclass]
|
||||||
|
struct ExampleContainer {
|
||||||
|
// represent the maximum length our container is pretending to be
|
||||||
|
max_length: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl ExampleContainer {
|
||||||
|
#[new]
|
||||||
|
fn new() -> Self {
|
||||||
|
ExampleContainer { max_length: 100 }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __getitem__(&self, key: &PyAny) -> PyResult<i32> {
|
||||||
|
if let Ok(position) = key.extract::<i32>() {
|
||||||
|
return Ok(position);
|
||||||
|
} else if let Ok(slice) = key.downcast::<PySlice>() {
|
||||||
|
// METHOD 1 - the use PySliceIndices to help with bounds checking and for cases when only start or end are provided
|
||||||
|
// in this case the start/stop/step all filled in to give valid values based on the max_length given
|
||||||
|
let index = slice.indices(self.max_length as c_long).unwrap();
|
||||||
|
let _delta = index.stop - index.start;
|
||||||
|
|
||||||
|
// METHOD 2 - Do the getattr manually really only needed if you have some special cases for stop/_step not being present
|
||||||
|
// convert to indices and this will help you deal with stop being the max length
|
||||||
|
let start: i32 = slice.getattr("start")?.extract()?;
|
||||||
|
// This particular example assumes stop is present, but note that if not present, this will cause us to return due to the
|
||||||
|
// extract failing. Not needing custom code to deal with this is a good reason to use the Indices method.
|
||||||
|
let stop: i32 = slice.getattr("stop")?.extract()?;
|
||||||
|
// example of grabbing step since it is not always present
|
||||||
|
let _step: i32 = match slice.getattr("step")?.extract() {
|
||||||
|
// if no value found assume step is 1
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => 1 as i32,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use something like this if you don't support negative stepping and want to give users
|
||||||
|
// leeway on how they provide their ordering
|
||||||
|
let (start, stop) = if start > stop {
|
||||||
|
(stop, start)
|
||||||
|
} else {
|
||||||
|
(start, stop)
|
||||||
|
};
|
||||||
|
let delta = stop - start;
|
||||||
|
|
||||||
|
return Ok(delta);
|
||||||
|
} else {
|
||||||
|
return Err(PyTypeError::new_err("Unsupported type"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn __setitem__(&self, idx: IntOrSlice, value: u32) -> PyResult<()> {
|
||||||
|
match idx {
|
||||||
|
IntOrSlice::Slice(slice) => {
|
||||||
|
let index = slice.indices(self.max_length as c_long).unwrap();
|
||||||
|
println!("Got a slice! {}-{}, step: {}, value: {}", index.start, index.stop, index.step, value);
|
||||||
|
}
|
||||||
|
IntOrSlice::Int(index) => {
|
||||||
|
println!("Got an index! {} : value: {}", index, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymodule]
|
||||||
|
#[pyo3(name = "getitem")]
|
||||||
|
fn example(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||||
|
// ? -https://github.com/PyO3/maturin/issues/475
|
||||||
|
m.add_class::<ExampleContainer>()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
import getitem
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
def test_simple():
|
||||||
|
container = getitem.ExampleContainer()
|
||||||
|
assert container[3] == 3
|
||||||
|
assert container[4] == 4
|
||||||
|
assert container[-1] == -1
|
||||||
|
assert container[5:3] == 2
|
||||||
|
assert container[3:5] == 2
|
||||||
|
# test setitem, but this just displays, no return to check
|
||||||
|
container[3:5] = 2
|
||||||
|
container[2] = 2
|
||||||
|
# and note we will get an error on this one since we didn't
|
||||||
|
# add strings
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
container["foo"] = 2
|
|
@ -0,0 +1 @@
|
||||||
|
Simple getitem example showing type-check for possible attribute types
|
Loading…
Reference in New Issue