Skip to content

Commit dcee95c

Browse files
committed
types
1 parent 54abe42 commit dcee95c

File tree

3 files changed

+126
-27
lines changed

3 files changed

+126
-27
lines changed

src/htmlcmp/compare_output.py

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,33 @@ class Config:
1717
thread_local = threading.local()
1818

1919

20-
def parse_json(path):
20+
def parse_json(path: Path):
21+
if not isinstance(path, Path):
22+
raise TypeError(f"Expected Path, got {type(path)}")
23+
if not path.is_file():
24+
raise FileNotFoundError(f"File not found: {path}")
25+
2126
with open(path) as f:
2227
return json.load(f)
2328

2429

25-
def compare_json(a, b):
30+
def compare_json(a: Path, b: Path):
31+
if not isinstance(a, Path) or not isinstance(b, Path):
32+
raise TypeError("Both arguments must be of type Path")
33+
if not a.is_file() or not b.is_file():
34+
raise FileNotFoundError("Both arguments must be files")
35+
2636
json_a = json.dumps(parse_json(a), sort_keys=True)
2737
json_b = json.dumps(parse_json(b), sort_keys=True)
2838
return json_a == json_b
2939

3040

31-
def compare_html(a, b, browser=None, diff_output=None):
41+
def compare_html(a: Path, b: Path, browser=None, diff_output: Path = None):
42+
if not isinstance(a, Path) or not isinstance(b, Path):
43+
raise TypeError("Both arguments must be of type Path")
44+
if not a.is_file() or not b.is_file():
45+
raise FileNotFoundError("Both arguments must be files")
46+
3247
if browser is None:
3348
browser = get_browser()
3449
diff, (image_a, image_b) = html_render_diff(a, b, browser=browser)
@@ -41,7 +56,12 @@ def compare_html(a, b, browser=None, diff_output=None):
4156
return result
4257

4358

44-
def compare_files(a, b, **kwargs):
59+
def compare_files(a: Path, b: Path, **kwargs):
60+
if not isinstance(a, Path) or not isinstance(b, Path):
61+
raise TypeError("Both arguments must be of type Path")
62+
if not a.is_file() or not b.is_file():
63+
raise FileNotFoundError("Both arguments must be files")
64+
4565
if filecmp.cmp(a, b):
4666
return True
4767
if a.suffix == ".json":
@@ -50,15 +70,25 @@ def compare_files(a, b, **kwargs):
5070
return compare_html(a, b, **kwargs)
5171

5272

53-
def comparable_file(path):
73+
def comparable_file(path: Path):
74+
if not isinstance(path, Path):
75+
raise TypeError(f"Expected Path, got {type(path)}")
76+
if not path.is_file():
77+
raise FileNotFoundError(f"File not found: {path}")
78+
5479
if path.suffix == ".json":
5580
return True
5681
if path.suffix == ".html":
5782
return True
5883
return False
5984

6085

