Skip to content

Commit

Permalink
improve API ergonomics
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielT committed Aug 29, 2023
1 parent c6309fd commit 01cdafe
Show file tree
Hide file tree
Showing 11 changed files with 493 additions and 198 deletions.
9 changes: 3 additions & 6 deletions autosar_data.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,13 @@ class AutosarModel:
"""
def __repr__(self) -> str: ...
def __str__(self) -> str: ...
def create_file(self, filename: str, version: AutosarVersion) -> ArxmlFile:
def create_file(self, filename: str, version: AutosarVersion = AutosarVersion.LATEST) -> ArxmlFile:
"""create a new file in the model"""
...
def load_buffer(self, buffer: str, filename: str, strict: bool) -> Tuple[ArxmlFile, List[str]]:
def load_buffer(self, buffer: str, filename: str, strict: bool = False) -> Tuple[ArxmlFile, List[str]]:
"""load a buffer (string) as arxml"""
...
def load_file(self, filename: str, strict: bool) -> Tuple[ArxmlFile, List[str]]:
def load_file(self, filename: str, strict: bool = False) -> Tuple[ArxmlFile, List[str]]:
"""load a file as arxml"""
...
def remove_file(self, arxmlfile: ArxmlFile) -> None:
Expand Down Expand Up @@ -253,9 +253,6 @@ class Element:
def set_attribute(self, attrname: AttributeName, chardata: CharacterData) -> None:
"""set the given attribute to the provided value. If the attribute is valid for this element it will be created or modified as needed."""
...
def set_attribute_string(self, attrname: AttributeName, value: str) -> None:
"""like set_attribute, but the input will be converted to the target data type as needed"""
...
def remove_attribute(self, attrname: AttributeName) -> None:
"""remove an attribute from the element"""
...
Expand Down
29 changes: 19 additions & 10 deletions src/arxmlfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ impl ArxmlFile {
self.serialize()
}

fn __richcmp__(&self, other: &ArxmlFile, op: pyo3::basic::CompareOp) -> bool {
fn __richcmp__(&self, other: &ArxmlFile, op: pyo3::basic::CompareOp) -> PyResult<bool> {
match op {
pyo3::pyclass::CompareOp::Eq => self.0 == other.0,
pyo3::pyclass::CompareOp::Ne => self.0 != other.0,
pyo3::pyclass::CompareOp::Lt
| pyo3::pyclass::CompareOp::Le
| pyo3::pyclass::CompareOp::Gt
| pyo3::pyclass::CompareOp::Ge => false,
pyo3::pyclass::CompareOp::Eq => Ok(self.0 == other.0),
pyo3::pyclass::CompareOp::Ne => Ok(self.0 != other.0),
pyo3::pyclass::CompareOp::Lt => Err(pyo3::exceptions::PyTypeError::new_err("'<' is not supported between instances of 'builtins.Element' and 'builtins.Element'")),
pyo3::pyclass::CompareOp::Le => Err(pyo3::exceptions::PyTypeError::new_err("'<=' is not supported between instances of 'builtins.Element' and 'builtins.Element'")),
pyo3::pyclass::CompareOp::Gt => Err(pyo3::exceptions::PyTypeError::new_err("'>' is not supported between instances of 'builtins.Element' and 'builtins.Element'")),
pyo3::pyclass::CompareOp::Ge => Err(pyo3::exceptions::PyTypeError::new_err("'>=' is not supported between instances of 'builtins.Element' and 'builtins.Element'")),
}
}

Expand Down Expand Up @@ -75,7 +75,10 @@ impl ArxmlFile {
IncompatibleAttributeError {
element: Element(element.to_owned()),
attribute: attribute.to_string(),
allowed_versions: expand_version_mask(*version_mask).iter().map(|&v| v.into()).collect(),
allowed_versions: expand_version_mask(*version_mask)
.iter()
.map(|&v| v.into())
.collect(),
target_version,
},
)
Expand All @@ -92,7 +95,10 @@ impl ArxmlFile {
element: Element(element.to_owned()),
attribute: attribute.to_string(),
attribute_value: attribute_value.to_owned(),
allowed_versions: expand_version_mask(*version_mask).iter().map(|&v| v.into()).collect(),
allowed_versions: expand_version_mask(*version_mask)
.iter()
.map(|&v| v.into())
.collect(),
target_version,
},
)
Expand All @@ -105,7 +111,10 @@ impl ArxmlFile {
py,
IncompatibleElementError {
element: Element(element.to_owned()),
allowed_versions: expand_version_mask(*version_mask).iter().map(|&v| v.into()).collect(),
allowed_versions: expand_version_mask(*version_mask)
.iter()
.map(|&v| v.into())
.collect(),
target_version,
},
)
Expand Down
34 changes: 13 additions & 21 deletions src/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ impl Element {
self.0.serialize()
}

fn __richcmp__(&self, other: &Element, op: pyo3::basic::CompareOp) -> bool {
fn __richcmp__(&self, other: &Element, op: pyo3::basic::CompareOp) -> PyResult<bool> {
match op {
pyo3::pyclass::CompareOp::Eq => self.0 == other.0,
pyo3::pyclass::CompareOp::Ne => self.0 != other.0,
pyo3::pyclass::CompareOp::Lt
| pyo3::pyclass::CompareOp::Le
| pyo3::pyclass::CompareOp::Gt
| pyo3::pyclass::CompareOp::Ge => false,
pyo3::pyclass::CompareOp::Eq => Ok(self.0 == other.0),
pyo3::pyclass::CompareOp::Ne => Ok(self.0 != other.0),
pyo3::pyclass::CompareOp::Lt => Err(pyo3::exceptions::PyTypeError::new_err("'<' is not supported between instances of 'builtins.Element' and 'builtins.Element'")),
pyo3::pyclass::CompareOp::Le => Err(pyo3::exceptions::PyTypeError::new_err("'<=' is not supported between instances of 'builtins.Element' and 'builtins.Element'")),
pyo3::pyclass::CompareOp::Gt => Err(pyo3::exceptions::PyTypeError::new_err("'>' is not supported between instances of 'builtins.Element' and 'builtins.Element'")),
pyo3::pyclass::CompareOp::Ge => Err(pyo3::exceptions::PyTypeError::new_err("'>=' is not supported between instances of 'builtins.Element' and 'builtins.Element'")),
}
}

Expand Down Expand Up @@ -251,10 +251,9 @@ impl Element {
}

fn remove_character_content_item(&self, position: usize) -> PyResult<()> {
match self.0.remove_character_content_item(position) {
Ok(()) => Ok(()),
Err(error) => Err(AutosarDataError::new_err(error.to_string())),
}
self.0
.remove_character_content_item(position)
.map_err(|error| AutosarDataError::new_err(error.to_string()))
}

#[getter]
Expand All @@ -272,19 +271,19 @@ impl Element {
AttributeIterator(self.0.attributes())
}

fn attribute_value(&self, attrname_str: String) -> PyResult<Option<PyObject>> {
pub(crate) fn attribute_value(&self, attrname_str: String) -> PyResult<Option<PyObject>> {
let attrname = get_attribute_name(attrname_str)?;
Ok(self
.0
.attribute_value(attrname)
.map(|cdata| character_data_to_object(&cdata)))
}

fn set_attribute(&self, attrname_str: String, value: PyObject) -> PyResult<()> {
pub(crate) fn set_attribute(&self, attrname_str: String, value: PyObject) -> PyResult<()> {
let attrname = get_attribute_name(attrname_str)?;
let attrspec = self.0.element_type().find_attribute_spec(attrname).ok_or(
AutosarDataError::new_err(
autosar_data_rs::AutosarDataError::IncorrectContentType.to_string(),
autosar_data_rs::AutosarDataError::InvalidAttribute.to_string(),
),
)?;
let cdata = extract_character_data(attrspec.spec, value)?;
Expand All @@ -293,13 +292,6 @@ impl Element {
.map_err(|error| AutosarDataError::new_err(error.to_string()))
}

fn set_attribute_string(&self, attrname_str: String, text: &str) -> PyResult<()> {
let attrname = get_attribute_name(attrname_str)?;
self.0
.set_attribute_string(attrname, text)
.map_err(|error| AutosarDataError::new_err(error.to_string()))
}

fn remove_attribute(&self, attrname_str: String) -> PyResult<bool> {
let attrname = get_attribute_name(attrname_str)?;
Ok(self.0.remove_attribute(attrname))
Expand Down
157 changes: 80 additions & 77 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::str::FromStr;

use ::autosar_data as autosar_data_rs;
use autosar_data_rs::CharacterData;
use autosar_data_specification::expand_version_mask;
use autosar_data_specification::CharacterDataSpec;
use pyo3::create_exception;
Expand Down Expand Up @@ -91,7 +92,9 @@ struct ElementType(autosar_data_specification::ElementType);
#[pyclass(frozen)]
/// An attribute on an element
struct Attribute {
#[pyo3(get)]
pub attrname: String,
#[pyo3(get)]
pub content: PyObject,
}

Expand Down Expand Up @@ -355,16 +358,6 @@ impl Attribute {
fn __str__(&self) -> String {
format!("Attribute({}=\"{}\")", self.attrname, self.content)
}

#[getter]
fn attrname(&self) -> String {
self.attrname.to_string()
}

#[getter]
fn content(&self) -> &PyObject {
&self.content
}
}

#[pymethods]
Expand All @@ -376,18 +369,18 @@ impl ValidSubElementInfo {

/// Provides functionality to read, modify and write Autosar arxml files,
/// both separately and in projects consisting of multiple files.
///
///
/// Classes:
///
///
/// - ArxmlFile
/// - AutosarModel
/// - AutosarVersion
/// - Element
/// - ElementType
/// - ValidSubElementInfo
///
///
/// Variables:
///
///
/// - __version__
#[pymodule]
fn autosar_data(py: Python, m: &PyModule) -> PyResult<()> {
Expand All @@ -414,73 +407,81 @@ fn autosar_data(py: Python, m: &PyModule) -> PyResult<()> {

fn extract_character_data(
spec: &CharacterDataSpec,
any: PyObject,
object: PyObject,
) -> PyResult<autosar_data_rs::CharacterData> {
Python::with_gil(|py| {
if let Ok(text) = any.extract::<String>(py) {
parse_cdata_string(spec, text).map_err(|_| {
AutosarDataError::new_err(
autosar_data_rs::AutosarDataError::IncorrectContentType.to_string(),
)
})
} else if let Ok(val) = any.extract::<u64>(py) {
Ok(autosar_data_rs::CharacterData::UnsignedInteger(val))
} else if let Ok(val) = any.extract::<f64>(py) {
Ok(autosar_data_rs::CharacterData::Double(val))
} else {
Err(AutosarDataError::new_err(
autosar_data_rs::AutosarDataError::IncorrectContentType.to_string(),
))
}
})
}

fn parse_cdata_string(
spec: &CharacterDataSpec,
text: String,
) -> Result<autosar_data_rs::CharacterData, ()> {
match spec {
CharacterDataSpec::Enum { items } => {
let enumitem = autosar_data_rs::EnumItem::from_str(&text).map_err(|_| ())?;
if items.iter().any(|(spec_item, _)| *spec_item == enumitem) {
Ok(autosar_data_rs::CharacterData::Enum(enumitem))
} else {
Err(())
let any: &PyAny = object.as_ref(py);
match spec {
CharacterDataSpec::Enum { .. } => {
if let Ok(strval) = any.extract::<String>() {
if let Ok(enumitem) = autosar_data_rs::EnumItem::from_str(&strval) {
Ok(CharacterData::Enum(enumitem))
} else {
Err(pyo3::exceptions::PyValueError::new_err(format!(
"string value '{strval}' cannot be converted to 'EnumItem'"
)))
}
} else {
Err(pyo3::exceptions::PyTypeError::new_err(format!(
"'{}' cannot be converted to 'EnumItem'",
any.get_type()
)))
}
}
}
CharacterDataSpec::Pattern {
check_fn,
max_length,
..
} => {
if text.len() < max_length.unwrap_or(usize::MAX) && check_fn(text.as_bytes()) {
Ok(autosar_data_rs::CharacterData::String(text))
} else {
Err(())
CharacterDataSpec::Pattern { .. } | CharacterDataSpec::String { .. } => {
if let Ok(text) = any.extract::<String>() {
Ok(CharacterData::String(text))
} else if let Ok(intval) = any.extract::<u64>() {
Ok(CharacterData::String(intval.to_string()))
} else if let Ok(floatval) = any.extract::<f64>() {
Ok(CharacterData::String(floatval.to_string()))
} else {
Err(pyo3::exceptions::PyTypeError::new_err(format!(
"'{}' cannot be converted to 'str'",
any.get_type()
)))
}
}
}
CharacterDataSpec::String { max_length, .. } => {
if text.len() < max_length.unwrap_or(usize::MAX) {
Ok(autosar_data_rs::CharacterData::String(text))
} else {
Err(())
CharacterDataSpec::UnsignedInteger => {
if let Ok(strval) = any.extract::<String>() {
if let Ok(intval) = strval.parse() {
Ok(CharacterData::UnsignedInteger(intval))
} else {
Err(pyo3::exceptions::PyValueError::new_err(format!(
"invalid literal '{strval}' for conversion to int"
)))
}
} else if let Ok(intval) = any.extract::<u64>() {
Ok(CharacterData::UnsignedInteger(intval))
} else {
Err(pyo3::exceptions::PyTypeError::new_err(format!(
"'{}' cannot be converted to 'int'",
any.get_type()
)))
}
}
}
CharacterDataSpec::UnsignedInteger => {
if let Ok(val) = text.parse() {
Ok(autosar_data_rs::CharacterData::UnsignedInteger(val))
} else {
Err(())
}
}
CharacterDataSpec::Double => {
if let Ok(val) = text.parse() {
Ok(autosar_data_rs::CharacterData::Double(val))
} else {
Err(())
CharacterDataSpec::Double => {
if let Ok(strval) = any.extract::<String>() {
if let Ok(floatval) = strval.parse() {
Ok(CharacterData::Double(floatval))
} else {
Err(pyo3::exceptions::PyValueError::new_err(format!(
"invalid literal '{strval}' for conversion to float"
)))
}
} else if let Ok(intval) = any.extract::<u64>() {
Ok(CharacterData::Double(intval as f64))
} else if let Ok(floatval) = any.extract::<f64>() {
Ok(CharacterData::Double(floatval))
} else {
Err(pyo3::exceptions::PyTypeError::new_err(format!(
"'{}' cannot be converted to 'float'",
any.get_type()
)))
}
}
}
}
})
}

fn character_data_to_object(cdata: &autosar_data_rs::CharacterData) -> PyObject {
Expand Down Expand Up @@ -525,9 +526,11 @@ fn version_mask_from_any(version_obj: PyObject) -> PyResult<u32> {
println!("mask from single version: {:x}", ver as u32);
Ok(ver as u32)
} else {
Err(AutosarDataError::new_err(
autosar_data_rs::AutosarDataError::IncorrectContentType.to_string(),
))
let any = version_obj.as_ref(py);
Err(pyo3::exceptions::PyTypeError::new_err(format!(
"'{}' cannot be converted to 'VersionSpecification'",
any.get_type()
)))
}
})
}
Loading

0 comments on commit 01cdafe

Please sign in to comment.