Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor ESO authentication and download #2681

Merged
merged 33 commits into from
Dec 8, 2023
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
7a1e1e4
refactor eso data retrieval using alma's 'download_files'
szampier Mar 3, 2023
8128eb4
fix authentication, remove unused method
szampier Mar 3, 2023
279a920
uncompress files with external command because Python's gzip does not…
szampier Mar 4, 2023
454f02d
reimplement file download so we don't need to send two requests for e…
szampier Mar 6, 2023
8f1827a
use token authentication for download
szampier Mar 6, 2023
b18c286
re-authenticate when token expires
szampier Mar 6, 2023
51d7ffc
remove argument `request_all_objects`
szampier Mar 8, 2023
f904533
rename argument continuation to overwrite and implement this feature
szampier Mar 8, 2023
ac2a3ea
fix r-string, fix test
szampier Mar 8, 2023
b12d5d7
create destination directory
szampier Mar 8, 2023
8190085
add logs, update doc
szampier Mar 8, 2023
1a1fdc2
annotate modified methods with type hints
szampier Mar 9, 2023
b3313bd
fix service name for keyring, update doc
szampier Mar 9, 2023
45e9b0c
fix log severity
szampier Mar 9, 2023
a139625
update eso doc
szampier Mar 10, 2023
2d631ec
attempt to fix doctest
szampier Mar 10, 2023
a367ca2
implement retrieval of associated calibration files
szampier Mar 11, 2023
42c8885
rebase `MockResponse`
szampier Mar 13, 2023
65af273
parse calselector multipart response, test calselector
szampier Mar 15, 2023
b4338ac
fix filename for windows
szampier Mar 15, 2023
0e53440
add new test data files to setup_package.py
szampier Mar 15, 2023
2d26f0e
batch calselector requests
szampier Oct 20, 2023
4bb3f5a
fix code style errors
szampier Oct 20, 2023
82f8925
download to .part file
szampier Oct 23, 2023
bc634dc
sanitize Content-Disposition's filename (rfc6266#section-4.3)
szampier Oct 24, 2023
1b927ee
Update ChangeLog
szampier Oct 27, 2023
8a46168
implement review comments
szampier Dec 7, 2023
43a5940
fix `test_each_instrument_SgrAstar` and try to increase coverage
szampier Dec 7, 2023
5ccd28d
add test
szampier Dec 7, 2023
006aca0
skip `test_unzip` on Windows
szampier Dec 7, 2023
f8ef983
add test to increase coverage
szampier Dec 7, 2023
a1efe74
keep original API and add deprecation warning for removed arguments
szampier Dec 8, 2023
9ea4138
increase test coverage
szampier Dec 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@
New Tools and Services
----------------------

eso
^^^

