From a4629d3dbdef2759ff6059446fcffd13ecf6a396 Mon Sep 17 00:00:00 2001 From: Martin Thoma Date: Sat, 17 Dec 2022 05:53:39 +0100 Subject: [PATCH] MAINT: Deprecate features with PyPDF2==3.0.0 (#1489) Deprecate features, but keep helpful exceptions. That means that the names still need to be there. The deprecated names will stay until 4.0.0 to help the community transition to the new names. --- PyPDF2/_merger.py | 60 +++++----- PyPDF2/_page.py | 179 ++++++++++------------------- PyPDF2/_reader.py | 71 ++++++------ PyPDF2/_utils.py | 49 +++++++- PyPDF2/_writer.py | 151 +++++++++++++----------- PyPDF2/errors.py | 6 + PyPDF2/generic/_base.py | 30 ++--- PyPDF2/generic/_data_structures.py | 47 ++++---- PyPDF2/generic/_outline.py | 4 +- PyPDF2/generic/_rectangle.py | 56 ++++----- PyPDF2/types.py | 8 +- PyPDF2/xmp.py | 26 +++-- docs/dev/deprecations.md | 10 +- tests/test_generic.py | 5 +- tests/test_merger.py | 31 +++-- tests/test_page.py | 20 ++-- tests/test_reader.py | 7 +- tests/test_utils.py | 17 ++- tests/test_writer.py | 28 +++-- 19 files changed, 414 insertions(+), 391 deletions(-) diff --git a/PyPDF2/_merger.py b/PyPDF2/_merger.py index 33e8725bf..4d7a659d8 100644 --- a/PyPDF2/_merger.py +++ b/PyPDF2/_merger.py @@ -46,8 +46,8 @@ from ._reader import PdfReader from ._utils import ( StrByteType, - deprecate_bookmark, - deprecate_with_replacement, + deprecation_bookmark, + deprecation_with_replacement, str_, ) from ._writer import PdfWriter @@ -102,7 +102,7 @@ class PdfMerger: file-like object. """ - @deprecate_bookmark(bookmarks="outline") + @deprecation_bookmark(bookmarks="outline") def __init__( self, strict: bool = False, fileobj: Union[Path, StrByteType] = "" ) -> None: @@ -130,7 +130,7 @@ def __exit__( self.write(self.fileobj) self.close() - @deprecate_bookmark(bookmark="outline_item", import_bookmarks="import_outline") + @deprecation_bookmark(bookmark="outline_item", import_bookmarks="import_outline") def merge( self, page_number: Optional[int] = None, @@ -171,9 +171,11 @@ def merge( old_term = "position" new_term = "page_number" warnings.warn( - message=( - f"{old_term} is deprecated as an argument. Use {new_term} instead" - ) + ( + f"{old_term} is deprecated as an argument and will be " + f"removed in PyPDF2=4.0.0. Use {new_term} instead" + ), + DeprecationWarning, ) else: raise ValueError( @@ -281,7 +283,7 @@ def _create_stream( ) return stream, encryption_obj - @deprecate_bookmark(bookmark="outline_item", import_bookmarks="import_outline") + @deprecation_bookmark(bookmark="outline_item", import_bookmarks="import_outline") def append( self, fileobj: Union[StrByteType, PdfReader, Path], @@ -375,7 +377,7 @@ def addMetadata(self, infos: Dict[str, Any]) -> None: # pragma: no cover Use :meth:`add_metadata` instead. """ - deprecate_with_replacement("addMetadata", "add_metadata") + deprecation_with_replacement("addMetadata", "add_metadata") self.add_metadata(infos) def setPageLayout(self, layout: LayoutType) -> None: # pragma: no cover @@ -384,7 +386,7 @@ def setPageLayout(self, layout: LayoutType) -> None: # pragma: no cover Use :meth:`set_page_layout` instead. """ - deprecate_with_replacement("setPageLayout", "set_page_layout") + deprecation_with_replacement("setPageLayout", "set_page_layout") self.set_page_layout(layout) def set_page_layout(self, layout: LayoutType) -> None: @@ -421,7 +423,7 @@ def setPageMode(self, mode: PagemodeType) -> None: # pragma: no cover Use :meth:`set_page_mode` instead. """ - deprecate_with_replacement("setPageMode", "set_page_mode") + deprecation_with_replacement("setPageMode", "set_page_mode", "3.0.0") self.set_page_mode(mode) def set_page_mode(self, mode: PagemodeType) -> None: @@ -513,7 +515,7 @@ def _write_dests(self) -> None: if pageno is not None: self.output.add_named_destination_object(named_dest) - @deprecate_bookmark(bookmarks="outline") + @deprecation_bookmark(bookmarks="outline") def _write_outline( self, outline: Optional[Iterable[OutlineItem]] = None, @@ -541,7 +543,7 @@ def _write_outline( del outline_item["/Page"], outline_item["/Type"] last_added = self.output.add_outline_item_dict(outline_item, parent) - @deprecate_bookmark(bookmark="outline_item") + @deprecation_bookmark(bookmark="outline_item") def _write_outline_item_on_page( self, outline_item: Union[OutlineItem, Destination], page: _MergedPage ) -> None: @@ -594,7 +596,7 @@ def _associate_dests_to_pages(self, pages: List[_MergedPage]) -> None: ) named_dest[NameObject("/Page")] = NumberObject(pageno) - @deprecate_bookmark(bookmarks="outline") + @deprecation_bookmark(bookmarks="outline") def _associate_outline_items_to_pages( self, pages: List[_MergedPage], outline: Optional[Iterable[OutlineItem]] = None ) -> None: @@ -619,7 +621,7 @@ def _associate_outline_items_to_pages( if pageno is not None: outline_item[NameObject("/Page")] = NumberObject(pageno) - @deprecate_bookmark(bookmark="outline_item") + @deprecation_bookmark(bookmark="outline_item") def find_outline_item( self, outline_item: Dict[str, Any], @@ -644,7 +646,7 @@ def find_outline_item( return None - @deprecate_bookmark(bookmark="outline_item") + @deprecation_bookmark(bookmark="outline_item") def find_bookmark( self, outline_item: Dict[str, Any], @@ -688,9 +690,11 @@ def add_outline_item( old_term = "pagenum" new_term = "page_number" warnings.warn( - message=( - f"{old_term} is deprecated as an argument. Use {new_term} instead" - ) + ( + f"{old_term} is deprecated as an argument and will be " + f"removed in PyPDF2==4.0.0. Use {new_term} instead" + ), + DeprecationWarning, ) page_number = pagenum if page_number is None: @@ -724,7 +728,7 @@ def addBookmark( .. deprecated:: 1.28.0 Use :meth:`add_outline_item` instead. """ - deprecate_with_replacement("addBookmark", "add_outline_item") + deprecation_with_replacement("addBookmark", "add_outline_item", "3.0.0") return self.add_outline_item( title, pagenum, @@ -750,7 +754,7 @@ def add_bookmark( .. deprecated:: 2.9.0 Use :meth:`add_outline_item` instead. """ - deprecate_with_replacement("addBookmark", "add_outline_item") + deprecation_with_replacement("addBookmark", "add_outline_item", "3.0.0") return self.add_outline_item( title, pagenum, @@ -766,7 +770,9 @@ def addNamedDestination(self, title: str, pagenum: int) -> None: # pragma: no c .. deprecated:: 1.28.0 Use :meth:`add_named_destination` instead. """ - deprecate_with_replacement("addNamedDestination", "add_named_destination") + deprecation_with_replacement( + "addNamedDestination", "add_named_destination", "3.0.0" + ) return self.add_named_destination(title, pagenum) def add_named_destination( @@ -789,9 +795,11 @@ def add_named_destination( old_term = "pagenum" new_term = "page_number" warnings.warn( - message=( - f"{old_term} is deprecated as an argument. Use {new_term} instead" - ) + ( + f"{old_term} is deprecated as an argument and will be " + f"removed in PyPDF2==4.0.0. Use {new_term} instead" + ), + DeprecationWarning, ) page_number = pagenum if page_number is None: @@ -806,7 +814,7 @@ def add_named_destination( class PdfFileMerger(PdfMerger): # pragma: no cover def __init__(self, *args: Any, **kwargs: Any) -> None: - deprecate_with_replacement("PdfFileMerger", "PdfMerger") + deprecation_with_replacement("PdfFileMerger", "PdfMerger", "3.0.0") if "strict" not in kwargs and len(args) < 1: kwargs["strict"] = True # maintain the default diff --git a/PyPDF2/_page.py b/PyPDF2/_page.py index 309874266..b725910f9 100644 --- a/PyPDF2/_page.py +++ b/PyPDF2/_page.py @@ -51,8 +51,8 @@ CompressedTransformationMatrix, File, TransformationMatrixType, - deprecate_no_replacement, - deprecate_with_replacement, + deprecation_no_replacement, + deprecation_with_replacement, logger_warning, matrix_multiply, ) @@ -73,7 +73,6 @@ NullObject, NumberObject, RectangleObject, - TextStringObject, encode_pdfdocencoding, ) @@ -133,7 +132,7 @@ def _get_rectangle(self: Any, name: str, defaults: Iterable[str]) -> RectangleOb def getRectangle( self: Any, name: str, defaults: Iterable[str] ) -> RectangleObject: # pragma: no cover - deprecate_no_replacement("getRectangle") + deprecation_no_replacement("getRectangle", "3.0.0") return _get_rectangle(self, name, defaults) @@ -145,7 +144,7 @@ def _set_rectangle(self: Any, name: str, value: Union[RectangleObject, float]) - def setRectangle( self: Any, name: str, value: Union[RectangleObject, float] ) -> None: # pragma: no cover - deprecate_no_replacement("setRectangle") + deprecation_no_replacement("setRectangle", "3.0.0") _set_rectangle(self, name, value) @@ -154,7 +153,7 @@ def _delete_rectangle(self: Any, name: str) -> None: def deleteRectangle(self: Any, name: str) -> None: # pragma: no cover - deprecate_no_replacement("deleteRectangle") + deprecation_no_replacement("deleteRectangle", "3.0.0") del self[name] @@ -169,7 +168,7 @@ def _create_rectangle_accessor(name: str, fallback: Iterable[str]) -> property: def createRectangleAccessor( name: str, fallback: Iterable[str] ) -> property: # pragma: no cover - deprecate_no_replacement("createRectangleAccessor") + deprecation_no_replacement("createRectangleAccessor", "3.0.0") return _create_rectangle_accessor(name, fallback) @@ -295,14 +294,18 @@ def __init__( self, pdf: Optional[PdfReaderProtocol] = None, indirect_reference: Optional[IndirectObject] = None, - indirect_ref: Optional[IndirectObject] = None, + indirect_ref: Optional[IndirectObject] = None, # deprecated ) -> None: DictionaryObject.__init__(self) self.pdf: Optional[PdfReaderProtocol] = pdf if indirect_ref is not None: # deprecated warnings.warn( - "Use indirect_reference instead of indirect_ref.", DeprecationWarning + ( + "indirect_ref is deprecated and will be removed in " + "PyPDF2 4.0.0. Use indirect_reference instead of indirect_ref." + ), + DeprecationWarning, ) if indirect_reference is not None: raise ValueError("Use indirect_reference instead of indirect_ref.") @@ -312,7 +315,11 @@ def __init__( @property def indirect_ref(self) -> Optional[IndirectObject]: # deprecated warnings.warn( - "Use indirect_reference instead of indirect_ref.", DeprecationWarning + ( + "indirect_ref is deprecated and will be removed in PyPDF2 4.0.0" + "Use indirect_reference instead of indirect_ref." + ), + DeprecationWarning, ) return self.indirect_reference @@ -387,7 +394,7 @@ def createBlankPage( Use :meth:`create_blank_page` instead. """ - deprecate_with_replacement("createBlankPage", "create_blank_page") + deprecation_with_replacement("createBlankPage", "create_blank_page", "3.0.0") return PageObject.create_blank_page(pdf, width, height) @property @@ -478,7 +485,7 @@ def rotate(self, angle: int) -> "PageObject": return self def rotate_clockwise(self, angle: int) -> "PageObject": # pragma: no cover - deprecate_with_replacement("rotate_clockwise", "rotate") + deprecation_with_replacement("rotate_clockwise", "rotate", "3.0.0") return self.rotate(angle) def rotateClockwise(self, angle: int) -> "PageObject": # pragma: no cover @@ -487,7 +494,7 @@ def rotateClockwise(self, angle: int) -> "PageObject": # pragma: no cover Use :meth:`rotate_clockwise` instead. """ - deprecate_with_replacement("rotateClockwise", "rotate") + deprecation_with_replacement("rotateClockwise", "rotate", "3.0.0") return self.rotate(angle) def rotateCounterClockwise(self, angle: int) -> "PageObject": # pragma: no cover @@ -496,7 +503,7 @@ def rotateCounterClockwise(self, angle: int) -> "PageObject": # pragma: no cove Use :meth:`rotate_clockwise` with a negative argument instead. """ - deprecate_with_replacement("rotateCounterClockwise", "rotate") + deprecation_with_replacement("rotateCounterClockwise", "rotate", "3.0.0") return self.rotate(-angle) @staticmethod @@ -592,7 +599,7 @@ def getContents(self) -> Optional[ContentStream]: # pragma: no cover Use :meth:`get_contents` instead. """ - deprecate_with_replacement("getContents", "get_contents") + deprecation_with_replacement("getContents", "get_contents", "3.0.0") return self.get_contents() def merge_page(self, page2: "PageObject", expand: bool = False) -> None: @@ -618,7 +625,7 @@ def mergePage(self, page2: "PageObject") -> None: # pragma: no cover Use :meth:`merge_page` instead. """ - deprecate_with_replacement("mergePage", "merge_page") + deprecation_with_replacement("mergePage", "merge_page", "3.0.0") return self.merge_page(page2) def _merge_page( @@ -785,9 +792,10 @@ def mergeTransformedPage( Use :meth:`add_transformation` and :meth:`merge_page` instead. """ - deprecate_with_replacement( + deprecation_with_replacement( "page.mergeTransformedPage(page2, ctm)", "page2.add_transformation(ctm); page.merge_page(page2)", + "3.0.0", ) if isinstance(ctm, Transformation): ctm = ctm.ctm @@ -818,9 +826,10 @@ def mergeScaledPage( Use :meth:`add_transformation` and :meth:`merge_page` instead. """ - deprecate_with_replacement( + deprecation_with_replacement( "page.mergeScaledPage(page2, scale, expand)", "page2.add_transformation(Transformation().scale(scale)); page.merge_page(page2, expand)", + "3.0.0", ) op = Transformation().scale(scale, scale) self.mergeTransformedPage(page2, op, expand) @@ -842,9 +851,10 @@ def mergeRotatedPage( Use :meth:`add_transformation` and :meth:`merge_page` instead. """ - deprecate_with_replacement( + deprecation_with_replacement( "page.mergeRotatedPage(page2, rotation, expand)", "page2.add_transformation(Transformation().rotate(rotation)); page.merge_page(page2, expand)", + "3.0.0", ) op = Transformation().rotate(rotation) self.mergeTransformedPage(page2, op, expand) @@ -867,9 +877,10 @@ def mergeTranslatedPage( Use :meth:`add_transformation` and :meth:`merge_page` instead. """ - deprecate_with_replacement( + deprecation_with_replacement( "page.mergeTranslatedPage(page2, tx, ty, expand)", "page2.add_transformation(Transformation().translate(tx, ty)); page.merge_page(page2, expand)", + "3.0.0", ) op = Transformation().translate(tx, ty) self.mergeTransformedPage(page2, op, expand) @@ -898,9 +909,10 @@ def mergeRotatedTranslatedPage( Use :meth:`add_transformation` and :meth:`merge_page` instead. """ - deprecate_with_replacement( + deprecation_with_replacement( "page.mergeRotatedTranslatedPage(page2, rotation, tx, ty, expand)", "page2.add_transformation(Transformation().rotate(rotation).translate(tx, ty)); page.merge_page(page2, expand)", + "3.0.0", ) op = Transformation().translate(-tx, -ty).rotate(rotation).translate(tx, ty) return self.mergeTransformedPage(page2, op, expand) @@ -923,9 +935,10 @@ def mergeRotatedScaledPage( Use :meth:`add_transformation` and :meth:`merge_page` instead. """ - deprecate_with_replacement( + deprecation_with_replacement( "page.mergeRotatedScaledPage(page2, rotation, scale, expand)", "page2.add_transformation(Transformation().rotate(rotation).scale(scale)); page.merge_page(page2, expand)", + "3.0.0", ) op = Transformation().rotate(rotation).scale(scale, scale) self.mergeTransformedPage(page2, op, expand) @@ -954,9 +967,10 @@ def mergeScaledTranslatedPage( Use :meth:`add_transformation` and :meth:`merge_page` instead. """ - deprecate_with_replacement( + deprecation_with_replacement( "page.mergeScaledTranslatedPage(page2, scale, tx, ty, expand)", "page2.add_transformation(Transformation().scale(scale).translate(tx, ty)); page.merge_page(page2, expand)", + "3.0.0", ) op = Transformation().scale(scale, scale).translate(tx, ty) return self.mergeTransformedPage(page2, op, expand) @@ -988,9 +1002,10 @@ def mergeRotatedScaledTranslatedPage( Use :meth:`add_transformation` and :meth:`merge_page` instead. """ - deprecate_with_replacement( + deprecation_with_replacement( "page.mergeRotatedScaledTranslatedPage(page2, rotation, tx, ty, expand)", "page2.add_transformation(Transformation().rotate(rotation).scale(scale)); page.merge_page(page2, expand)", + "3.0.0", ) op = Transformation().rotate(rotation).scale(scale, scale).translate(tx, ty) self.mergeTransformedPage(page2, op, expand) @@ -1059,7 +1074,7 @@ def addTransformation( Use :meth:`add_transformation` instead. """ - deprecate_with_replacement("addTransformation", "add_transformation") + deprecation_with_replacement("addTransformation", "add_transformation", "3.0.0") self.add_transformation(ctm) def scale(self, sx: float, sy: float) -> None: @@ -1129,7 +1144,7 @@ def scaleBy(self, factor: float) -> None: # pragma: no cover Use :meth:`scale_by` instead. """ - deprecate_with_replacement("scaleBy", "scale_by") + deprecation_with_replacement("scaleBy", "scale_by", "3.0.0") self.scale(factor, factor) def scale_to(self, width: float, height: float) -> None: @@ -1150,7 +1165,7 @@ def scaleTo(self, width: float, height: float) -> None: # pragma: no cover Use :meth:`scale_to` instead. """ - deprecate_with_replacement("scaleTo", "scale_to") + deprecation_with_replacement("scaleTo", "scale_to", "3.0.0") self.scale_to(width, height) def compress_content_streams(self) -> None: @@ -1173,87 +1188,11 @@ def compressContentStreams(self) -> None: # pragma: no cover Use :meth:`compress_content_streams` instead. """ - deprecate_with_replacement("compressContentStreams", "compress_content_streams") + deprecation_with_replacement( + "compressContentStreams", "compress_content_streams", "3.0.0" + ) self.compress_content_streams() - def _extract_text_old( - self, Tj_sep: str = "", TJ_sep: str = "" - ) -> str: # pragma: no cover - """ - Locate all text drawing commands, in the order they are provided in the - content stream, and extract the text. This works well for some PDF - files, but poorly for others, depending on the generator used. This will - be refined in the future. Do not rely on the order of text coming out of - this function, as it will change if this function is made more - sophisticated. - - :return: a string object. - """ - text = "" - content = self[PG.CONTENTS].get_object() - if not isinstance(content, ContentStream): - content = ContentStream(content, self.pdf) - # Note: we check all strings are TextStringObjects. ByteStringObjects - # are strings where the byte->string encoding was unknown, so adding - # them to the text here would be gibberish. - - space_scale = 1.0 - - for operands, operator in content.operations: - # Missing operators: - # Tf: text font - # Tfs: text font size - # Tc: '5.2.1 Character Spacing' - # Th: '5.2.3 Horizontal Scaling' - # Tl: '5.2.4 Leading' - # Tmode: '5.2.5 Text Rendering Mode' - # Trise: '5.2.6 Text Rise' - - if operator in [b"Tf", b"Tfs", b"Tc", b"Th", b"Tl", b"Tmode"]: - pass - elif operator == b"Tw": # word spacing - # See '5.2.2 Word Spacing' - space_scale = 1.0 + float(operands[0]) - elif operator == b"Tj": - # See 'TABLE 5.6 Text-showing operators' - _text = operands[0] - if isinstance(_text, TextStringObject): - text += Tj_sep - text += _text - text += "\n" - elif operator == b"T*": - # See 'TABLE 5.5 Text-positioning operators' - text += "\n" - elif operator == b"'": - # See 'TABLE 5.6 Text-showing operators' - text += "\n" - _text = operands[0] - if isinstance(_text, TextStringObject): - text += operands[0] - elif operator == b'"': - # See 'TABLE 5.6 Text-showing operators' - _text = operands[2] - if isinstance(_text, TextStringObject): - text += "\n" - text += _text - elif operator == b"TJ": - # See 'TABLE 5.6 Text-showing operators' - for i in operands[0]: - if isinstance(i, TextStringObject): - text += TJ_sep - text += i - elif isinstance(i, (NumberObject, FloatObject)): - # a positive value decreases and the negative value increases - # space - if int(i) < -space_scale * 250: - if len(text) == 0 or text[-1] != " ": - text += " " - else: - if len(text) > 1 and text[-1] == " ": - text = text[:-1] - text += "\n" - return text - def _debug_for_extract(self) -> str: # pragma: no cover out = "" for ope, op in ContentStream( @@ -1779,8 +1718,8 @@ def extract_text( Additionally you can provide visitor-methods to get informed on all operands and all text-objects. For example in some PDF files this can be useful to parse tables. - :param Tj_sep: Deprecated. Kept for compatibility until PyPDF2==4.0.0 - :param TJ_sep: Deprecated. Kept for compatibility until PyPDF2==4.0.0 + :param Tj_sep: Deprecated. Kept for compatibility until PyPDF2 4.0.0 + :param TJ_sep: Deprecated. Kept for compatibility until PyPDF2 4.0.0 :param Tuple[int, ...] orientations: list of orientations text_extraction will look for default = (0, 90, 180, 270) note: currently only 0(Up),90(turned Left), 180(upside Down), @@ -1829,7 +1768,7 @@ def extract_text( raise TypeError(f"Invalid positional parameter {args[0]}") if Tj_sep is not None or TJ_sep is not None: warnings.warn( - "parameters Tj_Sep, TJ_sep depreciated, and will be removed in PyPDF2 3.0.0.", + "parameters Tj_Sep, TJ_sep depreciated, and will be removed in PyPDF2 4.0.0.", DeprecationWarning, ) @@ -1882,7 +1821,7 @@ def extractText( Use :meth:`extract_text` instead. """ - deprecate_with_replacement("extractText", "extract_text") + deprecation_with_replacement("extractText", "extract_text", "3.0.0") return self.extract_text() def _get_fonts(self) -> Tuple[Set[str], Set[str]]: @@ -1911,7 +1850,7 @@ def mediaBox(self) -> RectangleObject: # pragma: no cover Use :py:attr:`mediabox` instead. """ - deprecate_with_replacement("mediaBox", "mediabox") + deprecation_with_replacement("mediaBox", "mediabox", "3.0.0") return self.mediabox @mediaBox.setter @@ -1921,7 +1860,7 @@ def mediaBox(self, value: RectangleObject) -> None: # pragma: no cover Use :py:attr:`mediabox` instead. """ - deprecate_with_replacement("mediaBox", "mediabox") + deprecation_with_replacement("mediaBox", "mediabox", "3.0.0") self.mediabox = value cropbox = _create_rectangle_accessor("/CropBox", (PG.MEDIABOX,)) @@ -1940,12 +1879,12 @@ def cropBox(self) -> RectangleObject: # pragma: no cover Use :py:attr:`cropbox` instead. """ - deprecate_with_replacement("cropBox", "cropbox") + deprecation_with_replacement("cropBox", "cropbox", "3.0.0") return self.cropbox @cropBox.setter def cropBox(self, value: RectangleObject) -> None: # pragma: no cover - deprecate_with_replacement("cropBox", "cropbox") + deprecation_with_replacement("cropBox", "cropbox", "3.0.0") self.cropbox = value bleedbox = _create_rectangle_accessor("/BleedBox", ("/CropBox", PG.MEDIABOX)) @@ -1962,12 +1901,12 @@ def bleedBox(self) -> RectangleObject: # pragma: no cover Use :py:attr:`bleedbox` instead. """ - deprecate_with_replacement("bleedBox", "bleedbox") + deprecation_with_replacement("bleedBox", "bleedbox", "3.0.0") return self.bleedbox @bleedBox.setter def bleedBox(self, value: RectangleObject) -> None: # pragma: no cover - deprecate_with_replacement("bleedBox", "bleedbox") + deprecation_with_replacement("bleedBox", "bleedbox", "3.0.0") self.bleedbox = value trimbox = _create_rectangle_accessor("/TrimBox", ("/CropBox", PG.MEDIABOX)) @@ -1983,12 +1922,12 @@ def trimBox(self) -> RectangleObject: # pragma: no cover Use :py:attr:`trimbox` instead. """ - deprecate_with_replacement("trimBox", "trimbox") + deprecation_with_replacement("trimBox", "trimbox", "3.0.0") return self.trimbox @trimBox.setter def trimBox(self, value: RectangleObject) -> None: # pragma: no cover - deprecate_with_replacement("trimBox", "trimbox") + deprecation_with_replacement("trimBox", "trimbox", "3.0.0") self.trimbox = value artbox = _create_rectangle_accessor("/ArtBox", ("/CropBox", PG.MEDIABOX)) @@ -2005,12 +1944,12 @@ def artBox(self) -> RectangleObject: # pragma: no cover Use :py:attr:`artbox` instead. """ - deprecate_with_replacement("artBox", "artbox") + deprecation_with_replacement("artBox", "artbox", "3.0.0") return self.artbox @artBox.setter def artBox(self, value: RectangleObject) -> None: # pragma: no cover - deprecate_with_replacement("artBox", "artbox") + deprecation_with_replacement("artBox", "artbox", "3.0.0") self.artbox = value @property diff --git a/PyPDF2/_reader.py b/PyPDF2/_reader.py index d7ccca91c..0a9144766 100644 --- a/PyPDF2/_reader.py +++ b/PyPDF2/_reader.py @@ -53,7 +53,8 @@ StreamType, b_, deprecate_no_replacement, - deprecate_with_replacement, + deprecation_no_replacement, + deprecation_with_replacement, logger_warning, read_non_whitespace, read_previous_line, @@ -111,7 +112,7 @@ def convert_to_int(d: bytes, size: int) -> Union[int, Tuple[Any, ...]]: def convertToInt( d: bytes, size: int ) -> Union[int, Tuple[Any, ...]]: # pragma: no cover - deprecate_with_replacement("convertToInt", "convert_to_int") + deprecation_with_replacement("convertToInt", "convert_to_int") return convert_to_int(d, size) @@ -146,7 +147,7 @@ def getText(self, key: str) -> Optional[str]: # pragma: no cover Use the attributes (e.g. :py:attr:`title` / :py:attr:`author`). """ - deprecate_no_replacement("getText") + deprecation_no_replacement("getText", "3.0.0") return self._get_text(key) @property @@ -382,7 +383,7 @@ def getDocumentInfo(self) -> Optional[DocumentInformation]: # pragma: no cover Use the attribute :py:attr:`metadata` instead. """ - deprecate_with_replacement("getDocumentInfo", "metadata") + deprecation_with_replacement("getDocumentInfo", "metadata", "3.0.0") return self.metadata @property @@ -392,7 +393,7 @@ def documentInfo(self) -> Optional[DocumentInformation]: # pragma: no cover Use the attribute :py:attr:`metadata` instead. """ - deprecate_with_replacement("documentInfo", "metadata") + deprecation_with_replacement("documentInfo", "metadata", "3.0.0") return self.metadata @property @@ -416,7 +417,7 @@ def getXmpMetadata(self) -> Optional[XmpInformation]: # pragma: no cover Use the attribute :py:attr:`xmp_metadata` instead. """ - deprecate_with_replacement("getXmpMetadata", "xmp_metadata") + deprecation_with_replacement("getXmpMetadata", "xmp_metadata", "3.0.0") return self.xmp_metadata @property @@ -426,7 +427,7 @@ def xmpMetadata(self) -> Optional[XmpInformation]: # pragma: no cover Use the attribute :py:attr:`xmp_metadata` instead. """ - deprecate_with_replacement("xmpMetadata", "xmp_metadata") + deprecation_with_replacement("xmpMetadata", "xmp_metadata", "3.0.0") return self.xmp_metadata def _get_num_pages(self) -> int: @@ -453,7 +454,7 @@ def getNumPages(self) -> int: # pragma: no cover Use :code:`len(reader.pages)` instead. """ - deprecate_with_replacement("reader.getNumPages", "len(reader.pages)") + deprecation_with_replacement("reader.getNumPages", "len(reader.pages)", "3.0.0") return self._get_num_pages() @property @@ -463,7 +464,7 @@ def numPages(self) -> int: # pragma: no cover Use :code:`len(reader.pages)` instead. """ - deprecate_with_replacement("reader.numPages", "len(reader.pages)") + deprecation_with_replacement("reader.numPages", "len(reader.pages)", "3.0.0") return self._get_num_pages() def getPage(self, pageNumber: int) -> PageObject: # pragma: no cover @@ -472,8 +473,8 @@ def getPage(self, pageNumber: int) -> PageObject: # pragma: no cover Use :code:`reader.pages[page_number]` instead. """ - deprecate_with_replacement( - "reader.getPage(pageNumber)", "reader.pages[page_number]" + deprecation_with_replacement( + "reader.getPage(pageNumber)", "reader.pages[page_number]", "3.0.0" ) return self._get_page(pageNumber) @@ -499,7 +500,7 @@ def namedDestinations(self) -> Dict[str, Any]: # pragma: no cover Use :py:attr:`named_destinations` instead. """ - deprecate_with_replacement("namedDestinations", "named_destinations") + deprecation_with_replacement("namedDestinations", "named_destinations", "3.0.0") return self.named_destinations @property @@ -569,7 +570,7 @@ def getFields( Use :meth:`get_fields` instead. """ - deprecate_with_replacement("getFields", "get_fields") + deprecation_with_replacement("getFields", "get_fields", "3.0.0") return self.get_fields(tree, retval, fileobj) def _build_field( @@ -664,7 +665,9 @@ def getFormTextFields(self) -> Dict[str, Any]: # pragma: no cover Use :meth:`get_form_text_fields` instead. """ - deprecate_with_replacement("getFormTextFields", "get_form_text_fields") + deprecation_with_replacement( + "getFormTextFields", "get_form_text_fields", "3.0.0" + ) return self.get_form_text_fields() def _get_named_destinations( @@ -726,7 +729,9 @@ def getNamedDestinations( Use :py:attr:`named_destinations` instead. """ - deprecate_with_replacement("getNamedDestinations", "named_destinations") + deprecation_with_replacement( + "getNamedDestinations", "named_destinations", "3.0.0" + ) return self._get_named_destinations(tree, retval) @property @@ -746,7 +751,7 @@ def outlines(self) -> OutlineType: # pragma: no cover Use :py:attr:`outline` instead. """ - deprecate_with_replacement("outlines", "outline") + deprecation_with_replacement("outlines", "outline", "3.0.0") return self.outline def _get_outline( @@ -798,7 +803,7 @@ def getOutlines( Use :py:attr:`outline` instead. """ - deprecate_with_replacement("getOutlines", "outline") + deprecation_with_replacement("getOutlines", "outline", "3.0.0") return self._get_outline(node, outline) @property @@ -850,7 +855,7 @@ def getPageNumber(self, page: PageObject) -> int: # pragma: no cover Use :meth:`get_page_number` instead. """ - deprecate_with_replacement("getPageNumber", "get_page_number") + deprecation_with_replacement("getPageNumber", "get_page_number", "3.0.0") return self.get_page_number(page) def get_destination_page_number(self, destination: Destination) -> int: @@ -870,8 +875,8 @@ def getDestinationPageNumber( Use :meth:`get_destination_page_number` instead. """ - deprecate_with_replacement( - "getDestinationPageNumber", "get_destination_page_number" + deprecation_with_replacement( + "getDestinationPageNumber", "get_destination_page_number", "3.0.0" ) return self.get_destination_page_number(destination) @@ -1016,7 +1021,7 @@ def getPageLayout(self) -> Optional[str]: # pragma: no cover Use :py:attr:`page_layout` instead. """ - deprecate_with_replacement("getPageLayout", "page_layout") + deprecation_with_replacement("getPageLayout", "page_layout", "3.0.0") return self.page_layout @property @@ -1026,7 +1031,7 @@ def pageLayout(self) -> Optional[str]: # pragma: no cover Use :py:attr:`page_layout` instead. """ - deprecate_with_replacement("pageLayout", "page_layout") + deprecation_with_replacement("pageLayout", "page_layout", "3.0.0") return self.page_layout @property @@ -1063,7 +1068,7 @@ def getPageMode(self) -> Optional[PagemodeType]: # pragma: no cover Use :py:attr:`page_mode` instead. """ - deprecate_with_replacement("getPageMode", "page_mode") + deprecation_with_replacement("getPageMode", "page_mode", "3.0.0") return self.page_mode @property @@ -1073,7 +1078,7 @@ def pageMode(self) -> Optional[PagemodeType]: # pragma: no cover Use :py:attr:`page_mode` instead. """ - deprecate_with_replacement("pageMode", "page_mode") + deprecation_with_replacement("pageMode", "page_mode", "3.0.0") return self.page_mode def _flatten( @@ -1321,7 +1326,7 @@ def getObject( Use :meth:`get_object` instead. """ - deprecate_with_replacement("getObject", "get_object") + deprecation_with_replacement("getObject", "get_object", "3.0.0") return self.get_object(indirectReference) def read_object_header(self, stream: StreamType) -> Tuple[int, int]: @@ -1360,7 +1365,7 @@ def readObjectHeader( Use :meth:`read_object_header` instead. """ - deprecate_with_replacement("readObjectHeader", "read_object_header") + deprecation_with_replacement("readObjectHeader", "read_object_header", "3.0.0") return self.read_object_header(stream) def cache_get_indirect_object( @@ -1376,8 +1381,8 @@ def cacheGetIndirectObject( Use :meth:`cache_get_indirect_object` instead. """ - deprecate_with_replacement( - "cacheGetIndirectObject", "cache_get_indirect_object" + deprecation_with_replacement( + "cacheGetIndirectObject", "cache_get_indirect_object", "3.0.0" ) return self.cache_get_indirect_object(generation, idnum) @@ -1402,7 +1407,7 @@ def cacheIndirectObject( Use :meth:`cache_indirect_object` instead. """ - deprecate_with_replacement("cacheIndirectObject", "cache_indirect_object") + deprecation_with_replacement("cacheIndirectObject", "cache_indirect_object") return self.cache_indirect_object(generation, idnum, obj) def read(self, stream: StreamType) -> None: @@ -1873,7 +1878,7 @@ def readNextEndLine( self, stream: StreamType, limit_offset: int = 0 ) -> bytes: # pragma: no cover """.. deprecated:: 1.28.0""" - deprecate_no_replacement("readNextEndLine") + deprecation_no_replacement("readNextEndLine", "3.0.0") return self.read_next_end_line(stream, limit_offset) def decrypt(self, password: Union[str, bytes]) -> PasswordType: @@ -1926,7 +1931,7 @@ def getIsEncrypted(self) -> bool: # pragma: no cover Use :py:attr:`is_encrypted` instead. """ - deprecate_with_replacement("getIsEncrypted", "is_encrypted") + deprecation_with_replacement("getIsEncrypted", "is_encrypted", "3.0.0") return self.is_encrypted @property @@ -1936,7 +1941,7 @@ def isEncrypted(self) -> bool: # pragma: no cover Use :py:attr:`is_encrypted` instead. """ - deprecate_with_replacement("isEncrypted", "is_encrypted") + deprecation_with_replacement("isEncrypted", "is_encrypted", "3.0.0") return self.is_encrypted @property @@ -1966,7 +1971,7 @@ def xfa(self) -> Optional[Dict[str, Any]]: class PdfFileReader(PdfReader): # pragma: no cover def __init__(self, *args: Any, **kwargs: Any) -> None: - deprecate_with_replacement("PdfFileReader", "PdfReader") + deprecation_with_replacement("PdfFileReader", "PdfReader", "3.0.0") if "strict" not in kwargs and len(args) < 2: kwargs["strict"] = True # maintain the default super().__init__(*args, **kwargs) diff --git a/PyPDF2/_utils.py b/PyPDF2/_utils.py index 947233919..b6f090b8d 100644 --- a/PyPDF2/_utils.py +++ b/PyPDF2/_utils.py @@ -54,7 +54,11 @@ except ImportError: from typing_extensions import TypeAlias -from .errors import STREAM_TRUNCATED_PREMATURELY, PdfStreamError +from .errors import ( + STREAM_TRUNCATED_PREMATURELY, + DeprecationError, + PdfStreamError, +) TransformationMatrixType: TypeAlias = Tuple[ Tuple[float, float, float], Tuple[float, float, float], Tuple[float, float, float] @@ -67,7 +71,9 @@ StrByteType = Union[str, StreamType] DEPR_MSG_NO_REPLACEMENT = "{} is deprecated and will be removed in PyPDF2 {}." +DEPR_MSG_NO_REPLACEMENT_HAPPENED = "{} is deprecated and was removed in PyPDF2 {}." DEPR_MSG = "{} is deprecated and will be removed in PyPDF2 3.0.0. Use {} instead." +DEPR_MSG_HAPPENED = "{} is deprecated and was removed in PyPDF2 {}. Use {} instead." def _get_max_pdf_version_header(header1: bytes, header2: bytes) -> bytes: @@ -338,19 +344,45 @@ def paeth_predictor(left: int, up: int, up_left: int) -> int: def deprecate(msg: str, stacklevel: int = 3) -> None: - warnings.warn(msg, PendingDeprecationWarning, stacklevel=stacklevel) + warnings.warn(msg, DeprecationWarning, stacklevel=stacklevel) + + +def deprecation(msg: str) -> None: + raise DeprecationError(msg) def deprecate_with_replacement( old_name: str, new_name: str, removed_in: str = "3.0.0" ) -> None: + """ + Raise an exception that a feature will be removed, but has a replacement. + """ deprecate(DEPR_MSG.format(old_name, new_name, removed_in), 4) +def deprecation_with_replacement( + old_name: str, new_name: str, removed_in: str = "3.0.0" +) -> None: + """ + Raise an exception that a feature was already removed, but has a replacement. + """ + deprecation(DEPR_MSG_HAPPENED.format(old_name, removed_in, new_name)) + + def deprecate_no_replacement(name: str, removed_in: str = "3.0.0") -> None: + """ + Raise an exception that a feature will be removed without replacement. + """ deprecate(DEPR_MSG_NO_REPLACEMENT.format(name, removed_in), 4) +def deprecation_no_replacement(name: str, removed_in: str = "3.0.0") -> None: + """ + Raise an exception that a feature was already removed without replacement. + """ + deprecation(DEPR_MSG_NO_REPLACEMENT_HAPPENED.format(name, removed_in)) + + def logger_warning(msg: str, src: str) -> None: """ Use this instead of logger.warning directly. @@ -370,7 +402,7 @@ def logger_warning(msg: str, src: str) -> None: logging.getLogger(src).warning(msg) -def deprecate_bookmark(**aliases: str) -> Callable: +def deprecation_bookmark(**aliases: str) -> Callable: """ Decorator for deprecated term "bookmark" To be used for methods and function arguments @@ -381,7 +413,7 @@ def deprecate_bookmark(**aliases: str) -> Callable: def decoration(func: Callable): # type: ignore @functools.wraps(func) def wrapper(*args, **kwargs): # type: ignore - rename_kwargs(func.__name__, kwargs, aliases) + rename_kwargs(func.__name__, kwargs, aliases, fail=True) return func(*args, **kwargs) return wrapper @@ -390,7 +422,7 @@ def wrapper(*args, **kwargs): # type: ignore def rename_kwargs( # type: ignore - func_name: str, kwargs: Dict[str, Any], aliases: Dict[str, str] + func_name: str, kwargs: Dict[str, Any], aliases: Dict[str, str], fail: bool = False ): """ Helper function to deprecate arguments. @@ -398,6 +430,10 @@ def rename_kwargs( # type: ignore for old_term, new_term in aliases.items(): if old_term in kwargs: + if fail: + raise DeprecationError( + f"{old_term} is deprecated as an argument. Use {new_term} instead" + ) if new_term in kwargs: raise TypeError( f"{func_name} received both {old_term} and {new_term} as an argument. " @@ -407,7 +443,8 @@ def rename_kwargs( # type: ignore warnings.warn( message=( f"{old_term} is deprecated as an argument. Use {new_term} instead" - ) + ), + category=DeprecationWarning, ) diff --git a/PyPDF2/_writer.py b/PyPDF2/_writer.py index dcb5ffee0..b2e92cdb1 100644 --- a/PyPDF2/_writer.py +++ b/PyPDF2/_writer.py @@ -66,8 +66,9 @@ StreamType, _get_max_pdf_version_header, b_, - deprecate_bookmark, deprecate_with_replacement, + deprecation_bookmark, + deprecation_with_replacement, logger_warning, ) from .constants import AnnotationDictionaryAttributes @@ -225,7 +226,7 @@ def get_object( else: indirect_reference = ido warnings.warn( - "The parameter 'ido' is depreciated and will be removed in PyPDF2 3.0.0.", + "The parameter 'ido' is depreciated and will be removed in PyPDF2 4.0.0.", DeprecationWarning, ) assert ( @@ -245,7 +246,7 @@ def getObject( Use :meth:`get_object` instead. """ - deprecate_with_replacement("getObject", "get_object") + deprecation_with_replacement("getObject", "get_object", "3.0.0") return self.get_object(ido) def _add_page( @@ -329,7 +330,7 @@ def addPage( Use :meth:`add_page` instead. """ - deprecate_with_replacement("addPage", "add_page") + deprecation_with_replacement("addPage", "add_page", "3.0.0") return self.add_page(page, excluded_keys) def insert_page( @@ -358,7 +359,7 @@ def insertPage( Use :meth:`insert_page` instead. """ - deprecate_with_replacement("insertPage", "insert_page") + deprecation_with_replacement("insertPage", "insert_page", "3.0.0") return self.insert_page(page, index, excluded_keys) def get_page( @@ -390,7 +391,7 @@ def getPage(self, pageNumber: int) -> PageObject: # pragma: no cover Use :code:`writer.pages[page_number]` instead. """ - deprecate_with_replacement("getPage", "writer.pages[page_number]") + deprecation_with_replacement("getPage", "writer.pages[page_number]", "3.0.0") return self.get_page(pageNumber) def _get_num_pages(self) -> int: @@ -403,7 +404,7 @@ def getNumPages(self) -> int: # pragma: no cover Use :code:`len(writer.pages)` instead. """ - deprecate_with_replacement("getNumPages", "len(writer.pages)") + deprecation_with_replacement("getNumPages", "len(writer.pages)", "3.0.0") return self._get_num_pages() @property @@ -438,7 +439,7 @@ def addBlankPage( Use :meth:`add_blank_page` instead. """ - deprecate_with_replacement("addBlankPage", "add_blank_page") + deprecation_with_replacement("addBlankPage", "add_blank_page", "3.0.0") return self.add_blank_page(width, height) def insert_blank_page( @@ -479,7 +480,7 @@ def insertBlankPage( Use :meth:`insertBlankPage` instead. """ - deprecate_with_replacement("insertBlankPage", "insert_blank_page") + deprecation_with_replacement("insertBlankPage", "insert_blank_page", "3.0.0") return self.insert_blank_page(width, height, index) @property @@ -573,7 +574,7 @@ def addJS(self, javascript: str) -> None: # pragma: no cover Use :meth:`add_js` instead. """ - deprecate_with_replacement("addJS", "add_js") + deprecation_with_replacement("addJS", "add_js", "3.0.0") return self.add_js(javascript) def add_attachment(self, filename: str, data: Union[str, bytes]) -> None: @@ -666,7 +667,7 @@ def addAttachment( Use :meth:`add_attachment` instead. """ - deprecate_with_replacement("addAttachment", "add_attachment") + deprecation_with_replacement("addAttachment", "add_attachment", "3.0.0") return self.add_attachment(fname, fdata) def append_pages_from_reader( @@ -707,7 +708,9 @@ def appendPagesFromReader( Use :meth:`append_pages_from_reader` instead. """ - deprecate_with_replacement("appendPagesFromReader", "append_pages_from_reader") + deprecation_with_replacement( + "appendPagesFromReader", "append_pages_from_reader", "3.0.0" + ) self.append_pages_from_reader(reader, after_page_append) def update_page_form_field_values( @@ -786,8 +789,8 @@ def updatePageFormFieldValues( Use :meth:`update_page_form_field_values` instead. """ - deprecate_with_replacement( - "updatePageFormFieldValues", "update_page_form_field_values" + deprecation_with_replacement( + "updatePageFormFieldValues", "update_page_form_field_values", "3.0.0" ) return self.update_page_form_field_values(page, fields, flags) @@ -805,8 +808,8 @@ def cloneReaderDocumentRoot(self, reader: PdfReader) -> None: # pragma: no cove Use :meth:`clone_reader_document_root` instead. """ - deprecate_with_replacement( - "cloneReaderDocumentRoot", "clone_reader_document_root" + deprecation_with_replacement( + "cloneReaderDocumentRoot", "clone_reader_document_root", "3.0.0" ) self.clone_reader_document_root(reader) @@ -840,8 +843,8 @@ def cloneDocumentFromReader( Use :meth:`clone_document_from_reader` instead. """ - deprecate_with_replacement( - "cloneDocumentFromReader", "clone_document_from_reader" + deprecation_with_replacement( + "cloneDocumentFromReader", "clone_document_from_reader", "3.0.0" ) self.clone_document_from_reader(reader, after_page_append) @@ -882,8 +885,8 @@ def encrypt( else: warnings.warn( "Please use 'user_password' instead of 'user_pwd'. " - "The 'user_pwd' argument is deprecated and will be removed " - "in PyPDF2==3.0.0." + "The 'user_pwd' argument is deprecated and " + "will be removed in PyPDF2 4.0.0." ) user_password = user_pwd if user_password is None: # deprecated @@ -900,8 +903,10 @@ def encrypt( new_term = "owner_password" warnings.warn( message=( - f"{old_term} is deprecated as an argument. Use {new_term} instead" - ) + f"{old_term} is deprecated as an argument and will be " + f"removed in PyPDF2 4.0.0. Use {new_term} instead" + ), + category=DeprecationWarning, ) owner_password = owner_pwd @@ -1055,7 +1060,7 @@ def addMetadata(self, infos: Dict[str, Any]) -> None: # pragma: no cover Use :meth:`add_metadata` instead. """ - deprecate_with_replacement("addMetadata", "add_metadata") + deprecation_with_replacement("addMetadata", "add_metadata", "3.0.0") self.add_metadata(infos) def _sweep_indirect_references( @@ -1189,7 +1194,7 @@ def getReference(self, obj: PdfObject) -> IndirectObject: # pragma: no cover Use :meth:`get_reference` instead. """ - deprecate_with_replacement("getReference", "get_reference") + deprecation_with_replacement("getReference", "get_reference", "3.0.0") return self.get_reference(obj) def get_outline_root(self) -> TreeObject: @@ -1236,7 +1241,7 @@ def getOutlineRoot(self) -> TreeObject: # pragma: no cover Use :meth:`get_outline_root` instead. """ - deprecate_with_replacement("getOutlineRoot", "get_outline_root") + deprecation_with_replacement("getOutlineRoot", "get_outline_root", "3.0.0") return self.get_outline_root() def get_named_dest_root(self) -> ArrayObject: @@ -1280,7 +1285,7 @@ def getNamedDestRoot(self) -> ArrayObject: # pragma: no cover Use :meth:`get_named_dest_root` instead. """ - deprecate_with_replacement("getNamedDestRoot", "get_named_dest_root") + deprecation_with_replacement("getNamedDestRoot", "get_named_dest_root", "3.0.0") return self.get_named_dest_root() def add_outline_item_destination( @@ -1299,8 +1304,10 @@ def add_outline_item_destination( new_term = "page_destination" warnings.warn( message=( - f"{old_term} is deprecated as an argument. Use {new_term} instead" - ) + f"{old_term} is deprecated as an argument and will be " + f"removed in PyPDF2 4.0.0. Use {new_term} instead" + ), + category=DeprecationWarning, ) page_destination = dest if page_destination is None: # deprecated @@ -1328,8 +1335,8 @@ def add_bookmark_destination( Use :meth:`add_outline_item_destination` instead. """ - deprecate_with_replacement( - "add_bookmark_destination", "add_outline_item_destination" + deprecation_with_replacement( + "add_bookmark_destination", "add_outline_item_destination", "3.0.0" ) return self.add_outline_item_destination(dest, parent) @@ -1341,12 +1348,12 @@ def addBookmarkDestination( Use :meth:`add_outline_item_destination` instead. """ - deprecate_with_replacement( - "addBookmarkDestination", "add_outline_item_destination" + deprecation_with_replacement( + "addBookmarkDestination", "add_outline_item_destination", "3.0.0" ) return self.add_outline_item_destination(dest, parent) - @deprecate_bookmark(bookmark="outline_item") + @deprecation_bookmark(bookmark="outline_item") def add_outline_item_dict( self, outline_item: OutlineItemType, @@ -1368,7 +1375,7 @@ def add_outline_item_dict( return self.add_outline_item_destination(outline_item_object, parent, before) - @deprecate_bookmark(bookmark="outline_item") + @deprecation_bookmark(bookmark="outline_item") def add_bookmark_dict( self, outline_item: OutlineItemType, parent: Optional[TreeObject] = None ) -> IndirectObject: # pragma: no cover @@ -1377,10 +1384,12 @@ def add_bookmark_dict( Use :meth:`add_outline_item_dict` instead. """ - deprecate_with_replacement("add_bookmark_dict", "add_outline_item_dict") + deprecation_with_replacement( + "add_bookmark_dict", "add_outline_item_dict", "3.0.0" + ) return self.add_outline_item_dict(outline_item, parent) - @deprecate_bookmark(bookmark="outline_item") + @deprecation_bookmark(bookmark="outline_item") def addBookmarkDict( self, outline_item: OutlineItemType, parent: Optional[TreeObject] = None ) -> IndirectObject: # pragma: no cover @@ -1389,7 +1398,9 @@ def addBookmarkDict( Use :meth:`add_outline_item_dict` instead. """ - deprecate_with_replacement("addBookmarkDict", "add_outline_item_dict") + deprecation_with_replacement( + "addBookmarkDict", "add_outline_item_dict", "3.0.0" + ) return self.add_outline_item_dict(outline_item, parent) def add_outline_item( @@ -1484,7 +1495,7 @@ def add_bookmark( Use :meth:`add_outline_item` instead. """ - deprecate_with_replacement("add_bookmark", "add_outline_item") + deprecation_with_replacement("add_bookmark", "add_outline_item", "3.0.0") return self.add_outline_item( title, pagenum, @@ -1511,7 +1522,7 @@ def addBookmark( Use :meth:`add_outline_item` instead. """ - deprecate_with_replacement("addBookmark", "add_outline_item") + deprecation_with_replacement("addBookmark", "add_outline_item", "3.0.0") return self.add_outline_item( title, pagenum, @@ -1557,8 +1568,10 @@ def add_named_destination_object( new_term = "page_destination" warnings.warn( message=( - f"{old_term} is deprecated as an argument. Use {new_term} instead" - ) + f"{old_term} is deprecated as an argument and will be " + f"removed in PyPDF2 4.0.0. Use {new_term} instead" + ), + category=DeprecationWarning, ) page_destination = dest if page_destination is None: # deprecated @@ -1579,8 +1592,8 @@ def addNamedDestinationObject( Use :meth:`add_named_destination_object` instead. """ - deprecate_with_replacement( - "addNamedDestinationObject", "add_named_destination_object" + deprecation_with_replacement( + "addNamedDestinationObject", "add_named_destination_object", "3.0.0" ) return self.add_named_destination_object(dest) @@ -1599,8 +1612,10 @@ def add_named_destination( new_term = "page_number" warnings.warn( message=( - f"{old_term} is deprecated as an argument. Use {new_term} instead" - ) + f"{old_term} is deprecated as an argument and will be " + f"removed in PyPDF2 4.0.0. Use {new_term} instead" + ), + category=DeprecationWarning, ) page_number = pagenum if page_number is None: @@ -1631,7 +1646,9 @@ def addNamedDestination( Use :meth:`add_named_destination` instead. """ - deprecate_with_replacement("addNamedDestination", "add_named_destination") + deprecation_with_replacement( + "addNamedDestination", "add_named_destination", "3.0.0" + ) return self.add_named_destination(title, pagenum) def remove_links(self) -> None: @@ -1649,7 +1666,7 @@ def removeLinks(self) -> None: # pragma: no cover Use :meth:`remove_links` instead. """ - deprecate_with_replacement("removeLinks", "remove_links") + deprecation_with_replacement("removeLinks", "remove_links", "3.0.0") return self.remove_links() def remove_images(self, ignore_byte_string_object: bool = False) -> None: @@ -1737,7 +1754,7 @@ def removeImages( Use :meth:`remove_images` instead. """ - deprecate_with_replacement("removeImages", "remove_images") + deprecation_with_replacement("removeImages", "remove_images", "3.0.0") return self.remove_images(ignoreByteStringObject) def remove_text(self, ignore_byte_string_object: bool = False) -> None: @@ -1792,7 +1809,7 @@ def removeText( Use :meth:`remove_text` instead. """ - deprecate_with_replacement("removeText", "remove_text") + deprecation_with_replacement("removeText", "remove_text", "3.0.0") return self.remove_text(ignoreByteStringObject) def add_uri( @@ -1818,7 +1835,9 @@ def add_uri( """ if pagenum is not None: warnings.warn( - "The 'pagenum' argument of add_uri is deprecated. Use 'page_number' instead." + "The 'pagenum' argument of add_uri is deprecated and will be " + "removed in PyPDF2 4.0.0. Use 'page_number' instead.", + category=DeprecationWarning, ) page_number = pagenum page_link = self.get_object(self._pages)[PA.KIDS][page_number] # type: ignore @@ -1880,7 +1899,7 @@ def addURI( Use :meth:`add_uri` instead. """ - deprecate_with_replacement("addURI", "add_uri") + deprecation_with_replacement("addURI", "add_uri", "3.0.0") return self.add_uri(pagenum, uri, rect, border) def add_link( @@ -1892,7 +1911,7 @@ def add_link( fit: FitType = "/Fit", *args: ZoomArgType, ) -> None: - deprecate_with_replacement( + deprecation_with_replacement( "add_link", "add_annotation(AnnotationBuilder.link(...))" ) @@ -1955,7 +1974,7 @@ def getPageLayout(self) -> Optional[LayoutType]: # pragma: no cover Use :py:attr:`page_layout` instead. """ - deprecate_with_replacement("getPageLayout", "page_layout") + deprecation_with_replacement("getPageLayout", "page_layout", "3.0.0") return self._get_page_layout() def _set_page_layout(self, layout: Union[NameObject, LayoutType]) -> None: @@ -2023,8 +2042,8 @@ def setPageLayout(self, layout: LayoutType) -> None: # pragma: no cover Use :py:attr:`page_layout` instead. """ - deprecate_with_replacement( - "writer.setPageLayout(val)", "writer.page_layout = val" + deprecation_with_replacement( + "writer.setPageLayout(val)", "writer.page_layout = val", "3.0.0" ) return self._set_page_layout(layout) @@ -2064,7 +2083,7 @@ def pageLayout(self) -> Optional[LayoutType]: # pragma: no cover Use :py:attr:`page_layout` instead. """ - deprecate_with_replacement("pageLayout", "page_layout") + deprecation_with_replacement("pageLayout", "page_layout", "3.0.0") return self.page_layout @pageLayout.setter @@ -2074,7 +2093,7 @@ def pageLayout(self, layout: LayoutType) -> None: # pragma: no cover Use :py:attr:`page_layout` instead. """ - deprecate_with_replacement("pageLayout", "page_layout") + deprecation_with_replacement("pageLayout", "page_layout", "3.0.0") self.page_layout = layout _valid_modes = ( @@ -2098,7 +2117,7 @@ def getPageMode(self) -> Optional[PagemodeType]: # pragma: no cover Use :py:attr:`page_mode` instead. """ - deprecate_with_replacement("getPageMode", "page_mode") + deprecation_with_replacement("getPageMode", "page_mode", "3.0.0") return self._get_page_mode() def set_page_mode(self, mode: PagemodeType) -> None: @@ -2123,7 +2142,9 @@ def setPageMode(self, mode: PagemodeType) -> None: # pragma: no cover Use :py:attr:`page_mode` instead. """ - deprecate_with_replacement("writer.setPageMode(val)", "writer.page_mode = val") + deprecation_with_replacement( + "writer.setPageMode(val)", "writer.page_mode = val", "3.0.0" + ) self.set_page_mode(mode) @property @@ -2160,7 +2181,7 @@ def pageMode(self) -> Optional[PagemodeType]: # pragma: no cover Use :py:attr:`page_mode` instead. """ - deprecate_with_replacement("pageMode", "page_mode") + deprecation_with_replacement("pageMode", "page_mode", "3.0.0") return self.page_mode @pageMode.setter @@ -2170,7 +2191,7 @@ def pageMode(self, mode: PagemodeType) -> None: # pragma: no cover Use :py:attr:`page_mode` instead. """ - deprecate_with_replacement("pageMode", "page_mode") + deprecation_with_replacement("pageMode", "page_mode", "3.0.0") self.page_mode = mode def add_annotation(self, page_number: int, annotation: Dict[str, Any]) -> None: @@ -2306,7 +2327,7 @@ def append( None, fileobj, outline_item, pages, import_outline, excluded_fields ) - @deprecate_bookmark(bookmark="outline_item", import_bookmarks="import_outline") + @deprecation_bookmark(bookmark="outline_item", import_bookmarks="import_outline") def merge( self, position: Optional[int], @@ -2662,7 +2683,7 @@ def close(self) -> None: """To match the functions from Merger""" return - # @deprecate_bookmark(bookmark="outline_item") + # @deprecation_bookmark(bookmark="outline_item") def find_outline_item( self, outline_item: Dict[str, Any], @@ -2693,7 +2714,7 @@ def find_outline_item( else: return None - @deprecate_bookmark(bookmark="outline_item") + @deprecation_bookmark(bookmark="outline_item") def find_bookmark( self, outline_item: Dict[str, Any], @@ -2797,5 +2818,5 @@ def _create_outline_item( class PdfFileWriter(PdfWriter): # pragma: no cover def __init__(self, *args: Any, **kwargs: Any) -> None: - deprecate_with_replacement("PdfFileWriter", "PdfWriter") + deprecation_with_replacement("PdfFileWriter", "PdfWriter", "3.0.0") super().__init__(*args, **kwargs) diff --git a/PyPDF2/errors.py b/PyPDF2/errors.py index d00bc7c12..a84b05691 100644 --- a/PyPDF2/errors.py +++ b/PyPDF2/errors.py @@ -5,6 +5,12 @@ """ +class DeprecationError(Exception): + """Raised when a deprecated feature is used.""" + + pass + + class DependencyError(Exception): pass diff --git a/PyPDF2/generic/_base.py b/PyPDF2/generic/_base.py index 872c529ad..00b9c17bf 100644 --- a/PyPDF2/generic/_base.py +++ b/PyPDF2/generic/_base.py @@ -37,7 +37,7 @@ from .._utils import ( StreamType, b_, - deprecate_with_replacement, + deprecation_with_replacement, hex_str, hexencode, logger_warning, @@ -119,7 +119,7 @@ def get_object(self) -> Optional["PdfObject"]: return self def getObject(self) -> Optional["PdfObject"]: # pragma: no cover - deprecate_with_replacement("getObject", "get_object") + deprecation_with_replacement("getObject", "get_object", "3.0.0") return self.get_object() def write_to_stream( @@ -153,7 +153,7 @@ def read_from_stream(stream: StreamType) -> "NullObject": def writeToStream( self, stream: StreamType, encryption_key: Union[None, str, bytes] ) -> None: # pragma: no cover - deprecate_with_replacement("writeToStream", "write_to_stream") + deprecation_with_replacement("writeToStream", "write_to_stream", "3.0.0") self.write_to_stream(stream, encryption_key) def __repr__(self) -> str: @@ -161,7 +161,7 @@ def __repr__(self) -> str: @staticmethod def readFromStream(stream: StreamType) -> "NullObject": # pragma: no cover - deprecate_with_replacement("readFromStream", "read_from_stream") + deprecation_with_replacement("readFromStream", "read_from_stream", "3.0.0") return NullObject.read_from_stream(stream) @@ -202,7 +202,7 @@ def write_to_stream( def writeToStream( self, stream: StreamType, encryption_key: Union[None, str, bytes] ) -> None: # pragma: no cover - deprecate_with_replacement("writeToStream", "write_to_stream") + deprecation_with_replacement("writeToStream", "write_to_stream", "3.0.0") self.write_to_stream(stream, encryption_key) @staticmethod @@ -218,7 +218,7 @@ def read_from_stream(stream: StreamType) -> "BooleanObject": @staticmethod def readFromStream(stream: StreamType) -> "BooleanObject": # pragma: no cover - deprecate_with_replacement("readFromStream", "read_from_stream") + deprecation_with_replacement("readFromStream", "read_from_stream", "3.0.0") return BooleanObject.read_from_stream(stream) @@ -284,7 +284,7 @@ def write_to_stream( def writeToStream( self, stream: StreamType, encryption_key: Union[None, str, bytes] ) -> None: # pragma: no cover - deprecate_with_replacement("writeToStream", "write_to_stream") + deprecation_with_replacement("writeToStream", "write_to_stream", "3.0.0") self.write_to_stream(stream, encryption_key) @staticmethod @@ -318,7 +318,7 @@ def read_from_stream(stream: StreamType, pdf: Any) -> "IndirectObject": # PdfRe def readFromStream( stream: StreamType, pdf: Any # PdfReader ) -> "IndirectObject": # pragma: no cover - deprecate_with_replacement("readFromStream", "read_from_stream") + deprecation_with_replacement("readFromStream", "read_from_stream", "3.0.0") return IndirectObject.read_from_stream(stream, pdf) @@ -363,7 +363,7 @@ def write_to_stream( def writeToStream( self, stream: StreamType, encryption_key: Union[None, str, bytes] ) -> None: # pragma: no cover - deprecate_with_replacement("writeToStream", "write_to_stream") + deprecation_with_replacement("writeToStream", "write_to_stream", "3.0.0") self.write_to_stream(stream, encryption_key) @@ -397,7 +397,7 @@ def write_to_stream( def writeToStream( self, stream: StreamType, encryption_key: Union[None, str, bytes] ) -> None: # pragma: no cover - deprecate_with_replacement("writeToStream", "write_to_stream") + deprecation_with_replacement("writeToStream", "write_to_stream", "3.0.0") self.write_to_stream(stream, encryption_key) @staticmethod @@ -411,7 +411,7 @@ def read_from_stream(stream: StreamType) -> Union["NumberObject", "FloatObject"] def readFromStream( stream: StreamType, ) -> Union["NumberObject", "FloatObject"]: # pragma: no cover - deprecate_with_replacement("readFromStream", "read_from_stream") + deprecation_with_replacement("readFromStream", "read_from_stream", "3.0.0") return NumberObject.read_from_stream(stream) @@ -455,7 +455,7 @@ def write_to_stream( def writeToStream( self, stream: StreamType, encryption_key: Union[None, str, bytes] ) -> None: # pragma: no cover - deprecate_with_replacement("writeToStream", "write_to_stream") + deprecation_with_replacement("writeToStream", "write_to_stream", "3.0.0") self.write_to_stream(stream, encryption_key) @@ -537,7 +537,7 @@ def write_to_stream( def writeToStream( self, stream: StreamType, encryption_key: Union[None, str, bytes] ) -> None: # pragma: no cover - deprecate_with_replacement("writeToStream", "write_to_stream") + deprecation_with_replacement("writeToStream", "write_to_stream", "3.0.0") self.write_to_stream(stream, encryption_key) @@ -569,7 +569,7 @@ def write_to_stream( def writeToStream( self, stream: StreamType, encryption_key: Union[None, str, bytes] ) -> None: # pragma: no cover - deprecate_with_replacement("writeToStream", "write_to_stream") + deprecation_with_replacement("writeToStream", "write_to_stream", "3.0.0") self.write_to_stream(stream, encryption_key) def renumber(self) -> bytes: @@ -632,7 +632,7 @@ def read_from_stream(stream: StreamType, pdf: Any) -> "NameObject": # PdfReader def readFromStream( stream: StreamType, pdf: Any # PdfReader ) -> "NameObject": # pragma: no cover - deprecate_with_replacement("readFromStream", "read_from_stream") + deprecation_with_replacement("readFromStream", "read_from_stream", "3.0.0") return NameObject.read_from_stream(stream, pdf) diff --git a/PyPDF2/generic/_data_structures.py b/PyPDF2/generic/_data_structures.py index a60133532..19f5be9fb 100644 --- a/PyPDF2/generic/_data_structures.py +++ b/PyPDF2/generic/_data_structures.py @@ -40,6 +40,7 @@ StreamType, b_, deprecate_with_replacement, + deprecation_with_replacement, hex_str, logger_warning, read_non_whitespace, @@ -121,7 +122,7 @@ def write_to_stream( def writeToStream( self, stream: StreamType, encryption_key: Union[None, str, bytes] ) -> None: # pragma: no cover - deprecate_with_replacement("writeToStream", "write_to_stream") + deprecation_with_replacement("writeToStream", "write_to_stream", "3.0.0") self.write_to_stream(stream, encryption_key) @staticmethod @@ -153,7 +154,7 @@ def read_from_stream( def readFromStream( stream: StreamType, pdf: Any # PdfReader ) -> "ArrayObject": # pragma: no cover - deprecate_with_replacement("readFromStream", "read_from_stream") + deprecation_with_replacement("readFromStream", "read_from_stream", "3.0.0") return ArrayObject.read_from_stream(stream, pdf) @@ -295,7 +296,7 @@ def getXmpMetadata( Use :meth:`xmp_metadata` instead. """ - deprecate_with_replacement("getXmpMetadata", "xmp_metadata") + deprecation_with_replacement("getXmpMetadata", "xmp_metadata", "3.0.0") return self.xmp_metadata @property @@ -305,7 +306,7 @@ def xmpMetadata(self) -> Optional[PdfObject]: # pragma: no cover Use :meth:`xmp_metadata` instead. """ - deprecate_with_replacement("xmpMetadata", "xmp_metadata") + deprecation_with_replacement("xmpMetadata", "xmp_metadata", "3.0.0") return self.xmp_metadata def write_to_stream( @@ -322,7 +323,7 @@ def write_to_stream( def writeToStream( self, stream: StreamType, encryption_key: Union[None, str, bytes] ) -> None: # pragma: no cover - deprecate_with_replacement("writeToStream", "write_to_stream") + deprecation_with_replacement("writeToStream", "write_to_stream", "3.0.0") self.write_to_stream(stream, encryption_key) @staticmethod @@ -465,7 +466,7 @@ def read_unsized_from_steam(stream: StreamType, pdf: Any) -> bytes: # PdfReader def readFromStream( stream: StreamType, pdf: Any # PdfReader ) -> "DictionaryObject": # pragma: no cover - deprecate_with_replacement("readFromStream", "read_from_stream") + deprecation_with_replacement("readFromStream", "read_from_stream", "3.0.0") return DictionaryObject.read_from_stream(stream, pdf) @@ -499,7 +500,7 @@ def children(self) -> Iterable[Any]: child = child_ref.get_object() def addChild(self, child: Any, pdf: Any) -> None: # pragma: no cover - deprecate_with_replacement("addChild", "add_child") + deprecation_with_replacement("addChild", "add_child", "3.0.0") self.add_child(child, pdf) def add_child(self, child: Any, pdf: PdfWriterProtocol) -> None: @@ -561,7 +562,7 @@ def inc_parent_counter( inc_parent_counter(self, child_obj.get("/Count", 1)) def removeChild(self, child: Any) -> None: # pragma: no cover - deprecate_with_replacement("removeChild", "remove_child") + deprecation_with_replacement("removeChild", "remove_child", "3.0.0") self.remove_child(child) def _remove_node_from_tree( @@ -707,12 +708,12 @@ def hash_value_data(self) -> bytes: @property def decodedSelf(self) -> Optional["DecodedStreamObject"]: # pragma: no cover - deprecate_with_replacement("decodedSelf", "decoded_self") + deprecation_with_replacement("decodedSelf", "decoded_self", "3.0.0") return self.decoded_self @decodedSelf.setter def decodedSelf(self, value: "DecodedStreamObject") -> None: # pragma: no cover - deprecate_with_replacement("decodedSelf", "decoded_self") + deprecation_with_replacement("decodedSelf", "decoded_self", "3.0.0") self.decoded_self = value @property @@ -760,7 +761,7 @@ def initialize_from_dictionary( return retval def flateEncode(self) -> "EncodedStreamObject": # pragma: no cover - deprecate_with_replacement("flateEncode", "flate_encode") + deprecation_with_replacement("flateEncode", "flate_encode", "3.0.0") return self.flate_encode() def flate_encode(self) -> "EncodedStreamObject": @@ -791,11 +792,11 @@ def set_data(self, data: Any) -> Any: self._data = data def getData(self) -> Any: # pragma: no cover - deprecate_with_replacement("getData", "get_data") + deprecation_with_replacement("getData", "get_data", "3.0.0") return self._data def setData(self, data: Any) -> None: # pragma: no cover - deprecate_with_replacement("setData", "set_data") + deprecation_with_replacement("setData", "set_data", "3.0.0") self.set_data(data) @@ -805,12 +806,12 @@ def __init__(self) -> None: @property def decodedSelf(self) -> Optional["DecodedStreamObject"]: # pragma: no cover - deprecate_with_replacement("decodedSelf", "decoded_self") + deprecation_with_replacement("decodedSelf", "decoded_self", "3.0.0") return self.decoded_self @decodedSelf.setter def decodedSelf(self, value: DecodedStreamObject) -> None: # pragma: no cover - deprecate_with_replacement("decodedSelf", "decoded_self") + deprecation_with_replacement("decodedSelf", "decoded_self", "3.0.0") self.decoded_self = value def get_data(self) -> Union[None, str, bytes]: @@ -831,14 +832,14 @@ def get_data(self) -> Union[None, str, bytes]: return decoded._data def getData(self) -> Union[None, str, bytes]: # pragma: no cover - deprecate_with_replacement("getData", "get_data") + deprecation_with_replacement("getData", "get_data", "3.0.0") return self.get_data() def set_data(self, data: Any) -> None: # pragma: no cover raise PdfReadError("Creating EncodedStreamObject is not currently supported") def setData(self, data: Any) -> None: # pragma: no cover - deprecate_with_replacement("setData", "set_data") + deprecation_with_replacement("setData", "set_data", "3.0.0") return self.set_data(data) @@ -1114,7 +1115,7 @@ def fieldType(self) -> Optional[NameObject]: # pragma: no cover Use :py:attr:`field_type` instead. """ - deprecate_with_replacement("fieldType", "field_type") + deprecation_with_replacement("fieldType", "field_type", "3.0.0") return self.field_type @property @@ -1144,7 +1145,7 @@ def altName(self) -> Optional[str]: # pragma: no cover Use :py:attr:`alternate_name` instead. """ - deprecate_with_replacement("altName", "alternate_name") + deprecation_with_replacement("altName", "alternate_name", "3.0.0") return self.alternate_name @property @@ -1163,7 +1164,7 @@ def mappingName(self) -> Optional[str]: # pragma: no cover Use :py:attr:`mapping_name` instead. """ - deprecate_with_replacement("mappingName", "mapping_name") + deprecation_with_replacement("mappingName", "mapping_name", "3.0.0") return self.mapping_name @property @@ -1194,7 +1195,7 @@ def defaultValue(self) -> Optional[Any]: # pragma: no cover Use :py:attr:`default_value` instead. """ - deprecate_with_replacement("defaultValue", "default_value") + deprecation_with_replacement("defaultValue", "default_value", "3.0.0") return self.default_value @property @@ -1213,7 +1214,7 @@ def additionalActions(self) -> Optional[DictionaryObject]: # pragma: no cover Use :py:attr:`additional_actions` instead. """ - deprecate_with_replacement("additionalActions", "additional_actions") + deprecation_with_replacement("additionalActions", "additional_actions", "3.0.0") return self.additional_actions @@ -1296,7 +1297,7 @@ def getDestArray(self) -> "ArrayObject": # pragma: no cover Use :py:attr:`dest_array` instead. """ - deprecate_with_replacement("getDestArray", "dest_array") + deprecation_with_replacement("getDestArray", "dest_array", "3.0.0") return self.dest_array def write_to_stream( diff --git a/PyPDF2/generic/_outline.py b/PyPDF2/generic/_outline.py index 0c8b17cdf..c2e72c0ab 100644 --- a/PyPDF2/generic/_outline.py +++ b/PyPDF2/generic/_outline.py @@ -1,6 +1,6 @@ from typing import Any, Union -from .._utils import StreamType, deprecate_with_replacement +from .._utils import StreamType, deprecation_with_replacement from ._base import NameObject from ._data_structures import Destination @@ -31,5 +31,5 @@ def write_to_stream( class Bookmark(OutlineItem): # pragma: no cover def __init__(self, *args: Any, **kwargs: Any) -> None: - deprecate_with_replacement("Bookmark", "OutlineItem") + deprecation_with_replacement("Bookmark", "OutlineItem", "3.0.0") super().__init__(*args, **kwargs) diff --git a/PyPDF2/generic/_rectangle.py b/PyPDF2/generic/_rectangle.py index 2cbd1e2c4..3f41bfd59 100644 --- a/PyPDF2/generic/_rectangle.py +++ b/PyPDF2/generic/_rectangle.py @@ -1,7 +1,7 @@ import decimal from typing import Any, List, Tuple, Union -from .._utils import deprecate_no_replacement, deprecate_with_replacement +from .._utils import deprecation_no_replacement, deprecation_with_replacement from ._base import FloatObject, NumberObject from ._data_structures import ArrayObject @@ -42,7 +42,7 @@ def scale(self, sx: float, sy: float) -> "RectangleObject": def ensureIsNumber( self, value: Any ) -> Union[FloatObject, NumberObject]: # pragma: no cover - deprecate_no_replacement("ensureIsNumber") + deprecation_no_replacement("ensureIsNumber", "3.0.0") return self._ensure_is_number(value) def __repr__(self) -> str: @@ -81,35 +81,35 @@ def top(self, f: float) -> None: self[3] = FloatObject(f) def getLowerLeft_x(self) -> FloatObject: # pragma: no cover - deprecate_with_replacement("getLowerLeft_x", "left") + deprecation_with_replacement("getLowerLeft_x", "left", "3.0.0") return self.left def getLowerLeft_y(self) -> FloatObject: # pragma: no cover - deprecate_with_replacement("getLowerLeft_y", "bottom") + deprecation_with_replacement("getLowerLeft_y", "bottom", "3.0.0") return self.bottom def getUpperRight_x(self) -> FloatObject: # pragma: no cover - deprecate_with_replacement("getUpperRight_x", "right") + deprecation_with_replacement("getUpperRight_x", "right", "3.0.0") return self.right def getUpperRight_y(self) -> FloatObject: # pragma: no cover - deprecate_with_replacement("getUpperRight_y", "top") + deprecation_with_replacement("getUpperRight_y", "top", "3.0.0") return self.top def getUpperLeft_x(self) -> FloatObject: # pragma: no cover - deprecate_with_replacement("getUpperLeft_x", "left") + deprecation_with_replacement("getUpperLeft_x", "left", "3.0.0") return self.left def getUpperLeft_y(self) -> FloatObject: # pragma: no cover - deprecate_with_replacement("getUpperLeft_y", "top") + deprecation_with_replacement("getUpperLeft_y", "top", "3.0.0") return self.top def getLowerRight_x(self) -> FloatObject: # pragma: no cover - deprecate_with_replacement("getLowerRight_x", "right") + deprecation_with_replacement("getLowerRight_x", "right", "3.0.0") return self.right def getLowerRight_y(self) -> FloatObject: # pragma: no cover - deprecate_with_replacement("getLowerRight_y", "bottom") + deprecation_with_replacement("getLowerRight_y", "bottom", "3.0.0") return self.bottom @property @@ -163,41 +163,41 @@ def upper_right(self, value: List[Any]) -> None: def getLowerLeft( self, ) -> Tuple[decimal.Decimal, decimal.Decimal]: # pragma: no cover - deprecate_with_replacement("getLowerLeft", "lower_left") + deprecation_with_replacement("getLowerLeft", "lower_left", "3.0.0") return self.lower_left def getLowerRight( self, ) -> Tuple[decimal.Decimal, decimal.Decimal]: # pragma: no cover - deprecate_with_replacement("getLowerRight", "lower_right") + deprecation_with_replacement("getLowerRight", "lower_right", "3.0.0") return self.lower_right def getUpperLeft( self, ) -> Tuple[decimal.Decimal, decimal.Decimal]: # pragma: no cover - deprecate_with_replacement("getUpperLeft", "upper_left") + deprecation_with_replacement("getUpperLeft", "upper_left", "3.0.0") return self.upper_left def getUpperRight( self, ) -> Tuple[decimal.Decimal, decimal.Decimal]: # pragma: no cover - deprecate_with_replacement("getUpperRight", "upper_right") + deprecation_with_replacement("getUpperRight", "upper_right", "3.0.0") return self.upper_right def setLowerLeft(self, value: Tuple[float, float]) -> None: # pragma: no cover - deprecate_with_replacement("setLowerLeft", "lower_left") + deprecation_with_replacement("setLowerLeft", "lower_left", "3.0.0") self.lower_left = value # type: ignore def setLowerRight(self, value: Tuple[float, float]) -> None: # pragma: no cover - deprecate_with_replacement("setLowerRight", "lower_right") + deprecation_with_replacement("setLowerRight", "lower_right", "3.0.0") self[2], self[1] = (self._ensure_is_number(x) for x in value) def setUpperLeft(self, value: Tuple[float, float]) -> None: # pragma: no cover - deprecate_with_replacement("setUpperLeft", "upper_left") + deprecation_with_replacement("setUpperLeft", "upper_left", "3.0.0") self[0], self[3] = (self._ensure_is_number(x) for x in value) def setUpperRight(self, value: Tuple[float, float]) -> None: # pragma: no cover - deprecate_with_replacement("setUpperRight", "upper_right") + deprecation_with_replacement("setUpperRight", "upper_right", "3.0.0") self[2], self[3] = (self._ensure_is_number(x) for x in value) @property @@ -205,7 +205,7 @@ def width(self) -> decimal.Decimal: return self.right - self.left def getWidth(self) -> decimal.Decimal: # pragma: no cover - deprecate_with_replacement("getWidth", "width") + deprecation_with_replacement("getWidth", "width", "3.0.0") return self.width @property @@ -213,53 +213,53 @@ def height(self) -> decimal.Decimal: return self.top - self.bottom def getHeight(self) -> decimal.Decimal: # pragma: no cover - deprecate_with_replacement("getHeight", "height") + deprecation_with_replacement("getHeight", "height", "3.0.0") return self.height @property def lowerLeft(self) -> Tuple[decimal.Decimal, decimal.Decimal]: # pragma: no cover - deprecate_with_replacement("lowerLeft", "lower_left") + deprecation_with_replacement("lowerLeft", "lower_left", "3.0.0") return self.lower_left @lowerLeft.setter def lowerLeft( self, value: Tuple[decimal.Decimal, decimal.Decimal] ) -> None: # pragma: no cover - deprecate_with_replacement("lowerLeft", "lower_left") + deprecation_with_replacement("lowerLeft", "lower_left", "3.0.0") self.lower_left = value @property def lowerRight(self) -> Tuple[decimal.Decimal, decimal.Decimal]: # pragma: no cover - deprecate_with_replacement("lowerRight", "lower_right") + deprecation_with_replacement("lowerRight", "lower_right", "3.0.0") return self.lower_right @lowerRight.setter def lowerRight( self, value: Tuple[decimal.Decimal, decimal.Decimal] ) -> None: # pragma: no cover - deprecate_with_replacement("lowerRight", "lower_right") + deprecation_with_replacement("lowerRight", "lower_right", "3.0.0") self.lower_right = value @property def upperLeft(self) -> Tuple[decimal.Decimal, decimal.Decimal]: # pragma: no cover - deprecate_with_replacement("upperLeft", "upper_left") + deprecation_with_replacement("upperLeft", "upper_left", "3.0.0") return self.upper_left @upperLeft.setter def upperLeft( self, value: Tuple[decimal.Decimal, decimal.Decimal] ) -> None: # pragma: no cover - deprecate_with_replacement("upperLeft", "upper_left") + deprecation_with_replacement("upperLeft", "upper_left", "3.0.0") self.upper_left = value @property def upperRight(self) -> Tuple[decimal.Decimal, decimal.Decimal]: # pragma: no cover - deprecate_with_replacement("upperRight", "upper_right") + deprecation_with_replacement("upperRight", "upper_right", "3.0.0") return self.upper_right @upperRight.setter def upperRight( self, value: Tuple[decimal.Decimal, decimal.Decimal] ) -> None: # pragma: no cover - deprecate_with_replacement("upperRight", "upper_right") + deprecation_with_replacement("upperRight", "upper_right", "3.0.0") self.upper_right = value diff --git a/PyPDF2/types.py b/PyPDF2/types.py index 9683c1edd..92cba6fe9 100644 --- a/PyPDF2/types.py +++ b/PyPDF2/types.py @@ -20,8 +20,6 @@ BorderArrayType: TypeAlias = List[Union[NameObject, NumberObject, ArrayObject]] OutlineItemType: TypeAlias = Union[OutlineItem, Destination] -# BookmarkTypes is deprecated. Use OutlineItemType instead -BookmarkTypes: TypeAlias = OutlineItemType # Remove with PyPDF2==3.0.0 FitType: TypeAlias = Literal[ "/Fit", "/XYZ", "/FitH", "/FitV", "/FitR", "/FitB", "/FitBH", "/FitBV" ] @@ -29,13 +27,11 @@ ZoomArgType: TypeAlias = Union[NumberObject, NullObject, float] ZoomArgsType: TypeAlias = List[ZoomArgType] -# Recursive types are not yet supported by mypy: -# OutlinesType = List[Union[Destination, "OutlinesType"]] +# Recursive types like the following are not yet supported by mypy: +# OutlineType = List[Union[Destination, "OutlineType"]] # See https://github.com/python/mypy/issues/731 # Hence use this for the moment: OutlineType = List[Union[Destination, List[Union[Destination, List[Destination]]]]] -# OutlinesType is deprecated. Use OutlineType instead -OutlinesType: TypeAlias = OutlineType # Remove with PyPDF2==3.0.0 LayoutType: TypeAlias = Literal[ "/NoLayout", diff --git a/PyPDF2/xmp.py b/PyPDF2/xmp.py index b93b797bd..de4328239 100644 --- a/PyPDF2/xmp.py +++ b/PyPDF2/xmp.py @@ -23,7 +23,11 @@ from xml.dom.minidom import parseString from xml.parsers.expat import ExpatError -from ._utils import StreamType, deprecate_with_replacement +from ._utils import ( + StreamType, + deprecate_with_replacement, + deprecation_with_replacement, +) from .errors import PdfReadError from .generic import ContentStream, PdfObject @@ -242,7 +246,7 @@ def writeToStream( Use :meth:`write_to_stream` instead. """ - deprecate_with_replacement("writeToStream", "write_to_stream") + deprecation_with_replacement("writeToStream", "write_to_stream", "3.0.0") self.write_to_stream(stream, encryption_key) def get_element(self, about_uri: str, namespace: str, name: str) -> Iterator[Any]: @@ -261,7 +265,7 @@ def getElement( Use :meth:`get_element` instead. """ - deprecate_with_replacement("getElement", "get_element") + deprecation_with_replacement("getElement", "get_element", "3.0.0") return self.get_element(aboutUri, namespace, name) def get_nodes_in_namespace(self, about_uri: str, namespace: str) -> Iterator[Any]: @@ -283,7 +287,9 @@ def getNodesInNamespace( Use :meth:`get_nodes_in_namespace` instead. """ - deprecate_with_replacement("getNodesInNamespace", "get_nodes_in_namespace") + deprecation_with_replacement( + "getNodesInNamespace", "get_nodes_in_namespace", "3.0.0" + ) return self.get_nodes_in_namespace(aboutUri, namespace) def _get_text(self, element: XmlElement) -> str: @@ -450,12 +456,12 @@ def xmp_metadataDate(self, value: datetime.datetime) -> None: # pragma: no cove @property def xmp_creatorTool(self) -> str: # pragma: no cover - deprecate_with_replacement("xmp_creatorTool", "xmp_creator_tool") + deprecation_with_replacement("xmp_creatorTool", "xmp_creator_tool", "3.0.0") return self.xmp_creator_tool @xmp_creatorTool.setter def xmp_creatorTool(self, value: str) -> None: # pragma: no cover - deprecate_with_replacement("xmp_creatorTool", "xmp_creator_tool") + deprecation_with_replacement("xmp_creatorTool", "xmp_creator_tool", "3.0.0") self.xmp_creator_tool = value xmpmm_document_id = property(_getter_single(XMPMM_NAMESPACE, "DocumentID")) @@ -465,12 +471,12 @@ def xmp_creatorTool(self, value: str) -> None: # pragma: no cover @property def xmpmm_documentId(self) -> str: # pragma: no cover - deprecate_with_replacement("xmpmm_documentId", "xmpmm_document_id") + deprecation_with_replacement("xmpmm_documentId", "xmpmm_document_id", "3.0.0") return self.xmpmm_document_id @xmpmm_documentId.setter def xmpmm_documentId(self, value: str) -> None: # pragma: no cover - deprecate_with_replacement("xmpmm_documentId", "xmpmm_document_id") + deprecation_with_replacement("xmpmm_documentId", "xmpmm_document_id", "3.0.0") self.xmpmm_document_id = value xmpmm_instance_id = property(_getter_single(XMPMM_NAMESPACE, "InstanceID")) @@ -481,12 +487,12 @@ def xmpmm_documentId(self, value: str) -> None: # pragma: no cover @property def xmpmm_instanceId(self) -> str: # pragma: no cover - deprecate_with_replacement("xmpmm_instanceId", "xmpmm_instance_id") + deprecation_with_replacement("xmpmm_instanceId", "xmpmm_instance_id", "3.0.0") return cast(str, self.xmpmm_instance_id) @xmpmm_instanceId.setter def xmpmm_instanceId(self, value: str) -> None: # pragma: no cover - deprecate_with_replacement("xmpmm_instanceId", "xmpmm_instance_id") + deprecation_with_replacement("xmpmm_instanceId", "xmpmm_instance_id", "3.0.0") self.xmpmm_instance_id = value @property diff --git a/docs/dev/deprecations.md b/docs/dev/deprecations.md index f439429f3..89f778e44 100644 --- a/docs/dev/deprecations.md +++ b/docs/dev/deprecations.md @@ -29,14 +29,14 @@ we do it: and when it will happen. The docs let users know about the deprecation and when it will happen and the new function. The CHANGELOG informs about it. -2. `(x+1).0.0`: Remove / change the code in the breaking way, - but keep/add DeprecationWarnings messages. +2. `(x+1).0.0`: Remove / change the code in the breaking way by replacing + DeprecationWarnings by DeprecationErrors. We do this to help people who didn't look at the warnings before. The CHANGELOG informs about it. -3. `(x+2).0.0`: The DeprecationWarnings are removed. +3. `(x+2).0.0`: The DeprecationErrors are removed. -This means the users have 3 warnings in the CHANGELOG, a PendingDeprecationWarning -until the next major release and a DeprecationWarning until the major release +This means the users have 3 warnings in the CHANGELOG, a DeprecationWarning +until the next major release and a DeprecationError until the major release after that. Please note that adding warnings can be a breaking change for some users; most diff --git a/tests/test_generic.py b/tests/test_generic.py index cffacc964..b02692d97 100644 --- a/tests/test_generic.py +++ b/tests/test_generic.py @@ -967,7 +967,10 @@ def test_cloning(caplog): obj3 = obj2.indirect_reference.clone(writer) assert len(writer._objects) == n + 1 assert obj2.indirect_reference == obj3.indirect_reference - assert obj2.indirect_reference == obj2._reference_clone(obj2, writer).indirect_reference + assert ( + obj2.indirect_reference + == obj2._reference_clone(obj2, writer).indirect_reference + ) assert len(writer._objects) == n + 1 assert obj2.indirect_reference == obj3.indirect_reference diff --git a/tests/test_merger.py b/tests/test_merger.py index f33c29d7e..f5d078900 100644 --- a/tests/test_merger.py +++ b/tests/test_merger.py @@ -7,6 +7,7 @@ import PyPDF2 from PyPDF2 import PdfMerger, PdfReader, PdfWriter +from PyPDF2.errors import DeprecationError from PyPDF2.generic import Destination, Fit from . import get_pdf_from_url @@ -664,44 +665,38 @@ def test_iss1145_with_writer(): merger.close() -def test_deprecate_bookmark_decorator_warning(): +def test_deprecation_bookmark_decorator_deprecationexcp(): reader = PdfReader(RESOURCE_ROOT / "outlines-with-invalid-destinations.pdf") merger = PdfMerger() - with pytest.warns( - UserWarning, + with pytest.raises( + DeprecationError, match="import_bookmarks is deprecated as an argument. Use import_outline instead", ): merger.merge(0, reader, import_bookmarks=True) -def test_deprecate_bookmark_decorator_warning_with_writer(): +def test_deprecation_bookmark_decorator_deprecationexcp_with_writer(): reader = PdfReader(RESOURCE_ROOT / "outlines-with-invalid-destinations.pdf") merger = PdfWriter() - with pytest.warns( - UserWarning, + with pytest.raises( + DeprecationError, match="import_bookmarks is deprecated as an argument. Use import_outline instead", ): merger.merge(0, reader, import_bookmarks=True) -@pytest.mark.filterwarnings("ignore::UserWarning") -def test_deprecate_bookmark_decorator_output(): +def test_deprecation_bookmark_decorator_output(): reader = PdfReader(RESOURCE_ROOT / "outlines-with-invalid-destinations.pdf") merger = PdfMerger() - merger.merge(0, reader, import_bookmarks=True) - first_oi_title = 'Valid Destination: Action /GoTo Named Destination "section.1"' - assert merger.outline[0].title == first_oi_title + with pytest.raises(DeprecationError): + merger.merge(0, reader, import_bookmarks=True) -@pytest.mark.filterwarnings("ignore::UserWarning") -def test_deprecate_bookmark_decorator_output_with_writer(): +def test_deprecation_bookmark_decorator_output_with_writer(): reader = PdfReader(RESOURCE_ROOT / "outlines-with-invalid-destinations.pdf") merger = PdfWriter() - merger.merge(0, reader, import_bookmarks=True) - first_oi_title = 'Valid Destination: Action /GoTo Named Destination "section.1"' - # TODO? : add outline property ??? - # assert merger.outline[0].title == first_oi_title - assert merger.find_outline_item(first_oi_title) == [0] + with pytest.raises(DeprecationError): + merger.merge(0, reader, import_bookmarks=True) @pytest.mark.external diff --git a/tests/test_page.py b/tests/test_page.py index 2c2ea3a9f..ee4af14a0 100644 --- a/tests/test_page.py +++ b/tests/test_page.py @@ -10,7 +10,7 @@ from PyPDF2 import PdfReader, PdfWriter, Transformation from PyPDF2._page import PageObject, set_custom_rtl from PyPDF2.constants import PageAttributes as PG -from PyPDF2.errors import PdfReadWarning +from PyPDF2.errors import DeprecationError, PdfReadWarning from PyPDF2.generic import ( ArrayObject, DictionaryObject, @@ -131,8 +131,10 @@ def test_transformation_equivalence(): # Option 2: The old way page_box2 = deepcopy(page_box) page_base2 = deepcopy(page_base) - with pytest.warns(PendingDeprecationWarning): + with pytest.raises(DeprecationError): page_base2.mergeTransformedPage(page_box2, op, expand=False) + page_box2.add_transformation(op) + page_base2.merge_page(page_box2) # Should be the same assert page_base1[NameObject(PG.CONTENTS)] == page_base2[NameObject(PG.CONTENTS)] @@ -165,21 +167,21 @@ def test_page_transformations(): reader = PdfReader(pdf_path) page: PageObject = reader.pages[0] - with pytest.warns(PendingDeprecationWarning): + with pytest.raises(DeprecationError): page.mergeRotatedPage(page, 90, expand=True) - with pytest.warns(PendingDeprecationWarning): + with pytest.raises(DeprecationError): page.mergeRotatedScaledPage(page, 90, 1, expand=True) - with pytest.warns(PendingDeprecationWarning): + with pytest.raises(DeprecationError): page.mergeRotatedScaledTranslatedPage( page, 90, scale=1, tx=1, ty=1, expand=True ) - with pytest.warns(PendingDeprecationWarning): + with pytest.raises(DeprecationError): page.mergeRotatedTranslatedPage(page, 90, 100, 100, expand=False) - with pytest.warns(PendingDeprecationWarning): + with pytest.raises(DeprecationError): page.mergeScaledPage(page, 2, expand=False) - with pytest.warns(PendingDeprecationWarning): + with pytest.raises(DeprecationError): page.mergeScaledTranslatedPage(page, 1, 1, 1) - with pytest.warns(PendingDeprecationWarning): + with pytest.raises(DeprecationError): page.mergeTranslatedPage(page, 100, 100, expand=False) page.add_transformation((1, 0, 0, 0, 0, 0)) diff --git a/tests/test_reader.py b/tests/test_reader.py index 25018ae0f..fda8f929f 100644 --- a/tests/test_reader.py +++ b/tests/test_reader.py @@ -11,6 +11,7 @@ from PyPDF2.constants import ImageAttributes as IA from PyPDF2.constants import PageAttributes as PG from PyPDF2.errors import ( + DeprecationError, EmptyFileError, FileNotDecryptedError, PdfReadError, @@ -739,11 +740,11 @@ def test_convert_to_int_error(): def test_convertToInt_deprecated(): msg = ( - "convertToInt is deprecated and will be removed in PyPDF2 3.0.0. " + "convertToInt is deprecated and was removed in PyPDF2 3.0.0. " "Use convert_to_int instead." ) - with pytest.warns( - PendingDeprecationWarning, + with pytest.raises( + DeprecationError, match=msg, ): assert convertToInt(b"\x01", 8) == 1 diff --git a/tests/test_utils.py b/tests/test_utils.py index a5f5fa186..1f67878db 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -10,7 +10,7 @@ File, _get_max_pdf_version_header, _human_readable_bytes, - deprecate_bookmark, + deprecation_bookmark, mark_location, matrix_multiply, read_block_backwards, @@ -20,7 +20,7 @@ skip_over_comment, skip_over_whitespace, ) -from PyPDF2.errors import PdfReadError, PdfStreamError +from PyPDF2.errors import DeprecationError, PdfReadError, PdfStreamError from . import get_pdf_from_url @@ -109,7 +109,7 @@ def test_b(): def test_deprecate_no_replacement(): - with pytest.warns(PendingDeprecationWarning) as warn: + with pytest.warns(DeprecationWarning) as warn: PyPDF2._utils.deprecate_no_replacement("foo") error_msg = "foo is deprecated and will be removed in PyPDF2 3.0.0." assert warn[0].message.args[0] == error_msg @@ -236,17 +236,14 @@ def test_read_block_backwards_exception(): assert exc.value.args[0] == "Could not read malformed PDF file" -def test_deprecate_bookmark(): - @deprecate_bookmark(old_param="new_param") +def test_deprecation_bookmark(): + @deprecation_bookmark(old_param="new_param") def foo(old_param=1, baz=2): return old_param * baz - with pytest.raises(TypeError) as exc: + with pytest.raises(DeprecationError) as exc: foo(old_param=12, new_param=13) - expected_msg = ( - "foo received both old_param and new_param as an argument. " - "old_param is deprecated. Use new_param instead." - ) + expected_msg = "old_param is deprecated as an argument. Use new_param instead" assert exc.value.args[0] == expected_msg diff --git a/tests/test_writer.py b/tests/test_writer.py index f179a1443..cf3a46380 100644 --- a/tests/test_writer.py +++ b/tests/test_writer.py @@ -1,14 +1,15 @@ import os +import re from io import BytesIO from pathlib import Path import pytest from PyPDF2 import PageObject, PdfMerger, PdfReader, PdfWriter -from PyPDF2.errors import PageSizeNotDefinedError +from PyPDF2.errors import DeprecationError, PageSizeNotDefinedError from PyPDF2.generic import ( - Fit, ArrayObject, + Fit, IndirectObject, NameObject, NumberObject, @@ -112,7 +113,7 @@ def writer_operate(writer): ) writer.add_blank_page() writer.add_uri(2, "https://example.com", RectangleObject([0, 0, 100, 100])) - with pytest.warns(PendingDeprecationWarning): + with pytest.raises(DeprecationError): writer.add_link(2, 1, RectangleObject([0, 0, 100, 100])) assert writer._get_page_layout() is None writer.page_layout = "broken" @@ -582,9 +583,14 @@ def test_add_link(): for page in reader.pages: writer.add_page(page) - with pytest.warns( - PendingDeprecationWarning, - match="add_link is deprecated and will be removed in PyPDF2", + with pytest.raises( + DeprecationError, + match=( + re.escape( + "add_link is deprecated and was removed in PyPDF2 3.0.0. " + "Use add_annotation(AnnotationBuilder.link(...)) instead." + ) + ), ): writer.add_link( 1, @@ -791,17 +797,17 @@ def test_add_single_annotation(): writer.write(fp) # Cleanup - os.remove(target) # remove for testing + os.remove(target) # comment out for testing -def test_deprecate_bookmark_decorator(): +def test_deprecation_bookmark_decorator(): reader = PdfReader(RESOURCE_ROOT / "outlines-with-invalid-destinations.pdf") page = reader.pages[0] outline_item = reader.outline[0] writer = PdfWriter() writer.add_page(page) - with pytest.warns( - UserWarning, + with pytest.raises( + DeprecationError, match="bookmark is deprecated as an argument. Use outline_item instead", ): writer.add_outline_item_dict(bookmark=outline_item) @@ -827,7 +833,7 @@ def test_colors_in_outline_item(): assert [str(c) for c in outline_item.color] == [str(p) for p in purple_rgb] # Cleanup - os.remove(target) # remove for testing + os.remove(target) # comment out for testing @pytest.mark.samples