usage

register functions with sutools

Using the register decorator @su.register on your functions will register it with sutools meta_handler. Stored functions are available across tools. This registry is intended to be used by logger and cli utilities.

import sutools as su

@su.register
def add(x : int, y : int):
    '''add two integers'''
    return x + y

You can also register async functions, these will be executed using asyncio.run() given a valid coroutine function

import sutools as su
import asyncio

@su.register
async def delay_add(x : int, y : int):
    '''add two integers after 1 sec'''
    await asyncio.sleep(1)
    return x + y

NOTE: Adding type hinting to your functions enforces types in the cli and adds positional arg class identifiers in the help docs for the command.

cli - initialization standard

It is suggested to define the command line interface after if __name__ == ‘__main__’. Any code before the cli will run even if a cli command is used; code after the cli definition will not run when passing a cli command.

import sutools as su

# registered functions...

su.logger(*args, **kwargs) # optional

# module level function calls...

if __name__ == '__main__':
    # main code (will run even when using cli commands)...
    su.cli(*args, **kwargs)
    # main code (will NOT run when using cli commands)...

NOTE: The CLI should be defined after the logger if you choose to use the two utilities in parallel.

cli - usage example

The logger utility should be instantiated after any registered functions but before any module level functions.

"""This module does random stuff."""
import sutools as su

@su.register
def meet(name : str, greeting : str = 'hello', farewell : str = 'goodbye') -> str:
    '''meet a person'''
    output = f'\n{greeting} {name}\n{farewell} {name}'
    su.log().meeting.info(output)
    return output

    su.logger() # optional

# module level function calls...

if __name__ == '__main__':
    # main code (will run even when using cli commands)...
    su.cli(desc = __doc__)
    # main code (will NOT run when using cli commands)...

NOTE: Adding type hinting to your functions enforces types in the cli and adds positional arg class identifiers in the help docs for the command.

command usage:

python module.py meet foo

output:

hello foo
goodbye foo

module help output:

usage: module [-h] {meet} ...

This module does random stuff.

options:
-h, --help  show this help message and exit

commands:
{meet}
    meet      meet a person

command help output:

usage: module meet [-gr <class 'str'>] [-fa <class 'str'>] [-h] name

meet(name: str, greeting: str = 'hello', farewell: str = 'goodbye') -> str

positional arguments:
name                  <class 'str'>

options:
-gr <class 'str'>, --greeting <class 'str'>
                        default: hello
-fa <class 'str'>, --farewell <class 'str'>
                        default: goodbye
-h, --help            Show this help message and exit.

cli - using variadic functions

Variadic functions are compatible with sutools cli utility. When calling kwargs from the cli; key=value should be used instead of and -, these are reserved for default arguments.

NOTE: since input is from stdin it will always be of type <string>, sutools will not infer the data type you must infer your needed type within the function.

"""This module does random stuff."""
import sutools as su

@su.register
def variadic(*args, **kwargs):

    print("Positional arguments:")
    for arg in args:
        print(arg)

    print("Keyword arguments:")
    for key, value in kwargs.items():
        print(f"{key} = {value}")

    su.logger() # optional

# module level function calls...

if __name__ == '__main__':
    # main code (will run even when using cli commands)...
    su.cli(desc = __doc__)
    # main code (will NOT run when using cli commands)...

command usage:

python module.py variadic 1 2 3 foo=1 bar=2

output:

Positional arguments:
1
2
3
Keyword arguments:
foo = 1
bar = 2

logger - initialization standard

The logger utility should be instantiated after any registered functions but before any module level functions.

import sutools as su

# registered functions...

su.logger(*args, **kwargs)

# module level function calls...

if __name__ == '__main__':
    # main code (will run even when using cli commands)...
    su.cli(*args, **kwargs) # optional
    # main code (will NOT run when using cli commands)...

logger - usage examples

