Skip to content

Commit

Permalink
Merge 37b2d7c into 28b354f
Browse files Browse the repository at this point in the history
  • Loading branch information
Kilo59 committed Apr 28, 2018
2 parents 28b354f + 37b2d7c commit ff7165d
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 37 deletions.
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# bt_expense folders and files to ignore
*.xlsx
!Expenses.xlsx
!Expenses_Template.xlsx

# Byte-compiled / optimized / DLL files
__pycache__/
Expand Down Expand Up @@ -110,3 +110,7 @@ ENV/
*.PNG

*.pdf

\.pytest_cache/v/cache/

*.jpg
4 changes: 3 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ env:
- CC_TEST_REPORTER_ID=6ea71d63c94102cdc54e78a76a58e3803e36f5247d1ba4cc04a1ed9af2e5623b
language: python
python:
- "3.4"
- "3.5"
- "3.6"
# - "3.7-dev"
Expand All @@ -20,8 +21,9 @@ before_script:
- ./cc-test-reporter before-build
# command to run tests
script:
- pwd
- ls
- pytest --cov-report term --cov-report xml --cov=bt_expense tests/
- pytest -v --cov-report term --cov-report xml --cov=bt_expense tests/
after_script:
- ./cc-test-reporter after-build -t coverage.py --exit-code $TRAVIS_TEST_RESULT
after_success:
Expand Down
Binary file added bt_expense/Expenses_Template.xlsx
Binary file not shown.
119 changes: 98 additions & 21 deletions bt_expense/bt_expense.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
Pull expenses from Excel Spreadsheet and upload to BigTime via REST HTTP call.
"""
import os
import json
from pprint import pprint as pp
# import openpyxl as opxl
# import requests as r
# from pprint import pformat as pf
import requests as r
from openpyxl import load_workbook

# CD
os.chdir('bt_expense')
# os.chdir('bt_expense')

# Constants
BASE = 'https://iq.bigtime.net/BigtimeData/api/v2'
Expand All @@ -20,8 +21,77 @@
'cat': {}, }


class Authorizer(object):
"""authorizes a BitTime REST API session.
Can authorize using a user login and password or an API key.
User login and password will be used to obtain an API key.
If API key is provided, skip the step of obtaining API key"""

def __init__(self, workbook_filename='Expenses.xlsx', staffsid=None):
self.wb_name = workbook_filename
self.staffsid = staffsid
self.auth_header = self._build_credentials()
self.userid = self.auth_header['userid']
self.userpwd = self.auth_header['pwd']
self.api_key = None
self._authorized = False
self.header = self.authorize_session()

def _build_credentials(self):
"""Pulls Login information from the `Setup` worksheet. Return dictionary
for Auth Header."""
keys = get_values('Setup', 'A1', 'A4', workbook_name=self.wb_name)
values = get_values('Setup', 'B1', 'B4', workbook_name=self.wb_name)
header = {k: v for (k, v) in zip(keys, values)}
header['Content-Type'] = 'application/json'
return header

def authorize_session(self):
response = r.post('{}/session'.format(BASE),
headers={'Content-Type': 'application/json'},
data=json.dumps(self.auth_header).encode('utf-8'))
if str(response.status_code)[0] is not '2':
raise(ConnectionRefusedError)
response_dict = json.loads(response.text)
self.api_key = response_dict['token']
self.staffsid = response_dict['staffsid']
header = {'X-Auth-Token': self.api_key,
'X-Auth-Realm': self.auth_header['Firm'],
'Content-Type': self.auth_header['Content-Type']}
self._authorized = True
print('Session Header\n', header)
return header


class Expensor(Authorizer):

def post_expenses(self):
projs = get_values('Expenses', 'F2', 'F121')
cats = get_values('Expenses', 'G2', 'G121')
dates = get_values('Expenses', 'C2', 'C121')
costs = get_values('Expenses', 'D2', 'D121')
notes = get_values('Expenses', 'E2', 'E121')

expense_url = '{}/expense/detail'.format(BASE)
for proj, cat, date, cost, note in zip(projs, cats,
dates, costs, notes):
# print(str(date)[:10])
content = {'staffsid': int(self.staffsid),
'projectsid': int(proj),
'catsid': int(cat),
'dt': str(date)[:10],
'CostIN': cost,
'Nt': note,
'notes': 'March Expense',
'ApprovalStatus': 0}
print(r.post(expense_url, headers=self.header,
data=json.dumps(content).encode()))
return len(costs)


def get_wb(workbook_name='Expenses.xlsx'):
return load_workbook(filename=workbook_name)
return load_workbook(filename=workbook_name, data_only=True)


def build_lookup_dictn_from_excel():
Expand All @@ -35,42 +105,49 @@ def build_lookup_dictn_from_excel():
return project_ids, category_ids


def get_values(sheet_name, start, stop=None):
def get_values(sheet_name, start, stop=None, workbook_name='Expenses.xlsx'):
"""Pulls a column (or section) of values from a Worksheet.
Returns a list."""
values = []
sheet = get_wb()[sheet_name]
sheet = get_wb(workbook_name)[sheet_name]
if not stop:
stop = sheet.max_row
cells = [c[0].value for c in sheet[start:stop]]
values = [c for c in cells if c is not None]
return values


def build_credentials():
"""Pulls Login information from the `Setup` worksheet. Return dictionary
for Auth Header."""
keys = get_values('Setup', 'A1', 'A4')
values = get_values('Setup', 'B1', 'B4')
header = {k: v for (k, v) in zip(keys, values)}
# TODO: Format for BigTime
return header


def get_picklist(picklist_name):
def get_picklist(auth_object, picklist_name):
"""Pulls a BigTime 'Picklist'
Use to build project and expense catagory lookup tables"""
Use to build project and expense catagory lookup tables.
Requires Admin account."""
# TODO: complete `get_picklist()` function
valid_picklists = ['projects', 'ExpenseCodes']
if picklist_name not in valid_picklists:
raise ValueError('Not a valid picklist')
header = build_credentials()
return picklist_name
pick_list_url = '{0}/picklist/{1}'.format(BASE, picklist_name)
print(pick_list_url)
response = r.get(pick_list_url, headers=auth_object.header)
return response.json()
# return response.json()


