Skip to content

Commit

Permalink
Merge pull request #88 from pyvirtobs/vosi-rework
Browse files Browse the repository at this point in the history
Rework VOSI parsing using astropy xml handling
  • Loading branch information
funbaker committed Aug 29, 2017
2 parents b4e651f + a89c29f commit d71dae6
Show file tree
Hide file tree
Showing 24 changed files with 3,416 additions and 381 deletions.
8 changes: 4 additions & 4 deletions docs/pyvo/data_access.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1195,17 +1195,17 @@ There are three types of service metadata:
>>> print(service.available)
True
>>> print(service.up_since)
datetime.datetime(2000, 0, 0, 0, 0, 0)
'2000-01-01T00:00:00Z'
>>> print(service.capabilities)
>>> print(service.tables.keys())
>>> print(list(service.tables.keys()))

The keys within tables are the fully qualified table names as they can
be used in queries. To inspect the column metadata for a table, see the column
be used in queries. To inspect the column metadata for a table, see the columns
property of a give table.

>>> service.tables["rave.main"].columns

See also http://docs.astropy.org/en/stable/table/index.html.
See also `~pyvo.io.vosi`

.. note::
Some TAP services have tables metadata of several megabytes.
Expand Down
15 changes: 15 additions & 0 deletions docs/pyvo/io.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

.. py:module:: pyvo.io
=========================
The pyvo.io Package
=========================

This package contains io functionallity for pyvo

.. toctree::
:maxdepth: 1

vosi

.. automodapi:: pyvo.io.vosi
2 changes: 1 addition & 1 deletion docs/pyvo/ref.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ Virtual Observatory (VO) using Python.
mod
dal
registry

io
14 changes: 14 additions & 0 deletions docs/pyvo/vosi.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

.. py:module:: pyvo.io.vosi
=========================
The pyvo.io.vosi Package
=========================

This package contains io functionallity for pyvo

.. automodapi:: pyvo.io.vosi.endpoint
.. automodapi:: pyvo.io.vosi.vodataservice
.. automodapi:: pyvo.io.vosi.voresource
.. automodapi:: pyvo.io.vosi.tapregext
.. automodapi:: pyvo.io.vosi.exceptions
20 changes: 7 additions & 13 deletions pyvo/dal/mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import requests

from .query import DALServiceError
from ..tools import vosi
from ..io import vosi

class AvailabilityMixin(object):
"""
Expand All @@ -20,14 +20,8 @@ class AvailabilityMixin(object):
@property
def availability(self):
"""
returns availability as a tuple in the following form:
Returns
-------
[0] : bool
whether the service is available or not
[1] : datetime
the time since the server is running
Service Availability as a
:py:class:`~pyvo.io.vosi.availability.Availability` object
"""
if self._availability == (None, None):
avail_url = '{0}/availability'.format(self.baseurl)
Expand All @@ -42,22 +36,22 @@ def availability(self):
# requests doesn't decode the content by default
response.raw.read = partial(response.raw.read, decode_content=True)

self._availability = vosi.parse_availability(response.raw)
self._availability = vosi.parse_availability(response.raw.read)
return self._availability

@property
def available(self):
"""
True if the service is available, False otherwise
"""
return self.availability[0]
return self.availability.available

@property
def up_since(self):
"""
datetime the service was started
"""
return self.availability[1]
return self.availability.upsince


class CapabilityMixin(object):
Expand All @@ -83,5 +77,5 @@ def capabilities(self):
# requests doesn't decode the content by default
response.raw.read = partial(response.raw.read, decode_content=True)

self._capabilities = vosi.parse_capabilities(response.raw)
self._capabilities = vosi.parse_capabilities(response.raw.read)
return self._capabilities
94 changes: 81 additions & 13 deletions pyvo/dal/tap.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
DALServiceError, DALQueryError)
from .mixin import AvailabilityMixin, CapabilityMixin
from .datalink import DatalinkMixin
from ..tools import vosi, uws
from ..io import vosi, uws
from ..io.vosi import tapregext as tr

__all__ = [
"search", "escape", "TAPService", "TAPQuery", "AsyncTAPJob", "TAPResults"]
Expand Down Expand Up @@ -63,6 +64,72 @@ def search(url, query, language="ADQL", maxrec=None, uploads=None, **keywords):
service = TAPService(url)
return service.search(query, language, maxrec, uploads, **keywords)

class VOSITables(object):
"""
This class encapsulates access to the VOSITables using a given Endpoint.
Access to table names is like accessing dictionary keys. using iterator
syntax or `keys()`
"""
def __init__(self, vosi_tables, endpoint_url):
self._vosi_tables = vosi_tables
self._endpoint_url = endpoint_url
self._cache = {}

def __len__(self):
return self._vosi_tables.ntables

def __getitem__(self, key):
return self._get_table(key)

def __iter__(self):
return self.keys()

def _get_table(self, name):
if name in self._cache:
return self._cache[name]

table = self._vosi_tables.get_table_by_name(name)

if not table.columns and not table.foreignkeys:
tables_url = '{}/{}'.format(self._endpoint_url, name)
response = requests.get(tables_url, stream=True)

try:
response.raise_for_status()
except requests.RequestException as ex:
raise DALServiceError.from_except(ex, tables_url)

# requests doesn't decode the content by default
response.raw.read = partial(response.raw.read, decode_content=True)

table = vosi.parse_tables(response.raw.read).get_first_table()
self._cache[name] = table

return table

def keys(self):
"""
Iterates over the keys (table names).
"""
for table in self._vosi_tables.iter_tables():
yield table.name

def values(self):
"""
Iterates over the values (tables).
Gathers missing values from endpoint if necessary.
"""
for name in self.keys():
yield self._get_table(name)

def items(self):
"""
Iterates over keys and values (table names and tables).
Gathers missing values from endpoint if necessary.
"""
for name in self.keys():
yield (name, self._get_table(name))

class TAPService(DALService, AvailabilityMixin, CapabilityMixin):
"""
a representation of a Table Access Protocol service
Expand All @@ -84,7 +151,7 @@ def __init__(self, baseurl):
@property
def tables(self):
"""
returns tables as a flat OrderedDict
returns tables as a dict-like object
"""
if self._tables is None:
tables_url = '{0}/tables'.format(self.baseurl)
Expand All @@ -99,7 +166,8 @@ def tables(self):
# requests doesn't decode the content by default
response.raw.read = partial(response.raw.read, decode_content=True)

