Skip to content

Commit 51397c2

Browse files
Write output as CSV and add tests.
1 parent 2b4d56d commit 51397c2

File tree

4 files changed

+291
-96
lines changed

4 files changed

+291
-96
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ repos:
1717
hooks:
1818
- id: yamllint
1919
- repo: https://github.com/awslabs/cfn-python-lint
20-
rev: v1.36.0
20+
rev: v1.36.1
2121
hooks:
2222
- id: cfn-python-lint
2323
files: template\.(json|yml|yaml)$

Pipfile.lock

Lines changed: 13 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mips_api/__init__.py

Lines changed: 74 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
from datetime import date
1+
import csv
2+
import io
23
import json
34
import logging
45
import os
56
import re
7+
from datetime import date
8+
69

710
import backoff
811
import boto3
@@ -16,9 +19,6 @@
1619
_mip_url_login = "https://login.mip.com/api/v1/sso/mipadv/login"
1720
_mip_url_coa_segments = "https://api.mip.com/api/coa/segments"
1821
_mip_url_coa_accounts = "https://api.mip.com/api/coa/segments/accounts"
19-
# _mip_url_current_balance = (
20-
# "https://api.mip.com/api/v1/operations/balances/accounts/documents"
21-
# )
2222
_mip_url_current_balance = (
2323
"https://api.mip.com/api/model/CBODispBal/methods/GetAccountBalances"
2424
)
@@ -84,6 +84,21 @@ def _param_priority_list(params):
8484
return None
8585

8686

87+
# helper functions to encapsulate the body, headers, and status code
88+
def _build_return_json(code, body):
89+
return {
90+
"statusCode": code,
91+
"body": json.dumps(body, indent=2),
92+
}
93+
94+
95+
def _build_return_text(code, body):
96+
return {
97+
"statusCode": code,
98+
"body": body,
99+
}
100+
101+
87102
def collect_secrets(ssm_path):
88103
"""Collect secure parameters from SSM"""
89104

@@ -215,29 +230,6 @@ def _request_balance(access_token, period_from, period_to):
215230
timeout = 4
216231
LOG.info("Getting balances")
217232

