Skip to content

bpo-32820: __format__ method for ipaddress #5627

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 27 commits into from
Sep 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
45ea3c6
bits method and test_bits
ewosborne Feb 11, 2018
b470a10
Cleaned up assert string
ewosborne Feb 11, 2018
4c8af71
blurb
ewosborne Feb 11, 2018
d1f9732
added docstring
ewosborne Feb 11, 2018
b72330d
Faster method, per Eric Smith
ewosborne Feb 12, 2018
6c7d1a4
redoing as __format__
ewosborne Feb 13, 2018
6a1d467
added ipv6 method
ewosborne Feb 13, 2018
41f637a
test cases and cleanup
ewosborne Feb 13, 2018
eddcc39
updated news
ewosborne Feb 13, 2018
eabf6d9
cleanup and NEWS.d
ewosborne Feb 13, 2018
ba05386
cleaned up old NEWS
ewosborne Feb 13, 2018
2bd9f60
removed cut and paste leftover
ewosborne Feb 15, 2018
d865609
one more cleanup
ewosborne Feb 15, 2018
798af6e
moved to regexp, moved away from v4- and v6-specific versions of __fo…
ewosborne Mar 4, 2018
db383e1
More cleanup, added ipv6 test cases
ewosborne Mar 4, 2018
b433282
more cleanup
ewosborne Mar 4, 2018
41109cc
more cleanup
ewosborne Mar 4, 2018
861b5e4
cleanup
ewosborne Mar 4, 2018
398b2c7
cleanup
ewosborne Mar 5, 2018
0274c0f
cleanup per review, part 1
ewosborne Sep 12, 2018
6d5fbdb
addressed review comments around help string and regexp matching
ewosborne Sep 12, 2018
d9061d2
wrapped v6 test strings. contiguous integers: break at 72char. with u…
ewosborne Sep 13, 2018
1401142
's' and '' tests for pv4 and ipv6
ewosborne Sep 13, 2018
acc1f06
whitespace cleanup
ewosborne Sep 15, 2018
8354756
Remove trailing whitespace
zware Sep 9, 2019
aaaa45c
Remove more trailing whitespace
zware Sep 11, 2019
8e1d9ca
Remove an excess blank line
zware Sep 11, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions Lib/ipaddress.py
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,78 @@ def _get_address_key(self):
def __reduce__(self):
return self.__class__, (self._ip,)

def __format__(self, fmt):
"""Returns an IP address as a formatted string.

Supported presentation types are:
's': returns the IP address as a string (default)
'b' or 'n': converts to binary and returns a zero-padded string
'X' or 'x': converts to upper- or lower-case hex and returns a zero-padded string

For binary and hex presentation types, the alternate form specifier
'#' and the grouping option '_' are supported.
"""


# Support string formatting
if not fmt or fmt[-1] == 's':
# let format() handle it
return format(str(self), fmt)

# From here on down, support for 'bnXx'

import re
fmt_re = '^(?P<alternate>#?)(?P<grouping>_?)(?P<fmt_base>[xbnX]){1}$'
m = re.match(fmt_re, fmt)
if not m:
return super().__format__(fmt)

groupdict = m.groupdict()
alternate = groupdict['alternate']
grouping = groupdict['grouping']
fmt_base = groupdict['fmt_base']

# Set some defaults
if fmt_base == 'n':
if self._version == 4:
fmt_base = 'b' # Binary is default for ipv4
if self._version == 6:
fmt_base = 'x' # Hex is default for ipv6

# Handle binary formatting
if fmt_base == 'b':
if self._version == 4:
# resulting string is '0b' + 32 bits
# plus 7 _ if needed
padlen = IPV4LENGTH+2 + (7*len(grouping))
elif self._version == 6:
# resulting string is '0b' + 128 bits
# plus 31 _ if needed
padlen = IPV6LENGTH+2 + (31*len(grouping))

# Handle hex formatting
elif fmt_base in 'Xx':
if self._version == 4:
# resulting string is '0x' + 8 hex digits
# plus a single _ if needed
padlen = int(IPV4LENGTH/4)+2 + len(grouping)
elif self._version == 6:
# resulting string is '0x' + 32 hex digits
# plus 7 _ if needed
padlen = int(IPV6LENGTH/4)+2 + (7*len(grouping))

