Skip to content

Commit

Permalink
MAINT: Add initial type support with mypy (#853)
Browse files Browse the repository at this point in the history
This includes adding a type m
  • Loading branch information
MartinThoma committed May 2, 2022
1 parent f06375e commit b580a45
Show file tree
Hide file tree
Showing 11 changed files with 180 additions and 122 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/github-ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ jobs:
run: |
python -OO -m coverage run --parallel-mode -m pytest tests -vv
if: matrix.python-version == '3.10.1'
- name: Test with mypy
run : |
mypy PyPDF2 --show-error-codes
- name: Upload coverage data
uses: actions/upload-artifact@v3
with:
Expand Down
11 changes: 8 additions & 3 deletions PyPDF2/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@
import struct
from io import StringIO

try:
from typing import Literal # type: ignore[attr-defined]
except ImportError:
from typing_extensions import Literal # type: ignore[misc]

from PyPDF2.constants import CcittFaxDecodeParameters as CCITT
from PyPDF2.constants import ColorSpaces
from PyPDF2.constants import FilterTypeAbbreviations as FTA
Expand Down Expand Up @@ -66,8 +71,8 @@ def compress(data):
except ImportError: # pragma: no cover
# Unable to import zlib. Attempt to use the System.IO.Compression
# library from the .NET framework. (IronPython only)
import System
from System import IO, Array
import System # type: ignore[import]
from System import IO, Array # type: ignore[import]

def _string_to_bytearr(buf):
retval = Array.CreateInstance(System.Byte, len(buf))
Expand Down Expand Up @@ -536,7 +541,7 @@ def _xobj_to_image(x_object_obj):
size = (x_object_obj[IA.WIDTH], x_object_obj[IA.HEIGHT])
data = x_object_obj.getData()
if x_object_obj[IA.COLOR_SPACE] == ColorSpaces.DEVICE_RGB:
mode = "RGB"
mode: Literal["RGB", "P"] = "RGB"
else:
mode = "P"
extension = None
Expand Down
39 changes: 24 additions & 15 deletions PyPDF2/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import re
import warnings
from io import BytesIO
from typing import Dict, Optional

from PyPDF2.constants import FilterTypes as FT
from PyPDF2.constants import StreamAttributes as SA
Expand All @@ -57,7 +58,13 @@
IndirectPattern = re.compile(b_(r"[+-]?(\d+)\s+(\d+)\s+R[^a-zA-Z]"))


def readObject(stream, pdf):
class PdfObject:
def getObject(self):
"""Resolve indirect references."""
return self


def readObject(stream, pdf) -> PdfObject:
tok = stream.read(1)
stream.seek(-1, 1) # reset to start
idx = ObjectPrefix.find(tok)
Expand Down Expand Up @@ -101,12 +108,6 @@ def readObject(stream, pdf):
return NumberObject.readFromStream(stream)


class PdfObject:
def getObject(self):
"""Resolve indirect references."""
return self


class NullObject(PdfObject):
def writeToStream(self, stream, encryption_key):
stream.write(b_("null"))
Expand Down Expand Up @@ -508,7 +509,7 @@ def readFromStream(stream, pdf):
# Name objects should represent irregular characters
# with a '#' followed by the symbol's hex number
if not pdf.strict:
warnings.warn("Illegal character in Name Object", utils.PdfReadWarning)
warnings.warn("Illegal character in Name Object", PdfReadWarning)
return NameObject(name)
else:
raise PdfReadError("Illegal character in Name Object")
Expand Down Expand Up @@ -805,10 +806,18 @@ def emptyTree(self):

class StreamObject(DictionaryObject):
def __init__(self):
self._data = None
self.__data: Optional[str] = None
self.decodedSelf = None

def writeToStream(self, stream, encryption_key):
@property
def _data(self):
return self.__data

@_data.setter
def _data(self, value):
self.__data = value

def writeToStream(self, stream, encryption_key) -> None:
self[NameObject(SA.LENGTH)] = NumberObject(len(self._data))
DictionaryObject.writeToStream(self, stream, encryption_key)
del self[SA.LENGTH]
Expand Down Expand Up @@ -989,7 +998,8 @@ def _readInlineImage(self, stream):
data.write(tok)
return {"settings": settings, "data": data.getvalue()}

def _getData(self):
@property
def _data(self):
newdata = BytesIO()
for operands, operator in self.operations:
if operator == b_("INLINE IMAGE"):
Expand All @@ -1008,11 +1018,10 @@ def _getData(self):
newdata.write(b_("\n"))
return newdata.getvalue()

def _setData(self, value):
@_data.setter
def _data(self, value):
self.__parseContentStream(BytesIO(b_(value)))

_data = property(_getData, _setData)


class RectangleObject(ArrayObject):
"""
Expand Down Expand Up @@ -1686,7 +1695,7 @@ def decode_pdfdocencoding(byte_array):

assert len(_pdfDocEncoding) == 256

_pdfDocEncoding_rev = {}
_pdfDocEncoding_rev: Dict[str, int] = {}
for i in range(256):
char = _pdfDocEncoding[i]
if char == "\u0000":
Expand Down
21 changes: 19 additions & 2 deletions PyPDF2/merger.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

from io import BytesIO
from io import FileIO as file
from typing import List, Optional, Union

from PyPDF2._reader import PdfFileReader
from PyPDF2._writer import PdfFileWriter
Expand All @@ -37,6 +38,8 @@

StreamIO = BytesIO

ERR_CLOSED_WRITER = "close() was called and thus the writer cannot be used anymore"


class _MergedPage:
"""
Expand Down Expand Up @@ -68,7 +71,7 @@ class PdfFileMerger:
def __init__(self, strict=False):
self.inputs = []
self.pages = []
self.output = PdfFileWriter()
self.output: Optional[PdfFileWriter] = PdfFileWriter()
self.bookmarks = []
self.named_dests = []
self.id_count = 0
Expand Down Expand Up @@ -220,6 +223,8 @@ def write(self, fileobj):
:param fileobj: Output file. Can be a filename or any kind of
file-like object.
"""
if self.output is None:
raise RuntimeError(ERR_CLOSED_WRITER)
my_file = False
if isinstance(fileobj, str):
fileobj = file(fileobj, "wb")
Expand Down Expand Up @@ -267,6 +272,8 @@ def addMetadata(self, infos):
and each value is your new metadata.
Example: ``{u'/Title': u'My title'}``
"""
if self.output is None:
raise RuntimeError(ERR_CLOSED_WRITER)
self.output.addMetadata(infos)

def setPageLayout(self, layout):
Expand All @@ -293,6 +300,8 @@ def setPageLayout(self, layout):
* - /TwoPageRight
- Show two pages at a time, odd-numbered pages on the right
"""
if self.output is None:
raise RuntimeError(ERR_CLOSED_WRITER)
self.output.setPageLayout(layout)

def setPageMode(self, mode):
Expand All @@ -317,6 +326,8 @@ def setPageMode(self, mode):
* - /UseAttachments
- Show attachments panel
"""
if self.output is None:
raise RuntimeError(ERR_CLOSED_WRITER)
self.output.setPageMode(mode)

def _trim_dests(self, pdf, dests, pages):
Expand Down Expand Up @@ -359,6 +370,8 @@ def _trim_outline(self, pdf, outline, pages):
return new_outline

def _write_dests(self):
if self.output is None:
raise RuntimeError(ERR_CLOSED_WRITER)
for named_dest in self.named_dests:
pageno = None
if "/Page" in named_dest:
Expand All @@ -371,6 +384,8 @@ def _write_dests(self):
self.output.addNamedDestinationObject(named_dest)

def _write_bookmarks(self, bookmarks=None, parent=None):
if self.output is None:
raise RuntimeError(ERR_CLOSED_WRITER)
if bookmarks is None:
bookmarks = self.bookmarks

Expand Down Expand Up @@ -533,13 +548,15 @@ def addBookmark(
:param str fit: The fit of the destination page. See
:meth:`addLink()<addLin>` for details.
"""
if self.output is None:
raise RuntimeError(ERR_CLOSED_WRITER)
if len(self.output.getObject(self.output._pages)["/Kids"]) > 0:
page_ref = self.output.getObject(self.output._pages)["/Kids"][pagenum]
else:
page_ref = self.output.getObject(self.output._pages)

action = DictionaryObject()
zoom_args = []
zoom_args: List[Union[NumberObject, NullObject]] = []
for a in args:
if a is not None:
zoom_args.append(NumberObject(a))
Expand Down
4 changes: 3 additions & 1 deletion PyPDF2/pagerange.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"""

import re
from typing import List, Tuple, Union

from PyPDF2.errors import ParseError

Expand Down Expand Up @@ -96,6 +97,7 @@ def to_slice(self):
def __str__(self):
"""A string like "1:2:3"."""
s = self._slice
indices: Union[Tuple[int, int], Tuple[int, int, int]]
if s.step is None:
if s.start is not None and s.stop == s.start + 1:
return str(s.start)
Expand Down Expand Up @@ -133,7 +135,7 @@ def parse_filename_page_ranges(args):
expressions, slice objects, or PageRange objects.
A filename not followed by a page range indicates all pages of the file.
"""
pairs = []
pairs: List[Tuple[str, PageRange]] = []
pdf_filename = None
did_page_range = False
for arg in args + [None]:
Expand Down
Empty file added PyPDF2/py.typed
Empty file.
Loading

0 comments on commit b580a45

Please sign in to comment.