From aefb0aa70ff0921adc122da520ec7f4ff1b26ef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Brigitta=20Sip=C5=91cz?= Date: Tue, 4 Oct 2022 23:22:48 -0700 Subject: [PATCH 1/5] MAINT: minor fixes to setup.cfg and MANIFEST --- MANIFEST.in | 5 ++--- setup.cfg | 7 +++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 4eacd6613..25e007196 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,14 +1,13 @@ include README.rst include CHANGES.rst +include LICENSE.rst include setup.cfg include pyproject.toml -recursive-include packagename *.pyx *.c *.pxd +recursive-include pyvo *py recursive-include docs * recursive-include licenses * -recursive-include cextern * -recursive-include scripts * prune build prune docs/_build diff --git a/setup.cfg b/setup.cfg index 1092cf436..ce33359bf 100644 --- a/setup.cfg +++ b/setup.cfg @@ -18,13 +18,12 @@ filterwarnings = [flake8] max-line-length = 110 -max-doc-length = 79 -exclude = __init__.py, setup_package.py, conf.py, setup.py, version.py, conftest.py +max-doc-length = 110 +exclude = __init__.py, conf.py, setup.py, version.py, conftest.py [pycodestyle] max-line-length = 110 -max-doc-length = 79 -exclude = extern,sphinx,*parsetab.py +max-doc-length = 110 [metadata] name = pyvo From a80b821df809b06029e254f01d9eea6ea548d9c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Brigitta=20Sip=C5=91cz?= Date: Sun, 16 Oct 2022 16:41:27 +0200 Subject: [PATCH 2/5] TST: do run full flake8 checks, use only specific ignores --- setup.cfg | 3 +++ tox.ini | 30 ++---------------------------- 2 files changed, 5 insertions(+), 28 deletions(-) diff --git a/setup.cfg b/setup.cfg index ce33359bf..21d3c4a2c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -20,6 +20,9 @@ filterwarnings = max-line-length = 110 max-doc-length = 110 exclude = __init__.py, conf.py, setup.py, version.py, conftest.py +# W503 line break before operator +ignore = W503 + [pycodestyle] max-line-length = 110 diff --git a/tox.ini b/tox.ini index 3975c1017..2352db861 100644 --- a/tox.ini +++ b/tox.ini @@ -62,34 +62,8 @@ commands = sphinx-build -W -b html . _build/html [testenv:codestyle] -# We list the warnings/errors to check for here rather than in setup.cfg because -# we don't want these options to apply whenever anyone calls flake8 from the -# command-line or their code editor - in this case all warnings/errors should be -# checked for. The warnings/errors we check for here are: -# E101 - mix of tabs and spaces -# W191 - use of tabs -# W291 - trailing whitespace -# W292 - no newline at end of file -# W293 - trailing whitespace -# W391 - blank line at end of file -# E111 - 4 spaces per indentation level -# E112 - 4 spaces per indentation level -# E113 - 4 spaces per indentation level -# E301 - expected 1 blank line, found 0 -# E302 - expected 2 blank lines, found 0 -# E303 - too many blank lines (3) -# E304 - blank lines found after function decorator -# E305 - expected 2 blank lines after class or function definition -# E306 - expected 1 blank line before a nested definition -# E502 - the backslash is redundant between brackets -# E722 - do not use bare except -# E901 - SyntaxError or IndentationError -# E902 - IOError -# E999: SyntaxError -- failed to compile a file into an Abstract Syntax Tree -# F822: undefined name in __all__ -# F823: local variable name referenced before assignment skip_install = true -description = check code style, e.g. with flake8 +description = check code style deps = flake8 changedir = {toxinidir} -commands = flake8 pyvo --count --select=E101,W191,W291,W292,W293,W391,E111,E112,E113,E30,E502,E722,E901,E902,E999,F822,F823 +commands = flake8 pyvo --count From ffd4d1f5f588bb6d2f92f8e6719358a1c1ad15ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Brigitta=20Sip=C5=91cz?= Date: Sun, 16 Oct 2022 17:56:56 +0200 Subject: [PATCH 3/5] MAINT: run autopep8 and touch up manually --- pyvo/dal/adhoc.py | 14 +- pyvo/dal/dbapi2.py | 3 +- pyvo/dal/params.py | 16 +- pyvo/dal/query.py | 4 +- pyvo/dal/sia2.py | 32 +-- pyvo/dal/tests/test_adhoc.py | 14 +- pyvo/dal/tests/test_datalink.py | 10 +- pyvo/dal/tests/test_mimetype.py | 8 +- pyvo/dal/tests/test_params.py | 12 +- pyvo/dal/tests/test_sia2.py | 14 +- pyvo/dal/tests/test_sia2_remote.py | 6 +- pyvo/dal/tests/test_tap.py | 8 +- pyvo/io/vosi/exceptions.py | 4 +- pyvo/io/vosi/tests/test_capabilities.py | 15 +- pyvo/io/vosi/tests/test_tables.py | 8 +- pyvo/io/vosi/vodataservice.py | 21 +- pyvo/registry/regtap.py | 134 ++++++------ pyvo/registry/rtcons.py | 117 ++++++----- pyvo/registry/tests/commonfixtures.py | 4 +- pyvo/registry/tests/test_regtap.py | 192 ++++++++--------- pyvo/registry/tests/test_rtcons.py | 208 +++++++++---------- pyvo/samp.py | 6 +- pyvo/utils/formatting.py | 2 +- pyvo/utils/protofeature.py | 24 ++- pyvo/utils/prototype.py | 32 +-- pyvo/utils/tests/test_http.py | 1 - pyvo/utils/tests/test_prototype.py | 16 +- pyvo/utils/tests/test_vocabularies_remote.py | 4 +- pyvo/utils/vocabularies.py | 7 +- pyvo/utils/xml/elements.py | 7 +- pyvo/utils/xml/tests/test_elements.py | 11 +- 31 files changed, 491 insertions(+), 463 deletions(-) diff --git a/pyvo/dal/adhoc.py b/pyvo/dal/adhoc.py index 4079a13cc..6a2217c71 100644 --- a/pyvo/dal/adhoc.py +++ b/pyvo/dal/adhoc.py @@ -107,6 +107,7 @@ class AdhocServiceResultsMixin: """ Mixing for adhoc:service functionallity for results classes. """ + def __init__(self, votable, url=None, session=None): super().__init__(votable, url=url, session=session) @@ -231,6 +232,7 @@ class DatalinkRecordMixin: - ``getdataset()`` considers datalink. """ + def getdatalink(self): try: datalink = self._results.get_adhocservice_by_ivoid(DATALINK_IVOID) @@ -384,9 +386,9 @@ def from_resource(cls, rows, resource, session=None, **kwargs): elif np.isscalar(input_param.value) and input_param.value: query_params[name] = input_param.value elif ( - not np.isscalar(input_param.value) and - input_param.value.all() and - len(input_param.value) + not np.isscalar(input_param.value) + and input_param.value.all() + and len(input_param.value) ): query_params[name] = " ".join( str(_) for _ in input_param.value) @@ -562,9 +564,9 @@ def bysemantics(self, semantics, include_narrower=True): for term in core_terms: if term in voc["terms"]: additional_terms.extend(voc["terms"][term]["narrower"]) - core_terms = core_terms+additional_terms + core_terms = core_terms + additional_terms - semantics = set("#"+term for term in core_terms) | set(other_terms) + semantics = set("#" + term for term in core_terms) | set(other_terms) for record in self: if record.semantics in semantics: yield record @@ -636,6 +638,7 @@ class SodaRecordMixin: If used, it's result class must have `pyvo.dal.datalink.AdhocServiceResultsMixin` mixed in. """ + def _get_soda_resource(self): try: return self._results.get_adhocservice_by_ivoid(SODA_SYNC_IVOID) @@ -856,6 +859,7 @@ class SodaQuery(DatalinkQuery, AxisParamMixin): """ a class for preparing a query to a SODA Service. """ + def __init__( self, baseurl, circle=None, range=None, polygon=None, band=None, **kwargs): diff --git a/pyvo/dal/dbapi2.py b/pyvo/dal/dbapi2.py index 43e80181e..8282999e2 100644 --- a/pyvo/dal/dbapi2.py +++ b/pyvo/dal/dbapi2.py @@ -91,7 +91,8 @@ def __init__(self, *values): self._values = values @property - def id(self): return self._values[0] + def id(self): + return self._values[0] def __eq__(self, other): if not isinstance(other, TypeObject): diff --git a/pyvo/dal/params.py b/pyvo/dal/params.py index 8c085cdbd..dae3774f9 100644 --- a/pyvo/dal/params.py +++ b/pyvo/dal/params.py @@ -49,6 +49,7 @@ def unify_value(func): Decorator for serialize method to do unit conversion on input value. The decorator converts the input value to the unit in the input param. """ + def wrapper(self, value): if self._param.unit: value = Quantity(value) @@ -80,6 +81,7 @@ class Converter: Base class for all converters. Each subclass handles the conversion of a input value based on a specific xtype. """ + def __init__(self, param): self._param = param @@ -231,6 +233,7 @@ class AbstractDalQueryParam(MutableSet, metaclass=abc.ABCMeta): Duplicates in the set are determine based on the formatted DAL representation of the value. """ + def __init__(self, values=()): """ Parameters @@ -281,6 +284,7 @@ class StrQueryParam(AbstractDalQueryParam): Representation of a unitless, single-value parameter. The formatter is just a str() cast """ + def get_dal_format(self, val): return str(val) @@ -290,6 +294,7 @@ class PosQueryParam(AbstractDalQueryParam): Representation of a position parameter. Depending on the number of entries, the resulting DAL format is CIRCLE, RANGE or POLYGON. """ + def get_dal_format(self, val): """ formats the tuple values into a string to be sent to the service @@ -308,7 +313,7 @@ def get_dal_format(self, val): 'even 6 and above (POLYGON) accepted.'.format(val)) return '{} {}'.format(shape, ' '.join( [str(val.to(u.deg).value) if isinstance(val, Quantity) else - str((val*u.deg).value) for val in val])) + str((val * u.deg).value) for val in val])) def _validate_pos(self, pos): """ @@ -323,7 +328,7 @@ def _validate_pos(self, pos): radius = pos[2] * u.deg else: radius = pos[2] - if radius <= 0*u.deg or radius.to(u.deg) > 90*u.deg: + if radius <= 0 * u.deg or radius.to(u.deg) > 90 * u.deg: raise ValueError('Invalid circle radius: {}'.format(radius)) elif len(pos) == 4: ra_min = pos[0] if isinstance(pos[0], Quantity) else pos[0] * u.deg @@ -366,6 +371,7 @@ class IntervalQueryParam(AbstractDalQueryParam): """ Representation of an interval DAL parameter. """ + def __init__(self, unit=None, equivalencies=None): """ Parameters @@ -397,10 +403,10 @@ def get_dal_format(self, val): low, high)) if self._unit: if not isinstance(low, Quantity): - low = low*self._unit + low = low * self._unit low = low.to(self._unit, equivalencies=self._equivalencies).value if not isinstance(high, Quantity): - high = high*self._unit + high = high * self._unit high = high.to(self._unit, equivalencies=self._equivalencies).value if low > high: @@ -414,6 +420,7 @@ class TimeQueryParam(AbstractDalQueryParam): """ Representation of a timestamp parameter. """ + def get_dal_format(self, val): if isinstance(val, tuple): if len(val) == 1: @@ -442,6 +449,7 @@ class EnumQueryParam(AbstractDalQueryParam): """ Representation of an enum parameter """ + def __init__(self, allowed_values): """ Parameters diff --git a/pyvo/dal/query.py b/pyvo/dal/query.py index fb68b1dc3..6ce8a5f5c 100644 --- a/pyvo/dal/query.py +++ b/pyvo/dal/query.py @@ -715,8 +715,8 @@ def getdataurl(self): for fieldname in self._results.fieldnames: field = self._results.getdesc(fieldname) if (field.utype and "access.reference" in field.utype.lower()) or ( - field.ucd and "meta.dataset" in field.ucd and - "meta.ref.url" in field.ucd + field.ucd and "meta.dataset" in field.ucd + and "meta.ref.url" in field.ucd ): out = self[fieldname] if isinstance(out, bytes): diff --git a/pyvo/dal/sia2.py b/pyvo/dal/sia2.py index 9dc9f5e6e..a0a884c7e 100644 --- a/pyvo/dal/sia2.py +++ b/pyvo/dal/sia2.py @@ -169,9 +169,9 @@ def __init__(self, baseurl, session=None): if cap.standardid.lower() == SIA2_STANDARD_ID.lower(): for interface in cap.interfaces: if interface.accessurls and \ - not (len(interface.securitymethods) == 1 and - interface.securitymethods[0].standardid == - 'ivo://ivoa.net/sso#BasicAA'): + not (len(interface.securitymethods) == 1 + and interface.securitymethods[0].standardid + == 'ivo://ivoa.net/sso#BasicAA'): self.query_ep = interface.accessurls[0].content break @@ -712,7 +712,7 @@ def access_estsize(self): required. Provision of dataset size estimates is important whenever it is possible that datasets can be very large. """ - return self.get('access_estsize')*1000*u.byte + return self.get('access_estsize') * 1000 * u.byte # SPATIAL CHARACTERISATION @property @@ -720,14 +720,14 @@ def s_ra(self): """ ICRS Right Ascension of the center of the observation """ - return self.get('s_ra')*u.deg + return self.get('s_ra') * u.deg @property def s_dec(self): """ CRS Declination of the center of the observation """ - return self.get('s_dec')*u.deg + return self.get('s_dec') * u.deg @property def s_fov(self): @@ -744,7 +744,7 @@ def s_fov(self): data product. The spatial coverage of a data product can be more precisely specified using the region attribute. """ - return self.get('s_fov')*u.deg + return self.get('s_fov') * u.deg @property def s_region(self): @@ -773,7 +773,7 @@ def s_resolution(self): characterisation may be necessary to fully specify the spatial characteristics of the data. """ - return self.get('s_resolution')*u.arcsec + return self.get('s_resolution') * u.arcsec @property def s_xel1(self): @@ -809,7 +809,7 @@ def s_resolution_min(self): Resolution min value on spatial axis (FHWM of PSF) """ rmin = self.get('s_resolution_min', default=None) - return rmin if not rmin else rmin*u.arcsec + return rmin if not rmin else rmin * u.arcsec @property def s_resolution_max(self): @@ -898,14 +898,14 @@ def t_exptime(self): often adjusted to achieve similar signal to noise ratio for different targets. """ - return self.get('t_exptime')*u.second + return self.get('t_exptime') * u.second @property def t_resolution(self): """ Estimated or average value of the temporal resolution. """ - return self.get('t_resolution')*u.second + return self.get('t_resolution') * u.second @property def t_calib_status(self): @@ -922,7 +922,7 @@ def t_stat_error(self): Time coord statistical error on the time measurements in seconds """ ter = self.get('t_stat_error', default=None) - return ter if not ter else ter*u.second + return ter if not ter else ter * u.second # SPECTRAL CHARACTERISATION @property @@ -961,14 +961,14 @@ def em_min(self): """ Minimum of the spectral interval covered by the observation """ - return self.get('em_min')*u.meter + return self.get('em_min') * u.meter @property def em_max(self): """ Maximum of the spectral interval covered by the observation """ - return self.get('em_max')*u.meter + return self.get('em_max') * u.meter @property def em_res_power(self): @@ -1001,7 +1001,7 @@ def em_resolution(self): power is preferable due to the LSF variation along the spectral axis. """ if 'em_resolution' in self.keys(): - return self.get('em_resolution')*u.meter + return self.get('em_resolution') * u.meter return None @property @@ -1010,7 +1010,7 @@ def em_stat_error(self): Spectral coord statistical error (accuracy along the spectral axis) """ if 'em_stat_error' in self.keys(): - return self.get('em_stat_error')*u.meter + return self.get('em_stat_error') * u.meter return None # OBSERVABLE AXIS diff --git a/pyvo/dal/tests/test_adhoc.py b/pyvo/dal/tests/test_adhoc.py index 7696d021f..26a3f86c7 100644 --- a/pyvo/dal/tests/test_adhoc.py +++ b/pyvo/dal/tests/test_adhoc.py @@ -15,7 +15,7 @@ def test_pos(): class TestClass(dict, AxisParamMixin): pass test_obj = TestClass() - test_obj.pos.add((1, 2, 3)*u.deg) + test_obj.pos.add((1, 2, 3) * u.deg) assert len(test_obj._pos) == 1 assert test_obj['POS'] == ['CIRCLE 1.0 2.0 3.0'] @@ -41,9 +41,9 @@ class TestClass(dict, AxisParamMixin): 'POLYGON 1.0 2.0 3.0 4.0 5.0 6.0'] # test borders - test_obj.pos.discard((1, 2, 3)*u.deg) + test_obj.pos.discard((1, 2, 3) * u.deg) test_obj.pos.discard((1, 2, 3, 4, 5, 6)) - assert(len(test_obj._pos) == 0) + assert (len(test_obj._pos) == 0) test_obj.pos.add((0, 90, 90)) assert len(test_obj._pos) == 1 assert test_obj['POS'] == ['CIRCLE 0.0 90.0 90.0'] @@ -92,12 +92,12 @@ class TestClass(dict, AxisParamMixin): test_obj.band.add(33) assert 33 in test_obj.band assert test_obj['BAND'] == ['33.0 33.0'] - test_obj.band.add((50*u.meter, 500)) + test_obj.band.add((50 * u.meter, 500)) assert 33 in test_obj.band - assert (50*u.meter, 500) in test_obj.band + assert (50 * u.meter, 500) in test_obj.band assert test_obj['BAND'] == ['33.0 33.0', '50.0 500.0'] test_obj.band.discard(33) - assert (50*u.meter, 500) in test_obj.band + assert (50 * u.meter, 500) in test_obj.band assert test_obj['BAND'] == ['50.0 500.0'] test_obj.band.pop() assert not test_obj.band @@ -198,7 +198,7 @@ def test_soda_query(): assert not hasattr(test_obj, '_polygon') assert not hasattr(test_obj, '_range') - test_obj.range = (8, 9, 3, 4)*u.deg + test_obj.range = (8, 9, 3, 4) * u.deg assert test_obj['POS'] == 'RANGE 8.0 9.0 3.0 4.0' assert test_obj._range is not None assert not hasattr(test_obj, '_polygon') diff --git a/pyvo/dal/tests/test_datalink.py b/pyvo/dal/tests/test_datalink.py index 0d9b2d94e..b7b869d3c 100644 --- a/pyvo/dal/tests/test_datalink.py +++ b/pyvo/dal/tests/test_datalink.py @@ -167,7 +167,7 @@ def test_access_with_expansion(self): def test_access_without_expansion(self): datalink = DatalinkResults.from_result_url('http://example.com/proc') res = [r["access_url"] for r in datalink.bysemantics( - ["#this", "#preview"], include_narrower=False)] + ["#this", "#preview"], include_narrower=False)] assert len(res) == 2 assert res[0].endswith("eq010000ms/20100927.comb_avg.0001.fits.fz") assert res[1].endswith("http://dc.zah.uni-heidelberg.de/wider.dat") @@ -183,10 +183,10 @@ def test_all_mixed(self): datalink = DatalinkResults.from_result_url('http://example.com/proc') res = [r["access_url"] for r in datalink.bysemantics([ - "urn:example:rdf/dlext#oracle", - 'http://www.ivoa.net/rdf/datalink/core#preview', - '#this', - 'non-existing-term'])] + "urn:example:rdf/dlext#oracle", + 'http://www.ivoa.net/rdf/datalink/core#preview', + '#this', + 'non-existing-term'])] assert len(res) == 4 assert res[0].endswith("eq010000ms/20100927.comb_avg.0001.fits.fz") assert res[1].endswith("comb_avg.0001.fits.fz?preview=True") diff --git a/pyvo/dal/tests/test_mimetype.py b/pyvo/dal/tests/test_mimetype.py index 0ce26ccd1..345d6b2a3 100644 --- a/pyvo/dal/tests/test_mimetype.py +++ b/pyvo/dal/tests/test_mimetype.py @@ -18,7 +18,7 @@ get_pkg_data_contents, package=__package__, encoding='binary') try: - from PIL import Image + from PIL import Image # noqa: F401 HAS_PILLOW = True except ImportError: HAS_PILLOW = False @@ -44,14 +44,14 @@ def callback(request, context): @pytest.mark.skipif('not HAS_PILLOW') def test_mime_object_maker(): - assert 'Text content' == mime_object_maker(mime_url+'mime-text', + assert 'Text content' == mime_object_maker(mime_url + 'mime-text', 'text/csv') - img = mime_object_maker(mime_url+'image', 'image/jpeg') + img = mime_object_maker(mime_url + 'image', 'image/jpeg') assert img assert 'JPEG' == img.format - fits = mime_object_maker(mime_url+'fits', 'application/fits') + fits = mime_object_maker(mime_url + 'fits', 'application/fits') assert 2 == len(fits) # error cases diff --git a/pyvo/dal/tests/test_params.py b/pyvo/dal/tests/test_params.py index 1abba9bd4..96853f3e7 100644 --- a/pyvo/dal/tests/test_params.py +++ b/pyvo/dal/tests/test_params.py @@ -179,7 +179,7 @@ def test_units(): datalink = DatalinkResults.from_result_url('http://example.com/proc_units') proc = datalink[0] - proc.process(band=(6000*u.Angstrom, 80000*u.Angstrom)) + proc.process(band=(6000 * u.Angstrom, 80000 * u.Angstrom)) @pytest.mark.usefixtures('proc_inf') @@ -216,12 +216,12 @@ def test_dal_format(): iqp = IntervalQueryParam(unit=u.m, equivalencies=u.spectral()) assert '1.0 1.0' == iqp.get_dal_format(1) assert '1.0 2.0' == iqp.get_dal_format((1, 2)) - assert '1.0 2.0' == iqp.get_dal_format((100*u.cm, 200*u.cm)) - assert '1.0 2.0' == iqp.get_dal_format((100, 200)*u.cm) - assert '0.14989622900000002 1.0' == iqp.get_dal_format((100*u.cm, 2*u.GHz)) - assert '14.9896229 29.9792458' == iqp.get_dal_format((0.01, 0.02)*u.GHz) + assert '1.0 2.0' == iqp.get_dal_format((100 * u.cm, 200 * u.cm)) + assert '1.0 2.0' == iqp.get_dal_format((100, 200) * u.cm) + assert '0.14989622900000002 1.0' == iqp.get_dal_format((100 * u.cm, 2 * u.GHz)) + assert '14.9896229 29.9792458' == iqp.get_dal_format((0.01, 0.02) * u.GHz) # Quantity intervals are corrected in terms of min and max .. - assert '1.0 2.0' == iqp.get_dal_format((2, 1)*u.m) + assert '1.0 2.0' == iqp.get_dal_format((2, 1) * u.m) # But unitless intervals are not with pytest.raises(ValueError): iqp.get_dal_format((2, 1)) diff --git a/pyvo/dal/tests/test_sia2.py b/pyvo/dal/tests/test_sia2.py index 498b628a7..279797231 100644 --- a/pyvo/dal/tests/test_sia2.py +++ b/pyvo/dal/tests/test_sia2.py @@ -50,7 +50,7 @@ def _test_result(record): @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") def test_search(): results = search('https://example.com/sia', - pos=(33.3*u.deg, 4.2*u.deg, 0.0166*u.deg)) + pos=(33.3 * u.deg, 4.2 * u.deg, 0.0166 * u.deg)) result = results[0] _test_result(result) @@ -106,10 +106,10 @@ def test_search(self): positions = [ (2, 4, 0.0166 * u.deg), (12, 12.5, 34, 36), - (12.0*u.deg, 34.0*u.deg, - 14.0*u.deg, 35.0*u.deg, - 14.0*u.deg, 36.0*u.deg, - 12.0*u.deg, 35.0*u.deg)] + (12.0 * u.deg, 34.0 * u.deg, + 14.0 * u.deg, 35.0 * u.deg, + 14.0 * u.deg, 36.0 * u.deg, + 12.0 * u.deg, 35.0 * u.deg)] # each position for pos in positions: @@ -129,10 +129,10 @@ def test_query(self): query = SIAQuery('someurl') query.field_of_view.add((10, 20)) assert query['FOV'] == ['10.0 20.0'] - query.field_of_view.add((1*u.rad, 60)) + query.field_of_view.add((1 * u.rad, 60)) assert query['FOV'] == ['10.0 20.0', '57.29577951308232 60.0'] - query.spatial_resolution.add((1*u.arcsec, 2)) + query.spatial_resolution.add((1 * u.arcsec, 2)) assert query['SPATRES'] == ['1.0 2.0'] query.spectral_resolving_power.add((3, 5)) diff --git a/pyvo/dal/tests/test_sia2_remote.py b/pyvo/dal/tests/test_sia2_remote.py index a99d1c3db..f278004d6 100644 --- a/pyvo/dal/tests/test_sia2_remote.py +++ b/pyvo/dal/tests/test_sia2_remote.py @@ -161,7 +161,7 @@ def test_spatial_res(self): results = search(CADC_SIA_URL, spatial_resolution=(1, 2), maxrec=5) assert len(results) == 5 for rr in results: - assert 1*u.arcsec <= rr.s_resolution <= 2*u.arcsec + assert 1 * u.arcsec <= rr.s_resolution <= 2 * u.arcsec @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_spec_resp(self): @@ -176,7 +176,7 @@ def test_exptime(self): maxrec=5) assert len(results) == 5 for rr in results: - assert 1*u.second <= rr.t_exptime <= 2*u.second + assert 1 * u.second <= rr.t_exptime <= 2 * u.second @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_timeres(self): @@ -238,4 +238,4 @@ def test_res_format(self): assert len(results) == 5 for rr in results: assert rr.access_format == \ - 'application/x-votable+xml;content=datalink' + 'application/x-votable+xml;content=datalink' diff --git a/pyvo/dal/tests/test_tap.py b/pyvo/dal/tests/test_tap.py index 402a9f14d..89f9e106a 100644 --- a/pyvo/dal/tests/test_tap.py +++ b/pyvo/dal/tests/test_tap.py @@ -63,7 +63,7 @@ def match_request(request): 'Wrong file format' elif b'VOTable' in data: assert request.headers['Content-Type'] == \ - 'application/x-votable+xml', 'Wrong file format' + 'application/x-votable+xml', 'Wrong file format' else: assert False, 'BUG' return True @@ -92,10 +92,10 @@ def match_request(request): 'Wrong file format' elif b'\t' in data: assert request.headers['Content-Type'] == \ - 'text/tab-separated-values', 'Wrong file format' + 'text/tab-separated-values', 'Wrong file format' elif b'FITSTable' in data: assert request.headers['Content-Type'] == \ - 'application/fits', 'Wrong file format' + 'application/fits', 'Wrong file format' else: assert False, 'BUG' return True @@ -688,7 +688,7 @@ def match_request_text(request): headers={'Location': 'https://example.com/tap/uws'}) rm.get('https://example.com/tap/uws', [{'content': get_index_job("PENDING")}, - {'content':get_index_job("COMPLETED")}]) + {'content': get_index_job("COMPLETED")}]) rm.post('https://example.com/tap/uws/phase', status_code=200) # finally the call service.create_index(table_name='abc', column_name='col1', diff --git a/pyvo/io/vosi/exceptions.py b/pyvo/io/vosi/exceptions.py index 297197dee..dbe7a0eeb 100644 --- a/pyvo/io/vosi/exceptions.py +++ b/pyvo/io/vosi/exceptions.py @@ -188,8 +188,8 @@ class W15(VOSIWarning, XMLWarning): """ message_template = ( - 'pyvo.dal is designed for VOSITables version 1.0, and 1.1, but ' + - 'this file is {}') + 'pyvo.dal is designed for VOSITables version 1.0, and 1.1, but ' + + 'this file is {}') default_args = ('x',) diff --git a/pyvo/io/vosi/tests/test_capabilities.py b/pyvo/io/vosi/tests/test_capabilities.py index 2534766c0..e6eab8744 100644 --- a/pyvo/io/vosi/tests/test_capabilities.py +++ b/pyvo/io/vosi/tests/test_capabilities.py @@ -4,7 +4,6 @@ Tests for pyvo.io.vosi """ import io -import logging from operator import eq as equals import pytest @@ -20,7 +19,7 @@ @pytest.fixture() def parsed_caps(): return vosi.parse_capabilities(get_pkg_data_filename( - "data/capabilities.xml")) + "data/capabilities.xml")) @pytest.mark.usefixtures("parsed_caps") @@ -165,20 +164,20 @@ def test_mirrors_parsed(self, parsed_caps): def test_mirrors_have_titles(self, parsed_caps): assert [m.title for m in parsed_caps[3].interfaces[0].mirrorurls - ] == ["https version", "Paris mirror"] + ] == ["https version", "Paris mirror"] def test_mirrors_have_urls(self, parsed_caps): assert [m.content for m in parsed_caps[3].interfaces[0].mirrorurls - ] == ['https://example.org/tap', 'https://paris.example.org/tap'] + ] == ['https://example.org/tap', 'https://paris.example.org/tap'] def test_testquerystring_parsed(self, parsed_caps): assert (parsed_caps[3].interfaces[0].testquerystring.content - == 'QUERY=SELECT%20*%20FROM%20tap_schema.tables&LANG=ADQL') + == 'QUERY=SELECT%20*%20FROM%20tap_schema.tables&LANG=ADQL') @pytest.fixture() def cap_with_free_prefix(recwarn): - caps = vosi.parse_capabilities(io.BytesIO(b""" + caps = vosi.parse_capabilities(io.BytesIO(b""" """)) - return recwarn, caps + return recwarn, caps # this is a test for when people ignore the canonical prefixes for @@ -206,7 +205,7 @@ def cap_with_free_prefix(recwarn): class TestFreePrefixes: def test_parses_without_warning(self, cap_with_free_prefix): warnings, _ = cap_with_free_prefix - assert len(warnings)==0 + assert len(warnings) == 0 def test_parses_as_tapregext(self, cap_with_free_prefix): _, cap = cap_with_free_prefix diff --git a/pyvo/io/vosi/tests/test_tables.py b/pyvo/io/vosi/tests/test_tables.py index e8f4fd56e..8b1c5df29 100644 --- a/pyvo/io/vosi/tests/test_tables.py +++ b/pyvo/io/vosi/tests/test_tables.py @@ -376,8 +376,8 @@ def test_no_table_description(self): """Test handling of describing tables with no description """ tableset = vosi.parse_tables( - get_pkg_data_filename( - "data/tables/no_table_description.xml")) + get_pkg_data_filename( + "data/tables/no_table_description.xml")) nodesc_table = tableset.get_first_table() assert nodesc_table.description is None @@ -390,8 +390,8 @@ def test_single_table_description(self): """Test describing a table with a single description """ tableset = vosi.parse_tables( - get_pkg_data_filename( - "data/tables/single_table_description.xml")) + get_pkg_data_filename( + "data/tables/single_table_description.xml")) onedesc_table = tableset.get_first_table() describe_string = 'A test table with a single description' assert describe_string in onedesc_table.description diff --git a/pyvo/io/vosi/vodataservice.py b/pyvo/io/vosi/vodataservice.py index 34e98996d..a1c08d684 100644 --- a/pyvo/io/vosi/vodataservice.py +++ b/pyvo/io/vosi/vodataservice.py @@ -80,6 +80,7 @@ class TableSet(Element, HomogeneousList): The set of tables hosted by a resource. """ + def __init__( self, config=None, pos=None, _name='tableset', version='1.1', **kwargs ): @@ -140,6 +141,7 @@ class TableSchema(Element, HomogeneousList): A detailed description of a logically-related set of tables. """ + def __init__(self, config=None, pos=None, _name='schema', **kwargs): HomogeneousList.__init__(self, Table) Element.__init__(self, config, pos, _name, **kwargs) @@ -251,9 +253,10 @@ class ParamHTTP(vr.Interface): Note that the URL for help with this service can be put into the Service/ReferenceURL element. """ + def __init__(self, config=None, pos=None, _name='', **kwargs): super().__init__( - config=config, pos=pos, _name=_name, **kwargs) + config=config, pos=pos, _name=_name, **kwargs) self._querytypes = HomogeneousList(str) self._resulttype = None @@ -290,6 +293,7 @@ class Table(Element): Table element as described in http://www.ivoa.net/xml/VODataService/v1.1 """ + def __init__( self, config=None, pos=None, _name='table', version='1.1', **kwargs ): @@ -455,9 +459,10 @@ class BaseParam(Element): normally employ a sub-class of this type (e.g. Param), rather than this type directly. """ + def __init__(self, config=None, pos=None, _name='', **kwargs): super().__init__( - config=config, pos=pos, _name=_name, **kwargs) + config=config, pos=pos, _name=_name, **kwargs) self._name = None self._description = None @@ -561,7 +566,7 @@ def from_field(cls, field): def __init__(self, config=None, pos=None, _name='', std=None, **kwargs): super().__init__( - config=config, pos=pos, _name=_name, **kwargs) + config=config, pos=pos, _name=_name, **kwargs) self._datatype = None self._flags = HomogeneousList(str) @@ -627,6 +632,7 @@ class InputParam(BaseParam): A description of a service or function parameter having a fixed data type. """ + def __init__( self, config=None, pos=None, _name='', use="optional", std="1", **kwargs): @@ -693,6 +699,7 @@ class DataType(ContentMixin, ElementWithXSIType): This XML type is used as a parent for defining data types with a restricted set of names. """ + def __init__( self, config=None, pos=None, _name='dataType', arraysize=None, delim=None, extendedType=None, extendedSchema=None, @@ -799,6 +806,7 @@ class SimpleDataType(DataType): This set is intended for describing simple input parameters to a service or function. """ + def _content_check(self, value): if value is not None: valid_values = { @@ -826,6 +834,7 @@ class VOTableType(TableDataType): VOTableType element as described in http://www.ivoa.net/xml/VODataService/v1.1 """ + def _content_check(self, value): if value is not None: valid_values = ( @@ -844,6 +853,7 @@ class TAPDataType(TableDataType): an abstract parent for the specific data types supported by the Table Access Protocol. """ + def __init__( self, config=None, pos=None, _name='dataType', size=None, **kwargs ): @@ -880,6 +890,7 @@ class TAPType(TAPDataType): a data type supported explicitly by the Table Access Protocol (v1.0). """ + def _content_check(self, value): if value is not None: valid_values = ( @@ -895,9 +906,10 @@ class FKColumn(Element): FKColumn element as described in http://www.ivoa.net/xml/VODataService/v1.1 """ + def __init__(self, config=None, pos=None, _name='fkColumn', **kwargs): super().__init__( - config=config, pos=pos, _name=_name, **kwargs) + config=config, pos=pos, _name=_name, **kwargs) self._fromcolumn = None self._targetcolumn = None @@ -942,6 +954,7 @@ class ForeignKey(Element): ForeignKey element as described in http://www.ivoa.net/xml/VODataService/v1.1 """ + def __init__(self, config=None, pos=None, _name='foreignKey', **kwargs): Element.__init__(self, config, pos, _name, **kwargs) diff --git a/pyvo/registry/regtap.py b/pyvo/registry/regtap.py index 93de16bc0..fbb677610 100644 --- a/pyvo/registry/regtap.py +++ b/pyvo/registry/regtap.py @@ -30,10 +30,10 @@ __all__ = ["search", "get_RegTAP_query", - "RegistryResource", "RegistryResults", "ivoid2service"] + "RegistryResource", "RegistryResults", "ivoid2service"] REGISTRY_BASEURL = os.environ.get("IVOA_REGISTRY", "http://reg.g-vo.org/tap" - ).rstrip("/") + ).rstrip("/") # ADQL only has string_agg, where we need string arrays. We fake arrays @@ -65,7 +65,7 @@ def expand_stdid(s): """ if s is None or "://" in s: return s - return "ivo://ivoa.net/std/"+s + return "ivo://ivoa.net/std/" + s @functools.lru_cache(1) @@ -80,15 +80,15 @@ def get_RegTAP_service(): return tap.TAPService(REGISTRY_BASEURL) -def get_RegTAP_query(*constraints:rtcons.Constraint, - includeaux=False, **kwargs): +def get_RegTAP_query(*constraints: rtcons.Constraint, + includeaux=False, **kwargs): """returns SQL for a RegTAP query for constraints and keywords. This function's parameters are as for search; this is basically a wrapper for rtcons.build_regtap_query maintaining the legacy keyword-based interface. """ - constraints = list(constraints)+rtcons.keywords_to_constraints(kwargs) + constraints = list(constraints) + rtcons.keywords_to_constraints(kwargs) # maintain legacy includeaux by locating any Servicetype constraints # and replacing them with ones that includes auxiliaries. @@ -100,10 +100,8 @@ def get_RegTAP_query(*constraints:rtcons.Constraint, return rtcons.build_regtap_query(constraints) -def search(*constraints:rtcons.Constraint, - includeaux:bool=False, - maxrec:int=None, - **kwargs): +def search(*constraints: rtcons.Constraint, includeaux: bool = False, maxrec: int = None, **kwargs): + """ execute a simple query to the RegTAP registry. @@ -189,6 +187,7 @@ class RegistryResults(dalq.DALResults): matters, you need to use full ivoids as index. """ + def getrecord(self, index): """ return all the attributes of a resource record with the given index @@ -209,11 +208,11 @@ def get_summary(self): like to inspect the matches in, perhaps, notebooks. """ return table.Table([ - list(range(len(self))), - [r.short_name for r in self], - [r.res_title for r in self], - [r.res_description for r in self], - [", ".join(sorted(r.access_modes())) for r in self]], + list(range(len(self))), + [r.short_name for r in self], + [r.res_title for r in self], + [r.res_description for r in self], + [", ".join(sorted(r.access_modes())) for r in self]], names=("index", "short_name", "title", "description", "interfaces"), descriptions=( "Index to access the resource within self", @@ -225,12 +224,12 @@ def get_summary(self): @functools.lru_cache(maxsize=None) def _get_ivo_index(self): return dict((r.ivoid, index) - for index, r in enumerate(self)) + for index, r in enumerate(self)) @functools.lru_cache(maxsize=None) def _get_short_name_index(self): return dict((r.short_name, index) - for index, r in enumerate(self)) + for index, r in enumerate(self)) def __getitem__(self, item): """ @@ -256,6 +255,7 @@ class _BrowserService: """A pseudo-service class just opening a web browser for browser-based services. """ + def __init__(self, access_url): self.access_url = access_url @@ -281,11 +281,11 @@ class Interface: as in regtap (e.g., lowercased for the standardIDs). """ service_for_standardid = { - "ivo://ivoa.net/std/conesearch": scs.SCSService, - "ivo://ivoa.net/std/sia": sia.SIAService, - "ivo://ivoa.net/std/ssa": ssa.SSAService, - "ivo://ivoa.net/std/sla": sla.SLAService, - "ivo://ivoa.net/std/tap": tap.TAPService} + "ivo://ivoa.net/std/conesearch": scs.SCSService, + "ivo://ivoa.net/std/sia": sia.SIAService, + "ivo://ivoa.net/std/ssa": ssa.SSAService, + "ivo://ivoa.net/std/sla": sla.SLAService, + "ivo://ivoa.net/std/tap": tap.TAPService} def __init__(self, access_url, standard_id, intf_type, intf_role): self.access_url = access_url @@ -293,25 +293,25 @@ def __init__(self, access_url, standard_id, intf_type, intf_role): self.is_vosi = standard_id.startswith("ivo://ivoa.net/std/vosi") self.type = intf_type or None self.role = intf_role or None - self.is_standard = self.role=="std" + self.is_standard = self.role == "std" def __repr__(self): return (f"Interface({self.access_url!r}, {self.standard_id!r}," - f" {self.type!r}, {self.role!r})") + f" {self.type!r}, {self.role!r})") def to_service(self): - if self.type=="vr:webbrowser": + if self.type == "vr:webbrowser": return _BrowserService(self.access_url) if self.standard_id is None or not self.is_standard: raise ValueError("This is not a standard interface. PyVO" - " cannot speak to it.") + " cannot speak to it.") service_class = self.service_for_standardid.get( self.standard_id.split("#")[0]) if service_class is None: raise ValueError("PyVO has no support for interfaces with" - f" standard id {self.standard_id}.") + f" standard id {self.standard_id}.") return service_class(self.access_url) @@ -334,7 +334,7 @@ def supports(self, standard_id): """ if not self.standard_id: return False - return self.standard_id.split("#")[0]==standard_id.split("#")[0] + return self.standard_id.split("#")[0] == standard_id.split("#")[0] class RegistryResource(dalq.Record): @@ -379,26 +379,26 @@ class RegistryResource(dalq.Record): (f"\n ivo_string_agg(COALESCE(intf_type, ''), '{TOKEN_SEP}')", "intf_types"), (f"\n ivo_string_agg(COALESCE(intf_role, ''), '{TOKEN_SEP}')", - "intf_roles"),] + "intf_roles"), ] def __init__(self, results, index, session=None): dalq.Record.__init__(self, results, index, session) self._mapping["access_urls" - ] = self._parse_pseudo_array(self._mapping["access_urls"]) + ] = self._parse_pseudo_array(self._mapping["access_urls"]) self._mapping["standard_ids" - ] = self._parse_pseudo_array(self._mapping["standard_ids"]) + ] = self._parse_pseudo_array(self._mapping["standard_ids"]) self._mapping["intf_types" - ] = self._parse_pseudo_array(self._mapping["intf_types"]) + ] = self._parse_pseudo_array(self._mapping["intf_types"]) self._mapping["intf_roles" - ] = self._parse_pseudo_array(self._mapping["intf_roles"]) + ] = self._parse_pseudo_array(self._mapping["intf_roles"]) self.interfaces = [Interface(*props) - for props in zip( - self["access_urls"], - self["standard_ids"], - self["intf_types"], - self["intf_roles"])] + for props in zip( + self["access_urls"], + self["standard_ids"], + self["intf_types"], + self["intf_roles"])] @staticmethod def _parse_pseudo_array(literal): @@ -530,11 +530,11 @@ def access_url(self): # multiple times in here. Be cool about that situation: access_urls = list(sorted(set(self["access_urls"]))) - if len(access_urls)==0: + if len(access_urls) == 0: raise dalq.DALQueryError( f"The resource {self.ivoid} has no queriable interfaces.") - elif len(access_urls)>1: + elif len(access_urls) > 1: warnings.warn(AstropyDeprecationWarning( f"The resource {self.ivoid} has multiple capabilities. " " You should explicitly pick one using get_service. " @@ -548,7 +548,7 @@ def standard_id(self): the IVOA standard identifier """ standard_ids = list(set(self["standard_ids"])) - if len(standard_ids)==1: + if len(standard_ids) == 1: return standard_ids[0] else: raise dalq.DALQueryError( @@ -568,14 +568,14 @@ def access_modes(self): This will ignore VOSI (infrastructure) services. """ return set(shorten_stdid(intf.standard_id) or "web" - for intf in self.interfaces - if (intf.standard_id or intf.type=="vr:webbrowser") - and not intf.is_vosi) + for intf in self.interfaces + if (intf.standard_id or intf.type == "vr:webbrowser") + and not intf.is_vosi) def get_interface(self, - service_type:str, - lax:bool=True, - std_only:bool=False): + service_type: str, + lax: bool = True, + std_only: bool = False): """returns a regtap.Interface class for service_type. Parameters @@ -591,11 +591,11 @@ def get_interface(self, want when you want to construct pyVO service objects later. This parameter is ignored for the "web" service type. """ - if service_type=="web": + if service_type == "web": # this works very much differently in the Registry # than do the proper services candidates = [intf for intf in self.interfaces - if intf.type=="vr:webbrowser"] + if intf.type == "vr:webbrowser"] else: service_type = expand_stdid( @@ -603,23 +603,23 @@ def get_interface(self, service_type, service_type)) candidates = [intf for intf in self.interfaces - if ((not std_only) or intf.is_standard) - and not intf.is_vosi - and ((not service_type) or intf.supports(service_type))] + if ((not std_only) or intf.is_standard) + and not intf.is_vosi + and ((not service_type) or intf.supports(service_type))] if not candidates: raise ValueError( "No matching interface.") - if len(candidates)>1 and not lax: + if len(candidates) > 1 and not lax: raise ValueError("Multiple matching interfaces found." - " Perhaps pass in service_type or use a Servicetype" - " constrain in the registry.search? Or use lax=True?") + " Perhaps pass in service_type or use a Servicetype" + " constrain in the registry.search? Or use lax=True?") return candidates[0] def get_service(self, - service_type:str=None, - lax:bool=True): + service_type: str = None, + lax: bool = True): """ return an appropriate DALService subclass for this resource that can be used to search the resource using service_type. @@ -664,7 +664,7 @@ def get_service(self, opening a web browser on the access URL. """ return self.get_interface(service_type, lax, std_only=True - ).to_service() + ).to_service() @property def service(self): @@ -736,9 +736,9 @@ def describe(self, verbose=False, width=78, file=None): print("Short Name: " + self.short_name, file=file) print("IVOA Identifier: " + self.ivoid, file=file) print("Access modes: " + ", ".join(sorted(self.access_modes())), - file=file) + file=file) - if len(self._mapping["access_urls"])==1: + if len(self._mapping["access_urls"]) == 1: print("Base URL: " + self.access_url, file=file) else: print("Multi-capabilty service -- use get_service()", file=file) @@ -774,7 +774,7 @@ def get_contact(self): WHERE base_role='contact' AND ivoid={}""".format( - rtcons.make_sql_literal(self.ivoid))) + rtcons.make_sql_literal(self.ivoid))) contacts = [] for row in res: @@ -845,11 +845,11 @@ def get_tables(self, table_limit=20): """SELECT table_name, table_description, table_index, table_title FROM rr.res_table WHERE ivoid={}""".format( - rtcons.make_sql_literal(self.ivoid))) - if len(tables)>table_limit: + rtcons.make_sql_literal(self.ivoid))) + if len(tables) > table_limit: raise dalq.DALQueryError(f"Resource {self.ivoid} reports" - f" {len(tables)} tables. Pass a higher table_limit" - " to see them all.") + f" {len(tables)} tables. Pass a higher table_limit" + " to see them all.") res = {} for table_row in tables: @@ -881,10 +881,10 @@ def ivoid2service(ivoid, servicetype=None): if servicetype is not None: constraints.append(rtcons.Servicetype(servicetype)) resources = search(*constraints) - if len(resources)==0: + if len(resources) == 0: if servicetype: raise dalq.DALQueryError(f"No resource {ivoid} with" - f" {servicetype} capability.") + f" {servicetype} capability.") else: raise dalq.DALQueryError(f"No resource {ivoid}") diff --git a/pyvo/registry/rtcons.py b/pyvo/registry/rtcons.py index 1c1e678ea..2d86e6a50 100644 --- a/pyvo/registry/rtcons.py +++ b/pyvo/registry/rtcons.py @@ -17,34 +17,33 @@ from astropy.coordinates import SkyCoord import numpy -from ..dal import tap from ..dal import query as dalq from ..utils import vocabularies from .import regtap __all__ = ["Freetext", "Author", "Servicetype", "Waveband", - "Datamodel", "Ivoid", "UCD", "Spatial", "Spectral", "Temporal", - "build_regtap_query"] + "Datamodel", "Ivoid", "UCD", "Spatial", "Spectral", "Temporal", + "build_regtap_query"] # a mapping of service type shorthands to the ivoids of the # corresponding standards. This is mostly to keep legacy APIs. # In the future, preferably rely on shorten_stdid and expand_stdid # from regtap. -SERVICE_TYPE_MAP = dict((k, "ivo://ivoa.net/std/"+v) - for k, v in [ - ("image", "sia"), - ("sia", "sia"), - ("spectrum", "ssa"), - ("ssap", "ssa"), - ("ssa", "ssa"), - ("scs", "conesearch"), - ("conesearch", "conesearch"), - ("line", "slap"), - ("slap", "slap"), - ("table", "tap"), - ("tap", "tap"), +SERVICE_TYPE_MAP = dict((k, "ivo://ivoa.net/std/" + v) + for k, v in [ + ("image", "sia"), + ("sia", "sia"), + ("spectrum", "ssa"), + ("ssap", "ssa"), + ("ssa", "ssa"), + ("scs", "conesearch"), + ("conesearch", "conesearch"), + ("line", "slap"), + ("slap", "slap"), + ("table", "tap"), + ("tap", "tap"), ]) @@ -96,7 +95,7 @@ def make_sql_literal(value): else: raise ValueError("Cannot format {} as a SQL literal" - .format(repr(value))) + .format(repr(value))) def format_function_call(func_name, args): @@ -166,7 +165,7 @@ def get_search_condition(self): """ if self._condition is None: raise NotImplementedError("{} is an abstract Constraint" - .format(self.__class__.__name__)) + .format(self.__class__.__name__)) return self._condition.format(**self._get_sql_literals()) @@ -187,7 +186,7 @@ class Freetext(Constraint): """ _keyword = "keywords" - def __init__(self, *words:str): + def __init__(self, *words: str): """ Parameters @@ -205,18 +204,18 @@ def __init__(self, *words:str): # faster than direct ORs. base_queries = [ "SELECT ivoid FROM rr.resource WHERE" - " 1=ivo_hasword(res_description, {{{parname}}})", + " 1=ivo_hasword(res_description, {{{parname}}})", "SELECT ivoid FROM rr.resource WHERE" - " 1=ivo_hasword(res_title, {{{parname}}})", + " 1=ivo_hasword(res_title, {{{parname}}})", "SELECT ivoid FROM rr.res_subject WHERE" - " res_subject ILIKE {{{parpatname}}}"] + " res_subject ILIKE {{{parpatname}}}"] self._fillers, subqueries = {}, [] for index, word in enumerate(words): parname = "fulltext{}".format(index) parpatname = "fulltextpar{}".format(index) self._fillers[parname] = word - self._fillers[parpatname] = '%'+word+'%' + self._fillers[parpatname] = '%' + word + '%' for q in base_queries: subqueries.append(q.format(**locals())) @@ -233,7 +232,7 @@ class Author(Constraint): """ _keyword = "author" - def __init__(self, name:str): + def __init__(self, name: str): """ Parameters @@ -301,8 +300,8 @@ def __init__(self, *stds): self.stdids.add(std) else: raise dalq.DALQueryError("Service type {} is neither a full" - " standard URI nor one of the bespoke identifiers" - " {}".format(std, ", ".join(SERVICE_TYPE_MAP))) + " standard URI nor one of the bespoke identifiers" + " {}".format(std, ", ".join(SERVICE_TYPE_MAP))) def get_search_condition(self): # we sort the stdids to make it easy for tests (and it's @@ -317,7 +316,7 @@ def include_auxiliary_services(self): This is a convenience to maintain registry.search's signature. """ return Servicetype(*(self.stdids | set( - std+'#aux' for std in self.stdids))) + std + '#aux' for std in self.stdids))) class Waveband(Constraint): @@ -348,7 +347,7 @@ def __init__(self, *bands): """ if self.__class__._legal_terms is None: self.__class__._legal_terms = {w.lower() for w in - vocabularies.get_vocabulary("messenger")["terms"]} + vocabularies.get_vocabulary("messenger")["terms"]} bands = [band.lower() for band in bands] for band in bands: @@ -398,7 +397,7 @@ def __init__(self, dmname): dmname = dmname.lower() if dmname not in self._known_dms: raise dalq.DALQueryError("Unknown data model id {}. Known are: {}." - .format(dmname, ", ".join(sorted(self._known_dms)))) + .format(dmname, ", ".join(sorted(self._known_dms)))) self._condition = getattr(self, f"_make_{dmname}_constraint")() def _make_obscore_constraint(self): @@ -407,7 +406,7 @@ def _make_obscore_constraint(self): self._extra_tables = ["rr.res_detail"] obscore_pat = 'ivo://ivoa.net/std/obscore%' return ("detail_xpath = '/capability/dataModel/@ivo-id'" - f" AND 1 = ivo_nocasematch(detail_value, '{obscore_pat}')") + f" AND 1 = ivo_nocasematch(detail_value, '{obscore_pat}')") def _make_epntap_constraint(self): self._extra_tables = ["rr.res_table"] @@ -415,14 +414,14 @@ def _make_epntap_constraint(self): # any new identifiers (utypes case-fold). return " OR ".join( f"table_utype LIKE {pat}'" for pat in - ['ivo://vopdc.obspm/std/epncore#schema-2.%', - 'ivo://ivoa.net/std/epntap#table-2.%']) + ['ivo://vopdc.obspm/std/epncore#schema-2.%', + 'ivo://ivoa.net/std/epntap#table-2.%']) def _make_regtap_constraint(self): self._extra_tables = ["rr.res_detail"] regtap_pat = 'ivo://ivoa.net/std/RegTAP#1.%' return ("detail_xpath = '/capability/dataModel/@ivo-id'" - f" AND 1 = ivo_nocasematch(detail_value, '{regtap_pat}')") + f" AND 1 = ivo_nocasematch(detail_value, '{regtap_pat}')") class Ivoid(Constraint): @@ -468,7 +467,7 @@ def __init__(self, *patterns): self._condition = " OR ".join( f"ucd LIKE {{ucd{i}}}" for i in range(len(patterns))) self._fillers = dict((f"ucd{index}", pattern) - for index, pattern in enumerate(patterns)) + for index, pattern in enumerate(patterns)) class Spatial(Constraint): @@ -546,20 +545,20 @@ def tomoc(s): elif isinstance(geom_spec, SkyCoord): geom = tomoc(format_function_call("POINT", - (geom_spec.ra.value, geom_spec.dec.value))) + (geom_spec.ra.value, geom_spec.dec.value))) - elif len(geom_spec)==2: + elif len(geom_spec) == 2: if isinstance(geom_spec[0], SkyCoord): geom = tomoc(format_function_call("CIRCLE", - [geom_spec[0].ra.value, geom_spec[0].dec.value, - geom_spec[1]])) + [geom_spec[0].ra.value, geom_spec[0].dec.value, + geom_spec[1]])) else: geom = tomoc(format_function_call("POINT", geom_spec)) - elif len(geom_spec)==3: + elif len(geom_spec) == 3: geom = tomoc(format_function_call("CIRCLE", geom_spec)) - elif len(geom_spec)%2==0: + elif len(geom_spec) % 2 == 0: geom = tomoc(format_function_call("POLYGON", geom_spec)) else: @@ -621,7 +620,7 @@ def __init__(self, spec): "spec_lo": self._to_joule(spec[0]), "spec_hi": self._to_joule(spec[1])} self._condition = ("1 = ivo_interval_overlaps(" - "spectral_start, spectral_end, {spec_lo}, {spec_hi})") + "spectral_start, spectral_end, {spec_lo}, {spec_hi})") else: self._fillers = { @@ -640,19 +639,19 @@ def _to_joule(self, quant): # is it an energy? return quant.to(units.Joule).value except units.UnitConversionError: - pass # try next + pass # try next try: # is it a wavelength? - return (constants.h*constants.c/quant.to(units.m)).value + return (constants.h * constants.c / quant.to(units.m)).value except units.UnitConversionError: - pass # try next + pass # try next try: # is it a frequency? - return (constants.h*quant.to(units.Hz)).value + return (constants.h * quant.to(units.Hz)).value except units.UnitConversionError: - pass # fall through to give up + pass # fall through to give up raise ValueError(f"Cannot make a spectral quantity out of {quant}") @@ -705,7 +704,7 @@ def __init__(self, times): "time_lo": self._to_mjd(times[0]), "time_hi": self._to_mjd(times[1])} self._condition = ("1 = ivo_interval_overlaps(" - "time_start, time_end, {time_lo}, {time_hi})") + "time_start, time_end, {time_lo}, {time_hi})") else: self._fillers = { @@ -725,7 +724,7 @@ def _to_mjd(self, quant): val = quant.to_value('mjd') if not isinstance(val, numpy.number): raise ValueError("RegTAP time constraints must be made from" - " single time instants.") + " single time instants.") return val @@ -758,11 +757,11 @@ def build_regtap_query(constraints): if isinstance(constraint, str): constraint = Freetext(constraint) - serialized.append("("+constraint.get_search_condition()+")") + serialized.append("(" + constraint.get_search_condition() + ")") extra_tables |= set(constraint._extra_tables) joined_tables = ["rr.resource", "rr.capability", "rr.interface" - ]+list(extra_tables) + ] + list(extra_tables) # see comment in regtap.RegistryResource for the following # oddity @@ -775,13 +774,13 @@ def build_regtap_query(constraints): select_clause.append("{} AS {}".format(*col_desc)) fragments = ["SELECT", - ", ".join(select_clause), - "FROM", - "\nNATURAL LEFT OUTER JOIN ".join(joined_tables), - "WHERE", - "\n AND ".join(serialized), - "GROUP BY", - ", ".join(plain_columns)] + ", ".join(select_clause), + "FROM", + "\nNATURAL LEFT OUTER JOIN ".join(joined_tables), + "WHERE", + "\n AND ".join(serialized), + "GROUP BY", + ", ".join(plain_columns)] return "\n".join(fragments) @@ -807,8 +806,8 @@ def keywords_to_constraints(keywords): for keyword, value in keywords.items(): if keyword not in _KEYWORD_TO_CONSTRAINT: raise TypeError(f"{keyword} is not a valid registry" - " constraint keyword. Use one of {}.".format( - ", ".join(sorted(_KEYWORD_TO_CONSTRAINT)))) + " constraint keyword. Use one of {}.".format( + ", ".join(sorted(_KEYWORD_TO_CONSTRAINT)))) constraint_class = _KEYWORD_TO_CONSTRAINT[keyword] if (isinstance(value, (tuple, list)) diff --git a/pyvo/registry/tests/commonfixtures.py b/pyvo/registry/tests/commonfixtures.py index 15dfcef70..532bebbcb 100644 --- a/pyvo/registry/tests/commonfixtures.py +++ b/pyvo/registry/tests/commonfixtures.py @@ -3,8 +3,6 @@ Common fixtures for pyVO registry tests """ -import os - import pytest from astropy.utils.data import ( @@ -12,7 +10,7 @@ import_file_to_cache) # We need to populate the vocabulary cache with our test data; # we cannot use requests_mock here because a.u.data uses urllib. -from astropy.utils.data import _get_download_cache_loc, _url_to_dirname +from astropy.utils.data import _get_download_cache_loc, _url_to_dirname # noqa: F401 @pytest.fixture() diff --git a/pyvo/registry/tests/test_regtap.py b/pyvo/registry/tests/test_regtap.py index 59abd25f2..98c07a463 100644 --- a/pyvo/registry/tests/test_regtap.py +++ b/pyvo/registry/tests/test_regtap.py @@ -34,7 +34,7 @@ def callback(request, context): return get_pkg_data_contents('data/capabilities.xml') with mocker.register_uri( - 'GET', REGISTRY_BASEURL+'/capabilities', content=callback + 'GET', REGISTRY_BASEURL + '/capabilities', content=callback ) as matcher: yield matcher @@ -46,15 +46,14 @@ def callback(request, context): # with open("data/regtap.xml", "wb") as f: # f.write(requests.get(regtap.REGISTRY_BASEURL+"/sync", { # "LANG": "ADQL", -# "QUERY": regtap.get_RegTAP_query( -# keywords="pulsar", ucd=["pos.distance"])}).content) +# "QUERY": regtap.get_RegTAP_query(keywords="pulsar", ucd=["pos.distance"])}).content) @pytest.fixture() def regtap_pulsar_distance_response(mocker): with mocker.register_uri( - 'POST', REGISTRY_BASEURL+'/sync', - content=get_pkg_data_contents('data/regtap.xml')) as matcher: + 'POST', REGISTRY_BASEURL + '/sync', + content=get_pkg_data_contents('data/regtap.xml')) as matcher: yield matcher @@ -75,7 +74,7 @@ def keywordstest_callback(request, context): return get_pkg_data_contents('data/regtap.xml') with mocker.register_uri( - 'POST', REGISTRY_BASEURL+'/sync', + 'POST', REGISTRY_BASEURL + '/sync', content=keywordstest_callback ) as matcher: yield matcher @@ -94,7 +93,7 @@ def keywordstest_callback(request, context): return get_pkg_data_contents('data/regtap.xml') with mocker.register_uri( - 'POST', REGISTRY_BASEURL+'/sync', + 'POST', REGISTRY_BASEURL + '/sync', content=keywordstest_callback ) as matcher: yield matcher @@ -115,7 +114,7 @@ def servicetypetest_callback(request, context): return get_pkg_data_contents('data/regtap.xml') with mocker.register_uri( - 'POST', REGISTRY_BASEURL+'/sync', + 'POST', REGISTRY_BASEURL + '/sync', content=servicetypetest_callback ) as matcher: yield matcher @@ -132,7 +131,7 @@ def wavebandtest_callback(request, content): return get_pkg_data_contents('data/regtap.xml') with mocker.register_uri( - 'POST', REGISTRY_BASEURL+'/sync', + 'POST', REGISTRY_BASEURL + '/sync', content=wavebandtest_callback ) as matcher: yield matcher @@ -154,7 +153,7 @@ def datamodeltest_callback(request, content): return get_pkg_data_contents('data/regtap.xml') with mocker.register_uri( - 'POST', REGISTRY_BASEURL+'/sync', + 'POST', REGISTRY_BASEURL + '/sync', content=datamodeltest_callback ) as matcher: yield matcher @@ -171,7 +170,7 @@ def auxtest_callback(request, context): return get_pkg_data_contents('data/regtap.xml') with mocker.register_uri( - 'POST', REGISTRY_BASEURL+'/sync', + 'POST', REGISTRY_BASEURL + '/sync', content=auxtest_callback, ) as matcher: yield matcher @@ -179,17 +178,17 @@ def auxtest_callback(request, context): @pytest.fixture() def multi_interface_fixture(mocker): -# to update this, run -# import requests -# from pyvo.registry import regtap -# -# with open("data/multi-interface.xml", "wb") as f: -# f.write(requests.get(regtap.REGISTRY_BASEURL+"/sync", { -# "LANG": "ADQL", -# "QUERY": regtap.get_RegTAP_query( -# ivoid="ivo://org.gavo.dc/flashheros/q/ssa")}).content) + # to update this, run + # import requests + # from pyvo.registry import regtap + # + # with open("data/multi-interface.xml", "wb") as f: + # f.write(requests.get(regtap.REGISTRY_BASEURL+"/sync", { + # "LANG": "ADQL", + # "QUERY": regtap.get_RegTAP_query( + # ivoid="ivo://org.gavo.dc/flashheros/q/ssa")}).content) with mocker.register_uri( - 'POST', REGISTRY_BASEURL+'/sync', + 'POST', REGISTRY_BASEURL + '/sync', content=get_pkg_data_contents('data/multi-interface.xml') ) as matcher: yield matcher @@ -198,7 +197,7 @@ def multi_interface_fixture(mocker): @pytest.fixture() def flash_service(multi_interface_fixture): return regtap.search( - ivoid="ivo://org.gavo.dc/flashheros/q/ssa")[0] + ivoid="ivo://org.gavo.dc/flashheros/q/ssa")[0] class TestInterfaceClass: @@ -208,22 +207,22 @@ def test_basic(self): assert intf.standard_id is None assert intf.type is None assert intf.role is None - assert intf.is_standard == False + assert intf.is_standard is False assert not intf.is_vosi def test_repr(self): intf = regtap.Interface("http://example.org", "ivo://gavo/std/a", - "vs:paramhttp", "std") + "vs:paramhttp", "std") assert (repr(intf) == "Interface('http://example.org'," - " 'ivo://gavo/std/a', 'vs:paramhttp', 'std')") + " 'ivo://gavo/std/a', 'vs:paramhttp', 'std')") intf = regtap.Interface("http://example.org", "ivo://gavo/std/a", - None, None) + None, None) assert repr(intf) == ("Interface('http://example.org'," - " 'ivo://gavo/std/a', None, None)") + " 'ivo://gavo/std/a', None, None)") def test_unknown_standard(self): intf = regtap.Interface("http://example.org", "ivo://gavo/std/a", - "vs:paramhttp", "std") + "vs:paramhttp", "std") assert intf.is_standard with pytest.raises(ValueError) as excinfo: intf.to_service() @@ -234,14 +233,14 @@ def test_unknown_standard(self): def test_known_standard(self): intf = regtap.Interface("http://example.org", - "ivo://ivoa.net/std/tap#aux", "vs:paramhttp", "std") + "ivo://ivoa.net/std/tap#aux", "vs:paramhttp", "std") assert isinstance(intf.to_service(), tap.TAPService) assert not intf.is_vosi def test_secondary_interface(self): intf = regtap.Interface("http://example.org", - "ivo://ivoa.net/std/tap#aux", - "vs:webbrowser", "web") + "ivo://ivoa.net/std/tap#aux", + "vs:webbrowser", "web") with pytest.raises(ValueError) as excinfo: intf.to_service() @@ -251,8 +250,8 @@ def test_secondary_interface(self): def test_VOSI(self): intf = regtap.Interface("http://example.org", - "ivo://ivoa.net/std/vosi#capabilities", - "vs:ParamHTTP", "std") + "ivo://ivoa.net/std/vosi#capabilities", + "vs:ParamHTTP", "std") assert intf.is_vosi @@ -304,26 +303,26 @@ def test_bad_servicetype_aux(): def test_spatial(): assert (rtcons.keywords_to_constraints({ "spatial": (23, -40)})[0].get_search_condition() - == "1 = CONTAINS(MOC(6, POINT(23, -40)), coverage)") + == "1 = CONTAINS(MOC(6, POINT(23, -40)), coverage)") def test_spectral(): assert (rtcons.keywords_to_constraints({ - "spectral": (1e-17, 2e-17)})[0].get_search_condition() == - "1 = ivo_interval_overlaps(spectral_start, spectral_end, 1e-17, 2e-17)") + "spectral": (1e-17, 2e-17)})[0].get_search_condition() + == "1 = ivo_interval_overlaps(spectral_start, spectral_end, 1e-17, 2e-17)") def test_to_table(multi_interface_fixture, capabilities): t = regtap.search( ivoid="ivo://org.gavo.dc/flashheros/q/ssa").get_summary() assert (set(t.columns.keys()) - == {'index', 'short_name', 'title', 'description', 'interfaces'}) + == {'index', 'short_name', 'title', 'description', 'interfaces'}) assert t["index"][0] == 0 assert t["title"][0] == 'Flash/Heros SSAP' assert (t["description"][0][:40] - == 'Spectra from the Flash and Heros Echelle') + == 'Spectra from the Flash and Heros Echelle') assert (t["interfaces"][0] - == 'datalink#links-1.0, soda#sync-1.0, ssa, tap#aux, web') + == 'datalink#links-1.0, soda#sync-1.0, ssa, tap#aux, web') @pytest.fixture() @@ -333,18 +332,18 @@ def rt_pulsar_distance(regtap_pulsar_distance_response, capabilities): def test_record_fields(rt_pulsar_distance): rec = rt_pulsar_distance["VII/156"] - assert rec.ivoid=="ivo://cds.vizier/vii/156" - assert rec.res_type=="vs:catalogservice" - assert rec.short_name=="VII/156" - assert rec.res_title=="Catalog of 558 Pulsars" - assert rec.content_levels==['research'] - assert rec.res_description[:20]=="The catalogue is an up-to-date"[:20] - assert rec.reference_url=="http://cdsarc.unistra.fr/cgi-bin/cat/VII/156" - assert rec.creators==['Taylor J.H.', ' Manchester R.N.', ' Lyne A.G.'] - assert rec.content_types==['catalog'] - assert rec.source_format=="bibcode" - assert rec.source_value=="1993ApJS...88..529T" - assert rec.waveband==['radio'] + assert rec.ivoid == "ivo://cds.vizier/vii/156" + assert rec.res_type == "vs:catalogservice" + assert rec.short_name == "VII/156" + assert rec.res_title == "Catalog of 558 Pulsars" + assert rec.content_levels == ['research'] + assert rec.res_description[:20] == "The catalogue is an up-to-date"[:20] + assert rec.reference_url == "http://cdsarc.unistra.fr/cgi-bin/cat/VII/156" + assert rec.creators == ['Taylor J.H.', ' Manchester R.N.', ' Lyne A.G.'] + assert rec.content_types == ['catalog'] + assert rec.source_format == "bibcode" + assert rec.source_value == "1993ApJS...88..529T" + assert rec.waveband == ['radio'] # access URL, standard_id and friends exercised in TestInterfaceSelection @@ -352,21 +351,21 @@ class TestResultIndexing: def test_get_with_index(self, rt_pulsar_distance): # this is expecte to break when the fixture is updated assert (rt_pulsar_distance[0].res_title - == 'Pulsar Timing for Fermi Gamma-ray Space Telescope') + == 'Pulsar Timing for Fermi Gamma-ray Space Telescope') def test_get_with_short_name(self, rt_pulsar_distance): assert (rt_pulsar_distance["ATNF"].res_title - == 'ATNF Pulsar Catalog') + == 'ATNF Pulsar Catalog') def test_get_with_ivoid(self, rt_pulsar_distance): assert (rt_pulsar_distance["ivo://nasa.heasarc/atnfpulsar" - ].res_title == 'ATNF Pulsar Catalog') + ].res_title == 'ATNF Pulsar Catalog') def test_out_of_range(self, rt_pulsar_distance): with pytest.raises(IndexError) as excinfo: rt_pulsar_distance[40320] assert (str(excinfo.value) - == "index 40320 is out of bounds for axis 0 with size 23") + == "index 40320 is out of bounds for axis 0 with size 23") def test_bad_key(self, rt_pulsar_distance): with pytest.raises(KeyError) as excinfo: @@ -377,16 +376,17 @@ def test_not_indexable(self, rt_pulsar_distance): with pytest.raises(IndexError) as excinfo: rt_pulsar_distance[None] assert (str(excinfo.value) - == "No resource matching None") + == "No resource matching None") @pytest.mark.usefixtures('multi_interface_fixture', 'capabilities', - 'flash_service') + 'flash_service') class TestInterfaceSelection: """ tests for the selection and generation of services within RegistryResource. """ + def test_exactly_one_result(self): results = regtap.search( ivoid="ivo://org.gavo.dc/flashheros/q/ssa") @@ -402,33 +402,33 @@ def test_standard_id_multi(self, flash_service): _ = flash_service.standard_id assert str(excinfo.value) == ("This resource supports several" - " standards (datalink#links-1.0, soda#sync-1.0, ssa," - " tap#aux, web). Use get_service or restrict your query" - " using Servicetype.") + " standards (datalink#links-1.0, soda#sync-1.0, ssa," + " tap#aux, web). Use get_service or restrict your query" + " using Servicetype.") def test_get_web_interface(self, flash_service): svc = flash_service.get_service("web") assert isinstance(svc, - regtap._BrowserService) + regtap._BrowserService) assert (svc.access_url - == "http://dc.zah.uni-heidelberg.de/flashheros/q/web/form") + == "http://dc.zah.uni-heidelberg.de/flashheros/q/web/form") def test_get_aux_interface(self, flash_service): svc = flash_service.get_service("tap#aux") assert (svc._baseurl - == "http://dc.zah.uni-heidelberg.de/tap") + == "http://dc.zah.uni-heidelberg.de/tap") def test_get_aux_as_main(self, flash_service): assert (flash_service.get_service("tap")._baseurl - == "http://dc.zah.uni-heidelberg.de/tap") + == "http://dc.zah.uni-heidelberg.de/tap") def test_get__main_from_aux(self, flash_service): assert (flash_service.get_service("tap")._baseurl - == "http://dc.zah.uni-heidelberg.de/tap") + == "http://dc.zah.uni-heidelberg.de/tap") def test_get_by_alias(self, flash_service): assert (flash_service.get_service("spectrum")._baseurl - == "http://dc.zah.uni-heidelberg.de/fhssa?") + == "http://dc.zah.uni-heidelberg.de/fhssa?") def test_get_unsupported_standard(self, flash_service): with pytest.raises(ValueError) as excinfo: @@ -463,10 +463,11 @@ class _FakeResult: regtap.TOKEN_SEP -- this is how they ought to come in from RegTAP services. """ + def __init__(self, d): self.fieldnames = list(d.keys()) vals = [regtap.TOKEN_SEP.join(v) if isinstance(v, list) else v - for v in d.values()] + for v in d.values()] class _: class array: @@ -494,12 +495,13 @@ class TestInterfaceRejection: """tests for various artificial corner cases where interface selection should fail (or just not fail). """ + def test_nonunique(self): rsc = _makeRegistryRecord({ "access_urls": ["http://a", "http://b"], - "standard_ids": ["ivo://ivoa.net/std/tap"]*2, - "intf_types": ["vs:paramhttp"]*2, - "intf_roles": ["std"]*2, + "standard_ids": ["ivo://ivoa.net/std/tap"] * 2, + "intf_types": ["vs:paramhttp"] * 2, + "intf_roles": ["std"] * 2, }) with pytest.raises(ValueError) as excinfo: rsc.get_service("tap", lax=False) @@ -512,24 +514,24 @@ def test_nonunique(self): def test_nonunique_lax(self): rsc = _makeRegistryRecord({ "access_urls": ["http://a", "http://b"], - "standard_ids": ["ivo://ivoa.net/std/tap"]*2, - "intf_types": ["vs:paramhttp"]*2, - "intf_roles": ["std"]*2, + "standard_ids": ["ivo://ivoa.net/std/tap"] * 2, + "intf_types": ["vs:paramhttp"] * 2, + "intf_roles": ["std"] * 2, }) assert (rsc.get_service("tap")._baseurl - == "http://a") + == "http://a") def test_nonstd_ignored(self): rsc = _makeRegistryRecord({ "access_urls": ["http://a", "http://b"], - "standard_ids": ["ivo://ivoa.net/std/tap"]*2, - "intf_types": ["vs:paramhttp"]*2, + "standard_ids": ["ivo://ivoa.net/std/tap"] * 2, + "intf_types": ["vs:paramhttp"] * 2, "intf_roles": ["std", ""] }) assert (rsc.get_service("tap", lax=False)._baseurl - == "http://a") + == "http://a") def test_select_single_matching_service(self): rsc = _makeRegistryRecord({ @@ -564,12 +566,12 @@ def test_get_contact(): rsc = _makeRegistryRecord( {"ivoid": "ivo://org.gavo.dc/flashheros/q/ssa"}) assert (rsc.get_contact() - == "GAVO Data Center Team (++49 6221 54 1837)" + == "GAVO Data Center Team (++49 6221 54 1837)" " ") @pytest.mark.usefixtures('multi_interface_fixture', 'capabilities', - 'flash_service') + 'flash_service') class TestExtraResourceMethods: """ tests for methods of RegistryResource containing some non-trivial @@ -593,7 +595,7 @@ def test_describe_multi(self, flash_service): assert "Flash/Heros SSAP" in output assert ("Access modes: datalink#links-1.0, soda#sync-1.0," - " ssa, tap#aux, web" in output) + " ssa, tap#aux, web" in output) assert "Multi-capabilty service" in output assert "More info: http://dc.zah" in output @@ -609,7 +611,7 @@ def test_no_access_url(self): rsc.access_url assert str(excinfo.value) == ("The resource" - " ivo://pyvo/test_regtap.py has no queriable interfaces.") + " ivo://pyvo/test_regtap.py has no queriable interfaces.") def test_unique_access_url(self): rsc = _makeRegistryRecord({ @@ -623,13 +625,13 @@ def test_unique_access_url(self): def test_ambiguous_access_url_warns(self, recwarn): rsc = _makeRegistryRecord({ "access_urls": ["http://a", "http://b"], - "standard_ids": ["ivo://ivoa.net/std/tap"]*2, - "intf_types": ["vs:paramhttp"]*2, - "intf_roles": ["std"]*2, + "standard_ids": ["ivo://ivoa.net/std/tap"] * 2, + "intf_types": ["vs:paramhttp"] * 2, + "intf_roles": ["std"] * 2, }) assert rsc.access_url == "http://a" assert [str(w.message)[:50] for w in recwarn - ] == ['The resource ivo://pyvo/test_regtap.py has multipl'] + ] == ['The resource ivo://pyvo/test_regtap.py has multipl'] # TODO: While I suppose the contact test should keep requiring network, @@ -653,31 +655,31 @@ def test_get_tables_limit_enforced(self): rsc.get_tables() assert re.match(r"Resource ivo://org.gavo.dc/tap reports \d+ tables." - " Pass a higher table_limit to see them all.", str(excinfo.value)) + " Pass a higher table_limit to see them all.", str(excinfo.value)) @pytest.mark.remote_data def test_get_tables_names(self, flash_tables): assert (list(sorted(flash_tables.keys())) - == ["flashheros.data", "ivoa.obscore"]) + == ["flashheros.data", "ivoa.obscore"]) @pytest.mark.remote_data def test_get_tables_table_instance(self, flash_tables): assert (flash_tables["ivoa.obscore"].name - == "ivoa.obscore") + == "ivoa.obscore") assert (flash_tables["ivoa.obscore"].description - == "This data collection is queryable in GAVO Data" - " Center's obscore table.") + == "This data collection is queryable in GAVO Data" + " Center's obscore table.") assert (flash_tables["flashheros.data"].title - == "Flash/Heros SSA table") + == "Flash/Heros SSA table") assert (flash_tables["flashheros.data"].origin.ivoid - == "ivo://org.gavo.dc/flashheros/q/ssa") + == "ivo://org.gavo.dc/flashheros/q/ssa") @pytest.mark.remote_data def test_get_tables_column_meta(self, flash_tables): def getflashcol(name): for col in flash_tables["flashheros.data"].columns: - if name==col.name: + if name == col.name: return col raise KeyError(name) @@ -693,7 +695,7 @@ def getflashcol(name): assert getflashcol("ssa_specend").unit == "m" assert (getflashcol("ssa_specend").utype - == "ssa:char.spectralaxis.coverage.bounds.stop") + == "ssa:char.spectralaxis.coverage.bounds.stop") assert (getflashcol("ssa_fluxcalib").description - == "Type of flux calibration") + == "Type of flux calibration") diff --git a/pyvo/registry/tests/test_rtcons.py b/pyvo/registry/tests/test_rtcons.py index fea4f33c6..cb5e12947 100644 --- a/pyvo/registry/tests/test_rtcons.py +++ b/pyvo/registry/tests/test_rtcons.py @@ -16,7 +16,7 @@ from pyvo.registry import rtcons from pyvo.dal import query as dalq -from .commonfixtures import messenger_vocabulary +from .commonfixtures import messenger_vocabulary # noqa: F401 class TestAbstractConstraint: @@ -36,7 +36,7 @@ class _WithFillers(rtcons.Constraint): "anInt": 210, "aFloat": 5e7, "numpyStuff": numpy.float64(23.7), - "timestamp": datetime.datetime(2021, 6, 30, 9, 1),} + "timestamp": datetime.datetime(2021, 6, 30, 9, 1), } return _WithFillers()._get_sql_literals() @@ -54,7 +54,7 @@ def test_float(self, literals): assert literals["aFloat"] == "50000000.0" def test_numpy(self, literals): - assert float(literals["numpyStuff"])-23.7<1e-10 + assert float(literals["numpyStuff"]) - 23.7 < 1e-10 def test_timestamp(self, literals): assert literals["timestamp"] == "'2021-06-30T09:01:00'" @@ -67,40 +67,44 @@ def test_odd_type_rejected(self): class TestFreetextConstraint: def test_basic(self): - assert (rtcons.Freetext("star").get_search_condition() - == "ivoid IN (SELECT ivoid FROM rr.resource WHERE 1=ivo_hasword(res_description, 'star') UNION SELECT ivoid FROM rr.resource WHERE 1=ivo_hasword(res_title, 'star') UNION SELECT ivoid FROM rr.res_subject WHERE res_subject ILIKE '%star%')") + assert rtcons.Freetext("star").get_search_condition() == ( + "ivoid IN (SELECT ivoid FROM rr.resource WHERE 1=ivo_hasword(res_description, 'star') " + "UNION SELECT ivoid FROM rr.resource WHERE 1=ivo_hasword(res_title, 'star') " + "UNION SELECT ivoid FROM rr.res_subject WHERE res_subject ILIKE '%star%')") def test_interesting_literal(self): - assert (rtcons.Freetext("α Cen's planets").get_search_condition() - == "ivoid IN (SELECT ivoid FROM rr.resource WHERE 1=ivo_hasword(res_description, 'α Cen''s planets') UNION SELECT ivoid FROM rr.resource WHERE 1=ivo_hasword(res_title, 'α Cen''s planets') UNION SELECT ivoid FROM rr.res_subject WHERE res_subject ILIKE '%α Cen''s planets%')") + assert rtcons.Freetext("α Cen's planets").get_search_condition() == ( + "ivoid IN (SELECT ivoid FROM rr.resource WHERE 1=ivo_hasword(res_description, 'α Cen''s planets')" + " UNION SELECT ivoid FROM rr.resource WHERE 1=ivo_hasword(res_title, 'α Cen''s planets') " + "UNION SELECT ivoid FROM rr.res_subject WHERE res_subject ILIKE '%α Cen''s planets%')") class TestAuthorConstraint: def test_basic(self): assert (rtcons.Author("%Hubble%").get_search_condition() - == "role_name LIKE '%Hubble%' AND base_role='creator'") + == "role_name LIKE '%Hubble%' AND base_role='creator'") class TestServicetypeConstraint: def test_standardmap(self): assert (rtcons.Servicetype("scs").get_search_condition() - == "standard_id IN ('ivo://ivoa.net/std/conesearch')") + == "standard_id IN ('ivo://ivoa.net/std/conesearch')") def test_fulluri(self): assert (rtcons.Servicetype("http://extstandards/invention" - ).get_search_condition() - == "standard_id IN ('http://extstandards/invention')") + ).get_search_condition() + == "standard_id IN ('http://extstandards/invention')") def test_multi(self): assert (rtcons.Servicetype("http://extstandards/invention", "image" - ).get_search_condition() - == "standard_id IN ('http://extstandards/invention'," + ).get_search_condition() + == "standard_id IN ('http://extstandards/invention'," " 'ivo://ivoa.net/std/sia')") def test_includeaux(self): assert (rtcons.Servicetype("http://extstandards/invention", "image" - ).include_auxiliary_services().get_search_condition() - == "standard_id IN ('http://extstandards/invention'," + ).include_auxiliary_services().get_search_condition() + == "standard_id IN ('http://extstandards/invention'," " 'http://extstandards/invention#aux'," " 'ivo://ivoa.net/std/sia'," " 'ivo://ivoa.net/std/sia#aux')") @@ -109,30 +113,31 @@ def test_junk_rejected(self): with pytest.raises(dalq.DALQueryError) as excinfo: rtcons.Servicetype("junk") assert str(excinfo.value) == ("Service type junk is neither" - " a full standard URI nor one of the bespoke identifiers" - " image, sia, spectrum, ssap, ssa, scs, conesearch, line, slap," - " table, tap") + " a full standard URI nor one of the bespoke identifiers" + " image, sia, spectrum, ssap, ssa, scs, conesearch, line, slap," + " table, tap") def test_legacy_term(self): assert (rtcons.Servicetype("conesearch").get_search_condition() - == "standard_id IN ('ivo://ivoa.net/std/conesearch')") + == "standard_id IN ('ivo://ivoa.net/std/conesearch')") @pytest.mark.usefixtures('messenger_vocabulary') class TestWavebandConstraint: def test_basic(self): assert (rtcons.Waveband("Infrared", "EUV").get_search_condition() - == "1 = ivo_hashlist_has(rr.resource.waveband, 'infrared')" + == "1 = ivo_hashlist_has(rr.resource.waveband, 'infrared')" " OR 1 = ivo_hashlist_has(rr.resource.waveband, 'euv')") def test_junk_rejected(self): with pytest.raises(dalq.DALQueryError) as excinfo: rtcons.Waveband("junk") - assert str(excinfo.value) == ("Waveband junk is not in the IVOA messenger vocabulary http://www.ivoa.net/rdf/messenger.") + assert str(excinfo.value) == ( + "Waveband junk is not in the IVOA messenger vocabulary http://www.ivoa.net/rdf/messenger.") def test_normalisation(self): assert (rtcons.Waveband("oPtIcAl").get_search_condition() - == "1 = ivo_hashlist_has(rr.resource.waveband, 'optical')") + == "1 = ivo_hashlist_has(rr.resource.waveband, 'optical')") class TestDatamodelConstraint: @@ -145,75 +150,69 @@ def test_junk_rejected(self): def test_obscore(self): cons = rtcons.Datamodel("ObsCore") assert (cons.get_search_condition() - == "detail_xpath = '/capability/dataModel/@ivo-id'" - " AND 1 = ivo_nocasematch(detail_value," + == "detail_xpath = '/capability/dataModel/@ivo-id'" + " AND 1 = ivo_nocasematch(detail_value," " 'ivo://ivoa.net/std/obscore%')") - assert(cons._extra_tables==["rr.res_detail"]) + assert (cons._extra_tables == ["rr.res_detail"]) def test_epntap(self): cons = rtcons.Datamodel("epntap") assert (cons.get_search_condition() - == "table_utype LIKE ivo://vopdc.obspm/std/epncore#schema-2.%'" - " OR table_utype LIKE ivo://ivoa.net/std/epntap#table-2.%'") - assert(cons._extra_tables==["rr.res_table"]) + == "table_utype LIKE ivo://vopdc.obspm/std/epncore#schema-2.%'" + " OR table_utype LIKE ivo://ivoa.net/std/epntap#table-2.%'") + assert (cons._extra_tables == ["rr.res_table"]) def test_regtap(self): cons = rtcons.Datamodel("regtap") assert (cons.get_search_condition() - == "detail_xpath = '/capability/dataModel/@ivo-id'" - " AND 1 = ivo_nocasematch(detail_value," + == "detail_xpath = '/capability/dataModel/@ivo-id'" + " AND 1 = ivo_nocasematch(detail_value," " 'ivo://ivoa.net/std/RegTAP#1.%')") - assert(cons._extra_tables==["rr.res_detail"]) + assert (cons._extra_tables == ["rr.res_detail"]) class TestIvoidConstraint: def test_basic(self): cons = rtcons.Ivoid("ivo://example/some_path") - assert (cons.get_search_condition() == - "ivoid = 'ivo://example/some_path'") + assert (cons.get_search_condition() + == "ivoid = 'ivo://example/some_path'") class TestUCDConstraint: def test_basic(self): cons = rtcons.UCD("phot.mag;em.opt.%", "phot.mag;em.ir.%") - assert (cons.get_search_condition() == - "ucd LIKE 'phot.mag;em.opt.%' OR ucd LIKE 'phot.mag;em.ir.%'") + assert (cons.get_search_condition() + == "ucd LIKE 'phot.mag;em.opt.%' OR ucd LIKE 'phot.mag;em.ir.%'") class TestSpatialConstraint: def test_point(self): cons = registry.Spatial([23, -40]) - assert (cons.get_search_condition() == - "1 = CONTAINS(MOC(6, POINT(23, -40)), coverage)") - assert(cons._extra_tables==["rr.stc_spatial"]) + assert cons.get_search_condition() == "1 = CONTAINS(MOC(6, POINT(23, -40)), coverage)" + assert cons._extra_tables == ["rr.stc_spatial"] def test_circle_and_order(self): cons = registry.Spatial([23, -40, 0.25], order=7) - assert (cons.get_search_condition() == - "1 = CONTAINS(MOC(7, CIRCLE(23, -40, 0.25)), coverage)") + assert cons.get_search_condition() == "1 = CONTAINS(MOC(7, CIRCLE(23, -40, 0.25)), coverage)" def test_polygon(self): cons = registry.Spatial([23, -40, 26, -39, 25, -43]) - assert (cons.get_search_condition() == - "1 = CONTAINS(MOC(6, POLYGON(23, -40, 26, -39, 25, -43))," - " coverage)") + assert cons.get_search_condition() == ( + "1 = CONTAINS(MOC(6, POLYGON(23, -40, 26, -39, 25, -43)), coverage)") def test_moc(self): cons = registry.Spatial("0/1-3 3/") - assert (cons.get_search_condition() == - "1 = CONTAINS(MOC('0/1-3 3/'), coverage)") + assert cons.get_search_condition() == "1 = CONTAINS(MOC('0/1-3 3/'), coverage)" def test_SkyCoord(self): - cons = registry.Spatial(SkyCoord(3*units.deg, -30*units.deg)) - assert (cons.get_search_condition() == - "1 = CONTAINS(MOC(6, POINT(3.0, -30.0)), coverage)") - assert(cons._extra_tables==["rr.stc_spatial"]) + cons = registry.Spatial(SkyCoord(3 * units.deg, -30 * units.deg)) + assert cons.get_search_condition() == "1 = CONTAINS(MOC(6, POINT(3.0, -30.0)), coverage)" + assert cons._extra_tables == ["rr.stc_spatial"] def test_SkyCoord_Circle(self): - cons = registry.Spatial((SkyCoord(3*units.deg, -30*units.deg), 3)) - assert (cons.get_search_condition() == - "1 = CONTAINS(MOC(6, CIRCLE(3.0, -30.0, 3)), coverage)") - assert(cons._extra_tables==["rr.stc_spatial"]) + cons = registry.Spatial((SkyCoord(3 * units.deg, -30 * units.deg), 3)) + assert cons.get_search_condition() == "1 = CONTAINS(MOC(6, CIRCLE(3.0, -30.0, 3)), coverage)" + assert cons._extra_tables == ["rr.stc_spatial"] class TestSpectralConstraint: @@ -222,98 +221,93 @@ class TestSpectralConstraint: # that would be useful there. def test_energy_float(self): cons = registry.Spectral(1e-19) - assert (cons.get_search_condition() == - "1e-19 BETWEEN spectral_start AND spectral_end") + assert cons.get_search_condition() == "1e-19 BETWEEN spectral_start AND spectral_end" def test_energy_eV(self): - cons = registry.Spectral(5*units.eV) - assert (cons.get_search_condition() == - "8.01088317e-19 BETWEEN spectral_start AND spectral_end") + cons = registry.Spectral(5 * units.eV) + assert cons.get_search_condition() == "8.01088317e-19 BETWEEN spectral_start AND spectral_end" def test_energy_interval(self): - cons = registry.Spectral((1e-10*units.erg, 2e-10*units.erg)) - assert (cons.get_search_condition() == - "1 = ivo_interval_overlaps(spectral_start, spectral_end," - " 1e-17, 2e-17)") + cons = registry.Spectral((1e-10 * units.erg, 2e-10 * units.erg)) + assert cons.get_search_condition() == ( + "1 = ivo_interval_overlaps(spectral_start, spectral_end, 1e-17, 2e-17)") def test_wavelength(self): - cons = registry.Spectral(5000*units.Angstrom) - assert (cons.get_search_condition() == - "3.9728917142978567e-19 BETWEEN spectral_start AND spectral_end") + cons = registry.Spectral(5000 * units.Angstrom) + assert cons.get_search_condition() == "3.9728917142978567e-19 BETWEEN spectral_start AND spectral_end" def test_wavelength_interval(self): - cons = registry.Spectral((20*units.cm, 22*units.cm)) - assert (cons.get_search_condition() == - "1 = ivo_interval_overlaps(spectral_start, spectral_end," - " 9.932229285744642e-25, 9.029299350676949e-25)") + cons = registry.Spectral((20 * units.cm, 22 * units.cm)) + assert (cons.get_search_condition() + == "1 = ivo_interval_overlaps(spectral_start, spectral_end," + " 9.932229285744642e-25, 9.029299350676949e-25)") def test_frequency(self): - cons = registry.Spectral(2*units.GHz) - assert (cons.get_search_condition() == - "1.32521403e-24 BETWEEN spectral_start AND spectral_end") + cons = registry.Spectral(2 * units.GHz) + assert (cons.get_search_condition() + == "1.32521403e-24 BETWEEN spectral_start AND spectral_end") def test_frequency_interval(self): - cons = registry.Spectral((88*units.MHz, 102*units.MHz)) - assert (cons.get_search_condition() == - "1 = ivo_interval_overlaps(spectral_start, spectral_end," - " 5.830941732e-26, 6.758591553e-26)") + cons = registry.Spectral((88 * units.MHz, 102 * units.MHz)) + assert (cons.get_search_condition() + == "1 = ivo_interval_overlaps(spectral_start, spectral_end," + " 5.830941732e-26, 6.758591553e-26)") class TestTemporalConstraint: def test_plain_float(self): cons = registry.Temporal((54130, 54200)) - assert (cons.get_search_condition() == - "1 = ivo_interval_overlaps(time_start, time_end," - " 54130, 54200)") + assert (cons.get_search_condition() + == "1 = ivo_interval_overlaps(time_start, time_end, 54130, 54200)") def test_single_time(self): cons = registry.Temporal(time.Time('2022-01-10')) - assert (cons.get_search_condition() == - "59589.0 BETWEEN time_start AND time_end") + assert (cons.get_search_condition() + == "59589.0 BETWEEN time_start AND time_end") def test_time_interval(self): cons = registry.Temporal((time.Time(2459000, format='jd'), - time.Time(59002, format='mjd'))) - assert (cons.get_search_condition() == - "1 = ivo_interval_overlaps(time_start, time_end, 58999.5, 59002.0)") + time.Time(59002, format='mjd'))) + assert (cons.get_search_condition() + == "1 = ivo_interval_overlaps(time_start, time_end, 58999.5, 59002.0)") def test_multi_times_rejected(self): with pytest.raises(ValueError) as excinfo: - cons = registry.Temporal(time.Time(['1999-01-01', '2010-01-01'])) + registry.Temporal(time.Time(['1999-01-01', '2010-01-01'])) assert (str(excinfo.value) == "RegTAP time constraints must" - " be made from single time instants.") + " be made from single time instants.") class TestWhereClauseBuilding: @staticmethod def where_clause_for(*args, **kwargs): - cons = list(args)+rtcons.keywords_to_constraints(kwargs) + cons = list(args) + rtcons.keywords_to_constraints(kwargs) return rtcons.build_regtap_query(cons - ).split("\nWHERE\n", 1)[1].split("\nGROUP BY\n")[0] + ).split("\nWHERE\n", 1)[1].split("\nGROUP BY\n")[0] @pytest.mark.usefixtures('messenger_vocabulary') def test_from_constraints(self): assert self.where_clause_for( rtcons.Waveband("EUV"), rtcons.Author("%Hubble%") - ) == ("(1 = ivo_hashlist_has(rr.resource.waveband, 'euv'))\n" - " AND (role_name LIKE '%Hubble%' AND base_role='creator')") + ) == ("(1 = ivo_hashlist_has(rr.resource.waveband, 'euv'))\n" + " AND (role_name LIKE '%Hubble%' AND base_role='creator')") @pytest.mark.usefixtures('messenger_vocabulary') def test_from_keywords(self): assert self.where_clause_for( waveband="EUV", author="%Hubble%" - ) == ("(1 = ivo_hashlist_has(rr.resource.waveband, 'euv'))\n" - " AND (role_name LIKE '%Hubble%' AND base_role='creator')") + ) == ("(1 = ivo_hashlist_has(rr.resource.waveband, 'euv'))\n" + " AND (role_name LIKE '%Hubble%' AND base_role='creator')") @pytest.mark.usefixtures('messenger_vocabulary') def test_mixed(self): assert self.where_clause_for( rtcons.Waveband("EUV"), author="%Hubble%" - ) == ("(1 = ivo_hashlist_has(rr.resource.waveband, 'euv'))\n" - " AND (role_name LIKE '%Hubble%' AND base_role='creator')") + ) == ("(1 = ivo_hashlist_has(rr.resource.waveband, 'euv'))\n" + " AND (role_name LIKE '%Hubble%' AND base_role='creator')") def test_bad_keyword(self): with pytest.raises(TypeError) as excinfo: @@ -324,22 +318,22 @@ def test_bad_keyword(self): # way to track changes; so, let's update the assertion as we # go. assert str(excinfo.value) == ("foo is not a valid registry" - " constraint keyword. Use one of" - " author, datamodel, ivoid, keywords, servicetype," - " spatial, spectral, temporal, ucd, waveband.") + " constraint keyword. Use one of" + " author, datamodel, ivoid, keywords, servicetype," + " spatial, spectral, temporal, ucd, waveband.") def test_with_legacy_keyword(self): assert self.where_clause_for( "plain", "string" - ) == ( + ) == ( '(ivoid IN (SELECT ivoid FROM rr.resource WHERE ' - "1=ivo_hasword(res_description, 'plain') UNION SELECT ivoid FROM rr.resource " - "WHERE 1=ivo_hasword(res_title, 'plain') UNION SELECT ivoid FROM " - "rr.res_subject WHERE res_subject ILIKE '%plain%'))\n" - ' AND (ivoid IN (SELECT ivoid FROM rr.resource WHERE ' - "1=ivo_hasword(res_description, 'string') UNION SELECT ivoid FROM rr.resource " - "WHERE 1=ivo_hasword(res_title, 'string') UNION SELECT ivoid FROM " - "rr.res_subject WHERE res_subject ILIKE '%string%'))") + "1=ivo_hasword(res_description, 'plain') UNION SELECT ivoid FROM rr.resource " + "WHERE 1=ivo_hasword(res_title, 'plain') UNION SELECT ivoid FROM " + "rr.res_subject WHERE res_subject ILIKE '%plain%'))\n" + ' AND (ivoid IN (SELECT ivoid FROM rr.resource WHERE ' + "1=ivo_hasword(res_description, 'string') UNION SELECT ivoid FROM rr.resource " + "WHERE 1=ivo_hasword(res_title, 'string') UNION SELECT ivoid FROM " + "rr.res_subject WHERE res_subject ILIKE '%string%'))") class TestSelectClause: @@ -348,7 +342,7 @@ def test_expected_columns(self): # is changed. Just update the assertion then. assert rtcons.build_regtap_query( rtcons.keywords_to_constraints({"author": "%Hubble%"}) - ).split("\nFROM\nrr.resource\n")[0] == ( + ).split("\nFROM\nrr.resource\n")[0] == ( "SELECT\n" "ivoid, " "res_type, " @@ -372,7 +366,7 @@ def test_group_by_columns(self): # Again, this will break as regtap.RegistryResource.expected_columns # is changed. Just update the assertion then. assert rtcons.build_regtap_query([rtcons.Author("%Hubble%")] - ).split("\nGROUP BY\n")[-1] == ( + ).split("\nGROUP BY\n")[-1] == ( "ivoid, " "res_type, " "short_name, " diff --git a/pyvo/samp.py b/pyvo/samp.py index 559641e3f..f864a5047 100644 --- a/pyvo/samp.py +++ b/pyvo/samp.py @@ -36,8 +36,8 @@ def send_table_to(conn, table, client_name=None, name="data"): message = { "samp.mtype": "table.load.votable", "samp.params": { - "url": url, - "name": name, + "url": url, + "name": name, }, } if client_name is None: @@ -87,7 +87,7 @@ def send_image_to(conn, url, client_name=None, name="data"): asks an image client to open a remote image via SAMP. """ send_product_to( - conn, url, "image.load.fits", + conn, url, "image.load.fits", client_name=client_name, name=name) diff --git a/pyvo/utils/formatting.py b/pyvo/utils/formatting.py index c8a19799d..6f0217095 100644 --- a/pyvo/utils/formatting.py +++ b/pyvo/utils/formatting.py @@ -35,7 +35,7 @@ def para_format_desc(text, width=78): _musubs = [ - (re.compile(r"<"), "<"), (re.compile(r">"), ">"), + (re.compile(r"<"), "<"), (re.compile(r">"), ">"), (re.compile(r"&"), "&"), (re.compile(r""), ''), (re.compile(r"

