Reputation: 6348
I'm trying to pretty-print a HTTP request (that I've mocked here).
from typing import NamedTuple
class RequestMock(NamedTuple):
method = 'POST'
url = 'https://bob.com'
body = 'body1\nbody2'
headers = {'a': '1', 'b': '2'}
I have a function that does this:
req = RequestMock()
def print1(req):
headers = '\n'.join(f'{k}: {v}' for k, v in req.headers.items())
s = '\n'.join([
f'{req.method} {req.url}',
headers,
req.body
])
print(s)
print1(req)
# POST https://bob.com
# a: 1
# b: 2
# body1
# body2
But when I've tried to rewrite it with f-strings
for clarity and ease of modification, I get some bad indents:
# what I want the code to look like
def print2(req):
headers = '\n'.join(f'{k}: {v}' for k, v in req.headers.items())
s = f"""
{req.method} {req.url}
{headers}
{req.body}
"""
print(s)
print2(req)
# POST https://bob.com
# a: 1
# b: 2
# body1
# body2
I know this is because I'm defining strings with newlines and putting them in a triple-quoted string. Is there a simple way to get the output I'm looking with a triple-quoted f-string
defined in a function and without having to know the indentation level of its definition? I've played with textwrap.indent
, textwrap.dedent
, str.lstrip
, re
, etc., but the code stops being simple and pythonic fast. The closest thing I've come up with is the following, but the length is awkward and I feel like I'm repeating myself.
def print3(req):
headers = '\n'.join(f'{k}: {v}' for k, v in req.headers.items())
s = textwrap.dedent("""
{method} {url}
{headers}
{body}
""").strip()
s = s.format(
method=req.method,
url=req.url,
headers=headers,
body=req.body,
)
print(s)
print3(req)
# POST https://bob.com
# a: 1
# b: 2
# body1
# body2
Upvotes: 11
Views: 31759
Reputation: 431
The answer I've come with is to use the templating module jinja2
. Here's a great start guide. It's also not massively different from f
string templates.
Obviously, if you don't want the indenting, then just don't use it in the triple-quoted string.
Here's an example where I use triple-quotes to define the jinja2
template, set up the I you want in a dictionary, then merge the dictionary into the template. The parameters to render()
are similar to the older "".format()
.
#! /usr/bin/python3
import jinja2
my_template = """<html>
<body>
{{ data.item_one }}
<P>
{{ data.item_two }}
</body>
</html>
"""
my_dict = { "data" : {
"item_one": "This is line one",
"item_two": "This is line two"
}
}
environment = jinja2.Environment()
template = environment.from_string(my_template)
print(template.render(**my_dict))
then run it
$ ./jin.py
<html>
<body>
This is line one
<P>
This is line two
</body>
</html>
Upvotes: 0
Reputation: 1468
You can fix it with 2 tiny changes:
def print6(req, **w):
headers = '\n'.join(f'{k}: {v}' for k, v in req.headers.items())
method, url, body = \
w['method'], w['url'], w['body']
# < note the changes belowwwwwwwwwwww >
s = '\n'.join(line.lstrip() for line in f"""
{method} {url}
{headers}
{body}
""".split('\n')) # and note this .split('\n') over here
print(s)
print6(req)
Upvotes: 1
Reputation: 16623
I think you can try to take advantage of implicit string concatenation for a semi-nice looking solution:
def print4(req):
headers = '\n'.join(f'{k}: {v}' for k, v in req.headers.items())
s = (f'{req.method} {req.url}\n'
f'{headers}\n'
f'{req.body}')
print(s)
print4(req)
Output:
POST https://bob.com
a: 1
b: 2
body1
body2
Note that, if you want, you can take out the parentheses and use backslashes:
s = f'{req.method} {req.url}\n' \
f'{headers}\n' \
f'{req.body}'
However, the style guide prefers parentheses over backslashes.
Another option:
def print5(req):
headers = '\n'.join(f'{k}: {v}' for k, v in req.headers.items())
s = f"""
{req.method} {req.url}
{headers}
{req.body}
"""
s = '\n'.join(l.lstrip() for l in s.splitlines())
print(s)
Upvotes: 5