Using the ECMWF Climate Data Store (CDS) to Download ERA5 Data in a Conda Environment

William.Hatheway

Well-known member

Using the ECMWF Climate Data Store (CDS) to Download ERA5 Data in a Conda Environment​

This guide shows you how to:

  1. Create a Conda environment called CDS with pip and cdsapi
  2. Activate the environment
  3. Set up your CDS API token (.cdsapirc)
  4. Use the Python script Download_CDS_ERA5_Data_Hourly.py to download hourly ERA5 pressure-level and surface data

1. Create the Conda Environment CDS​

Make sure you have conda (Anaconda or Miniconda) installed.

1.1 Create the environment (with Python and pip)​

conda create -n CDS python=3.10 pip
  • -n CDS → environment name is CDS
  • python=3.10 → sets Python version
  • pip → ensures pip is available in the environment

1.2 Activate the environment​

conda activate CDS
Your shell prompt should now show (CDS) at the beginning.

1.3 Install cdsapi inside CDS​

pip install "cdsapi>=0.7.7"
This installs the official Climate Data Store API client library.


2. Get and Set Up Your CDS API Token​

The script uses the cdsapi client, which reads your credentials from a file called ~/.cdsapirc.

2.1 Get your token from the CDS website​

  1. Log in to the Copernicus Climate Data Store (CDS).
  2. Go to the CDS API / personal access token page.
  3. You will see something like:
    url: https://cds.climate.copernicus.eu/api
    key: YOUR_PERSONAL_ACCESS_TOKEN
⚠️ Keep your key secret. Treat it like a password.

2.2 Create the .cdsapirc file​

On Linux/macOS (or WSL / Git Bash on Windows):

nano ~/.cdsapirc
Paste in:

url: https://cds.climate.copernicus.eu/api
key: YOUR_PERSONAL_ACCESS_TOKEN
Save and exit.

2.3 Quick test: can cdsapi see your token?​

With the CDS environment active:

python - << 'EOF'
import cdsapi
c = cdsapi.Client()
print("CDS client initialized successfully.")
EOF
If no error appears, your token and URL are configured correctly.


3. Overview of Download_CDS_ERA5_Data_Hourly.py​

The main script is:

Download_CDS_ERA5_Data_Hourly.py
It does four main things:

  1. Imports & description
    """
    Downloads hourly ERA5 pressure-level and surface-level data from CDS.

    - pressure-level files → ./pressure/
    - surface-level files → ./surface/
    """
    import os
    from datetime import date, timedelta
    from pathlib import Path
    import cdsapi
    • Uses cdsapi for downloads, Path for directories, date/timedelta for looping over days.
  2. CDS config helpers
    • _parse_cds_config(path)
      • Reads a .cdsapirc-style file and extracts url and key.
    • get_cds_client()
      • Looks for:
        • ~/.cdsapirc (home directory), or
        • .cdsapirc in the script directory
      • If found, returns a configured cdsapi.Client.
      • If not, interactively asks for:
        • URL and key or
        • Path to an existing .cdsapirc.
  3. User input helpers
    • prompt_int(prompt_text, default)
      • Prompts for an integer with a default (for year/month/day).
    • prompt_float(prompt_text, default)
      • Prompts for a float with a default (for lat/lon).
  4. Main download logic
    • download_era5_data(start_year, start_month, start_day, end_year, end_month, end_day, north_latitude, south_latitude, west_longitude, east_longitude, https_proxy=None)
    • Inside this function:
    • Optional proxy support via HTTPS_PROXY / https_proxy env vars.
    • Calls get_cds_client() to get an authenticated cdsapi.Client.
    • Creates output folders next to the script:
      • pressure/
      • surface/
    • Builds a list of all 24 hours:
      times = [
      "00:00", "01:00", "02:00", "03:00",
      "04:00", "05:00", "06:00", "07:00",
      "08:00", "09:00", "10:00", "11:00",
      "12:00", "13:00", "14:00", "15:00",
      "16:00", "17:00", "18:00", "19:00",
      "20:00", "21:00", "22:00", "23:00",
      ]
    • Loops from the start date to the end dateand for each day/hour:
      • Builds file names like:
        • pressure/preslev_YYYYMMDD_HHMM.grib
        • surface/surface_YYYYMMDD_HHMM.grib
      • Issues two CDS requests per hour:
      • (a) Pressure levels
      c.retrieve(
      "reanalysis-era5-pressure-levels",
      {
      "product_type": "reanalysis",
      "format": "grib",
      "year": stryear,
      "month": strmonth,
      "day": strday,
      "time": [hour],
      "variable": [
      "divergence", "fraction_of_cloud_cover", "geopotential",
      "ozone_mass_mixing_ratio", "potential_vorticity",
      "relative_humidity",
      "specific_cloud_ice_water_content",
      "specific_cloud_liquid_water_content",
      "specific_humidity",
      "specific_rain_water_content",
      "specific_snow_water_content",
      "temperature",
      "u_component_of_wind",
      "v_component_of_wind",
      "vertical_velocity",
      "vorticity",
      ],
      "pressure_level": [
      "10", "20", "30", "50", "70", "100", "125", "150", "175",
      "200", "225", "250", "300", "350", "400", "450", "500",
      "550", "600", "650", "700", "750", "775", "800", "825",
      "850", "875", "900", "925", "950", "975", "1000",
      ],
      "area": [
      north_latitude, west_longitude,
      south_latitude, east_longitude,
      ],
      "grid": [0.25, 0.25],
      },
      str(pressure_filename),
      )
      (b) Single (surface) levels
      c.retrieve(
      "reanalysis-era5-single-levels",
      {
      "product_type": "reanalysis",
      "format": "grib",
      "year": stryear,
      "month": strmonth,
      "day": strday,
      "time": [hour],
      "variable": [
      "10m_u_component_of_wind",
      "10m_v_component_of_wind",
      "2m_dewpoint_temperature",
      "2m_temperature",
      "land_sea_mask",
      "mean_sea_level_pressure",
      "sea_ice_cover",
      "sea_surface_temperature",
      "skin_temperature",
      "snow_density",
      "snow_depth",
      "soil_temperature_level_1",
      "soil_temperature_level_2",
      "soil_temperature_level_3",
      "soil_temperature_level_4",
      "surface_pressure",
      "volumetric_soil_water_layer_1",
      "volumetric_soil_water_layer_2",
      "volumetric_soil_water_layer_3",
      "volumetric_soil_water_layer_4",
      ],
      "area": [
      north_latitude, west_longitude,
      south_latitude, east_longitude,
      ],
      "grid": [0.25, 0.25],
      },
      str(surface_filename),
      )
    • After all downloads:
      print("Hourly ERA5 downloads complete.")
      print(f"Pressure files saved in: {pressure_dir}")
      print(f"Surface files saved in: {surface_dir}")

