Skip to content

fermi: migrate from LATDataQuery.cgi scraping to the new REST API - #3647

Open
PazSheimy wants to merge 17 commits into
astropy:mainfrom
PazSheimy:fermi-rest-api
Open

PazSheimy wants to merge 17 commits into
astropy:mainfrom
PazSheimy:fermi-rest-api

Conversation

@PazSheimy

@PazSheimy PazSheimy commented Aug 18, 2026 •

Copy link
Copy Markdown

Fixes #3646
Closes #1849

Background

The Fermi LAT data server is replacing the LATDataQuery.cgi form endpoint
with a JSON REST API. Both will run in parallel for some time, but the REST
API will eventually replace LATDataQuery.cgi entirely, so this PR migrates
the module in advance for astroquery users.

  • Base URL: https://fermi.gsfc.nasa.gov/ssc/data/access/lat/query/api/v1
    (docs). I work on the
    Fermi data-server side and wrote this migration against the new API.

What changed

  • query_object_async() POSTs a JSON payload to {base}/query and returns
    the server-assigned query_id. Previously it returned the URL of an HTML
    results page
  • _parse_result() polls {base}/query/{id}/status and reads
    {base}/query/{id}/results, so the synchronous
    query_object() keeps its exact previous signature and return type
    (a list of FITS file URLs) — existing user scripts are unaffected.
  • All regex/HTML scraping is removed, along with the module's direct
    requests usage (everything now goes through BaseQuery._request).
  • New public methods: get_status(), list_results(), and get_file_urls()
    (which accepts an optional max_wait to bound the wait).
  • New query options exposed by the module: zenithangle,
    coordsystem (J2000/B1950/Galactic - previously only J2000), and all-sky queries
    (radius > 60 deg with an observation window <= 24 h); literal "RA,Dec"
    strings bypass name resolution.
  • Failed queries now raise RemoteServiceError;
    a query that never completes can be bounded with max_wait
    (raises TimeoutError).
  • Rejected queries surface the server's own error message: an HTTP error with
    a JSON body like {"error": "Invalid energy range ..."} is raised as a
    RemoteServiceError carrying that message (closes the old TODO from Fail with useful failure messages on genuine failures. #1849).
  • GetFermilatDatafile / get_fermilat_datafile kept as deprecated shims
    delegating to get_file_urls().
  • Removed the "Experimental" import-time warning; the module now follows the
    standard astroquery async/sync pattern.
  • Docs (docs/fermi/fermi.rst) and changelog updated.

Testing

  • Unit tests rewritten against mocked JSON responses (submit, status
    running/complete/failed, results), including payload-shape tests, error
    paths, and a forward-compatibility test for absolute file URLs:
    15 pass offline.
  • Remote tests (--remote-data) run against the live API on 2026-08-14:
    4/4 pass (basic query, async + status, zenith angle, all-sky).

@codecov

codecov Bot commented Aug 18, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 73.87%. Comparing base (e0a0f4c) to head (f60bdc8).
⚠️ Report is 83 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3647      +/-   ##
==========================================
+ Coverage   73.45%   73.87%   +0.41%     
==========================================
  Files         230      230              
  Lines       21424    21639     +215     
==========================================
+ Hits        15738    15986     +248     
+ Misses       5686     5653      -33     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@bsipocz bsipocz added the fermi label Aug 18, 2026
@PazSheimy
PazSheimy marked this pull request as ready for review September 4, 2026 18:02
@PazSheimy

Copy link
Copy Markdown
Author

Note on CI: the devdeps job failure is unrelated to this PR

@PazSheimy

Copy link
Copy Markdown
Author

Friendly ping @bsipocz — this PR is ready for review whenever someone has time. Since opening it I've synced with current main and all CI checks pass. I work on the Fermi data-server side, I'm happy to answer any questions about the API itself, and glad to adjust anything about the module's approach.

@bsipocz

bsipocz commented Sep 21, 2026

Copy link
Copy Markdown
Member

@PazSheimy - yes, it's somewhere at the top of my review queue, but there are a few other big PRs that preceed it.

@bsipocz bsipocz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the PR!

I have some API related comments, mostly about aiming for API consistency with the other modules. I would think at least some of them need to be resolved before we merge this.

]

