Skip to content

Commit f700fd9

Browse files
committed
..
1 parent a3b1f66 commit f700fd9

File tree

3 files changed

+40
-69
lines changed

3 files changed

+40
-69
lines changed

petab/v2/conditions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
import sympy as sp
1010

1111
from .. import v2
12+
from ..v1.lint import assert_no_leading_trailing_whitespace
1213
from .C import *
13-
from .lint import assert_no_leading_trailing_whitespace
1414

1515
__all__ = [
1616
"get_condition_df",

petab/v2/core.py

Lines changed: 26 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,15 @@ def _convert_nan_to_none(v):
6868
return v
6969

7070

71+
def _valid_petab_id(v: str) -> str:
72+
"""Field validator for PEtab IDs."""
73+
if not v:
74+
raise ValueError("ID must not be empty.")
75+
if not is_valid_identifier(v):
76+
raise ValueError(f"Invalid ID: {v}")
77+
return v
78+
79+
7180
class ObservableTransformation(str, Enum):
7281
"""Observable transformation types.
7382
@@ -141,7 +150,9 @@ class Observable(BaseModel):
141150
"""Observable definition."""
142151

143152
#: Observable ID.
144-
id: str = Field(alias=C.OBSERVABLE_ID)
153+
id: Annotated[str, AfterValidator(_valid_petab_id)] = Field(
154+
alias=C.OBSERVABLE_ID
155+
)
145156
#: Observable name.
146157
name: str | None = Field(alias=C.OBSERVABLE_NAME, default=None)
147158
#: Observable formula.
@@ -162,15 +173,6 @@ class Observable(BaseModel):
162173
arbitrary_types_allowed=True, populate_by_name=True
163174
)
164175

165-
@field_validator("id")
166-
@classmethod
167-
def _validate_id(cls, v):
168-
if not v:
169-
raise ValueError("ID must not be empty.")
170-
if not is_valid_identifier(v):
171-
raise ValueError(f"Invalid ID: {v}")
172-
return v
173-
174176
@field_validator(
175177
"name",
176178
"formula",
@@ -313,9 +315,11 @@ class Change(BaseModel):
313315
"""
314316

315317
#: The ID of the target entity to change.
316-
target_id: str | None = Field(alias=C.TARGET_ID, default=None)
318+
target_id: Annotated[str, AfterValidator(_valid_petab_id)] = Field(
319+
alias=C.TARGET_ID
320+
)
317321
#: The value to set the target entity to.
318-
target_value: sp.Basic | None = Field(alias=C.TARGET_VALUE, default=None)
322+
target_value: sp.Basic = Field(alias=C.TARGET_VALUE)
319323

320324
#: :meta private:
321325
model_config = ConfigDict(
@@ -324,16 +328,6 @@ class Change(BaseModel):
324328
use_enum_values=True,
325329
)
326330

327-
@model_validator(mode="before")
328-
@classmethod
329-
def _validate_id(cls, data: dict):
330-
target_id = data.get("target_id", data.get(C.TARGET_ID))
331-
332-
if not is_valid_identifier(target_id):
333-
raise ValueError(f"Invalid ID: {target_id}")
334-
335-
return data
336-
337331
@field_validator("target_value", mode="before")
338332
@classmethod
339333
def _sympify(cls, v):
@@ -366,22 +360,15 @@ class Condition(BaseModel):
366360
"""
367361

368362
#: The condition ID.
369-
id: str = Field(alias=C.CONDITION_ID)
363+
id: Annotated[str, AfterValidator(_valid_petab_id)] = Field(
364+
alias=C.CONDITION_ID
365+
)
370366
#: The changes associated with this condition.
371367
changes: list[Change]
372368

373369
#: :meta private:
374370
model_config = ConfigDict(populate_by_name=True)
375371

376-
@field_validator("id")
377-
@classmethod
378-
def _validate_id(cls, v):
379-
if not v:
380-
raise ValueError("ID must not be empty.")
381-
if not is_valid_identifier(v):
382-
raise ValueError(f"Invalid ID: {v}")
383-
return v
384-
385372
def __add__(self, other: Change) -> Condition:
386373
"""Add a change to the set."""
387374
if not isinstance(other, Change):
@@ -488,8 +475,6 @@ class ExperimentPeriod(BaseModel):
488475
def _validate_id(cls, condition_id):
489476
if pd.isna(condition_id) or not condition_id:
490477
return None
491-
# if not condition_id:
492-
# raise ValueError("ID must not be empty.")
493478
if not is_valid_identifier(condition_id):
494479
raise ValueError(f"Invalid ID: {condition_id}")
495480
return condition_id
@@ -504,7 +489,9 @@ class Experiment(BaseModel):
504489
"""
505490

506491
#: The experiment ID.
507-
id: str = Field(alias=C.EXPERIMENT_ID)
492+
id: Annotated[str, AfterValidator(_valid_petab_id)] = Field(
493+
alias=C.EXPERIMENT_ID
494+
)
508495
#: The periods of the experiment.
509496
periods: list[ExperimentPeriod] = []
510497

@@ -513,15 +500,6 @@ class Experiment(BaseModel):
513500
arbitrary_types_allowed=True, populate_by_name=True
514501
)
515502

