Skip to content

Commit

Permalink
Merge pull request #352 from padix-key/master
Browse files Browse the repository at this point in the history
Fix attribute access Atom, AtomArray and AtomArrayStack
  • Loading branch information
padix-key authored Aug 27, 2021
2 parents 7642539 + 6d9eed0 commit 61989f0
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 28 deletions.
48 changes: 22 additions & 26 deletions src/biotite/structure/atoms.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,9 @@ def __getattr__(self, attr):
return self._bonds
if attr == "box":
return self._box
elif attr in self._annot:
# Call method of 'object' superclass to avoid infinite recursive
# calls of '__getattr__()'
elif attr in super().__getattribute__("_annot"):
return self._annot[attr]
else:
raise AttributeError(
Expand Down Expand Up @@ -301,7 +303,7 @@ def __setattr__(self, attr, value):
)
if value.shape[-1] != 3:
raise TypeError("Expected 3 coordinates for each atom")
self._coord = value.astype(np.float32, copy=False)
super().__setattr__("_coord", value.astype(np.float32, copy=False))

elif attr == "bonds":
if isinstance(value, BondList):
Expand All @@ -310,41 +312,37 @@ def __setattr__(self, attr, value):
f"Array length is {self._array_length}, "
f"but bond list has {value.get_atom_count()} atoms"
)
self._bonds = value
super().__setattr__("_bonds", value)
elif value is None:
# Remove bond list
self._bonds = None
super().__setattr__("_bonds", None)
else:
raise TypeError("Value must be 'BondList'")

elif attr == "box":
if value is None:
self._box = None
elif isinstance(self, AtomArray):
if value.ndim != 2:
raise ValueError(
"A 2-dimensional ndarray is expected "
"for an AtomArray"
)
elif isinstance(self, AtomArrayStack):
if value.ndim != 3:
raise ValueError(
"A 3-dimensional ndarray is expected "
"for an AtomArrayStack"
)
if isinstance(value, np.ndarray):
if isinstance(self, AtomArray):
if value.ndim != 2:
raise ValueError(
"A 2-dimensional ndarray is expected "
"for an AtomArray"
)
else: # AtomArrayStack
if value.ndim != 3:
raise ValueError(
"A 3-dimensional ndarray is expected "
"for an AtomArrayStack"
)
if value.shape[-2:] != (3,3):
raise TypeError("Box must be a 3x3 matrix (three vectors)")
self._box = value.astype(np.float32, copy=False)
box = value.astype(np.float32, copy=False)
super().__setattr__("_box", box)
elif value is None:
# Remove box
self._box = None
super().__setattr__("_box", None)
else:
raise TypeError("Box must be ndarray of floats or None")

# This condition is required, since otherwise
# call of the next one would result
# in indefinite calls of __setattr__
elif attr == "_annot":
super().__setattr__(attr, value)
elif attr in self._annot:
Expand Down Expand Up @@ -514,16 +512,14 @@ def shape(self):
return ()

def __getattr__(self, attr):
if attr in self._annot:
if attr in super().__getattribute__("_annot"):
return self._annot[attr]
else:
raise AttributeError(
f"'{type(self).__name__}' object has no attribute '{attr}'"
)

def __setattr__(self, attr, value):
# First condition is required, to avoid indefinite calls of
# __getattr__()
if attr == "_annot":
super().__setattr__(attr, value)
elif attr == "coord":
Expand Down
21 changes: 19 additions & 2 deletions tests/structure/test_atoms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
# under the 3-Clause BSD License. Please see 'LICENSE.rst' for further
# information.

import biotite.structure as struc
import pickle
import numpy as np
import pytest
import biotite.structure as struc


@pytest.fixture
Expand Down Expand Up @@ -176,4 +177,20 @@ def test_array_from_atoms(atom_list):
atom.some_annotation = 42
array = struc.array(atom_list)
assert np.all(array.some_annotation == np.full(array.array_length(), 42))
assert np.issubdtype(array.some_annotation.dtype, np.integer)
assert np.issubdtype(array.some_annotation.dtype, np.integer)


def test_pickle(atom, array, stack):
"""
Check if pickling and unpickling works.
This test is necessary since the classes implement the
:meth:`__getattr__()` and :meth:`__setattr__()` methods.
"""
test_atom = pickle.loads(pickle.dumps(atom))
assert test_atom == atom

test_array = pickle.loads(pickle.dumps(array))
assert test_array == array

test_stack = pickle.loads(pickle.dumps(stack))
assert test_stack == stack

0 comments on commit 61989f0

Please sign in to comment.