3
3
# Licensed under a 3-clause BSD style license (see LICENSE)
4
4
from __future__ import annotations
5
5
6
+ import ast
6
7
import copy
7
8
import functools
8
9
import importlib
@@ -1479,7 +1480,7 @@ def load_config(yaml_str: str) -> Any:
1479
1480
import sys
1480
1481
1481
1482
process = subprocess .Popen (
1482
- [sys .executable , '-c' , 'import sys, yaml; print(repr( yaml.safe_load(sys.stdin.read() )))' ],
1483
+ [sys .executable , '-c' , 'import sys, yaml; print(yaml.safe_load(sys.stdin.read()))' ],
1483
1484
stdin = subprocess .PIPE ,
1484
1485
stdout = subprocess .PIPE ,
1485
1486
stderr = subprocess .PIPE ,
@@ -1488,11 +1489,59 @@ def load_config(yaml_str: str) -> Any:
1488
1489
if process .returncode != 0 :
1489
1490
raise ValueError (f'Failed to load config: { stderr .decode ()} ' )
1490
1491
1491
- decoded = stdout .strip ().decode ()
1492
+ decoded_string = stdout .strip ().decode ()
1493
+
1494
+ # We need to process inf and nan in a special way since they are not valid Python literals.
1495
+ # We'll replace them with placeholders to avoid parsing errors, and then restore them after parsing.
1496
+
1497
+ INF_PLACEHOLDER = '__PYTHON_INF__'
1498
+ NEG_INF_PLACEHOLDER = '__PYTHON_NEG_INF__'
1499
+ NAN_PLACEHOLDER = '__PYTHON_NAN__'
1500
+
1501
+ # AST Transformer to replace inf/nan names with placeholder constants
1502
+ class _SpecialFloatValuesTransformer (ast .NodeTransformer ):
1503
+ def visit_Name (self , node : ast .Name ) -> ast .AST :
1504
+ if node .id == 'inf' :
1505
+ return ast .Constant (value = INF_PLACEHOLDER )
1506
+ elif node .id == 'nan' :
1507
+ return ast .Constant (value = NAN_PLACEHOLDER )
1508
+ return node
1509
+
1510
+ def visit_UnaryOp (self , node : ast .UnaryOp ) -> ast .AST :
1511
+ if isinstance (node .op , ast .USub ) and isinstance (node .operand , ast .Name ) and node .operand .id == 'inf' :
1512
+ return ast .Constant (value = NEG_INF_PLACEHOLDER )
1513
+ return self .generic_visit (node )
1514
+
1515
+ # Helper to restore placeholder strings to actual float values
1516
+ def _restore_special_floats (data : Any ) -> Any :
1517
+ if isinstance (data , dict ):
1518
+ return {key : _restore_special_floats (value ) for key , value in data .items ()}
1519
+ elif isinstance (data , list ):
1520
+ return [_restore_special_floats (item ) for item in data ]
1521
+ elif isinstance (data , str ):
1522
+ if data == INF_PLACEHOLDER :
1523
+ return float ('inf' )
1524
+ elif data == NEG_INF_PLACEHOLDER :
1525
+ return float ('-inf' )
1526
+ elif data == NAN_PLACEHOLDER :
1527
+ return float ('nan' )
1528
+ return data
1529
+
1492
1530
try :
1493
- # Handle special values like nan, inf, -inf, which are not valid Python literals.
1494
- special_values = {"nan" : float ("nan" ), "inf" : float ("inf" )}
1495
- return eval (decoded , special_values )
1496
- # a single, literal unquoted string
1531
+ if not decoded_string :
1532
+ return None
1533
+
1534
+ # Parse the string as a Python expression
1535
+ ast_node = ast .parse (decoded_string , mode = 'eval' ).body
1536
+
1537
+ # Replace inf/nan with placeholders
1538
+ transformer = _SpecialFloatValuesTransformer ()
1539
+ transformed_ast_node = transformer .visit (ast_node )
1540
+
1541
+ # Evaluate the AST node to get the actual value
1542
+ data_with_placeholders = ast .literal_eval (transformed_ast_node )
1543
+
1544
+ # Restore placeholders to actual float values
1545
+ return _restore_special_floats (data_with_placeholders )
1497
1546
except Exception :
1498
- return decoded
1547
+ return decoded_string
0 commit comments