From d2623ecf991a8e3bddc79958ed755c78d82abfd8 Mon Sep 17 00:00:00 2001
From: thorstenhater <24411438+thorstenhater@users.noreply.github.com>
Date: Mon, 11 Jan 2021 13:05:12 +0100
Subject: [PATCH] More pythonic membership on mechanism_catalogue. (#1306)

Introduce two minor changes to the Python API
to handle mechanism_catalogues idiomatically.

Instead of

import arbor as A

cat = A.default_catalogue()

if cat.has('hh'):
  print("Found HH.")

for mech in cat.keys():
   print("*", mech)

we can now write

import arbor as A

cat = A.default_catalogue()

if 'hh' in cat:
  print("Found HH.")

for mech in cat:
   print("*", mech)

which is closer to the expectations of Python users.
---
 doc/python/mechanisms.rst | 24 +++++++++++++++++++++---
 python/mechanism.cpp      | 23 ++++++++++++++++++++---
 2 files changed, 41 insertions(+), 6 deletions(-)

diff --git a/doc/python/mechanisms.rst b/doc/python/mechanisms.rst
index b55aa460..d744ddfa 100644
--- a/doc/python/mechanisms.rst
+++ b/doc/python/mechanisms.rst
@@ -246,10 +246,19 @@ Mechanism catalogues
     2. A further hierarchy of *derived* mechanisms, that allow specialization of
        global parameters, ion bindings, and implementations.
 
-    .. py:method:: has(name)
+    .. py:method:: __contains__(name)
 
         Test if mechanism with *name* is in the catalogue.
 
+        Note: This enables the following idiom
+
+        .. code-block:: Python
+
+            import arbor
+
+            if 'hh' in arbor.default_catalogue():
+              print("Found HH mechanism.")
+
         :param name: name of mechanism.
         :type name: str
         :return: bool
@@ -280,11 +289,20 @@ Mechanism catalogues
         :return: mechanism metadata
         :rtype: :class:`mechanism_info`
 
-    .. py:method:: names()
+    .. py:method:: __iter___()
 
         Return a list names of all the mechanisms in the catalogue.
 
-        :return: list
+        Note: This enables the following idiom
+
+        .. code-block:: Python
+
+            import arbor
+
+            for name in arbor.default_catalogue():
+              print(name)
+
+        :return: :class:`py_mech_cat_iterator`
 
     .. py:method:: derive(name, parent, globals={}, ions={})
 
diff --git a/python/mechanism.cpp b/python/mechanism.cpp
index 70e2c528..a1ac7a9d 100644
--- a/python/mechanism.cpp
+++ b/python/mechanism.cpp
@@ -104,12 +104,29 @@ void register_mechanisms(pybind11::module& m) {
                     return util::pprintf("(arbor.mechanism_info)"); });
 
     pybind11::class_<arb::mechanism_catalogue> cat(m, "catalogue");
+
+    struct py_mech_cat_iterator {
+        py_mech_cat_iterator(const arb::mechanism_catalogue &cat, pybind11::object ref) : names(cat.mechanism_names()), ref(ref), idx{0} { }
+        std::vector<std::string> names;
+        pybind11::object ref; // keep a reference to cat lest it dies while we iterate
+        size_t idx = 0;
+        std::string next() {
+            if (idx == names.size()) throw pybind11::stop_iteration();
+            return names[idx++];
+        }
+    };
+
+    pybind11::class_<py_mech_cat_iterator>(cat, "MechCatIterator")
+        .def("__iter__", [](py_mech_cat_iterator &it) -> py_mech_cat_iterator& { return it; })
+        .def("__next__", &py_mech_cat_iterator::next);
+
     cat
         .def(pybind11::init<const arb::mechanism_catalogue&>())
-        .def("has", &arb::mechanism_catalogue::has,
+        .def("__contains__", &arb::mechanism_catalogue::has,
              "name"_a, "Is 'name' in the catalogue?")
-        .def("keys", &arb::mechanism_catalogue::mechanism_names,
-             "Return a list of all mechanisms in this catalogues.")
+        .def("__iter__",
+             [](pybind11::object cat) { return py_mech_cat_iterator(cat.cast<const arb::mechanism_catalogue &>(), cat); },
+             "Return an iterator over all mechanism names in this catalogues.")
         .def("is_derived", &arb::mechanism_catalogue::is_derived,
                 "name"_a, "Is 'name' a derived mechanism or can it be implicitly derived?")
         .def("__getitem__",
-- 
GitLab