From 293303b59c23a97a32a6e77ddf9e96a5b70aa53e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Hatcher?= Date: Mon, 2 Jun 2025 15:34:07 +0200 Subject: [PATCH] UPDATE: Add PyUserModel for the Hackaton --- Cargo.lock | 2 +- bindings/python/Cargo.toml | 2 +- bindings/python/pyproject.toml | 2 +- bindings/python/src/lib.rs | 83 +++++++++++++++++++++++++++- bindings/python/tests/test_create.py | 15 +++++ 5 files changed, 100 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cbcdd99..f293ae9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -784,7 +784,7 @@ dependencies = [ [[package]] name = "pyroncalc" -version = "0.5.0" +version = "0.5.1" dependencies = [ "ironcalc", "pyo3", diff --git a/bindings/python/Cargo.toml b/bindings/python/Cargo.toml index 7d9a5f6..8053254 100644 --- a/bindings/python/Cargo.toml +++ b/bindings/python/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyroncalc" -version = "0.5.0" +version = "0.5.1" edition = "2021" diff --git a/bindings/python/pyproject.toml b/bindings/python/pyproject.toml index c3128a5..3ba4472 100644 --- a/bindings/python/pyproject.toml +++ b/bindings/python/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "ironcalc" -version = "0.5.0" +version = "0.5.1" description = "Create, edit and evaluate Excel spreadsheets" requires-python = ">=3.10" keywords = [ diff --git a/bindings/python/src/lib.rs b/bindings/python/src/lib.rs index 2c9b9fa..4a188c2 100644 --- a/bindings/python/src/lib.rs +++ b/bindings/python/src/lib.rs @@ -3,7 +3,7 @@ use pyo3::{create_exception, prelude::*, wrap_pyfunction}; use types::{PySheetProperty, PyStyle}; use xlsx::base::types::Style; -use xlsx::base::Model; +use xlsx::base::{Model, UserModel}; use xlsx::export::{save_to_icalc, save_to_xlsx}; use xlsx::import; @@ -14,6 +14,55 @@ use crate::types::PyCellType; create_exception!(_ironcalc, WorkbookError, PyException); +#[pyclass] +pub struct PyUserModel { + /// The user model, which is a wrapper around the Model + pub model: UserModel, +} + +#[pymethods] +impl PyUserModel { + /// Saves the user model to an xlsx file + pub fn save_to_xlsx(&self, file: &str) -> PyResult<()> { + let model = self.model.get_model(); + save_to_xlsx(model, file).map_err(|e| WorkbookError::new_err(e.to_string())) + } + + /// Saves the user model to file in the internal binary ic format + pub fn save_to_icalc(&self, file: &str) -> PyResult<()> { + let model = self.model.get_model(); + save_to_icalc(model, file).map_err(|e| WorkbookError::new_err(e.to_string())) + } + + pub fn apply_external_diffs(&mut self, external_diffs: &[u8]) -> PyResult<()> { + self.model + .apply_external_diffs(external_diffs) + .map_err(|e| WorkbookError::new_err(e.to_string())) + } + + pub fn flush_send_queue(&mut self) -> Vec { + self.model.flush_send_queue() + } + + pub fn set_user_input( + &mut self, + sheet: u32, + row: i32, + column: i32, + value: &str, + ) -> PyResult<()> { + self.model + .set_user_input(sheet, row, column, value) + .map_err(|e| WorkbookError::new_err(e.to_string())) + } + + pub fn get_formatted_cell_value(&self, sheet: u32, row: i32, column: i32) -> PyResult { + self.model + .get_formatted_cell_value(sheet, row, column) + .map_err(|e| WorkbookError::new_err(e.to_string())) + } +} + /// This is a model implementing the 'raw' API #[pyclass] pub struct PyModel { @@ -257,6 +306,33 @@ pub fn create(name: &str, locale: &str, tz: &str) -> PyResult { Ok(PyModel { model }) } +#[pyfunction] +pub fn create_user_model(name: &str, locale: &str, tz: &str) -> PyResult { + let model = UserModel::new_empty(name, locale, tz) + .map_err(|e| WorkbookError::new_err(e.to_string()))?; + Ok(PyUserModel { model }) +} + +#[pyfunction] +pub fn create_user_model_from_xlsx( + file_path: &str, + locale: &str, + tz: &str, +) -> PyResult { + let model = import::load_from_xlsx(file_path, locale, tz) + .map_err(|e| WorkbookError::new_err(e.to_string()))?; + let model = UserModel::from_model(model); + Ok(PyUserModel { model }) +} + +#[pyfunction] +pub fn create_user_model_from_icalc(file_name: &str) -> PyResult { + let model = + import::load_from_icalc(file_name).map_err(|e| WorkbookError::new_err(e.to_string()))?; + let model = UserModel::from_model(model); + Ok(PyUserModel { model }) +} + #[pyfunction] #[allow(clippy::panic)] pub fn test_panic() { @@ -274,5 +350,10 @@ fn ironcalc(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(load_from_icalc, m)?)?; m.add_function(wrap_pyfunction!(test_panic, m)?)?; + // User model functions + m.add_function(wrap_pyfunction!(create_user_model, m)?)?; + m.add_function(wrap_pyfunction!(create_user_model_from_xlsx, m)?)?; + m.add_function(wrap_pyfunction!(create_user_model_from_icalc, m)?)?; + Ok(()) } diff --git a/bindings/python/tests/test_create.py b/bindings/python/tests/test_create.py index 7ad96c8..02e5048 100644 --- a/bindings/python/tests/test_create.py +++ b/bindings/python/tests/test_create.py @@ -6,3 +6,18 @@ def test_simple(): model.evaluate() assert model.get_formatted_cell_value(0, 1, 1) == "3" + +def test_simple_user(): + model = ic.create_user_model("model", "en", "UTC") + model.set_user_input(0, 1, 1, "=1+2") + model.set_user_input(0, 1, 2, "=A1+3") + + assert model.get_formatted_cell_value(0, 1, 1) == "3" + assert model.get_formatted_cell_value(0, 1, 2) == "6" + + diffs = model.flush_send_queue() + + model2 = ic.create_user_model("model", "en", "UTC") + model2.apply_external_diffs(diffs) + assert model2.get_formatted_cell_value(0, 1, 1) == "3" + assert model2.get_formatted_cell_value(0, 1, 2) == "6" \ No newline at end of file