8000 Downloads ignore the `session` (and therefore custom proxies) passed to the client · Issue #162 · ecmwf/cdsapi · GitHub
[go: up one dir, main page]

Skip to content

Downloads ignore the session (and therefore custom proxies) passed to the client #162

Open

Description

@jpizquierdo

Summary

Note: with the new API tokens, cdsapi.Client delegates to
ecmwf.datastores.legacy_client.LegacyClient, so the root cause lives in
ecmwf-datastores-client.
Upstream issue/PR: ecmwf/ecmwf-datastores-client#73 / ecmwf/ecmwf-datastores-client#74.

When a custom requests.Session is passed to the client (e.g. to route traffic
through a corporate proxy), API requests (submit, status polling) correctly use
that session, but the final file download does not. The download is
performed by multiurl.download() without the session, so it creates a fresh
default session that bypasses the proxy. On networks that require a proxy for
outbound traffic, the download fails with a DNS resolution error:

Recovering from connection error [HTTPSConnectionPool(host='object-store.os-api.cci2.ecmwf.int', port=443):
Max retries exceeded with url: /cci2-prod-cache-2/2026-03-18/....grib
(Caused by NameResolutionError("HTTPSConnection(host='object-store.os-api.cci2.ecmwf.int', port=443):
Failed to resolve 'object-store.os-api.cci2.ecmwf.int' ([Errno 11001] getaddrinfo failed)"))], attempt 1 of 500

Environment

  • ecmwf-datastores-client 0.5.3
  • cdsapi 0.7.7 (which delegates to ecmwf.datastores.legacy_client.LegacyClient)
  • multiurl 0.3.9
  • Python 3.14, Windows

Reproduction

import requests
import cdsapi

session = requests.Session()
session.proxies = {
    "http": "http://user:pass@proxy.example.com:3128",
    "https": "http://user:pass@proxy.example.com:3128",
}

client 
6862
= cdsapi.Client(session=session)
client.retrieve(
    "reanalysis-era5-pressure-levels",
    {
        "product_type": ["reanalysis"],
        "variable": ["geopotential"],
        "year": ["2024"],
        "month": ["03"],
        "day": ["01"],
        "time": ["13:00"],
        "pressure_level": ["1000"],
        "data_format": "grib",
    },
    "download.grib",
)

On a machine where direct outbound traffic is blocked (corporate network), the
request is submitted and polled successfully (these go through the proxied
session), but the download of the resulting object-store URL fails because it
does not go through the proxy.

Root cause

In ecmwf/datastores/processing.py, Results._download() calls:

multiurl.download(
    url,
    target=target,
    maximum_retries=0,
    **self.request_options,
    **download_options,
)

self.request_options only contains timeout and verify (see
Client._request_options in ecmwf/datastores/client.py). The session is
never forwarded, so multiurl's HTTPDownloaderBase.__init__ falls back to
the module-level requests object:

self.session = requests if session is None else session

Note that multiurl.download() already accepts a session keyword argument —
it is simply never passed by this client.

Proposed fix

Forward the session to multiurl.download() in Results._download():

multiurl.download(
    url,
    target=target,
    maximum_retries=0,
    session=self.session,
    **self.request_options,
    **download_options,
)

(self.session is already available on ApiResponse.)

Workaround

Monkey-patch multiurl.download before calling retrieve() (session is the
same session passed to cdsapi.Client):

import multiurl

original = multiurl.download

def download_with_session(url, target, **kwargs):
    kwargs.setdefault("session", session)
    return original(url, target, **kwargs)

multiurl.download = download_with_session

This works because processing.py resolves multiurl.download at call time.

Related issues

Both are likely affected by this: even with a proxied session (or proxy settings),
the final download does not go through it.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      0