From 86366965799cbbb57349eee3b718509208033831 Mon Sep 17 00:00:00 2001 From: jimmysway Date: Tue, 19 Aug 2025 14:24:23 -0400 Subject: [PATCH 1/2] Closes #92 Added fetch function as an out-of-band utility to easily fetch invoices --- process_report/invoices/invoice.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/process_report/invoices/invoice.py b/process_report/invoices/invoice.py index cdfbe11..bc3587d 100644 --- a/process_report/invoices/invoice.py +++ b/process_report/invoices/invoice.py @@ -1,5 +1,6 @@ from dataclasses import dataclass import pandas +import os import process_report.util as util @@ -122,3 +123,20 @@ def export(self): def export_s3(self, s3_bucket): s3_bucket.upload_file(self.output_path, self.output_s3_key) s3_bucket.upload_file(self.output_path, self.output_s3_archive_key) + + def fetch(self, s3_bucket=None): + """Fetches the exported invoice CSV from S3 and loads it into self.data. + + If s3_bucket is not provided, uses the default invoice bucket. + The CSV is downloaded to the current working directory using its base filename. + """ + s3_key = self.output_s3_key + local_filename = os.path.basename(s3_key) + + if s3_bucket is None: + # Fallback to default bucket utility which also downloads + util.fetch_s3(s3_key) + else: + s3_bucket.download_file(s3_key, local_filename) + + self.data = pandas.read_csv(local_filename) From 1368d463df124407e3d089d3942c93c374e4c8ba Mon Sep 17 00:00:00 2001 From: jimmysway Date: Fri, 5 Sep 2025 11:10:10 -0400 Subject: [PATCH 2/2] Added test for testing fetch out-of-band utility --- .../tests/unit/invoices/test_base_invoice.py | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/process_report/tests/unit/invoices/test_base_invoice.py b/process_report/tests/unit/invoices/test_base_invoice.py index 44b7db2..44a1de6 100644 --- a/process_report/tests/unit/invoices/test_base_invoice.py +++ b/process_report/tests/unit/invoices/test_base_invoice.py @@ -1,5 +1,9 @@ from unittest import TestCase, mock import pandas +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from unittest.mock import MagicMock from process_report.tests import util as test_utils @@ -17,6 +21,33 @@ def test_filter_exported_columns(self): self.assertTrue(result_invoice.equals(answer_invoice)) + @mock.patch("pandas.read_csv") + def test_fetch_with_mock_s3_bucket(self, mock_read_csv: "MagicMock") -> None: + """Test that fetch() method loads data correctly when S3 bucket is mocked.""" + test_invoice = test_utils.new_base_invoice( + name="TestInvoice", invoice_month="2024-08" + ) + expected_data = pandas.DataFrame( + { + "Invoice Month": ["2024-08", "2024-08"], + "Project - Allocation": ["P1", "P2"], + "Manager (PI)": ["pi1@bu.edu", "pi2@harvard.edu"], + "Cost": [100.00, 150.00], + } + ) + mock_read_csv.return_value = expected_data + + mock_s3_bucket = mock.MagicMock() + mock_s3_bucket.download_file.return_value = None + + test_invoice.fetch(mock_s3_bucket) + + self.assertIsNotNone(test_invoice.data) + self.assertTrue(test_invoice.data.equals(expected_data)) + mock_s3_bucket.download_file.assert_called_once_with( + "Invoices/2024-08/TestInvoice 2024-08.csv", "TestInvoice 2024-08.csv" + ) + class TestUploadToS3(TestCase): @mock.patch("process_report.util.get_invoice_bucket")