516-
@field_validator("id")
517-
@classmethod
518-
def _validate_id(cls, v):
519-
if not v:
520-
raise ValueError("ID must not be empty.")
521-
if not is_valid_identifier(v):
522-
raise ValueError(f"Invalid ID: {v}")
523-
return v
524-
525503
def __add__(self, other: ExperimentPeriod) -> Experiment:
526504
"""Add a period to the experiment."""
527505
if not isinstance(other, ExperimentPeriod):
@@ -747,7 +725,9 @@ class Mapping(BaseModel):
747725
"""Mapping PEtab entities to model entities."""
748726

749727
#: PEtab entity ID.
750-
petab_id: str = Field(alias=C.PETAB_ENTITY_ID)
728+
petab_id: Annotated[str, AfterValidator(_valid_petab_id)] = Field(
729+
alias=C.PETAB_ENTITY_ID
730+
)
751731
#: Model entity ID.
752732
model_id: Annotated[str | None, BeforeValidator(_convert_nan_to_none)] = (
753733
Field(alias=C.MODEL_ENTITY_ID, default=None)
@@ -760,17 +740,6 @@ class Mapping(BaseModel):
760740
#: :meta private:
761741
model_config = ConfigDict(populate_by_name=True)
762742

763-
@field_validator(
764-
"petab_id",
765-
)
766-
@classmethod
767-
def _validate_id(cls, v):
768-
if not v:
769-
raise ValueError("ID must not be empty.")
770-
if not is_valid_identifier(v):
771-
raise ValueError(f"Invalid ID: {v}")
772-
return v
773-
774743

775744
class MappingTable(BaseModel):
776745
"""PEtab mapping table."""
@@ -913,7 +882,7 @@ def _validate(self) -> Self:
913882
)
914883

915884
if self.lb is not None and self.ub is not None and self.lb >= self.ub:
916-
raise ValueError("Lower bound must be less than upper bound")
885+
raise ValueError("Lower bound must be less than upper bound.")
917886

918887
# TODO parameterScale?
919888

petab/v2/lint.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@
1414
import sympy as sp
1515

1616
from .. import v2
17-
from ..v1.visualize.lint import validate_visualization_df
18-
from ..v2.C import *
1917
from .problem import Problem
2018

2119
logger = logging.getLogger(__name__)
@@ -165,7 +163,7 @@ def lint_problem(problem: Problem | str | Path) -> ValidationResultList:
165163
Arguments:
166164
problem:
167165
PEtab problem to check. Instance of :class:`Problem` or path
168-
to a PEtab problem yaml file.
166+
to a PEtab problem YAML file.
169167
Returns:
170168
A list of validation results. Empty if no issues were found.
171169
"""
@@ -324,7 +322,7 @@ def run(self, problem: Problem) -> ValidationIssue | None:
324322

325323

326324
class CheckPosLogMeasurements(ValidationTask):
327-
"""A task to check that measurements for observables with
325+
"""Check that measurements for observables with
328326
log-transformation are positive."""
329327

330328
def run(self, problem: Problem) -> ValidationIssue | None:
@@ -669,7 +667,9 @@ class CheckVisualizationTable(ValidationTask):
669667

670668
def run(self, problem: Problem) -> ValidationIssue | None:
671669
if problem.visualization_df is None:
672-
return
670+
return None
671+
672+
from ..v1.visualize.lint import validate_visualization_df
673673

674674
if validate_visualization_df(problem):
675675
return ValidationIssue(
@@ -698,22 +698,23 @@ def get_valid_parameters_for_parameter_table(
698698
# - remove parameters for which condition table columns exist
699699
# - remove placeholder parameters
700700
# (only partial overrides are not supported)
701+
701702
# must not go into parameter table
702-
blackset = set(get_placeholders(problem))
703+
invalid = set(get_placeholders(problem))
703704

704705
# condition table targets
705-
blackset |= {
706+
invalid |= {
706707
change.target_id
707708
for cond in problem.conditions_table.conditions
708709
for change in cond.changes
709710
}
710711

711712
# don't use sets here, to have deterministic ordering,
712-
# e.g. for creating parameter tables
713+
# e.g., for creating parameter tables
713714
parameter_ids = OrderedDict.fromkeys(
714715
p
715716
for p in problem.model.get_valid_parameters_for_parameter_table()
716-
if p not in blackset
717+
if p not in invalid
717718
)
718719

719720
for mapping in problem.mapping_table.mappings:
@@ -723,14 +724,14 @@ def get_valid_parameters_for_parameter_table(
723724
# add output parameters from observables table
724725
output_parameters = get_output_parameters(problem)
725726
for p in output_parameters:
726-
if p not in blackset:
727+
if p not in invalid:
727728
parameter_ids[p] = None
728729

729730
# Append parameters from measurement table, unless they occur as condition
730731
# table columns
731732
def append_overrides(overrides):
732733
for p in overrides:
733-
if isinstance(p, sp.Symbol) and (str_p := str(p)) not in blackset:
734+
if isinstance(p, sp.Symbol) and (str_p := str(p)) not in invalid:
734735
parameter_ids[str_p] = None
735736

736737
for measurement in problem.measurement_table.measurements:
@@ -932,4 +933,5 @@ def get_placeholders(
932933
CheckUnusedConditions(),
933934
# TODO: atomize checks, update to long condition table, re-enable
934935
# CheckVisualizationTable(),
936+
# TODO validate mapping table
935937
]

0 commit comments

Comments
 (0)