Skip to content

Commit 290ccdd

Browse files
committed
typing
1 parent 90c2480 commit 290ccdd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+6140
-4656
lines changed

.github/workflows/continuous-integration-workflow.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ jobs:
7070
pylint fpdf test tutorial/tuto*.py
7171
bandit -c .banditrc.yml -r contributors/ fpdf/ tutorial/
7272
semgrep scan --config auto --error --strict --exclude-rule=python.lang.security.insecure-hash-function.insecure-hash-function fpdf
73+
- name: Run type checkers 🔎
74+
run: |
75+
mypy
7376
- name: Scan project with grype 🔎
7477
uses: anchore/scan-action@3c9a191a0fbab285ca6b8530b5de5a642cba332f # v7.2.2
7578
with:

.pre-commit-config.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,21 @@ repos:
2323
rev: v1.30.2
2424
hooks:
2525
- id: typos
26+
- repo: https://github.com/pre-commit/mirrors-mypy
27+
rev: 'v1.19.0'
28+
hooks:
29+
- id: mypy
30+
args:
31+
- --config-file=./pyproject.toml
32+
files: ^fpdf/.*\.py$
33+
additional_dependencies:
34+
- cryptography
35+
- defusedxml
36+
- endesive
37+
- fonttools
38+
- numpy
39+
- Pillow
40+
- uharfbuzz
2641
- repo: local
2742
hooks:
2843
- id: no-print-in-sources

