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
diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py
index 873c764..c389f05 100644
--- a/Lib/ipaddress.py
+++ b/Lib/ipaddress.py
@@ -618,6 +618,78 @@
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):