self._tables = vosi.parse_tables(response.raw)
self._tables = VOSITables(
vosi.parse_tables(response.raw.read), tables_url)
return self._tables

@property
Expand All @@ -114,9 +182,9 @@ def maxrec(self):
"""
try:
for capa in self.capabilities:
if "outputLimit" in capa:
return capa["outputLimit"]["default"]["value"]
except KeyError:
if isinstance(capa, tr.TableAccess):
return capa.outputlimit.default.value
except AttributeError:
pass
raise DALServiceError("Default limit not exposed by the service")

Expand All @@ -132,9 +200,9 @@ def hardlimit(self):
"""
try:
for capa in self.capabilities:
if "outputLimit" in capa:
return capa["outputLimit"]["hard"]["value"]
except KeyError:
if isinstance(capa, tr.TableAccess):
return capa.outputlimit.hard.value
except AttributeError:
pass
raise DALServiceError("Hard limit not exposed by the service")

Expand All @@ -143,11 +211,11 @@ def upload_methods(self):
"""
a list of upload methods in form of IVOA identifiers
"""
_upload_methods = []
upload_methods = []
for capa in self.capabilities:
if "uploadMethods" in capa:
_upload_methods += capa["uploadMethods"]
return _upload_methods
if isinstance(capa, tr.TableAccess):
upload_methods += capa.uploadmethods
return upload_methods

def run_sync(
self, query, language="ADQL", maxrec=None, uploads=None,
Expand Down
1 change: 0 additions & 1 deletion pyvo/tools/__init__.py → pyvo/io/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
# Licensed under a 3-clause BSD style license - see LICENSE.rst
from . import uws, vosi
File renamed without changes.
File renamed without changes.
2 changes: 2 additions & 0 deletions pyvo/io/vosi/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Licensed under a 3-clause BSD style license - see LICENSE.rst
from .endpoint import parse_tables, parse_capabilities, parse_availability
83 changes: 83 additions & 0 deletions pyvo/io/vosi/availability.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Licensed under a 3-clause BSD style license - see LICENSE.rst
from __future__ import (absolute_import, division, print_function,
unicode_literals)

from astropy.extern import six

from astropy.utils.collections import HomogeneousList
from astropy.utils.xml import check as xml_check
from astropy.io.votable.exceptions import vo_raise, vo_warn, warn_or_raise

from .util import (
make_add_simplecontent, make_add_complexcontent, Element, ValueMixin)
from . import voresource as vr
from .exceptions import W32, W33, W34, W35

__all__ = ["Availability"]

######################################################################
# FACTORY FUNCTIONS
def _convert_boolean(value, default=None):
return {
'false': False,
'0': False,
'true': True,
'1': True
}.get(value, default)

######################################################################
# ELEMENT CLASSES
class Availability(Element):
def __init__(self, config=None, pos=None, **kwargs):
super(Availability, self).__init__(config=config, pos=pos, **kwargs)

self._tag_mapping.update({
"available": make_add_simplecontent(
self, "available", "available", W32),
"upSince": make_add_simplecontent(self, "upSince", "upsince", W33),
"downAt": make_add_simplecontent(self, "downAt", "downat", W34),
"backAt": make_add_simplecontent(self, "backAt", "backat", W35),
"note": make_add_simplecontent(self, "note", "notes")
})

self._available = None
self._upsince = None
self._downat = None
self._backat = None
self._notes = HomogeneousList(six.text_type)

@property
def available(self):
return self._available

@available.setter
def available(self, available):
self._available = _convert_boolean(available)

@property
def upsince(self):
return self._upsince

@upsince.setter
def upsince(self, upsince):
self._upsince = upsince

@property
def downat(self):
return self._downat

@downat.setter
def downat(self, downat):
self._downat = downat

@property
def backat(self):
return self._backat

@backat.setter
def backat(self, backat):
self._backat = backat

@property
def notes(self):
return self._notes
Loading

0 comments on commit d71dae6

Please sign in to comment.