diff --git a/python/cable_cell_io.cpp b/python/cable_cell_io.cpp
index b5c1272cf5cc171460a6d6f3d7d45054e3d4fe13..2ff3ac8bb670a71609f51e3d02dacca11957e705 100644
--- a/python/cable_cell_io.cpp
+++ b/python/cable_cell_io.cpp
@@ -9,14 +9,18 @@
 #include <arborio/cableio.hpp>
 
 #include "error.hpp"
+#include "util.hpp"
 #include "strprintf.hpp"
 
 namespace pyarb {
 
-arborio::cable_cell_component load_component(const std::string& fname) {
+namespace py = pybind11;
+
+arborio::cable_cell_component load_component(py::object fn) {
+    const auto fname = util::to_path(fn);
     std::ifstream fid{fname};
     if (!fid.good()) {
-        throw pyarb_error("Can't open file '{}'" + fname);
+        throw arb::file_not_found_error(fname);
     }
     auto component = arborio::parse_component(fid);
     if (!component) {
@@ -26,13 +30,13 @@ arborio::cable_cell_component load_component(const std::string& fname) {
 };
 
 template<typename T>
-void write_component(const T& component, const std::string& fname) {
-    std::ofstream fid(fname);
+void write_component(const T& component, py::object fn) {
+    std::ofstream fid(util::to_path(fn));
     arborio::write_component(fid, component, arborio::meta_data{});
 }
 
-void write_component(const arborio::cable_cell_component& component, const std::string& fname) {
-    std::ofstream fid(fname);
+void write_component(const arborio::cable_cell_component& component, py::object fn) {
+    std::ofstream fid(util::to_path(fn));
     arborio::write_component(fid, component);
 }
 
@@ -43,40 +47,40 @@ void register_cable_loader(pybind11::module& m) {
           "Load arbor-component (decor, morphology, label_dict, cable_cell) from file.");
 
     m.def("write_component",
-          [](const arborio::cable_cell_component& d, const std::string& fname) {
-            return write_component(d, fname);
+          [](const arborio::cable_cell_component& d, py::object fn) {
+            return write_component(d, fn);
           },
           pybind11::arg_v("object", "the cable_component object."),
-          pybind11::arg_v("filename", "the name of the file."),
+          pybind11::arg_v("filename", "the path of the file."),
           "Write cable_component to file.");
 
     m.def("write_component",
-          [](const arb::decor& d, const std::string& fname) {
-            return write_component<arb::decor>(d, fname);
+          [](const arb::decor& d, py::object fn) {
+            return write_component<arb::decor>(d, fn);
           },
           pybind11::arg_v("object", "the decor object."),
           pybind11::arg_v("filename", "the name of the file."),
           "Write decor to file.");
 
     m.def("write_component",
-          [](const arb::label_dict& d, const std::string& fname) {
-            return write_component<arb::label_dict>(d, fname);
+          [](const arb::label_dict& d, py::object fn) {
+            return write_component<arb::label_dict>(d, fn);
           },
           pybind11::arg_v("object", "the label_dict object."),
           pybind11::arg_v("filename", "the name of the file."),
           "Write label_dict to file.");
 
     m.def("write_component",
-          [](const arb::morphology& d, const std::string& fname) {
-            return write_component<arb::morphology>(d, fname);
+          [](const arb::morphology& d, py::object fn) {
+            return write_component<arb::morphology>(d, fn);
           },
           pybind11::arg_v("object", "the morphology object."),
           pybind11::arg_v("filename", "the name of the file."),
           "Write morphology to file.");
 
     m.def("write_component",
-          [](const arb::cable_cell& d, const std::string& fname) {
-            return write_component<arb::cable_cell>(d, fname);
+          [](const arb::cable_cell& d, py::object fn) {
+            return write_component<arb::cable_cell>(d, fn);
           },
           pybind11::arg_v("object", "the cable_cell object."),
           pybind11::arg_v("filename", "the name of the file."),
diff --git a/python/mechanism.cpp b/python/mechanism.cpp
index a774cad46c9f6fd5e8cf9fa33d6e9389d964c13f..80db103093da13430aefdcd59c7e2a8c2ca90627 100644
--- a/python/mechanism.cpp
+++ b/python/mechanism.cpp
@@ -11,6 +11,7 @@
 
 #include "arbor/mechinfo.hpp"
 
+#include "util.hpp"
 #include "conversion.hpp"
 #include "strprintf.hpp"
 
@@ -197,7 +198,7 @@ void register_mechanisms(pybind11::module& m) {
     m.def("default_catalogue", [](){return arb::global_default_catalogue();});
     m.def("allen_catalogue", [](){return arb::global_allen_catalogue();});
     m.def("bbp_catalogue", [](){return arb::global_bbp_catalogue();});
-    m.def("load_catalogue", [](const std::string& fn){return arb::load_catalogue(fn);});
+    m.def("load_catalogue", [](pybind11::object fn) { return arb::load_catalogue(util::to_string(fn)); });
 
     // arb::mechanism_desc
     // For specifying a mechanism in the cable_cell interface.
diff --git a/python/morphology.cpp b/python/morphology.cpp
index a5d3fb0d15804616503c23b69c9109b67a5d19ca..8f4293ca13dba4128ee1f66e9ff5fc32c3ec8f54 100644
--- a/python/morphology.cpp
+++ b/python/morphology.cpp
@@ -20,6 +20,7 @@
 #include <arborio/neuroml.hpp>
 #endif
 
+#include "util.hpp"
 #include "error.hpp"
 #include "proxy.hpp"
 #include "strprintf.hpp"
@@ -250,10 +251,11 @@ void register_morphology(py::module& m) {
     // Function that creates a morphology from an swc file.
     // Wraps calls to C++ functions arborio::parse_swc() and arborio::load_swc_arbor().
     m.def("load_swc_arbor",
-        [](std::string fname) {
+        [](py::object fn) {
+            const auto fname = util::to_path(fn);
             std::ifstream fid{fname};
             if (!fid.good()) {
-                throw pyarb_error(util::pprintf("can't open file '{}'", fname));
+                throw arb::file_not_found_error(fname);
             }
             try {
                 auto data = arborio::parse_swc(fid);
@@ -275,10 +277,11 @@ void register_morphology(py::module& m) {
         "  are no gaps in the resulting morphology.");
 
     m.def("load_swc_neuron",
-        [](std::string fname) {
+        [](py::object fn) {
+            const auto fname = util::to_path(fn);
             std::ifstream fid{fname};
             if (!fid.good()) {
-                throw pyarb_error(util::pprintf("can't open file '{}'", fname));
+                throw arb::file_not_found_error(fname);
             }
             try {
                 auto data = arborio::parse_swc(fid);
@@ -383,10 +386,11 @@ void register_morphology(py::module& m) {
     neuroml
         // constructors
         .def(py::init(
-            [](std::string fname) {
+            [](py::object fn) {
+                const auto fname = util::to_path(fn);
                 std::ifstream fid{fname};
                 if (!fid.good()) {
-                    throw pyarb_error(util::pprintf("can't open file '{}'", fname));
+                    throw arb::file_not_found_error(fname);
                 }
                 try {
                     std::string string_data((std::istreambuf_iterator<char>(fid)),
diff --git a/python/util.hpp b/python/util.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..fe1b6b07d100b7e46fe41b82029aa51282344b33
--- /dev/null
+++ b/python/util.hpp
@@ -0,0 +1,27 @@
+#pragma once
+
+#include <pybind11/pybind11.h>
+
+#include "strprintf.hpp"
+
+namespace pyarb {
+namespace util {
+
+namespace py = pybind11;
+
+inline
+std::string to_path(py::object fn) {
+    if (py::isinstance<py::str>(fn)) {
+        return std::string{py::str(fn)};
+    }
+    else if (py::isinstance(fn,
+                            py::module_::import("pathlib").attr("Path"))) {
+        return std::string{py::str(fn)};
+    }
+    throw std::runtime_error(
+        util::strprintf("Cannot convert objects of type '{}' to a path-like.",
+                        std::string{py::str(fn.get_type())}));
+}
+
+}
+}