Skip to content

Commit a97e3c0

Browse files
committed
Use ast.literal_eval to parse the config.
1 parent 8dae1b8 commit a97e3c0

File tree

2 files changed

+63
-7
lines changed

2 files changed

+63
-7
lines changed

datadog_checks_base/datadog_checks/base/checks/base.py

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# Licensed under a 3-clause BSD style license (see LICENSE)
44
from __future__ import annotations
55

6+
import ast
67
import copy
78
import functools
89
import importlib
@@ -1479,7 +1480,7 @@ def load_config(yaml_str: str) -> Any:
14791480
import sys
14801481

14811482
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()))'],
14831484
stdin=subprocess.PIPE,
14841485
stdout=subprocess.PIPE,
14851486
stderr=subprocess.PIPE,
@@ -1488,11 +1489,59 @@ def load_config(yaml_str: str) -> Any:
14881489
if process.returncode != 0:
14891490
raise ValueError(f'Failed to load config: {stderr.decode()}')
14901491

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+
14921530
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)
14971546
except Exception:
1498-
return decoded
1547+
return decoded_string

datadog_checks_base/tests/base/checks/test_agent_check.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,13 @@ def test_parse_special_values_in_load_config():
8383
assert "number" in config
8484
assert math.isnan(config["number"])
8585

86+
# Assert that a string with an inf inside it is parsed as a string
87+
assert AgentCheck.load_config("string: \"hi inf\"") == {"string": "hi inf"}
88+
assert AgentCheck.load_config("string: hi inf") == {"string": "hi inf"}
89+
assert AgentCheck.load_config("string: \"this inf is in the middle\"") == {"string": "this inf is in the middle"}
90+
assert AgentCheck.load_config("string: this inf is in the middle") == {"string": "this inf is in the middle"}
91+
assert AgentCheck.load_config("string: infinity") == {"string": "infinity"}
92+
8693

8794
def test_persistent_cache(datadog_agent):
8895
check = AgentCheck()

0 commit comments

Comments
 (0)