61-
def submit_compare_dirs(a, b, executor, diff_output=None, **kwargs):
86+
def submit_compare_dirs(a: Path, b: Path, executor, diff_output: Path = None, **kwargs):
87+
if not isinstance(a, Path) or not isinstance(b, Path):
88+
raise TypeError("Both arguments must be of type Path")
89+
if not a.is_dir() or not b.is_dir():
90+
raise FileNotFoundError("Both arguments must be directories")
91+
6292
results = {
6393
"common_dirs": [],
6494
"common_files": [],
@@ -123,7 +153,16 @@ def compare(path_a, path_b, diff_output):
123153
return results
124154

125155

126-
def print_results(results, a, b, level=0, prefix=""):
156+
def print_results(
157+
results: dict[str, list[Path]], a: Path, b: Path, level: int = 0, prefix: str = ""
158+
):
159+
if not isinstance(a, Path) or not isinstance(b, Path):
160+
raise TypeError("Both arguments must be of type Path")
161+
if not a.is_dir() or not b.is_dir():
162+
raise FileNotFoundError("Both arguments must be directories")
163+
if not isinstance(results, dict):
164+
raise TypeError("Results must be a dictionary")
165+
127166
prefix_file = prefix + "├── "
128167
if level == 0:
129168
print(f"compare dir {a} with {b}")

src/htmlcmp/compare_output_server.py

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,18 @@
88
from pathlib import Path
99
from concurrent.futures import ThreadPoolExecutor
1010

11-
from compare_output import comparable_file, compare_files
1211
from flask import Flask, send_from_directory, send_file
1312
import watchdog.observers
1413
import watchdog.events
1514

15+
from compare_output import comparable_file, compare_files
1616
from html_render_diff import get_browser, html_render_diff
1717

1818

1919
class Config:
20-
path_a = None
21-
path_b = None
22-
driver = None
20+
path_a: Path = None
21+
path_b: Path = None
22+
driver: str = None
2323
observer = None
2424
comparator = None
2525
browser = None
@@ -46,7 +46,12 @@ def dispatch(self, event):
4646
def start(self):
4747
self._observer.start()
4848

49-
def init_compare(a, b):
49+
def init_compare(a: Path, b: Path):
50+
if not isinstance(a, Path) or not isinstance(b, Path):
51+
raise TypeError("Paths must be of type Path")
52+
if not a.is_dir() or not b.is_dir():
53+
raise ValueError("Both paths must be directories")
54+
5055
common_path = a / Config.path_a
5156

5257
left = sorted(p.name for p in a.iterdir())
@@ -70,7 +75,7 @@ def join(self):
7075

7176

7277
class Comparator:
73-
def __init__(self, max_workers):
78+
def __init__(self, max_workers: int):
7479
def initializer():
7580
browser = getattr(Config.thread_local, "browser", None)
7681
if browser is None:
@@ -83,7 +88,10 @@ def initializer():
8388
self._result = {}
8489
self._future = {}
8590

86-
def submit(self, path):
91+
def submit(self, path: Path):
92+
if not isinstance(path, Path):
93+
raise TypeError("Path must be of type Path")
94+
8795
if path in self._future:
8896
try:
8997
self._future[path].cancel()
@@ -95,7 +103,14 @@ def submit(self, path):
95103
self._result[path] = "pending"
96104
self._future[path] = self._executor.submit(self.compare, path)
97105

98-
def compare(self, path):
106+
def compare(self, path: Path):
107+
if not isinstance(path, Path):
108+
raise TypeError("Path must be of type Path")
109+
if not path.is_file():
110+
raise ValueError("Path must be a file")
111+
if path not in self._future:
112+
raise RuntimeError("Path not submitted for comparison")
113+
99114
browser = getattr(Config.thread_local, "browser", None)
100115
result = compare_files(
101116
Config.path_a / path,
@@ -105,12 +120,18 @@ def compare(self, path):
105120
self._result[path] = "same" if result else "different"
106121
self._future.pop(path)
107122

108-
def result(self, path):
123+
def result(self, path: Path):
124+
if not isinstance(path, Path):
125+
raise TypeError("Path must be of type Path")
126+
109127
if path in self._result:
110128
return self._result[path]
111129
return "unknown"
112130

113-
def result_symbol(self, path):
131+
def result_symbol(self, path: Path):
132+
if not isinstance(path, Path):
133+
raise TypeError("Path must be of type Path")
134+
114135
result = self.result(path)
115136
if result == "pending":
116137
return "🔄"
@@ -120,7 +141,10 @@ def result_symbol(self, path):
120141
return "❌"
121142
return "⛔"
122143

123-
def result_css(self, path):
144+
def result_css(self, path: Path):
145+
if not isinstance(path, Path):
146+
raise TypeError("Path must be of type Path")
147+
124148
result = self.result(path)
125149
if result == "pending":
126150
return "color:blue;"
@@ -136,7 +160,12 @@ def result_css(self, path):
136160

137161
@app.route("/")
138162
def root():
139-
def print_tree(a, b):
163+
def print_tree(a: Path, b: Path):
164+
if not isinstance(a, Path) or not isinstance(b, Path):
165+
raise TypeError("Paths must be of type Path")
166+
if not a.is_dir() or not b.is_dir():
167+
raise ValueError("Both paths must be directories")
168+
140169
common_path = a / Config.path_a
141170

142171
left = sorted(p.name for p in a.iterdir())
@@ -209,7 +238,10 @@ def print_tree(a, b):
209238

210239

211240
@app.route("/compare/<path:path>")
212-
def compare(path):
241+
def compare(path: str):
242+
if not isinstance(path, str):
243+
raise TypeError("Path must be a string")
244+
213245
return f"""<!DOCTYPE html>
214246
<html>
215247
<head>
@@ -219,7 +251,7 @@ def compare(path):
219251
</head>
220252
<body style="display:flex;flex-flow:row;">
221253
<div style="display:flex;flex:1;flex-flow:column;margin:5px;">
222-
<a href="/file/a/{path}">{Config.path_a /path}</a>
254+
<a href="/file/a/{path}">{Config.path_a / path}</a>
223255
<iframe id="a" src="/file/a/{path}" title="a" frameborder="0" align="left" style="flex:1;"></iframe>
224256
</div>
225257
<div style="display:flex;flex:0 0 50px;flex-flow:column;">
@@ -246,7 +278,10 @@ def compare(path):
246278

247279

248280
@app.route("/image_diff/<path:path>")
249-
def image_diff(path):
281+
def image_diff(path: str):
282+
if not isinstance(path, str):
283+
raise TypeError("Path must be a string")
284+
250285
diff, _ = html_render_diff(
251286
Config.path_a / path,
252287
Config.path_b / path,
@@ -259,7 +294,12 @@ def image_diff(path):
259294

260295

261296
@app.route("/file/<variant>/<path:path>")
262-
def file(variant, path):
297+
def file(variant: str, path: str):
298+
if not isinstance(variant, str) or not isinstance(path, str):
299+
raise TypeError("Variant and path must be strings")
300+
if variant not in ["a", "b"]:
301+
raise ValueError("Variant must be 'a' or 'b'")
302+
263303
variant_root = Config.path_a if variant == "a" else Config.path_b
264304
return send_from_directory(variant_root, path)
265305

src/htmlcmp/html_render_diff.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,19 @@
1414
from selenium.webdriver.support.ui import WebDriverWait
1515

1616

17-
def to_url(path):
17+
def to_url(path: Path):
18+
if not isinstance(path, Path):
19+
raise TypeError(f"Expected Path, got {type(path)}")
20+
1821
if path.is_file():
1922
return path.resolve().as_uri()
2023
return path
2124

2225

23-
def screenshot(browser, url):
26+
def screenshot(browser, url: str):
27+
if not isinstance(url, str):
28+
raise TypeError(f"Expected str, got {type(url)}")
29+
2430
browser.get(url)
2531

2632
target_find_by = By.TAG_NAME
@@ -49,7 +55,16 @@ def screenshot(browser, url):
4955
return Image.open(io.BytesIO(png))
5056

5157

52-
def get_browser(driver="firefox", max_width=1000, max_height=10000):
58+
def get_browser(
59+
driver: str = "firefox", max_width: int = 1000, max_height: int = 10000
60+
):
61+
if not isinstance(driver, str):
62+
raise TypeError(f"Expected str, got {type(driver)}")
63+
if not isinstance(max_width, int) or not isinstance(max_height, int):
64+
raise TypeError(
65+
f"Expected int for max_width and max_height, got {type(max_width)} and {type(max_height)}"
66+
)
67+
5368
if driver == "phantomjs":
5469
browser = webdriver.PhantomJS()
5570
elif driver == "firefox":
@@ -64,7 +79,12 @@ def get_browser(driver="firefox", max_width=1000, max_height=10000):
6479
return browser
6580

6681

67-
def html_render_diff(a, b, browser):
82+
def html_render_diff(a: Path, b: Path, browser):
83+
if not isinstance(a, Path) or not isinstance(b, Path):
84+
raise TypeError("Both arguments must be of type Path")
85+
if not a.is_file() or not b.is_file():
86+
raise FileNotFoundError("Both arguments must be files")
87+
6888
image_a = screenshot(browser, to_url(a))
6989
image_b = screenshot(browser, to_url(b))
7090

0 commit comments

Comments
 (0)