218-
# api_response = requests.get(
219-
# _mip_url_balance_template,
220-
# headers={"Authorization-Token": access_token},
221-
# timeout=timeout,
222-
# )
223-
# api_response.raise_for_status()
224-
# json_response = api_response.json()
225-
# LOG.debug(f"Raw balance template json: {json_response}")
226-
227-
# body = {
228-
# "dateFrom": "03/01/2025",
229-
# "dateTo": "04/01/2025",
230-
# "segmentInformation": [
231-
# {
232-
# "filterSelected": True,
233-
# "filterItem": "PRG",
234-
# "filterOperator": "<>",
235-
# "filterCriteria1": "<Blank>",
236-
# "filterCriteria2": "",
237-
# },
238-
# ],
239-
# }
240-
241233
# copied from chrome dev tools while clicking through the web ui
242234
body = {
243235
"BOInformation": {
@@ -357,11 +349,11 @@ def _balance_requests(org_name, secrets):
357349

358350
end = today.replace(day=1) # first of this month
359351
end_str = end.strftime("%m/%d/%Y")
360-
LOG.info(f"End day is {end}")
352+
LOG.info(f"End day is {end_str}")
361353

362354
start = end.replace(month=end.month - 1)
363355
start_str = start.strftime("%m/%d/%Y") # first day of last month
364-
LOG.info(f"Start day is {start}")
356+
LOG.info(f"Start day is {start_str}")
365357

366358
try:
367359
# get mip access token
@@ -377,6 +369,11 @@ def _balance_requests(org_name, secrets):
377369
except Exception as exc:
378370
LOG.exception("Error logging out")
379371

372+
bal_dict["period_from"] = start_str
373+
bal_dict["period_to"] = end_str
374+
375+
LOG.debug(f"Balance dict: {bal_dict}")
376+
380377
return bal_dict
381378

382379

@@ -457,17 +454,17 @@ def s3_cache(src_dict, bucket, path):
457454

458455
def chart_cache(org_name, secrets, bucket, path, inactive):
459456
"""
460-
Access the Chart of Accounts from MIP Cloud, and implement a write-through
461-
cache of successful responses to tolerate long-term faults in the upstream
462-
API.
457+
Access the Chart of Accounts from MIP Cloud, and implement a
458+
write-through cache of successful responses to tolerate long-term
459+
faults in the upstream API.
463460
464-
A successful API response will be stored in S3 indefinitely, to be retrieved
465-
and used in the case of an API failure.
461+
A successful API response will be stored in S3 indefinitely, to be
462+
retrieved and used in the case of an API failure.
466463
467-
The S3 bucket has versioning enabled for disaster recovery, but this means
468-
that every PUT request will create a new S3 object. In order to minimize
469-
the number of objects in the bucket, read the cache value on every run and
470-
only update the S3 object if it changes.
464+
The S3 bucket has versioning enabled for disaster recovery, but this
465+
means that every PUT request will create a new S3 object. In order
466+
to minimize the number of objects in the bucket, read the cache
467+
value on every run and only update the S3 object if it changes.
471468
"""
472469

473470
# get the upstream API response
@@ -539,11 +536,12 @@ def process_chart(
539536

540537
if priority_codes is not None:
541538
if short in priority_codes:
542-
# Since Python 3.7, python dictionaries preserve insertion
543-
# order, so to prepend an item to the top of the dictionary,
544-
# we create a new dictionary inserting the target code first,
545-
# then add the previous output, and finally save the new
546-
# dictionary as our output dictionary.
539+
# Since Python 3.7, python dictionaries preserve
540+
# insertion order, so to prepend an item to the top
541+
# of the dictionary, we create a new dictionary
542+
# inserting the target code first, then add the
543+
# previous output, and finally save the new
544+
# dictionary as our output.
547545
new_chart = {short: name}
548546
new_chart.update(out_chart)
549547
out_chart = new_chart
@@ -613,12 +611,17 @@ def list_tags(chart_dict, limit):
613611
return tags
614612

615613

616-
def format_balance(bal_dict, coa_dict):
614+
def process_balance(bal_dict, coa_dict):
615+
617616
# check for success
617+
if "executionResult" not in bal_dict:
618+
LOG.error(f"No execution result found: '{bal_dict}'")
619+
raise KeyError("No 'executionResult' found")
620+
618621
result = bal_dict["executionResult"]
619622
if result != "SUCCESS":
620623
LOG.error(f"Execution result is not 'SUCCESS': '{result}'")
621-
raise ValueError
624+
raise ValueError("Execution result is not 'SUCCESS'")
622625

623626
# collate api response into a dict
624627
_data = {}
@@ -647,38 +650,52 @@ def format_balance(bal_dict, coa_dict):
647650
LOG.debug(f"Raw internal balance dict: {_data}")
648651

649652
# List of rows in CSV
650-
csv_out = []
653+
out_rows = []
651654

652655
# Add header row
653656
headers = [
654657
"AccountNumber",
655658
"AccountName",
656-
"Period",
659+
"PeriodStart",
660+
"PeriodEnd",
657661
"StartBalance",
658662
"Activity",
659663
"EndBalance",
660664
]
661-
csv_out.append(headers)
665+
out_rows.append(headers)
662666

663667
# Generate rows from input dict
664668
for k, v in _data.items():
665669
name = None
666670
if k not in coa_dict:
667-
LOG.error(f"Key {k} not found in coa dict")
671+
LOG.error(f"Key {k} not found in chart of accounts")
668672
name = k
669673
else:
670674
name = coa_dict[k]
671675

672676
row = [
673677
k,
674678
name,
679+
bal_dict["period_from"],
680+
bal_dict["period_to"],
675681
v["balance_start"],
676682
v["activity"],
677683
v["balance_end"],
678684
]
679-
csv_out.append(row)
685+
out_rows.append(row)
680686

681-
return csv_out
687+
return out_rows
688+
689+
690+
def format_balance(bal_dict, coa_dict):
691+
csv_out = io.StringIO()
692+
csv_writer = csv.writer(csv_out)
693+
694+
csv_rows = process_balance(bal_dict, coa_dict)
695+
for row in csv_rows:
696+
csv_writer.writerow(row)
697+
698+
return csv_out.getvalue()
682699

683700

684701
def lambda_handler(event, context):
@@ -703,13 +720,6 @@ def lambda_handler(event, context):
703720
Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html
704721
"""
705722

706-
# helper functions to encapsulate the body, headers, and status code
707-
def _build_return(code, body):
708-
return {
709-
"statusCode": code,
710-
"body": json.dumps(body, indent=2),
711-
}
712-
713723
try:
714724
# collect environment variables
715725
mip_org = _get_os_var("MipsOrg")
@@ -776,26 +786,26 @@ def _build_return(code, body):
776786
)
777787
bal_csv = format_balance(raw_bal, coa_chart)
778788

779-
return _build_return(200, bal_csv)
789+
return _build_return_text(200, bal_csv)
780790
else:
781791

782792
if event_path == api_routes["ApiChartOfAccounts"]:
783793
# conditionally filter the output
784794
_coa_chart = limit_chart(coa_chart, limit_length)
785-
return _build_return(200, _coa_chart)
795+
return _build_return_json(200, _coa_chart)
786796

787797
elif event_path == api_routes["ApiValidTags"]:
788798
# build a list of strings from the processed dictionary
789799
valid_tags = list_tags(coa_chart, limit_length)
790-
return _build_return(200, valid_tags)
800+
return _build_return_json(200, valid_tags)
791801
else:
792-
return _build_return(404, {"error": "Invalid request path"})
802+
return _build_return_json(404, {"error": "Invalid request path"})
793803

794804
else:
795-
return _build_return(
805+
return _build_return_json(
796806
400, {"error": f"Invalid event: No path found: {event}"}
797807
)
798808

799809
except Exception as exc:
800810
LOG.exception(exc)
801-
return _build_return(500, {"error": str(exc)})
811+
return _build_return_json(500, {"error": str(exc)})

0 commit comments

Comments
 (0)