From 5c0632029d6126956a81b709281f648f0b6391cb Mon Sep 17 00:00:00 2001 From: Ben Smith Date: Tue, 8 Sep 2026 22:16:12 +0000 Subject: [PATCH] Never prompt for an Earthdata login, and attempt it only once A DPS job log from the first tile that got as far as the solve (08ee68ea, 2026-09-08) carried this three times: Enter your Earthdata Login username: That is my regression from replacing login(strategy='netrc') with a bare login(): the default is strategy='all', which ends at the INTERACTIVE strategy. On a worker there is no TTY, so it printed a prompt into the job log and then failed on the closed stdin -- harmless only because the caller tolerates failure. A batch job must never wait on a human, and a prompt buried in a log that scrolls past is worse than a clean warning. The strategies are now tried explicitly -- 'environment', then 'netrc' -- and 'interactive' is not among them. The outcome is remembered, so a tile that searches once per granule pays for at most one attempt rather than one per call, which is why the prompt appeared three times in a single job. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_013MdMEXw2s6eeGtaGcaDxpF --- pointCollection/io_utils.py | 46 ++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/pointCollection/io_utils.py b/pointCollection/io_utils.py index adc84d4..6aa9a65 100644 --- a/pointCollection/io_utils.py +++ b/pointCollection/io_utils.py @@ -192,9 +192,13 @@ def _s3fs_from_maap(daac, **kwargs): return None, None +# Remembers the outcome of try_earthaccess_login(): None = not yet attempted. +_EARTHACCESS_LOGIN_TRIED = None + + def try_earthaccess_login(): """ - Log in to Earthdata if we can, and carry on if we cannot. + Log in to Earthdata if we can, without ever blocking on a prompt. A CMR metadata search needs no authentication -- only granule READS do, and those get their credentials from get_s3fs(), which on MAAP uses MAAP's @@ -203,19 +207,39 @@ def try_earthaccess_login(): MAAP DPS worker cannot satisfy: it runs as root with no ~/.netrc, so the search raised LoginStrategyUnavailable before ever reaching CMR. - Bare login() tries environment, then netrc, then interactive, so a local - user's existing setup still works. Failure warns rather than raising, - because the caller very likely does not need it. + The strategies are tried EXPLICITLY, and 'interactive' is not among them. + Bare login() defaults to strategy='all', which ends at an interactive + prompt: on a DPS worker that printed "Enter your Earthdata Login username:" + into the job log for every call and then failed on the closed stdin. A + batch job must never wait on a human, and a prompt in a log that scrolls + past is worse than a clean warning. + + The result is remembered, so a tile that searches once per granule pays for + at most one attempt instead of one per call. """ + global _EARTHACCESS_LOGIN_TRIED + if _EARTHACCESS_LOGIN_TRIED is not None: + return _EARTHACCESS_LOGIN_TRIED + import warnings import earthaccess - try: - earthaccess.login() - except Exception as exc: - warnings.warn(f'earthaccess.login() failed ({type(exc).__name__}: {exc}); ' - 'continuing, since a CMR search needs no credentials. ' - 'Granule reads get their credentials separately, via ' - 'pointCollection.io_utils.get_s3fs().') + + failures = [] + for strategy in ('environment', 'netrc'): + try: + earthaccess.login(strategy=strategy) + _EARTHACCESS_LOGIN_TRIED = True + return True + except Exception as exc: + failures.append(f'{strategy}: {type(exc).__name__}: {exc}') + + warnings.warn('earthaccess login unavailable (' + '; '.join(failures) + '). ' + 'Continuing, since a CMR search needs no credentials. Granule ' + 'reads get their credentials separately, via ' + 'pointCollection.io_utils.get_s3fs(). The interactive ' + 'strategy is deliberately not tried.') + _EARTHACCESS_LOGIN_TRIED = False + return False def _expiry_timestamp(creds):