Latest Threat Research:SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains.Details
Socket
Book a DemoInstallSign in
Socket

pyvo

Package Overview
Dependencies
Maintainers
5
Versions
42
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

pyvo - npm Package Compare versions

Comparing version
1.6.1
to
1.6.2
+27
pyvo/io/vosi/tests/data/capabilities/minimal-tapregext.xml
<?xml version="1.0"?>
<?xml-stylesheet href='/static/xsl/vosi.xsl' type='text/xsl'?>
<cap:capabilities xmlns:cap="http://www.ivoa.net/xml/VOSICapabilities/v1.0" xmlns:tr="http://www.ivoa.net/xml/TAPRegExt/v1.0" xmlns:vg="http://www.ivoa.net/xml/VORegistry/v1.0" xmlns:vr="http://www.ivoa.net/xml/VOResource/v1.0" xmlns:vs="http://www.ivoa.net/xml/VODataService/v1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.ivoa.net/xml/VOSICapabilities/v1.0 http://vo.ari.uni-heidelberg.de/docs/schemata/VOSICapabilities-v1.0.xsd http://www.ivoa.net/xml/TAPRegExt/v1.0 http://vo.ari.uni-heidelberg.de/docs/schemata/TAPRegExt-v1.0.xsd http://www.ivoa.net/xml/VORegistry/v1.0 http://vo.ari.uni-heidelberg.de/docs/schemata/VORegistry-v1.0.xsd http://www.ivoa.net/xml/VOResource/v1.0 http://vo.ari.uni-heidelberg.de/docs/schemata/VOResource-v1.1.xsd http://www.ivoa.net/xml/VODataService/v1.1 http://vo.ari.uni-heidelberg.de/docs/schemata/VODataService-v1.1.xsd">
<capability standardID="ivo://ivoa.net/std/VOSI#capabilities">
<interface xsi:type="vs:ParamHTTP">
<accessURL use="full">http://example.org/tap/capabilities</accessURL>
</interface>
</capability>
<capability standardID="ivo://ivoa.net/std/VOSI#tables">
<interface xsi:type="vs:ParamHTTP">
<accessURL use="full">http://example.org/tap/tables</accessURL>
</interface>
</capability>
<capability standardID="ivo://ivoa.net/std/TAP" xsi:type="tr:TableAccess">
<interface role="std" xsi:type="vs:ParamHTTP">
<accessURL use="base">http://example.org/tap</accessURL>
</interface>
<language>
<name>ADQL</name>
<version ivo-id="ivo://ivoa.net/std/ADQL#v2.0">2.0</version>
<description>ADQL 2.0</description>
</language>
<outputFormat ivo-id="ivo://ivoa.net/std/TAPRegExt#output-votable-binary">
<mime>text/xml</mime>
</outputFormat>
</capability>
</cap:capabilities>
+3
-3

@@ -26,3 +26,3 @@ # This test job is separated out into its own workflow to be able to trigger separately

- name: Set up Python 3.12
uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
with:

@@ -36,3 +36,3 @@ python-version: "3.12"

- name: Upload coverage to codecov
uses: codecov/codecov-action@13ce06bfc6bbe3ecf90edbbf1bc32fe5978ca1d3 # v5.3.1
uses: codecov/codecov-action@0565863a31f2c772f9f0395002a31e3f06189574 # v5.4.0
with:

@@ -47,3 +47,3 @@ file: ./coverage.xml

- name: Set up Python 3.13
uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
with:

@@ -50,0 +50,0 @@ python-version: "3.13-dev"

@@ -43,3 +43,3 @@ # Developer version testing is in separate workflow

- name: Set up Python
uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
with:

@@ -65,3 +65,3 @@ python-version: ${{ matrix.python-version }}

- name: Set up Python
uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
with:

@@ -82,3 +82,3 @@ python-version: '3.10'

- name: Set up Python 3.8
uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
with:

@@ -99,3 +99,3 @@ python-version: 3.8

- name: Set up Python 3.10
uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
with:

@@ -102,0 +102,0 @@ python-version: '3.10'

@@ -0,1 +1,12 @@