if __name__ == '__main__':
print(__doc__)
print('**DIR:', os.getcwd())
build_lookup_dictn_from_excel()
pp(BT_LOOKUP)
pp(build_credentials())
# pp(build_credentials())
NRC_AUTH = Authorizer()
pp(NRC_AUTH.auth_header)
pp(NRC_AUTH.api_key)
print('*' * 79)
# expense_codes = get_picklist(NRC_AUTH, 'ExpenseCodes')
# with open('expense_codes.csv', 'w') as f_out:
# f_out.write('Id,Name')
# for expense_object in expense_codes:
# f_out.write('{},{}\n'.format(expense_object['Id'],
# expense_object['Name']))
exp1 = Expensor(staffsid=859)
pp(exp1.header)
# pp(exp1.post_expenses())
8 changes: 6 additions & 2 deletions tests/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,19 @@
"""
import os
import sys
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../bt_expense')))
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__),
'../bt_expense')))

import bt_expense


def fixpath(path):
path = os.path.normpath(os.path.expanduser(path))
if path.startswith("\\"): return "C:" + path
if path.startswith("\\"):
return "C:" + path
return path


print('USING context.py')

if __name__ == '__main__':
Expand Down
41 changes: 29 additions & 12 deletions tests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,52 @@
"""
import os
import unittest

import pytest
from sys import version_info
# import pytest

from context import bt_expense as bte
from context import fixpath

PYTHON_VER = version_info[0]

TEST_DIR = fixpath(os.path.abspath(os.path.dirname(__file__)))
ROOT_DIR = fixpath(os.path.dirname(TEST_DIR))
MAIN_DIR = fixpath('{}/bt_expense'.format(ROOT_DIR))
WORKBOOK_NAME = 'bt_expense/Expenses_Template.xlsx'


def test_pulling_column_values():
try:
os.chdir(MAIN_DIR)
except FileNotFoundError:
pass
a1 = bte.get_values('Expenses', 'A1',
workbook_name='Expenses_Template.xlsx')[0]
assert a1, 'Project'
os.chdir(ROOT_DIR)

class SmokeTest(unittest.TestCase):
"""Test that nothing is on fire."""
def setUp(self):
os.chdir(TEST_DIR)

def tearDown(self):
os.chdir(ROOT_DIR)
def test_pulling_auth_info():
expected_keys = ['userid', 'pwd', 'Firm', 'AuthType']
actual_keys = bte.get_values('Setup', 'A1', 'A4',
workbook_name=WORKBOOK_NAME)
for key in expected_keys:
assert key in actual_keys

def test_pulling_column_values(self):
os.chdir(MAIN_DIR)
a1 = bte.get_values('Expenses', 'A1')[0]
self.assertEqual(a1, 'Project')

def test_authorizer_object_creation():
try:
bte.Authorizer(workbook_filename=WORKBOOK_NAME)
except ConnectionRefusedError as E:
print(E)


if __name__ == "__main__":
print('Python {}'.format(PYTHON_VER))
print(__doc__)
print(__file__)
print('root:', ROOT_DIR)
print('test:', TEST_DIR)
print('main:', MAIN_DIR)
unittest.main()
pytest.main(args=['-v'])

0 comments on commit ff7165d

Please sign in to comment.