pax_global_header 0000666 0000000 0000000 00000000064 12316104655 0014515 g ustar 00root root 0000000 0000000 52 comment=4dca913cc1111d3de8696ec391428a46706a68cf
catcher-4dca913cc1111d3de8696ec391428a46706a68cf/ 0000775 0000000 0000000 00000000000 12316104655 0020052 5 ustar 00root root 0000000 0000000 catcher-4dca913cc1111d3de8696ec391428a46706a68cf/.gitignore 0000664 0000000 0000000 00000000262 12316104655 0022042 0 ustar 00root root 0000000 0000000 *.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-4dca913cc1111d3de8696ec391428a46706a68cf/Makefile 0000664 0000000 0000000 00000003004 12316104655 0021507 0 ustar 00root root 0000000 0000000 PYTHON=`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-4dca913cc1111d3de8696ec391428a46706a68cf/README.rst 0000664 0000000 0000000 00000001566 12316104655 0021551 0 ustar 00root root 0000000 0000000 Catcher - 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/7000
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-4dca913cc1111d3de8696ec391428a46706a68cf/catcher.sublime-project 0000664 0000000 0000000 00000000105 12316104655 0024505 0 ustar 00root root 0000000 0000000 {
"folders":
[
{
"path": "/home/eugeny/Work/catcher"
}
]
}
catcher-4dca913cc1111d3de8696ec391428a46706a68cf/catcher/ 0000775 0000000 0000000 00000000000 12316104655 0021463 5 ustar 00root root 0000000 0000000 catcher-4dca913cc1111d3de8696ec391428a46706a68cf/catcher/__init__.py 0000664 0000000 0000000 00000000171 12316104655 0023573 0 ustar 00root root 0000000 0000000 from . import formatters
from . import uploaders
from .collector import collect, backup
from _version import __version__
catcher-4dca913cc1111d3de8696ec391428a46706a68cf/catcher/_version.py 0000664 0000000 0000000 00000000026 12316104655 0023657 0 ustar 00root root 0000000 0000000 __version__ = '0.1.7'
catcher-4dca913cc1111d3de8696ec391428a46706a68cf/catcher/collector.py 0000664 0000000 0000000 00000001621 12316104655 0024023 0 ustar 00root root 0000000 0000000 from 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-4dca913cc1111d3de8696ec391428a46706a68cf/catcher/formatters/ 0000775 0000000 0000000 00000000000 12316104655 0023651 5 ustar 00root root 0000000 0000000 catcher-4dca913cc1111d3de8696ec391428a46706a68cf/catcher/formatters/__init__.py 0000664 0000000 0000000 00000000100 12316104655 0025751 0 ustar 00root root 0000000 0000000 from .text import TextFormatter
from .html import HTMLFormatter
catcher-4dca913cc1111d3de8696ec391428a46706a68cf/catcher/formatters/html.py 0000664 0000000 0000000 00000012660 12316104655 0025174 0 ustar 00root root 0000000 0000000 from 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 is None:
${repr(x) | h}
% elif type(x) == dict:
% for key, value in x.items():
${key | h}
${type(value).__name__ | h}
${object(value, depth + 1)}
% endfor
% elif type(x) == list:
${len(x)} items
% for value in x:
${type(value).__name__ | h}
${object(value, depth + 1)}
% endfor
% else:
% if hasattr(x, '__dict__'):
${repr(x) | h}
${object(extract_attrs(x), depth + 1)}
% else:
${repr(x) | h}
% endif
% endif
%def>
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 | h}
% endfor
Locals
${object(frame.locals)}
% endfor
""", default_filters=['decode.utf8'], input_encoding='utf-8', output_encoding='utf-8')
class HTMLFormatter:
def format(self, report, maxdepth=5):
return _template.render(maxdepth=maxdepth, report=report, datetime=datetime)
catcher-4dca913cc1111d3de8696ec391428a46706a68cf/catcher/formatters/text.py 0000664 0000000 0000000 00000001457 12316104655 0025216 0 ustar 00root root 0000000 0000000 from 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-4dca913cc1111d3de8696ec391428a46706a68cf/catcher/tests.py 0000664 0000000 0000000 00000001106 12316104655 0023175 0 ustar 00root root 0000000 0000000 from __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-4dca913cc1111d3de8696ec391428a46706a68cf/catcher/uploaders/ 0000775 0000000 0000000 00000000000 12316104655 0023461 5 ustar 00root root 0000000 0000000 catcher-4dca913cc1111d3de8696ec391428a46706a68cf/catcher/uploaders/__init__.py 0000664 0000000 0000000 00000000121 12316104655 0025564 0 ustar 00root root 0000000 0000000 from .ajentiorg import AjentiOrgUploader
from .pastehtml import PasteHTMLUploader catcher-4dca913cc1111d3de8696ec391428a46706a68cf/catcher/uploaders/ajentiorg.py 0000664 0000000 0000000 00000000427 12316104655 0026020 0 ustar 00root root 0000000 0000000 from 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-4dca913cc1111d3de8696ec391428a46706a68cf/catcher/uploaders/pastehtml.py 0000664 0000000 0000000 00000000344 12316104655 0026035 0 ustar 00root root 0000000 0000000 import 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-4dca913cc1111d3de8696ec391428a46706a68cf/debian/ 0000775 0000000 0000000 00000000000 12316104655 0021274 5 ustar 00root root 0000000 0000000 catcher-4dca913cc1111d3de8696ec391428a46706a68cf/debian/changelog.in 0000664 0000000 0000000 00000000215 12316104655 0023551 0 ustar 00root root 0000000 0000000 python-catcher (__VERSION__) UNRELEASED; urgency=low
* Initial release.
-- Eugeny Pankov Thu, 7 Feb 2013 00:12:00 +0300
catcher-4dca913cc1111d3de8696ec391428a46706a68cf/debian/compat 0000664 0000000 0000000 00000000002 12316104655 0022472 0 ustar 00root root 0000000 0000000 8
catcher-4dca913cc1111d3de8696ec391428a46706a68cf/debian/control 0000664 0000000 0000000 00000000727 12316104655 0022705 0 ustar 00root root 0000000 0000000 Source: 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 Python catcher-4dca913cc1111d3de8696ec391428a46706a68cf/debian/copyright 0000664 0000000 0000000 00000000203 12316104655 0023222 0 ustar 00root root 0000000 0000000 Copyright (c) 2012-2013 The Ajenti Team
* Eugeny Pankov & contributors
LICENSED UNDER LGPLv3
See LICENSE.
catcher-4dca913cc1111d3de8696ec391428a46706a68cf/debian/postinst 0000664 0000000 0000000 00000000026 12316104655 0023100 0 ustar 00root root 0000000 0000000 #!/bin/sh
#DEBHELPER#
catcher-4dca913cc1111d3de8696ec391428a46706a68cf/debian/prerm 0000664 0000000 0000000 00000000027 12316104655 0022343 0 ustar 00root root 0000000 0000000 #!/bin/sh
#DEBHELPER#
catcher-4dca913cc1111d3de8696ec391428a46706a68cf/debian/pyversions 0000664 0000000 0000000 00000000005 12316104655 0023433 0 ustar 00root root 0000000 0000000 2.6-
catcher-4dca913cc1111d3de8696ec391428a46706a68cf/debian/rules 0000775 0000000 0000000 00000000365 12316104655 0022360 0 ustar 00root root 0000000 0000000 #!/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-4dca913cc1111d3de8696ec391428a46706a68cf/debian/watch 0000664 0000000 0000000 00000000012 12316104655 0022316 0 ustar 00root root 0000000 0000000 version=3
catcher-4dca913cc1111d3de8696ec391428a46706a68cf/dist/ 0000775 0000000 0000000 00000000000 12316104655 0021015 5 ustar 00root root 0000000 0000000 catcher-4dca913cc1111d3de8696ec391428a46706a68cf/dist/python-catcher.spec.in 0000664 0000000 0000000 00000001537 12316104655 0025234 0 ustar 00root root 0000000 0000000 %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-4dca913cc1111d3de8696ec391428a46706a68cf/setup.py 0000664 0000000 0000000 00000000666 12316104655 0021574 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
from distutils.core import setup
from setuptools import find_packages
execfile('catcher/_version.py')
setup(
name='python-catcher',
version=__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*']),
)