Repository: PEtab-dev/libpetab-python

Issue:
Add consistent support for pathlib.Path
Would be quite convenient if `Path`s could be used in addition path strings.

Discussion:
codecov-commenter: # [Codecov](https://codecov.io/gh/PEtab-dev/libpetab-python/pull/93?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PEtab-dev) Report
> Merging [#93](https://codecov.io/gh/PEtab-dev/libpetab-python/pull/93?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PEtab-dev) (cd5d49b) into [develop](https://codecov.io/gh/PEtab-dev/libpetab-python/commit/f6fd4896f557497a16afe3773467eb06a2614a79?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PEtab-dev) (f6fd489) will **increase** coverage by `0.04%`.
> The diff coverage is `90.56%`.

[![Impacted file tree graph](https://codecov.io/gh/PEtab-dev/libpetab-python/pull/93/graphs/tree.svg?width=650&height=150&src=pr&token=Z0YPFA5GGM&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PEtab-dev)](https://codecov.io/gh/PEtab-dev/libpetab-python/pull/93?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PEtab-dev)

```diff
@@             Coverage Diff             @@
##           develop      #93      +/-   ##
===========================================
+ Coverage    79.54%   79.58%   +0.04%     
===========================================
  Files           26       26              
  Lines         2933     2939       +6     
  Branches       708      708              
===========================================
+ Hits          2333     2339       +6     
  Misses         423      423              
  Partials       177      177              
```


| [Impacted Files](https://codecov.io/gh/PEtab-dev/libpetab-python/pull/93?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PEtab-dev) | Coverage Δ | |
|---|---|---|
| [petab/problem.py](https://codecov.io/gh/PEtab-dev/libpetab-python/pull/93/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PEtab-dev#diff-cGV0YWIvcHJvYmxlbS5weQ==) | `64.47% <33.33%> (ø)` | |
| [petab/yaml.py](https://codecov.io/gh/PEtab-dev/libpetab-python/pull/93/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PEtab-dev#diff-cGV0YWIveWFtbC5weQ==) | `90.78% <92.30%> (ø)` | |
| [petab/conditions.py](https://codecov.io/gh/PEtab-dev/libpetab-python/pull/93/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PEtab-dev#diff-cGV0YWIvY29uZGl0aW9ucy5weQ==) | `90.69% <100.00%> (+0.22%)` | :arrow_up: |
| [petab/core.py](https://codecov.io/gh/PEtab-dev/libpetab-python/pull/93/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PEtab-dev#diff-cGV0YWIvY29yZS5weQ==) | `87.02% <100.00%> (+0.09%)` | :arrow_up: |
| [petab/measurements.py](https://codecov.io/gh/PEtab-dev/libpetab-python/pull/93/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PEtab-dev#diff-cGV0YWIvbWVhc3VyZW1lbnRzLnB5) | `82.02% <100.00%> (+0.20%)` | :arrow_up: |
| [petab/observables.py](https://codecov.io/gh/PEtab-dev/libpetab-python/pull/93/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PEtab-dev#diff-cGV0YWIvb2JzZXJ2YWJsZXMucHk=) | `97.14% <100.00%> (+0.04%)` | :arrow_up: |
| [petab/parameters.py](https://codecov.io/gh/PEtab-dev/libpetab-python/pull/93/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PEtab-dev#diff-cGV0YWIvcGFyYW1ldGVycy5weQ==) | `86.53% <100.00%> (+0.08%)` | :arrow_up: |
| [petab/sbml.py](https://codecov.io/gh/PEtab-dev/libpetab-python/pull/93/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PEtab-dev#diff-cGV0YWIvc2JtbC5weQ==) | `57.97% <100.00%> (+0.30%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/PEtab-dev/libpetab-python/pull/93?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PEtab-dev).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PEtab-dev)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/PEtab-dev/libpetab-python/pull/93?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PEtab-dev). Last update [f6fd489...cd5d49b](https://codecov.io/gh/PEtab-dev/libpetab-python/pull/93?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PEtab-dev). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PEtab-dev).

dweindl: > Maybe nice to define e.g. `petab.C.TYPE_PATH = Union[str, Path]`, could make the type hints a little neater. Not sure if it can be used as `isinstance(..., TYPE_PATH)` though.

Does look very neat, agreed. Briefly thought about it, but then decided that it might be more convenient to users to directly have the types there, without looking up what other type is...
dilpath: Ah, additionally, the tests were changed to use `Path`, so maybe `str` is no longer tested? Fine as is though

Patch:
diff --git a/petab/conditions.py b/petab/conditions.py
index 4cb7853..c9ed93c 100644
--- a/petab/conditions.py
+++ b/petab/conditions.py
@@ -1,11 +1,12 @@
 """Functions operating on the PEtab condition table"""
 
-from typing import Iterable, Optional, List, Union
+from pathlib import Path
+from typing import Iterable, List, Optional, Union
 
 import numpy as np
 import pandas as pd
 
-from . import lint, core
+from . import core, lint
 from .C import *
 
 __all__ = ['get_condition_df', 'write_condition_df', 'create_condition_df',
@@ -13,7 +14,7 @@ __all__ = ['get_condition_df', 'write_condition_df', 'create_condition_df',
 
 
 def get_condition_df(
-        condition_file: Union[str, pd.DataFrame, None]
+        condition_file: Union[str, pd.DataFrame, Path, None]
 ) -> pd.DataFrame:
     """Read the provided condition file into a ``pandas.Dataframe``
 
@@ -25,7 +26,7 @@ def get_condition_df(
     if condition_file is None:
         return condition_file
 
-    if isinstance(condition_file, str):
+    if isinstance(condition_file, (str, Path)):
         condition_file = pd.read_csv(condition_file, sep='\t',
                                      float_precision='round_trip')
 
@@ -44,7 +45,7 @@ def get_condition_df(
     return condition_file
 
 
-def write_condition_df(df: pd.DataFrame, filename: str) -> None:
+def write_condition_df(df: pd.DataFrame, filename: Union[str, Path]) -> None:
     """Write PEtab condition table
 
     Arguments:
diff --git a/petab/core.py b/petab/core.py
index 42fba6b..749cadd 100644
--- a/petab/core.py
+++ b/petab/core.py
@@ -1,5 +1,5 @@
 """PEtab core functions (or functions that don't fit anywhere else)"""
-
+from pathlib import Path
 import logging
 import os
 import re
@@ -43,7 +43,7 @@ def write_simulation_df(df: pd.DataFrame, filename: str) -> None:
     df.to_csv(filename, sep='\t', index=False)
 
 
-def get_visualization_df(visualization_file: str) -> pd.DataFrame:
+def get_visualization_df(visualization_file: Union[str, Path]) -> pd.DataFrame:
     """Read PEtab visualization table
 
     Arguments:
@@ -62,7 +62,9 @@ def get_visualization_df(visualization_file: str) -> pd.DataFrame:
     return vis_spec
 
 
-def write_visualization_df(df: pd.DataFrame, filename: str) -> None:
+def write_visualization_df(
+        df: pd.DataFrame, filename: Union[str, Path]
+) -> None:
     """Write PEtab visualization table
 
     Arguments:
@@ -241,8 +243,8 @@ def is_empty(val) -> bool:
 
 
 def create_combine_archive(
-        yaml_file: str,
-        filename: str,
+        yaml_file: Union[str, Path],
+        filename: Union[str, Path],
         family_name: Optional[str] = None,
         given_name: Optional[str] = None,
         email: Optional[str] = None,
@@ -260,7 +262,7 @@ def create_combine_archive(
         organization: Organization of archive creator
     """
 
-    path_prefix = os.path.dirname(yaml_file)
+    path_prefix = os.path.dirname(str(yaml_file))
     yaml_config = yaml.load_yaml(yaml_file)
 
     # function-level import, because module-level import interfered with
@@ -285,7 +287,7 @@ def create_combine_archive(
 
     # Add PEtab files and metadata
     archive.addFile(
-        yaml_file,
+        str(yaml_file),
         os.path.basename(yaml_file),
         libcombine.KnownFormats.lookupFormat("yaml"),
         True
@@ -353,7 +355,7 @@ def create_combine_archive(
     description.addCreator(creator)
 
     archive.addMetadata(".", description)
-    archive.writeToFile(filename)
+    archive.writeToFile(str(filename))
 
 
 def unique_preserve_order(seq: Sequence) -> List:
diff --git a/petab/measurements.py b/petab/measurements.py
index 37de135..7351448 100644
--- a/petab/measurements.py
+++ b/petab/measurements.py
@@ -1,16 +1,16 @@
 """Functions operating on the PEtab measurement table"""
 # noqa: F405
 
-
 import itertools
 import numbers
-from typing import List, Union, Dict
+from pathlib import Path
+from typing import Dict, List, Union
 from warnings import warn
 
 import numpy as np
 import pandas as pd
 
-from . import (lint, core, observables)
+from . import (core, lint, observables)
 from .C import *  # noqa: F403
 
 __all__ = ['assert_overrides_match_parameter_count',
@@ -26,7 +26,7 @@ __all__ = ['assert_overrides_match_parameter_count',
 
 
 def get_measurement_df(
-        measurement_file: Union[None, str, pd.DataFrame]
+        measurement_file: Union[None, str, Path, pd.DataFrame]
 ) -> pd.DataFrame:
     """
     Read the provided measurement file into a ``pandas.Dataframe``.
@@ -40,7 +40,7 @@ def get_measurement_df(
     if measurement_file is None:
         return measurement_file
 
-    if isinstance(measurement_file, str):
+    if isinstance(measurement_file, (str, Path)):
         measurement_file = pd.read_csv(measurement_file, sep='\t',
                                        float_precision='round_trip')
 
@@ -50,7 +50,7 @@ def get_measurement_df(
     return measurement_file
 
 
-def write_measurement_df(df: pd.DataFrame, filename: str) -> None:
+def write_measurement_df(df: pd.DataFrame, filename: Union[str, Path]) -> None:
     """Write PEtab measurement table
 
     Arguments:
diff --git a/petab/observables.py b/petab/observables.py
index d19d568..fc27648 100644
--- a/petab/observables.py
+++ b/petab/observables.py
@@ -1,14 +1,15 @@
 """Functions for working with the PEtab observables table"""
 
+import re
 from collections import OrderedDict
-from typing import Union, List
+from pathlib import Path
+from typing import List, Union
 
 import libsbml
 import pandas as pd
-import re
 import sympy as sp
 
-from . import lint, core
+from . import core, lint
 from .C import *  # noqa: F403
 
 __all__ = ['create_observable_df',
@@ -20,7 +21,7 @@ __all__ = ['create_observable_df',
 
 
 def get_observable_df(
-        observable_file: Union[str, pd.DataFrame, None]
+        observable_file: Union[str, pd.DataFrame, Path, None]
 ) -> pd.DataFrame:
     """
     Read the provided observable file into a ``pandas.Dataframe``.
@@ -34,7 +35,7 @@ def get_observable_df(
     if observable_file is None:
         return observable_file
 
-    if isinstance(observable_file, str):
+    if isinstance(observable_file, (str, Path)):
         observable_file = pd.read_csv(observable_file, sep='\t',
                                       float_precision='round_trip')
 
@@ -53,7 +54,7 @@ def get_observable_df(
     return observable_file
 
 
-def write_observable_df(df: pd.DataFrame, filename: str) -> None:
+def write_observable_df(df: pd.DataFrame, filename: Union[str, Path]) -> None:
     """Write PEtab observable table
 
     Arguments:
diff --git a/petab/parameters.py b/petab/parameters.py
index 8b15bfa..0d6c28e 100644
--- a/petab/parameters.py
+++ b/petab/parameters.py
@@ -1,14 +1,15 @@
 """Functions operating on the PEtab parameter table"""
 
 import numbers
-import pandas as pd
-import numpy as np
 from collections import OrderedDict
-from typing import Iterable, Set, List, Tuple, Dict, Union
+from pathlib import Path
+from typing import Dict, Iterable, List, Set, Tuple, Union
 
 import libsbml
+import numpy as np
+import pandas as pd
 
-from . import lint, core, measurements, conditions, observables
+from . import conditions, core, lint, measurements, observables
 from .C import *  # noqa: F403
 
 __all__ = ['create_parameter_df',
@@ -27,7 +28,7 @@ __all__ = ['create_parameter_df',
 
 
 def get_parameter_df(
-        parameter_file: Union[str, List[str], pd.DataFrame, None]
+        parameter_file: Union[str, Path, List[str], pd.DataFrame, None]
 ) -> pd.DataFrame:
     """
     Read the provided parameter file into a ``pandas.Dataframe``.
@@ -46,7 +47,7 @@ def get_parameter_df(
     if isinstance(parameter_file, pd.DataFrame):
         parameter_df = parameter_file
 
-    if isinstance(parameter_file, str):
+    if isinstance(parameter_file, (str, Path)):
         parameter_df = pd.read_csv(parameter_file, sep='\t',
                                    float_precision='round_trip')
 
@@ -82,7 +83,7 @@ def get_parameter_df(
     return parameter_df
 
 
-def write_parameter_df(df: pd.DataFrame, filename: str) -> None:
+def write_parameter_df(df: pd.DataFrame, filename: Union[str, Path]) -> None:
     """Write PEtab parameter table
 
     Arguments:
diff --git a/petab/problem.py b/petab/problem.py
index cf6ea1f..69f1f10 100644
--- a/petab/problem.py
+++ b/petab/problem.py
@@ -1,8 +1,7 @@
 """PEtab Problem class"""
 
 import os
-# Renamed to `Path_` to avoid unknown error during Sphinx doc build
-from pathlib import Path as Path_
+from pathlib import Path
 import tempfile
 from typing import Dict, Iterable, List, Optional, Union
 from warnings import warn
@@ -237,7 +236,7 @@ class Problem:
         )
 
     @staticmethod
-    def from_combine(filename: str) -> 'Problem':
+    def from_combine(filename: Union[Path, str]) -> 'Problem':
         """Read PEtab COMBINE archive (http://co.mbine.org/documents/archive).
 
         See also :py:func:`petab.create_combine_archive`.
@@ -258,7 +257,7 @@ class Problem:
                 "(python-libcombine) must be installed.")
 
         archive = libcombine.CombineArchive()
-        if archive.initializeFromArchive(filename) is None:
+        if archive.initializeFromArchive(str(filename)) is None:
             print(f"Invalid Combine Archive: {filename}")
             return None
 
@@ -273,7 +272,7 @@ class Problem:
 
     def to_files_generic(
         self,
-        prefix_path: Union[str, Path_],
+        prefix_path: Union[str, Path],
     ) -> None:
         """Save a PEtab problem to generic file names.
 
@@ -292,7 +291,7 @@ class Problem:
         Returns:
             The path to the PEtab problem YAML file.
         """
-        prefix_path = Path_(prefix_path)
+        prefix_path = Path(prefix_path)
 
         # Generate generic filenames for data tables in the PEtab problem that
         # contain data.
@@ -326,7 +325,7 @@ class Problem:
                  visualization_file: Optional[str] = None,
                  observable_file: Optional[str] = None,
                  yaml_file: Optional[str] = None,
-                 prefix_path: Optional[Union[str, Path_]] = None,
+                 prefix_path: Optional[Union[str, Path]] = None,
                  relative_paths: bool = True,) -> None:
         """
         Write PEtab tables to files for this problem
@@ -359,9 +358,9 @@ class Problem:
                 If a destination was provided for a non-existing entity.
         """
         if prefix_path is not None:
-            prefix_path = Path_(prefix_path)
+            prefix_path = Path(prefix_path)
 
-            def add_prefix(path0: Union[None, str, Path_]) -> str:
+            def add_prefix(path0: Union[None, str, Path]) -> str:
                 if path0 is None:
                     return path0
                 return str(prefix_path / path0)
diff --git a/petab/sbml.py b/petab/sbml.py
index caa62f9..5999e6a 100644
--- a/petab/sbml.py
+++ b/petab/sbml.py
@@ -1,10 +1,12 @@
 """Functions for interacting with SBML models"""
-from warnings import warn
 import logging
-from pandas.io.common import get_handle, is_url, is_file_like
 import re
-from typing import Dict, Any, List, Union, Tuple
+from pathlib import Path
+from typing import Any, Dict, List, Tuple, Union
+from warnings import warn
+
 import libsbml
+from pandas.io.common import get_handle, is_file_like, is_url
 
 logger = logging.getLogger(__name__)
 __all__ = ['add_global_parameter',
@@ -413,7 +415,10 @@ def get_model_parameters(sbml_model: libsbml.Model, with_values=False
             if sbml_model.getAssignmentRuleByVariable(p.getId()) is None}
 
 
-def write_sbml(sbml_doc: libsbml.SBMLDocument, filename: str) -> None:
+def write_sbml(
+        sbml_doc: libsbml.SBMLDocument,
+        filename: Union[Path, str]
+) -> None:
     """Write PEtab visualization table
 
     Arguments:
@@ -421,7 +426,7 @@ def write_sbml(sbml_doc: libsbml.SBMLDocument, filename: str) -> None:
         filename: Destination file name
     """
     sbml_writer = libsbml.SBMLWriter()
-    ret = sbml_writer.writeSBMLToFile(sbml_doc, filename)
+    ret = sbml_writer.writeSBMLToFile(sbml_doc, str(filename))
     if not ret:
         raise RuntimeError(f"libSBML reported error {ret} when trying to "
                            f"create SBML file {filename}.")
diff --git a/petab/yaml.py b/petab/yaml.py
index 9d71280..89f7559 100644
--- a/petab/yaml.py
+++ b/petab/yaml.py
@@ -1,8 +1,8 @@
 """Code regarding the PEtab YAML config files"""
 
 import os
-from pathlib import Path as _Path
-from typing import Any, Dict, Union, Optional, List
+from pathlib import Path
+from typing import Any, Dict, List, Optional, Union
 
 import jsonschema
 import numpy as np
@@ -61,7 +61,9 @@ def validate_yaml_syntax(
 
 
 def validate_yaml_semantics(
-        yaml_config: Union[Dict, str], path_prefix: Optional[str] = None):
+        yaml_config: Union[Dict, str],
+        path_prefix: Optional[str] = None
+):
     """Validate PEtab YAML file semantics
 
     Check for existence of files. Assumes valid syntax.
@@ -80,8 +82,8 @@ def validate_yaml_semantics(
         AssertionError: in case of problems
     """
     if not path_prefix:
-        if isinstance(yaml_config, str):
-            path_prefix = os.path.dirname(yaml_config)
+        if isinstance(yaml_config, (str, Path)):
+            path_prefix = os.path.dirname(str(yaml_config))
         else:
             path_prefix = ""
 
@@ -109,7 +111,7 @@ def validate_yaml_semantics(
                     _check_file(os.path.join(path_prefix, filename), field)
 
 
-def load_yaml(yaml_config: Union[Dict, str]) -> Dict:
+def load_yaml(yaml_config: Union[Dict, Path, str]) -> Dict:
     """Load YAML
 
     Convenience function to allow for providing YAML inputs as filename, URL
@@ -132,7 +134,7 @@ def load_yaml(yaml_config: Union[Dict, str]) -> Dict:
     return yaml.safe_load(handle)
 
 
-def is_composite_problem(yaml_config: Union[Dict, str]) -> bool:
+def is_composite_problem(yaml_config: Union[Dict, str, Path]) -> bool:
     """Does this YAML file comprise multiple models?
 
     Arguments:
@@ -164,7 +166,10 @@ def assert_single_condition_and_sbml_file(problem_config: Dict) -> None:
             'implemented.')
 
 
-def write_yaml(yaml_config: Dict[str, Any], filename: str) -> None:
+def write_yaml(
+        yaml_config: Dict[str, Any],
+        filename: Union[str, Path]
+) -> None:
     """Write PEtab YAML file
 
     Arguments:
@@ -178,13 +183,14 @@ def write_yaml(yaml_config: Dict[str, Any], filename: str) -> None:
 
 
 def create_problem_yaml(
-        sbml_files: Union[str, List[str]],
-        condition_files: Union[str, List[str]],
-        measurement_files: Union[str, List[str]],
-        parameter_file: str,
-        observable_files: Union[str, List[str]],
-        yaml_file: str,
-        visualization_files: Optional[Union[str, List[str]]] = None,
+        sbml_files: Union[str, Path, List[Union[str, Path]]],
+        condition_files: Union[str, Path, List[Union[str, Path]]],
+        measurement_files: Union[str, Path, List[Union[str, Path]]],
+        parameter_file: Union[str, Path],
+        observable_files: Union[str, Path, List[Union[str, Path]]],
+        yaml_file: Union[str, Path],
+        visualization_files:
+        Optional[Union[str, Path, List[Union[str, Path]]]] = None,
         relative_paths: bool = True,
 ) -> None:
     """
@@ -204,19 +210,19 @@ def create_problem_yaml(
             location of the YAML file. If ``False``, then paths are left
             unchanged.
     """
-    if isinstance(sbml_files, str):
+    if isinstance(sbml_files, (Path, str)):
         sbml_files = [sbml_files]
-    if isinstance(condition_files, str):
+    if isinstance(condition_files, (Path, str)):
         condition_files = [condition_files]
-    if isinstance(measurement_files, str):
+    if isinstance(measurement_files, (Path, str)):
         measurement_files = [measurement_files]
-    if isinstance(observable_files, str):
+    if isinstance(observable_files, (Path, str)):
         observable_files = [observable_files]
-    if isinstance(visualization_files, str):
+    if isinstance(visualization_files, (Path, str)):
         visualization_files = [visualization_files]
 
     if relative_paths:
-        yaml_file_dir = _Path(yaml_file).parent
+        yaml_file_dir = Path(yaml_file).parent
 
         def get_rel_to_yaml(paths: Union[List[str], None]):
             if paths is None:


Test patch:
diff --git a/tests/test_combine.py b/tests/test_combine.py
index 47f8e57..0991d41 100644
--- a/tests/test_combine.py
+++ b/tests/test_combine.py
@@ -1,9 +1,8 @@
 """Test COMBINE archive"""
-import os
 import tempfile
+from pathlib import Path
 
 import pandas as pd
-
 import petab
 from petab.C import *
 
@@ -69,21 +68,22 @@ def test_combine_archive(minimal_sbml_model):
     with tempfile.TemporaryDirectory(prefix='petab_test_combine_archive') \
             as tempdir:
         # Write test data
-        petab.write_sbml(document, os.path.join(tempdir, sbml_file_name))
+        outdir = Path(tempdir)
+        petab.write_sbml(document, outdir / sbml_file_name)
         petab.write_measurement_df(
-            measurement_df, os.path.join(tempdir, measurement_file_name))
+            measurement_df, outdir / measurement_file_name)
         petab.write_parameter_df(
-            parameter_df, os.path.join(tempdir, parameter_file_name))
+            parameter_df, outdir / parameter_file_name)
         petab.write_observable_df(
-            observable_df, os.path.join(tempdir, observable_file_name))
+            observable_df, outdir / observable_file_name)
         petab.write_condition_df(
-            condition_df, os.path.join(tempdir, condition_file_name))
-        petab.write_yaml(yaml_config, os.path.join(tempdir, yaml_file_name))
+            condition_df, outdir / condition_file_name)
+        petab.write_yaml(yaml_config, outdir / yaml_file_name)
 
-        archive_file_name = os.path.join(tempdir, 'test.omex')
+        archive_file_name = outdir / 'test.omex'
 
         # Create COMBINE archive
-        petab.create_combine_archive(os.path.join(tempdir, yaml_file_name),
+        petab.create_combine_archive(outdir / yaml_file_name,
                                      archive_file_name, family_name="Tester")
 
         # Read COMBINE archive
diff --git a/tests/test_conditions.py b/tests/test_conditions.py
index d644362..b507abf 100644
--- a/tests/test_conditions.py
+++ b/tests/test_conditions.py
@@ -1,11 +1,12 @@
 """Tests related to petab.conditions"""
-import numpy as np
-import pandas as pd
-import tempfile
-import pytest
 import os
+import tempfile
+from pathlib import Path
 
+import numpy as np
+import pandas as pd
 import petab
+import pytest
 from petab import conditions
 from petab.C import *
 
@@ -76,8 +77,8 @@ def test_write_condition_df():
         'fixedParameter1': [1.0, 2.0]
     }).set_index(CONDITION_ID)
 
-    with tempfile.NamedTemporaryFile(mode='w', delete=True) as fh:
-        file_name = fh.name
+    with tempfile.TemporaryDirectory() as temp_dir:
+        file_name = Path(temp_dir) / "conditions.tsv"
         petab.write_condition_df(condition_df, file_name)
         re_df = petab.get_condition_df(file_name)
         assert (condition_df == re_df).all().all()
diff --git a/tests/test_measurements.py b/tests/test_measurements.py
index 8a13771..fe74933 100644
--- a/tests/test_measurements.py
+++ b/tests/test_measurements.py
@@ -1,8 +1,9 @@
 """Tests related to petab.measurements"""
-import numpy as np
-import pandas as pd
 import tempfile
+from pathlib import Path
 
+import numpy as np
+import pandas as pd
 import petab
 from petab.C import *
 
@@ -36,8 +37,8 @@ def test_write_measurement_df():
         NOISE_PARAMETERS: ['p3;p4', 'p5']
     })
 
-    with tempfile.NamedTemporaryFile(mode='w', delete=True) as fh:
-        file_name = fh.name
+    with tempfile.TemporaryDirectory() as temp_dir:
+        file_name = Path(temp_dir) / "parameters.tsv"
         petab.write_measurement_df(measurement_df, file_name)
         re_df = petab.get_measurement_df(file_name).replace(np.nan, '')
         assert (measurement_df == re_df).all().all()
diff --git a/tests/test_observables.py b/tests/test_observables.py
index d877753..80722f0 100644
--- a/tests/test_observables.py
+++ b/tests/test_observables.py
@@ -1,9 +1,10 @@
 """Tests for petab.observables"""
-import pandas as pd
-import pytest
 import tempfile
+from pathlib import Path
 
+import pandas as pd
 import petab
+import pytest
 from petab.C import *
 
 # import fixtures
@@ -53,8 +54,8 @@ def test_write_observable_df():
         NOISE_FORMULA: [1],
     }).set_index(OBSERVABLE_ID)
 
-    with tempfile.NamedTemporaryFile(mode='w', delete=True) as fh:
-        file_name = fh.name
+    with tempfile.TemporaryDirectory() as temp_dir:
+        file_name = Path(temp_dir) / "observables.tsv"
         petab.write_observable_df(observable_df, file_name)
         re_df = petab.get_observable_df(file_name)
         assert (observable_df == re_df).all().all()
diff --git a/tests/test_parameters.py b/tests/test_parameters.py
index 33f1997..1727a73 100644
--- a/tests/test_parameters.py
+++ b/tests/test_parameters.py
@@ -1,10 +1,11 @@
 """Tests for petab/parameters.py"""
 import tempfile
-import pytest
+from pathlib import Path
+
 import numpy as np
 import pandas as pd
-
 import petab
+import pytest
 from petab.C import *
 
 
@@ -116,8 +117,8 @@ def test_write_parameter_df():
         PARAMETER_NAME: ['ɑ', 'β'],
     }).set_index(PARAMETER_ID)
 
-    with tempfile.NamedTemporaryFile(mode='w', delete=True) as fh:
-        file_name = fh.name
+    with tempfile.TemporaryDirectory() as temp_dir:
+        file_name = Path(temp_dir) / "parameters.tsv"
         petab.write_parameter_df(parameter_df, file_name)
         re_df = petab.get_parameter_df(file_name)
         assert (parameter_df == re_df).all().all()