PyPDFForm 5.2.4__tar.gz → 5.2.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PKG-INFO +1 -1
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/__init__.py +1 -1
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/egress.py +98 -12
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/utils.py +32 -14
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/wrapper.py +4 -2
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm.egg-info/PKG-INFO +1 -1
- {pypdfform-5.2.4 → pypdfform-5.2.5}/tests/test_functional.py +16 -4
- {pypdfform-5.2.4 → pypdfform-5.2.5}/LICENSE +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/cli/__init__.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/cli/common.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/cli/create.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/cli/entry.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/cli/inspect.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/cli/remove.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/cli/root.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/cli/schemas/__init__.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/cli/schemas/create.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/cli/schemas/update.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/cli/update.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/__init__.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/adapter.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/annotations/__init__.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/annotations/base.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/annotations/link.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/annotations/stamp.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/annotations/text.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/annotations/text_markup.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/assets/__init__.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/assets/bedrock.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/assets/blank.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/constants.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/coordinate.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/deprecation.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/filler.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/font.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/hooks.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/image.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/middleware/__init__.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/middleware/base.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/middleware/checkbox.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/middleware/dropdown.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/middleware/image.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/middleware/radio.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/middleware/signature.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/middleware/text.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/patterns.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/raw/__init__.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/raw/circle.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/raw/ellipse.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/raw/image.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/raw/line.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/raw/rect.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/raw/text.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/template.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/types.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/watermark.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/widgets/__init__.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/widgets/base.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/widgets/checkbox.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/widgets/dropdown.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/widgets/image.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/widgets/radio.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/widgets/signature.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm/lib/widgets/text.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm.egg-info/SOURCES.txt +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm.egg-info/dependency_links.txt +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm.egg-info/entry_points.txt +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm.egg-info/requires.txt +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/PyPDFForm.egg-info/top_level.txt +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/README.md +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/pyproject.toml +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/setup.cfg +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/tests/test_bulk_create_fields.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/tests/test_create_widget.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/tests/test_draw_elements.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/tests/test_dropdown.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/tests/test_extract_middleware_attributes.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/tests/test_fill_max_length_text_field.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/tests/test_font_widths.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/tests/test_generate_appearance_streams.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/tests/test_js.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/tests/test_need_appearances.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/tests/test_paragraph.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/tests/test_signature.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/tests/test_use_full_widget_name.py +0 -0
- {pypdfform-5.2.4 → pypdfform-5.2.5}/tests/test_widget_attr_trigger.py +0 -0
|
@@ -28,6 +28,7 @@ from .constants import (
|
|
|
28
28
|
Fields,
|
|
29
29
|
JavaScript,
|
|
30
30
|
OpenAction,
|
|
31
|
+
Parent,
|
|
31
32
|
Root,
|
|
32
33
|
S,
|
|
33
34
|
Title,
|
|
@@ -140,10 +141,10 @@ def rebuild_acroform_fields(
|
|
|
140
141
|
|
|
141
142
|
The existing `/Fields` array is replaced, creating an AcroForm dictionary
|
|
142
143
|
when necessary. Each page annotation is resolved to a widget key, and only
|
|
143
|
-
annotations whose keys are present in `widget_keys`
|
|
144
|
-
array. Page annotation arrays are left
|
|
145
|
-
annotations are found, the original PDF
|
|
146
|
-
avoid an unnecessary rewrite.
|
|
144
|
+
annotations whose keys are present in `widget_keys` contribute their
|
|
145
|
+
top-level field object to the new array. Page annotation arrays are left
|
|
146
|
+
unchanged. When no matching page annotations are found, the original PDF
|
|
147
|
+
stream is returned unchanged to avoid an unnecessary rewrite.
|
|
147
148
|
|
|
148
149
|
Args:
|
|
149
150
|
pdf (bytes): The PDF stream whose AcroForm fields should be rebuilt.
|
|
@@ -155,25 +156,110 @@ def rebuild_acroform_fields(
|
|
|
155
156
|
bytes: The PDF stream with a rebuilt AcroForm `/Fields` array, or the
|
|
156
157
|
original stream when there are no matching widgets to rebuild.
|
|
157
158
|
"""
|
|
159
|
+
if not widget_keys:
|
|
160
|
+
return pdf
|
|
161
|
+
|
|
158
162
|
writer = PdfWriter(BytesIO(pdf))
|
|
159
163
|
root = writer._root_object # type: ignore # noqa: SLF001 # # pylint: disable=W0212
|
|
160
164
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
root[AcroForm][NameObject(Fields)] = ArrayObject([])
|
|
164
|
-
|
|
165
|
-
needs_update = False
|
|
165
|
+
fields = ArrayObject([])
|
|
166
|
+
seen_fields = set()
|
|
166
167
|
for page in writer.pages:
|
|
167
168
|
for annot in page.get(Annots, []):
|
|
168
169
|
key = get_widget_key(annot.get_object(), use_full_widget_name)
|
|
169
170
|
if key in widget_keys:
|
|
170
|
-
|
|
171
|
-
|
|
171
|
+
field_ref = _get_root_field_reference(writer, annot)
|
|
172
|
+
field_key = _field_reference_key(field_ref)
|
|
173
|
+
if field_key not in seen_fields:
|
|
174
|
+
fields.append(field_ref)
|
|
175
|
+
seen_fields.add(field_key)
|
|
172
176
|
|
|
173
|
-
if not
|
|
177
|
+
if not seen_fields:
|
|
174
178
|
return pdf
|
|
175
179
|
|
|
180
|
+
if AcroForm not in root:
|
|
181
|
+
root[NameObject(AcroForm)] = DictionaryObject({})
|
|
182
|
+
root[AcroForm][NameObject(Fields)] = fields
|
|
183
|
+
|
|
176
184
|
with BytesIO() as f:
|
|
177
185
|
writer.write(f)
|
|
178
186
|
f.seek(0)
|
|
179
187
|
return f.read()
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _get_root_field_reference(writer: PdfWriter, annot):
|
|
191
|
+
"""
|
|
192
|
+
Returns the top-level AcroForm field reference for an annotation.
|
|
193
|
+
|
|
194
|
+
Widget annotations can be leaf nodes under a parent field dictionary. The
|
|
195
|
+
catalog `/AcroForm/Fields` array must point at root fields rather than
|
|
196
|
+
child widgets, otherwise readers such as pypdf will not expose hierarchical
|
|
197
|
+
fields like radio groups via `get_fields`.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
writer (PdfWriter): The PDF writer that owns the annotation objects.
|
|
201
|
+
annot: The widget annotation reference or object.
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
IndirectObject: The top-level AcroForm field reference.
|
|
205
|
+
"""
|
|
206
|
+
field = annot
|
|
207
|
+
field_object = field.get_object()
|
|
208
|
+
visited = set()
|
|
209
|
+
|
|
210
|
+
while Parent in field_object and id(field_object) not in visited:
|
|
211
|
+
visited.add(id(field_object))
|
|
212
|
+
field = field_object[Parent]
|
|
213
|
+
field_object = field.get_object()
|
|
214
|
+
|
|
215
|
+
return _ensure_indirect_reference(writer, field)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _ensure_indirect_reference(writer: PdfWriter, field):
|
|
219
|
+
"""
|
|
220
|
+
Returns an indirect reference for a field object.
|
|
221
|
+
|
|
222
|
+
pypdf expects entries in `/AcroForm/Fields` to have an indirect reference
|
|
223
|
+
when it builds field objects. Parent fields cloned from page annotations can
|
|
224
|
+
appear as direct dictionaries that still know their original indirect
|
|
225
|
+
reference, so prefer that before adding a new indirect object.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
writer (PdfWriter): The PDF writer that will own newly added objects.
|
|
229
|
+
field: The field reference or direct field object to normalize.
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
IndirectObject: An indirect reference to the field object.
|
|
233
|
+
"""
|
|
234
|
+
field_object = field.get_object()
|
|
235
|
+
indirect_reference = getattr(field_object, "indirect_reference", None)
|
|
236
|
+
if indirect_reference is not None:
|
|
237
|
+
return indirect_reference
|
|
238
|
+
|
|
239
|
+
result = field
|
|
240
|
+
if not hasattr(field, "idnum"):
|
|
241
|
+
result = writer._add_object(field_object) # type: ignore[attr-defined] # noqa: SLF001 # pylint: disable=W0212
|
|
242
|
+
|
|
243
|
+
return result
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _field_reference_key(field_ref) -> tuple:
|
|
247
|
+
"""
|
|
248
|
+
Returns a stable key for deduplicating root fields in one writer.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
field_ref: The field reference to identify.
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
tuple: A stable key for the field reference.
|
|
255
|
+
"""
|
|
256
|
+
field_object = field_ref.get_object()
|
|
257
|
+
indirect_reference = getattr(field_object, "indirect_reference", field_ref)
|
|
258
|
+
idnum = getattr(indirect_reference, "idnum", None)
|
|
259
|
+
generation = getattr(indirect_reference, "generation", None)
|
|
260
|
+
|
|
261
|
+
result = (id(field_object),)
|
|
262
|
+
if idnum is not None:
|
|
263
|
+
result = idnum, generation
|
|
264
|
+
|
|
265
|
+
return result
|
|
@@ -354,16 +354,15 @@ def get_version(pdf: bytes) -> str | None:
|
|
|
354
354
|
start with a known PDF version identifier.
|
|
355
355
|
"""
|
|
356
356
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
break
|
|
357
|
+
version_identifier_length = len(VERSION_IDENTIFIERS[0])
|
|
358
|
+
version_identifier = pdf[:version_identifier_length]
|
|
359
|
+
if version_identifier not in VERSION_IDENTIFIERS:
|
|
360
|
+
return None
|
|
362
361
|
|
|
363
|
-
return
|
|
362
|
+
return version_identifier[len(VERSION_IDENTIFIER_PREFIX) :].decode()
|
|
364
363
|
|
|
365
364
|
|
|
366
|
-
def set_version(pdf: bytes, old: str, new: str) -> bytes:
|
|
365
|
+
def set_version(pdf: bytes, old: str | None, new: str | None) -> bytes:
|
|
367
366
|
"""
|
|
368
367
|
Replaces the first PDF header version marker in a byte stream.
|
|
369
368
|
|
|
@@ -374,15 +373,34 @@ def set_version(pdf: bytes, old: str, new: str) -> bytes:
|
|
|
374
373
|
|
|
375
374
|
Args:
|
|
376
375
|
pdf (bytes): The PDF stream to update.
|
|
377
|
-
old (str): The currently present PDF version string.
|
|
378
|
-
|
|
376
|
+
old (str | None): The currently present PDF version string. When
|
|
377
|
+
None, the header token after `%PDF-` is replaced if one exists.
|
|
378
|
+
new (str | None): The PDF version string to write into the header. When
|
|
379
|
+
None, the PDF stream is returned unchanged.
|
|
379
380
|
|
|
380
381
|
Returns:
|
|
381
382
|
bytes: The PDF stream with the first matching version marker replaced.
|
|
382
383
|
"""
|
|
383
384
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
385
|
+
if new is None or old == new:
|
|
386
|
+
return pdf
|
|
387
|
+
|
|
388
|
+
new_header = VERSION_IDENTIFIER_PREFIX + new.encode()
|
|
389
|
+
|
|
390
|
+
if old is None:
|
|
391
|
+
if not pdf.startswith(VERSION_IDENTIFIER_PREFIX):
|
|
392
|
+
return pdf
|
|
393
|
+
|
|
394
|
+
header_end = len(pdf)
|
|
395
|
+
for separator in (b"\r", b"\n"):
|
|
396
|
+
separator_index = pdf.find(separator, len(VERSION_IDENTIFIER_PREFIX))
|
|
397
|
+
if separator_index != -1:
|
|
398
|
+
header_end = min(header_end, separator_index)
|
|
399
|
+
|
|
400
|
+
return new_header + pdf[header_end:]
|
|
401
|
+
|
|
402
|
+
old_header = VERSION_IDENTIFIER_PREFIX + old.encode()
|
|
403
|
+
if pdf.startswith(old_header):
|
|
404
|
+
return new_header + pdf[len(old_header) :]
|
|
405
|
+
|
|
406
|
+
return pdf.replace(old_header, new_header, 1)
|
|
@@ -585,8 +585,10 @@ class PdfWrapper:
|
|
|
585
585
|
PdfWrapper: The `PdfWrapper` object, allowing for method chaining.
|
|
586
586
|
"""
|
|
587
587
|
|
|
588
|
-
|
|
589
|
-
|
|
588
|
+
current_stream = self._read()
|
|
589
|
+
updated_stream = set_version(current_stream, self.version, version)
|
|
590
|
+
if updated_stream != current_stream:
|
|
591
|
+
self._stream = updated_stream
|
|
590
592
|
self._version = version
|
|
591
593
|
|
|
592
594
|
return self
|
|
@@ -21,6 +21,7 @@ from PyPDFForm.lib.constants import (
|
|
|
21
21
|
from PyPDFForm.lib.deprecation import deprecation_notice
|
|
22
22
|
from PyPDFForm.lib.middleware.base import Widget
|
|
23
23
|
from PyPDFForm.lib.template import get_widget_key, get_widgets_by_page
|
|
24
|
+
from PyPDFForm.lib.utils import get_version, set_version
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
def test_deprecation_warning():
|
|
@@ -113,10 +114,11 @@ def test_write(template_stream, tmp_path):
|
|
|
113
114
|
|
|
114
115
|
def test_write_io(template_stream):
|
|
115
116
|
buff = BytesIO()
|
|
116
|
-
PdfWrapper(template_stream)
|
|
117
|
+
obj = PdfWrapper(template_stream)
|
|
118
|
+
obj.write(buff)
|
|
117
119
|
buff.seek(0)
|
|
118
120
|
|
|
119
|
-
assert buff.read()
|
|
121
|
+
assert PdfWrapper(buff.read()).widgets.keys() == obj.widgets.keys()
|
|
120
122
|
|
|
121
123
|
|
|
122
124
|
def test_fill_flatten_then_unflatten(template_stream, pdf_samples, data_dict, request):
|
|
@@ -507,7 +509,14 @@ def test_version(pdf_samples):
|
|
|
507
509
|
|
|
508
510
|
obj = PdfWrapper(os.path.join(pdf_samples, "versions", "unknown.pdf"))
|
|
509
511
|
assert obj.version is None
|
|
510
|
-
|
|
512
|
+
stream = obj.read()
|
|
513
|
+
assert stream.startswith(b"%PDF-2.1")
|
|
514
|
+
assert get_version(b"not a pdf") is None
|
|
515
|
+
assert set_version(stream, obj.version, None) == stream
|
|
516
|
+
assert set_version(b"not a pdf", None, "2.0") == b"not a pdf"
|
|
517
|
+
assert set_version(b"prefix %PDF-1.7\n", "1.7", "2.0") == b"prefix %PDF-2.0\n"
|
|
518
|
+
assert obj.change_version("2.0").version == "2.0"
|
|
519
|
+
assert obj.read().startswith(b"%PDF-2.0")
|
|
511
520
|
|
|
512
521
|
|
|
513
522
|
@pytest.mark.requires_zlib_over_zlib_ng
|
|
@@ -855,7 +864,10 @@ def test_remove_fields_update_widgets(template_stream):
|
|
|
855
864
|
|
|
856
865
|
|
|
857
866
|
def test_remove_fields_no_keys_specified(template_stream):
|
|
858
|
-
assert
|
|
867
|
+
assert (
|
|
868
|
+
PdfWrapper(template_stream).remove_fields([]).widgets.keys()
|
|
869
|
+
== PdfWrapper(template_stream).widgets.keys()
|
|
870
|
+
)
|
|
859
871
|
|
|
860
872
|
|
|
861
873
|
def test_merge(template_stream):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|