Skip to content

Commit d5fa2ec

Browse files
committed
v2: Paths as pathlib.Path & validate assignment
For petab.v2 pydantic models, change path attributes to `pathlib.Path`, and validate assignments.
1 parent f9bad6a commit d5fa2ec

File tree

3 files changed

+62
-19
lines changed

3 files changed

+62
-19
lines changed

petab/v1/yaml.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ def write_yaml(yaml_config: dict[str, Any], filename: str | Path) -> None:
242242
"""
243243
Path(filename).parent.mkdir(parents=True, exist_ok=True)
244244
with open(filename, "w") as outfile:
245-
yaml.dump(
245+
yaml.safe_dump(
246246
yaml_config, outfile, default_flow_style=False, sort_keys=False
247247
)
248248

petab/v2/core.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,10 @@ class Observable(BaseModel):
204204

205205
#: :meta private:
206206
model_config = ConfigDict(
207-
arbitrary_types_allowed=True, populate_by_name=True, extra="allow"
207+
arbitrary_types_allowed=True,
208+
populate_by_name=True,
209+
extra="allow",
210+
validate_assignment=True,
208211
)
209212

210213
@field_validator(
@@ -344,6 +347,7 @@ class Change(BaseModel):
344347
populate_by_name=True,
345348
use_enum_values=True,
346349
extra="allow",
350+
validate_assignment=True,
347351
)
348352

349353
@field_validator("target_value", mode="before")
@@ -385,7 +389,9 @@ class Condition(BaseModel):
385389
changes: list[Change]
386390

387391
#: :meta private:
388-
model_config = ConfigDict(populate_by_name=True, extra="allow")
392+
model_config = ConfigDict(
393+
populate_by_name=True, extra="allow", validate_assignment=True
394+
)
389395

390396
def __add__(self, other: Change) -> Condition:
391397
"""Add a change to the set."""
@@ -503,7 +509,9 @@ class ExperimentPeriod(BaseModel):
503509
condition_ids: list[str] = Field(default_factory=list)
504510

505511
#: :meta private:
506-
model_config = ConfigDict(populate_by_name=True, extra="allow")
512+
model_config = ConfigDict(
513+
populate_by_name=True, extra="allow", validate_assignment=True
514+
)
507515

508516
@field_validator("condition_ids", mode="before")
509517
@classmethod
@@ -544,7 +552,10 @@ class Experiment(BaseModel):
544552

545553
#: :meta private:
546554
model_config = ConfigDict(
547-
arbitrary_types_allowed=True, populate_by_name=True, extra="allow"
555+
arbitrary_types_allowed=True,
556+
populate_by_name=True,
557+
extra="allow",
558+
validate_assignment=True,
548559
)
549560

550561
def __add__(self, other: ExperimentPeriod) -> Experiment:
@@ -682,7 +693,10 @@ class Measurement(BaseModel):
682693

683694
#: :meta private:
684695
model_config = ConfigDict(
685-
arbitrary_types_allowed=True, populate_by_name=True, extra="allow"
696+
arbitrary_types_allowed=True,
697+
populate_by_name=True,
698+
extra="allow",
699+
validate_assignment=True,
686700
)
687701

688702
@field_validator(
@@ -806,7 +820,9 @@ class Mapping(BaseModel):
806820
)
807821

808822
#: :meta private:
809-
model_config = ConfigDict(populate_by_name=True, extra="allow")
823+
model_config = ConfigDict(
824+
populate_by_name=True, extra="allow", validate_assignment=True
825+
)
810826

811827

812828
class MappingTable(BaseModel):
@@ -909,6 +925,7 @@ class Parameter(BaseModel):
909925
populate_by_name=True,
910926
use_enum_values=True,
911927
extra="allow",
928+
validate_assignment=True,
912929
)
913930

914931
@field_validator("id")

petab/v2/problem.py

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,13 @@
1616
import numpy as np
1717
import pandas as pd
1818
import sympy as sp
19-
from pydantic import AnyUrl, BaseModel, Field, field_validator
19+
from pydantic import (
20+
AnyUrl,
21+
BaseModel,
22+
ConfigDict,
23+
Field,
24+
field_validator,
25+
)
2026

2127
from ..v1 import (
2228
parameter_mapping,
@@ -1124,9 +1130,13 @@ def model_dump(self, **kwargs) -> dict[str, Any]:
11241130
class ModelFile(BaseModel):
11251131
"""A file in the PEtab problem configuration."""
11261132

1127-
location: str | AnyUrl
1133+
location: AnyUrl | Path
11281134
language: str
11291135

1136+
model_config = ConfigDict(
1137+
validate_assignment=True,
1138+
)
1139+
11301140

11311141
class ExtensionConfig(BaseModel):
11321142
"""The configuration of a PEtab extension."""
@@ -1139,13 +1149,13 @@ class ProblemConfig(BaseModel):
11391149
"""The PEtab problem configuration."""
11401150

11411151
#: The path to the PEtab problem configuration.
1142-
filepath: str | AnyUrl | None = Field(
1152+
filepath: AnyUrl | Path | None = Field(
11431153
None,
11441154
description="The path to the PEtab problem configuration.",
11451155
exclude=True,
11461156
)
11471157
#: The base path to resolve relative paths.
1148-
base_path: str | AnyUrl | None = Field(
1158+
base_path: AnyUrl | Path | None = Field(
11491159
None,
11501160
description="The base path to resolve relative paths.",
11511161
exclude=True,
@@ -1156,17 +1166,16 @@ class ProblemConfig(BaseModel):
11561166
# TODO https://github.com/PEtab-dev/PEtab/pull/641:
11571167
# rename to parameter_files in yaml for consistency with other files?
11581168
# always a list?
1159-
parameter_files: list[str | AnyUrl] = Field(
1169+
parameter_files: list[AnyUrl | Path] = Field(
11601170
default=[], alias=PARAMETER_FILES
11611171
)
11621172

1163-
# TODO: consider changing str to Path
11641173
model_files: dict[str, ModelFile] | None = {}
1165-
measurement_files: list[str | AnyUrl] = []
1166-
condition_files: list[str | AnyUrl] = []
1167-
experiment_files: list[str | AnyUrl] = []
1168-
observable_files: list[str | AnyUrl] = []
1169-
mapping_files: list[str | AnyUrl] = []
1174+
measurement_files: list[AnyUrl | Path] = []
1175+
condition_files: list[AnyUrl | Path] = []
1176+
experiment_files: list[AnyUrl | Path] = []
1177+
observable_files: list[AnyUrl | Path] = []
1178+
mapping_files: list[AnyUrl | Path] = []
11701179

11711180
#: Extensions used by the problem.
11721181
extensions: list[ExtensionConfig] | dict = {}
@@ -1194,7 +1203,24 @@ def to_yaml(self, filename: str | Path):
11941203
"""
11951204
from ..v1.yaml import write_yaml
11961205

1197-
write_yaml(self.model_dump(by_alias=True), filename)
1206+
data = self.model_dump(by_alias=True)
1207+
# convert Paths to strings for YAML serialization
1208+
for key in (
1209+
"measurement_files",
1210+
"condition_files",
1211+
"experiment_files",
1212+
"observable_files",
1213+
"mapping_files",
1214+
"parameter_files",
1215+
):
1216+
data[key] = list(map(str, data[key]))
1217+
1218+
for model_id in data.get("model_files", {}):
1219+
data["model_files"][model_id][MODEL_LOCATION] = str(
1220+
data["model_files"][model_id]["location"]
1221+
)
1222+
1223+
write_yaml(data, filename)
11981224

11991225
@property
12001226
def format_version_tuple(self) -> tuple[int, int, int, str]:

0 commit comments

Comments
 (0)