Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
44 changes: 34 additions & 10 deletions pypeit/par/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

.. include:: ../include/links.rst
"""
import ast

from configobj import ConfigObj
from IPython import embed
Expand Down Expand Up @@ -59,17 +60,40 @@ def recursive_dict_evaluate(d):
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.
# 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:
d[k] = utils.eval_tuple(d[k])
except (PypeItError, SyntaxError) as e:
# The tuple evaluation 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
result = ast.literal_eval(reconstructed)
if isinstance(result, tuple):
if any(isinstance(x, tuple) for x in result):
# Nested tuples or mixed: e.g., "(1,5),(2,6)" or
# "1,(2,6)"
d[k] = list(result)
else:
# Single tuple: e.g., "(2,6)" → wrap in a list
d[k] = [result]
else:
d[k] = result
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
continue

if isinstance(d[k], list):
Expand Down
37 changes: 37 additions & 0 deletions pypeit/tests/test_pypeitpar.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,47 @@
from IPython import embed

import pytest
from configobj import ConfigObj

from pypeit.par import pypeitpar
from pypeit.par import parset
from pypeit.par import util
from pypeit.spectrographs.util import load_spectrograph


def test_detnum_mixed_tuple():
"""Test parsing of detnum with mixed int and tuple values."""

# Mixed int and tuple
cfg = ConfigObj(['[rdx]', 'detnum = 1,(2,6)'])
result = util.recursive_dict_evaluate(dict(cfg))
assert result['rdx']['detnum'] == [1, (2, 6)]

# Single tuple
cfg = ConfigObj(['[rdx]', 'detnum = (2,6)'])
result = util.recursive_dict_evaluate(dict(cfg))
assert result['rdx']['detnum'] == [(2, 6)]

# Plain list of ints
cfg = ConfigObj(['[rdx]', 'detnum = 1,2,3'])
result = util.recursive_dict_evaluate(dict(cfg))
assert result['rdx']['detnum'] == [1, 2, 3]

# Multiple tuples (DEIMOS style)
cfg = ConfigObj(['[rdx]', 'detnum = (1,5),(2,6),(3,7),(4,8)'])
result = util.recursive_dict_evaluate(dict(cfg))
assert result['rdx']['detnum'] == [(1, 5), (2, 6), (3, 7), (4, 8)]

# Single tuple GMOS style
cfg = ConfigObj(['[rdx]', 'detnum = (1,2,3)'])
result = util.recursive_dict_evaluate(dict(cfg))
assert result['rdx']['detnum'] == [(1, 2, 3)]

# Int followed by multiple tuples
cfg = ConfigObj(['[rdx]', 'detnum = 1,(2,6),(3,7)'])
result = util.recursive_dict_evaluate(dict(cfg))
assert result['rdx']['detnum'] == [1, (2, 6), (3, 7)]


def test_framegroup():
pypeitpar.FrameGroupPar()
Expand Down Expand Up @@ -252,3 +287,5 @@ def test_lists():
with pytest.raises(TypeError):
p['calibrations']['alignment']['locations'] = 0.0
_p = pypeitpar.PypeItPar.from_cfg_lines(cfg_lines=p.to_config()) # Once as tuple


Loading