From f1bb491700e669d6e0cae4287628f976400b8564 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 28 Jan 2026 13:20:36 +0000 Subject: [PATCH 1/5] Initial plan From 1e0fc79e9e750f92f666c24f07cd98ca3f54dc27 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 28 Jan 2026 13:24:23 +0000 Subject: [PATCH 2/5] Fix empty GFF file handling and add tests Co-authored-by: Adamtaranto <2160099+Adamtaranto@users.noreply.github.com> --- src/flexidot/utils/file_handling.py | 53 +++++++++++++--------- test-GFF_Shading_Legend_n5.png | Bin 0 -> 32804 bytes tests/test-data/empty.gff3 | 2 + tests/test_gff_handling.py | 68 ++++++++++++++++++++++++++++ 4 files changed, 102 insertions(+), 21 deletions(-) create mode 100644 test-GFF_Shading_Legend_n5.png create mode 100644 tests/test-data/empty.gff3 create mode 100644 tests/test_gff_handling.py diff --git a/src/flexidot/utils/file_handling.py b/src/flexidot/utils/file_handling.py index db0d373..04f1b2b 100644 --- a/src/flexidot/utils/file_handling.py +++ b/src/flexidot/utils/file_handling.py @@ -342,28 +342,33 @@ def read_gffs( ) logging.info(text) - # create color legend - colors, alphas = [], [] - for item in sorted(used_feats): - colors.append(color_dict[item][0]) - alphas.append(color_dict[item][1]) - legend_figure( - colors=colors, - lcs_shading_num=len(used_feats), - type_nuc=type_nuc, - bins=sorted(used_feats), - alphas=alphas, - gff_legend=True, - prefix=prefix, - filetype=filetype, - ) + # check if any annotations were found + if len(used_feats) == 0: + text = 'Warning: No annotation records found in GFF file(s). Plot will be generated without annotations.\n' + logging.warning(text) + else: + # create color legend + colors, alphas = [], [] + for item in sorted(used_feats): + colors.append(color_dict[item][0]) + alphas.append(color_dict[item][1]) + legend_figure( + colors=colors, + lcs_shading_num=len(used_feats), + type_nuc=type_nuc, + bins=sorted(used_feats), + alphas=alphas, + gff_legend=True, + prefix=prefix, + filetype=filetype, + ) - # print settings - text = 'GFF Feature Types: %s\nGFF Colors: %s' % ( - ', '.join(sorted(used_feats)), - ', '.join(sorted(colors)), - ) - logging.info(text) + # print settings + text = 'GFF Feature Types: %s\nGFF Colors: %s' % ( + ', '.join(sorted(used_feats)), + ', '.join(sorted(colors)), + ) + logging.info(text) return feat_dict @@ -548,6 +553,12 @@ def legend_figure( alphas = [] alphas = [1] * len(colors) + # handle empty colors list + if len(colors) == 0: + text = 'Warning: No colors provided for legend. Skipping legend creation.\n' + logging.warning(text) + return None + # legend data points data_points = list(range(len(colors))) if not gff_legend: diff --git a/test-GFF_Shading_Legend_n5.png b/test-GFF_Shading_Legend_n5.png new file mode 100644 index 0000000000000000000000000000000000000000..26d6ef8840f60bca5a36f86ce07c70aad8412ea2 GIT binary patch literal 32804 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¨Bxp#ecjV))U92|JJ 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 QiEprxo;n>;AAI@NZ{&#H9{>OV literal 0 HcmV?d00001 diff --git a/tests/test-data/empty.gff3 b/tests/test-data/empty.gff3 new file mode 100644 index 0000000..bbfaa17 --- /dev/null +++ b/tests/test-data/empty.gff3 @@ -0,0 +1,2 @@ +##gff-version 3 +# This is an empty GFF3 file with only header and comments diff --git a/tests/test_gff_handling.py b/tests/test_gff_handling.py new file mode 100644 index 0000000..a8042a9 --- /dev/null +++ b/tests/test_gff_handling.py @@ -0,0 +1,68 @@ +"""Tests for GFF file handling functions.""" + +from pathlib import Path + + +from flexidot.utils.file_handling import read_gffs + +# Test data paths +TEST_DATA_DIR = Path(__file__).parent / 'test-data' +EMPTY_GFF_FILE = TEST_DATA_DIR / 'empty.gff3' +EXAMPLE_GFF_FILE = TEST_DATA_DIR / 'example.gff3' + + +class TestReadGffs: + """Tests for read_gffs function.""" + + def test_read_empty_gff(self): + """Test reading an empty GFF file with no annotation records.""" + # Should not raise an error even with empty GFF + feat_dict = read_gffs( + str(EMPTY_GFF_FILE), + color_dict={'others': ('grey', 1, 0)}, + type_nuc=True, + prefix='test', + filetype='png', + ) + # Should return empty dictionary + assert feat_dict == {} + + def test_read_valid_gff(self): + """Test reading a valid GFF file with annotations.""" + feat_dict = read_gffs( + str(EXAMPLE_GFF_FILE), + color_dict={ + 'spacer1': ('blue', 1, 0), + 'repeat_region': ('red', 1, 0), + 'spacerzoom': ('green', 1, 0), + 'spacer2': ('yellow', 1, 0), + 'spacer3': ('purple', 1, 0), + 'others': ('grey', 1, 0), + }, + type_nuc=True, + prefix='test', + filetype='png', + ) + # Should return non-empty dictionary + assert len(feat_dict) > 0 + assert 'Seq2' in feat_dict + + def test_read_multiple_gffs_with_empty(self): + """Test reading multiple GFF files where one is empty.""" + feat_dict = read_gffs( + [str(EXAMPLE_GFF_FILE), str(EMPTY_GFF_FILE)], + color_dict={ + 'spacer1': ('blue', 1, 0), + 'repeat_region': ('red', 1, 0), + 'spacerzoom': ('green', 1, 0), + 'spacer2': ('yellow', 1, 0), + 'spacer3': ('purple', 1, 0), + 'others': ('grey', 1, 0), + }, + type_nuc=True, + prefix='test', + filetype='png', + ) + # Should still work and return data from the non-empty file + assert len(feat_dict) > 0 + assert 'Seq2' in feat_dict From cb51cba5b172e212c6ebc240652ccba3a90f7f58 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 28 Jan 2026 13:24:46 +0000 Subject: [PATCH 3/5] Remove accidentally committed PNG file and update .gitignore Co-authored-by: Adamtaranto <2160099+Adamtaranto@users.noreply.github.com> --- .gitignore | 5 +++++ test-GFF_Shading_Legend_n5.png | Bin 32804 -> 0 bytes 2 files changed, 5 insertions(+) delete mode 100644 test-GFF_Shading_Legend_n5.png diff --git a/.gitignore b/.gitignore index eeb5ed3..308676a 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,8 @@ ENV/ # mypy .mypy_cache/ + +# Test output files +*.png +*.pdf +*.svg diff --git a/test-GFF_Shading_Legend_n5.png b/test-GFF_Shading_Legend_n5.png deleted file mode 100644 index 26d6ef8840f60bca5a36f86ce07c70aad8412ea2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32804 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¨Bxp#ecjV))U92|JJ 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 QiEprxo;n>;AAI@NZ{&#H9{>OV From 6c4685cc40ace8aecbf76fbc4da6a6cdee84a8bd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 28 Jan 2026 13:26:58 +0000 Subject: [PATCH 4/5] Add warning messages for empty alignment files and tests Co-authored-by: Adamtaranto <2160099+Adamtaranto@users.noreply.github.com> --- src/flexidot/utils/alignments.py | 16 ++++++++++++-- tests/test_gff_handling.py | 37 ++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/flexidot/utils/alignments.py b/src/flexidot/utils/alignments.py index c81f624..89f54e5 100644 --- a/src/flexidot/utils/alignments.py +++ b/src/flexidot/utils/alignments.py @@ -133,7 +133,13 @@ def parse_blast6( } ) - logging.info(f'Parsed {len(alignments)} alignments from BLAST6 file: {filepath}') + if len(alignments) == 0: + logging.warning( + f'No alignments found in BLAST6 file: {filepath}. ' + 'Plot will be generated without alignment overlays.' + ) + else: + logging.info(f'Parsed {len(alignments)} alignments from BLAST6 file: {filepath}') return alignments @@ -260,7 +266,13 @@ def parse_paf( } ) - logging.info(f'Parsed {len(alignments)} alignments from PAF file: {filepath}') + if len(alignments) == 0: + logging.warning( + f'No alignments found in PAF file: {filepath}. ' + 'Plot will be generated without alignment overlays.' + ) + else: + logging.info(f'Parsed {len(alignments)} alignments from PAF file: {filepath}') return alignments diff --git a/tests/test_gff_handling.py b/tests/test_gff_handling.py index a8042a9..3bee377 100644 --- a/tests/test_gff_handling.py +++ b/tests/test_gff_handling.py @@ -3,6 +3,7 @@ from pathlib import Path +from flexidot.utils.alignments import load_alignments from flexidot.utils.file_handling import read_gffs # Test data paths @@ -66,3 +67,39 @@ def test_read_multiple_gffs_with_empty(self): # Should still work and return data from the non-empty file assert len(feat_dict) > 0 assert 'Seq2' in feat_dict + + +class TestEmptyAlignments: + """Tests for empty alignment file handling.""" + + def test_empty_blast6_file(self, tmp_path): + """Test reading an empty BLAST6 file.""" + empty_file = tmp_path / 'empty.blast6' + empty_file.write_text('') + + alignments = load_alignments(str(empty_file), file_format='blast6') + assert alignments == [] + + def test_empty_paf_file(self, tmp_path): + """Test reading an empty PAF file.""" + empty_file = tmp_path / 'empty.paf' + empty_file.write_text('') + + alignments = load_alignments(str(empty_file), file_format='paf') + assert alignments == [] + + def test_blast6_file_with_only_comments(self, tmp_path): + """Test reading a BLAST6 file with only comments.""" + comment_file = tmp_path / 'comments.blast6' + comment_file.write_text('# This is a comment\n# Another comment\n') + + alignments = load_alignments(str(comment_file), file_format='blast6') + assert alignments == [] + + def test_paf_file_with_only_comments(self, tmp_path): + """Test reading a PAF file with only comments.""" + comment_file = tmp_path / 'comments.paf' + comment_file.write_text('# This is a comment\n# Another comment\n') + + alignments = load_alignments(str(comment_file), file_format='paf') + assert alignments == [] From 6f349fa4c04226d8aa1b19ad7e13f580bc62398c Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 02:42:42 +0000 Subject: [PATCH 5/5] Style fixes by Ruff --- src/flexidot/utils/alignments.py | 4 +++- tests/test_gff_handling.py | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/flexidot/utils/alignments.py b/src/flexidot/utils/alignments.py index 89f54e5..8454e63 100644 --- a/src/flexidot/utils/alignments.py +++ b/src/flexidot/utils/alignments.py @@ -139,7 +139,9 @@ def parse_blast6( 'Plot will be generated without alignment overlays.' ) else: - logging.info(f'Parsed {len(alignments)} alignments from BLAST6 file: {filepath}') + logging.info( + f'Parsed {len(alignments)} alignments from BLAST6 file: {filepath}' + ) return alignments diff --git a/tests/test_gff_handling.py b/tests/test_gff_handling.py index 3bee377..0275f8e 100644 --- a/tests/test_gff_handling.py +++ b/tests/test_gff_handling.py @@ -2,7 +2,6 @@ from pathlib import Path - from flexidot.utils.alignments import load_alignments from flexidot.utils.file_handling import read_gffs