Skip to content

Commit

Permalink
Update AsyncTAPJob.result to strictly follow TAP spec result ID requi…
Browse files Browse the repository at this point in the history
…rement
  • Loading branch information
stvoutsin committed Jan 28, 2025
1 parent 50d3e7a commit 42c76e0
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 3 deletions.
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ Enhancements and Fixes

- Make deletion of TAP jobs optional via a new ``delete`` kwarg. [#640]

- Change AsyncTAPJob.result to return None if no result is found explicitly [#644]


Deprecations and Removals
-------------------------

Expand Down
15 changes: 12 additions & 3 deletions pyvo/dal/tap.py
Original file line number Diff line number Diff line change
Expand Up @@ -861,14 +861,14 @@ def results(self):
@property
def result(self):
"""
The job result if exists
Returns the UWS result with id='result' if it exists, otherwise None.
"""
try:
for r in self._job.results:
if r.id_ == 'result':
return r

return self._job.results[0]
return None
except IndexError:
return None

Expand All @@ -885,7 +885,10 @@ def result_uri(self):
the uri of the result
"""
try:
uri = self.result.href
result = self.result
if result is None:
return None
uri = result.href
if not urlparse(uri).netloc:
uri = urljoin(self.url, uri)
return uri
Expand Down Expand Up @@ -1007,6 +1010,12 @@ def fetch_result(self):
"""
returns the result votable if query is finished
"""
result_uri = self.result_uri
if result_uri is None:
self._update()
self.raise_if_error()
raise DALServiceError("No result URI available", self.url)

try:
response = self._session.get(self.result_uri, stream=True)
response.raise_for_status()
Expand Down
104 changes: 104 additions & 0 deletions pyvo/dal/tests/test_tap.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import tempfile

import pytest
import requests
import requests_mock

from pyvo.dal.tap import escape, search, AsyncTAPJob, TAPService
Expand Down Expand Up @@ -355,6 +356,12 @@ def async_fixture(mocker):
yield from mock_server.use(mocker)


@pytest.fixture()
def async_fixture_with_timeout(mocker):
mock_server = MockAsyncTAPServer()
yield from mock_server.use(mocker)


@pytest.fixture()
def tables(mocker):
def callback_tables(request, context):
Expand Down Expand Up @@ -737,6 +744,103 @@ def match_request_text(request):
finally:
prototype.deactivate_features('cadc-tb-upload')

@pytest.mark.usefixtures('async_fixture')
def test_job_no_result(self):
service = TAPService('http://example.com/tap')
job = service.submit_job("SELECT * FROM ivoa.obscore")
with pytest.raises(DALServiceError) as excinfo:
job.fetch_result()

assert "No result URI available" in str(excinfo.value)
job.delete()

@pytest.mark.usefixtures('async_fixture')
def test_fetch_result_network_error(self):
service = TAPService('http://example.com/tap')
job = service.submit_job("SELECT * FROM ivoa.obscore")
job.run()
job.wait()
status_response = '''<?xml version="1.0" encoding="UTF-8"?>
<uws:job xmlns:uws="http://www.ivoa.net/xml/UWS/v1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<uws:jobId>1</uws:jobId>
<uws:phase>COMPLETED</uws:phase>
<uws:results>
<uws:result id="result" xsi:type="vot:VOTable"
href="http://example.com/tap/async/1/results/result"/>
</uws:results>
</uws:job>'''

with requests_mock.Mocker() as rm:
rm.get(f'http://example.com/tap/async/{job.job_id}',
text=status_response)
rm.get(
f'http://example.com/tap/async/{job.job_id}/results/result',
exc=requests.exceptions.ConnectTimeout
)

with pytest.raises(DALServiceError) as excinfo:
job.fetch_result()

assert "Unknown service error" in str(excinfo.value)

job.delete()

@pytest.mark.usefixtures('async_fixture')
def test_job_no_result_uri(self):
status_response = '''<?xml version="1.0" encoding="UTF-8"?>
<uws:job xmlns:uws="http://www.ivoa.net/xml/UWS/v1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<uws:jobId>1</uws:jobId>
<uws:phase>COMPLETED</uws:phase>
<uws:results>
<uws:result id="diag" xlink:href="uws:executing:10"/>
</uws:results>
</uws:job>'''

service = TAPService('http://example.com/tap')
job = service.submit_job("SELECT * FROM ivoa.obscore")
job.run()
job.wait()

with requests_mock.Mocker() as rm:
rm.get(f'http://example.com/tap/async/{job.job_id}',
text=status_response)
job._update()
with pytest.raises(DALServiceError) as excinfo:
job.fetch_result()

assert "No result URI available" in str(excinfo.value)

job.delete()

@pytest.mark.usefixtures('async_fixture')
def test_job_with_empty_error(self):
error_response = '''<?xml version="1.0" encoding="UTF-8"?>
<uws:job xmlns:uws="http://www.ivoa.net/xml/UWS/v1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<uws:jobId>1</uws:jobId>
<uws:phase>ERROR</uws:phase>
<uws:results/>
<uws:errorSummary>
<uws:message></uws:message>
</uws:errorSummary>
</uws:job>'''

service = TAPService('http://example.com/tap')
job = service.submit_job("SELECT * FROM ivoa.obscore")
job.run()
job.wait()

with requests_mock.Mocker() as rm:
rm.get(f'http://example.com/tap/async/{job.job_id}',
text=error_response)
job._update()
with pytest.raises(DALQueryError) as excinfo:
job.fetch_result()

assert "<No useful error from server>" in str(excinfo.value)


@pytest.mark.usefixtures("tapservice")
class TestTAPCapabilities:
Expand Down

0 comments on commit 42c76e0

Please sign in to comment.