11
11
from warnings import warn
12
12
13
13
import pandas as pd
14
+ from pydantic import AnyUrl , BaseModel , Field , RootModel
14
15
15
16
from . import (
16
17
conditions ,
@@ -79,6 +80,7 @@ def __init__(
79
80
observable_df : pd .DataFrame = None ,
80
81
mapping_df : pd .DataFrame = None ,
81
82
extensions_config : dict = None ,
83
+ config : ProblemConfig = None ,
82
84
):
83
85
self .condition_df : pd .DataFrame | None = condition_df
84
86
self .measurement_df : pd .DataFrame | None = measurement_df
@@ -113,6 +115,7 @@ def __init__(
113
115
114
116
self .model : Model | None = model
115
117
self .extensions_config = extensions_config or {}
118
+ self .config = config
116
119
117
120
def __getattr__ (self , name ):
118
121
# For backward-compatibility, allow access to SBML model related
@@ -262,10 +265,14 @@ def from_yaml(
262
265
yaml_config: PEtab configuration as dictionary or YAML file name
263
266
base_path: Base directory or URL to resolve relative paths
264
267
"""
268
+ # path to the yaml file
269
+ filepath = None
270
+
265
271
if isinstance (yaml_config , Path ):
266
272
yaml_config = str (yaml_config )
267
273
268
274
if isinstance (yaml_config , str ):
275
+ filepath = yaml_config
269
276
if base_path is None :
270
277
base_path = get_path_prefix (yaml_config )
271
278
yaml_config = yaml .load_yaml (yaml_config )
@@ -297,59 +304,58 @@ def get_path(filename):
297
304
DeprecationWarning ,
298
305
stacklevel = 2 ,
299
306
)
307
+ config = ProblemConfig (
308
+ ** yaml_config , base_path = base_path , filepath = filepath
309
+ )
310
+ problem0 = config .problems [0 ]
311
+ # currently required for handling PEtab v2 in here
312
+ problem0_ = yaml_config ["problems" ][0 ]
300
313
301
- problem0 = yaml_config ["problems" ][0 ]
302
-
303
- if isinstance (yaml_config [PARAMETER_FILE ], list ):
314
+ if isinstance (config .parameter_file , list ):
304
315
parameter_df = parameters .get_parameter_df (
305
- [get_path (f ) for f in yaml_config [ PARAMETER_FILE ] ]
316
+ [get_path (f ) for f in config . parameter_file ]
306
317
)
307
318
else :
308
319
parameter_df = (
309
- parameters .get_parameter_df (
310
- get_path (yaml_config [PARAMETER_FILE ])
311
- )
312
- if yaml_config [PARAMETER_FILE ]
320
+ parameters .get_parameter_df (get_path (config .parameter_file ))
321
+ if config .parameter_file
313
322
else None
314
323
)
315
-
316
- if yaml_config [FORMAT_VERSION ] in [1 , "1" , "1.0.0" ]:
317
- if len (problem0 [SBML_FILES ]) > 1 :
324
+ if config .format_version .root in [1 , "1" , "1.0.0" ]:
325
+ if len (problem0 .sbml_files ) > 1 :
318
326
# TODO https://github.com/PEtab-dev/libpetab-python/issues/6
319
327
raise NotImplementedError (
320
328
"Support for multiple models is not yet implemented."
321
329
)
322
330
323
331
model = (
324
332
model_factory (
325
- get_path (problem0 [ SBML_FILES ] [0 ]),
333
+ get_path (problem0 . sbml_files [0 ]),
326
334
MODEL_TYPE_SBML ,
327
335
model_id = None ,
328
336
)
329
- if problem0 [ SBML_FILES ]
337
+ if problem0 . sbml_files
330
338
else None
331
339
)
332
340
else :
333
- if len (problem0 [MODEL_FILES ]) > 1 :
341
+ if len (problem0_ [MODEL_FILES ]) > 1 :
334
342
# TODO https://github.com/PEtab-dev/libpetab-python/issues/6
335
343
raise NotImplementedError (
336
344
"Support for multiple models is not yet implemented."
337
345
)
338
- if not problem0 [MODEL_FILES ]:
346
+ if not problem0_ [MODEL_FILES ]:
339
347
model = None
340
348
else :
341
349
model_id , model_info = next (
342
- iter (problem0 [MODEL_FILES ].items ())
350
+ iter (problem0_ [MODEL_FILES ].items ())
343
351
)
344
352
model = model_factory (
345
353
get_path (model_info [MODEL_LOCATION ]),
346
354
model_info [MODEL_LANGUAGE ],
347
355
model_id = model_id ,
348
356
)
349
357
350
- measurement_files = [
351
- get_path (f ) for f in problem0 .get (MEASUREMENT_FILES , [])
352
- ]
358
+ measurement_files = [get_path (f ) for f in problem0 .measurement_files ]
353
359
# If there are multiple tables, we will merge them
354
360
measurement_df = (
355
361
core .concat_tables (
@@ -359,9 +365,7 @@ def get_path(filename):
359
365
else None
360
366
)
361
367
362
- condition_files = [
363
- get_path (f ) for f in problem0 .get (CONDITION_FILES , [])
364
- ]
368
+ condition_files = [get_path (f ) for f in problem0 .condition_files ]
365
369
# If there are multiple tables, we will merge them
366
370
condition_df = (
367
371
core .concat_tables (condition_files , conditions .get_condition_df )
@@ -370,7 +374,7 @@ def get_path(filename):
370
374
)
371
375
372
376
visualization_files = [
373
- get_path (f ) for f in problem0 .get ( VISUALIZATION_FILES , [])
377
+ get_path (f ) for f in problem0 .visualization_files
374
378
]
375
379
# If there are multiple tables, we will merge them
376
380
visualization_df = (
@@ -379,17 +383,15 @@ def get_path(filename):
379
383
else None
380
384
)
381
385
382
- observable_files = [
383
- get_path (f ) for f in problem0 .get (OBSERVABLE_FILES , [])
384
- ]
386
+ observable_files = [get_path (f ) for f in problem0 .observable_files ]
385
387
# If there are multiple tables, we will merge them
386
388
observable_df = (
387
389
core .concat_tables (observable_files , observables .get_observable_df )
388
390
if observable_files
389
391
else None
390
392
)
391
393
392
- mapping_files = [get_path (f ) for f in problem0 .get (MAPPING_FILES , [])]
394
+ mapping_files = [get_path (f ) for f in problem0_ .get (MAPPING_FILES , [])]
393
395
# If there are multiple tables, we will merge them
394
396
mapping_df = (
395
397
core .concat_tables (mapping_files , mapping .get_mapping_df )
@@ -406,6 +408,7 @@ def get_path(filename):
406
408
visualization_df = visualization_df ,
407
409
mapping_df = mapping_df ,
408
410
extensions_config = yaml_config .get (EXTENSIONS , {}),
411
+ config = config ,
409
412
)
410
413
411
414
@staticmethod
@@ -1184,3 +1187,50 @@ def add_measurement(
1184
1187
if self .measurement_df is not None
1185
1188
else tmp_df
1186
1189
)
1190
+
1191
+
1192
+ class VersionNumber (RootModel ):
1193
+ root : str | int
1194
+
1195
+
1196
+ class ListOfFiles (RootModel ):
1197
+ """List of files."""
1198
+
1199
+ root : list [str | AnyUrl ] = Field (..., description = "List of files." )
1200
+
1201
+ def __iter__ (self ):
1202
+ return iter (self .root )
1203
+
1204
+ def __len__ (self ):
1205
+ return len (self .root )
1206
+
1207
+ def __getitem__ (self , index ):
1208
+ return self .root [index ]
1209
+
1210
+
1211
+ class SubProblem (BaseModel ):
1212
+ """A `problems` object in the PEtab problem configuration."""
1213
+
1214
+ sbml_files : ListOfFiles = []
1215
+ measurement_files : ListOfFiles = []
1216
+ condition_files : ListOfFiles = []
1217
+ observable_files : ListOfFiles = []
1218
+ visualization_files : ListOfFiles = []
1219
+
1220
+
1221
+ class ProblemConfig (BaseModel ):
1222
+ """The PEtab problem configuration."""
1223
+
1224
+ filepath : str | AnyUrl | None = Field (
1225
+ None ,
1226
+ description = "The path to the PEtab problem configuration." ,
1227
+ exclude = True ,
1228
+ )
1229
+ base_path : str | AnyUrl | None = Field (
1230
+ None ,
1231
+ description = "The base path to resolve relative paths." ,
1232
+ exclude = True ,
1233
+ )
1234
+ format_version : VersionNumber = 1
1235
+ parameter_file : str | AnyUrl | None = None
1236
+ problems : list [SubProblem ] = []
0 commit comments