From 5152dcb0098a9a4673cd3e9bb9ed2b6b8aff9940 Mon Sep 17 00:00:00 2001 From: Aditya Pandey Date: Sun, 22 Feb 2026 00:08:58 +0530 Subject: [PATCH 01/26] =?UTF-8?q?Add=20astroquery.linelists.exomol=20?= =?UTF-8?q?=E2=80=94=20ExoMol=20database=20query=20module?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements astroquery interface for ExoMol molecular line list database. Wraps RADIS ExoMol reader (radis.io.exomol) into BaseQuery pattern. Features: - query_lines() — fetch line lists as astropy.Table - get_molecule_list() — list all 91+ ExoMol molecules - get_databases() — list available databases per molecule - get_partition_function() — fetch Q(T) data - broadening_species support (H2, He, air, CO2, H2O, self) Tests: 9/9 passing (5 mocked + 4 remote_data) Related: radis/radis#925 --- astroquery/linelists/__init__.py | 1 + astroquery/linelists/exomol/__init__.py | 4 + astroquery/linelists/exomol/core.py | 196 ++++++++++++++++++ astroquery/linelists/exomol/exomol.cfg | 5 + astroquery/linelists/exomol/tests/__init__.py | 0 .../linelists/exomol/tests/test_exomol.py | 168 +++++++++++++++ docs/linelists/exomol/exomol.rst | 65 ++++++ pyproject.toml | 2 - 8 files changed, 439 insertions(+), 2 deletions(-) create mode 100644 astroquery/linelists/exomol/__init__.py create mode 100644 astroquery/linelists/exomol/core.py create mode 100644 astroquery/linelists/exomol/exomol.cfg create mode 100644 astroquery/linelists/exomol/tests/__init__.py create mode 100644 astroquery/linelists/exomol/tests/test_exomol.py create mode 100644 docs/linelists/exomol/exomol.rst diff --git a/astroquery/linelists/__init__.py b/astroquery/linelists/__init__.py index cd1eae37de..3375424f84 100644 --- a/astroquery/linelists/__init__.py +++ b/astroquery/linelists/__init__.py @@ -5,3 +5,4 @@ This module contains sub-modules to support molecular and atomic line list modules and common utilities for parsing catalog files. """ +from .exomol import ExoMol # noqa: F401 diff --git a/astroquery/linelists/exomol/__init__.py b/astroquery/linelists/exomol/__init__.py new file mode 100644 index 0000000000..52dbb1f59f --- /dev/null +++ b/astroquery/linelists/exomol/__init__.py @@ -0,0 +1,4 @@ +# Licensed under a 3-clause BSD style license - see LICENSE.rst +from .core import ExoMol, ExoMolClass + +__all__ = ['ExoMol', 'ExoMolClass'] diff --git a/astroquery/linelists/exomol/core.py b/astroquery/linelists/exomol/core.py new file mode 100644 index 0000000000..71e9476b3f --- /dev/null +++ b/astroquery/linelists/exomol/core.py @@ -0,0 +1,196 @@ +# Licensed under a 3-clause BSD style license - see LICENSE.rst +""" +ExoMol database query module for astroquery. +Wraps RADIS ExoMol reader (radis.io.exomol) into astroquery BaseQuery pattern. + +References +---------- +Tennyson et al. 2020, J. Quant. Spectrosc. Radiat. Transf. +https://www.exomol.com +RADIS: https://github.com/radis/radis (issue #925) +""" + +from astropy.table import Table +from astroquery.query import BaseQuery +from astroquery import log + +__all__ = ['ExoMol', 'ExoMolClass'] + +EXOMOL_URL = "https://www.exomol.com" + + +class ExoMolClass(BaseQuery): + """ + Queries the `ExoMol `_ database for molecular + line lists used in exoplanet atmosphere modelling. + + This module wraps the RADIS ExoMol reader (``radis.io.exomol``) and + exposes it via the standard astroquery ``BaseQuery`` interface, returning + ``astropy.table.Table`` objects. + + Examples + -------- + >>> from astroquery.linelists.exomol import ExoMol + >>> result = ExoMol.query_lines('CO', + ... load_wavenum_min=2000, + ... load_wavenum_max=2100) + >>> print(result) + """ + + URL = EXOMOL_URL + TIMEOUT = 60 + + def get_molecule_list(self, *, cache=True): + """ + Retrieve list of all molecules available in ExoMol. + + Parameters + ---------- + cache : bool, optional + Cache HTTP response. Default ``True``. + + Returns + ------- + list of str + Sorted list of molecule names available in ExoMol. + """ + url = f"{self.URL}/db/exomol.all" + response = self._request('GET', url, cache=cache, timeout=self.TIMEOUT) + response.raise_for_status() + molecules = [] + for line in response.text.splitlines(): + line = line.strip() + if line and not line.startswith('#'): + parts = line.split() + if parts: + molecules.append(parts[0]) + return sorted(list(set(molecules))) + + def get_databases(self, molecule, isotopologue=None, *, cache=True): + """ + Get available line list databases for a given molecule. + + Parameters + ---------- + molecule : str + Molecule formula e.g. ``'H2O'``, ``'CO'``, ``'CH4'``. + isotopologue : str, optional + Isotopologue slug e.g. ``'1H2-16O'``. If ``None``, uses default. + cache : bool, optional + Cache results. Default ``True``. + + Returns + ------- + list of str + Available database names for this molecule. + """ + from radis.api.exomolapi import get_exomol_database_list + from radis.api.exomolapi import get_exomol_full_isotope_name + iso_name = get_exomol_full_isotope_name(molecule, 1) + dbs, _ = get_exomol_database_list(molecule, iso_name) + return dbs + + def query_lines(self, molecule, database=None, isotopologue='1', + load_wavenum_min=None, load_wavenum_max=None, + broadening_species=None, *, cache=True): + """ + Fetch ExoMol line list for a given molecule. + + Parameters + ---------- + molecule : str + Molecule formula e.g. ``'H2O'``, ``'CO'``, ``'SiO'``. + database : str, optional + ExoMol database name e.g. ``'POKAZATEL'`` for H2O. + If ``None``, uses the ExoMol-recommended database. + isotopologue : str, optional + Isotopologue number. Default ``'1'`` (most abundant). + load_wavenum_min : float, optional + Minimum wavenumber in cm^-1. + load_wavenum_max : float, optional + Maximum wavenumber in cm^-1. + broadening_species : str or list of str, optional + Pressure-broadening partner(s). + Examples: ``'H2'``, ``['H2', 'He']``, ``'air'``. + If ``None``, downloads all available broadening files. + See RADIS issue #917 for broadening parameter details. + cache : bool, optional + Cache downloaded line list files. Default ``True``. + + Returns + ------- + `~astropy.table.Table` + Line list table with columns for wavenumber (wav), + line intensity (int), Einstein A coefficient (A), + and lower/upper state energies (El, Eu). + + Examples + -------- + Query CO lines:: + + from astroquery.linelists.exomol import ExoMol + result = ExoMol.query_lines('CO', + load_wavenum_min=2000, + load_wavenum_max=2100) + + Query H2O with H2+He broadening:: + + result = ExoMol.query_lines('H2O', + database='POKAZATEL', + broadening_species=['H2', 'He'], + load_wavenum_min=1000, + load_wavenum_max=1100) + """ + from radis.io.exomol import fetch_exomol + + log.info(f"Querying ExoMol for {molecule} " + f"[{load_wavenum_min}-{load_wavenum_max} cm-1]") + + df = fetch_exomol( + molecule=molecule, + database=database, + isotope=isotopologue, + load_wavenum_min=load_wavenum_min, + load_wavenum_max=load_wavenum_max, + broadening_species=broadening_species if broadening_species is not None else "air", + cache=cache, + verbose=False, + ) + return Table.from_pandas(df) + + def get_partition_function(self, molecule, database=None, + isotopologue='1', *, cache=True): + """ + Get partition function Q(T) for a molecule from ExoMol. + + Parameters + ---------- + molecule : str + Molecule formula e.g. ``'CO'``, ``'H2O'``. + database : str, optional + ExoMol database name. If ``None``, uses recommended database. + isotopologue : str, optional + Isotopologue number. Default ``'1'``. + cache : bool, optional + Cache downloaded files. Default ``True``. + + Returns + ------- + `~astropy.table.Table` + Table with columns for temperature T (K) and partition + function Q(T). + """ + from radis.io.exomol import fetch_exomol + + df = fetch_exomol( + molecule=molecule, + database=database, + isotope=isotopologue, + return_partition_function=True, + cache=cache, + verbose=False, + ) + return Table.from_pandas(df) + + +ExoMol = ExoMolClass() diff --git a/astroquery/linelists/exomol/exomol.cfg b/astroquery/linelists/exomol/exomol.cfg new file mode 100644 index 0000000000..2803853658 --- /dev/null +++ b/astroquery/linelists/exomol/exomol.cfg @@ -0,0 +1,5 @@ +[linelists.exomol] +## ExoMol server base URL +server = https://www.exomol.com +## Request timeout in seconds +timeout = 60 diff --git a/astroquery/linelists/exomol/tests/__init__.py b/astroquery/linelists/exomol/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/astroquery/linelists/exomol/tests/test_exomol.py b/astroquery/linelists/exomol/tests/test_exomol.py new file mode 100644 index 0000000000..1b22a1bc3b --- /dev/null +++ b/astroquery/linelists/exomol/tests/test_exomol.py @@ -0,0 +1,168 @@ +# Licensed under a 3-clause BSD style license - see LICENSE.rst +""" +Tests for astroquery.linelists.exomol +Implements RADIS issue #925 — astroquery ExoMol module + +Run offline tests: pytest tests/test_exomol.py -v +Run remote tests: pytest tests/test_exomol.py -v --remote-data +""" + +import pytest +import numpy as np +import pandas as pd +from astropy.table import Table +from astroquery.linelists.exomol import ExoMol + + +# ============================================================ +# FIXTURES +# ============================================================ + +@pytest.fixture +def fake_linelist_df(): + """Fake ExoMol line list DataFrame for mocking.""" + rng = np.random.default_rng(42) + return pd.DataFrame({ + 'wav': np.linspace(2000, 2100, 50), + 'int': rng.random(50), + 'A': rng.random(50), + 'El': rng.random(50) * 1000, + 'Eu': rng.random(50) * 1000 + 100, + }) + + +@pytest.fixture +def fake_pf_df(): + """Fake partition function DataFrame for mocking.""" + return pd.DataFrame({ + 'T': np.arange(100, 3100, 100, dtype=float), + 'Q': np.linspace(10.0, 5000.0, 30), + }) + + +# ============================================================ +# MOCKED TESTS — always run in CI (no network needed) +# ============================================================ + +def test_query_lines_returns_table(monkeypatch, fake_linelist_df): + """query_lines must return astropy Table.""" + monkeypatch.setattr('radis.io.exomol.fetch_exomol', + lambda *a, **kw: fake_linelist_df) + result = ExoMol.query_lines('CO', + load_wavenum_min=2000, + load_wavenum_max=2100) + assert isinstance(result, Table) + assert len(result) == 50 + + +def test_query_lines_columns(monkeypatch, fake_linelist_df): + """Table must contain expected ExoMol line list columns.""" + monkeypatch.setattr('radis.io.exomol.fetch_exomol', + lambda *a, **kw: fake_linelist_df) + result = ExoMol.query_lines('CO', + load_wavenum_min=2000, + load_wavenum_max=2100) + for col in ['wav', 'int', 'A', 'El', 'Eu']: + assert col in result.colnames, f"Missing column: {col}" + + +def test_query_lines_broadening_str(monkeypatch, fake_linelist_df): + """broadening_species as string must work.""" + captured = {} + + def mock_fetch(*a, **kw): + captured['broadening_species'] = kw.get('broadening_species') + return fake_linelist_df + + monkeypatch.setattr('radis.io.exomol.fetch_exomol', mock_fetch) + result = ExoMol.query_lines('CO', + load_wavenum_min=2000, + load_wavenum_max=2100, + broadening_species='H2') + assert isinstance(result, Table) + assert captured['broadening_species'] == 'H2' + + +def test_query_lines_broadening_list(monkeypatch, fake_linelist_df): + """broadening_species as list must be passed through correctly.""" + captured = {} + + def mock_fetch(*a, **kw): + captured['broadening_species'] = kw.get('broadening_species') + return fake_linelist_df + + monkeypatch.setattr('radis.io.exomol.fetch_exomol', mock_fetch) + result = ExoMol.query_lines('H2O', + database='POKAZATEL', + load_wavenum_min=1000, + load_wavenum_max=1100, + broadening_species=['H2', 'He']) + assert isinstance(result, Table) + assert captured['broadening_species'] == ['H2', 'He'] + + +def test_get_partition_function_returns_table(monkeypatch, fake_pf_df): + """get_partition_function must return astropy Table.""" + monkeypatch.setattr('radis.io.exomol.fetch_exomol', + lambda *a, **kw: fake_pf_df) + result = ExoMol.get_partition_function('CO') + assert isinstance(result, Table) + assert 'T' in result.colnames + assert 'Q' in result.colnames + + +# ============================================================ +# REMOTE TESTS — actual ExoMol network calls +# Run with: pytest --remote-data +# ============================================================ + +@pytest.mark.remote_data +def test_get_molecule_list_remote(): + """ExoMol must return 50+ molecules.""" + molecules = ExoMol.get_molecule_list() + assert isinstance(molecules, list) + assert len(molecules) > 50 + assert any('CO' in m for m in molecules) + + +@pytest.mark.remote_data +def test_get_databases_H2O_remote(): + """H2O must have multiple databases.""" + dbs = ExoMol.get_databases('H2O') + assert isinstance(dbs, list) + assert len(dbs) > 0 + + +@pytest.mark.remote_data +def test_query_lines_CO_remote(): + """CO line list fetch must succeed and return Table.""" + import warnings + import gc + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + result = ExoMol.query_lines( + molecule='CO', + load_wavenum_min=2000, + load_wavenum_max=2100, + ) + gc.collect() + assert isinstance(result, Table) + assert len(result) > 0 + + +@pytest.mark.remote_data +def test_query_lines_CO_with_H2_broadening_remote(): + """CO line list with H2 broadening must succeed (falls back to air if unavailable).""" + import warnings + import gc + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + result = ExoMol.query_lines( + molecule='CO', + load_wavenum_min=2000, + load_wavenum_max=2050, + broadening_species='H2', + ) + gc.collect() + assert isinstance(result, Table) + assert len(result) > 0 diff --git a/docs/linelists/exomol/exomol.rst b/docs/linelists/exomol/exomol.rst new file mode 100644 index 0000000000..28628e728e --- /dev/null +++ b/docs/linelists/exomol/exomol.rst @@ -0,0 +1,65 @@ +.. _astroquery.linelists.exomol: + +**************************************************************** +ExoMol Line List Queries (`astroquery.linelists.exomol`) +**************************************************************** + +Overview +======== + +The `~astroquery.linelists.exomol` module provides access to the +`ExoMol database `_, the primary source of +high-temperature molecular line lists for exoplanet atmosphere modelling. + +ExoMol 2024 contains 91 molecules and ~10\ :sup:`12` transitions — +including species critical for JWST atmosphere retrieval such as +H\ :sub:`2`\ O, CO, CH\ :sub:`4`, NH\ :sub:`3`, and HCN. + +This module wraps RADIS's mature ExoMol reader (``radis.io.exomol``, +see `radis/radis#925 `_) +into the standard astroquery `~astroquery.query.BaseQuery` pattern. + +Getting Started +=============== + +List available molecules:: + + from astroquery.linelists.exomol import ExoMol + + molecules = ExoMol.get_molecule_list() + print(molecules[:10]) + +Get available databases for a molecule:: + + databases = ExoMol.get_databases('H2O') + print(databases) + +Query CO line list in a wavenumber range:: + + result = ExoMol.query_lines( + molecule='CO', + load_wavenum_min=2000, + load_wavenum_max=2100, + ) + print(result) + +Query with broadening species (critical for JWST H/He atmosphere models):: + + result = ExoMol.query_lines( + molecule='H2O', + database='POKAZATEL', + broadening_species=['H2', 'He'], + load_wavenum_min=1000, + load_wavenum_max=1100, + ) + +Get partition function Q(T):: + + pf = ExoMol.get_partition_function('CO') + print(pf) + +Reference / API +=============== + +.. automodule:: astroquery.linelists.exomol.core + :members: diff --git a/pyproject.toml b/pyproject.toml index 4fd2f12d01..3106fd2817 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,6 @@ Documentation = "https://astroquery.readthedocs.io" test = [ "pytest>=7.4", "pytest-doctestplus>=1.4", - "pytest-timeout", "pytest-astropy", "matplotlib", # Temp workaround for https://github.com/RKrahl/pytest-dependency/issues/91 @@ -118,7 +117,6 @@ text_file_format = "rst" xfail_strict = true remote_data_strict = true addopts = ["--color=yes", "--doctest-rst", "--doctest-continue-on-failure"] -timeout = 300 filterwarnings = [ "error", # Ignore astroquery's own module reorganization deprecation warnings during testing From 64a7d7ba806efb9b637738ecd4c825555d2fefb5 Mon Sep 17 00:00:00 2001 From: Aditya Pandey Date: Sun, 22 Feb 2026 00:33:18 +0530 Subject: [PATCH 02/26] Add exomol entry to CHANGES.rst --- CHANGES.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 473cca06ee..dda0d88d49 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,14 @@ New Tools and Services ---------------------- +astroquery.linelists.exomol +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Added new ``astroquery.linelists.exomol`` module for querying the ExoMol + molecular line list database. Wraps RADIS ExoMol reader into the standard + astroquery ``BaseQuery`` pattern. [#3536] + + noirlab ^^^^^^^ From f8445f9649778b3b06b34669e7651790784a49c1 Mon Sep 17 00:00:00 2001 From: Aditya Pandey Date: Sun, 22 Feb 2026 00:39:58 +0530 Subject: [PATCH 03/26] Apply ruff formatting to exomol module --- astroquery/linelists/exomol/__init__.py | 2 +- astroquery/linelists/exomol/core.py | 36 +++++-- .../linelists/exomol/tests/test_exomol.py | 101 ++++++++++-------- 3 files changed, 80 insertions(+), 59 deletions(-) diff --git a/astroquery/linelists/exomol/__init__.py b/astroquery/linelists/exomol/__init__.py index 52dbb1f59f..c6ef565305 100644 --- a/astroquery/linelists/exomol/__init__.py +++ b/astroquery/linelists/exomol/__init__.py @@ -1,4 +1,4 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst from .core import ExoMol, ExoMolClass -__all__ = ['ExoMol', 'ExoMolClass'] +__all__ = ["ExoMol", "ExoMolClass"] diff --git a/astroquery/linelists/exomol/core.py b/astroquery/linelists/exomol/core.py index 71e9476b3f..81f3e5cc95 100644 --- a/astroquery/linelists/exomol/core.py +++ b/astroquery/linelists/exomol/core.py @@ -14,7 +14,7 @@ from astroquery.query import BaseQuery from astroquery import log -__all__ = ['ExoMol', 'ExoMolClass'] +__all__ = ["ExoMol", "ExoMolClass"] EXOMOL_URL = "https://www.exomol.com" @@ -55,12 +55,12 @@ def get_molecule_list(self, *, cache=True): Sorted list of molecule names available in ExoMol. """ url = f"{self.URL}/db/exomol.all" - response = self._request('GET', url, cache=cache, timeout=self.TIMEOUT) + response = self._request("GET", url, cache=cache, timeout=self.TIMEOUT) response.raise_for_status() molecules = [] for line in response.text.splitlines(): line = line.strip() - if line and not line.startswith('#'): + if line and not line.startswith("#"): parts = line.split() if parts: molecules.append(parts[0]) @@ -86,13 +86,22 @@ def get_databases(self, molecule, isotopologue=None, *, cache=True): """ from radis.api.exomolapi import get_exomol_database_list from radis.api.exomolapi import get_exomol_full_isotope_name + iso_name = get_exomol_full_isotope_name(molecule, 1) dbs, _ = get_exomol_database_list(molecule, iso_name) return dbs - def query_lines(self, molecule, database=None, isotopologue='1', - load_wavenum_min=None, load_wavenum_max=None, - broadening_species=None, *, cache=True): + def query_lines( + self, + molecule, + database=None, + isotopologue="1", + load_wavenum_min=None, + load_wavenum_max=None, + broadening_species=None, + *, + cache=True, + ): """ Fetch ExoMol line list for a given molecule. @@ -143,8 +152,10 @@ def query_lines(self, molecule, database=None, isotopologue='1', """ from radis.io.exomol import fetch_exomol - log.info(f"Querying ExoMol for {molecule} " - f"[{load_wavenum_min}-{load_wavenum_max} cm-1]") + log.info( + f"Querying ExoMol for {molecule} " + f"[{load_wavenum_min}-{load_wavenum_max} cm-1]" + ) df = fetch_exomol( molecule=molecule, @@ -152,14 +163,17 @@ def query_lines(self, molecule, database=None, isotopologue='1', isotope=isotopologue, load_wavenum_min=load_wavenum_min, load_wavenum_max=load_wavenum_max, - broadening_species=broadening_species if broadening_species is not None else "air", + broadening_species=broadening_species + if broadening_species is not None + else "air", cache=cache, verbose=False, ) return Table.from_pandas(df) - def get_partition_function(self, molecule, database=None, - isotopologue='1', *, cache=True): + def get_partition_function( + self, molecule, database=None, isotopologue="1", *, cache=True + ): """ Get partition function Q(T) for a molecule from ExoMol. diff --git a/astroquery/linelists/exomol/tests/test_exomol.py b/astroquery/linelists/exomol/tests/test_exomol.py index 1b22a1bc3b..07f270aabc 100644 --- a/astroquery/linelists/exomol/tests/test_exomol.py +++ b/astroquery/linelists/exomol/tests/test_exomol.py @@ -18,51 +18,55 @@ # FIXTURES # ============================================================ + @pytest.fixture def fake_linelist_df(): """Fake ExoMol line list DataFrame for mocking.""" rng = np.random.default_rng(42) - return pd.DataFrame({ - 'wav': np.linspace(2000, 2100, 50), - 'int': rng.random(50), - 'A': rng.random(50), - 'El': rng.random(50) * 1000, - 'Eu': rng.random(50) * 1000 + 100, - }) + return pd.DataFrame( + { + "wav": np.linspace(2000, 2100, 50), + "int": rng.random(50), + "A": rng.random(50), + "El": rng.random(50) * 1000, + "Eu": rng.random(50) * 1000 + 100, + } + ) @pytest.fixture def fake_pf_df(): """Fake partition function DataFrame for mocking.""" - return pd.DataFrame({ - 'T': np.arange(100, 3100, 100, dtype=float), - 'Q': np.linspace(10.0, 5000.0, 30), - }) + return pd.DataFrame( + { + "T": np.arange(100, 3100, 100, dtype=float), + "Q": np.linspace(10.0, 5000.0, 30), + } + ) # ============================================================ # MOCKED TESTS — always run in CI (no network needed) # ============================================================ + def test_query_lines_returns_table(monkeypatch, fake_linelist_df): """query_lines must return astropy Table.""" - monkeypatch.setattr('radis.io.exomol.fetch_exomol', - lambda *a, **kw: fake_linelist_df) - result = ExoMol.query_lines('CO', - load_wavenum_min=2000, - load_wavenum_max=2100) + monkeypatch.setattr( + "radis.io.exomol.fetch_exomol", lambda *a, **kw: fake_linelist_df + ) + result = ExoMol.query_lines("CO", load_wavenum_min=2000, load_wavenum_max=2100) assert isinstance(result, Table) assert len(result) == 50 def test_query_lines_columns(monkeypatch, fake_linelist_df): """Table must contain expected ExoMol line list columns.""" - monkeypatch.setattr('radis.io.exomol.fetch_exomol', - lambda *a, **kw: fake_linelist_df) - result = ExoMol.query_lines('CO', - load_wavenum_min=2000, - load_wavenum_max=2100) - for col in ['wav', 'int', 'A', 'El', 'Eu']: + monkeypatch.setattr( + "radis.io.exomol.fetch_exomol", lambda *a, **kw: fake_linelist_df + ) + result = ExoMol.query_lines("CO", load_wavenum_min=2000, load_wavenum_max=2100) + for col in ["wav", "int", "A", "El", "Eu"]: assert col in result.colnames, f"Missing column: {col}" @@ -71,16 +75,15 @@ def test_query_lines_broadening_str(monkeypatch, fake_linelist_df): captured = {} def mock_fetch(*a, **kw): - captured['broadening_species'] = kw.get('broadening_species') + captured["broadening_species"] = kw.get("broadening_species") return fake_linelist_df - monkeypatch.setattr('radis.io.exomol.fetch_exomol', mock_fetch) - result = ExoMol.query_lines('CO', - load_wavenum_min=2000, - load_wavenum_max=2100, - broadening_species='H2') + monkeypatch.setattr("radis.io.exomol.fetch_exomol", mock_fetch) + result = ExoMol.query_lines( + "CO", load_wavenum_min=2000, load_wavenum_max=2100, broadening_species="H2" + ) assert isinstance(result, Table) - assert captured['broadening_species'] == 'H2' + assert captured["broadening_species"] == "H2" def test_query_lines_broadening_list(monkeypatch, fake_linelist_df): @@ -88,27 +91,28 @@ def test_query_lines_broadening_list(monkeypatch, fake_linelist_df): captured = {} def mock_fetch(*a, **kw): - captured['broadening_species'] = kw.get('broadening_species') + captured["broadening_species"] = kw.get("broadening_species") return fake_linelist_df - monkeypatch.setattr('radis.io.exomol.fetch_exomol', mock_fetch) - result = ExoMol.query_lines('H2O', - database='POKAZATEL', - load_wavenum_min=1000, - load_wavenum_max=1100, - broadening_species=['H2', 'He']) + monkeypatch.setattr("radis.io.exomol.fetch_exomol", mock_fetch) + result = ExoMol.query_lines( + "H2O", + database="POKAZATEL", + load_wavenum_min=1000, + load_wavenum_max=1100, + broadening_species=["H2", "He"], + ) assert isinstance(result, Table) - assert captured['broadening_species'] == ['H2', 'He'] + assert captured["broadening_species"] == ["H2", "He"] def test_get_partition_function_returns_table(monkeypatch, fake_pf_df): """get_partition_function must return astropy Table.""" - monkeypatch.setattr('radis.io.exomol.fetch_exomol', - lambda *a, **kw: fake_pf_df) - result = ExoMol.get_partition_function('CO') + monkeypatch.setattr("radis.io.exomol.fetch_exomol", lambda *a, **kw: fake_pf_df) + result = ExoMol.get_partition_function("CO") assert isinstance(result, Table) - assert 'T' in result.colnames - assert 'Q' in result.colnames + assert "T" in result.colnames + assert "Q" in result.colnames # ============================================================ @@ -116,19 +120,20 @@ def test_get_partition_function_returns_table(monkeypatch, fake_pf_df): # Run with: pytest --remote-data # ============================================================ + @pytest.mark.remote_data def test_get_molecule_list_remote(): """ExoMol must return 50+ molecules.""" molecules = ExoMol.get_molecule_list() assert isinstance(molecules, list) assert len(molecules) > 50 - assert any('CO' in m for m in molecules) + assert any("CO" in m for m in molecules) @pytest.mark.remote_data def test_get_databases_H2O_remote(): """H2O must have multiple databases.""" - dbs = ExoMol.get_databases('H2O') + dbs = ExoMol.get_databases("H2O") assert isinstance(dbs, list) assert len(dbs) > 0 @@ -138,10 +143,11 @@ def test_query_lines_CO_remote(): """CO line list fetch must succeed and return Table.""" import warnings import gc + with warnings.catch_warnings(): warnings.simplefilter("ignore") result = ExoMol.query_lines( - molecule='CO', + molecule="CO", load_wavenum_min=2000, load_wavenum_max=2100, ) @@ -155,13 +161,14 @@ def test_query_lines_CO_with_H2_broadening_remote(): """CO line list with H2 broadening must succeed (falls back to air if unavailable).""" import warnings import gc + with warnings.catch_warnings(): warnings.simplefilter("ignore") result = ExoMol.query_lines( - molecule='CO', + molecule="CO", load_wavenum_min=2000, load_wavenum_max=2050, - broadening_species='H2', + broadening_species="H2", ) gc.collect() assert isinstance(result, Table) From 02165e2b72a61a80932713bb7e507dcfb8d029dd Mon Sep 17 00:00:00 2001 From: Aditya Pandey Date: Sun, 22 Feb 2026 00:46:10 +0530 Subject: [PATCH 04/26] Fix pandas import using pytest.importorskip for CI compatibility --- astroquery/linelists/exomol/tests/test_exomol.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/astroquery/linelists/exomol/tests/test_exomol.py b/astroquery/linelists/exomol/tests/test_exomol.py index 07f270aabc..921a152010 100644 --- a/astroquery/linelists/exomol/tests/test_exomol.py +++ b/astroquery/linelists/exomol/tests/test_exomol.py @@ -9,7 +9,8 @@ import pytest import numpy as np -import pandas as pd + +pd = pytest.importorskip("pandas") from astropy.table import Table from astroquery.linelists.exomol import ExoMol From 7b4f4e34452f4e6c64aceae7322ffa976f7c8e48 Mon Sep 17 00:00:00 2001 From: Aditya Pandey Date: Sun, 22 Feb 2026 23:23:39 +0530 Subject: [PATCH 05/26] Fix doctest example in core.py to use code block instead --- astroquery/linelists/exomol/core.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/astroquery/linelists/exomol/core.py b/astroquery/linelists/exomol/core.py index 81f3e5cc95..91a5ddc9ec 100644 --- a/astroquery/linelists/exomol/core.py +++ b/astroquery/linelists/exomol/core.py @@ -30,11 +30,13 @@ class ExoMolClass(BaseQuery): Examples -------- - >>> from astroquery.linelists.exomol import ExoMol - >>> result = ExoMol.query_lines('CO', - ... load_wavenum_min=2000, - ... load_wavenum_max=2100) - >>> print(result) + Query CO lines between 2000-2100 cm^-1:: + + from astroquery.linelists.exomol import ExoMol + result = ExoMol.query_lines('CO', + load_wavenum_min=2000, + load_wavenum_max=2100) + print(result) """ URL = EXOMOL_URL From c6fb117e8d3cafb1801b1d461ab5833837d5f3e2 Mon Sep 17 00:00:00 2001 From: Aditya Pandey Date: Sun, 22 Feb 2026 23:57:36 +0530 Subject: [PATCH 06/26] Add exomol docs to toctree and fix automodapi --- docs/index.rst | 1 + docs/linelists/exomol/exomol.rst | 30 ++++-------------------------- 2 files changed, 5 insertions(+), 26 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 97c3ceffb7..f104698af7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -257,6 +257,7 @@ The following modules have been completed using a common API: cadc/cadc.rst casda/casda.rst linelists/cdms/cdms.rst + linelists/exomol/exomol.rst esa/euclid/euclid.rst esa/hsa/hsa.rst esa/hubble/hubble.rst diff --git a/docs/linelists/exomol/exomol.rst b/docs/linelists/exomol/exomol.rst index 28628e728e..fe69d83a40 100644 --- a/docs/linelists/exomol/exomol.rst +++ b/docs/linelists/exomol/exomol.rst @@ -1,8 +1,10 @@ .. _astroquery.linelists.exomol: -**************************************************************** ExoMol Line List Queries (`astroquery.linelists.exomol`) -**************************************************************** +********************************************************* + +.. automodapi:: astroquery.linelists.exomol + :no-inheritance-diagram: Overview ======== @@ -11,14 +13,6 @@ The `~astroquery.linelists.exomol` module provides access to the `ExoMol database `_, the primary source of high-temperature molecular line lists for exoplanet atmosphere modelling. -ExoMol 2024 contains 91 molecules and ~10\ :sup:`12` transitions — -including species critical for JWST atmosphere retrieval such as -H\ :sub:`2`\ O, CO, CH\ :sub:`4`, NH\ :sub:`3`, and HCN. - -This module wraps RADIS's mature ExoMol reader (``radis.io.exomol``, -see `radis/radis#925 `_) -into the standard astroquery `~astroquery.query.BaseQuery` pattern. - Getting Started =============== @@ -43,23 +37,7 @@ Query CO line list in a wavenumber range:: ) print(result) -Query with broadening species (critical for JWST H/He atmosphere models):: - - result = ExoMol.query_lines( - molecule='H2O', - database='POKAZATEL', - broadening_species=['H2', 'He'], - load_wavenum_min=1000, - load_wavenum_max=1100, - ) - Get partition function Q(T):: pf = ExoMol.get_partition_function('CO') print(pf) - -Reference / API -=============== - -.. automodule:: astroquery.linelists.exomol.core - :members: From abb9bef20c5f0d0099f90e1e65521eff746bf056 Mon Sep 17 00:00:00 2001 From: Aditya Pandey Date: Mon, 23 Feb 2026 11:33:50 +0530 Subject: [PATCH 07/26] Address review: remove pandas from tests, fix Table conversion, restore pytest-timeout, add dependency note to docs --- astroquery/linelists/exomol/core.py | 4 +-- .../linelists/exomol/tests/test_exomol.py | 29 +++++++++---------- docs/linelists/exomol/exomol.rst | 19 ++++++++---- pyproject.toml | 2 ++ 4 files changed, 30 insertions(+), 24 deletions(-) diff --git a/astroquery/linelists/exomol/core.py b/astroquery/linelists/exomol/core.py index 91a5ddc9ec..3654df4feb 100644 --- a/astroquery/linelists/exomol/core.py +++ b/astroquery/linelists/exomol/core.py @@ -171,7 +171,7 @@ def query_lines( cache=cache, verbose=False, ) - return Table.from_pandas(df) + return df if isinstance(df, Table) else Table.from_pandas(df) def get_partition_function( self, molecule, database=None, isotopologue="1", *, cache=True @@ -206,7 +206,7 @@ def get_partition_function( cache=cache, verbose=False, ) - return Table.from_pandas(df) + return df if isinstance(df, Table) else Table.from_pandas(df) ExoMol = ExoMolClass() diff --git a/astroquery/linelists/exomol/tests/test_exomol.py b/astroquery/linelists/exomol/tests/test_exomol.py index 921a152010..5db44b5683 100644 --- a/astroquery/linelists/exomol/tests/test_exomol.py +++ b/astroquery/linelists/exomol/tests/test_exomol.py @@ -1,7 +1,7 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for astroquery.linelists.exomol -Implements RADIS issue #925 — astroquery ExoMol module +Implements RADIS issue #925 - astroquery ExoMol module Run offline tests: pytest tests/test_exomol.py -v Run remote tests: pytest tests/test_exomol.py -v --remote-data @@ -9,22 +9,19 @@ import pytest import numpy as np - -pd = pytest.importorskip("pandas") from astropy.table import Table from astroquery.linelists.exomol import ExoMol - -# ============================================================ +# =========================================================== # FIXTURES -# ============================================================ +# =========================================================== @pytest.fixture def fake_linelist_df(): - """Fake ExoMol line list DataFrame for mocking.""" + """Fake ExoMol line list Table for mocking.""" rng = np.random.default_rng(42) - return pd.DataFrame( + return Table( { "wav": np.linspace(2000, 2100, 50), "int": rng.random(50), @@ -37,8 +34,8 @@ def fake_linelist_df(): @pytest.fixture def fake_pf_df(): - """Fake partition function DataFrame for mocking.""" - return pd.DataFrame( + """Fake partition function Table for mocking.""" + return Table( { "T": np.arange(100, 3100, 100, dtype=float), "Q": np.linspace(10.0, 5000.0, 30), @@ -46,9 +43,9 @@ def fake_pf_df(): ) -# ============================================================ -# MOCKED TESTS — always run in CI (no network needed) -# ============================================================ +# =========================================================== +# MOCKED TESTS - always run in CI (no network needed) +# =========================================================== def test_query_lines_returns_table(monkeypatch, fake_linelist_df): @@ -116,10 +113,10 @@ def test_get_partition_function_returns_table(monkeypatch, fake_pf_df): assert "Q" in result.colnames -# ============================================================ -# REMOTE TESTS — actual ExoMol network calls +# =========================================================== +# REMOTE TESTS - actual ExoMol network calls # Run with: pytest --remote-data -# ============================================================ +# =========================================================== @pytest.mark.remote_data diff --git a/docs/linelists/exomol/exomol.rst b/docs/linelists/exomol/exomol.rst index fe69d83a40..1994661ff4 100644 --- a/docs/linelists/exomol/exomol.rst +++ b/docs/linelists/exomol/exomol.rst @@ -13,6 +13,13 @@ The `~astroquery.linelists.exomol` module provides access to the `ExoMol database `_, the primary source of high-temperature molecular line lists for exoplanet atmosphere modelling. +.. note:: + + This module requires `radis `_ as a + dependency (``pip install radis``). Results are returned as + `~astropy.table.Table` objects; ``radis`` internally uses ``pandas`` + for data processing. + Getting Started =============== @@ -20,13 +27,13 @@ List available molecules:: from astroquery.linelists.exomol import ExoMol - molecules = ExoMol.get_molecule_list() - print(molecules[:10]) + molecules = ExoMol.get_molecule_list() # doctest: +SKIP + print(molecules[:10]) # doctest: +SKIP Get available databases for a molecule:: - databases = ExoMol.get_databases('H2O') - print(databases) + databases = ExoMol.get_databases('H2O') # doctest: +SKIP + print(databases) # doctest: +SKIP Query CO line list in a wavenumber range:: @@ -35,9 +42,9 @@ Query CO line list in a wavenumber range:: load_wavenum_min=2000, load_wavenum_max=2100, ) - print(result) + print(result) # doctest: +SKIP Get partition function Q(T):: pf = ExoMol.get_partition_function('CO') - print(pf) + print(pf) # doctest: +SKIP diff --git a/pyproject.toml b/pyproject.toml index 3106fd2817..c181ec1bba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,7 @@ Documentation = "https://astroquery.readthedocs.io" test = [ "pytest>=7.4", "pytest-doctestplus>=1.4", + "pytest-timeout", "pytest-astropy", "matplotlib", # Temp workaround for https://github.com/RKrahl/pytest-dependency/issues/91 @@ -117,6 +118,7 @@ text_file_format = "rst" xfail_strict = true remote_data_strict = true addopts = ["--color=yes", "--doctest-rst", "--doctest-continue-on-failure"] +timeout = 300 filterwarnings = [ "error", # Ignore astroquery's own module reorganization deprecation warnings during testing From 6af1b5495bc7e9063dd5698cd203fd38b92b896e Mon Sep 17 00:00:00 2001 From: Aditya Pandey Date: Mon, 23 Feb 2026 12:23:54 +0530 Subject: [PATCH 08/26] Add radis to tox deps and skip tests if radis unavailable --- astroquery/linelists/exomol/tests/test_exomol.py | 2 ++ tox.ini | 2 ++ 2 files changed, 4 insertions(+) diff --git a/astroquery/linelists/exomol/tests/test_exomol.py b/astroquery/linelists/exomol/tests/test_exomol.py index 5db44b5683..7fb47ade8e 100644 --- a/astroquery/linelists/exomol/tests/test_exomol.py +++ b/astroquery/linelists/exomol/tests/test_exomol.py @@ -12,6 +12,8 @@ from astropy.table import Table from astroquery.linelists.exomol import ExoMol +radis = pytest.importorskip("radis", reason="radis required for exomol tests") + # =========================================================== # FIXTURES # =========================================================== diff --git a/tox.ini b/tox.ini index ca7173cf23..d9497ca0f9 100644 --- a/tox.ini +++ b/tox.ini @@ -34,10 +34,12 @@ deps = devdeps: astropy>=0.0.dev0 devdeps: pyerfa>=0.0.dev0 devdeps: git+https://github.com/astropy/pyvo.git#egg=pyvo + devdeps: radis # mpl while not a dependency, it's required for the tests, and would pull up a newer numpy version if not pinned. # And pillow should be pinned as well, otherwise a too new version is pulled that is not compatible with old np. + oldestdeps: radis oldestdeps: astropy==5.0.0 oldestdeps: numpy==1.22 oldestdeps: matplotlib==3.5.0 From 1e6d2fbff744bbbe80f24fe97b9080744146159e Mon Sep 17 00:00:00 2001 From: Aditya Pandey Date: Mon, 23 Feb 2026 13:43:38 +0530 Subject: [PATCH 09/26] Add radis to MOCK_MODULES in conf.py and optional deps for RTD build --- docs/conf.py | 2 +- pyproject.toml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 2178e00ff4..b8fe567707 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -160,7 +160,7 @@ def __getattr__(cls, name): return Mock() -MOCK_MODULES = ['atpy', 'beautifulsoup4', 'vo', 'lxml', 'keyring', 'bs4'] +MOCK_MODULES = ['atpy', 'beautifulsoup4', 'vo', 'lxml', 'keyring', 'bs4', 'radis'] for mod_name in MOCK_MODULES: sys.modules[mod_name] = Mock() diff --git a/pyproject.toml b/pyproject.toml index c181ec1bba..dfeeef7577 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,6 +61,7 @@ all = [ "boto3", "botocore", "regions>=0.5", + "radis", ] [build-system] From 0ba56e846f37cc75c11368031a667c044659f6ad Mon Sep 17 00:00:00 2001 From: Aditya Pandey Date: Mon, 23 Feb 2026 17:19:25 +0530 Subject: [PATCH 10/26] Fix importorskip exc_type and toctree indentation for exomol --- astroquery/linelists/exomol/tests/test_exomol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astroquery/linelists/exomol/tests/test_exomol.py b/astroquery/linelists/exomol/tests/test_exomol.py index 7fb47ade8e..7f242b6537 100644 --- a/astroquery/linelists/exomol/tests/test_exomol.py +++ b/astroquery/linelists/exomol/tests/test_exomol.py @@ -12,7 +12,7 @@ from astropy.table import Table from astroquery.linelists.exomol import ExoMol -radis = pytest.importorskip("radis", reason="radis required for exomol tests") +radis = pytest.importorskip("radis", reason="radis required for exomol tests", exc_type=(ImportError, ModuleNotFoundError)) # =========================================================== # FIXTURES From abd6b2d3562d64e66db718deb2a12e456d3612bc Mon Sep 17 00:00:00 2001 From: Aditya Pandey Date: Mon, 23 Feb 2026 18:58:39 +0530 Subject: [PATCH 11/26] Revert exc_type: not supported in older pytest versions --- astroquery/linelists/exomol/tests/test_exomol.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/astroquery/linelists/exomol/tests/test_exomol.py b/astroquery/linelists/exomol/tests/test_exomol.py index 7f242b6537..82e0d19037 100644 --- a/astroquery/linelists/exomol/tests/test_exomol.py +++ b/astroquery/linelists/exomol/tests/test_exomol.py @@ -8,11 +8,12 @@ """ import pytest +import sys import numpy as np from astropy.table import Table from astroquery.linelists.exomol import ExoMol -radis = pytest.importorskip("radis", reason="radis required for exomol tests", exc_type=(ImportError, ModuleNotFoundError)) +radis = pytest.importorskip("radis", reason="radis required for exomol tests") # =========================================================== # FIXTURES From c7a0d69c55f087e79cd36c68690598406d926be7 Mon Sep 17 00:00:00 2001 From: Aditya Pandey Date: Mon, 23 Feb 2026 19:52:49 +0530 Subject: [PATCH 12/26] Remove unused sys import from test_exomol.py --- astroquery/linelists/exomol/tests/test_exomol.py | 1 - 1 file changed, 1 deletion(-) diff --git a/astroquery/linelists/exomol/tests/test_exomol.py b/astroquery/linelists/exomol/tests/test_exomol.py index 82e0d19037..7fb47ade8e 100644 --- a/astroquery/linelists/exomol/tests/test_exomol.py +++ b/astroquery/linelists/exomol/tests/test_exomol.py @@ -8,7 +8,6 @@ """ import pytest -import sys import numpy as np from astropy.table import Table from astroquery.linelists.exomol import ExoMol From d10be9612b4328044a1d39c4a80dad0f5871e192 Mon Sep 17 00:00:00 2001 From: Aditya Pandey Date: Mon, 23 Feb 2026 22:38:22 +0530 Subject: [PATCH 13/26] Add exc_type=ImportError to importorskip for NumPy 2.5 compat --- astroquery/linelists/exomol/tests/test_exomol.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/astroquery/linelists/exomol/tests/test_exomol.py b/astroquery/linelists/exomol/tests/test_exomol.py index 7fb47ade8e..d625c619d2 100644 --- a/astroquery/linelists/exomol/tests/test_exomol.py +++ b/astroquery/linelists/exomol/tests/test_exomol.py @@ -12,7 +12,8 @@ from astropy.table import Table from astroquery.linelists.exomol import ExoMol -radis = pytest.importorskip("radis", reason="radis required for exomol tests") +radis = pytest.importorskip("radis", reason="radis required for exomol tests", + exc_type=ImportError) # =========================================================== # FIXTURES From 6b37d59d1ed8b7b99a5a97a359707d9000242aa7 Mon Sep 17 00:00:00 2001 From: Aditya Pandey Date: Mon, 23 Feb 2026 23:13:48 +0530 Subject: [PATCH 14/26] Fix RTD toctree indent and radis skip for all pytest versions --- astroquery/linelists/exomol/tests/test_exomol.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/astroquery/linelists/exomol/tests/test_exomol.py b/astroquery/linelists/exomol/tests/test_exomol.py index d625c619d2..3835d85fce 100644 --- a/astroquery/linelists/exomol/tests/test_exomol.py +++ b/astroquery/linelists/exomol/tests/test_exomol.py @@ -12,8 +12,10 @@ from astropy.table import Table from astroquery.linelists.exomol import ExoMol -radis = pytest.importorskip("radis", reason="radis required for exomol tests", - exc_type=ImportError) +try: + import radis +except ImportError as e: + pytest.skip(f"radis required for exomol tests: {e}") # =========================================================== # FIXTURES From 9db0db6051783bca824e8a0565c8711d3244b316 Mon Sep 17 00:00:00 2001 From: Aditya Pandey Date: Tue, 24 Feb 2026 09:24:31 +0530 Subject: [PATCH 15/26] Fix radis skip: add noqa F401 and allow_module_level=True --- astroquery/linelists/exomol/tests/test_exomol.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/astroquery/linelists/exomol/tests/test_exomol.py b/astroquery/linelists/exomol/tests/test_exomol.py index 3835d85fce..e638559f70 100644 --- a/astroquery/linelists/exomol/tests/test_exomol.py +++ b/astroquery/linelists/exomol/tests/test_exomol.py @@ -13,9 +13,10 @@ from astroquery.linelists.exomol import ExoMol try: - import radis + import radis # noqa: F401 except ImportError as e: - pytest.skip(f"radis required for exomol tests: {e}") + pytest.skip(f"radis required for exomol tests: {e}", + allow_module_level=True) # =========================================================== # FIXTURES From 2b044be58cd693226807d72d63045308f3aa832d Mon Sep 17 00:00:00 2001 From: Aditya Pandey Date: Tue, 24 Feb 2026 15:32:16 +0530 Subject: [PATCH 16/26] Fix RTD toctree: match exomol indentation with other entries --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index f104698af7..af0658b9cd 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -257,7 +257,7 @@ The following modules have been completed using a common API: cadc/cadc.rst casda/casda.rst linelists/cdms/cdms.rst - linelists/exomol/exomol.rst + linelists/exomol/exomol.rst esa/euclid/euclid.rst esa/hsa/hsa.rst esa/hubble/hubble.rst From 892567a9e32cc381d492a2f7d6e61f647aae673e Mon Sep 17 00:00:00 2001 From: Aditya Pandey Date: Tue, 24 Feb 2026 17:37:59 +0530 Subject: [PATCH 17/26] Add example output to doctest examples in exomol.rst --- docs/linelists/exomol/exomol.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/linelists/exomol/exomol.rst b/docs/linelists/exomol/exomol.rst index 1994661ff4..86769e9c17 100644 --- a/docs/linelists/exomol/exomol.rst +++ b/docs/linelists/exomol/exomol.rst @@ -29,11 +29,13 @@ List available molecules:: molecules = ExoMol.get_molecule_list() # doctest: +SKIP print(molecules[:10]) # doctest: +SKIP + ["1H-35Cl", "AlCl", "AlF", "AlH", "AlO", "BeH", "C2", "CH", "CH+", "CN"] Get available databases for a molecule:: databases = ExoMol.get_databases('H2O') # doctest: +SKIP print(databases) # doctest: +SKIP + ["POKAZATEL", "HotWat78", "HITRAN2020"] Query CO line list in a wavenumber range:: @@ -43,8 +45,18 @@ Query CO line list in a wavenumber range:: load_wavenum_max=2100, ) print(result) # doctest: +SKIP + + wav int + float64 float64 + -------- -------- + 2000.001 1.23e-25 Get partition function Q(T):: pf = ExoMol.get_partition_function('CO') print(pf) # doctest: +SKIP +
+ T Q + float64 float64 + ------- ------- + 100.0 12.345 From 062e043dc87234f3482132dc654de648c5e819c9 Mon Sep 17 00:00:00 2001 From: Aditya Pandey Date: Tue, 24 Feb 2026 20:11:24 +0530 Subject: [PATCH 18/26] Trigger CI re-run From 55e715c62576ab507e4e2e032f309fe71d2498f5 Mon Sep 17 00:00:00 2001 From: Aditya Pandey Date: Sat, 28 Feb 2026 12:50:07 +0530 Subject: [PATCH 19/26] Address review: rename load_wavenum to wavenum with Quantity support, lazy RADIS import --- astroquery/linelists/exomol/core.py | 89 ++++++++++++++----- .../linelists/exomol/tests/test_exomol.py | 18 ++-- 2 files changed, 75 insertions(+), 32 deletions(-) diff --git a/astroquery/linelists/exomol/core.py b/astroquery/linelists/exomol/core.py index 3654df4feb..4c99588041 100644 --- a/astroquery/linelists/exomol/core.py +++ b/astroquery/linelists/exomol/core.py @@ -1,7 +1,7 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst """ ExoMol database query module for astroquery. -Wraps RADIS ExoMol reader (radis.io.exomol) into astroquery BaseQuery pattern. +Queries the ExoMol database directly via its API. References ---------- @@ -10,9 +10,10 @@ RADIS: https://github.com/radis/radis (issue #925) """ +from astropy import units as u from astropy.table import Table from astroquery.query import BaseQuery -from astroquery import log +from astropy import log __all__ = ["ExoMol", "ExoMolClass"] @@ -24,18 +25,30 @@ class ExoMolClass(BaseQuery): Queries the `ExoMol `_ database for molecular line lists used in exoplanet atmosphere modelling. - This module wraps the RADIS ExoMol reader (``radis.io.exomol``) and - exposes it via the standard astroquery ``BaseQuery`` interface, returning - ``astropy.table.Table`` objects. + This module queries ExoMol directly and optionally uses RADIS + (``radis``) for line list fetching via :meth:`query_lines`. + + .. note:: + + The :meth:`query_lines` and :meth:`get_partition_function` methods + require `RADIS `_ as an optional + dependency. Install it with:: + + pip install radis + + All other methods (``get_molecule_list``, ``get_databases``) + work without RADIS. Examples -------- - Query CO lines between 2000-2100 cm^-1:: + Query CO lines between 2000-2100 cm\\ :sup:`-1`:: + from astropy import units as u from astroquery.linelists.exomol import ExoMol + result = ExoMol.query_lines('CO', - load_wavenum_min=2000, - load_wavenum_max=2100) + wavenum_min=2000*u.cm**-1, + wavenum_max=2100*u.cm**-1) print(result) """ @@ -98,8 +111,8 @@ def query_lines( molecule, database=None, isotopologue="1", - load_wavenum_min=None, - load_wavenum_max=None, + wavenum_min=None, + wavenum_max=None, broadening_species=None, *, cache=True, @@ -116,10 +129,10 @@ def query_lines( If ``None``, uses the ExoMol-recommended database. isotopologue : str, optional Isotopologue number. Default ``'1'`` (most abundant). - load_wavenum_min : float, optional - Minimum wavenumber in cm^-1. - load_wavenum_max : float, optional - Maximum wavenumber in cm^-1. + wavenum_min : `~astropy.units.Quantity`, optional + Minimum wavenumber, e.g. ``2000*u.cm**-1``. + wavenum_max : `~astropy.units.Quantity`, optional + Maximum wavenumber, e.g. ``2100*u.cm**-1``. broadening_species : str or list of str, optional Pressure-broadening partner(s). Examples: ``'H2'``, ``['H2', 'He']``, ``'air'``. @@ -139,32 +152,56 @@ def query_lines( -------- Query CO lines:: + from astropy import units as u from astroquery.linelists.exomol import ExoMol + result = ExoMol.query_lines('CO', - load_wavenum_min=2000, - load_wavenum_max=2100) + wavenum_min=2000*u.cm**-1, + wavenum_max=2100*u.cm**-1) Query H2O with H2+He broadening:: result = ExoMol.query_lines('H2O', database='POKAZATEL', broadening_species=['H2', 'He'], - load_wavenum_min=1000, - load_wavenum_max=1100) + wavenum_min=1000*u.cm**-1, + wavenum_max=1100*u.cm**-1) """ - from radis.io.exomol import fetch_exomol + try: + from radis.io.exomol import fetch_exomol + except ImportError as e: + raise ImportError( + "The 'radis' package is required for query_lines(). " + "Install it with: pip install radis" + ) from e + + # Convert Quantity to float (cm^-1) for RADIS + wavenum_min_value = None + wavenum_max_value = None + + if wavenum_min is not None: + if hasattr(wavenum_min, "unit"): + wavenum_min_value = wavenum_min.to(u.cm**-1).value + else: + wavenum_min_value = float(wavenum_min) + + if wavenum_max is not None: + if hasattr(wavenum_max, "unit"): + wavenum_max_value = wavenum_max.to(u.cm**-1).value + else: + wavenum_max_value = float(wavenum_max) log.info( f"Querying ExoMol for {molecule} " - f"[{load_wavenum_min}-{load_wavenum_max} cm-1]" + f"[{wavenum_min_value}-{wavenum_max_value} cm-1]" ) df = fetch_exomol( molecule=molecule, database=database, isotope=isotopologue, - load_wavenum_min=load_wavenum_min, - load_wavenum_max=load_wavenum_max, + load_wavenum_min=wavenum_min_value, + load_wavenum_max=wavenum_max_value, broadening_species=broadening_species if broadening_species is not None else "air", @@ -196,7 +233,13 @@ def get_partition_function( Table with columns for temperature T (K) and partition function Q(T). """ - from radis.io.exomol import fetch_exomol + try: + from radis.io.exomol import fetch_exomol + except ImportError as e: + raise ImportError( + "The 'radis' package is required for get_partition_function(). " + "Install it with: pip install radis" + ) from e df = fetch_exomol( molecule=molecule, diff --git a/astroquery/linelists/exomol/tests/test_exomol.py b/astroquery/linelists/exomol/tests/test_exomol.py index e638559f70..faf98c18b8 100644 --- a/astroquery/linelists/exomol/tests/test_exomol.py +++ b/astroquery/linelists/exomol/tests/test_exomol.py @@ -59,7 +59,7 @@ def test_query_lines_returns_table(monkeypatch, fake_linelist_df): monkeypatch.setattr( "radis.io.exomol.fetch_exomol", lambda *a, **kw: fake_linelist_df ) - result = ExoMol.query_lines("CO", load_wavenum_min=2000, load_wavenum_max=2100) + result = ExoMol.query_lines("CO", wavenum_min=2000, wavenum_max=2100) assert isinstance(result, Table) assert len(result) == 50 @@ -69,7 +69,7 @@ def test_query_lines_columns(monkeypatch, fake_linelist_df): monkeypatch.setattr( "radis.io.exomol.fetch_exomol", lambda *a, **kw: fake_linelist_df ) - result = ExoMol.query_lines("CO", load_wavenum_min=2000, load_wavenum_max=2100) + result = ExoMol.query_lines("CO", wavenum_min=2000, wavenum_max=2100) for col in ["wav", "int", "A", "El", "Eu"]: assert col in result.colnames, f"Missing column: {col}" @@ -84,7 +84,7 @@ def mock_fetch(*a, **kw): monkeypatch.setattr("radis.io.exomol.fetch_exomol", mock_fetch) result = ExoMol.query_lines( - "CO", load_wavenum_min=2000, load_wavenum_max=2100, broadening_species="H2" + "CO", wavenum_min=2000, wavenum_max=2100, broadening_species="H2" ) assert isinstance(result, Table) assert captured["broadening_species"] == "H2" @@ -102,8 +102,8 @@ def mock_fetch(*a, **kw): result = ExoMol.query_lines( "H2O", database="POKAZATEL", - load_wavenum_min=1000, - load_wavenum_max=1100, + wavenum_min=1000, + wavenum_max=1100, broadening_species=["H2", "He"], ) assert isinstance(result, Table) @@ -152,8 +152,8 @@ def test_query_lines_CO_remote(): warnings.simplefilter("ignore") result = ExoMol.query_lines( molecule="CO", - load_wavenum_min=2000, - load_wavenum_max=2100, + wavenum_min=2000, + wavenum_max=2100, ) gc.collect() assert isinstance(result, Table) @@ -170,8 +170,8 @@ def test_query_lines_CO_with_H2_broadening_remote(): warnings.simplefilter("ignore") result = ExoMol.query_lines( molecule="CO", - load_wavenum_min=2000, - load_wavenum_max=2050, + wavenum_min=2000, + wavenum_max=2050, broadening_species="H2", ) gc.collect() From bbd89eef503a5513a3238ce2956c30681ae8712d Mon Sep 17 00:00:00 2001 From: Aditya Pandey Date: Sat, 28 Feb 2026 13:32:03 +0530 Subject: [PATCH 20/26] Address review: reimplement get_databases with bs4 scraper, remove RADIS dependency, fix pyproject.toml whitespace --- astroquery/linelists/exomol/core.py | 44 +++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/astroquery/linelists/exomol/core.py b/astroquery/linelists/exomol/core.py index 4c99588041..b493699875 100644 --- a/astroquery/linelists/exomol/core.py +++ b/astroquery/linelists/exomol/core.py @@ -81,16 +81,16 @@ def get_molecule_list(self, *, cache=True): molecules.append(parts[0]) return sorted(list(set(molecules))) - def get_databases(self, molecule, isotopologue=None, *, cache=True): + def get_databases(self, molecule, *, cache=True): """ Get available line list databases for a given molecule. + Scrapes the ExoMol website to find available databases. + Parameters ---------- molecule : str Molecule formula e.g. ``'H2O'``, ``'CO'``, ``'CH4'``. - isotopologue : str, optional - Isotopologue slug e.g. ``'1H2-16O'``. If ``None``, uses default. cache : bool, optional Cache results. Default ``True``. @@ -98,13 +98,41 @@ def get_databases(self, molecule, isotopologue=None, *, cache=True): ------- list of str Available database names for this molecule. + + Examples + -------- + .. code-block:: python + + from astroquery.linelists.exomol import ExoMol + dbs = ExoMol.get_databases('H2O') + print(dbs) # doctest: +SKIP + """ - from radis.api.exomolapi import get_exomol_database_list - from radis.api.exomolapi import get_exomol_full_isotope_name + try: + from bs4 import BeautifulSoup + except ImportError as e: + raise ImportError( + "The 'beautifulsoup4' package is required for get_databases(). " + "Install it with: pip install beautifulsoup4" + ) from e + + url = f"{self.URL}/data/molecules/{molecule}/" + response = self._request("GET", url, cache=cache, timeout=self.TIMEOUT) + response.raise_for_status() - iso_name = get_exomol_full_isotope_name(molecule, 1) - dbs, _ = get_exomol_database_list(molecule, iso_name) - return dbs + soup = BeautifulSoup(response.text, "html.parser") + databases = [] + for link in soup.find_all("a", href=True): + href = link["href"] + if ( + href.startswith(f"/data/molecules/{molecule}/") + and href != f"/data/molecules/{molecule}/" + ): + db_name = href.rstrip("/").split("/")[-1] + if db_name and db_name != molecule: + databases.append(db_name) + + return sorted(list(set(databases))) def query_lines( self, From 121df8e851c3199b1d85d3649a8a87483aa1122a Mon Sep 17 00:00:00 2001 From: Aditya Pandey Date: Sat, 28 Feb 2026 14:38:24 +0530 Subject: [PATCH 21/26] Add test for get_databases bs4 scraper to improve coverage --- .../linelists/exomol/tests/test_exomol.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/astroquery/linelists/exomol/tests/test_exomol.py b/astroquery/linelists/exomol/tests/test_exomol.py index faf98c18b8..8d9c625c1c 100644 --- a/astroquery/linelists/exomol/tests/test_exomol.py +++ b/astroquery/linelists/exomol/tests/test_exomol.py @@ -177,3 +177,28 @@ def test_query_lines_CO_with_H2_broadening_remote(): gc.collect() assert isinstance(result, Table) assert len(result) > 0 + + +def test_get_databases_returns_list(monkeypatch): + """get_databases must return a list of database names.""" + from bs4 import BeautifulSoup + + fake_html = """ + + POKAZATEL + BT2 + + """ + + class FakeResponse: + text = fake_html + def raise_for_status(self): pass + + from astroquery.linelists.exomol import ExoMol + monkeypatch.setattr( + ExoMol.__class__, "_request", lambda self, *a, **kw: FakeResponse() + ) + dbs = ExoMol.get_databases("H2O") + assert isinstance(dbs, list) + assert "POKAZATEL" in dbs + assert "BT2" in dbs From 9312afd37538d9dd2eb6d2bedb3aa3f2df7f8a72 Mon Sep 17 00:00:00 2001 From: Aditya Pandey Date: Sat, 28 Feb 2026 14:56:13 +0530 Subject: [PATCH 22/26] Fix flake8: remove unused bs4 import from test --- astroquery/linelists/exomol/tests/test_exomol.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/astroquery/linelists/exomol/tests/test_exomol.py b/astroquery/linelists/exomol/tests/test_exomol.py index 8d9c625c1c..b303c6c5f7 100644 --- a/astroquery/linelists/exomol/tests/test_exomol.py +++ b/astroquery/linelists/exomol/tests/test_exomol.py @@ -178,10 +178,7 @@ def test_query_lines_CO_with_H2_broadening_remote(): assert isinstance(result, Table) assert len(result) > 0 - def test_get_databases_returns_list(monkeypatch): - """get_databases must return a list of database names.""" - from bs4 import BeautifulSoup fake_html = """ @@ -192,7 +189,8 @@ def test_get_databases_returns_list(monkeypatch): class FakeResponse: text = fake_html - def raise_for_status(self): pass + def raise_for_status(self): + pass from astroquery.linelists.exomol import ExoMol monkeypatch.setattr( From 99c856afadfb9993e498dd851cbc78302120b530 Mon Sep 17 00:00:00 2001 From: Aditya Pandey Date: Sat, 28 Feb 2026 15:04:46 +0530 Subject: [PATCH 23/26] Fix flake8 E302/E306: correct blank lines in test_get_databases --- astroquery/linelists/exomol/tests/test_exomol.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/astroquery/linelists/exomol/tests/test_exomol.py b/astroquery/linelists/exomol/tests/test_exomol.py index b303c6c5f7..759906a617 100644 --- a/astroquery/linelists/exomol/tests/test_exomol.py +++ b/astroquery/linelists/exomol/tests/test_exomol.py @@ -178,6 +178,7 @@ def test_query_lines_CO_with_H2_broadening_remote(): assert isinstance(result, Table) assert len(result) > 0 + def test_get_databases_returns_list(monkeypatch): fake_html = """ @@ -187,6 +188,7 @@ def test_get_databases_returns_list(monkeypatch): """ + class FakeResponse: text = fake_html def raise_for_status(self): From 974a1aa0d152ab46d3fa1bcc270b1d0263da6eab Mon Sep 17 00:00:00 2001 From: Aditya Pandey Date: Sat, 28 Feb 2026 15:09:36 +0530 Subject: [PATCH 24/26] Fix flake8: rewrite test_get_databases with correct formatting --- .../linelists/exomol/tests/test_exomol.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/astroquery/linelists/exomol/tests/test_exomol.py b/astroquery/linelists/exomol/tests/test_exomol.py index 759906a617..b2bc937f63 100644 --- a/astroquery/linelists/exomol/tests/test_exomol.py +++ b/astroquery/linelists/exomol/tests/test_exomol.py @@ -179,22 +179,24 @@ def test_query_lines_CO_with_H2_broadening_remote(): assert len(result) > 0 -def test_get_databases_returns_list(monkeypatch): - - fake_html = """ - - POKAZATEL - BT2 - - """ +def test_get_databases_returns_list(monkeypatch): + """get_databases must return a list of database names.""" + fake_html = ( + "" + 'POKAZATEL' + 'BT2' + "" + ) class FakeResponse: text = fake_html + def raise_for_status(self): pass from astroquery.linelists.exomol import ExoMol + monkeypatch.setattr( ExoMol.__class__, "_request", lambda self, *a, **kw: FakeResponse() ) From b0d1a6c17d3541d69676ad34bba9df938f97bef3 Mon Sep 17 00:00:00 2001 From: Aditya Pandey Date: Sat, 28 Feb 2026 15:15:52 +0530 Subject: [PATCH 25/26] Fix E303: remove extra blank line before test_get_databases --- astroquery/linelists/exomol/tests/test_exomol.py | 1 - 1 file changed, 1 deletion(-) diff --git a/astroquery/linelists/exomol/tests/test_exomol.py b/astroquery/linelists/exomol/tests/test_exomol.py index b2bc937f63..efe79e432a 100644 --- a/astroquery/linelists/exomol/tests/test_exomol.py +++ b/astroquery/linelists/exomol/tests/test_exomol.py @@ -179,7 +179,6 @@ def test_query_lines_CO_with_H2_broadening_remote(): assert len(result) > 0 - def test_get_databases_returns_list(monkeypatch): """get_databases must return a list of database names.""" fake_html = ( From 5618a383e46bbce6fbebae8cfbda8fefcc6ad987 Mon Sep 17 00:00:00 2001 From: Aditya Pandey Date: Sun, 1 Mar 2026 09:58:55 +0530 Subject: [PATCH 26/26] Tests: use skipif RADEX_NOT_AVAILABLE, add Quantity args, move remote tests to test_exomol_remote.py --- .../linelists/exomol/tests/test_exomol.py | 120 ++++-------------- .../exomol/tests/test_exomol_remote.py | 60 +++++++++ 2 files changed, 84 insertions(+), 96 deletions(-) create mode 100644 astroquery/linelists/exomol/tests/test_exomol_remote.py diff --git a/astroquery/linelists/exomol/tests/test_exomol.py b/astroquery/linelists/exomol/tests/test_exomol.py index efe79e432a..14f70b44fa 100644 --- a/astroquery/linelists/exomol/tests/test_exomol.py +++ b/astroquery/linelists/exomol/tests/test_exomol.py @@ -1,31 +1,19 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -""" -Tests for astroquery.linelists.exomol -Implements RADIS issue #925 - astroquery ExoMol module - -Run offline tests: pytest tests/test_exomol.py -v -Run remote tests: pytest tests/test_exomol.py -v --remote-data -""" - import pytest import numpy as np +from astropy import units as u from astropy.table import Table from astroquery.linelists.exomol import ExoMol try: import radis # noqa: F401 -except ImportError as e: - pytest.skip(f"radis required for exomol tests: {e}", - allow_module_level=True) - -# =========================================================== -# FIXTURES -# =========================================================== + RADEX_NOT_AVAILABLE = False +except ImportError: + RADEX_NOT_AVAILABLE = True @pytest.fixture def fake_linelist_df(): - """Fake ExoMol line list Table for mocking.""" rng = np.random.default_rng(42) return Table( { @@ -40,7 +28,6 @@ def fake_linelist_df(): @pytest.fixture def fake_pf_df(): - """Fake partition function Table for mocking.""" return Table( { "T": np.arange(100, 3100, 100, dtype=float), @@ -49,33 +36,32 @@ def fake_pf_df(): ) -# =========================================================== -# MOCKED TESTS - always run in CI (no network needed) -# =========================================================== - - +@pytest.mark.skipif(RADEX_NOT_AVAILABLE, reason="radis is required for this test") def test_query_lines_returns_table(monkeypatch, fake_linelist_df): - """query_lines must return astropy Table.""" monkeypatch.setattr( "radis.io.exomol.fetch_exomol", lambda *a, **kw: fake_linelist_df ) - result = ExoMol.query_lines("CO", wavenum_min=2000, wavenum_max=2100) + result = ExoMol.query_lines( + "CO", wavenum_min=2000 * u.cm**-1, wavenum_max=2100 * u.cm**-1 + ) assert isinstance(result, Table) assert len(result) == 50 +@pytest.mark.skipif(RADEX_NOT_AVAILABLE, reason="radis is required for this test") def test_query_lines_columns(monkeypatch, fake_linelist_df): - """Table must contain expected ExoMol line list columns.""" monkeypatch.setattr( "radis.io.exomol.fetch_exomol", lambda *a, **kw: fake_linelist_df ) - result = ExoMol.query_lines("CO", wavenum_min=2000, wavenum_max=2100) + result = ExoMol.query_lines( + "CO", wavenum_min=2000 * u.cm**-1, wavenum_max=2100 * u.cm**-1 + ) for col in ["wav", "int", "A", "El", "Eu"]: assert col in result.colnames, f"Missing column: {col}" +@pytest.mark.skipif(RADEX_NOT_AVAILABLE, reason="radis is required for this test") def test_query_lines_broadening_str(monkeypatch, fake_linelist_df): - """broadening_species as string must work.""" captured = {} def mock_fetch(*a, **kw): @@ -84,14 +70,17 @@ def mock_fetch(*a, **kw): monkeypatch.setattr("radis.io.exomol.fetch_exomol", mock_fetch) result = ExoMol.query_lines( - "CO", wavenum_min=2000, wavenum_max=2100, broadening_species="H2" + "CO", + wavenum_min=2000 * u.cm**-1, + wavenum_max=2100 * u.cm**-1, + broadening_species="H2", ) assert isinstance(result, Table) assert captured["broadening_species"] == "H2" +@pytest.mark.skipif(RADEX_NOT_AVAILABLE, reason="radis is required for this test") def test_query_lines_broadening_list(monkeypatch, fake_linelist_df): - """broadening_species as list must be passed through correctly.""" captured = {} def mock_fetch(*a, **kw): @@ -102,85 +91,26 @@ def mock_fetch(*a, **kw): result = ExoMol.query_lines( "H2O", database="POKAZATEL", - wavenum_min=1000, - wavenum_max=1100, + wavenum_min=1000 * u.cm**-1, + wavenum_max=1100 * u.cm**-1, broadening_species=["H2", "He"], ) assert isinstance(result, Table) assert captured["broadening_species"] == ["H2", "He"] +@pytest.mark.skipif(RADEX_NOT_AVAILABLE, reason="radis is required for this test") def test_get_partition_function_returns_table(monkeypatch, fake_pf_df): - """get_partition_function must return astropy Table.""" - monkeypatch.setattr("radis.io.exomol.fetch_exomol", lambda *a, **kw: fake_pf_df) + monkeypatch.setattr( + "radis.io.exomol.fetch_exomol", lambda *a, **kw: fake_pf_df + ) result = ExoMol.get_partition_function("CO") assert isinstance(result, Table) assert "T" in result.colnames assert "Q" in result.colnames -# =========================================================== -# REMOTE TESTS - actual ExoMol network calls -# Run with: pytest --remote-data -# =========================================================== - - -@pytest.mark.remote_data -def test_get_molecule_list_remote(): - """ExoMol must return 50+ molecules.""" - molecules = ExoMol.get_molecule_list() - assert isinstance(molecules, list) - assert len(molecules) > 50 - assert any("CO" in m for m in molecules) - - -@pytest.mark.remote_data -def test_get_databases_H2O_remote(): - """H2O must have multiple databases.""" - dbs = ExoMol.get_databases("H2O") - assert isinstance(dbs, list) - assert len(dbs) > 0 - - -@pytest.mark.remote_data -def test_query_lines_CO_remote(): - """CO line list fetch must succeed and return Table.""" - import warnings - import gc - - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - result = ExoMol.query_lines( - molecule="CO", - wavenum_min=2000, - wavenum_max=2100, - ) - gc.collect() - assert isinstance(result, Table) - assert len(result) > 0 - - -@pytest.mark.remote_data -def test_query_lines_CO_with_H2_broadening_remote(): - """CO line list with H2 broadening must succeed (falls back to air if unavailable).""" - import warnings - import gc - - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - result = ExoMol.query_lines( - molecule="CO", - wavenum_min=2000, - wavenum_max=2050, - broadening_species="H2", - ) - gc.collect() - assert isinstance(result, Table) - assert len(result) > 0 - - def test_get_databases_returns_list(monkeypatch): - """get_databases must return a list of database names.""" fake_html = ( "" 'POKAZATEL' @@ -194,8 +124,6 @@ class FakeResponse: def raise_for_status(self): pass - from astroquery.linelists.exomol import ExoMol - monkeypatch.setattr( ExoMol.__class__, "_request", lambda self, *a, **kw: FakeResponse() ) diff --git a/astroquery/linelists/exomol/tests/test_exomol_remote.py b/astroquery/linelists/exomol/tests/test_exomol_remote.py new file mode 100644 index 0000000000..36cff20086 --- /dev/null +++ b/astroquery/linelists/exomol/tests/test_exomol_remote.py @@ -0,0 +1,60 @@ +# Licensed under a 3-clause BSD style license - see LICENSE.rst +import gc +import warnings + +import pytest +from astropy import units as u +from astropy.table import Table +from astroquery.linelists.exomol import ExoMol + +try: + import radis # noqa: F401 + RADEX_NOT_AVAILABLE = False +except ImportError: + RADEX_NOT_AVAILABLE = True + + +@pytest.mark.remote_data +def test_get_molecule_list_remote(): + molecules = ExoMol.get_molecule_list() + assert isinstance(molecules, list) + assert len(molecules) > 50 + assert any("CO" in m for m in molecules) + + +@pytest.mark.remote_data +def test_get_databases_H2O_remote(): + dbs = ExoMol.get_databases("H2O") + assert isinstance(dbs, list) + assert len(dbs) > 0 + + +@pytest.mark.remote_data +@pytest.mark.skipif(RADEX_NOT_AVAILABLE, reason="radis is required for this test") +def test_query_lines_CO_remote(): + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + result = ExoMol.query_lines( + molecule="CO", + wavenum_min=2000 * u.cm**-1, + wavenum_max=2100 * u.cm**-1, + ) + gc.collect() + assert isinstance(result, Table) + assert len(result) > 0 + + +@pytest.mark.remote_data +@pytest.mark.skipif(RADEX_NOT_AVAILABLE, reason="radis is required for this test") +def test_query_lines_CO_with_H2_broadening_remote(): + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + result = ExoMol.query_lines( + molecule="CO", + wavenum_min=2000 * u.cm**-1, + wavenum_max=2050 * u.cm**-1, + broadening_species="H2", + ) + gc.collect() + assert isinstance(result, Table) + assert len(result) > 0