Skip to content

Commit 6b58ec2

Browse files
authored
Merge pull request #33 from ThomasBouche/feature/issue28
Add option Temporary Directory
2 parents d094d0e + dd7905f commit 6b58ec2

File tree

5 files changed

+57
-15
lines changed

5 files changed

+57
-15
lines changed

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
11
# Changelog
22

3-
## TODO
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6+
7+
## [0.2.0] - January, 2024
8+
9+
### Features
10+
11+
* Add an optional temporary directory: `temp_dir` (#28).

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ df_arome = arome_client.get_coverage(
8383
pressures=None, # Optional: pressure level
8484
long = (-5.1413, 9.5602), # Optional: longitude
8585
lat = (41.33356, 51.0889), # Optional: latitude
86-
coverage_id=None # Optional: an alternative to indicator/run/interval
86+
coverage_id=None, # Optional: an alternative to indicator/run/interval
87+
temp_dir=None, # Optional: Directory to store the temporary file
8788
)
8889
```
8990
Note: The coverage_id can be used instead of indicator, run, and interval.

docs/pages/how_to.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ df_arome = arome_client.get_coverage(
8080
pressures=None, # Optional: pressure level
8181
long = (-5.1413, 9.5602), # Optional: longitude
8282
lat = (41.33356, 51.0889), # Optional: latitude
83-
coverage_id=None # Optional: an alternative to indicator/run/interval
83+
coverage_id=None, # Optional: an alternative to indicator/run/interval
84+
temp_dir=None, # Optional: Directory to store the temporary file
8485
)
8586
```
8687

src/meteole/forecast.py

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
import datetime as dt
44
import logging
5+
import os
56
import re
7+
import shutil
68
import tempfile
79
from abc import ABC, abstractmethod
810
from functools import reduce
@@ -147,6 +149,7 @@ def get_coverage(
147149
run: str | None = None,
148150
interval: str | None = None,
149151
coverage_id: str = "",
152+
temp_dir: str | None = None,
150153
) -> pd.DataFrame:
151154
"""Return the coverage data (i.e., the weather forecast data).
152155
@@ -163,6 +166,7 @@ def get_coverage(
163166
raises an error if specified. Defaults to "P1D" for time-aggregated indicators such
164167
as TOTAL_PRECIPITATION.
165168
coverage_id: An id of a coverage, use get_capabilities() to get them.
169+
temp_dir (str | None): Directory to store the temporary file. Defaults to None.
166170
167171
Returns:
168172
pd.DataFrame: The complete run for the specified execution.
@@ -192,6 +196,7 @@ def get_coverage(
192196
forecast_horizon=forecast_horizon,
193197
lat=lat,
194198
long=long,
199+
temp_dir=temp_dir,
195200
)
196201
for forecast_horizon in forecast_horizons
197202
for pressure in pressures
@@ -403,7 +408,11 @@ def _get_coverage_description(self, coverage_id: str) -> dict[Any, Any]:
403408
response = self._client.get(url, params=params)
404409
return xmltodict.parse(response.text)
405410

406-
def _grib_bytes_to_df(self, grib_str: bytes) -> pd.DataFrame:
411+
def _grib_bytes_to_df(
412+
self,
413+
grib_str: bytes,
414+
temp_dir: str | None = None,
415+
) -> pd.DataFrame:
407416
"""(Protected)
408417
Converts GRIB data (in binary format) into a pandas DataFrame.
409418
@@ -413,6 +422,7 @@ def _grib_bytes_to_df(self, grib_str: bytes) -> pd.DataFrame:
413422
414423
Args:
415424
grib_str (bytes): Binary GRIB data as a byte string.
425+
temp_dir (str | None): Directory to store the temporary file. Defaults to None.
416426
417427
Returns:
418428
pd.DataFrame: A pandas DataFrame containing the extracted GRIB data,
@@ -427,8 +437,18 @@ def _grib_bytes_to_df(self, grib_str: bytes) -> pd.DataFrame:
427437
- The temporary file used for parsing is automatically deleted after use.
428438
- Ensure the input GRIB data is valid and encoded in a binary format.
429439
"""
440+
created_temp_dir = False
441+
442+
if temp_dir:
443+
if not os.path.exists(temp_dir):
444+
os.makedirs(temp_dir)
445+
created_temp_dir = True
446+
temp_subdir = os.path.join(temp_dir, "temp_grib")
447+
os.makedirs(temp_subdir, exist_ok=True)
448+
else:
449+
temp_subdir = tempfile.mkdtemp()
430450

431-
with tempfile.NamedTemporaryFile() as temp_file:
451+
with tempfile.NamedTemporaryFile(dir=temp_subdir, delete=False) as temp_file:
432452
# Write the GRIB binary data to the temporary file
433453
temp_file.write(grib_str)
434454
temp_file.flush() # Ensure the data is written to disk
@@ -439,6 +459,11 @@ def _grib_bytes_to_df(self, grib_str: bytes) -> pd.DataFrame:
439459
# Convert the Dataset to a pandas DataFrame
440460
df = ds.to_dataframe().reset_index()
441461

462+
if created_temp_dir and temp_dir is not None:
463+
shutil.rmtree(temp_dir)
464+
else:
465+
shutil.rmtree(temp_subdir)
466+
442467
return df
443468

444469
def _get_data_single_forecast(
@@ -449,6 +474,7 @@ def _get_data_single_forecast(
449474
height: int | None,
450475
lat: tuple,
451476
long: tuple,
477+
temp_dir: str | None = None,
452478
) -> pd.DataFrame:
453479
"""(Protected)
454480
Return the forecast's data for a given time and indicator.
@@ -460,6 +486,7 @@ def _get_data_single_forecast(
460486
forecast_horizon (int): the forecast horizon in hours (how many hours ahead)
461487
lat (tuple): minimum and maximum latitude
462488
long (tuple): minimum and maximum longitude
489+
temp_dir (str | None): Directory to store the temporary file. Defaults to None.
463490
464491
Returns:
465492
pd.DataFrame: The forecast for the specified time.
@@ -474,7 +501,7 @@ def _get_data_single_forecast(
474501
long=long,
475502
)
476503

477-
df: pd.DataFrame = self._grib_bytes_to_df(grib_binary)
504+
df: pd.DataFrame = self._grib_bytes_to_df(grib_binary, temp_dir=temp_dir)
478505

479506
# Drop and rename columns
480507
df.drop(columns=["surface", "valid_time"], errors="ignore", inplace=True)
@@ -521,10 +548,7 @@ def _get_coverage_file(
521548
long: tuple = (-12, 16),
522549
) -> bytes:
523550
"""(Protected)
524-
Retrieves raster data for a specified model prediction and saves it to a file.
525-
526-
If no `filepath` is provided, the file is saved to a default cache directory under
527-
the current working directory.
551+
Retrieves data for a specified model prediction.
528552
529553
Args:
530554
coverage_id (str): The coverage ID to retrieve. Use `get_coverage` to list available coverage IDs.
@@ -537,10 +561,6 @@ def _get_coverage_file(
537561
Defaults to (37.5, 55.4), covering the latitudes of France.
538562
long (tuple[float, float], optional): Tuple specifying the minimum and maximum longitudes.
539563
Defaults to (-12, 16), covering the longitudes of France.
540-
file_format (str, optional): The format of the raster file. Supported formats are "grib" and "tiff".
541-
Defaults to "grib".
542-
filepath (Path, optional): The file path where the raster file will be saved. If not specified,
543-
the file is saved to a cache directory.
544564
545565
Returns:
546566
Path: The file path to the saved raster data.
@@ -605,6 +625,7 @@ def get_combined_coverage(
605625
lat: tuple = FRANCE_METRO_LATITUDES,
606626
long: tuple = FRANCE_METRO_LONGITUDES,
607627
forecast_horizons: list[int] | None = None,
628+
temp_dir: str | None = None,
608629
) -> pd.DataFrame:
609630
"""
610631
Get a combined DataFrame of coverage data for multiple indicators and different runs.
@@ -624,6 +645,7 @@ def get_combined_coverage(
624645
lat (tuple): The latitude range as (min_latitude, max_latitude). Defaults to FRANCE_METRO_LATITUDES.
625646
long (tuple): The longitude range as (min_longitude, max_longitude). Defaults to FRANCE_METRO_LONGITUDES.
626647
forecast_horizons (list[int] | None): A list of forecast horizon values in hours. Defaults to None.
648+
temp_dir (str | None): Directory to store the temporary file. Defaults to None.
627649
628650
Returns:
629651
pd.DataFrame: A combined DataFrame containing coverage data for all specified runs and indicators.
@@ -643,6 +665,7 @@ def get_combined_coverage(
643665
pressures=pressures,
644666
intervals=intervals,
645667
forecast_horizons=forecast_horizons,
668+
temp_dir=temp_dir,
646669
)
647670
for run in runs
648671
]
@@ -658,6 +681,7 @@ def _get_combined_coverage_for_single_run(
658681
lat: tuple = FRANCE_METRO_LATITUDES,
659682
long: tuple = FRANCE_METRO_LONGITUDES,
660683
forecast_horizons: list[int] | None = None,
684+
temp_dir: str | None = None,
661685
) -> pd.DataFrame:
662686
"""(Protected)
663687
Get a combined DataFrame of coverage data for a given run considering a list of indicators.
@@ -677,6 +701,7 @@ def _get_combined_coverage_for_single_run(
677701
lat (tuple): The latitude range as (min_latitude, max_latitude). Defaults to FRANCE_METRO_LATITUDES.
678702
long (tuple): The longitude range as (min_longitude, max_longitude). Defaults to FRANCE_METRO_LONGITUDES.
679703
forecast_horizons (list[int] | None): A list of forecast horizon values in hours. Defaults to None.
704+
temp_dir (str | None): Directory to store the temporary file. Defaults to None.
680705
681706
Returns:
682707
pd.DataFrame: A combined DataFrame containing coverage data for all specified runs and indicators.
@@ -737,6 +762,7 @@ def _check_params_length(params: list[Any] | None, arg_name: str) -> list[Any]:
737762
heights=[height] if height is not None else [],
738763
pressures=[pressure] if pressure is not None else [],
739764
forecast_horizons=forecast_horizons,
765+
temp_dir=temp_dir,
740766
)
741767
for coverage_id, height, pressure in zip(coverage_ids, heights, pressures)
742768
]

tests/test_forecasts.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,13 @@ def test_get_coverage(self, mock_get_data_single_forecast, mock_get_capabilities
235235
)
236236

237237
mock_get_data_single_forecast.assert_called_once_with(
238-
coverage_id="toto", height=2, pressure=None, forecast_horizon=0, lat=(37.5, 55.4), long=(-12, 16)
238+
coverage_id="toto",
239+
height=2,
240+
pressure=None,
241+
forecast_horizon=0,
242+
lat=(37.5, 55.4),
243+
long=(-12, 16),
244+
temp_dir=None,
239245
)
240246

241247
@patch("meteole._arome.AromeForecast.get_coverage_description")

0 commit comments

Comments
 (0)