pax_global_header00006660000000000000000000000064122076205550014516gustar00rootroot0000000000000052 comment=7623a6c551be97ba7d87a77cc3e7d9e4e2c2bfbc catcher-7623a6c551be97ba7d87a77cc3e7d9e4e2c2bfbc/000077500000000000000000000000001220762055500204465ustar00rootroot00000000000000catcher-7623a6c551be97ba7d87a77cc3e7d9e4e2c2bfbc/.gitignore000066400000000000000000000002621220762055500224360ustar00rootroot00000000000000*.pyc *.pyo .DS_Store *~ *.sublime-workspace .coverage debian/python-catcher* dist/*.rpm dist/*.deb dist/*.gz *.egg* build/* debian/files debian/*stamp* docs/build nosetests* catcher-7623a6c551be97ba7d87a77cc3e7d9e4e2c2bfbc/Makefile000066400000000000000000000030041220762055500221030ustar00rootroot00000000000000PYTHON=`which python` DESTDIR=/ BUILDIR=$(CURDIR)/debian/python-catcher RPMTOPDIR=$(CURDIR)/build PROJECT=python-catcher DEBPROJECT=python-catcher VERSION=`python -c "from catcher import __version__; print __version__"` PREFIX=/usr all: install: $(PYTHON) setup.py install --root $(DESTDIR) $(COMPILE) --prefix $(PREFIX) rpm: tgz rm -rf dist/*.rpm cat dist/$(PROJECT).spec.in | sed s/__VERSION__/$(VERSION)/g > $(PROJECT).spec mkdir -p build/SOURCES || true cp dist/$(PROJECT)*.tar.gz build/SOURCES rpmbuild --define '_topdir $(RPMTOPDIR)' -bb $(PROJECT).spec mv build/RPMS/noarch/$(PROJECT)*.rpm dist rm $(PROJECT).spec deb: tgz rm -rf dist/*.deb cat debian/changelog.in | sed s/__VERSION__/$(VERSION)/g | sed "s/__DATE__/$(DATE)/g" > debian/changelog cp dist/$(PROJECT)*.tar.gz .. rename -f 's/$(PROJECT)-(.*)\.tar\.gz/$(DEBPROJECT)_$$1\.orig\.tar\.gz/' ../* dpkg-buildpackage -b -rfakeroot -us -uc mv ../$(DEBPROJECT)*.deb dist/ rm ../$(DEBPROJECT)*.orig.tar.gz rm ../$(DEBPROJECT)*.changes rm debian/changelog upload-deb: deb scp dist/*.deb root@ajenti.org:/srv/repo/ng/debian ssh root@ajenti.org /srv/repo/rebuild-debian.sh upload-rpm: rpm scp dist/*.rpm root@ajenti.org:/srv/repo/ng/centos/6 ssh root@ajenti.org /srv/repo/rebuild-centos.sh upload-tgz: tgz $(PYTHON) setup.py sdist upload tgz: rm dist/*.tar.gz || true $(PYTHON) setup.py sdist clean: $(PYTHON) setup.py clean rm -rf build/ debian/$(PROJECT)* debian/*stamp* debian/files MANIFEST *.egg-info find . -name '*.pyc' -delete catcher-7623a6c551be97ba7d87a77cc3e7d9e4e2c2bfbc/README.rst000066400000000000000000000015641220762055500221430ustar00rootroot00000000000000Catcher - Beautiful tracebacks ============================== **python-catcher** module generates rich HTML tracebacks (including source code and locals), submits it to the web and generates a permalink. Quick use:: import catcher try: launch_important_stuff() except Exception, e: report = catcher.collect(e) html = catcher.formatters.HTMLFormatter().format(report, maxdepth=4) url = catcher.uploaders.AjentiOrgUploader().upload(html) print 'Application has crashed. Please submit this link along with the bug report:' print url Example report: http://ajenti.org/catcher/view/10 Report overview: .. image:: http://habrastorage.org/storage2/f05/ea4/779/f05ea4779fccf0087fa24a380bd92b45.png One stack frame with locals: .. image:: http://habrastorage.org/storage2/4b8/188/5fe/4b81885fe8582d835c557af1d71884b9.png catcher-7623a6c551be97ba7d87a77cc3e7d9e4e2c2bfbc/catcher.sublime-project000066400000000000000000000001051220762055500251010ustar00rootroot00000000000000{ "folders": [ { "path": "/home/eugeny/Work/catcher" } ] } catcher-7623a6c551be97ba7d87a77cc3e7d9e4e2c2bfbc/catcher/000077500000000000000000000000001220762055500220575ustar00rootroot00000000000000catcher-7623a6c551be97ba7d87a77cc3e7d9e4e2c2bfbc/catcher/__init__.py000066400000000000000000000001561220762055500241720ustar00rootroot00000000000000from . import formatters from . import uploaders from .collector import collect, backup __version__ = '0.1.3'catcher-7623a6c551be97ba7d87a77cc3e7d9e4e2c2bfbc/catcher/collector.py000066400000000000000000000016211220762055500244170ustar00rootroot00000000000000from collections import namedtuple import sys import time import inspect Report = namedtuple('Report', ['timestamp', 'exception', 'traceback']) Frame = namedtuple('Frame', ['file', 'line', 'code', 'locals']) def __collect_frame(frame): return Frame( file=inspect.getfile(frame), line=frame.f_lineno, locals=frame.f_locals, code=inspect.getsourcelines(frame), ) def backup(exception): exception.traceback_backup = sys.exc_info()[2] def collect(exception): traceback = [] if hasattr(exception, 'traceback_backup'): tb = exception.traceback_backup else: exc_info = sys.exc_info() tb = exc_info[2] while tb: frame = tb.tb_frame traceback.append(__collect_frame(frame)) tb = tb.tb_next return Report( timestamp=time.time(), exception=exception, traceback=traceback, ) catcher-7623a6c551be97ba7d87a77cc3e7d9e4e2c2bfbc/catcher/formatters/000077500000000000000000000000001220762055500242455ustar00rootroot00000000000000catcher-7623a6c551be97ba7d87a77cc3e7d9e4e2c2bfbc/catcher/formatters/__init__.py000066400000000000000000000001001220762055500263450ustar00rootroot00000000000000from .text import TextFormatter from .html import HTMLFormatter catcher-7623a6c551be97ba7d87a77cc3e7d9e4e2c2bfbc/catcher/formatters/html.py000066400000000000000000000124641220762055500255720ustar00rootroot00000000000000from mako.template import Template from datetime import datetime _template = Template(""" Error report <% def id(): id._last_id += 1 return id._last_id id._last_id = 0 def extract_attrs(object): r = {} for k in dir(object): if not k.startswith('__') and hasattr(object, k): v = getattr(object, k) if not type(v).__name__.endswith('method'): r[k] = v return r %> <%def name="object(x, depth=0)" buffered="True"> % if depth > maxdepth: <% return "[too deep]" %> % endif <% objid = id() %> % if type(x) in [str, int, long, float, set] or x in [None]: ${repr(x) | h} % elif type(x) == dict: % for key, value in x.items(): % endfor
${key | h} ${type(value).__name__ | h} ${object(value, depth + 1)}
% elif type(x) == list: ${len(x)} items % for value in x: % endfor
${type(value).__name__ | h} ${object(value, depth + 1)}
% else: % if hasattr(x, '__dict__'): ${repr(x) | h}
${object(extract_attrs(x), depth + 1)}
% else: ${repr(x) | h} % endif % endif

Error report

Timestamp
${ datetime.fromtimestamp(report.timestamp) }

Exception

${object(report.exception)}

Traceback

% for frame in report.traceback: <% frameid = id() %>

${ frame.file } : ${ frame.line }

% for index, line in enumerate(frame.code[0]):
${ frame.code[1] + index } ${ line }
% endfor

Locals

${object(frame.locals)}

% endfor
""") class HTMLFormatter: def format(self, report, maxdepth=5): return _template.render(maxdepth=maxdepth, report=report, datetime=datetime) catcher-7623a6c551be97ba7d87a77cc3e7d9e4e2c2bfbc/catcher/formatters/text.py000066400000000000000000000014571220762055500256120ustar00rootroot00000000000000from datetime import datetime class TextFormatter: def __format_frame(self, frame): lines, current_line = frame.code code = ''.join( ' ' + ('>>' if lines.index(line) == frame.line - current_line else ' ') + ' ' + line for line in lines ) return """ %(file)s:%(line)s %(code)s """ % { 'file': frame.file, 'line': frame.line, 'code': code, } def format(self, report): traceback = '\n'.join(self.__format_frame(frame) for frame in report.traceback) return """ Error report at %(timestamp)s Traceback: %(traceback)s """ % { 'timestamp': datetime.fromtimestamp(int(report.timestamp)), 'traceback': traceback, } catcher-7623a6c551be97ba7d87a77cc3e7d9e4e2c2bfbc/catcher/tests.py000066400000000000000000000011061220762055500235710ustar00rootroot00000000000000from __future__ import print_function import unittest import catcher class CollectorTest (unittest.TestCase): class Tester: def divide(self, a, b): return a / b def inner(self): self.divide(2, 0) def test(self): self.inner() def test_collection(self): try: CollectorTest.Tester().test() except Exception as e: report = catcher.collect(e) html = catcher.formatters.HTMLFormatter().format(report) print(catcher.uploaders.AjentiOrgUploader().upload(html)) catcher-7623a6c551be97ba7d87a77cc3e7d9e4e2c2bfbc/catcher/uploaders/000077500000000000000000000000001220762055500240555ustar00rootroot00000000000000catcher-7623a6c551be97ba7d87a77cc3e7d9e4e2c2bfbc/catcher/uploaders/__init__.py000066400000000000000000000001211220762055500261600ustar00rootroot00000000000000from .ajentiorg import AjentiOrgUploader from .pastehtml import PasteHTMLUploadercatcher-7623a6c551be97ba7d87a77cc3e7d9e4e2c2bfbc/catcher/uploaders/ajentiorg.py000066400000000000000000000004271220762055500264140ustar00rootroot00000000000000from base64 import b64encode import requests import zlib class AjentiOrgUploader: def upload(self, data): return requests.post( 'http://ajenti.org/catcher/submit', data={'text': b64encode(zlib.compress(data.encode('utf-8')))} ).text catcher-7623a6c551be97ba7d87a77cc3e7d9e4e2c2bfbc/catcher/uploaders/pastehtml.py000066400000000000000000000003441220762055500264310ustar00rootroot00000000000000import requests class PasteHTMLUploader: def upload(self, data): return requests.post( 'http://pastehtml.com/upload/create?input_type=html&result=address', data={'txt': data} ).text catcher-7623a6c551be97ba7d87a77cc3e7d9e4e2c2bfbc/debian/000077500000000000000000000000001220762055500216705ustar00rootroot00000000000000catcher-7623a6c551be97ba7d87a77cc3e7d9e4e2c2bfbc/debian/changelog.in000066400000000000000000000002151220762055500241450ustar00rootroot00000000000000python-catcher (__VERSION__) UNRELEASED; urgency=low * Initial release. -- Eugeny Pankov Thu, 7 Feb 2013 00:12:00 +0300 catcher-7623a6c551be97ba7d87a77cc3e7d9e4e2c2bfbc/debian/compat000066400000000000000000000000021220762055500230660ustar00rootroot000000000000008 catcher-7623a6c551be97ba7d87a77cc3e7d9e4e2c2bfbc/debian/control000066400000000000000000000007271220762055500233010ustar00rootroot00000000000000Source: python-catcher Section: python Priority: optional Maintainer: Eugeny Pankov Build-Depends: debhelper (>=8.0.0), python-support (>= 0.6), cdbs (>= 0.4.49) XS-Python-Version: >=2.6 Standards-Version: 3.9.1 Package: python-catcher Architecture: all Homepage: http://github.com/Eugeny/catcher XB-Python-Version: ${python:Versions} Depends: ${misc:Depends}, ${python:Depends}, python-requests, python-mako Description: Beautiful stack traces for Pythoncatcher-7623a6c551be97ba7d87a77cc3e7d9e4e2c2bfbc/debian/copyright000066400000000000000000000002031220762055500236160ustar00rootroot00000000000000Copyright (c) 2012-2013 The Ajenti Team * Eugeny Pankov & contributors LICENSED UNDER LGPLv3 See LICENSE. catcher-7623a6c551be97ba7d87a77cc3e7d9e4e2c2bfbc/debian/postinst000066400000000000000000000000261220762055500234740ustar00rootroot00000000000000#!/bin/sh #DEBHELPER# catcher-7623a6c551be97ba7d87a77cc3e7d9e4e2c2bfbc/debian/prerm000066400000000000000000000000271220762055500227370ustar00rootroot00000000000000#!/bin/sh #DEBHELPER# catcher-7623a6c551be97ba7d87a77cc3e7d9e4e2c2bfbc/debian/pyversions000066400000000000000000000000051220762055500240270ustar00rootroot000000000000002.6- catcher-7623a6c551be97ba7d87a77cc3e7d9e4e2c2bfbc/debian/rules000077500000000000000000000003651220762055500227540ustar00rootroot00000000000000#!/usr/bin/make -f # -*- makefile -*- DEB_PYTHON_SYSTEM := pysupport include /usr/share/cdbs/1/rules/debhelper.mk include /usr/share/cdbs/1/class/python-distutils.mk clean:: rm -rf build build-stamp configure-stamp build/ MANIFEST dh_clean catcher-7623a6c551be97ba7d87a77cc3e7d9e4e2c2bfbc/debian/watch000066400000000000000000000000121220762055500227120ustar00rootroot00000000000000version=3 catcher-7623a6c551be97ba7d87a77cc3e7d9e4e2c2bfbc/dist/000077500000000000000000000000001220762055500214115ustar00rootroot00000000000000catcher-7623a6c551be97ba7d87a77cc3e7d9e4e2c2bfbc/dist/python-catcher.spec.in000066400000000000000000000015371220762055500256300ustar00rootroot00000000000000%define name python-catcher %define version __VERSION__ %define unmangled_version __VERSION__ %define release 1 Summary: Beautiful stack traces for Python Name: %{name} Version: %{version} Release: %{release} Source0: %{name}-%{version}.tar.gz License: LGPLv3 Group: Development/Libraries BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot Prefix: %{_prefix} BuildArch: noarch Vendor: Eugene Pankov Url: http://ajenti.org/ requires: python-requests, python-mako %description Beautiful stack traces for Python %prep %setup -n %{name}-%{unmangled_version} -n %{name}-%{unmangled_version} %build python setup.py build %install python setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES --prefix=/usr %clean rm -rf $RPM_BUILD_ROOT %files -f INSTALLED_FILES %defattr(-,root,root) catcher-7623a6c551be97ba7d87a77cc3e7d9e4e2c2bfbc/setup.py000066400000000000000000000006551220762055500221660ustar00rootroot00000000000000#!/usr/bin/env python from distutils.core import setup from setuptools import find_packages import catcher setup( name='python-catcher', version=catcher.__version__, install_requires=[ 'requests', 'Mako', ], description='Beautiful stack traces for Python', author='Eugene Pankov', author_email='e@ajenti.org', url='http://ajenti.org/', packages=find_packages(exclude=['*test*']), )