From 60c3ad27740fe9b71188ea3d42247791b7f0d3dd Mon Sep 17 00:00:00 2001 From: Garrett Koller Date: Tue, 10 Aug 2021 23:01:51 -0400 Subject: [PATCH 001/162] Patch: Document metaprogrammed module attributes Expanded documentation to include dynamically-generated module-level attributes that cannot be detected or documented without running the code. Specifically: * adafruit_logging.NOTSET * adafruit_logging.DEBUG * adafruit_logging.INFO * adafruit_logging.WARNING * adafruit_logging.ERROR * adafruit_logging.CRITICAL are all module-level attributes that are generated using this block of metaprogramming (line 65): ``` LEVELS = [ (00, "NOTSET"), (10, "DEBUG"), (20, "INFO"), (30, "WARNING"), (40, "ERROR"), (50, "CRITICAL"), ] for level_value, level_name in LEVELS: globals()[level_name] = level_value ``` --- adafruit_logging.py | 151 +++++++++++++++++++++++++++++--------------- 1 file changed, 99 insertions(+), 52 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index a095315..3aaf755 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -2,6 +2,7 @@ # # SPDX-License-Identifier: MIT +# noinspection PyUnresolvedReferences """ `adafruit_logging` ================================================================================ @@ -22,6 +23,30 @@ * Adafruit CircuitPython firmware for the supported boards: https://github.com/adafruit/circuitpython/releases +Attributes +---------- + LEVELS : list + A list of tuples representing the valid logging levels used by + this module. Each tuple contains exactly two elements: one int and one + str. The int in each tuple represents the relative severity of that + level (00 to 50). The str in each tuple is the string representation of + that logging level ("NOTSET" to "CRITICAL"; see below). + NOTSET : int + The NOTSET logging level, which is a dummy logging level that can be + used to indicate that a `Logger` should not print any logging messages, + regardless of how severe those messages might be (including CRITICAL). + DEBUG : int + The DEBUG logging level, which is the lowest (least severe) real level. + INFO : int + The INFO logging level for informative/informational messages. + WARNING : int + The WARNING logging level for warnings that should be addressed/fixed. + ERROR : int + The ERROR logging level for Python exceptions that occur during runtime. + CRITICAL : int + The CRITICAL logging level, which is the highest (most severe) level for + unrecoverable errors that have caused the code to halt and exit. + """ # pylint:disable=redefined-outer-name,consider-using-enumerate,no-self-use # pylint:disable=invalid-name @@ -30,6 +55,11 @@ __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Logger.git" +# noinspection PyUnresolvedReferences +# pylint:disable=undefined-all-variable +__all__ = ['LEVELS', 'NOTSET', 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL', + 'level_for', 'LoggingHandler', 'PrintHandler', 'logger_cache', + 'null_logger', 'getLogger', 'Logger', 'NullLogger'] LEVELS = [ @@ -41,20 +71,20 @@ (50, "CRITICAL"), ] -for value, name in LEVELS: - globals()[name] = value +for __value, __name in LEVELS: + globals()[__name] = __value -def level_for(value): - """Convert a numberic level to the most appropriate name. +def level_for(level_value): + """Convert a numeric level to the most appropriate name. - :param value: a numeric level + :param int level_value: a numeric level """ for i in range(len(LEVELS)): - if value == LEVELS[i][0]: + if level_value == LEVELS[i][0]: return LEVELS[i][1] - if value < LEVELS[i][0]: + if level_value < LEVELS[i][0]: return LEVELS[i - 1][1] return LEVELS[0][1] @@ -62,18 +92,20 @@ def level_for(value): class LoggingHandler: """Abstract logging message handler.""" - def format(self, level, msg): + def format(self, log_level, message): """Generate a timestamped message. - :param level: the logging level - :param msg: the message to log + :param int log_level: the logging level + :param str message: the message to log """ - return "{0}: {1} - {2}".format(time.monotonic(), level_for(level), msg) + return "{0}: {1} - {2}".format(time.monotonic(), + level_for(log_level), + message) - def emit(self, level, msg): + def emit(self, log_level, message): """Send a message where it should go. - Place holder for subclass implementations. + Placeholder for subclass implementations. """ raise NotImplementedError() @@ -81,14 +113,14 @@ def emit(self, level, msg): class PrintHandler(LoggingHandler): """Send logging messages to the console by using print.""" - def emit(self, level, msg): + def emit(self, log_level, message): """Send a message to teh console. - :param level: the logging level - :param msg: the message to log + :param int log_level: the logging level + :param str message: the message to log """ - print(self.format(level, msg)) + print(self.format(log_level, message)) # The level module-global variables get created when loaded @@ -97,23 +129,24 @@ def emit(self, level, msg): logger_cache = dict() null_logger = None + # pylint:disable=global-statement -def getLogger(name): +def getLogger(logger_name): """Create or retrieve a logger by name. - :param name: the name of the logger to create/retrieve None will cause the - NullLogger instance to be returned. + :param str logger_name: The name of the `Logger` to create/retrieve. `None` + will cause the `NullLogger` instance to be returned. """ global null_logger - if not name or name == "": + if not logger_name or logger_name == "": if not null_logger: null_logger = NullLogger() return null_logger - if name not in logger_cache: - logger_cache[name] = Logger() - return logger_cache[name] + if logger_name not in logger_cache: + logger_cache[logger_name] = Logger() + return logger_cache[logger_name] # pylint:enable=global-statement @@ -124,16 +157,17 @@ class Logger: def __init__(self): """Create an instance.""" + # noinspection PyUnresolvedReferences self._level = NOTSET self._handler = PrintHandler() - def setLevel(self, value): - """Set the logging cuttoff level. + def setLevel(self, log_level): + """Set the logging cutoff level. - :param value: the lowest level to output + :param int log_level: the lowest level to output """ - self._level = value + self._level = log_level def getEffectiveLevel(self): """Get the effective level for this logger. @@ -143,91 +177,104 @@ def getEffectiveLevel(self): """ return self._level - def addHandler(self, hldr): + def addHandler(self, handler): """Sets the handler of this logger to the specified handler. *NOTE* this is slightly different from the CPython equivalent which adds - the handler rather than replaceing it. + the handler rather than replacing it. - :param hldr: the handler + :param handler: the handler """ - self._handler = hldr + self._handler = handler - def log(self, level, format_string, *args): + def log(self, log_level, format_string, *args): """Log a message. - :param level: the priority level at which to log - :param format_string: the core message string with embedded formatting directives - :param args: arguments to ``format_string.format()``, can be empty + :param int log_level: the priority level at which to log + :param str format_string: the core message string with embedded + formatting directives + :param args: arguments to ``format_string.format()``; can be empty """ - if level >= self._level: - self._handler.emit(level, format_string % args) + if log_level >= self._level: + self._handler.emit(log_level, format_string % args) def debug(self, format_string, *args): """Log a debug message. - :param format_string: the core message string with embedded formatting directives - :param args: arguments to ``format_string.format()``, can be empty + :param str format_string: the core message string with embedded + formatting directives + :param args: arguments to ``format_string.format()``; can be empty """ + # noinspection PyUnresolvedReferences self.log(DEBUG, format_string, *args) def info(self, format_string, *args): """Log a info message. - :param format_string: the core message string with embedded formatting directives - :param args: arguments to ``format_string.format()``, can be empty + :param str format_string: the core message string with embedded + formatting directives + :param args: arguments to ``format_string.format()``; can be empty """ + # noinspection PyUnresolvedReferences self.log(INFO, format_string, *args) def warning(self, format_string, *args): """Log a warning message. - :param format_string: the core message string with embedded formatting directives - :param args: arguments to ``format_string.format()``, can be empty + :param str format_string: the core message string with embedded + formatting directives + :param args: arguments to ``format_string.format()``; can be empty """ + # noinspection PyUnresolvedReferences self.log(WARNING, format_string, *args) def error(self, format_string, *args): """Log a error message. - :param format_string: the core message string with embedded formatting directives - :param args: arguments to ``format_string.format()``, can be empty + :param str format_string: the core message string with embedded + formatting directives + :param args: arguments to ``format_string.format()``; can be empty """ + # noinspection PyUnresolvedReferences self.log(ERROR, format_string, *args) def critical(self, format_string, *args): """Log a critical message. - :param format_string: the core message string with embedded formatting directives - :param args: arguments to ``format_string.format()``, can be empty + :param str format_string: the core message string with embedded + formatting directives + :param args: arguments to ``format_string.format()``; can be empty """ + # noinspection PyUnresolvedReferences self.log(CRITICAL, format_string, *args) class NullLogger: """Provide an empty logger. - This can be used in place of a real logger to more efficiently disable logging.""" + This can be used in place of a real logger to more efficiently disable + logging.""" def __init__(self): """Dummy implementation.""" - def setLevel(self, value): + def setLevel(self, log_level): """Dummy implementation.""" def getEffectiveLevel(self): """Dummy implementation.""" + # noinspection PyUnresolvedReferences return NOTSET - def addHandler(self, hldr): + def addHandler(self, handler): """Dummy implementation.""" - def log(self, level, format_string, *args): + def log(self, log_level, format_string, *args): """Dummy implementation.""" def debug(self, format_string, *args): From 97f6c405096a56a148d09cf86e974128c6cf5829 Mon Sep 17 00:00:00 2001 From: Garrett Koller Date: Wed, 11 Aug 2021 15:07:46 -0400 Subject: [PATCH 002/162] Patch: Removed PyCharm-specific code inspection comments, added param type to `Logger.addHandler(..)` docstring, and reverted `level_for(..)` param rename from `level_value` back to `value` as suggested by @brentru --- adafruit_logging.py | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 3aaf755..ef76283 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -2,7 +2,6 @@ # # SPDX-License-Identifier: MIT -# noinspection PyUnresolvedReferences """ `adafruit_logging` ================================================================================ @@ -55,7 +54,6 @@ __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Logger.git" -# noinspection PyUnresolvedReferences # pylint:disable=undefined-all-variable __all__ = ['LEVELS', 'NOTSET', 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL', 'level_for', 'LoggingHandler', 'PrintHandler', 'logger_cache', @@ -75,16 +73,16 @@ globals()[__name] = __value -def level_for(level_value): +def level_for(value): """Convert a numeric level to the most appropriate name. - :param int level_value: a numeric level + :param int value: a numeric level """ for i in range(len(LEVELS)): - if level_value == LEVELS[i][0]: + if value == LEVELS[i][0]: return LEVELS[i][1] - if level_value < LEVELS[i][0]: + if value < LEVELS[i][0]: return LEVELS[i - 1][1] return LEVELS[0][1] @@ -157,7 +155,6 @@ class Logger: def __init__(self): """Create an instance.""" - # noinspection PyUnresolvedReferences self._level = NOTSET self._handler = PrintHandler() @@ -182,7 +179,7 @@ def addHandler(self, handler): *NOTE* this is slightly different from the CPython equivalent which adds the handler rather than replacing it. - :param handler: the handler + :param LoggingHandler handler: the handler """ self._handler = handler @@ -207,7 +204,6 @@ def debug(self, format_string, *args): :param args: arguments to ``format_string.format()``; can be empty """ - # noinspection PyUnresolvedReferences self.log(DEBUG, format_string, *args) def info(self, format_string, *args): @@ -218,7 +214,6 @@ def info(self, format_string, *args): :param args: arguments to ``format_string.format()``; can be empty """ - # noinspection PyUnresolvedReferences self.log(INFO, format_string, *args) def warning(self, format_string, *args): @@ -229,7 +224,6 @@ def warning(self, format_string, *args): :param args: arguments to ``format_string.format()``; can be empty """ - # noinspection PyUnresolvedReferences self.log(WARNING, format_string, *args) def error(self, format_string, *args): @@ -240,7 +234,6 @@ def error(self, format_string, *args): :param args: arguments to ``format_string.format()``; can be empty """ - # noinspection PyUnresolvedReferences self.log(ERROR, format_string, *args) def critical(self, format_string, *args): @@ -251,7 +244,6 @@ def critical(self, format_string, *args): :param args: arguments to ``format_string.format()``; can be empty """ - # noinspection PyUnresolvedReferences self.log(CRITICAL, format_string, *args) @@ -268,7 +260,6 @@ def setLevel(self, log_level): def getEffectiveLevel(self): """Dummy implementation.""" - # noinspection PyUnresolvedReferences return NOTSET def addHandler(self, handler): From 9b7859eafbe2651e95668ac612e6cdf3cafa5f11 Mon Sep 17 00:00:00 2001 From: Garrett Koller Date: Fri, 13 Aug 2021 06:22:06 -0400 Subject: [PATCH 003/162] Patch: Reformatted code using black --- adafruit_logging.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index ef76283..c3f844e 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -55,9 +55,23 @@ __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Logger.git" # pylint:disable=undefined-all-variable -__all__ = ['LEVELS', 'NOTSET', 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL', - 'level_for', 'LoggingHandler', 'PrintHandler', 'logger_cache', - 'null_logger', 'getLogger', 'Logger', 'NullLogger'] +__all__ = [ + "LEVELS", + "NOTSET", + "DEBUG", + "INFO", + "WARNING", + "ERROR", + "CRITICAL", + "level_for", + "LoggingHandler", + "PrintHandler", + "logger_cache", + "null_logger", + "getLogger", + "Logger", + "NullLogger", +] LEVELS = [ @@ -97,9 +111,7 @@ def format(self, log_level, message): :param str message: the message to log """ - return "{0}: {1} - {2}".format(time.monotonic(), - level_for(log_level), - message) + return "{0}: {1} - {2}".format(time.monotonic(), level_for(log_level), message) def emit(self, log_level, message): """Send a message where it should go. From 62d200b465ab1aab62d6fe841039e46f9cca9ebf Mon Sep 17 00:00:00 2001 From: dherrada Date: Fri, 5 Nov 2021 14:49:30 -0400 Subject: [PATCH 004/162] Disabled unspecified-encoding pylint check Signed-off-by: dherrada --- .pylintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index 5efe7da..a836eb2 100644 --- a/.pylintrc +++ b/.pylintrc @@ -59,7 +59,7 @@ confidence= # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" # disable=import-error,print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call -disable=print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call,import-error,bad-continuation +disable=print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call,import-error,bad-continuation,unspecified-encoding # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option From 096b23685c739f039d3290e967472ab4c25d2a99 Mon Sep 17 00:00:00 2001 From: dherrada Date: Tue, 9 Nov 2021 15:31:00 -0500 Subject: [PATCH 005/162] Updated readthedocs file --- .readthedocs.yaml | 15 +++++++++++++++ .readthedocs.yml | 11 ----------- 2 files changed, 15 insertions(+), 11 deletions(-) create mode 100644 .readthedocs.yaml delete mode 100644 .readthedocs.yml diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..95ec218 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense + +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +python: + version: "3.6" + install: + - requirements: docs/requirements.txt + - requirements: requirements.txt diff --git a/.readthedocs.yml b/.readthedocs.yml deleted file mode 100644 index 24a1921..0000000 --- a/.readthedocs.yml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries -# -# SPDX-License-Identifier: Unlicense - -# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries -# -# SPDX-License-Identifier: Unlicense - -python: - version: 3 -requirements_file: docs/requirements.txt From 4746e1429ae9fc4a55cd6950575bce3bcf00f77d Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sun, 14 Nov 2021 15:30:46 -0500 Subject: [PATCH 006/162] Add version and repo information --- adafruit_logging/extensions.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/adafruit_logging/extensions.py b/adafruit_logging/extensions.py index 356ad29..ee31804 100644 --- a/adafruit_logging/extensions.py +++ b/adafruit_logging/extensions.py @@ -13,6 +13,9 @@ from . import LoggingHandler +__version__ = "0.0.0-auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Logging.git" + class FileHandler(LoggingHandler): """File handler for working with log files off of the microcontroller (like From 34bcc6804ac4cb83ebb24cf37cffcb60696f9b9d Mon Sep 17 00:00:00 2001 From: foamyguy Date: Tue, 23 Nov 2021 13:14:39 -0600 Subject: [PATCH 007/162] update rtd py version --- .readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 95ec218..1335112 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -9,7 +9,7 @@ version: 2 python: - version: "3.6" + version: "3.7" install: - requirements: docs/requirements.txt - requirements: requirements.txt From a31c6a8ac18f86703b4e20f8ebf2c052bb7a1228 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Sun, 12 Dec 2021 09:49:26 -0600 Subject: [PATCH 008/162] use the more descriptive arg names in extensions --- adafruit_logging/extensions.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/adafruit_logging/extensions.py b/adafruit_logging/extensions.py index ee31804..0ea110a 100644 --- a/adafruit_logging/extensions.py +++ b/adafruit_logging/extensions.py @@ -34,18 +34,18 @@ def close(self): """Closes the file""" self.logfile.close() - def format(self, level: int, msg: str): + def format(self, log_level: int, message: str): """Generate a string to log :param level: The level of the message :param msg: The message to format """ - return super().format(level, msg) + "\r\n" + return super().format(log_level, message) + "\r\n" - def emit(self, level: int, msg: str): + def emit(self, log_level: int, message: str): """Generate the message and write it to the UART. :param level: The level of the message :param msg: The message to log """ - self.logfile.write(self.format(level, msg)) + self.logfile.write(self.format(log_level, message)) From fba68e16f834c90800a434f840269ef88b74b629 Mon Sep 17 00:00:00 2001 From: Jingleheimer Date: Mon, 17 Jan 2022 17:31:32 -0500 Subject: [PATCH 009/162] Added format specifier used in log messages so that all values are printed with three decimal places padded with 0 where necessary. --- adafruit_logging/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_logging/__init__.py b/adafruit_logging/__init__.py index 33f8acf..f2418d7 100644 --- a/adafruit_logging/__init__.py +++ b/adafruit_logging/__init__.py @@ -111,7 +111,7 @@ def format(self, log_level: int, message: str) -> str: :param str message: the message to log """ - return "{0}: {1} - {2}".format(time.monotonic(), level_for(log_level), message) + return "{0:<0.3f}: {1} - {2}".format(time.monotonic(), level_for(log_level), message) def emit(self, log_level: int, message: str): """Send a message where it should go. From 35bce690c7b8f4ee6fca2ef685c6d763da23db64 Mon Sep 17 00:00:00 2001 From: dherrada Date: Thu, 13 Jan 2022 16:27:30 -0500 Subject: [PATCH 010/162] First part of patch Signed-off-by: dherrada --- .../PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md | 2 +- .github/workflows/build.yml | 6 +++--- .github/workflows/release.yml | 8 ++++---- .readthedocs.yaml | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md b/.github/PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md index 71ef8f8..8de294e 100644 --- a/.github/PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md +++ b/.github/PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md @@ -4,7 +4,7 @@ Thank you for contributing! Before you submit a pull request, please read the following. -Make sure any changes you're submitting are in line with the CircuitPython Design Guide, available here: https://circuitpython.readthedocs.io/en/latest/docs/design_guide.html +Make sure any changes you're submitting are in line with the CircuitPython Design Guide, available here: https://docs.circuitpython.org/en/latest/docs/design_guide.html If your changes are to documentation, please verify that the documentation builds locally by following the steps found here: https://adafru.it/build-docs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ca35544..474520d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,10 +22,10 @@ jobs: awk -F '\/' '{ print tolower($2) }' | tr '_' '-' ) - - name: Set up Python 3.7 - uses: actions/setup-python@v1 + - name: Set up Python 3.x + uses: actions/setup-python@v2 with: - python-version: 3.7 + python-version: "3.x" - name: Versions run: | python3 --version diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6d0015a..a65e5de 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,10 +24,10 @@ jobs: awk -F '\/' '{ print tolower($2) }' | tr '_' '-' ) - - name: Set up Python 3.6 - uses: actions/setup-python@v1 + - name: Set up Python 3.x + uses: actions/setup-python@v2 with: - python-version: 3.6 + python-version: "3.x" - name: Versions run: | python3 --version @@ -67,7 +67,7 @@ jobs: echo ::set-output name=setup-py::$( find . -wholename './setup.py' ) - name: Set up Python if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: python-version: '3.x' - name: Install dependencies diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 1335112..f8b2891 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -9,7 +9,7 @@ version: 2 python: - version: "3.7" + version: "3.x" install: - requirements: docs/requirements.txt - requirements: requirements.txt From 0551bb2e85c150b75df414b955ff0648c8041312 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 24 Jan 2022 11:54:02 -0600 Subject: [PATCH 011/162] code format --- adafruit_logging/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/adafruit_logging/__init__.py b/adafruit_logging/__init__.py index f2418d7..f5bcdf4 100644 --- a/adafruit_logging/__init__.py +++ b/adafruit_logging/__init__.py @@ -111,7 +111,9 @@ def format(self, log_level: int, message: str) -> str: :param str message: the message to log """ - return "{0:<0.3f}: {1} - {2}".format(time.monotonic(), level_for(log_level), message) + return "{0:<0.3f}: {1} - {2}".format( + time.monotonic(), level_for(log_level), message + ) def emit(self, log_level: int, message: str): """Send a message where it should go. From 7bedb6e6ece86eab78d2d232f27971161423bbdd Mon Sep 17 00:00:00 2001 From: dherrada Date: Mon, 24 Jan 2022 16:46:16 -0500 Subject: [PATCH 012/162] Updated docs link, updated python docs link, updated setup.py --- README.rst | 4 ++-- docs/conf.py | 4 ++-- docs/index.rst | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index ceca24a..9102bc6 100644 --- a/README.rst +++ b/README.rst @@ -2,7 +2,7 @@ Introduction ============ .. image:: https://readthedocs.org/projects/adafruit-circuitpython-logging/badge/?version=latest - :target: https://circuitpython.readthedocs.io/projects/logging/en/latest/ + :target: https://docs.circuitpython.org/projects/logging/en/latest/ :alt: Documentation Status .. image:: https://img.shields.io/discord/327254708534116352.svg @@ -43,7 +43,7 @@ Usage Example Documentation ============= -API documentation for this library can be found on `Read the Docs `_. +API documentation for this library can be found on `Read the Docs `_. Contributing ============ diff --git a/docs/conf.py b/docs/conf.py index d48191c..fe113ab 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -29,8 +29,8 @@ intersphinx_mapping = { - "python": ("https://docs.python.org/3.4", None), - "CircuitPython": ("https://circuitpython.readthedocs.io/en/latest/", None), + "python": ("https://docs.python.org/3", None), + "CircuitPython": ("https://docs.circuitpython.org/en/latest/", None), } # Add any paths that contain templates here, relative to this directory. diff --git a/docs/index.rst b/docs/index.rst index 974b1a2..50c412d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -30,7 +30,7 @@ Table of Contents :caption: Other Links Download - CircuitPython Reference Documentation + CircuitPython Reference Documentation CircuitPython Support Forum Discord Chat Adafruit Learning System From 3322e9122bb7f8fac30e43509cbce16c145f5a78 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 10 Feb 2022 09:57:04 -0500 Subject: [PATCH 013/162] Consolidate Documentation sections of README --- README.rst | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 9102bc6..280273a 100644 --- a/README.rst +++ b/README.rst @@ -45,14 +45,11 @@ Documentation API documentation for this library can be found on `Read the Docs `_. +For information on building library documentation, please check out `this guide `_. + Contributing ============ Contributions are welcome! Please read our `Code of Conduct `_ before contributing to help this project stay welcoming. - -Documentation -============= - -For information on building library documentation, please check out `this guide `_. From 026675e3f159470702ec51c03621d0655f3f8dfd Mon Sep 17 00:00:00 2001 From: dherrada Date: Mon, 14 Feb 2022 15:35:02 -0500 Subject: [PATCH 014/162] Fixed readthedocs build Signed-off-by: dherrada --- .readthedocs.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index f8b2891..33c2a61 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -8,8 +8,12 @@ # Required version: 2 +build: + os: ubuntu-20.04 + tools: + python: "3" + python: - version: "3.x" install: - requirements: docs/requirements.txt - requirements: requirements.txt From 2922a331ec832f06f14554b8139ed97a0c23d0e0 Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Mon, 28 Mar 2022 15:52:04 -0400 Subject: [PATCH 015/162] Update Black to latest. Signed-off-by: Kattni Rembor --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1b9fadc..7467c1d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ repos: - repo: https://github.com/python/black - rev: 20.8b1 + rev: 22.3.0 hooks: - id: black - repo: https://github.com/fsfe/reuse-tool From 6f92247826f2c919ebe00d6c1a73d3ca2074fbb5 Mon Sep 17 00:00:00 2001 From: Eva Herrada <33632497+evaherrada@users.noreply.github.com> Date: Thu, 21 Apr 2022 18:42:29 -0400 Subject: [PATCH 016/162] Update .gitignore --- .gitignore | 50 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 5e66017..544ec4a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,19 +1,47 @@ -# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-FileCopyrightText: 2022 Kattni Rembor, written for Adafruit Industries # -# SPDX-License-Identifier: Unlicense +# SPDX-License-Identifier: MIT -# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries -# -# SPDX-License-Identifier: Unlicense +# Do not include files and directories created by your personal work environment, such as the IDE +# you use, except for those already listed here. Pull requests including changes to this file will +# not be accepted. + +# This .gitignore file contains rules for files generated by working with CircuitPython libraries, +# including building Sphinx, testing with pip, and creating a virual environment, as well as the +# MacOS and IDE-specific files generated by using MacOS in general, or the PyCharm or VSCode IDEs. + +# If you find that there are files being generated on your machine that should not be included in +# your git commit, you should create a .gitignore_global file on your computer to include the +# files created by your personal setup. To do so, follow the two steps below. + +# First, create a file called .gitignore_global somewhere convenient for you, and add rules for +# the files you want to exclude from git commits. +# Second, configure Git to use the exclude file for all Git repositories by running the +# following via commandline, replacing "path/to/your/" with the actual path to your newly created +# .gitignore_global file: +# git config --global core.excludesfile path/to/your/.gitignore_global + +# CircuitPython-specific files *.mpy -.idea + +# Python-specific files __pycache__ -_build *.pyc + +# Sphinx build-specific files +_build + +# This file results from running `pip -e install .` in a local repository +*.egg-info + +# Virtual environment-specific files .env -bundles + +# MacOS-specific files *.DS_Store -.eggs -dist -**/*.egg-info + +# IDE-specific files +.idea +.vscode +*~ From 30281e12e12a8c715ef9fd78fb48a7d9f367d7f3 Mon Sep 17 00:00:00 2001 From: evaherrada Date: Fri, 22 Apr 2022 15:58:52 -0400 Subject: [PATCH 017/162] Patch: Replaced discord badge image --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 280273a..a6f4a94 100644 --- a/README.rst +++ b/README.rst @@ -5,7 +5,7 @@ Introduction :target: https://docs.circuitpython.org/projects/logging/en/latest/ :alt: Documentation Status -.. image:: https://img.shields.io/discord/327254708534116352.svg +.. image:: https://github.com/adafruit/Adafruit_CircuitPython_Bundle/blob/main/badges/adafruit_discord.svg :target: https://adafru.it/discord :alt: Discord From bb9117b94c5b6f0c86c7605574c4480ada5e125b Mon Sep 17 00:00:00 2001 From: foamyguy Date: Sun, 24 Apr 2022 14:04:31 -0500 Subject: [PATCH 018/162] change discord badge --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index a6f4a94..0734de9 100644 --- a/README.rst +++ b/README.rst @@ -5,7 +5,7 @@ Introduction :target: https://docs.circuitpython.org/projects/logging/en/latest/ :alt: Documentation Status -.. image:: https://github.com/adafruit/Adafruit_CircuitPython_Bundle/blob/main/badges/adafruit_discord.svg +.. image:: https://raw.githubusercontent.com/adafruit/Adafruit_CircuitPython_Bundle/main/badges/adafruit_discord.svg :target: https://adafru.it/discord :alt: Discord From 7054bf1cb80ddc9581bd7f115d6d5c95957b47cf Mon Sep 17 00:00:00 2001 From: Melissa LeBlanc-Williams Date: Fri, 6 May 2022 14:40:46 -0700 Subject: [PATCH 019/162] Update to be a PYPI package --- setup.py | 54 +++++++++++++++++++++++++++++++++++++++++++++++ setup.py.disabled | 6 ------ 2 files changed, 54 insertions(+), 6 deletions(-) create mode 100644 setup.py delete mode 100644 setup.py.disabled diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..49afab3 --- /dev/null +++ b/setup.py @@ -0,0 +1,54 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +"""A setuptools based setup module. +See: +https://packaging.python.org/en/latest/distributing.html +https://github.com/pypa/sampleproject +""" + +from setuptools import setup + +# To use a consistent encoding +from codecs import open +from os import path + +here = path.abspath(path.dirname(__file__)) + +# Get the long description from the README file +with open(path.join(here, "README.rst"), encoding="utf-8") as f: + long_description = f.read() + +setup( + name="adafruit-circuitpython-logging", + use_scm_version=True, + setup_requires=["setuptools_scm"], + description="Logging module for CircuitPython", + long_description=long_description, + long_description_content_type="text/x-rst", + # The project's main homepage. + url="https://github.com/adafruit/Adafruit_CircuitPython_Logging", + # Author details + author="Adafruit Industries", + author_email="circuitpython@adafruit.com", + install_requires=[ + "Adafruit-Blinka", + ], + # Choose your license + license="MIT", + # See https://pypi.python.org/pypi?%3Aaction=list_classifiers + classifiers=[ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "Topic :: Software Development :: Libraries", + "Topic :: System :: Hardware", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + ], + # What does your project relate to? + keywords="adafruit blinka circuitpython micropython logging logger", + # You can just specify the packages manually here if your project is + # simple. Or you can use find_packages(). + packages=["adafruit_logging"], +) \ No newline at end of file diff --git a/setup.py.disabled b/setup.py.disabled deleted file mode 100644 index 4d5eddc..0000000 --- a/setup.py.disabled +++ /dev/null @@ -1,6 +0,0 @@ -# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries -# -# SPDX-License-Identifier: MIT - -"""This library is not deployed to PyPI. It is either a board-specific helper library or -does not make sense for use on single board computers and Linux.""" From b963a9588cddab9e5f5a1629740f83bfcc4186df Mon Sep 17 00:00:00 2001 From: Melissa LeBlanc-Williams Date: Fri, 6 May 2022 14:46:08 -0700 Subject: [PATCH 020/162] Ran black --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 49afab3..60ade1a 100644 --- a/setup.py +++ b/setup.py @@ -51,4 +51,4 @@ # You can just specify the packages manually here if your project is # simple. Or you can use find_packages(). packages=["adafruit_logging"], -) \ No newline at end of file +) From 6a89a2f1624f2dfd33c72d417f9f7f1f22c2559d Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sun, 15 May 2022 12:49:26 -0400 Subject: [PATCH 021/162] Patch .pre-commit-config.yaml --- .pre-commit-config.yaml | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7467c1d..3343606 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,40 +3,40 @@ # SPDX-License-Identifier: Unlicense repos: -- repo: https://github.com/python/black + - repo: https://github.com/python/black rev: 22.3.0 hooks: - - id: black -- repo: https://github.com/fsfe/reuse-tool - rev: v0.12.1 + - id: black + - repo: https://github.com/fsfe/reuse-tool + rev: v0.14.0 hooks: - - id: reuse -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.3.0 + - id: reuse + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.2.0 hooks: - - id: check-yaml - - id: end-of-file-fixer - - id: trailing-whitespace -- repo: https://github.com/pycqa/pylint + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + - repo: https://github.com/pycqa/pylint rev: v2.11.1 hooks: - - id: pylint + - id: pylint name: pylint (library code) types: [python] args: - --disable=consider-using-f-string exclude: "^(docs/|examples/|tests/|setup.py$)" - - id: pylint + - id: pylint name: pylint (example code) description: Run pylint rules on "examples/*.py" files types: [python] files: "^examples/" args: - - --disable=missing-docstring,invalid-name,consider-using-f-string,duplicate-code - - id: pylint + - --disable=missing-docstring,invalid-name,consider-using-f-string,duplicate-code + - id: pylint name: pylint (test code) description: Run pylint rules on "tests/*.py" files types: [python] files: "^tests/" args: - - --disable=missing-docstring,consider-using-f-string,duplicate-code + - --disable=missing-docstring,consider-using-f-string,duplicate-code From 981c4ed1ec4bf8fd3ec97f343edca6291fdb571a Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 21 May 2022 20:28:46 -0400 Subject: [PATCH 022/162] Make `level_for()` private --- adafruit_logging/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/adafruit_logging/__init__.py b/adafruit_logging/__init__.py index f5bcdf4..a9be055 100644 --- a/adafruit_logging/__init__.py +++ b/adafruit_logging/__init__.py @@ -63,7 +63,7 @@ "WARNING", "ERROR", "CRITICAL", - "level_for", + "_level_for", "LoggingHandler", "PrintHandler", "logger_cache", @@ -87,7 +87,7 @@ globals()[__name] = __value -def level_for(value: int) -> str: +def _level_for(value: int) -> str: """Convert a numeric level to the most appropriate name. :param int value: a numeric level @@ -112,7 +112,7 @@ def format(self, log_level: int, message: str) -> str: """ return "{0:<0.3f}: {1} - {2}".format( - time.monotonic(), level_for(log_level), message + time.monotonic(), _level_for(log_level), message ) def emit(self, log_level: int, message: str): From 1de0e3ea2be0ea744054d2e5b536e1f0711ad385 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 21 May 2022 20:35:14 -0400 Subject: [PATCH 023/162] Make `format` private to not confict with CPython --- adafruit_logging/__init__.py | 4 ++-- adafruit_logging/extensions.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/adafruit_logging/__init__.py b/adafruit_logging/__init__.py index a9be055..9a4342c 100644 --- a/adafruit_logging/__init__.py +++ b/adafruit_logging/__init__.py @@ -104,7 +104,7 @@ def _level_for(value: int) -> str: class LoggingHandler: """Abstract logging message handler.""" - def format(self, log_level: int, message: str) -> str: + def _format(self, log_level: int, message: str) -> str: """Generate a timestamped message. :param int log_level: the logging level @@ -132,7 +132,7 @@ def emit(self, log_level: int, message: str): :param str message: the message to log """ - print(self.format(log_level, message)) + print(self._format(log_level, message)) # The level module-global variables get created when loaded diff --git a/adafruit_logging/extensions.py b/adafruit_logging/extensions.py index 0ea110a..3843220 100644 --- a/adafruit_logging/extensions.py +++ b/adafruit_logging/extensions.py @@ -34,13 +34,13 @@ def close(self): """Closes the file""" self.logfile.close() - def format(self, log_level: int, message: str): + def _format(self, log_level: int, message: str): """Generate a string to log :param level: The level of the message :param msg: The message to format """ - return super().format(log_level, message) + "\r\n" + return super()._format(log_level, message) + "\r\n" def emit(self, log_level: int, message: str): """Generate the message and write it to the UART. @@ -48,4 +48,4 @@ def emit(self, log_level: int, message: str): :param level: The level of the message :param msg: The message to log """ - self.logfile.write(self.format(log_level, message)) + self.logfile.write(self._format(log_level, message)) From 9e11346c23051064f6bfce7a32a222373aef6960 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 21 May 2022 21:27:09 -0400 Subject: [PATCH 024/162] Transform LoggingHandler to Handler --- adafruit_logging/__init__.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/adafruit_logging/__init__.py b/adafruit_logging/__init__.py index 9a4342c..d7f85ef 100644 --- a/adafruit_logging/__init__.py +++ b/adafruit_logging/__init__.py @@ -101,9 +101,14 @@ def _level_for(value: int) -> str: return LEVELS[0][1] -class LoggingHandler: +class Handler: """Abstract logging message handler.""" + def __init__(self, level=NOTSET): + self.level = level + """Level of the handler; this is currently unused, and + only the level of the logger is used""" + def _format(self, log_level: int, message: str) -> str: """Generate a timestamped message. @@ -115,7 +120,7 @@ def _format(self, log_level: int, message: str) -> str: time.monotonic(), _level_for(log_level), message ) - def emit(self, log_level: int, message: str): + def _emit(self, log_level: int, message: str): """Send a message where it should go. Placeholder for subclass implementations. """ From 06ee36690e88ddf14bace2633aaee8c1abf702df Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 21 May 2022 21:27:53 -0400 Subject: [PATCH 025/162] Convert PrintHandler to StreamHandler with default --- adafruit_logging/__init__.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/adafruit_logging/__init__.py b/adafruit_logging/__init__.py index d7f85ef..646e245 100644 --- a/adafruit_logging/__init__.py +++ b/adafruit_logging/__init__.py @@ -127,10 +127,21 @@ def _emit(self, log_level: int, message: str): raise NotImplementedError() -class PrintHandler(LoggingHandler): - """Send logging messages to the console by using print.""" +class StreamHandler(Handler): + """Send logging messages to a stream, `sys.stderr` (typically + the serial console) by default. + + :param stream: The stream to log to, default is `sys.stderr` + """ - def emit(self, log_level: int, message: str): + def __init__(self, stream=None): + super.__init__(self) + if stream is None: + stream = sys.stderr + self.stream = stream + """The stream to log to""" + + def _emit(self, log_level: int, message: str): """Send a message to the console. :param int log_level: the logging level From 6d5609cddfbb3c1df1bfbddc012b33f7bedab38b Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 21 May 2022 21:28:50 -0400 Subject: [PATCH 026/162] getLogger() default return is None, remove `null_logger` --- adafruit_logging/__init__.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/adafruit_logging/__init__.py b/adafruit_logging/__init__.py index 646e245..cd6f909 100644 --- a/adafruit_logging/__init__.py +++ b/adafruit_logging/__init__.py @@ -155,25 +155,19 @@ def _emit(self, log_level: int, message: str): # pylint:disable=undefined-variable logger_cache = {} -null_logger = None # pylint:disable=global-statement def getLogger(logger_name: str) -> "Logger": - """Create or retrieve a logger by name. + """Create or retrieve a logger by name. Only caches loggers + made using this function, and does not cache any made directly. :param str logger_name: The name of the `Logger` to create/retrieve. `None` will cause the `NullLogger` instance to be returned. """ - global null_logger - if not logger_name or logger_name == "": - if not null_logger: - null_logger = NullLogger() - return null_logger - if logger_name not in logger_cache: - logger_cache[logger_name] = Logger() - return logger_cache[logger_name] + logger_cache[logger_name] = Logger(logger_name) + return logger_cache.get(logger_name, None) # pylint:enable=global-statement From 5206a8d7e42155bab65c269b1967bfa56d58a4f9 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 21 May 2022 21:33:11 -0400 Subject: [PATCH 027/162] Add function for adding logger to global dict --- adafruit_logging/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/adafruit_logging/__init__.py b/adafruit_logging/__init__.py index cd6f909..6e3d8e8 100644 --- a/adafruit_logging/__init__.py +++ b/adafruit_logging/__init__.py @@ -156,6 +156,11 @@ def _emit(self, log_level: int, message: str): logger_cache = {} +def _addLogger(logger_name: str): + """Adds the logger if it doesn't already exist""" + if logger_name not in logger_cache: + logger_cache[logger_name] = Logger(logger_name) + # pylint:disable=global-statement def getLogger(logger_name: str) -> "Logger": """Create or retrieve a logger by name. Only caches loggers From 0c8c08e569a68d3501be595678ceb8f6f21effc0 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 21 May 2022 21:33:55 -0400 Subject: [PATCH 028/162] getLogger() uses _addLogger(), default return is None --- adafruit_logging/__init__.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/adafruit_logging/__init__.py b/adafruit_logging/__init__.py index 6e3d8e8..b13dcc4 100644 --- a/adafruit_logging/__init__.py +++ b/adafruit_logging/__init__.py @@ -163,15 +163,13 @@ def _addLogger(logger_name: str): # pylint:disable=global-statement def getLogger(logger_name: str) -> "Logger": - """Create or retrieve a logger by name. Only caches loggers - made using this function, and does not cache any made directly. + """Create or retrieve a logger by name :param str logger_name: The name of the `Logger` to create/retrieve. `None` will cause the `NullLogger` instance to be returned. """ - if logger_name not in logger_cache: - logger_cache[logger_name] = Logger(logger_name) + _addLogger(logger_name) return logger_cache.get(logger_name, None) From b925c035ffa93a8a1ad4128f733220bb8a10215b Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 21 May 2022 21:34:26 -0400 Subject: [PATCH 029/162] Logger() takes name and level args --- adafruit_logging/__init__.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/adafruit_logging/__init__.py b/adafruit_logging/__init__.py index b13dcc4..ff7bb63 100644 --- a/adafruit_logging/__init__.py +++ b/adafruit_logging/__init__.py @@ -179,10 +179,14 @@ def getLogger(logger_name: str) -> "Logger": class Logger: """Provide a logging api.""" - def __init__(self): + def __init__(self, name: str, level=NOTSET): """Create an instance.""" - self._level = NOTSET - self._handler = PrintHandler() + self._level = level + self.name = name + """The name of the logger, this should be unique for proper + functionality of `getLogger()`""" + self._handler = None + _addLogger(name) def setLevel(self, log_level: int): """Set the logging cutoff level. From ef98f1b05d6fc0719c4a70abf80d808061662686 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 21 May 2022 21:34:48 -0400 Subject: [PATCH 030/162] Fix typeannotation of addHandler() --- adafruit_logging/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/adafruit_logging/__init__.py b/adafruit_logging/__init__.py index ff7bb63..6668cb1 100644 --- a/adafruit_logging/__init__.py +++ b/adafruit_logging/__init__.py @@ -204,8 +204,9 @@ def getEffectiveLevel(self) -> int: """ return self._level - def addHandler(self, handler: LoggingHandler): + def addHandler(self, handler: Handler): """Sets the handler of this logger to the specified handler. + *NOTE* this is slightly different from the CPython equivalent which adds the handler rather than replacing it. From d7217524028ee24090e04584aca2462bc069d698 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 21 May 2022 21:35:39 -0400 Subject: [PATCH 031/162] Add hasHandlers() to compensate for changes to functionality --- adafruit_logging/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/adafruit_logging/__init__.py b/adafruit_logging/__init__.py index 6668cb1..1d63c92 100644 --- a/adafruit_logging/__init__.py +++ b/adafruit_logging/__init__.py @@ -215,7 +215,11 @@ def addHandler(self, handler: Handler): """ self._handler = handler - def log(self, log_level: int, format_string: str, *args): + def hasHandlers(self) -> bool: + """Whether any handlers have been set for this logger""" + return self._handler is not None + + def log(self, level: int, msg: str, *args): """Log a message. :param int log_level: the priority level at which to log From 800c3776b5da5b46a44af2b2c236733fe221bc2b Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 21 May 2022 21:36:36 -0400 Subject: [PATCH 032/162] Logging methods now account for changes, arg name changes --- adafruit_logging/__init__.py | 39 ++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/adafruit_logging/__init__.py b/adafruit_logging/__init__.py index 1d63c92..929be0d 100644 --- a/adafruit_logging/__init__.py +++ b/adafruit_logging/__init__.py @@ -222,64 +222,65 @@ def hasHandlers(self) -> bool: def log(self, level: int, msg: str, *args): """Log a message. - :param int log_level: the priority level at which to log - :param str format_string: the core message string with embedded + :param int level: the priority level at which to log + :param str msg: the core message string with embedded formatting directives :param args: arguments to ``format_string.format()``; can be empty """ - if log_level >= self._level: - self._handler.emit(log_level, format_string % args) + if level >= self._level: + self._handler._emit(level, msg % args) - def debug(self, format_string: str, *args): + def debug(self, msg: str, *args): """Log a debug message. - :param str format_string: the core message string with embedded + :param str fmsg: the core message string with embedded formatting directives :param args: arguments to ``format_string.format()``; can be empty """ - self.log(DEBUG, format_string, *args) + self.log(DEBUG, msg, *args) - def info(self, format_string: str, *args): + def info(self, msg: str, *args): """Log a info message. - :param str format_string: the core message string with embedded + :param str msg: the core message string with embedded formatting directives :param args: arguments to ``format_string.format()``; can be empty """ - self.log(INFO, format_string, *args) + self.log(INFO, msg, *args) - def warning(self, format_string: str, *args): + def warning(self, msg: str, *args): """Log a warning message. - :param str format_string: the core message string with embedded + :param str msg: the core message string with embedded formatting directives :param args: arguments to ``format_string.format()``; can be empty """ - self.log(WARNING, format_string, *args) + self.log(WARNING, msg, *args) - def error(self, format_string: str, *args): + def error(self, msg: str, *args): """Log a error message. - :param str format_string: the core message string with embedded + :param str msg: the core message string with embedded formatting directives :param args: arguments to ``format_string.format()``; can be empty """ - self.log(ERROR, format_string, *args) + self.log(ERROR, msg, *args) - def critical(self, format_string: str, *args): + def critical(self, msg: str, *args): """Log a critical message. - :param str format_string: the core message string with embedded + :param str msg: the core message string with embedded formatting directives :param args: arguments to ``format_string.format()``; can be empty """ - self.log(CRITICAL, format_string, *args) + self.log(CRITICAL, msg, *args) + class NullLogger: From 393d878c168a1f206eddf6b8b788960e641b1f61 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 21 May 2022 21:37:10 -0400 Subject: [PATCH 033/162] NullLogger is now NullHandler --- adafruit_logging/__init__.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/adafruit_logging/__init__.py b/adafruit_logging/__init__.py index 929be0d..a4a668b 100644 --- a/adafruit_logging/__init__.py +++ b/adafruit_logging/__init__.py @@ -282,15 +282,12 @@ def critical(self, msg: str, *args): self.log(CRITICAL, msg, *args) +class NullHandler(Handler): + """Provide an empty log handler. -class NullLogger: - """Provide an empty logger. - This can be used in place of a real logger to more efficiently disable + This can be used in place of a real log handler to more efficiently disable logging.""" - def __init__(self): - """Dummy implementation.""" - def setLevel(self, log_level: int): """Dummy implementation.""" From 69821d2387b44bf21c21d49756939c8a677de4b8 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 21 May 2022 21:37:46 -0400 Subject: [PATCH 034/162] addHandler() type annotation updated --- adafruit_logging/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_logging/__init__.py b/adafruit_logging/__init__.py index a4a668b..b25973f 100644 --- a/adafruit_logging/__init__.py +++ b/adafruit_logging/__init__.py @@ -295,7 +295,7 @@ def getEffectiveLevel(self) -> int: """Dummy implementation.""" return NOTSET - def addHandler(self, handler: LoggingHandler): + def addHandler(self, handler: Handler): """Dummy implementation.""" def log(self, log_level: int, format_string: str, *args): From e53bcf22d3f7d46e2d5835b4eb3d88003e8215b0 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 21 May 2022 21:38:03 -0400 Subject: [PATCH 035/162] FileHandler moved to __init__,py --- adafruit_logging/__init__.py | 44 ++++++++++++++++++++++++++--- adafruit_logging/extensions.py | 51 ---------------------------------- 2 files changed, 40 insertions(+), 55 deletions(-) delete mode 100644 adafruit_logging/extensions.py diff --git a/adafruit_logging/__init__.py b/adafruit_logging/__init__.py index b25973f..6db5298 100644 --- a/adafruit_logging/__init__.py +++ b/adafruit_logging/__init__.py @@ -51,6 +51,7 @@ # pylint:disable=invalid-name import time +import sys __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Logger.git" @@ -64,13 +65,13 @@ "ERROR", "CRITICAL", "_level_for", - "LoggingHandler", - "PrintHandler", + "Handler", + "StreamHandler", "logger_cache", - "null_logger", "getLogger", "Logger", - "NullLogger", + "NullHandler", + "FileHandler", ] @@ -315,3 +316,38 @@ def error(self, format_string: str, *args): def critical(self, format_string: str, *args): """Dummy implementation.""" + + +class FileHandler(StreamHandler): + """File handler for working with log files off of the microcontroller (like + an SD card) + + :param str filename: The filename of the log file + :param str mode: Whether to write ('w') or append ('a'); default is to append + """ + + def __init__(self, filename: str, mode: str = "a") -> None: + self.logfile = open( # pylint: disable=consider-using-with + filename, mode, encoding="utf-8" + ) + + def close(self): + """Closes the file""" + self.logfile.close() + + def _format(self, log_level: int, message: str): + """Generate a string to log + + :param level: The level of the message + :param msg: The message to format + """ + return super()._format(log_level, message) + "\r\n" + + def _emit(self, log_level: int, message: str): + """Generate the message and write it to the UART. + + :param level: The level of the message + :param msg: The message to log + """ + self.logfile.write(self._format(log_level, message)) + diff --git a/adafruit_logging/extensions.py b/adafruit_logging/extensions.py deleted file mode 100644 index 3843220..0000000 --- a/adafruit_logging/extensions.py +++ /dev/null @@ -1,51 +0,0 @@ -# SPDX-FileCopyrightText: 2021 Alec Delaney for Adafruit Industries -# -# SPDX-License-Identifier: MIT - -""" -`extensions` -==================================================== - -CircuitPython logging extension for logging to files - -* Author(s): Alec Delaney -""" - -from . import LoggingHandler - -__version__ = "0.0.0-auto.0" -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Logging.git" - - -class FileHandler(LoggingHandler): - """File handler for working with log files off of the microcontroller (like - an SD card) - - :param filepath: The filepath to the log file - :param mode: Whether to write ('w') or append ('a'); default is to append - """ - - def __init__(self, filepath: str, mode: str = "a"): - self.logfile = open( # pylint: disable=consider-using-with - filepath, mode, encoding="utf-8" - ) - - def close(self): - """Closes the file""" - self.logfile.close() - - def _format(self, log_level: int, message: str): - """Generate a string to log - - :param level: The level of the message - :param msg: The message to format - """ - return super()._format(log_level, message) + "\r\n" - - def emit(self, log_level: int, message: str): - """Generate the message and write it to the UART. - - :param level: The level of the message - :param msg: The message to log - """ - self.logfile.write(self._format(log_level, message)) From 1eae0186b4abf44239804053ced6b0536fee8c08 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 21 May 2022 21:39:59 -0400 Subject: [PATCH 036/162] adafruit_logging changed to file py_module --- adafruit_logging/__init__.py => adafruit_logging.py | 0 setup.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename adafruit_logging/__init__.py => adafruit_logging.py (100%) diff --git a/adafruit_logging/__init__.py b/adafruit_logging.py similarity index 100% rename from adafruit_logging/__init__.py rename to adafruit_logging.py diff --git a/setup.py b/setup.py index 60ade1a..428e4c2 100644 --- a/setup.py +++ b/setup.py @@ -50,5 +50,5 @@ keywords="adafruit blinka circuitpython micropython logging logger", # You can just specify the packages manually here if your project is # simple. Or you can use find_packages(). - packages=["adafruit_logging"], + py_modules=["adafruit_logging"], ) From 880b850eca8043af17124764f9ec315d1bf3cbf6 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 21 May 2022 21:40:18 -0400 Subject: [PATCH 037/162] Reformatted per pre-commit --- adafruit_logging.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 6db5298..1742d91 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -131,7 +131,7 @@ def _emit(self, log_level: int, message: str): class StreamHandler(Handler): """Send logging messages to a stream, `sys.stderr` (typically the serial console) by default. - + :param stream: The stream to log to, default is `sys.stderr` """ @@ -141,7 +141,7 @@ def __init__(self, stream=None): stream = sys.stderr self.stream = stream """The stream to log to""" - + def _emit(self, log_level: int, message: str): """Send a message to the console. @@ -157,11 +157,13 @@ def _emit(self, log_level: int, message: str): logger_cache = {} + def _addLogger(logger_name: str): """Adds the logger if it doesn't already exist""" if logger_name not in logger_cache: logger_cache[logger_name] = Logger(logger_name) + # pylint:disable=global-statement def getLogger(logger_name: str) -> "Logger": """Create or retrieve a logger by name @@ -350,4 +352,3 @@ def _emit(self, log_level: int, message: str): :param msg: The message to log """ self.logfile.write(self._format(log_level, message)) - From 7a796da214f97a03cc22f4f71c51f65867f77327 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 21 May 2022 21:49:44 -0400 Subject: [PATCH 038/162] Linted, reformatted per pre-commit --- adafruit_logging.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 1742d91..fca9ece 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -102,10 +102,11 @@ def _level_for(value: int) -> str: return LEVELS[0][1] +# pylint: disable=too-few-public-methods class Handler: """Abstract logging message handler.""" - def __init__(self, level=NOTSET): + def __init__(self, level=NOTSET): # pylint: disable=undefined-variable self.level = level """Level of the handler; this is currently unused, and only the level of the logger is used""" @@ -128,6 +129,7 @@ def _emit(self, log_level: int, message: str): raise NotImplementedError() +# pylint: disable=too-few-public-methods class StreamHandler(Handler): """Send logging messages to a stream, `sys.stderr` (typically the serial console) by default. @@ -136,7 +138,7 @@ class StreamHandler(Handler): """ def __init__(self, stream=None): - super.__init__(self) + super().__init__() if stream is None: stream = sys.stderr self.stream = stream @@ -232,7 +234,7 @@ def log(self, level: int, msg: str, *args): """ if level >= self._level: - self._handler._emit(level, msg % args) + self._handler._emit(level, msg % args) # pylint: disable=protected-access def debug(self, msg: str, *args): """Log a debug message. @@ -319,6 +321,9 @@ def error(self, format_string: str, *args): def critical(self, format_string: str, *args): """Dummy implementation.""" + def _emit(self, log_level: int, message: str): + """Dummy implementation""" + class FileHandler(StreamHandler): """File handler for working with log files off of the microcontroller (like @@ -329,13 +334,12 @@ class FileHandler(StreamHandler): """ def __init__(self, filename: str, mode: str = "a") -> None: - self.logfile = open( # pylint: disable=consider-using-with - filename, mode, encoding="utf-8" - ) + # pylint: disable=consider-using-with + super().__init__(open(filename, mode=mode)) def close(self): """Closes the file""" - self.logfile.close() + self.stream.close() def _format(self, log_level: int, message: str): """Generate a string to log @@ -351,4 +355,4 @@ def _emit(self, log_level: int, message: str): :param level: The level of the message :param msg: The message to log """ - self.logfile.write(self._format(log_level, message)) + self.stream.write(self._format(log_level, message)) From 30ceea1c0b6186b50739588601ea47f9115c7b23 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 21 May 2022 22:01:43 -0400 Subject: [PATCH 039/162] Update examples --- examples/logging_filehandler.py | 2 +- examples/logging_simpletest.py | 9 +-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/examples/logging_filehandler.py b/examples/logging_filehandler.py index 37e5de6..e96015f 100644 --- a/examples/logging_filehandler.py +++ b/examples/logging_filehandler.py @@ -7,7 +7,7 @@ import storage import adafruit_sdcard import adafruit_logging as logging -from adafruit_logging.extensions import FileHandler +from adafruit_logging import FileHandler # Get chip select pin depending on the board, this one is for the Feather M4 Express sd_cs = board.D10 diff --git a/examples/logging_simpletest.py b/examples/logging_simpletest.py index 3286bce..c6c7d4a 100644 --- a/examples/logging_simpletest.py +++ b/examples/logging_simpletest.py @@ -19,14 +19,7 @@ # This should produce no output null_logger = logging.getLogger(None) - -null_logger.setLevel(logging.ERROR) -null_logger.info("Info message") -null_logger.error("Error message") - -# This should produce no output - -null_logger = logging.getLogger("") +null_logger.addHandler(logging.NullHandler) null_logger.setLevel(logging.ERROR) null_logger.info("Info message") From a8d103724a4d33097eef5bd4cd9cc4f2f36fe9d0 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 21 May 2022 22:03:18 -0400 Subject: [PATCH 040/162] Fixed getLogger() docstring --- adafruit_logging.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index fca9ece..c4b75bf 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -170,8 +170,7 @@ def _addLogger(logger_name: str): def getLogger(logger_name: str) -> "Logger": """Create or retrieve a logger by name - :param str logger_name: The name of the `Logger` to create/retrieve. `None` - will cause the `NullLogger` instance to be returned. + :param str logger_name: The name of the `Logger` to create/retrieve. """ _addLogger(logger_name) From 61fd3cf0233adb822089807a57e6d9d64f003d7a Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 21 May 2022 22:13:41 -0400 Subject: [PATCH 041/162] Prevent recursive _addLogger() and __init__() --- adafruit_logging.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index c4b75bf..387f44e 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -168,7 +168,8 @@ def _addLogger(logger_name: str): # pylint:disable=global-statement def getLogger(logger_name: str) -> "Logger": - """Create or retrieve a logger by name + """Create or retrieve a logger by name; only retrieves loggers + made using this function :param str logger_name: The name of the `Logger` to create/retrieve. @@ -190,7 +191,6 @@ def __init__(self, name: str, level=NOTSET): """The name of the logger, this should be unique for proper functionality of `getLogger()`""" self._handler = None - _addLogger(name) def setLevel(self, log_level: int): """Set the logging cutoff level. From 969f3b0c3c8f5c7de758d1479fce9ac162a3df5e Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 21 May 2022 22:21:51 -0400 Subject: [PATCH 042/162] Change arg names --- adafruit_logging.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 387f44e..07f2005 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -305,19 +305,19 @@ def addHandler(self, handler: Handler): def log(self, log_level: int, format_string: str, *args): """Dummy implementation.""" - def debug(self, format_string: str, *args): + def debug(self, msg: str, *args): """Dummy implementation.""" - def info(self, format_string: str, *args): + def info(self, msg: str, *args): """Dummy implementation.""" - def warning(self, format_string: str, *args): + def warning(self, msg: str, *args): """Dummy implementation.""" - def error(self, format_string: str, *args): + def error(self, msg: str, *args): """Dummy implementation.""" - def critical(self, format_string: str, *args): + def critical(self, msg: str, *args): """Dummy implementation.""" def _emit(self, log_level: int, message: str): From 15fc22f8970ea6f66082ce58bf8db8588380e9f1 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 21 May 2022 22:22:10 -0400 Subject: [PATCH 043/162] Fix simple test example --- examples/logging_simpletest.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/logging_simpletest.py b/examples/logging_simpletest.py index c6c7d4a..425565c 100644 --- a/examples/logging_simpletest.py +++ b/examples/logging_simpletest.py @@ -11,6 +11,8 @@ # This should produce an error output logger = logging.getLogger("test") +print_logger = logging.StreamHandler() +logger.addHandler(print_logger) logger.setLevel(logging.ERROR) logger.info("Info message") @@ -19,7 +21,8 @@ # This should produce no output null_logger = logging.getLogger(None) -null_logger.addHandler(logging.NullHandler) +null_handler = logging.NullHandler() +null_logger.addHandler(null_handler) null_logger.setLevel(logging.ERROR) null_logger.info("Info message") From 75cf4c9d4e9a0b8ea10ac7fb2ab82ccf09a96178 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 21 May 2022 22:54:14 -0400 Subject: [PATCH 044/162] Update file logging example --- examples/logging_filehandler.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/logging_filehandler.py b/examples/logging_filehandler.py index e96015f..92f9d0d 100644 --- a/examples/logging_filehandler.py +++ b/examples/logging_filehandler.py @@ -28,3 +28,6 @@ logger.info("Logger initialized!") logger.debug("You can even add debug statements to the log!") + +# If you're done with the FileHandler, close it +file_handler.close() From 335af26db04d56ca28d28a12f1f86f3683298998 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 21 May 2022 23:03:35 -0400 Subject: [PATCH 045/162] Logger names should be strings --- examples/logging_simpletest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/logging_simpletest.py b/examples/logging_simpletest.py index 425565c..3eb258b 100644 --- a/examples/logging_simpletest.py +++ b/examples/logging_simpletest.py @@ -20,7 +20,7 @@ # This should produce no output -null_logger = logging.getLogger(None) +null_logger = logging.getLogger("null") null_handler = logging.NullHandler() null_logger.addHandler(null_handler) From e4275d1d51e474ec51b0770be7a39643d38f84a4 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sun, 22 May 2022 00:18:55 -0400 Subject: [PATCH 046/162] Increase min lines similarity Signed-off-by: Alec Delaney --- .pylintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index a836eb2..59976e7 100644 --- a/.pylintrc +++ b/.pylintrc @@ -256,7 +256,7 @@ ignore-docstrings=yes ignore-imports=yes # Minimum lines number of a similarity. -min-similarity-lines=4 +min-similarity-lines=12 [BASIC] From b50e8b95c0adcba80483dd9f60582c1d88f18fa7 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sun, 22 May 2022 00:18:23 -0400 Subject: [PATCH 047/162] Switch to inclusive terminology Signed-off-by: Alec Delaney --- .pylintrc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pylintrc b/.pylintrc index 59976e7..3636ad5 100644 --- a/.pylintrc +++ b/.pylintrc @@ -13,11 +13,11 @@ # run arbitrary code extension-pkg-whitelist= -# Add files or directories to the blacklist. They should be base names, not +# Add files or directories to the ignore-list. They should be base names, not # paths. ignore=CVS -# Add files or directories matching the regex patterns to the blacklist. The +# Add files or directories matching the regex patterns to the ignore-list. The # regex matches against base names, not paths. ignore-patterns= From 6faddada2a07bf825152ca054c1f338682a47141 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 28 May 2022 12:58:47 -0400 Subject: [PATCH 048/162] Change arg name for addHandler() --- adafruit_logging.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 07f2005..33f6c4c 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -208,7 +208,7 @@ def getEffectiveLevel(self) -> int: """ return self._level - def addHandler(self, handler: Handler): + def addHandler(self, hdlr: Handler): """Sets the handler of this logger to the specified handler. *NOTE* this is slightly different from the CPython equivalent which adds @@ -217,7 +217,7 @@ def addHandler(self, handler: Handler): :param LoggingHandler handler: the handler """ - self._handler = handler + self._handler = hdlr def hasHandlers(self) -> bool: """Whether any handlers have been set for this logger""" From 85ad94cd12249bcd1ea44fa1deb582975d577f59 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 28 May 2022 13:01:20 -0400 Subject: [PATCH 049/162] Use brackets to get logger in getLogger() --- adafruit_logging.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 33f6c4c..0217e8b 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -169,13 +169,14 @@ def _addLogger(logger_name: str): # pylint:disable=global-statement def getLogger(logger_name: str) -> "Logger": """Create or retrieve a logger by name; only retrieves loggers - made using this function + made using this function; if a Logger with this name does not + exist it is created :param str logger_name: The name of the `Logger` to create/retrieve. """ _addLogger(logger_name) - return logger_cache.get(logger_name, None) + return logger_cache[logger_name] # pylint:enable=global-statement From 1815d54014a873cf9660fbae802d2792549f0c43 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Mon, 30 May 2022 14:25:04 -0400 Subject: [PATCH 050/162] Set language to "en" for documentation Signed-off-by: Alec Delaney --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index fe113ab..6419b1b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -60,7 +60,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. From af08521bc8a688a32a767bf1af8a674ab294d127 Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Fri, 3 Jun 2022 11:56:10 -0400 Subject: [PATCH 051/162] Update adafruit_logging.py --- adafruit_logging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 0217e8b..34160a1 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -215,7 +215,7 @@ def addHandler(self, hdlr: Handler): *NOTE* this is slightly different from the CPython equivalent which adds the handler rather than replacing it. - :param LoggingHandler handler: the handler + :param Handler hdlr: the handler """ self._handler = hdlr From 782884484453a827e9769910670f5bbe3634a43b Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Fri, 3 Jun 2022 12:34:44 -0400 Subject: [PATCH 052/162] Only log if there is a handler --- adafruit_logging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 34160a1..99f1771 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -233,7 +233,7 @@ def log(self, level: int, msg: str, *args): :param args: arguments to ``format_string.format()``; can be empty """ - if level >= self._level: + if _handler and level >= self._level: self._handler._emit(level, msg % args) # pylint: disable=protected-access def debug(self, msg: str, *args): From ea091f394ea4eb22fad5e5deb911fd439482b9d3 Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Fri, 3 Jun 2022 13:00:23 -0400 Subject: [PATCH 053/162] Add missing use of self --- adafruit_logging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 99f1771..5430b97 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -233,7 +233,7 @@ def log(self, level: int, msg: str, *args): :param args: arguments to ``format_string.format()``; can be empty """ - if _handler and level >= self._level: + if self._handler and level >= self._level: self._handler._emit(level, msg % args) # pylint: disable=protected-access def debug(self, msg: str, *args): From affcb28e775e89d1d0e66ddacda2cecc42d064ad Mon Sep 17 00:00:00 2001 From: evaherrada Date: Tue, 7 Jun 2022 15:34:33 -0400 Subject: [PATCH 054/162] Added cp.org link to index.rst --- docs/index.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 50c412d..87a7a52 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -29,7 +29,8 @@ Table of Contents .. toctree:: :caption: Other Links - Download + Download from GitHub + Download Library Bundle CircuitPython Reference Documentation CircuitPython Support Forum Discord Chat From 6efa8de7e4e71e9f5569a3d00436315a0496f7cd Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Wed, 8 Jun 2022 08:26:28 -0400 Subject: [PATCH 055/162] Add self to author list --- adafruit_logging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 5430b97..d275ef0 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -9,7 +9,7 @@ Logging module for CircuitPython -* Author(s): Dave Astels +* Author(s): Dave Astels, Alec Delaney Implementation Notes -------------------- From 371087f119fd592ce5cbe8098219e22f446d4650 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Wed, 15 Jun 2022 18:19:12 -0400 Subject: [PATCH 056/162] Add note about differences from CPython --- adafruit_logging.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/adafruit_logging.py b/adafruit_logging.py index d275ef0..da532d9 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -46,6 +46,13 @@ The CRITICAL logging level, which is the highest (most severe) level for unrecoverable errors that have caused the code to halt and exit. +.. note:: + + This module has a few key differences compared to its CPython counterpart, notably + that loggers can only be assigned one handler at a time. Calling ``addHander()`` + replaces the currently stored handler for that logger. Additionally, the default + formatting for handlers is different. + """ # pylint:disable=redefined-outer-name,consider-using-enumerate,no-self-use # pylint:disable=invalid-name From 46f575a78616df4d804f827dc5c59bc466e9a4f1 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Wed, 15 Jun 2022 18:19:28 -0400 Subject: [PATCH 057/162] Add additional imports --- adafruit_logging.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/adafruit_logging.py b/adafruit_logging.py index da532d9..b19543d 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -59,6 +59,13 @@ import time import sys +from collections import namedtuple + +try: + from typing import Optional, Union + from io import TextIOWrapper, StringIO +except: + pass __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Logger.git" From b430238e008ea018cadedba387d65b00d282b83a Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 16 Jun 2022 22:44:12 -0400 Subject: [PATCH 058/162] Use enumerate --- adafruit_logging.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index b19543d..adf08ab 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -108,10 +108,10 @@ def _level_for(value: int) -> str: :param int value: a numeric level """ - for i in range(len(LEVELS)): - if value == LEVELS[i][0]: - return LEVELS[i][1] - if value < LEVELS[i][0]: + for i, level in enumerate(LEVELS): + if value == level[0]: + return level[1] + if value < level[0]: return LEVELS[i - 1][1] return LEVELS[0][1] From a1c03b70bc49b0ea5a76ab52f15bdb099b221d66 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 16 Jun 2022 22:45:52 -0400 Subject: [PATCH 059/162] Guard against ImportError only --- adafruit_logging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index adf08ab..a632c9a 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -64,7 +64,7 @@ try: from typing import Optional, Union from io import TextIOWrapper, StringIO -except: +except ImportError: pass __version__ = "0.0.0-auto.0" From 9b5fe687f91d607db30f5dff7c20f46ef8fedad7 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 16 Jun 2022 22:46:23 -0400 Subject: [PATCH 060/162] Update pylint disables --- adafruit_logging.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index a632c9a..b4167d8 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -54,8 +54,8 @@ formatting for handlers is different. """ -# pylint:disable=redefined-outer-name,consider-using-enumerate,no-self-use -# pylint:disable=invalid-name + +# pylint: disable=invalid-name,undefined-variable import time import sys From a7478099d74dbbb8ad2e8b0dc1e6d8d1e234dc2c Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 16 Jun 2022 22:47:06 -0400 Subject: [PATCH 061/162] Create LogRecord and factory function --- adafruit_logging.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/adafruit_logging.py b/adafruit_logging.py index b4167d8..7801a58 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -116,6 +116,25 @@ def _level_for(value: int) -> str: return LEVELS[0][1] +LogRecord = namedtuple( + "_LogRecord", ("name", "levelno", "levelname", "msg", "created", "args") +) +"""An object used to hold the contents of a log record. The following attributes can +be retrieved from it: + +- ``name`` - The name of the logger +- ``levelno`` - The log level number +- ``levelname`` - The log level name +- ``msg`` - The log message +- ``created`` - When the log record was created +- ``args`` - The additional positional arguments provided +""" + +_logRecordFactory = lambda name, level, msg, args: LogRecord( + name, level, _level_for(level), msg, time.monotonic(), args +) + + # pylint: disable=too-few-public-methods class Handler: """Abstract logging message handler.""" From c53476898d96d7e3efd889d4ff1635c32f68dee4 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 16 Jun 2022 22:47:44 -0400 Subject: [PATCH 062/162] Make CPython compatiable handlers --- adafruit_logging.py | 143 ++++++++++++++++++++------------------------ 1 file changed, 64 insertions(+), 79 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 7801a58..25092d7 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -139,12 +139,15 @@ def _level_for(value: int) -> str: class Handler: """Abstract logging message handler.""" - def __init__(self, level=NOTSET): # pylint: disable=undefined-variable + def __init__( + self, level: int = NOTSET + ) -> None: # pylint: disable=undefined-variable self.level = level """Level of the handler; this is currently unused, and only the level of the logger is used""" - def _format(self, log_level: int, message: str) -> str: + # pylint: disable=no-self-use + def format(self, record: LogRecord) -> str: """Generate a timestamped message. :param int log_level: the logging level @@ -152,10 +155,10 @@ def _format(self, log_level: int, message: str) -> str: """ return "{0:<0.3f}: {1} - {2}".format( - time.monotonic(), _level_for(log_level), message + record.created, record.levelname, record.msg ) - def _emit(self, log_level: int, message: str): + def emit(self, record: LogRecord) -> None: """Send a message where it should go. Placeholder for subclass implementations. """ @@ -170,21 +173,21 @@ class StreamHandler(Handler): :param stream: The stream to log to, default is `sys.stderr` """ - def __init__(self, stream=None): + def __init__(self, stream: Optional[Union[TextIOWrapper, StringIO]] = None) -> None: super().__init__() if stream is None: stream = sys.stderr self.stream = stream """The stream to log to""" - def _emit(self, log_level: int, message: str): + def emit(self, record: LogRecord) -> None: """Send a message to the console. :param int log_level: the logging level :param str message: the message to log """ - print(self._format(log_level, message)) + self.stream.write(self.format(record)) # The level module-global variables get created when loaded @@ -193,7 +196,7 @@ def _emit(self, log_level: int, message: str): logger_cache = {} -def _addLogger(logger_name: str): +def _addLogger(logger_name: str) -> None: """Adds the logger if it doesn't already exist""" if logger_name not in logger_cache: logger_cache[logger_name] = Logger(logger_name) @@ -206,7 +209,6 @@ def getLogger(logger_name: str) -> "Logger": exist it is created :param str logger_name: The name of the `Logger` to create/retrieve. - """ _addLogger(logger_name) return logger_cache[logger_name] @@ -218,7 +220,7 @@ def getLogger(logger_name: str) -> "Logger": class Logger: """Provide a logging api.""" - def __init__(self, name: str, level=NOTSET): + def __init__(self, name: str, level: int = NOTSET) -> None: """Create an instance.""" self._level = level self.name = name @@ -226,27 +228,27 @@ def __init__(self, name: str, level=NOTSET): functionality of `getLogger()`""" self._handler = None - def setLevel(self, log_level: int): + def setLevel(self, log_level: int) -> None: """Set the logging cutoff level. :param int log_level: the lowest level to output - """ + self._level = log_level def getEffectiveLevel(self) -> int: """Get the effective level for this logger. :return: the lowest level to output - """ + return self._level - def addHandler(self, hdlr: Handler): + def addHandler(self, hdlr: Handler) -> None: """Sets the handler of this logger to the specified handler. - *NOTE* this is slightly different from the CPython equivalent which adds - the handler rather than replacing it. + *NOTE* This is slightly different from the CPython equivalent + which adds the handler rather than replacing it. :param Handler hdlr: the handler @@ -257,104 +259,87 @@ def hasHandlers(self) -> bool: """Whether any handlers have been set for this logger""" return self._handler is not None - def log(self, level: int, msg: str, *args): + def _log(self, level: int, msg: str, *args) -> None: + record = _logRecordFactory(self.name, level, msg % args, args) + if self._handler and level >= self._level: + self._handler.emit(record) + + def log(self, level: int, msg: str, *args) -> None: """Log a message. :param int level: the priority level at which to log :param str msg: the core message string with embedded formatting directives :param args: arguments to ``format_string.format()``; can be empty - """ - if self._handler and level >= self._level: - self._handler._emit(level, msg % args) # pylint: disable=protected-access - def debug(self, msg: str, *args): + self._log(level, msg, *args) + + def debug(self, msg: str, *args) -> None: """Log a debug message. :param str fmsg: the core message string with embedded - formatting directives - :param args: arguments to ``format_string.format()``; can be empty - + formatting directives + :param args: arguments to ``format_string.format()``; + can be empty """ - self.log(DEBUG, msg, *args) + self._log(DEBUG, msg, *args) - def info(self, msg: str, *args): + def info(self, msg: str, *args) -> None: """Log a info message. :param str msg: the core message string with embedded - formatting directives - :param args: arguments to ``format_string.format()``; can be empty - + formatting directives + :param args: arguments to ``format_string.format()``; + can be empty """ - self.log(INFO, msg, *args) - def warning(self, msg: str, *args): + self._log(INFO, msg, *args) + + def warning(self, msg: str, *args) -> None: """Log a warning message. :param str msg: the core message string with embedded - formatting directives - :param args: arguments to ``format_string.format()``; can be empty - + formatting directives + :param args: arguments to ``format_string.format()``; + can be empty """ - self.log(WARNING, msg, *args) - def error(self, msg: str, *args): + self._log(WARNING, msg, *args) + + def error(self, msg: str, *args) -> None: """Log a error message. :param str msg: the core message string with embedded - formatting directives - :param args: arguments to ``format_string.format()``; can be empty - + formatting directives + :param args: arguments to ``format_string.format()``; + can be empty """ - self.log(ERROR, msg, *args) - def critical(self, msg: str, *args): + self._log(ERROR, msg, *args) + + def critical(self, msg: str, *args) -> None: """Log a critical message. :param str msg: the core message string with embedded - formatting directives - :param args: arguments to ``format_string.format()``; can be empty - + formatting directives + :param args: arguments to ``format_string.format()``; + can be empty """ - self.log(CRITICAL, msg, *args) + self._log(CRITICAL, msg, *args) class NullHandler(Handler): """Provide an empty log handler. This can be used in place of a real log handler to more efficiently disable - logging.""" - - def setLevel(self, log_level: int): - """Dummy implementation.""" - - def getEffectiveLevel(self) -> int: - """Dummy implementation.""" - return NOTSET - - def addHandler(self, handler: Handler): - """Dummy implementation.""" - - def log(self, log_level: int, format_string: str, *args): - """Dummy implementation.""" - - def debug(self, msg: str, *args): - """Dummy implementation.""" - - def info(self, msg: str, *args): - """Dummy implementation.""" - - def warning(self, msg: str, *args): - """Dummy implementation.""" - - def error(self, msg: str, *args): - """Dummy implementation.""" + logging. + """ - def critical(self, msg: str, *args): - """Dummy implementation.""" + def format(self, record: LogRecord) -> str: + """Dummy implementation""" - def _emit(self, log_level: int, message: str): + def emit(self, record: LogRecord) -> None: """Dummy implementation""" @@ -370,22 +355,22 @@ def __init__(self, filename: str, mode: str = "a") -> None: # pylint: disable=consider-using-with super().__init__(open(filename, mode=mode)) - def close(self): + def close(self) -> None: """Closes the file""" self.stream.close() - def _format(self, log_level: int, message: str): + def format(self, record: LogRecord) -> str: """Generate a string to log :param level: The level of the message :param msg: The message to format """ - return super()._format(log_level, message) + "\r\n" + return super().format(record) + "\r\n" - def _emit(self, log_level: int, message: str): + def emit(self, record: LogRecord) -> None: """Generate the message and write it to the UART. :param level: The level of the message :param msg: The message to log """ - self.stream.write(self._format(log_level, message)) + self.stream.write(self.format(record)) From 08148b98835861cb213ea939fca862f9b7fe39b7 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Fri, 17 Jun 2022 08:08:41 -0400 Subject: [PATCH 063/162] Move comment --- adafruit_logging.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 25092d7..474043a 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -88,6 +88,7 @@ "FileHandler", ] +# The level module-global variables get created when loaded LEVELS = [ (00, "NOTSET"), @@ -189,10 +190,6 @@ def emit(self, record: LogRecord) -> None: """ self.stream.write(self.format(record)) - -# The level module-global variables get created when loaded -# pylint:disable=undefined-variable - logger_cache = {} From 0e31d32bb225ea5fc769ed18d169ba30c50017d2 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Fri, 17 Jun 2022 08:08:51 -0400 Subject: [PATCH 064/162] Update __all__ --- adafruit_logging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 474043a..8300352 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -78,7 +78,6 @@ "WARNING", "ERROR", "CRITICAL", - "_level_for", "Handler", "StreamHandler", "logger_cache", @@ -86,6 +85,7 @@ "Logger", "NullHandler", "FileHandler", + "LogRecord", ] # The level module-global variables get created when loaded From cb90aef44985a1369044c91755e71b4fb17485f7 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Fri, 17 Jun 2022 08:09:02 -0400 Subject: [PATCH 065/162] Add newline --- adafruit_logging.py | 1 + 1 file changed, 1 insertion(+) diff --git a/adafruit_logging.py b/adafruit_logging.py index 8300352..db4777d 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -69,6 +69,7 @@ __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Logger.git" + # pylint:disable=undefined-all-variable __all__ = [ "LEVELS", From 42b383ac4fb699ddfd248d81a5b3c10c0364dc2c Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Fri, 17 Jun 2022 08:14:06 -0400 Subject: [PATCH 066/162] Add flush() before close() --- adafruit_logging.py | 96 +++++++++++++++++++++++---------------------- 1 file changed, 49 insertions(+), 47 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index db4777d..5cae19e 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -191,6 +191,55 @@ def emit(self, record: LogRecord) -> None: """ self.stream.write(self.format(record)) + +class FileHandler(StreamHandler): + """File handler for working with log files off of the microcontroller (like + an SD card) + + :param str filename: The filename of the log file + :param str mode: Whether to write ('w') or append ('a'); default is to append + """ + + def __init__(self, filename: str, mode: str = "a") -> None: + # pylint: disable=consider-using-with + super().__init__(open(filename, mode=mode)) + + def close(self) -> None: + """Closes the file""" + self.stream.flush() + self.stream.close() + + def format(self, record: LogRecord) -> str: + """Generate a string to log + + :param level: The level of the message + :param msg: The message to format + """ + return super().format(record) + "\r\n" + + def emit(self, record: LogRecord) -> None: + """Generate the message and write it to the UART. + + :param level: The level of the message + :param msg: The message to log + """ + self.stream.write(self.format(record)) + + +class NullHandler(Handler): + """Provide an empty log handler. + + This can be used in place of a real log handler to more efficiently disable + logging. + """ + + def format(self, record: LogRecord) -> str: + """Dummy implementation""" + + def emit(self, record: LogRecord) -> None: + """Dummy implementation""" + + logger_cache = {} @@ -325,50 +374,3 @@ def critical(self, msg: str, *args) -> None: can be empty """ self._log(CRITICAL, msg, *args) - - -class NullHandler(Handler): - """Provide an empty log handler. - - This can be used in place of a real log handler to more efficiently disable - logging. - """ - - def format(self, record: LogRecord) -> str: - """Dummy implementation""" - - def emit(self, record: LogRecord) -> None: - """Dummy implementation""" - - -class FileHandler(StreamHandler): - """File handler for working with log files off of the microcontroller (like - an SD card) - - :param str filename: The filename of the log file - :param str mode: Whether to write ('w') or append ('a'); default is to append - """ - - def __init__(self, filename: str, mode: str = "a") -> None: - # pylint: disable=consider-using-with - super().__init__(open(filename, mode=mode)) - - def close(self) -> None: - """Closes the file""" - self.stream.close() - - def format(self, record: LogRecord) -> str: - """Generate a string to log - - :param level: The level of the message - :param msg: The message to format - """ - return super().format(record) + "\r\n" - - def emit(self, record: LogRecord) -> None: - """Generate the message and write it to the UART. - - :param level: The level of the message - :param msg: The message to log - """ - self.stream.write(self.format(record)) From 00b6d7712f59a3b13a9b6f7b151cab9f321af2ca Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Fri, 17 Jun 2022 08:15:32 -0400 Subject: [PATCH 067/162] Remove format from NullHandler --- adafruit_logging.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 5cae19e..3cfff86 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -233,9 +233,6 @@ class NullHandler(Handler): logging. """ - def format(self, record: LogRecord) -> str: - """Dummy implementation""" - def emit(self, record: LogRecord) -> None: """Dummy implementation""" From d72581aab15b55c980747e3bf43af5ab823defc5 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Fri, 17 Jun 2022 08:16:06 -0400 Subject: [PATCH 068/162] Remove unnecessary pylint disable --- adafruit_logging.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 3cfff86..26a6ad5 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -258,9 +258,6 @@ def getLogger(logger_name: str) -> "Logger": return logger_cache[logger_name] -# pylint:enable=global-statement - - class Logger: """Provide a logging api.""" From 3efabd07eccb9da1cb706e80b7c09d1d1774bde7 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Fri, 17 Jun 2022 08:20:19 -0400 Subject: [PATCH 069/162] Adding/removing newlines --- adafruit_logging.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 26a6ad5..57970bc 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -108,7 +108,6 @@ def _level_for(value: int) -> str: """Convert a numeric level to the most appropriate name. :param int value: a numeric level - """ for i, level in enumerate(LEVELS): if value == level[0]: @@ -154,8 +153,8 @@ def format(self, record: LogRecord) -> str: :param int log_level: the logging level :param str message: the message to log - """ + return "{0:<0.3f}: {1} - {2}".format( record.created, record.levelname, record.msg ) @@ -164,6 +163,7 @@ def emit(self, record: LogRecord) -> None: """Send a message where it should go. Placeholder for subclass implementations. """ + raise NotImplementedError() @@ -187,7 +187,6 @@ def emit(self, record: LogRecord) -> None: :param int log_level: the logging level :param str message: the message to log - """ self.stream.write(self.format(record)) @@ -292,7 +291,6 @@ def addHandler(self, hdlr: Handler) -> None: which adds the handler rather than replacing it. :param Handler hdlr: the handler - """ self._handler = hdlr From 035ef10ab20552c8ebfc1eca0bc126b696d0e2cd Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Fri, 17 Jun 2022 08:21:24 -0400 Subject: [PATCH 070/162] Update Logger docstring --- adafruit_logging.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 57970bc..c7cee12 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -258,7 +258,12 @@ def getLogger(logger_name: str) -> "Logger": class Logger: - """Provide a logging api.""" + """The actual logger that will provide the logging API. + + :param str name: The name of the logger, typically assigned by the + value from `getLogger` + :param int level: (optional) The log level, default is ``NOTSET`` + """ def __init__(self, name: str, level: int = NOTSET) -> None: """Create an instance.""" From ee37fb5e35fa471f0e6699a97adf0df1cba63113 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Fri, 17 Jun 2022 08:21:48 -0400 Subject: [PATCH 071/162] Reformat params in docstring --- adafruit_logging.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index c7cee12..be0c00f 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -313,8 +313,9 @@ def log(self, level: int, msg: str, *args) -> None: :param int level: the priority level at which to log :param str msg: the core message string with embedded - formatting directives - :param args: arguments to ``format_string.format()``; can be empty + formatting directives + :param args: arguments to ``format_string.format()``; + can be empty """ self._log(level, msg, *args) From 67011dbcf4f61e22e24e769d762a0d5844ebba96 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Fri, 17 Jun 2022 08:23:07 -0400 Subject: [PATCH 072/162] Remove unnecessary pylint disables --- adafruit_logging.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index be0c00f..0d46f85 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -136,7 +136,6 @@ def _level_for(value: int) -> str: ) -# pylint: disable=too-few-public-methods class Handler: """Abstract logging message handler.""" @@ -245,7 +244,6 @@ def _addLogger(logger_name: str) -> None: logger_cache[logger_name] = Logger(logger_name) -# pylint:disable=global-statement def getLogger(logger_name: str) -> "Logger": """Create or retrieve a logger by name; only retrieves loggers made using this function; if a Logger with this name does not From bb5d16a1635b6c58fe8bfebaef876d314c611584 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Fri, 17 Jun 2022 08:23:54 -0400 Subject: [PATCH 073/162] Reduce header line --- adafruit_logging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 0d46f85..573d880 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -4,7 +4,7 @@ """ `adafruit_logging` -================================================================================ +================== Logging module for CircuitPython From d83f21dc1fc24ed0381f4c005394a5d286b11077 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Fri, 17 Jun 2022 08:30:01 -0400 Subject: [PATCH 074/162] Remove level from Handler --- adafruit_logging.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 573d880..a830627 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -139,13 +139,6 @@ def _level_for(value: int) -> str: class Handler: """Abstract logging message handler.""" - def __init__( - self, level: int = NOTSET - ) -> None: # pylint: disable=undefined-variable - self.level = level - """Level of the handler; this is currently unused, and - only the level of the logger is used""" - # pylint: disable=no-self-use def format(self, record: LogRecord) -> str: """Generate a timestamped message. From 102abb7269adc650c289acf98264dc7dec83a7a5 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Fri, 17 Jun 2022 08:38:10 -0400 Subject: [PATCH 075/162] Move note higher in docstring --- adafruit_logging.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index a830627..c87a911 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -22,6 +22,13 @@ * Adafruit CircuitPython firmware for the supported boards: https://github.com/adafruit/circuitpython/releases +.. note:: + + This module has a few key differences compared to its CPython counterpart, notably + that loggers can only be assigned one handler at a time. Calling ``addHander()`` + replaces the currently stored handler for that logger. Additionally, the default + formatting for handlers is different. + Attributes ---------- LEVELS : list @@ -46,13 +53,6 @@ The CRITICAL logging level, which is the highest (most severe) level for unrecoverable errors that have caused the code to halt and exit. -.. note:: - - This module has a few key differences compared to its CPython counterpart, notably - that loggers can only be assigned one handler at a time. Calling ``addHander()`` - replaces the currently stored handler for that logger. Additionally, the default - formatting for handlers is different. - """ # pylint: disable=invalid-name,undefined-variable From 535e4f04e1a4e988b57a6db8adce82ef44d5575a Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Fri, 17 Jun 2022 08:39:45 -0400 Subject: [PATCH 076/162] Fix logging method docstrings --- adafruit_logging.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index c87a911..f90ec5b 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -305,7 +305,7 @@ def log(self, level: int, msg: str, *args) -> None: :param int level: the priority level at which to log :param str msg: the core message string with embedded formatting directives - :param args: arguments to ``format_string.format()``; + :param args: arguments to ``msg % args``; can be empty """ @@ -316,7 +316,7 @@ def debug(self, msg: str, *args) -> None: :param str fmsg: the core message string with embedded formatting directives - :param args: arguments to ``format_string.format()``; + :param args: arguments to ``msg % args``; can be empty """ self._log(DEBUG, msg, *args) @@ -326,7 +326,7 @@ def info(self, msg: str, *args) -> None: :param str msg: the core message string with embedded formatting directives - :param args: arguments to ``format_string.format()``; + :param args: arguments to ``msg % args``; can be empty """ @@ -337,7 +337,7 @@ def warning(self, msg: str, *args) -> None: :param str msg: the core message string with embedded formatting directives - :param args: arguments to ``format_string.format()``; + :param args: arguments to ``msg % args``; can be empty """ @@ -348,7 +348,7 @@ def error(self, msg: str, *args) -> None: :param str msg: the core message string with embedded formatting directives - :param args: arguments to ``format_string.format()``; + :param args: arguments to ``msg % args``; can be empty """ @@ -359,7 +359,7 @@ def critical(self, msg: str, *args) -> None: :param str msg: the core message string with embedded formatting directives - :param args: arguments to ``format_string.format()``; + :param args: arguments to ``msg % args``; can be empty """ self._log(CRITICAL, msg, *args) From 63c54eae539d8b754d3f0545f1eb133c20c90dcb Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 18 Jun 2022 10:18:57 -0400 Subject: [PATCH 077/162] Update docstrings --- adafruit_logging.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index f90ec5b..dcf7b7d 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -143,8 +143,7 @@ class Handler: def format(self, record: LogRecord) -> str: """Generate a timestamped message. - :param int log_level: the logging level - :param str message: the message to log + :param record: The record (message object) to be logged """ return "{0:<0.3f}: {1} - {2}".format( @@ -154,6 +153,8 @@ def format(self, record: LogRecord) -> str: def emit(self, record: LogRecord) -> None: """Send a message where it should go. Placeholder for subclass implementations. + + :param record: The record (message object) to be logged """ raise NotImplementedError() @@ -177,8 +178,7 @@ def __init__(self, stream: Optional[Union[TextIOWrapper, StringIO]] = None) -> N def emit(self, record: LogRecord) -> None: """Send a message to the console. - :param int log_level: the logging level - :param str message: the message to log + :param record: The record (message object) to be logged """ self.stream.write(self.format(record)) @@ -203,16 +203,14 @@ def close(self) -> None: def format(self, record: LogRecord) -> str: """Generate a string to log - :param level: The level of the message - :param msg: The message to format + :param record: The record (message object) to be logged """ return super().format(record) + "\r\n" def emit(self, record: LogRecord) -> None: """Generate the message and write it to the UART. - :param level: The level of the message - :param msg: The message to log + :param record: The record (message object) to be logged """ self.stream.write(self.format(record)) @@ -286,7 +284,7 @@ def addHandler(self, hdlr: Handler) -> None: *NOTE* This is slightly different from the CPython equivalent which adds the handler rather than replacing it. - :param Handler hdlr: the handler + :param Handler hdlr: The handler to add """ self._handler = hdlr From 3f4c59d8d57cb12e53d1d45489d64029ed19063a Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 18 Jun 2022 10:20:14 -0400 Subject: [PATCH 078/162] Use Protocol for StreamHandler.__init__() --- adafruit_logging.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index dcf7b7d..b1f7dfd 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -62,8 +62,14 @@ from collections import namedtuple try: - from typing import Optional, Union - from io import TextIOWrapper, StringIO + from typing import Optional + from typing_extensions import Protocol + + class WriteableStream(Protocol): + + def write(self, buf: str) -> int: + ... + except ImportError: pass @@ -165,10 +171,12 @@ class StreamHandler(Handler): """Send logging messages to a stream, `sys.stderr` (typically the serial console) by default. - :param stream: The stream to log to, default is `sys.stderr` + :param stream: The stream to log to, default is `sys.stderr`; + can accept any stream that implements ``stream.write()`` + with string inputs """ - def __init__(self, stream: Optional[Union[TextIOWrapper, StringIO]] = None) -> None: + def __init__(self, stream: Optional[WriteableStream] = None) -> None: super().__init__() if stream is None: stream = sys.stderr From acb6e04f1d0b96e5df7fdfc4590b94464934523a Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 18 Jun 2022 10:26:18 -0400 Subject: [PATCH 079/162] Linted and reformatted per pre-commit --- adafruit_logging.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index b1f7dfd..805af5c 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -66,9 +66,13 @@ from typing_extensions import Protocol class WriteableStream(Protocol): + """Any stream that can ``write`` strings""" def write(self, buf: str) -> int: - ... + """Write to the stream + + :param str buf: The string data to write to the stream + """ except ImportError: pass From 5c706853a0272c9a7c40fdd11c4f576a9195d417 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 18 Jun 2022 11:40:09 -0400 Subject: [PATCH 080/162] Add typing_extensions as dependency --- requirements.txt | 1 + setup.py | 1 + 2 files changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index 5f97a2d..3396d72 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,4 @@ # SPDX-License-Identifier: Unlicense Adafruit-Blinka +typing-extensions diff --git a/setup.py b/setup.py index 428e4c2..e95da64 100644 --- a/setup.py +++ b/setup.py @@ -34,6 +34,7 @@ author_email="circuitpython@adafruit.com", install_requires=[ "Adafruit-Blinka", + "typing-extensions" ], # Choose your license license="MIT", From 295f0dc0a8e6112434f61d04d57a5c06ba0fc2d0 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 18 Jun 2022 11:47:03 -0400 Subject: [PATCH 081/162] Reformatted setup.py --- setup.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/setup.py b/setup.py index e95da64..86b612c 100644 --- a/setup.py +++ b/setup.py @@ -32,10 +32,7 @@ # Author details author="Adafruit Industries", author_email="circuitpython@adafruit.com", - install_requires=[ - "Adafruit-Blinka", - "typing-extensions" - ], + install_requires=["Adafruit-Blinka", "typing-extensions"], # Choose your license license="MIT", # See https://pypi.python.org/pypi?%3Aaction=list_classifiers From 8fa157182e9c3efaa0ca972ff1e1886744edd345 Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Tue, 21 Jun 2022 10:09:34 -0400 Subject: [PATCH 082/162] Update adafruit_logging.py Use try/except for Protocol import Co-authored-by: Dan Halbert --- adafruit_logging.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 805af5c..5f01061 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -63,7 +63,10 @@ try: from typing import Optional - from typing_extensions import Protocol + try: + from typing import Protocol + except ImportError: + from typing_extensions import Protocol class WriteableStream(Protocol): """Any stream that can ``write`` strings""" From 368ebf59e71e49a7169768bdc691c235ba171596 Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Tue, 21 Jun 2022 10:18:42 -0400 Subject: [PATCH 083/162] fix indentation --- adafruit_logging.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 5f01061..651c8a2 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -63,10 +63,10 @@ try: from typing import Optional - try: - from typing import Protocol - except ImportError: - from typing_extensions import Protocol + try: + from typing import Protocol + except ImportError: + from typing_extensions import Protocol class WriteableStream(Protocol): """Any stream that can ``write`` strings""" From 1cc39f5e8802000bcfebb48bfcff8c3020aca5fb Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Tue, 21 Jun 2022 10:23:15 -0400 Subject: [PATCH 084/162] Add newline per pre-commit reformatting --- adafruit_logging.py | 1 + 1 file changed, 1 insertion(+) diff --git a/adafruit_logging.py b/adafruit_logging.py index 651c8a2..5c5d6d6 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -63,6 +63,7 @@ try: from typing import Optional + try: from typing import Protocol except ImportError: From b4443715b4e71b0a0d1e400c1b36c25392819cd2 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Wed, 6 Jul 2022 11:03:44 -0400 Subject: [PATCH 085/162] Attach StreamHandler by default to new Loggers --- adafruit_logging.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 5c5d6d6..35cceeb 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -248,7 +248,9 @@ def emit(self, record: LogRecord) -> None: def _addLogger(logger_name: str) -> None: """Adds the logger if it doesn't already exist""" if logger_name not in logger_cache: - logger_cache[logger_name] = Logger(logger_name) + new_logger = Logger(logger_name) + new_logger.addHandler(StreamHandler()) + logger_cache[logger_name] = new_logger def getLogger(logger_name: str) -> "Logger": From 9378026f162437e2ab90455fa6d2c4a3b0516862 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Wed, 6 Jul 2022 11:08:05 -0400 Subject: [PATCH 086/162] Add newlines to StreamHandler emits --- adafruit_logging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 35cceeb..a3a5858 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -196,7 +196,7 @@ def emit(self, record: LogRecord) -> None: :param record: The record (message object) to be logged """ - self.stream.write(self.format(record)) + self.stream.write(self.format(record) + "\n") class FileHandler(StreamHandler): From 5efaa9ad46d7342848828258c73424bc8bcd27b6 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 11 Jul 2022 16:03:19 -0500 Subject: [PATCH 087/162] fix attribute list in docs --- adafruit_logging.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index a3a5858..666dc24 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -31,25 +31,25 @@ Attributes ---------- - LEVELS : list + LEVELS: list A list of tuples representing the valid logging levels used by this module. Each tuple contains exactly two elements: one int and one str. The int in each tuple represents the relative severity of that level (00 to 50). The str in each tuple is the string representation of that logging level ("NOTSET" to "CRITICAL"; see below). - NOTSET : int + NOTSET: int The NOTSET logging level, which is a dummy logging level that can be used to indicate that a `Logger` should not print any logging messages, regardless of how severe those messages might be (including CRITICAL). - DEBUG : int + DEBUG: int The DEBUG logging level, which is the lowest (least severe) real level. - INFO : int + INFO: int The INFO logging level for informative/informational messages. - WARNING : int + WARNING: int The WARNING logging level for warnings that should be addressed/fixed. - ERROR : int + ERROR: int The ERROR logging level for Python exceptions that occur during runtime. - CRITICAL : int + CRITICAL: int The CRITICAL logging level, which is the highest (most severe) level for unrecoverable errors that have caused the code to halt and exit. From 20caee5c755becf4567c28c6d001aeaf5b575adc Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 11 Jul 2022 17:06:35 -0500 Subject: [PATCH 088/162] fix NOTSET description to match CPython --- adafruit_logging.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 666dc24..af9330e 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -38,9 +38,9 @@ level (00 to 50). The str in each tuple is the string representation of that logging level ("NOTSET" to "CRITICAL"; see below). NOTSET: int - The NOTSET logging level, which is a dummy logging level that can be - used to indicate that a `Logger` should not print any logging messages, - regardless of how severe those messages might be (including CRITICAL). + The NOTSET logging level, which is the default logging level that can be + used to indicate that a `Logger` should process any logging messages, + regardless of how severe those messages are. DEBUG: int The DEBUG logging level, which is the lowest (least severe) real level. INFO: int From 87629734b5ff957f3320ac025324826585574d9c Mon Sep 17 00:00:00 2001 From: evaherrada Date: Tue, 2 Aug 2022 17:00:45 -0400 Subject: [PATCH 089/162] Added Black formatting badge --- README.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.rst b/README.rst index 0734de9..cc26428 100644 --- a/README.rst +++ b/README.rst @@ -13,6 +13,10 @@ Introduction :target: https://github.com/adafruit/Adafruit_CircuitPython_Logger :alt: Build Status +.. image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/psf/black + :alt: Code Style: Black + Logging module for CircuitPython From c94e49970b4c98a35806fdf2e4ea9946eed490ff Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Mon, 8 Aug 2022 22:05:55 -0400 Subject: [PATCH 090/162] Switched to pyproject.toml --- .github/workflows/build.yml | 18 ++++++------ .github/workflows/release.yml | 17 +++++++----- optional_requirements.txt | 3 ++ pyproject.toml | 44 +++++++++++++++++++++++++++++ requirements.txt | 6 +--- setup.py | 52 ----------------------------------- 6 files changed, 68 insertions(+), 72 deletions(-) create mode 100644 optional_requirements.txt create mode 100644 pyproject.toml delete mode 100644 setup.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 474520d..22f6582 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -47,6 +47,8 @@ jobs: pip install --force-reinstall Sphinx sphinx-rtd-theme pre-commit - name: Library version run: git describe --dirty --always --tags + - name: Setup problem matchers + uses: adafruit/circuitpython-action-library-ci-problem-matchers@v1 - name: Pre-commit hooks run: | pre-commit run --all-files @@ -60,16 +62,16 @@ jobs: - name: Build docs working-directory: docs run: sphinx-build -E -W -b html . _build/html - - name: Check For setup.py + - name: Check For pyproject.toml id: need-pypi run: | - echo ::set-output name=setup-py::$( find . -wholename './setup.py' ) + echo ::set-output name=pyproject-toml::$( find . -wholename './pyproject.toml' ) - name: Build Python package - if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') + if: contains(steps.need-pypi.outputs.pyproject-toml, 'pyproject.toml') run: | - pip install --upgrade setuptools wheel twine readme_renderer testresources - python setup.py sdist - python setup.py bdist_wheel --universal + pip install --upgrade build twine + for file in $(find -not -path "./.*" -not -path "./docs*" \( -name "*.py" -o -name "*.toml" \) ); do + sed -i -e "s/0.0.0-auto.0/1.2.3/" $file; + done; + python -m build twine check dist/* - - name: Setup problem matchers - uses: adafruit/circuitpython-action-library-ci-problem-matchers@v1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a65e5de..d1b4f8d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -61,25 +61,28 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - - name: Check For setup.py + - name: Check For pyproject.toml id: need-pypi run: | - echo ::set-output name=setup-py::$( find . -wholename './setup.py' ) + echo ::set-output name=pyproject-toml::$( find . -wholename './pyproject.toml' ) - name: Set up Python - if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') + if: contains(steps.need-pypi.outputs.pyproject-toml, 'pyproject.toml') uses: actions/setup-python@v2 with: python-version: '3.x' - name: Install dependencies - if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') + if: contains(steps.need-pypi.outputs.pyproject-toml, 'pyproject.toml') run: | python -m pip install --upgrade pip - pip install setuptools wheel twine + pip install --upgrade build twine - name: Build and publish - if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') + if: contains(steps.need-pypi.outputs.pyproject-toml, 'pyproject.toml') env: TWINE_USERNAME: ${{ secrets.pypi_username }} TWINE_PASSWORD: ${{ secrets.pypi_password }} run: | - python setup.py sdist + for file in $(find -not -path "./.*" -not -path "./docs*" \( -name "*.py" -o -name "*.toml" \) ); do + sed -i -e "s/0.0.0-auto.0/${{github.event.release.tag_name}}/" $file; + done; + python -m build twine upload dist/* diff --git a/optional_requirements.txt b/optional_requirements.txt new file mode 100644 index 0000000..d4e27c4 --- /dev/null +++ b/optional_requirements.txt @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: 2022 Alec Delaney, for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..a4ab204 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,44 @@ +# SPDX-FileCopyrightText: 2022 Alec Delaney for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +[build-system] +requires = [ + "setuptools", + "wheel", +] + +[project] +name = "adafruit-circuitpython-logging" +description = "Logging module for CircuitPython" +version = "0.0.0-auto.0" +readme = "README.rst" +authors = [ + {name = "Adafruit Industries", email = "circuitpython@adafruit.com"} +] +urls = {Homepage = "https://github.com/adafruit/Adafruit_CircuitPython_Logging"} +keywords = [ + "adafruit", + "blinka", + "circuitpython", + "micropython", + "logging", + "logger", +] +license = {text = "MIT"} +classifiers = [ + "Intended Audience :: Developers", + "Topic :: Software Development :: Libraries", + "Topic :: Software Development :: Embedded Systems", + "Topic :: System :: Hardware", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", +] +dynamic = ["dependencies", "optional-dependencies"] + +[tool.setuptools] +py-modules = ["adafruit_logging"] + +[tool.setuptools.dynamic] +dependencies = {file = ["requirements.txt"]} +optional-dependencies = {optional = {file = ["optional_requirements.txt"]}} diff --git a/requirements.txt b/requirements.txt index 3396d72..452ef26 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,4 @@ -# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries -# -# SPDX-License-Identifier: Unlicense - -# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-FileCopyrightText: 2022 Alec Delaney, for Adafruit Industries # # SPDX-License-Identifier: Unlicense diff --git a/setup.py b/setup.py deleted file mode 100644 index 86b612c..0000000 --- a/setup.py +++ /dev/null @@ -1,52 +0,0 @@ -# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries -# -# SPDX-License-Identifier: MIT - -"""A setuptools based setup module. -See: -https://packaging.python.org/en/latest/distributing.html -https://github.com/pypa/sampleproject -""" - -from setuptools import setup - -# To use a consistent encoding -from codecs import open -from os import path - -here = path.abspath(path.dirname(__file__)) - -# Get the long description from the README file -with open(path.join(here, "README.rst"), encoding="utf-8") as f: - long_description = f.read() - -setup( - name="adafruit-circuitpython-logging", - use_scm_version=True, - setup_requires=["setuptools_scm"], - description="Logging module for CircuitPython", - long_description=long_description, - long_description_content_type="text/x-rst", - # The project's main homepage. - url="https://github.com/adafruit/Adafruit_CircuitPython_Logging", - # Author details - author="Adafruit Industries", - author_email="circuitpython@adafruit.com", - install_requires=["Adafruit-Blinka", "typing-extensions"], - # Choose your license - license="MIT", - # See https://pypi.python.org/pypi?%3Aaction=list_classifiers - classifiers=[ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "Topic :: Software Development :: Libraries", - "Topic :: System :: Hardware", - "License :: OSI Approved :: MIT License", - "Programming Language :: Python :: 3", - ], - # What does your project relate to? - keywords="adafruit blinka circuitpython micropython logging logger", - # You can just specify the packages manually here if your project is - # simple. Or you can use find_packages(). - py_modules=["adafruit_logging"], -) From f087d97a3cb422c3043141901ed0b3f55b2bfb3a Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Tue, 9 Aug 2022 12:03:54 -0400 Subject: [PATCH 091/162] Add setuptools-scm to build system requirements Signed-off-by: Alec Delaney --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index a4ab204..82efcef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,6 +6,7 @@ requires = [ "setuptools", "wheel", + "setuptools-scm", ] [project] From f521bf557c389b61f1680c5b00d59f49b91eeb0f Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Tue, 16 Aug 2022 18:09:15 -0400 Subject: [PATCH 092/162] Update version string --- adafruit_logging.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index af9330e..cabcdff 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -81,7 +81,7 @@ def write(self, buf: str) -> int: except ImportError: pass -__version__ = "0.0.0-auto.0" +__version__ = "0.0.0+auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Logger.git" # pylint:disable=undefined-all-variable diff --git a/pyproject.toml b/pyproject.toml index 82efcef..00c7665 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ requires = [ [project] name = "adafruit-circuitpython-logging" description = "Logging module for CircuitPython" -version = "0.0.0-auto.0" +version = "0.0.0+auto.0" readme = "README.rst" authors = [ {name = "Adafruit Industries", email = "circuitpython@adafruit.com"} From d9d9e6043f7faff122402be797b3651bd5979edb Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Tue, 16 Aug 2022 21:09:15 -0400 Subject: [PATCH 093/162] Fix version strings in workflow files --- .github/workflows/build.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 22f6582..cb2f60e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -71,7 +71,7 @@ jobs: run: | pip install --upgrade build twine for file in $(find -not -path "./.*" -not -path "./docs*" \( -name "*.py" -o -name "*.toml" \) ); do - sed -i -e "s/0.0.0-auto.0/1.2.3/" $file; + sed -i -e "s/0.0.0+auto.0/1.2.3/" $file; done; python -m build twine check dist/* diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d1b4f8d..f3a0325 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -82,7 +82,7 @@ jobs: TWINE_PASSWORD: ${{ secrets.pypi_password }} run: | for file in $(find -not -path "./.*" -not -path "./docs*" \( -name "*.py" -o -name "*.toml" \) ); do - sed -i -e "s/0.0.0-auto.0/${{github.event.release.tag_name}}/" $file; + sed -i -e "s/0.0.0+auto.0/${{github.event.release.tag_name}}/" $file; done; python -m build twine upload dist/* From 36ceb4a411cbe3808bb69260839bebe29abfc22b Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Mon, 22 Aug 2022 21:36:33 -0400 Subject: [PATCH 094/162] Keep copyright up to date in documentation --- docs/conf.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 6419b1b..8ccc828 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -6,6 +6,7 @@ import os import sys +import datetime sys.path.insert(0, os.path.abspath("..")) @@ -43,7 +44,8 @@ # General information about the project. project = "Adafruit Logger Library" -copyright = "2019 Dave Astels" +current_year = str(datetime.datetime.now().year) +copyright = current_year + " Dave Astels" author = "Dave Astels" # The version info for the project you're documenting, acts as replacement for From 06aed543810357961532bf5f594a4c4215c8ea94 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Tue, 23 Aug 2022 17:26:22 -0400 Subject: [PATCH 095/162] Use year duration range for copyright attribution --- docs/conf.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 8ccc828..2029037 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -44,8 +44,14 @@ # General information about the project. project = "Adafruit Logger Library" +creation_year = "2019" current_year = str(datetime.datetime.now().year) -copyright = current_year + " Dave Astels" +year_duration = ( + current_year + if current_year == creation_year + else creation_year + " - " + current_year +) +copyright = year_duration + " Dave Astels" author = "Dave Astels" # The version info for the project you're documenting, acts as replacement for From f304cf4ee9a1a6891e98ad6ad579e2afd9ab6c24 Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Tue, 27 Sep 2022 19:03:15 -0400 Subject: [PATCH 096/162] Simplify import of Protocol --- adafruit_logging.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index cabcdff..aa40bfa 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -62,12 +62,8 @@ from collections import namedtuple try: - from typing import Optional - - try: - from typing import Protocol - except ImportError: - from typing_extensions import Protocol + from typing import Optional, Hashable + from typing_extensions import Protocol class WriteableStream(Protocol): """Any stream that can ``write`` strings""" From 5a7999c9737f86e342c48d752770b79a8377740d Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Tue, 27 Sep 2022 19:03:57 -0400 Subject: [PATCH 097/162] Allow usage with a root logger --- adafruit_logging.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index aa40bfa..c9f8661 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -113,6 +113,8 @@ def write(self, buf: str) -> int: for __value, __name in LEVELS: globals()[__name] = __value +_ROOT_LOGGER_SENTINEL = object() + def _level_for(value: int) -> str: """Convert a numeric level to the most appropriate name. @@ -241,7 +243,7 @@ def emit(self, record: LogRecord) -> None: logger_cache = {} -def _addLogger(logger_name: str) -> None: +def _addLogger(logger_name: Hashable) -> None: """Adds the logger if it doesn't already exist""" if logger_name not in logger_cache: new_logger = Logger(logger_name) @@ -249,12 +251,14 @@ def _addLogger(logger_name: str) -> None: logger_cache[logger_name] = new_logger -def getLogger(logger_name: str) -> "Logger": +def getLogger(logger_name: Hashable = _ROOT_LOGGER_SENTINEL) -> "Logger": """Create or retrieve a logger by name; only retrieves loggers made using this function; if a Logger with this name does not exist it is created - :param str logger_name: The name of the `Logger` to create/retrieve. + :param Hashable logger_name: The name of the `Logger` to create/retrieve, this + is typically a ``str``. If none is provided, the single root logger will + be created/retrieved. """ _addLogger(logger_name) return logger_cache[logger_name] @@ -263,12 +267,12 @@ def getLogger(logger_name: str) -> "Logger": class Logger: """The actual logger that will provide the logging API. - :param str name: The name of the logger, typically assigned by the - value from `getLogger` + :param Hashable name: The name of the logger, typically assigned by the + value from `getLogger`; this is typically a ``str`` :param int level: (optional) The log level, default is ``NOTSET`` """ - def __init__(self, name: str, level: int = NOTSET) -> None: + def __init__(self, name: Hashable, level: int = NOTSET) -> None: """Create an instance.""" self._level = level self.name = name From 9cd43d14f2a7228208261e384b5d39ca5a23b8fe Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Tue, 4 Oct 2022 19:59:51 -0400 Subject: [PATCH 098/162] Change sentinel object to string --- adafruit_logging.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index c9f8661..db4b699 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -113,8 +113,6 @@ def write(self, buf: str) -> int: for __value, __name in LEVELS: globals()[__name] = __value -_ROOT_LOGGER_SENTINEL = object() - def _level_for(value: int) -> str: """Convert a numeric level to the most appropriate name. @@ -251,14 +249,15 @@ def _addLogger(logger_name: Hashable) -> None: logger_cache[logger_name] = new_logger -def getLogger(logger_name: Hashable = _ROOT_LOGGER_SENTINEL) -> "Logger": +def getLogger(logger_name: Hashable = "") -> "Logger": """Create or retrieve a logger by name; only retrieves loggers made using this function; if a Logger with this name does not exist it is created :param Hashable logger_name: The name of the `Logger` to create/retrieve, this is typically a ``str``. If none is provided, the single root logger will - be created/retrieved. + be created/retrieved. Note that unlike CPython, a blank string will also + access the root logger. """ _addLogger(logger_name) return logger_cache[logger_name] From 68ab4bdfd8690217201b3aa6b60174d77d34f7e2 Mon Sep 17 00:00:00 2001 From: Isaac Benitez Date: Thu, 20 Oct 2022 22:01:17 -0700 Subject: [PATCH 099/162] Make default logging level be consistent with CPython --- adafruit_logging.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index db4b699..1de37fb 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -38,15 +38,15 @@ level (00 to 50). The str in each tuple is the string representation of that logging level ("NOTSET" to "CRITICAL"; see below). NOTSET: int - The NOTSET logging level, which is the default logging level that can be - used to indicate that a `Logger` should process any logging messages, - regardless of how severe those messages are. + The NOTSET logging level can be used to indicate that a `Logger` should + process any logging messages, regardless of how severe those messages are. DEBUG: int The DEBUG logging level, which is the lowest (least severe) real level. INFO: int The INFO logging level for informative/informational messages. WARNING: int - The WARNING logging level for warnings that should be addressed/fixed. + The WARNING logging level, which is the default logging level, for warnings + that should be addressed/fixed. ERROR: int The ERROR logging level for Python exceptions that occur during runtime. CRITICAL: int @@ -268,10 +268,10 @@ class Logger: :param Hashable name: The name of the logger, typically assigned by the value from `getLogger`; this is typically a ``str`` - :param int level: (optional) The log level, default is ``NOTSET`` + :param int level: (optional) The log level, default is ``WARNING`` """ - def __init__(self, name: Hashable, level: int = NOTSET) -> None: + def __init__(self, name: Hashable, level: int = WARNING) -> None: """Create an instance.""" self._level = level self.name = name From 6f1b3d503dc8584125b91a011ce40aa726ae92f7 Mon Sep 17 00:00:00 2001 From: Isaac Benitez Date: Thu, 20 Oct 2022 22:13:29 -0700 Subject: [PATCH 100/162] Fix docstring indentation --- adafruit_logging.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 1de37fb..abd65c5 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -45,8 +45,8 @@ INFO: int The INFO logging level for informative/informational messages. WARNING: int - The WARNING logging level, which is the default logging level, for warnings - that should be addressed/fixed. + The WARNING logging level, which is the default logging level, for warnings + that should be addressed/fixed. ERROR: int The ERROR logging level for Python exceptions that occur during runtime. CRITICAL: int From 258a80125cef1263f1e07654ae257b5c09396f6c Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Fri, 4 Nov 2022 00:02:50 -0400 Subject: [PATCH 101/162] Switching to composite actions --- .github/workflows/build.yml | 67 +---------------------- .github/workflows/release.yml | 88 ------------------------------ .github/workflows/release_gh.yml | 14 +++++ .github/workflows/release_pypi.yml | 14 +++++ 4 files changed, 30 insertions(+), 153 deletions(-) delete mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/release_gh.yml create mode 100644 .github/workflows/release_pypi.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cb2f60e..041a337 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,68 +10,5 @@ jobs: test: runs-on: ubuntu-latest steps: - - name: Dump GitHub context - env: - GITHUB_CONTEXT: ${{ toJson(github) }} - run: echo "$GITHUB_CONTEXT" - - name: Translate Repo Name For Build Tools filename_prefix - id: repo-name - run: | - echo ::set-output name=repo-name::$( - echo ${{ github.repository }} | - awk -F '\/' '{ print tolower($2) }' | - tr '_' '-' - ) - - name: Set up Python 3.x - uses: actions/setup-python@v2 - with: - python-version: "3.x" - - name: Versions - run: | - python3 --version - - name: Checkout Current Repo - uses: actions/checkout@v1 - with: - submodules: true - - name: Checkout tools repo - uses: actions/checkout@v2 - with: - repository: adafruit/actions-ci-circuitpython-libs - path: actions-ci - - name: Install dependencies - # (e.g. - apt-get: gettext, etc; pip: circuitpython-build-tools, requirements.txt; etc.) - run: | - source actions-ci/install.sh - - name: Pip install Sphinx, pre-commit - run: | - pip install --force-reinstall Sphinx sphinx-rtd-theme pre-commit - - name: Library version - run: git describe --dirty --always --tags - - name: Setup problem matchers - uses: adafruit/circuitpython-action-library-ci-problem-matchers@v1 - - name: Pre-commit hooks - run: | - pre-commit run --all-files - - name: Build assets - run: circuitpython-build-bundles --filename_prefix ${{ steps.repo-name.outputs.repo-name }} --library_location . - - name: Archive bundles - uses: actions/upload-artifact@v2 - with: - name: bundles - path: ${{ github.workspace }}/bundles/ - - name: Build docs - working-directory: docs - run: sphinx-build -E -W -b html . _build/html - - name: Check For pyproject.toml - id: need-pypi - run: | - echo ::set-output name=pyproject-toml::$( find . -wholename './pyproject.toml' ) - - name: Build Python package - if: contains(steps.need-pypi.outputs.pyproject-toml, 'pyproject.toml') - run: | - pip install --upgrade build twine - for file in $(find -not -path "./.*" -not -path "./docs*" \( -name "*.py" -o -name "*.toml" \) ); do - sed -i -e "s/0.0.0+auto.0/1.2.3/" $file; - done; - python -m build - twine check dist/* + - name: Run Build CI workflow + uses: adafruit/workflows-circuitpython-libs/build@main diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index f3a0325..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,88 +0,0 @@ -# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries -# -# SPDX-License-Identifier: MIT - -name: Release Actions - -on: - release: - types: [published] - -jobs: - upload-release-assets: - runs-on: ubuntu-latest - steps: - - name: Dump GitHub context - env: - GITHUB_CONTEXT: ${{ toJson(github) }} - run: echo "$GITHUB_CONTEXT" - - name: Translate Repo Name For Build Tools filename_prefix - id: repo-name - run: | - echo ::set-output name=repo-name::$( - echo ${{ github.repository }} | - awk -F '\/' '{ print tolower($2) }' | - tr '_' '-' - ) - - name: Set up Python 3.x - uses: actions/setup-python@v2 - with: - python-version: "3.x" - - name: Versions - run: | - python3 --version - - name: Checkout Current Repo - uses: actions/checkout@v1 - with: - submodules: true - - name: Checkout tools repo - uses: actions/checkout@v2 - with: - repository: adafruit/actions-ci-circuitpython-libs - path: actions-ci - - name: Install deps - run: | - source actions-ci/install.sh - - name: Build assets - run: circuitpython-build-bundles --filename_prefix ${{ steps.repo-name.outputs.repo-name }} --library_location . - - name: Upload Release Assets - # the 'official' actions version does not yet support dynamically - # supplying asset names to upload. @csexton's version chosen based on - # discussion in the issue below, as its the simplest to implement and - # allows for selecting files with a pattern. - # https://github.com/actions/upload-release-asset/issues/4 - #uses: actions/upload-release-asset@v1.0.1 - uses: csexton/release-asset-action@master - with: - pattern: "bundles/*" - github-token: ${{ secrets.GITHUB_TOKEN }} - - upload-pypi: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - name: Check For pyproject.toml - id: need-pypi - run: | - echo ::set-output name=pyproject-toml::$( find . -wholename './pyproject.toml' ) - - name: Set up Python - if: contains(steps.need-pypi.outputs.pyproject-toml, 'pyproject.toml') - uses: actions/setup-python@v2 - with: - python-version: '3.x' - - name: Install dependencies - if: contains(steps.need-pypi.outputs.pyproject-toml, 'pyproject.toml') - run: | - python -m pip install --upgrade pip - pip install --upgrade build twine - - name: Build and publish - if: contains(steps.need-pypi.outputs.pyproject-toml, 'pyproject.toml') - env: - TWINE_USERNAME: ${{ secrets.pypi_username }} - TWINE_PASSWORD: ${{ secrets.pypi_password }} - run: | - for file in $(find -not -path "./.*" -not -path "./docs*" \( -name "*.py" -o -name "*.toml" \) ); do - sed -i -e "s/0.0.0+auto.0/${{github.event.release.tag_name}}/" $file; - done; - python -m build - twine upload dist/* diff --git a/.github/workflows/release_gh.yml b/.github/workflows/release_gh.yml new file mode 100644 index 0000000..041a337 --- /dev/null +++ b/.github/workflows/release_gh.yml @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +name: Build CI + +on: [pull_request, push] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Run Build CI workflow + uses: adafruit/workflows-circuitpython-libs/build@main diff --git a/.github/workflows/release_pypi.yml b/.github/workflows/release_pypi.yml new file mode 100644 index 0000000..041a337 --- /dev/null +++ b/.github/workflows/release_pypi.yml @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +name: Build CI + +on: [pull_request, push] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Run Build CI workflow + uses: adafruit/workflows-circuitpython-libs/build@main From 1029196b26eee9e9dcc4b3861cdc9f1b7d40393d Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Fri, 4 Nov 2022 00:47:00 -0400 Subject: [PATCH 102/162] Updated pylint version to 2.13.0 --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3343606..4c43710 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/pycqa/pylint - rev: v2.11.1 + rev: v2.13.0 hooks: - id: pylint name: pylint (library code) From ae9509faba358c40696481d13e6f6517a367b2ae Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Fri, 4 Nov 2022 08:15:21 -0400 Subject: [PATCH 103/162] Update pylint to 2.15.5 --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4c43710..0e5fccc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/pycqa/pylint - rev: v2.13.0 + rev: v2.15.5 hooks: - id: pylint name: pylint (library code) From e7fa1434f76db7dd8b6f14dcad3316ba875e1588 Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Fri, 4 Nov 2022 09:12:45 -0400 Subject: [PATCH 104/162] Fix release CI files --- .github/workflows/release_gh.yml | 14 +++++++++----- .github/workflows/release_pypi.yml | 15 ++++++++++----- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/.github/workflows/release_gh.yml b/.github/workflows/release_gh.yml index 041a337..b8aa8d6 100644 --- a/.github/workflows/release_gh.yml +++ b/.github/workflows/release_gh.yml @@ -2,13 +2,17 @@ # # SPDX-License-Identifier: MIT -name: Build CI +name: GitHub Release Actions -on: [pull_request, push] +on: + release: + types: [published] jobs: - test: + upload-release-assets: runs-on: ubuntu-latest steps: - - name: Run Build CI workflow - uses: adafruit/workflows-circuitpython-libs/build@main + - name: Run GitHub Release CI workflow + uses: adafruit/workflows-circuitpython-libs/release-gh@main + with: + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release_pypi.yml b/.github/workflows/release_pypi.yml index 041a337..65775b7 100644 --- a/.github/workflows/release_pypi.yml +++ b/.github/workflows/release_pypi.yml @@ -2,13 +2,18 @@ # # SPDX-License-Identifier: MIT -name: Build CI +name: PyPI Release Actions -on: [pull_request, push] +on: + release: + types: [published] jobs: - test: + upload-release-assets: runs-on: ubuntu-latest steps: - - name: Run Build CI workflow - uses: adafruit/workflows-circuitpython-libs/build@main + - name: Run PyPI Release CI workflow + uses: adafruit/workflows-circuitpython-libs/release-pypi@main + with: + pypi-username: ${{ secrets.pypi_username }} + pypi-password: ${{ secrets.pypi_password }} From 04048b5a9aad22f22348b89530f73b3f38bd2d78 Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Fri, 4 Nov 2022 18:34:33 -0400 Subject: [PATCH 105/162] Update .pylintrc for v2.15.5 --- .pylintrc | 49 ++++--------------------------------------------- 1 file changed, 4 insertions(+), 45 deletions(-) diff --git a/.pylintrc b/.pylintrc index 3636ad5..40208c3 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,8 +1,4 @@ -# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries -# -# SPDX-License-Identifier: Unlicense - -# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries # # SPDX-License-Identifier: Unlicense @@ -30,7 +26,7 @@ jobs=1 # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. -load-plugins= +load-plugins=pylint.extensions.no_self_use # Pickle collected data for later comparisons. persistent=yes @@ -58,8 +54,8 @@ confidence= # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" -# disable=import-error,print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call -disable=print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call,import-error,bad-continuation,unspecified-encoding +# disable=import-error,raw-checker-failed,bad-inline-option,locally-disabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,deprecated-str-translate-call +disable=raw-checker-failed,bad-inline-option,locally-disabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,import-error,pointless-string-statement,unspecified-encoding # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option @@ -229,12 +225,6 @@ max-line-length=100 # Maximum number of lines in a module max-module-lines=1000 -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma,dict-separator - # Allow the body of a class to be on the same line as the declaration if body # contains single statement. single-line-class-stmt=no @@ -261,38 +251,22 @@ min-similarity-lines=12 [BASIC] -# Naming hint for argument names -argument-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - # Regular expression matching correct argument names argument-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ -# Naming hint for attribute names -attr-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - # Regular expression matching correct attribute names attr-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ # Bad variable names which should always be refused, separated by a comma bad-names=foo,bar,baz,toto,tutu,tata -# Naming hint for class attribute names -class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - # Regular expression matching correct class attribute names class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ -# Naming hint for class names -# class-name-hint=[A-Z_][a-zA-Z0-9]+$ -class-name-hint=[A-Z_][a-zA-Z0-9_]+$ - # Regular expression matching correct class names # class-rgx=[A-Z_][a-zA-Z0-9]+$ class-rgx=[A-Z_][a-zA-Z0-9_]+$ -# Naming hint for constant names -const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - # Regular expression matching correct constant names const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ @@ -300,9 +274,6 @@ const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ # ones are exempt. docstring-min-length=-1 -# Naming hint for function names -function-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - # Regular expression matching correct function names function-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ @@ -313,21 +284,12 @@ good-names=r,g,b,w,i,j,k,n,x,y,z,ex,ok,Run,_ # Include a hint for the correct naming format with invalid-name include-naming-hint=no -# Naming hint for inline iteration names -inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ - # Regular expression matching correct inline iteration names inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ -# Naming hint for method names -method-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - # Regular expression matching correct method names method-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ -# Naming hint for module names -module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - # Regular expression matching correct module names module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ @@ -343,9 +305,6 @@ no-docstring-rgx=^_ # to this list to register other decorators that produce valid properties. property-classes=abc.abstractproperty -# Naming hint for variable names -variable-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - # Regular expression matching correct variable names variable-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ From 2d43758e7feaf20d2f60a5d96b74c27512411173 Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Mon, 7 Nov 2022 20:19:34 -0500 Subject: [PATCH 106/162] Fix pylint errors --- adafruit_logging.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index cabcdff..a25f083 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -145,9 +145,10 @@ def _level_for(value: int) -> str: - ``args`` - The additional positional arguments provided """ -_logRecordFactory = lambda name, level, msg, args: LogRecord( - name, level, _level_for(level), msg, time.monotonic(), args -) +def _logRecordFactory(name, level, msg, args): + return LogRecord( + name, level, _level_for(level), msg, time.monotonic(), args + ) class Handler: From 734999f5198da43393ae977d5425645645b1dd52 Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Mon, 7 Nov 2022 20:31:32 -0500 Subject: [PATCH 107/162] Reformatted per pre-commit --- adafruit_logging.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index b910cc8..203d7c3 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -141,10 +141,9 @@ def _level_for(value: int) -> str: - ``args`` - The additional positional arguments provided """ + def _logRecordFactory(name, level, msg, args): - return LogRecord( - name, level, _level_for(level), msg, time.monotonic(), args - ) + return LogRecord(name, level, _level_for(level), msg, time.monotonic(), args) class Handler: From d8392367e5222dabd22353ad7ada67a6b3b0ffba Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Thu, 17 Nov 2022 13:10:13 +0100 Subject: [PATCH 108/162] cleanup pylint warnings --- adafruit_logging.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 203d7c3..21cae6b 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -156,9 +156,7 @@ def format(self, record: LogRecord) -> str: :param record: The record (message object) to be logged """ - return "{0:<0.3f}: {1} - {2}".format( - record.created, record.levelname, record.msg - ) + return f"{record.created:<0.3f}: {record.levelname} - {record.msg}" def emit(self, record: LogRecord) -> None: """Send a message where it should go. From 7bba4fc429ac323bb31d96d31a3e7f4e9757127a Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Sat, 19 Nov 2022 19:45:15 +0100 Subject: [PATCH 109/162] allow multiple handlers fixes #43 --- adafruit_logging.py | 41 +++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 21cae6b..fb23f49 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -25,9 +25,8 @@ .. note:: This module has a few key differences compared to its CPython counterpart, notably - that loggers can only be assigned one handler at a time. Calling ``addHander()`` - replaces the currently stored handler for that logger. Additionally, the default - formatting for handlers is different. + that loggers do not form a hierarchy that allows record propagation. + Additionally, the default formatting for handlers is different. Attributes ---------- @@ -275,7 +274,8 @@ def __init__(self, name: Hashable, level: int = WARNING) -> None: self.name = name """The name of the logger, this should be unique for proper functionality of `getLogger()`""" - self._handler = None + self._handlers = [] + self.emittedNoHandlerWarning = False def setLevel(self, log_level: int) -> None: """Set the logging cutoff level. @@ -294,23 +294,40 @@ def getEffectiveLevel(self) -> int: return self._level def addHandler(self, hdlr: Handler) -> None: - """Sets the handler of this logger to the specified handler. - - *NOTE* This is slightly different from the CPython equivalent - which adds the handler rather than replacing it. + """Adds the handler to this logger. :param Handler hdlr: The handler to add """ - self._handler = hdlr + self._handlers.append(hdlr) + + def removeHandler(self, hdlr: Handler) -> None: + """Remove handler from this logger. + + :param Handler hdlr: The handler to remove + """ + self._handlers.remove(hdlr) def hasHandlers(self) -> bool: """Whether any handlers have been set for this logger""" - return self._handler is not None + return len(self._handlers) > 0 def _log(self, level: int, msg: str, *args) -> None: record = _logRecordFactory(self.name, level, msg % args, args) - if self._handler and level >= self._level: - self._handler.emit(record) + self.handle(record) + + def handle(self, record: LogRecord) -> None: + """Pass the record to all handlers registered with this logger. + + :param LogRecord record: log record + """ + if not self.hasHandlers() and not self.emittedNoHandlerWarning: + sys.stderr.write(f"Logger '{self.name}' has no handlers\n") + self.emittedNoHandlerWarning = True + return + + if record.levelno >= self._level: + for handler in self._handlers: + handler.emit(record) def log(self, level: int, msg: str, *args) -> None: """Log a message. From ab8b0e2f1373eeb3ba98c23887e232af904996cf Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Sat, 19 Nov 2022 20:12:04 +0100 Subject: [PATCH 110/162] allow handlers to have log level --- adafruit_logging.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index fb23f49..6e660ed 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -146,7 +146,17 @@ def _logRecordFactory(name, level, msg, args): class Handler: - """Abstract logging message handler.""" + """Base logging message handler.""" + + def __init__(self, level: int = NOTSET) -> None: + """Create Handler instance""" + self.level = level + + def setLevel(self, level: int) -> None: + """ + Set the logging level of this handler. + """ + self.level = level # pylint: disable=no-self-use def format(self, record: LogRecord) -> str: @@ -327,7 +337,8 @@ def handle(self, record: LogRecord) -> None: if record.levelno >= self._level: for handler in self._handlers: - handler.emit(record) + if record.levelno >= handler.level: + handler.emit(record) def log(self, level: int, msg: str, *args) -> None: """Log a message. From 6d865f120082ae731ab092d2fd70d4510d9c1ea3 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Sat, 19 Nov 2022 21:15:48 +0100 Subject: [PATCH 111/162] handle exceptions in handlers by default --- adafruit_logging.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 6e660ed..e2df557 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -140,6 +140,8 @@ def _level_for(value: int) -> str: - ``args`` - The additional positional arguments provided """ +raiseExceptions = False + def _logRecordFactory(name, level, msg, args): return LogRecord(name, level, _level_for(level), msg, time.monotonic(), args) @@ -338,7 +340,14 @@ def handle(self, record: LogRecord) -> None: if record.levelno >= self._level: for handler in self._handlers: if record.levelno >= handler.level: - handler.emit(record) + try: + handler.emit(record) + except Exception as e: # pylint: disable=broad-except + if raiseExceptions: + raise e + if sys.stderr: + sys.stderr.write(f"Handler {handler} produced exception: " + f"{repr(e)}\n") def log(self, level: int, msg: str, *args) -> None: """Log a message. From dcf122e7a089aca0c103f8cc45d083329e46d93f Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Sat, 19 Nov 2022 21:28:56 +0100 Subject: [PATCH 112/162] rework exception handling semantics The choice is now whether to print a warning. Previously this was about re-raising the exception. This is done for stability's sake. --- adafruit_logging.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index e2df557..fea6312 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -140,7 +140,10 @@ def _level_for(value: int) -> str: - ``args`` - The additional positional arguments provided """ -raiseExceptions = False +printHandlerExceptions = True +""" +Whether to print exceptions caught during handler's emit(). +""" def _logRecordFactory(name, level, msg, args): @@ -343,11 +346,9 @@ def handle(self, record: LogRecord) -> None: try: handler.emit(record) except Exception as e: # pylint: disable=broad-except - if raiseExceptions: - raise e - if sys.stderr: - sys.stderr.write(f"Handler {handler} produced exception: " - f"{repr(e)}\n") + if sys.stderr and printHandlerExceptions: + sys.stderr.write(f"Handler {repr(handler)} produced exception: " + f"{str(e)}\n") def log(self, level: int, msg: str, *args) -> None: """Log a message. From 134605a7bcc7b9f16c8bc1fadb22768a11acf1a7 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Sat, 19 Nov 2022 21:40:53 +0100 Subject: [PATCH 113/162] apply black --- adafruit_logging.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index fea6312..7c84475 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -140,10 +140,8 @@ def _level_for(value: int) -> str: - ``args`` - The additional positional arguments provided """ +# Whether to print exceptions caught during handler's emit(). printHandlerExceptions = True -""" -Whether to print exceptions caught during handler's emit(). -""" def _logRecordFactory(name, level, msg, args): @@ -347,8 +345,9 @@ def handle(self, record: LogRecord) -> None: handler.emit(record) except Exception as e: # pylint: disable=broad-except if sys.stderr and printHandlerExceptions: - sys.stderr.write(f"Handler {repr(handler)} produced exception: " - f"{str(e)}\n") + sys.stderr.write( + f"Handler {handler} produced exception: {e}\n" + ) def log(self, level: int, msg: str, *args) -> None: """Log a message. From eb70acf5d245186abd21ffb7571643fcd8ae5637 Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Thu, 1 Sep 2022 20:16:31 -0400 Subject: [PATCH 114/162] Add .venv to .gitignore Signed-off-by: Alec Delaney <89490472+tekktrik@users.noreply.github.com> --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 544ec4a..db3d538 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ _build # Virtual environment-specific files .env +.venv # MacOS-specific files *.DS_Store From 01359e76a65ddb36ae147c0458ecde4183c8b66b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Kotal?= Date: Mon, 5 Dec 2022 12:25:52 +0100 Subject: [PATCH 115/162] allow the handler exceptions to propagate --- adafruit_logging.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 7c84475..6e660ed 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -140,9 +140,6 @@ def _level_for(value: int) -> str: - ``args`` - The additional positional arguments provided """ -# Whether to print exceptions caught during handler's emit(). -printHandlerExceptions = True - def _logRecordFactory(name, level, msg, args): return LogRecord(name, level, _level_for(level), msg, time.monotonic(), args) @@ -341,13 +338,7 @@ def handle(self, record: LogRecord) -> None: if record.levelno >= self._level: for handler in self._handlers: if record.levelno >= handler.level: - try: - handler.emit(record) - except Exception as e: # pylint: disable=broad-except - if sys.stderr and printHandlerExceptions: - sys.stderr.write( - f"Handler {handler} produced exception: {e}\n" - ) + handler.emit(record) def log(self, level: int, msg: str, *args) -> None: """Log a message. From 54ad09120e4e01e35e63cb15443b2d7ef1a7e4eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Kotal?= Date: Mon, 12 Dec 2022 21:06:52 +0100 Subject: [PATCH 116/162] match CPython behavior w.r.t. default (last resort) logger --- adafruit_logging.py | 21 ++++++++++++++++++--- examples/logging_simpletest.py | 25 +++++++++++++++++-------- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 6e660ed..9d97e0f 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -246,13 +246,13 @@ def emit(self, record: LogRecord) -> None: logger_cache = {} +_default_handler = StreamHandler() def _addLogger(logger_name: Hashable) -> None: """Adds the logger if it doesn't already exist""" if logger_name not in logger_cache: new_logger = Logger(logger_name) - new_logger.addHandler(StreamHandler()) logger_cache[logger_name] = new_logger @@ -330,15 +330,30 @@ def handle(self, record: LogRecord) -> None: :param LogRecord record: log record """ - if not self.hasHandlers() and not self.emittedNoHandlerWarning: - sys.stderr.write(f"Logger '{self.name}' has no handlers\n") + if ( + _default_handler is None + and not self.hasHandlers() + and not self.emittedNoHandlerWarning + ): + sys.stderr.write( + f"Logger '{self.name}' has no handlers and default handler is None\n" + ) self.emittedNoHandlerWarning = True return + emitted = False if record.levelno >= self._level: for handler in self._handlers: if record.levelno >= handler.level: handler.emit(record) + emitted = True + + if ( + not emitted + and _default_handler + and record.levelno >= _default_handler.level + ): + _default_handler.emit(record) def log(self, level: int, msg: str, *args) -> None: """Log a message. diff --git a/examples/logging_simpletest.py b/examples/logging_simpletest.py index 3eb258b..c8099a1 100644 --- a/examples/logging_simpletest.py +++ b/examples/logging_simpletest.py @@ -8,22 +8,31 @@ import adafruit_logging as logging -# This should produce an error output +# This should produce an info output via default handler. + +logger_default_handler = logging.getLogger("default_handler") +logger_default_handler.setLevel(logging.INFO) +logger_default_handler.info("Default handler: Info message") +assert not logger_default_handler.hasHandlers() + +# This should produce an error output via Stream Handler. logger = logging.getLogger("test") -print_logger = logging.StreamHandler() -logger.addHandler(print_logger) +print_handler = logging.StreamHandler() +logger.addHandler(print_handler) +assert logger.hasHandlers() logger.setLevel(logging.ERROR) -logger.info("Info message") -logger.error("Error message") +logger.info("Stream Handler: Info message") +logger.error("Stream Handler: Error message") -# This should produce no output +# This should produce no output at all. null_logger = logging.getLogger("null") null_handler = logging.NullHandler() null_logger.addHandler(null_handler) +assert null_logger.hasHandlers() null_logger.setLevel(logging.ERROR) -null_logger.info("Info message") -null_logger.error("Error message") +null_logger.info("Null Handler: Info message") +null_logger.error("Null Handler: Error message") From a6baf60a30c39cbf0e79fac4b15a35ac0f15ce59 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Wed, 14 Dec 2022 00:14:30 +0100 Subject: [PATCH 117/162] add MQTT handler example --- examples/logging_mqtt_handler.py | 43 ++++++++++++++++++++++++++++++++ examples/mqtt_handler.py | 38 ++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100755 examples/logging_mqtt_handler.py create mode 100644 examples/mqtt_handler.py diff --git a/examples/logging_mqtt_handler.py b/examples/logging_mqtt_handler.py new file mode 100755 index 0000000..f75d64f --- /dev/null +++ b/examples/logging_mqtt_handler.py @@ -0,0 +1,43 @@ +""" +Demonstrate how to use a single logger to emit log records to +both console and MQTT broker, in this case Adafruit IO. +""" + +import json +import socket +import ssl + +import adafruit_logging as logging +import adafruit_minimqtt.adafruit_minimqtt as MQTT + +from mqtt_handler import MQTTHandler + +logger = logging.getLogger(__name__) + +broker = "io.adafruit.com" +port = 8883 +username = "Adafruit_IO_username" +password = "Adafruit_IO_key" +feedname = "Adafruit_feed_name" +mqtt_topic = f"{username}/feeds/{feedname}" +mqtt_client = MQTT.MQTT( + broker=broker, + port=port, + username=username, + password=password, + socket_pool=socket, + ssl_context=ssl.create_default_context(), +) +mqtt_client.connect() +mqtt_handler = MQTTHandler(mqtt_client, mqtt_topic) +print("adding MQTT handler") +logger.addHandler(mqtt_handler) + +stream_handler = logging.StreamHandler() +print("adding Stream handler") +logger.addHandler(stream_handler) + +data = "foo bar" +print("logging begins !") +# This should emit both to the console as well as to the MQTT broker. +logger.warning(json.dumps(data)) diff --git a/examples/mqtt_handler.py b/examples/mqtt_handler.py new file mode 100644 index 0000000..ccb6ff9 --- /dev/null +++ b/examples/mqtt_handler.py @@ -0,0 +1,38 @@ +""" +MQTT logging handler - log records will be published as MQTT messages +""" + +import adafruit_minimqtt.adafruit_minimqtt as MQTT +# adafruit_logging defines log levels dynamically. +# pylint: disable=no-name-in-module +from adafruit_logging import NOTSET, Handler, LogRecord + + +class MQTTHandler(Handler): + """ + Log handler that emits log records as MQTT PUBLISH messages. + """ + def __init__(self, mqtt_client: MQTT.MQTT, topic: str) -> None: + """ + Assumes that the MQTT client object is already connected. + """ + super().__init__() + + self._mqtt_client = mqtt_client + self._topic = topic + + # To make it work also in CPython. + self.level = NOTSET + + def emit(self, record: LogRecord) -> None: + """ + Publish message from the LogRecord to the MQTT broker. + """ + self._mqtt_client.publish(self._topic, record.msg) + + # To make this work also in CPython's logging. + def handle(self, record: LogRecord) -> None: + """ + Handle the log record. Here, it means just emit. + """ + self.emit(record) From e35bf212b8c25ab5f6d89ce709e6e3f69f9ea650 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Wed, 14 Dec 2022 00:26:09 +0100 Subject: [PATCH 118/162] reorder imports per pylint --- examples/logging_mqtt_handler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/logging_mqtt_handler.py b/examples/logging_mqtt_handler.py index f75d64f..9a22999 100755 --- a/examples/logging_mqtt_handler.py +++ b/examples/logging_mqtt_handler.py @@ -7,11 +7,11 @@ import socket import ssl -import adafruit_logging as logging import adafruit_minimqtt.adafruit_minimqtt as MQTT - from mqtt_handler import MQTTHandler +import adafruit_logging as logging + logger = logging.getLogger(__name__) broker = "io.adafruit.com" From 99587f0ce11f3b581962e10f5ec8b9b9ec95f3c3 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Wed, 14 Dec 2022 00:28:58 +0100 Subject: [PATCH 119/162] use black --- examples/mqtt_handler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/mqtt_handler.py b/examples/mqtt_handler.py index ccb6ff9..a7b3bed 100644 --- a/examples/mqtt_handler.py +++ b/examples/mqtt_handler.py @@ -3,6 +3,7 @@ """ import adafruit_minimqtt.adafruit_minimqtt as MQTT + # adafruit_logging defines log levels dynamically. # pylint: disable=no-name-in-module from adafruit_logging import NOTSET, Handler, LogRecord @@ -12,6 +13,7 @@ class MQTTHandler(Handler): """ Log handler that emits log records as MQTT PUBLISH messages. """ + def __init__(self, mqtt_client: MQTT.MQTT, topic: str) -> None: """ Assumes that the MQTT client object is already connected. From 31cb6107f319e8e258e984293dc05ae109f1ff89 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Wed, 14 Dec 2022 00:31:18 +0100 Subject: [PATCH 120/162] add copyright/lincense headers --- examples/logging_mqtt_handler.py | 2 ++ examples/mqtt_handler.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/examples/logging_mqtt_handler.py b/examples/logging_mqtt_handler.py index 9a22999..79344eb 100755 --- a/examples/logging_mqtt_handler.py +++ b/examples/logging_mqtt_handler.py @@ -1,3 +1,5 @@ +# SPDX-FileCopyrightText: 2022 vladak +# SPDX-License-Identifier: Unlicense """ Demonstrate how to use a single logger to emit log records to both console and MQTT broker, in this case Adafruit IO. diff --git a/examples/mqtt_handler.py b/examples/mqtt_handler.py index a7b3bed..febd479 100644 --- a/examples/mqtt_handler.py +++ b/examples/mqtt_handler.py @@ -1,3 +1,5 @@ +# SPDX-FileCopyrightText: 2022 vladak +# SPDX-License-Identifier: Unlicense """ MQTT logging handler - log records will be published as MQTT messages """ From 722fef9f016bbf4505ec5191710e04765b230c65 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Wed, 14 Dec 2022 23:01:00 +0100 Subject: [PATCH 121/162] make the handler a bit more resilient --- examples/mqtt_handler.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/mqtt_handler.py b/examples/mqtt_handler.py index febd479..11e183b 100644 --- a/examples/mqtt_handler.py +++ b/examples/mqtt_handler.py @@ -30,9 +30,13 @@ def __init__(self, mqtt_client: MQTT.MQTT, topic: str) -> None: def emit(self, record: LogRecord) -> None: """ - Publish message from the LogRecord to the MQTT broker. + Publish message from the LogRecord to the MQTT broker, if connected. """ - self._mqtt_client.publish(self._topic, record.msg) + try: + if self._mqtt_client.is_connected(): + self._mqtt_client.publish(self._topic, record.msg) + except MQTT.MMQTTException: + pass # To make this work also in CPython's logging. def handle(self, record: LogRecord) -> None: From 068c585f85a43cf5de317891e0a957d0c85a1605 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Tue, 20 Dec 2022 12:29:11 +0100 Subject: [PATCH 122/162] fold MQTTHandler to logging_mqtt_handler.py --- examples/logging_mqtt_handler.py | 106 ++++++++++++++++++++++--------- examples/mqtt_handler.py | 46 -------------- 2 files changed, 76 insertions(+), 76 deletions(-) delete mode 100644 examples/mqtt_handler.py diff --git a/examples/logging_mqtt_handler.py b/examples/logging_mqtt_handler.py index 79344eb..c408ae4 100755 --- a/examples/logging_mqtt_handler.py +++ b/examples/logging_mqtt_handler.py @@ -10,36 +10,82 @@ import ssl import adafruit_minimqtt.adafruit_minimqtt as MQTT -from mqtt_handler import MQTTHandler import adafruit_logging as logging +# adafruit_logging defines log levels dynamically. +# pylint: disable=no-name-in-module +from adafruit_logging import NOTSET, Handler, LogRecord -logger = logging.getLogger(__name__) - -broker = "io.adafruit.com" -port = 8883 -username = "Adafruit_IO_username" -password = "Adafruit_IO_key" -feedname = "Adafruit_feed_name" -mqtt_topic = f"{username}/feeds/{feedname}" -mqtt_client = MQTT.MQTT( - broker=broker, - port=port, - username=username, - password=password, - socket_pool=socket, - ssl_context=ssl.create_default_context(), -) -mqtt_client.connect() -mqtt_handler = MQTTHandler(mqtt_client, mqtt_topic) -print("adding MQTT handler") -logger.addHandler(mqtt_handler) - -stream_handler = logging.StreamHandler() -print("adding Stream handler") -logger.addHandler(stream_handler) - -data = "foo bar" -print("logging begins !") -# This should emit both to the console as well as to the MQTT broker. -logger.warning(json.dumps(data)) + +class MQTTHandler(Handler): + """ + Log handler that emits log records as MQTT PUBLISH messages. + """ + + def __init__(self, mqtt_client: MQTT.MQTT, topic: str) -> None: + """ + Assumes that the MQTT client object is already connected. + """ + super().__init__() + + self._mqtt_client = mqtt_client + self._topic = topic + + # To make it work also in CPython. + self.level = NOTSET + + def emit(self, record: LogRecord) -> None: + """ + Publish message from the LogRecord to the MQTT broker, if connected. + """ + try: + if self._mqtt_client.is_connected(): + self._mqtt_client.publish(self._topic, record.msg) + except MQTT.MMQTTException: + pass + + # To make this work also in CPython's logging. + def handle(self, record: LogRecord) -> None: + """ + Handle the log record. Here, it means just emit. + """ + self.emit(record) + + +def main(): + """ + Demonstrate how to use MQTT log handler. + """ + logger = logging.getLogger(__name__) + + broker = "io.adafruit.com" + port = 8883 + username = "Adafruit_IO_username" + password = "Adafruit_IO_key" + feedname = "Adafruit_feed_name" + mqtt_topic = f"{username}/feeds/{feedname}" + mqtt_client = MQTT.MQTT( + broker=broker, + port=port, + username=username, + password=password, + socket_pool=socket, + ssl_context=ssl.create_default_context(), + ) + mqtt_client.connect() + mqtt_handler = MQTTHandler(mqtt_client, mqtt_topic) + print("adding MQTT handler") + logger.addHandler(mqtt_handler) + + stream_handler = logging.StreamHandler() + print("adding Stream handler") + logger.addHandler(stream_handler) + + data = "foo bar" + print("logging begins !") + # This should emit both to the console as well as to the MQTT broker. + logger.warning(json.dumps(data)) + + +if __name__ == "__main__": + main() diff --git a/examples/mqtt_handler.py b/examples/mqtt_handler.py deleted file mode 100644 index 11e183b..0000000 --- a/examples/mqtt_handler.py +++ /dev/null @@ -1,46 +0,0 @@ -# SPDX-FileCopyrightText: 2022 vladak -# SPDX-License-Identifier: Unlicense -""" -MQTT logging handler - log records will be published as MQTT messages -""" - -import adafruit_minimqtt.adafruit_minimqtt as MQTT - -# adafruit_logging defines log levels dynamically. -# pylint: disable=no-name-in-module -from adafruit_logging import NOTSET, Handler, LogRecord - - -class MQTTHandler(Handler): - """ - Log handler that emits log records as MQTT PUBLISH messages. - """ - - def __init__(self, mqtt_client: MQTT.MQTT, topic: str) -> None: - """ - Assumes that the MQTT client object is already connected. - """ - super().__init__() - - self._mqtt_client = mqtt_client - self._topic = topic - - # To make it work also in CPython. - self.level = NOTSET - - def emit(self, record: LogRecord) -> None: - """ - Publish message from the LogRecord to the MQTT broker, if connected. - """ - try: - if self._mqtt_client.is_connected(): - self._mqtt_client.publish(self._topic, record.msg) - except MQTT.MMQTTException: - pass - - # To make this work also in CPython's logging. - def handle(self, record: LogRecord) -> None: - """ - Handle the log record. Here, it means just emit. - """ - self.emit(record) From da733596e59582af628d552ba069ac0e0d3368ac Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Tue, 20 Dec 2022 12:46:34 +0100 Subject: [PATCH 123/162] apply black --- examples/logging_mqtt_handler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/logging_mqtt_handler.py b/examples/logging_mqtt_handler.py index c408ae4..9db578e 100755 --- a/examples/logging_mqtt_handler.py +++ b/examples/logging_mqtt_handler.py @@ -12,6 +12,7 @@ import adafruit_minimqtt.adafruit_minimqtt as MQTT import adafruit_logging as logging + # adafruit_logging defines log levels dynamically. # pylint: disable=no-name-in-module from adafruit_logging import NOTSET, Handler, LogRecord From 60f083b95c82acb9a1726452c5618b847a9d0097 Mon Sep 17 00:00:00 2001 From: David Sanders Date: Sat, 31 Dec 2022 10:12:55 -0800 Subject: [PATCH 124/162] Fix typo in docstring --- adafruit_logging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 9d97e0f..600cfd4 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -370,7 +370,7 @@ def log(self, level: int, msg: str, *args) -> None: def debug(self, msg: str, *args) -> None: """Log a debug message. - :param str fmsg: the core message string with embedded + :param str msg: the core message string with embedded formatting directives :param args: arguments to ``msg % args``; can be empty From 3c6dd3e296f23c591abb19d364cb2b5b285d3a26 Mon Sep 17 00:00:00 2001 From: David Sanders Date: Sat, 31 Dec 2022 11:01:56 -0800 Subject: [PATCH 125/162] Make StreamHandler terminator configurable --- adafruit_logging.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 9d97e0f..8d1450c 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -187,6 +187,8 @@ class StreamHandler(Handler): with string inputs """ + terminator = "\n" + def __init__(self, stream: Optional[WriteableStream] = None) -> None: super().__init__() if stream is None: @@ -199,7 +201,7 @@ def emit(self, record: LogRecord) -> None: :param record: The record (message object) to be logged """ - self.stream.write(self.format(record) + "\n") + self.stream.write(self.format(record) + self.terminator) class FileHandler(StreamHandler): From 9cf1449d362f160a4ce9fc415449c7f16fe481df Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Thu, 19 Jan 2023 23:39:55 -0500 Subject: [PATCH 126/162] Add upload url to release action Signed-off-by: Alec Delaney <89490472+tekktrik@users.noreply.github.com> --- .github/workflows/release_gh.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release_gh.yml b/.github/workflows/release_gh.yml index b8aa8d6..9acec60 100644 --- a/.github/workflows/release_gh.yml +++ b/.github/workflows/release_gh.yml @@ -16,3 +16,4 @@ jobs: uses: adafruit/workflows-circuitpython-libs/release-gh@main with: github-token: ${{ secrets.GITHUB_TOKEN }} + upload-url: ${{ github.event.release.upload_url }} From 64d2a888c2b9f06afae0c40a73d902c428c16eb6 Mon Sep 17 00:00:00 2001 From: tyeth Date: Sat, 6 May 2023 15:03:22 +0100 Subject: [PATCH 127/162] Update adafruit_logging.py for no args fix incompatibility with python logging module and no args and please black linting --- adafruit_logging.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 91971f0..d146496 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -324,7 +324,9 @@ def hasHandlers(self) -> bool: return len(self._handlers) > 0 def _log(self, level: int, msg: str, *args) -> None: - record = _logRecordFactory(self.name, level, msg % args, args) + record = _logRecordFactory( + self.name, level, (msg % args) if args else msg, args + ) self.handle(record) def handle(self, record: LogRecord) -> None: From 9785b10860cb0f268446747cfd4794f726210ffd Mon Sep 17 00:00:00 2001 From: Tekktrik Date: Tue, 9 May 2023 20:26:25 -0400 Subject: [PATCH 128/162] Update pre-commit hooks Signed-off-by: Tekktrik --- .pre-commit-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0e5fccc..70ade69 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,21 +4,21 @@ repos: - repo: https://github.com/python/black - rev: 22.3.0 + rev: 23.3.0 hooks: - id: black - repo: https://github.com/fsfe/reuse-tool - rev: v0.14.0 + rev: v1.1.2 hooks: - id: reuse - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.2.0 + rev: v4.4.0 hooks: - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/pycqa/pylint - rev: v2.15.5 + rev: v2.17.4 hooks: - id: pylint name: pylint (library code) From e249fe040688f96c451528a1a383a86cfa0473d9 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Fri, 12 May 2023 17:56:23 -0500 Subject: [PATCH 129/162] add sphinxcontrib.jquery --- docs/conf.py | 1 + docs/requirements.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/conf.py b/docs/conf.py index 2029037..cbdea5e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -17,6 +17,7 @@ # ones. extensions = [ "sphinx.ext.autodoc", + "sphinxcontrib.jquery", "sphinx.ext.intersphinx", "sphinx.ext.napoleon", "sphinx.ext.todo", diff --git a/docs/requirements.txt b/docs/requirements.txt index 88e6733..797aa04 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -3,3 +3,4 @@ # SPDX-License-Identifier: Unlicense sphinx>=4.0.0 +sphinxcontrib-jquery From ad853bcb50ca0b3400c6d146f3241c685186dce3 Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Tue, 23 May 2023 22:58:15 -0400 Subject: [PATCH 130/162] Update .pylintrc --- .pylintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index 40208c3..f945e92 100644 --- a/.pylintrc +++ b/.pylintrc @@ -396,4 +396,4 @@ min-public-methods=1 # Exceptions that will emit a warning when being caught. Defaults to # "Exception" -overgeneral-exceptions=Exception +overgeneral-exceptions=builtins.Exception From 4b39479ddfc0f224ec45eb901693db49ddefef18 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 18 Sep 2023 16:19:34 -0500 Subject: [PATCH 131/162] "fix rtd theme " --- docs/conf.py | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index cbdea5e..f52e773 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -101,19 +101,10 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -on_rtd = os.environ.get("READTHEDOCS", None) == "True" - -if not on_rtd: # only import and set the theme if we're building docs locally - try: - import sphinx_rtd_theme - - html_theme = "sphinx_rtd_theme" - html_theme_path = [sphinx_rtd_theme.get_html_theme_path(), "."] - except: - html_theme = "default" - html_theme_path = ["."] -else: - html_theme_path = ["."] +import sphinx_rtd_theme + +html_theme = "sphinx_rtd_theme" +html_theme_path = [sphinx_rtd_theme.get_html_theme_path(), "."] # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, From 10a0db2fed52977f853e4ea872de5215249d9024 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 16 Oct 2023 14:30:31 -0500 Subject: [PATCH 132/162] unpin sphinx and add sphinx-rtd-theme to docs reqs Signed-off-by: foamyguy --- docs/requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 797aa04..979f568 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,5 +2,6 @@ # # SPDX-License-Identifier: Unlicense -sphinx>=4.0.0 +sphinx sphinxcontrib-jquery +sphinx-rtd-theme From 1392cddec92056d62fd5f0645463ea4a8181e5ec Mon Sep 17 00:00:00 2001 From: Pat Date: Sun, 28 Apr 2024 17:15:26 -0500 Subject: [PATCH 133/162] Update adafruit_logging.py --- adafruit_logging.py | 63 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index d146496..3015ae0 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -210,11 +210,69 @@ class FileHandler(StreamHandler): :param str filename: The filename of the log file :param str mode: Whether to write ('w') or append ('a'); default is to append + :param int LogFileSizeLimit: The max allowable size of the log file in bytes. """ - def __init__(self, filename: str, mode: str = "a") -> None: + def __init__(self, filename: str, mode: str = "a", LogFileSizeLimit: int = None) -> None: # pylint: disable=consider-using-with - super().__init__(open(filename, mode=mode)) + if mode is 'r': + raise ValueError("Can't write to a read only file") + + self._LogFileName = filename + self._WriteMode = mode + self._OldFilePostfix = "_old" #TODO: Make this settable by the user? + self._LogFileSizeLimit = LogFileSizeLimit + + #Here we are assuming that if there is a period in the filename, the stuff after the period + #is the extension of the file. It is possible that is not the case, but probably unlikely. + if '.' in filename: + [basefilename, extension] = filename.rsplit(".", 1) + self._OldLogFileName = basefilename + self._OldFilePostfix + "." + extension + else: + basefilename = filename + self._OldLogFileName = basefilename + self._OldFilePostfix + + super().__init__(open(self._LogFileName, mode=self._WriteMode)) + self.CheckLogSize() + + def CheckLogSize(self) -> None: + if self._LogFileSizeLimit is None: + #No log limit set + return + + #Close the log file. Probably needed if we want to delete/rename files. + self.close() + + #Get the size of the log file. + try: + LogFileSize = os.stat(self._LogFileName)[6] + except OSError as e: + if e.args[0] == 2: + #Log file does not exsist. This is okay. + LogFileSize = None + else: + raise e + + #Get the size of the old log file. + try: + OldLogFileSize = os.stat(self._OldLogFileName)[6] + except OSError as e: + if e.args[0] == 2: + #Log file does not exsist. This is okay. + OldLogFileSize = None + else: + raise e + + #This checks if the log file is too big. If so, it deletes the old log file and renames the + #log file to the old log filename. + if LogFileSize > self._LogFileSizeLimit: + print("Rotating Logs") + if OldLogFileSize is not None: + os.remove(self._OldLogFileName) + os.rename(self._LogFileName, self._OldLogFileName) + + #Reopen the file. + self.stream = open(self._LogFileName, mode=self._WriteMode) def close(self) -> None: """Closes the file""" @@ -233,6 +291,7 @@ def emit(self, record: LogRecord) -> None: :param record: The record (message object) to be logged """ + self.CheckLogSize() self.stream.write(self.format(record)) From 4d824c70289a7d192cba12727882c30785e9a853 Mon Sep 17 00:00:00 2001 From: Pat Date: Sun, 28 Apr 2024 20:33:45 -0500 Subject: [PATCH 134/162] Update the docs --- adafruit_logging.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 3015ae0..57ecd26 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -206,7 +206,10 @@ def emit(self, record: LogRecord) -> None: class FileHandler(StreamHandler): """File handler for working with log files off of the microcontroller (like - an SD card) + an SD card). This handler implements a very simple log rotating system. If LogFileSizeLimit + is set, the handler will check to see if the log file is larger than the given limit. If the + log file is larger than the limit, it will rename it to the filename with _old appended. If + a file with _old already exsists, it will be deleted. :param str filename: The filename of the log file :param str mode: Whether to write ('w') or append ('a'); default is to append From 7b02107aac0a28bfd6b433fb3a912e33591afbea Mon Sep 17 00:00:00 2001 From: Pat Date: Sun, 28 Apr 2024 20:55:35 -0500 Subject: [PATCH 135/162] Updated from pylint and black --- adafruit_logging.py | 53 +++++++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 57ecd26..3055202 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -61,6 +61,7 @@ from collections import namedtuple try: + # pylint: disable=deprecated-class from typing import Optional, Hashable from typing_extensions import Protocol @@ -216,65 +217,69 @@ class FileHandler(StreamHandler): :param int LogFileSizeLimit: The max allowable size of the log file in bytes. """ - def __init__(self, filename: str, mode: str = "a", LogFileSizeLimit: int = None) -> None: - # pylint: disable=consider-using-with - if mode is 'r': + def __init__( + self, filename: str, mode: str = "a", LogFileSizeLimit: int = None + ) -> None: + if mode == "r": raise ValueError("Can't write to a read only file") - + self._LogFileName = filename self._WriteMode = mode - self._OldFilePostfix = "_old" #TODO: Make this settable by the user? + self._OldFilePostfix = "_old" # TODO: Make this settable by the user? self._LogFileSizeLimit = LogFileSizeLimit - - #Here we are assuming that if there is a period in the filename, the stuff after the period - #is the extension of the file. It is possible that is not the case, but probably unlikely. - if '.' in filename: + + # Here we are assuming that if there is a period in the filename, the stuff after the period + # is the extension of the file. It is possible that is not the case, but probably unlikely. + if "." in filename: [basefilename, extension] = filename.rsplit(".", 1) self._OldLogFileName = basefilename + self._OldFilePostfix + "." + extension else: basefilename = filename self._OldLogFileName = basefilename + self._OldFilePostfix + # pylint: disable=consider-using-with super().__init__(open(self._LogFileName, mode=self._WriteMode)) self.CheckLogSize() - + def CheckLogSize(self) -> None: + """Check the size of the log file and rotate if needed.""" if self._LogFileSizeLimit is None: - #No log limit set + # No log limit set return - - #Close the log file. Probably needed if we want to delete/rename files. + + # Close the log file. Probably needed if we want to delete/rename files. self.close() - - #Get the size of the log file. + + # Get the size of the log file. try: LogFileSize = os.stat(self._LogFileName)[6] except OSError as e: if e.args[0] == 2: - #Log file does not exsist. This is okay. + # Log file does not exsist. This is okay. LogFileSize = None else: raise e - - #Get the size of the old log file. + + # Get the size of the old log file. try: OldLogFileSize = os.stat(self._OldLogFileName)[6] except OSError as e: if e.args[0] == 2: - #Log file does not exsist. This is okay. + # Log file does not exsist. This is okay. OldLogFileSize = None else: raise e - - #This checks if the log file is too big. If so, it deletes the old log file and renames the - #log file to the old log filename. + + # This checks if the log file is too big. If so, it deletes the old log file and renames the + # log file to the old log filename. if LogFileSize > self._LogFileSizeLimit: print("Rotating Logs") if OldLogFileSize is not None: os.remove(self._OldLogFileName) os.rename(self._LogFileName, self._OldLogFileName) - - #Reopen the file. + + # Reopen the file. + # pylint: disable=consider-using-with self.stream = open(self._LogFileName, mode=self._WriteMode) def close(self) -> None: From 216afff33afc20171e00eeb38dc60d5f91561a8d Mon Sep 17 00:00:00 2001 From: Pat Date: Sun, 28 Apr 2024 21:14:23 -0500 Subject: [PATCH 136/162] Add the postfix as a settable variable --- adafruit_logging.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 3055202..64758c9 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -215,17 +215,22 @@ class FileHandler(StreamHandler): :param str filename: The filename of the log file :param str mode: Whether to write ('w') or append ('a'); default is to append :param int LogFileSizeLimit: The max allowable size of the log file in bytes. + :param str OldFilePostfix: What to append to the filename for the old log file. """ def __init__( - self, filename: str, mode: str = "a", LogFileSizeLimit: int = None + self, + filename: str, + mode: str = "a", + LogFileSizeLimit: int = None, + OldFilePostfix: str = "_old", ) -> None: if mode == "r": raise ValueError("Can't write to a read only file") self._LogFileName = filename self._WriteMode = mode - self._OldFilePostfix = "_old" # TODO: Make this settable by the user? + self._OldFilePostfix = OldFilePostfix self._LogFileSizeLimit = LogFileSizeLimit # Here we are assuming that if there is a period in the filename, the stuff after the period From 8153ddcfce1ab7426cd0eb41b7c61750520ad265 Mon Sep 17 00:00:00 2001 From: Pat Date: Sun, 28 Apr 2024 21:44:22 -0500 Subject: [PATCH 137/162] Add missing import and remove some debug strings. --- adafruit_logging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 64758c9..86a9327 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -58,6 +58,7 @@ import time import sys +import os from collections import namedtuple try: @@ -278,7 +279,6 @@ def CheckLogSize(self) -> None: # This checks if the log file is too big. If so, it deletes the old log file and renames the # log file to the old log filename. if LogFileSize > self._LogFileSizeLimit: - print("Rotating Logs") if OldLogFileSize is not None: os.remove(self._OldLogFileName) os.rename(self._LogFileName, self._OldLogFileName) From d0691ceb0f85a0d8154a94d5944dd723a7cecb40 Mon Sep 17 00:00:00 2001 From: Pat Date: Sun, 28 Apr 2024 21:57:41 -0500 Subject: [PATCH 138/162] Update the help text --- adafruit_logging.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 86a9327..6eb7c5c 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -207,16 +207,19 @@ def emit(self, record: LogRecord) -> None: class FileHandler(StreamHandler): - """File handler for working with log files off of the microcontroller (like - an SD card). This handler implements a very simple log rotating system. If LogFileSizeLimit - is set, the handler will check to see if the log file is larger than the given limit. If the - log file is larger than the limit, it will rename it to the filename with _old appended. If - a file with _old already exsists, it will be deleted. + """File handler for working with log files off of the microcontroller (like an SD card). + This handler implements a very simple log rotating system. If LogFileSizeLimit is set, the + handler will check to see if the log file is larger than the given limit. If the log file is + larger than the limit, it is renamed and a new file is started for log entries. The old log + file is renamed with the OldFilePostfix variable appended to the name. If another file exsists + with this old file name, it will be deleted. Because there are two log files. The max size of + the log files is two times the limit set by LogFileSizeLimit. :param str filename: The filename of the log file :param str mode: Whether to write ('w') or append ('a'); default is to append :param int LogFileSizeLimit: The max allowable size of the log file in bytes. - :param str OldFilePostfix: What to append to the filename for the old log file. + :param str OldFilePostfix: What to append to the filename for the old log file. Defaults + to '_old'. """ def __init__( From f5178594307f181ae9cdc49b85f0f03adf2afe5f Mon Sep 17 00:00:00 2001 From: Pat Date: Sun, 28 Apr 2024 21:59:56 -0500 Subject: [PATCH 139/162] Add author info. --- adafruit_logging.py | 1 + 1 file changed, 1 insertion(+) diff --git a/adafruit_logging.py b/adafruit_logging.py index 6eb7c5c..beec4dd 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -1,4 +1,5 @@ # SPDX-FileCopyrightText: 2019 Dave Astels for Adafruit Industries +# SPDX-FileCopyrightText: 2024 Pat Satyshur # # SPDX-License-Identifier: MIT From 767fda40387608857f49c2b8bbee2d43c6af0c98 Mon Sep 17 00:00:00 2001 From: Pat Date: Thu, 16 May 2024 23:02:25 -0500 Subject: [PATCH 140/162] Update adafruit_logging.py Update the code to make a subclass of filehandler that implements some of the RotatingFileHandler from: https://docs.python.org/3/library/logging.handlers.html#rotatingfilehandler --- adafruit_logging.py | 171 ++++++++++++++++++++++++++------------------ 1 file changed, 101 insertions(+), 70 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index beec4dd..42d87d9 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -208,60 +208,118 @@ def emit(self, record: LogRecord) -> None: class FileHandler(StreamHandler): - """File handler for working with log files off of the microcontroller (like an SD card). - This handler implements a very simple log rotating system. If LogFileSizeLimit is set, the - handler will check to see if the log file is larger than the given limit. If the log file is - larger than the limit, it is renamed and a new file is started for log entries. The old log - file is renamed with the OldFilePostfix variable appended to the name. If another file exsists - with this old file name, it will be deleted. Because there are two log files. The max size of - the log files is two times the limit set by LogFileSizeLimit. + """File handler for working with log files off of the microcontroller (like + an SD card) :param str filename: The filename of the log file :param str mode: Whether to write ('w') or append ('a'); default is to append - :param int LogFileSizeLimit: The max allowable size of the log file in bytes. - :param str OldFilePostfix: What to append to the filename for the old log file. Defaults - to '_old'. + """ + + def __init__(self, filename: str, mode: str = "a") -> None: + # pylint: disable=consider-using-with + if mode == "r": + raise ValueError("Can't write to a read only file") + super().__init__(open(filename, mode=mode)) + + def close(self) -> None: + """Closes the file""" + self.stream.flush() + self.stream.close() + + def format(self, record: LogRecord) -> str: + """Generate a string to log + + :param record: The record (message object) to be logged + """ + return super().format(record) + "\r\n" + + def emit(self, record: LogRecord) -> None: + """Generate the message and write it to the file. + + :param record: The record (message object) to be logged + """ + self.stream.write(self.format(record)) + + +class RotatingFileHandler(FileHandler): + """File handler for writing log files to flash memory or external memory such as an SD card. + This handler implements a very simple log rotating system similar to the python function of the + same name (https://docs.python.org/3/library/logging.handlers.html#rotatingfilehandler) + + If maxBytes is set, the handler will check to see if the log file is larger than the given + limit. If the log file is larger than the limit, it is renamed and a new file is started. + The old log file will be renamed with a numerical appendix '.1', '.2', etc... The variable + backupCount controls how many old log files to keep. For example, if the filename is 'log.txt' + and backupCount is 5, you will end up with six log files: 'log.txt', 'log.txt.1', 'log.txt.3', + up to 'log.txt.5' Therefore, the maximum amount of disk space the logs can use is + maxBytes*(backupCount+1). + + If either maxBytes or backupCount is not set, or set to zero, the log rotation is disabled. + This will result in a single log file with a name `filename` that will grow without bound. + + :param str filename: The filename of the log file + :param str mode: Whether to write ('w') or append ('a'); default is to append + :param int maxBytes: The max allowable size of the log file in bytes. + :param int backupCount: The number of old log files to keep. """ def __init__( self, filename: str, mode: str = "a", - LogFileSizeLimit: int = None, - OldFilePostfix: str = "_old", + maxBytes: int = 0, + backupCount: int = 0, ) -> None: - if mode == "r": - raise ValueError("Can't write to a read only file") + if maxBytes < 0: + raise ValueError("maxBytes must be a positive number") + if backupCount < 0: + raise ValueError("backupCount must be a positive number") self._LogFileName = filename self._WriteMode = mode - self._OldFilePostfix = OldFilePostfix - self._LogFileSizeLimit = LogFileSizeLimit - - # Here we are assuming that if there is a period in the filename, the stuff after the period - # is the extension of the file. It is possible that is not the case, but probably unlikely. - if "." in filename: - [basefilename, extension] = filename.rsplit(".", 1) - self._OldLogFileName = basefilename + self._OldFilePostfix + "." + extension - else: - basefilename = filename - self._OldLogFileName = basefilename + self._OldFilePostfix - - # pylint: disable=consider-using-with - super().__init__(open(self._LogFileName, mode=self._WriteMode)) - self.CheckLogSize() + self._maxBytes = maxBytes + self._backupCount = backupCount - def CheckLogSize(self) -> None: - """Check the size of the log file and rotate if needed.""" - if self._LogFileSizeLimit is None: - # No log limit set - return + # Open the file and save the handle to self.stream + super().__init__(self._LogFileName, mode=self._WriteMode) + # TODO: What do we do if the log file already exsists? + def doRollover(self) -> None: + """Roll over the log files. This should not need to be called directly""" + # At this point, we have already determined that we need to roll the log files. # Close the log file. Probably needed if we want to delete/rename files. self.close() - # Get the size of the log file. + for i in range(self._backupCount, 0, -1): + CurrentFileName = self._LogFileName + "." + str(i) + CurrentFileNamePlus = self._LogFileName + "." + str(i + 1) + try: + if i == self._backupCount: + # This is the oldest log file. Delete this one. + os.remove(CurrentFileName) + else: + # Rename the current file to the next number in the sequence. + os.rename(CurrentFileName, CurrentFileNamePlus) + except OSError as e: + if e.args[0] == 2: + # File does not exsist. This is okay. + pass + else: + raise e + + # Rename the current log to the first backup + os.rename(self._LogFileName, CurrentFileName) + + # Reopen the file. + # pylint: disable=consider-using-with + self.stream = open(self._LogFileName, mode=self._WriteMode) + + def GetLogSize(self) -> int: + """Check the size of the log file.""" + # TODO: Is this needed? I am catching the case where the file does not exsist, + # but I don't know if that is a realistic case anymore. try: + self.stream.flush() # We need to call this or the file size is always zero. LogFileSize = os.stat(self._LogFileName)[6] except OSError as e: if e.args[0] == 2: @@ -269,46 +327,19 @@ def CheckLogSize(self) -> None: LogFileSize = None else: raise e - - # Get the size of the old log file. - try: - OldLogFileSize = os.stat(self._OldLogFileName)[6] - except OSError as e: - if e.args[0] == 2: - # Log file does not exsist. This is okay. - OldLogFileSize = None - else: - raise e - - # This checks if the log file is too big. If so, it deletes the old log file and renames the - # log file to the old log filename. - if LogFileSize > self._LogFileSizeLimit: - if OldLogFileSize is not None: - os.remove(self._OldLogFileName) - os.rename(self._LogFileName, self._OldLogFileName) - - # Reopen the file. - # pylint: disable=consider-using-with - self.stream = open(self._LogFileName, mode=self._WriteMode) - - def close(self) -> None: - """Closes the file""" - self.stream.flush() - self.stream.close() - - def format(self, record: LogRecord) -> str: - """Generate a string to log - - :param record: The record (message object) to be logged - """ - return super().format(record) + "\r\n" + return LogFileSize def emit(self, record: LogRecord) -> None: - """Generate the message and write it to the UART. + """Generate the message and write it to the file. :param record: The record (message object) to be logged """ - self.CheckLogSize() + if ( + (self.GetLogSize() >= self._maxBytes) + and (self._maxBytes > 0) + and (self._backupCount > 0) + ): + self.doRollover() self.stream.write(self.format(record)) From c81d30e8a825f91642a7b57c076bce017dec36d3 Mon Sep 17 00:00:00 2001 From: Pat Date: Thu, 16 May 2024 23:22:36 -0500 Subject: [PATCH 141/162] Update adafruit_logging.py Remove some debug statements --- adafruit_logging.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 42d87d9..370f252 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -282,7 +282,6 @@ def __init__( # Open the file and save the handle to self.stream super().__init__(self._LogFileName, mode=self._WriteMode) - # TODO: What do we do if the log file already exsists? def doRollover(self) -> None: """Roll over the log files. This should not need to be called directly""" @@ -316,8 +315,6 @@ def doRollover(self) -> None: def GetLogSize(self) -> int: """Check the size of the log file.""" - # TODO: Is this needed? I am catching the case where the file does not exsist, - # but I don't know if that is a realistic case anymore. try: self.stream.flush() # We need to call this or the file size is always zero. LogFileSize = os.stat(self._LogFileName)[6] From 337770e62bd250e8af777e8e9f703fee7ac862f9 Mon Sep 17 00:00:00 2001 From: Pat Date: Sun, 2 Jun 2024 22:18:58 -0500 Subject: [PATCH 142/162] Add flush to the file write functions --- adafruit_logging.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/adafruit_logging.py b/adafruit_logging.py index 370f252..fc552d7 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -239,6 +239,7 @@ def emit(self, record: LogRecord) -> None: :param record: The record (message object) to be logged """ self.stream.write(self.format(record)) + self.stream.flush() class RotatingFileHandler(FileHandler): @@ -338,6 +339,7 @@ def emit(self, record: LogRecord) -> None: ): self.doRollover() self.stream.write(self.format(record)) + self.stream.flush() class NullHandler(Handler): From 4e0c2f33fe1bc76b65df8e384c930e7b7c205919 Mon Sep 17 00:00:00 2001 From: Pat Date: Mon, 3 Jun 2024 19:21:40 -0500 Subject: [PATCH 143/162] Add flush function to the stream handler. --- adafruit_logging.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index fc552d7..03a0351 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -206,6 +206,12 @@ def emit(self, record: LogRecord) -> None: """ self.stream.write(self.format(record) + self.terminator) + def flush(self) -> None: + """flush the stream. You might need to call this if your messages + are not appearing in the log file. + """ + self.stream.flush() + class FileHandler(StreamHandler): """File handler for working with log files off of the microcontroller (like @@ -239,7 +245,6 @@ def emit(self, record: LogRecord) -> None: :param record: The record (message object) to be logged """ self.stream.write(self.format(record)) - self.stream.flush() class RotatingFileHandler(FileHandler): @@ -339,7 +344,6 @@ def emit(self, record: LogRecord) -> None: ): self.doRollover() self.stream.write(self.format(record)) - self.stream.flush() class NullHandler(Handler): From 18a4a3de2d41d3aab2523ab2066fc0504be8a15e Mon Sep 17 00:00:00 2001 From: Pat Date: Mon, 3 Jun 2024 20:21:31 -0500 Subject: [PATCH 144/162] Added a flushHandlers() function to the logger. --- adafruit_logging.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/adafruit_logging.py b/adafruit_logging.py index 03a0351..006f431 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -179,6 +179,9 @@ def emit(self, record: LogRecord) -> None: raise NotImplementedError() + def flush(self) -> None: + """Placeholder for flush function in subclasses.""" + # pylint: disable=too-few-public-methods class StreamHandler(Handler): @@ -415,6 +418,13 @@ def getEffectiveLevel(self) -> int: return self._level + def flushHandlers(self) -> None: + """Flush all handlers. This will ensure that all data is immediately written to the streams. + This can be useful if you need to make sure the log is written before a reset. + """ + for handlerName in self._handlers: + handlerName.flush() + def addHandler(self, hdlr: Handler) -> None: """Adds the handler to this logger. From 3cc0d4f0e13886b581ded13aa523849d6a319b55 Mon Sep 17 00:00:00 2001 From: Pat Date: Tue, 4 Jun 2024 21:10:59 -0500 Subject: [PATCH 145/162] Revert to flushing after file writes. --- adafruit_logging.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 006f431..f1e171f 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -248,6 +248,7 @@ def emit(self, record: LogRecord) -> None: :param record: The record (message object) to be logged """ self.stream.write(self.format(record)) + self.stream.flush() class RotatingFileHandler(FileHandler): @@ -347,6 +348,7 @@ def emit(self, record: LogRecord) -> None: ): self.doRollover() self.stream.write(self.format(record)) + self.stream.flush() class NullHandler(Handler): @@ -418,13 +420,6 @@ def getEffectiveLevel(self) -> int: return self._level - def flushHandlers(self) -> None: - """Flush all handlers. This will ensure that all data is immediately written to the streams. - This can be useful if you need to make sure the log is written before a reset. - """ - for handlerName in self._handlers: - handlerName.flush() - def addHandler(self, hdlr: Handler) -> None: """Adds the handler to this logger. From 39a7927f2fa44ecb1802cef5c76dc8350c65ae14 Mon Sep 17 00:00:00 2001 From: Phil Underwood Date: Thu, 20 Jun 2024 13:12:13 +0100 Subject: [PATCH 146/162] Add support for multiline logs and exceptions --- adafruit_logging.py | 42 ++++++++++++++++++++++++++++------ examples/logging_simpletest.py | 9 +++++++- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index f1e171f..6394f16 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -202,6 +202,15 @@ def __init__(self, stream: Optional[WriteableStream] = None) -> None: self.stream = stream """The stream to log to""" + def format(self, record: LogRecord) -> str: + """Generate a string to log + + :param record: The record (message object) to be logged + """ + text = super().format(record) + lines = text.splitlines() + return self.terminator.join(lines) + self.terminator + def emit(self, record: LogRecord) -> None: """Send a message to the console. @@ -224,6 +233,8 @@ class FileHandler(StreamHandler): :param str mode: Whether to write ('w') or append ('a'); default is to append """ + terminator = "\r\n" + def __init__(self, filename: str, mode: str = "a") -> None: # pylint: disable=consider-using-with if mode == "r": @@ -235,13 +246,6 @@ def close(self) -> None: self.stream.flush() self.stream.close() - def format(self, record: LogRecord) -> str: - """Generate a string to log - - :param record: The record (message object) to be logged - """ - return super().format(record) + "\r\n" - def emit(self, record: LogRecord) -> None: """Generate the message and write it to the file. @@ -538,3 +542,27 @@ def critical(self, msg: str, *args) -> None: can be empty """ self._log(CRITICAL, msg, *args) + + # pylint: disable=no-value-for-parameter; value and tb are optional for traceback + def exception(self, err: Exception) -> None: + """Convenience method for logging an ERROR with exception information. + + :param Exception err: the exception to be logged + """ + try: + # pylint: disable=import-outside-toplevel; not available on all boards + import traceback + except ImportError: + self._log( + ERROR, + "%s: %s (No traceback on this board)", + err.__class__.__name__, + str(err), + ) + else: + lines = [str(err)] + traceback.format_exception(err) + lines = str(err) + "\n".join(lines) + # some of the returned strings from format_exception already have newlines in them, + # so we can't add the indent in the above line - needs to be done separately + lines = lines.replace("\n", "\n ") + self._log(ERROR, lines) diff --git a/examples/logging_simpletest.py b/examples/logging_simpletest.py index c8099a1..3bb25e1 100644 --- a/examples/logging_simpletest.py +++ b/examples/logging_simpletest.py @@ -25,7 +25,10 @@ logger.setLevel(logging.ERROR) logger.info("Stream Handler: Info message") logger.error("Stream Handler: Error message") - +try: + raise RuntimeError("Test exception handling") +except RuntimeError as e: + logger.exception(e) # This should produce no output at all. null_logger = logging.getLogger("null") @@ -36,3 +39,7 @@ null_logger.setLevel(logging.ERROR) null_logger.info("Null Handler: Info message") null_logger.error("Null Handler: Error message") +try: + raise RuntimeError("Test exception handling") +except RuntimeError as e: + null_logger.exception(e) From f7dc544add8acc98fb76696b9aa2cb0cfd54a684 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 5 Aug 2024 18:15:36 -0500 Subject: [PATCH 147/162] adding Formatter and example including timestamp --- adafruit_logging.py | 68 ++++++++++++++++++++++++++- examples/logging_formatter_example.py | 33 +++++++++++++ 2 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 examples/logging_formatter_example.py diff --git a/adafruit_logging.py b/adafruit_logging.py index 6394f16..4d0ae46 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -148,12 +148,71 @@ def _logRecordFactory(name, level, msg, args): return LogRecord(name, level, _level_for(level), msg, time.monotonic(), args) +class Formatter: + """ + Responsible for converting a LogRecord to an output string to be + interpreted by a human or external system. + + Only implements a sub-set of CPython logging.Formatter behavior, + but retains all the same arguments in order to match the API. + + The only init arguments currently supported are: fmt and defaults. + All others are currently ignored + + The only style value currently supported is '{'. CPython has support + for some others, but this implementation does not. Additionally, the + default value for style in this implementation is '{' whereas the default + style value in CPython is '%' + """ + + def __init__( # pylint: disable=too-many-arguments + self, fmt=None, datefmt=None, style="{", validate=True, defaults=None + ): + self.fmt = fmt + self.datefmt = datefmt + self.style = style + if self.style != "{": + raise ValueError("Only '{' formatting sytle is supported at this time.") + + self.validate = validate + self.defaults = defaults + + def format(self, record: LogRecord) -> str: + """ + Format the given LogRecord into an output string + """ + if self.fmt is None: + return record.msg + + vals = { + "name": record.name, + "levelno": record.levelno, + "levelname": record.levelname, + "message": record.msg, + "created": record.created, + "args": record.args, + } + if "{asctime}" in self.fmt: + now = time.localtime() + vals["asctime"] = ( + f"{now.tm_year}-{now.tm_mon:02d}-{now.tm_mday:02d} " + f"{now.tm_hour:02d}:{now.tm_min:02d}:{now.tm_sec:02d}" + ) + + if self.defaults: + for key, val in self.defaults.items(): + vals[key] = val + + return self.fmt.format(**vals) + + class Handler: """Base logging message handler.""" def __init__(self, level: int = NOTSET) -> None: """Create Handler instance""" self.level = level + self.formatter = None def setLevel(self, level: int) -> None: """ @@ -167,7 +226,8 @@ def format(self, record: LogRecord) -> str: :param record: The record (message object) to be logged """ - + if self.formatter: + return self.formatter.format(record) return f"{record.created:<0.3f}: {record.levelname} - {record.msg}" def emit(self, record: LogRecord) -> None: @@ -182,6 +242,12 @@ def emit(self, record: LogRecord) -> None: def flush(self) -> None: """Placeholder for flush function in subclasses.""" + def setFormatter(self, formatter: Formatter) -> None: + """ + Set the Formatter to be used by this Handler. + """ + self.formatter = formatter + # pylint: disable=too-few-public-methods class StreamHandler(Handler): diff --git a/examples/logging_formatter_example.py b/examples/logging_formatter_example.py new file mode 100644 index 0000000..5e35e37 --- /dev/null +++ b/examples/logging_formatter_example.py @@ -0,0 +1,33 @@ +# SPDX-FileCopyrightText: 2024 Tim Cocks +# SPDX-License-Identifier: MIT + + +"""Briefly exercise the logger and null logger.""" + +import adafruit_logging as logging +# To test on CPython, un-comment below and comment out above +# import logging + + +logger = logging.getLogger("example") +logger.setLevel(logging.INFO) +print_handler = logging.StreamHandler() +logger.addHandler(print_handler) + +default_formatter = logging.Formatter() +print_handler.setFormatter(default_formatter) +logger.info("Default formatter example") + + +timestamp_formatter = logging.Formatter( + fmt="{asctime} {levelname}: {message}", style="{" +) +print_handler.setFormatter(timestamp_formatter) +logger.info("Timestamp formatter example") + + +custom_vals_formatter = logging.Formatter( + fmt="{ip} {levelname}: {message}", style="{", defaults={"ip": "192.168.1.188"} +) +print_handler.setFormatter(custom_vals_formatter) +logger.info("Custom formatter example") From 8f53ae15afafde6bb0c3e7abefdc079d43e18296 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 5 Aug 2024 18:33:48 -0500 Subject: [PATCH 148/162] update comment --- examples/logging_formatter_example.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/logging_formatter_example.py b/examples/logging_formatter_example.py index 5e35e37..460e028 100644 --- a/examples/logging_formatter_example.py +++ b/examples/logging_formatter_example.py @@ -2,9 +2,11 @@ # SPDX-License-Identifier: MIT -"""Briefly exercise the logger and null logger.""" +"""Illustrate usage of default and custom Formatters including +one with timestamps.""" import adafruit_logging as logging + # To test on CPython, un-comment below and comment out above # import logging From 555981ac12e5937b3fa05560b214242ca277edc2 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 5 Aug 2024 18:38:38 -0500 Subject: [PATCH 149/162] fix unsupported syntx --- adafruit_logging.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 4d0ae46..f2041b8 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -194,10 +194,10 @@ def format(self, record: LogRecord) -> str: } if "{asctime}" in self.fmt: now = time.localtime() - vals["asctime"] = ( - f"{now.tm_year}-{now.tm_mon:02d}-{now.tm_mday:02d} " - f"{now.tm_hour:02d}:{now.tm_min:02d}:{now.tm_sec:02d}" - ) + # pylint: disable=line-too-long + vals[ + "asctime" + ] = f"{now.tm_year}-{now.tm_mon:02d}-{now.tm_mday:02d} {now.tm_hour:02d}:{now.tm_min:02d}:{now.tm_sec:02d}" if self.defaults: for key, val in self.defaults.items(): From 48fd4412ed46114e61fb1c9ab7c7aba918d236df Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 5 Aug 2024 19:04:29 -0500 Subject: [PATCH 150/162] remove extra newlines --- adafruit_logging.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 6394f16..117c1bf 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -209,7 +209,7 @@ def format(self, record: LogRecord) -> str: """ text = super().format(record) lines = text.splitlines() - return self.terminator.join(lines) + self.terminator + return self.terminator.join(lines) def emit(self, record: LogRecord) -> None: """Send a message to the console. @@ -561,7 +561,7 @@ def exception(self, err: Exception) -> None: ) else: lines = [str(err)] + traceback.format_exception(err) - lines = str(err) + "\n".join(lines) + lines = str(err) + "".join(lines) # some of the returned strings from format_exception already have newlines in them, # so we can't add the indent in the above line - needs to be done separately lines = lines.replace("\n", "\n ") From ef8c237e276e34af2a54b17a4e6d9eae899b5b9f Mon Sep 17 00:00:00 2001 From: foamyguy Date: Tue, 6 Aug 2024 10:52:17 -0500 Subject: [PATCH 151/162] implement % sytle and change default to it --- adafruit_logging.py | 35 ++++++++++++++++++--------- examples/logging_formatter_example.py | 21 +++++++++++++--- 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index f2041b8..029b834 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -64,7 +64,7 @@ try: # pylint: disable=deprecated-class - from typing import Optional, Hashable + from typing import Optional, Hashable, Dict from typing_extensions import Protocol class WriteableStream(Protocol): @@ -156,23 +156,28 @@ class Formatter: Only implements a sub-set of CPython logging.Formatter behavior, but retains all the same arguments in order to match the API. - The only init arguments currently supported are: fmt and defaults. - All others are currently ignored + The only init arguments currently supported are: fmt, defaults and + style. All others are currently ignored - The only style value currently supported is '{'. CPython has support - for some others, but this implementation does not. Additionally, the - default value for style in this implementation is '{' whereas the default - style value in CPython is '%' + The only two styles currently supported are '%' and '{'. The default + style is '{' """ def __init__( # pylint: disable=too-many-arguments - self, fmt=None, datefmt=None, style="{", validate=True, defaults=None + self, + fmt: Optional[str] = None, + datefmt: Optional[str] = None, + style: str = "%", + validate: bool = True, + defaults: Dict = None, ): self.fmt = fmt self.datefmt = datefmt self.style = style - if self.style != "{": - raise ValueError("Only '{' formatting sytle is supported at this time.") + if self.style not in ("{", "%"): + raise ValueError( + "Only '%' and '{' formatting style are supported at this time." + ) self.validate = validate self.defaults = defaults @@ -192,7 +197,7 @@ def format(self, record: LogRecord) -> str: "created": record.created, "args": record.args, } - if "{asctime}" in self.fmt: + if "{asctime}" in self.fmt or "%(asctime)s" in self.fmt: now = time.localtime() # pylint: disable=line-too-long vals[ @@ -203,6 +208,14 @@ def format(self, record: LogRecord) -> str: for key, val in self.defaults.items(): vals[key] = val + if self.style not in ("{", "%"): + raise ValueError( + "Only '%' and '{' formatting style are supported at this time." + ) + + if self.style == "%": + return self.fmt % vals + return self.fmt.format(**vals) diff --git a/examples/logging_formatter_example.py b/examples/logging_formatter_example.py index 460e028..ccd2eea 100644 --- a/examples/logging_formatter_example.py +++ b/examples/logging_formatter_example.py @@ -17,19 +17,32 @@ logger.addHandler(print_handler) default_formatter = logging.Formatter() + print_handler.setFormatter(default_formatter) logger.info("Default formatter example") -timestamp_formatter = logging.Formatter( - fmt="{asctime} {levelname}: {message}", style="{" -) +timestamp_formatter = logging.Formatter(fmt="%(asctime)s %(levelname)s: %(message)s") print_handler.setFormatter(timestamp_formatter) logger.info("Timestamp formatter example") custom_vals_formatter = logging.Formatter( - fmt="{ip} {levelname}: {message}", style="{", defaults={"ip": "192.168.1.188"} + fmt="%(ip)s %(levelname)s: %(message)s", defaults={"ip": "192.168.1.188"} ) print_handler.setFormatter(custom_vals_formatter) logger.info("Custom formatter example") + + +bracket_timestamp_formatter = logging.Formatter( + fmt="{asctime} {levelname}: {message}", style="{" +) +print_handler.setFormatter(bracket_timestamp_formatter) +logger.info("Timestamp formatter bracket style example") + + +bracket_custom_vals_formatter = logging.Formatter( + fmt="{ip} {levelname}: {message}", style="{", defaults={"ip": "192.168.1.188"} +) +print_handler.setFormatter(bracket_custom_vals_formatter) +logger.info("Custom formatter bracket style example") From c9ae545190816800bb564892e1fd786ab7adba14 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Tue, 6 Aug 2024 13:12:53 -0500 Subject: [PATCH 152/162] prevent defaults from clobbering --- adafruit_logging.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 029b834..d037b15 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -206,7 +206,8 @@ def format(self, record: LogRecord) -> str: if self.defaults: for key, val in self.defaults.items(): - vals[key] = val + if key not in vals: + vals[key] = val if self.style not in ("{", "%"): raise ValueError( From 66e3cfd3b445e381985641a0a6c6ad3fbe7d2599 Mon Sep 17 00:00:00 2001 From: aggieNick02 Date: Thu, 26 Sep 2024 12:30:43 -0500 Subject: [PATCH 153/162] Fix log files not containing any newlines ever (affects FileHandler and RotatingFileHandler) --- adafruit_logging.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index dc3c652..6bf8de2 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -331,7 +331,7 @@ def emit(self, record: LogRecord) -> None: :param record: The record (message object) to be logged """ - self.stream.write(self.format(record)) + super().emit(record) self.stream.flush() @@ -431,9 +431,7 @@ def emit(self, record: LogRecord) -> None: and (self._backupCount > 0) ): self.doRollover() - self.stream.write(self.format(record)) - self.stream.flush() - + super().emit(record) class NullHandler(Handler): """Provide an empty log handler. From 633045a39746750c021b9632bcb977b76fa58e5a Mon Sep 17 00:00:00 2001 From: aggieNick02 Date: Thu, 26 Sep 2024 12:42:32 -0500 Subject: [PATCH 154/162] fix formatting --- adafruit_logging.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 6bf8de2..d70c306 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -200,9 +200,9 @@ def format(self, record: LogRecord) -> str: if "{asctime}" in self.fmt or "%(asctime)s" in self.fmt: now = time.localtime() # pylint: disable=line-too-long - vals[ - "asctime" - ] = f"{now.tm_year}-{now.tm_mon:02d}-{now.tm_mday:02d} {now.tm_hour:02d}:{now.tm_min:02d}:{now.tm_sec:02d}" + vals["asctime"] = ( + f"{now.tm_year}-{now.tm_mon:02d}-{now.tm_mday:02d} {now.tm_hour:02d}:{now.tm_min:02d}:{now.tm_sec:02d}" + ) if self.defaults: for key, val in self.defaults.items(): @@ -433,6 +433,7 @@ def emit(self, record: LogRecord) -> None: self.doRollover() super().emit(record) + class NullHandler(Handler): """Provide an empty log handler. From bbef817f2ff82243949378479b29505ad2ee8cc5 Mon Sep 17 00:00:00 2001 From: aggieNick02 Date: Thu, 26 Sep 2024 12:50:41 -0500 Subject: [PATCH 155/162] one more attempt at fixing formatting --- adafruit_logging.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index d70c306..28d1021 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -200,9 +200,9 @@ def format(self, record: LogRecord) -> str: if "{asctime}" in self.fmt or "%(asctime)s" in self.fmt: now = time.localtime() # pylint: disable=line-too-long - vals["asctime"] = ( - f"{now.tm_year}-{now.tm_mon:02d}-{now.tm_mday:02d} {now.tm_hour:02d}:{now.tm_min:02d}:{now.tm_sec:02d}" - ) + vals[ + "asctime" + ] = f"{now.tm_year}-{now.tm_mon:02d}-{now.tm_mday:02d} {now.tm_hour:02d}:{now.tm_min:02d}:{now.tm_sec:02d}" if self.defaults: for key, val in self.defaults.items(): From 618da53a108507ff95d201719ccc0d3c03685e23 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 7 Oct 2024 09:24:05 -0500 Subject: [PATCH 156/162] remove deprecated get_html_theme_path() call Signed-off-by: foamyguy --- docs/conf.py | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index f52e773..8afefaa 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -104,7 +104,6 @@ import sphinx_rtd_theme html_theme = "sphinx_rtd_theme" -html_theme_path = [sphinx_rtd_theme.get_html_theme_path(), "."] # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, From 3440b399e5999fd76a1bd09e13a5c1e9bef53eb1 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Tue, 14 Jan 2025 11:32:34 -0600 Subject: [PATCH 157/162] add sphinx configuration to rtd.yaml Signed-off-by: foamyguy --- .readthedocs.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 33c2a61..88bca9f 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -8,6 +8,9 @@ # Required version: 2 +sphinx: + configuration: docs/conf.py + build: os: ubuntu-20.04 tools: From 2e410bada543e2b444d2ea925a564138ca5a16c8 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Fri, 16 May 2025 16:34:03 +0000 Subject: [PATCH 158/162] change to ruff --- .gitattributes | 11 + .pre-commit-config.yaml | 43 +-- .pylintrc | 399 -------------------------- README.rst | 6 +- adafruit_logging.py | 62 ++-- docs/api.rst | 3 + docs/conf.py | 8 +- examples/logging_filehandler.py | 5 +- examples/logging_formatter_example.py | 4 +- examples/logging_mqtt_handler.py | 1 - examples/logging_simpletest.py | 3 - ruff.toml | 108 +++++++ 12 files changed, 159 insertions(+), 494 deletions(-) create mode 100644 .gitattributes delete mode 100644 .pylintrc create mode 100644 ruff.toml diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..21c125c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2024 Justin Myers for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense + +.py text eol=lf +.rst text eol=lf +.txt text eol=lf +.yaml text eol=lf +.toml text eol=lf +.license text eol=lf +.md text eol=lf diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 70ade69..ff19dde 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,42 +1,21 @@ -# SPDX-FileCopyrightText: 2020 Diego Elio Pettenò +# SPDX-FileCopyrightText: 2024 Justin Myers for Adafruit Industries # # SPDX-License-Identifier: Unlicense repos: - - repo: https://github.com/python/black - rev: 23.3.0 - hooks: - - id: black - - repo: https://github.com/fsfe/reuse-tool - rev: v1.1.2 - hooks: - - id: reuse - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace - - repo: https://github.com/pycqa/pylint - rev: v2.17.4 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.3.4 hooks: - - id: pylint - name: pylint (library code) - types: [python] - args: - - --disable=consider-using-f-string - exclude: "^(docs/|examples/|tests/|setup.py$)" - - id: pylint - name: pylint (example code) - description: Run pylint rules on "examples/*.py" files - types: [python] - files: "^examples/" - args: - - --disable=missing-docstring,invalid-name,consider-using-f-string,duplicate-code - - id: pylint - name: pylint (test code) - description: Run pylint rules on "tests/*.py" files - types: [python] - files: "^tests/" - args: - - --disable=missing-docstring,consider-using-f-string,duplicate-code + - id: ruff-format + - id: ruff + args: ["--fix"] + - repo: https://github.com/fsfe/reuse-tool + rev: v3.0.1 + hooks: + - id: reuse diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index f945e92..0000000 --- a/.pylintrc +++ /dev/null @@ -1,399 +0,0 @@ -# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries -# -# SPDX-License-Identifier: Unlicense - -[MASTER] - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code -extension-pkg-whitelist= - -# Add files or directories to the ignore-list. They should be base names, not -# paths. -ignore=CVS - -# Add files or directories matching the regex patterns to the ignore-list. The -# regex matches against base names, not paths. -ignore-patterns= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Use multiple processes to speed up Pylint. -jobs=1 - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins=pylint.extensions.no_self_use - -# Pickle collected data for later comparisons. -persistent=yes - -# Specify a configuration file. -#rcfile= - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED -confidence= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" -# disable=import-error,raw-checker-failed,bad-inline-option,locally-disabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,deprecated-str-translate-call -disable=raw-checker-failed,bad-inline-option,locally-disabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,import-error,pointless-string-statement,unspecified-encoding - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -enable= - - -[REPORTS] - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details -#msg-template= - -# Set the output format. Available formats are text, parseable, colorized, json -# and msvs (visual studio).You can also give a reporter class, eg -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Tells whether to display a full report or only the messages -reports=no - -# Activate the evaluation score. -score=yes - - -[REFACTORING] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - - -[LOGGING] - -# Logging modules to check that the string format arguments are in logging -# function parameter format -logging-modules=logging - - -[SPELLING] - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -# notes=FIXME,XXX,TODO -notes=FIXME,XXX - - -[TYPECHECK] - -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# This flag controls whether pylint should warn about no-member and similar -# checks whenever an opaque object is returned when inferring. The inference -# can return multiple potential results while evaluating a Python object, but -# some branches might not be evaluated, which results in partial inference. In -# that case, it might be useful to still emit no-member and other checks for -# the rest of the inferred objects. -ignore-on-opaque-inference=yes - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules=board - -# Show a hint with possible names when a member name was not found. The aspect -# of finding the hint is based on edit distance. -missing-member-hint=yes - -# The minimum edit distance a name should have in order to be considered a -# similar match for a missing member name. -missing-member-hint-distance=1 - -# The total number of similar names that should be taken in consideration when -# showing a hint for a missing member. -missing-member-max-choices=1 - - -[VARIABLES] - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-builtins= - -# Tells whether unused global variables should be treated as a violation. -allow-global-unused-variables=yes - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_,_cb - -# A regular expression matching the name of dummy variables (i.e. expectedly -# not used). -dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore -ignored-argument-names=_.*|^ignored_|^unused_ - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six.moves,future.builtins - - -[FORMAT] - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -# expected-line-ending-format= -expected-line-ending-format=LF - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )??$ - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Maximum number of characters on a single line. -max-line-length=100 - -# Maximum number of lines in a module -max-module-lines=1000 - -# Allow the body of a class to be on the same line as the declaration if body -# contains single statement. -single-line-class-stmt=no - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - - -[SIMILARITIES] - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=yes - -# Minimum lines number of a similarity. -min-similarity-lines=12 - - -[BASIC] - -# Regular expression matching correct argument names -argument-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct attribute names -attr-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Bad variable names which should always be refused, separated by a comma -bad-names=foo,bar,baz,toto,tutu,tata - -# Regular expression matching correct class attribute names -class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Regular expression matching correct class names -# class-rgx=[A-Z_][a-zA-Z0-9]+$ -class-rgx=[A-Z_][a-zA-Z0-9_]+$ - -# Regular expression matching correct constant names -const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - -# Regular expression matching correct function names -function-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Good variable names which should always be accepted, separated by a comma -# good-names=i,j,k,ex,Run,_ -good-names=r,g,b,w,i,j,k,n,x,y,z,ex,ok,Run,_ - -# Include a hint for the correct naming format with invalid-name -include-naming-hint=no - -# Regular expression matching correct inline iteration names -inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ - -# Regular expression matching correct method names -method-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct module names -module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=^_ - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. -property-classes=abc.abstractproperty - -# Regular expression matching correct variable names -variable-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - - -[IMPORTS] - -# Allow wildcard imports from modules that define __all__. -allow-wildcard-with-all=no - -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=optparse,tkinter.tix - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled) -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__,__new__,setUp - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict,_fields,_replace,_source,_make - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs - - -[DESIGN] - -# Maximum number of arguments for function / method -max-args=5 - -# Maximum number of attributes for a class (see R0902). -# max-attributes=7 -max-attributes=11 - -# Maximum number of boolean expressions in a if statement -max-bool-expr=5 - -# Maximum number of branch for function / method body -max-branches=12 - -# Maximum number of locals for function / method body -max-locals=15 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - -# Maximum number of return / yield for function / method body -max-returns=6 - -# Maximum number of statements in function / method body -max-statements=50 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=1 - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=builtins.Exception diff --git a/README.rst b/README.rst index cc26428..eecf292 100644 --- a/README.rst +++ b/README.rst @@ -13,9 +13,9 @@ Introduction :target: https://github.com/adafruit/Adafruit_CircuitPython_Logger :alt: Build Status -.. image:: https://img.shields.io/badge/code%20style-black-000000.svg - :target: https://github.com/psf/black - :alt: Code Style: Black +.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json + :target: https://github.com/astral-sh/ruff + :alt: Code Style: Ruff Logging module for CircuitPython diff --git a/adafruit_logging.py b/adafruit_logging.py index 28d1021..5aba5f4 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -55,16 +55,14 @@ """ -# pylint: disable=invalid-name,undefined-variable - -import time -import sys import os +import sys +import time from collections import namedtuple try: - # pylint: disable=deprecated-class - from typing import Optional, Hashable, Dict + from typing import Dict, Hashable, Optional + from typing_extensions import Protocol class WriteableStream(Protocol): @@ -82,7 +80,6 @@ def write(self, buf: str) -> int: __version__ = "0.0.0+auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Logger.git" -# pylint:disable=undefined-all-variable __all__ = [ "LEVELS", "NOTSET", @@ -129,9 +126,7 @@ def _level_for(value: int) -> str: return LEVELS[0][1] -LogRecord = namedtuple( - "_LogRecord", ("name", "levelno", "levelname", "msg", "created", "args") -) +LogRecord = namedtuple("_LogRecord", ("name", "levelno", "levelname", "msg", "created", "args")) """An object used to hold the contents of a log record. The following attributes can be retrieved from it: @@ -163,7 +158,7 @@ class Formatter: style is '{' """ - def __init__( # pylint: disable=too-many-arguments + def __init__( self, fmt: Optional[str] = None, datefmt: Optional[str] = None, @@ -174,10 +169,8 @@ def __init__( # pylint: disable=too-many-arguments self.fmt = fmt self.datefmt = datefmt self.style = style - if self.style not in ("{", "%"): - raise ValueError( - "Only '%' and '{' formatting style are supported at this time." - ) + if self.style not in {"{", "%"}: + raise ValueError("Only '%' and '{' formatting style are supported at this time.") self.validate = validate self.defaults = defaults @@ -199,20 +192,17 @@ def format(self, record: LogRecord) -> str: } if "{asctime}" in self.fmt or "%(asctime)s" in self.fmt: now = time.localtime() - # pylint: disable=line-too-long - vals[ - "asctime" - ] = f"{now.tm_year}-{now.tm_mon:02d}-{now.tm_mday:02d} {now.tm_hour:02d}:{now.tm_min:02d}:{now.tm_sec:02d}" + vals["asctime"] = ( + f"{now.tm_year}-{now.tm_mon:02d}-{now.tm_mday:02d} {now.tm_hour:02d}:{now.tm_min:02d}:{now.tm_sec:02d}" # noqa: E501 + ) if self.defaults: for key, val in self.defaults.items(): if key not in vals: vals[key] = val - if self.style not in ("{", "%"): - raise ValueError( - "Only '%' and '{' formatting style are supported at this time." - ) + if self.style not in {"{", "%"}: + raise ValueError("Only '%' and '{' formatting style are supported at this time.") if self.style == "%": return self.fmt % vals @@ -234,7 +224,6 @@ def setLevel(self, level: int) -> None: """ self.level = level - # pylint: disable=no-self-use def format(self, record: LogRecord) -> str: """Generate a timestamped message. @@ -263,7 +252,6 @@ def setFormatter(self, formatter: Formatter) -> None: self.formatter = formatter -# pylint: disable=too-few-public-methods class StreamHandler(Handler): """Send logging messages to a stream, `sys.stderr` (typically the serial console) by default. @@ -316,7 +304,6 @@ class FileHandler(StreamHandler): terminator = "\r\n" def __init__(self, filename: str, mode: str = "a") -> None: - # pylint: disable=consider-using-with if mode == "r": raise ValueError("Can't write to a read only file") super().__init__(open(filename, mode=mode)) @@ -404,7 +391,6 @@ def doRollover(self) -> None: os.rename(self._LogFileName, CurrentFileName) # Reopen the file. - # pylint: disable=consider-using-with self.stream = open(self._LogFileName, mode=self._WriteMode) def GetLogSize(self) -> int: @@ -522,9 +508,7 @@ def hasHandlers(self) -> bool: return len(self._handlers) > 0 def _log(self, level: int, msg: str, *args) -> None: - record = _logRecordFactory( - self.name, level, (msg % args) if args else msg, args - ) + record = _logRecordFactory(self.name, level, (msg % args) if args else msg, args) self.handle(record) def handle(self, record: LogRecord) -> None: @@ -532,14 +516,8 @@ def handle(self, record: LogRecord) -> None: :param LogRecord record: log record """ - if ( - _default_handler is None - and not self.hasHandlers() - and not self.emittedNoHandlerWarning - ): - sys.stderr.write( - f"Logger '{self.name}' has no handlers and default handler is None\n" - ) + if _default_handler is None and not self.hasHandlers() and not self.emittedNoHandlerWarning: + sys.stderr.write(f"Logger '{self.name}' has no handlers and default handler is None\n") self.emittedNoHandlerWarning = True return @@ -550,11 +528,7 @@ def handle(self, record: LogRecord) -> None: handler.emit(record) emitted = True - if ( - not emitted - and _default_handler - and record.levelno >= _default_handler.level - ): + if not emitted and _default_handler and record.levelno >= _default_handler.level: _default_handler.emit(record) def log(self, level: int, msg: str, *args) -> None: @@ -622,14 +596,12 @@ def critical(self, msg: str, *args) -> None: """ self._log(CRITICAL, msg, *args) - # pylint: disable=no-value-for-parameter; value and tb are optional for traceback def exception(self, err: Exception) -> None: """Convenience method for logging an ERROR with exception information. :param Exception err: the exception to be logged """ try: - # pylint: disable=import-outside-toplevel; not available on all boards import traceback except ImportError: self._log( diff --git a/docs/api.rst b/docs/api.rst index aced610..fe3e4ee 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -4,5 +4,8 @@ .. If your library file(s) are nested in a directory (e.g. /adafruit_foo/foo.py) .. use this format as the module name: "adafruit_foo.foo" +API Reference +############# + .. automodule:: adafruit_logging :members: diff --git a/docs/conf.py b/docs/conf.py index 8afefaa..a7b784c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,12 +1,10 @@ -# -*- coding: utf-8 -*- - # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # # SPDX-License-Identifier: MIT +import datetime import os import sys -import datetime sys.path.insert(0, os.path.abspath("..")) @@ -48,9 +46,7 @@ creation_year = "2019" current_year = str(datetime.datetime.now().year) year_duration = ( - current_year - if current_year == creation_year - else creation_year + " - " + current_year + current_year if current_year == creation_year else creation_year + " - " + current_year ) copyright = year_duration + " Dave Astels" author = "Dave Astels" diff --git a/examples/logging_filehandler.py b/examples/logging_filehandler.py index 92f9d0d..fc56a7a 100644 --- a/examples/logging_filehandler.py +++ b/examples/logging_filehandler.py @@ -1,11 +1,12 @@ # SPDX-FileCopyrightText: 2021 Alec Delaney # SPDX-License-Identifier: MIT +import adafruit_sdcard import board import busio -from digitalio import DigitalInOut import storage -import adafruit_sdcard +from digitalio import DigitalInOut + import adafruit_logging as logging from adafruit_logging import FileHandler diff --git a/examples/logging_formatter_example.py b/examples/logging_formatter_example.py index ccd2eea..02dd640 100644 --- a/examples/logging_formatter_example.py +++ b/examples/logging_formatter_example.py @@ -34,9 +34,7 @@ logger.info("Custom formatter example") -bracket_timestamp_formatter = logging.Formatter( - fmt="{asctime} {levelname}: {message}", style="{" -) +bracket_timestamp_formatter = logging.Formatter(fmt="{asctime} {levelname}: {message}", style="{") print_handler.setFormatter(bracket_timestamp_formatter) logger.info("Timestamp formatter bracket style example") diff --git a/examples/logging_mqtt_handler.py b/examples/logging_mqtt_handler.py index 9db578e..6de8309 100755 --- a/examples/logging_mqtt_handler.py +++ b/examples/logging_mqtt_handler.py @@ -14,7 +14,6 @@ import adafruit_logging as logging # adafruit_logging defines log levels dynamically. -# pylint: disable=no-name-in-module from adafruit_logging import NOTSET, Handler, LogRecord diff --git a/examples/logging_simpletest.py b/examples/logging_simpletest.py index 3bb25e1..8bdda9b 100644 --- a/examples/logging_simpletest.py +++ b/examples/logging_simpletest.py @@ -1,9 +1,6 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT -# pylint:disable=undefined-variable,wildcard-import,no-name-in-module -# pylint:disable=no-member,invalid-name - """Briefly exercise the logger and null logger.""" import adafruit_logging as logging diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..73e9efc --- /dev/null +++ b/ruff.toml @@ -0,0 +1,108 @@ +# SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +target-version = "py38" +line-length = 100 + +[lint] +preview = true +select = ["I", "PL", "UP"] + +extend-select = [ + "D419", # empty-docstring + "E501", # line-too-long + "W291", # trailing-whitespace + "PLC0414", # useless-import-alias + "PLC2401", # non-ascii-name + "PLC2801", # unnecessary-dunder-call + "PLC3002", # unnecessary-direct-lambda-call + "E999", # syntax-error + "PLE0101", # return-in-init + "F706", # return-outside-function + "F704", # yield-outside-function + "PLE0116", # continue-in-finally + "PLE0117", # nonlocal-without-binding + "PLE0241", # duplicate-bases + "PLE0302", # unexpected-special-method-signature + "PLE0604", # invalid-all-object + "PLE0605", # invalid-all-format + "PLE0643", # potential-index-error + "PLE0704", # misplaced-bare-raise + "PLE1141", # dict-iter-missing-items + "PLE1142", # await-outside-async + "PLE1205", # logging-too-many-args + "PLE1206", # logging-too-few-args + "PLE1307", # bad-string-format-type + "PLE1310", # bad-str-strip-call + "PLE1507", # invalid-envvar-value + "PLE2502", # bidirectional-unicode + "PLE2510", # invalid-character-backspace + "PLE2512", # invalid-character-sub + "PLE2513", # invalid-character-esc + "PLE2514", # invalid-character-nul + "PLE2515", # invalid-character-zero-width-space + "PLR0124", # comparison-with-itself + "PLR0202", # no-classmethod-decorator + "PLR0203", # no-staticmethod-decorator + "UP004", # useless-object-inheritance + "PLR0206", # property-with-parameters + "PLR0904", # too-many-public-methods + "PLR0911", # too-many-return-statements + "PLR0912", # too-many-branches + "PLR0913", # too-many-arguments + "PLR0914", # too-many-locals + "PLR0915", # too-many-statements + "PLR0916", # too-many-boolean-expressions + "PLR1702", # too-many-nested-blocks + "PLR1704", # redefined-argument-from-local + "PLR1711", # useless-return + "C416", # unnecessary-comprehension + "PLR1733", # unnecessary-dict-index-lookup + "PLR1736", # unnecessary-list-index-lookup + + # ruff reports this rule is unstable + #"PLR6301", # no-self-use + + "PLW0108", # unnecessary-lambda + "PLW0120", # useless-else-on-loop + "PLW0127", # self-assigning-variable + "PLW0129", # assert-on-string-literal + "B033", # duplicate-value + "PLW0131", # named-expr-without-context + "PLW0245", # super-without-brackets + "PLW0406", # import-self + "PLW0602", # global-variable-not-assigned + "PLW0603", # global-statement + "PLW0604", # global-at-module-level + + # fails on the try: import typing used by libraries + #"F401", # unused-import + + "F841", # unused-variable + "E722", # bare-except + "PLW0711", # binary-op-exception + "PLW1501", # bad-open-mode + "PLW1508", # invalid-envvar-default + "PLW1509", # subprocess-popen-preexec-fn + "PLW2101", # useless-with-lock + "PLW3301", # nested-min-max +] + +ignore = [ + "PLR2004", # magic-value-comparison + "UP030", # format literals + "PLW1514", # unspecified-encoding + "PLR0913", # too-many-arguments + "PLR0915", # too-many-statements + "PLR0917", # too-many-positional-arguments + "PLR0904", # too-many-public-methods + "PLR0912", # too-many-branches + "PLR0916", # too-many-boolean-expressions + "PLR6301", # could-be-static no-self-use + "PLC0415", # import outside toplevel + "PLC2701", # private import +] + +[format] +line-ending = "lf" From 3799989fbfec37d7c8af8d05ad923efbceb24dff Mon Sep 17 00:00:00 2001 From: foamyguy Date: Wed, 4 Jun 2025 10:00:20 -0500 Subject: [PATCH 159/162] update rtd.yml file Signed-off-by: foamyguy --- .readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 88bca9f..255dafd 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -12,7 +12,7 @@ sphinx: configuration: docs/conf.py build: - os: ubuntu-20.04 + os: ubuntu-lts-latest tools: python: "3" From 65a3d8a1f5f5fbd016493bacebeba3b6222f066c Mon Sep 17 00:00:00 2001 From: Doug Wegscheid Date: Tue, 23 Sep 2025 11:21:45 -0400 Subject: [PATCH 160/162] Add module-level .log functions. --- adafruit_logging.py | 48 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/adafruit_logging.py b/adafruit_logging.py index 5aba5f4..ac0dc19 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -617,3 +617,51 @@ def exception(self, err: Exception) -> None: # so we can't add the indent in the above line - needs to be done separately lines = lines.replace("\n", "\n ") self._log(ERROR, lines) + +def critical(msg, *args, **kwargs): + """ + Log a message with severity 'CRITICAL' on the root logger. + """ + getLogger().critical(msg, *args, **kwargs) + +def fatal(msg, *args, **kwargs): + """ + Don't use this function, use critical() instead. + """ + critical(msg, *args, **kwargs) + +def error(msg, *args, **kwargs): + """ + Log a message with severity 'ERROR' on the root logger. + """ + getLogger().error(msg, *args, **kwargs) + +def warning(msg, *args, **kwargs): + """ + Log a message with severity 'WARNING' on the root logger. + """ + getLogger().warning(msg, *args, **kwargs) + +def warn(msg, *args, **kwargs): + """ + Don't use this function, use warning() instead. + """ + warning(msg, *args, **kwargs) + +def info(msg, *args, **kwargs): + """ + Log a message with severity 'INFO' on the root logger. + """ + getLogger().info(msg, *args, **kwargs) + +def debug(msg, *args, **kwargs): + """ + Log a message with severity 'DEBUG' on the root logger. + """ + getLogger().debug(msg, *args, **kwargs) + +def log(level, msg, *args, **kwargs): + """ + Log 'msg % args' with the integer severity 'level' on the root logger. + """ + getLogger().log(level, msg, *args, **kwargs) From 539f85ddebadc50a24ed360eed9d1360a06ae47d Mon Sep 17 00:00:00 2001 From: Doug Wegscheid Date: Tue, 23 Sep 2025 11:39:16 -0400 Subject: [PATCH 161/162] ruff... --- adafruit_logging.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/adafruit_logging.py b/adafruit_logging.py index ac0dc19..0b135aa 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -618,48 +618,56 @@ def exception(self, err: Exception) -> None: lines = lines.replace("\n", "\n ") self._log(ERROR, lines) + def critical(msg, *args, **kwargs): """ Log a message with severity 'CRITICAL' on the root logger. """ getLogger().critical(msg, *args, **kwargs) + def fatal(msg, *args, **kwargs): """ Don't use this function, use critical() instead. """ critical(msg, *args, **kwargs) + def error(msg, *args, **kwargs): """ Log a message with severity 'ERROR' on the root logger. """ getLogger().error(msg, *args, **kwargs) + def warning(msg, *args, **kwargs): """ Log a message with severity 'WARNING' on the root logger. """ getLogger().warning(msg, *args, **kwargs) + def warn(msg, *args, **kwargs): """ Don't use this function, use warning() instead. """ warning(msg, *args, **kwargs) + def info(msg, *args, **kwargs): """ Log a message with severity 'INFO' on the root logger. """ getLogger().info(msg, *args, **kwargs) + def debug(msg, *args, **kwargs): """ Log a message with severity 'DEBUG' on the root logger. """ getLogger().debug(msg, *args, **kwargs) + def log(level, msg, *args, **kwargs): """ Log 'msg % args' with the integer severity 'level' on the root logger. From 24c00a78a6ee6a41a87a8675e75742f990f1ee94 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Fri, 10 Oct 2025 16:18:40 -0500 Subject: [PATCH 162/162] remove deprecated ruff rule, workaround RTD theme property inline issue. Signed-off-by: foamyguy --- docs/_static/custom.css | 8 ++++++++ docs/conf.py | 3 +++ ruff.toml | 1 - 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 docs/_static/custom.css diff --git a/docs/_static/custom.css b/docs/_static/custom.css new file mode 100644 index 0000000..d60cf4b --- /dev/null +++ b/docs/_static/custom.css @@ -0,0 +1,8 @@ +/* SPDX-FileCopyrightText: 2025 Sam Blenny + * SPDX-License-Identifier: MIT + */ + +/* Monkey patch the rtd theme to prevent horizontal stacking of short items + * see https://github.com/readthedocs/sphinx_rtd_theme/issues/1301 + */ +.py.property{display: block !important;} diff --git a/docs/conf.py b/docs/conf.py index a7b784c..c1dd48b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -106,6 +106,9 @@ # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] +# Include extra css to work around rtd theme glitches +html_css_files = ["custom.css"] + # The name of an image file (relative to this directory) to use as a favicon of # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. diff --git a/ruff.toml b/ruff.toml index 73e9efc..2cc8fc7 100644 --- a/ruff.toml +++ b/ruff.toml @@ -17,7 +17,6 @@ extend-select = [ "PLC2401", # non-ascii-name "PLC2801", # unnecessary-dunder-call "PLC3002", # unnecessary-direct-lambda-call - "E999", # syntax-error "PLE0101", # return-in-init "F706", # return-outside-function "F704", # yield-outside-function