3
3
import atexit
4
4
import base64
5
5
import os .path
6
+ import platform
7
+ import re
8
+ import subprocess
6
9
import sys
10
+ import zipfile
7
11
from datetime import datetime
8
12
from pathlib import Path
9
- from shutil import copy
10
13
from time import sleep
11
14
from typing import Dict , List , Optional
12
15
15
18
from selenium import webdriver
16
19
from selenium .webdriver .chrome .options import Options
17
20
from selenium .webdriver .chrome .service import Service
18
- from webdriver_manager .chrome import ChromeDriverManager
19
- from webdriver_manager .core .download_manager import WDMDownloadManager
20
- from webdriver_manager .core .driver import Driver
21
- from webdriver_manager .core .driver_cache import DriverCacheManager
22
- from webdriver_manager .core .file_manager import FileManager
23
- from webdriver_manager .core .http import HttpClient
24
- from webdriver_manager .core .os_manager import OperationSystemManager
21
+ from webdriver_manager .core .os_manager import ChromeType , OperationSystemManager
25
22
26
- __version__ = "0.0.8 "
23
+ __version__ = "0.0.12 "
27
24
28
25
PATH_TO_HTML2PDF_JS = os .path .join (
29
26
os .path .dirname (os .path .join (__file__ )), "html2pdf_js" , "html2pdf.min.js"
39
36
sys .stdout = open (sys .stdout .fileno (), mode = "w" , encoding = "utf8" , closefd = False )
40
37
41
38
42
- class HTML2Print_HTTPClient (HttpClient ):
43
- def get (self , url , params = None , ** kwargs ) -> Response :
44
- last_error : Optional [Exception ] = None
45
- for attempt in range (1 , 3 ):
46
- print ( # noqa: T201
47
- f"html2print: sending GET request attempt { attempt } : { url } "
48
- )
49
- try :
50
- return requests .get (url , params , timeout = (5 , 5 ), ** kwargs )
51
- except requests .exceptions .ConnectTimeout as connect_timeout_ :
52
- last_error = connect_timeout_
53
- except requests .exceptions .ReadTimeout as read_timeout_ :
54
- last_error = read_timeout_
55
- except Exception as exception_ :
56
- raise AssertionError (
57
- "html2print: unknown exception" , exception_
58
- ) from None
39
+ class ChromeDriverManager :
40
+ def get_chrome_driver (self , path_to_cache_dir : str ):
41
+ chrome_version = self .get_chrome_version ()
42
+ chrome_major_version = chrome_version .split ("." )[0 ]
43
+
59
44
print ( # noqa: T201
60
- f"html2print: "
61
- f"failed to get response for URL: { url } with error: { last_error } "
45
+ f"html2print: Installed Chrome version: { chrome_version } "
62
46
)
63
47
48
+ system_map = {
49
+ "Windows" : "win32" ,
50
+ "Darwin" : "mac-arm64"
51
+ if platform .machine () == "arm64"
52
+ else "mac-x64" ,
53
+ "Linux" : "linux64" ,
54
+ }
55
+ os_type = system_map [platform .system ()]
56
+ is_windows = platform .system () == "Windows"
64
57
65
- class HTML2Print_CacheManager (DriverCacheManager ):
66
- def __init__ (self , file_manager : FileManager , path_to_cache_dir : str ):
67
- super ().__init__ (file_manager = file_manager )
68
- self .path_to_cache_dir : str = path_to_cache_dir
69
-
70
- def find_driver (self , driver : Driver ):
71
- path_to_cached_chrome_driver_dir = os .path .join (
72
- self .path_to_cache_dir , "chromedriver"
73
- )
74
-
75
- os_type = self .get_os_type ()
76
- browser_type = driver .get_browser_type ()
77
- browser_version = self ._os_system_manager .get_browser_version_from_os (
78
- browser_type
58
+ print ( # noqa: T201
59
+ f"html2print: OS system: { platform .system ()} , OS type: { os_type } ."
79
60
)
80
- assert browser_version is not None , browser_version
81
61
82
62
path_to_cached_chrome_driver_dir = os .path .join (
83
- path_to_cached_chrome_driver_dir , browser_version , os_type
63
+ path_to_cache_dir , chrome_major_version
84
64
)
85
65
path_to_cached_chrome_driver = os .path .join (
86
- path_to_cached_chrome_driver_dir , "chromedriver"
66
+ path_to_cached_chrome_driver_dir ,
67
+ f"chromedriver-{ os_type } " ,
68
+ "chromedriver" ,
87
69
)
70
+ if is_windows :
71
+ path_to_cached_chrome_driver += ".exe"
72
+
88
73
if os .path .isfile (path_to_cached_chrome_driver ):
89
74
print ( # noqa: T201
90
75
f"html2print: ChromeDriver exists in the local cache: "
@@ -95,25 +80,144 @@ def find_driver(self, driver: Driver):
95
80
f"html2print: ChromeDriver does not exist in the local cache: "
96
81
f"{ path_to_cached_chrome_driver } "
97
82
)
98
- path_to_downloaded_chrome_driver = super ().find_driver (driver )
99
- if path_to_downloaded_chrome_driver is None :
100
- print ( # noqa: T201
101
- f"html2print: could not get a downloaded ChromeDriver: "
102
- f"{ path_to_cached_chrome_driver } "
83
+
84
+ path_to_downloaded_chrome_driver = self ._download_chromedriver (
85
+ chrome_major_version ,
86
+ os_type ,
87
+ path_to_cached_chrome_driver_dir ,
88
+ path_to_cached_chrome_driver ,
89
+ )
90
+ assert os .path .isfile (path_to_downloaded_chrome_driver )
91
+ os .chmod (path_to_downloaded_chrome_driver , 0o755 )
92
+
93
+ return path_to_downloaded_chrome_driver
94
+
95
+ @staticmethod
96
+ def _download_chromedriver (
97
+ chrome_major_version ,
98
+ os_type : str ,
99
+ path_to_driver_cache_dir ,
100
+ path_to_cached_chrome_driver ,
101
+ ):
102
+ url = "https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json"
103
+ response = ChromeDriverManager .send_http_get_request (url ).json ()
104
+
105
+ matching_versions = [
106
+ item
107
+ for item in response ["versions" ]
108
+ if item ["version" ].startswith (chrome_major_version )
109
+ ]
110
+
111
+ if not matching_versions :
112
+ raise Exception (
113
+ f"No compatible ChromeDriver found for Chrome version { chrome_major_version } "
114
+ )
115
+
116
+ latest_version = matching_versions [- 1 ]
117
+
118
+ driver_url : str
119
+ chrome_downloadable_versions = latest_version ["downloads" ][
120
+ "chromedriver"
121
+ ]
122
+ for chrome_downloadable_version_ in chrome_downloadable_versions :
123
+ if chrome_downloadable_version_ ["platform" ] == os_type :
124
+ driver_url = chrome_downloadable_version_ ["url" ]
125
+ break
126
+ else :
127
+ raise RuntimeError (
128
+ f"Could not find a downloadable URL from downloadable versions: { chrome_downloadable_versions } "
103
129
)
104
- return None
105
130
106
131
print ( # noqa: T201
107
- f"html2print: saving chromedriver to StrictDoc's local cache: "
108
- f"{ path_to_downloaded_chrome_driver } -> { path_to_cached_chrome_driver } "
132
+ f"html2print: downloading ChromeDriver from: { driver_url } "
109
133
)
110
- Path (path_to_cached_chrome_driver_dir ).mkdir (
111
- parents = True , exist_ok = True
134
+ response = ChromeDriverManager .send_http_get_request (driver_url )
135
+
136
+ Path (path_to_driver_cache_dir ).mkdir (parents = True , exist_ok = True )
137
+ zip_path = os .path .join (path_to_driver_cache_dir , "chromedriver.zip" )
138
+ print ( # noqa: T201
139
+ f"html2print: saving downloaded ChromeDriver to path: { zip_path } "
112
140
)
113
- copy (path_to_downloaded_chrome_driver , path_to_cached_chrome_driver )
141
+ with open (zip_path , "wb" ) as file :
142
+ file .write (response .content )
114
143
144
+ with zipfile .ZipFile (zip_path , "r" ) as zip_ref :
145
+ zip_ref .extractall (path_to_driver_cache_dir )
146
+
147
+ print ( # noqa: T201
148
+ f"html2print: ChromeDriver downloaded to: { path_to_cached_chrome_driver } "
149
+ )
115
150
return path_to_cached_chrome_driver
116
151
152
+ @staticmethod
153
+ def send_http_get_request (url , params = None , ** kwargs ) -> Response :
154
+ last_error : Optional [Exception ] = None
155
+ for attempt in range (1 , 4 ):
156
+ print ( # noqa: T201
157
+ f"html2print: sending GET request attempt { attempt } : { url } "
158
+ )
159
+ try :
160
+ return requests .get (url , params , timeout = (5 , 5 ), ** kwargs )
161
+ except requests .exceptions .ConnectTimeout as connect_timeout_ :
162
+ last_error = connect_timeout_
163
+ except requests .exceptions .ReadTimeout as read_timeout_ :
164
+ last_error = read_timeout_
165
+ except Exception as exception_ :
166
+ raise AssertionError (
167
+ "html2print: unknown exception" , exception_
168
+ ) from None
169
+ print ( # noqa: T201
170
+ f"html2print: "
171
+ f"failed to get response for URL: { url } with error: { last_error } "
172
+ )
173
+
174
+ @staticmethod
175
+ def get_chrome_version ():
176
+ # Special case: GitHub Actions macOS CI machines have both
177
+ # Google Chrome for Testing and normal Google Chrome installed, and
178
+ # sometimes their versions are of different major version families.
179
+ # The solution is to check if the Google Chrome for Testing is available,
180
+ # and use its version instead of the normal one.
181
+ if platform .system () == "Darwin" :
182
+ chrome_path = "/Applications/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing"
183
+ try :
184
+ print ( # noqa: T201
185
+ "html2print: "
186
+ "checking if there is Google Chrome for Testing instead of "
187
+ "a normal Chrome available."
188
+ )
189
+
190
+ version_output = subprocess .run (
191
+ [chrome_path , "--version" ],
192
+ capture_output = True ,
193
+ text = True ,
194
+ check = True ,
195
+ )
196
+ chrome_version = version_output .stdout .strip ()
197
+ match = re .search (r"\d+(\.\d+)+" , chrome_version )
198
+ if not match :
199
+ raise RuntimeError (
200
+ "Cannot extract the version part using regex."
201
+ )
202
+
203
+ chrome_version = match .group (0 )
204
+
205
+ print ( # noqa: T201
206
+ f"html2print: Google Chrome for Testing Version: { chrome_version } "
207
+ )
208
+
209
+ return chrome_version
210
+ except FileNotFoundError :
211
+ print ("html2print: Chrome for Testing not available." ) # noqa: T201
212
+ except Exception as e :
213
+ print ( # noqa: T201
214
+ f"html2print: Error getting Google Chrome for Testing version: { e } "
215
+ )
216
+
217
+ os_manager = OperationSystemManager (os_type = None )
218
+ version = os_manager .get_browser_version_from_os (ChromeType .GOOGLE )
219
+ return version
220
+
117
221
118
222
def get_inches_from_millimeters (mm : float ) -> float :
119
223
return mm / 25.4
@@ -190,23 +294,12 @@ class Done(Exception):
190
294
return data
191
295
192
296
193
- def get_chrome_driver (path_to_cache_dir : str ) -> str :
194
- cache_manager = HTML2Print_CacheManager (
195
- file_manager = FileManager (os_system_manager = OperationSystemManager ()),
196
- path_to_cache_dir = path_to_cache_dir ,
197
- )
198
-
199
- http_client = HTML2Print_HTTPClient ()
200
- download_manager = WDMDownloadManager (http_client )
201
- path_to_chrome = ChromeDriverManager (
202
- download_manager = download_manager , cache_manager = cache_manager
203
- ).install ()
204
- return path_to_chrome
205
-
206
-
207
297
def create_webdriver (chromedriver : Optional [str ], path_to_cache_dir : str ):
298
+ print ("html2print: creating ChromeDriver service." , flush = True ) # noqa: T201
208
299
if chromedriver is None :
209
- path_to_chrome = get_chrome_driver (path_to_cache_dir )
300
+ path_to_chrome = ChromeDriverManager ().get_chrome_driver (
301
+ path_to_cache_dir
302
+ )
210
303
else :
211
304
path_to_chrome = chromedriver
212
305
print (f"html2print: ChromeDriver available at path: { path_to_chrome } " ) # noqa: T201
@@ -254,6 +347,8 @@ def main():
254
347
command_subparsers = parser .add_subparsers (title = "command" , dest = "command" )
255
348
command_subparsers .required = True
256
349
350
+ print (f"html2print: version { __version__ } " ) # noqa: T201
351
+
257
352
#
258
353
# Get driver command.
259
354
#
@@ -295,22 +390,20 @@ def main():
295
390
path_to_cache_dir : str
296
391
if args .command == "get_driver" :
297
392
path_to_cache_dir = (
298
- args .cache_dir
299
- if args .cache_dir is not None
300
- else (DEFAULT_CACHE_DIR )
393
+ args .cache_dir if args .cache_dir is not None else DEFAULT_CACHE_DIR
301
394
)
302
395
303
- path_to_chrome = get_chrome_driver (path_to_cache_dir )
396
+ path_to_chrome = ChromeDriverManager ().get_chrome_driver (
397
+ path_to_cache_dir
398
+ )
304
399
print (f"html2print: ChromeDriver available at path: { path_to_chrome } " ) # noqa: T201
305
400
sys .exit (0 )
306
401
307
402
elif args .command == "print" :
308
403
paths : List [str ] = args .paths
309
404
310
405
path_to_cache_dir = (
311
- args .cache_dir
312
- if args .cache_dir is not None
313
- else (DEFAULT_CACHE_DIR )
406
+ args .cache_dir if args .cache_dir is not None else DEFAULT_CACHE_DIR
314
407
)
315
408
driver = create_webdriver (args .chromedriver , path_to_cache_dir )
316
409
0 commit comments