modules. Make config optionally halt on unrecognized arguments.
Make profanity filter smarter.
#!/usr/bin/env python3
import functools
-import importlib
import logging
import os
from inspect import stack
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
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):
# 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.
"""
yield from _permute(seq, "")
+
def _permute(seq: Sequence[Any], path):
if len(seq) == 0:
yield 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
'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
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
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,
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
'poop chute',
'poopchute',
'porn',
+ 'pron',
'pornhub',
'porno',
'pornographi',
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 = [
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',
@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