Skip to content

Commit 19e7778

Browse files
committed
Merge branch 'main-2.0' into release-2.62
# Conflicts: # poetry.lock # pyproject.toml # weather_provider_api/routers/weather/sources/cds/client/cds_api_tools.py # weather_provider_api/routers/weather/sources/cds/client/era5_utils.py
2 parents 24d7976 + 74115e3 commit 19e7778

File tree

10 files changed

+283
-70
lines changed

10 files changed

+283
-70
lines changed

.github/workflows/github-pages-deployment.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
run: |
2323
sphinx-build ./sphinx-docs/ _build
2424
- name: Upload GitHub Pages artifact
25-
uses: actions/upload-pages-artifact@v2.0.0
25+
uses: actions/upload-pages-artifact@v3
2626
with:
2727
path: _build
2828
- name: Push artifact to pages

.github/workflows/pull-request-workflow.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ jobs:
3434
run: poetry install -v --with dev
3535
# Run Coverage
3636
- name: Run Coverage
37+
env:
38+
CDSAPI_URL: ${{ secrets.CDSAPI_URL }}
39+
CDSAPI_KEY: ${{ secrets.CDSAPI_KEY }}
40+
CDSAPI_VERIFY: 1
3741
run: |
3842
poetry run coverage run -m pytest
3943
poetry run coverage report

.github/workflows/release-candidate-workflow.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ jobs:
3535
run: poetry install -v --with dev
3636
# Run Coverage
3737
- name: Run Coverage
38+
env:
39+
CDSAPI_URL: ${{ secrets.CDSAPI_URL }}
40+
CDSAPI_KEY: ${{ secrets.CDSAPI_KEY }}
41+
CDSAPI_VERIFY: 1
3842
run: |
3943
poetry run coverage run -m pytest
4044
poetry run coverage report

.github/workflows/version-update-workflow.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ jobs:
3535
run: poetry install -v --with dev
3636
# Run Coverage
3737
- name: Run Coverage
38+
env:
39+
CDSAPI_URL: ${{ secrets.CDSAPI_URL }}
40+
CDSAPI_KEY: ${{ secrets.CDSAPI_KEY }}
41+
CDSAPI_VERIFY: 1
3842
run: |
3943
poetry run coverage run -m pytest
4044
poetry run coverage report

poetry.lock

Lines changed: 216 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "weather_provider_api"
3-
version = "2.62.50"
3+
version = "2.62.0"
44
description = "Weather Provider Libraries and API"
55
authors = ["Verbindingsteam", "Raoul Linnenbank <[email protected]>"]
66
license = "MPL-2.0"

tests/test_api_view_v1.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,6 @@ def test_header_accept_type():
1717

1818
# The get_source, get_sources and get_sync_models only pass on requests to other functions and do not need to be tested
1919

20-
# All of the code inside get_sync_weather consists of either externally called functions that are already covered in
21-
# their respective modules, or either Python system calls or calls to external libraries outside of the testing scope.
20+
# All the code inside get_sync_weather consists of either externally called functions that are already covered in
21+
# their respective modules, or either Python system calls or calls to external libraries outside the testing scope.
2222
# Full coverage is therefore implied and assumed

weather_provider_api/routers/weather/sources/cds/client/cds_api_tools.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,9 @@ def request_parameters(self) -> dict[str, str | list[str] | tuple[float]]:
9898
"day": self.day,
9999
"time": self.time,
100100
"area": self.area,
101+
"data_format": self.data_format,
102+
"download_format": self.download_format,
101103
}
102104

103105

104-
CDS_CLIENT = cdsapi.Client(info_callback=_info_callback())
106+
CDS_CLIENT = cdsapi.Client(info_callback=_info_callback(), url="https://cds.climate.copernicus.eu/api")

weather_provider_api/routers/weather/sources/cds/client/era5_utils.py

