forked from reingart/pyfpdf
-
Notifications
You must be signed in to change notification settings - Fork 265
/
Copy pathrecorder.py
78 lines (62 loc) · 2.7 KB
/
recorder.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
"""
A wrapper class to allow rewinding/replaying changes made to a FPDF instance.
The contents of this module are internal to fpdf2, and not part of the public API.
They may change at any time without prior warning or any deprecation period,
in non-backward-compatible ways.
"""
import types, warnings
from copy import deepcopy
from .deprecation import get_stack_level
from .errors import FPDFException
class FPDFRecorder:
"""
The class is aimed to be used as wrapper around fpdf.FPDF:
pdf = FPDF()
recorder = FPDFRecorder(pdf)
Its aim is dual:
* allow to **rewind** to the state of the FPDF instance passed to its constructor,
reverting all changes made to its internal state
* allow to **replay** again all the methods calls performed
on the recorder instance between its creation and the last call to rewind()
Note that method can be called on a FPDFRecorder instance using its .pdf attribute
so that they are not recorded & replayed later, on a call to .replay().
Note that using this class means to duplicate the FPDF `bytearray` buffer:
when generating large PDFs, doubling memory usage may be troublesome.
"""
def __init__(self, pdf, accept_page_break=True):
self.pdf = pdf
self._initial = deepcopy(self.pdf.__dict__)
self._calls = []
if not accept_page_break:
self.accept_page_break = False
def __getattr__(self, name):
attr = getattr(self.pdf, name)
if callable(attr):
return CallRecorder(attr, self._calls)
return attr
def rewind(self):
self.pdf.__dict__ = self._initial
self._initial = deepcopy(self.pdf.__dict__)
def replay(self):
for call in self._calls:
func, args, kwargs = call
try:
result = func(*args, **kwargs)
if isinstance(result, types.GeneratorType):
warnings.warn(
"Detected usage of a context manager inside an unbreakable() section, which is not supported",
stacklevel=get_stack_level(),
)
# The results of other methods can also be invalidated: .pages_count, page_no(), get_x() / get_y(), will_page_break()
except Exception as error:
raise FPDFException(
f"Failed to replay FPDF call: {func}(*{args}, **{kwargs})"
) from error
self._calls = []
class CallRecorder:
def __init__(self, func, calls):
self._func = func
self._calls = calls
def __call__(self, *args, **kwargs):
self._calls.append((self._func, args, kwargs))
return self._func(*args, **kwargs)