Skip to content

Commit c3236cc

Browse files
dweindldilpath
andauthored
GHA: Run tests on windows-latest, macos-latest, ubuntu-latest (#91)
* Run test suite on windows-latest, macos-latest, ubuntu-latest * Fixed various issues related to temporary files on Windows * Add support for pathlib.Path to some functions missed in #93 * Fix loading remote yaml files on Windows Closes #80 Co-authored-by: Dilan Pathirana <[email protected]>
1 parent 1bd8e1d commit c3236cc

File tree

9 files changed

+184
-170
lines changed

9 files changed

+184
-170
lines changed

.github/workflows/ci_tests.yml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ on:
88

99
jobs:
1010
build:
11-
runs-on: ubuntu-latest
1211
strategy:
1312
matrix:
13+
platform: [windows-latest, macos-latest, ubuntu-latest]
1414
python-version: [3.7, 3.8, 3.9, "3.10"]
15+
runs-on: ${{ matrix.platform }}
1516

1617
steps:
1718
- name: Check out repository
@@ -36,13 +37,18 @@ jobs:
3637
pip install -r .ci_pip_reqs.txt
3738
pip install -e .[reports,combine]
3839
40+
- name: Run flake8
41+
run: |
42+
python -m flake8 --exclude=build,doc,example,tmp --extend-ignore=F403,F405
43+
if: matrix.platform == 'ubuntu-latest'
44+
3945
- name: Run tests
4046
run: |
4147
pytest --cov --cov-report=xml tests
42-
python -m flake8 --exclude=build,doc,example,tmp --extend-ignore=F403,F405
4348
4449
- name: Coverage
4550
uses: codecov/codecov-action@v1
4651
with:
4752
token: ${{ secrets.CODECOV_TOKEN }}
4853
file: ./coverage.xml
54+
if: matrix.platform == 'ubuntu-latest'

petab/core.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,8 @@ def flatten_timepoint_specific_output_overrides(
179179

180180

181181
def concat_tables(
182-
tables: Union[str, pd.DataFrame, Iterable[Union[pd.DataFrame, str]]],
182+
tables: Union[str, Path, pd.DataFrame,
183+
Iterable[Union[pd.DataFrame, str, Path]]],
183184
file_parser: Optional[Callable] = None
184185
) -> pd.DataFrame:
185186
"""Concatenate DataFrames provided as DataFrames or filenames, and a parser
@@ -198,14 +199,14 @@ def concat_tables(
198199
if isinstance(tables, pd.DataFrame):
199200
return tables
200201

201-
if isinstance(tables, str):
202+
if isinstance(tables, (str, Path)):
202203
return file_parser(tables)
203204

204205
df = pd.DataFrame()
205206

206207
for tmp_df in tables:
207208
# load from file, if necessary
208-
if isinstance(tmp_df, str):
209+
if isinstance(tmp_df, (str, Path)):
209210
tmp_df = file_parser(tmp_df)
210211

211212
df = df.append(tmp_df, sort=False,

petab/problem.py

Lines changed: 63 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
"""PEtab Problem class"""
22

33
import os
4-
from pathlib import Path
54
import tempfile
5+
from pathlib import Path, PurePosixPath
66
from typing import Dict, Iterable, List, Optional, Union
7+
from urllib.parse import unquote, urlparse, urlunparse
78
from warnings import warn
89

9-
import pandas as pd
1010
import libsbml
11-
from . import (parameter_mapping, measurements, conditions, parameters,
12-
sampling, sbml, yaml, core, observables, format_version)
11+
import pandas as pd
12+
13+
from . import (conditions, core, format_version, measurements, observables,
14+
parameter_mapping, parameters, sampling, sbml, yaml)
1315
from .C import * # noqa: F403
1416

1517
__all__ = ['Problem', 'get_default_condition_file_name',
@@ -88,13 +90,18 @@ def __setstate__(self, state):
8890
self.__dict__.update(state)
8991

9092
@staticmethod
91-
def from_files(sbml_file: str = None,
92-
condition_file: str = None,
93-
measurement_file: Union[str, Iterable[str]] = None,
94-
parameter_file: Union[str, List[str]] = None,
95-
visualization_files: Union[str, Iterable[str]] = None,
96-
observable_files: Union[str, Iterable[str]] = None
97-
) -> 'Problem':
93+
def from_files(
94+
sbml_file: Union[str, Path, None] = None,
95+
condition_file: Union[str, Path, None] = None,
96+
measurement_file: Union[str, Path,
97+
Iterable[Union[str, Path]]] = None,
98+
parameter_file: Union[str, Path,
99+
Iterable[Union[str, Path]]] = None,
100+
visualization_files: Union[str, Path,
101+
Iterable[Union[str, Path]]] = None,
102+
observable_files: Union[str, Path,
103+
Iterable[Union[str, Path]]] = None
104+
) -> 'Problem':
98105
"""
99106
Factory method to load model and tables from files.
100107
@@ -146,18 +153,41 @@ def from_files(sbml_file: str = None,
146153
visualization_df=visualization_df)
147154

148155
@staticmethod
149-
def from_yaml(yaml_config: Union[Dict, str]) -> 'Problem':
156+
def from_yaml(yaml_config: Union[Dict, Path, str]) -> 'Problem':
150157
"""
151158
Factory method to load model and tables as specified by YAML file.
152159
153160
Arguments:
154161
yaml_config: PEtab configuration as dictionary or YAML file name
155162
"""
163+
if isinstance(yaml_config, Path):
164+
yaml_config = str(yaml_config)
165+
166+
get_path = lambda filename: filename # noqa: E731
156167
if isinstance(yaml_config, str):
157-
path_prefix = os.path.dirname(yaml_config)
168+
yaml_path = yaml_config
158169
yaml_config = yaml.load_yaml(yaml_config)
159-
else:
160-
path_prefix = ""
170+
171+
# yaml_config may be path or URL
172+
path_url = urlparse(yaml_path)
173+
if not path_url.scheme or \
174+
(path_url.scheme != 'file' and not path_url.netloc):
175+
# a regular file path string
176+
path_prefix = Path(yaml_path).parent
177+
get_path = lambda filename: \
178+
path_prefix / filename # noqa: E731
179+
else:
180+
# a URL
181+
# extract parent path from
182+
url_path = unquote(urlparse(yaml_path).path)
183+
parent_path = str(PurePosixPath(url_path).parent)
184+
path_prefix = urlunparse(
185+
(path_url.scheme, path_url.netloc, parent_path,
186+
path_url.params, path_url.query, path_url.fragment)
187+
)
188+
# need "/" on windows, not "\"
189+
get_path = lambda filename: \
190+
f"{path_prefix}/{filename}" # noqa: E731
161191

162192
if yaml.is_composite_problem(yaml_config):
163193
raise ValueError('petab.Problem.from_yaml() can only be used for '
@@ -176,26 +206,21 @@ def from_yaml(yaml_config: Union[Dict, str]) -> 'Problem':
176206

177207
if isinstance(yaml_config[PARAMETER_FILE], list):
178208
parameter_file = [
179-
os.path.join(path_prefix, f)
180-
for f in yaml_config[PARAMETER_FILE]
209+
get_path(f) for f in yaml_config[PARAMETER_FILE]
181210
]
182211
else:
183-
parameter_file = os.path.join(
184-
path_prefix, yaml_config[PARAMETER_FILE])
212+
parameter_file = get_path(yaml_config[PARAMETER_FILE])
185213

186214
return Problem.from_files(
187-
sbml_file=os.path.join(path_prefix, problem0[SBML_FILES][0]),
188-
measurement_file=[os.path.join(path_prefix, f)
215+
sbml_file=get_path(problem0[SBML_FILES][0]),
216+
measurement_file=[get_path(f)
189217
for f in problem0[MEASUREMENT_FILES]],
190-
condition_file=os.path.join(
191-
path_prefix, problem0[CONDITION_FILES][0]),
218+
condition_file=get_path(problem0[CONDITION_FILES][0]),
192219
parameter_file=parameter_file,
193220
visualization_files=[
194-
os.path.join(path_prefix, f)
195-
for f in problem0.get(VISUALIZATION_FILES, [])],
221+
get_path(f) for f in problem0.get(VISUALIZATION_FILES, [])],
196222
observable_files=[
197-
os.path.join(path_prefix, f)
198-
for f in problem0.get(OBSERVABLE_FILES, [])]
223+
get_path(f) for f in problem0.get(OBSERVABLE_FILES, [])]
199224
)
200225

201226
@staticmethod
@@ -273,7 +298,7 @@ def from_combine(filename: Union[Path, str]) -> 'Problem':
273298
def to_files_generic(
274299
self,
275300
prefix_path: Union[str, Path],
276-
) -> None:
301+
) -> str:
277302
"""Save a PEtab problem to generic file names.
278303
279304
The PEtab problem YAML file is always created. PEtab data files are
@@ -318,15 +343,16 @@ def to_files_generic(
318343
return str(prefix_path / filenames['yaml_file'])
319344

320345
def to_files(self,
321-
sbml_file: Optional[str] = None,
322-
condition_file: Optional[str] = None,
323-
measurement_file: Optional[str] = None,
324-
parameter_file: Optional[str] = None,
325-
visualization_file: Optional[str] = None,
326-
observable_file: Optional[str] = None,
327-
yaml_file: Optional[str] = None,
328-
prefix_path: Optional[Union[str, Path]] = None,
329-
relative_paths: bool = True,) -> None:
346+
sbml_file: Union[None, str, Path] = None,
347+
condition_file: Union[None, str, Path] = None,
348+
measurement_file: Union[None, str, Path] = None,
349+
parameter_file: Union[None, str, Path] = None,
350+
visualization_file: Union[None, str, Path] = None,
351+
observable_file: Union[None, str, Path] = None,
352+
yaml_file: Union[None, str, Path] = None,
353+
prefix_path: Union[None, str, Path] = None,
354+
relative_paths: bool = True,
355+
) -> None:
330356
"""
331357
Write PEtab tables to files for this problem
332358

petab/visualize/data_overview.py

Lines changed: 13 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
Functions for creating an overview report of a PEtab problem
33
"""
44

5-
import os
5+
from pathlib import Path
66
from shutil import copyfile
7+
from typing import Union
78

89
import pandas as pd
910
import petab
@@ -12,16 +13,21 @@
1213
__all__ = ['create_report']
1314

1415

15-
def create_report(problem: petab.Problem, model_name: str) -> None:
16+
def create_report(
17+
problem: petab.Problem,
18+
model_name: str,
19+
output_path: Union[str, Path] = ''
20+
) -> None:
1621
"""Create an HTML overview data / model overview report
1722
1823
Arguments:
1924
problem: PEtab problem
2025
model_name: Name of the model, used for file name for report
26+
output_path: Output directory
2127
"""
2228

23-
template_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)),
24-
'templates')
29+
template_dir = Path(__file__).absolute().parent / 'templates'
30+
output_path = Path(output_path)
2531
template_file = "report.html"
2632

2733
data_per_observable = get_data_per_observable(problem.measurement_df)
@@ -30,18 +36,17 @@ def create_report(problem: petab.Problem, model_name: str) -> None:
3036

3137
# Setup template engine
3238
import jinja2
33-
template_loader = jinja2.FileSystemLoader(
34-
searchpath=template_dir)
39+
template_loader = jinja2.FileSystemLoader(searchpath=template_dir)
3540
template_env = jinja2.Environment(loader=template_loader)
3641
template = template_env.get_template(template_file)
3742

3843
# Render and save
3944
output_text = template.render(problem=problem, model_name=model_name,
4045
data_per_observable=data_per_observable,
4146
num_conditions=num_conditions)
42-
with open(model_name + '.html', 'w') as html_file:
47+
with open(output_path / f'{model_name}.html', 'w') as html_file:
4348
html_file.write(output_text)
44-
copyfile(os.path.join(template_dir, 'mystyle.css'), 'mystyle.css')
49+
copyfile(template_dir / 'mystyle.css', output_path / 'mystyle.css')
4550

4651

4752
def get_data_per_observable(measurement_df: pd.DataFrame) -> pd.DataFrame:
@@ -70,24 +75,3 @@ def get_data_per_observable(measurement_df: pd.DataFrame) -> pd.DataFrame:
7075
data_per_observable = data_per_observable.astype(int)
7176

7277
return data_per_observable
73-
74-
75-
def main():
76-
"""Data overview generation with example data from the repository for
77-
testing
78-
"""
79-
80-
root_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
81-
'..', '..', 'doc/example/example_Fujita/')
82-
problem = petab.Problem.from_files(
83-
sbml_file=os.path.join(root_path, 'Fujita_model.xml'),
84-
condition_file=os.path.join(root_path,
85-
'Fujita_experimentalCondition.tsv'),
86-
measurement_file=os.path.join(root_path, 'Fujita_measurementData.tsv'),
87-
parameter_file=os.path.join(root_path, 'Fujita_parameters.tsv'),
88-
)
89-
create_report(problem, 'Fujita')
90-
91-
92-
if __name__ == '__main__':
93-
main()

petab/yaml.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
'create_problem_yaml']
2020

2121

22-
def validate(yaml_config: Union[Dict, str], path_prefix: Optional[str] = None):
22+
def validate(
23+
yaml_config: Union[Dict, str, Path],
24+
path_prefix: Union[None, str, Path] = None):
2325
"""Validate syntax and semantics of PEtab config YAML
2426
2527
Arguments:
@@ -37,7 +39,7 @@ def validate(yaml_config: Union[Dict, str], path_prefix: Optional[str] = None):
3739

3840

3941
def validate_yaml_syntax(
40-
yaml_config: Union[Dict, str],
42+
yaml_config: Union[Dict, str, Path],
4143
schema: Union[None, Dict, str] = None):
4244
"""Validate PEtab YAML file syntax
4345
@@ -61,8 +63,8 @@ def validate_yaml_syntax(
6163

6264

6365
def validate_yaml_semantics(
64-
yaml_config: Union[Dict, str],
65-
path_prefix: Optional[str] = None
66+
yaml_config: Union[Dict, str, Path],
67+
path_prefix: Union[None, str, Path] = None
6668
):
6769
"""Validate PEtab YAML file semantics
6870

0 commit comments

Comments
 (0)