bpo-32820: __format__ method for ipaddress (#5627)

* bits method and test_bits

* Cleaned up assert string

* blurb

* added docstring

* Faster method, per Eric Smith

* redoing as __format__

* added ipv6 method

* test cases and cleanup

* updated news

* cleanup and NEWS.d

* cleaned up old NEWS

* removed cut and paste leftover

* one more cleanup

* moved to regexp, moved away from v4- and v6-specific versions of __format__

* More cleanup, added ipv6 test cases

* more cleanup

* more cleanup

* cleanup

* cleanup

* cleanup per review, part 1

* addressed review comments around help string and regexp matching

* wrapped v6 test strings. contiguous integers: break at 72char. with underscores: break so that it looks clean.

*  's' and '' tests for pv4 and ipv6

* whitespace cleanup

* Remove trailing whitespace

* Remove more trailing whitespace

* Remove an excess blank line
This commit is contained in:
ewosborne 2019-09-12 05:03:31 -04:00 committed by Zachary Ware
parent 92777d5e5a
commit f9c95a4ba2
3 changed files with 142 additions and 0 deletions

View file

@ -618,6 +618,78 @@ class _BaseAddress(_IPAddressBase):
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):