"), ''), (re.compile(r"°"), " deg"), (re.compile(r"\$((?:[^\$]*[\*\+=/^_~><\\][^\$]*)|(?:\w+))\$"), r'\1'), diff --git a/pyvo/utils/protofeature.py b/pyvo/utils/protofeature.py index 7e4d4ce0b..f2560f0d7 100644 --- a/pyvo/utils/protofeature.py +++ b/pyvo/utils/protofeature.py @@ -6,10 +6,11 @@ @dataclass class Feature: """ - A prototype feature implementing a standard that is currently in the process of being approved, but that might - change as a result of the approval process. A Feature must have a name. Optionally, a feature may have a *url* - that is displayed to the user in case a feature is used without the user explicitly opting in on its usage. - The URL is expected to contain more information about the standard and its state in the approval process. + A prototype feature implementing a standard that is currently in the process of being + approved, but that might change as a result of the approval process. A Feature must + have a name. Optionally, a feature may have a *url* that is displayed to the user in + case a feature is used without the user explicitly opting in on its usage. The URL is + expected to contain more information about the standard and its state in the approval process. """ name: str url: str = '' @@ -29,9 +30,9 @@ def error(self, function_name): """ Format an error message when the feature is being accesses without the user having opted in its usage. - This function will be used as a callback when an error message needs to be displayed to the user, with the - function name that was accessed as an argument. Extensions of this class may have additional information to - display. + This function will be used as a callback when an error message needs to be displayed to + the user, with the function name that was accessed as an argument. Extensions of this + class may have additional information to display. Parameters ---------- @@ -43,10 +44,11 @@ def error(self, function_name): str: The error message to be displayed to the user. """ - message = f'{function_name} is part of a prototype feature ({self.name}) that has not been activated. ' \ - f'For information about prototype features please refer to ' \ - f'https://pyvo.readthedocs.io/en/latest/utils/prototypes.html .' + message = (f'{function_name} is part of a prototype feature ({self.name}) that has not ' + 'been activated. For information about prototype features please refer to ' + 'https://pyvo.readthedocs.io/en/latest/utils/prototypes .') if self.url: message += f' For more information about the {self.name} feature please visit {self.url}.' - message += f' To suppress this error and enable the feature use `pyvo.utils.activate_features(\'{self.name}\')`' + message += (" To suppress this error and enable the feature use " + f"`pyvo.utils.activate_features('{self.name}')`") return message diff --git a/pyvo/utils/prototype.py b/pyvo/utils/prototype.py index 7dc4cbc47..9dbc83606 100644 --- a/pyvo/utils/prototype.py +++ b/pyvo/utils/prototype.py @@ -10,13 +10,16 @@ features: Dict[str, "Feature"] = { -'cadc-tb-upload': Feature('cadc-tb-upload', 'https://wiki.ivoa.net/twiki/bin/view/IVOA/TAP-1_1-Next', False) + 'cadc-tb-upload': Feature('cadc-tb-upload', + 'https://wiki.ivoa.net/twiki/bin/view/IVOA/TAP-1_1-Next', + False) } def prototype_feature(*args): """ - Decorator for functions and classes that implement unstable standards which haven't been approved yet. + Decorator for functions and classes that implement unstable standards + which haven't been approved yet. The decorator can be used to tag individual functions or methods. Please refer to the user documentation for details. @@ -24,13 +27,16 @@ def prototype_feature(*args): Parameters ---------- args: iterable of arguments. - Currently, the decorator must always be called with one and only one argument, a string representing - the feature's name associated with the decorated class or functions. Additional arguments will be ignored, - while using the decorator without any arguments will result in a ``PrototypeError`` error. + Currently, the decorator must always be called with one and only one + argument, a string representing the feature's name associated with + the decorated class or functions. Additional arguments will be ignored, + while using the decorator without any arguments will result in a + ``PrototypeError`` error. Returns ------- - The class or function it decorates, which will be associated to the feature provided as argument. + The class or function it decorates, which will be associated to the + feature provided as argument. """ feature_name = _parse_args(*args) @@ -53,8 +59,9 @@ def activate_features(*feature_names: Iterable[str]): Parameters ---------- feature_names: Iterable[str] - An arbitrary number of feature names. If a feature with that name does not exist, a `PrototypeWarning` will - be issued. If no arguments are provided, all features will be activated + An arbitrary number of feature names. If a feature with that name does + not exist, a `PrototypeWarning` will be issued. If no arguments are + provided, all features will be activated Returns ------- @@ -70,8 +77,9 @@ def deactivate_features(*feature_names: Iterable[str]): Parameters ---------- feature_names: Iterable[str] - An arbitrary number of feature names. If a feature with that name does not exist, a `PrototypeWarning` will - be issued. If no arguments are provided, all features will be de-activated + An arbitrary number of feature names. If a feature with that name does + not exist, a `PrototypeWarning` will be issued. If no arguments are + provided, all features will be de-activated Returns ------- @@ -90,8 +98,8 @@ class PrototypeWarning(PyvoUserWarning): def _parse_args(*args): if not args or callable(args[0]): - raise PrototypeError("The `prototype_feature` decorator must always be called with the feature name as an " - "argument") + raise PrototypeError("The `prototype_feature` decorator must always be called with the " + "feature name as an argument") return args[0] diff --git a/pyvo/utils/tests/test_http.py b/pyvo/utils/tests/test_http.py index efc78ee65..884c9b88a 100644 --- a/pyvo/utils/tests/test_http.py +++ b/pyvo/utils/tests/test_http.py @@ -9,6 +9,5 @@ def test_create_session(): - system = platform.system() test_session = create_session() assert test_session.headers['User-Agent'] == 'python-pyvo/{} ({})'.format(version, platform.system()) diff --git a/pyvo/utils/tests/test_prototype.py b/pyvo/utils/tests/test_prototype.py index 346638019..98546f3bd 100644 --- a/pyvo/utils/tests/test_prototype.py +++ b/pyvo/utils/tests/test_prototype.py @@ -1,4 +1,3 @@ -import warnings from copy import deepcopy from unittest import mock @@ -39,7 +38,8 @@ def test_feature_turned_off_by_default(prototype_function): with pytest.raises(PrototypeError) as e: prototype_function(None) - assert 'i_am_prototype is part of a prototype feature (my-feature) that has not been activated. ' in str(e.value) + assert 'i_am_prototype is part of a prototype feature (my-feature) that has not been activated. ' in str( + e.value) assert 'please visit http://somewhere/else' in str(e.value) assert 'https://pyvo.readthedocs.io/en/latest/utils/prototypes.html' in str(e.value) assert "pyvo.utils.activate_features('my-feature')" in str(e.value) @@ -127,8 +127,8 @@ def test_decorator_without_args_errors_out(): def function(): pass - assert str(e.value) == "The `prototype_feature` decorator must always be called with the feature name as an " \ - "argument" + assert str(e.value) == ("The `prototype_feature` decorator must always be called with the feature " + "name as an argument") def test_decorator_without_args_around_class(): @@ -137,8 +137,8 @@ def test_decorator_without_args_around_class(): class Class: pass - assert str(e.value) == "The `prototype_feature` decorator must always be called with the feature name as an " \ - "argument" + assert str(e.value) == ("The `prototype_feature` decorator must always be called with the feature " + "name as an argument") def test_decorator_with_no_arguments_and_class(): @@ -147,5 +147,5 @@ def test_decorator_with_no_arguments_and_class(): class Class: pass - assert str(e.value) == "The `prototype_feature` decorator must always be called with the feature name as an " \ - "argument" + assert str(e.value) == ("The `prototype_feature` decorator must always be called with the feature " + "name as an argument") diff --git a/pyvo/utils/tests/test_vocabularies_remote.py b/pyvo/utils/tests/test_vocabularies_remote.py index 702fa3fca..3ec8da508 100644 --- a/pyvo/utils/tests/test_vocabularies_remote.py +++ b/pyvo/utils/tests/test_vocabularies_remote.py @@ -33,7 +33,7 @@ def test_basic_getting(self): def test_label_getting(self): voc = vocabularies.get_vocabulary("datalink/core") assert (vocabularies.get_label(voc, "coderived") - == "Coderived Data") + == "Coderived Data") def test_label_getting_default(self): voc = vocabularies.get_vocabulary("datalink/core") @@ -53,7 +53,7 @@ def test_refreshing(self): last_change = 0 for entry in entries: last_change = max(last_change, entry.stat().st_mtime) - assert time.time()-last_change<2 + assert time.time() - last_change < 2 def test_non_existing_voc(self): with pytest.raises(vocabularies.VocabularyError): diff --git a/pyvo/utils/vocabularies.py b/pyvo/utils/vocabularies.py index ee28e7369..5a762b61f 100644 --- a/pyvo/utils/vocabularies.py +++ b/pyvo/utils/vocabularies.py @@ -11,7 +11,6 @@ import os import time import warnings -from urllib import request from astropy.utils.data import download_file, clear_download_cache @@ -36,7 +35,7 @@ def get_vocabulary(voc_name, force_update=False): This will use a cache to avoid repeated updates, but it will attempt to re-download if the cached copy is older than 6 months. """ - src_url = IVOA_VOCABULARY_ROOT+voc_name + src_url = IVOA_VOCABULARY_ROOT + voc_name if force_update: clear_download_cache(src_url) @@ -50,11 +49,11 @@ def get_vocabulary(voc_name, force_update=False): raise VocabularyError("No such vocabulary: {} ({})".format( voc_name, msg)) - if time.time()-os.path.getmtime(src_name)>3600*60*150: + if time.time() - os.path.getmtime(src_name) > 3600 * 60 * 150: # attempt a re-retrieval, but ignore failure try: src_name = download_file( - IVOA_VOCABULARY_ROOT+voc_name, + IVOA_VOCABULARY_ROOT + voc_name, cache="update", show_progress=False, http_headers={"accept": "application/x-desise+json"}) except Exception as msg: diff --git a/pyvo/utils/xml/elements.py b/pyvo/utils/xml/elements.py index f12b1f917..53167c082 100644 --- a/pyvo/utils/xml/elements.py +++ b/pyvo/utils/xml/elements.py @@ -96,6 +96,7 @@ class xmlelement(property): """ """ + def __init__( self, fget=None, fset=None, fdel=None, fadd=None, fformat=None, doc=None, name=None, ns=None, plain=False, cls=None, multiple_exc=None @@ -291,6 +292,7 @@ class Element: Subclasses and Mixins must initialize their independent attributes after calling ``super().__init__``. """ + def __init__(self, config=None, pos=None, _name='', _ns='', **kwargs): if config is None: config = {} @@ -399,10 +401,10 @@ def __new__(cls, *args, **kwargs): # really needs to be fixed when we switch to namespace-aware # parsing. for name, val in kwargs.items(): - if name=="xsi:type": + if name == "xsi:type": xsi_type = val break - elif name.split(":")[-1]=="type": + elif name.split(":")[-1] == "type": xsi_type = val if xsi_type is None: @@ -423,6 +425,7 @@ class ContentMixin(Element): """ Mixin class for elements with inner content. """ + def __init__(self, config=None, pos=None, _name=None, _ns=None, **kwargs): super().__init__(config, pos, _name, _ns, **kwargs) self._content = None diff --git a/pyvo/utils/xml/tests/test_elements.py b/pyvo/utils/xml/tests/test_elements.py index c18dbd32b..3002f3c2e 100644 --- a/pyvo/utils/xml/tests/test_elements.py +++ b/pyvo/utils/xml/tests/test_elements.py @@ -6,7 +6,6 @@ import io -import pytest from astropy.utils.xml import iterparser from pyvo.utils.xml import elements @@ -58,33 +57,33 @@ def test_no_type(self): def test_prefixed_type(self): found_type = self._parse_string(b'' - ).tbase.__class__ + ).tbase.__class__ assert found_type.__name__ == "TOther1" def test_unprefixed_type(self): # This is undesired behaviour; this test should fail once # we've properly parsing XML found_type = self._parse_string(b'' - ).tbase.__class__ + ).tbase.__class__ assert found_type.__name__ == "TOther1" def test_badprefixed_type(self): found_type = self._parse_string(b'' - ).tbase.__class__ + ).tbase.__class__ assert found_type.__name__ == "TOther2" def test_xsi_ignorable(self): # This is again unwelcome behaviour, but unavoidable as long # as we hack around namespaces found_type = self._parse_string(b'' - ).tbase.__class__ + ).tbase.__class__ assert found_type.__name__ == "TOther2" def test_xsi_preferred(self): # Another piece unwelcome behaviour. found_type = self._parse_string( b'' - ).tbase.__class__ + ).tbase.__class__ assert found_type.__name__ == "TOther2" def test_bad_type(self, recwarn): From d82bb9aad3a8e883f896e503e5a176a5cb8b2e12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Brigitta=20Sip=C5=91cz?= Date: Tue, 18 Oct 2022 10:18:06 +0200 Subject: [PATCH 4/5] MAINT: fix new linting issues --- pyvo/utils/protofeature.py | 2 +- pyvo/utils/tests/test_vocabularies_remote.py | 10 +++++----- pyvo/utils/vocabularies.py | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pyvo/utils/protofeature.py b/pyvo/utils/protofeature.py index f2560f0d7..c5bf146c3 100644 --- a/pyvo/utils/protofeature.py +++ b/pyvo/utils/protofeature.py @@ -46,7 +46,7 @@ class may have additional information to display. """ message = (f'{function_name} is part of a prototype feature ({self.name}) that has not ' 'been activated. For information about prototype features please refer to ' - 'https://pyvo.readthedocs.io/en/latest/utils/prototypes .') + 'https://pyvo.readthedocs.io/en/latest/utils/prototypes.html .') if self.url: message += f' For more information about the {self.name} feature please visit {self.url}.' message += (" To suppress this error and enable the feature use " diff --git a/pyvo/utils/tests/test_vocabularies_remote.py b/pyvo/utils/tests/test_vocabularies_remote.py index 3ec8da508..e8ce8762d 100644 --- a/pyvo/utils/tests/test_vocabularies_remote.py +++ b/pyvo/utils/tests/test_vocabularies_remote.py @@ -65,24 +65,24 @@ def test_failed_update(self): fake_voc = "http://www.ivoa.net/rdf/astropy-test-failure" cache_dir = pathlib.Path(data._get_download_cache_loc() - )/data._url_to_dirname(fake_voc) + ) / data._url_to_dirname(fake_voc) cache_dir.mkdir(exist_ok=True) - cache_name = cache_dir/"contents" + cache_name = cache_dir / "contents" with open(cache_name, "w") as f: f.write("{}") - with open(cache_dir/"url", "w") as f: + with open(cache_dir / "url", "w") as f: f.write(fake_voc) os.utime(cache_name, (1000000000, 1000000000)) with pytest.warns(PyvoUserWarning) as msgs: - voc = vocabularies.get_vocabulary("astropy-test-failure") + vocabularies.get_vocabulary("astropy-test-failure") # this sometimes catches a warning about an unclosed socket that, # I think, originates somewhere else; let me work around it for # the moment. for msg in msgs: if str(msg.message) == ("Updating cache for the vocabulary" - " astropy-test-failure failed: HTTP Error 404: Not Found"): + " astropy-test-failure failed: HTTP Error 404: Not Found"): break else: raise AssertionError("No warning about failed cache update") diff --git a/pyvo/utils/vocabularies.py b/pyvo/utils/vocabularies.py index 5a762b61f..9b67ca5eb 100644 --- a/pyvo/utils/vocabularies.py +++ b/pyvo/utils/vocabularies.py @@ -58,8 +58,8 @@ def get_vocabulary(voc_name, force_update=False): http_headers={"accept": "application/x-desise+json"}) except Exception as msg: warnings.warn("Updating cache for the vocabulary" - f" {voc_name} failed: {msg}", - category=PyvoUserWarning) + f" {voc_name} failed: {msg}", + category=PyvoUserWarning) with open(src_name, "r", encoding="utf-8") as f: return json.load(f) From 675fde5ddd42007e5078f5e6c9097e3201ac1188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Brigitta=20Sip=C5=91cz?= Date: Tue, 1 Nov 2022 19:54:25 +0100 Subject: [PATCH 5/5] MAINT: few more manual touch-ups --- pyvo/io/vosi/tests/test_capabilities.py | 8 ++++---- pyvo/registry/tests/test_rtcons.py | 7 +++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/pyvo/io/vosi/tests/test_capabilities.py b/pyvo/io/vosi/tests/test_capabilities.py index e6eab8744..16066b495 100644 --- a/pyvo/io/vosi/tests/test_capabilities.py +++ b/pyvo/io/vosi/tests/test_capabilities.py @@ -163,12 +163,12 @@ def test_mirrors_parsed(self, parsed_caps): assert len(parsed_caps[3].interfaces[0].mirrorurls) == 2 def test_mirrors_have_titles(self, parsed_caps): - assert [m.title for m in parsed_caps[3].interfaces[0].mirrorurls - ] == ["https version", "Paris mirror"] + assert ([m.title for m in parsed_caps[3].interfaces[0].mirrorurls] + == ["https version", "Paris mirror"]) def test_mirrors_have_urls(self, parsed_caps): - assert [m.content for m in parsed_caps[3].interfaces[0].mirrorurls - ] == ['https://example.org/tap', 'https://paris.example.org/tap'] + assert ([m.content for m in parsed_caps[3].interfaces[0].mirrorurls] + == ['https://example.org/tap', 'https://paris.example.org/tap']) def test_testquerystring_parsed(self, parsed_caps): assert (parsed_caps[3].interfaces[0].testquerystring.content diff --git a/pyvo/registry/tests/test_rtcons.py b/pyvo/registry/tests/test_rtcons.py index cb5e12947..8d4e1857d 100644 --- a/pyvo/registry/tests/test_rtcons.py +++ b/pyvo/registry/tests/test_rtcons.py @@ -75,8 +75,8 @@ def test_basic(self): def test_interesting_literal(self): assert rtcons.Freetext("α Cen's planets").get_search_condition() == ( "ivoid IN (SELECT ivoid FROM rr.resource WHERE 1=ivo_hasword(res_description, 'α Cen''s planets')" - " UNION SELECT ivoid FROM rr.resource WHERE 1=ivo_hasword(res_title, 'α Cen''s planets') " - "UNION SELECT ivoid FROM rr.res_subject WHERE res_subject ILIKE '%α Cen''s planets%')") + " UNION SELECT ivoid FROM rr.resource WHERE 1=ivo_hasword(res_title, 'α Cen''s planets')" + " UNION SELECT ivoid FROM rr.res_subject WHERE res_subject ILIKE '%α Cen''s planets%')") class TestAuthorConstraint: @@ -282,8 +282,7 @@ class TestWhereClauseBuilding: @staticmethod def where_clause_for(*args, **kwargs): cons = list(args) + rtcons.keywords_to_constraints(kwargs) - return rtcons.build_regtap_query(cons - ).split("\nWHERE\n", 1)[1].split("\nGROUP BY\n")[0] + return rtcons.build_regtap_query(cons).split("\nWHERE\n", 1)[1].split("\nGROUP BY\n")[0] @pytest.mark.usefixtures('messenger_vocabulary') def test_from_constraints(self):