accessing defined loggers is done with a log() helper function. Note the use of su.log() in the below functions to access a specified logger before defining the log level and message.

using registered function names

import sutools as su

@su.register
def add(x : int, y : int):
    '''add two integers'''
    su.log().add.info(f'{x} + {y} = {x+y}')
    return x + y

@su.register
def subtract(x : int, y : int):
    '''subtract two integers'''
    su.log().subtract.info(f'{x} - {y} = {x-y}')
    return x - y

su.logger() # logger definition

# module level function calls
add(1,2)
subtract(1,2)

if __name__ == '__main__':
    # main code (will run even when using cli commands)...
    su.cli() # optional
    # main code (will NOT run when using cli commands)...

log output

16:16:34, 961 add INFO 1 + 2 = 3
16:16:34, 961 subtract INFO 1 - 2 = -1

using custom logger names

import sutools as su

@su.register
def add(x : int, y : int):
    '''add two integers'''
    su.log().logger1.info(f'{x} + {y} = {x+y}')
    return x + y

@su.register
def subtract(x : int, y : int):
    '''subtract two integers'''
    su.log().logger2.info(f'{x} - {y} = {x-y}')
    return x - y

su.logger(loggers=['logger1','logger2']) # logger definition

# module level function calls
add(1,2)
subtract(1,2)

if __name__ == '__main__':
    # main code (will run even when using cli commands)...
    su.cli() # optional
    # main code (will NOT run when using cli commands)...

log output

16:16:34, 961 add INFO 1 + 2 = 3
16:16:34, 961 subtract INFO 1 - 2 = -1

benchy - usage example

The benchy decorator is designed to collect performance timing and call info for selected functions. This can be used in combination with @su.register, the decorators are order independent.

import sutools as su

@su.benchy
@su.register
def add(x : int, y : int):
    '''add two integers'''
    return x + y

@su.register
@su.benchy
def subtract(x : int, y : int):
    '''subtract two integers'''
    return x - y

@su.benchy
@su.register
def calc(x : int, y : int, atype : str = '+') -> int:
    '''caclulates a thing'''
    if atype == '+':
        res = add(x, y)
    elif atype == '-':
        res = subtract(x, y)
    return res

add(1,2)
add(2,2)
subtract(1,2)
calc(2,3, atype='-')

After the functions have been executed, the benchmark report can be accessed with su.benchy.report.

# print the benchmark report
print(su.benchy.report)

example output

{'add': [{'args': [{'type': 'int', 'value': 1}, {'type': 'int', 'value': 2}],
        'benchmark': 0.00015466799959540367,
        'kwargs': None,
        'result': {'type': 'int', 'value': 3}},
        {'args': [{'type': 'int', 'value': 2}, {'type': 'int', 'value': 2}],
        'benchmark': 6.068096263334155e-05,
        'kwargs': None,
        'result': {'type': 'int', 'value': 4}}],
'calc': [{'args': [{'type': 'int', 'value': 2}, {'type': 'int', 'value': 3}],
        'benchmark': 4.855601582676172e-05,
        'kwargs': {'atype': {'length': 1, 'type': 'str'}},
        'result': {'type': 'int', 'value': 5}}],
'subtract': [{'args': [{'type': 'int', 'value': 1}, {'type': 'int', 'value': 2}],
        'benchmark': 5.205394700169563e-05,
        'kwargs': None,
        'result': {'type': 'int', 'value': -1}}]}

The output of the benchmark report will adhere to the following format. function > call records. Call records consist of {args, kwargs, result, benchmark} there will be a record for each call of a given function.

NOTE: given an iterable for arg, kwarg, or result the object will be summarized in terms of vector length.

{'function_name': [{'args': [{'type': 'arg_type', 'value': int}]
                    'benchmark': float,
                    'kwargs': {'kwarg_name': {'type': 'arg_type', 'length': int, }}
                    'result': {'type': 'arg_type', 'value': float}}]}