Source code for vdat.libvdat.symlink

"""Symlink raw files into a redux directory
"""
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import datetime as dt
import logging
import multiprocessing
import os

from astropy.io import fits
import peewee
import six

from pyhetdex.doc import docstring
import pyhetdex.tools.files.file_tools as ft
import pyhetdex.tools.processes as proc
from pyhetdex.tools import six_ext

import vdat.database as db
import vdat.utilities as vutil
import vdat.config as confp


# lock for the _symlink_cal function
_manager = multiprocessing.Manager()
_lock = _manager.Lock()

# date formats
DATE_HEAD_KEY = '{} {}'
"""Format string for the ``DATE-OBS`` and ``UT`` keys from the fits files"""
FMT_HEAD_KEY = "%Y-%m-%d %H:%M:%S.%f"
"""Format for converting ``DATE_HEAD_KEY`` into a :class:`~datetime.datetime`
instance"""
FMT_DATE_DIR = "%Y%m%d_%H%M%S"
"""Format for converting a :class:`~datetime.datetime` instance into as string
used as directory name"""



@docstring.format_docstring(tmp=vutil.SHOT_FILE)
[docs]def _scan_dirs(redux_dir): """Scan the redux directories updating the '{tmp}' file if the redux directory has changed and filling the database Parameters ---------- redux_dir : string name of the redux directory """ def _yield_rows(): for dir_ in ft.scan_dirs(redux_dir, followlinks=False): # If the shot file exists create the database entry try: shot_file = vutil.read_shot_file(dir_) except six_ext.FileOpenError: # the shot file doesn't exist, continue the loop continue # if the redux directory has changed update it in the shot file if shot_file[0]['redux_dir'] != redux_dir: append = False for l in shot_file: l['path'] = l['path'].replace(l['redux_dir'], redux_dir) l['redux_dir'] = redux_dir vutil.write_to_shot_file(dir_, append=append, **l) append = True if len(shot_file) == 1: shot_file = shot_file[0] elif len(shot_file) > 1: shot_file = vutil.merge_dicts(shot_file, exclude=['id', 'zero_dir', 'cal_dir', 'ifus']) yield shot_file # create the database entries rows = list(_yield_rows()) if not rows: # reduction directory empty return with db.connect(): insert_query = db.VDATDir.insert_many(rows) insert_query.execute()
[docs]def _mkdir(dirname, log, failsafe=True): """Create the directory. If it exists, log it as error and, if ``failsafe`` is False, re-raise the exception Parameters ---------- dirname : string name of the directory to create log : :class:`logging.Logging` instance log messages to this logger safe : bool, optional if true silently ignores :class:`OSError`` due to existing directories Raises ------ :class:`~vdat.utilities.VDATDirError` if the creation fails with a :class:`OSError` and ``failsafe`` is False """ try: os.makedirs(dirname) log.debug("Directory '%s' created", dirname) except OSError as e: if failsafe and e.errno == 17 and 'File exists' in e.strerror: log.debug("Cannot create output directory '%s'. Error: %s", dirname, str(e)) else: six.raise_from(vutil.VDATDirError(e), e)
[docs]def _average_timestamps(dates, infmt=FMT_HEAD_KEY, outfmt=FMT_DATE_DIR): """Average the list of timestamps. Parameters ---------- dates : list of strings strings containing timestamps infmt : strings, optional format of ``dates`` outfmt : string, optional format of the output time stamp Returns ------- avg_timestamp : :class:`datetime.datetime` instance average time string ``avg_timestamp`` formatted according to ``outfmt`` Raises ------ :class:`~vdat.utilities.VDATDateError` if it fails to parse dates from the fits headers """ try: timestamps = [dt.datetime.strptime(d, infmt) for d in dates] except ValueError as e: six.raise_from(vutil.VDATDateError(e), e) avg_deltas = sum((t - timestamps[0] for t in timestamps), dt.timedelta()) // len(timestamps) avg_timestamp = timestamps[0] + avg_deltas return avg_timestamp, avg_timestamp.strftime(outfmt)
[docs]def _find_nearest(q, timestamp, n_nearest=1, nearest_then=None): """Go through the list of query results, order them according to the absolute distance from ``timestamp`` and return the ``n_nearest``. Parameters ---------- q : :class:`peewee.SelectQuery` query to use timestamp : :class:`~datetime.datetime` instance timestamp to use as reference n_nearest : int, optional maximum number of directories returned; set it to negative to return all nearest_then : :class:`~datetime.timedelta` instance if not None, don't consider any directory whose delta time is larger than ``nearest_then``; applied after n_nearest Returns ------- sorted_q : list of query results ordered with respect to the timestamp """ def _key(element): """Create the key for ordering as timedeltas from ``timestamp``""" return abs(element.timestamp - timestamp) def _filter(element): """Test whether the ``timedelta`` is less than ``nearest_then``""" return _key(element) < nearest_then sorted_q = sorted(q, key=_key) if n_nearest > 0: sorted_q = sorted_q[:n_nearest] if nearest_then: sorted_q = list(filter(_filter, sorted_q)) return sorted_q
[docs]def _db_create_references(): # search reference zero and calibration directories with db.connect(): qzro = db.VDATDir.select().where((db.VDATDir.type_ == 'zro') & (db.VDATDir.is_clone == False)) qcal = db.VDATDir.select().where((db.VDATDir.type_ == 'cal') & (db.VDATDir.is_clone == False)) for vdir in db.VDATDir.select().where(db.VDATDir.type_ << ['cal', 'sci']): # for both find the reference zero directory ref_zro = _find_nearest(qzro, vdir.timestamp) if ref_zro: vdir.zero_dir = ref_zro[0] if vdir.type_ == 'sci': ref_cal = _find_nearest(qcal, vdir.timestamp) if ref_cal: vdir.cal_dir = ref_cal[0] vdir.save()