# -*- coding: utf-8 -*-
"""
Utility functions for PypeIt parameter sets
.. include:: ../include/links.rst
"""
import ast
from configobj import ConfigObj
from IPython import embed
from pypeit import PypeItError
from pypeit import utils
[docs]
def _eval_ignore():
"""Provides a list of strings that should not be evaluated."""
return [ 'open', 'file', 'dict', 'list', 'tuple' ]
[docs]
def recursive_dict_evaluate(d):
"""
Recursively run :func:`eval` on each element of the provided
dictionary.
A raw read of a configuration file with `configobj`_ results in a
dictionary that contains strings or lists of strings. However, when
assigning the values for the various ParSets, the `from_dict`
methods expect the dictionary values to have the appropriate type.
E.g., the `configobj`_ will have something like d['foo'] = '1', when
the `from_dict` method expects the value to be an integer (d['foo']
= 1).
This function tries to evaluate *all* dictionary values, except for
those listed above in the :func:`_eval_ignore` function. Any value
in this list or where::
ast_literal_eval(d[k]) for k in d.keys()
raises an exception is returned as the original string.
This is currently only used in
:func:`~pypeit.par.pypeitpar.PypitPar.from_cfg_file`; see further comments
there.
Args:
d (dict):
Dictionary of values to evaluate
Returns:
dict: Identical to input dictionary, but with all string values
replaced with the result of `eval(d[k])` for all `k` in
`d.keys()`.
"""
ignore = _eval_ignore()
for k in d.keys():
if isinstance(d[k], dict):
# Recursive call to deal with nested dictionaries
d[k] = recursive_dict_evaluate(d[k])
continue
if isinstance(d[k], list) and any(['(' in e for e in d[k]]):
# NOTE: This enables syntax for constructing one or more tuples,
# including mixed lists of plain values and tuples (e.g.,
# "detnum = 1,(2,6)").
#
# First, try reconstructing the full expression and evaluating it
# with ast.literal_eval. This correctly handles mixed cases like
# "1,(2,6)" that _eval_iter cannot parse.
reconstructed = ','.join(d[k])
try:
result = ast.literal_eval(reconstructed)
except (ValueError, SyntaxError):
# ast.literal_eval failed; fall back to the legacy eval_tuple
# approach.
try:
d[k] = utils.eval_tuple(d[k])
except (PypeItError, SyntaxError):
# The tuple evaluation also failed. Assume that this can
# be handled later in the code and leave the dictionary
# element unaltered.
#
# SyntaxError is raised for entries that include a tuple
# for the mosaic and a series of locations in the mosaiced
# image, like add_slits, rm_slits, and manual.
pass
else:
# Wrap plain tuple e.g. "(2,6)" → [(2,6)]; convert nested/mixed
# tuple e.g. "(1,5),(2,6)" or "1,(2,6)" to a list.
if isinstance(result, tuple):
result = [result] if not any(isinstance(x, tuple) for x in result) else list(result)
d[k] = result
continue
if isinstance(d[k], list):
replacement = []
for v in d[k]:
if v in ignore:
replacement += [ v ]
else:
try:
replacement += [ utils.ast_literal_eval(v) ]
except:
replacement += [ v ]
d[k] = replacement
continue
try:
d[k] = utils.ast_literal_eval(d[k]) if d[k] not in ignore else d[k]
except:
pass
return d
[docs]
def parset_to_dict(par):
"""
Convert the provided parset into a dictionary.
Args:
par (ParSet):
Returns:
dict: Converted ParSet
"""
try:
d = dict(ConfigObj(par.to_config(section_name='tmp'))['tmp'])
except:
d = dict(ConfigObj(par.to_config()))
return recursive_dict_evaluate(d)