Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions news/setter-property.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
**Added:**

* prevent direct modification of `all_arrays` using `@property`

**Changed:**

* <news item>

**Deprecated:**

* <news item>

**Removed:**

* <news item>

**Fixed:**

* <news item>

**Security:**

* <news item>
45 changes: 28 additions & 17 deletions src/diffpy/utils/diffraction_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,17 @@ def __rtruediv__(self, other):
divided.on_q[1] = other.on_q[1] / self.on_q[1]
return divided

@property
def all_arrays(self):
return self._all_arrays
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A private property _all_arrays is used internally.


@all_arrays.setter
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when the user attempts to modify all_array, throw an exception. I've thought it might be better to use exception rather than warning since our _set_xarrays is doing some heavy lifting work and we want our users to use the way we want them to do?

def all_arrays(self, value):
raise AttributeError(
"Direct modification of 'all_arrays' is not allowed."
"Please use 'insert_scattering_quantity' to modify it."
)

def set_angles_from_list(self, angles_list):
self.angles = angles_list
self.n_steps = len(angles_list) - 1.0
Expand Down Expand Up @@ -259,25 +270,25 @@ def get_angle_index(self, angle):
raise IndexError(f"WARNING: no angle {angle} found in angles list")

def _set_xarrays(self, xarray, xtype):
self.all_arrays = np.empty(shape=(len(xarray), 4))
self._all_arrays = np.empty(shape=(len(xarray), 4))
if xtype.lower() in QQUANTITIES:
self.all_arrays[:, 1] = xarray
self.all_arrays[:, 2] = q_to_tth(xarray, self.wavelength)
self.all_arrays[:, 3] = q_to_d(xarray)
self._all_arrays[:, 1] = xarray
self._all_arrays[:, 2] = q_to_tth(xarray, self.wavelength)
self._all_arrays[:, 3] = q_to_d(xarray)
elif xtype.lower() in ANGLEQUANTITIES:
self.all_arrays[:, 2] = xarray
self.all_arrays[:, 1] = tth_to_q(xarray, self.wavelength)
self.all_arrays[:, 3] = tth_to_d(xarray, self.wavelength)
self._all_arrays[:, 2] = xarray
self._all_arrays[:, 1] = tth_to_q(xarray, self.wavelength)
self._all_arrays[:, 3] = tth_to_d(xarray, self.wavelength)
elif xtype.lower() in DQUANTITIES:
self.all_arrays[:, 3] = xarray
self.all_arrays[:, 1] = d_to_q(xarray)
self.all_arrays[:, 2] = d_to_tth(xarray, self.wavelength)
self.qmin = np.nanmin(self.all_arrays[:, 1], initial=np.inf)
self.qmax = np.nanmax(self.all_arrays[:, 1], initial=0.0)
self.tthmin = np.nanmin(self.all_arrays[:, 2], initial=np.inf)
self.tthmax = np.nanmax(self.all_arrays[:, 2], initial=0.0)
self.dmin = np.nanmin(self.all_arrays[:, 3], initial=np.inf)
self.dmax = np.nanmax(self.all_arrays[:, 3], initial=0.0)
self._all_arrays[:, 3] = xarray
self._all_arrays[:, 1] = d_to_q(xarray)
self._all_arrays[:, 2] = d_to_tth(xarray, self.wavelength)
self.qmin = np.nanmin(self._all_arrays[:, 1], initial=np.inf)
self.qmax = np.nanmax(self._all_arrays[:, 1], initial=0.0)
self.tthmin = np.nanmin(self._all_arrays[:, 2], initial=np.inf)
self.tthmax = np.nanmax(self._all_arrays[:, 2], initial=0.0)
self.dmin = np.nanmin(self._all_arrays[:, 3], initial=np.inf)
self.dmax = np.nanmax(self._all_arrays[:, 3], initial=0.0)

def insert_scattering_quantity(
self,
Expand Down Expand Up @@ -309,7 +320,7 @@ def insert_scattering_quantity(

"""
self._set_xarrays(xarray, xtype)
self.all_arrays[:, 0] = yarray
self._all_arrays[:, 0] = yarray
self.input_xtype = xtype
# only update these optional values if non-empty quantities are passed to avoid overwriting
# valid data inadvertently
Expand Down
26 changes: 24 additions & 2 deletions tests/test_diffraction_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,5 +341,27 @@ def test_dump(tmp_path, mocker):

@pytest.mark.parametrize("inputs, expected", tc_params)
def test_constructor(inputs, expected):
actualdo = DiffractionObject(**inputs)
compare_dicts(actualdo.__dict__, expected)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use a library DeepDiff to compare two dicts instead? We've manually defined compare_dicts and dicts_equal within test_diffraction_objects.py which can be replaced using DeepDiff across all .py files.

actual_do = DiffractionObject(**inputs)
actual_dict = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we normally do this work using actual_do.__dict__ so the test will fail if someone adds a new attribute to DiffractionObject

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, done.

"all_arrays": actual_do.all_arrays,
"metadata": actual_do.metadata,
"input_xtype": actual_do.input_xtype,
"name": actual_do.name,
"scat_quantity": actual_do.scat_quantity,
"qmin": actual_do.qmin,
"qmax": actual_do.qmax,
"tthmin": actual_do.tthmin,
"tthmax": actual_do.tthmax,
"dmin": actual_do.dmin,
"dmax": actual_do.dmax,
"wavelength": actual_do.wavelength,
}
compare_dicts(actual_dict, expected)


def test_all_array_setter():
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test for throwing an exception when the user attempts to modify all_arrays directly.

actual_do = DiffractionObject()

# Attempt to directly modify the property
with pytest.raises(AttributeError, match="Direct modification of 'all_arrays' is not allowed."):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's follow our favorite pattern. "what went wrong. How to fix it". The first part is done (though I would add the word "attribute" before 'all_arrays') but not the second? I think we want to point them towards using insert_diffraction_object or sthg, but give it some thought.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes done.

actual_do.all_arrays = np.empty((4, 4))
Loading