"""
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 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()