diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index fb69250..37bc8e6 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -25,7 +25,7 @@ jobs: uses: PyO3/maturin-action@v1 with: target: ${{ matrix.target }} - args: --release --out dist -i 3.7 3.8 3.9 3.10 3.11 3.12 + args: --release --out dist -i 3.7 3.8 3.9 3.10 3.11 3.12 3.13 sccache: 'true' manylinux: auto - name: Upload wheels diff --git a/Cargo.toml b/Cargo.toml index 202975a..07a2c63 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ name = "stam" crate-type = ["cdylib"] [dependencies] -pyo3 = { version = "0.20.3", features = ["chrono"] } +pyo3 = { version = "0.22.3", features = ["chrono"] } rayon = "1.10.0" stam = "0.16.3" stam-tools = "0.9.0" diff --git a/src/annotation.rs b/src/annotation.rs index 33e9e66..6b71fa1 100644 --- a/src/annotation.rs +++ b/src/annotation.rs @@ -46,8 +46,8 @@ impl PyAnnotation { handle: AnnotationHandle, store: Arc>, py: Python<'py>, - ) -> &'py PyAny { - Self::new(handle, store).into_py(py).into_ref(py) + ) -> Bound<'py, PyAny> { + Self::new(handle, store).into_py(py).into_bound(py) } } @@ -123,8 +123,8 @@ impl PyAnnotation { /// as an annotation may reference multiple texts. /// /// If you are sure an annotation only references a single contingent text slice or are okay with slices being concatenated, then you can use `str()` instead. - fn text<'py>(&self, py: Python<'py>) -> Py { - let list: &PyList = PyList::empty(py); + fn text<'py>(&self, py: Python<'py>) -> Bound<'py, PyList> { + let list = PyList::empty_bound(py); self.map(|annotation| { for text in annotation.text() { list.append(text).ok(); @@ -152,13 +152,13 @@ impl PyAnnotation { /// Note that this will always return a list (even it if only contains a single element), /// as an annotation may reference multiple text selections. #[pyo3(signature = (*args, **kwargs))] - fn textselections( + fn textselections<'py>( &self, - args: &PyTuple, - kwargs: Option<&PyDict>, + args: Bound<'py, PyTuple>, + kwargs: Option>, ) -> PyResult { - let limit = get_limit(kwargs); - if !has_filters(args, kwargs) { + let limit = get_limit(&kwargs); + if !has_filters(&args, &kwargs) { self.map(|annotation| { Ok(PyTextSelections::from_iter( annotation.textselections().limit(limit), @@ -174,8 +174,8 @@ impl PyAnnotation { AnnotationDepth::One, None, ), - args, - kwargs, + &args, + &kwargs, |annotation, query| { PyTextSelections::from_query(query, annotation.store(), &self.store, limit) }, @@ -185,14 +185,14 @@ impl PyAnnotation { /// Returns annotations this annotation refers to (i.e. using an AnnotationSelector) #[pyo3(signature = (*args, **kwargs))] - fn annotations_in_targets( + fn annotations_in_targets<'py>( &self, - args: &PyTuple, - kwargs: Option<&PyDict>, + args: Bound<'py, PyTuple>, + kwargs: Option>, ) -> PyResult { - let limit = get_limit(kwargs); - let recursive = get_recursive(kwargs, AnnotationDepth::One); - if !has_filters(args, kwargs) { + let limit = get_limit(&kwargs); + let recursive = get_recursive(&kwargs, AnnotationDepth::One); + if !has_filters(&args, &kwargs) { self.map(|annotation| { Ok(PyAnnotations::from_iter( annotation.annotations_in_targets(recursive).limit(limit), @@ -208,8 +208,8 @@ impl PyAnnotation { recursive, None, ), - args, - kwargs, + &args, + &kwargs, |annotation, query| { PyAnnotations::from_query(query, annotation.store(), &self.store, limit) }, @@ -219,9 +219,13 @@ impl PyAnnotation { /// Returns annotations that are referring to this annotation (i.e. others using an AnnotationSelector) #[pyo3(signature = (*args, **kwargs))] - fn annotations(&self, args: &PyTuple, kwargs: Option<&PyDict>) -> PyResult { - let limit = get_limit(kwargs); - if !has_filters(args, kwargs) { + fn annotations<'py>( + &self, + args: Bound<'py, PyTuple>, + kwargs: Option>, + ) -> PyResult { + let limit = get_limit(&kwargs); + if !has_filters(&args, &kwargs) { self.map(|annotation| { Ok(PyAnnotations::from_iter( annotation.annotations().limit(limit), @@ -237,8 +241,8 @@ impl PyAnnotation { AnnotationDepth::One, None, ), - args, - kwargs, + &args, + &kwargs, |annotation, query| { PyAnnotations::from_query(query, annotation.store(), &self.store, limit) }, @@ -247,8 +251,12 @@ impl PyAnnotation { } #[pyo3(signature = (*args, **kwargs))] - fn test_annotations(&self, args: &PyTuple, kwargs: Option<&PyDict>) -> PyResult { - if !has_filters(args, kwargs) { + fn test_annotations<'py>( + &self, + args: Bound<'py, PyTuple>, + kwargs: Option>, + ) -> PyResult { + if !has_filters(&args, &kwargs) { self.map(|annotation| Ok(annotation.annotations().test())) } else { self.map_with_query( @@ -259,21 +267,21 @@ impl PyAnnotation { AnnotationDepth::One, None, ), - args, - kwargs, + &args, + &kwargs, |annotation, query| Ok(annotation.store().query(query)?.test()), ) } } #[pyo3(signature = (*args, **kwargs))] - fn test_annotations_in_targets( + fn test_annotations_in_targets<'py>( &self, - args: &PyTuple, - kwargs: Option<&PyDict>, + args: Bound<'py, PyTuple>, + kwargs: Option>, ) -> PyResult { - let recursive = get_recursive(kwargs, AnnotationDepth::One); - if !has_filters(args, kwargs) { + let recursive = get_recursive(&kwargs, AnnotationDepth::One); + if !has_filters(&args, &kwargs) { self.map(|annotation| Ok(annotation.annotations_in_targets(recursive).test())) } else { self.map_with_query( @@ -284,8 +292,8 @@ impl PyAnnotation { recursive, None, ), - args, - kwargs, + &args, + &kwargs, |annotation, query| Ok(annotation.store().query(query)?.test()), ) } @@ -293,8 +301,8 @@ impl PyAnnotation { /// Returns a list of resources this annotation refers to #[pyo3(signature = (limit=None))] - fn resources<'py>(&self, limit: Option, py: Python<'py>) -> Py { - let list: &PyList = PyList::empty(py); + fn resources<'py>(&self, limit: Option, py: Python<'py>) -> Bound<'py, PyList> { + let list = PyList::empty_bound(py); self.map(|annotation| { for (i, resource) in annotation.resources().enumerate() { list.append(PyTextResource::new_py( @@ -315,8 +323,8 @@ impl PyAnnotation { /// Returns the resources this annotation refers to (as metadata) in a list #[pyo3(signature = (limit=None))] - fn datasets<'py>(&self, limit: Option, py: Python<'py>) -> Py { - let list: &PyList = PyList::empty(py); + fn datasets<'py>(&self, limit: Option, py: Python<'py>) -> Bound<'py, PyList> { + let list = PyList::empty_bound(py); self.map(|annotation| { for (i, dataset) in annotation.datasets().enumerate() { list.append(PyAnnotationDataSet::new_py( @@ -367,9 +375,13 @@ impl PyAnnotation { /// Returns annotation data instances that pertain to this annotation. #[pyo3(signature = (*args, **kwargs))] - fn data(&self, args: &PyTuple, kwargs: Option<&PyDict>) -> PyResult { - let limit = get_limit(kwargs); - if !has_filters(args, kwargs) { + fn data<'py>( + &self, + args: Bound<'py, PyTuple>, + kwargs: Option>, + ) -> PyResult { + let limit = get_limit(&kwargs); + if !has_filters(&args, &kwargs) { self.map(|annotation| { Ok(PyData::from_iter( annotation.data().limit(limit), @@ -385,8 +397,8 @@ impl PyAnnotation { AnnotationDepth::One, None, ), - args, - kwargs, + &args, + &kwargs, |annotation, query| { PyData::from_query(query, annotation.store(), &self.store, limit) }, @@ -395,8 +407,12 @@ impl PyAnnotation { } #[pyo3(signature = (*args, **kwargs))] - fn test_data(&self, args: &PyTuple, kwargs: Option<&PyDict>) -> PyResult { - if !has_filters(args, kwargs) { + fn test_data<'py>( + &self, + args: Bound<'py, PyTuple>, + kwargs: Option>, + ) -> PyResult { + if !has_filters(&args, &kwargs) { self.map(|annotation| Ok(annotation.data().test())) } else { self.map_with_query( @@ -407,8 +423,8 @@ impl PyAnnotation { AnnotationDepth::One, None, ), - args, - kwargs, + &args, + &kwargs, |annotation, query| Ok(annotation.store().query(query)?.test()), ) } @@ -421,14 +437,14 @@ impl PyAnnotation { } #[pyo3(signature = (operator, *args, **kwargs))] - fn related_text( + fn related_text<'py>( &self, operator: PyTextSelectionOperator, - args: &PyTuple, - kwargs: Option<&PyDict>, + args: Bound<'py, PyTuple>, + kwargs: Option>, ) -> PyResult { - let limit = get_limit(kwargs); - if !has_filters(args, kwargs) { + let limit = get_limit(&kwargs); + if !has_filters(&args, &kwargs) { self.map(|annotation| { Ok(PyTextSelections::from_iter( annotation.related_text(operator.operator).limit(limit), @@ -442,8 +458,8 @@ impl PyAnnotation { var: "main", operator: operator.operator, //MAYBE TODO: check if we need to invert an operator here? }, - args, - kwargs, + &args, + &kwargs, |annotation, query| { PyTextSelections::from_query(query, annotation.store(), &self.store, limit) }, @@ -456,7 +472,8 @@ impl PyAnnotation { } /// Returns the annotation as a W3C Web Annotation in JSON-LD, as a string - fn webannotation(&self, kwargs: Option<&PyDict>) -> PyResult { + #[pyo3(signature = (kwargs=None))] + fn webannotation<'py>(&self, kwargs: Option<&Bound<'py, PyDict>>) -> PyResult { let mut config = WebAnnoConfig::default(); if let Some(kwargs) = kwargs { if let Ok(Some(v)) = kwargs.get_item("default_annotation_iri") { @@ -522,13 +539,13 @@ impl PyAnnotation { } #[pyo3(signature = (via, **kwargs))] - fn transpose( + fn transpose<'py>( &mut self, via: &PyAnnotation, - kwargs: Option<&PyDict>, + kwargs: Option>, ) -> PyResult { let transpose_config = if let Some(kwargs) = kwargs { - get_transpose_config(kwargs) + get_transpose_config(&kwargs)? } else { TransposeConfig::default() }; @@ -560,9 +577,9 @@ impl PyAnnotation { fn alignments<'py>( &self, py: Python<'py>, - kwargs: Option<&'py PyDict>, - ) -> PyResult<&'py PyList> { - let alignments = PyList::empty(py); + kwargs: Option>, + ) -> PyResult> { + let alignments = PyList::empty_bound(py); let storeclone = self.store.clone(); let mut annotations = false; if let Some(kwargs) = kwargs { @@ -577,7 +594,7 @@ impl PyAnnotation { if let (Some(left), Some(right)) = (annoiter.next(), annoiter.next()) { //complex transposition for (text1, text2) in left.textselections().zip(right.textselections()) { - let alignment = PyList::empty(py); + let alignment = PyList::empty_bound(py); if annotations { alignment .append(PyAnnotation::new_py(left.handle(), storeclone.clone(), py)) @@ -601,7 +618,7 @@ impl PyAnnotation { //simple transposition let mut textiter = annotation.textselections(); if let (Some(text1), Some(text2)) = (textiter.next(), textiter.next()) { - let alignment = PyList::empty(py); + let alignment = PyList::empty_bound(py); alignment .append(PyTextSelection::from_result_to_py(text1, &storeclone, py)) .map_err(|_| StamError::OtherError("failed to extract alignment"))?; @@ -663,9 +680,13 @@ impl PyAnnotations { /// Returns annotation data instances used by the annotations in this collection. #[pyo3(signature = (*args, **kwargs))] - fn data(&self, args: &PyTuple, kwargs: Option<&PyDict>) -> PyResult { - let limit = get_limit(kwargs); - if !has_filters(args, kwargs) { + fn data<'py>( + &self, + args: Bound<'py, PyTuple>, + kwargs: Option>, + ) -> PyResult { + let limit = get_limit(&kwargs); + if !has_filters(&args, &kwargs) { self.map(|annotations, _store| { Ok(PyData::from_iter( annotations.items().data().limit(limit), @@ -681,16 +702,20 @@ impl PyAnnotations { AnnotationDepth::One, None, ), - args, - kwargs, + &args, + &kwargs, |query, store| PyData::from_query(query, store, &self.store, limit), ) } } #[pyo3(signature = (*args, **kwargs))] - fn test_data(&self, args: &PyTuple, kwargs: Option<&PyDict>) -> PyResult { - if !has_filters(args, kwargs) { + fn test_data<'py>( + &self, + args: Bound<'py, PyTuple>, + kwargs: Option>, + ) -> PyResult { + if !has_filters(&args, &kwargs) { self.map(|annotations, _| Ok(annotations.items().data().test())) } else { self.map_with_query( @@ -701,17 +726,21 @@ impl PyAnnotations { AnnotationDepth::One, None, ), - args, - kwargs, + &args, + &kwargs, |query, store| Ok(store.query(query)?.test()), ) } } #[pyo3(signature = (*args, **kwargs))] - fn annotations(&self, args: &PyTuple, kwargs: Option<&PyDict>) -> PyResult { - let limit = get_limit(kwargs); - if !has_filters(args, kwargs) { + fn annotations<'py>( + &self, + args: Bound<'py, PyTuple>, + kwargs: Option>, + ) -> PyResult { + let limit = get_limit(&kwargs); + if !has_filters(&args, &kwargs) { self.map(|annotations, _store| { Ok(PyAnnotations::from_iter( annotations.items().annotations().limit(limit), @@ -727,16 +756,20 @@ impl PyAnnotations { AnnotationDepth::One, None, ), - args, - kwargs, + &args, + &kwargs, |query, store| PyAnnotations::from_query(query, store, &self.store, limit), ) } } #[pyo3(signature = (*args, **kwargs))] - fn test_annotations(&self, args: &PyTuple, kwargs: Option<&PyDict>) -> PyResult { - if !has_filters(args, kwargs) { + fn test_annotationsi<'py>( + &self, + args: Bound<'py, PyTuple>, + kwargs: Option>, + ) -> PyResult { + if !has_filters(&args, &kwargs) { self.map(|annotations, _| Ok(annotations.items().annotations().test())) } else { self.map_with_query( @@ -747,22 +780,22 @@ impl PyAnnotations { AnnotationDepth::One, None, ), - args, - kwargs, + &args, + &kwargs, |query, store| Ok(store.query(query)?.test()), ) } } #[pyo3(signature = (*args, **kwargs))] - fn annotations_in_targets( + fn annotations_in_targets<'py>( &self, - args: &PyTuple, - kwargs: Option<&PyDict>, + args: Bound<'py, PyTuple>, + kwargs: Option>, ) -> PyResult { - let limit = get_limit(kwargs); - let recursive = get_recursive(kwargs, AnnotationDepth::One); - if !has_filters(args, kwargs) { + let limit = get_limit(&kwargs); + let recursive = get_recursive(&kwargs, AnnotationDepth::One); + if !has_filters(&args, &kwargs) { self.map(|annotations, _store| { Ok(PyAnnotations::from_iter( annotations @@ -776,21 +809,21 @@ impl PyAnnotations { self.map_with_query( Type::Annotation, Constraint::AnnotationVariable("main", SelectionQualifier::Normal, recursive, None), - args, - kwargs, + &args, + &kwargs, |query, store| PyAnnotations::from_query(query, store, &self.store, limit), ) } } #[pyo3(signature = (*args, **kwargs))] - fn test_annotations_in_targets( + fn test_annotations_in_targets<'py>( &self, - args: &PyTuple, - kwargs: Option<&PyDict>, + args: Bound<'py, PyTuple>, + kwargs: Option>, ) -> PyResult { - let recursive = get_recursive(kwargs, AnnotationDepth::One); - if !has_filters(args, kwargs) { + let recursive = get_recursive(&kwargs, AnnotationDepth::One); + if !has_filters(&args, &kwargs) { self.map(|annotations, _| { Ok(annotations.items().annotations_in_targets(recursive).test()) }) @@ -798,21 +831,21 @@ impl PyAnnotations { self.map_with_query( Type::Annotation, Constraint::AnnotationVariable("main", SelectionQualifier::Normal, recursive, None), - args, - kwargs, + &args, + &kwargs, |query, store| Ok(store.query(query)?.test()), ) } } #[pyo3(signature = (*args,**kwargs))] - fn textselections( + fn textselections<'py>( &self, - args: &PyTuple, - kwargs: Option<&PyDict>, + args: Bound<'py, PyTuple>, + kwargs: Option>, ) -> PyResult { - let limit = get_limit(kwargs); - if !has_filters(args, kwargs) { + let limit = get_limit(&kwargs); + if !has_filters(&args, &kwargs) { self.map(|annotations, _store| { Ok(PyTextSelections::from_iter( annotations.items().textselections().limit(limit), @@ -828,22 +861,22 @@ impl PyAnnotations { AnnotationDepth::One, None, ), - args, - kwargs, + &args, + &kwargs, |query, store| PyTextSelections::from_query(query, store, &self.store, limit), ) } } #[pyo3(signature = (operator, *args, **kwargs))] - fn related_text( + fn related_text<'py>( &self, operator: PyTextSelectionOperator, - args: &PyTuple, - kwargs: Option<&PyDict>, + args: Bound<'py, PyTuple>, + kwargs: Option>, ) -> PyResult { - let limit = get_limit(kwargs); - if !has_filters(args, kwargs) { + let limit = get_limit(&kwargs); + if !has_filters(&args, &kwargs) { self.map(|annotations, _store| { Ok(PyTextSelections::from_iter( annotations @@ -860,8 +893,8 @@ impl PyAnnotations { var: "main", operator: operator.operator, //MAYBE TODO: check if we need to invert an operator here? }, - args, - kwargs, + &args, + &kwargs, |query, store| PyTextSelections::from_query(query, store, &self.store, limit), ) } @@ -949,12 +982,12 @@ impl PyAnnotations { } } - fn map_with_query( + pub(crate) fn map_with_query<'py, T, F>( &self, resulttype: Type, constraint: Constraint, - args: &PyTuple, - kwargs: Option<&PyDict>, + args: &Bound<'py, PyTuple>, + kwargs: &Option>, f: F, ) -> Result where @@ -1063,12 +1096,12 @@ impl PyAnnotation { } } - fn map_with_query( + pub(crate) fn map_with_query<'py, T, F>( &self, resulttype: Type, constraint: Constraint, - args: &PyTuple, - kwargs: Option<&PyDict>, + args: &Bound<'py, PyTuple>, + kwargs: &Option>, f: F, ) -> Result where @@ -1093,44 +1126,42 @@ impl PyAnnotation { } } -pub fn get_transpose_config(kwargs: &PyDict) -> TransposeConfig { +pub fn get_transpose_config(kwargs: &Bound) -> PyResult { let mut config = TransposeConfig::default(); for (key, value) in kwargs { - if let Some(key) = key.extract().unwrap() { - match key { - "debug" => { - if let Ok(Some(value)) = value.extract() { - config.debug = value; - } + match key.downcast()?.extract()? { + "debug" => { + if let Ok(Some(value)) = value.extract() { + config.debug = value; } - "allow_simple" => { - if let Ok(Some(value)) = value.extract() { - config.allow_simple = value; - } + } + "allow_simple" => { + if let Ok(Some(value)) = value.extract() { + config.allow_simple = value; } - "no_transposition" => { - if let Ok(Some(value)) = value.extract() { - config.no_transposition = value; - } + } + "no_transposition" => { + if let Ok(Some(value)) = value.extract() { + config.no_transposition = value; } - "no_resegmentation" => { - if let Ok(Some(value)) = value.extract() { - config.no_resegmentation = value; - } + } + "no_resegmentation" => { + if let Ok(Some(value)) = value.extract() { + config.no_resegmentation = value; } - "transposition_id" => { - if let Ok(Some(value)) = value.extract() { - config.transposition_id = value; - } + } + "transposition_id" => { + if let Ok(Some(value)) = value.extract() { + config.transposition_id = value; } - "resegmentation_id" => { - if let Ok(Some(value)) = value.extract() { - config.resegmentation_id = value; - } + } + "resegmentation_id" => { + if let Ok(Some(value)) = value.extract() { + config.resegmentation_id = value; } - _ => eprintln!("Ignored unknown kwargs option for transpose(): {}", key), } + _ => eprintln!("Ignored unknown kwargs option for transpose(): {}", key), } } - config + Ok(config) } diff --git a/src/annotationdata.rs b/src/annotationdata.rs index 4f74542..61da6e1 100644 --- a/src/annotationdata.rs +++ b/src/annotationdata.rs @@ -79,32 +79,44 @@ impl PyDataKey { } #[pyo3(signature = (*args, **kwargs))] - fn data(&self, args: &PyTuple, kwargs: Option<&PyDict>) -> PyResult { - let limit = get_limit(kwargs); - if !has_filters(args, kwargs) { + fn data<'py>( + &self, + args: Bound<'py, PyTuple>, + kwargs: Option>, + ) -> PyResult { + let limit = get_limit(&kwargs); + if !has_filters(&args, &kwargs) { self.map(|key| Ok(PyData::from_iter(key.data().limit(limit), &self.store))) } else { - self.map_with_query(Type::AnnotationData, args, kwargs, |key, query| { + self.map_with_query(Type::AnnotationData, &args, &kwargs, |key, query| { PyData::from_query(query, key.rootstore(), &self.store, limit) }) } } #[pyo3(signature = (*args, **kwargs))] - fn test_data(&self, args: &PyTuple, kwargs: Option<&PyDict>) -> PyResult { - if !has_filters(args, kwargs) { + fn test_data<'py>( + &self, + args: Bound<'py, PyTuple>, + kwargs: Option>, + ) -> PyResult { + if !has_filters(&args, &kwargs) { self.map(|key| Ok(key.data().test())) } else { - self.map_with_query(Type::AnnotationData, args, kwargs, |key, query| { + self.map_with_query(Type::AnnotationData, &args, &kwargs, |key, query| { Ok(key.rootstore().query(query)?.test()) }) } } #[pyo3(signature = (*args, **kwargs))] - fn annotations(&self, args: &PyTuple, kwargs: Option<&PyDict>) -> PyResult { - let limit = get_limit(kwargs); - if !has_filters(args, kwargs) { + fn annotations<'py>( + &self, + args: Bound<'py, PyTuple>, + kwargs: Option>, + ) -> PyResult { + let limit = get_limit(&kwargs); + if !has_filters(&args, &kwargs) { self.map(|key| { Ok(PyAnnotations::from_iter( key.annotations().limit(limit), @@ -112,18 +124,22 @@ impl PyDataKey { )) }) } else { - self.map_with_query(Type::Annotation, args, kwargs, |key, query| { + self.map_with_query(Type::Annotation, &args, &kwargs, |key, query| { PyAnnotations::from_query(query, key.rootstore(), &self.store, limit) }) } } #[pyo3(signature = (*args, **kwargs))] - fn test_annotations(&self, args: &PyTuple, kwargs: Option<&PyDict>) -> PyResult { - if !has_filters(args, kwargs) { + fn test_annotations<'py>( + &self, + args: Bound<'py, PyTuple>, + kwargs: Option>, + ) -> PyResult { + if !has_filters(&args, &kwargs) { self.map(|key| Ok(key.annotations().test())) } else { - self.map_with_query(Type::Annotation, args, kwargs, |key, query| { + self.map_with_query(Type::Annotation, &args, &kwargs, |key, query| { Ok(key.rootstore().query(query)?.test()) }) } @@ -181,11 +197,11 @@ impl PyDataKey { } } - fn map_with_query( + pub(crate) fn map_with_query<'py, T, F>( &self, resulttype: Type, - args: &PyTuple, - kwargs: Option<&PyDict>, + args: &Bound<'py, PyTuple>, + kwargs: &Option>, f: F, ) -> Result where @@ -238,7 +254,7 @@ impl PyAnnotationData { } } -pub(crate) fn datavalue_from_py<'py>(value: &'py PyAny) -> Result { +pub(crate) fn datavalue_from_py<'py>(value: Bound<'py, PyAny>) -> Result { if let Ok(value) = value.extract() { Ok(DataValue::String(value)) } else if let Ok(value) = value.extract() { @@ -253,7 +269,7 @@ pub(crate) fn datavalue_from_py<'py>(value: &'py PyAny) -> Result() { - let value: &PyList = value.downcast().unwrap(); + let value: &Bound<'py, PyList> = value.downcast().expect("downcast must succeed"); let mut list: Vec = Vec::new(); for item in value { let pyitem = datavalue_from_py(item)?; @@ -270,25 +286,25 @@ pub(crate) fn datavalue_from_py<'py>(value: &'py PyAny) -> Result( datavalue: &DataValue, py: Python<'py>, -) -> Result<&'py PyAny, StamError> { +) -> Result, StamError> { match datavalue { - DataValue::String(s) => Ok(s.into_py(py).into_ref(py)), - DataValue::Float(f) => Ok(f.into_py(py).into_ref(py)), - DataValue::Int(v) => Ok(v.into_py(py).into_ref(py)), - DataValue::Bool(v) => Ok(v.into_py(py).into_ref(py)), - DataValue::Datetime(v) => Ok(v.into_py(py).into_ref(py)), + DataValue::String(s) => Ok(s.into_py(py).into_bound(py)), + DataValue::Float(f) => Ok(f.into_py(py).into_bound(py)), + DataValue::Int(v) => Ok(v.into_py(py).into_bound(py)), + DataValue::Bool(v) => Ok(v.into_py(py).into_bound(py)), + DataValue::Datetime(v) => Ok(v.into_py(py).into_bound(py)), DataValue::Null => { //feels a bit hacky, but I can't find a PyNone to return as PyAny let x: Option = None; - Ok(x.into_py(py).into_ref(py)) + Ok(x.into_py(py).into_bound(py)) } DataValue::List(v) => { - let pylist = PyList::empty(py); + let pylist = PyList::empty_bound(py); for item in v.iter() { let pyvalue = datavalue_into_py(item, py)?; pylist.append(pyvalue).expect("adding value to list"); } - Ok(pylist) + Ok(pylist.into_any()) } } } @@ -309,12 +325,12 @@ impl std::fmt::Display for PyDataValue { #[pymethods] impl PyDataValue { // Get the actual value - fn get<'py>(&self, py: Python<'py>) -> PyResult<&'py PyAny> { + fn get<'py>(&self, py: Python<'py>) -> PyResult> { datavalue_into_py(&self.value, py).map_err(|err| PyStamError::new_err(format!("{}", err))) } #[new] - fn new<'py>(value: &PyAny) -> PyResult { + fn new<'py>(value: Bound<'py, PyAny>) -> PyResult { Ok(PyDataValue { value: datavalue_from_py(value) .map_err(|err| PyStamError::new_err(format!("{}", err)))?, @@ -452,9 +468,13 @@ impl PyAnnotationData { } #[pyo3(signature = (*args, **kwargs))] - fn annotations(&self, args: &PyTuple, kwargs: Option<&PyDict>) -> PyResult { - let limit = get_limit(kwargs); - if !has_filters(args, kwargs) { + fn annotations<'py>( + &self, + args: Bound<'py, PyTuple>, + kwargs: Option>, + ) -> PyResult { + let limit = get_limit(&kwargs); + if !has_filters(&args, &kwargs) { self.map(|data| { Ok(PyAnnotations::from_iter( data.annotations().limit(limit), @@ -462,18 +482,22 @@ impl PyAnnotationData { )) }) } else { - self.map_with_query(Type::Annotation, args, kwargs, |data, query| { + self.map_with_query(Type::Annotation, &args, &kwargs, |data, query| { PyAnnotations::from_query(query, data.rootstore(), &self.store, limit) }) } } #[pyo3(signature = (*args, **kwargs))] - fn test_annotations(&self, args: &PyTuple, kwargs: Option<&PyDict>) -> PyResult { - if !has_filters(args, kwargs) { + fn test_annotations<'py>( + &self, + args: Bound<'py, PyTuple>, + kwargs: Option>, + ) -> PyResult { + if !has_filters(&args, &kwargs) { self.map(|key| Ok(key.annotations().test())) } else { - self.map_with_query(Type::Annotation, args, kwargs, |key, query| { + self.map_with_query(Type::Annotation, &args, &kwargs, |key, query| { Ok(key.rootstore().query(query)?.test()) }) } @@ -531,11 +555,11 @@ impl PyAnnotationData { } } - fn map_with_query( + pub(crate) fn map_with_query<'py, T, F>( &self, resulttype: Type, - args: &PyTuple, - kwargs: Option<&PyDict>, + args: &Bound<'py, PyTuple>, + kwargs: &Option>, f: F, ) -> Result where @@ -563,7 +587,9 @@ impl PyAnnotationData { /// Build an AnnotationDataBuilder from a python dictionary (or string referring to an existing public ID) /// Expects a Python dictionary with fields "id", "key","set", "value" (or a subpart thereof). The field values /// may be STAM data types or plain strings with public IDs. -pub(crate) fn annotationdata_builder<'a>(data: &'a PyAny) -> PyResult> { +pub(crate) fn annotationdata_builder<'py>( + data: Bound<'py, PyAny>, +) -> PyResult> { let mut builder = AnnotationDataBuilder::new(); if data.is_instance_of::() { let adata: PyRef<'_, PyAnnotationData> = data.extract()?; @@ -608,8 +634,8 @@ pub(crate) fn annotationdata_builder<'a>(data: &'a PyAny) -> PyResult() { - let id = data.downcast::()?; - Ok(AnnotationDataBuilder::new().with_id(id.to_str()?.into())) + let id: String = data.downcast()?.extract()?; + Ok(AnnotationDataBuilder::new().with_id(id.into())) } else { Err(PyValueError::new_err( "Argument to build AnnotationData must be a dictionary (with fields id, key, set, value), a string (with a public ID), or an AnnotationData instance. A list containing any multiple of those types is also allowed in certain circumstances.", @@ -617,7 +643,9 @@ pub(crate) fn annotationdata_builder<'a>(data: &'a PyAny) -> PyResult Result, StamError> { +pub(crate) fn dataoperator_from_kwargs<'py>( + kwargs: &Bound<'py, PyDict>, +) -> Result>, StamError> { if let Ok(Some(value)) = kwargs.get_item("value") { Ok(Some(dataoperator_from_py(value)?)) } else if let Ok(Some(value)) = kwargs.get_item("value_not") { @@ -650,7 +678,7 @@ pub(crate) fn dataoperator_from_kwargs(kwargs: &PyDict) -> Result() { - let values: &PyTuple = values.downcast().unwrap(); + let values: &Bound<'py, PyTuple> = values.downcast().unwrap(); let mut suboperators = Vec::with_capacity(values.len()); for value in values { suboperators.push(dataoperator_from_py(value)?) @@ -661,7 +689,7 @@ pub(crate) fn dataoperator_from_kwargs(kwargs: &PyDict) -> Result() { - let values: &PyTuple = values.downcast().unwrap(); + let values: &Bound<'py, PyTuple> = values.downcast().unwrap(); let mut suboperators = Vec::with_capacity(values.len()); for value in values { suboperators.push(dataoperator_from_py(value)?) @@ -709,11 +737,13 @@ pub(crate) fn dataoperator_from_kwargs(kwargs: &PyDict) -> Result Result { +pub(crate) fn dataoperator_from_py<'py>( + value: Bound<'py, PyAny>, +) -> Result, StamError> { if value.is_none() { Ok(DataOperator::Null) - } else if let Ok(value) = value.extract() { - Ok(DataOperator::Equals(value)) + } else if let Ok(value) = value.extract::() { + Ok(DataOperator::Equals(value.into())) } else if let Ok(value) = value.extract() { Ok(DataOperator::EqualsInt(value)) } else if let Ok(value) = value.extract() { @@ -731,7 +761,9 @@ pub(crate) fn dataoperator_from_py(value: &PyAny) -> Result Result { +pub(crate) fn dataoperator_greater_from_py<'py>( + value: Bound<'py, PyAny>, +) -> Result { if let Ok(value) = value.extract() { Ok(DataOperator::GreaterThan(value)) } else if let Ok(value) = value.extract() { @@ -743,7 +775,9 @@ pub(crate) fn dataoperator_greater_from_py(value: &PyAny) -> Result Result { +pub(crate) fn dataoperator_less_from_py<'py>( + value: Bound<'py, PyAny>, +) -> Result { if let Ok(value) = value.extract() { Ok(DataOperator::LessThan(value)) } else if let Ok(value) = value.extract() { @@ -755,7 +789,9 @@ pub(crate) fn dataoperator_less_from_py(value: &PyAny) -> Result Result { +pub(crate) fn dataoperator_greatereq_from_py<'py>( + value: Bound<'py, PyAny>, +) -> Result { if let Ok(value) = value.extract() { Ok(DataOperator::GreaterThanOrEqual(value)) } else if let Ok(value) = value.extract() { @@ -767,7 +803,9 @@ pub(crate) fn dataoperator_greatereq_from_py(value: &PyAny) -> Result Result { +pub(crate) fn dataoperator_lesseq_from_py<'py>( + value: Bound<'py, PyAny>, +) -> Result { if let Ok(value) = value.extract() { Ok(DataOperator::LessThanOrEqual(value)) } else if let Ok(value) = value.extract() { @@ -831,9 +869,13 @@ impl PyData { } #[pyo3(signature = (*args, **kwargs))] - fn annotations(&self, args: &PyTuple, kwargs: Option<&PyDict>) -> PyResult { - let limit = get_limit(kwargs); - if !has_filters(args, kwargs) { + fn annotations<'py>( + &self, + args: Bound<'py, PyTuple>, + kwargs: Option>, + ) -> PyResult { + let limit = get_limit(&kwargs); + if !has_filters(&args, &kwargs) { self.map(|data, _store| { Ok(PyAnnotations::from_iter( data.items().annotations().limit(limit), @@ -841,18 +883,22 @@ impl PyData { )) }) } else { - self.map_with_query(Type::Annotation, args, kwargs, |query, store| { + self.map_with_query(Type::Annotation, &args, &kwargs, |query, store| { PyAnnotations::from_query(query, store, &self.store, limit) }) } } #[pyo3(signature = (*args, **kwargs))] - fn test_annotations(&self, args: &PyTuple, kwargs: Option<&PyDict>) -> PyResult { - if !has_filters(args, kwargs) { + fn test_annotations<'py>( + &self, + args: Bound<'py, PyTuple>, + kwargs: Option>, + ) -> PyResult { + if !has_filters(&args, &kwargs) { self.map(|data, _| Ok(data.items().annotations().test())) } else { - self.map_with_query(Type::Annotation, args, kwargs, |query, store| { + self.map_with_query(Type::Annotation, &args, &kwargs, |query, store| { Ok(store.query(query)?.test()) }) } @@ -911,11 +957,11 @@ impl PyData { } } - fn map_with_query( + pub(crate) fn map_with_query<'py, T, F>( &self, resulttype: Type, - args: &PyTuple, - kwargs: Option<&PyDict>, + args: &Bound<'py, PyTuple>, + kwargs: &Option>, f: F, ) -> Result where diff --git a/src/annotationdataset.rs b/src/annotationdataset.rs index 93cd3c9..4cf64fc 100644 --- a/src/annotationdataset.rs +++ b/src/annotationdataset.rs @@ -35,8 +35,8 @@ impl PyAnnotationDataSet { handle: AnnotationDataSetHandle, store: Arc>, py: Python<'py>, - ) -> &'py PyAny { - Self::new(handle, store).into_py(py).into_ref(py) + ) -> Bound<'py, PyAny> { + Self::new(handle, store).into_py(py).into_bound(py) } } @@ -131,10 +131,11 @@ impl PyAnnotationDataSet { } /// Create a new AnnotationData instance and adds it to the dataset + #[pyo3(signature=(key, value, id=None))] fn add_data<'py>( &self, key: &str, - value: &'py PyAny, + value: Bound<'py, PyAny>, id: Option<&str>, ) -> PyResult { let datakey = if let Ok(datakey) = self.key(key) { @@ -190,31 +191,39 @@ impl PyAnnotationDataSet { } #[pyo3(signature = (*args, **kwargs))] - fn data(&self, args: &PyTuple, kwargs: Option<&PyDict>) -> PyResult { - let limit = get_limit(kwargs); - if !has_filters(args, kwargs) { + fn data<'py>( + &self, + args: Bound<'py, PyTuple>, + kwargs: Option>, + ) -> PyResult { + let limit = get_limit(&kwargs); + if !has_filters(&args, &kwargs) { self.map(|dataset| Ok(PyData::from_iter(dataset.data().limit(limit), &self.store))) } else { self.map_with_query( Type::AnnotationData, Constraint::DataSetVariable("main", SelectionQualifier::Normal), - args, - kwargs, + &args, + &kwargs, |dataset, query| PyData::from_query(query, dataset.store(), &self.store, limit), ) } } #[pyo3(signature = (*args, **kwargs))] - fn test_data(&self, args: &PyTuple, kwargs: Option<&PyDict>) -> PyResult { - if !has_filters(args, kwargs) { + fn test_data<'py>( + &self, + args: Bound<'py, PyTuple>, + kwargs: Option>, + ) -> PyResult { + if !has_filters(&args, &kwargs) { self.map(|dataset| Ok(dataset.data().test())) } else { self.map_with_query( Type::AnnotationData, Constraint::DataSetVariable("main", SelectionQualifier::Normal), - args, - kwargs, + &args, + &kwargs, |dataset, query| Ok(dataset.store().query(query)?.test()), ) } @@ -286,12 +295,12 @@ impl PyAnnotationDataSet { } } - fn map_with_query( + pub(crate) fn map_with_query<'py, T, F>( &self, resulttype: Type, constraint: Constraint, - args: &PyTuple, - kwargs: Option<&PyDict>, + args: &Bound<'py, PyTuple>, + kwargs: &Option>, f: F, ) -> Result where diff --git a/src/annotationstore.rs b/src/annotationstore.rs index b60cd04..25b12b4 100644 --- a/src/annotationstore.rs +++ b/src/annotationstore.rs @@ -1,5 +1,4 @@ use pyo3::exceptions::{PyRuntimeError, PyTypeError, PyValueError}; -use pyo3::ffi::PyVarObject; use pyo3::prelude::*; use pyo3::types::*; use rayon::iter::{IntoParallelIterator, ParallelIterator}; @@ -42,58 +41,51 @@ impl PyAnnotationStore { #[new] #[pyo3(signature = (**kwargs))] #[pyo3(text_signature = "(self, id=None, file=None, string=None, config=None)")] - fn new<'py>(kwargs: Option<&PyDict>, py: Python<'py>) -> PyResult { + fn new<'py>(kwargs: Option>, py: Python<'py>) -> PyResult { if let Some(kwargs) = kwargs { - let mut config: &PyDict = PyDict::new(py); - for (key, value) in kwargs { - if let Some(key) = key.extract().unwrap() { - match key { - "config" => { - if let Ok(Some(value)) = value.extract() { - config = value; - } + let mut config = PyDict::new_bound(py); + for (key, value) in kwargs.iter() { + match key.downcast()?.extract()? { + "config" => { + if let Ok(Some(value)) = value.extract() { + config = value; } - _ => continue, } + _ => continue, } } - for (key, value) in kwargs { - if let Some(key) = key.extract().unwrap() { - match key { - "config" => continue, //already handled - "file" => { - if let Ok(Some(value)) = value.extract() { - return match AnnotationStore::from_file(value, get_config(config)) { - Ok(store) => Ok(PyAnnotationStore { - store: Arc::new(RwLock::new(store)), - }), - Err(err) => Err(PyStamError::new_err(format!("{}", err))), - }; - } - } - "string" => { - if let Ok(Some(value)) = value.extract() { - return match AnnotationStore::from_str(value, get_config(config)) { - Ok(store) => Ok(PyAnnotationStore { - store: Arc::new(RwLock::new(store)), - }), - Err(err) => Err(PyStamError::new_err(format!("{}", err))), - }; - } - } - "id" => { - if let Ok(Some(value)) = value.extract::>() { - return Ok(PyAnnotationStore { - store: Arc::new(RwLock::new( - AnnotationStore::default() - .with_id(value) - .with_config(get_config(config)), - )), - }); - } - } - _ => eprintln!("Ignored unknown kwargs option {}", key), + for (key, value) in kwargs.iter() { + match key.downcast()?.extract()? { + "config" => continue, //already handled + "file" => { + let value = value.downcast()?.extract()?; + return match AnnotationStore::from_file(value, get_config(&config)?) { + Ok(store) => Ok(PyAnnotationStore { + store: Arc::new(RwLock::new(store)), + }), + Err(err) => Err(PyStamError::new_err(format!("{}", err))), + }; + } + "string" => { + let value = value.downcast()?.extract()?; + return match AnnotationStore::from_str(value, get_config(&config)?) { + Ok(store) => Ok(PyAnnotationStore { + store: Arc::new(RwLock::new(store)), + }), + Err(err) => Err(PyStamError::new_err(format!("{}", err))), + }; + } + "id" => { + let value: String = value.downcast()?.extract()?; + return Ok(PyAnnotationStore { + store: Arc::new(RwLock::new( + AnnotationStore::default() + .with_id(value) + .with_config(get_config(&config)?), + )), + }); } + _ => eprintln!("Ignored unknown kwargs option {}", key), } } } @@ -179,6 +171,7 @@ impl PyAnnotationStore { } /// Create a new TextResource or load an existing one and adds it to the store + #[pyo3(signature = (filename=None, text=None, id=None))] fn add_resource( &mut self, filename: Option<&str>, @@ -218,6 +211,7 @@ impl PyAnnotationStore { } /// Create a new AnnotationDataSet or load an existing one and adds it to the store + #[pyo3(signature = (id=None, filename=None))] fn add_dataset( &mut self, id: Option<&str>, @@ -278,10 +272,10 @@ impl PyAnnotationStore { /// Alternatively, you can pass an existing`AnnotationData` instance. /// `id` (:obj:`str`, `optional`) - The public ID for the annotation #[pyo3(signature = (target, data, id=None))] - fn annotate( + fn annotate<'py>( &mut self, target: PySelector, - data: &PyAny, //dictionary or list of dictionaries + data: Bound<'py, PyAny>, //dictionary or list of dictionaries id: Option, ) -> PyResult { let mut builder = AnnotationBuilder::new(); @@ -290,7 +284,7 @@ impl PyAnnotationStore { } builder = builder.with_target(target.build()); if data.is_instance_of::() { - let data: &PyList = data.downcast().unwrap(); + let data: Bound<'py, PyList> = data.downcast()?.clone(); for databuilder in data.iter() { let databuilder = annotationdata_builder(databuilder)?; builder = builder.with_data_builder(databuilder); @@ -317,10 +311,14 @@ impl PyAnnotationStore { } #[pyo3(signature = (*args, **kwargs))] - fn annotations(&self, args: &PyTuple, kwargs: Option<&PyDict>) -> PyResult { - let limit = get_limit(kwargs); - let substore = get_substore(kwargs); - if !has_filters(args, kwargs) { + fn annotations<'py>( + &self, + args: Bound<'py, PyTuple>, + kwargs: Option>, + ) -> PyResult { + let limit = get_limit(&kwargs); + let substore = get_substore(&kwargs); + if !has_filters(&args, &kwargs) { if substore == Some(false) { self.map(|store| { Ok(PyAnnotations::from_iter( @@ -337,7 +335,7 @@ impl PyAnnotationStore { }) } } else { - self.map_with_query(Type::Annotation, args, kwargs, |query, store| { + self.map_with_query(Type::Annotation, &args, &kwargs, |query, store| { PyAnnotations::from_query(query, store, &self.store, limit) }) } @@ -393,12 +391,16 @@ impl PyAnnotationStore { } #[pyo3(signature = (*args, **kwargs))] - fn data(&self, args: &PyTuple, kwargs: Option<&PyDict>) -> PyResult { - let limit = get_limit(kwargs); - if !has_filters(args, kwargs) { + fn data<'py>( + &self, + args: Bound<'py, PyTuple>, + kwargs: Option>, + ) -> PyResult { + let limit = get_limit(&kwargs); + if !has_filters(&args, &kwargs) { self.map(|store| Ok(PyData::from_iter(store.data().limit(limit), &self.store))) } else { - self.map_with_query(Type::AnnotationData, args, kwargs, |query, store| { + self.map_with_query(Type::AnnotationData, &args, &kwargs, |query, store| { PyData::from_query(query, store, &self.store, limit) }) } @@ -408,13 +410,13 @@ impl PyAnnotationStore { fn query<'py>( &mut self, querystring: &str, - kwargs: Option<&'py PyDict>, + kwargs: Option>, py: Python<'py>, - ) -> PyResult<&'py PyList> { + ) -> PyResult> { let clonedstore = self.store.clone(); self.map_mut(|store| { let (mut query, _) = Query::parse(querystring)?; - let readonly = get_bool(kwargs, "readonly", false); + let readonly = get_bool(&kwargs, "readonly", false); if let Some(kwargs) = kwargs { //bind keyword arguments as variables in the query for (varname, value) in kwargs.iter() { @@ -490,8 +492,12 @@ impl PyAnnotationStore { } #[pyo3(signature = (item, **kwargs))] - fn remove<'py>(&mut self, item: &'py PyAny, kwargs: Option<&'py PyDict>) -> PyResult<()> { - let strict = get_bool(kwargs, "strict", false); + fn remove<'py>( + &mut self, + item: Bound<'py, PyAny>, + kwargs: Option>, + ) -> PyResult<()> { + let strict = get_bool(&kwargs, "strict", false); if item.is_instance_of::() { let item: PyRef<'py, PyAnnotation> = item.extract()?; self.map_store_mut(|store| store.remove(item.handle)) @@ -516,8 +522,8 @@ impl PyAnnotationStore { fn view<'py>( &self, querystring: &str, - args: &'py PyTuple, - kwargs: Option<&'py PyDict>, + args: Bound<'py, PyTuple>, + kwargs: Option>, ) -> PyResult { let mut append_querystring = querystring.to_string(); if querystring.trim().ends_with("}") { @@ -544,14 +550,14 @@ impl PyAnnotationStore { append_querystring.as_str() )) })?; - let legend = get_bool(kwargs, "legend", true); - let titles = get_bool(kwargs, "titles", true); - let interactive = get_bool(kwargs, "interactive", true); - let selectionvar = get_opt_string(kwargs, "use", None); - let autocollapse = get_bool(kwargs, "autocollapse", false); - let header = get_opt_string(kwargs, "header", None); - let footer = get_opt_string(kwargs, "footer", None); - let format = get_opt_string(kwargs, "format", Some("html")); + let legend = get_bool(&kwargs, "legend", true); + let titles = get_bool(&kwargs, "titles", true); + let interactive = get_bool(&kwargs, "interactive", true); + let selectionvar = get_opt_string(&kwargs, "use", None); + let autocollapse = get_bool(&kwargs, "autocollapse", false); + let header = get_opt_string(&kwargs, "header", None); + let footer = get_opt_string(&kwargs, "footer", None); + let format = get_opt_string(&kwargs, "format", Some("html")); match format.as_deref() { Some("html") => self .map_store(|store| { @@ -589,15 +595,16 @@ impl PyAnnotationStore { } #[pyo3(signature = (querystrings, retain))] - fn split<'py>(&mut self, querystrings: Vec<&str>, retain: bool) -> PyResult<()> { + fn split<'py>(&mut self, querystrings: Vec, retain: bool) -> PyResult<()> { let mode = if retain { SplitMode::Retain } else { SplitMode::Delete }; let mut queries: Vec = Vec::new(); - for querystring in querystrings { + for querystring in querystrings.iter() { let query: Query = querystring + .as_str() .try_into() .map_err(|err| PyStamError::new_err(format!("{}", err)))?; queries.push(query); @@ -606,13 +613,13 @@ impl PyAnnotationStore { } #[pyo3(signature = (*args, **kwargs))] - fn align_texts( + fn align_texts<'py>( &mut self, args: Vec<(PyTextSelection, PyTextSelection)>, - kwargs: Option<&PyDict>, + kwargs: Option>, ) -> PyResult>> { let alignmentconfig = if let Some(kwargs) = kwargs { - get_alignmentconfig(kwargs)? + get_alignmentconfig(&kwargs)? } else { AlignmentConfig::default() }; @@ -729,11 +736,11 @@ impl PyAnnotationStore { self.map_store_mut(f) } - pub(crate) fn map_with_query( + pub(crate) fn map_with_query<'py, T, F>( &self, resulttype: Type, - args: &PyTuple, - kwargs: Option<&PyDict>, + args: &Bound<'py, PyTuple>, + kwargs: &Option>, f: F, ) -> Result where diff --git a/src/config.rs b/src/config.rs index bbe9d4e..aba9202 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,84 +5,82 @@ use pyo3::types::*; use stam::*; use stamtools::align::{AbsoluteOrRelative, AlignmentAlgorithm, AlignmentConfig}; -pub fn get_config(kwargs: &PyDict) -> Config { +pub fn get_config<'py>(kwargs: &Bound<'py, PyDict>) -> PyResult { let mut config = Config::default(); for (key, value) in kwargs { - if let Some(key) = key.extract().unwrap() { - match key { - "use_include" => { - if let Ok(Some(value)) = value.extract() { - config = config.with_use_include(value); - } + match key.downcast()?.extract()? { + "use_include" => { + if let Ok(Some(value)) = value.extract() { + config = config.with_use_include(value); } - "debug" => { - if let Ok(Some(value)) = value.extract() { - config = config.with_debug(value); - } + } + "debug" => { + if let Ok(Some(value)) = value.extract() { + config = config.with_debug(value); } - "workdir" => { - if let Ok(Some(value)) = value.extract() { - config = config.with_workdir(value); - } + } + "workdir" => { + if let Ok(Some(value)) = value.extract() { + config = config.with_workdir(value); } - "textrelationmap" => { - if let Ok(Some(value)) = value.extract() { - config = config.with_textrelationmap(value); - } + } + "textrelationmap" => { + if let Ok(Some(value)) = value.extract() { + config = config.with_textrelationmap(value); } - "resource_annotation_map" => { - if let Ok(Some(value)) = value.extract() { - config = config.with_resource_annotation_map(value); - } + } + "resource_annotation_map" => { + if let Ok(Some(value)) = value.extract() { + config = config.with_resource_annotation_map(value); } - "dataset_annotation_map" => { - if let Ok(Some(value)) = value.extract() { - config = config.with_dataset_annotation_map(value); - } + } + "dataset_annotation_map" => { + if let Ok(Some(value)) = value.extract() { + config = config.with_dataset_annotation_map(value); } - "annotation_annotation_map" => { - if let Ok(Some(value)) = value.extract() { - config = config.with_annotation_annotation_map(value); - } + } + "annotation_annotation_map" => { + if let Ok(Some(value)) = value.extract() { + config = config.with_annotation_annotation_map(value); } - "key_annotation_metamap" => { - if let Ok(Some(value)) = value.extract() { - config = config.with_key_annotation_metamap(value); - } + } + "key_annotation_metamap" => { + if let Ok(Some(value)) = value.extract() { + config = config.with_key_annotation_metamap(value); } - "data_annotation_metamap" => { - if let Ok(Some(value)) = value.extract() { - config = config.with_data_annotation_metamap(value); - } + } + "data_annotation_metamap" => { + if let Ok(Some(value)) = value.extract() { + config = config.with_data_annotation_metamap(value); } - "generate_ids" => { - if let Ok(Some(value)) = value.extract() { - config = config.with_generate_ids(value); - } + } + "generate_ids" => { + if let Ok(Some(value)) = value.extract() { + config = config.with_generate_ids(value); } - "shrink_to_fit" => { - if let Ok(Some(value)) = value.extract() { - config = config.with_shrink_to_fit(value); - } + } + "shrink_to_fit" => { + if let Ok(Some(value)) = value.extract() { + config = config.with_shrink_to_fit(value); } - "strip_temp_ids" => { - if let Ok(Some(value)) = value.extract() { - config = config.with_strip_temp_ids(value); - } + } + "strip_temp_ids" => { + if let Ok(Some(value)) = value.extract() { + config = config.with_strip_temp_ids(value); } - "milestone_interval" => { - if let Ok(Some(value)) = value.extract() { - config = config.with_milestone_interval(value); - } + } + "milestone_interval" => { + if let Ok(Some(value)) = value.extract() { + config = config.with_milestone_interval(value); } - _ => eprintln!("Ignored unknown kwargs option {}", key), } + _ => eprintln!("Ignored unknown kwargs option {}", key), } } - config + Ok(config) } -pub fn get_alignmentconfig(kwargs: &PyDict) -> PyResult { +pub fn get_alignmentconfig(kwargs: &Bound<'_, PyDict>) -> PyResult { let mut alignmentconfig = AlignmentConfig::default(); for key in kwargs.keys() { let key: &str = key.extract()?; diff --git a/src/lib.rs b/src/lib.rs index a0b809f..a35cf2f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,8 +26,8 @@ use crate::textselection::{PyTextSelection, PyTextSelectionOperator, PyTextSelec const VERSION: &'static str = env!("CARGO_PKG_VERSION"); #[pymodule] -fn stam(py: Python<'_>, m: &PyModule) -> PyResult<()> { - m.add("StamError", py.get_type::())?; +fn stam(py: Python<'_>, m: Bound<'_, PyModule>) -> PyResult<()> { + m.add("StamError", py.get_type_bound::())?; m.add("VERSION", VERSION)?; m.add_class::()?; m.add_class::()?; diff --git a/src/query.rs b/src/query.rs index 2c8f3c0..fc66e50 100644 --- a/src/query.rs +++ b/src/query.rs @@ -29,7 +29,7 @@ fn new_contextvar(used_contextvarnames: &mut usize) -> &'static str { fn add_filter<'store, 'py, 'context>( query: &mut Query<'store>, store: &'store AnnotationStore, - filter: &'py PyAny, + filter: Bound<'py, PyAny>, operator: Option>, mut used_contextvarnames: usize, ) -> PyResult @@ -38,8 +38,8 @@ where 'context: 'store, { if filter.is_instance_of::() { - let filter: &PyDict = filter.extract()?; - let operator = dataoperator_from_kwargs(filter) + let filter = filter.downcast()?; + let operator = dataoperator_from_kwargs(&filter) .map_err(|err| PyValueError::new_err(format!("{}", err)))? .or(operator); if filter.contains("substore")? { @@ -156,10 +156,10 @@ where query.constrain(Constraint::Value(operator, SelectionQualifier::Normal)); } } else if filter.is_instance_of::() { - let vec: Vec<&PyAny> = filter.extract()?; + let vec: Vec> = filter.extract()?; used_contextvarnames = add_multi_filter(query, store, vec, used_contextvarnames)?; } else if filter.is_instance_of::() { - let vec: Vec<&PyAny> = filter.extract()?; + let vec: Vec> = filter.extract()?; used_contextvarnames = add_multi_filter(query, store, vec, used_contextvarnames)?; } else if filter.is_instance_of::() { let data: PyRef<'_, PyAnnotationData> = filter.extract()?; @@ -246,7 +246,7 @@ where fn add_multi_filter<'a>( query: &mut Query<'a>, store: &'a AnnotationStore, - filter: Vec<&'a PyAny>, + filter: Vec>, mut used_contextvarnames: usize, ) -> PyResult { if filter.iter().all(|x| x.is_instance_of::()) { @@ -277,7 +277,8 @@ fn add_multi_filter<'a>( )); } else { for item in filter.iter() { - used_contextvarnames = add_filter(query, store, item, None, used_contextvarnames)?; + used_contextvarnames = + add_filter(query, store, item.clone(), None, used_contextvarnames)?; } } Ok(used_contextvarnames) @@ -285,8 +286,8 @@ fn add_multi_filter<'a>( pub(crate) fn build_query<'store, 'py>( mut query: Query<'store>, - args: &'py PyTuple, - kwargs: Option<&'py PyDict>, + args: &Bound<'py, PyTuple>, + kwargs: &Option>, store: &'store AnnotationStore, ) -> PyResult> where @@ -343,7 +344,7 @@ where add_filter( &mut query, store, - kwargs.as_ref(), + kwargs.clone().into_any(), None, used_contextvarnames, )?; @@ -352,15 +353,26 @@ where Ok(query) } -pub(crate) fn has_filters(args: &PyTuple, kwargs: Option<&PyDict>) -> bool { +pub(crate) fn has_filters<'py>( + args: &Bound<'py, PyTuple>, + kwargs: &Option>, +) -> bool { if !args.is_empty() { return true; } if let Some(kwargs) = kwargs { for key in kwargs.keys() { - if let Ok(Some("limit")) | Ok(Some("recursive")) | Ok(Some("substore")) = key.extract() - { - continue; //this doesn't count as a filter + if key.is_instance_of::() { + let key: &str = key + .downcast() + .expect("downcast must work") + .extract() + .expect("extract must work"); + if let "limit" | "recursive" | "substore" = key { + continue; //this doesn't count as a filter + } else { + return true; + } } else { return true; } @@ -369,7 +381,10 @@ pub(crate) fn has_filters(args: &PyTuple, kwargs: Option<&PyDict>) -> bool { false } -pub(crate) fn get_recursive(kwargs: Option<&PyDict>, default: AnnotationDepth) -> AnnotationDepth { +pub(crate) fn get_recursive( + kwargs: &Option>, + default: AnnotationDepth, +) -> AnnotationDepth { if let Some(kwargs) = kwargs { if let Ok(Some(v)) = kwargs.get_item("recursive") { if let Ok(v) = v.extract::() { @@ -384,7 +399,7 @@ pub(crate) fn get_recursive(kwargs: Option<&PyDict>, default: AnnotationDepth) - default } -pub(crate) fn get_bool(kwargs: Option<&PyDict>, name: &str, default: bool) -> bool { +pub(crate) fn get_bool(kwargs: &Option>, name: &str, default: bool) -> bool { if let Some(kwargs) = kwargs { if let Ok(Some(v)) = kwargs.get_item(name) { if let Ok(v) = v.extract::() { @@ -396,7 +411,7 @@ pub(crate) fn get_bool(kwargs: Option<&PyDict>, name: &str, default: bool) -> bo } pub(crate) fn get_opt_string( - kwargs: Option<&PyDict>, + kwargs: &Option>, name: &str, default: Option<&str>, ) -> Option { @@ -410,7 +425,7 @@ pub(crate) fn get_opt_string( default.map(|s| s.to_string()) } -pub(crate) fn get_limit(kwargs: Option<&PyDict>) -> Option { +pub(crate) fn get_limit(kwargs: &Option>) -> Option { if let Some(kwargs) = kwargs { if let Ok(Some(limit)) = kwargs.get_item("limit") { if let Ok(limit) = limit.extract::() { @@ -421,7 +436,7 @@ pub(crate) fn get_limit(kwargs: Option<&PyDict>) -> Option { None } -pub(crate) fn get_debug(kwargs: Option<&PyDict>) -> bool { +pub(crate) fn get_debug(kwargs: &Option>) -> bool { if let Some(kwargs) = kwargs { if let Ok(Some(debug)) = kwargs.get_item("debug") { if let Ok(debug) = debug.extract::() { @@ -432,7 +447,7 @@ pub(crate) fn get_debug(kwargs: Option<&PyDict>) -> bool { false } -pub(crate) fn get_substore(kwargs: Option<&PyDict>) -> Option { +pub(crate) fn get_substore(kwargs: &Option>) -> Option { if let Some(kwargs) = kwargs { if let Ok(Some(substore)) = kwargs.get_item("substore") { if let Ok(substore) = substore.extract::() { @@ -484,10 +499,10 @@ pub(crate) fn query_to_python<'py>( iter: QueryIter, store: Arc>, py: Python<'py>, -) -> Result<&'py PyList, StamError> { - let results = PyList::empty(py); +) -> Result, StamError> { + let results = PyList::empty_bound(py); for resultitems in iter { - let dict = PyDict::new(py); + let dict = PyDict::new_bound(py); for (result, name) in resultitems.iter().zip(resultitems.names()) { if name.is_none() { continue; @@ -499,7 +514,7 @@ pub(crate) fn query_to_python<'py>( name, PyAnnotation::new(annotation.handle(), store.clone()) .into_py(py) - .into_ref(py), + .into_bound(py), ) .unwrap(); } @@ -508,7 +523,7 @@ pub(crate) fn query_to_python<'py>( name, PyAnnotationData::new(data.handle(), data.set().handle(), store.clone()) .into_py(py) - .into_ref(py), + .into_bound(py), ) .unwrap(); } @@ -517,7 +532,7 @@ pub(crate) fn query_to_python<'py>( name, PyDataKey::new(key.handle(), key.set().handle(), store.clone()) .into_py(py) - .into_ref(py), + .into_bound(py), ) .unwrap(); } @@ -526,7 +541,7 @@ pub(crate) fn query_to_python<'py>( name, PyTextResource::new(resource.handle(), store.clone()) .into_py(py) - .into_ref(py), + .into_bound(py), ) .unwrap(); } @@ -535,7 +550,7 @@ pub(crate) fn query_to_python<'py>( name, PyAnnotationDataSet::new(dataset.handle(), store.clone()) .into_py(py) - .into_ref(py), + .into_bound(py), ) .unwrap(); } @@ -551,7 +566,7 @@ pub(crate) fn query_to_python<'py>( store.clone(), ) .into_py(py) - .into_ref(py), + .into_bound(py), ) .unwrap(); } @@ -560,7 +575,7 @@ pub(crate) fn query_to_python<'py>( name, PyAnnotationSubStore::new(substore.handle(), store.clone()) .into_py(py) - .into_ref(py), + .into_bound(py), ) .unwrap(); } diff --git a/src/resources.rs b/src/resources.rs index 45d0bb9..bd01071 100644 --- a/src/resources.rs +++ b/src/resources.rs @@ -36,8 +36,8 @@ impl PyTextResource { handle: TextResourceHandle, store: Arc>, py: Python<'py>, - ) -> &'py PyAny { - Self::new(handle, store).into_py(py).into_ref(py) + ) -> Bound<'py, PyAny> { + Self::new(handle, store).into_py(py).into_bound(py) } } @@ -84,17 +84,21 @@ impl PyTextResource { } /// Returns the full text of the resource (by value, aka a copy) - fn __str__<'py>(&self, py: Python<'py>) -> PyResult<&'py PyString> { + fn __str__<'py>(&self, py: Python<'py>) -> PyResult> { self.text(py) } /// Returns a string (by value, aka copy) of a slice of the text - fn __getitem__<'py>(&self, slice: &PySlice, py: Python<'py>) -> PyResult<&'py PyString> { + fn __getitem__<'py>( + &self, + slice: Bound<'py, PySlice>, + py: Python<'py>, + ) -> PyResult> { self.map(|resource| { let slice = slice .indices(resource.textlen().try_into().unwrap()) .expect("expected valid slice"); - Ok(PyString::new( + Ok(PyString::new_bound( py, resource .text_by_offset(&Offset::simple(slice.start as usize, slice.stop as usize))?, @@ -103,8 +107,8 @@ impl PyTextResource { } /// 'Returns the full text of the resource (by value, aka a copy) - fn text<'py>(&self, py: Python<'py>) -> PyResult<&'py PyString> { - self.map(|resource| Ok(PyString::new(py, resource.text()))) + fn text<'py>(&self, py: Python<'py>) -> PyResult> { + self.map(|resource| Ok(PyString::new_bound(py, resource.text()))) } /// Returns the length of the resources's text in unicode points (same as `len(self.text())` but more performant) @@ -125,14 +129,15 @@ impl PyTextResource { } /// Searches for the text fragment and returns a list of [`TextSelection`] instances with all matches (or up to the specified limit) - fn find_text( + #[pyo3(signature=(fragment,limit=None,case_sensitive=None))] + fn find_text<'py>( &self, fragment: &str, limit: Option, case_sensitive: Option, - py: Python, - ) -> Py { - let list: &PyList = PyList::empty(py); + py: Python<'py>, + ) -> Bound<'py, PyList> { + let list = PyList::empty_bound(py); self.map(|res| { if case_sensitive == Some(false) { for (i, textselection) in res.find_text_nocase(fragment).enumerate() { @@ -166,17 +171,19 @@ impl PyTextResource { list.into() } - fn find_text_sequence( + #[pyo3(signature=(fragments,case_sensitive=None, allow_skip_whitespace=None, allow_skip_punctuation=None,allow_skip_numeric=None,allow_skip_alphabetic=None))] + fn find_text_sequence<'py>( &self, - fragments: Vec<&str>, + fragments: Vec, case_sensitive: Option, allow_skip_whitespace: Option, allow_skip_punctuation: Option, allow_skip_numeric: Option, allow_skip_alphabetic: Option, - py: Python, - ) -> Py { - let list: &PyList = PyList::empty(py); + py: Python<'py>, + ) -> Bound<'py, PyList> { + let fragments: Vec<&str> = fragments.iter().map(|s| s.as_str()).collect(); + let list = PyList::empty_bound(py); self.map(|res| { let results = res.find_text_sequence( &fragments, @@ -222,13 +229,14 @@ impl PyTextResource { /// false. All of this only matters if you supply multiple regular expressions. /// /// Results are returned in the exact order they are found in the text - fn find_text_regex( + #[pyo3(signature=(expressions,allow_overlap=None, limit=None))] + fn find_text_regex<'py>( &self, - expressions: &PyList, + expressions: Bound<'py, PyList>, allow_overlap: Option, limit: Option, - py: Python, - ) -> PyResult> { + py: Python<'py>, + ) -> PyResult> { //MAYBE TODO: there's room for performance improvement here probably let mut regexps: Vec = Vec::new(); for expression in expressions.iter() { @@ -241,13 +249,13 @@ impl PyTextResource { )) })?); } - let list: &PyList = PyList::empty(py); + let list = PyList::empty_bound(py); self.map(|res| { for (i, regexmatch) in res .find_text_regex(®exps, None, allow_overlap.unwrap_or(false))? .enumerate() { - let textselections: &PyList = PyList::empty(py); + let textselections = PyList::empty_bound(py); for textselection in regexmatch.textselections() { textselections .append(PyTextSelection::from_result_to_py( @@ -257,7 +265,7 @@ impl PyTextResource { )) .ok(); } - let dict: &PyDict = PyDict::new(py); + let dict = PyDict::new_bound(py); dict.set_item("textselections", textselections).unwrap(); dict.set_item("expression_index", regexmatch.expression_index()) .unwrap(); @@ -276,8 +284,14 @@ impl PyTextResource { /// Returns a list of [`TextSelection`] instances that split the text according to the specified delimiter. /// You can set `limit` to the max number of elements you want to return. - fn split_text(&self, delimiter: &str, limit: Option, py: Python) -> Py { - let list: &PyList = PyList::empty(py); + #[pyo3(signature=(delimiter,limit=None))] + fn split_text<'py>( + &self, + delimiter: &str, + limit: Option, + py: Python<'py>, + ) -> Bound<'py, PyList> { + let list = PyList::empty_bound(py); self.map(|res| { for (i, textselection) in res.split_text(delimiter).enumerate() { list.append(PyTextSelection::from_result_to_py( @@ -425,9 +439,13 @@ impl PyTextResource { /// Returns annotations that are referring to this resource via a TextSelector #[pyo3(signature = (*args, **kwargs))] - fn annotations(&self, args: &PyTuple, kwargs: Option<&PyDict>) -> PyResult { - let limit = get_limit(kwargs); - if !has_filters(args, kwargs) { + fn annotations<'py>( + &self, + args: Bound<'py, PyTuple>, + kwargs: Option>, + ) -> PyResult { + let limit = get_limit(&kwargs); + if !has_filters(&args, &kwargs) { self.map(|resource| { Ok(PyAnnotations::from_iter( resource.annotations().limit(limit), @@ -438,8 +456,8 @@ impl PyTextResource { self.map_with_query( Type::Annotation, Constraint::ResourceVariable("main", SelectionQualifier::Normal, None), - args, - kwargs, + &args, + &kwargs, |annotation, query| { PyAnnotations::from_query(query, annotation.store(), &self.store, limit) }, @@ -448,13 +466,13 @@ impl PyTextResource { } #[pyo3(signature = (*args, **kwargs))] - fn annotations_as_metadata( + fn annotations_as_metadata<'py>( &self, - args: &PyTuple, - kwargs: Option<&PyDict>, + args: Bound<'py, PyTuple>, + kwargs: Option>, ) -> PyResult { - let limit = get_limit(kwargs); - if !has_filters(args, kwargs) { + let limit = get_limit(&kwargs); + if !has_filters(&args, &kwargs) { self.map(|resource| { Ok(PyAnnotations::from_iter( resource.annotations_as_metadata().limit(limit), @@ -465,8 +483,8 @@ impl PyTextResource { self.map_with_query( Type::Annotation, Constraint::ResourceVariable("main", SelectionQualifier::Metadata, None), - args, - kwargs, + &args, + &kwargs, |annotation, query| { PyAnnotations::from_query(query, annotation.store(), &self.store, limit) }, @@ -475,34 +493,38 @@ impl PyTextResource { } #[pyo3(signature = (*args, **kwargs))] - fn test_annotations(&self, args: &PyTuple, kwargs: Option<&PyDict>) -> PyResult { - if !has_filters(args, kwargs) { + fn test_annotations<'py>( + &self, + args: Bound<'py, PyTuple>, + kwargs: Option>, + ) -> PyResult { + if !has_filters(&args, &kwargs) { self.map(|resource| Ok(resource.annotations().test())) } else { self.map_with_query( Type::Annotation, Constraint::ResourceVariable("main", SelectionQualifier::Normal, None), - args, - kwargs, + &args, + &kwargs, |resource, query| Ok(resource.store().query(query)?.test()), ) } } #[pyo3(signature = (*args, **kwargs))] - fn test_annotations_as_metadata( + fn test_annotations_as_metadata<'py>( &self, - args: &PyTuple, - kwargs: Option<&PyDict>, + args: Bound<'py, PyTuple>, + kwargs: Option>, ) -> PyResult { - if !has_filters(args, kwargs) { + if !has_filters(&args, &kwargs) { self.map(|resource| Ok(resource.annotations_as_metadata().test())) } else { self.map_with_query( Type::Annotation, Constraint::ResourceVariable("main", SelectionQualifier::Metadata, None), - args, - kwargs, + &args, + &kwargs, |resource, query| Ok(resource.store().query(query)?.test()), ) } @@ -530,7 +552,7 @@ impl PyTextResource { &self, operator: PyTextSelectionOperator, referenceselections: Vec, - kwargs: Option<&PyDict>, + kwargs: Option>, ) -> PyResult { self.map(|textselection| { let mut refset = TextSelectionSet::new(self.handle); @@ -577,12 +599,12 @@ impl PyTextResource { } } - fn map_with_query( + pub(crate) fn map_with_query<'py, T, F>( &self, resulttype: Type, constraint: Constraint, - args: &PyTuple, - kwargs: Option<&PyDict>, + args: &Bound<'py, PyTuple>, + kwargs: &Option>, f: F, ) -> Result where @@ -626,7 +648,7 @@ pub(crate) struct PyCursor { #[pymethods] impl PyCursor { #[new] - #[pyo3(text_signature = "(self, index, endaliged=None)")] + #[pyo3(signature = (index, endaligned=None))] fn new(index: isize, endaligned: Option) -> PyResult { if endaligned.unwrap_or(false) { if index <= 0 { diff --git a/src/selector.rs b/src/selector.rs index 4bbd430..b16d121 100644 --- a/src/selector.rs +++ b/src/selector.rs @@ -299,6 +299,7 @@ impl PySelector { #[staticmethod] /// Shortcut static method to construct a AnnotationSelector + #[pyo3(signature = (annotation, offset=None))] fn annotationselector( annotation: PyRef, offset: Option>, diff --git a/src/substore.rs b/src/substore.rs index bce14d0..f6b2b0e 100644 --- a/src/substore.rs +++ b/src/substore.rs @@ -31,12 +31,13 @@ impl PyAnnotationSubStore { Self { handle, store } } + #[allow(dead_code)] pub(crate) fn new_py<'py>( handle: AnnotationSubStoreHandle, store: Arc>, py: Python<'py>, - ) -> &'py PyAny { - Self::new(handle, store).into_py(py).into_ref(py) + ) -> Bound<'py, PyAny> { + Self::new(handle, store).into_py(py).into_bound(py) } } @@ -82,7 +83,7 @@ impl PyAnnotationSubStore { self.handle.as_usize() } - fn associate(&mut self, item: &PyAny) -> PyResult<()> { + fn associate<'py>(&mut self, item: Bound<'py, PyAny>) -> PyResult<()> { if item.is_instance_of::() { let item: PyRef = item.extract()?; let substore_handle = self.handle; diff --git a/src/textselection.rs b/src/textselection.rs index eb66d47..3b6a896 100644 --- a/src/textselection.rs +++ b/src/textselection.rs @@ -59,29 +59,33 @@ impl PyTextSelection { result: ResultTextSelection<'_>, store: &Arc>, py: Python<'py>, - ) -> &'py PyAny { - Self::from_result(result, store).into_py(py).into_ref(py) + ) -> Bound<'py, PyAny> { + Self::from_result(result, store).into_py(py).into_bound(py) } } #[pymethods] impl PyTextSelection { /// Resolves a text selection to the actual underlying text - fn text<'py>(&self, py: Python<'py>) -> PyResult<&'py PyString> { - self.map(|textselection| Ok(PyString::new(py, textselection.text()))) + fn text<'py>(&self, py: Python<'py>) -> PyResult> { + self.map(|textselection| Ok(PyString::new_bound(py, textselection.text()))) } - fn __str__<'py>(&self, py: Python<'py>) -> PyResult<&'py PyString> { + fn __str__<'py>(&self, py: Python<'py>) -> PyResult> { self.text(py) } /// Returns a string (by value, aka copy) of a slice of the text - fn __getitem__<'py>(&self, slice: &PySlice, py: Python<'py>) -> PyResult<&'py PyString> { + fn __getitem__<'py>( + &self, + slice: Bound<'py, PySlice>, + py: Python<'py>, + ) -> PyResult> { self.map(|textselection| { let slice = slice .indices(textselection.textlen().try_into().unwrap()) .expect("expected valid slice"); - Ok(PyString::new( + Ok(PyString::new_bound( py, textselection .text_by_offset(&Offset::simple(slice.start as usize, slice.stop as usize))?, @@ -149,14 +153,15 @@ impl PyTextSelection { } /// Searches for the text fragment and returns a list of [`TextSelection`] instances with all matches (or up to the specified limit) - fn find_text( + #[pyo3(signature = (fragment,limit=None,case_sensitive=None))] + fn find_text<'py>( &self, fragment: &str, limit: Option, case_sensitive: Option, - py: Python, - ) -> Py { - let list: &PyList = PyList::empty(py); + py: Python<'py>, + ) -> Bound<'py, PyList> { + let list = PyList::empty_bound(py); self.map(|textselection| { if case_sensitive == Some(false) { for (i, textselection) in textselection.find_text_nocase(fragment).enumerate() { @@ -190,20 +195,21 @@ impl PyTextSelection { list.into() } - fn find_text_sequence( + #[pyo3(signature = (fragments,case_sensitive=None, allow_skip_whitespace=None, allow_skip_punctuation=None, allow_skip_numeric=None, allow_skip_alphabetic=None))] + fn find_text_sequence<'py>( &self, - fragments: Vec<&str>, + fragments: Vec, case_sensitive: Option, allow_skip_whitespace: Option, allow_skip_punctuation: Option, allow_skip_numeric: Option, allow_skip_alphabetic: Option, - py: Python, - ) -> Py { - let list: &PyList = PyList::empty(py); + py: Python<'py>, + ) -> Bound<'py, PyList> { + let list: Bound<'py, PyList> = PyList::empty_bound(py); self.map(|textselection| { let results = textselection.find_text_sequence( - &fragments, + &fragments.iter().map(|s| s.as_str()).collect::>(), |c| { if (allow_skip_whitespace == Some(false) && c.is_whitespace()) || (allow_skip_punctuation == Some(false) && c.is_ascii_punctuation()) @@ -235,8 +241,9 @@ impl PyTextSelection { /// Returns a tuple of [`TextSelection`] instances that split the text according to the specified delimiter. /// You can set `limit` to the max number of elements you want to return. + #[pyo3(signature = (delimiter,limit=None))] fn split_text(&self, delimiter: &str, limit: Option, py: Python) -> Py { - let list: &PyList = PyList::empty(py); + let list = PyList::empty_bound(py); self.map(|textselection| { for (i, textselection) in textselection.split_text(delimiter).enumerate() { list.append(PyTextSelection::from_result_to_py( @@ -305,9 +312,13 @@ impl PyTextSelection { } #[pyo3(signature = (*args, **kwargs))] - fn annotations(&self, args: &PyTuple, kwargs: Option<&PyDict>) -> PyResult { - let limit = get_limit(kwargs); - if !has_filters(args, kwargs) { + fn annotations<'py>( + &self, + args: Bound<'py, PyTuple>, + kwargs: Option>, + ) -> PyResult { + let limit = get_limit(&kwargs); + if !has_filters(&args, &kwargs) { self.map(|textselection| { Ok(PyAnnotations::from_iter( textselection.annotations().limit(limit), @@ -318,8 +329,8 @@ impl PyTextSelection { self.map_with_query( Type::Annotation, Constraint::TextVariable("main"), - args, - kwargs, + &args, + &kwargs, |textselection, query| { PyAnnotations::from_query(query, textselection.rootstore(), &self.store, limit) }, @@ -328,44 +339,52 @@ impl PyTextSelection { } #[pyo3(signature = (*args, **kwargs))] - fn test_annotations(&self, args: &PyTuple, kwargs: Option<&PyDict>) -> PyResult { - if !has_filters(args, kwargs) { + fn test_annotations<'py>( + &self, + args: Bound<'py, PyTuple>, + kwargs: Option>, + ) -> PyResult { + if !has_filters(&args, &kwargs) { self.map(|textselection| Ok(textselection.annotations().test())) } else { self.map_with_query( Type::Annotation, Constraint::TextVariable("main"), - args, - kwargs, + &args, + &kwargs, |textselection, query| Ok(textselection.rootstore().query(query)?.test()), ) } } #[pyo3(signature = (*args, **kwargs))] - fn test_data(&self, args: &PyTuple, kwargs: Option<&PyDict>) -> PyResult { - if !has_filters(args, kwargs) { + fn test_data<'py>( + &self, + args: Bound<'py, PyTuple>, + kwargs: Option>, + ) -> PyResult { + if !has_filters(&args, &kwargs) { self.map(|textselection| Ok(textselection.annotations().data().test())) } else { self.map_with_query( Type::AnnotationData, Constraint::TextVariable("main"), - args, - kwargs, + &args, + &kwargs, |textselection, query| Ok(textselection.rootstore().query(query)?.test()), ) } } #[pyo3(signature = (operator, *args, **kwargs))] - fn related_text( + fn related_text<'py>( &self, operator: PyTextSelectionOperator, - args: &PyTuple, - kwargs: Option<&PyDict>, + args: Bound<'py, PyTuple>, + kwargs: Option>, ) -> PyResult { - let limit = get_limit(kwargs); - if !has_filters(args, kwargs) { + let limit = get_limit(&kwargs); + if !has_filters(&args, &kwargs) { self.map(|textselection| { Ok(PyTextSelections::from_iter( textselection.related_text(operator.operator).limit(limit), @@ -379,8 +398,8 @@ impl PyTextSelection { var: "main", operator: operator.operator, //MAYBE TODO: check if we need to invert an operator here? }, - args, - kwargs, + &args, + &kwargs, |textselection, query| { PyTextSelections::from_query( query, @@ -517,13 +536,13 @@ impl PyTextSelection { } #[pyo3(signature = (other, **kwargs))] - fn align_texts( + fn align_texts<'py>( &mut self, other: PyTextSelection, - kwargs: Option<&PyDict>, + kwargs: Option>, ) -> PyResult> { let alignmentconfig = if let Some(kwargs) = kwargs { - get_alignmentconfig(kwargs)? + get_alignmentconfig(&kwargs)? } else { AlignmentConfig::default() }; @@ -596,12 +615,12 @@ impl PyTextSelection { } } - fn map_with_query( + pub(crate) fn map_with_query<'py, T, F>( &self, resulttype: Type, constraint: Constraint, - args: &PyTuple, - kwargs: Option<&PyDict>, + args: &Bound<'py, PyTuple>, + kwargs: &Option>, f: F, ) -> Result where @@ -713,9 +732,13 @@ impl PyTextSelections { } #[pyo3(signature = (*args, **kwargs))] - fn annotations(&self, args: &PyTuple, kwargs: Option<&PyDict>) -> PyResult { - let limit = get_limit(kwargs); - if !has_filters(args, kwargs) { + fn annotations<'py>( + &self, + args: Bound<'py, PyTuple>, + kwargs: Option>, + ) -> PyResult { + let limit = get_limit(&kwargs); + if !has_filters(&args, &kwargs) { self.map(|textselections, _store| { Ok(PyAnnotations::from_iter( textselections @@ -730,16 +753,20 @@ impl PyTextSelections { self.map_with_query( Type::Annotation, Constraint::TextVariable("main"), - args, - kwargs, + &args, + &kwargs, |query, store| PyAnnotations::from_query(query, store, &self.store, limit), ) } } #[pyo3(signature = (*args, **kwargs))] - fn test_annotations(&self, args: &PyTuple, kwargs: Option<&PyDict>) -> PyResult { - if !has_filters(args, kwargs) { + fn test_annotations<'py>( + &self, + args: Bound<'py, PyTuple>, + kwargs: Option>, + ) -> PyResult { + if !has_filters(&args, &kwargs) { self.map(|annotations, _| { Ok(annotations .items() @@ -751,17 +778,21 @@ impl PyTextSelections { self.map_with_query( Type::Annotation, Constraint::TextVariable("main"), - args, - kwargs, + &args, + &kwargs, |query, store| Ok(store.query(query)?.test()), ) } } #[pyo3(signature = (*args, **kwargs))] - fn data(&self, args: &PyTuple, kwargs: Option<&PyDict>) -> PyResult { - let limit = get_limit(kwargs); - if !has_filters(args, kwargs) { + fn data<'py>( + &self, + args: Bound<'py, PyTuple>, + kwargs: Option>, + ) -> PyResult { + let limit = get_limit(&kwargs); + if !has_filters(&args, &kwargs) { self.map(|textselections, _store| { Ok(PyData::from_iter( textselections @@ -777,16 +808,20 @@ impl PyTextSelections { self.map_with_query( Type::AnnotationData, Constraint::TextVariable("main"), - args, - kwargs, + &args, + &kwargs, |query, store| PyData::from_query(query, store, &self.store, limit), ) } } #[pyo3(signature = (*args, **kwargs))] - fn test_data(&self, args: &PyTuple, kwargs: Option<&PyDict>) -> PyResult { - if !has_filters(args, kwargs) { + fn test_data<'py>( + &self, + args: Bound<'py, PyTuple>, + kwargs: Option>, + ) -> PyResult { + if !has_filters(&args, &kwargs) { self.map(|textselections, _| { Ok(textselections .items() @@ -799,22 +834,22 @@ impl PyTextSelections { self.map_with_query( Type::AnnotationData, Constraint::TextVariable("main"), - args, - kwargs, + &args, + &kwargs, |query, store| Ok(store.query(query)?.test()), ) } } #[pyo3(signature = (operator, *args, **kwargs))] - fn related_text( + fn related_text<'py>( &self, operator: PyTextSelectionOperator, - args: &PyTuple, - kwargs: Option<&PyDict>, + args: Bound<'py, PyTuple>, + kwargs: Option>, ) -> PyResult { - let limit = get_limit(kwargs); - if !has_filters(args, kwargs) { + let limit = get_limit(&kwargs); + if !has_filters(&args, &kwargs) { self.map(|textselections, _store| { Ok(PyTextSelections::from_iter( textselections @@ -832,8 +867,8 @@ impl PyTextSelections { var: "main", operator: operator.operator, //MAYBE TODO: check if we need to invert an operator here? }, - args, - kwargs, + &args, + &kwargs, |query, store| PyTextSelections::from_query(query, store, &self.store, limit), ) } @@ -945,12 +980,12 @@ impl PyTextSelections { } } - fn map_with_query( + pub(crate) fn map_with_query<'py, T, F>( &self, resulttype: Type, constraint: Constraint, - args: &PyTuple, - kwargs: Option<&PyDict>, + args: &Bound<'py, PyTuple>, + kwargs: &Option>, f: F, ) -> Result where @@ -1060,6 +1095,7 @@ pub(crate) struct PyTextSelectionOperator { impl PyTextSelectionOperator { #[staticmethod] /// Create an operator to test if two textselection(sets) occupy cover the exact same TextSelections, and all are covered (cf. textfabric's `==`), commutative, transitive + #[pyo3(signature = (all=None,negate=None))] fn equals(all: Option, negate: Option) -> PyResult { Ok(Self { operator: TextSelectionOperator::Equals { @@ -1073,6 +1109,7 @@ impl PyTextSelectionOperator { /// Create an operator to test if two textselection(sets) overlap. /// Each TextSelection in A overlaps with a TextSelection in B (cf. textfabric's `&&`), commutative /// If modifier `all` is set: Each TextSelection in A overlaps with all TextSelection in B (cf. textfabric's `&&`), commutative + #[pyo3(signature = (all=None,negate=None))] fn overlaps(all: Option, negate: Option) -> PyResult { Ok(Self { operator: TextSelectionOperator::Overlaps { @@ -1086,6 +1123,7 @@ impl PyTextSelectionOperator { /// Create an operator to test if two textselection(sets) are embedded. /// All TextSelections in B are embedded by a TextSelection in A (cf. textfabric's `[[`) /// If modifier `all` is set: All TextSelections in B are embedded by all TextSelection in A (cf. textfabric's `[[`) + #[pyo3(signature = (all=None,negate=None))] fn embeds(all: Option, negate: Option) -> PyResult { Ok(Self { operator: TextSelectionOperator::Embeds { @@ -1099,6 +1137,7 @@ impl PyTextSelectionOperator { /// Create an operator to test if two textselection(sets) are embedded. /// All TextSelections in B are embedded by a TextSelection in A (cf. textfabric's `[[`) /// If modifier `all` is set: All TextSelections in B are embedded by all TextSelection in A (cf. textfabric's `[[`) + #[pyo3(signature = (all=None,negate=None, limit=None))] fn embedded(all: Option, negate: Option, limit: Option) -> PyResult { Ok(Self { operator: TextSelectionOperator::Embedded { @@ -1110,6 +1149,7 @@ impl PyTextSelectionOperator { } #[staticmethod] + #[pyo3(signature = (all=None,negate=None, limit=None))] fn before(all: Option, negate: Option, limit: Option) -> PyResult { Ok(Self { operator: TextSelectionOperator::Before { @@ -1121,6 +1161,7 @@ impl PyTextSelectionOperator { } #[staticmethod] + #[pyo3(signature = (all=None,negate=None, limit=None))] fn after(all: Option, negate: Option, limit: Option) -> PyResult { Ok(Self { operator: TextSelectionOperator::After { @@ -1132,6 +1173,7 @@ impl PyTextSelectionOperator { } #[staticmethod] + #[pyo3(signature = (all=None,negate=None, allow_whitespace=None))] fn precedes( all: Option, negate: Option, @@ -1147,6 +1189,7 @@ impl PyTextSelectionOperator { } #[staticmethod] + #[pyo3(signature = (all=None,negate=None, allow_whitespace=None))] fn succeeds( all: Option, negate: Option, @@ -1165,6 +1208,7 @@ impl PyTextSelectionOperator { /// Create an operator to test if two textselection(sets) have the same begin position /// Each TextSelection in A starts where a TextSelection in B starts /// If modifier `all` is set: The leftmost TextSelection in A starts where the leftmost TextSelection in B start (cf. textfabric's `=:`) + #[pyo3(signature = (all=None,negate=None))] fn samebegin(all: Option, negate: Option) -> PyResult { Ok(Self { operator: TextSelectionOperator::SameBegin { @@ -1178,6 +1222,7 @@ impl PyTextSelectionOperator { /// Create an operator to test if two textselection(sets) have the same end position /// Each TextSelection in A ends where a TextSelection in B ends /// If modifier `all` is set: The rightmost TextSelection in A ends where the rights TextSelection in B ends (cf. textfabric's `:=`) + #[pyo3(signature = (all=None,negate=None))] fn sameend(all: Option, negate: Option) -> PyResult { Ok(Self { operator: TextSelectionOperator::SameEnd { diff --git a/test.py b/test.py index 228cdd9..a499c89 100644 --- a/test.py +++ b/test.py @@ -840,7 +840,7 @@ def setUp(self): def test_align1(self): align1 = self.store.resource("align1").textselection(Offset.whole()) localalign1 = self.store.resource("localalign1").textselection(Offset.whole()) - transpositions = localalign1.align_text(align1, algorithm="local") + transpositions = localalign1.align_texts(align1, algorithm="local") count = 0 for transposition in transpositions: count += 1 @@ -853,7 +853,7 @@ def test_align1(self): def test_align2(self): align1 = self.store.resource("align1").textselection(Offset.whole()) localalign2 = self.store.resource("localalign2").textselection(Offset.whole()) - transpositions = localalign2.align_text(align1, algorithm="local") + transpositions = localalign2.align_texts(align1, algorithm="local") count = 0 for transposition in transpositions: count += 1 @@ -868,7 +868,7 @@ def test_align2(self): def test_align3(self): align2 = self.store.resource("align2").textselection(Offset.whole()) localalign2 = self.store.resource("localalign2").textselection(Offset.whole()) - transpositions = localalign2.align_text(align2, algorithm="global") + transpositions = localalign2.align_texts(align2, algorithm="global") count = 0 for transposition in transpositions: count += 1