Skip to content

Commit 6b9be7e

Browse files
committed
add type annotations to support static type checks
1 parent acca8b3 commit 6b9be7e

File tree

6 files changed

+113
-81
lines changed

6 files changed

+113
-81
lines changed

lib/fdiff/__main__.py

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@
33
import os
44
import sys
55
import argparse
6+
from typing import Iterator, Iterable, List, Optional, Text, Tuple
67

7-
from fdiff import __version__
8-
from fdiff.color import color_unified_diff_line
9-
from fdiff.diff import external_diff, u_diff
10-
from fdiff.textiter import head, tail
11-
from fdiff.utils import file_exists, get_tables_argument_list
8+
from . import __version__
9+
from .color import color_unified_diff_line
10+
from .diff import external_diff, u_diff
11+
from .textiter import head, tail
12+
from .utils import file_exists, get_tables_argument_list
1213

1314

14-
def main(): # pragma: no cover
15+
def main() -> None: # pragma: no cover
1516
# try/except block rationale:
1617
# handles "premature" socket closure exception that is
1718
# raised by Python when stdout is piped to tools like
@@ -27,13 +28,11 @@ def main(): # pragma: no cover
2728
sys.exit(0)
2829

2930

30-
def run(argv):
31+
def run(argv: List[Text]) -> None:
3132
# ------------------------------------------
3233
# argparse command line argument definitions
3334
# ------------------------------------------
34-
parser = argparse.ArgumentParser(
35-
description="An OpenType table diff tool for fonts."
36-
)
35+
parser = argparse.ArgumentParser(description="An OpenType table diff tool for fonts.")
3736
parser.add_argument("--version", action="version", version=f"fdiff v{__version__}")
3837
parser.add_argument(
3938
"-c",
@@ -66,7 +65,7 @@ def run(argv):
6665
parser.add_argument("PREFILE", help="Font file path/URL 1")
6766
parser.add_argument("POSTFILE", help="Font file path/URL 2")
6867

69-
args = parser.parse_args(argv)
68+
args: argparse.Namespace = parser.parse_args(argv)
7069

7170
# /////////////////////////////////////////////////////////
7271
#
@@ -110,12 +109,12 @@ def run(argv):
110109
# the command line arguments
111110
# set as a Python list if it was defined on the command line
112111
# or as None if it was not set on the command line
113-
include_list = get_tables_argument_list(args.include)
114-
exclude_list = get_tables_argument_list(args.exclude)
112+
include_list: Optional[List[Text]] = get_tables_argument_list(args.include)
113+
exclude_list: Optional[List[Text]] = get_tables_argument_list(args.exclude)
115114

116115
# flip logic of the command line flag for multi process
117116
# optimization use
118-
use_mp = not args.nomp
117+
use_mp: bool = not args.nomp
119118

120119
if args.external:
121120
# ------------------------------
@@ -138,7 +137,7 @@ def run(argv):
138137
sys.exit(1)
139138

140139
try:
141-
diff = external_diff(
140+
ext_diff: Iterable[Tuple[Text, Optional[int]]] = external_diff(
142141
args.external,
143142
args.PREFILE,
144143
args.POSTFILE,
@@ -148,7 +147,7 @@ def run(argv):
148147
)
149148

150149
# write stdout from external tool
151-
for line, exit_code in diff:
150+
for line, exit_code in ext_diff:
152151
# format with color if color flag is entered on command line
153152
if args.color:
154153
sys.stdout.write(color_unified_diff_line(line))
@@ -165,7 +164,7 @@ def run(argv):
165164
# ---------------
166165
# perform the unified diff analysis
167166
try:
168-
diff = u_diff(
167+
uni_diff: Iterator[Text] = u_diff(
169168
args.PREFILE,
170169
args.POSTFILE,
171170
context_lines=args.lines,
@@ -180,11 +179,11 @@ def run(argv):
180179
# re-define the line contents of the diff iterable
181180
# if head or tail is requested
182181
if args.head:
183-
iterable = head(diff, args.head)
182+
iterable = head(uni_diff, args.head)
184183
elif args.tail:
185-
iterable = tail(diff, args.tail)
184+
iterable = tail(uni_diff, args.tail)
186185
else:
187-
iterable = diff
186+
iterable = uni_diff
188187

189188
# print unified diff results to standard output stream
190189
has_diff = False

lib/fdiff/aio.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
#!/usr/bin/env python3
22

3-
import aiofiles
3+
import os
4+
from typing import AnyStr, Text, Union
45

6+
import aiofiles # type: ignore
57

6-
async def async_write_bin(path, binary):
8+
9+
async def async_write_bin(
10+
path: Union[AnyStr, "os.PathLike[Text]"], binary: bytes
11+
) -> None:
712
"""Asynchronous IO writes of binary data `binary` to disk on the file path `path`"""
8-
async with aiofiles.open(path, "wb") as f:
13+
async with aiofiles.open(path, "wb") as f: # type: ignore
914
await f.write(binary)

lib/fdiff/color.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#!/usr/bin/env python3
22

3-
ansicolors = {
3+
from typing import Dict, Text
4+
5+
ansicolors: Dict[Text, Text] = {
46
"BLACK": "\033[30m",
57
"RED": "\033[31m",
68
"GREEN": "\033[32m",
@@ -13,13 +15,13 @@
1315
"RESET": "\033[0m",
1416
}
1517

16-
green_start = ansicolors["GREEN"]
17-
red_start = ansicolors["RED"]
18-
cyan_start = ansicolors["CYAN"]
19-
reset = ansicolors["RESET"]
18+
green_start: Text = ansicolors["GREEN"]
19+
red_start: Text = ansicolors["RED"]
20+
cyan_start: Text = ansicolors["CYAN"]
21+
reset: Text = ansicolors["RESET"]
2022

2123

22-
def color_unified_diff_line(line):
24+
def color_unified_diff_line(line: Text) -> Text:
2325
"""Returns an ANSI escape code colored string with color based
2426
on the unified diff line type."""
2527
if line[0:2] == "+ ":

lib/fdiff/diff.py

Lines changed: 55 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,17 @@
77
import shlex
88
import subprocess
99
import tempfile
10+
from typing import Any, Iterator, Iterable, List, Optional, Text, Tuple
1011

11-
from fontTools.ttLib import TTFont
12+
from fontTools.ttLib import TTFont # type: ignore
1213

13-
from fdiff.exceptions import AIOError
14-
from fdiff.remote import (
14+
from .exceptions import AIOError
15+
from .remote import (
1516
_get_filepath_from_url,
1617
create_async_get_request_session_and_run,
1718
)
1819

19-
from fdiff.utils import get_file_modtime
20+
from .utils import get_file_modtime
2021

2122

2223
#
@@ -26,7 +27,7 @@
2627
#
2728

2829

29-
def _async_fetch_files(dirpath, urls):
30+
def _async_fetch_files(dirpath: Text, urls: List[Text]) -> None:
3031
loop = asyncio.get_event_loop()
3132
tasks = loop.run_until_complete(
3233
create_async_get_request_session_and_run(urls, dirpath)
@@ -45,8 +46,13 @@ def _async_fetch_files(dirpath, urls):
4546

4647

4748
def _get_fonts_and_save_xml(
48-
filepath_a, filepath_b, tmpdirpath, include_tables, exclude_tables, use_multiprocess
49-
):
49+
filepath_a: Text,
50+
filepath_b: Text,
51+
tmpdirpath: Text,
52+
include_tables: Optional[List[Text]],
53+
exclude_tables: Optional[List[Text]],
54+
use_multiprocess: bool,
55+
) -> Tuple[Text, Text, Text, Text, Text, Text]:
5056
post_pathname, postpath, pre_pathname, prepath = _get_pre_post_paths(
5157
filepath_a, filepath_b, tmpdirpath
5258
)
@@ -69,8 +75,12 @@ def _get_fonts_and_save_xml(
6975
return left_ttxpath, right_ttxpath, pre_pathname, prepath, post_pathname, postpath
7076

7177

72-
def _get_pre_post_paths(filepath_a, filepath_b, dirpath):
73-
urls = []
78+
def _get_pre_post_paths(
79+
filepath_a: Text,
80+
filepath_b: Text,
81+
dirpath: Text,
82+
) -> Tuple[Text, Text, Text, Text]:
83+
urls: List[Text] = []
7484
if filepath_a.startswith("http"):
7585
urls.append(filepath_a)
7686
prepath = _get_filepath_from_url(filepath_a, dirpath)
@@ -94,14 +104,14 @@ def _get_pre_post_paths(filepath_a, filepath_b, dirpath):
94104

95105

96106
def _mp_save_ttx_xml(
97-
tt_left,
98-
tt_right,
99-
left_ttxpath,
100-
right_ttxpath,
101-
exclude_tables,
102-
include_tables,
103-
use_multiprocess,
104-
):
107+
tt_left: Any,
108+
tt_right: Any,
109+
left_ttxpath: Text,
110+
right_ttxpath: Text,
111+
exclude_tables: Optional[List[Text]],
112+
include_tables: Optional[List[Text]],
113+
use_multiprocess: bool,
114+
) -> None:
105115
if use_multiprocess and cpu_count() > 1:
106116
# Use parallel fontTools.ttLib.TTFont.saveXML dump
107117
# by default on multi CPU systems. This is a performance
@@ -121,13 +131,20 @@ def _mp_save_ttx_xml(
121131
_ttfont_save_xml(tt_right, right_ttxpath, include_tables, exclude_tables)
122132

123133

124-
def _ttfont_save_xml(ttf, filepath, include_tables, exclude_tables):
134+
def _ttfont_save_xml(
135+
ttf: Any,
136+
filepath: Text,
137+
include_tables: Optional[List[Text]],
138+
exclude_tables: Optional[List[Text]],
139+
) -> bool:
125140
"""Writes TTX specification formatted XML to disk on filepath."""
126141
ttf.saveXML(filepath, tables=include_tables, skipTables=exclude_tables)
127142
return True
128143

129144

130-
def _validate_table_excludes(exclude_tables, tt_left, tt_right):
145+
def _validate_table_excludes(
146+
exclude_tables: Optional[List[Text]], tt_left: Any, tt_right: Any
147+
) -> None:
131148
# Validation: exclude_tables request should be for tables that are in one of
132149
# the two fonts. Mis-specified OT table definitions could otherwise result
133150
# in the presence of a table in the diff when the request was to exclude it.
@@ -140,7 +157,9 @@ def _validate_table_excludes(exclude_tables, tt_left, tt_right):
140157
)
141158

142159

143-
def _validate_table_includes(include_tables, tt_left, tt_right):
160+
def _validate_table_includes(
161+
include_tables: Optional[List[Text]], tt_left: Any, tt_right: Any
162+
) -> None:
144163
# Validation: include_tables request should be for tables that are in one of
145164
# the two fonts. This otherwise silently passes with exit status code 0 which
146165
# could lead to the interpretation of no diff between two files when the table
@@ -164,13 +183,13 @@ def _validate_table_includes(include_tables, tt_left, tt_right):
164183

165184

166185
def u_diff(
167-
filepath_a,
168-
filepath_b,
169-
context_lines=3,
170-
include_tables=None,
171-
exclude_tables=None,
172-
use_multiprocess=True,
173-
):
186+
filepath_a: Text,
187+
filepath_b: Text,
188+
context_lines: int = 3,
189+
include_tables: Optional[List[Text]] = None,
190+
exclude_tables: Optional[List[Text]] = None,
191+
use_multiprocess: bool = True,
192+
) -> Iterator[Text]:
174193
"""Performs a unified diff on a TTX serialized data format dump of font binary data using
175194
a modified version of the Python standard libary difflib module.
176195
@@ -230,13 +249,13 @@ def u_diff(
230249

231250

232251
def external_diff(
233-
command,
234-
filepath_a,
235-
filepath_b,
236-
include_tables=None,
237-
exclude_tables=None,
238-
use_multiprocess=True,
239-
):
252+
command: Text,
253+
filepath_a: Text,
254+
filepath_b: Text,
255+
include_tables: Optional[List[Text]] = None,
256+
exclude_tables: Optional[List[Text]] = None,
257+
use_multiprocess: bool = True,
258+
) -> Iterable[Tuple[Text, Optional[int]]]:
240259
"""Performs a unified diff on a TTX serialized data format dump of font binary data using
241260
an external diff executable that is requested by the caller via `command`
242261
@@ -285,10 +304,10 @@ def external_diff(
285304
)
286305

287306
while True:
288-
output = process.stdout.readline()
307+
output = process.stdout.readline() # type: ignore
289308
exit_status = process.poll()
290309
if len(output) == 0 and exit_status is not None:
291-
err = process.stderr.read()
310+
err = process.stderr.read() # type: ignore
292311
if err:
293312
raise IOError(err)
294313
yield output, exit_status

lib/fdiff/remote.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,31 @@
33
import os.path
44
import urllib.parse
55

6-
from collections import namedtuple
6+
# from collections import namedtuple
7+
from typing import Any, AnyStr, List, NamedTuple, Optional, Text, Tuple
78

8-
import aiohttp
9+
import aiohttp # type: ignore
910
import asyncio
1011

11-
from fdiff.aio import async_write_bin
12+
from .aio import async_write_bin
1213

1314

14-
def _get_filepath_from_url(url, dirpath):
15+
class FWRes(NamedTuple):
16+
url: Text
17+
filepath: Optional[Text]
18+
http_status: int
19+
write_success: bool
20+
21+
22+
def _get_filepath_from_url(url: Text, dirpath: Text) -> Text:
1523
"""Returns filepath from base file name in URL and directory path."""
1624
url_path_list = urllib.parse.urlsplit(url)
1725
abs_filepath = url_path_list.path
1826
basepath = os.path.split(abs_filepath)[-1]
1927
return os.path.join(dirpath, basepath)
2028

2129

22-
async def async_fetch(session, url):
30+
async def async_fetch(session: Any, url: AnyStr) -> Tuple[AnyStr, Any, Any]:
2331
"""Asynchronous I/O HTTP GET request with a ClientSession instantiated
2432
from the aiohttp library."""
2533
async with session.get(url) as response:
@@ -31,15 +39,12 @@ async def async_fetch(session, url):
3139
return url, status, binary
3240

3341

34-
async def async_fetch_and_write(session, url, dirpath):
42+
async def async_fetch_and_write(session: Any, url: Text, dirpath: Text) -> FWRes:
3543
"""Asynchronous I/O HTTP GET request with a ClientSession instantiated
3644
from the aiohttp library, followed by an asynchronous I/O file write of
3745
the binary to disk with the aiofiles library.
3846
3947
:returns `FWRes` namedtuple with url, filepath, http_status, write_success fields"""
40-
FWResponse = namedtuple(
41-
"FWRes", ["url", "filepath", "http_status", "write_success"]
42-
)
4348
url, status, binary = await async_fetch(session, url)
4449
if status != 200:
4550
filepath = None
@@ -49,12 +54,14 @@ async def async_fetch_and_write(session, url, dirpath):
4954
await async_write_bin(filepath, binary)
5055
write_success = True
5156

52-
return FWResponse(
57+
return FWRes(
5358
url=url, filepath=filepath, http_status=status, write_success=write_success
5459
)
5560

5661

57-
async def create_async_get_request_session_and_run(urls, dirpath):
62+
async def create_async_get_request_session_and_run(
63+
urls: List[Text], dirpath: Text
64+
) -> List[Any]:
5865
"""Creates an aiohttp library ClientSession and performs asynchronous GET requests +
5966
binary file writes with the binary response from the GET request.
6067

0 commit comments

Comments
 (0)