diff --git a/pywr-python/Cargo.toml b/pywr-python/Cargo.toml index 4ca77314..3ef30490 100644 --- a/pywr-python/Cargo.toml +++ b/pywr-python/Cargo.toml @@ -22,7 +22,7 @@ pyo3-log = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } chrono = { workspace = true } - +pywr-v1-schema = { workspace = true } pywr-core = { path = "../pywr-core" } pywr-schema = { path = "../pywr-schema" } diff --git a/pywr-python/pywr/__init__.py b/pywr-python/pywr/__init__.py index a32b63f7..8e6b8167 100644 --- a/pywr-python/pywr/__init__.py +++ b/pywr-python/pywr/__init__.py @@ -1,7 +1,7 @@ from pathlib import Path from typing import Optional -from .pywr import Schema, Model # type: ignore +from .pywr import Schema, Model, convert_model_from_v1_json_string, convert_metric_from_v1_json_string # type: ignore def run_from_path( diff --git a/pywr-python/src/lib.rs b/pywr-python/src/lib.rs index b79cda0f..c3d082c0 100644 --- a/pywr-python/src/lib.rs +++ b/pywr-python/src/lib.rs @@ -1,7 +1,7 @@ use chrono::NaiveDateTime; use pyo3::exceptions::PyRuntimeError; use pyo3::prelude::*; -use pyo3::types::{PyDict, PyType}; +use pyo3::types::{PyDict, PyTuple, PyType}; /// Python API /// @@ -16,6 +16,8 @@ use pywr_core::solvers::{ClpSolver, ClpSolverSettings, ClpSolverSettingsBuilder} #[cfg(feature = "highs")] use pywr_core::solvers::{HighsSolver, HighsSolverSettings, HighsSolverSettingsBuilder}; use pywr_schema::model::DateType; +use pywr_schema::parameters::TryIntoV2Parameter; +use pywr_schema::ConversionError; use std::fmt; use std::path::PathBuf; use std::str::FromStr; @@ -95,13 +97,56 @@ impl Schema { Ok(data) } - /// Convert the schema to a Pywr model. + /// Build the schema in to a Pywr model. fn build(&mut self, data_path: Option, output_path: Option) -> PyResult { let model = self.schema.build_model(data_path.as_deref(), output_path.as_deref())?; Ok(Model { model }) } } +/// Convert a Pywr v1.x JSON string to a Pywr v2.x schema. +#[pyfunction] +fn convert_model_from_v1_json_string(py: Python, data: &str) -> PyResult> { + // Try to convert + let (schema, errors) = + pywr_schema::PywrModel::from_v1_str(data).map_err(|e| PyRuntimeError::new_err(e.to_string()))?; + + // Create a new schema object + let py_schema = Schema { schema }; + let py_errors = errors.into_iter().map(|e| e.to_string()).collect::>(); + + let result = PyTuple::new_bound(py, &[py_schema.into_py(py), py_errors.into_py(py)]).into(); + Ok(result) +} + +#[pyclass] +pub struct Metric { + metric: pywr_schema::metric::Metric, +} + +#[pymethods] +impl Metric { + /// Serialize the metric to a JSON string. + fn to_json_string(&self) -> PyResult { + let data = serde_json::to_string_pretty(&self.metric).map_err(|e| PyRuntimeError::new_err(e.to_string()))?; + Ok(data) + } +} + +/// Convert a Pywr v1.x JSON string to a Pywr v2.x metric. +#[pyfunction] +fn convert_metric_from_v1_json_string(_py: Python, data: &str) -> PyResult { + let v1: pywr_v1_schema::parameters::ParameterValue = + serde_json::from_str(data).map_err(|e| PyRuntimeError::new_err(e.to_string()))?; + + let metric = v1 + .try_into_v2_parameter(None, &mut 0) + .map_err(|e: ConversionError| PyRuntimeError::new_err(e.to_string()))?; + + let py_metric = Metric { metric }; + Ok(py_metric) +} + #[pyclass] pub struct Model { model: pywr_core::models::Model, @@ -203,8 +248,11 @@ fn build_highs_settings(kwargs: Option<&Bound<'_, PyDict>>) -> PyResult) -> PyResult<()> { pyo3_log::init(); + m.add_function(wrap_pyfunction!(convert_model_from_v1_json_string, m)?)?; + m.add_function(wrap_pyfunction!(convert_metric_from_v1_json_string, m)?)?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; Ok(()) } diff --git a/pywr-schema/src/model.rs b/pywr-schema/src/model.rs index b1bfda0e..39f0a60e 100644 --- a/pywr-schema/src/model.rs +++ b/pywr-schema/src/model.rs @@ -667,6 +667,15 @@ impl PywrModel { errors, ) } + + /// Convert a v1 JSON string to a v2 model. + /// + /// See [`PywrModel::from_v1`] for more information. + pub fn from_v1_str(v1: &str) -> Result<(Self, Vec), pywr_v1_schema::model::PywrSchemaError> { + let v1_model: pywr_v1_schema::PywrModel = serde_json::from_str(v1)?; + + Ok(Self::from_v1(v1_model)) + } } #[derive(serde::Deserialize, serde::Serialize, Clone)]