Skip to content

Commit 70da0a8

Browse files
author
Stepheny Perez
authored
Merge pull request #53 from podaac/release/1.3.1
Release/1.3.1
2 parents 2f9a8b9 + ef5d650 commit 70da0a8

10 files changed

+176
-13
lines changed

.github/workflows/build-pipeline.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ jobs:
127127
tag: ${{ env.software_version }}
128128
message: "Version ${{ env.software_version }}"
129129
- name: Publish UMM-S with new version
130-
uses: podaac/cmr-umm-updater@0.1.1
130+
uses: podaac/cmr-umm-updater@0.2.1
131131
if: |
132132
github.ref == 'refs/heads/main' ||
133133
startsWith(github.ref, 'refs/heads/release')
@@ -136,9 +136,11 @@ jobs:
136136
provider: 'POCLOUD'
137137
env: ${{ env.venue }}
138138
version: ${{ env.software_version }}
139+
timeout: 60
139140
env:
140-
cmr_user: ${{secrets.CMR_USER}}
141-
cmr_pass: ${{secrets.CMR_PASS}}
141+
LAUNCHPAD_TOKEN_SIT: ${{secrets.LAUNCHPAD_TOKEN_SIT}}
142+
LAUNCHPAD_TOKEN_UAT: ${{secrets.LAUNCHPAD_TOKEN_UAT}}
143+
LAUNCHPAD_TOKEN_OPS: ${{secrets.LAUNCHPAD_TOKEN_OPS}}
142144
- name: Build Docs
143145
run: |
144146
poetry run sphinx-build -b html ./docs docs/_build/

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
### Fixed
1313
### Security
1414

