Skip to content
Open
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ esa.euclid

- New cross-match method [#3386]

- New method, ``get_datalinks_metadata``, to retrieve additional columns
from the datalinks metadata. [#3438]

esa.hubble
^^^^^^^^^^

Expand Down
30 changes: 28 additions & 2 deletions astroquery/esa/euclid/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1418,7 +1418,7 @@ def get_spectrum(self, *, source_id, schema='sedm', retrieval_type="ALL", output

return files

def get_datalinks(self, ids, *, linking_parameter='SOURCE_ID', verbose=False):
def get_datalinks(self, ids, *, linking_parameter='SOURCE_ID', options=None, verbose=False):
"""Gets datalinks associated to the provided identifiers
TAP+ only

Expand All @@ -1428,6 +1428,8 @@ def get_datalinks(self, ids, *, linking_parameter='SOURCE_ID', verbose=False):
list of identifiers
linking_parameter : str, optional, default SOURCE_ID, valid values: SOURCE_ID
By default, all the identifiers are considered as source_id
options : str, optional, default None
To let customize the server behaviour
Copy link
Member

Choose a reason for hiding this comment

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

options feels very generic; could we be more specific naming this?
Also, I would find it useful to mention what valid values could be for this, from the example below 'METADATA' is one, but how could the user know what else works?

Copy link
Member

Choose a reason for hiding this comment

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

In addition, adding a new kwarg is an API change, thus will need to be mentioned in the changelog

verbose : bool, optional, default 'False'
flag to display information about the process

Expand All @@ -1437,7 +1439,31 @@ def get_datalinks(self, ids, *, linking_parameter='SOURCE_ID', verbose=False):

"""

return self.__eucliddata.get_datalinks(ids=ids, linking_parameter=linking_parameter, verbose=verbose)
return self.__eucliddata.get_datalinks(ids=ids,
linking_parameter=linking_parameter,
options=options,
verbose=verbose)

def get_datalinks_metadata(self, ids, *, linking_parameter='SOURCE_ID', verbose=False):
Copy link
Member

Choose a reason for hiding this comment

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

Why have this as a separate method rather than just documenting the one needs to have use options='METADATA'? The narrative documentation example below throw me off the track as starting with that one, and based on the method name I expected only metadata rather than it having the same result as using get_datalinks and some extra metadata.

"""Gets datalinks associated to the provided identifiers, including additional metadata (the datalabs_path)
TAP+ only

Parameters
----------
ids : str, int, list of str or list of int, mandatory
List of identifiers
linking_parameter : str, optional, default SOURCE_ID, valid values: SOURCE_ID
By default, all the identifiers are considered as source_id
verbose : bool, optional, default 'False'
Flag to display information about the process

Returns
-------
A table object

"""

return self.get_datalinks(ids=ids, linking_parameter=linking_parameter, options='METADATA', verbose=verbose)

def get_scientific_product_list(self, *, observation_id=None, tile_index=None, category=None, group=None,
product_type=None, dataset_release='REGREPROC1_R2', verbose=False):
Expand Down
22 changes: 20 additions & 2 deletions astroquery/esa/euclid/tests/test_euclidtap.py
Original file line number Diff line number Diff line change
Expand Up @@ -1220,19 +1220,37 @@ def test_logout(mock_logout):


def test_get_datalinks(monkeypatch):
def get_datalinks_monkeypatched(self, ids, linking_parameter, verbose):
def get_datalinks_monkeypatched(self, ids, linking_parameter, options, verbose):
return Table()

# `EuclidClass` is a subclass of `TapPlus`, but it does not inherit
# `get_datalinks()`, it replaces it with a call to the `get_datalinks()`
# of its `__gaiadata`.
# of its `__eucliddata`.
monkeypatch.setattr(TapPlus, "get_datalinks", get_datalinks_monkeypatched)
euclid = EuclidClass(show_server_messages=False)

result = euclid.get_datalinks(ids=[12345678], verbose=True)
assert isinstance(result, Table)


def test_get_datalinks_metadata(monkeypatch):
def get_datalinks_monkeypatched(self, ids, linking_parameter, options, verbose):
table = TapTableMeta()
table.name = options
return table

# `EuclidClass` is a subclass of `TapPlus`, but it does not inherit
# `get_datalinks()`, it replaces it with a call to the `get_datalinks()`
# of its `__eucliddata`. get_datalinks_metadata delegates to get_datalinks,
# which in turn delegates to TapPlus' get_datalinks
monkeypatch.setattr(TapPlus, "get_datalinks", get_datalinks_monkeypatched)
euclid = EuclidClass(show_server_messages=False)

result = euclid.get_datalinks_metadata(ids=[12345678], verbose=True)
assert isinstance(result, TapTableMeta)
assert result.name == "METADATA"


@pytest.mark.parametrize("background", [False, True])
def test_cross_match_basic(monkeypatch, background, cross_match_basic_kwargs, mock_querier_async):
def load_table_monkeypatched(self, table, verbose):
Expand Down
22 changes: 13 additions & 9 deletions astroquery/utils/tap/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1247,20 +1247,22 @@ def is_valid_user(self, *, user_id=None, verbose=False):
print(f"USER response = {user}")
return user.startswith(f"{user_id}:") and user.count("\\n") == 0

def get_datalinks(self, ids, *, linking_parameter=None, verbose=False):
def get_datalinks(self, ids, *, linking_parameter=None, options=None, verbose=False):
Copy link
Member

Choose a reason for hiding this comment

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

This will need to be mentioned in the changelog

Copy link
Author

@jsaizsantos jsaizsantos Oct 20, 2025

Choose a reason for hiding this comment

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

Dear @bsipocz, thanks for your comments! I try to explain:

The best option for me, ideally, would have been to just add a new method without changing the signature of the parent get_datalinks. However, in order to reuse code and avoid copy-paste, I called the existing parent get_datalinks method from the new get_datalinks_metadata, and for getting the new behavior from the server, we needed a new argument to that method of the parent TapPlus class.

You say with good reason that this argument name is generic, and it is on purpose: the idea is that it could be reused in future cases to customize the behavior of the server (as there are several classes extending TapPlus, for different ESA archives, every one having their requirements) without needing to touch this get_datalinks method once and again, as it happened this time. The value of the options argument would tell the exact additional behavior.

I will tackle your suggestions:

  • Rename options to extra_options.
  • Indicate the only valid value for it right now, which is METADATA for the Euclid archive.
  • Mention the change of signature in the changelog.

"""Gets datalinks associated to the provided identifiers

Parameters
----------
ids : str list, mandatory
list of identifiers
List of identifiers
linking_parameter : str, optional, default SOURCE_ID, valid values: SOURCE_ID, TRANSIT_ID, IMAGE_ID
By default, all the identifiers are considered as source_id
SOURCE_ID: the identifiers are considered as source_id
TRANSIT_ID: the identifiers are considered as transit_id
IMAGE_ID: the identifiers are considered as sif_observation_id
options : str, optional, default None
If present, an extra parameter OPTIONS will be added to the call, to be interpreted by the TAP service
verbose : bool, optional, default 'False'
flag to display information about the process
Flag to display information about the process

Returns
-------
Expand All @@ -1282,15 +1284,17 @@ def get_datalinks(self, ids, *, linking_parameter=None, verbose=False):
if linking_parameter is not None:
ids_arg = f'{ids_arg}&LINKING_PARAMETER={linking_parameter}'

if options is not None:
ids_arg = f'{ids_arg}&OPTIONS={options}'

if verbose:
print(f"Datalink request: {ids_arg}")
connHandler = self.__getconnhandler()
response = connHandler.execute_datalinkpost(subcontext="links",
data=ids_arg,
verbose=verbose)
print(f"Datalink request: ID={ids_arg}")

conn_handler = self.__getconnhandler()
response = conn_handler.execute_datalinkpost(subcontext="links", data=ids_arg, verbose=verbose)
if verbose:
print(response.status, response.reason)
connHandler.check_launch_response_status(response, verbose, 200)
conn_handler.check_launch_response_status(response, verbose, 200)
if verbose:
print("Done.")
results = utils.read_http_response(response, "votable", use_names_over_ids=self.use_names_over_ids)
Expand Down
14 changes: 14 additions & 0 deletions docs/esa/euclid/euclid.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1215,6 +1215,20 @@ To find out the resources associated with a given source:
sedm 2707008224650763513 SOURCE_ID https://eas.esac.esa.int/sas-dd/data?ID=sedm+2707008224650763513&RETRIEVAL_TYPE=SPECTRA_BGS #this Spectra Blue Source --


Euclid also provides a way to execute a similar call by getting the datalabs_path as an additional column:

.. Skipping authentication requiring examples
.. doctest-skip::

>>> from astroquery.esa.euclid import Euclid
>>> Euclid.login()
>>> result = Euclid.get_datalinks_metadata(ids=2707008224650763513)
>>> print(result)
ID linking_parameter access_url service_def ... content_type content_length datalabs_path
... byte
------------------------ ----------------- ------------------------------------------------------------------------------------------------------- ----------- ... ------------ -------------- -----------------------------------
sedm 2707008224650763513 SOURCE_ID https://eas.esac.esa.int/sas-dd/datalink/data?ID=sedm+2707008224650763513&RETRIEVAL_TYPE=SPECTRA_RGS ... -- /data/euclid_q1/Q1_R1/SIR/102158586
sedm 2707008224650763513 SOURCE_ID https://eas.esac.esa.int/sas-dd/datalink/data?ID=sedm+2707008224650763513&RETRIEVAL_TYPE=SPECTRA_BGS ... -- /data/euclid_q1/Q1_R1/SIR/102158586


The query below retrieves a random sample of Euclid sources having spectra.
Expand Down
Loading