Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Ensure AsyncTAPJob.result strictly follows TAP spec result ID requirement #644

Merged
merged 1 commit into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading