Skip to content

Commit 53efeb4

Browse files
Copilottanghaibao
andauthored
Fix AttributeError: 'LayoutLine' object has no attribute 'color' (#835)
* Initial plan * Initial plan: fix AttributeError in assign_array and blank line handling in synteny Layout Co-authored-by: tanghaibao <106987+tanghaibao@users.noreply.github.com> * Fix AttributeError in assign_array and blank line handling in synteny Layout Co-authored-by: tanghaibao <106987+tanghaibao@users.noreply.github.com> * Style fixes by Black --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: tanghaibao <106987+tanghaibao@users.noreply.github.com>
1 parent bdfda5e commit 53efeb4

File tree

6 files changed

+78
-2
lines changed

6 files changed

+78
-2
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,5 @@ src/jcvi/version.py
7474
# Test output files
7575
tests/**/*.synfind
7676
tests/**/*.lifted.anchors
77+
tests/**/*.pdf
78+
tests/**/grape_peach.bed

src/jcvi/graphics/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ def __init__(self, filename):
132132
def assign_array(self, attrib, array):
133133
assert len(array) == len(self)
134134
for x, c in zip(self, array):
135-
if not getattr(x, attrib):
135+
if not getattr(x, attrib, None):
136136
setattr(x, attrib, c)
137137

138138
def assign_colors(self, seed: Optional[int] = None):

src/jcvi/graphics/synteny.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ def __init__(self, filename, delimiter=",", seed: Optional[int] = None):
9999
fp = open(filename, encoding="utf-8")
100100
self.edges = []
101101
for row in fp:
102+
row = row.strip()
103+
if not row:
104+
continue
102105
if row[0] == "#":
103106
continue
104107
if row[0] == "e":

tests/graphics/test_base.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,52 @@ def test_update_figname(figname, format, expected):
5555
from jcvi.graphics.base import update_figname
5656

5757
assert update_figname(figname, format) == expected, "Expect {}".format(expected)
58+
59+
60+
def test_assign_array_missing_attribute():
61+
"""Test that assign_array works when the attribute is not set on the object.
62+
63+
This guards against AttributeError: 'LayoutLine' object has no attribute 'color'
64+
when an object without the attribute is present in the layout list.
65+
"""
66+
from jcvi.graphics.base import AbstractLayout
67+
68+
class SimpleItem:
69+
pass
70+
71+
class MockLayout(AbstractLayout):
72+
def __init__(self):
73+
super().__init__("/dev/null")
74+
75+
ml = MockLayout()
76+
item = SimpleItem()
77+
ml.append(item)
78+
79+
# Should not raise AttributeError even if 'color' is not set on item
80+
ml.assign_array("color", ["red"])
81+
assert item.color == "red"
82+
83+
84+
def test_assign_array_existing_attribute_preserved():
85+
"""Test that assign_array does not overwrite an existing truthy attribute."""
86+
from jcvi.graphics.base import AbstractLayout
87+
88+
class SimpleItem:
89+
def __init__(self, color):
90+
self.color = color
91+
92+
class MockLayout(AbstractLayout):
93+
def __init__(self):
94+
super().__init__("/dev/null")
95+
96+
ml = MockLayout()
97+
item_with_color = SimpleItem("blue")
98+
item_without_color = SimpleItem("")
99+
ml.append(item_with_color)
100+
ml.append(item_without_color)
101+
102+
ml.assign_array("color", ["red", "green"])
103+
# Existing truthy color should be preserved
104+
assert item_with_color.color == "blue"
105+
# Empty/falsy color should be replaced
106+
assert item_without_color.color == "green"

tests/graphics/test_karyotype.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,5 @@ def test_main():
4242
image_name = karyotype_main(
4343
["seqids_with_empty_lines", "layout", "-o", "karyotype_with_empty_lines.pdf"]
4444
)
45+
assert op.exists(image_name)
4546
os.chdir(cwd)

tests/graphics/test_synteny.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33

44
import os
55
import os.path as op
6+
import tempfile
7+
68
import pytest
79

810
from jcvi.apps.base import cleanup
911
from jcvi.formats.bed import merge
10-
from jcvi.graphics.synteny import LayoutLine, main as synteny_main
12+
from jcvi.graphics.synteny import Layout, LayoutLine, main as synteny_main
1113
from jcvi.utils.validator import ValidationError
1214

1315

@@ -57,3 +59,22 @@ def test_main():
5759
image_name = synteny_main(["blocks", "grape_peach.bed", "blocks.layout"])
5860
assert op.exists(image_name)
5961
os.chdir(cwd)
62+
63+
64+
def test_layout_with_blank_lines():
65+
"""Test that Layout handles blank lines in the layout file gracefully."""
66+
with tempfile.NamedTemporaryFile(mode="w", suffix=".layout", delete=False) as f:
67+
f.write("# x,y,rotation,ha,va,color\n")
68+
f.write("\n")
69+
f.write("0.5, 0.6, 0, left, center, m\n")
70+
f.write("\n")
71+
f.write("0.5, 0.4, 0, left, center, k\n")
72+
tmpfile = f.name
73+
74+
try:
75+
layout = Layout(tmpfile)
76+
assert len(layout) == 2
77+
assert layout[0].color == "m"
78+
assert layout[1].color == "k"
79+
finally:
80+
os.unlink(tmpfile)

0 commit comments

Comments
 (0)