Repository: RadioAstronomySoftwareGroup/pyuvdata
Pull request #632 (2019-07-06 21:13:19)

Option in UVBase.check() to ignore unset required parameters
<!--- Provide a general summary of your changes in the Title above -->

## Description
<!--- Describe your changes in detail -->

Adds a new option to the UVBase.check(), `only_check_set`. This ignores cases when required parameters aren't set, and only checks non-None parameters for acceptability.

## Motivation and Context
<!--- Why is this change required? What problem does it solve? -->
In pyuvsim, it happened that we weren't setting up certain parameters correctly at the start (specifically, `integration_time`). Though the initial UVData object does not have all the required parameters set, those that are set need to be checked for acceptability. I'm not aware of any other way to do this.
<!--- If it fixes an open issue, please link to the issue here. If this PR closes an issue, put the word 'closes' before the issue link to auto-close the issue when the PR is merged. -->

## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Documentation change (documentation changes only)
- [ ] Version change
- [ ] Build or continuous integration change


## Checklist:
<!--- You may remove the checklists that don't apply to your change type(s) or just leave them empty -->
<!--- Go over all the following points, and replace the space with an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [ ] I have read the [contribution guide](https://github.com/RadioAstronomySoftwareGroup/pyuvdata/blob/master/.github/CONTRIBUTING.md).
- [ ] My code follows the code style of this project.

New feature checklist:
- [x] I have added or updated the docstrings associated with my feature using the [numpy docstring format](https://numpydoc.readthedocs.io/en/latest/format.html).
- [ ] I have updated the tutorial to highlight my new feature (if appropriate).
- [x] I have added tests to cover my new feature.
- [x] All new and existing tests pass in both python 2 and python 3.
- [x] I have updated the [CHANGELOG](https://github.com/RadioAstronomySoftwareGroup/pyuvdata/blob/master/CHANGELOG.md).

codecov[bot]: # [Codecov](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/632?src=pr&el=h1) Report
> Merging [#632](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/632?src=pr&el=desc) into [master](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/commit/67eca3a770ec22814fac626fab59f6834f136eb8?src=pr&el=desc) will **increase** coverage by `<.01%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/632/graphs/tree.svg?width=650&token=4QykcCeB1H&height=150&src=pr)](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/632?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #632      +/-   ##
==========================================
+ Coverage   98.87%   98.87%   +<.01%     
==========================================
  Files          20       20              
  Lines        8062     8064       +2     
==========================================
+ Hits         7971     7973       +2     
  Misses         91       91
```


| [Impacted Files](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/632?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pyuvdata/uvbase.py](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/632/diff?src=pr&el=tree#diff-cHl1dmRhdGEvdXZiYXNlLnB5) | `100% <100%> (ø)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/632?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/632?src=pr&el=footer). Last update [67eca3a...ed88429](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/632?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8e555eef..2e7dffb5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file.
 ## [1.4.0] - 2019-05-23
 
 ### Added
+- Option in UVBase.check() to ignore whether required parameters are set.
 - made an option to not save the `lst_array` to uvfits files.
 - `conjugate_bls` option to `UVData.get_antenna_redundancies`
 - `UVData.conjugate_bls` method to conjugate baselines to get the desired baseline directions.
diff --git a/pyuvdata/uvbase.py b/pyuvdata/uvbase.py
index 72dea485..e4b9154f 100644
--- a/pyuvdata/uvbase.py
+++ b/pyuvdata/uvbase.py
@@ -71,6 +71,7 @@ class UVBase(object):
 
     def prop_fget(self, param_name):
         """Getter method for UVParameter properties."""
+
         def fget(self):
             this_param = getattr(self, param_name)
             return this_param.value
@@ -78,6 +79,7 @@ class UVBase(object):
 
     def prop_fset(self, param_name):
         """Setter method for UVParameter properties."""
+
         def fset(self, value):
             this_param = getattr(self, param_name)
             this_param.value = value
@@ -86,6 +88,7 @@ class UVBase(object):
 
     def degree_prop_fget(self, param_name):
         """Degree getter method for AngleParameter properties."""
+
         def fget(self):
             this_param = getattr(self, param_name)
             return this_param.degrees()
@@ -93,6 +96,7 @@ class UVBase(object):
 
     def degree_prop_fset(self, param_name):
         """Degree setter method for AngleParameter properties."""
+
         def fset(self, value):
             this_param = getattr(self, param_name)
             this_param.set_degrees(value)
@@ -101,6 +105,7 @@ class UVBase(object):
 
     def lat_lon_alt_prop_fget(self, param_name):
         """Lat/lon/alt getter method for LocationParameter properties."""
+
         def fget(self):
             this_param = getattr(self, param_name)
             return this_param.lat_lon_alt()
@@ -108,6 +113,7 @@ class UVBase(object):
 
     def lat_lon_alt_prop_fset(self, param_name):
         """Lat/lon/alt setter method for LocationParameter properties."""
+
         def fset(self, value):
             this_param = getattr(self, param_name)
             this_param.set_lat_lon_alt(value)
@@ -116,6 +122,7 @@ class UVBase(object):
 
     def lat_lon_alt_degrees_prop_fget(self, param_name):
         """Lat/lon/alt degree getter method for LocationParameter properties."""
+
         def fget(self):
             this_param = getattr(self, param_name)
             return this_param.lat_lon_alt_degrees()
@@ -123,6 +130,7 @@ class UVBase(object):
 
     def lat_lon_alt_degrees_prop_fset(self, param_name):
         """Lat/lon/alt degree setter method for LocationParameter properties."""
+
         def fset(self, value):
             this_param = getattr(self, param_name)
             this_param.set_lat_lon_alt_degrees(value)
@@ -222,16 +230,19 @@ class UVBase(object):
         """Not equal."""
         return not self.__eq__(other)
 
-    def check(self, check_extra=True, run_check_acceptability=True):
+    def check(self, check_extra=True, run_check_acceptability=True, ignore_requirements=False):
         """
-        Check that required parameters exist. Check that parameters have
-        appropriate shapes and optionally that the values are acceptable.
+        Check that required parameters exist and have the correct shapes.
+        Optionally, check that the values are acceptable.
 
         Args:
             check_extra: If true, check shapes and values on all parameters,
                 otherwise only check required parameters.
-            run_check_acceptability: Option to check if values in required parameters
+            run_check_acceptability: Option to check if values in parameters
                 are acceptable. Default is True.
+            ignore_requirements: Do not error if a required parameter isn't set.
+                Default is False. This allows the user to run the shape/acceptability checks
+                on parameters in a partially-defined UVData object.
         """
         if check_extra:
             p_check = [p for p in self.required()] + [p for p in self.extra()]
@@ -242,6 +253,8 @@ class UVBase(object):
             param = getattr(self, p)
             # Check required parameter exists
             if param.value is None:
+                if ignore_requirements:
+                    continue
                 if param.required is True:
                     raise ValueError('Required UVParameter ' + p
                                      + ' has not been set.')


Test patch:
diff --git a/pyuvdata/tests/test_uvbase.py b/pyuvdata/tests/test_uvbase.py
index 4bc48ba2..304ae860 100644
--- a/pyuvdata/tests/test_uvbase.py
+++ b/pyuvdata/tests/test_uvbase.py
@@ -69,6 +69,9 @@ class UVTest(UVBase):
         self._optional_int2 = uvp.UVParameter('optional_int2', description='optional integer value',
                                               expected_type=int, value=5, required=False)
 
+        self._unset_int1 = uvp.UVParameter('unset_int1', description='An unset parameter.',
+                                           expected_type=int, value=None, required=False)
+
         super(UVTest, self).__init__()
 
 
@@ -230,3 +233,10 @@ def test_location_degree_none():
 def test_warning():
     output = _warning("hello world")
     assert output, "hello world\n"
+
+
+def test_check_ignore_unset():
+    test_obj = UVTest()
+    test_obj._unset_int1.required = True
+    pytest.raises(ValueError, test_obj.check)
+    assert test_obj.check(ignore_requirements=True)