diff --git a/justfile b/justfile index 4033110..84a1db4 100644 --- a/justfile +++ b/justfile @@ -10,10 +10,10 @@ lint: just --justfile {{justfile()}} clippy clippy: - cargo clippy --all-targets + cargo clippy --all-targets --all-features check: - cargo check --all-targets + cargo check --all-targets --all-features fmt: cargo fmt --all diff --git a/src/native/common.rs b/src/native/common.rs index 0670b00..40c4a06 100644 --- a/src/native/common.rs +++ b/src/native/common.rs @@ -2,7 +2,10 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; #[cfg(feature = "python")] -use pyo3::{prelude::*, types::PyDateTime}; +use pyo3::{ + prelude::*, + types::{PyDateTime, PyDict}, +}; use crate::native::deserializers::{ default_datetime_none, default_string_none, deserialize_empty_string_as_none, @@ -76,6 +79,17 @@ impl Value { fn value(&self) -> PyResult { Ok(self.value.clone()) } + + pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult> { + let dict = PyDict::new_bound(py); + dict.set_item("by", &self.by)?; + dict.set_item("by_unique_id", &self.by_unique_id)?; + dict.set_item("role", &self.role)?; + dict.set_item("when", to_py_datetime(py, &self.when)?)?; + dict.set_item("value", &self.value)?; + + Ok(dict) + } } #[cfg(not(feature = "python"))] @@ -144,6 +158,17 @@ impl Reason { fn value(&self) -> PyResult { Ok(self.value.clone()) } + + pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult> { + let dict = PyDict::new_bound(py); + dict.set_item("by", &self.by)?; + dict.set_item("by_unique_id", &self.by_unique_id)?; + dict.set_item("role", &self.role)?; + dict.set_item("when", to_py_datetime(py, &self.when)?)?; + dict.set_item("value", &self.value)?; + + Ok(dict) + } } #[cfg(not(feature = "python"))] @@ -167,6 +192,42 @@ pub struct Entry { pub reason: Option, } +#[cfg(feature = "python")] +#[pymethods] +impl Entry { + #[getter] + fn entry_id(&self) -> PyResult { + Ok(self.entry_id.clone()) + } + + #[getter] + fn value(&self) -> PyResult> { + Ok(self.value.clone()) + } + + #[getter] + fn reason(&self) -> PyResult> { + Ok(self.reason.clone()) + } + + pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult> { + let dict = PyDict::new_bound(py); + dict.set_item("entry_id", &self.entry_id)?; + if let Some(value) = &self.value { + dict.set_item("value", value.to_dict(py)?)?; + } else { + dict.set_item("value", py.None())?; + } + if let Some(reason) = &self.reason { + dict.set_item("reason", reason.to_dict(py)?)?; + } else { + dict.set_item("reason", py.None())?; + } + + Ok(dict) + } +} + #[cfg(not(feature = "python"))] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[serde(rename_all = "camelCase")] @@ -250,6 +311,29 @@ impl Field { fn entries(&self) -> PyResult>> { Ok(self.entries.clone()) } + + pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult> { + let dict = PyDict::new_bound(py); + dict.set_item("name", &self.name)?; + dict.set_item("field_type", &self.field_type)?; + dict.set_item("data_type", &self.data_type)?; + dict.set_item("error_code", &self.error_code)?; + dict.set_item("when_created", to_py_datetime(py, &self.when_created)?)?; + dict.set_item("keep_history", self.keep_history)?; + + let mut entry_dicts = Vec::new(); + if let Some(entries) = &self.entries { + for entry in entries { + let entry_dict = entry.to_dict(py)?; + entry_dicts.push(entry_dict.to_object(py)); + } + dict.set_item("entries", entry_dicts)?; + } else { + dict.set_item("entries", py.None())?; + } + + Ok(dict) + } } #[cfg(not(feature = "python"))] @@ -283,6 +367,50 @@ pub struct Category { pub fields: Option>, } +#[cfg(feature = "python")] +#[pymethods] +impl Category { + #[getter] + fn name(&self) -> PyResult { + Ok(self.name.clone()) + } + + #[getter] + fn category_type(&self) -> PyResult { + Ok(self.category_type.clone()) + } + + #[getter] + fn highest_index(&self) -> PyResult { + Ok(self.highest_index) + } + + #[getter] + fn fields(&self) -> PyResult>> { + Ok(self.fields.clone()) + } + + pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult> { + let dict = PyDict::new_bound(py); + dict.set_item("name", &self.name)?; + dict.set_item("category_type", &self.category_type)?; + dict.set_item("highest_index", self.highest_index)?; + + let mut field_dicts = Vec::new(); + if let Some(fields) = &self.fields { + for field in fields { + let field_dict = field.to_dict(py)?; + field_dicts.push(field_dict.to_object(py)); + } + dict.set_item("fields", field_dicts)?; + } else { + dict.set_item("fields", py.None())?; + } + + Ok(dict) + } +} + #[cfg(not(feature = "python"))] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[serde(rename_all = "camelCase")] @@ -336,6 +464,16 @@ impl State { fn date_signed<'py>(&self, py: Python<'py>) -> PyResult>> { to_py_datetime_option(py, &self.date_signed) } + + pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult> { + let dict = PyDict::new_bound(py); + dict.set_item("value", &self.value)?; + dict.set_item("signer", &self.signer)?; + dict.set_item("signer_unique_id", &self.signer_unique_id)?; + dict.set_item("date_signed", to_py_datetime_option(py, &self.date_signed)?)?; + + Ok(dict) + } } #[cfg(not(feature = "python"))] @@ -539,4 +677,52 @@ impl Form { fn categories(&self) -> PyResult>> { Ok(self.categories.clone()) } + + pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult> { + let dict = PyDict::new_bound(py); + dict.set_item("name", &self.name)?; + dict.set_item( + "last_modified", + to_py_datetime_option(py, &self.last_modified)?, + )?; + dict.set_item("who_last_modified_name", &self.who_last_modified_name)?; + dict.set_item("who_last_modified_role", &self.who_last_modified_role)?; + dict.set_item("when_created", self.when_created)?; + dict.set_item("has_errors", self.has_errors)?; + dict.set_item("has_warnings", self.has_warnings)?; + dict.set_item("locked", self.locked)?; + dict.set_item("user", &self.user)?; + dict.set_item( + "date_time_changed", + to_py_datetime_option(py, &self.date_time_changed)?, + )?; + dict.set_item("form_title", &self.form_title)?; + dict.set_item("form_index", self.form_index)?; + dict.set_item("form_group", &self.form_group)?; + dict.set_item("form_state", &self.form_state)?; + + let mut state_dicts = Vec::new(); + if let Some(states) = &self.states { + for state in states { + let state_dict = state.to_dict(py)?; + state_dicts.push(state_dict.to_object(py)); + } + dict.set_item("states", state_dicts)?; + } else { + dict.set_item("states", py.None())?; + } + + if let Some(categories) = &self.categories { + let mut category_dicts = Vec::new(); + for category in categories { + let category_dict = category.to_dict(py)?; + category_dicts.push(category_dict.to_object(py)); + } + dict.set_item("categories", category_dicts)?; + } else { + dict.set_item("categories", py.None())?; + } + + Ok(dict) + } } diff --git a/src/native/site_native.rs b/src/native/site_native.rs index 0089f06..3d94e02 100644 --- a/src/native/site_native.rs +++ b/src/native/site_native.rs @@ -1,7 +1,10 @@ use chrono::{DateTime, Utc}; #[cfg(feature = "python")] -use pyo3::{prelude::*, types::PyDateTime}; +use pyo3::{ + prelude::*, + types::{PyDateTime, PyDict}, +}; use serde::{Deserialize, Serialize}; @@ -91,6 +94,33 @@ impl Site { fn forms(&self) -> PyResult>> { Ok(self.forms.clone()) } + + pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult> { + let dict = PyDict::new_bound(py); + dict.set_item("name", &self.name)?; + dict.set_item("unique_id", &self.unique_id)?; + dict.set_item("number_of_patients", self.number_of_patients)?; + dict.set_item( + "count_of_randomized_patients", + self.count_of_randomized_patients, + )?; + dict.set_item("when_created", to_py_datetime(py, &self.when_created)?)?; + dict.set_item("creator", &self.creator)?; + dict.set_item("number_of_forms", self.number_of_forms)?; + + let mut form_dicts = Vec::new(); + if let Some(forms) = &self.forms { + for form in forms { + let form_dict = form.to_dict(py)?; + form_dicts.push(form_dict.to_object(py)); + } + dict.set_item("forms", form_dicts)?; + } else { + dict.set_item("forms", py.None())?; + } + + Ok(dict) + } } #[cfg(not(feature = "python"))] @@ -113,6 +143,27 @@ pub struct SiteNative { pub sites: Vec, } +#[cfg(feature = "python")] +#[pymethods] +impl SiteNative { + #[getter] + fn sites(&self) -> PyResult> { + Ok(self.sites.clone()) + } + + /// Convert the class instance to a dictionary + fn to_dict<'py>(&self, py: Python<'py>) -> PyResult> { + let dict = PyDict::new_bound(py); + let mut site_dicts = Vec::new(); + for site in &self.sites { + let site_dict = site.to_dict(py)?; + site_dicts.push(site_dict.to_object(py)); + } + dict.set_item("sites", site_dicts)?; + Ok(dict) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/native/subject_native.rs b/src/native/subject_native.rs index fb27ba9..5ff7b2f 100644 --- a/src/native/subject_native.rs +++ b/src/native/subject_native.rs @@ -1,7 +1,10 @@ use chrono::{DateTime, Utc}; #[cfg(feature = "python")] -use pyo3::{prelude::*, types::PyDateTime}; +use pyo3::{ + prelude::*, + types::{PyDateTime, PyDict}, +}; #[cfg(feature = "python")] use crate::native::deserializers::to_py_datetime; @@ -110,6 +113,31 @@ impl Patient { fn forms(&self) -> PyResult>> { Ok(self.forms.clone()) } + + pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult> { + let dict = PyDict::new_bound(py); + dict.set_item("patient_id", &self.patient_id)?; + dict.set_item("unique_id", &self.unique_id)?; + dict.set_item("when_created", to_py_datetime(py, &self.when_created)?)?; + dict.set_item("creator", &self.creator)?; + dict.set_item("site_name", &self.site_name)?; + dict.set_item("site_unique_id", &self.site_unique_id)?; + dict.set_item("last_language", &self.last_language)?; + dict.set_item("number_of_forms", self.number_of_forms)?; + + let mut form_dicts = Vec::new(); + if let Some(forms) = &self.forms { + for form in forms { + let form_dict = form.to_dict(py)?; + form_dicts.push(form_dict.to_object(py)); + } + dict.set_item("forms", form_dicts)?; + } else { + dict.set_item("forms", py.None())?; + } + + Ok(dict) + } } #[cfg(not(feature = "python"))] @@ -131,6 +159,27 @@ pub struct SubjectNative { pub patients: Vec, } +#[cfg(feature = "python")] +#[pymethods] +impl SubjectNative { + #[getter] + fn sites(&self) -> PyResult> { + Ok(self.patients.clone()) + } + + /// Convert the class instance to a dictionary + fn to_dict<'py>(&self, py: Python<'py>) -> PyResult> { + let dict = PyDict::new_bound(py); + let mut patient_dicts = Vec::new(); + for patient in &self.patients { + let patient_dict = patient.to_dict(py)?; + patient_dicts.push(patient_dict.to_object(py)); + } + dict.set_item("patients", patient_dicts)?; + Ok(dict) + } +} + #[cfg(test)] mod tests { use insta::assert_yaml_snapshot; diff --git a/src/native/user_native.rs b/src/native/user_native.rs index 895c9f7..38bb7c4 100644 --- a/src/native/user_native.rs +++ b/src/native/user_native.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "python")] -use pyo3::prelude::*; +use pyo3::{prelude::*, types::PyDict}; pub use crate::native::{ common::{Category, Entry, Field, Form, Reason, State, Value}, @@ -48,6 +48,51 @@ pub struct User { pub forms: Option>, } +#[cfg(feature = "python")] +#[pymethods] +impl User { + #[getter] + fn unique_id(&self) -> PyResult { + Ok(self.unique_id.clone()) + } + + #[getter] + fn last_language(&self) -> PyResult> { + Ok(self.last_language.clone()) + } + + #[getter] + fn creator(&self) -> PyResult { + Ok(self.creator.clone()) + } + + #[getter] + fn forms(&self) -> PyResult>> { + Ok(self.forms.clone()) + } + + pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult> { + let dict = PyDict::new_bound(py); + dict.set_item("unique_id", &self.unique_id)?; + dict.set_item("last_language", &self.last_language)?; + dict.set_item("creator", &self.creator)?; + dict.set_item("number_of_forms", self.number_of_forms)?; + + let mut form_dicts = Vec::new(); + if let Some(forms) = &self.forms { + for form in forms { + let form_dict = form.to_dict(py)?; + form_dicts.push(form_dict.to_object(py)); + } + dict.set_item("forms", form_dicts)?; + } else { + dict.set_item("forms", py.None())?; + } + + Ok(dict) + } +} + #[cfg(not(feature = "python"))] /// Contains the information from the Prelude native user XML. #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] @@ -67,6 +112,27 @@ pub struct UserNative { pub users: Vec, } +#[cfg(feature = "python")] +#[pymethods] +impl UserNative { + #[getter] + fn users(&self) -> PyResult> { + Ok(self.users.clone()) + } + + /// Convert the class instance to a dictionary + fn to_dict<'py>(&self, py: Python<'py>) -> PyResult> { + let dict = PyDict::new_bound(py); + let mut user_dicts = Vec::new(); + for user in &self.users { + let user_dict = user.to_dict(py)?; + user_dicts.push(user_dict.to_object(py)); + } + dict.set_item("users", user_dicts)?; + Ok(dict) + } +} + #[cfg(test)] mod tests { use super::*;