Skip to content

Commit d71e5cf

Browse files
committed
No longer show overflow warning if user set maxrec
1 parent 30a4ecb commit d71e5cf

File tree

6 files changed

+307
-10
lines changed

6 files changed

+307
-10
lines changed

CHANGES.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
Enhancements and Fixes
55
----------------------
66

7+
- No longer show a warning when an overflow status is encountered with a maxrec set by the user [#690]
8+
79
- Add support for the jobInfo element for UWS jobs [#679]
810

911
- Fix job result handling to prioritize standard URL structure [#684]

docs/dal/index.rst

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -348,13 +348,23 @@ rows they will return before overflowing:
348348
20000
349349

350350
To retrieve more rows than that (often conservative) default limit, you
351-
must override maxrec in the call to ``search``. A warning can be expected if
352-
you reach the ``maxrec`` limit:
351+
must override maxrec in the call to ``search``. PyVO will only warn about
352+
truncation when it's unexpected. If you request 5 records and get 5 records,
353+
no warning is issued:
353354

354355
.. doctest-remote-data::
355356

356-
>>> tap_results = tap_service.search("SELECT * FROM arihip.main", maxrec=5) # doctest: +SHOW_WARNINGS
357-
DALOverflowWarning: Result set limited by user- or server-supplied MAXREC parameter.
357+
>>> tap_results = tap_service.search("SELECT * FROM arihip.main", maxrec=5)
358+
>>> len(tap_results)
359+
5
360+
361+
However, if results are truncated by server limits without you specifying
362+
maxrec, you'll receive a helpful warning:
363+
364+
.. doctest-remote-data::
365+
366+
>>> tap_results = tap_service.search("SELECT * FROM arihip.main") # doctest: +SHOW_WARNINGS
367+
DALOverflowWarning: Results truncated due to server limits. Consider setting a maxrec value.
358368

359369
Services will not let you raise maxrec beyond the hard match limit:
360370

pyvo/dal/query.py

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ def from_result_url(cls, result_url, *, session=None):
303303
url=result_url,
304304
session=session)
305305

306-
def __init__(self, votable, *, url=None, session=None):
306+
def __init__(self, votable, *, url=None, session=None, user_maxrec=None):
307307
"""
308308
initialize the cursor. This constructor is not typically called
309309
by directly applications; rather an instance is obtained from calling
@@ -318,6 +318,8 @@ def __init__(self, votable, *, url=None, session=None):
318318
the URL that produced the response
319319
session : object
320320
optional session to use for network requests
321+
user_maxrec: int
322+
the maximum number of records that the user requested.
321323
322324
Raises
323325
------
@@ -332,14 +334,14 @@ def __init__(self, votable, *, url=None, session=None):
332334

333335
self._url = url
334336
self._session = use_session(session)
337+
self._user_maxrec = user_maxrec
335338

336339
self._status = self._findstatus(votable)
337340
if self._status[0].lower() not in ("ok", "overflow"):
338341
raise DALQueryError(self._status[1], self._status[0], url)
339342

340343
if self._status[0].lower() == "overflow":
341-
warn("Result set limited by user- or server-supplied MAXREC parameter.",
342-
category=DALOverflowWarning)
344+
self._handle_overflow_warning(user_maxrec)
343345

344346
self._resultstable = self._findresultstable(votable)
345347
if not self._resultstable:
@@ -355,6 +357,54 @@ def __init__(self, votable, *, url=None, session=None):
355357

356358
self._infos = self._findinfos(votable)
357359

360+
def _handle_overflow_warning(self, user_maxrec=None):
361+
"""
362+
Handle overflow warning - can be overridden by subclasses.
363+
364+
Default implementation issues a generic overflow warning.
365+
Subclasses can override this to customize or suppress warnings.
366+
367+
Parameters
368+
----------
369+
user_maxrec : int, optional
370+
The maximum records requested by the user
371+
"""
372+
warn("Result set limited by user- or server-supplied MAXREC "
373+
"parameter.", category=DALOverflowWarning)
374+
375+
def check_overflow_warning(self, user_maxrec=None):
376+
"""
377+
Check for overflow warnings and issue them if appropriate.
378+
It will check if the results were truncated due to server limits
379+
and issue a warning if the number of records returned is less than
380+
the maximum records requested by the user.
381+
382+
If the results were truncated it distinguishes between expected
383+
truncation (where the user requested a maxrec)
384+
and unexpected truncation (where the user did not specify a maxrec).
385+
If the results were truncated it issues a warning.
386+
387+
Parameters
388+
----------
389+
user_maxrec : int, optional
390+
The maximum records explicitly requested by the user
391+
"""
392+
if self._status[0].lower() == "overflow":
393+
maxrec_to_check = user_maxrec if user_maxrec is not None else self._user_maxrec
394+
395+
if (maxrec_to_check is not None
396+
and len(self.resultstable.array) == maxrec_to_check):
397+
pass
398+
else:
399+
if maxrec_to_check is not None:
400+
warn(f"Results truncated at {len(self.resultstable.array)} records by service limits "
401+
f"(you requested maxrec={maxrec_to_check})",
402+
category=DALOverflowWarning)
403+
else:
404+
warn("Results truncated due to server limits. Consider "
405+
"setting a maxrec value.",
406+
category=DALOverflowWarning)
407+
358408
def _findresultstable(self, votable):
359409
# this can be overridden to specialize for a particular DAL protocol
360410
res = self._findresultsresource(votable)

pyvo/dal/tap.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,7 @@ def create(
654654
uploads=uploads, session=session, **keywords)
655655
response = tapquery.submit()
656656
job = cls(response.url, session=session)
657+
job._user_maxrec = maxrec
657658
return job
658659

659660
def __init__(self, url, *, session=None, delete=True):
@@ -672,6 +673,7 @@ def __init__(self, url, *, session=None, delete=True):
672673
self._url = url
673674
self._session = use_session(session)
674675
self._delete_on_exit = delete
676+
self._user_maxrec = None
675677
self._update()
676678

677679
def __enter__(self):
@@ -1047,7 +1049,9 @@ def fetch_result(self):
10471049

10481050
response.raw.read = partial(
10491051
response.raw.read, decode_content=True)
1050-
return TAPResults(votableparse(response.raw.read), url=self.result_uri, session=self._session)
1052+
result = TAPResults(votableparse(response.raw.read), url=self.result_uri, session=self._session)
1053+
result.check_overflow_warning(self._user_maxrec)
1054+
return result
10511055

10521056

10531057
class TAPQuery(DALQuery):
@@ -1103,6 +1107,8 @@ def __init__(
11031107
self["REQUEST"] = "doQuery"
11041108
self["LANG"] = language
11051109

1110+
self._user_maxrec = maxrec
1111+
11061112
if maxrec:
11071113
self["MAXREC"] = maxrec
11081114

@@ -1155,7 +1161,14 @@ def execute(self):
11551161
DALFormatError
11561162
for errors parsing the VOTable response
11571163
"""
1158-
return TAPResults(self.execute_votable(), url=self.queryurl, session=self._session)
1164+
result = TAPResults(
1165+
self.execute_votable(),
1166+
url=self.queryurl,
1167+
session=self._session
1168+
)
1169+
result.check_overflow_warning(self._user_maxrec)
1170+
1171+
return result
11591172

11601173
def submit(self, *, post=False):
11611174
"""
@@ -1265,6 +1278,15 @@ def getrecord(self, index):
12651278
"""
12661279
return TAPRecord(self, index, session=self._session)
12671280

1281+
def _handle_overflow_warning(self, user_maxrec=None):
1282+
"""
1283+
TAP-specific overflow warning handling.
1284+
1285+
For TAP results we suppress the default overflow warning during
1286+
initialization because TAPQuery.execute() will call check_overflow_warning()
1287+
"""
1288+
pass
1289+
12681290

12691291
class TAPRecord(SodaRecordMixin, DatalinkRecordMixin, Record):
12701292
pass

pyvo/dal/tests/test_query.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""
44
Tests for pyvo.dal.query
55
"""
6+
import warnings
67
from functools import partial
78

89
from contextlib import ExitStack
@@ -419,6 +420,70 @@ def test_columnaliases(self):
419420
assert dalresults.fieldname_with_ucd('baz') is None
420421
assert dalresults.fieldname_with_utype('foobaz') is None
421422

423+
def test_check_overflow_warning_no_maxrec(self):
424+
with pytest.warns(DALOverflowWarning):
425+
dalresults = DALResults.from_result_url('http://example.com/query/overflowstatus')
426+
427+
with pytest.warns(DALOverflowWarning, match="Results truncated due to server limits"):
428+
dalresults.check_overflow_warning()
429+
430+
def test_check_overflow_warning_exact_match(self):
431+
with pytest.warns(DALOverflowWarning):
432+
dalresults = DALResults.from_result_url('http://example.com/query/overflowstatus')
433+
434+
with warnings.catch_warnings():
435+
warnings.simplefilter("error")
436+
dalresults.check_overflow_warning(user_maxrec=3)
437+
438+
def test_check_overflow_warning_service_truncation(self):
439+
with pytest.warns(DALOverflowWarning):
440+
dalresults = DALResults.from_result_url('http://example.com/query/overflowstatus')
441+
442+
with pytest.warns(DALOverflowWarning, match="Results truncated at 3 records by service limits"):
443+
dalresults.check_overflow_warning(user_maxrec=1000)
444+
445+
def test_check_overflow_warning_uses_stored_maxrec(self):
446+
with pytest.warns(DALOverflowWarning):
447+
dalresults = DALResults.from_result_url('http://example.com/query/overflowstatus')
448+
449+
dalresults._user_maxrec = 1000
450+
451+
with pytest.warns(DALOverflowWarning, match="Results truncated at 3 records by service limits"):
452+
dalresults.check_overflow_warning()
453+
454+
def test_check_overflow_warning_parameter_overrides_stored(self):
455+
with pytest.warns(DALOverflowWarning):
456+
dalresults = DALResults.from_result_url('http://example.com/query/overflowstatus')
457+
458+
dalresults._user_maxrec = 1000
459+
460+
with warnings.catch_warnings():
461+
warnings.simplefilter("error")
462+
dalresults.check_overflow_warning(user_maxrec=3)
463+
464+
def test_check_overflow_warning_no_overflow_status(self):
465+
dalresults = DALResults.from_result_url('http://example.com/query/basic')
466+
467+
with warnings.catch_warnings():
468+
warnings.simplefilter("error")
469+
dalresults.check_overflow_warning(user_maxrec=1000)
470+
471+
def test_handle_overflow_warning_default_behavior(self):
472+
votable = votableparse(BytesIO(get_pkg_data_contents('data/query/overflowstatus.xml')))
473+
474+
with pytest.warns(DALOverflowWarning,
475+
match="Result set limited by user- or server-supplied MAXREC parameter."):
476+
_ = DALResults(votable, url='http://test.com')
477+
478+
def test_stored_user_maxrec_initialization(self):
479+
votable = votableparse(BytesIO(get_pkg_data_contents('data/query/basic.xml')))
480+
481+
result = DALResults(votable, url='http://test.com', user_maxrec=100)
482+
assert result._user_maxrec == 100
483+
484+
result2 = DALResults(votable, url='http://test.com')
485+
assert result2._user_maxrec is None
486+
422487

423488
@pytest.mark.filterwarnings('ignore::astropy.io.votable.exceptions.W03')
424489
@pytest.mark.filterwarnings('ignore::astropy.io.votable.exceptions.W06')

0 commit comments

Comments
 (0)