Skip to content

Commit aec2112

Browse files
authored
Merge pull request #1 from cocolato/dev
init
2 parents 935fded + a673dee commit aec2112

File tree

11 files changed

+273
-0
lines changed

11 files changed

+273
-0
lines changed

.flake8

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[flake8]
2+
max-line-length=120

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/venv
2+
__pycache__
3+
*.pyc
4+
/.pytest_cache
5+
*.dump
6+
test.py

poetry.lock

Lines changed: 33 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pydumpling/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from __future__ import absolute_import, division, print_function, unicode_literals
2+
3+
from .debug_dumpling import debug_dumpling, load_dumpling
4+
from .pydumpling import save_dumping, __version__
5+
6+
__version__ == __version__
7+
__all__ = ["debug_dumpling", "load_dumpling", "save_dumping"]

pydumpling/debug_dumpling.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from __future__ import absolute_import, division, print_function, unicode_literals
2+
3+
import gzip
4+
import pdb
5+
import dill
6+
import pickle
7+
from distutils.version import StrictVersion
8+
import inspect
9+
import types
10+
from .fake_types import FakeFrame, FakeTraceback, FakeCode
11+
12+
13+
def load_dumpling(filename):
14+
with gzip.open(filename, "rb") as f:
15+
try:
16+
return dill.load(f)
17+
except Exception:
18+
return pickle.load(f)
19+
20+
21+
def debug_dumpling(dump_file, pdb=pdb):
22+
inspect.isframe = lambda obj: isinstance(
23+
obj, types.FrameType) or obj.__class__ == FakeFrame
24+
inspect.istraceback = lambda obj: isinstance(
25+
obj, types.TracebackType) or obj.__class__ == FakeTraceback
26+
inspect.iscode = lambda obj: isinstance(
27+
obj, types.CodeType) or obj.__class__ == FakeCode
28+
dumpling = load_dumpling(dump_file)
29+
if not StrictVersion("0.0.1") <= StrictVersion(dumpling["version"]) < StrictVersion("1.0.0"):
30+
raise ValueError("Unsupported dumpling version: %s" %
31+
dumpling["version"])
32+
tb = dumpling["traceback"]
33+
pdb.post_mortem(tb)