4. Interactive Main Block​

At the bottom of the script:

if __name__ == "__main__":
print("=== ERA5 Download Configuration ===")
print("Press Enter to accept the default value shown in [brackets].\n")

# Default values
default_start_year = 2024
default_start_month = 9
default_start_day = 25

default_end_year = 2024
default_end_month = 9
default_end_day = 29

default_north_lat = 45.0
default_south_lat = 5.0
default_west_lon = 60.0
default_east_lon = 100.0
The script then:

  1. Prompts for start/end year, month, day using prompt_int.
  2. Prompts for north/south latitude and west/east longitude using prompt_float.
  3. Optionally asks for an HTTPS proxy.
  4. Prints a summary of your choices.
  5. Calls download_era5_data(...) with the values you entered.
From a user perspective:

  1. Run the script.
  2. Accept defaults or enter your own values.
  3. Files appear in pressure/ and surface/ directories next to the script.

5. How to Run the Script in the CDS Conda Environment​

Assuming:

  • Script path:
    /media/yourdisk/Download_CDS_ERA5_Data_Hourly.py
  • CDS environment is set up.
  • .cdsapirc is configured.

5.1 Activate the environment​

conda activate CDS

5.2 Run the script​

python "/media/yourdisk/Download_CDS_ERA5_Data_Hourly.py"

5.3 Follow the prompts​

You’ll see something like:

=== ERA5 Download Configuration ===
Press Enter to accept the default value shown in [brackets].

Start date:
Start year [2024]:
Start month [9]:
Start day [25]:

End date:
End year [2024]:
End month [9]:
End day [29]:

Geographic bounding box (latitudes/longitudes):
Note: North > South; longitudes in degrees (West/East, can be negative).
North latitude [45.0]:
South latitude [5.0]:
West longitude [60.0]:
East longitude [100.0]:

HTTPS proxy (optional).
Example: http://user: password@proxy-server: port
Enter HTTPS proxy (or leave blank for none):
Press Enter to accept defaults or type your own values. The script will print messages like:

Requesting data for 20240925 00:00
and will write the GRIB files into:

  • pressure/
  • surface/

Pytthon Code attached in Zip Files
 

Attachments

Last edited:
Back
Top