retstr = f'{int(self):#0{padlen}{grouping}{fmt_base}}'

if fmt_base == 'X':
retstr = retstr.upper()

# If alternate is not set, strip the two leftmost
# characters ('0b')
if not alternate:
retstr = retstr[2:]

return retstr


@functools.total_ordering
class _BaseNetwork(_IPAddressBase):
Expand Down
66 changes: 66 additions & 0 deletions Lib/test/test_ipaddress.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,31 @@ def assertBadLength(length):
class AddressTestCase_v4(BaseTestCase, CommonTestMixin_v4):
factory = ipaddress.IPv4Address

def test_format(self):
v4 = ipaddress.IPv4Address("1.2.3.42")
v4_pairs = [
("b" ,"00000001000000100000001100101010"),
("n" ,"00000001000000100000001100101010"),
("x" ,"0102032a"),
("X" ,"0102032A"),
("_b" ,"0000_0001_0000_0010_0000_0011_0010_1010"),
("_n" ,"0000_0001_0000_0010_0000_0011_0010_1010"),
("_x" ,"0102_032a"),
("_X" ,"0102_032A"),
("#b" ,"0b00000001000000100000001100101010"),
("#n" ,"0b00000001000000100000001100101010"),
("#x" ,"0x0102032a"),
("#X" ,"0X0102032A"),
("#_b" ,"0b0000_0001_0000_0010_0000_0011_0010_1010"),
("#_n" ,"0b0000_0001_0000_0010_0000_0011_0010_1010"),
("#_x" ,"0x0102_032a"),
("#_X" ,"0X0102_032A"),
("s" ,"1.2.3.42"),
("" ,"1.2.3.42"),
]
for (fmt, txt) in v4_pairs:
self.assertEqual(txt, format(v4, fmt))

def test_network_passed_as_address(self):
addr = "127.0.0.1/24"
with self.assertAddressError("Unexpected '/' in %r", addr):
Expand Down Expand Up @@ -266,6 +291,47 @@ def test_weakref(self):
class AddressTestCase_v6(BaseTestCase, CommonTestMixin_v6):
factory = ipaddress.IPv6Address

def test_format(self):

v6 = ipaddress.IPv6Address("::1.2.3.42")
v6_pairs = [
("b",
"000000000000000000000000000000000000000000000000000000"
"000000000000000000000000000000000000000000000000010000"
"00100000001100101010"),
("n", "0000000000000000000000000102032a"),
("x", "0000000000000000000000000102032a"),
("X", "0000000000000000000000000102032A"),
("_b",
"0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000"
"_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000"
"_0000_0000_0000_0000_0001_0000_0010_0000_0011_0010"
"_1010"),
("_n", "0000_0000_0000_0000_0000_0000_0102_032a"),
("_x", "0000_0000_0000_0000_0000_0000_0102_032a"),
("_X", "0000_0000_0000_0000_0000_0000_0102_032A"),
("#b",
"0b0000000000000000000000000000000000000000000000000000"
"000000000000000000000000000000000000000000000000000100"
"0000100000001100101010"),
("#n", "0x0000000000000000000000000102032a"),
("#x", "0x0000000000000000000000000102032a"),
("#X", "0X0000000000000000000000000102032A"),
("#_b",
"0b0000_0000_0000_0000_0000_0000_0000_0000_0000_0000"
"_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000"
"_0000_0000_0000_0000_0000_0001_0000_0010_0000_0011"
"_0010_1010"),
("#_n", "0x0000_0000_0000_0000_0000_0000_0102_032a"),
("#_x", "0x0000_0000_0000_0000_0000_0000_0102_032a"),
("#_X", "0X0000_0000_0000_0000_0000_0000_0102_032A"),
("s", "::102:32a"),
("", "::102:32a"),
]

for (fmt, txt) in v6_pairs:
self.assertEqual(txt, format(v6, fmt))

def test_network_passed_as_address(self):
addr = "::1/24"
with self.assertAddressError("Unexpected '/' in %r", addr):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Added __format__ to IPv4 and IPv6 classes. Always outputs a fully zero-
padded string. Supports b/x/n modifiers (bin/hex/native format). Native
format for IPv4 is bin, native format for IPv6 is hex. Also supports '#' and
'_' modifiers.