From 5269fcdc6e1bd62ce27e7e7b2d8993cde346477b Mon Sep 17 00:00:00 2001 From: nanglo123 Date: Thu, 12 Sep 2024 09:22:48 -0400 Subject: [PATCH] Remove deprecated json_encoder from ConfigDict for serializing SympyExprStr objects. Use updated field serializer decorator --- mira/metamodel/comparison.py | 10 ++-------- mira/metamodel/template_model.py | 29 +++++++++++++---------------- mira/metamodel/templates.py | 24 +++++++++--------------- mira/metamodel/units.py | 18 ++++++------------ tests/test_model_api.py | 8 +++++--- 5 files changed, 35 insertions(+), 54 deletions(-) diff --git a/mira/metamodel/comparison.py b/mira/metamodel/comparison.py index a92acfcb4..5fafcac5c 100644 --- a/mira/metamodel/comparison.py +++ b/mira/metamodel/comparison.py @@ -74,14 +74,8 @@ class IntraModelEdge(DataEdge): class ModelComparisonGraphdata(BaseModel): """A data structure holding a graph representation of TemplateModel delta""" - # TODO[pydantic]: The following keys were removed: `json_encoders`. - # Check https://docs.pydantic.dev/dev-v2/migration/#changes-to-config for more information. - model_config = ConfigDict(arbitrary_types_allowed=True, json_encoders={ - SympyExprStr: lambda e: str(e), - }, json_decoders={ - SympyExprStr: lambda e: safe_parse_expr(e), - Template: lambda t: Template.from_json(data=t), - }) + + model_config = ConfigDict(arbitrary_types_allowed=True) template_models: Dict[int, TemplateModel] = Field( ..., description="A mapping of template model keys to template models" diff --git a/mira/metamodel/template_model.py b/mira/metamodel/template_model.py index 485b82afd..8570ced48 100644 --- a/mira/metamodel/template_model.py +++ b/mira/metamodel/template_model.py @@ -20,7 +20,7 @@ import networkx as nx import sympy import mira.metamodel.io -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, field_serializer from .templates import * from .units import Unit from .utils import safe_parse_expr, SympyExprStr @@ -36,11 +36,8 @@ class Initial(BaseModel): expression: SympyExprStr = Field( description="The expression for the initial." ) - # TODO[pydantic]: The following keys were removed: `json_encoders`. - # Check https://docs.pydantic.dev/dev-v2/migration/#changes-to-config for more information. - model_config = ConfigDict(arbitrary_types_allowed=True, json_encoders={ - SympyExprStr: lambda e: str(e), - }, json_decoders={SympyExprStr: lambda e: sympy.parse_expr(e)}) + + model_config = ConfigDict(arbitrary_types_allowed=True) @classmethod def from_json(cls, data: Dict[str, Any], locals_dict=None) -> "Initial": @@ -68,6 +65,10 @@ def from_json(cls, data: Dict[str, Any], locals_dict=None) -> "Initial": expression = safe_parse_expr(expression_str, local_dict=locals_dict) return cls(concept=concept, expression=SympyExprStr(expression)) + @field_serializer('expression') + def serialize_expression(self, expression): + return str(expression) + def substitute_parameter(self, name, value): """ Substitute a parameter value into the initial expression. @@ -135,16 +136,17 @@ class Observable(Concept): readout is not defined as a state variable but is rather a function of state variables. """ - # TODO[pydantic]: The following keys were removed: `json_encoders`. - # Check https://docs.pydantic.dev/dev-v2/migration/#changes-to-config for more information. - model_config = ConfigDict(arbitrary_types_allowed=True, json_encoders={ - SympyExprStr: lambda e: str(e), - }, json_decoders={SympyExprStr: lambda e: safe_parse_expr(e)}) + + model_config = ConfigDict(arbitrary_types_allowed=True) expression: SympyExprStr = Field( description="The expression for the observable." ) + @field_serializer('expression') + def serialize_expression(self, expression): + return str(expression) + def substitute_parameter(self, name, value): """ Substitute a parameter value into the observable expression. @@ -367,11 +369,6 @@ class TemplateModel(BaseModel): description="A structure containing time-related annotations. " "Note that all annotations are optional.", ) - # TODO[pydantic]: The following keys were removed: `json_encoders`. - # Check https://docs.pydantic.dev/dev-v2/migration/#changes-to-config for more information. - model_config = ConfigDict(json_encoders={ - SympyExprStr: lambda e: str(e), - }, json_decoders={SympyExprStr: lambda e: safe_parse_expr(e)}) def get_parameters_from_rate_law(self, rate_law) -> Set[str]: """Given a rate law, find its elements that are model parameters. diff --git a/mira/metamodel/templates.py b/mira/metamodel/templates.py index f0d292d48..7e8ce6c59 100644 --- a/mira/metamodel/templates.py +++ b/mira/metamodel/templates.py @@ -62,7 +62,7 @@ import networkx as nx import pydantic import sympy -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, field_serializer try: from typing import Annotated # py39+ @@ -128,13 +128,8 @@ class Concept(BaseModel): None, description="The units of the concept." ) _base_name: str = pydantic.PrivateAttr(None) - # TODO[pydantic]: The following keys were removed: `json_encoders`. - # Check https://docs.pydantic.dev/dev-v2/migration/#changes-to-config for more information. - model_config = ConfigDict(arbitrary_types_allowed=True, json_encoders={ - SympyExprStr: lambda e: str(e), - }, json_decoders={ - SympyExprStr: lambda e: sympy.parse_expr(e) - }) + + model_config = ConfigDict(arbitrary_types_allowed=True) def __eq__(self, other): if isinstance(other, Concept): @@ -399,13 +394,8 @@ def from_json(cls, data) -> "Concept": class Template(BaseModel): """The Template is a parent class for model processes""" - # TODO[pydantic]: The following keys were removed: `json_encoders`. - # Check https://docs.pydantic.dev/dev-v2/migration/#changes-to-config for more information. - model_config = ConfigDict(arbitrary_types_allowed=True, json_encoders={ - SympyExprStr: lambda e: str(e), - }, json_decoders={ - SympyExprStr: lambda e: safe_parse_expr(e) - }) + + model_config = ConfigDict(arbitrary_types_allowed=True) rate_law: Optional[SympyExprStr] = Field( default=None, description="The rate law for the template." @@ -466,6 +456,10 @@ def from_json(cls, data, rate_symbols=None) -> "Template": if k not in {'rate_law', 'type'}}, rate_law=rate) + @field_serializer('rate_law') + def serialize_expression(self, rate_law): + return str(rate_law) + def is_equal_to(self, other: "Template", with_context: bool = False, config: Config = None) -> bool: """Check if this template is equal to another template diff --git a/mira/metamodel/units.py b/mira/metamodel/units.py index ac1378290..31c2af9fa 100644 --- a/mira/metamodel/units.py +++ b/mira/metamodel/units.py @@ -14,7 +14,7 @@ from typing import Dict, Any import sympy -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, field_serializer from .utils import SympyExprStr @@ -34,17 +34,7 @@ def load_units(): class Unit(BaseModel): """A unit of measurement.""" - # TODO[pydantic]: The following keys were removed: `json_encoders`. - # Check https://docs.pydantic.dev/dev-v2/migration/#changes-to-config for more information. - model_config = ConfigDict( - arbitrary_types_allowed=True, - json_encoders={ - SympyExprStr: lambda e: str(e), - }, - #json_decoders={ - # SympyExprStr: lambda e: sympy.parse_expr(e) - #} - ) + model_config = ConfigDict(arbitrary_types_allowed=True) expression: SympyExprStr = Field( description="The expression for the unit." @@ -66,6 +56,10 @@ def model_validate(cls, obj): obj['expression'] = SympyExprStr(obj['expression']) return super().model_validate(obj) + @field_serializer('expression') + def serialize_expression(self, expression): + return str(expression) + person_units = Unit(expression=sympy.Symbol('person')) day_units = Unit(expression=sympy.Symbol('day')) diff --git a/tests/test_model_api.py b/tests/test_model_api.py index 8c34a995b..c799cd8f9 100644 --- a/tests/test_model_api.py +++ b/tests/test_model_api.py @@ -586,16 +586,18 @@ def test_n_way_comparison_askenet(self): "exclude_defaults": True, "exclude_unset": True, "exclude_none": True, + # This key is outdated in Pydantic2 # "skip_defaults": True, } # Compare the ModelComparisonResponse models - assert local_response == resp_model # If assertion fails the diff is printed + # If assertion fails the diff is printed + assert local_response.model_dump() == resp_model.model_dump() local_sorted_str = sorted_json_str( - json.loads(local_response.model_dump_json(**dict_options)), + json.loads(local_response.model_dump_json()), skip_empty=True ) resp_sorted_str = sorted_json_str( - json.loads(resp_model.model_dump_json(**dict_options)), + json.loads(resp_model.model_dump_json()), skip_empty=True ) self.assertEqual(local_sorted_str, resp_sorted_str)