import warnings
warnings.warn("Experimental: Fermi-LAT has not yet been refactored to have "

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice 🎉

Comment thread astroquery/fermi/core.py Outdated
Comment thread astroquery/fermi/core.py Outdated
# ------------------------------------------------------------------
# submission
# ------------------------------------------------------------------
def query_object_async(self, *args, **kwargs):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

have we make args and kwargs explicit? Only list the required positional argument and accepted keyword arguments in the signature and not have a signature that swallows everything

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Also, this is a good spot to say we are moving away from the duplicated method and method_async approach, so I would say a substantial refactor like this PR can do that work and cleanup the API to only keep the non async methods around.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Happy to do this cleanup
I'll make query_object() an explicitly defined method with the full signature and move submission/polling to private helpers.

One question: should query_object_async be removed outright, or kept for one release as a deprecated shim? (Worth noting the old query_object_async returned an HTML results-page URL that won't exist once the CGI retires so pre-existing callers break either way; the shim would just make it a deprecation warning instead of an AttributeError.)

Comment thread astroquery/fermi/core.py Outdated
_COORD_PAIR_RE = re.compile(r'^\s*[-+]?\d+(\.\d*)?\s*,\s*[-+]?\d+(\.\d*)?\s*$')


@async_to_sync

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

you can remove this

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done. @async_to_sync is removed; query_object() is now an explicitly-defined method and submission/polling are private helpers. I kept query_object_async as a deprecated shim for now, matching the PR's existing deprecation pattern — happy to remove it outright instead if you prefer, it's a two-line change.

Comment thread astroquery/fermi/core.py Outdated

Notes
-----
Prior to the REST API migration this method returned the URL of an

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No need to keep any mention of the old API here. If you want to you can keep some note boxes in the narrative documentation though and formal deprecations in the code itself.

@PazSheimy PazSheimy Sep 25, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done — removed.

Comment thread astroquery/fermi/core.py Outdated
_raise_for_status(response, context=f"Fermi LAT results ({query_id})")
return response.json().get('files', [])

def wait_for_completion(self, query_id, *, check_frequency=None,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

does this have to be user facing/public API?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Made it private (_wait_for_completion) — users still control polling via the check_frequency/max_wait keywords on the public methods.

Comment thread astroquery/fermi/core.py
time.sleep(check_frequency * 60)
elapsed_time += check_frequency

def get_file_urls(self, query_id, *, check_frequency=None, max_wait=None,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The method name here may need some coordination -- there are a couple other PRs in the works that deal with datalink urls and uris, but getting a conclusion on that should not be a blocker for this PR, it's just a heads up

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Understood
happy to rename to match whatever convention comes out of the datalink discussions, here or in a follow-up PR.

Comment thread astroquery/fermi/core.py
return _fermi_format_coords(c)
except (u.UnitsError, TypeError):
raise Exception("Coordinates not specified correctly")
def _raise_for_status(response, *, context):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

everywhere else we just use response.raise_for_status(), I would do that here, too rather than override it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done — error detection now goes through response.raise_for_status(); the helper only catches the HTTPError and re-raises as RemoteServiceError with the server's JSON error message attached (that message is the #1849 fix, so I kept it). Happy to simplify to a bare raise_for_status() if you'd prefer.

Comment thread astroquery/fermi/core.py Outdated

def __call__(self, result_url, *, check_frequency=1, verbose=False):
self.result_url = result_url
def _fermi_format_coords(c, *, coordsystem='J2000'):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

do we really need this as a separate method, or could be merged into the previous one?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Merged into _parse_coordinates.

@bsipocz bsipocz added this to the 0.4.13 milestone Sep 22, 2026

This branch has not been deployed

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fermi: LATDataQuery.cgi is being replaced by a REST API — module update available Fail with useful failure messages on genuine failures.

2 participants