diff --git a/.travis.yml b/.travis.yml
index 252dcbf5fdf40d7dd1d290ffe0735af593a62141..9f9d3effab76d1da68a2cfc5e0100016c4949ab4 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -156,6 +156,7 @@ install:
       python$PY get-pip.py
       pip --version
     fi
+  - if [[ ( "$WITH_PYTHON" == "true" ) && ( "$TRAVIS_OS_NAME" == "osx" ) ]]; then pip$PY install numpy; fi
   - |
     if [[ "$WITH_DISTRIBUTED" == "mpi" ]]; then
       if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt
index 7e5b02f3e4e8d14d6864761022b920bc32706faa..4b37a93911f13ee40254bd5816c6743a257bf0d4 100644
--- a/python/CMakeLists.txt
+++ b/python/CMakeLists.txt
@@ -18,7 +18,8 @@ add_subdirectory(pybind11)
 add_library(pyarb MODULE
     config.cpp
     context.cpp
-    exception.cpp
+    event_generator.cpp
+    identifiers.cpp
     mpi.cpp
     pyarb.cpp
     strings.cpp
diff --git a/python/config.cpp b/python/config.cpp
index 5b1aa2eb19dfcba36a1413fdaf36ab411dfb63b6..29636c8d525012e89f8eedb70e0ebf5a4d52f5cc 100644
--- a/python/config.cpp
+++ b/python/config.cpp
@@ -1,8 +1,8 @@
-#include <arbor/version.hpp>
-
-#include <sstream>
 #include <iomanip>
 #include <ios>
+#include <sstream>
+
+#include <arbor/version.hpp>
 
 #include <pybind11/pybind11.h>
 #include <pybind11/stl.h>
diff --git a/python/context.cpp b/python/context.cpp
index b3ddb8c8ba63f9003b3b4b00f26078a2b2163988..61492e59baf49484ac0269961a5e2cfcd35d9e75 100644
--- a/python/context.cpp
+++ b/python/context.cpp
@@ -1,16 +1,15 @@
 #include <iostream>
-
 #include <sstream>
 #include <string>
 
 #include <arbor/context.hpp>
 #include <arbor/version.hpp>
 
+#include <pybind11/pybind11.h>
+
 #include "context.hpp"
 #include "strings.hpp"
 
-#include <pybind11/pybind11.h>
-
 #ifdef ARB_MPI_ENABLED
 #include "mpi.hpp"
 #endif
diff --git a/python/conversion.hpp b/python/conversion.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..811eb74eaf70c70fe73d305e622a17e758bdc9a9
--- /dev/null
+++ b/python/conversion.hpp
@@ -0,0 +1,51 @@
+#pragma once
+
+#include <pybind11/pybind11.h>
+#include <pybind11/pytypes.h>
+
+#include "error.hpp"
+
+namespace pyarb {
+
+// A helper function for converting from a Python object to a C++ optional wrapper.
+// Throws an runtime_error exception with msg if either the Python object
+// can't be converted to type T, or if the predicate is false for the value.
+template <typename T, typename F>
+arb::util::optional<T> py2optional(pybind11::object o, const char* msg, F&& pred) {
+    bool ok = true;
+    T value;
+
+    if (!o.is_none()) {
+        try {
+            value = o.cast<T>();
+            ok = pred(value);
+        }
+        catch (...) {
+            ok = false;
+        }
+    }
+
+    if (!ok) {
+        throw pyarb_error(msg);
+    }
+
+    return o.is_none()? arb::util::nullopt: arb::util::optional<T>(std::move(value));
+}
+
+template <typename T>
+arb::util::optional<T> py2optional(pybind11::object o, const char* msg) {
+    T value;
+
+    if (!o.is_none()) {
+        try {
+            value = o.cast<T>();
+        }
+        catch (...) {
+            throw pyarb_error(msg);
+        }
+    }
+
+    return o.is_none()? arb::util::nullopt: arb::util::optional<T>(std::move(value));
+}
+
+}
diff --git a/python/error.hpp b/python/error.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..fbcd5ba7216094cdc08aacae17ac5cd25396deab
--- /dev/null
+++ b/python/error.hpp
@@ -0,0 +1,32 @@
+#pragma once
+
+#include <stdexcept>
+#include <string>
+
+#include <arbor/util/optional.hpp>
+
+#include <pybind11/pybind11.h>
+#include <pybind11/pytypes.h>
+#include <pybind11/stl.h>
+
+// from https://pybind11.readthedocs.io/en/stable/advanced/cast/stl.html?highlight=boost%3A%3Aoptional#c-17-library-containers
+namespace pybind11 { namespace detail {
+    template <typename T>
+    struct type_caster<arb::util::optional<T>> : optional_caster<arb::util::optional<T>> {};
+}}
+
+namespace pyarb {
+
+// Python wrapper errors
+
+struct pyarb_error: std::runtime_error {
+    pyarb_error(const std::string& what_msg):
+        std::runtime_error(what_msg) {}
+};
+
+static
+void assert_throw(bool pred, const char* msg) {
+    if (!pred) throw pyarb_error(msg);
+}
+
+} // namespace pyarb
diff --git a/python/event_generator.cpp b/python/event_generator.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f31bbe15b0591f8b75e55e80291776a472d20680
--- /dev/null
+++ b/python/event_generator.cpp
@@ -0,0 +1,304 @@
+#include <stdexcept>
+#include <sstream>
+#include <string>
+
+#include <arbor/common_types.hpp>
+#include <arbor/event_generator.hpp>
+#include <arbor/schedule.hpp>
+
+#include <pybind11/pybind11.h>
+#include <pybind11/pytypes.h>
+#include <pybind11/stl.h>
+
+#include "conversion.hpp"
+#include "error.hpp"
+
+namespace pyarb {
+
+namespace {
+auto is_nonneg = [](auto&& t){ return t>=0.; };
+}
+
+// A Python shim that holds the information that describes an
+// arb::regular_schedule. This is wrapped in pybind11, and users constructing
+// a regular_schedule in python are manipulating this type. This is converted to
+// an arb::regular_schedule when a C++ recipe is created from a Python recipe.
+struct regular_schedule_shim {
+    using time_type = arb::time_type;
+    using opt_time_type = arb::util::optional<time_type>;
+
+    opt_time_type tstart = {};
+    opt_time_type tstop = {};
+    time_type dt = 0;
+
+    regular_schedule_shim() = default;
+
+    regular_schedule_shim(pybind11::object t0, time_type deltat, pybind11::object t1) {
+        set_tstart(t0);
+        set_tstop(t1);
+        set_dt(deltat);
+    }
+
+    // getter and setter (in order to assert when being set)
+    void set_tstart(pybind11::object t) {
+        tstart = py2optional<time_type>(t, "tstart must a non-negative number, or None.", is_nonneg);
+    };
+    void set_tstop(pybind11::object t) {
+        tstop = py2optional<time_type>(t, "tstop must a non-negative number, or None.", is_nonneg);
+    };
+    void set_dt(time_type delta_t) {
+        pyarb::assert_throw(is_nonneg(delta_t), "dt must be a non-negative number.");
+        dt = delta_t;
+    };
+
+    opt_time_type get_tstart() const { return tstart; }
+    opt_time_type get_dt()     const { return dt; }
+    opt_time_type get_tstop()  const { return tstop; }
+
+    arb::schedule schedule() const {
+        return arb::regular_schedule(
+                tstart.value_or(arb::terminal_time),
+                dt,
+                tstop.value_or(arb::terminal_time));
+    }
+
+};
+
+// A Python shim for arb::explicit_schedule.
+// This is wrapped in pybind11, and users constructing an explicit_schedule in
+// Python are manipulating this type. This is converted to an
+// arb::explicit_schedule when a C++ recipe is created from a Python recipe.
+
+struct explicit_schedule_shim {
+    using time_type = arb::time_type;
+
+    std::vector<time_type> times;
+
+    explicit_schedule_shim() = default;
+
+    explicit_schedule_shim(std::vector<time_type> t) {
+        set_times(t);
+    }
+
+    // getter and setter (in order to assert when being set)
+    void set_times(std::vector<time_type> t) {
+        times = std::move(t);
+        // Sort the times in ascending order if necessary
+        if (!std::is_sorted(times.begin(), times.end())) {
+            std::sort(times.begin(), times.end());
+        }
+        // Assert that there are no negative times
+        if (times.size()) {
+            pyarb::assert_throw(is_nonneg(times[0]),
+                    "explicit time schedule can not contain negative values.");
+        }
+    };
+
+    std::vector<time_type> get_times() const { return times; }
+
+    arb::schedule schedule() const {
+        return arb::explicit_schedule(times);
+    }
+};
+
+// A Python shim for arb::poisson_schedule.
+// This is wrapped in pybind11, and users constructing a poisson_schedule in
+// Python are manipulating this type. This is converted to an
+// arb::poisson_schedule when a C++ recipe is created from a Python recipe.
+
+struct poisson_schedule_shim {
+    using rng_type = std::mt19937_64;
+    using time_type = arb::time_type;
+
+    time_type tstart = arb::terminal_time;
+    time_type freq = 10.;
+    rng_type::result_type seed = 0;
+
+    poisson_schedule_shim() = default;
+
+    poisson_schedule_shim(time_type ts, time_type f, rng_type::result_type s) {
+        set_tstart(ts);
+        set_freq(f);
+        seed = s;
+    }
+
+    void set_tstart(time_type t) {
+        pyarb::assert_throw(is_nonneg(t), "tstart must be a non-negative number.");
+        tstart = t;
+    };
+
+    void set_freq(time_type f) {
+        pyarb::assert_throw(is_nonneg(f), "frequency must be a non-negative number.");
+        freq = f;
+    };
+
+    const time_type get_tstart() const { return tstart; }
+    const time_type get_freq() const { return freq; }
+
+    arb::schedule schedule() const {
+        // convert frequency to kHz.
+        return arb::poisson_schedule(tstart, freq/1000., rng_type(seed));
+    }
+};
+
+// Helper template for printing C++ optional types in Python.
+// Prints either the value, or None if optional value is not set.
+template <typename T>
+std::string to_string(const arb::util::optional<T>& o, std::string unit) {
+    if (!o) return "None";
+
+    std::stringstream s;
+    s << *o << " " << unit;
+    return s.str();
+}
+
+std::string schedule_regular_string(const regular_schedule_shim& r) {
+    std::stringstream s;
+    s << "<regular_schedule: "
+      << "tstart " << to_string(r.tstart, "ms") << ", "
+      << "dt " << r.dt << " ms, "
+      << "tstop " << to_string(r.tstop, "ms") << ">";
+    return s.str();
+};
+
+std::string schedule_explicit_string(const explicit_schedule_shim& e) {
+    std::stringstream s;
+    s << "<explicit_schedule: times [";
+    bool first = true;
+    for (auto t: e.times) {
+        if (!first) {
+            s << " ";
+        }
+        s << t;
+        first = false;
+    }
+    s << "] ms>";
+    return s.str();
+};
+
+std::string schedule_poisson_string(const poisson_schedule_shim& p) {
+    std::stringstream s;
+    s << "<poisson_schedule: tstart " << p.tstart << " ms"
+      << ", freq " << p.freq << " Hz"
+      << ", seed " << p.seed << ">";
+    return s.str();
+};
+
+struct event_generator_shim {
+    arb::cell_member_type target;
+    double weight;
+    arb::schedule time_sched;
+
+    event_generator_shim(arb::cell_member_type cell, double event_weight, arb::schedule sched):
+        target(cell),
+        weight(event_weight),
+        time_sched(std::move(sched))
+    {}
+};
+
+template <typename Sched>
+event_generator_shim make_event_generator(
+        arb::cell_member_type target,
+        double weight,
+        const Sched& sched)
+{
+    return event_generator_shim(target, weight, sched.schedule());
+}
+
+void register_event_generators(pybind11::module& m) {
+    using namespace pybind11::literals;
+    using time_type = arb::time_type;
+
+    // Regular schedule
+    pybind11::class_<regular_schedule_shim> regular_schedule(m, "regular_schedule",
+        "Describes a regular schedule with multiples of dt within the interval [tstart, tstop).");
+
+    regular_schedule
+        .def(pybind11::init<pybind11::object, time_type, pybind11::object>(),
+            "tstart"_a = pybind11::none(), "dt"_a = 0., "tstop"_a = pybind11::none(),
+            "Construct a regular schedule with arguments:\n"
+            "  tstart: The delivery time of the first event in the sequence (in ms, default None).\n"
+            "  dt:     The interval between time points (in ms, default 0).\n"
+            "  tstop:  No events delivered after this time (in ms, default None).")
+        .def_property("tstart", &regular_schedule_shim::get_tstart, &regular_schedule_shim::set_tstart,
+            "The delivery time of the first event in the sequence (in ms).")
+        .def_property("tstop", &regular_schedule_shim::get_tstop, &regular_schedule_shim::set_tstop,
+            "No events delivered after this time (in ms).")
+        .def_property("dt", &regular_schedule_shim::get_dt, &regular_schedule_shim::set_dt,
+            "The interval between time points (in ms).")
+        .def("__str__", &schedule_regular_string)
+        .def("__repr__",&schedule_regular_string);
+
+    // Explicit schedule
+    pybind11::class_<explicit_schedule_shim> explicit_schedule(m, "explicit_schedule",
+        "Describes an explicit schedule at a predetermined (sorted) sequence of times.");
+
+    explicit_schedule
+        .def(pybind11::init<>(),
+            "Construct an empty explicit schedule.\n")
+        .def(pybind11::init<std::vector<time_type>>(),
+            "times"_a,
+            "Construct an explicit schedule with argument:\n"
+            "  times: A list of times (in ms, default []).")
+        .def_property("times", &explicit_schedule_shim::get_times, &explicit_schedule_shim::set_times,
+            "A list of times (in ms).")
+        .def("__str__", &schedule_explicit_string)
+        .def("__repr__",&schedule_explicit_string);
+
+    // Poisson schedule
+    pybind11::class_<poisson_schedule_shim> poisson_schedule(m, "poisson_schedule",
+        "Describes a schedule according to a Poisson process.");
+
+    poisson_schedule
+        .def(pybind11::init<time_type, time_type, std::mt19937_64::result_type>(),
+            "tstart"_a = 0., "freq"_a = 10., "seed"_a = 0,
+            "Construct a Poisson schedule with arguments:\n"
+            "  tstart: The delivery time of the first event in the sequence (in ms, default 0 ms).\n"
+            "  freq:   The expected frequency (in Hz, default 10 Hz).\n"
+            "  seed:   The seed for the random number generator (default 0).")
+        .def_property("tstart", &poisson_schedule_shim::get_tstart, &poisson_schedule_shim::set_tstart,
+            "The delivery time of the first event in the sequence (in ms).")
+        .def_property("freq", &poisson_schedule_shim::get_freq, &poisson_schedule_shim::set_freq,
+            "The expected frequency (in Hz).")
+        .def_readwrite("seed", &poisson_schedule_shim::seed,
+            "The seed for the random number generator.")
+        .def("__str__", &schedule_poisson_string)
+        .def("__repr__",&schedule_poisson_string);
+
+// Event generator
+    pybind11::class_<event_generator_shim> event_generator(m, "event_generator");
+
+    event_generator
+        .def(pybind11::init<>(
+            [](arb::cell_member_type target, double weight, const regular_schedule_shim& sched){
+                return make_event_generator(target, weight, sched);}),
+            "target"_a, "weight"_a, "sched"_a,
+            "Construct an event generator with arguments:\n"
+            "  target: The target synapse (gid, local_id).\n"
+            "  weight: The weight of events to deliver.\n"
+            "  sched:  A regular schedule of the events.")
+        .def(pybind11::init<>(
+            [](arb::cell_member_type target, double weight, const explicit_schedule_shim& sched){
+                return make_event_generator(target, weight, sched);}),
+            "target"_a, "weight"_a, "sched"_a,
+            "Construct an event generator with arguments:\n"
+            "  target: The target synapse (gid, local_id).\n"
+            "  weight: The weight of events to deliver.\n"
+            "  sched:  An explicit schedule of the events.")
+        .def(pybind11::init<>(
+            [](arb::cell_member_type target, double weight, const poisson_schedule_shim& sched){
+                return make_event_generator(target, weight, sched);}),
+            "target"_a, "weight"_a, "sched"_a,
+            "Construct an event generator with arguments:\n"
+            "  target: The target synapse (gid, local_id).\n"
+            "  weight: The weight of events to deliver.\n"
+            "  sched:  A poisson schedule of the events.")
+        .def_readwrite("target", &event_generator_shim::target,
+             "The target synapse (gid, local_id).")
+        .def_readwrite("weight", &event_generator_shim::weight,
+             "The weight of events to deliver.")
+        .def("__str__", [](const event_generator_shim&){return "<arbor.event_generator>";})
+        .def("__repr__", [](const event_generator_shim&){return "<arbor.event_generator>";});
+}
+
+} // namespace pyarb
diff --git a/python/exception.cpp b/python/exception.cpp
deleted file mode 100644
index 3be0e787a3ee78294aedd7e90242e500f413c8c0..0000000000000000000000000000000000000000
--- a/python/exception.cpp
+++ /dev/null
@@ -1,11 +0,0 @@
-#include <string>
-
-#include "exception.hpp"
-
-namespace pyarb {
-
-python_error::python_error(const std::string& message):
-    arbor_exception("arbor python wrapper error: " + message + "\n")
-{}
-
-} // namespace pyarb
diff --git a/python/exception.hpp b/python/exception.hpp
deleted file mode 100644
index 5f352154b8597ecbb4e8e8a245a76891ee4928da..0000000000000000000000000000000000000000
--- a/python/exception.hpp
+++ /dev/null
@@ -1,17 +0,0 @@
-#pragma once
-
-#include <string>
-
-#include <arbor/arbexcept.hpp>
-
-namespace pyarb {
-
-using arb::arbor_exception;
-
-// Python wrapper errors
-
-struct python_error: arbor_exception {
-    explicit python_error(const std::string& message);
-};
-
-} // namespace pyarb
diff --git a/python/identifiers.cpp b/python/identifiers.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..cb6159a2c750fa15f8e1d91f19b733c6a99f3217
--- /dev/null
+++ b/python/identifiers.cpp
@@ -0,0 +1,43 @@
+#include <string>
+
+#include <arbor/common_types.hpp>
+
+#include <pybind11/pybind11.h>
+
+#include "strings.hpp"
+
+namespace pyarb {
+
+void register_identifiers(pybind11::module& m) {
+    using namespace pybind11::literals;
+
+    pybind11::class_<arb::cell_member_type> cell_member(m, "cell_member",
+        "For global identification of a cell-local item.\n\n"
+        "Items of cell_member must:\n"
+        "(1) be associated with a unique cell, identified by the member gid;\n"
+        "(2) identify an item within a cell-local collection by the member index.\n");
+
+    cell_member
+        .def(pybind11::init<>(),
+            "Construct a cell member with default values gid = 0 and index = 0.")
+        .def(pybind11::init(
+            [](arb::cell_gid_type gid, arb::cell_lid_type idx) {
+                arb::cell_member_type m;
+                m.gid = gid;
+                m.index = idx;
+                return m;
+            }),
+            "gid"_a,
+            "index"_a,
+            "Construct a cell member with arguments:/n"
+               "  gid:     The global identifier of the cell.\n"
+               "  index:   The cell-local index of the item.\n")
+        .def_readwrite("gid",   &arb::cell_member_type::gid,
+            "The global identifier of the cell.")
+        .def_readwrite("index", &arb::cell_member_type::index,
+            "Cell-local index of the item.")
+        .def("__str__",  &cell_member_string)
+        .def("__repr__", &cell_member_string);
+}
+
+} // namespace pyarb
diff --git a/python/mpi.cpp b/python/mpi.cpp
index b6c14c35d5832a5eebb0b0a5577629303927a9bc..0fd0dbadde66b9544288f211a6b5131f8227d9a5 100644
--- a/python/mpi.cpp
+++ b/python/mpi.cpp
@@ -6,10 +6,10 @@
 #include <pybind11/pybind11.h>
 
 #ifdef ARB_MPI_ENABLED
-#include <arbor/communication/mpi_error.hpp>
-
 #include <mpi.h>
 
+#include <arbor/communication/mpi_error.hpp>
+
 #include "mpi.hpp"
 
 #ifdef ARB_WITH_MPI4PY
diff --git a/python/pyarb.cpp b/python/pyarb.cpp
index 1b9d11b5f337e4195e38303450be4a29e927cb0e..e085141ea0157b0fd157a1d2033b1ac22daac8cc 100644
--- a/python/pyarb.cpp
+++ b/python/pyarb.cpp
@@ -8,6 +8,8 @@ namespace pyarb {
 
 void register_config(pybind11::module& m);
 void register_contexts(pybind11::module& m);
+void register_event_generators(pybind11::module& m);
+void register_identifiers(pybind11::module& m);
 #ifdef ARB_MPI_ENABLED
 void register_mpi(pybind11::module& m);
 #endif
@@ -19,6 +21,8 @@ PYBIND11_MODULE(arbor, m) {
 
     pyarb::register_config(m);
     pyarb::register_contexts(m);
+    pyarb::register_event_generators(m);
+    pyarb::register_identifiers(m);
     #ifdef ARB_MPI_ENABLED
     pyarb::register_mpi(m);
     #endif
diff --git a/python/strings.cpp b/python/strings.cpp
index cf0e2c100ab277d09072489d0525f189a713745e..1788766602d6b511f31ed2f08f529759eb7c78da 100644
--- a/python/strings.cpp
+++ b/python/strings.cpp
@@ -1,12 +1,20 @@
-#include <string>
 #include <sstream>
+#include <string>
 
+#include <arbor/common_types.hpp>
 #include <arbor/context.hpp>
 
 #include "strings.hpp"
 
 namespace pyarb {
 
+std::string cell_member_string(const arb::cell_member_type& m) {
+    std::stringstream s;
+    s << "<cell_member: gid " << m.gid
+      << ", index " << m.index << ">";
+    return s.str();
+}
+
 std::string context_string(const arb::context& c) {
     std::stringstream s;
     const bool gpu = arb::has_gpu(c);
diff --git a/python/strings.hpp b/python/strings.hpp
index c0e9bc042bd1e22c1fbb0aae8b5bf5e56c27ff59..dba70801706d456f680758edb90b31383fb54b1d 100644
--- a/python/strings.hpp
+++ b/python/strings.hpp
@@ -1,15 +1,14 @@
 #pragma once
 
-/*
- * Utilities for generating string representations of types.
- */
-
 #include <string>
 
+#include <arbor/common_types.hpp>
 #include <arbor/context.hpp>
 
+// Utilities for generating string representations of types.
 namespace pyarb {
 
+std::string cell_member_string(const arb::cell_member_type&);
 std::string context_string(const arb::context&);
 std::string proc_allocation_string(const arb::proc_allocation&);
 
diff --git a/python/test/unit/runner.py b/python/test/unit/runner.py
index 56cd8a67c99ee4b12bc3625a2e27b137417db420..1ea70555e0505cfbbbab34e692fb6eae42176890 100644
--- a/python/test/unit/runner.py
+++ b/python/test/unit/runner.py
@@ -11,14 +11,20 @@ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../.
 try:
     import options
     import test_contexts
+    import test_identifiers
+    import test_event_generators
     # add more if needed
 except ModuleNotFoundError:
     from test import options
     from test.unit import test_contexts
+    from test.unit import test_identifiers
+    from test.unit import test_event_generators
     # add more if needed
 
 test_modules = [\
-    test_contexts\
+    test_contexts,\
+    test_identifiers,\
+    test_event_generators\
 ] # add more if needed
 
 def suite():
diff --git a/python/test/unit/test_contexts.py b/python/test/unit/test_contexts.py
index 5e2389e55236c2b437a661dfa235fadef2077268..6ec70a62acd6ed2a446e63e47db6e2de4388bd4b 100644
--- a/python/test/unit/test_contexts.py
+++ b/python/test/unit/test_contexts.py
@@ -20,7 +20,7 @@ all tests for non-distributed arb.context
 """
 
 class Contexts(unittest.TestCase):
-    def test_default(self):
+    def test_default_context(self):
         ctx = arb.context()
 
     def test_resources(self):
diff --git a/python/test/unit/test_event_generators.py b/python/test/unit/test_event_generators.py
new file mode 100644
index 0000000000000000000000000000000000000000..9c7ef47200c0e3e62a3270c59340eb7e1eef1258
--- /dev/null
+++ b/python/test/unit/test_event_generators.py
@@ -0,0 +1,165 @@
+# -*- coding: utf-8 -*-
+#
+# test_event_generators.py
+
+import unittest
+import numpy as np
+
+import arbor as arb
+
+# to be able to run .py file from child directory
+import sys, os
+sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../')))
+
+try:
+    import options
+except ModuleNotFoundError:
+    from test import options
+
+"""
+all tests for event generators (regular, explicit, poisson)
+"""
+
+class RegularSchedule(unittest.TestCase):
+    def test_none_contor_regular_schedule(self):
+        rs = arb.regular_schedule(tstart=None, tstop=None)
+
+    def test_tstart_dt_tstop_contor_regular_schedule(self):
+        rs = arb.regular_schedule(10., 1., 20.)
+        self.assertEqual(rs.tstart, 10.)
+        self.assertEqual(rs.dt, 1.)
+        self.assertEqual(rs.tstop, 20.)
+
+    def test_set_tstart_dt_tstop_regular_schedule(self):
+        rs = arb.regular_schedule()
+        rs.tstart = 17.
+        rs.dt = 0.5
+        rs.tstop = 42.
+        self.assertEqual(rs.tstart, 17.)
+        self.assertAlmostEqual(rs.dt, 0.5)
+        self.assertEqual(rs.tstop, 42.)
+
+    def test_event_generator_regular_schedule(self):
+        cm = arb.cell_member()
+        cm.gid = 42
+        cm.index = 3
+        rs = arb.regular_schedule(2.0, 1., 100.)
+        rg = arb.event_generator(cm, 3.14, rs)
+        self.assertEqual(rg.target.gid, 42)
+        self.assertEqual(rg.target.index, 3)
+        self.assertAlmostEqual(rg.weight, 3.14)
+
+    def test_exceptions_regular_schedule(self):
+        with self.assertRaisesRegex(RuntimeError,
+            "tstart must a non-negative number, or None."):
+            arb.regular_schedule(tstart=-1.)
+        with self.assertRaisesRegex(RuntimeError,
+            "dt must be a non-negative number."):
+            arb.regular_schedule(dt=-0.1)
+        with self.assertRaises(TypeError):
+            arb.regular_schedule(dt=None)
+        with self.assertRaises(TypeError):
+            arb.regular_schedule(dt="dt")
+        with self.assertRaisesRegex(RuntimeError,
+            "tstop must a non-negative number, or None."):
+            arb.regular_schedule(tstop='tstop')
+
+class ExplicitSchedule(unittest.TestCase):
+    def test_times_contor_explicit_schedule(self):
+        es = arb.explicit_schedule([1, 2, 3, 4.5])
+        self.assertEqual(es.times, [1, 2, 3, 4.5])
+
+    def test_set_times_explicit_schedule(self):
+        es = arb.explicit_schedule()
+        es.times = [42, 43, 44, 55.5, 100]
+        self.assertEqual(es.times, [42, 43, 44, 55.5, 100])
+
+    def test_event_generator_explicit_schedule(self):
+        cm = arb.cell_member()
+        cm.gid = 0
+        cm.index = 42
+        es = arb.explicit_schedule([0,1,2,3,4.4])
+        eg = arb.event_generator(cm, -0.01, es)
+        self.assertEqual(eg.target.gid, 0)
+        self.assertEqual(eg.target.index, 42)
+        self.assertAlmostEqual(eg.weight, -0.01)
+
+    def test_exceptions_explicit_schedule(self):
+        with self.assertRaisesRegex(RuntimeError,
+            "explicit time schedule can not contain negative values."):
+            arb.explicit_schedule([-1])
+        with self.assertRaises(TypeError):
+            arb.explicit_schedule(['times'])
+        with self.assertRaises(TypeError):
+            arb.explicit_schedule([None])
+        with self.assertRaises(TypeError):
+            arb.explicit_schedule([[1,2,3]])
+
+class PoissonSchedule(unittest.TestCase):
+    def test_freq_seed_contor_poisson_schedule(self):
+        ps = arb.poisson_schedule(freq = 5., seed = 42)
+        self.assertEqual(ps.freq, 5.)
+        self.assertEqual(ps.seed, 42)
+
+    def test_tstart_freq_seed_contor_poisson_schedule(self):
+        ps = arb.poisson_schedule(10., 100., 1000)
+        self.assertEqual(ps.tstart, 10.)
+        self.assertEqual(ps.freq, 100.)
+        self.assertEqual(ps.seed, 1000)
+
+    def test_set_tstart_freq_seed_poisson_schedule(self):
+        ps = arb.poisson_schedule()
+        ps.tstart = 4.5
+        ps.freq = 5.5
+        ps.seed = 83
+        self.assertAlmostEqual(ps.tstart, 4.5)
+        self.assertAlmostEqual(ps.freq, 5.5)
+        self.assertEqual(ps.seed, 83)
+
+    def test_event_generator_poisson_schedule(self):
+        cm = arb.cell_member()
+        cm.gid = 4
+        cm.index = 2
+        ps = arb.poisson_schedule(0., 10., 0)
+        pg = arb.event_generator(cm, 42., ps)
+        self.assertEqual(pg.target.gid, 4)
+        self.assertEqual(pg.target.index, 2)
+        self.assertEqual(pg.weight, 42.)
+
+    def test_exceptions_poisson_schedule(self):
+        with self.assertRaisesRegex(RuntimeError,
+            "tstart must be a non-negative number."):
+            arb.poisson_schedule(tstart=-10.)
+        with self.assertRaises(TypeError):
+            arb.poisson_schedule(tstart=None)
+        with self.assertRaises(TypeError):
+            arb.poisson_schedule(tstart="tstart")
+        with self.assertRaisesRegex(RuntimeError,
+            "frequency must be a non-negative number."):
+            arb.poisson_schedule(freq=-100.)
+        with self.assertRaises(TypeError):
+            arb.poisson_schedule(freq="freq")
+        with self.assertRaises(TypeError):
+            arb.poisson_schedule(seed=-1)
+        with self.assertRaises(TypeError):
+            arb.poisson_schedule(seed=10.)
+        with self.assertRaises(TypeError):
+            arb.poisson_schedule(seed="seed")
+        with self.assertRaises(TypeError):
+            arb.poisson_schedule(seed=None)
+
+def suite():
+    # specify class and test functions in tuple (here: all tests starting with 'test' from classes RegularSchedule, ExplicitSchedule and PoissonSchedule
+    suite = unittest.TestSuite()
+    suite.addTests(unittest.makeSuite(RegularSchedule, ('test')))
+    suite.addTests(unittest.makeSuite(ExplicitSchedule, ('test')))
+    suite.addTests(unittest.makeSuite(PoissonSchedule, ('test')))
+    return suite
+
+def run():
+    v = options.parse_arguments().verbosity
+    runner = unittest.TextTestRunner(verbosity = v)
+    runner.run(suite())
+
+if __name__ == "__main__":
+    run()
diff --git a/python/test/unit/test_identifiers.py b/python/test/unit/test_identifiers.py
new file mode 100644
index 0000000000000000000000000000000000000000..4bd41794fae19834b080aaca60d4b114a5ac0990
--- /dev/null
+++ b/python/test/unit/test_identifiers.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+#
+# test_identifiers.py
+
+import unittest
+
+import arbor as arb
+
+# to be able to run .py file from child directory
+import sys, os
+sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../')))
+
+try:
+    import options
+except ModuleNotFoundError:
+    from test import options
+
+"""
+all tests for identifiers, indexes, kinds
+"""
+
+class CellMembers(unittest.TestCase):
+    def test_default_cell_member(self):
+        cm = arb.cell_member()
+        self.assertEqual(cm.gid, 0)
+        self.assertEqual(cm.index, 0)
+
+    def test_gid_index_contor_cell_member(self):
+        cm = arb.cell_member(17,42)
+        self.assertEqual(cm.gid, 17)
+        self.assertEqual(cm.index, 42)
+
+    def test_set_git_index_cell_member(self):
+        cm = arb.cell_member()
+        cm.gid = 13
+        cm.index = 23
+        self.assertEqual(cm.gid,13)
+        self.assertEqual(cm.index, 23)
+
+def suite():
+    # specify class and test functions in tuple (here: all tests starting with 'test' from class Contexts
+    suite = unittest.makeSuite(CellMembers, ('test'))
+    return suite
+
+def run():
+    v = options.parse_arguments().verbosity
+    runner = unittest.TextTestRunner(verbosity = v)
+    runner.run(suite())
+
+if __name__ == "__main__":
+    run()