import log21
log21.print(log21.get_color('#FF0000') + 'This' + log21.get_color((0, 255, 0)) + ' is' + log21.get_color('Blue') +
' Blue' + log21.get_colors('BackgroundWhite', 'Black') + ' 8)')
logger = log21.get_logger('My Logger', level_names={21: 'SpecialInfo', log21.WARNING: ' ! ', log21.ERROR: '!!!'})
logger.info('You are reading the README.md file...')
logger.log(21, 'Here', '%s', 'GO!', args=('we',))
logger.setLevel(log21.WARNING)
logger.warning("We can't log messages with a level less than 30 anymore!")
logger.debug("You won't see this!")
logger.info("Am I visible?")
logger.error(log21.get_colors('LightRed') + "I'm still here ;1")
Argument Parsing (See Also: Argumentify)
import log21
from log21 import ColorizingArgumentParser, get_logger, get_colors as gc
parser = ColorizingArgumentParser(description="This is a simple example of a ColorizingArgumentParser.",
colors={'help': 'LightCyan'})
parser.add_argument('test1', action='store', help='Test 1')
parser.add_argument('test2', action='store', help='Test 2')
parser.add_argument('--optional-arg', '-o', action='store', type=int, help='An optional integer')
parser.add_argument('--verbose', '-v', action='store_true', help='Increase verbosity.')
args = parser.parse_args()
logger = get_logger('My Logger', level_names={log21.DEBUG: ' ? ', log21.INFO: ' + ', log21.WARNING: ' ! ',
log21.ERROR: '!!!'})
if args.verbose:
logger.setLevel(log21.DEBUG)
else:
logger.setLevel(log21.INFO)
logger.debug(gc('LightBlue') + 'Verbose mode on!')
logger.debug('Arguments:\n'
'\tTest 1: %s\n'
'\tTest 2: %s\n'
'\tOptional: %s', args=(args.test1, args.test2, args.optional_arg))
logger.info(gc('LightGreen') + args.test1)
logger.info(gc('LightWhite') + 'Done!')
import json
import log21
data = json.load(open('json.json', 'r'))
# Prints data using python's built-in print function
print(data)
# Uses `log21.pprint` to print the data
log21.pprint(data)
# Uses `log21.tree_print` to print the data
log21.tree_print(data)
import log21
window = log21.get_logging_window('My Logging Window', width=80)
window.font = ('Courier New', 9)
# Basic logging
window.info('This is a basic logging message.')
# Using ANSI and HEX colors
# List of ANSI colors: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
# ANSI color format: \033[<attribute>m
window.info('\033[91mThis is RED message.')
window.info('\033[102mThis is message with GREEN background.')
# HEX color format: \033#<HEX-COLOR>hf (where f represents the foreground color) and
# \033#<HEX-COLOR>hb (where b represents the background color)
window.info('\x1b#009900hbThis is a text with GREEN background.')
window.info('\033#0000FFhf\033[103mThis is message with BLUE foreground and YELLOW background.')
import random, string
# And here is a text with random colors
text = 'I have random colors XD'
colored_text = ''
for character in text:
color = '\033#' + ''.join(random.choice(string.hexdigits) for _ in range(6)) + 'hf'
colored_text += color + character
window.error(colored_text)
# See more examples in
# https://github.com/MPCodeWriter21/log21/blob/066efc1e72542531012d36974bbf6cd4c5941378/log21/LoggingWindow.py#L155
# and
# https://github.com/MPCodeWriter21/log21/blob/066efc1e72542531012d36974bbf6cd4c5941378/log21/__init__.py#L144
# Example 1
import log21, time
# Define a very simple log21 progress bar
progress_bar = log21.ProgressBar()
# And here is a simple loop that will print the progress bar
for i in range(100):
progress_bar(i + 1, 100)
time.sleep(0.08)
# Example 2
import time, random
from log21 import ProgressBar, get_colors as gc
# Let's customize the progress bar a little bit this time
progress_bar = ProgressBar(
width=50,
fill='#',
empty='-',
prefix='[',
suffix=']',
colors={'progress in-progress': gc('Bright Red'), 'progress complete': gc('Bright Cyan'),
'percentage in-progress': gc('Green'), 'percentage complete': gc('Bright Cyan'),
'prefix-color in-progress': gc('Bright White'), 'prefix-color complete': gc('Bright White'),
'prefix-color failed': gc('Bright White'), 'suffix-color in-progress': gc('Bright White'),
'suffix-color complete': gc('Bright White'), 'suffix-color failed': gc('Bright White')})
for i in range(84):
progress_bar(i + 1, 84)
time.sleep(random.uniform(0.05, 0.21))
Argumentify (Check out the manual way)
# Common Section
import log21
class ReversedText:
def __init__(self, text: str):
self._text = text[::-1]
def __str__(self):
return self._text
def __repr__(self):
return f"<{self.__class__.__name__}(text='{self._text}') at {hex(id(self))}>"
# Old way
def main():
"""Here is my main function"""
parser = log21.ColorizingArgumentParser()
parser.add_argument('--positional-arg', '-p', action='store', type=int,
required=True, help="This argument is positional!")
parser.add_argument('--optional-arg', '-o', action='store', type=ReversedText,
help="Whatever you pass here will be REVERSED!")
parser.add_argument('--arg-with-default', '-a', action='store', default=21,
help="The default value is 21")
parser.add_argument('--additional-arg', '-A', action='store',
help="This one is extra.")
parser.add_argument('--verbose', '-v', action='store_true',
help="Increase verbosity")
args = parser.parse_args()
if args.verbose:
log21.basic_config(level='DEBUG')
log21.info(f"positional_arg = {args.positional_arg}")
log21.info(f"optional_arg = {args.optional_arg}")
log21.debug(f"arg_with_default = {args.arg_with_default}")
log21.debug(f"additional_arg = {args.additional_arg}")
if __name__ == '__main__':
main()
# New way
def main(positional_arg: int, /, optional_arg: ReversedText, arg_with_default: int = 21,
additional_arg=None, verbose: bool = False):
"""Some description
:param positional_arg: This argument is positional!
:param optional_arg: Whatever you pass here will be REVERSED!
:param arg_with_default: The default value is 21
:param additional_arg: This one is extra.
:param verbose: Increase verbosity
"""
if verbose:
log21.basic_config(level='DEBUG')
log21.info(f"{positional_arg = }")
log21.info(f"{optional_arg = !s}")
log21.debug(f"{arg_with_default = }")
log21.debug(f"{additional_arg = !s}")
if __name__ == '__main__':
log21.argumentify(main)
Example with multiple functions as entry-point:
import ast
import operator
from functools import reduce
import log21
# `safe_eval` Based on https://stackoverflow.com/a/9558001/1113207
# Supported Operators
operators = {
ast.Add: operator.add,
ast.Sub: operator.sub,
ast.Mult: operator.mul,
ast.Div: operator.truediv,
ast.FloorDiv: operator.floordiv,
ast.Pow: operator.pow,
ast.BitXor: operator.xor,
ast.USub: operator.neg
}
def safe_eval(expr: str):
"""Safely evaluate a mathematical expression.
>>> eval_expr('2^6')
4
>>> eval_expr('2**6')
64
>>> eval_expr('1 + 2*3**(4^5) / (6 + -7)')
-5.0
:param expr: expression to evaluate
:raises SyntaxError: on invalid expression
:return: result of the evaluation
"""
try:
return _eval(ast.parse(expr, mode='eval').body)
except (TypeError, KeyError, SyntaxError):
log21.error(f'Invalid expression: {expr}')
raise
def _eval(node: ast.AST):
"""Internal implementation of `safe_eval`.
:param node: AST node to evaluate
:raises TypeError: on invalid node
:raises KeyError: on invalid operator
:raises ZeroDivisionError: on division by zero
:raises ValueError: on invalid literal
:raises SyntaxError: on invalid syntax
:return: result of the evaluation
"""
if isinstance(node, ast.Num): # <number>
return node.n
if isinstance(node, ast.BinOp): # <left> <operator> <right>
return operators[type(node.op)](_eval(node.left), _eval(node.right))
if isinstance(node, ast.UnaryOp): # <operator> <operand> e.g., -1
return operators[type(node.op)](_eval(node.operand))
raise TypeError(node)
# Example code
def addition(*numbers: float):
"""Addition of numbers.
Args:
numbers (float): numbers to add
"""
if len(numbers) < 2:
log21.error('At least two numbers are required! Use `-n`.')
return
log21.info(f'Result: {sum(numbers)}')
def multiplication(*numbers: float):
"""Multiplication of numbers.
Args:
numbers (float): numbers to multiply
"""
if len(numbers) < 2:
log21.error('At least two numbers are required! Use `-n`.')
return
log21.info(f'Result: {reduce(lambda x, y: x * y, numbers)}')
def calc(*inputs: str, verbose: bool = False):
"""Calculate numbers.
:param inputs: numbers and operators
"""
expression = ' '.join(inputs)
if len(expression) < 3:
log21.error('At least two numbers and one operator are required! Use `-i`.')
return
if verbose:
log21.basic_config(level='DEBUG')
log21.debug(f'Expression: {expression}')
try:
log21.info(f'Result: {safe_eval(expression)}')
except (TypeError, KeyError, SyntaxError):
pass
if __name__ == "__main__":
log21.argumentify({'add': addition, 'mul': multiplication, 'calc': calc})
Example with parser errors:
# Common Section
import log21
class ReversedText:
def __init__(self, text: str):
self._text = text[::-1]
def __str__(self):
return self._text
def __repr__(self):
return f"<{self.__class__.__name__}(text='{self._text}') at {hex(id(self))}>"
# New way
def main(positional_arg: int, /, optional_arg: ReversedText, arg_with_default: int = 21,
additional_arg=None, verbose: bool = False, quiet: bool = False):
"""Some description
:param positional_arg: This argument is positional!
:param optional_arg: Whatever you pass here will be REVERSED!
:param arg_with_default: The default value is 21
:param additional_arg: This one is extra.
:param verbose: Increase verbosity
:param quiet: Make the script quiet
"""
if verbose and quiet:
raise log21.IncompatibleArguments(
'--verbose',
'--quiet',
message="You can not make the script quiet and except it to be more verbose!"
)
if verbose:
log21.basic_config(level='DEBUG')
if quiet:
log21.basic_config(level='WARN')
log21.info(f"{positional_arg = }")
log21.info(f"{optional_arg = !s}")
log21.warn("THIS IS IMPORTANT!!!")
log21.debug(f"{arg_with_default = }")
log21.debug(f"{additional_arg = !s}")
if __name__ == '__main__':
log21.argumentify(main)