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
35 changes: 35 additions & 0 deletions petab/_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Private, version-independent utility functions for PEtab."""

from pathlib import Path

from pydantic import AnyUrl, TypeAdapter

PathOrUrlAdapter = TypeAdapter(AnyUrl | Path)


def _generate_path(
file_path: str | Path | AnyUrl,
base_path: Path | str | AnyUrl | None = None,
) -> str:
"""
Generate a local path or URL from a file path and an optional base path.
:return: A string representing the relative or absolute path or URL.
Absolute if `file_path` or `base_path` is an absolute path or URL,
relative otherwise.
"""
if base_path is None:
return str(file_path)

file_path = PathOrUrlAdapter.validate_python(file_path)
if isinstance(file_path, AnyUrl):
# if URL, this is absolute
return str(file_path)

base_path = PathOrUrlAdapter.validate_python(base_path)
if isinstance(base_path, Path):
# if file_path is absolute, base_path will be ignored
return str(base_path / file_path)

# combine URL parts
return f"{base_path}/{file_path}"
28 changes: 21 additions & 7 deletions petab/v1/models/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,22 @@ def __repr__(self):

@staticmethod
@abc.abstractmethod
def from_file(filepath_or_buffer: Any, model_id: str) -> Model:
def from_file(
filepath_or_buffer: Any, model_id: str, base_path: str | Path = None
) -> Model:
"""Load the model from the given path/URL

:param filepath_or_buffer: URL or path of the model
:param filepath_or_buffer:
Absolute or relative path/URL to the model file.
If relative, it is interpreted relative to `base_path`, if given.
:param base_path: Base path for relative paths in the model file.
:param model_id: Model ID
:returns: A ``Model`` instance holding the given model
"""
...

@abc.abstractmethod
def to_file(self, filename: [str, Path]):
def to_file(self, filename: str | Path | None = None):
"""Save the model to the given file

:param filename: Destination filename
Expand Down Expand Up @@ -131,11 +136,16 @@ def is_state_variable(self, id_: str) -> bool:


def model_factory(
filepath_or_buffer: Any, model_language: str, model_id: str = None
filepath_or_buffer: Any,
model_language: str,
model_id: str = None,
base_path: str | Path = None,
) -> Model:
"""Create a PEtab model instance from the given model

:param filepath_or_buffer: Path/URL of the model
:param filepath_or_buffer: Path/URL of the model.
Absolute or relative to `base_path` if given.
:param base_path: Base path for relative paths in the model file.
:param model_language: PEtab model language ID for the given model
:param model_id: PEtab model ID for the given model
:returns: A :py:class:`Model` instance representing the given model
Expand All @@ -145,12 +155,16 @@ def model_factory(
if model_language == MODEL_TYPE_SBML:
from .sbml_model import SbmlModel

return SbmlModel.from_file(filepath_or_buffer, model_id=model_id)
return SbmlModel.from_file(
filepath_or_buffer, model_id=model_id, base_path=base_path
)

if model_language == MODEL_TYPE_PYSB:
from .pysb_model import PySBModel

return PySBModel.from_file(filepath_or_buffer, model_id=model_id)
return PySBModel.from_file(
filepath_or_buffer, model_id=model_id, base_path=base_path
)

if model_language in known_model_types:
raise NotImplementedError(
Expand Down
31 changes: 26 additions & 5 deletions petab/v1/models/pysb_model.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Functions for handling PySB models"""

from __future__ import annotations

import itertools
import re
import sys
Expand All @@ -9,6 +11,7 @@

import pysb

from ..._utils import _generate_path
from .. import is_valid_identifier
from . import MODEL_TYPE_PYSB
from .model import Model
Expand Down Expand Up @@ -54,9 +57,18 @@ class PySBModel(Model):

type_id = MODEL_TYPE_PYSB

def __init__(self, model: pysb.Model, model_id: str = None):
def __init__(
self,
model: pysb.Model,
model_id: str = None,
rel_path: Path | str | None = None,
base_path: str | Path | None = None,
):
super().__init__()

self.rel_path = rel_path
self.base_path = base_path

self.model = model
self._model_id = model_id or self.model.name

Expand All @@ -68,16 +80,25 @@ def __init__(self, model: pysb.Model, model_id: str = None):
)

@staticmethod
def from_file(filepath_or_buffer, model_id: str = None):
def from_file(
filepath_or_buffer, model_id: str = None, base_path: str | Path = None
) -> PySBModel:
return PySBModel(
model=_pysb_model_from_path(filepath_or_buffer), model_id=model_id
model=_pysb_model_from_path(
_generate_path(filepath_or_buffer, base_path)
),
model_id=model_id,
rel_path=filepath_or_buffer,
base_path=base_path,
)

def to_file(self, filename: [str, Path]):
def to_file(self, filename: str | Path | None = None) -> None:
from pysb.export import export

model_source = export(self.model, "pysb_flat")
with open(filename, "w") as f:
with open(
filename or _generate_path(self.rel_path, self.base_path), "w"
) as f:
f.write(model_source)

@property
Expand Down
19 changes: 15 additions & 4 deletions petab/v1/models/sbml_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import sympy as sp
from sympy.abc import _clash

from ..._utils import _generate_path
from ..sbml import (
get_sbml_model,
is_sbml_consistent,
Expand All @@ -33,6 +34,8 @@ def __init__(
sbml_reader: libsbml.SBMLReader = None,
sbml_document: libsbml.SBMLDocument = None,
model_id: str = None,
rel_path: Path | str | None = None,
base_path: str | Path | None = None,
):
"""Constructor.

Expand All @@ -42,6 +45,9 @@ def __init__(
:param model_id: Model ID. Defaults to the SBML model ID."""
super().__init__()

self.rel_path = rel_path
self.base_path = base_path

if sbml_model is None and sbml_document is None:
raise ValueError(
"Either sbml_model or sbml_document must be given."
Expand Down Expand Up @@ -87,15 +93,19 @@ def __setstate__(self, state):
self.__dict__.update(state)

@staticmethod
def from_file(filepath_or_buffer, model_id: str = None) -> SbmlModel:
def from_file(
filepath_or_buffer, model_id: str = None, base_path: str | Path = None
) -> SbmlModel:
sbml_reader, sbml_document, sbml_model = get_sbml_model(
filepath_or_buffer
_generate_path(filepath_or_buffer, base_path=base_path)
)
return SbmlModel(
sbml_model=sbml_model,
sbml_reader=sbml_reader,
sbml_document=sbml_document,
model_id=model_id,
rel_path=filepath_or_buffer,
base_path=base_path,
)

@staticmethod
Expand Down Expand Up @@ -159,9 +169,10 @@ def model_id(self):
def model_id(self, model_id):
self._model_id = model_id

def to_file(self, filename: [str, Path]):
def to_file(self, filename: str | Path | None = None) -> None:
write_sbml(
self.sbml_document or self.sbml_model.getSBMLDocument(), filename
self.sbml_document or self.sbml_model.getSBMLDocument(),
filename or _generate_path(self.rel_path, self.base_path),
)

def get_parameter_value(self, id_: str) -> float:
Expand Down
Loading
Loading