- Authenticate with ESO using APIs and tokens instead of HTML forms. [#2681]
- Discontinue usage of old Request Handler for dataset retrieval in favor of new dataportal API. [#2681]
- Local reimplementation of astroquery's ``_download_file`` to fix some issues and avoid sending a HEAD request
just to get the original filename. [#1580]
- Restore support for .Z files. [#1818]
Comment on lines +13 to +14
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: We list the number of the PR in the changelog, not the issues fixed. I'll fix this up when cleaning up the changelog for release

- Update tests and documentation.

ipac.irsa
^^^^^^^^^

Expand Down
659 changes: 293 additions & 366 deletions astroquery/eso/core.py

Large diffs are not rendered by default.

353 changes: 353 additions & 0 deletions astroquery/eso/tests/data/FORS2.2021-01-02T00_59_12.533_raw2raw.xml

Large diffs are not rendered by default.

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions astroquery/eso/tests/data/oidc_token.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"access_token": "some-access-token",
"id_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2Nzg2Mjg5NTl9.qqKrC1MesQQmLtqsFOm2kxe4f_Nqo4EPqgpup30c6Mg",
"token_type": "bearer",
"expires_in": 28800,
"scope": ""
}
Binary file added astroquery/eso/tests/data/testfile.fits.Z
Binary file not shown.
3 changes: 3 additions & 0 deletions astroquery/eso/tests/setup_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,8 @@ def get_package_data():
paths = [os.path.join('data', '*.pickle'),
os.path.join('data', '*.html'),
os.path.join('data', '*.tbl'),
os.path.join('data', '*.xml'),
os.path.join('data', '*.json'),
os.path.join('data', '*.fits*')
]
return {'astroquery.eso.tests': paths}
98 changes: 82 additions & 16 deletions astroquery/eso/tests/test_eso.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Licensed under a 3-clause BSD style license - see LICENSE.rst
import os
from astroquery.utils.mocks import MockResponse

from astroquery.utils.mocks import MockResponse
from ...eso import Eso

DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
Expand All @@ -11,21 +11,21 @@ def data_path(filename):
return os.path.join(DATA_DIR, filename)


DATA_FILES = {'GET': {'http://archive.eso.org/wdb/wdb/eso/eso_archive_main/form':
'main_query_form.html',
'http://archive.eso.org/wdb/wdb/eso/amber/form':
'amber_query_form.html',
'http://archive.eso.org/wdb/wdb/adp/phase3_main/form':
'vvv_sgra_form.html',
},
'POST': {'http://archive.eso.org/wdb/wdb/eso/eso_archive_main/query':
'main_sgra_query.tbl',
'http://archive.eso.org/wdb/wdb/eso/amber/query':
'amber_sgra_query.tbl',
'http://archive.eso.org/wdb/wdb/adp/phase3_main/query':
'vvv_sgra_survey_response.tbl',
}
}
DATA_FILES = {
'GET':
{
'http://archive.eso.org/wdb/wdb/eso/eso_archive_main/form': 'main_query_form.html',
'http://archive.eso.org/wdb/wdb/eso/amber/form': 'amber_query_form.html',
'http://archive.eso.org/wdb/wdb/adp/phase3_main/form': 'vvv_sgra_form.html',
Eso.AUTH_URL: 'oidc_token.json',
},
'POST':
{
'http://archive.eso.org/wdb/wdb/eso/eso_archive_main/query': 'main_sgra_query.tbl',
'http://archive.eso.org/wdb/wdb/eso/amber/query': 'amber_sgra_query.tbl',
'http://archive.eso.org/wdb/wdb/adp/phase3_main/query': 'vvv_sgra_survey_response.tbl',
}
}


def eso_request(request_type, url, **kwargs):
Expand All @@ -34,6 +34,32 @@ def eso_request(request_type, url, **kwargs):
return response


def download_request(url, **kwargs):
filename = 'testfile.fits.Z'
with open(data_path(filename), 'rb') as f:
header = {'Content-Disposition': f'filename={filename}'}
response = MockResponse(content=f.read(), url=url, headers=header)
return response


def calselector_request(url, **kwargs):
is_multipart = len(kwargs['data']['dp_id']) > 1
if is_multipart:
filename = 'FORS2.2021-01-02T00_59_12.533_raw2raw_multipart.xml'
header = {
'Content-Type': 'multipart/form-data; boundary=uFQlfs9nBIDEAIoz0_ZM-O2SXKsZ2iSd4h7H;charset=UTF-8'
}
else:
filename = 'FORS2.2021-01-02T00_59_12.533_raw2raw.xml'
header = {
'Content-Disposition': f'filename="{filename}"',
'Content-Type': 'application/xml; content=calselector'
}
with open(data_path(filename), 'rb') as f:
response = MockResponse(content=f.read(), url=url, headers=header)
return response


# @pytest.fixture
# def patch_get(request):
# mp = request.getfixturevalue("monkeypatch")
Expand Down Expand Up @@ -92,3 +118,43 @@ def test_vvv(monkeypatch):
assert result_s is not None
assert 'Object' in result_s.colnames
assert 'b333' in result_s['Object']


def test_authenticate(monkeypatch):
eso = Eso()
monkeypatch.setattr(eso, '_request', eso_request)
eso.cache_location = DATA_DIR
authenticated = eso._authenticate(username="someuser", password="somepassword")
assert authenticated is True


def test_download(monkeypatch):
fileid = 'testfile'
url = Eso.DOWNLOAD_URL + fileid
eso = Eso()
destination = os.path.join(DATA_DIR, 'downloads')
os.makedirs(destination, exist_ok=True)
monkeypatch.setattr(eso._session, 'get', download_request)
filename, downloaded = eso._download_eso_file(url, destination=destination, overwrite=True)
assert downloaded is True
assert fileid in filename


def test_calselector(monkeypatch):
eso = Eso()
dataset = 'FORS2.2021-01-02T00:59:12.533'
monkeypatch.setattr(eso._session, 'post', calselector_request)
result = eso.find_associated_files([dataset], savexml=False, destination=data_path('downloads'))
assert isinstance(result, list)
assert len(result) == 50
assert dataset not in result


def test_calselector_multipart(monkeypatch):
eso = Eso()
datasets = ['FORS2.2021-01-02T00:59:12.533', 'FORS2.2021-01-02T00:59:12.534']
monkeypatch.setattr(eso._session, 'post', calselector_request)
result = eso.find_associated_files(datasets, savexml=False, destination=data_path('downloads'))
assert isinstance(result, list)
assert len(result) == 99
assert datasets[0] not in result and datasets[1] not in result
55 changes: 16 additions & 39 deletions astroquery/eso/tests/test_eso_remote.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# Licensed under a 3-clause BSD style license - see LICENSE.rst
import warnings

import numpy as np
import pytest
import warnings

from astroquery.exceptions import LoginError, NoResultsWarning
from astroquery.eso import Eso
from astroquery.exceptions import NoResultsWarning

instrument_list = [u'fors1', u'fors2', u'sphere', u'vimos', u'omegacam',
u'hawki', u'isaac', u'naco', u'visir', u'vircam', u'apex',
Expand Down Expand Up @@ -68,18 +69,6 @@ def test_multisurvey(self, tmp_path):
assert 'b333_414_58214' in result_s['Object']
assert 'Pistol-Star' in result_s['Object']

def test_nologin(self):
# WARNING: this test will fail if you haven't cleared your cache and
# you have downloaded this file!
eso = Eso()

with pytest.raises(LoginError) as exc:
eso.retrieve_data('AMBER.2006-03-14T07:40:19.830')

assert (exc.value.args[0]
== ("If you do not pass a username to login(), you should "
"configure a default one!"))

def test_empty_return(self):
# test for empty return with an object from the North
eso = Eso()
Expand Down Expand Up @@ -112,40 +101,28 @@ def test_list_instruments(self):
# we only care about the sets matching
assert set(inst) == set(instrument_list)

@pytest.mark.skipif('not Eso.USERNAME')
def test_retrieve_data(self):
eso = Eso()
eso.login()
result = eso.retrieve_data(["MIDI.2014-07-25T02:03:11.561"])
assert len(result) > 0
assert "MIDI.2014-07-25T02:03:11.561" in result[0]
result = eso.retrieve_data("MIDI.2014-07-25T02:03:11.561")
assert isinstance(result, str)
result = eso.retrieve_data("MIDI.2014-07-25T02:03:11.561",
request_all_objects=True)
file_id = 'AMBER.2006-03-14T07:40:19.830'
result = eso.retrieve_data(file_id)
assert isinstance(result, str)
assert file_id in result

@pytest.mark.skipif('not Eso.USERNAME')
def test_retrieve_data_twice(self):
def test_retrieve_data_authenticated(self):
eso = Eso()
eso.login()
eso.retrieve_data("MIDI.2014-07-25T02:03:11.561")
eso.retrieve_data("AMBER.2006-03-14T07:40:19.830")
file_id = 'AMBER.2006-03-14T07:40:19.830'
result = eso.retrieve_data(file_id)
assert isinstance(result, str)
assert file_id in result

@pytest.mark.skipif('not Eso.USERNAME')
def test_retrieve_data_and_calib(self):
def test_retrieve_data_list(self):
eso = Eso()
eso.login()
result = eso.retrieve_data(["FORS2.2016-06-22T01:44:01.585"],
with_calib='raw')
assert len(result) == 59
# Try again, from cache this time
result = eso.retrieve_data(["FORS2.2016-06-22T01:44:01.585"],
with_calib='raw')
# Here we get only 1 file path for the science file: as this file
# exists, no request is made to get the associated calibrations file
# list.
assert len(result) == 1
datasets = ['MIDI.2014-07-25T02:03:11.561', 'AMBER.2006-03-14T07:40:19.830']
result = eso.retrieve_data(datasets)
assert isinstance(result, list)
assert len(result) == 2

# TODO: remove filter when https://github.com/astropy/astroquery/issues/2539 is fixed
@pytest.mark.filterwarnings("ignore::pytest.PytestUnraisableExceptionWarning")
Expand Down
17 changes: 15 additions & 2 deletions astroquery/utils/mocks.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Licensed under a 3-clause BSD style license - see LICENSE.rst

import json
from functools import partial
from io import BytesIO


# The MockResponse class is currently relied upon in code and thus
# temporarily got moved here to avoid adding pytest as a
Expand All @@ -12,24 +15,34 @@ class MockResponse:
A mocked/non-remote version of `astroquery.query.AstroResponse`
"""

def __init__(self, content=None, *, url=None, headers={}, content_type=None,
def __init__(self, content=None, *, url=None, headers=None, content_type=None,
stream=False, auth=None, status_code=200, verify=True,
allow_redirects=True, json=None):
assert content is None or hasattr(content, 'decode')
self.content = content
self.raw = content
self.headers = headers
self.headers = headers or {}
if content_type is not None:
self.headers.update({'Content-Type': content_type})
self.url = url
self.auth = auth
self.status_code = status_code

def __enter__(self):
return self

def __exit__(self, exc_type, exc_val, exc_tb):
pass

def iter_lines(self):
content = self.content.split(b"\n")
for line in content:
yield line

def iter_content(self, chunk_size):
stream = BytesIO(self.content)
return iter(partial(stream.read, chunk_size), b'')

def raise_for_status(self):
pass

Expand Down
Loading