From e1776679a70c717eb2fd90265ad4df0d3bbf14ba Mon Sep 17 00:00:00 2001 From: Nate Kean <14845347+garlic-os@users.noreply.github.com> Date: Wed, 26 Jun 2024 14:47:54 -0500 Subject: [PATCH 01/10] Add None checks to aws.get_s3_file --- tools/RAiDER/cli/raider.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/RAiDER/cli/raider.py b/tools/RAiDER/cli/raider.py index facf34eab..4a9feaf6e 100644 --- a/tools/RAiDER/cli/raider.py +++ b/tools/RAiDER/cli/raider.py @@ -532,6 +532,8 @@ def calcDelaysGUNW(iargs: list[str] = None) -> xr.Dataset: if not iargs.file and iargs.bucket: # only use GUNW ID for checking if HRRR available iargs.file = aws.get_s3_file(iargs.bucket, iargs.input_bucket_prefix, '.nc') + if iargs.file is None: + raise ValueError(f'GUNW product file could not be found at s3://{iargs.bucket}/{iargs.input_bucket_prefix}') if iargs.weather_model == 'HRRR' and (iargs.interpolate_time == 'azimuth_time_grid'): file_name_str = str(iargs.file) gunw_nc_name = file_name_str.split('/')[-1] @@ -548,12 +550,16 @@ def calcDelaysGUNW(iargs: list[str] = None) -> xr.Dataset: print('Nothing to do because outside of weather model range') return json_file_path = aws.get_s3_file(iargs.bucket, iargs.input_bucket_prefix, '.json') + if json_file_path is None: + raise ValueError(f'GUNW metadata file could not be found at s3://{iargs.bucket}/{iargs.input_bucket_prefix}') json_data = json.load(open(json_file_path)) json_data['metadata'].setdefault('weather_model', []).append(iargs.weather_model) json.dump(json_data, open(json_file_path, 'w')) # also get browse image -- if RAiDER is running in its own HyP3 job, the browse image will be needed for ingest browse_file_path = aws.get_s3_file(iargs.bucket, iargs.input_bucket_prefix, '.png') + if browse_file_path is None: + raise ValueError(f'GUNW browse image could not be found at s3://{iargs.bucket}/{iargs.input_bucket_prefix}') From c27c7a6e5f0cbd3042fd48de6cc44a2c2da1b393 Mon Sep 17 00:00:00 2001 From: Nate Kean <14845347+garlic-os@users.noreply.github.com> Date: Wed, 26 Jun 2024 14:51:04 -0500 Subject: [PATCH 02/10] AWS: Add type annotations --- environment.yml | 2 ++ tools/RAiDER/aws.py | 9 +++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/environment.yml b/environment.yml index e40ef8232..8eb22d2bb 100644 --- a/environment.yml +++ b/environment.yml @@ -67,3 +67,5 @@ dependencies: - jupyter_contrib_nbextensions - jupyterlab - wand + # For development + - boto3-type-annotations diff --git a/tools/RAiDER/aws.py b/tools/RAiDER/aws.py index 6e08a13f1..f25ece63a 100644 --- a/tools/RAiDER/aws.py +++ b/tools/RAiDER/aws.py @@ -1,12 +1,13 @@ +from typing import Optional, Union +from boto3_type_annotations.s3 import Client from mimetypes import guess_type from pathlib import Path -from typing import Union import boto3 from RAiDER.logger import logger -S3_CLIENT = boto3.client('s3') +S3_CLIENT: Client = boto3.client('s3') def get_tag_set() -> dict: @@ -28,7 +29,7 @@ def get_content_type(file_location: Union[Path, str]) -> str: return content_type -def upload_file_to_s3(path_to_file: Union[str, Path], bucket: str, prefix: str = ''): +def upload_file_to_s3(path_to_file: Union[str, Path], bucket: str, prefix: str = '') -> None: path_to_file = Path(path_to_file) key = str(Path(prefix) / path_to_file) extra_args = {'ContentType': get_content_type(key)} @@ -41,7 +42,7 @@ def upload_file_to_s3(path_to_file: Union[str, Path], bucket: str, prefix: str = S3_CLIENT.put_object_tagging(Bucket=bucket, Key=key, Tagging=tag_set) -def get_s3_file(bucket_name, bucket_prefix, file_type: str): +def get_s3_file(bucket_name: str, bucket_prefix: str, file_type: str) -> Optional[str]: result = S3_CLIENT.list_objects_v2(Bucket=bucket_name, Prefix=bucket_prefix) for s3_object in result['Contents']: key = s3_object['Key'] From 3fdbd5f33bff0252d92f428ecd67386f3ac5239e Mon Sep 17 00:00:00 2001 From: Nate Kean <14845347+garlic-os@users.noreply.github.com> Date: Wed, 26 Jun 2024 14:52:00 -0500 Subject: [PATCH 03/10] autopep8 --- tools/RAiDER/aws.py | 6 +- tools/RAiDER/cli/raider.py | 150 ++++++++++++++++++++++--------------- 2 files changed, 92 insertions(+), 64 deletions(-) diff --git a/tools/RAiDER/aws.py b/tools/RAiDER/aws.py index f25ece63a..369abd695 100644 --- a/tools/RAiDER/aws.py +++ b/tools/RAiDER/aws.py @@ -43,7 +43,10 @@ def upload_file_to_s3(path_to_file: Union[str, Path], bucket: str, prefix: str = def get_s3_file(bucket_name: str, bucket_prefix: str, file_type: str) -> Optional[str]: - result = S3_CLIENT.list_objects_v2(Bucket=bucket_name, Prefix=bucket_prefix) + result = S3_CLIENT.list_objects_v2( + Bucket=bucket_name, + Prefix=bucket_prefix + ) for s3_object in result['Contents']: key = s3_object['Key'] if key.endswith(file_type): @@ -51,4 +54,3 @@ def get_s3_file(bucket_name: str, bucket_prefix: str, file_type: str) -> Optiona logger.info(f'Downloading s3://{bucket_name}/{key} to {file_name}') S3_CLIENT.download_file(bucket_name, key, file_name) return file_name - diff --git a/tools/RAiDER/cli/raider.py b/tools/RAiDER/cli/raider.py index 4a9feaf6e..8d293f072 100644 --- a/tools/RAiDER/cli/raider.py +++ b/tools/RAiDER/cli/raider.py @@ -68,13 +68,15 @@ def read_template_file(fname): params = yaml.safe_load(f) except yaml.YAMLError as exc: print(exc) - raise ValueError('Something is wrong with the yaml file {}'.format(fname)) + raise ValueError( + 'Something is wrong with the yaml file {}'.format(fname)) # Drop any values not specified params = drop_nans(params) # Need to ensure that all the groups exist, even if they are not specified by the user - group_keys = ['date_group', 'time_group', 'aoi_group', 'height_group', 'los_group', 'runtime_group'] + group_keys = ['date_group', 'time_group', 'aoi_group', + 'height_group', 'los_group', 'runtime_group'] for key in group_keys: if not key in params.keys(): params[key] = {} @@ -91,13 +93,13 @@ def read_template_file(fname): if key == 'date_group': template['date_list'] = parse_dates(AttributeDict(value)) if key == 'aoi_group': - ## in case a DEM is passed and should be used + # in case a DEM is passed and should be used dct_temp = {**AttributeDict(value), **AttributeDict(params['height_group'])} template['aoi'] = get_query_region(AttributeDict(dct_temp)) if key == 'los_group': - template['los'] = get_los(AttributeDict(value)) + template['los'] = get_los(AttributeDict(value)) template['zref'] = AttributeDict(value).get('zref') if key == 'look_dir': if value.lower() not in ['right', 'left']: @@ -121,7 +123,7 @@ def read_template_file(fname): ) if key == 'weather_model': - template[key]= enforce_wm(value, template['aoi']) + template[key] = enforce_wm(value, template['aoi']) template['aoi']._cube_spacing_m = template['cube_spacing_in_m'] return AttributeDict(template) @@ -150,9 +152,8 @@ def calcDelays(iargs=None): '\n\t raider.py -g' p = argparse.ArgumentParser( - description = - 'Command line interface for RAiDER processing with a configure file.' - 'Default options can be found by running: raider.py --generate_config', + description='Command line interface for RAiDER processing with a configure file.' + 'Default options can be found by running: raider.py --generate_config', epilog=examples, formatter_class=argparse.RawDescriptionHelpFormatter) p.add_argument( @@ -171,12 +172,12 @@ def calcDelays(iargs=None): help='only download a weather model.' ) - ## if not None, will replace first argument (customTemplateFile) + # if not None, will replace first argument (customTemplateFile) args = p.parse_args(args=iargs) # default input file template_file = os.path.join(os.path.dirname(RAiDER.__file__), - 'cli', 'raider.yaml') + 'cli', 'raider.yaml') if args.generate_template: dst = os.path.join(os.getcwd(), 'raider.yaml') @@ -184,7 +185,6 @@ def calcDelays(iargs=None): logger.info('Wrote: %s', dst) sys.exit() - # check: existence of input template files if (not args.customTemplateFile and not os.path.isfile(os.path.basename(template_file)) @@ -197,7 +197,7 @@ def calcDelays(iargs=None): print(examples) raise SystemExit(f'ERROR: {msg}') - if args.customTemplateFile: + if args.customTemplateFile: # check the existence if not os.path.isfile(args.customTemplateFile): raise FileNotFoundError(args.customTemplateFile) @@ -210,7 +210,7 @@ def calcDelays(iargs=None): params = read_template_file(args.customTemplateFile) # Argument checking - params = checkArgs(params) + params = checkArgs(params) dl_only = True if params['download_only'] or args.download_only else False if not params.verbose: @@ -230,7 +230,7 @@ def calcDelays(iargs=None): # add a buffer determined by latitude for ray tracing if los.ray_trace(): wm_bounds = aoi.calc_buffer_ray(los.getSensorDirection(), - lookDir=los.getLookDirection(), incAngle=30) + lookDir=los.getLookDirection(), incAngle=30) else: wm_bounds = aoi.bounds() @@ -289,9 +289,11 @@ def calcDelays(iargs=None): # log when something else happens and then re-raise the error except Exception as e: S, N, W, E = wm_bounds - logger.info(f'Weather model point bounds are {S:.2f}/{N:.2f}/{W:.2f}/{E:.2f}') + logger.info(f'Weather model point bounds are { + S:.2f}/{N:.2f}/{W:.2f}/{E:.2f}') logger.info(f'Query datetime: {tt}') - msg = f'Downloading and/or preparation of {model._Name} failed.' + msg = f'Downloading and/or preparation of { + model._Name} failed.' logger.error(e) logger.error('Weather model files are: {}'.format(wfiles)) logger.error(msg) @@ -302,14 +304,15 @@ def calcDelays(iargs=None): continue # Get the weather model file - weather_model_file = getWeatherFile(wfiles, times, t, model._Name, interp_method) + weather_model_file = getWeatherFile( + wfiles, times, t, model._Name, interp_method) # Now process the delays try: wet_delay, hydro_delay = tropo_delay( t, weather_model_file, aoi, los, - height_levels = params['height_levels'], - out_proj = params['output_projection'], + height_levels=params['height_levels'], + out_proj=params['output_projection'], zref=params['zref'] ) except RuntimeError: @@ -344,23 +347,26 @@ def calcDelays(iargs=None): if out_filename.endswith(".nc"): ds.to_netcdf(out_filename, mode="w") elif out_filename.endswith(".h5"): - ds.to_netcdf(out_filename, engine="h5netcdf", invalid_netcdf=True) + ds.to_netcdf(out_filename, engine="h5netcdf", + invalid_netcdf=True) - logger.info('\nSuccessfully wrote delay cube to: %s\n', out_filename) + logger.info( + '\nSuccessfully wrote delay cube to: %s\n', out_filename) # Dataset returned: station files, radar_raster, geocoded_file else: if aoi.type() == 'station_file': out_filename = f'{os.path.splitext(out_filename)[0]}.csv' if aoi.type() in ['station_file', 'radar_rasters', 'geocoded_file']: - writeDelays(aoi, wet_delay, hydro_delay, out_filename, f, outformat=params['raster_format']) + writeDelays(aoi, wet_delay, hydro_delay, out_filename, + f, outformat=params['raster_format']) wet_filenames.append(out_filename) return wet_filenames -## ------------------------------------------------------ downloadGNSSDelays.py +# ------------------------------------------------------ downloadGNSSDelays.py def downloadGNSS(): """Parse command line arguments using argparse.""" from RAiDER.gnss.downloadGNSSDelays import main as dlGNSS @@ -441,13 +447,13 @@ def downloadGNSS(): add_cpus(misc) add_verbose(misc) - args = p.parse_args() + args = p.parse_args() dlGNSS(args) return -## ------------------------------------------------------------ prepFromGUNW.py +# ------------------------------------------------------------ prepFromGUNW.py def calcDelaysGUNW(iargs: list[str] = None) -> xr.Dataset: p = argparse.ArgumentParser( @@ -513,7 +519,8 @@ def calcDelaysGUNW(iargs: list[str] = None) -> xr.Dataset: iargs.input_bucket_prefix = iargs.bucket_prefix if iargs.interpolate_time not in ['none', 'center_time', 'azimuth_time_grid']: - raise ValueError('interpolate_time arg must be in [\'none\', \'center_time\', \'azimuth_time_grid\']') + raise ValueError( + 'interpolate_time arg must be in [\'none\', \'center_time\', \'azimuth_time_grid\']') if iargs.weather_model == 'None': # NOTE: HyP3's current step function implementation does not have a good way of conditionally @@ -527,13 +534,16 @@ def calcDelaysGUNW(iargs: list[str] = None) -> xr.Dataset: file_name = iargs.file.split('/')[-1] gunw_id = file_name.replace('.nc', '') if not RAiDER.aria.prepFromGUNW.check_hrrr_dataset_availablity_for_s1_azimuth_time_interpolation(gunw_id): - raise NoWeatherModelData('The required HRRR data for time-grid interpolation is not available') + raise NoWeatherModelData( + 'The required HRRR data for time-grid interpolation is not available') if not iargs.file and iargs.bucket: # only use GUNW ID for checking if HRRR available - iargs.file = aws.get_s3_file(iargs.bucket, iargs.input_bucket_prefix, '.nc') + iargs.file = aws.get_s3_file( + iargs.bucket, iargs.input_bucket_prefix, '.nc') if iargs.file is None: - raise ValueError(f'GUNW product file could not be found at s3://{iargs.bucket}/{iargs.input_bucket_prefix}') + raise ValueError(f'GUNW product file could not be found at s3://{ + iargs.bucket}/{iargs.input_bucket_prefix}') if iargs.weather_model == 'HRRR' and (iargs.interpolate_time == 'azimuth_time_grid'): file_name_str = str(iargs.file) gunw_nc_name = file_name_str.split('/')[-1] @@ -549,19 +559,22 @@ def calcDelaysGUNW(iargs: list[str] = None) -> xr.Dataset: # we include this within this portion of the control flow. print('Nothing to do because outside of weather model range') return - json_file_path = aws.get_s3_file(iargs.bucket, iargs.input_bucket_prefix, '.json') + json_file_path = aws.get_s3_file( + iargs.bucket, iargs.input_bucket_prefix, '.json') if json_file_path is None: - raise ValueError(f'GUNW metadata file could not be found at s3://{iargs.bucket}/{iargs.input_bucket_prefix}') + raise ValueError(f'GUNW metadata file could not be found at s3://{ + iargs.bucket}/{iargs.input_bucket_prefix}') json_data = json.load(open(json_file_path)) - json_data['metadata'].setdefault('weather_model', []).append(iargs.weather_model) + json_data['metadata'].setdefault( + 'weather_model', []).append(iargs.weather_model) json.dump(json_data, open(json_file_path, 'w')) # also get browse image -- if RAiDER is running in its own HyP3 job, the browse image will be needed for ingest - browse_file_path = aws.get_s3_file(iargs.bucket, iargs.input_bucket_prefix, '.png') + browse_file_path = aws.get_s3_file( + iargs.bucket, iargs.input_bucket_prefix, '.png') if browse_file_path is None: - raise ValueError(f'GUNW browse image could not be found at s3://{iargs.bucket}/{iargs.input_bucket_prefix}') - - + raise ValueError(f'GUNW browse image could not be found at s3://{ + iargs.bucket}/{iargs.input_bucket_prefix}') elif not iargs.file: raise ValueError('Either argument --file or --bucket must be provided') @@ -584,12 +597,14 @@ def calcDelaysGUNW(iargs: list[str] = None) -> xr.Dataset: # upload to s3 if iargs.bucket: aws.upload_file_to_s3(iargs.file, iargs.bucket, iargs.bucket_prefix) - aws.upload_file_to_s3(json_file_path, iargs.bucket, iargs.bucket_prefix) - aws.upload_file_to_s3(browse_file_path, iargs.bucket, iargs.bucket_prefix) + aws.upload_file_to_s3( + json_file_path, iargs.bucket, iargs.bucket_prefix) + aws.upload_file_to_s3( + browse_file_path, iargs.bucket, iargs.bucket_prefix) return ds -## ------------------------------------------------------------ processDelays.py +# ------------------------------------------------------------ processDelays.py def combineZTDFiles(): ''' Command-line program to process delay files from RAiDER and GNSS into a single file. @@ -635,13 +650,14 @@ def getWeatherFile(wfiles, times, t, model, interp_method='none'): try: Nfiles_expected = EXPECTED_NUM_FILES[interp_method] except KeyError: - raise ValueError('getWeatherFile: interp_method {} is not known'.format(interp_method)) + raise ValueError( + 'getWeatherFile: interp_method {} is not known'.format(interp_method)) Nmatch = (Nfiles_expected == Nfiles) Tmatch = (Nfiles == Ntimes) # Case 1: no files downloaded - if Nfiles==0: + if Nfiles == 0: logger.error('No weather model data was successfully processed.') return None @@ -649,28 +665,29 @@ def getWeatherFile(wfiles, times, t, model, interp_method='none'): if (interp_method == 'none'): weather_model_file = wfiles[0] - elif (interp_method == 'center_time'): - if Nmatch: # Case 3: two weather files downloaded + if Nmatch: # Case 3: two weather files downloaded weather_model_file = combine_weather_files( wfiles, t, model, interp_method='center_time' ) - elif Tmatch: # Case 4: Exact time is available without interpolation - logger.warning('Time interpolation is not needed as exact time is available') + elif Tmatch: # Case 4: Exact time is available without interpolation + logger.warning( + 'Time interpolation is not needed as exact time is available') weather_model_file = wfiles[0] - elif Nfiles == 1: # Case 5: one file does not download for some reason - logger.warning('getWeatherFile: One datetime is not available to download, defaulting to nearest available date') + elif Nfiles == 1: # Case 5: one file does not download for some reason + logger.warning( + 'getWeatherFile: One datetime is not available to download, defaulting to nearest available date') weather_model_file = wfiles[0] else: raise WrongNumberOfFiles(Nfiles_expected, Nfiles) elif (interp_method) == 'azimuth_time_grid': - if Nmatch or Tmatch: # Case 6: all files downloaded + if Nmatch or Tmatch: # Case 6: all files downloaded weather_model_file = combine_weather_files( wfiles, t, @@ -684,7 +701,7 @@ def getWeatherFile(wfiles, times, t, model, interp_method='none'): else: N = len(wfiles) raise NotImplementedError(f'The {interp_method} with {N} retrieved weather model files was not well posed ' - 'for the current workflow.') + 'for the current workflow.') return weather_model_file @@ -692,7 +709,8 @@ def getWeatherFile(wfiles, times, t, model, interp_method='none'): def combine_weather_files(wfiles, t, model, interp_method='center_time'): '''Interpolate downloaded weather files and save to a single file''' - STYLE = {'center_time': '_timeInterp_', 'azimuth_time_grid': '_timeInterpAziGrid_'} + STYLE = {'center_time': '_timeInterp_', + 'azimuth_time_grid': '_timeInterpAziGrid_'} # read the individual datetime datasets datasets = [xr.open_dataset(f) for f in wfiles] @@ -700,9 +718,10 @@ def combine_weather_files(wfiles, t, model, interp_method='center_time'): # Pull the datetimes from the datasets times = [] for ds in datasets: - times.append(datetime.datetime.strptime(ds.attrs['datetime'], '%Y_%m_%dT%H_%M_%S')) + times.append(datetime.datetime.strptime( + ds.attrs['datetime'], '%Y_%m_%dT%H_%M_%S')) - if len(times)==0: + if len(times) == 0: raise NoWeatherModelData() # calculate relative weights of each dataset @@ -723,8 +742,8 @@ def combine_weather_files(wfiles, t, model, interp_method='center_time'): weather_model_file = os.path.join( os.path.dirname(wfiles[0]), os.path.basename(wfiles[0]).split('_')[0] + '_' + - t.strftime('%Y_%m_%dT%H_%M_%S') + STYLE[interp_method] + - '_'.join(wfiles[0].split('_')[-4:]), + t.strftime('%Y_%m_%dT%H_%M_%S') + STYLE[interp_method] + + '_'.join(wfiles[0].split('_')[-4:]), ) # write the combined results to disk @@ -742,7 +761,8 @@ def combine_files_using_azimuth_time(wfiles, t, times): # Pull the datetimes from the datasets times = [] for ds in datasets: - times.append(datetime.datetime.strptime(ds.attrs['datetime'], '%Y_%m_%dT%H_%M_%S')) + times.append(datetime.datetime.strptime( + ds.attrs['datetime'], '%Y_%m_%dT%H_%M_%S')) model = datasets[0].attrs['model_name'] @@ -760,7 +780,8 @@ def combine_files_using_azimuth_time(wfiles, t, times): # Give the weighted combination a new file name weather_model_file = os.path.join( os.path.dirname(wfiles[0]), - os.path.basename(wfiles[0]).split('_')[0] + '_' + t.strftime('%Y_%m_%dT%H_%M_%S') + '_timeInterpAziGrid_' + '_'.join(wfiles[0].split('_')[-4:]), + os.path.basename(wfiles[0]).split('_')[0] + '_' + t.strftime( + '%Y_%m_%dT%H_%M_%S') + '_timeInterpAziGrid_' + '_'.join(wfiles[0].split('_')[-4:]), ) # write the combined results to disk @@ -771,13 +792,15 @@ def combine_files_using_azimuth_time(wfiles, t, times): def get_weights_time_interp(times, t): '''Calculate weights for time interpolation using simple inverse linear weighting''' - date1,date2 = times - wgts = [ 1 - get_dt(t, date1) / get_dt(date2, date1), 1 - get_dt(date2, t) / get_dt(date2, date1)] + date1, date2 = times + wgts = [1 - get_dt(t, date1) / get_dt(date2, date1), 1 - + get_dt(date2, t) / get_dt(date2, date1)] try: assert np.isclose(np.sum(wgts), 1) except AssertionError: - logger.error('Time interpolation weights do not sum to one; something is off with query datetime: %s', t) + logger.error( + 'Time interpolation weights do not sum to one; something is off with query datetime: %s', t) return None return wgts @@ -803,10 +826,13 @@ def get_time_grid_for_aztime_interp(datasets, t, model): hgt = np.broadcast_to(z_1d[:, None, None], (m, n, p)) else: - raise NotImplementedError('Azimuth Time is currently only implemented for HRRR') + raise NotImplementedError( + 'Azimuth Time is currently only implemented for HRRR') - time_grid = get_s1_azimuth_time_grid(lon, lat, hgt, t) # This is the acq time from loop + time_grid = get_s1_azimuth_time_grid( + lon, lat, hgt, t) # This is the acq time from loop if np.any(np.isnan(time_grid)): - raise ValueError('The Time Grid return nans meaning no orbit was downloaded.') + raise ValueError( + 'The Time Grid return nans meaning no orbit was downloaded.') return time_grid From 831da7f3139af8dfbcd9637349a89fce748587d9 Mon Sep 17 00:00:00 2001 From: Nate Kean <14845347+garlic-os@users.noreply.github.com> Date: Wed, 26 Jun 2024 14:58:05 -0500 Subject: [PATCH 04/10] Update CHANGELOG.md for #648 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ddaace79..f6178feda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ### Changed * [651](https://github.com/dbekaert/RAiDER/pull/651) Removed use of deprecated argument to `pandas.read_csv`. +### Fixed +* [648](https://github.com/dbekaert/RAiDER/issues/648) - Fixed opaque error message if a GUNW file is not produced while HyP3 independently against a previous INSAR_ISCE. ## [0.5.1] ### Changed From cf1ae492cd8dcd3997ac83788119d0c734fd7c53 Mon Sep 17 00:00:00 2001 From: Nate Kean <14845347+garlic-os@users.noreply.github.com> Date: Wed, 26 Jun 2024 14:58:51 -0500 Subject: [PATCH 05/10] Fix syntax errors from autopep8 Scary that it can produce invalid code... --- tools/RAiDER/cli/raider.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/tools/RAiDER/cli/raider.py b/tools/RAiDER/cli/raider.py index 8d293f072..82f6ffcd8 100644 --- a/tools/RAiDER/cli/raider.py +++ b/tools/RAiDER/cli/raider.py @@ -542,8 +542,10 @@ def calcDelaysGUNW(iargs: list[str] = None) -> xr.Dataset: iargs.file = aws.get_s3_file( iargs.bucket, iargs.input_bucket_prefix, '.nc') if iargs.file is None: - raise ValueError(f'GUNW product file could not be found at s3://{ - iargs.bucket}/{iargs.input_bucket_prefix}') + raise ValueError( + 'GUNW product file could not be found at' + f's3://{iargs.bucket}/{iargs.input_bucket_prefix}' + ) if iargs.weather_model == 'HRRR' and (iargs.interpolate_time == 'azimuth_time_grid'): file_name_str = str(iargs.file) gunw_nc_name = file_name_str.split('/')[-1] @@ -562,8 +564,10 @@ def calcDelaysGUNW(iargs: list[str] = None) -> xr.Dataset: json_file_path = aws.get_s3_file( iargs.bucket, iargs.input_bucket_prefix, '.json') if json_file_path is None: - raise ValueError(f'GUNW metadata file could not be found at s3://{ - iargs.bucket}/{iargs.input_bucket_prefix}') + raise ValueError( + 'GUNW metadata file could not be found at' + f's3://{iargs.bucket}/{iargs.input_bucket_prefix}' + ) json_data = json.load(open(json_file_path)) json_data['metadata'].setdefault( 'weather_model', []).append(iargs.weather_model) @@ -573,8 +577,10 @@ def calcDelaysGUNW(iargs: list[str] = None) -> xr.Dataset: browse_file_path = aws.get_s3_file( iargs.bucket, iargs.input_bucket_prefix, '.png') if browse_file_path is None: - raise ValueError(f'GUNW browse image could not be found at s3://{ - iargs.bucket}/{iargs.input_bucket_prefix}') + raise ValueError( + 'GUNW browse image could not be found at' + f's3://{iargs.bucket}/{iargs.input_bucket_prefix}' + ) elif not iargs.file: raise ValueError('Either argument --file or --bucket must be provided') From 6087b4eec883d8f692d467ccd55bf88000ee82a3 Mon Sep 17 00:00:00 2001 From: Nate Kean <14845347+garlic-os@users.noreply.github.com> Date: Sat, 6 Jul 2024 17:02:54 -0500 Subject: [PATCH 06/10] Create regression tests for #658 --- test/test_raises_for_missing_gunw.py | 82 ++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 test/test_raises_for_missing_gunw.py diff --git a/test/test_raises_for_missing_gunw.py b/test/test_raises_for_missing_gunw.py new file mode 100644 index 000000000..482e4fb66 --- /dev/null +++ b/test/test_raises_for_missing_gunw.py @@ -0,0 +1,82 @@ +''' +Regression tests for issue #648: +Bad error message when GUNW file missing in S3 bucket + +Program should raise an error if the GUNW product file, metadata file, +or browse image is missing that clearly explains what went wrong, as opposed to +a generic error message resulting from a side effect of the error. +''' +from contextlib import contextmanager +from typing import List + +import pytest +import shutil +from tempfile import TemporaryDirectory +from pathlib import Path +import RAiDER.aws +import RAiDER.cli.raider + + +EXAMPLE_GUNW_PATH = 'test/gunw_test_data/S1-GUNW-D-R-059-tops-20230320_20220418-180300-00179W_00051N-PP-c92e-v2_0_6.nc' +EXAMPLE_JSON_DATA_PATH = 'test/gunw_test_data/S1-GUNW-A-R-064-tops-20210723_20210711-015001-35393N_33512N-PP-6267-v2_0_4.json' + + +@pytest.fixture +def iargs() -> List[str]: + return [ + '--bucket', 'dummy-bucket', + '--input-bucket-prefix', 'dummy-input-prefix', + '--weather-model', 'ERA5', + ] + +@contextmanager +def make_gunw_path(): + with TemporaryDirectory() as tempdir: + shutil.copy(EXAMPLE_GUNW_PATH, tempdir) + yield Path(tempdir) / Path(EXAMPLE_GUNW_PATH).name + +@contextmanager +def make_json_data_path(): + with TemporaryDirectory() as tempdir: + shutil.copy(EXAMPLE_JSON_DATA_PATH, tempdir) + yield Path(tempdir) / Path(EXAMPLE_JSON_DATA_PATH).name + + +# Patch aws.get_s3_file to produce None then check for the correct error message +def test_missing_product_file(mocker, iargs): + side_effect = [ + None, # GUNW product file + # program should fail + ] + mocker.patch('RAiDER.aws.get_s3_file', side_effect=side_effect) + with pytest.raises(ValueError) as excinfo: + RAiDER.cli.raider.calcDelaysGUNW(iargs) + assert "GUNW product file could not be found" in str(excinfo.value) + + +# Patch aws.get_s3_file to produce None then check for the correct error message +def test_missing_metadata_file(mocker, iargs): + with make_gunw_path() as gunw_path: + side_effect = [ + gunw_path, # GUNW product file + None, # GUNW metadata file + # program should fail + ] + mocker.patch('RAiDER.aws.get_s3_file', side_effect=side_effect) + with pytest.raises(ValueError) as excinfo: + RAiDER.cli.raider.calcDelaysGUNW(iargs) + assert "GUNW metadata file could not be found" in str(excinfo.value) + + +def test_missing_browse_image(mocker, iargs): + with make_gunw_path() as gunw_path, make_json_data_path() as json_data_path: + side_effect = [ + gunw_path, # GUNW product file + json_data_path, # GUNW metadata file + None, # GUNW browse image + # program should fail + ] + mocker.patch('RAiDER.aws.get_s3_file', side_effect=side_effect) + with pytest.raises(ValueError) as excinfo: + RAiDER.cli.raider.calcDelaysGUNW(iargs) + assert "GUNW browse image could not be found" in str(excinfo.value) From e09f1acf7184e1d3ac879977284f6496b26c214a Mon Sep 17 00:00:00 2001 From: Nate Kean <14845347+garlic-os@users.noreply.github.com> Date: Sat, 6 Jul 2024 18:48:46 -0500 Subject: [PATCH 07/10] Prefix test data paths with TEST_DIR --- test/test_raises_for_missing_gunw.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/test_raises_for_missing_gunw.py b/test/test_raises_for_missing_gunw.py index 482e4fb66..9b748d7af 100644 --- a/test/test_raises_for_missing_gunw.py +++ b/test/test_raises_for_missing_gunw.py @@ -10,15 +10,16 @@ from typing import List import pytest +from test import TEST_DIR + import shutil from tempfile import TemporaryDirectory from pathlib import Path import RAiDER.aws import RAiDER.cli.raider - -EXAMPLE_GUNW_PATH = 'test/gunw_test_data/S1-GUNW-D-R-059-tops-20230320_20220418-180300-00179W_00051N-PP-c92e-v2_0_6.nc' -EXAMPLE_JSON_DATA_PATH = 'test/gunw_test_data/S1-GUNW-A-R-064-tops-20210723_20210711-015001-35393N_33512N-PP-6267-v2_0_4.json' +EXAMPLE_GUNW_PATH = Path(TEST_DIR) / 'gunw_test_data/S1-GUNW-D-R-059-tops-20230320_20220418-180300-00179W_00051N-PP-c92e-v2_0_6.nc' +EXAMPLE_JSON_DATA_PATH = Path(TEST_DIR) / 'gunw_test_data/S1-GUNW-A-R-064-tops-20210723_20210711-015001-35393N_33512N-PP-6267-v2_0_4.json' @pytest.fixture From 23eb9b32564acd7e3d4e1a732b11790098ed18ec Mon Sep 17 00:00:00 2001 From: Nate Kean <14845347+garlic-os@users.noreply.github.com> Date: Sat, 6 Jul 2024 19:15:00 -0500 Subject: [PATCH 08/10] Switch boto3-type-annotations for boto3-stubs --- environment.yml | 2 +- tools/RAiDER/aws.py | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/environment.yml b/environment.yml index 8385b769c..1a4c7085b 100644 --- a/environment.yml +++ b/environment.yml @@ -68,4 +68,4 @@ dependencies: - jupyterlab - wand # For development - - boto3-type-annotations + - boto3-stubs diff --git a/tools/RAiDER/aws.py b/tools/RAiDER/aws.py index 369abd695..6afb44e5a 100644 --- a/tools/RAiDER/aws.py +++ b/tools/RAiDER/aws.py @@ -1,5 +1,4 @@ from typing import Optional, Union -from boto3_type_annotations.s3 import Client from mimetypes import guess_type from pathlib import Path @@ -7,10 +6,10 @@ from RAiDER.logger import logger -S3_CLIENT: Client = boto3.client('s3') +S3_CLIENT = boto3.client('s3') -def get_tag_set() -> dict: +def get_tag_set(): tag_set = { 'TagSet': [ { From b2374066df363bbc7abb97a575bc093e4d04e3f4 Mon Sep 17 00:00:00 2001 From: Nate Kean <14845347+garlic-os@users.noreply.github.com> Date: Sat, 6 Jul 2024 19:32:53 -0500 Subject: [PATCH 09/10] Fix autopep8 corruption https://github.com/hhatto/autopep8/issues/712 --- tools/RAiDER/cli/raider.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/RAiDER/cli/raider.py b/tools/RAiDER/cli/raider.py index 451b89542..dc8c6442f 100644 --- a/tools/RAiDER/cli/raider.py +++ b/tools/RAiDER/cli/raider.py @@ -289,8 +289,10 @@ def calcDelays(iargs=None): # log when something else happens and then re-raise the error except Exception as e: S, N, W, E = wm_bounds - logger.info(f'Weather model point bounds are { - S:.2f}/{N:.2f}/{W:.2f}/{E:.2f}') + logger.info( + 'Weather model point bounds are' + f'{S:.2f}/{N:.2f}/{W:.2f}/{E:.2f}' + ) logger.info(f'Query datetime: {tt}') msg = f'Downloading and/or preparation of { model._Name} failed.' From b23302a99c870b13501908bbafcc9ea44783a479 Mon Sep 17 00:00:00 2001 From: Nate Kean <14845347+garlic-os@users.noreply.github.com> Date: Sat, 6 Jul 2024 20:18:09 -0500 Subject: [PATCH 10/10] Fix autopep8 corruption https://github.com/hhatto/autopep8/issues/712 --- tools/RAiDER/cli/raider.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/RAiDER/cli/raider.py b/tools/RAiDER/cli/raider.py index dc8c6442f..4f125ca26 100644 --- a/tools/RAiDER/cli/raider.py +++ b/tools/RAiDER/cli/raider.py @@ -294,11 +294,11 @@ def calcDelays(iargs=None): f'{S:.2f}/{N:.2f}/{W:.2f}/{E:.2f}' ) logger.info(f'Query datetime: {tt}') - msg = f'Downloading and/or preparation of { - model._Name} failed.' logger.error(e) logger.error('Weather model files are: {}'.format(wfiles)) - logger.error(msg) + logger.error( + f'Downloading and/or preparation of {model._Name} failed.' + ) continue # dont process the delays for download only