Secure your code as it's written. Use Snyk Code to scan source code in minutes - no build needed - and fix issues immediately.
"""Convert a `BinOp` `%` formatted str with a tuple on the right to an f-string.
Takes an ast.BinOp representing `"1. %s 2. %s" % (a, b)`
and converted it to a ast.JoinedStr representing `f"1. {a} 2. {b}"`
Args:
node (ast.BinOp): The node to convert to a f-string
Returns ast.JoinedStr (f-string)
"""
format_str = node.left.s
matches = VAR_KEY_PATTERN.findall(format_str)
if len(node.right.elts) != len(matches):
raise FlyntException("string formatting length mismatch")
str_vars = deque(node.right.elts)
segments = []
blocks = deque(VAR_KEY_PATTERN.split(format_str))
segments.append(ast_string_node(blocks.popleft().replace("%%", "%")))
while len(blocks) > 0:
fmt_prefix = blocks.popleft()
fmt_spec = blocks.popleft()
val = str_vars.popleft()
fv = formatted_value(fmt_prefix, fmt_spec, val)
segments.append(fv)
return ast.Subscript(
value=node.right, slice=ast.Index(value=ast.Str(s=key))
)
for block in blocks:
# if this block matches a %(arg)s pattern then inject f-string instead
if DICT_PATTERN.match(block):
prefix, var_key, fmt_str = spec.pop()
fv = formatted_value(prefix, fmt_str, make_fv(var_key))
segments.append(fv)
else:
# no match means it's just a literal string
segments.append(ast.Str(s=block.replace("%%", "%")))
if mapping:
raise FlyntException("Not all keys were matched - probably an error.")
return ast.JoinedStr(segments)
code: str, quote_type: str = QuoteTypes.triple_double
) -> Tuple[str, bool]:
"""Convert a block of code to an f-string
Args:
code: The code to convert.
quote_type: the quote type to use for the transformed result
Returns:
Tuple: resulting code, boolean: was it changed?
"""
try:
tree = ast.parse(code)
converted, changed, str_in_str = fstringify_node(copy.deepcopy(tree))
except (SyntaxError, FlyntException, Exception) as e:
if state.verbose:
if isinstance(e, ConversionRefused):
print(f"Not converting code '{code}': {e}")
print(e)
else:
print(f"Exception {e} during conversion of code '{code}'")
traceback.print_exc()
state.invalid_conversions += 1
return code, False
else:
if changed:
new_code = astor.to_source(converted)
new_code = new_code.strip()
new_code = set_quote_type(
new_code, quote_type if not str_in_str else QuoteTypes.double
)
"Default text alignment has changed between percent fmt and fstrings. "
"Proceeding would result in changed code behaviour."
)
fv = ast_formatted_value(
val, fmt_str=fmt_prefix, conversion=conversion_methods[fmt_spec]
)
else:
fmt_spec = translate_conversion_types.get(fmt_spec, fmt_spec)
if fmt_spec == "d":
if state.aggressive:
val = ast.Call(
func=ast.Name(id="int", ctx=ast.Load()), args=[val], keywords={}
)
fmt_spec = ""
else:
raise FlyntException(
"Skipping %d formatting - fstrings behave differently from % formatting."
)
fv = ast_formatted_value(val, fmt_str=fmt_prefix + fmt_spec)
return fv
def formatted_value(fmt_prefix, fmt_spec, val):
if fmt_spec in conversion_methods:
if not state.aggressive and fmt_prefix:
raise FlyntException(
"Default text alignment has changed between percent fmt and fstrings. "
"Proceeding would result in changed code behaviour."
)
fv = ast_formatted_value(
val, fmt_str=fmt_prefix, conversion=conversion_methods[fmt_spec]
)
else:
fmt_spec = translate_conversion_types.get(fmt_spec, fmt_spec)
if fmt_spec == "d":
if state.aggressive:
val = ast.Call(
func=ast.Name(id="int", ctx=ast.Load()), args=[val], keywords={}
)
fmt_spec = ""
else:
raise FlyntException(
def get_quote_type(self):
assert self.toknum is token.STRING
for qt in QuoteTypes.all:
if self.tokval[: len(qt)] == qt and self.tokval[-len(qt) :] == qt:
return qt
if self.is_legacy_unicode_string():
for qt in QuoteTypes.all:
if self.tokval[1 : len(qt) + 1] == qt and self.tokval[-len(qt) :] == qt:
return qt
raise FlyntException(f"Can't determine quote type of the string {self.tokval}.")
def ast_formatted_value(
val, fmt_str: str = None, conversion=None
) -> ast.FormattedValue:
if astor.to_source(val)[0] == "{":
raise FlyntException(
"values starting with '{' are better left not transformed."
)
if fmt_str:
format_spec = ast.JoinedStr([ast_string_node(fmt_str.replace(":", ""))])
else:
format_spec = None
if conversion is None:
conversion = -1
else:
conversion = ord(conversion.replace("!", ""))
return ast.FormattedValue(value=val, conversion=conversion, format_spec=format_spec)
def transform_binop(node):
if isinstance(
node.right,
tuple(supported_operands),
):
return transform_generic(node)
elif isinstance(node.right, ast.Tuple):
return transform_tuple(node), False
elif isinstance(node.right, ast.Dict):
# todo adapt transform dict to Dict literal
return transform_dict(node), False
raise FlyntException("unexpected `node.right` class")