From 6d912993c170cfc0a6e84a8317a5de3f9ccb6ad7 Mon Sep 17 00:00:00 2001 From: Michael Hines Date: Wed, 22 Jan 2025 08:47:23 -0500 Subject: [PATCH 1/6] Python 3.13.1 broke [s for s in sl] where sl is a SectionList. Manual partial cherrypick of 00ba007fa93. #3276 --- src/nrnpython/nrnpy_hoc.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/nrnpython/nrnpy_hoc.cpp b/src/nrnpython/nrnpy_hoc.cpp index 2d225e7df6..c8988ce1ec 100644 --- a/src/nrnpython/nrnpy_hoc.cpp +++ b/src/nrnpython/nrnpy_hoc.cpp @@ -1596,13 +1596,16 @@ PyObject* nrnpy_forall(PyObject* self, PyObject* args) { static PyObject* hocobj_iter(PyObject* self) { // printf("hocobj_iter %p\n", self); PyHocObject* po = (PyHocObject*) self; - if (po->type_ == PyHoc::HocObject) { + if (po->type_ == PyHoc::HocObject || po->type_ == PyHoc::HocSectionListIterator) { if (po->ho_->ctemplate == hoc_vec_template_) { return PySeqIter_New(self); } else if (po->ho_->ctemplate == hoc_list_template_) { return PySeqIter_New(self); } else if (po->ho_->ctemplate == hoc_sectionlist_template_) { // need a clone of self so nested loops do not share iteritem_ + // The HocSectionListIter arm of the outer 'if' became necessary + // at Python-3.13.1 upon which the following body is executed + // twice. See https://github.com/python/cpython/issues/127682 PyObject* po2 = nrnpy_ho2po(po->ho_); PyHocObject* pho2 = (PyHocObject*) po2; pho2->type_ = PyHoc::HocSectionListIterator; From c624c943a7d35752ef94c9c6586fa3a55bb7a27b Mon Sep 17 00:00:00 2001 From: nrnhines Date: Thu, 21 Nov 2024 20:17:47 -0500 Subject: [PATCH 2/6] Launching nrniv -python with Python 3.13.0 does not allow use of gui. (#3239) --- src/nrnpython/nrnpython.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/nrnpython/nrnpython.cpp b/src/nrnpython/nrnpython.cpp index b774623aa5..b09a77c07d 100644 --- a/src/nrnpython/nrnpython.cpp +++ b/src/nrnpython/nrnpython.cpp @@ -210,6 +210,13 @@ extern "C" int nrnpython_start(int b) { // del g // Also, NEURONMainMenu/File/Quit did not work. The solution to both // seems to be to just avoid gui threads if MINGW and launched nrniv + + // Beginning with Python 3.13.0 it seems that the readline + // module has not been loaded yet. Since PyInit_readline sets + // PyOS_ReadlineFunctionPointer = call_readline; without checking, + // we need to import here. + PyRun_SimpleString("import readline as nrn_readline"); + PyOS_ReadlineFunctionPointer = nrnpython_getline; // Is there a -c "command" or file.py arg. From c49b24401457e9a1c0a2ac7d363beab502ecbcda Mon Sep 17 00:00:00 2001 From: nrnhines Date: Mon, 2 Dec 2024 06:54:14 -0800 Subject: [PATCH 3/6] Fix thread sanitizer leak when launching Python 3.13 and using from neuron import h, gui. (#3243) * nrnpython_finalize can only cleanup and Py_Finalize from MainThread * save stdin terminal settings on import hoc and restore on h.quit() --- share/lib/python/neuron/gui.py | 38 ++++++++++++++++++----------- src/nrnpython/inithoc.cpp | 44 +++++++++++++++++++++++++++++++--- 2 files changed, 65 insertions(+), 17 deletions(-) diff --git a/share/lib/python/neuron/gui.py b/share/lib/python/neuron/gui.py index 3314aa0d52..c049d6d638 100644 --- a/share/lib/python/neuron/gui.py +++ b/share/lib/python/neuron/gui.py @@ -7,11 +7,11 @@ Note that python threads are not used if nrniv is launched instead of Python """ - from neuron import h - +from contextlib import contextmanager import threading import time +import atexit # recursive, especially in case stop/start pairs called from doNotify code. _lock = threading.RLock() @@ -29,9 +29,10 @@ def process_events(): _lock.acquire() try: h.doNotify() - except: - print("Exception in gui thread") - _lock.release() + except Exception as e: + print(f"Exception in gui thread: {e}") + finally: + _lock.release() class Timer: @@ -67,31 +68,40 @@ def end(self): class LoopTimer(threading.Thread): """ - a Timer that calls f every interval + A Timer that calls a function at regular intervals. """ def __init__(self, interval, fun): - """ - @param interval: time in seconds between call to fun() - @param fun: the function to call on timer update - """ self.started = False self.interval = interval self.fun = fun - threading.Thread.__init__(self) - self.setDaemon(True) + self._running = threading.Event() + threading.Thread.__init__(self, daemon=True) def run(self): h.nrniv_bind_thread(threading.current_thread().ident) self.started = True - while True: + self._running.set() + while self._running.is_set(): self.fun() time.sleep(self.interval) + def stop(self): + """Stop the timer thread and wait for it to terminate.""" + self._running.clear() + self.join() + -if h.nrnversion(9) == "2": # launched with python (instead of nrniv) +if h.nrnversion(9) == "2": # Launched with Python (instead of nrniv) timer = LoopTimer(0.1, process_events) timer.start() + + def cleanup(): + if timer.started: + timer.stop() + + atexit.register(cleanup) + while not timer.started: time.sleep(0.001) diff --git a/src/nrnpython/inithoc.cpp b/src/nrnpython/inithoc.cpp index 5744c7c88e..30120c4e39 100644 --- a/src/nrnpython/inithoc.cpp +++ b/src/nrnpython/inithoc.cpp @@ -215,6 +215,33 @@ static int have_opt(const char* arg) { return 0; } +#if defined(__linux__) || defined(DARWIN) + +/* we do this because thread sanitizer does not allow system calls. + In particular + system("stty sane") + returns an error code of 139 +*/ + +#include +#include +#include + +static struct termios original_termios; + +static void save_original_terminal_settings() { + if (tcgetattr(STDIN_FILENO, &original_termios) == -1) { + std::cerr << "Error getting original terminal attributes\n"; + } +} + +static void restore_original_terminal_settings() { + if (tcsetattr(STDIN_FILENO, TCSANOW, &original_termios) == -1) { + std::cerr << "Error restoring terminal attributes\n"; + } +} +#endif // __linux__ + void nrnpython_finalize() { #if USE_PTHREAD pthread_t now = pthread_self(); @@ -222,11 +249,18 @@ void nrnpython_finalize() { #else { #endif + // Call python_gui_cleanup() if defined in Python + PyRun_SimpleString( + "try:\n" + " gui.cleanup()\n" + "except NameError:\n" + " pass\n"); + + // Finalize Python Py_Finalize(); } -#if linux - if (system("stty sane > /dev/null 2>&1")) { - } // 'if' to avoid ignoring return value warning +#if defined(__linux__) || defined(DARWIN) + restore_original_terminal_settings(); #endif } @@ -239,6 +273,10 @@ extern "C" PyObject* PyInit_hoc() { main_thread_ = pthread_self(); #endif +#if defined(__linux__) || defined(DARWIN) + save_original_terminal_settings(); +#endif // __linux__ + if (nrn_global_argv) { // ivocmain was already called so already loaded return nrnpy_hoc(); } From c70a56b902833c4c7ea119847af527eaf44b84fd Mon Sep 17 00:00:00 2001 From: nrnhines Date: Tue, 3 Dec 2024 05:46:23 -0800 Subject: [PATCH 4/6] On h.quit() terminal settings are same as when neuron.hoc was imported. (#3259) * Print tcgetatt/tcsetattr error messages only if STDIN is a tty. --- src/nrnpython/inithoc.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nrnpython/inithoc.cpp b/src/nrnpython/inithoc.cpp index 30120c4e39..27f96329a9 100644 --- a/src/nrnpython/inithoc.cpp +++ b/src/nrnpython/inithoc.cpp @@ -230,13 +230,13 @@ static int have_opt(const char* arg) { static struct termios original_termios; static void save_original_terminal_settings() { - if (tcgetattr(STDIN_FILENO, &original_termios) == -1) { + if (tcgetattr(STDIN_FILENO, &original_termios) == -1 && isatty(STDIN_FILENO)) { std::cerr << "Error getting original terminal attributes\n"; } } static void restore_original_terminal_settings() { - if (tcsetattr(STDIN_FILENO, TCSANOW, &original_termios) == -1) { + if (tcsetattr(STDIN_FILENO, TCSANOW, &original_termios) == -1 && isatty(STDIN_FILENO)) { std::cerr << "Error restoring terminal attributes\n"; } } From 6a40b94fb44e5ebc795a013a28decb900746b79e Mon Sep 17 00:00:00 2001 From: Michael Hines Date: Sun, 16 Mar 2025 13:16:37 -0400 Subject: [PATCH 5/6] RX3D requires numpy and Cython but versions not specified. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 0ca138e607..45b0c4473d 100644 --- a/setup.py +++ b/setup.py @@ -410,7 +410,7 @@ def setup_package(): NRN_COLLECT_DIRS = ["bin", "lib", "include", "share"] docs_require = [] # sphinx, themes, etc - maybe_rxd_reqs = ["numpy<2", "Cython<3"] if Components.RX3D else [] + maybe_rxd_reqs = ["numpy", "Cython"] if Components.RX3D else [] maybe_docs = docs_require if "docs" in sys.argv else [] maybe_test_runner = ["pytest-runner"] if "test" in sys.argv else [] From 26ff143f2a24c1dce30385c3905ab9371d107945 Mon Sep 17 00:00:00 2001 From: Michael Hines Date: Mon, 17 Mar 2025 19:54:48 -0400 Subject: [PATCH 6/6] update numpy, cython, bokeh versions --- .readthedocs.yml | 3 +++ docs/docs_requirements.txt | 10 ++++++---- nrn_requirements.txt | 7 +++---- share/lib/python/neuron/__init__.py | 10 ++++++++++ 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index dc4223186f..8ebb9bf98a 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,5 +1,8 @@ version: 2 +sphinx: + configuration: docs/conf.py + build: os: "ubuntu-22.04" tools: diff --git a/docs/docs_requirements.txt b/docs/docs_requirements.txt index a8a10b7fdd..15b927bb43 100644 --- a/docs/docs_requirements.txt +++ b/docs/docs_requirements.txt @@ -1,18 +1,20 @@ sphinx +# do not check import of next line sphinx_rtd_theme jupyter nbconvert -recommonmark +myst_parser matplotlib -# bokeh 3 seems to break docs notebooks -bokeh<3 +bokeh>=3 # do not check import of next line ipython plotnine -numpy<2 +numpy>=2 plotly nbsphinx jinja2 sphinx-design +sphinx-inline-tabs packaging==21.3 tenacity<8.4 +anywidget diff --git a/nrn_requirements.txt b/nrn_requirements.txt index c4ce7d3ae0..661904304c 100644 --- a/nrn_requirements.txt +++ b/nrn_requirements.txt @@ -2,12 +2,11 @@ wheel setuptools<=70.3.0 scikit-build matplotlib -# bokeh 3 seems to break docs notebooks -bokeh<3 +bokeh>=3 ipython -cython<3 +cython>=3 packaging pytest pytest-cov mpi4py -numpy<2 +numpy>=2 diff --git a/share/lib/python/neuron/__init__.py b/share/lib/python/neuron/__init__.py index 823fbec2d2..ea2a8ee085 100644 --- a/share/lib/python/neuron/__init__.py +++ b/share/lib/python/neuron/__init__.py @@ -1751,3 +1751,13 @@ def _mview_html_tree(hlist, inside_mechanisms_in_use=0): if _get_ipython() is not None: html_formatter = _get_ipython().display_formatter.formatters["text/html"] html_formatter.for_type(hoc.HocObject, _hocobj_html) + +# in case Bokeh is installed, register a serialization function for hoc.Vector +try: + from bokeh.core.serialization import Serializer + + Serializer.register( + type(h.Vector), lambda obj, serializer: [serializer.encode(item) for item in obj] + ) +except ImportError: + pass