@@ -345,6 +345,13 @@ def __init__(self, value, metadata):
345345 self .value = value
346346 self .metadata = metadata
347347
348+ def __eq__ (self , other ):
349+ # type: (Any) -> bool
350+ if not isinstance (other , AnnotatedValue ):
351+ return False
352+
353+ return self .value == other .value and self .metadata == other .metadata
354+
348355 @classmethod
349356 def removed_because_raw_data (cls ):
350357 # type: () -> AnnotatedValue
@@ -1052,6 +1059,24 @@ def _is_in_project_root(abs_path, project_root):
10521059 return False
10531060
10541061
1062+ def _truncate_by_bytes (string , max_bytes ):
1063+ # type: (str, int) -> str
1064+ """
1065+ Truncate a UTF-8-encodable string to the last full codepoint so that it fits in max_bytes.
1066+ """
1067+ truncated = string .encode ("utf-8" )[: max_bytes - 3 ].decode ("utf-8" , errors = "ignore" )
1068+
1069+ return truncated + "..."
1070+
1071+
1072+ def _get_size_in_bytes (value ):
1073+ # type: (str) -> Optional[int]
1074+ try :
1075+ return len (value .encode ("utf-8" ))
1076+ except (UnicodeEncodeError , UnicodeDecodeError ):
1077+ return None
1078+
1079+
10551080def strip_string (value , max_length = None ):
10561081 # type: (str, Optional[int]) -> Union[AnnotatedValue, str]
10571082 if not value :
@@ -1060,17 +1085,25 @@ def strip_string(value, max_length=None):
10601085 if max_length is None :
10611086 max_length = DEFAULT_MAX_VALUE_LENGTH
10621087
1063- length = len (value .encode ("utf-8" ))
1088+ byte_size = _get_size_in_bytes (value )
1089+ text_size = len (value )
10641090
1065- if length > max_length :
1066- return AnnotatedValue (
1067- value = value [: max_length - 3 ] + "..." ,
1068- metadata = {
1069- "len" : length ,
1070- "rem" : [["!limit" , "x" , max_length - 3 , max_length ]],
1071- },
1072- )
1073- return value
1091+ if byte_size is not None and byte_size > max_length :
1092+ # truncate to max_length bytes, preserving code points
1093+ truncated_value = _truncate_by_bytes (value , max_length )
1094+ elif text_size is not None and text_size > max_length :
1095+ # fallback to truncating by string length
1096+ truncated_value = value [: max_length - 3 ] + "..."
1097+ else :
1098+ return value
1099+
1100+ return AnnotatedValue (
1101+ value = truncated_value ,
1102+ metadata = {
1103+ "len" : byte_size or text_size ,
1104+ "rem" : [["!limit" , "x" , max_length - 3 , max_length ]],
1105+ },
1106+ )
10741107
10751108
10761109def parse_version (version ):
@@ -1616,3 +1649,18 @@ def nanosecond_time():
16161649def now ():
16171650 # type: () -> float
16181651 return time .perf_counter ()
1652+
1653+
1654+ try :
1655+ from gevent .monkey import is_module_patched
1656+ except ImportError :
1657+
1658+ def is_module_patched (* args , ** kwargs ):
1659+ # type: (*Any, **Any) -> bool
1660+ # unable to import from gevent means no modules have been patched
1661+ return False
1662+
1663+
1664+ def is_gevent ():
1665+ # type: () -> bool
1666+ return is_module_patched ("threading" ) or is_module_patched ("_thread" )
0 commit comments