Last active
July 12, 2018 17:23
-
-
Save patkujawa-wf/ae978919f4c68c9c8de8 to your computer and use it in GitHub Desktop.
all-purpose function for dumping any python thing in a mostly-readable manner (aka dump)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import logging | |
# MAGIC-NUMBER: max length is just some guess at a reasonable size, e.g. 80 cols by 500 lines | |
def dump(value, msg='DUMP', max_length=80 * 500, stdout=False, pick=(), skip=(), | |
loglevel=logging.DEBUG): | |
""" | |
Write as verbose of a description of the value as possible to logging.DEBUG. | |
See http://stackoverflow.com/q/27830888/116891 | |
:param value: The item of interest. | |
:type value: object | |
:param msg: Prefix for the logged item (default='DUMP') | |
:type msg: basestring | |
:param max_length: Longest allowed string length (set to None for unlimited) | |
:type max_length: int | |
:param stdout: If true, print instead of logging (default=False) | |
:type stdout: bool | |
:param pick: If specified, dump only values for these keys of the item | |
(value must be a dict or allow __dict__ access). | |
The name comes from http://underscorejs.org/#pick. | |
:type pick: iterable of basestring | |
:param skip: If specified, exclude values for these keys of the item | |
(value must be a dict or allow __dict__ access). | |
:type skip: iterable of basestring | |
:param loglevel: Defaults to logging.DEBUG. One of the logging constants. | |
:type loglevel: int | |
:return: True if message dumped | |
:rtype: bool | |
""" | |
if not logging.getLogger().isEnabledFor(loglevel) and not stdout: | |
return | |
if pick: | |
d = value if isinstance(value, dict) else value.__dict__ | |
filtered = { | |
property_name: d[property_name] | |
for property_name in pick | |
if property_name in d | |
} | |
value = filtered | |
if skip: | |
d = value if isinstance(value, dict) else value.__dict__ | |
filtered = { | |
property_name: d[property_name] | |
for property_name in d | |
if property_name not in skip | |
} | |
value = filtered | |
kwargs = dict(indent=2, sort_keys=True) | |
try: | |
import json | |
info = json.dumps(value, **kwargs) | |
except Exception: | |
# JSON doesn't like circular references :/ | |
try: | |
string_repr = repr(value) | |
# Replace python primitives, single-quotes, unicode, etc | |
string_repr = string_repr\ | |
.replace('None', 'null')\ | |
.replace('True', 'true')\ | |
.replace('False', 'false')\ | |
.replace("u'", "'")\ | |
.replace("'", '"') | |
# Replace object and function repr's like <MyObject ...> | |
string_repr = re.sub(r':(\s+)(<[^>]+>)', r':\1"\2"', string_repr) | |
# Replace tuples with lists, very naively | |
string_repr = string_repr.replace('(', '[').replace(')', ']') | |
info = json.dumps(json.loads(string_repr), **kwargs) | |
except Exception: | |
from pprint import pformat | |
info = pformat(value, indent=2) | |
def _out(formatted_string, *format_args): | |
"""Format the string and output it to the correct location.""" | |
if stdout: | |
print(formatted_string % format_args) | |
else: | |
logging.log(loglevel, formatted_string, *format_args) | |
if max_length is None or len(info) <= max_length: | |
_out('%s: %s', msg, info) | |
return True | |
else: | |
_out( | |
'Did not dump "%s" due to length restriction. Increase max_length if desired.', msg | |
) | |
return False |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This was created as an answer to my question/request: Work around python's json module not liking circular references - Stack Overflow