1.6.2 (2025-04-07)
==================
Bug Fixes
---------
- Fix performance issues with datalink results. [#654]
- More careful NULL value handling in tapregext data limits. [#659]
1.6.1 (2025-02-12)

@@ -2,0 +13,0 @@ ==================

@@ -409,3 +409,3 @@ .. _pyvo-data-access:

>>> # authenticate. For ex: auth_session.credentials.set_client_certificate('<cert_file>')
>>> tap_service = vo.dal.TAPService("https://ws-cadc.canfar.net/youcat", auth_session)
>>> tap_service = vo.dal.TAPService("https://ws-cadc.canfar.net/youcat", session=auth_session)
>>>

@@ -820,3 +820,3 @@ >>> table_definition = '''

... ).run_sync("select top 5 * from califadr3.cubes order by califaid")
>>> for dl in rows.iter_datalinks(): # doctest: +IGNORE_WARNINGS
>>> for dl in rows.iter_datalinks(preserve_order=True): # doctest: +IGNORE_WARNINGS
... print(next(dl.bysemantics("#preview"))["access_url"])

@@ -823,0 +823,0 @@ http://dc.g-vo.org/getproduct/califa/datadr3/V1200/IC5376.V1200.rscube.fits?preview=True

@@ -584,3 +584,3 @@ .. _pyvo-registry:

export IVOA_REGISTRY="http://vao.stsci.edu/RegTAP/TapService.aspx"
export IVOA_REGISTRY="https://mast.stsci.edu/vo-tap/api/v0.1/registry"

@@ -607,3 +607,4 @@ before starting python (or the notebook processor).

http://voparis-rr.obspm.fr/tap
https://vao.stsci.edu/RegTAP/TapService.aspx
https://mast.stsci.edu/vo-tap/api/v0.1/registry
https://registry.euro-vo.org/regtap/tap

@@ -610,0 +611,0 @@

@@ -1,11 +0,10 @@

Metadata-Version: 2.2
Metadata-Version: 2.4
Name: pyvo
Version: 1.6.1
Version: 1.6.2
Summary: Astropy affiliated package for accessing Virtual Observatory data and services
Author: the PyVO Developers
License: BSD
License: BSD-3-Clause
Project-URL: Source, https://github.com/astropy/pyvo
Project-URL: Documentation, https:/pyvo.readthedocs.io
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent

@@ -17,3 +16,2 @@ Classifier: Programming Language :: Python

Classifier: Topic :: Software Development :: Libraries
Classifier: License :: OSI Approved :: BSD License
Requires-Python: >=3.8

@@ -32,2 +30,3 @@ License-File: LICENSE.rst

Requires-Dist: sphinx-astropy; extra == "docs"
Dynamic: license-file

@@ -34,0 +33,0 @@ PyVO

@@ -1,11 +0,10 @@

Metadata-Version: 2.2
Metadata-Version: 2.4
Name: pyvo
Version: 1.6.1
Version: 1.6.2
Summary: Astropy affiliated package for accessing Virtual Observatory data and services
Author: the PyVO Developers
License: BSD
License: BSD-3-Clause
Project-URL: Source, https://github.com/astropy/pyvo
Project-URL: Documentation, https:/pyvo.readthedocs.io
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent

@@ -17,3 +16,2 @@ Classifier: Programming Language :: Python

Classifier: Topic :: Software Development :: Libraries
Classifier: License :: OSI Approved :: BSD License
Requires-Python: >=3.8

@@ -32,2 +30,3 @@ License-File: LICENSE.rst

Requires-Dist: sphinx-astropy; extra == "docs"
Dynamic: license-file

@@ -34,0 +33,0 @@ PyVO

@@ -158,2 +158,3 @@ .gitignore

pyvo/io/vosi/tests/data/tables.xml
pyvo/io/vosi/tests/data/capabilities/minimal-tapregext.xml
pyvo/io/vosi/tests/data/capabilities/multiple_capa_descriptions.xml

@@ -160,0 +161,0 @@ pyvo/io/vosi/tests/data/tables/datatypes_tap.xml

@@ -9,3 +9,2 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst

import requests
from collections import OrderedDict

@@ -22,3 +21,7 @@ from .query import DALResults, DALQuery, DALService, Record

from astropy.io.votable.tree import Resource, Group
from astropy.io.votable.tree import Resource, Group, VOTableFile
try:
from astropy.io.votable.tree import TableElement
except ImportError:
from astropy.io.votable.tree import Table as TableElement
from astropy.utils.collections import HomogeneousList

@@ -128,3 +131,3 @@

----------
ivoid : str
ivo_id : str
the ivoid of the service we want to have.

@@ -173,45 +176,123 @@

"""
Mixin for datalink functionallity for results classes.
Mixin for datalink functionality for results classes.
"""
def _iter_datalinks_from_dlblock(self, datalink_service):
def _iter_datalinks_from_dlblock(self, preserve_order=False):
"""yields datalinks from the current rows using a datalink
service RESOURCE.
Parameters
----------
preserve_order : bool
True to return the datalinks keeping the order of the current rows.
NOTE: There might be a performance penalty for keeping the order as
one request per row is sent to the service. When the order of the
datalinks is not important, the execution is optimized to query the
service in batches.
"""
remaining_ids = [] # remaining IDs to processed
current_batch = None # retrieved but not returned yet
current_ids = [] # retrieved but not returned
processed_ids = [] # retrived and returned IDs
batch_size = None # size of the batch
def _get_results_tb(rows, dl_batch_tb):
# Creates a new DL result table with the given rows as the results data
# and the dl_batch_tb as the template for both table fields and other resources
tb = VOTableFile()
new_table = TableElement(tb)
new_table.fields.extend(dl_batch_tb.get_first_table().fields)
new_table.create_arrays(len(rows))
for index, row in enumerate(rows):
new_table.array[index] = row
results_resource = Resource()
results_resource.type = "results"
results_resource.tables.append(new_table)
tb.resources.append(results_resource)
# now add all the other resources from the dl_batch_tb
for resource in dl_batch_tb.resources:
if resource.type == "meta":
tb.resources.append(resource)
return tb
for row in self:
if not current_ids:
if batch_size is None:
# first call.
self.query = DatalinkQuery.from_resource(
[_ for _ in self],
self._datalink,
session=self._session,
original_row=row)
remaining_ids = self.query['ID']
if not remaining_ids:
# we are done
return
if batch_size:
# subsequent calls are limitted to batch size
self.query['ID'] = remaining_ids[:batch_size]
current_batch = self.query.execute(post=True)
current_ids = list(OrderedDict.fromkeys(
[_ for _ in current_batch.to_table()['ID']]))
if not current_ids:
raise DALServiceError(
'Could not retrieve datalinks for: {}'.format(
', '.join([_ for _ in remaining_ids])))
batch_size = len(current_ids)
id1 = current_ids.pop(0)
processed_ids.append(id1)
remaining_ids.remove(id1)
yield current_batch.clone_byid(
id1,
original_row=row)
if preserve_order:
# one request at the time to preserve order
for row in self:
self.query = DatalinkQuery.from_resource(
row, self._datalink, session=self._session, original_row=None)
yield DatalinkResults(
self.query.execute(post=True).votable,
original_row=row)
return
# results from batch calls are not guaranteed to be in the same order with
# the results rows. To map the links back to the original result rows,
# create a dictionary of IDs to result rows, where ID is the value of
# the column given by the ref field of the service descriptor (input parameter)
original_rows = {}
input_params = _get_input_params_from_resource(self._datalink)
for name, input_param in input_params.items():
if input_param.ref:
for row in self:
original_rows[row[input_param.ref]] = row
self.query = DatalinkQuery.from_resource(
[_ for _ in self],
self._datalink,
session=self._session,
original_row=None)
remaining_ids = self.query['ID']
if not remaining_ids:
# we are done before starting
return
current_batch = self.query.execute(post=True)
if len(current_batch) == 0:
raise DALServiceError(
'Could not retrieve datalinks for: {}'.format(
', '.join([_ for _ in remaining_ids])))
batch_size = 0 # unknown yet
batch_size_determined = False # apparent only after first returned batch
while remaining_ids:
start_remaining_ids = len(remaining_ids)
res_votable = current_batch.votable.get_first_table()
id_index = 0 # Datalink spec: ID is the first column
# Datalink spec: "... all links for a single ID value must be served in
# consecutive rows in the output"
# Accordingly, the line below should not be necessary but in
# practice it might be needed
np.ma.MaskedArray.sort(res_votable.array, id_index)
last_id = res_votable.array[id_index][0]
rows = []
for index, row in enumerate(res_votable.array):
if row[id_index] == last_id:
rows.append(row)
else:
yield DatalinkResults(_get_results_tb(rows, current_batch.votable),
original_row=original_rows.get(last_id, None))
if not batch_size_determined:
batch_size += 1
if last_id in remaining_ids:
remaining_ids.remove(last_id)
# proceed to the next ID
last_id = row[id_index]
rows = [row]
if last_id in remaining_ids:
remaining_ids.remove(last_id)
if not batch_size_determined:
batch_size += 1
batch_size_determined = True
yield DatalinkResults(_get_results_tb(rows, current_batch.votable),
original_row=original_rows.get(last_id, None))
if not remaining_ids:
return # we are done
if len(remaining_ids) == start_remaining_ids:
# no progress
raise DALServiceError(
'Could not retrieve datalinks for: {}'.format(
', '.join([_ for _ in remaining_ids])))
self.query['ID'] = remaining_ids[:batch_size]
current_batch = self.query.execute(post=True)
if not current_batch:
raise DALServiceError(
'Could not retrieve datalinks for: {}'.format(
', '.join([_ for _ in remaining_ids])))
@staticmethod

@@ -293,13 +374,18 @@ def _guess_access_format(row):

def iter_datalinks(self):
def iter_datalinks(self, preserve_order=False):
"""
Iterates over all datalinks in a DALResult.
Parameters
----------
preserve_order : bool
True to return the datalinks keeping the order of the current rows.
NOTE: There might be a performance penalty for keeping the order as
one request per row is sent to the service. When the order of the
datalinks is not important, the execution is optimized to query the
service in batches.
"""
# To reduce the number of calls to the Datalink service, multiple
# IDs are sent in batches. The appropriate batch size is not available
# as each service has its own criteria for determining the maximum
# number of IDs processed per request. To overcome this limitation,
# this method initially sends all the available IDs and if the
# response is partial, uses the size of the response as the batch
# size.
if not hasattr(self, '_datalink'):

@@ -315,3 +401,4 @@ try:

else:
yield from self._iter_datalinks_from_dlblock(self._datalink)
yield from self._iter_datalinks_from_dlblock(
preserve_order=preserve_order)

@@ -426,4 +513,2 @@

----------
baseurl : str
the base URL for the Datalink service
id : str

@@ -946,3 +1031,3 @@ the dataset identifier

class AxisParamMixin():
class AxisParamMixin:
"""

@@ -949,0 +1034,0 @@ Stores the axis parameters (pos, band, time and pol) used in SODA

@@ -87,5 +87,3 @@ """

a message describing the cause of the error
code : int
the HTTP error code (as an integer)
cause : str
cause : Exception
an exception issued as the underlying cause. A value

@@ -121,3 +119,3 @@ of None indicates that no underlying exception was

----------
cause : str
cause : Exception
an exception issued as the underlying cause. A value

@@ -155,3 +153,3 @@ of None indicates that no underlying exception was caught.

the HTTP error code (as an integer)
cause : str
cause : Exception
an exception issued as the underlying cause. A value

@@ -158,0 +156,0 @@ of None indicates that no underlying exception was

@@ -24,3 +24,3 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst

__all__ = ["DALService", "DALServiceError", "DALQuery", "DALQueryError",
"DALResults", "Record"]
"DALResults", "Record", "UploadList"]

@@ -315,3 +315,3 @@ import os

----------
votable : str
votable : astropy.io.votable.tree.VOTableFile
the service response parsed into an

@@ -318,0 +318,0 @@ astropy.io.votable.tree.VOTableFile instance.

@@ -189,3 +189,3 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst

io.BytesIO(response.content)).getroot()
exampleElements = root.findall('.//*[@property="query"]')
example_elements = root.findall('.//*[@property="query"]')
except Exception as ex:

@@ -195,3 +195,3 @@ raise DALServiceError.from_except(ex, examples_uri)

examples = [TAPQuery(self.baseurl, example.text)
for example in exampleElements]
for example in example_elements]

@@ -469,3 +469,3 @@ for continuation in root.findall('.//*[@property="continuation"]'):

This includes the interface capabilities, and the content description
if it doesn't contains multiple data collections (in other words, it is
if it doesn't contain multiple data collections (in other words, it is
not a TAP service).

@@ -472,0 +472,0 @@ """

@@ -147,19 +147,13 @@ #!/usr/bin/env python

datalinks = next(results.iter_datalinks())
for preserve_order in [False, True]:
dl_res = set(results.iter_datalinks(preserve_order=preserve_order))
assert len(dl_res) == 1
datalinks = dl_res.pop()
assert datalinks.original_row["accsize"] == 100800
assert datalinks.original_row["accsize"] == 100800
assert 4 == len(datalinks)
for dl in datalinks:
assert dl.semantics in ['#this', '#preview', '#progenitor', '#proc']
row = datalinks[0]
assert row.semantics == "#progenitor"
row = datalinks[1]
assert row.semantics == "#proc"
row = datalinks[2]
assert row.semantics == "#this"
row = datalinks[3]
assert row.semantics == "#preview"
@pytest.mark.usefixtures('obscore_datalink', 'res_datalink')

@@ -174,5 +168,6 @@ @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W27")

dls = list(results.iter_datalinks())
assert len(dls) == 3
assert dls[0].original_row["obs_collection"] == "MACHO"
for preserve_order in [False, True]:
dls = list(results.iter_datalinks(preserve_order=preserve_order))
assert len(dls) == 3
assert dls[0].original_row["obs_collection"] == "MACHO"

@@ -179,0 +174,0 @@

@@ -20,3 +20,3 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst

class EndpointMixin():
class EndpointMixin:
def _get_endpoint_candidates(self, endpoint):

@@ -23,0 +23,0 @@ """Construct endpoint URLs from base URL and endpoint"""

@@ -372,2 +372,5 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst

def __str__(self):
return f"{self.unit}:{self.content}"
@xmlattribute

@@ -404,8 +407,8 @@ def unit(self):

def __repr__(self):
return '<DataLimits default={}:{} hard={}:{}/>'.format(
self.default.unit,
self.default.content,
self.hard.unit,
self.hard.content
)
parts = []
if self.default is not None:
parts.append("default={self.default}")
if self.hard is not None:
parts.append("hard={self.hard}")
return '<DataLimits {}/>'.format(" ".join(parts))

@@ -499,3 +502,3 @@ @xmlelement(cls=DataLimit, multiple_exc=W29)

if self.uploadlimit:
if self.uploadlimit and self.uploadlimit.hard:
print("Maximal size of uploads")

@@ -502,0 +505,0 @@ print(indent("Maximum {} {}".format(

@@ -6,2 +6,4 @@ #!/usr/bin/env python

"""
import contextlib
import io

@@ -26,2 +28,8 @@ from operator import eq as equals

@pytest.fixture(name='parsed_minimal_tapregext')
def _minimal_tapregext():
return vosi.parse_capabilities(get_pkg_data_filename(
"data/capabilities/minimal-tapregext.xml"))
@pytest.mark.usefixtures("parsed_caps")

@@ -156,2 +164,30 @@ class TestCapabilities:

@pytest.mark.usefixtures("parsed_minimal_tapregext")
class TestMinimalTAPRegExt:
def test_standard_id(self, parsed_minimal_tapregext):
assert isinstance(parsed_minimal_tapregext[2], tr.TableAccess)
def test_limits(self, parsed_minimal_tapregext):
assert parsed_minimal_tapregext[2]._uploadlimit is None
assert parsed_minimal_tapregext[2]._outputlimit is None
def test_adql(self, parsed_minimal_tapregext):
assert parsed_minimal_tapregext[2].get_adql().name == "ADQL"
def test_udfs(self, parsed_minimal_tapregext):
assert parsed_minimal_tapregext[2].get_adql(
).languagefeaturelists == []
def test_uploadmethods(self, parsed_minimal_tapregext):
assert parsed_minimal_tapregext[2].uploadmethods == []
def test_describe(self, parsed_minimal_tapregext):
with io.StringIO() as buf, contextlib.redirect_stdout(buf):
parsed_minimal_tapregext[2].describe()
output = buf.getvalue()
assert "http://example.org/tap" in output
assert "Output format text/xml" in output
assert "Maximal size" not in output
@pytest.mark.usefixtures("parsed_caps")

@@ -158,0 +194,0 @@ class TestInterface:

@@ -933,3 +933,3 @@ #!/usr/bin/env python

def test_endpoint_switching():
alt_svc = "http://vao.stsci.edu/RegTAP/TapService.aspx"
alt_svc = "https://mast.stsci.edu/vo-tap/api/v0.1/registry"
previous_url = regtap.REGISTRY_BASEURL

@@ -936,0 +936,0 @@ try:

@@ -8,2 +8,2 @@ # Note that we need to fall back to the hard-coded version if either

except Exception:
version = '1.6.1'
version = '1.6.2'

@@ -37,3 +37,3 @@ [tool:pytest]

author = the PyVO Developers
license = BSD
license = BSD-3-Clause
license_file = LICENSE.rst

@@ -47,3 +47,2 @@ edit_on_github = False

Intended Audience :: Science/Research
License :: OSI Approved :: BSD License
Operating System :: OS Independent

@@ -55,3 +54,2 @@ Programming Language :: Python

Topic :: Software Development :: Libraries
License :: OSI Approved :: BSD License

@@ -58,0 +56,0 @@ [options]