Source code for pychex.cli

"""
Pychex command-line interface

Usage:
  pychex authorize <username> [--config=<config_file>]
  pychex account_summary [--config=<config_file>] [--json]
  pychex --version
  pychex (--help | -h)

Options:
  -h --help               Show this screen.
  --version               Show the version.
  --config=<config_file>  The config file to use [default: ./pychex.cfg]
  --json                  Optionally display output as JSON
"""
from __future__ import absolute_import

import getpass
import json
import os
import requests

from base64 import b64decode, b64encode
from docopt import docopt
from PIL import Image

from . import __version__
from .paychex import BenefitsOnline, Paychex
from .exceptions import PychexInvalidPasswordError, PychexNoBolUsernameError

try:
    import configparser
    from io import BytesIO
except ImportError:  # Python 2.x fallback
    import ConfigParser as configparser
    from StringIO import StringIO as BytesIO

try:
    input = raw_input
except NameError:  # Python > 3.1 has no raw_input
    pass


[docs]class PychexCli: """ This is the command line interface reference implementation of the API. It is not meant for consumption by third parties. """ def __init__(self, arguments): """ Initializes a config file and credentials (username, bol_username, security_image_path, and password), then calls the appropriate method to handle the command requested by the user. Arguments: * *arguments* -- a dictionary of command line arguments generated by docopt. """ self.config_file = arguments['--config'] self.config = configparser.ConfigParser() self.creds = ['username', 'bol_username', 'security_image_path', 'password'] if arguments['authorize']: self.authorize(arguments) elif arguments['account_summary']: try: self.read_config() except (configparser.NoOptionError, configparser.NoSectionError, IOError): print('Error reading credentials, please run: ' 'pychex authenticate <username>') else: self.get_account_summary(arguments['--json'])
[docs] def get_input(self, text): """ Get's input from the command line Arguments: * *text* -- the prompt to display to the user """ return input(text).lower()
[docs] def read_config(self): """ Reads credentials from a config file, decrypts them, and saves them into member variables. """ with open(self.config_file) as cfg: self.config.readfp(cfg) for attr in self.creds: encoded_attr = self.config.get('pychex', attr).encode('utf8') setattr(self, attr, b64decode(encoded_attr).decode('utf8'))
[docs] def write_config(self): """ Encrypts credentials stored in member variables, and writes them to a config file. """ self.config.add_section('pychex') for attr in self.creds: encoded_attr = b64encode(getattr(self, attr).encode('utf8')) self.config.set('pychex', attr, encoded_attr.decode('utf8')) with open(self.config_file, 'w') as cfg: self.config.write(cfg)
[docs] def authorize(self, arguments): """ Authorizes a user with Paychex. Takes the following steps: * Collects the user's ``username`` * Posts the ``username`` to the Paychex server to get the security image * Displays the security image to the user for verification using the default image viewer * Collects the user's ``password`` * Logs in to Paychex and makes a SOAP request to get the user's Benefits OnLine username * Encrypts and stores the credentials to a config file Arguments: * *arguments* -- The same dictionary passed to the ``__init__`` method """ self.username = arguments['<username>'] paychex = Paychex(self.username) paychex.post_username() img_dat = requests.get(paychex.get_security_image()).content # Show the security image to the user for recognition Image.open(BytesIO(img_dat)).resize((256, 256)).show() choice = self.get_input('Is this your security image (Y/n)? ') # input returns the empty string for "enter" chose_yes = choice in set(['yes', 'y', 'ye', '']) chose_no = choice in set(['no', 'n']) if chose_no: print('Security image mismatch.') elif not chose_yes: print('Please respond with "yes" or "no".') else: # Get the password and write the credentials to a file self.password = getpass.getpass('Password (input hidden): ') self.security_image_path = paychex.security_image_path # Login and retrieve the BOL username for later use try: paychex.login(self.password) except PychexInvalidPasswordError: print('The supplied password is invalid') return try: self.bol_username = paychex.get_bol_username() except PychexNoBolUsernameError: print("It looks like you don't have BOL enabled") else: self.write_config() print('Credentials have been written to %s' % self.config_file)
[docs] def get_account_summary(self, as_json): """ Log in to Benefits OnLine, get the account summary and display it to the console. Arguments * *as_json* -- Output the account summary in JSON format. Useful if a program such as a bash script will be used to do further data processing. Default is ``False`` and the summary will be printed in a table that is more palatable to a human. """ # Login to Benefits OnLine benefits = BenefitsOnline(self.bol_username, self.password) benefits.login() # Login to the retirement services app and get the account summary retirement = benefits.retirement_services retirement.login() retirement.get_account_summary() if as_json: print(json.dumps({ 'current_balance': retirement.current_balance, 'vested_balance': retirement.vested_balance, 'personal_ror': retirement.personal_ror, 'balance_tab_info': retirement.balance_tab_info })) else: print('Current balance: %s' % retirement.current_balance) print('Vested balance: %s' % retirement.vested_balance) print('Personal RoR: %s\n' % retirement.personal_ror) print(retirement.formatted_summary())
def main(): arguments = docopt(__doc__, version='Pychex %s' % __version__) PychexCli(arguments) if __name__ == '__main__': main()