# !usr/bin/env python
# -*- coding: utf-8 -*-
#
# Licensed under a 3-clause BSD license.
#
# @Author: Brian Cherinka
# @Date: 2018-06-21 17:01:09
# @Last modified by: José Sánchez-Gallego (gallegoj@uw.edu)
# @Last Modified time: 2018-10-17 00:22:19
from __future__ import absolute_import, division, print_function
import abc
import os
import time
import six
import marvin
import marvin.tools.plate
from marvin.core.exceptions import MarvinError
import sdss_access.path
import sdss_access.sync
__ALL__ = ['VACContainer', 'VACMixIn']
class VACContainer(object):
def __repr__(self):
return '<VACContainer ({0})>'.format(', '.join(map(repr, list(self))))
def __dir__(self):
props = []
for value in self.__class__.__dict__.keys():
if not value.startswith('_'):
props.append(value)
return props
def __getitem__(self, value):
return getattr(self, value)
def __iter__(self):
for value in self.__dir__():
yield value
[docs]class VACMixIn(object, six.with_metaclass(abc.ABCMeta)):
"""MixIn that allows VAC integration in Marvin.
This parent class provides common tools for downloading data using
sdss_access or directly from the sandbox. `~VACMixIn.get_vacs` returns a
container with properties pointing to all the VACs that subclass from
`.VACMixIn`. In general, VACs can be added to a class in the following way:
.. code-block:: python
from marvin.contrib.vacs.base import VACMixIn
class Maps(MarvinToolsClass):
def __init__(self, *args, **kwargs):
...
self.vacs = VACMixIn.get_vacs(self)
and then the VACs can be accessed as properties in ``my_map.vacs``.
"""
# The name and description of the VAC.
name = None
description = None
def __init__(self):
if not sdss_access.sync.RsyncAccess:
raise MarvinError('sdss_access is not installed')
else:
self._release = marvin.config.release
is_public = 'DR' in self._release
rsync_release = self._release.lower() if is_public else None
self.rsync_access = sdss_access.sync.RsyncAccess(public=is_public, release=rsync_release)
def __repr__(self):
return '<VAC (name={0}, description={1})>'.format(self.name, self.description)
[docs] @abc.abstractmethod
def get_data(self, parent_object):
"""Returns VAC data that matches the `parent_object` target.
This method must be overridden in each subclass of `VACMixIn`. Details
will depend on the exact implementation and the type of VAC, but in
general each version of this method must:
* Check whether the VAC file exists locally.
* If it does not, download it using `~VACMixIn.download_vac`.
* Open the file using the appropriate library.
* Retrieve the VAC data matching ``parent_object``. Usually one will
use attributes in ``parent_object`` such as ``.mangaid`` or
``.plate`` to perform the match.
* Return the VAC data in whatever format is appropriate.
"""
pass
[docs] @staticmethod
def get_vacs(parent_object):
"""Returns a container with all the VACs subclassing from `VACMixIn`.
Because this method loops over ``VACMixIn.__subclasses__()``, all the
class that inherit from `VACMixIn` and that must be included in the
container need to have been imported before calling
`~VACMixIn.get_vacs`.
Parameters
----------
parent_object : object
The object to which the VACs are being attached. It will be passed
to `~VACMixIn.get_data` when the subclass of `VACMixIn` is
called.
Returns
-------
vac_container : object
An instance of a class that contains just a list of properties, one
for to each on of the VACs that subclass from `VACMixIn`.
"""
vac_container = VACContainer()
for subvac in VACMixIn.__subclasses__():
# Excludes VACs from showing up in Plate
if issubclass(parent_object.__class__, marvin.tools.plate.Plate):
continue
# Only shows VACs if in the include list.
if (hasattr(subvac, 'include') and subvac.include is not None and
not issubclass(parent_object.__class__, subvac.include)):
continue
# We need to set sv=subvac in the lambda function to prevent
# a cell-var-from-loop issue.
if parent_object._release in subvac.version:
setattr(VACContainer, subvac.name,
property(lambda self, sv=subvac: sv().get_data(parent_object)))
return vac_container
[docs] def download_vac(self, name=None, path_params={}, verbose=True):
"""Download the VAC using rsync and returns the local path."""
if name is None:
name = self.name
assert name in self.rsync_access.templates, 'VAC path has not been set in the tree.'
if verbose:
marvin.log.info('downloading file for VAC {0!r}'.format(self.name))
self.rsync_access.remote()
self.rsync_access.add(name, **path_params)
self.rsync_access.set_stream()
self.rsync_access.commit()
paths = self.rsync_access.get_paths()
# adding a millisecond pause for download to finish and file existence to register
time.sleep(0.001)
return paths[0] # doing this for single files, may need to change
[docs] def get_path(self, name=None, path_params={}):
"""Returns the local VAC path or False if it does not exist."""
if name is None:
name = self.name
# check for and expand any wildcards present in the path_params
if self.rsync_access.any(name, **path_params):
files = self.rsync_access.expand(name, **path_params)
return files[0]
else:
return False
[docs] def file_exists(self, name=None, path_params={}):
"""Check whether a file exists."""
if name is None:
name = self.name
if os.path.exists(self.get_path(name=name, path_params=path_params)):
return True
return False