Lines changed: 47 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ def era5_repository_update(update_settings: Era5UpdateSettings) -> RepositoryUpd
4646
starting_moment_of_update = datetime.now(UTC)
4747
cutoff_time = starting_moment_of_update + relativedelta(minutes=update_settings.maximum_runtime_in_minutes)
4848
logger.info(
49-
f"Starting update of ERA5 data for {update_settings.era5_dataset_to_update_from} to: {update_settings.target_storage_location}"
49+
f"Starting update of ERA5 data for {update_settings.era5_dataset_to_update_from} "
50+
f"to: {update_settings.target_storage_location}"
5051
)
5152
logger.debug(f" - Attempting update for time range: {update_settings.repository_time_range}")
5253
logger.debug(f" - Factors to process: {update_settings.factors_to_process}")
@@ -78,8 +79,9 @@ def _era5_update_month_by_month(
7879
)
7980

8081
while update_month > target_update_month:
82+
logger.info(f" > Processing month: {update_month.year}-{update_month.month}")
8183
if datetime.now(UTC) + relativedelta(minutes=average_time_per_month_in_minutes) > cutoff_time:
82-
print(
84+
logger.warning(
8385
"MAXIMUM RUNTIME REACHED: ",
8486
cutoff_time,
8587
datetime.now(UTC) + relativedelta(minutes=average_time_per_month_in_minutes),
@@ -97,7 +99,6 @@ def _era5_update_month_by_month(
9799
logger.warning("More than 50% of the months failed to process. Stopping update.")
98100
break
99101

100-
update_month = update_month - relativedelta(month=1)
101102
average_time_per_month_in_minutes = (
102103
(datetime.now(UTC) - starting_moment_of_update).total_seconds() / 60 / amount_of_months_processed
103104
)
@@ -116,7 +117,7 @@ def _era5_update_month(update_settings: Era5UpdateSettings, update_month: dateti
116117

117118
month_file_base = f"{update_settings.filename_prefix}_{update_month.year}_{update_month.month:02d}"
118119
month_file = update_settings.target_storage_location / f"{month_file_base}"
119-
threshold_date = (datetime.now(UTC) - relativedelta(days=5, months=3)).replace(day=1)
120+
threshold_date = (datetime.now(UTC) - relativedelta(days=5)).replace(day=1)
120121

121122
if file_requires_update(month_file, update_month, threshold_date):
122123
logger.debug(f" > File {month_file} requires update.")
@@ -132,19 +133,19 @@ def _era5_update_month(update_settings: Era5UpdateSettings, update_month: dateti
132133
month=[str(update_month.month)],
133134
day=[str(i) for i in list(range(1, 32))],
134135
time=[f"{hour:02d}:00" for hour in range(24)],
135-
area=(7.22, 50.75, 3.2, 53.7),
136+
area=(53.510403, 3.314971, 50.803721, 7.092053),
136137
),
137138
target_location=str(month_file_name),
138139
)
139140

140-
print("Stored file at: ", month_file_name)
141+
logger.debug("Stored file at: ", month_file_name)
141142

142143
_recombine_multiple_files(month_file_name)
143144

144145
_format_downloaded_file(month_file_name, update_settings.factor_dictionary)
145146

146147
month_file_name.rename(month_file.with_suffix(Era5FileSuffixes.FORMATTED))
147-
print("Renamed to: ", month_file.with_suffix(Era5FileSuffixes.UNFORMATTED))
148+
logger.debug("Renamed to: ", month_file.with_suffix(Era5FileSuffixes.FORMATTED))
148149
_finalize_formatted_file(month_file, update_month, threshold_date)
149150

150151
except Exception as e:
@@ -155,7 +156,9 @@ def _era5_update_month(update_settings: Era5UpdateSettings, update_month: dateti
155156

156157

157158
def _get_update_month(update_settings: Era5UpdateSettings) -> datetime:
158-
NORMAL_FIRST_MOMENT_AVAILABLE_FOR_ERA5 = datetime.now(UTC) - relativedelta(days=5)
159+
NORMAL_FIRST_MOMENT_AVAILABLE_FOR_ERA5 = (datetime.now(UTC) - relativedelta(days=5)).replace(
160+
hour=0, minute=0, second=0, microsecond=0
161+
)
159162
update_moment = update_settings.repository_time_range[1]
160163

161164
update_moment = (
@@ -184,11 +187,15 @@ def _verify_first_day_available_for_era5(update_moment: datetime, update_setting
184187
try:
185188
download_era5_data(
186189
dataset=update_settings.era5_dataset_to_update_from,
187-
product_type=update_settings.era5_product_type,
188-
weather_factors=["stl1"], # A factor that exists in all supported ERA5 datasets
189-
years=[update_moment.year],
190-
months=[update_moment.month],
191-
days=[update_moment.day],
190+
cds_request=CDSRequest(
191+
product_type=[update_settings.era5_product_type],
192+
variables=["stl1"], # A factor that exists in all supported ERA5 datasets
193+
year=[str(update_moment.year)],
194+
month=[str(update_moment.month)],
195+
day=[str(update_moment.day)],
196+
time=[f"{hour:02d}:00" for hour in range(2)],
197+
area=(53.510403, 3.314971, 50.803721, 7.092053), # The Netherlands area
198+
),
192199
target_location=tempfile.NamedTemporaryFile().name,
193200
)
194201
break
@@ -198,7 +205,8 @@ def _verify_first_day_available_for_era5(update_moment: datetime, update_setting
198205

199206
if update_moment < update_settings.repository_time_range[1] - relativedelta(days=45):
200207
raise ValueError(
201-
"The first day available for ERA5 data could not be found within 40 days of the target date. Aborting update."
208+
"The first day available for ERA5 data could not be found within 40 days of the target date. "
209+
"Aborting update."
202210
)
203211

204212
return update_moment
@@ -221,21 +229,20 @@ def _finalize_formatted_file(file_path: Path, current_moment: date, verification
221229
except Exception as e:
222230
logger.error(f" > Failed to remove temporary file {file_path.with_suffix(file_suffix)}: {e}")
223231

224-
# Rename the file to its proper name:
225-
if current_moment == verification_date.replace(day=1):
226-
# Current month means an incomplete file
227-
file_path.with_suffix(Era5FileSuffixes.FORMATTED).rename(file_path.with_suffix(Era5FileSuffixes.INCOMPLETE))
228-
logger.debug(
229-
f"Month [{current_moment}] was renamed to: {file_path.with_suffix(Era5FileSuffixes.INCOMPLETE)}"
230-
)
231-
elif permanent_month < current_moment < incomplete_month:
232-
# Non-permanent file
233-
file_path.with_suffix(Era5FileSuffixes.FORMATTED).rename(file_path.with_suffix(Era5FileSuffixes.TEMP))
234-
logger.debug(f"Month [{current_moment}] was renamed to: {file_path.with_suffix(Era5FileSuffixes.TEMP)}")
235-
else:
236-
# Permanent file
237-
file_path.with_suffix(Era5FileSuffixes.FORMATTED).rename(file_path.with_suffix(".nc"))
238-
logger.debug(f'Month [{current_moment}] was renamed to: {file_path.with_suffix(".nc")}')
232+
# Rename the file to its proper name:
233+
print("RENAMING FILE", current_moment, verification_date, permanent_month, incomplete_month)
234+
if current_moment == verification_date.replace(day=1):
235+
# Current month means an incomplete file
236+
file_path.with_suffix(Era5FileSuffixes.FORMATTED).rename(file_path.with_suffix(Era5FileSuffixes.INCOMPLETE))
237+
logger.debug(f"Month [{current_moment}] was renamed to: {file_path.with_suffix(Era5FileSuffixes.INCOMPLETE)}")
238+
elif permanent_month < current_moment < incomplete_month:
239+
# Non-permanent file
240+
file_path.with_suffix(Era5FileSuffixes.FORMATTED).rename(file_path.with_suffix(Era5FileSuffixes.TEMP))
241+
logger.debug(f"Month [{current_moment}] was renamed to: {file_path.with_suffix(Era5FileSuffixes.TEMP)}")
242+
else:
243+
# Permanent file
244+
file_path.with_suffix(Era5FileSuffixes.FORMATTED).rename(file_path.with_suffix(".nc"))
245+
logger.debug(f'Month [{current_moment}] was renamed to: {file_path.with_suffix(".nc")}')
239246

240247

241248
def file_requires_update(file_path: Path, current_month: date, verification_date: date) -> bool:
@@ -284,8 +291,8 @@ def _format_downloaded_file(unformatted_file: Path, allowed_factors: dict) -> No
284291
# We remove the expver index used to denominate temporary data (5) and regular data (1) and add a field for it
285292
# NOTE: We removed the drop_sel version as it didn't quite have the same result as drop yet. Reverting until
286293
# the proper use has been validated...
287-
ds_unformatted_expver5 = ds_unformatted.sel(expver=5).drop("expver").dropna("time", how="all")
288-
ds_unformatted_expver1 = ds_unformatted.sel(expver=1).drop("expver").dropna("time", how="all")
294+
ds_unformatted_expver5 = ds_unformatted.sel(expver=5).drop("expver").dropna("valid_time", how="all")
295+
ds_unformatted_expver1 = ds_unformatted.sel(expver=1).drop("expver").dropna("valid_time", how="all")
289296

290297
# Recombine the data
291298
ds_unformatted = ds_unformatted_expver1.merge(ds_unformatted_expver5)
@@ -299,8 +306,8 @@ def _format_downloaded_file(unformatted_file: Path, allowed_factors: dict) -> No
299306
ds_unformatted = ds_unformatted.rename_vars({factor: allowed_factors[factor]})
300307

301308
# Rename and encode data where needed:
302-
ds_unformatted.time.encoding["units"] = "hours since 2016-01-01"
303-
ds_unformatted = ds_unformatted.rename(name_dict={"latitude": "lat", "longitude": "lon"})
309+
ds_unformatted.valid_time.encoding["units"] = "hours since 2016-01-01"
310+
ds_unformatted = ds_unformatted.rename(name_dict={"latitude": "lat", "longitude": "lon", "valid_time": "time"})
304311

305312
# Store the data
306313
ds_unformatted.to_netcdf(path=unformatted_file, format="NETCDF4", engine="netcdf4")
@@ -336,20 +343,15 @@ def _recombine_multiple_files(unformatted_file: Path) -> None:
336343
with zipfile.ZipFile(unformatted_file, "r") as zip_ref:
337344
zip_ref.extractall(temp_dir)
338345

339-
combined_dataset = xr.Dataset()
340-
for file in Path(temp_dir).glob("*.nc"):
341-
# Now use xarray to open each NetCDF file and merge them
342-
new_file_dataset = xr.open_dataset(file)
343-
print("PROCESSING FILE: ", file)
346+
# Load the data
344347

345-
if not combined_dataset.time.size or combined_dataset.time.size == 0:
346-
print("SETTING FILE: ", file)
347-
combined_dataset = new_file_dataset.copy(deep=True)
348-
else:
349-
print("MERGING FILE: ", file)
350-
combined_dataset = xr.merge([combined_dataset, new_file_dataset])
348+
data_stream_land_accum = xr.open_dataset(Path(temp_dir).joinpath("data_stream-oper_stepType-accum.nc"))
349+
data_stream_land_instant = xr.open_dataset(Path(temp_dir).joinpath("data_stream-oper_stepType-instant.nc"))
350+
data_stream_wave_instant = xr.open_dataset(Path(temp_dir).joinpath("data_stream-wave_stepType-instant.nc"))
351351

352-
combined_dataset.to_netcdf(unformatted_file, format="NETCDF4", engine="netcdf4")
352+
# Merge the data
353+
combined_data = xr.merge([data_stream_land_accum, data_stream_land_instant, data_stream_wave_instant])
354+
combined_data.to_netcdf(unformatted_file, format="NETCDF4", engine="netcdf4")
353355

354356

355357
def download_era5_data(
@@ -358,8 +360,6 @@ def download_era5_data(
358360
target_location: str,
359361
) -> None:
360362
"""A function to download ERA5 data."""
361-
print(cds_request.request_parameters)
362-
363363
try:
364364
CDS_CLIENT.retrieve(
365365
dataset,

weather_provider_api/routers/weather/sources/knmi/models/pluim.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ def get_weather(
135135
ds = self._download_weather(coords, coords_stn_ind, stns, weather_factors)
136136

137137
ds = self._select_weather_from_given_period(ds, begin, end)
138-
ds = ds.dropna("time", "all") # Dropping any times that only carry NaN values
138+
ds = ds.dropna("time", how="all") # Dropping any times that only carry NaN values
139139

140140
return ds
141141

0 commit comments

Comments
 (0)