From 2a740a3c2399b979a1e663bc4ba5fe3d038cc4b4 Mon Sep 17 00:00:00 2001 From: Ishika Aahna <123102828+codedbyishika@users.noreply.github.com> Date: Thu, 30 Jul 2026 20:50:08 -0400 Subject: [PATCH 01/24] Pin Ruff (#72) --- .gitignore | 3 +++ .pre-commit-config.yaml | 9 ++++++--- pyproject.toml | 14 +++++++------- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 26f8e80..2c5bbdc 100644 --- a/.gitignore +++ b/.gitignore @@ -111,6 +111,9 @@ ipython_config.py .pdm-python .pdm-build/ +# uv +uv.lock + # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm __pypackages__/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ecc4517..8b786e5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,9 @@ repos: - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.2 + - repo: local hooks: - id: ruff - + name: ruff + entry: uv run --extra dev ruff check --force-exclude + language: system + types_or: [python, pyi] + require_serial: true \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index ff89887..b4639e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,13 +30,6 @@ dependencies = [ "sum-buddy", ] -[project.optional-dependencies] -dev = [ - "pytest", - "ruff", - "pre-commit" -] - keywords = [ "imageomics", "metadata", @@ -52,6 +45,13 @@ keywords = [ "sum-buddy", ] +[project.optional-dependencies] +dev = [ + "pytest", + "ruff==0.16.0", + "pre-commit" +] + [project.urls] Homepage = "https://github.com/Imageomics/cautious-robot" Repository = "https://github.com/Imageomics/cautious-robot.git" From f35e78af43caebb24feb297f9cbf51d9ee83d555 Mon Sep 17 00:00:00 2001 From: Ishika Aahna <123102828+codedbyishika@users.noreply.github.com> Date: Thu, 30 Jul 2026 20:51:36 -0400 Subject: [PATCH 02/24] Separate linting before tests (#70) * Move to single test workflow for PR and push reduce code duplication --------- Co-authored-by: egrace479 --- .github/workflows/run-push-tests.yml | 32 ------------------- .../{run-pr-tests.yml => run-tests.yml} | 28 ++++++++++++---- 2 files changed, 22 insertions(+), 38 deletions(-) delete mode 100644 .github/workflows/run-push-tests.yml rename .github/workflows/{run-pr-tests.yml => run-tests.yml} (64%) diff --git a/.github/workflows/run-push-tests.yml b/.github/workflows/run-push-tests.yml deleted file mode 100644 index 5137067..0000000 --- a/.github/workflows/run-push-tests.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: run-push-tests - -on: - push: - -jobs: - run-tests: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.10", "3.13"] - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: install dependencies - run: pip install .[dev] - shell: bash - - - name: run Ruff linter - run: ruff check . - shell: bash - - - name: run tests - run: pytest - shell: bash diff --git a/.github/workflows/run-pr-tests.yml b/.github/workflows/run-tests.yml similarity index 64% rename from .github/workflows/run-pr-tests.yml rename to .github/workflows/run-tests.yml index 7853216..6f584c2 100644 --- a/.github/workflows/run-pr-tests.yml +++ b/.github/workflows/run-tests.yml @@ -1,12 +1,32 @@ -name: run-PR-tests +name: run-tests on: pull_request: push: - branches: main jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.13" + + - name: install dependencies + run: pip install .[dev] + shell: bash + + - name: run Ruff linter + run: ruff check . + shell: bash + run-tests: + needs: lint runs-on: ubuntu-latest strategy: matrix: @@ -24,10 +44,6 @@ jobs: - name: install dependencies run: pip install .[dev] shell: bash - - - name: run Ruff linter - run: ruff check . - shell: bash - name: run tests run: pytest From d9f7765872e448d98c5931f11dbecf19bb8c1a86 Mon Sep 17 00:00:00 2001 From: Ishika Aahna <123102828+codedbyishika@users.noreply.github.com> Date: Fri, 31 Jul 2026 10:50:42 -0400 Subject: [PATCH 03/24] Change Ruff linter output format to GitHub (#73) --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 6f584c2..6a6481a 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -22,7 +22,7 @@ jobs: shell: bash - name: run Ruff linter - run: ruff check . + run: ruff check --output-format=github . shell: bash run-tests: From c639912dfb7d7087bbee81b8e41a28eb3d492bee Mon Sep 17 00:00:00 2001 From: Ishika Aahna <123102828+codedbyishika@users.noreply.github.com> Date: Fri, 31 Jul 2026 11:07:11 -0400 Subject: [PATCH 04/24] Include MissingColumnsError --- src/cautiousrobot/exceptions.py | 5 +++++ src/cautiousrobot/utils.py | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/cautiousrobot/exceptions.py b/src/cautiousrobot/exceptions.py index aef3983..58911bc 100644 --- a/src/cautiousrobot/exceptions.py +++ b/src/cautiousrobot/exceptions.py @@ -2,3 +2,8 @@ class EmptyDataFrameError(Exception): def __init__(self, df_name): message = f"Input DataFrame {df_name} is empty." super().__init__(message) + +class MissingColumnsError(Exception): + def __init__(self, csv_path, column_names, expected_columns): + message = f"The CSV at {csv_path} is missing column(s): {column_names}, defined as {expected_columns}." + super().__init__(message) \ No newline at end of file diff --git a/src/cautiousrobot/utils.py b/src/cautiousrobot/utils.py index 97561d4..f26a13f 100644 --- a/src/cautiousrobot/utils.py +++ b/src/cautiousrobot/utils.py @@ -5,6 +5,7 @@ import pandas as pd from PIL import Image +from cautiousrobot.exceptions import MissingColumnsError def log_response(log_data, index, image, file_path, response_code): @@ -44,7 +45,7 @@ def process_csv(csv_path, expected_cols): if expected_cols[col] not in list(df.columns): missing_cols.append(col) if len(missing_cols) > 0: - raise Exception(f"The CSV at {csv_path} is missing column(s): {missing_cols}, defined as {[expected_cols[col] for col in missing_cols]}.") # noqa: TRY002 + raise MissingColumnsError(csv_path, missing_cols, [expected_cols[col] for col in missing_cols]) return df From 7a25b0e126d8ab76936e829cdacbf47eae870b80 Mon Sep 17 00:00:00 2001 From: Ishika Aahna <123102828+codedbyishika@users.noreply.github.com> Date: Fri, 31 Jul 2026 11:08:12 -0400 Subject: [PATCH 05/24] Update __main__.py --- src/cautiousrobot/__main__.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/cautiousrobot/__main__.py b/src/cautiousrobot/__main__.py index 1548808..697f1a6 100644 --- a/src/cautiousrobot/__main__.py +++ b/src/cautiousrobot/__main__.py @@ -118,10 +118,8 @@ def main(): # Set up expected columns and process CSV expected_cols, subfolders = roll_call.setup_expected_columns(args) - try: - data_df = process_csv(csv_path, expected_cols) - except Exception as missing_cols: # noqa: BLE001 - sys.exit(f"{missing_cols} Please adjust inputs and try again.") + data_df = process_csv(csv_path, expected_cols) #Exception handling process_csv + # Validate data and handle missing filenames filename_col = expected_cols["filename_col"] From a252ae1a71bfa278aa8274b40ed36b4257972943 Mon Sep 17 00:00:00 2001 From: Ishika Aahna <123102828+codedbyishika@users.noreply.github.com> Date: Fri, 31 Jul 2026 11:11:42 -0400 Subject: [PATCH 06/24] Linting Fix --- src/cautiousrobot/__main__.py | 1 - src/cautiousrobot/utils.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cautiousrobot/__main__.py b/src/cautiousrobot/__main__.py index 697f1a6..9dec226 100644 --- a/src/cautiousrobot/__main__.py +++ b/src/cautiousrobot/__main__.py @@ -7,7 +7,6 @@ import argparse import hashlib -import sys import pandas as pd from sumbuddy import get_checksums diff --git a/src/cautiousrobot/utils.py b/src/cautiousrobot/utils.py index f26a13f..e8a1516 100644 --- a/src/cautiousrobot/utils.py +++ b/src/cautiousrobot/utils.py @@ -5,6 +5,7 @@ import pandas as pd from PIL import Image + from cautiousrobot.exceptions import MissingColumnsError From 079e5c8ffeb3c777722dbd460536bdc3cb035446 Mon Sep 17 00:00:00 2001 From: Ishika Aahna <123102828+codedbyishika@users.noreply.github.com> Date: Fri, 31 Jul 2026 11:43:11 -0400 Subject: [PATCH 07/24] Update test file using exception --- tests/test_download_images.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/test_download_images.py b/tests/test_download_images.py index 9e68c13..1cf3bfe 100644 --- a/tests/test_download_images.py +++ b/tests/test_download_images.py @@ -18,6 +18,7 @@ extract_extension_from_url, resolve_filename_with_extension, ) +from cautiousrobot.exceptions import MissingColumnsError TESTDATA_DIR = os.path.join(os.path.dirname(__file__), 'testdata') @@ -374,12 +375,12 @@ def test_main_missing_columns_error(self, mock_process_csv, mock_parse_args): mock_args.subdir_col = None mock_parse_args.return_value = mock_args - mock_process_csv.side_effect = Exception("Missing required columns") + mock_process_csv.side_effect = MissingColumnsError("test.csv", ["filename"], ["filename_col"],) - with self.assertRaises(SystemExit) as cm: + with self.assertRaises(MissingColumnsError) as cm: main() - - self.assertEqual(cm.exception.code, "Missing required columns Please adjust inputs and try again.") + + self.assertEqual(str(cm.exception), "The CSV at test.csv is missing column(s): ['filename'], defined as ['filename_col'].") @patch('cautiousrobot.__main__.parse_args') @patch('cautiousrobot.__main__.process_csv') From 89cfc9a0ede99360ec2cbd2128d3a2c5118a7de3 Mon Sep 17 00:00:00 2001 From: Ishika Aahna <123102828+codedbyishika@users.noreply.github.com> Date: Fri, 31 Jul 2026 15:05:18 -0400 Subject: [PATCH 08/24] Add ImageResizeError exception --- src/cautiousrobot/exceptions.py | 7 ++++++- src/cautiousrobot/utils.py | 12 ++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/cautiousrobot/exceptions.py b/src/cautiousrobot/exceptions.py index 58911bc..906fd12 100644 --- a/src/cautiousrobot/exceptions.py +++ b/src/cautiousrobot/exceptions.py @@ -6,4 +6,9 @@ def __init__(self, df_name): class MissingColumnsError(Exception): def __init__(self, csv_path, column_names, expected_columns): message = f"The CSV at {csv_path} is missing column(s): {column_names}, defined as {expected_columns}." - super().__init__(message) \ No newline at end of file + super().__init__(message) + +class ImageResizeError(Exception): + def __init__(self, image_name, reason): + message = f"Failed to resize image '{image_name}': {reason}" + super().__init__(message) \ No newline at end of file diff --git a/src/cautiousrobot/utils.py b/src/cautiousrobot/utils.py index e8a1516..3f00be2 100644 --- a/src/cautiousrobot/utils.py +++ b/src/cautiousrobot/utils.py @@ -4,9 +4,9 @@ import os import pandas as pd -from PIL import Image +from PIL import Image, UnidentifiedImageError -from cautiousrobot.exceptions import MissingColumnsError +from cautiousrobot.exceptions import ImageResizeError, MissingColumnsError def log_response(log_data, index, image, file_path, response_code): @@ -73,13 +73,13 @@ def downsample_and_save_image(image_dir_path, image_name, downsample_dir_path, d try: img = Image.open(f"{image_dir_path}/{image_name}") img.resize((downsample_size, downsample_size)).save(f"{downsample_dir_path}/{image_name}") - except Exception as e: # noqa: BLE001 - print(e) + except (OSError, UnidentifiedImageError) as error: log_errors = log_response( log_errors, index=image_index, image="downsized_" + image_name, file_path=file_path, - response_code=str(e) + response_code=str(error) ) - update_log(log=log_errors, index=image_index, filepath=error_log_filepath) \ No newline at end of file + update_log(log=log_errors, index=image_index, filepath=error_log_filepath) + raise ImageResizeError(image_name, str(error)) \ No newline at end of file From e3ff1c93b43d5a59cb828837d0d4db7729cc4826 Mon Sep 17 00:00:00 2001 From: Ishika Aahna <123102828+codedbyishika@users.noreply.github.com> Date: Fri, 31 Jul 2026 15:36:49 -0400 Subject: [PATCH 09/24] Update Test Fixtures and remove raising exception for single image download failure --- src/cautiousrobot/download.py | 6 +++--- src/cautiousrobot/exceptions.py | 7 +------ src/cautiousrobot/utils.py | 5 ++--- tests/test_download.py | 3 ++- tests/test_downsample.py | 2 +- 5 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/cautiousrobot/download.py b/src/cautiousrobot/download.py index 4cfb954..d2a14c2 100644 --- a/src/cautiousrobot/download.py +++ b/src/cautiousrobot/download.py @@ -68,7 +68,7 @@ def get_content_type_from_url(url): content_type = response.headers.get('content-type', '') # Split off parameters like '; charset=utf-8' return content_type.split(';')[0].strip() if content_type else None - except Exception: # noqa: BLE001 + except requests.RequestException: return None @@ -273,7 +273,7 @@ def download_single_image(url, image_name, image_dir_path, log_data, log_errors, while redo and attempts_remaining > 0: try: response = requests.get(url, stream=True) - except Exception as e: # noqa: BLE001 + except requests.RequestException as error: redo = True attempts_remaining -= 1 if attempts_remaining <= 0: @@ -281,7 +281,7 @@ def download_single_image(url, image_name, image_dir_path, log_data, log_errors, index=i, image=image_name, file_path=url, - response_code=str(e)) + response_code=str(error)) update_log(log=log_errors, index=i, filepath=error_log_filepath) continue diff --git a/src/cautiousrobot/exceptions.py b/src/cautiousrobot/exceptions.py index 906fd12..58911bc 100644 --- a/src/cautiousrobot/exceptions.py +++ b/src/cautiousrobot/exceptions.py @@ -6,9 +6,4 @@ def __init__(self, df_name): class MissingColumnsError(Exception): def __init__(self, csv_path, column_names, expected_columns): message = f"The CSV at {csv_path} is missing column(s): {column_names}, defined as {expected_columns}." - super().__init__(message) - -class ImageResizeError(Exception): - def __init__(self, image_name, reason): - message = f"Failed to resize image '{image_name}': {reason}" - super().__init__(message) \ No newline at end of file + super().__init__(message) \ No newline at end of file diff --git a/src/cautiousrobot/utils.py b/src/cautiousrobot/utils.py index 3f00be2..0737bc0 100644 --- a/src/cautiousrobot/utils.py +++ b/src/cautiousrobot/utils.py @@ -6,7 +6,7 @@ import pandas as pd from PIL import Image, UnidentifiedImageError -from cautiousrobot.exceptions import ImageResizeError, MissingColumnsError +from cautiousrobot.exceptions import MissingColumnsError def log_response(log_data, index, image, file_path, response_code): @@ -81,5 +81,4 @@ def downsample_and_save_image(image_dir_path, image_name, downsample_dir_path, d file_path=file_path, response_code=str(error) ) - update_log(log=log_errors, index=image_index, filepath=error_log_filepath) - raise ImageResizeError(image_name, str(error)) \ No newline at end of file + update_log(log=log_errors, index=image_index, filepath=error_log_filepath) \ No newline at end of file diff --git a/tests/test_download.py b/tests/test_download.py index 8dce3ee..0c9187b 100644 --- a/tests/test_download.py +++ b/tests/test_download.py @@ -2,6 +2,7 @@ from unittest.mock import patch import pandas as pd +import requests from cautiousrobot.download import download_images @@ -16,7 +17,7 @@ class TestDownload(unittest.TestCase): @patch('requests.get') def test_response_exception(self, get_mock): - get_mock.side_effect = Exception + get_mock.side_effect = requests.exceptions.ConnectionError("Connection failed") download_images(DUMMY_DATA, img_dir = IMG_DIR, log_filepath = LOG_FILEPATH, error_log_filepath = ERROR_LOG_FILEPATH) diff --git a/tests/test_downsample.py b/tests/test_downsample.py index dfa6d87..2689d21 100644 --- a/tests/test_downsample.py +++ b/tests/test_downsample.py @@ -96,7 +96,7 @@ def test_downsample_and_save_image_file_not_found(self, mock_update_log, mock_lo self.assertEqual(self.log_errors[0]['response_code'], "File not found") @patch("os.path.exists", return_value=False) - @patch("PIL.Image.open", side_effect=Exception("Unexpected error")) + @patch("PIL.Image.open", side_effect=OSError("Unexpected error")) @patch("cautiousrobot.utils.log_response") @patch("cautiousrobot.utils.update_log") def test_downsample_and_save_image_unexpected_error(self, mock_update_log, mock_log_response, mock_open, mock_exists): From 9645795c27152bd735d1f1b5e8dd80019eed34f3 Mon Sep 17 00:00:00 2001 From: Ishika Aahna <123102828+codedbyishika@users.noreply.github.com> Date: Fri, 31 Jul 2026 16:13:06 -0400 Subject: [PATCH 10/24] Specify Exceptions --- src/cautiousrobot/__main__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cautiousrobot/__main__.py b/src/cautiousrobot/__main__.py index 9dec226..3f6d62c 100644 --- a/src/cautiousrobot/__main__.py +++ b/src/cautiousrobot/__main__.py @@ -76,7 +76,7 @@ def process_checksums(img_dir, metadata_path, args, source_df): print(f"There are {checksum_df.shape[0]} files in {img_dir}. Based on {args.input_file}, there should be {expected_num_imgs} images.") return checksum_df, expected_num_imgs - except Exception as e: # noqa: BLE001 + except (OSError, ValueError, pd.errors.EmptyDataError, pd.errors.ParserError) as e: print(f"checksum calculation of downloaded images was unsuccessful due to {e}.") print(f"you can get checksums for the images downloaded to {img_dir} by running sum-buddy directly.") return None, None @@ -101,7 +101,7 @@ def verify_downloads(args, source_df, checksum_df, filename_col, metadata_path, print(f"See {metadata_path}_missing.csv for missing image info and check logs.") else: print(f"Buddy check successful. All {expected_num_imgs} expected images accounted for.") - except Exception as e: # noqa: BLE001 + except (KeyError, ValueError, TypeError, OSError) as e: print(f"Verification of download failed due to {type(e).__name__}: {e}.") print("'BuddyCheck.validate_download' can be run directly on DataFrames of the source and checksum CSVs after correcting for this error.") From 12ff135faf0501aef4eee055240d1318443577a4 Mon Sep 17 00:00:00 2001 From: Ishika Aahna <123102828+codedbyishika@users.noreply.github.com> Date: Fri, 31 Jul 2026 16:27:30 -0400 Subject: [PATCH 11/24] Test Fixture --- tests/test_download_images.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_download_images.py b/tests/test_download_images.py index 1cf3bfe..2594188 100644 --- a/tests/test_download_images.py +++ b/tests/test_download_images.py @@ -407,9 +407,10 @@ def test_main_non_unique_filenames(self, mock_process_csv, mock_parse_args): "filename_col is not a unique identifier for this dataset, please choose a column with unique values for filenames." ) + @patch('cautiousrobot.__main__.get_checksums') @patch('cautiousrobot.__main__.parse_args') @patch('cautiousrobot.__main__.process_csv') - def test_main_missing_filenames_noninteractive(self, mock_process_csv, mock_parse_args): + def test_main_missing_filenames_noninteractive(self, mock_process_csv, mock_parse_args, mock_get_checksums): mock_args = MagicMock() mock_args.input_file = 'test.csv' mock_args.img_name_col = 'filename_col' @@ -421,6 +422,7 @@ def test_main_missing_filenames_noninteractive(self, mock_process_csv, mock_pars mock_args.max_retries = 3 mock_args.checksum_algorithm = 'md5' mock_args.verifier_col = None + mock_get_checksums.return_value = None mock_parse_args.return_value = mock_args From ca5214368fbdb9caf3942c91a355403197c9428d Mon Sep 17 00:00:00 2001 From: Ishika Aahna <123102828+codedbyishika@users.noreply.github.com> Date: Sun, 2 Aug 2026 01:31:02 -0400 Subject: [PATCH 12/24] Change to custom exceptions --- src/cautiousrobot/__main__.py | 12 +++++------- src/cautiousrobot/exceptions.py | 18 +++++++++++++++++- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/cautiousrobot/__main__.py b/src/cautiousrobot/__main__.py index 3f6d62c..1321013 100644 --- a/src/cautiousrobot/__main__.py +++ b/src/cautiousrobot/__main__.py @@ -16,6 +16,7 @@ from cautiousrobot.download import download_images from cautiousrobot.roll_call import RollCall from cautiousrobot.utils import process_csv +from cautiousrobot.exceptions import ChecksumError, BuddyCheckError def parse_args(): @@ -76,9 +77,8 @@ def process_checksums(img_dir, metadata_path, args, source_df): print(f"There are {checksum_df.shape[0]} files in {img_dir}. Based on {args.input_file}, there should be {expected_num_imgs} images.") return checksum_df, expected_num_imgs - except (OSError, ValueError, pd.errors.EmptyDataError, pd.errors.ParserError) as e: - print(f"checksum calculation of downloaded images was unsuccessful due to {e}.") - print(f"you can get checksums for the images downloaded to {img_dir} by running sum-buddy directly.") + except ChecksumError as e: + print(e) return None, None @@ -101,10 +101,8 @@ def verify_downloads(args, source_df, checksum_df, filename_col, metadata_path, print(f"See {metadata_path}_missing.csv for missing image info and check logs.") else: print(f"Buddy check successful. All {expected_num_imgs} expected images accounted for.") - except (KeyError, ValueError, TypeError, OSError) as e: - print(f"Verification of download failed due to {type(e).__name__}: {e}.") - print("'BuddyCheck.validate_download' can be run directly on DataFrames of the source and checksum CSVs after correcting for this error.") - + except BuddyCheckError as e: + print(e) def main(): args = parse_args() diff --git a/src/cautiousrobot/exceptions.py b/src/cautiousrobot/exceptions.py index 58911bc..d11bb68 100644 --- a/src/cautiousrobot/exceptions.py +++ b/src/cautiousrobot/exceptions.py @@ -6,4 +6,20 @@ def __init__(self, df_name): class MissingColumnsError(Exception): def __init__(self, csv_path, column_names, expected_columns): message = f"The CSV at {csv_path} is missing column(s): {column_names}, defined as {expected_columns}." - super().__init__(message) \ No newline at end of file + super().__init__(message) + +class ChecksumError(Exception): + def __init__(self, error, img_dir): + self.error = error + self.img_dir = img_dir + + def __str__(self): + return (f"Checksum calculation of downloaded images was unsuccessful due to {self.error}.\n You can get checksums for the images downloaded to {self.img_dir} by running sum-buddy directly.") + +class BuddyCheckError(Exception): + def __init__(self, error, reason): + self.error = error + self.reason = reason + + def __str__(self): + return (f"Verification of download failed due to {self.error}: {self.reason}.\n'BuddyCheck.validate_download' can be run directly on DataFrames of the source and checksum CSVs after correcting for this error.") \ No newline at end of file From a88b6f5a5444bcb9b64111fecce2ab9f2163280a Mon Sep 17 00:00:00 2001 From: Ishika Aahna <123102828+codedbyishika@users.noreply.github.com> Date: Sun, 2 Aug 2026 01:43:41 -0400 Subject: [PATCH 13/24] Update exceptions.py --- src/cautiousrobot/exceptions.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/cautiousrobot/exceptions.py b/src/cautiousrobot/exceptions.py index d11bb68..ddf4539 100644 --- a/src/cautiousrobot/exceptions.py +++ b/src/cautiousrobot/exceptions.py @@ -12,14 +12,13 @@ class ChecksumError(Exception): def __init__(self, error, img_dir): self.error = error self.img_dir = img_dir - - def __str__(self): - return (f"Checksum calculation of downloaded images was unsuccessful due to {self.error}.\n You can get checksums for the images downloaded to {self.img_dir} by running sum-buddy directly.") - + message = f"Checksum calculation of downloaded images was unsuccessful due to {self.error}.\n You can get checksums for the images downloaded to {self.img_dir} by running sum-buddy directly." + super().__init__(message) + class BuddyCheckError(Exception): def __init__(self, error, reason): self.error = error self.reason = reason - - def __str__(self): - return (f"Verification of download failed due to {self.error}: {self.reason}.\n'BuddyCheck.validate_download' can be run directly on DataFrames of the source and checksum CSVs after correcting for this error.") \ No newline at end of file + message = f"Verification of download failed due to {self.error}: {self.reason}.\n'BuddyCheck.validate_download' can be run directly on DataFrames of the source and checksum CSVs after correcting for this error." + super().__init__(message) + \ No newline at end of file From a0f8b24edd6be5d1e9b0c2ecefe31528ebe9f934 Mon Sep 17 00:00:00 2001 From: Ishika Aahna <123102828+codedbyishika@users.noreply.github.com> Date: Sun, 2 Aug 2026 01:47:19 -0400 Subject: [PATCH 14/24] Update Exceptions --- src/cautiousrobot/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cautiousrobot/__main__.py b/src/cautiousrobot/__main__.py index 1321013..15ca142 100644 --- a/src/cautiousrobot/__main__.py +++ b/src/cautiousrobot/__main__.py @@ -14,9 +14,9 @@ from cautiousrobot.__about__ import __version__ from cautiousrobot.buddy_check import BuddyCheck from cautiousrobot.download import download_images +from cautiousrobot.exceptions import BuddyCheckError, ChecksumError from cautiousrobot.roll_call import RollCall from cautiousrobot.utils import process_csv -from cautiousrobot.exceptions import ChecksumError, BuddyCheckError def parse_args(): From 13677ba0b4b2a6b042bdb2a6943e1794fa125930 Mon Sep 17 00:00:00 2001 From: Ishika Aahna <123102828+codedbyishika@users.noreply.github.com> Date: Sun, 2 Aug 2026 02:02:48 -0400 Subject: [PATCH 15/24] Patch Fix and Mock Fix --- tests/test_download_images.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/tests/test_download_images.py b/tests/test_download_images.py index 2594188..2155ad9 100644 --- a/tests/test_download_images.py +++ b/tests/test_download_images.py @@ -326,7 +326,8 @@ class TestMainFunction(unittest.TestCase): @patch('cautiousrobot.__main__.BuddyCheck') @patch('os.path.exists') @patch('builtins.input', return_value='y') - def test_main_successful_execution(self, mock_input, mock_exists, mock_BuddyCheck, mock_get_checksums, mock_download_images, mock_process_csv, mock_parse_args): + @patch('cautiousrobot.__main__.process_checksums') + def test_main_successful_execution(self, mock_process_checksums, mock_input, mock_exists, mock_BuddyCheck, mock_get_checksums, mock_download_images, mock_process_csv, mock_parse_args): mock_args = MagicMock() mock_args.input_file = 'test.csv' mock_args.img_name_col = 'filename_col' @@ -338,6 +339,7 @@ def test_main_successful_execution(self, mock_input, mock_exists, mock_BuddyChec mock_args.max_retries = 3 mock_args.checksum_algorithm = 'md5' mock_args.verifier_col = None + mock_process_checksums.return_value = (None, None) mock_parse_args.return_value = mock_args mock_exists.return_value = False @@ -407,10 +409,12 @@ def test_main_non_unique_filenames(self, mock_process_csv, mock_parse_args): "filename_col is not a unique identifier for this dataset, please choose a column with unique values for filenames." ) + @patch('cautiousrobot.__main__.download_images') @patch('cautiousrobot.__main__.get_checksums') - @patch('cautiousrobot.__main__.parse_args') + @patch('cautiousrobot.__main__.process_checksums') @patch('cautiousrobot.__main__.process_csv') - def test_main_missing_filenames_noninteractive(self, mock_process_csv, mock_parse_args, mock_get_checksums): + @patch('cautiousrobot.__main__.parse_args') + def test_main_missing_filenames_noninteractive(self, mock_parse_args, mock_process_csv, mock_process_checksums, mock_get_checksums, mock_download_images): mock_args = MagicMock() mock_args.input_file = 'test.csv' mock_args.img_name_col = 'filename_col' @@ -422,7 +426,6 @@ def test_main_missing_filenames_noninteractive(self, mock_process_csv, mock_pars mock_args.max_retries = 3 mock_args.checksum_algorithm = 'md5' mock_args.verifier_col = None - mock_get_checksums.return_value = None mock_parse_args.return_value = mock_args @@ -431,6 +434,10 @@ def test_main_missing_filenames_noninteractive(self, mock_process_csv, mock_pars 'url_col': ['url1', 'url2', 'url3', 'url4'] }) + mock_process_csv.return_value = mock_data + mock_process_checksums.return_value = (None, None) + mock_get_checksums.return_value = None + mock_process_csv.return_value = mock_data # Should NOT exit now From 5a971aa8a6a456a4c218e14ec2becdade3648c11 Mon Sep 17 00:00:00 2001 From: Ishika Aahna <123102828+codedbyishika@users.noreply.github.com> Date: Tue, 4 Aug 2026 15:26:28 -0400 Subject: [PATCH 16/24] Remove the Ignoring Ruff Error --- src/cautiousrobot/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cautiousrobot/__init__.py b/src/cautiousrobot/__init__.py index 33d83c4..4010eb4 100644 --- a/src/cautiousrobot/__init__.py +++ b/src/cautiousrobot/__init__.py @@ -5,8 +5,8 @@ # Create instance of the class buddy_check_instance = BuddyCheck() -# Expose instance methods by executing them -buddy_check_instance.validate_download #noqa: B018 -buddy_check_instance.check_alignment #noqa: B018 +# Expose instance methods +_ = buddy_check_instance.validate_download +_ = buddy_check_instance.check_alignment __all__ = ["check_alignment", "download_images", "downsample_and_save_image", "validate_download"] From 86d732677e86218478c6a3cc8d9e0e961228c1cf Mon Sep 17 00:00:00 2001 From: Ishika Aahna <123102828+codedbyishika@users.noreply.github.com> Date: Tue, 4 Aug 2026 15:28:27 -0400 Subject: [PATCH 17/24] Remove Ruff Ignore for unused variable --- tests/test_roll_call.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_roll_call.py b/tests/test_roll_call.py index e8cd109..be4b167 100644 --- a/tests/test_roll_call.py +++ b/tests/test_roll_call.py @@ -252,7 +252,7 @@ def test_columns_lowercase_conversion(self): args.url_col = "FileURL" args.subdir_col = None - expected_cols, subfolders = self.rollcall.setup_expected_columns(args) #noqa: RUF059 + expected_cols, _ = self.rollcall.setup_expected_columns(args) self.assertEqual(expected_cols["filename_col"], "filename") self.assertEqual(expected_cols["url_col"], "fileurl") @@ -326,7 +326,7 @@ def test_dictionary_structure(self): args.url_col = "url" args.subdir_col = "folder" - expected_cols, subfolders = self.rollcall.setup_expected_columns(args) #noqa: RUF059 + expected_cols, _ = self.rollcall.setup_expected_columns(args) # Check dictionary has correct keys self.assertIn("filename_col", expected_cols) From 20ad23bd2134e180b2af50f0ad4290938234940a Mon Sep 17 00:00:00 2001 From: Ishika Aahna <123102828+codedbyishika@users.noreply.github.com> Date: Tue, 4 Aug 2026 15:52:15 -0400 Subject: [PATCH 18/24] Closing Files in Teardown --- tests/test_buddycheck.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_buddycheck.py b/tests/test_buddycheck.py index 171a8db..8153a59 100644 --- a/tests/test_buddycheck.py +++ b/tests/test_buddycheck.py @@ -33,6 +33,8 @@ def setUp(self): def tearDown(self): + self.img_source_file.close() + self.checksum_source_file.close() os.remove(self.img_source_file.name) os.remove(self.checksum_source_file.name) From 3143cde3a11c788a297676041d489e6819bd9c11 Mon Sep 17 00:00:00 2001 From: Ishika Aahna <123102828+codedbyishika@users.noreply.github.com> Date: Mon, 10 Aug 2026 12:55:25 -0400 Subject: [PATCH 19/24] Update File Handling --- tests/test_buddycheck.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/tests/test_buddycheck.py b/tests/test_buddycheck.py index 8153a59..89b5eb0 100644 --- a/tests/test_buddycheck.py +++ b/tests/test_buddycheck.py @@ -14,27 +14,26 @@ def setUp(self): self.buddy_check_filename = BuddyCheck(buddy_id='filename') self.buddy_check_id_col = BuddyCheck(buddy_id = "filename", buddy_col = "sha256") - self.img_source_file = tempfile.NamedTemporaryFile(delete=False, mode='w') # noqa: SIM115 - self.checksum_source_file = tempfile.NamedTemporaryFile(delete=False, mode='w') # noqa: SIM115 + #self.img_source_file = tempfile.NamedTemporaryFile(delete=False, mode='w') + #self.checksum_source_file = tempfile.NamedTemporaryFile(delete=False, mode='w') - self.img_source_file.write("""filename,checksum + + with tempfile.NamedTemporaryFile(delete=False, mode='w', suffix=".csv") as f: + f.write("""filename,checksum image1.jpg,abc123 image2.jpg,def456 -image3.jpg,ghi789 -""") - self.img_source_file.close() +image3.jpg,ghi789""") + self.img_source_file = f - self.checksum_source_file.write("""filename,md5 + with tempfile.NamedTemporaryFile(delete=False, mode='w', suffix=".csv") as f: + f.write("""filename,md5 image1.jpg,abc123 image2.jpg,def456 -image3.jpg,ghi789 -""") - self.checksum_source_file.close() +image3.jpg,ghi789""") + self.checksum_source_file = f def tearDown(self): - self.img_source_file.close() - self.checksum_source_file.close() os.remove(self.img_source_file.name) os.remove(self.checksum_source_file.name) From 81fa1bb7f0da798827951f9add37999343477856 Mon Sep 17 00:00:00 2001 From: Ishika Aahna <123102828+codedbyishika@users.noreply.github.com> Date: Thu, 13 Aug 2026 16:23:44 -0400 Subject: [PATCH 20/24] Update exposing instance methods to include buddy_check --- src/cautiousrobot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cautiousrobot/__init__.py b/src/cautiousrobot/__init__.py index 4010eb4..b4ce3e0 100644 --- a/src/cautiousrobot/__init__.py +++ b/src/cautiousrobot/__init__.py @@ -9,4 +9,4 @@ _ = buddy_check_instance.validate_download _ = buddy_check_instance.check_alignment -__all__ = ["check_alignment", "download_images", "downsample_and_save_image", "validate_download"] +__all__ = ["BuddyCheck", "download_images", "downsample_and_save_image"] From ea5f5e8bc20dbefee868359c789dbd29f74b8b96 Mon Sep 17 00:00:00 2001 From: Ishika Aahna <123102828+codedbyishika@users.noreply.github.com> Date: Fri, 14 Aug 2026 14:33:59 -0400 Subject: [PATCH 21/24] Apply suggestions from code review Co-authored-by: Elizabeth Campolongo <38985481+egrace479@users.noreply.github.com> --- src/cautiousrobot/__init__.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/cautiousrobot/__init__.py b/src/cautiousrobot/__init__.py index b4ce3e0..bdb6695 100644 --- a/src/cautiousrobot/__init__.py +++ b/src/cautiousrobot/__init__.py @@ -2,11 +2,6 @@ from cautiousrobot.download import download_images from cautiousrobot.utils import downsample_and_save_image -# Create instance of the class -buddy_check_instance = BuddyCheck() -# Expose instance methods -_ = buddy_check_instance.validate_download -_ = buddy_check_instance.check_alignment __all__ = ["BuddyCheck", "download_images", "downsample_and_save_image"] From 1b30b8d777106ecff2ccf4da69c3c9d82b1a2bf2 Mon Sep 17 00:00:00 2001 From: Ishika Aahna <123102828+codedbyishika@users.noreply.github.com> Date: Fri, 14 Aug 2026 15:44:07 -0400 Subject: [PATCH 22/24] Ruff Fix --- src/cautiousrobot/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/cautiousrobot/__init__.py b/src/cautiousrobot/__init__.py index bdb6695..8e9b5cc 100644 --- a/src/cautiousrobot/__init__.py +++ b/src/cautiousrobot/__init__.py @@ -2,6 +2,4 @@ from cautiousrobot.download import download_images from cautiousrobot.utils import downsample_and_save_image - - __all__ = ["BuddyCheck", "download_images", "downsample_and_save_image"] From 069fa475ae37aaec87d3b9040726f602e1295c5f Mon Sep 17 00:00:00 2001 From: Ishika Aahna <123102828+codedbyishika@users.noreply.github.com> Date: Fri, 14 Aug 2026 18:06:55 -0400 Subject: [PATCH 23/24] Update to include more exceptions --- src/cautiousrobot/__main__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cautiousrobot/__main__.py b/src/cautiousrobot/__main__.py index 15ca142..64d3f64 100644 --- a/src/cautiousrobot/__main__.py +++ b/src/cautiousrobot/__main__.py @@ -77,7 +77,7 @@ def process_checksums(img_dir, metadata_path, args, source_df): print(f"There are {checksum_df.shape[0]} files in {img_dir}. Based on {args.input_file}, there should be {expected_num_imgs} images.") return checksum_df, expected_num_imgs - except ChecksumError as e: + except (ChecksumError, OSError, IOError, ValueError, pd.errors.ParserError) as e: print(e) return None, None @@ -101,7 +101,7 @@ def verify_downloads(args, source_df, checksum_df, filename_col, metadata_path, print(f"See {metadata_path}_missing.csv for missing image info and check logs.") else: print(f"Buddy check successful. All {expected_num_imgs} expected images accounted for.") - except BuddyCheckError as e: + except (BuddyCheckError,EmptyDataFrameError, ValueError, KeyError) as e: print(e) def main(): From edeee5e5f210648868d14d672db9d223677574ce Mon Sep 17 00:00:00 2001 From: Ishika Aahna <123102828+codedbyishika@users.noreply.github.com> Date: Tue, 18 Aug 2026 23:42:12 -0400 Subject: [PATCH 24/24] Update __main__.py --- src/cautiousrobot/__main__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cautiousrobot/__main__.py b/src/cautiousrobot/__main__.py index 64d3f64..0c840c7 100644 --- a/src/cautiousrobot/__main__.py +++ b/src/cautiousrobot/__main__.py @@ -77,7 +77,7 @@ def process_checksums(img_dir, metadata_path, args, source_df): print(f"There are {checksum_df.shape[0]} files in {img_dir}. Based on {args.input_file}, there should be {expected_num_imgs} images.") return checksum_df, expected_num_imgs - except (ChecksumError, OSError, IOError, ValueError, pd.errors.ParserError) as e: + except (ChecksumError, OSError, ValueError, pd.errors.ParserError) as e: print(e) return None, None @@ -101,7 +101,7 @@ def verify_downloads(args, source_df, checksum_df, filename_col, metadata_path, print(f"See {metadata_path}_missing.csv for missing image info and check logs.") else: print(f"Buddy check successful. All {expected_num_imgs} expected images accounted for.") - except (BuddyCheckError,EmptyDataFrameError, ValueError, KeyError) as e: + except (BuddyCheckError, OSError, ValueError, KeyError) as e: print(e) def main():