diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b9e40ae --- /dev/null +++ b/.gitignore @@ -0,0 +1,94 @@ +# Created by .ignore support plugin (hsz.mobi) +### Python template +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# IPython Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# dotenv +.env + +# virtualenv +venv/ +ENV/ + +# Spyder project settings +.spyderproject + +# Rope project settings +.ropeproject + +.gitignore +.idea/ diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..5f0406c --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,24 @@ +Copyright (c) 2016, SymonSoft All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: +Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. +Neither the name of the SymonSoft nor the names of its contributors +may be used to endorse or promote products derived from this software +without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..428d6ae --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +# ppretty v.1.0 + +## About +Convert any python object to a human readable format. + +## Installation +```sh +$ pip install ppretty +``` + +## Examples +Here's a basic example: +```python +from ppretty import ppretty + + +class MyClass(object): + def __init__(self): + self.a = range(10) + + b = 'static var' + + @property + def _c(self): + return 'protected property' + +my_obj = MyClass() + +print ppretty(my_obj) +print +print ppretty(my_obj, indent=' ', width=40, seq_length=10, + show_protected=True, show_static=True, show_properties=True, show_address=True) +``` +Output: +``` +__main__.MyClass(a = [0, 1, ..., 8, 9]) + +__main__.MyClass at 0x1f6bda0L ( + _c = 'protected property', + a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + b = 'static var' +) +``` + +## License +BSD diff --git a/ppretty/__init__.py b/ppretty/__init__.py new file mode 100644 index 0000000..2de44d3 --- /dev/null +++ b/ppretty/__init__.py @@ -0,0 +1,3 @@ +from ppretty import ppretty + +__all__ = 'ppretty' diff --git a/ppretty/ppretty.py b/ppretty/ppretty.py new file mode 100644 index 0000000..5ffe603 --- /dev/null +++ b/ppretty/ppretty.py @@ -0,0 +1,161 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from functools import partial +from inspect import isroutine + + +def ppretty(obj, indent=' ', depth=4, width=120, seq_length=5, show_protected=False, show_private=False, show_static=False, show_properties=False, show_address=False): + seq_formats = {list: ('[', ']'), tuple: ('(', ')'), set: ('set([', '])'), dict: ('{', '}')} + + def inspect_object(current_obj, current_depth, current_width): + inspect_nested_object = partial(inspect_object, current_depth=current_depth - 1, current_width=current_width - len(indent)) + + # Basic types + if isinstance(current_obj, (int, long, float, basestring)): + return [repr(current_obj)] + + # Class object + if isinstance(current_obj, type): + module = current_obj.__module__ + '.' if hasattr(current_obj, '__module__') else '' + return [""] + + # None + if current_obj is None: + return ['None'] + + # Format block of lines + def format_block(lines, open_bkt='', close_bkt=''): + new_lines = [] + one_line = '' + if open_bkt: + new_lines.append(open_bkt) + one_line += open_bkt + for line in lines: + new_lines.append(indent + line) + if len(one_line) <= current_width: + one_line += line + if close_bkt: + if lines: + new_lines.append(close_bkt) + else: + new_lines[-1] += close_bkt + one_line += close_bkt + + return [one_line] if len(one_line) <= current_width and one_line else new_lines + + class SkipElement(object): + pass + + class ErrorAttr(object): + def __init__(self, e): + self.e = e + + def cut_seq(seq): + if current_depth < 1: + return [SkipElement()] + if len(seq) <= seq_length: + return seq + elif seq_length > 1: + seq = list(seq) if isinstance(seq, tuple) else seq + return seq[:seq_length / 2] + [SkipElement()] + seq[(1 - seq_length) / 2:] + return [SkipElement()] + + def format_seq(): + r = [] + items = cut_seq(obj_items) + for n, i in enumerate(items, 1): + if type(i) is SkipElement: + r.append('...') + else: + if type(current_obj) is dict: + (k, v) = i + k = inspect_nested_object(k) + v = inspect_nested_object(v) + k[-1] += ': ' + v.pop(0) + r.extend(k) + r.extend(format_block(v)) + elif type(current_obj) in seq_formats: + r.extend(inspect_nested_object(i)) + else: + (k, v) = i + k = [k] + v = inspect_nested_object(v) if type(v) is not ErrorAttr else [''] + k[-1] += ' = ' + v.pop(0) + r.extend(k) + r.extend(format_block(v)) + if n < len(items): + r[-1] += ', ' + return format_block(r, *brackets) + + # Sequence types + # Others objects are considered as sequence of members + if type(current_obj) in seq_formats: + if type(current_obj) is dict: + obj_items = current_obj.items() + else: + obj_items = current_obj + brackets = seq_formats[type(current_obj)] + else: + obj_items = [] + for k in dir(current_obj): + if not show_private and k.startswith('_') and '__' in k: + continue + if not show_protected and k.startswith('_'): + continue + try: + v = getattr(current_obj, k) + if isroutine(v): + continue + if not show_static and hasattr(type(current_obj), k) and v is getattr(type(current_obj), k): + continue + if not show_properties and hasattr(type(current_obj), k) and isinstance( + getattr(type(current_obj), k), property): + continue + except Exception as e: + v = ErrorAttr(e) + + obj_items.append((k, v)) + + module = current_obj.__module__ + '.' if hasattr(current_obj, '__module__') else '' + address = ' at ' + hex(id(current_obj)) + ' ' if show_address else '' + brackets = (module + type(current_obj).__name__ + address + '(', ')') + + return format_seq() + + return '\n'.join(inspect_object(obj, depth, width)) + + +if __name__ == '__main__': + class B(object): + def __init__(self, b): + self.b = b + + class A(object): + i = [-3, 4.5, ('6', B({'\x07': 8}))] + + def __init__(self, a): + self.a = a + + class C(object): + def __init__(self): + self.a = {u'1': A(2), '9': [10L, 11, {(12, 13): {14, None}}], 15: [16, 17, 18, 19, 20]} + self.b = 'd' + self._c = 'b' + self.e = C.D + + d = 'c' + + def foo(self): + pass + + @property + def bar(self): + return 'e' + + class D(object): + pass + + + print ppretty(C(), indent=' ', depth=8, width=41, seq_length=6, show_static=True, show_protected=True, show_properties=True, show_address=True) + print ppretty(C(), depth=8, width=200, seq_length=4) diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..a2f3748 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[metadata] +description-file = README.md \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..bd4af76 --- /dev/null +++ b/setup.py @@ -0,0 +1,22 @@ +from distutils.core import setup + + +setup( + name='ppretty', + packages=['ppretty'], + version='1.0', + description='Convert python objects to a human readable format', + author='SymonSoft', + author_email='symonsoft@gmail.com', + url='https://github.com/symonsoft/ppretty', + download_url='https://github.com/symonsoft/ppretty/tarball/1.0', + keywords=['pretty', 'print', 'format', 'object', 'human'], + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'License :: OSI Approved :: BSD License', + 'Operating System :: OS Independent', + 'Programming Language :: Python :: 2.7', + 'Topic :: Software Development :: Debuggers', + 'Topic :: Utilities' + ], +)