Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes passband history adding/parsing #979

Open
wants to merge 3 commits into
base: feature-blending
Choose a base branch
from
Open
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 125 additions & 36 deletions phoebe/atmospheres/passbands.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from phoebe import conf, mpi
from phoebe.utils import _bytes
from tqdm import tqdm
from itertools import product

import ndpolator

Expand All @@ -17,14 +16,13 @@
import numpy as np
from scipy import interpolate, integrate
from scipy.optimize import curve_fit as cfit
from datetime import datetime
from datetime import datetime as dt
import libphoebe
import os
import sys
import glob
import shutil
import json
import time
import re

# NOTE: python3 only
Expand All @@ -38,7 +36,7 @@

# define the URL to query for online passbands. See tables.phoebe-project.org
# repo for the source-code of the server
_url_tables_server = 'http://tables.phoebe-project.org'
_url_tables_server = 'https://staging.phoebe-project.org'
Comment on lines -41 to +39
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this definitely shouldn't be merged, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be merged into feature-blending. Staging is running the blending version of phoebe within the server.

# comment out the following line if testing tables.phoebe-project.org server locally:
# _url_tables_server = 'http://localhost:5555'

Expand Down Expand Up @@ -305,33 +303,57 @@ def log(self):

def add_to_history(self, history, max_length=46):
"""
Adds a history entry to the passband file header.
Adds a history entry to the Passband instance.

Parameters
----------
* `comment` (string, required): comment to be added to the passband header.
* `history` (string, required): comment to be added to the passband header.
* `max_length` (int, optional, default=46): maximum length of the history entry.

Raises
------
* ValueError: if the history entry is not a string.
* ValueError: if the history entry exceeds the maximum length.

Examples
--------
>>> pb = phoebe.get_passband('Johnson:V')
>>> pb.add_to_history('passband updated.')
"""

if not isinstance(history, str):
raise ValueError('passband header history entries must be strings.')
if len(history) > max_length:
raise ValueError(f'comment length should not exceed {max_length} characters.')

self.history.append(f'{time.ctime()}: {history}')
self.history.append(f'{_generate_timestamp()}: {history}')

def add_comment(self, comment):
def add_comment(self, comment, max_length=46):
"""
Adds a comment to the passband file header.
Adds a comment entry to the Passbands instance.

Parameters
----------
* `comment` (string, required): comment to be added to the passband header.
* `max_length` (int, optional, default=46): maximum length of the comment entry.

Raises
------
* ValueError: if the comment is not a string.
* ValueError: if the comment exceeds the maximum length.

Examples
--------
>>> pb = phoebe.get_passband('Johnson:V')
>>> pb.add_comment('this is a comment.')
"""

if not isinstance(comment, str):
raise ValueError('passband header comments must be strings.')
if len(comment) > max_length:
raise ValueError(f'comment length should not exceed {max_length} characters.')

self.comments.append(comment)
self.comments.append(comment)
aprsa marked this conversation as resolved.
Show resolved Hide resolved

def on_updated_ptf(self, ptf, wlunits=u.AA, oversampling=1, ptf_order=3):
"""
Expand Down Expand Up @@ -380,7 +402,7 @@ def save(self, archive, overwrite=True, update_timestamp=True, export_inorm_tabl
"""

# Timestamp is used for passband versioning.
timestamp = time.ctime() if update_timestamp else self.timestamp
timestamp = _generate_timestamp() if update_timestamp else self.timestamp

