From d430778b045eb2a938c03a2ddf56b8c6520daa8e Mon Sep 17 00:00:00 2001 From: "Ankur Sinha (Ankur Sinha Gmail)" Date: Thu, 12 Jun 2025 11:42:02 +0100 Subject: [PATCH 1/7] feat(jnml): add simple wrapper PyNeuroML will provide a `jnml` command too, which will use the bundled jNeuroML jar file. Note that to use another jnml, such as one from the jNeuroML release tar, one can simply either use the full path, or modify the $PATH etc. variables that are available on most systems. --- pyneuroml/runners.py | 5 ++++- pyneuroml/utils/jnmlwrapper.py | 36 ++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 pyneuroml/utils/jnmlwrapper.py diff --git a/pyneuroml/runners.py b/pyneuroml/runners.py index bfdcf894..1ed716ee 100644 --- a/pyneuroml/runners.py +++ b/pyneuroml/runners.py @@ -705,6 +705,7 @@ def run_jneuroml( report_jnml_output: bool = True, exit_on_fail: bool = False, return_string: bool = False, + output_prefix: str = " jNeuroML >> ", ) -> typing.Union[typing.Tuple[bool, str], bool]: """Run jnml with provided arguments. @@ -726,6 +727,8 @@ def run_jneuroml( :type exit_on_fail: bool :param return_string: toggle whether the output string should be returned :type return_string: bool + :param output_prefix: string to prefix the returned jNeuroML output with + :type output_prefix: str :returns: either a bool, or a Tuple (bool, str) depending on the value of return_string: True of jnml ran successfully, False if not; along with the @@ -756,7 +759,7 @@ def run_jneuroml( try: command = f'java -Xmx{max_memory} {pre_jar} -jar "{jar_path}" {pre_args} {target_file} {post_args}' retcode, output = execute_command_in_dir( - command, exec_in_dir, verbose=verbose, prefix=" jNeuroML >> " + command, exec_in_dir, verbose=verbose, prefix=output_prefix ) if retcode != 0: diff --git a/pyneuroml/utils/jnmlwrapper.py b/pyneuroml/utils/jnmlwrapper.py new file mode 100644 index 00000000..ba53169b --- /dev/null +++ b/pyneuroml/utils/jnmlwrapper.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +""" +Simple wrapper around jNeuroML to allow users to use jnml using the version +bundled in PyNeuroML + +File: pyneuroml/utils/jnmlwrapper.py + +Copyright 2025 NeuroML contributors +Author: Ankur Sinha +""" + +import os +import sys + +from ..runners import run_jneuroml + + +def __jnmlwrapper(): + """Wrapper around jNeuroML jar shipped with PyNeuroML. + + The following environment variables can be set: + + - JNML_MAX_MEMORY_LOCAL: set the maximum memory available to the Java + Virtual Machine (default: 400M) + + """ + max_memory = os.getenv("JNML_MAX_MEMORY_LOCAL", "400M") + + run_jneuroml( + pre_args=" ".join(sys.argv[1:]), + target_file="", + post_args="", + max_memory=max_memory, + report_jnml_output=True, + output_prefix="", + ) From fbb5c2a3ef19e73647ac3a03c89650a51050175a Mon Sep 17 00:00:00 2001 From: "Ankur Sinha (Ankur Sinha Gmail)" Date: Thu, 12 Jun 2025 11:43:13 +0100 Subject: [PATCH 2/7] feat(setup): add a jnml console script --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 303cc311..57934ff4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -58,6 +58,7 @@ console_scripts = pynml-sonata = neuromllite.SonataReader:main pynml-xpp = pyneuroml.xppaut:main pynml-swc2nml = pyneuroml.swc.ExportNML:main + jnml = pyneuroml.utils.jnmlwrapper:__jnmlwrapper [options.package_data] * = From a921feb6a62f54bf30521c95c3f3b40ae45c994e Mon Sep 17 00:00:00 2001 From: "Ankur Sinha (Ankur Sinha Gmail)" Date: Thu, 12 Jun 2025 11:45:55 +0100 Subject: [PATCH 3/7] ci: also test newly added `jnml` wrapper --- test-ghactions.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-ghactions.sh b/test-ghactions.sh index 11b9f47b..4db3520e 100755 --- a/test-ghactions.sh +++ b/test-ghactions.sh @@ -28,7 +28,7 @@ echo "## Testing all CLI tools" full_path=$(command -v pynml) bin_location=$(dirname $full_path) -for f in ${bin_location}/pynml* +for f in ${bin_location}/pynml* ${bin_location}/jnml do current_exec=$(basename $f) echo "-> Testing $current_exec runs" From 59d5e65f9d7c9624a9b1a55e427564a3f4c26fc1 Mon Sep 17 00:00:00 2001 From: "Ankur Sinha (Ankur Sinha Gmail)" Date: Thu, 12 Jun 2025 12:02:53 +0100 Subject: [PATCH 4/7] feat(jnml): remove pyneuroml output wrapping This ensures that the output matches what running `jnml` from the jNeuroML repo would show. This is required for omv etc. to continue functioning as is. --- pyneuroml/utils/jnmlwrapper.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pyneuroml/utils/jnmlwrapper.py b/pyneuroml/utils/jnmlwrapper.py index ba53169b..d0eeb5cd 100644 --- a/pyneuroml/utils/jnmlwrapper.py +++ b/pyneuroml/utils/jnmlwrapper.py @@ -9,6 +9,7 @@ Author: Ankur Sinha """ +import logging import os import sys @@ -25,12 +26,15 @@ def __jnmlwrapper(): """ max_memory = os.getenv("JNML_MAX_MEMORY_LOCAL", "400M") + logging.getLogger("pyneuroml.runners").setLevel(logging.ERROR) - run_jneuroml( + output = run_jneuroml( pre_args=" ".join(sys.argv[1:]), target_file="", post_args="", max_memory=max_memory, - report_jnml_output=True, + report_jnml_output=False, output_prefix="", + return_string=True, ) + print(output[1]) From eaa1c59c404c79a5d3517c2e5dc53e37ffd3fa53 Mon Sep 17 00:00:00 2001 From: "Ankur Sinha (Ankur Sinha Gmail)" Date: Thu, 12 Jun 2025 16:49:16 +0100 Subject: [PATCH 5/7] feat(jnml): fix output to stdout --- pyneuroml/utils/jnmlwrapper.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyneuroml/utils/jnmlwrapper.py b/pyneuroml/utils/jnmlwrapper.py index d0eeb5cd..35a31865 100644 --- a/pyneuroml/utils/jnmlwrapper.py +++ b/pyneuroml/utils/jnmlwrapper.py @@ -37,4 +37,6 @@ def __jnmlwrapper(): output_prefix="", return_string=True, ) - print(output[1]) + # if it errors, it'll always print out the command line output + if output[0]: + print(output[1]) From 5fc40035981ef7389c945208cd1a49397bee0f0a Mon Sep 17 00:00:00 2001 From: "Ankur Sinha (Ankur Sinha Gmail)" Date: Fri, 20 Jun 2025 15:48:44 +0100 Subject: [PATCH 6/7] fix(jnml-wrapper): ensure that command fails properly So that other things depending on it, like omv, get the right exit code to process. --- pyneuroml/utils/jnmlwrapper.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pyneuroml/utils/jnmlwrapper.py b/pyneuroml/utils/jnmlwrapper.py index 35a31865..ecbd4634 100644 --- a/pyneuroml/utils/jnmlwrapper.py +++ b/pyneuroml/utils/jnmlwrapper.py @@ -26,9 +26,9 @@ def __jnmlwrapper(): """ max_memory = os.getenv("JNML_MAX_MEMORY_LOCAL", "400M") - logging.getLogger("pyneuroml.runners").setLevel(logging.ERROR) + logging.getLogger("pyneuroml.runners").setLevel(logging.CRITICAL) - output = run_jneuroml( + retstat, output = run_jneuroml( pre_args=" ".join(sys.argv[1:]), target_file="", post_args="", @@ -36,7 +36,10 @@ def __jnmlwrapper(): report_jnml_output=False, output_prefix="", return_string=True, + exit_on_fail=True, ) - # if it errors, it'll always print out the command line output - if output[0]: - print(output[1]) + + # if command ran successfully, print the output + # if it didn't, `run_jneuroml` will throw a critical error + if retstat is True: + print(output) From 650ed42cc2db62fc257df9571de4594234f06949 Mon Sep 17 00:00:00 2001 From: "Ankur Sinha (Ankur Sinha Gmail)" Date: Fri, 20 Jun 2025 22:50:42 +0100 Subject: [PATCH 7/7] chore: reduce logging level of some errors --- pyneuroml/runners.py | 6 +++--- pyneuroml/utils/jnmlwrapper.py | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/pyneuroml/runners.py b/pyneuroml/runners.py index 1ed716ee..1c51ee42 100644 --- a/pyneuroml/runners.py +++ b/pyneuroml/runners.py @@ -922,7 +922,7 @@ def execute_command_in_dir_with_realtime_output( raise e if not p.returncode == 0: - logger.critical( + logger.error( "*** Problem running command (return code: %s): [%s]" % (p.returncode, command) ) @@ -993,14 +993,14 @@ def execute_command_in_dir( return return_string.decode("utf-8") except subprocess.CalledProcessError as e: - logger.critical("*** Problem running last command: %s" % e) + logger.error("*** Problem running last command: %s" % e) print("####################################################################") print("%s%s" % (prefix, e.output.decode().replace("\n", "\n" + prefix))) print("####################################################################") return (e.returncode, e.output.decode()) except Exception as e: - logger.critical("*** Unknown problem running command: %s" % e) + logger.error("*** Unknown problem running command: %s" % e) return (-1, str(e)) diff --git a/pyneuroml/utils/jnmlwrapper.py b/pyneuroml/utils/jnmlwrapper.py index ecbd4634..aa013a06 100644 --- a/pyneuroml/utils/jnmlwrapper.py +++ b/pyneuroml/utils/jnmlwrapper.py @@ -36,10 +36,13 @@ def __jnmlwrapper(): report_jnml_output=False, output_prefix="", return_string=True, - exit_on_fail=True, + exit_on_fail=False, ) # if command ran successfully, print the output # if it didn't, `run_jneuroml` will throw a critical error if retstat is True: print(output) + sys.exit(0) + else: + sys.exit(1)