-
Notifications
You must be signed in to change notification settings - Fork 99
Adds call_object_method. #2195
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Adds call_object_method. #2195
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,31 @@ | ||||||||||||||
| # (C) Crown Copyright, Met Office. All rights reserved. | ||||||||||||||
| # | ||||||||||||||
| # This file is part of 'IMPROVER' and is released under the BSD 3-Clause license. | ||||||||||||||
| # See LICENSE in the root of the repository for full licensing details. | ||||||||||||||
|
|
||||||||||||||
| """module to give access to callable methods on objects.""" | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| def call_object_method(obj: object, method_name: str, **kwargs): | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My thinking was that we did not have to be restricted to cubelike objects. ANY object could be used in this way, e.g. a NumPy array or a Pandas dataframe. I am wondering whether this would be better as a Plugin class though, with the init method taking the name of the attribute to be called, so that args and kwargs can be disambiguated. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure we need that degree of generality, especially given the benefit we might receive from assuming pipelines using cubes and cubelists. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've also realised that we don't need this at all. For any class object, you can do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see what you mean, but remaining benefit as I see it is in this handling of 1 or more argument (Cube or CubeList) in a way that simplifies handling of such method calls in the workflow (so you don't need an additional step to handle inputs). Illustration only: graph LR;
proc_C["call_cubelike_method(<br>collapsed, ...)"]
proc_A --> proc_C;
proc_B --> proc_C;
VS graph LR;
proc_A --> merge_cube;
proc_B --> merge_cube;
merge_cube --> collapsed[iris.cube.Cube.collapsed]
OR Wrapping every possible Cubes and CubeList method with its own plugin to handle the more than 1 input cube/cubelist. Just a thought -- not saying definitively what the right thing to do is here -- certainly a class plugin would be the way to distinguish between input object handling (plugin |
||||||||||||||
| """ | ||||||||||||||
| Calls a method on an object with the supplied arguments. | ||||||||||||||
|
|
||||||||||||||
| This method allows us to construct a callable method for DAGRunner to execute | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wouldn't reference what library you choose to execute these with.
Suggested change
|
||||||||||||||
| where the method to be called is on an object that comes from another plugin. | ||||||||||||||
|
|
||||||||||||||
| e.g. cube.collapsed("height", iris.analysis.SUM) becomes | ||||||||||||||
| call_object_method(cube, "collapsed", coords="height", aggregator=iris.analysis.SUM) | ||||||||||||||
|
|
||||||||||||||
| Args: | ||||||||||||||
| obj: | ||||||||||||||
| The object containing the method to be called. | ||||||||||||||
| method_name: | ||||||||||||||
| The name of the method to be called. | ||||||||||||||
| **kwargs: | ||||||||||||||
| The keyword arguments to be passed to the method. | ||||||||||||||
|
|
||||||||||||||
| Returns: | ||||||||||||||
| The return value from the called method. | ||||||||||||||
| """ | ||||||||||||||
| method = getattr(obj, method_name) | ||||||||||||||
| return method(**kwargs) | ||||||||||||||
|
Comment on lines
+30
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| # (C) Crown Copyright, Met Office. All rights reserved. | ||
| # | ||
| # This file is part of 'IMPROVER' and is released under the BSD 3-Clause license. | ||
| # See LICENSE in the root of the repository for full licensing details. | ||
| """Tests for the call_object_method utility function.""" | ||
|
|
||
| import iris.analysis | ||
| import numpy as np | ||
| import pytest | ||
| from iris.cube import Cube | ||
|
|
||
| from improver.synthetic_data.set_up_test_cubes import set_up_variable_cube | ||
| from improver.utilities.call_object_method import call_object_method | ||
|
|
||
|
|
||
| @pytest.fixture(name="cube") | ||
| def cube_fixture() -> Cube: | ||
| """Set up a cube of data""" | ||
| data = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]], dtype=np.float32) | ||
| cube = set_up_variable_cube( | ||
| data, | ||
| name="test_variable", | ||
| units="m/s", | ||
| vertical_levels=[1000, 2000], | ||
| height=True, | ||
| ) | ||
| return cube | ||
|
|
||
|
|
||
| def test_call_object_method(cube): | ||
| """Test that call_object_method correctly calls a method on an object.""" | ||
| result = call_object_method( | ||
| cube, "collapsed", coords="height", aggregator=iris.analysis.SUM | ||
| ) | ||
| expected_data = np.array([[6.0, 8.0], [10.0, 12.0]], dtype=np.float32) | ||
| assert np.array_equal(result.data, expected_data) | ||
| assert result.name() == "test_variable" | ||
| assert result.units == "m/s" | ||
|
|
||
|
|
||
| def test_call_object_method_invalid_method(cube): | ||
| """Test that call_object_method raises an AttributeError for an invalid method.""" | ||
| with pytest.raises(AttributeError): | ||
| call_object_method(cube, "non_existent_method") | ||
|
|
||
|
|
||
| def test_call_object_method_invalid_args(cube): | ||
| """Test that call_object_method raises a TypeError for invalid arguments.""" | ||
| with pytest.raises(TypeError): | ||
| call_object_method(cube, "collapsed", non_existent_arg=True) | ||
|
|
||
|
|
||
| def test_call_object_method_no_args(cube): | ||
| """Test that call_object_method works with no additional arguments.""" | ||
| result = call_object_method(cube, "copy") | ||
| assert result == cube | ||
| assert result is not cube # Ensure it's a copy, not the same object |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.