1+ from collections .abc import Mapping
2+
13from django .conf import settings
24from rest_framework .request import Request
35from rest_framework .response import Response
1012from sentry .runner .settings import configure , discover_configs
1113
1214
15+ def _coerce_early_feature_flag_value (value : object ) -> bool | None :
16+ """
17+ Map API input to a bool for SENTRY_FEATURES. Accepts JSON booleans, 0/1
18+ (common in scripts), and the strings "true"/"false" (case-insensitive).
19+ Returns None if the value cannot be interpreted safely.
20+ """
21+ if isinstance (value , bool ):
22+ return value
23+ if isinstance (value , int ):
24+ if value == 1 :
25+ return True
26+ if value == 0 :
27+ return False
28+ return None
29+ if isinstance (value , str ):
30+ lowered = value .lower ()
31+ if lowered == "true" :
32+ return True
33+ if lowered == "false" :
34+ return False
35+ return None
36+ return None
37+
38+
1339@all_silo_endpoint
1440class InternalFeatureFlagsEndpoint (Endpoint ):
1541 permission_classes = (SuperuserPermission ,)
@@ -36,18 +62,38 @@ def put(self, request: Request) -> Response:
3662 if not settings .SENTRY_SELF_HOSTED :
3763 return Response ("You are not self-hosting Sentry." , status = 403 )
3864
39- data = request .data .keys ()
40- valid_feature_flags = [flag for flag in data if SENTRY_EARLY_FEATURES .get (flag , False )]
65+ payload : object = request .data
66+ if not isinstance (payload , Mapping ):
67+ return Response (
68+ {"detail" : "Feature flag updates must be a JSON object." },
69+ status = 400 ,
70+ )
71+
72+ valid_feature_flags = [flag for flag in payload if flag in SENTRY_EARLY_FEATURES ]
73+ coerced_values : dict [str , bool ] = {}
74+ for valid_flag in valid_feature_flags :
75+ coerced = _coerce_early_feature_flag_value (payload .get (valid_flag ))
76+ if coerced is None :
77+ return Response (
78+ {
79+ "detail" : (
80+ f'Feature flag "{ valid_flag } " must be true or false '
81+ f"(boolean, 0 or 1, or the string true or false)."
82+ )
83+ },
84+ status = 400 ,
85+ )
86+ coerced_values [valid_flag ] = coerced
87+
4188 _ , py , yml = discover_configs ()
4289 # Open the file for reading and writing
4390 with open (py , "r+" ) as file :
4491 lines = file .readlines ()
4592 # print(lines)
4693 for valid_flag in valid_feature_flags :
4794 match_found = False
48- new_string = (
49- f'\n SENTRY_FEATURES["{ valid_flag } "]={ request .data .get (valid_flag , False )} \n '
50- )
95+ python_bool = "True" if coerced_values [valid_flag ] else "False"
96+ new_string = f'\n SENTRY_FEATURES["{ valid_flag } "]={ python_bool } \n '
5197 # Search for the string match and update lines
5298 for i , line in enumerate (lines ):
5399 if valid_flag in line :
0 commit comments