fpdf/__init__.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,28 @@
1515
* `fpdf.template.FlexTemplate`
1616
"""
1717

18-
import warnings, sys
18+
import sys
19+
import warnings
1920

2021
from .deprecation import WarnOnDeprecatedModuleAttributes
2122
from .enums import Align, TextMode, XPos, YPos
2223
from .errors import FPDFException
2324
from .fonts import FontFace, TextStyle
2425
from .fpdf import (
2526
FPDF,
26-
TitleStyle,
2727
FPDF_FONT_DIR as _FPDF_FONT_DIR,
2828
FPDF_VERSION as _FPDF_VERSION,
29+
TitleStyle,
2930
)
30-
from .html import HTMLMixin, HTML2FPDF
31+
from .html import HTML2FPDF, HTMLMixin
3132
from .prefs import ViewerPreferences
32-
from .template import Template, FlexTemplate
33+
from .template import FlexTemplate, Template
3334
from .util import get_scale_factor
3435

3536
try:
3637
# This module only exists in PyFPDF, it has been removed in fpdf2 since v2.5.7:
3738
# pylint: disable=import-self
38-
from . import ttfonts
39+
from . import ttfonts # type: ignore[attr-defined]
3940

4041
warnings.warn(
4142
"You have both PyFPDF & fpdf2 installed. "

fpdf/actions.py

Lines changed: 54 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,35 @@
1-
from abc import ABC
21
import warnings
2+
from abc import ABC
3+
from typing import TYPE_CHECKING, Optional, Union
34

45
from .syntax import PDFString, build_obj_dict, create_dictionary_string
56

7+
if TYPE_CHECKING:
8+
from .encryption import StandardSecurityHandler
9+
from .syntax import Destination
10+
611

712
class Action(ABC):
8-
def __init__(self, next_action=None):
13+
def __init__(self, next_action: Optional["Action"] = None) -> None:
914
"""
1015
Args:
1116
next (PDFObject | str): optional reference to another Action to trigger after this one
1217
"""
1318
self.next = next_action
1419

15-
def serialize(self, _security_handler=None, _obj_id=None):
20+
def serialize(
21+
self,
22+
_security_handler: Optional["StandardSecurityHandler"] = None,
23+
_obj_id: Optional[int] = None,
24+
) -> str:
1625
raise NotImplementedError
1726

18-
def _serialize(self, key_values=None, _security_handler=None, _obj_id=None):
27+
def _serialize(
28+
self,
29+
key_values: Optional[dict[str, object]] = None,
30+
_security_handler: Optional["StandardSecurityHandler"] = None,
31+
_obj_id: Optional[int] = None,
32+
) -> str:
1933
if key_values is None:
2034
key_values = {}
2135
if self.next:
@@ -25,11 +39,15 @@ def _serialize(self, key_values=None, _security_handler=None, _obj_id=None):
2539

2640

2741
class URIAction(Action):
28-
def __init__(self, uri, next_action=None):
29-
super().__init__(next)
42+
def __init__(self, uri: str, next_action: Optional[Action] = None) -> None:
43+
super().__init__(next_action)
3044
self.uri = uri
3145

32-
def serialize(self, _security_handler=None, _obj_id=None):
46+
def serialize(
47+
self,
48+
_security_handler: Optional["StandardSecurityHandler"] = None,
49+
_obj_id: Optional[int] = None,
50+
) -> str:
3351
return super()._serialize(
3452
{"s": "/URI", "u_r_i": PDFString(self.uri, encrypt=True)},
3553
_security_handler=_security_handler,
@@ -38,13 +56,17 @@ def serialize(self, _security_handler=None, _obj_id=None):
3856

3957

4058
class NamedAction(Action):
41-
def __init__(self, action_name, next_action=None):
42-
super().__init__(next)
59+
def __init__(self, action_name: str, next_action: Optional[Action] = None) -> None:
60+
super().__init__(next_action)
4361
if action_name not in ("NextPage", "PrevPage", "FirstPage", "LastPage"):
4462
warnings.warn("Non-standard named action added")
4563
self.action_name = action_name
4664

47-
def serialize(self, _security_handler=None, _obj_id=None):
65+
def serialize(
66+
self,
67+
_security_handler: Optional["StandardSecurityHandler"] = None,
68+
_obj_id: Optional[int] = None,
69+
) -> str:
4870
return super()._serialize(
4971
{"s": "/Named", "n": f"/{self.action_name}"},
5072
_security_handler=_security_handler,
@@ -53,13 +75,19 @@ def serialize(self, _security_handler=None, _obj_id=None):
5375

5476

5577
class GoToAction(Action):
56-
def __init__(self, dest, next_action=None):
78+
def __init__(
79+
self, dest: Union[str, "Destination"], next_action: Optional[Action] = None
80+
) -> None:
5781
super().__init__(next_action)
5882
if isinstance(dest, str) and dest.startswith("#"):
5983
dest = PDFString(dest[1:], encrypt=True)
6084
self.dest = dest
6185

62-
def serialize(self, _security_handler=None, _obj_id=None):
86+
def serialize(
87+
self,
88+
_security_handler: Optional["StandardSecurityHandler"] = None,
89+
_obj_id: Optional[int] = None,
90+
) -> str:
6391
return super()._serialize(
6492
{"s": "/GoTo", "d": self.dest},
6593
_security_handler=_security_handler,
@@ -68,12 +96,18 @@ def serialize(self, _security_handler=None, _obj_id=None):
6896

6997

7098
class GoToRemoteAction(Action):
71-
def __init__(self, file, dest, next_action=None):
99+
def __init__(
100+
self, file: str, dest: "Destination", next_action: Optional[Action] = None
101+
) -> None:
72102
super().__init__(next_action)
73103
self.file = file
74104
self.dest = dest
75105

76-
def serialize(self, _security_handler=None, _obj_id=None):
106+
def serialize(
107+
self,
108+
_security_handler: Optional["StandardSecurityHandler"] = None,
109+
_obj_id: Optional[int] = None,
110+
) -> str:
77111
return super()._serialize(
78112
{"s": "/GoToR", "f": PDFString(self.file, encrypt=True), "d": self.dest},
79113
_security_handler=_security_handler,
@@ -84,11 +118,15 @@ def serialize(self, _security_handler=None, _obj_id=None):
84118
class LaunchAction(Action):
85119
"As of 2022, this does not seem honored by neither Adobe Acrobat nor Sumatra readers."
86120

87-
def __init__(self, file, next_action=None):
121+
def __init__(self, file: str, next_action: Optional[Action] = None) -> None:
88122
super().__init__(next_action)
89123
self.file = file
90124

91-
def serialize(self, _security_handler=None, _obj_id=None):
125+
def serialize(
126+
self,
127+
_security_handler: Optional["StandardSecurityHandler"] = None,
128+
_obj_id: Optional[int] = None,
129+
) -> str:
92130
return super()._serialize(
93131
{"s": "/Launch", "f": PDFString(self.file, encrypt=True)},
94132
_security_handler=_security_handler,

fpdf/annotations.py

Lines changed: 47 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import hashlib
66
from datetime import datetime
7-
from typing import Optional, Tuple, Union
7+
from typing import TYPE_CHECKING, Any, Optional, Sequence, Union
88

99
from .actions import Action
1010
from .enums import (
@@ -14,18 +14,20 @@
1414
FileAttachmentAnnotationName,
1515
)
1616
from .syntax import (
17-
build_obj_dict,
1817
Destination,
1918
Name,
2019
PDFContentStream,
2120
PDFDate,
2221
PDFObject,
2322
PDFString,
23+
build_obj_dict,
24+
create_dictionary_string as pdf_dict,
25+
create_list_string as pdf_list,
26+
iobj_ref as pdf_ref,
2427
)
25-
from .syntax import create_dictionary_string as pdf_dict
26-
from .syntax import create_list_string as pdf_list
27-
from .syntax import iobj_ref as pdf_ref
2828

29+
if TYPE_CHECKING:
30+
from .encryption import StandardSecurityHandler
2931

3032
# cf. https://docs.verapdf.org/validation/pdfa-part1/#rule-653-2
3133
DEFAULT_ANNOT_FLAGS = (AnnotationFlag.PRINT,)
@@ -35,26 +37,26 @@ class AnnotationMixin:
3537
def __init__(
3638
self,
3739
subtype: str,
38-
x: int,
39-
y: int,
40-
width: int,
41-
height: int,
42-
flags: Union[Tuple[AnnotationFlag], Tuple[str]] = DEFAULT_ANNOT_FLAGS,
43-
contents: str = None,
44-
dest: Destination = None,
45-
action: Action = None,
46-
color: tuple = None,
47-
modification_time: datetime = None,
48-
title: str = None,
49-
quad_points: tuple = None,
50-
border_width: int = 0, # PDF readers support: displayed by Acrobat but not Sumatra
51-
name: Union[AnnotationName, FileAttachmentAnnotationName] = None,
52-
ink_list: Tuple[int] = (), # for ink annotations
53-
file_spec: str = None,
54-
field_type: str = None,
55-
value=None,
56-
default_appearance: str = None, # for free text annotations
57-
):
40+
x: float,
41+
y: float,
42+
width: float,
43+
height: float,
44+
flags: tuple[AnnotationFlag | str, ...] = DEFAULT_ANNOT_FLAGS,
45+
contents: Optional[str] = None,
46+
dest: Optional[Destination | PDFString] = None,
47+
action: Optional[Action] = None,
48+
color: Optional[tuple[float, float, float]] = None,
49+
modification_time: Optional[datetime] = None,
50+
title: Optional[str] = None,
51+
quad_points: Optional[Sequence[float]] = None,
52+
border_width: float = 0, # PDF readers support: displayed by Acrobat but not Sumatra
53+
name: Union[AnnotationName, FileAttachmentAnnotationName, None] = None,
54+
ink_list: Optional[tuple[float, ...]] = None, # for ink annotations
55+
file_spec: Optional[Union["FileSpec", str]] = None,
56+
field_type: Optional[str] = None,
57+
value: Optional[str] = None,
58+
default_appearance: Optional[str] = None, # for free text annotations
59+
) -> None:
5860
self.type = Name("Annot")
5961
self.subtype = Name(subtype)
6062
self.rect = f"[{x:.2f} {y - height:.2f} {x + width:.2f} {y:.2f}]"
@@ -69,14 +71,14 @@ def __init__(
6971
self.t = PDFString(title, encrypt=True) if title else None
7072
self.m = PDFDate(modification_time, encrypt=True) if modification_time else None
7173
self.quad_points = (
72-
pdf_list(f"{quad_point:.2f}" for quad_point in quad_points)
74+
pdf_list([f"{quad_point:.2f}" for quad_point in quad_points])
7375
if quad_points
7476
else None
7577
)
7678
self.p = None # must always be set before calling .serialize()
7779
self.name = name
7880
self.ink_list = (
79-
("[" + pdf_list(f"{coord:.2f}" for coord in ink_list) + "]")
81+
("[" + pdf_list([f"{coord:.2f}" for coord in ink_list]) + "]")
8082
if ink_list
8183
else None
8284
)
@@ -87,7 +89,7 @@ def __init__(
8789
class PDFAnnotation(AnnotationMixin, PDFObject):
8890
"A PDF annotation that get serialized as an obj<</>>endobj block"
8991

90-
def __init__(self, *args, **kwargs):
92+
def __init__(self, *args: Any, **kwargs: Any) -> None:
9193
super().__init__(*args, **kwargs)
9294

9395

@@ -115,15 +117,19 @@ class AnnotationDict(AnnotationMixin):
115117
"d_a",
116118
)
117119

118-
def serialize(self, _security_handler=None, _obj_id=None):
120+
def serialize(
121+
self,
122+
_security_handler: Optional["StandardSecurityHandler"] = None,
123+
_obj_id: Optional[int] = None,
124+
) -> str:
119125
obj_dict = build_obj_dict(
120126
{key: getattr(self, key) for key in dir(self)},
121127
_security_handler=_security_handler,
122128
_obj_id=_obj_id,
123129
)
124130
return pdf_dict(obj_dict)
125131

126-
def __repr__(self):
132+
def __repr__(self) -> str:
127133
keys = [key for key in dir(self) if not key.startswith("__")]
128134
d = {key: getattr(self, key) for key in keys}
129135
d = {key: value for key, value in d.items() if not callable(value)}
@@ -145,7 +151,7 @@ def __init__(
145151
):
146152
super().__init__(contents=contents, compress=compress)
147153
self.type = Name("EmbeddedFile")
148-
params = {"/Size": len(contents)}
154+
params: dict[str, object] = {"/Size": len(contents)}
149155
if creation_date:
150156
params["/CreationDate"] = PDFDate(creation_date, with_tz=True).serialize()
151157
if modification_date:
@@ -158,22 +164,22 @@ def __init__(
158164
if mime_type:
159165
self.subtype = Name(mime_type)
160166
self.params = pdf_dict(params)
161-
self._basename = basename # private so that it does not get serialized
162-
self._desc = desc # private so that it does not get serialized
163-
self._globally_enclosed = True
164-
self._af_relationship = af_relationship
165-
self._file_spec = None
167+
self._basename: str = basename # private so that it does not get serialized
168+
self._desc: str = desc # private so that it does not get serialized
169+
self._globally_enclosed: bool = True
170+
self._af_relationship: Optional[AssociatedFileRelationship] = af_relationship
171+
self._file_spec: Optional[FileSpec] = None
166172

167-
def globally_enclosed(self):
173+
def globally_enclosed(self) -> bool:
168174
return self._globally_enclosed
169175

170-
def set_globally_enclosed(self, value):
176+
def set_globally_enclosed(self, value: bool) -> None:
171177
self._globally_enclosed = value
172178

173-
def basename(self):
179+
def basename(self) -> str:
174180
return self._basename
175181

176-
def file_spec(self):
182+
def file_spec(self) -> "FileSpec":
177183
if not self._file_spec:
178184
self._file_spec = FileSpec(
179185
self, self._basename, self._desc, self._af_relationship
@@ -201,5 +207,5 @@ def __init__(
201207
self._embedded_file = embedded_file
202208

203209
@property
204-
def e_f(self):
210+
def e_f(self) -> str:
205211
return pdf_dict({"/F": pdf_ref(self._embedded_file.id)})

0 commit comments

Comments
 (0)