pydumpling/fake_types.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
from __future__ import absolute_import, division, print_function, unicode_literals
2+
3+
import os
4+
import six
5+
import dill
6+
7+
8+
class FakeType(object):
9+
@classmethod
10+
def _safe_repr(cls, v):
11+
try:
12+
return repr(v)
13+
except Exception as e:
14+
return "repr error: %s" % str(e)
15+
16+
@classmethod
17+
def _convert_dict(cls, v):
18+
return {cls._convert(k): cls._convert(i) for k, i in v.items()}
19+
20+
@classmethod
21+
def _convert_obj(cls, obj):
22+
try:
23+
return FakeClass(cls._safe_repr(obj), cls._convert_dict(obj.__dict__))
24+
except Exception:
25+
return cls._convert(obj)
26+
27+
@classmethod
28+
def _convert_seq(cls, v):
29+
return map(cls._convert, v)
30+
31+
@classmethod
32+
def _convert(cls, v):
33+
34+
if v is None:
35+
return v
36+
37+
if dill is not None:
38+
try:
39+
dill.dumps(v)
40+
return v
41+
except Exception:
42+
return cls._safe_repr(v)
43+
else:
44+
from datetime import date, time, datetime, timedelta
45+
46+
BUILTIN = (str, unicode, int, long, float, date, time, datetime, timedelta) if six.PY2 \
47+
else (str, int, float, date, time, datetime, timedelta) # noqa: F821
48+
49+
if type(v) in BUILTIN:
50+
return v
51+
52+
if isinstance(v, (tuple, list, set)):
53+
return type(v)(cls._convert_seq(v))
54+
55+
if isinstance(v, dict):
56+
return cls._convert_dict(v)
57+
58+
return cls._safe_repr(v)
59+
60+
61+
class FakeTraceback(FakeType):
62+
63+
def __init__(self, traceback):
64+
self.tb_frame = FakeFrame(
65+
traceback.tb_frame) if traceback.tb_frame else None
66+
self.tb_lineno = traceback.tb_lineno
67+
self.tb_next = FakeTraceback(
68+
traceback.tb_next) if traceback.tb_next else None
69+
self.tb_lasti = 0
70+
71+
72+
class FakeFrame(FakeType):
73+
def __init__(self, frame):
74+
self.f_code = FakeCode(frame.f_code)
75+
self.f_locals = self._convert_dict(frame.f_locals)
76+
if "self" in frame.f_locals:
77+
self.f_locals["self"] = self._convert_obj(frame.f_locals["self"])
78+
self.f_globals = self._convert_dict(frame.f_globals)
79+
self.f_lineno = frame.f_lineno
80+
self.f_back = FakeFrame(frame.f_back) if frame.f_back else None
81+
82+
83+
class FakeClass(FakeType):
84+
85+
def __init__(self, repr, vals):
86+
self.__repr = repr
87+
self.__dict__.update(vars)
88+
89+
def __repr__(self):
90+
return self.__repr
91+
92+
93+
class FakeCode(FakeType):
94+
95+
def __init__(self, code):
96+
self.co_filename = os.path.abspath(code.co_filename)
97+
self.co_name = code.co_name
98+
self.co_argcount = code.co_argcount
99+
self.co_consts = tuple(FakeCode(c) if hasattr(
100+
c, "co_filename") else c for c in code.co_consts)
101+
self.co_firstlineno = code.co_firstlineno
102+
self.co_lnotab = code.co_lnotab
103+
self.co_varnames = code.co_varnames
104+
self.co_flags = code.co_flags
105+
self.co_code = code.co_code
106+
self._co_lines = list(code.co_lines()) if hasattr(
107+
code, "co_lines") else []
108+
if hasattr(code, "co_kwonlyargcount"):
109+
self.co_kwonlyargcount = code.co_kwonlyargcount
110+
111+
def co_lines(self):
112+
return iter(self._co_lines)

pydumpling/pydumpling.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from __future__ import absolute_import, division, print_function, unicode_literals
2+
3+
import gzip
4+
import sys
5+
import dill
6+
import pickle
7+
import warnings
8+
from .fake_types import FakeTraceback
9+
10+
__version__ = "0.0.1"
11+
12+
13+
def save_dumping(filename=None, tb=None):
14+
try:
15+
if tb is None:
16+
tb = sys.exc_info()[2]
17+
18+
if filename is None:
19+
filename = "%s:%d.dump" % (
20+
tb.tb_frame.f_code.co_filename, tb.tb_frame.f_lineno)
21+
22+
fake_tb = FakeTraceback(tb)
23+
dumpling = {
24+
"traceback": fake_tb,
25+
"version": __version__,
26+
"dump_type": "DILL"
27+
}
28+
with gzip.open(filename, "wb") as f:
29+
try:
30+
dill.dump(dumpling, f, protocol=dill.HIGHEST_PROTOCOL)
31+
except Exception:
32+
dumpling["dump_type"] = "PICKLE"
33+
pickle.dump(dumpling, f, protocol=dill.HIGHEST_PROTOCOL)
34+
except Exception as e:
35+
err_msg = "Unexpected error: %s when dumping traceback" % str(e)
36+
warnings.warn(err_msg, RuntimeWarning)

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ readme = "README.md"
77

88
[tool.poetry.dependencies]
99
python = "^3.10"
10+
dill = "^0.3.6"
11+
six = "^1.16.0"
1012

1113

1214
[build-system]

test.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#! /usr/bin/env bash
2+
3+
set -e
4+
5+
pip install pytest pytest-order flake8 six
6+
7+
flake8 ./tests ./pydumpling
8+
python -m pytest

tests/test_debug.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from pydumpling import load_dumpling, __version__
2+
from pydumpling.fake_types import FakeTraceback
3+
import pytest
4+
5+
6+
@pytest.mark.order(2)
7+
def test_debug():
8+
dumpling = load_dumpling("test.dump")
9+
assert isinstance(dumpling["traceback"], FakeTraceback)
10+
assert dumpling["version"] == __version__

0 commit comments

Comments
 (0)