Skip to content

Commit

Permalink
🐍 Be more lenient when accepting args to file I/O (#1819)
Browse files Browse the repository at this point in the history
PyArbor I/O routines now try to cast `filename` arguments to a string.
This allows passing `pathlib.Path` objects.

Example
```py
here = Path(__file__).parent
cat = A.load_catalogue(here / 'local-catalogue.so')
```
  • Loading branch information
thorstenhater authored Feb 10, 2022
1 parent ba781cb commit 2176e15
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 24 deletions.
38 changes: 21 additions & 17 deletions python/cable_cell_io.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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);
}

Expand All @@ -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."),
Expand Down
3 changes: 2 additions & 1 deletion python/mechanism.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

#include "arbor/mechinfo.hpp"

#include "util.hpp"
#include "conversion.hpp"
#include "strprintf.hpp"

Expand Down Expand Up @@ -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.
Expand Down
16 changes: 10 additions & 6 deletions python/morphology.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include <arborio/neuroml.hpp>
#endif

#include "util.hpp"
#include "error.hpp"
#include "proxy.hpp"
#include "strprintf.hpp"
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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)),
Expand Down
27 changes: 27 additions & 0 deletions python/util.hpp
Original file line number Diff line number Diff line change
@@ -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())}));
}

}
}

0 comments on commit 2176e15

Please sign in to comment.