From 33a8005f711434b7291f16c570199d9288f6d64b Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Wed, 22 Apr 2026 22:10:45 -0700 Subject: [PATCH 01/24] intial pseudocode --- package/MDAnalysis/lib/log.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index d63ec547828..64edef04198 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -93,15 +93,15 @@ from .. import version -def start_logging(logfile="MDAnalysis.log", version=version.__version__): +def start_logging(stream="MDAnalysis.log", version=version.__version__): """Start logging of messages to file and console. The default logfile is named `MDAnalysis.log` and messages are logged with the tag *MDAnalysis*. """ - create("MDAnalysis", logfile=logfile) + create("MDAnalysis", stream=stream) logging.getLogger("MDAnalysis").info( - "MDAnalysis %s STARTED logging to %r", version, logfile + "MDAnalysis %s STARTED logging to %r", version, stream ) @@ -134,6 +134,14 @@ def create(logger_name="MDAnalysis", logfile="MDAnalysis.log"): logger.setLevel(logging.DEBUG) + ## TODO need logic for multiple handlers + # Pseudocode for create arguments and behavior + # 1. input : str -> create new log_file + # 2. File objects (like file.open() and sys.stdout/sys.stderr) -> new file + # 3. Iterable (like [file, sys.stdout]) -> create file and print to console + ## + + # handler that writes to logfile logfile_handler = logging.FileHandler(logfile) logfile_formatter = logging.Formatter( From 1bf0ee1e17fcd1665e7955d5683ee3dcf4389a58 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Wed, 22 Apr 2026 22:58:25 -0700 Subject: [PATCH 02/24] inital thought code (hadn't run it yet) --- package/MDAnalysis/lib/log.py | 37 +++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index 64edef04198..f63d36e18b5 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -87,6 +87,7 @@ import sys import logging import re +from collections.abc import Iterable from tqdm.auto import tqdm @@ -112,7 +113,7 @@ def stop_logging(): clear_handlers(logger) # this _should_ do the job... -def create(logger_name="MDAnalysis", logfile="MDAnalysis.log"): +def create(logger_name="MDAnalysis", stream="MDAnalysis.log", level='DEBUG'): """Create a top level logger. - The file logger logs everything (including DEBUG). @@ -132,7 +133,8 @@ def create(logger_name="MDAnalysis", logfile="MDAnalysis.log"): logger = logging.getLogger(logger_name) - logger.setLevel(logging.DEBUG) + #level parameter is from https://docs.python.org/3/library/logging.html#logging-levels + logger.setLevel(level.upper()) ## TODO need logic for multiple handlers # Pseudocode for create arguments and behavior @@ -141,6 +143,37 @@ def create(logger_name="MDAnalysis", logfile="MDAnalysis.log"): # 3. Iterable (like [file, sys.stdout]) -> create file and print to console ## + # create a master logger, and attach handles to it? + # See https://docs.python.org/3/library/logging.handlers.html# + # + + # am borowwing this pattern from fetch module + if isinstance(stream, str): + streams = tuple(stream,) + # Want to allow ndarrays, tuple, and list but exclude strings + elif isinstance(stream, Iterable): + streams = stream + else: + raise Exception('foobar') + + for stream in streams: + # https://docs.python.org/3/library/logging.handlers.html#streamhandler + # The StreamHandler class, located in the core logging package, + #sends logging output to streams such as sys.stdout, sys.stderr or + # any file-like object (or, more precisely, any object which supports write() and flush() methods). + + # note this is looks gross and probably need to be written - invert the if and else case? + if (hasattr(streams, 'flush') and callable(getattr(streams, 'flush'))) and \ + (hasattr(streams, 'write') and callable(getattr(streams, 'write'))): + handler = logging.StreamHandler(stream) + else: + handler = logging.FileHandler(stream) + + logger.addHandler(handler) + + return logger + + ## Old code # handler that writes to logfile logfile_handler = logging.FileHandler(logfile) From 24a6c313592ffc41b2314909b685df595261a392 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 07:28:36 -0700 Subject: [PATCH 03/24] more comments --- package/MDAnalysis/lib/log.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index f63d36e18b5..5871e3e5ec7 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -87,6 +87,7 @@ import sys import logging import re +import io from collections.abc import Iterable from tqdm.auto import tqdm @@ -138,19 +139,19 @@ def create(logger_name="MDAnalysis", stream="MDAnalysis.log", level='DEBUG'): ## TODO need logic for multiple handlers # Pseudocode for create arguments and behavior - # 1. input : str -> create new log_file - # 2. File objects (like file.open() and sys.stdout/sys.stderr) -> new file - # 3. Iterable (like [file, sys.stdout]) -> create file and print to console + # 1. input : file.log -> create file.log + # 2. File objects (like f = file.open() and sys.stdout/sys.stderr) -> log to stream + # 3. Iterable (like [file.log, sys.stdout]) -> create file.log and print to console ## - # create a master logger, and attach handles to it? + # create/call a master logger, and attach handles to it? # See https://docs.python.org/3/library/logging.handlers.html# # # am borowwing this pattern from fetch module - if isinstance(stream, str): - streams = tuple(stream,) # Want to allow ndarrays, tuple, and list but exclude strings + if isinstance(stream, str) or isinstance(stream, io.IOBase): + streams = (stream,) elif isinstance(stream, Iterable): streams = stream else: @@ -163,8 +164,9 @@ def create(logger_name="MDAnalysis", stream="MDAnalysis.log", level='DEBUG'): # any file-like object (or, more precisely, any object which supports write() and flush() methods). # note this is looks gross and probably need to be written - invert the if and else case? - if (hasattr(streams, 'flush') and callable(getattr(streams, 'flush'))) and \ - (hasattr(streams, 'write') and callable(getattr(streams, 'write'))): + # Doesn't work since FileHandler inherits from Steam handler + if (hasattr(stream, 'flush') and callable(getattr(stream, 'flush'))) and \ + (hasattr(stream, 'write') and callable(getattr(stream, 'write'))): handler = logging.StreamHandler(stream) else: handler = logging.FileHandler(stream) From 7a5c0b3614af02571dc1c716e2a3fc65a9e6bf2d Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 07:49:33 -0700 Subject: [PATCH 04/24] general cleanup --- package/MDAnalysis/lib/log.py | 37 ++++++++--------------------------- 1 file changed, 8 insertions(+), 29 deletions(-) diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index 5871e3e5ec7..6c57530e26e 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -87,7 +87,7 @@ import sys import logging import re -import io +import io, os from collections.abc import Iterable from tqdm.auto import tqdm @@ -149,8 +149,7 @@ def create(logger_name="MDAnalysis", stream="MDAnalysis.log", level='DEBUG'): # # am borowwing this pattern from fetch module - # Want to allow ndarrays, tuple, and list but exclude strings - if isinstance(stream, str) or isinstance(stream, io.IOBase): + if isinstance(stream, (str, os.PathLike)) or isinstance(stream, io.IOBase): streams = (stream,) elif isinstance(stream, Iterable): streams = stream @@ -163,38 +162,18 @@ def create(logger_name="MDAnalysis", stream="MDAnalysis.log", level='DEBUG'): #sends logging output to streams such as sys.stdout, sys.stderr or # any file-like object (or, more precisely, any object which supports write() and flush() methods). - # note this is looks gross and probably need to be written - invert the if and else case? - # Doesn't work since FileHandler inherits from Steam handler - if (hasattr(stream, 'flush') and callable(getattr(stream, 'flush'))) and \ - (hasattr(stream, 'write') and callable(getattr(stream, 'write'))): + # This only check the existance and not the functionality. Should be ok? + if hasattr(stream, "write") and hasattr(stream, "flush"): handler = logging.StreamHandler(stream) - else: + elif isinstance(stream, (str, os.PathLike)): handler = logging.FileHandler(stream) - + else: + raise Exception('foobar') + logger.addHandler(handler) return logger - ## Old code - - # handler that writes to logfile - logfile_handler = logging.FileHandler(logfile) - logfile_formatter = logging.Formatter( - "%(asctime)s %(name)-12s %(levelname)-8s %(message)s" - ) - logfile_handler.setFormatter(logfile_formatter) - logger.addHandler(logfile_handler) - - # define a Handler which writes INFO messages or higher to the sys.stderr - console_handler = logging.StreamHandler() - console_handler.setLevel(logging.INFO) - # set a format which is simpler for console use - formatter = logging.Formatter("%(name)-12s: %(levelname)-8s %(message)s") - console_handler.setFormatter(formatter) - logger.addHandler(console_handler) - - return logger - def clear_handlers(logger): """clean out handlers in the library top level logger From f1be0df87a98430e2aeb675b78e00bd68f797340 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 07:52:04 -0700 Subject: [PATCH 05/24] Post black Need to add a fmt_string parameter if want to replicate earlier behavior --- package/MDAnalysis/lib/log.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index 6c57530e26e..3b368052834 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -114,7 +114,7 @@ def stop_logging(): clear_handlers(logger) # this _should_ do the job... -def create(logger_name="MDAnalysis", stream="MDAnalysis.log", level='DEBUG'): +def create(logger_name="MDAnalysis", stream="MDAnalysis.log", level="DEBUG"): """Create a top level logger. - The file logger logs everything (including DEBUG). @@ -134,11 +134,11 @@ def create(logger_name="MDAnalysis", stream="MDAnalysis.log", level='DEBUG'): logger = logging.getLogger(logger_name) - #level parameter is from https://docs.python.org/3/library/logging.html#logging-levels + # level parameter is from https://docs.python.org/3/library/logging.html#logging-levels logger.setLevel(level.upper()) ## TODO need logic for multiple handlers - # Pseudocode for create arguments and behavior + # Pseudocode for create arguments and behavior # 1. input : file.log -> create file.log # 2. File objects (like f = file.open() and sys.stdout/sys.stderr) -> log to stream # 3. Iterable (like [file.log, sys.stdout]) -> create file.log and print to console @@ -147,31 +147,31 @@ def create(logger_name="MDAnalysis", stream="MDAnalysis.log", level='DEBUG'): # create/call a master logger, and attach handles to it? # See https://docs.python.org/3/library/logging.handlers.html# # - + # am borowwing this pattern from fetch module if isinstance(stream, (str, os.PathLike)) or isinstance(stream, io.IOBase): streams = (stream,) elif isinstance(stream, Iterable): streams = stream else: - raise Exception('foobar') - + raise Exception("foobar") + for stream in streams: # https://docs.python.org/3/library/logging.handlers.html#streamhandler # The StreamHandler class, located in the core logging package, - #sends logging output to streams such as sys.stdout, sys.stderr or + # sends logging output to streams such as sys.stdout, sys.stderr or # any file-like object (or, more precisely, any object which supports write() and flush() methods). - + # This only check the existance and not the functionality. Should be ok? if hasattr(stream, "write") and hasattr(stream, "flush"): handler = logging.StreamHandler(stream) elif isinstance(stream, (str, os.PathLike)): handler = logging.FileHandler(stream) else: - raise Exception('foobar') - + raise Exception("foobar") + logger.addHandler(handler) - + return logger From dbcfb1c5287b5ba4de9c75a491767f017b92f6f2 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 11:28:07 -0700 Subject: [PATCH 06/24] removed loop based control --- package/MDAnalysis/lib/log.py | 50 +++++++++++------------------------ 1 file changed, 15 insertions(+), 35 deletions(-) diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index 3b368052834..273338dbf10 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -87,7 +87,8 @@ import sys import logging import re -import io, os +import io +import os from collections.abc import Iterable from tqdm.auto import tqdm @@ -134,43 +135,22 @@ def create(logger_name="MDAnalysis", stream="MDAnalysis.log", level="DEBUG"): logger = logging.getLogger(logger_name) - # level parameter is from https://docs.python.org/3/library/logging.html#logging-levels logger.setLevel(level.upper()) - ## TODO need logic for multiple handlers - # Pseudocode for create arguments and behavior - # 1. input : file.log -> create file.log - # 2. File objects (like f = file.open() and sys.stdout/sys.stderr) -> log to stream - # 3. Iterable (like [file.log, sys.stdout]) -> create file.log and print to console - ## - - # create/call a master logger, and attach handles to it? - # See https://docs.python.org/3/library/logging.handlers.html# - # - - # am borowwing this pattern from fetch module - if isinstance(stream, (str, os.PathLike)) or isinstance(stream, io.IOBase): - streams = (stream,) - elif isinstance(stream, Iterable): - streams = stream + # https://docs.python.org/3/library/logging.handlers.html#streamhandler + # The StreamHandler class, located in the core logging package, + # sends logging output to streams such as sys.stdout, sys.stderr or + # any file-like object (or, more precisely, any object which supports write() and flush() methods). + + # This only check the existance and not the functionality. Should be ok? + if hasattr(stream, "write") and hasattr(stream, "flush"): + handler = logging.StreamHandler(stream) + elif isinstance(stream, (str, os.PathLike)): + handler = logging.FileHandler(stream) else: - raise Exception("foobar") - - for stream in streams: - # https://docs.python.org/3/library/logging.handlers.html#streamhandler - # The StreamHandler class, located in the core logging package, - # sends logging output to streams such as sys.stdout, sys.stderr or - # any file-like object (or, more precisely, any object which supports write() and flush() methods). - - # This only check the existance and not the functionality. Should be ok? - if hasattr(stream, "write") and hasattr(stream, "flush"): - handler = logging.StreamHandler(stream) - elif isinstance(stream, (str, os.PathLike)): - handler = logging.FileHandler(stream) - else: - raise Exception("foobar") - - logger.addHandler(handler) + raise TypeError("Input Stream is neither a file or a steam") + + logger.addHandler(handler) return logger From 93966fbb56f87deeb2e9999b06aa2e73a96150f7 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 11:34:01 -0700 Subject: [PATCH 07/24] Added formatter to handler --- package/MDAnalysis/lib/log.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index 273338dbf10..63d44fed314 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -115,7 +115,7 @@ def stop_logging(): clear_handlers(logger) # this _should_ do the job... -def create(logger_name="MDAnalysis", stream="MDAnalysis.log", level="DEBUG"): +def create(logger_name="MDAnalysis", stream="MDAnalysis.log", level="DEBUG", fmt=None): """Create a top level logger. - The file logger logs everything (including DEBUG). @@ -148,8 +148,9 @@ def create(logger_name="MDAnalysis", stream="MDAnalysis.log", level="DEBUG"): elif isinstance(stream, (str, os.PathLike)): handler = logging.FileHandler(stream) else: - raise TypeError("Input Stream is neither a file or a steam") - + raise TypeError("Input Stream is neither a file or a stream") + + handler.setFormatter(logging.Formatter(fmt)) logger.addHandler(handler) return logger From c5f6133887499f557ecedd201acaf38ade5589c4 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 11:39:10 -0700 Subject: [PATCH 08/24] Added format to start_logging() to replicate old behavior --- package/MDAnalysis/lib/log.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index 63d44fed314..9da468c3e6a 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -102,7 +102,18 @@ def start_logging(stream="MDAnalysis.log", version=version.__version__): The default logfile is named `MDAnalysis.log` and messages are logged with the tag *MDAnalysis*. """ - create("MDAnalysis", stream=stream) + create( + "MDAnalysis", + stream=stream, + level="DEBUG", + fmt="%(asctime)s %(name)-12s %(levelname)-8s %(message)s", + ) + create( + "MDAnalysis", + stream=sys.stdout, + level="INFO", + fmt="%(name)-12s: %(levelname)-8s %(message)s", + ) logging.getLogger("MDAnalysis").info( "MDAnalysis %s STARTED logging to %r", version, stream ) @@ -115,7 +126,9 @@ def stop_logging(): clear_handlers(logger) # this _should_ do the job... -def create(logger_name="MDAnalysis", stream="MDAnalysis.log", level="DEBUG", fmt=None): +def create( + logger_name="MDAnalysis", stream="MDAnalysis.log", level="DEBUG", fmt=None +): """Create a top level logger. - The file logger logs everything (including DEBUG). @@ -149,7 +162,7 @@ def create(logger_name="MDAnalysis", stream="MDAnalysis.log", level="DEBUG", fmt handler = logging.FileHandler(stream) else: raise TypeError("Input Stream is neither a file or a stream") - + handler.setFormatter(logging.Formatter(fmt)) logger.addHandler(handler) From e30d0ab9351d4c73667e586c2e34b3dac500df0a Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 11:41:20 -0700 Subject: [PATCH 09/24] Applied flake8 --- package/MDAnalysis/lib/log.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index 9da468c3e6a..1e4fdff7228 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -86,10 +86,7 @@ """ import sys import logging -import re -import io import os -from collections.abc import Iterable from tqdm.auto import tqdm @@ -151,9 +148,11 @@ def create( logger.setLevel(level.upper()) # https://docs.python.org/3/library/logging.handlers.html#streamhandler + # # The StreamHandler class, located in the core logging package, # sends logging output to streams such as sys.stdout, sys.stderr or - # any file-like object (or, more precisely, any object which supports write() and flush() methods). + # any file-like object (or, more precisely, any object which supports + # write() and flush() methods). # This only check the existance and not the functionality. Should be ok? if hasattr(stream, "write") and hasattr(stream, "flush"): From 5a5906d0a84ed6bd719dd0f663aab33caa369ce7 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 12:11:55 -0700 Subject: [PATCH 10/24] notes for test later --- package/MDAnalysis/lib/log.py | 5 ++--- testsuite/MDAnalysisTests/utils/test_log.py | 2 ++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index 1e4fdff7228..eef30ee1769 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -111,9 +111,6 @@ def start_logging(stream="MDAnalysis.log", version=version.__version__): level="INFO", fmt="%(name)-12s: %(levelname)-8s %(message)s", ) - logging.getLogger("MDAnalysis").info( - "MDAnalysis %s STARTED logging to %r", version, stream - ) def stop_logging(): @@ -165,6 +162,8 @@ def create( handler.setFormatter(logging.Formatter(fmt)) logger.addHandler(handler) + logger.info(f"MDAnalysis {version} STARTED logging to {stream!r}") + return logger diff --git a/testsuite/MDAnalysisTests/utils/test_log.py b/testsuite/MDAnalysisTests/utils/test_log.py index 0abaed2795d..cfb2194e7bd 100644 --- a/testsuite/MDAnalysisTests/utils/test_log.py +++ b/testsuite/MDAnalysisTests/utils/test_log.py @@ -40,6 +40,8 @@ def test_start_stop_logging(): finally: MDAnalysis.log.stop_logging() +# Write Tests here for later +# Idk why there are two seperate tests files: lib/test_log.py and utils/test_log.py class RedirectedStderr(object): """Temporarily replaces sys.stderr with *stream*. From f679251e7bfc401c760f51e88151d97ed3b3ce31 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 12:19:06 -0700 Subject: [PATCH 11/24] fix linter --- testsuite/MDAnalysisTests/utils/test_log.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/testsuite/MDAnalysisTests/utils/test_log.py b/testsuite/MDAnalysisTests/utils/test_log.py index cfb2194e7bd..9c9f99153dc 100644 --- a/testsuite/MDAnalysisTests/utils/test_log.py +++ b/testsuite/MDAnalysisTests/utils/test_log.py @@ -40,9 +40,11 @@ def test_start_stop_logging(): finally: MDAnalysis.log.stop_logging() -# Write Tests here for later + +# Write Tests here for later # Idk why there are two seperate tests files: lib/test_log.py and utils/test_log.py + class RedirectedStderr(object): """Temporarily replaces sys.stderr with *stream*. From 2ba1c2e40a5e72582e7c81883597904be0f410b9 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 20:30:58 -0700 Subject: [PATCH 12/24] put logger back to start_logging() --- package/MDAnalysis/lib/log.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index eef30ee1769..04fd49c61fc 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -90,6 +90,8 @@ from tqdm.auto import tqdm +# from MDAnalysis.lib.util import deprecate + from .. import version @@ -111,6 +113,9 @@ def start_logging(stream="MDAnalysis.log", version=version.__version__): level="INFO", fmt="%(name)-12s: %(levelname)-8s %(message)s", ) + logging.getLogger("MDAnalysis").info( + f"MDAnalysis {version} STARTED logging to {stream!r}" + ) def stop_logging(): @@ -162,8 +167,6 @@ def create( handler.setFormatter(logging.Formatter(fmt)) logger.addHandler(handler) - logger.info(f"MDAnalysis {version} STARTED logging to {stream!r}") - return logger From cca9258e7f8a5ac6938064c703ead801ac062fba Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 20:40:39 -0700 Subject: [PATCH 13/24] cleaned up create() --- package/MDAnalysis/lib/log.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index 04fd49c61fc..a3f099d6a54 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -126,7 +126,11 @@ def stop_logging(): def create( - logger_name="MDAnalysis", stream="MDAnalysis.log", level="DEBUG", fmt=None + logger_name="MDAnalysis", + stream="MDAnalysis.log", + level="DEBUG", + fmt=None, + mode="a", ): """Create a top level logger. @@ -146,23 +150,18 @@ def create( """ logger = logging.getLogger(logger_name) - logger.setLevel(level.upper()) + # This checks for file-like object per duck typing # https://docs.python.org/3/library/logging.handlers.html#streamhandler - # - # The StreamHandler class, located in the core logging package, - # sends logging output to streams such as sys.stdout, sys.stderr or - # any file-like object (or, more precisely, any object which supports - # write() and flush() methods). - - # This only check the existance and not the functionality. Should be ok? if hasattr(stream, "write") and hasattr(stream, "flush"): handler = logging.StreamHandler(stream) elif isinstance(stream, (str, os.PathLike)): - handler = logging.FileHandler(stream) + handler = logging.FileHandler(stream, mode=mode) else: - raise TypeError("Input Stream is neither a file or a stream") + raise TypeError( + "Input Stream is neither a string, PathLike object or a stream" + ) handler.setFormatter(logging.Formatter(fmt)) logger.addHandler(handler) From 94f056f5ef168f814f12c6469d3407ef6cd48309 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 21:15:54 -0700 Subject: [PATCH 14/24] removed MDAnalysis NullHandler --- package/MDAnalysis/__init__.py | 3 +-- package/MDAnalysis/lib/log.py | 17 ----------------- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/package/MDAnalysis/__init__.py b/package/MDAnalysis/__init__.py index 6843c3738ab..54388565846 100644 --- a/package/MDAnalysis/__init__.py +++ b/package/MDAnalysis/__init__.py @@ -197,10 +197,9 @@ StreamWarning, ) -from .lib import log from .lib.log import start_logging, stop_logging -logging.getLogger("MDAnalysis").addHandler(log.NullHandler()) +logging.getLogger("MDAnalysis").addHandler(logging.NullHandler()) del logging # only MDAnalysis DeprecationWarnings are loud by default diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index a3f099d6a54..4ccb74dc272 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -179,23 +179,6 @@ def clear_handlers(logger): logger.removeHandler(h) -class NullHandler(logging.Handler): - """Silent Handler. - - Useful as a default:: - - h = NullHandler() - logging.getLogger("MDAnalysis").addHandler(h) - del h - - see the advice on logging and libraries in - http://docs.python.org/library/logging.html?#configuring-logging-for-a-library - """ - - def emit(self, record): - pass - - class ProgressBar(tqdm): r"""Display a visual progress bar and time estimate. From 7f5812fa92f9ccb87cefc0b263bddf9a7790bd1b Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 21:36:06 -0700 Subject: [PATCH 15/24] readded top level import -- it broke some tests --- package/MDAnalysis/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/package/MDAnalysis/__init__.py b/package/MDAnalysis/__init__.py index 54388565846..8b5db410db5 100644 --- a/package/MDAnalysis/__init__.py +++ b/package/MDAnalysis/__init__.py @@ -197,6 +197,7 @@ StreamWarning, ) +from .lib import log from .lib.log import start_logging, stop_logging logging.getLogger("MDAnalysis").addHandler(logging.NullHandler()) From ba9315e379988e0a256791e184f62d2ab1c30a91 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 21:43:19 -0700 Subject: [PATCH 16/24] ported over tests --- testsuite/MDAnalysisTests/lib/test_log.py | 14 +++- testsuite/MDAnalysisTests/utils/test_log.py | 79 --------------------- 2 files changed, 12 insertions(+), 81 deletions(-) delete mode 100644 testsuite/MDAnalysisTests/utils/test_log.py diff --git a/testsuite/MDAnalysisTests/lib/test_log.py b/testsuite/MDAnalysisTests/lib/test_log.py index 541660ca4c7..6cd4ecffd26 100644 --- a/testsuite/MDAnalysisTests/lib/test_log.py +++ b/testsuite/MDAnalysisTests/lib/test_log.py @@ -20,11 +20,21 @@ # MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # -import warnings -import pytest +import logging + +import MDAnalysis from MDAnalysis.lib.log import ProgressBar +def test_start_stop_logging(): + try: + MDAnalysis.log.start_logging() + logger = logging.getLogger("MDAnalysis") + logger.info("Using the MDAnalysis logger works") + except Exception as err: + raise AssertionError("Problem with logger: {0}".format(err)) + finally: + MDAnalysis.log.stop_logging() class TestProgressBar(object): diff --git a/testsuite/MDAnalysisTests/utils/test_log.py b/testsuite/MDAnalysisTests/utils/test_log.py deleted file mode 100644 index 9c9f99153dc..00000000000 --- a/testsuite/MDAnalysisTests/utils/test_log.py +++ /dev/null @@ -1,79 +0,0 @@ -# -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*- -# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 fileencoding=utf-8 -# -# MDAnalysis --- https://www.mdanalysis.org -# Copyright (c) 2006-2017 The MDAnalysis Development Team and contributors -# (see the file AUTHORS for the full list of names) -# -# Released under the Lesser GNU Public Licence, v2.1 or any higher version -# -# Please cite your use of MDAnalysis in published work: -# -# R. J. Gowers, M. Linke, J. Barnoud, T. J. E. Reddy, M. N. Melo, S. L. Seyler, -# D. L. Dotson, J. Domanski, S. Buchoux, I. M. Kenney, and O. Beckstein. -# MDAnalysis: A Python package for the rapid analysis of molecular dynamics -# simulations. In S. Benthall and S. Rostrup editors, Proceedings of the 15th -# Python in Science Conference, pages 102-109, Austin, TX, 2016. SciPy. -# doi: 10.25080/majora-629e541a-00e -# -# N. Michaud-Agrawal, E. J. Denning, T. B. Woolf, and O. Beckstein. -# MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. -# J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 -# -from io import StringIO - -import logging -import sys - -import MDAnalysis -import MDAnalysis.lib.log -import pytest - - -def test_start_stop_logging(): - try: - MDAnalysis.log.start_logging() - logger = logging.getLogger("MDAnalysis") - logger.info("Using the MDAnalysis logger works") - except Exception as err: - raise AssertionError("Problem with logger: {0}".format(err)) - finally: - MDAnalysis.log.stop_logging() - - -# Write Tests here for later -# Idk why there are two seperate tests files: lib/test_log.py and utils/test_log.py - - -class RedirectedStderr(object): - """Temporarily replaces sys.stderr with *stream*. - - Deals with cached stderr, see - http://stackoverflow.com/questions/6796492/temporarily-redirect-stdout-stderr - """ - - def __init__(self, stream=None): - self._stderr = sys.stderr - self.stream = stream or sys.stdout - - def __enter__(self): - self.old_stderr = sys.stderr - self.old_stderr.flush() - sys.stderr = self.stream - - def __exit__(self, exc_type, exc_value, traceback): - self._stderr.flush() - sys.stderr = self.old_stderr - - -@pytest.fixture() -def buffer(): - return StringIO() - - -def _assert_in(output, string): - assert ( - string in output - ), "Output '{0}' does not match required format '{1}'.".format( - output.replace("\r", "\\r"), string.replace("\r", "\\r") - ) From 0e3623177a46c67384d4ff6ffcd29d4d0eaf78c3 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 21:45:56 -0700 Subject: [PATCH 17/24] Used black --- testsuite/MDAnalysisTests/lib/test_log.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/testsuite/MDAnalysisTests/lib/test_log.py b/testsuite/MDAnalysisTests/lib/test_log.py index 6cd4ecffd26..4efd69e1af0 100644 --- a/testsuite/MDAnalysisTests/lib/test_log.py +++ b/testsuite/MDAnalysisTests/lib/test_log.py @@ -21,11 +21,12 @@ # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # -import logging +import logging import MDAnalysis from MDAnalysis.lib.log import ProgressBar + def test_start_stop_logging(): try: MDAnalysis.log.start_logging() @@ -36,6 +37,7 @@ def test_start_stop_logging(): finally: MDAnalysis.log.stop_logging() + class TestProgressBar(object): def test_output(self, capsys): From e63f5ef6a399dd06fc65656d314976851cf024d3 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 23:19:10 -0700 Subject: [PATCH 18/24] Added Tests for start_logging() and stop_logging() --- package/MDAnalysis/lib/log.py | 4 +- testsuite/MDAnalysisTests/lib/test_log.py | 50 +++++++++++++++++++---- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index 4ccb74dc272..ad3acaff484 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -150,7 +150,6 @@ def create( """ logger = logging.getLogger(logger_name) - logger.setLevel(level.upper()) # This checks for file-like object per duck typing # https://docs.python.org/3/library/logging.handlers.html#streamhandler @@ -163,6 +162,7 @@ def create( "Input Stream is neither a string, PathLike object or a stream" ) + handler.setLevel(level.upper()) handler.setFormatter(logging.Formatter(fmt)) logger.addHandler(handler) @@ -175,7 +175,7 @@ def clear_handlers(logger): (only important for reload/debug cycles...) """ - for h in logger.handlers: + for h in list(logger.handlers): logger.removeHandler(h) diff --git a/testsuite/MDAnalysisTests/lib/test_log.py b/testsuite/MDAnalysisTests/lib/test_log.py index 4efd69e1af0..672ed3ddd6e 100644 --- a/testsuite/MDAnalysisTests/lib/test_log.py +++ b/testsuite/MDAnalysisTests/lib/test_log.py @@ -21,21 +21,53 @@ # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # +import sys import logging +import MDAnalysis as mda -import MDAnalysis +from os.path import basename from MDAnalysis.lib.log import ProgressBar -def test_start_stop_logging(): - try: - MDAnalysis.log.start_logging() +class TestConvenienceFunctions: + def test_start_logging(self, tmp_path): + mda.start_logging(tmp_path / "MDAnalysis.log") logger = logging.getLogger("MDAnalysis") - logger.info("Using the MDAnalysis logger works") - except Exception as err: - raise AssertionError("Problem with logger: {0}".format(err)) - finally: - MDAnalysis.log.stop_logging() + + # Test Handlers' presence and behavior + assert any(isinstance(h, logging.NullHandler) for h in logger.handlers) + any( + isinstance(h, logging.FileHandler) + and basename(h.stream.name) == "MDAnalysis.log" + and h.level == logging.DEBUG + for h in logger.handlers + ) + assert any( + isinstance(h, logging.StreamHandler) + and h.stream is sys.stdout + and h.level == logging.INFO + for h in logger.handlers + ) + + def test_stop_logging(self, tmp_path): + mda.lib.log.start_logging(tmp_path / "MDAnalysis.log") + logger = logging.getLogger("MDAnalysis") + mda.lib.log.stop_logging() + + assert len(logger.handlers) == 0 + + +# This doesn't test functionality at all +# Need rewrite +# def test_start_stop_logging(): +# try: +# MDAnalysis.log.start_logging() +# logger = logging.getLogger("MDAnalysis") +# logger.info("Using the MDAnalysis logger works") +# except Exception as err: +# raise AssertionError("Problem with logger: {0}".format(err)) +# finally: +# MDAnalysis.log.stop_logging() class TestProgressBar(object): From a81e4adedfb217bd2ceae00f62ff0070fc2e5c9a Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 23:25:23 -0700 Subject: [PATCH 19/24] Added comment --- testsuite/MDAnalysisTests/lib/test_log.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testsuite/MDAnalysisTests/lib/test_log.py b/testsuite/MDAnalysisTests/lib/test_log.py index 672ed3ddd6e..8b8c114e12b 100644 --- a/testsuite/MDAnalysisTests/lib/test_log.py +++ b/testsuite/MDAnalysisTests/lib/test_log.py @@ -34,7 +34,7 @@ def test_start_logging(self, tmp_path): mda.start_logging(tmp_path / "MDAnalysis.log") logger = logging.getLogger("MDAnalysis") - # Test Handlers' presence and behavior + # Test expected handlers' presence and behavior assert any(isinstance(h, logging.NullHandler) for h in logger.handlers) any( isinstance(h, logging.FileHandler) From f25aec7440165fd0f0169d9d8c01dc6ef274db18 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 23:25:23 -0700 Subject: [PATCH 20/24] Added comment --- testsuite/MDAnalysisTests/lib/test_log.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testsuite/MDAnalysisTests/lib/test_log.py b/testsuite/MDAnalysisTests/lib/test_log.py index 672ed3ddd6e..8b8c114e12b 100644 --- a/testsuite/MDAnalysisTests/lib/test_log.py +++ b/testsuite/MDAnalysisTests/lib/test_log.py @@ -34,7 +34,7 @@ def test_start_logging(self, tmp_path): mda.start_logging(tmp_path / "MDAnalysis.log") logger = logging.getLogger("MDAnalysis") - # Test Handlers' presence and behavior + # Test expected handlers' presence and behavior assert any(isinstance(h, logging.NullHandler) for h in logger.handlers) any( isinstance(h, logging.FileHandler) From bdaf241e7ae621a4e71797e2b6c2f78440752e5a Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 23:28:30 -0700 Subject: [PATCH 21/24] post black --- testsuite/MDAnalysisTests/lib/test_log.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/testsuite/MDAnalysisTests/lib/test_log.py b/testsuite/MDAnalysisTests/lib/test_log.py index 8b8c114e12b..d8a0a6e9e21 100644 --- a/testsuite/MDAnalysisTests/lib/test_log.py +++ b/testsuite/MDAnalysisTests/lib/test_log.py @@ -57,19 +57,6 @@ def test_stop_logging(self, tmp_path): assert len(logger.handlers) == 0 -# This doesn't test functionality at all -# Need rewrite -# def test_start_stop_logging(): -# try: -# MDAnalysis.log.start_logging() -# logger = logging.getLogger("MDAnalysis") -# logger.info("Using the MDAnalysis logger works") -# except Exception as err: -# raise AssertionError("Problem with logger: {0}".format(err)) -# finally: -# MDAnalysis.log.stop_logging() - - class TestProgressBar(object): def test_output(self, capsys): From d0b732f3ba78a9b8deaf2043a55f151d0b5fc749 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Fri, 24 Apr 2026 08:14:20 -0700 Subject: [PATCH 22/24] Added comments about deprecations --- package/MDAnalysis/__init__.py | 2 +- package/MDAnalysis/lib/log.py | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/package/MDAnalysis/__init__.py b/package/MDAnalysis/__init__.py index 8b5db410db5..fe9023f694a 100644 --- a/package/MDAnalysis/__init__.py +++ b/package/MDAnalysis/__init__.py @@ -200,7 +200,7 @@ from .lib import log from .lib.log import start_logging, stop_logging -logging.getLogger("MDAnalysis").addHandler(logging.NullHandler()) +logging.getLogger(__name__).addHandler(logging.NullHandler()) del logging # only MDAnalysis DeprecationWarnings are loud by default diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index ad3acaff484..a50399c83dc 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -94,6 +94,11 @@ from .. import version +# Things to deprecated: +# logfile -> stream (bc it could be any stream like object) +# +# version? Why would any user want to modify version in the first place? +# Just have the message logged it directly def start_logging(stream="MDAnalysis.log", version=version.__version__): """Start logging of messages to file and console. @@ -124,7 +129,22 @@ def stop_logging(): logger.info("MDAnalysis STOPPED logging") clear_handlers(logger) # this _should_ do the job... +# Things to deprecated overall: +# log.NullHandler -> replace with logging.NullHandler() warning +# create() -> add_handler() (confusing bc standard library has the same method) +# or create_handler()? +# +# For create(): +# logger_name -> None (very dangerous to have user choose since all loggers share the same namespace) +# logfile -> stream (bc it could be any stream like object, not just logfiles) + +# logger_name should be deprecated. +# Standard library recommends constructing through +# logging.getLogger(__name__) because ll loggers share the same namespace +# __name__ is MDAnalysis +# +# 2nd paragraph: https://docs.python.org/3/library/logging.html#logger-objects def create( logger_name="MDAnalysis", stream="MDAnalysis.log", From ec0ed2549c226d07606f2b81444393e6f1b5afb3 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Fri, 24 Apr 2026 08:22:59 -0700 Subject: [PATCH 23/24] Replaced logging.getLogger(MDAnalysis) with logging.getLogger(__name__) --- package/MDAnalysis/lib/log.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index a50399c83dc..2dbb17e3f24 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -118,14 +118,14 @@ def start_logging(stream="MDAnalysis.log", version=version.__version__): level="INFO", fmt="%(name)-12s: %(levelname)-8s %(message)s", ) - logging.getLogger("MDAnalysis").info( + logging.getLogger(__name__).info( f"MDAnalysis {version} STARTED logging to {stream!r}" ) def stop_logging(): """Stop logging to logfile and console.""" - logger = logging.getLogger("MDAnalysis") + logger = logging.getLogger(__name__) logger.info("MDAnalysis STOPPED logging") clear_handlers(logger) # this _should_ do the job... From 6d1a675884e34b3338b8f10fc045786ab976d4fd Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Fri, 24 Apr 2026 08:33:35 -0700 Subject: [PATCH 24/24] refined comments --- package/MDAnalysis/lib/log.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index 2dbb17e3f24..ab6e2c41288 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -100,6 +100,7 @@ # version? Why would any user want to modify version in the first place? # Just have the message logged it directly + def start_logging(stream="MDAnalysis.log", version=version.__version__): """Start logging of messages to file and console. @@ -129,22 +130,24 @@ def stop_logging(): logger.info("MDAnalysis STOPPED logging") clear_handlers(logger) # this _should_ do the job... + # Things to deprecated overall: # log.NullHandler -> replace with logging.NullHandler() warning -# create() -> add_handler() (confusing bc standard library has the same method) -# or create_handler()? +# create() -> add_handler() (potentially confusing bc standard library +# has the same method but in camelcase weirdly?) +# or create_handler()? # # For create(): -# logger_name -> None (very dangerous to have user choose since all loggers share the same namespace) # logfile -> stream (bc it could be any stream like object, not just logfiles) - - # logger_name should be deprecated. +# # Standard library recommends constructing through -# logging.getLogger(__name__) because ll loggers share the same namespace +# logging.getLogger(__name__) because all loggers share the same namespace +# and this is a systemmatic way of defining loggers +# # __name__ is MDAnalysis +# See 2nd paragraph: https://docs.python.org/3/library/logging.html#logger-objects # -# 2nd paragraph: https://docs.python.org/3/library/logging.html#logger-objects def create( logger_name="MDAnalysis", stream="MDAnalysis.log", @@ -169,6 +172,7 @@ def create( http://docs.python.org/library/logging.html?#logging-to-multiple-destinations """ + # replaced with logging.getLogger(__name__) logger = logging.getLogger(logger_name) # This checks for file-like object per duck typing