avilabsutils

Common utilities used by Avilay Labs


License
MIT
Install
pip install avilabsutils==1.2.1

Documentation

AviLabsUtils - grab bag of useful little functions

This package has the following utilities -

  • printer - various enhancements to the builtin print function
  • json_converter - adds support for datetime objects to the builtin json module
  • messenger - utility to send texts and emails
  • decorators - various function decorators for argument validation, retry logic, etc.

Installation

pip install avilabsutils

Printer

Use the printer module to print in various colors and flush the stdout buffer immediately.

Color Print

from avilabsutils import print_success, print_code, print_warn, print_error

print_success('This will print in green')
print_code('This will print in cyan')
print_warn('This will be in yellow')
print_error('And this will be in red')

Flush stdout

In long running processes when you want to print out intermediate progress messages, using the builtin print does not work because it will buffer the messages. In order to flush the stdout buffer immediately use print_now.

from avilabsutils import print_now

print_now('This will be printed immediately')

JsonConverter

For some strange reason the builtin json module does not handle datetime objects well. Use JsonConverter when your objects have datetime attributes.

from datetime import datetime, timezone
import json
from avilabsutils import JsonConverter

obj = {
    'user_id': 1,
    'created_on': datetime(2016, 1, 1, tzinfo=timezone.utc),
    'name': 'Cookie Monster'
}
print(json.dumps(obj, cls=JsonConverter))

This will print -

{"name": "Cookie Monster", "created_on": "2016-01-01T00:00:00+00:00", "user_id": 1}

Messenger

Use the Messenger object to send texts and emails.

from avilabsutils import Messenger

msngr = Messenger()  ## Make sure your SMTP creds are set

msngr.text(
    number=recipients_ten_digit_phone_num, 
    carrier='att', 
    msg='hello its me'
)
    
msngr.email(
    to='recipient@example.com', 
    sub='yo', 
    msg='how are you doing?'
)

Currently the following U.S carriers are supported -

  • ATT (carrier='att')
  • T-Mobile (carrier='t-mobile')
  • Verizon (carrier='verizon')
  • Sprint (carrier='sprint')

Set up SMTP creds

To use Messenger you need to your SMTP credentials. These can be set one of three ways -

  1. In environment variables
  2. In a hidden file
  3. Passed to the Messenger object

When Messenger object is created without any params, it will automatically look for creds - first in the environment variables, failing that, next in the hidden file.

Environment Variables

Set up SMTP_HOST, SMTP_EMAIL, and SMTP_PASSWORD. Below is an example for gmail, but you can use any smtp server.

export SMTP_HOST=smtp.gmail.com
export SMTP_PORT=587
export SMTP_EMAIL=<name>@gmail.com
export SMTP_PASSWORD=<your gmail password>

File

Create a file called .smtp_creds in your home directory. Below are the example contents for gmail, but you can use any smtp server. Contents of .smtp_creds -

[SMTP]
host: smtp.gmail.com
port: 587
email: <name>@gmail.com
password: <your gmail password>

Messenger Object

from avilabsutils import SmtpCreds, Messenger

smtp_creds = SmtpCreds(
    host='smtp.gmail.com',
    port=587,
    email='<name>@gmail.com',
    password='<your gmail password>'
)
msngr = Messenger(smtp_creds)
msngr.email('recipient@example.com', 'yo', 'how are you doing?')

Decorators

Debug decorator

This decorator will automatically log all incoming calls to the decorated function along with its input and output.

import logging
from avilabsutils.decorators import debug

logging.basicConfig(level=logging.DEBUG)


@debug
def funky_add(a, b):
    if a > 100 and b > 100:
        return a + b
    elif 50 < a < 100 and 50 < b < 100:
        return a * b
    else:
        return a - b


funky_add(10, 20)
funky_add(60, 70)
funky_add(200, 300)

This will print the following to stdout.

DEBUG:__main__:funky_add(10,20) = -10
DEBUG:__main__:funky_add(60,70) = 4200
DEBUG:__main__:funky_add(200,300) = 500

Of course, if you configure logger with a custom configuration instead of using the builtin basicConfig method, it will log to a file or any other appender.

Retry

This decorator will automatically retry the decorated function a set number of times before erroring out. If the function succeeds then it will return the value. If the function continues to fail even after retries, it will throw an exception. If no retry parameter is passed, it will retry 3 times by default.

import random
from avilabsutils.decorators import retry, MaxRetriesExceededError


@retry(max_retries=2)
def unpredictable():
    if random.choice([True, False]):
        return 42
    else:
        raise RuntimeError('KA-BOOM!!')


for i in range(5):
    try:
        print(unpredictable())
    except MaxRetriesExceededError as e:
        print(e)

On one of the runs on my computer I got the following output.

unpredictable() failed after 2 retries
42
42
42
unpredictable() failed after 2 retries

Validate input args

This decorator is used to validate input arguments to a function or a method. It comes in two flavors - one for use with functions, and another for use with methods. Their usage is exactly the same.

Validate function args

This decorator is used to apply validation rules to function arguments, including type checking.

from avilabsutils.decorators import validate_func_args, InvalidArgValueError, InvalidArgTypeError


@validate_func_args([
    ('tn', float, lambda x: x < 3.141),
    ('ary', list, lambda x: len(x) > 1),
    ('lang', str, None)
])
def foo(tn, ary, lang='Python'):
    print(tn, ary, lang)


foo(2.718, [1, 2], lang='C')

try:
    foo(6.626, [1, 2])
except InvalidArgValueError as e:
    print(e)

try:
    foo('pi', [1, 2])
except InvalidArgTypeError as e:
    print(e)

try:
    foo(2.718, [1, 2], lang=42)
except InvalidArgTypeError as e:
    print(e)

This will result in the following output -

2.718 [1, 2] C
Arg 0 has invalid value 6.626
Arg 0 must be of type <class 'float'>
Arg lang must be of type <class 'str'>

Notice how the lang argument is referred to by its name but other positional arguments are referred to by their positions.

Validate method args

This is the exact same as validate_func_args but is used for methods.

from avilabsutils.decorators import validate_method_args, InvalidArgValueError, InvalidArgTypeError


class Bar:
    @validate_method_args([
        ('tn', float, lambda x: x < 3.141),
        ('ary', list, lambda x: len(x) > 1),
        ('lang', str, None)
    ])
    def foo(self, tn, ary, lang):
        print(tn, ary, lang)


bar = Bar()

bar.foo(2.718, [1, 2], lang='C')

try:
    bar.foo(6.626, [1, 2])
except InvalidArgValueError as e:
    print(e)

try:
    bar.foo('pi', [1, 2])
except InvalidArgTypeError as e:
    print(e)
    
try:
    bar.foo(2.718, [1, 2], lang=42)
except InvalidArgTypeError as e:
    print(e)

This will result in the following output -

2.718 [1, 2] C
Arg 0 has invalid value 6.626
Arg 0 must be of type <class 'float'>
Arg lang must be of type <class 'str'>