15+
## [1.3.1]
16+
### Added
17+
- [issues/50](https://github.com/podaac/l2ss-py/issues/50): Spatial bounds are computed correctly for grouped empty subset operations
18+
- Added `timeout` option to `cmr-umm-updater`
19+
### Changed
20+
- Upgraded `cmr-umm-updater` to 0.2.1
21+
### Deprecated
22+
### Removed
23+
### Fixed
24+
- [issues/48](https://github.com/podaac/l2ss-py/issues/48): get_epoch_time_var was not able to pick up the 'time' variable for the TROPOMI CH4 collection. Extra elif statement was added to get the full time variable returned.
25+
- [issues/54](https://github.com/podaac/l2ss-py/issues/54): Skip encoding when xr dataset is empty
26+
### Security
27+
1528
## [1.3.0]
1629
### Added
1730
- [issues/27](https://github.com/podaac/l2ss-py/issues/27): Xarray is unable to handle variables with duplicate dimensions. Module dimension_cleanup.py added to handle variables that may have duplicate dimensions. Method remove_duplicate_dims() creates a new dimension identical dimension to the dimensions originally duplicated so the dimension does not need to be duplicated and can have the same shape and values.

cmr/ops_associations.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,7 @@ C2183155461-POCLOUD
3131
C2158344213-POCLOUD
3232
C2205618215-POCLOUD
3333
C2205618339-POCLOUD
34+
C2158348170-POCLOUD
35+
C2158348264-POCLOUD
36+
C2158350299-POCLOUD
37+
C2205121400-POCLOUD

cmr/uat_associations.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,6 @@ C1238538241-POCLOUD
1919
C1238658052-POCLOUD
2020
C1240739577-POCLOUD
2121
C1240739709-POCLOUD
22+
C1240739691-POCLOUD
23+
C1238621091-POCLOUD
24+
C1240739526-POCLOUD

podaac/subsetter/subset.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,13 @@ def get_time_epoch_var(dataset, time_var_name):
525525
epoch_var_name = time_var.attrs['comment'].split('plus')[0].strip()
526526
elif 'time' in dataset.variables.keys() and time_var_name != 'time':
527527
epoch_var_name = 'time'
528+
elif any('time' in s for s in list(dataset.variables.keys())) and time_var_name != 'time':
529+
for i in list(dataset.variables.keys()):
530+
group_list = i.split(GROUP_DELIM)
531+
if group_list[-1] == 'time':
532+
epoch_var_name = i
533+
break
534+
return epoch_var_name
528535
else:
529536
raise ValueError('Unable to determine time variables')
530537

@@ -977,6 +984,8 @@ def _rename_variables(dataset, base_dataset):
977984

978985
if variable.dtype == object:
979986
var_group.createVariable(new_var_name, 'S1', var_dims, fill_value=fill_value)
987+
elif variable.dtype == 'timedelta64[ns]':
988+
var_group.createVariable(new_var_name, 'i4', var_dims, fill_value=fill_value)
980989
else:
981990
var_group.createVariable(new_var_name, variable.dtype, var_dims, fill_value=fill_value)
982991

@@ -1110,7 +1119,8 @@ def subset(file_to_subset, bbox, output_file, variables=None, # pylint: disable
11101119
encoding = {}
11111120
compression = dict(zlib=True, complevel=5, _FillValue=None)
11121121

1113-
if (min_time or max_time) and any(dataset.dims.values()):
1122+
if (min_time or max_time) and not all(
1123+
dim_size == 1 for dim_size in dataset.dims.values()):
11141124
encoding = {
11151125
var_name: {
11161126
'units': nc_dataset.variables[var_name].__dict__['units'],
@@ -1131,6 +1141,10 @@ def subset(file_to_subset, bbox, output_file, variables=None, # pylint: disable
11311141

11321142
if has_groups:
11331143
recombine_grouped_datasets(datasets, output_file)
1144+
# Check if the spatial bounds are all 'None'. This means the
1145+
# subset result is empty.
1146+
if any(bound is None for bound in spatial_bounds):
1147+
return None
11341148
return np.array([[
11351149
min(lon[0][0][0] for lon in zip(spatial_bounds)),
11361150
max(lon[0][0][1] for lon in zip(spatial_bounds))

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
[tool.poetry]
1414
name = "l2ss-py"
15-
version = "1.3.0"
15+
version = "1.3.1-rc.9"
1616
description = "L2 Subsetter Service"
1717
authors = ["podaac-tva <[email protected]>"]
1818
license = "Apache-2.0"
13.5 MB
Binary file not shown.
-19.4 KB
Binary file not shown.

tests/test_subset.py

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1434,7 +1434,14 @@ def test_variable_dims_matched_tropomi(self):
14341434
var_name: [dim.split(subset.GROUP_DELIM)[-1] for dim in var.dimensions]
14351435
for var_name, var in in_nc.groups['PRODUCT'].variables.items()
14361436
}
1437-
1437+
1438+
# Get variables from METADATA group
1439+
in_var_dims.update(
1440+
{
1441+
var_name: [dim.split(subset.GROUP_DELIM)[-1] for dim in var.dimensions]
1442+
for var_name, var in in_nc.groups['METADATA'].groups['QA_STATISTICS'].variables.items()
1443+
}
1444+
)
14381445
# Include PRODUCT>SUPPORT_DATA>GEOLOCATIONS location
14391446
in_var_dims.update(
14401447
{
@@ -1502,6 +1509,42 @@ def test_temporal_merged_topex(self):
15021509
assert (out_ds.time.values >= start_delta_dt).all()
15031510
assert (out_ds.time.values <= end_delta_dt).all()
15041511

1512+
def test_get_time_epoch_var(self):
1513+
"""
1514+
Test that get_time_epoch_var method returns the 'time' variable for the tropomi CH4 granule"
1515+
"""
1516+
bbox = np.array(((-180, 180), (-90, 90)))
1517+
tropomi_file = 'S5P_OFFL_L2__CH4____20190319T110835_20190319T125006_07407_01_010202_20190325T125810_subset.nc4'
1518+
1519+
shutil.copyfile(os.path.join(self.test_data_dir, 'tropomi', tropomi_file),
1520+
os.path.join(self.subset_output_dir, tropomi_file))
1521+
1522+
1523+
nc_dataset = nc.Dataset(os.path.join(self.subset_output_dir, tropomi_file), mode='r')
1524+
1525+
nc_dataset = subset.transform_grouped_dataset(nc_dataset, os.path.join(self.subset_output_dir, tropomi_file))
1526+
1527+
args = {
1528+
'decode_coords': False,
1529+
'mask_and_scale': False,
1530+
'decode_times': False
1531+
}
1532+
1533+
with xr.open_dataset(
1534+
xr.backends.NetCDF4DataStore(nc_dataset),
1535+
**args
1536+
) as dataset:
1537+
1538+
lat_var_names, lon_var_names = subset.get_coord_variable_names(dataset)
1539+
time_var_names = [
1540+
subset.get_time_variable_name(
1541+
dataset, dataset[lat_var_name]
1542+
) for lat_var_name in lat_var_names
1543+
]
1544+
epoch_time_var = subset.get_time_epoch_var(dataset, time_var_names[0])
1545+
1546+
assert epoch_time_var.split('__')[-1] == 'time'
1547+
15051548
def test_temporal_variable_subset(self):
15061549
"""
15071550
Test that both a temporal and variable subset can be executed
@@ -1580,3 +1623,54 @@ def test_temporal_subset_lines(self):
15801623
)
15811624

15821625
assert ds.time.dims != ds.latitude.dims
1626+
1627+
def test_grouped_empty_subset(self):
1628+
"""
1629+
Test that an empty subset of a grouped dataset returns 'None'
1630+
spatial bounds.
1631+
"""
1632+
bbox = np.array(((-10, 10), (-10, 10)))
1633+
file = 'S6A_P4_2__LR_STD__ST_002_140_20201207T011501_20201207T013023_F00.nc'
1634+
output_file = "{}_{}".format(self._testMethodName, file)
1635+
1636+
shutil.copyfile(os.path.join(self.test_data_dir, 'sentinel_6', file),
1637+
os.path.join(self.subset_output_dir, file))
1638+
1639+
spatial_bounds = subset.subset(
1640+
file_to_subset=join(self.subset_output_dir, file),
1641+
bbox=bbox,
1642+
output_file=join(self.subset_output_dir, output_file)
1643+
)
1644+
1645+
assert spatial_bounds is None
1646+
1647+
def test_empty_temporal_subset(self):
1648+
"""
1649+
Test the edge case where a subsetted empty granule
1650+
(due to bbox) is temporally subset, which causes the encoding
1651+
step to fail due to size '1' data for each dimension.
1652+
"""
1653+
# 37.707:38.484
1654+
bbox = np.array(((37.707, 38.484), (-13.265, -12.812)))
1655+
file = '20190927000500-JPL-L2P_GHRSST-SSTskin-MODIS_A-D-v02.0-fv01.0.nc'
1656+
output_file = "{}_{}".format(self._testMethodName, file)
1657+
min_time = '2019-09-01'
1658+
max_time = '2019-09-30'
1659+
1660+
subset.subset(
1661+
file_to_subset=join(self.test_data_dir, file),
1662+
bbox=bbox,
1663+
output_file=join(self.subset_output_dir, output_file),
1664+
min_time=min_time,
1665+
max_time=max_time
1666+
)
1667+
1668+
# Check that all times are within the given bounds. Open
1669+
# dataset using 'decode_times=True' for auto-conversions to
1670+
# datetime
1671+
ds = xr.open_dataset(
1672+
join(self.subset_output_dir, output_file),
1673+
decode_coords=False
1674+
)
1675+
1676+
assert all(dim_size == 1 for dim_size in ds.dims.values())

tests/test_subset_harmony.py

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,24 @@
2121
import json
2222
import os.path
2323
import sys
24+
import pytest
25+
import shutil
26+
import tempfile
2427
from unittest.mock import patch, MagicMock
2528

2629
import podaac.subsetter
2730
from podaac.subsetter.subset_harmony import L2SubsetterService
2831
from harmony.util import config
2932

3033

34+
@pytest.fixture()
35+
def temp_dir():
36+
test_data_dir = os.path.dirname(os.path.realpath(__file__))
37+
temp_dir = tempfile.mkdtemp(dir=test_data_dir)
38+
yield temp_dir
39+
shutil.rmtree(temp_dir)
40+
41+
3142
def spy_on(method):
3243
"""
3344
Creates a spy for the given object instance method which records results
@@ -61,7 +72,7 @@ def wrapper(self, *args, **kwargs):
6172
return wrapper
6273

6374

64-
def test_service_invoke(mock_environ):
75+
def test_service_invoke(mock_environ, temp_dir):
6576
test_dir = os.path.dirname(os.path.realpath(__file__))
6677
input_json = json.load(
6778
open(os.path.join(test_dir, 'data', 'test_subset_harmony', 'test_service_invoke.input.json')))
@@ -101,12 +112,34 @@ def test_service_invoke(mock_environ):
101112
# When subset function returns 'None', bbox should not be passed to
102113
# the Harmony service lib 'async_add_local_file_partial_result'
103114
# function
115+
base_file_name = 'S6A_P4_2__LR_STD__ST_002_140_20201207T011501_20201207T013023_F00.nc'
116+
test_granule = os.path.join(
117+
test_dir,
118+
'data',
119+
'sentinel_6',
120+
base_file_name
121+
)
122+
123+
shutil.copyfile(
124+
test_granule,
125+
os.path.join(temp_dir, base_file_name)
126+
)
127+
128+
test_granule_temp = os.path.join(temp_dir, base_file_name)
129+
130+
input_json['sources'][0]['granules'][0]['url'] = f'file://{test_granule_temp}'
131+
input_json['sources'][0]['variables'][0]['name'] = '/data_01/wind_speed_alt'
132+
input_json['subset']['bbox'] = [-10, -10, 10, 10]
133+
134+
test_args = [
135+
podaac.subsetter.subset_harmony.__file__,
136+
"--harmony-action", "invoke",
137+
"--harmony-input", json.dumps(input_json)
138+
]
139+
104140
process_item_spy = spy_on(L2SubsetterService.process_item)
105141
with patch.object(sys, 'argv', test_args), \
106-
patch.object(L2SubsetterService, 'process_item', process_item_spy), \
107-
patch('podaac.subsetter.subset.subset') as mock_subsetter:
108-
109-
mock_subsetter.return_value = None
142+
patch.object(L2SubsetterService, 'process_item', process_item_spy):
110143

111144
podaac.subsetter.subset_harmony.main(config(False))
112145

@@ -118,5 +151,5 @@ def test_service_invoke(mock_environ):
118151
assert result.bbox == [-1, -2, 3, 4]
119152

120153
# Uses a filename that indicates no spatial subsetting
121-
filename = 'JA1_GPN_2PeP001_002_20020115_060706_20020115_070316_bathymetry.nc4'
122-
assert result.assets['data'].title == filename
154+
# filename = 'JA1_GPN_2PeP001_002_20020115_060706_20020115_070316_bathymetry.nc4'
155+
assert 'subsetted' not in result.assets['data'].title

0 commit comments

Comments
 (0)