Make logging optionally remove global handlers added by (shitty) pip
authorScott <[email protected]>
Fri, 7 Jan 2022 19:14:29 +0000 (11:14 -0800)
committerScott <[email protected]>
Fri, 7 Jan 2022 19:14:29 +0000 (11:14 -0800)
modules.  Make config optionally halt on unrecognized arguments.
Make profanity filter smarter.

bootstrap.py
config.py
list_utils.py
logging_utils.py
profanity_filter.py
site_config.py
smart_home/outlets.py

index 03bb505c6d3a1aed2a6177aba4fc3565d538fd17..7ed8b403d1108736eed4a39162bf248a48d811f1 100644 (file)
@@ -1,7 +1,6 @@
 #!/usr/bin/env python3
 
 import functools
-import importlib
 import logging
 import os
 from inspect import stack
@@ -55,6 +54,7 @@ args.add_argument(
 
 original_hook = sys.excepthook
 
+
 def handle_uncaught_exception(exc_type, exc_value, exc_tb):
     """
     Top-level exception handler for exceptions that make it past any exception
index ea5f68a296b66ea8946a61e6f85fe1891a0b33a8..dc0042d5b664228a67a04925435b2e9f83729386 100644 (file)
--- a/config.py
+++ b/config.py
@@ -133,6 +133,16 @@ group.add_argument(
     default=None,
     help='Populate config file compatible with --config_loadfile to save global config for later use.',
 )
+group.add_argument(
+    '--config_rejects_unrecognized_arguments',
+    default=False,
+    action='store_true',
+    help=(
+        'If present, config will raise an exception if it doesn\'t recognize an argument.  The ' +
+        'default behavior is to ignore this so as to allow interoperability with programs that ' +
+        'want to use their own argparse calls to parse their own, separate commandline args.'
+    )
+)
 
 
 def is_flag_already_in_argv(var: str):
@@ -249,6 +259,12 @@ def parse(entry_module: Optional[str]) -> Dict[str, Any]:
     # future argument parsers.  For example, unittest_main in python
     # has some of its own flags.  If we didn't recognize it, maybe
     # someone else will.
+    if len(unknown) > 0:
+        if config['config_rejects_unrecognized_arguments']:
+            raise Exception(
+                f'Encountered unrecognized config argument(s) {unknown} with --config_rejects_unrecognized_arguments enabled; halting.'
+            )
+        saved_messages.append(f'Config encountered unrecognized commandline arguments: {unknown}')
     sys.argv = sys.argv[:1] + unknown
 
     # Check for savefile and populate it if requested.
index 992f1ae4207228711c17573bb779cc0aaae2d0f8..88c436be24f88e73093f2c6f509a157e181ed6f9 100644 (file)
@@ -216,6 +216,7 @@ def permute(seq: Sequence[Any]):
     """
     yield from _permute(seq, "")
 
+
 def _permute(seq: Sequence[Any], path):
     if len(seq) == 0:
         yield path
@@ -228,7 +229,7 @@ def _permute(seq: Sequence[Any], path):
         yield from _permute(cdr, path + car)
 
 
-def binary_search(lst: Sequence[Any], target:Any) -> Tuple[bool, int]:
+def binary_search(lst: Sequence[Any], target: Any) -> Tuple[bool, int]:
     """Performs a binary search on lst (which must already be sorted).
     Returns a Tuple composed of a bool which indicates whether the
     target was found and an int which indicates the index closest to
index 278cbf0f14910e25af66e7852d51cb268eeea26c..005761a5cccf3d5e90bd9ff3020543aed6dcc59c 100644 (file)
@@ -143,6 +143,17 @@ cfg.add_argument(
         'module:function, or :function and <level> is a logging level (e.g. INFO, DEBUG...)'
     )
 )
+cfg.add_argument(
+    '--logging_clear_spammy_handlers',
+    action=argparse_utils.ActionNoYes,
+    default=False,
+    help=(
+        'Should logging code clear preexisting global logging handlers and thus insist that is ' +
+        'alone can add handlers.  Use this to work around annoying modules that insert global ' +
+        'handlers with formats and logging levels you might now want.  Caveat emptor, this may ' +
+        'cause you to miss logging messages.'
+    )
+)
 
 
 built_in_print = print
@@ -377,6 +388,12 @@ def initialize_logging(logger=None) -> logging.Logger:
     if logger is None:
         logger = logging.getLogger()       # Root logger
 
+    spammy_handlers = 0
+    if config.config['logging_clear_spammy_handlers']:
+        while logger.hasHandlers():
+            logger.removeHandler(logger.handlers[0])
+            spammy_handlers += 1
+
     if config.config['logging_config_file'] is not None:
         logging.config.fileConfig('logging.conf')
         return logger
@@ -410,7 +427,7 @@ def initialize_logging(logger=None) -> logging.Logger:
             if config.config['logging_syslog_facility']:
                 facility_name = 'LOG_' + config.config['logging_syslog_facility']
             facility = SysLogHandler.__dict__.get(facility_name, SysLogHandler.LOG_USER)
-            handler = SysLogHandler(facility=SysLogHandler.LOG_CRON, address='/dev/log')
+            handler = SysLogHandler(facility=facility, address='/dev/log')
             handler.setFormatter(
                 MillisecondAwareFormatter(
                     fmt=fmt,
@@ -485,6 +502,36 @@ def initialize_logging(logger=None) -> logging.Logger:
             built_in_print(*arg, **kwarg)
         builtins.print = print_and_also_log
 
+    logger.debug(f'Initialized logger; default logging level is {default_logging_level}.')
+    if config.config['logging_clear_spammy_handlers'] and spammy_handlers > 0:
+        logger.warning(
+            'Logging cleared {spammy_handlers} global handlers (--logging_clear_spammy_handlers)'
+        )
+    logger.debug(f'Logging format is "{fmt}"')
+    if config.config['logging_syslog']:
+        logger.debug(f'Logging to syslog as {facility_name} with normal severity mapping')
+    if config.config['logging_filename']:
+        logger.debug(f'Logging to filename {config.config["logging_filename"]} with rotation')
+    if config.config['logging_console']:
+        logger.debug(f'Logging to the console.')
+    if config.config['logging_info_is_print']:
+        logger.debug(
+            'Logging logger.info messages will be repeated on stdout (--logging_info_is_print)'
+        )
+    if config.config['logging_squelch_repeats_enabled']:
+        logger.debug(
+            'Logging code is allowed to request repeated messages be squelched (--logging_squelch_repeats_enabled)'
+        )
+    if config.config['logging_probabilistically_enabled']:
+        logger.debug(
+            'Logging code is allowed to request probabilistic logging (--logging_probabilistically_enabled)'
+        )
+    if config.config['lmodule']:
+        logger.debug(
+            'Logging dynamic per-module logging enabled (--lmodule={config.config["lmodule"]})'
+        )
+    if config.config['logging_captures_prints']:
+        logger.debug('Logging will capture printed messages (--logging_captures_prints)')
     return logger
 
 
index 5621cef94489f6b5446a9e786777a8cb93e68be4..fe5422179ba9a50c678188e088689184f139a14d 100755 (executable)
@@ -347,6 +347,7 @@ class ProfanityFilter(object):
             'poop chute',
             'poopchute',
             'porn',
+            'pron',
             'pornhub',
             'porno',
             'pornographi',
@@ -471,6 +472,11 @@ class ProfanityFilter(object):
     def _normalize(self, text: str) -> str:
         result = text.lower()
         result = result.replace("_", " ")
+        result = result.replace('0', 'o')
+        result = result.replace('1', 'l')
+        result = result.replace('4', 'a')
+        result = result.replace('5', 's')
+        result = result.replace('3', 'e')
         for x in string.punctuation:
             result = result.replace(x, "")
         chunks = [
index caaf3d8c1c49d9e3d26e5ab8efde06c8461808a7..492623f4b855fc990883a706db93dfd5615921d0 100644 (file)
@@ -12,8 +12,8 @@ from type.locations import Location
 logger = logging.getLogger(__name__)
 
 args = config.add_commandline_args(
-    f'({__file__})',
-    'Args related to __file__'
+    f'Global Site Config ({__file__})',
+    f'Args related to global site-specific configuration'
 )
 args.add_argument(
     '--site_config_override_location',
index 68dfd2b8913216453a230ebdb2c2c90e6914e54a..8fd09487a48b5c49478bb5d84b91d481dc36adfc 100644 (file)
@@ -238,13 +238,13 @@ class GoogleOutlet(BaseOutlet):
 
 @decorator_utils.singleton
 class MerossWrapper(object):
-    """Note that instantiating this class causes HTTP traffic with an
-    external Meross server.  Meross blocks customers who hit their
-    servers too aggressively so MerossOutlet is lazy about creating
-    instances of this class.
+    """Global singleton helper class for MerossOutlets.  Note that
+    instantiating this class causes HTTP traffic with an external
+    Meross server.  Meross blocks customers who hit their servers too
+    aggressively so MerossOutlet is lazy about creating instances of
+    this class.
 
     """
-
     def __init__(self):
         self.loop = asyncio.get_event_loop()
         self.email = os.environ.get('MEROSS_EMAIL') or scott_secrets.MEROSS_EMAIL