UPDATE: Python bindings for the user API
This commit is contained in:
committed by
Nicolás Hatcher Andrés
parent
faa0ff9b69
commit
07854f1593
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "pyroncalc"
|
||||
version = "0.5.0"
|
||||
version = "0.5.7"
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@ crate-type = ["cdylib"]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
xlsx = { package= "ironcalc", path = "../../xlsx", version = "0.5.0" }
|
||||
pyo3 = { version = "0.23", features = ["extension-module"] }
|
||||
pyo3 = { version = "0.25", features = ["extension-module"] }
|
||||
bitcode = "0.6.3"
|
||||
|
||||
|
||||
[features]
|
||||
|
||||
@@ -10,9 +10,6 @@ You can add cell values, retrieve them and most importantly you can evaluate spr
|
||||
pip install ironcalc
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
## Compile and test
|
||||
|
||||
To compile this and test it:
|
||||
@@ -28,4 +25,18 @@ examples $ python example.py
|
||||
|
||||
From there if you use `python` you can `import ironcalc`. You can either create a new file, read it from a JSON string or import from Excel.
|
||||
|
||||
Hopefully the API is straightforward.
|
||||
Hopefully the API is straightforward.
|
||||
|
||||
## Creating documentation
|
||||
|
||||
We use sphinx
|
||||
|
||||
```
|
||||
python -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install maturin
|
||||
pip install sphinx
|
||||
maturin develop
|
||||
sphinx-build -M html docs html
|
||||
python -m http.server --directory html/html/
|
||||
```
|
||||
|
||||
9
bindings/python/build_docs.sh
Executable file
9
bindings/python/build_docs.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
python -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install patchelf
|
||||
pip install maturin
|
||||
pip install sphinx
|
||||
maturin develop
|
||||
sphinx-build -M html docs html
|
||||
python -m http.server --directory html/html/
|
||||
@@ -8,7 +8,8 @@ IronCalc
|
||||
installation
|
||||
usage_examples
|
||||
top_level_methods
|
||||
api_reference
|
||||
raw_api_reference
|
||||
user_api_reference
|
||||
objects
|
||||
|
||||
IronCalc is a spreadsheet engine that allows you to create, modify and save spreadsheets.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
Raw API Reference
|
||||
-----------------
|
||||
|
||||
In general methods in IronCalc use a 0-index base for the the sheet index and 1-index base for the row and column indexes.
|
||||
|
||||
@@ -28,7 +28,7 @@ In general methods in IronCalc use a 0-index base for the the sheet index and 1-
|
||||
|
||||
.. method:: get_cell_content(sheet: int, row: int, column: int) -> str
|
||||
|
||||
Returns the raw content of a cell. If the cell contains a formula,
|
||||
Returns the raw content of a cell. If the cell contains a formula,
|
||||
the returned string starts with ``"="``.
|
||||
|
||||
:param sheet: The sheet index (0-based).
|
||||
@@ -47,7 +47,7 @@ In general methods in IronCalc use a 0-index base for the the sheet index and 1-
|
||||
|
||||
.. method:: get_formatted_cell_value(sheet: int, row: int, column: int) -> str
|
||||
|
||||
Returns the cell’s value as a formatted string, taking into
|
||||
Returns the cell’s value as a formatted string, taking into
|
||||
account any number/currency/date formatting.
|
||||
|
||||
:param sheet: The sheet index (0-based).
|
||||
@@ -167,7 +167,7 @@ In general methods in IronCalc use a 0-index base for the the sheet index and 1-
|
||||
|
||||
.. method:: get_worksheets_properties() -> List[PySheetProperty]
|
||||
|
||||
Returns a list of :class:`PySheetProperty` describing each worksheet’s
|
||||
Returns a list of :class:`PySheetProperty` describing each worksheet’s
|
||||
name, visibility state, ID, and tab color.
|
||||
|
||||
:rtype: list of PySheetProperty
|
||||
@@ -204,7 +204,7 @@ In general methods in IronCalc use a 0-index base for the the sheet index and 1-
|
||||
|
||||
.. method:: test_panic()
|
||||
|
||||
A test method that deliberately panics in Rust.
|
||||
A test method that deliberately panics in Rust.
|
||||
Used for testing panic handling at the method level.
|
||||
|
||||
:raises WorkbookError: (wrapped Rust panic)
|
||||
@@ -1,6 +1,13 @@
|
||||
Top Level Methods
|
||||
-----------------
|
||||
|
||||
This module provides a set of top-level methods for creating and loading IronCalc models.
|
||||
|
||||
.. autofunction:: ironcalc.create
|
||||
.. autofunction:: ironcalc.load_from_xlsx
|
||||
.. autofunction:: ironcalc.load_from_icalc
|
||||
.. autofunction:: ironcalc.load_from_icalc
|
||||
.. autofunction:: ironcalc.load_from_bytes
|
||||
.. autofunction:: ironcalc.create_user_model
|
||||
.. autofunction:: ironcalc.create_user_model_from_bytes
|
||||
.. autofunction:: ironcalc.create_user_model_from_xlsx
|
||||
.. autofunction:: ironcalc.create_user_model_from_icalc
|
||||
41
bindings/python/docs/user_api_reference.rst
Normal file
41
bindings/python/docs/user_api_reference.rst
Normal file
@@ -0,0 +1,41 @@
|
||||
User API Reference
|
||||
------------------
|
||||
|
||||
This is the "user api". Models here have history, they evaluate automatically with each change and have a "diff" history.
|
||||
|
||||
|
||||
.. method:: save_to_xlsx(file: str)
|
||||
|
||||
Saves the user model to file in the XLSX format.
|
||||
|
||||
::param file: The file path to save the model to.
|
||||
|
||||
.. method:: save_to_icalc(file: str)
|
||||
|
||||
Saves the user model to file in the internal binary ic format.
|
||||
|
||||
::param file: The file path to save the model to.
|
||||
|
||||
.. method:: apply_external_diffs(external_diffs: bytes)
|
||||
|
||||
Applies external diffs to the model. This is used to apply changes from other instances of the model.
|
||||
|
||||
::param external_diffs: The external diffs to apply, as a byte array.
|
||||
|
||||
.. method:: flush_send_queue() -> bytes
|
||||
|
||||
Flushes the send queue and returns the bytes to be sent to the client. This is used to send changes to the client.
|
||||
|
||||
.. method:: set_user_input(sheet: int, row: int, column: int, value: str)
|
||||
|
||||
Sets an input in a cell, as would be done by a user typing into a spreadsheet cell.
|
||||
|
||||
.. method:: get_formatted_cell_value(sheet: int, row: int, column: int) -> str
|
||||
|
||||
Returns the cell’s value as a formatted string, taking into account any number/currency/date formatting.
|
||||
|
||||
.. method:: to_bytes() -> bytes
|
||||
|
||||
Returns the model as a byte array. This is useful for sending the model over a network or saving it to a file.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "ironcalc"
|
||||
version = "0.5.0"
|
||||
version = "0.5.6"
|
||||
description = "Create, edit and evaluate Excel spreadsheets"
|
||||
requires-python = ">=3.10"
|
||||
keywords = [
|
||||
|
||||
@@ -2,8 +2,8 @@ use pyo3::exceptions::PyException;
|
||||
use pyo3::{create_exception, prelude::*, wrap_pyfunction};
|
||||
|
||||
use types::{PySheetProperty, PyStyle};
|
||||
use xlsx::base::types::Style;
|
||||
use xlsx::base::Model;
|
||||
use xlsx::base::types::{Style, Workbook};
|
||||
use xlsx::base::{Model, UserModel};
|
||||
|
||||
use xlsx::export::{save_to_icalc, save_to_xlsx};
|
||||
use xlsx::import;
|
||||
@@ -14,6 +14,60 @@ 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<u8> {
|
||||
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<String> {
|
||||
self.model
|
||||
.get_formatted_cell_value(sheet, row, column)
|
||||
.map_err(|e| WorkbookError::new_err(e.to_string()))
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> PyResult<Vec<u8>> {
|
||||
let bytes = self.model.to_bytes();
|
||||
Ok(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
/// This is a model implementing the 'raw' API
|
||||
#[pyclass]
|
||||
pub struct PyModel {
|
||||
@@ -32,6 +86,12 @@ impl PyModel {
|
||||
save_to_icalc(&self.model, file).map_err(|e| WorkbookError::new_err(e.to_string()))
|
||||
}
|
||||
|
||||
/// To bytes
|
||||
pub fn to_bytes(&self) -> PyResult<Vec<u8>> {
|
||||
let bytes = self.model.to_bytes();
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
/// Evaluates the workbook
|
||||
pub fn evaluate(&mut self) {
|
||||
self.model.evaluate()
|
||||
@@ -249,7 +309,19 @@ pub fn load_from_icalc(file_name: &str) -> PyResult<PyModel> {
|
||||
Ok(PyModel { model })
|
||||
}
|
||||
|
||||
/// Creates an empty model
|
||||
/// Loads a function from bytes
|
||||
/// This function expects the bytes to be in the internal binary ic format
|
||||
/// which is the same format used by the `save_to_icalc` function.
|
||||
#[pyfunction]
|
||||
pub fn load_from_bytes(bytes: &[u8]) -> PyResult<PyModel> {
|
||||
let workbook: Workbook =
|
||||
bitcode::decode(bytes).map_err(|e| WorkbookError::new_err(e.to_string()))?;
|
||||
let model =
|
||||
Model::from_workbook(workbook).map_err(|e| WorkbookError::new_err(e.to_string()))?;
|
||||
Ok(PyModel { model })
|
||||
}
|
||||
|
||||
/// Creates an empty model in the raw API
|
||||
#[pyfunction]
|
||||
pub fn create(name: &str, locale: &str, tz: &str) -> PyResult<PyModel> {
|
||||
let model =
|
||||
@@ -257,6 +329,49 @@ pub fn create(name: &str, locale: &str, tz: &str) -> PyResult<PyModel> {
|
||||
Ok(PyModel { model })
|
||||
}
|
||||
|
||||
/// Creates a model with the user model API
|
||||
#[pyfunction]
|
||||
pub fn create_user_model(name: &str, locale: &str, tz: &str) -> PyResult<PyUserModel> {
|
||||
let model = UserModel::new_empty(name, locale, tz)
|
||||
.map_err(|e| WorkbookError::new_err(e.to_string()))?;
|
||||
Ok(PyUserModel { model })
|
||||
}
|
||||
|
||||
/// Creates a user model from an Excel file
|
||||
#[pyfunction]
|
||||
pub fn create_user_model_from_xlsx(
|
||||
file_path: &str,
|
||||
locale: &str,
|
||||
tz: &str,
|
||||
) -> PyResult<PyUserModel> {
|
||||
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 })
|
||||
}
|
||||
|
||||
/// Creates a user model from an icalc file
|
||||
#[pyfunction]
|
||||
pub fn create_user_model_from_icalc(file_name: &str) -> PyResult<PyUserModel> {
|
||||
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 })
|
||||
}
|
||||
|
||||
/// Creates a user model from bytes
|
||||
/// This function expects the bytes to be in the internal binary ic format
|
||||
/// which is the same format used by the `save_to_icalc` function.
|
||||
#[pyfunction]
|
||||
pub fn create_user_model_from_bytes(bytes: &[u8]) -> PyResult<PyUserModel> {
|
||||
let workbook: Workbook =
|
||||
bitcode::decode(bytes).map_err(|e| WorkbookError::new_err(e.to_string()))?;
|
||||
let model =
|
||||
Model::from_workbook(workbook).map_err(|e| WorkbookError::new_err(e.to_string()))?;
|
||||
let user_model = UserModel::from_model(model);
|
||||
Ok(PyUserModel { model: user_model })
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
#[allow(clippy::panic)]
|
||||
pub fn test_panic() {
|
||||
@@ -272,7 +387,14 @@ fn ironcalc(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(create, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(load_from_xlsx, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(load_from_icalc, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(load_from_bytes, 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_bytes, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(create_user_model_from_xlsx, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(create_user_model_from_icalc, m)?)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -6,3 +6,24 @@ def test_simple():
|
||||
model.evaluate()
|
||||
|
||||
assert model.get_formatted_cell_value(0, 1, 1) == "3"
|
||||
|
||||
bytes = model.to_bytes()
|
||||
|
||||
model2 = ic.load_from_bytes(bytes)
|
||||
assert model2.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"
|
||||
|
||||
Reference in New Issue
Block a user