Prefer breaking the implicit string concatenation over breaking before % (#5947)

This commit is contained in:
Micha Reiser 2023-07-24 18:30:42 +02:00 committed by GitHub
parent 42d969f19f
commit fdb3c8852f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 637 additions and 80 deletions

View file

@ -0,0 +1,120 @@
raise ImproperlyConfigured(
"The app module %r has multiple filesystem locations (%r); "
"you must configure this app with an AppConfig subclass "
"with a 'path' class attr ibute." % (module, paths)
)
raise ImproperlyConfigured(
"The app module %r has multiple filesystem locations (%r); "
"you must configure this app with an AppConfig subclass "
"with a 'path' class attr ibute."
%
# comment
(module, paths)
)
# Only important in parenthesized context because implicit string continuation otherwise doesn't expand
"The app module %r has multiple filesystem locations (%r); " "you must configure this app with an AppConfig subclass " "with a 'path' class attribute." % (
module,
paths,
)
("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" "cccccccccccccccccccccccccccccccccccccccccccc" % (aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, b, c, d))
("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" "cccccccccccccccccccccccccccccccccccccccccccc" % aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa)
def test():
return (
"\n%(modified_count)s %(identifier)s %(action)s"
"%(destination)s%(unmodified)s%(post_processed)s."
) % {
"modified_count": modified_count,
"identifier": "static file" + ("" if modified_count == 1 else "s"),
"action": "symlinked" if self.symlink else "copied",
"destination": (" to '%s'" % destination_path if destination_path else ""),
"unmodified": (
", %s unmodified" % unmodified_count if collected["unmodified"] else ""
),
"post_processed": (
collected["post_processed"]
and ", %s post-processed" % post_processed_count
or ""
),
}
# trailing expression comment
self._assert_skipping(
SkipTestCase("test_foo").test_foo,
ValueError,
"skipUnlessDBFeature cannot be used on test_foo (test_utils.tests."
"SkippingTestCase.test_skip_unless_db_feature.<locals>.SkipTestCase%s) "
"as SkippingTestCase.test_skip_unless_db_feature.<locals>.SkipTestCase "
"doesn't allow queries against the 'default' database."
# Python 3.11 uses fully qualified test name in the output.
% (".test_foo" if PY311 else ""),
)
# dangling operator comment
self._assert_skipping(
SkipTestCase("test_foo").test_foo,
ValueError,
"skipUnlessDBFeature cannot be used on test_foo (test_utils.tests."
"SkippingTestCase.test_skip_unless_db_feature.<locals>.SkipTestCase%s) "
"as SkippingTestCase.test_skip_unless_db_feature.<locals>.SkipTestCase "
"doesn't allow queries against the 'default' database."
% # Python 3.11 uses fully qualified test name in the output.
(".test_foo" if PY311 else ""),
)
# Black keeps as many operands as fit on the same line as the `%`. Ruff does not. This is intentional as these are rare and complicated things significantly
(
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
"cccccccccccccccccccccccccc"
% aaaaaaaaaaaa
+ x
)
(
b + c + d +
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
"cccccccccccccccccccccccccc"
% aaaaaaaaaaaa
+ x
)
(
b < c > d <
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
"cccccccccccccccccccccccccc"
% aaaaaaaaaaaa
> x
)
self.assertEqual(
response.status_code,
status_code,
msg_prefix + "Couldn't retrieve content: Response code was %d"
" (expected %d)" % (response.status_code, status_code),
)
def test():
return (
"((TIME_TO_SEC(%(lhs)s) * 1000000 + MICROSECOND(%(lhs)s)) -"
" (TIME_TO_SEC(%(rhs)s) * 1000000 + MICROSECOND(%(rhs)s)))"
) % {"lhs": lhs_sql, "rhs": rhs_sql}, tuple(lhs_params) * 2 + tuple(rhs_params) * 2
def test2():
return "RETURNING %s INTO %s" % (
", ".join(field_names),
", ".join(["%s"] * len(params)),
), tuple(params)
def test3():
return (
"(CASE WHEN JSON_TYPE(%s, %%s) IN (%s) "
"THEN JSON_TYPE(%s, %%s) ELSE JSON_EXTRACT(%s, %%s) END)"
) % (lhs, datatype_values, lhs, lhs), (tuple(params) + (json_path,)) * 3

View file

@ -1,19 +1,27 @@
use crate::comments::{trailing_comments, trailing_node_comments};
use crate::expression::parentheses::{
in_parentheses_only_group, in_parentheses_only_soft_line_break,
in_parentheses_only_soft_line_break_or_space, is_expression_parenthesized, NeedsParentheses,
OptionalParentheses,
};
use crate::expression::Parentheses;
use crate::prelude::*;
use crate::FormatNodeRule;
use ruff_formatter::{write, FormatOwnedWithRule, FormatRefWithRule, FormatRuleWithOptions};
use ruff_python_ast::node::{AnyNodeRef, AstNode};
use std::iter;
use rustpython_parser::ast::{
Constant, Expr, ExprAttribute, ExprBinOp, ExprConstant, ExprUnaryOp, Operator, UnaryOp,
};
use smallvec::SmallVec;
use std::iter;
use ruff_formatter::{
format_args, write, FormatOwnedWithRule, FormatRefWithRule, FormatRuleWithOptions,
};
use ruff_python_ast::node::{AnyNodeRef, AstNode};
use ruff_python_ast::str::is_implicit_concatenation;
use crate::comments::{trailing_comments, trailing_node_comments};
use crate::expression::expr_constant::ExprConstantLayout;
use crate::expression::parentheses::{
in_parentheses_only_group, in_parentheses_only_soft_line_break,
in_parentheses_only_soft_line_break_or_space, is_expression_parenthesized, parenthesized,
NeedsParentheses, OptionalParentheses,
};
use crate::expression::string::StringLayout;
use crate::expression::Parentheses;
use crate::prelude::*;
use crate::FormatNodeRule;
#[derive(Default)]
pub struct FormatExprBinOp {
@ -33,11 +41,56 @@ impl FormatNodeRule<ExprBinOp> for FormatExprBinOp {
fn fmt_fields(&self, item: &ExprBinOp, f: &mut PyFormatter) -> FormatResult<()> {
let comments = f.context().comments().clone();
match Self::layout(item, f.context()) {
BinOpLayout::LeftString(expression) => {
let right_has_leading_comment = f
.context()
.comments()
.has_leading_comments(item.right.as_ref());
let format_right_and_op = format_with(|f| {
if right_has_leading_comment {
space().fmt(f)?;
} else {
soft_line_break_or_space().fmt(f)?;
}
item.op.format().fmt(f)?;
if right_has_leading_comment {
hard_line_break().fmt(f)?;
} else {
space().fmt(f)?;
}
group(&item.right.format()).fmt(f)
});
let format_left = format_with(|f: &mut PyFormatter| {
let format_string =
expression.format().with_options(ExprConstantLayout::String(
StringLayout::ImplicitConcatenatedBinaryLeftSide,
));
if is_expression_parenthesized(expression.into(), f.context().source()) {
parenthesized("(", &format_string, ")").fmt(f)
} else {
format_string.fmt(f)
}
});
group(&format_args![format_left, group(&format_right_and_op)]).fmt(f)
}
BinOpLayout::Default => {
let format_inner = format_with(|f: &mut PyFormatter| {
let source = f.context().source();
let binary_chain: SmallVec<[&ExprBinOp; 4]> = iter::successors(Some(item), |parent| {
let binary_chain: SmallVec<[&ExprBinOp; 4]> =
iter::successors(Some(item), |parent| {
parent.left.as_bin_op_expr().and_then(|bin_expression| {
if is_expression_parenthesized(bin_expression.as_any_node_ref(), source) {
if is_expression_parenthesized(
bin_expression.as_any_node_ref(),
source,
) {
None
} else {
Some(bin_expression)
@ -81,7 +134,9 @@ impl FormatNodeRule<ExprBinOp> for FormatExprBinOp {
)?;
// Format the operator on its own line if the right side has any leading comments.
if comments.has_leading_comments(right.as_ref()) || !operator_comments.is_empty() {
if comments.has_leading_comments(right.as_ref())
|| !operator_comments.is_empty()
{
hard_line_break().fmt(f)?;
} else if needs_space {
space().fmt(f)?;
@ -103,6 +158,8 @@ impl FormatNodeRule<ExprBinOp> for FormatExprBinOp {
in_parentheses_only_group(&format_inner).fmt(f)
}
}
}
fn fmt_dangling_comments(&self, _node: &ExprBinOp, _f: &mut PyFormatter) -> FormatResult<()> {
// Handled inside of `fmt_fields`
@ -110,6 +167,34 @@ impl FormatNodeRule<ExprBinOp> for FormatExprBinOp {
}
}
impl FormatExprBinOp {
fn layout<'a>(bin_op: &'a ExprBinOp, context: &PyFormatContext) -> BinOpLayout<'a> {
if let Some(
constant @ ExprConstant {
value: Constant::Str(_),
range,
..
},
) = bin_op.left.as_constant_expr()
{
let comments = context.comments();
if bin_op.op == Operator::Mod
&& context.node_level().is_parenthesized()
&& !comments.has_dangling_comments(constant)
&& !comments.has_dangling_comments(bin_op)
&& is_implicit_concatenation(&context.source()[*range])
{
BinOpLayout::LeftString(constant)
} else {
BinOpLayout::Default
}
} else {
BinOpLayout::Default
}
}
}
const fn is_simple_power_expression(expr: &ExprBinOp) -> bool {
expr.op.is_pow() && is_simple_power_operand(&expr.left) && is_simple_power_operand(&expr.right)
}
@ -132,6 +217,24 @@ const fn is_simple_power_operand(expr: &Expr) -> bool {
}
}
#[derive(Copy, Clone, Debug)]
enum BinOpLayout<'a> {
Default,
/// Specific layout for an implicit concatenated string using the "old" c-style formatting.
///
/// ```python
/// (
/// "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa %s"
/// "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb %s" % (a, b)
/// )
/// ```
///
/// Prefers breaking the string parts over breaking in front of the `%` because it looks better if it
/// is kept on the same line.
LeftString(&'a ExprConstant),
}
#[derive(Copy, Clone)]
pub struct FormatOperator;

View file

@ -1,17 +1,37 @@
use ruff_text_size::{TextLen, TextRange};
use rustpython_parser::ast::{Constant, ExprConstant, Ranged};
use ruff_formatter::FormatRuleWithOptions;
use ruff_python_ast::node::AnyNodeRef;
use ruff_python_ast::str::is_implicit_concatenation;
use crate::expression::number::{FormatComplex, FormatFloat, FormatInt};
use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses};
use crate::expression::string::{FormatString, StringPrefix, StringQuotes};
use crate::expression::string::{FormatString, StringLayout, StringPrefix, StringQuotes};
use crate::prelude::*;
use crate::{not_yet_implemented_custom_text, FormatNodeRule};
#[derive(Default)]
pub struct FormatExprConstant;
pub struct FormatExprConstant {
layout: ExprConstantLayout,
}
#[derive(Copy, Clone, Debug, Default)]
pub enum ExprConstantLayout {
#[default]
Default,
String(StringLayout),
}
impl FormatRuleWithOptions<ExprConstant, PyFormatContext<'_>> for FormatExprConstant {
type Options = ExprConstantLayout;
fn with_options(mut self, options: Self::Options) -> Self {
self.layout = options;
self
}
}
impl FormatNodeRule<ExprConstant> for FormatExprConstant {
fn fmt_fields(&self, item: &ExprConstant, f: &mut PyFormatter) -> FormatResult<()> {
@ -31,7 +51,13 @@ impl FormatNodeRule<ExprConstant> for FormatExprConstant {
Constant::Int(_) => FormatInt::new(item).fmt(f),
Constant::Float(_) => FormatFloat::new(item).fmt(f),
Constant::Complex { .. } => FormatComplex::new(item).fmt(f),
Constant::Str(_) => FormatString::new(item).fmt(f),
Constant::Str(_) => {
let string_layout = match self.layout {
ExprConstantLayout::Default => StringLayout::Default,
ExprConstantLayout::String(layout) => layout,
};
FormatString::new(item).with_layout(string_layout).fmt(f)
}
Constant::Bytes(_) => {
not_yet_implemented_custom_text(r#"b"NOT_YET_IMPLEMENTED_BYTE_STRING""#).fmt(f)
}

View file

@ -18,17 +18,36 @@ use crate::QuoteStyle;
pub(super) struct FormatString<'a> {
constant: &'a ExprConstant,
layout: StringLayout,
}
#[derive(Default, Copy, Clone, Debug)]
pub enum StringLayout {
#[default]
Default,
ImplicitConcatenatedBinaryLeftSide,
}
impl<'a> FormatString<'a> {
pub(super) fn new(constant: &'a ExprConstant) -> Self {
debug_assert!(constant.value.is_str());
Self { constant }
Self {
constant,
layout: StringLayout::Default,
}
}
pub(super) fn with_layout(mut self, layout: StringLayout) -> Self {
self.layout = layout;
self
}
}
impl<'a> Format<PyFormatContext<'_>> for FormatString<'a> {
fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
match self.layout {
StringLayout::Default => {
let string_range = self.constant.range();
let string_content = f.context().locator().slice(string_range);
@ -38,6 +57,11 @@ impl<'a> Format<PyFormatContext<'_>> for FormatString<'a> {
FormatStringPart::new(string_range).fmt(f)
}
}
StringLayout::ImplicitConcatenatedBinaryLeftSide => {
FormatStringContinuation::new(self.constant).fmt(f)
}
}
}
}
struct FormatStringContinuation<'a> {

View file

@ -0,0 +1,284 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/binary_implicit_string.py
---
## Input
```py
raise ImproperlyConfigured(
"The app module %r has multiple filesystem locations (%r); "
"you must configure this app with an AppConfig subclass "
"with a 'path' class attr ibute." % (module, paths)
)
raise ImproperlyConfigured(
"The app module %r has multiple filesystem locations (%r); "
"you must configure this app with an AppConfig subclass "
"with a 'path' class attr ibute."
%
# comment
(module, paths)
)
# Only important in parenthesized context because implicit string continuation otherwise doesn't expand
"The app module %r has multiple filesystem locations (%r); " "you must configure this app with an AppConfig subclass " "with a 'path' class attribute." % (
module,
paths,
)
("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" "cccccccccccccccccccccccccccccccccccccccccccc" % (aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, b, c, d))
("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" "cccccccccccccccccccccccccccccccccccccccccccc" % aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa)
def test():
return (
"\n%(modified_count)s %(identifier)s %(action)s"
"%(destination)s%(unmodified)s%(post_processed)s."
) % {
"modified_count": modified_count,
"identifier": "static file" + ("" if modified_count == 1 else "s"),
"action": "symlinked" if self.symlink else "copied",
"destination": (" to '%s'" % destination_path if destination_path else ""),
"unmodified": (
", %s unmodified" % unmodified_count if collected["unmodified"] else ""
),
"post_processed": (
collected["post_processed"]
and ", %s post-processed" % post_processed_count
or ""
),
}
# trailing expression comment
self._assert_skipping(
SkipTestCase("test_foo").test_foo,
ValueError,
"skipUnlessDBFeature cannot be used on test_foo (test_utils.tests."
"SkippingTestCase.test_skip_unless_db_feature.<locals>.SkipTestCase%s) "
"as SkippingTestCase.test_skip_unless_db_feature.<locals>.SkipTestCase "
"doesn't allow queries against the 'default' database."
# Python 3.11 uses fully qualified test name in the output.
% (".test_foo" if PY311 else ""),
)
# dangling operator comment
self._assert_skipping(
SkipTestCase("test_foo").test_foo,
ValueError,
"skipUnlessDBFeature cannot be used on test_foo (test_utils.tests."
"SkippingTestCase.test_skip_unless_db_feature.<locals>.SkipTestCase%s) "
"as SkippingTestCase.test_skip_unless_db_feature.<locals>.SkipTestCase "
"doesn't allow queries against the 'default' database."
% # Python 3.11 uses fully qualified test name in the output.
(".test_foo" if PY311 else ""),
)
# Black keeps as many operands as fit on the same line as the `%`. Ruff does not. This is intentional as these are rare and complicated things significantly
(
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
"cccccccccccccccccccccccccc"
% aaaaaaaaaaaa
+ x
)
(
b + c + d +
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
"cccccccccccccccccccccccccc"
% aaaaaaaaaaaa
+ x
)
(
b < c > d <
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
"cccccccccccccccccccccccccc"
% aaaaaaaaaaaa
> x
)
self.assertEqual(
response.status_code,
status_code,
msg_prefix + "Couldn't retrieve content: Response code was %d"
" (expected %d)" % (response.status_code, status_code),
)
def test():
return (
"((TIME_TO_SEC(%(lhs)s) * 1000000 + MICROSECOND(%(lhs)s)) -"
" (TIME_TO_SEC(%(rhs)s) * 1000000 + MICROSECOND(%(rhs)s)))"
) % {"lhs": lhs_sql, "rhs": rhs_sql}, tuple(lhs_params) * 2 + tuple(rhs_params) * 2
def test2():
return "RETURNING %s INTO %s" % (
", ".join(field_names),
", ".join(["%s"] * len(params)),
), tuple(params)
def test3():
return (
"(CASE WHEN JSON_TYPE(%s, %%s) IN (%s) "
"THEN JSON_TYPE(%s, %%s) ELSE JSON_EXTRACT(%s, %%s) END)"
) % (lhs, datatype_values, lhs, lhs), (tuple(params) + (json_path,)) * 3
```
## Output
```py
raise ImproperlyConfigured(
"The app module %r has multiple filesystem locations (%r); "
"you must configure this app with an AppConfig subclass "
"with a 'path' class attr ibute." % (module, paths)
)
raise ImproperlyConfigured(
"The app module %r has multiple filesystem locations (%r); "
"you must configure this app with an AppConfig subclass "
"with a 'path' class attr ibute." %
# comment
(module, paths)
)
# Only important in parenthesized context because implicit string continuation otherwise doesn't expand
"The app module %r has multiple filesystem locations (%r); " "you must configure this app with an AppConfig subclass " "with a 'path' class attribute." % (
module,
paths,
)
(
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
"cccccccccccccccccccccccccccccccccccccccccccc"
% (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
b,
c,
d,
)
)
(
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
"cccccccccccccccccccccccccccccccccccccccccccc"
% aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
)
def test():
return (
"\n%(modified_count)s %(identifier)s %(action)s"
"%(destination)s%(unmodified)s%(post_processed)s."
) % {
"modified_count": modified_count,
"identifier": "static file" + ("" if modified_count == 1 else "s"),
"action": "symlinked" if self.symlink else "copied",
"destination": (" to '%s'" % destination_path if destination_path else ""),
"unmodified": (
", %s unmodified" % unmodified_count if collected["unmodified"] else ""
),
"post_processed": (
collected["post_processed"] and ", %s post-processed" % post_processed_count
or ""
),
}
# trailing expression comment
self._assert_skipping(
SkipTestCase("test_foo").test_foo,
ValueError,
"skipUnlessDBFeature cannot be used on test_foo (test_utils.tests."
"SkippingTestCase.test_skip_unless_db_feature.<locals>.SkipTestCase%s) "
"as SkippingTestCase.test_skip_unless_db_feature.<locals>.SkipTestCase "
"doesn't allow queries against the 'default' database."
# Python 3.11 uses fully qualified test name in the output.
% (".test_foo" if PY311 else ""),
)
# dangling operator comment
self._assert_skipping(
SkipTestCase("test_foo").test_foo,
ValueError,
"skipUnlessDBFeature cannot be used on test_foo (test_utils.tests."
"SkippingTestCase.test_skip_unless_db_feature.<locals>.SkipTestCase%s) "
"as SkippingTestCase.test_skip_unless_db_feature.<locals>.SkipTestCase "
"doesn't allow queries against the 'default' database."
% # Python 3.11 uses fully qualified test name in the output.
(".test_foo" if PY311 else ""),
)
# Black keeps as many operands as fit on the same line as the `%`. Ruff does not. This is intentional as these are rare and complicated things significantly
(
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
"cccccccccccccccccccccccccc"
% aaaaaaaaaaaa
+ x
)
(
b
+ c
+ d
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
"cccccccccccccccccccccccccc" % aaaaaaaaaaaa
+ x
)
(
b
< c
> d
< "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
"cccccccccccccccccccccccccc" % aaaaaaaaaaaa
> x
)
self.assertEqual(
response.status_code,
status_code,
msg_prefix
+ "Couldn't retrieve content: Response code was %d"
" (expected %d)" % (response.status_code, status_code),
)
def test():
return (
(
"((TIME_TO_SEC(%(lhs)s) * 1000000 + MICROSECOND(%(lhs)s)) -"
" (TIME_TO_SEC(%(rhs)s) * 1000000 + MICROSECOND(%(rhs)s)))"
) % {"lhs": lhs_sql, "rhs": rhs_sql},
tuple(lhs_params) * 2 + tuple(rhs_params) * 2,
)
def test2():
return (
"RETURNING %s INTO %s"
% (
", ".join(field_names),
", ".join(["%s"] * len(params)),
),
tuple(params),
)
def test3():
return (
(
"(CASE WHEN JSON_TYPE(%s, %%s) IN (%s) "
"THEN JSON_TYPE(%s, %%s) ELSE JSON_EXTRACT(%s, %%s) END)"
) % (lhs, datatype_values, lhs, lhs),
(tuple(params) + (json_path,)) * 3,
)
```