import sys as _sys
from typing import Any, Callable, Dict, Generator, Mapping, Optional, TextIO, Union
__all__ = ["pp"]
DecodedDict = Mapping[str, str]
EncodedDict = Mapping[str, Mapping[str, bytes]]
SpecDict = Mapping[str, Mapping[str, Any]]
[docs]def pp(
doc: Union[DecodedDict, EncodedDict],
spec: SpecDict,
desc_width: int = 30,
stream: Optional[TextIO] = None,
line_width: int = 80,
) -> None:
r"""Pretty Print Python dict containing ISO8583 data.
Parameters
----------
doc : dict
Dict containing ISO8583 data
spec : dict
A Python dict defining ISO8583 specification.
See iso8583.specs module for examples.
desc_width : int, optional
Field description width (default 30).
Specify 0 to print no descriptions.
stream : stream, optional
An output stream. The only method used on the stream
object is the file protocol's write() method.
If not specified, the :func:`iso8583.pp` adopts
`sys.stdout`.
line_width : int, optional
Attempted maximum width of output line (default 80).
Notes
-----
For the most correct information to be displayed by :func:`iso8583.pp`
it's recommended to call it after :func:`iso8583.encode` or
:func:`iso8583.decode`.
Examples
--------
>>> import iso8583
>>> from iso8583.specs import default_ascii as spec
>>> s = b"02004010100000000000161234567890123456123456840"
>>> doc_dec, doc_enc = iso8583.decode(s, spec)
>>> iso8583.pp(doc_dec, spec)
t Message Type : '0200'
p Bitmap, Primary : '4010100000000000'
2 Primary Account Number (PAN) : '1234567890123456'
12 Time, Local Transaction : '123456'
20 PAN Country Code : '840'
"""
desc_width = int(desc_width)
line_width = int(line_width)
if stream is None:
stream = _sys.stdout
if "h" in doc and spec["h"]["max_len"] > 0:
_pp_field(doc, spec, desc_width, stream, line_width, "h")
if "t" in doc:
_pp_field(doc, spec, desc_width, stream, line_width, "t")
if "p" in doc:
_pp_field(doc, spec, desc_width, stream, line_width, "p")
for field_key in sorted(
[k for k in doc.keys() if isinstance(k, str) and k.isnumeric()], key=int
):
_pp_field(doc, spec, desc_width, stream, line_width, field_key)
def _pp_field(
doc: Union[DecodedDict, EncodedDict],
spec: SpecDict,
desc_width: int,
stream: TextIO,
line_width: int,
field_key: str,
) -> None:
indent = 5
stream.write("{index:3s}".format(index=str(field_key)))
if desc_width > 0:
stream.write(
" {desc: <{desc_width}}".format(
desc=spec[field_key]["desc"][:desc_width],
desc_width=desc_width,
)
)
indent += desc_width + 1
stream.write(": ")
doc_field = doc[field_key]
if isinstance(doc_field, dict):
field_length = doc_field.get("len", b"")
if len(field_length) > 0:
stream.write("{} ".format(repr(field_length)))
indent += len(repr(field_length)) + 1
obj = doc_field.get("data", b"")
else:
obj = doc_field
rep = repr(obj)
if len(rep) + indent > line_width:
p = _dispatch.get(type(obj).__repr__, None)
if p is not None:
p(obj, stream, indent, line_width)
stream.write("\n")
return
stream.write("{}\n".format(repr(obj)))
_dispatch: Dict[Any, Callable[[Any, TextIO, int, int], None]] = {}
def _pprint_str(obj: str, stream: TextIO, indent: int, width: int) -> None:
if len(obj) <= 1:
stream.write(repr(obj))
return
delim = ""
for rep in _wrap_str_repr(obj, width - indent):
stream.write(delim)
stream.write(rep)
if not delim:
delim = "\n" + " " * indent
_dispatch[str.__repr__] = _pprint_str
def _pprint_bytes(obj: bytes, stream: TextIO, indent: int, width: int) -> None:
if len(obj) <= 1:
stream.write(repr(obj))
return
delim = ""
for rep in _wrap_bytes_repr(obj, width - indent):
stream.write(delim)
stream.write(rep)
if not delim:
delim = "\n" + " " * indent
_dispatch[bytes.__repr__] = _pprint_bytes
def _wrap_str_repr(obj: str, width: int) -> Generator[str, None, None]:
# because this functions yields entire width of a string at a time,
# reduce width by 2 to account for '' around repr(str)
if width > 3:
width -= 2
else:
width = 1
for i in range(0, len(obj), width):
yield repr(obj[i : i + width])
def _wrap_bytes_repr(obj: bytes, width: int) -> Generator[str, None, None]:
current = b""
for i in range(0, len(obj)):
part = obj[i : i + 1]
candidate = current + part
if len(repr(candidate)) > width:
if current:
yield repr(current)
current = part
else:
current = candidate
if current:
yield repr(current)