Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,16 @@
`pi`/`e`, and prints floats without loss of precision. PEtab v2 condition
and measurement tables now write expressions in PEtab syntax
(by @dweindl in https://github.com/PEtab-dev/libpetab-python/pull/545)
* `petab.v2.ProblemConfig.to_yaml` no longer writes empty optional entries
(`condition_files`, `experiment_files`, `mapping_files`, `extensions`)
(by @dweindl in https://github.com/PEtab-dev/libpetab-python/pull/547)

**Features**
* `petab1to2` and `petab_files_1to2` can skip validation via `validate=False`
(by @FFroehlich in https://github.com/PEtab-dev/libpetab-python/pull/534)
* Added `petab.v2.C.PROBLEM_CONFIG_REQUIRED_KEYS` and
`petab.v2.C.PROBLEM_CONFIG_OPTIONAL_KEYS`
(by @dweindl in https://github.com/PEtab-dev/libpetab-python/pull/547)

## 0.9 series

Expand Down
19 changes: 19 additions & 0 deletions petab/v2/C.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,8 @@
# YAML
#: PEtab version key in the YAML file
FORMAT_VERSION = "format_version"
#: Problem ID key in the YAML file
PROBLEM_ID = "id"
#: Parameter files key in the YAML file
PARAMETER_FILES = "parameter_files"
#: Problems key in the YAML file
Expand Down Expand Up @@ -261,6 +263,23 @@
#: PEtab SciML extension
EXT_ID_SCIML = "sciml"

#: Required top-level keys in the problem YAML file
PROBLEM_CONFIG_REQUIRED_KEYS = [
FORMAT_VERSION,
PARAMETER_FILES,
MODEL_FILES,
OBSERVABLE_FILES,
MEASUREMENT_FILES,
]
#: Optional top-level keys in the problem YAML file
PROBLEM_CONFIG_OPTIONAL_KEYS = [
PROBLEM_ID,
CONDITION_FILES,
EXPERIMENT_FILES,
MAPPING_FILES,
EXTENSIONS,
]


# MAPPING

Expand Down
10 changes: 8 additions & 2 deletions petab/v2/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -2812,14 +2812,20 @@ def to_yaml(self, filename: str | Path):
data["model_files"][model_id][C.MODEL_LOCATION] = str(
data["model_files"][model_id][C.MODEL_LOCATION]
)
if data["id"] is None:
if data[C.PROBLEM_ID] is None:
# The schema requires a valid id or no id field at all.
del data["id"]
del data[C.PROBLEM_ID]

for ext_id in list(data[C.EXTENSIONS]):
if ext_id == C.EXT_ID_SCIML:
data[C.EXTENSIONS][ext_id] = self.extensions[ext_id].to_yaml()

# Omit empty optional entries. Required ones are kept even if empty.
for key in C.PROBLEM_CONFIG_OPTIONAL_KEYS:
# an empty `id` is invalid and must not be dropped silently

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strange that the spec allows "empty" for the optional tables but not for the optional ID?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, still some inconsistencies there..

if key != C.PROBLEM_ID and not data[key]:
del data[key]

write_yaml(data, filename)

@property
Expand Down
58 changes: 57 additions & 1 deletion tests/v2/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
UPPER_BOUND,
)
from petab.v2.core import *
from petab.v2.core import ExtensionConfig
from petab.v2.core import ExtensionConfig, ModelFile
from petab.v2.lint import CheckUnusedConditions, ValidationIssueSeverity
from petab.v2.models.sbml_model import SbmlModel
from petab.v2.petab1to2 import petab1to2
Expand Down Expand Up @@ -780,6 +780,62 @@ def test_problem_config_extensions_rejects_non_dict():
)


def test_problem_config_keys_match_schema():
"""The required/optional YAML keys in `C` match the PEtab v2 schema."""
from petab.v1.yaml import SCHEMAS, load_yaml

schema = load_yaml(SCHEMAS[(2, 0)])
assert set(C.PROBLEM_CONFIG_REQUIRED_KEYS) == set(schema["required"])
assert set(C.PROBLEM_CONFIG_OPTIONAL_KEYS) == set(
schema["properties"]
) - set(schema["required"])


def test_problem_config_to_yaml_omits_empty_optional(tmp_path):
"""Empty optional entries are not written; empty required ones are."""
from petab.v1.yaml import load_yaml, validate_yaml_syntax

pc = ProblemConfig(
parameter_files=["parameters.tsv"],
model_files={
"model": ModelFile(location="model.xml", language="sbml")
},
measurement_files=["measurements.tsv"],
)
yaml_path = tmp_path / "problem.yaml"
pc.to_yaml(yaml_path)

yaml_config = load_yaml(yaml_path)
assert set(yaml_config) == {
C.FORMAT_VERSION,
C.PARAMETER_FILES,
C.MODEL_FILES,
C.MEASUREMENT_FILES,
C.OBSERVABLE_FILES,
}
assert yaml_config[C.OBSERVABLE_FILES] == []
validate_yaml_syntax(yaml_config)
reloaded = ProblemConfig(**yaml_config, base_path=tmp_path)
assert reloaded.model_dump() == pc.model_dump()

# non-empty optional entries are kept
pc.condition_files = ["conditions.tsv"]
pc.experiment_files = ["experiments.tsv"]
pc.mapping_files = ["mapping.tsv"]
pc.extensions = {"my_ext": {"version": "1.0.0", "required": False}}
pc.to_yaml(yaml_path)

yaml_config = load_yaml(yaml_path)
assert yaml_config[C.CONDITION_FILES] == ["conditions.tsv"]
assert yaml_config[C.EXPERIMENT_FILES] == ["experiments.tsv"]
assert yaml_config[C.MAPPING_FILES] == ["mapping.tsv"]
assert yaml_config[C.EXTENSIONS] == {
"my_ext": {"version": "1.0.0", "required": False}
}
reloaded = ProblemConfig(**yaml_config, base_path=tmp_path)
assert reloaded.model_dump() == pc.model_dump()


def test_validate_unsupported_extension_severity():
"""libpetab-python doesn't mathematically interpret extensions, so an
unsupported extension only ever produces a WARNING (that the problem
Expand Down
Loading