header = fits.Header()
header['PHOEBEVN'] = phoebe_version
Expand Down Expand Up @@ -1094,13 +1116,12 @@ def export_legacy_ldcoeffs(self, models, atm='ck2004', filename=None, intens_wei
grid = self.ndp[atm].table['ld@photon'][1] if intens_weighting == 'photon' else self.ndp[atm].table['ld@energy'][1]

if filename is not None:
import time
f = open(filename, 'w')
f.write('# PASS_SET %s\n' % self.pbset)
f.write('# PASSBAND %s\n' % self.pbname)
f.write(f'# PASS_SET {self.pbset}\n')
f.write(f'# PASSBAND {self.pbname}\n')
f.write('# VERSION 1.0\n\n')
f.write('# Exported from PHOEBE-2 passband on %s\n' % (time.ctime()))
f.write('# The coefficients are computed for the %s-weighted regime from %s atmospheres.\n\n' % (intens_weighting, atm))
f.write(f'# Exported from PHOEBE-2 passband on {_generate_timestamp()}\n')
f.write(f'# The coefficients are computed for the {intens_weighting}-weighted regime from {atm} atmospheres.\n\n')

mods = np.loadtxt(models)
for mod in mods:
Expand Down Expand Up @@ -1885,12 +1906,50 @@ def bindex(self, teffs=5772., loggs=4.43, abuns=0.0, mus=1.0, atm='ck2004', inte
raise ValueError('Atmosphere parameters out of bounds: Teff=%s, logg=%s, abun=%s' % (Teff[nanmask], logg[nanmask], abun[nanmask]))
return retval


def _generate_timestamp():
"""
Generates a timestamp string in the format 'YYYY-MM-DD HH:MM:SS'.

Returns
----------
* (string) timestamp string
"""

return dt.now().strftime("%Y-%m-%d %H:%M:%S")


def _timestamp_to_dt(timestamp):
"""
Converts a timestamp string to a datetime object.

Arguments
----------
* `timestamp` (string): timestamp string in the format 'YYYY-MM-DD HH:MM:SS' or 'Mon Jan 01 00:00:00 2000'

Raises
----------
* TypeError: if timestamp is not of type string
* ValueError: if timestamp is not in the correct format

Returns
----------
* (datetime) datetime object
"""

if timestamp is None:
return None
elif not isinstance(timestamp, str):
if not isinstance(timestamp, str):
raise TypeError("timestamp not of type string")
return datetime.strptime(timestamp, "%a %b %d %H:%M:%S %Y")

try:
# if timestamp is in the new format, 'YYYY-MM-DD HH:MM:SS':
return dt.strptime(timestamp, "%Y-%m-%d %H:%M:%S")
except ValueError:
# if timestamp is in the old format, 'Mon Jan 01 00:00:00 2000':
return dt.strptime(timestamp, "%a %b %d %H:%M:%S %Y")
else:
raise ValueError(f'timestamp "{timestamp}" not in the correct format')

def _init_passband(fullpath, check_for_update=True):
"""
Expand Down Expand Up @@ -2177,34 +2236,64 @@ def list_passband_online_history(passband, since_installed=True):
----------
* (dict): dictionary with timestamps as keys and messages and values.
"""

if passband not in list_online_passbands(repeat_errors=False):
raise ValueError("'{}' passband not available online".format(passband))
raise ValueError(f'{passband} passband not available online')

url = '{}/pbs/history/{}?phoebe_version={}'.format(_url_tables_server, passband, phoebe_version)
url = f'{_url_tables_server}/pbs/history/{passband}?phoebe_version={phoebe_version}'

try:
resp = urlopen(url, timeout=3)
except Exception as err:
msg = "connection to online passbands at {} could not be established. Check your internet connection or try again later. If the problem persists and you're using a Mac, you may need to update openssl (see http://phoebe-project.org/help/faq).".format(_url_tables_server)
msg += " Original error from urlopen: {} {}".format(err.__class__.__name__, str(err))
msg = f"connection to online passbands at {_url_tables_server} could not be established. Check your internet connection or try again later. If the problem persists and you're using a Mac, you may need to update openssl (see http://phoebe-project.org/help/faq)."
msg += f" Original error from urlopen: {err.__class__.__name__} {str(err)}"

logger.warning(msg)
return {str(time.ctime()): "could not retrieve history entries"}
else:
try:
all_history = json.loads(resp.read().decode('utf-8'), object_pairs_hook=parse_json).get('passband_history', {}).get(passband, {})
except Exception as err:
msg = "Parsing response from online passbands at {} failed.".format(_url_tables_server)
msg += " Original error from json.loads: {} {}".format(err.__class__.__name__, str(err))
return {_generate_timestamp(): "could not retrieve history entries"}

logger.warning(msg)
return {str(time.ctime()): "could not parse history entries"}
def _parse(entry):
# parses the history entry into a timestamp and message
parts = entry.split(':')

if since_installed:
installed_timestamp = _timestamp_to_dt(_pbtable.get(passband, {}).get('timestamp', None))
return {k:v for k,v in all_history.items() if installed_timestamp < _timestamp_to_dt(k)} if installed_timestamp is not None else all_history
else:
return all_history
# try the new format first: 'YYYY-MM-DD HH:MM:SS'
for i in range(len(parts)-1, 0, -1):
try:
timestamp = ':'.join(parts[:i])
dt.strptime(timestamp, "%Y-%m-%d %H:%M:%S")
message = ':'.join(parts[i:]).strip()
return timestamp, message
except ValueError:
continue

# try the old format: 'Mon Jan 01 00:00:00 2000'
for i in range(len(parts)-1, 0, -1):
try:
timestamp = ':'.join(parts[:i])
ts = dt.strptime(timestamp, "%a %b %d %H:%M:%S %Y")
# if successful, convert to the new format:
timestamp = ts.strftime('%Y-%m-%d %H:%M:%S')
message = ':'.join(parts[i:]).strip()
return timestamp, message
except ValueError:
continue

try:
all_history = json.loads(resp.read().decode('utf-8'), object_pairs_hook=parse_json).get('passband_history', {}).get(passband, {})
# convert all_history to dict with timestamps as keys and messages as values:
all_history = dict([_parse(entry) for entry in all_history])

except Exception as err:
msg = f"Parsing response from online passbands at {_url_tables_server} failed."
msg += f" Original error from json.loads: {err.__class__.__name__} {str(err)}"

logger.warning(msg)
return {_generate_timestamp(): "could not parse history entries"}

if since_installed:
installed_timestamp = _timestamp_to_dt(_pbtable.get(passband, {}).get('timestamp', None))
return {k:v for k,v in all_history.items() if installed_timestamp < _timestamp_to_dt(k)} if installed_timestamp is not None else all_history
else:
return all_history

def update_passband_available(passband, history_dict=False):
"""
Expand Down
Loading