webdriver-manager 4.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- webdriver_manager/__init__.py +1 -0
- webdriver_manager/chrome.py +55 -0
- webdriver_manager/core/__init__.py +0 -0
- webdriver_manager/core/archive.py +23 -0
- webdriver_manager/core/config.py +41 -0
- webdriver_manager/core/constants.py +25 -0
- webdriver_manager/core/download_manager.py +42 -0
- webdriver_manager/core/driver.py +77 -0
- webdriver_manager/core/driver_cache.py +212 -0
- webdriver_manager/core/file_manager.py +117 -0
- webdriver_manager/core/http.py +38 -0
- webdriver_manager/core/logger.py +31 -0
- webdriver_manager/core/manager.py +78 -0
- webdriver_manager/core/os_manager.py +167 -0
- webdriver_manager/core/utils.py +66 -0
- webdriver_manager/drivers/__init__.py +0 -0
- webdriver_manager/drivers/chrome.py +233 -0
- webdriver_manager/drivers/edge.py +55 -0
- webdriver_manager/drivers/firefox.py +57 -0
- webdriver_manager/drivers/ie.py +90 -0
- webdriver_manager/drivers/opera.py +59 -0
- webdriver_manager/firefox.py +66 -0
- webdriver_manager/microsoft.py +86 -0
- webdriver_manager/opera.py +71 -0
- webdriver_manager/py.typed +0 -0
- webdriver_manager-4.1.1.dist-info/METADATA +588 -0
- webdriver_manager-4.1.1.dist-info/RECORD +30 -0
- webdriver_manager-4.1.1.dist-info/WHEEL +5 -0
- webdriver_manager-4.1.1.dist-info/licenses/LICENSE +201 -0
- webdriver_manager-4.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "4.1.1"
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from webdriver_manager.core.download_manager import DownloadManager
|
|
5
|
+
from webdriver_manager.core.driver_cache import DriverCacheManager
|
|
6
|
+
from webdriver_manager.core.manager import DriverManager
|
|
7
|
+
from webdriver_manager.core.os_manager import OperationSystemManager, ChromeType
|
|
8
|
+
from webdriver_manager.drivers.chrome import ChromeDriver
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ChromeDriverManager(DriverManager):
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
driver_version: Optional[str] = None,
|
|
15
|
+
name: str = "chromedriver",
|
|
16
|
+
url: str = "https://storage.googleapis.com/chrome-for-testing-public/",
|
|
17
|
+
latest_release_url: str = "https://googlechromelabs.github.io/chrome-for-testing/LATEST_RELEASE_STABLE",
|
|
18
|
+
chrome_type: str = ChromeType.GOOGLE,
|
|
19
|
+
download_manager: Optional[DownloadManager] = None,
|
|
20
|
+
cache_manager: Optional[DriverCacheManager] = None,
|
|
21
|
+
os_system_manager: Optional[OperationSystemManager] = None
|
|
22
|
+
):
|
|
23
|
+
super().__init__(
|
|
24
|
+
download_manager=download_manager,
|
|
25
|
+
cache_manager=cache_manager,
|
|
26
|
+
os_system_manager=os_system_manager
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
self.driver = ChromeDriver(
|
|
30
|
+
name=name,
|
|
31
|
+
driver_version=driver_version,
|
|
32
|
+
url=url,
|
|
33
|
+
latest_release_url=latest_release_url,
|
|
34
|
+
chrome_type=chrome_type,
|
|
35
|
+
http_client=self.http_client,
|
|
36
|
+
os_system_manager=os_system_manager
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def install(self) -> str:
|
|
40
|
+
driver_path = self._get_driver_binary_path(self.driver)
|
|
41
|
+
os.chmod(driver_path, 0o755)
|
|
42
|
+
return driver_path
|
|
43
|
+
|
|
44
|
+
def get_os_type(self):
|
|
45
|
+
os_type = super().get_os_type()
|
|
46
|
+
if "win" in os_type:
|
|
47
|
+
return "win64" if self._os_system_manager.get_os_architecture() == 64 else "win32"
|
|
48
|
+
|
|
49
|
+
if not self._os_system_manager.is_mac_os(os_type):
|
|
50
|
+
return os_type
|
|
51
|
+
|
|
52
|
+
if self._os_system_manager.is_arch():
|
|
53
|
+
return "mac_arm64"
|
|
54
|
+
|
|
55
|
+
return os_type
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import zipfile
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class LinuxZipFileWithPermissions(zipfile.ZipFile):
|
|
6
|
+
"""Class for extract files in linux with right permissions"""
|
|
7
|
+
|
|
8
|
+
def extract(self, member, path=None, pwd=None):
|
|
9
|
+
if not isinstance(member, zipfile.ZipInfo):
|
|
10
|
+
member = self.getinfo(member)
|
|
11
|
+
|
|
12
|
+
if path is None:
|
|
13
|
+
path = os.getcwd()
|
|
14
|
+
|
|
15
|
+
ret_val = self._extract_member(member, path, pwd) # noqa
|
|
16
|
+
attr = member.external_attr >> 16
|
|
17
|
+
os.chmod(ret_val, attr)
|
|
18
|
+
return ret_val
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Archive(object):
|
|
22
|
+
def __init__(self, path: str):
|
|
23
|
+
self.file_path = path
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from dotenv import load_dotenv
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def str2bool(value):
|
|
6
|
+
return value.lower() in ['true', '1']
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
load_dotenv()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def ssl_verify():
|
|
13
|
+
return str2bool(os.getenv("WDM_SSL_VERIFY", "true"))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def gh_token():
|
|
17
|
+
return os.getenv("GH_TOKEN", None)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def wdm_local():
|
|
21
|
+
return str2bool(os.getenv("WDM_LOCAL", "false"))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def wdm_log_level():
|
|
25
|
+
default_level = 20
|
|
26
|
+
try:
|
|
27
|
+
return int(os.getenv("WDM_LOG", default_level))
|
|
28
|
+
except Exception:
|
|
29
|
+
return default_level
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def wdm_progress_bar():
|
|
33
|
+
default_level = 1
|
|
34
|
+
try:
|
|
35
|
+
return int(os.getenv("WDM_PROGRESS_BAR", default_level))
|
|
36
|
+
except Exception:
|
|
37
|
+
return default_level
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_xdist_worker_id():
|
|
41
|
+
return os.getenv("PYTEST_XDIST_WORKER", '')
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import tempfile
|
|
4
|
+
|
|
5
|
+
ROOT_FOLDER_NAME = ".wdm"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_default_project_root_cache_path():
|
|
9
|
+
base_path = sys.path[0]
|
|
10
|
+
if getattr(sys, "frozen", False) or base_path.endswith(".zip"):
|
|
11
|
+
base_path = os.getcwd()
|
|
12
|
+
return os.path.join(base_path, ROOT_FOLDER_NAME)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
DEFAULT_PROJECT_ROOT_CACHE_PATH = get_default_project_root_cache_path()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_default_user_home_cache_path():
|
|
19
|
+
home = os.path.expanduser("~")
|
|
20
|
+
if not home or home in (os.path.sep, "/") or home.startswith("~"):
|
|
21
|
+
home = tempfile.gettempdir()
|
|
22
|
+
return os.path.join(home, ROOT_FOLDER_NAME)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
DEFAULT_USER_HOME_CACHE_PATH = get_default_user_home_cache_path()
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from abc import ABC
|
|
3
|
+
|
|
4
|
+
from webdriver_manager.core.file_manager import File
|
|
5
|
+
from webdriver_manager.core.http import WDMHttpClient
|
|
6
|
+
from webdriver_manager.core.logger import log
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DownloadManager(ABC):
|
|
10
|
+
def __init__(self, http_client):
|
|
11
|
+
self._http_client = http_client
|
|
12
|
+
|
|
13
|
+
def download_file(self, url: str) -> File:
|
|
14
|
+
raise NotImplementedError
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def http_client(self):
|
|
18
|
+
return self._http_client
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class WDMDownloadManager(DownloadManager):
|
|
22
|
+
def __init__(self, http_client=None):
|
|
23
|
+
if http_client is None:
|
|
24
|
+
http_client = WDMHttpClient()
|
|
25
|
+
super().__init__(http_client)
|
|
26
|
+
|
|
27
|
+
def download_file(self, url: str) -> File:
|
|
28
|
+
log(f"About to download new driver from {url}")
|
|
29
|
+
response = self._http_client.get(url)
|
|
30
|
+
log(f"Driver downloading response is {response.status_code}")
|
|
31
|
+
file_name = self.extract_filename_from_url(url)
|
|
32
|
+
return File(response, file_name)
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def extract_filename_from_url(url):
|
|
36
|
+
# Split the URL by '/'
|
|
37
|
+
url_parts = url.split('/')
|
|
38
|
+
# Get the last part of the URL, which should be the filename
|
|
39
|
+
filename = url_parts[-1]
|
|
40
|
+
# Decode the URL-encoded filename
|
|
41
|
+
filename = os.path.basename(filename)
|
|
42
|
+
return filename
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from webdriver_manager.core.logger import log
|
|
2
|
+
from webdriver_manager.core.config import gh_token
|
|
3
|
+
from webdriver_manager.core.os_manager import OperationSystemManager
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Driver(object):
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
name,
|
|
10
|
+
driver_version_to_download,
|
|
11
|
+
url,
|
|
12
|
+
latest_release_url,
|
|
13
|
+
http_client,
|
|
14
|
+
os_system_manager):
|
|
15
|
+
self._name = name
|
|
16
|
+
self._url = url
|
|
17
|
+
self._latest_release_url = latest_release_url
|
|
18
|
+
self._http_client = http_client
|
|
19
|
+
self._browser_version = None
|
|
20
|
+
self._driver_version_to_download = driver_version_to_download
|
|
21
|
+
self._os_system_manager = os_system_manager
|
|
22
|
+
if not self._os_system_manager:
|
|
23
|
+
self._os_system_manager = OperationSystemManager()
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def auth_header(self):
|
|
27
|
+
token = gh_token()
|
|
28
|
+
if token:
|
|
29
|
+
log("GH_TOKEN will be used to perform requests")
|
|
30
|
+
return {"Authorization": f"token {token}"}
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
def get_name(self):
|
|
34
|
+
return self._name
|
|
35
|
+
|
|
36
|
+
def get_driver_download_url(self, os_type):
|
|
37
|
+
return f"{self._url}/{self.get_driver_version_to_download()}/{self._name}_{os_type}.zip"
|
|
38
|
+
|
|
39
|
+
def get_driver_version_to_download(self):
|
|
40
|
+
"""
|
|
41
|
+
Downloads version from parameter if version not None or "latest".
|
|
42
|
+
Downloads latest, if version is "latest" or browser could not been determined.
|
|
43
|
+
Downloads determined browser version driver in all other ways as a bonus fallback for lazy users.
|
|
44
|
+
"""
|
|
45
|
+
if self._driver_version_to_download:
|
|
46
|
+
return self._driver_version_to_download
|
|
47
|
+
|
|
48
|
+
return self.get_latest_release_version()
|
|
49
|
+
|
|
50
|
+
def get_latest_release_version(self):
|
|
51
|
+
# type: () -> str
|
|
52
|
+
raise NotImplementedError("Please implement this method")
|
|
53
|
+
|
|
54
|
+
def get_browser_version_from_os(self):
|
|
55
|
+
"""
|
|
56
|
+
Use-cases:
|
|
57
|
+
- for key in metadata;
|
|
58
|
+
- for printing nice logs;
|
|
59
|
+
- for fallback if version was not set at all.
|
|
60
|
+
Note: the fallback may have collisions in user cases when previous browser was not uninstalled properly.
|
|
61
|
+
"""
|
|
62
|
+
if self._browser_version is None:
|
|
63
|
+
self._browser_version = self._os_system_manager.get_browser_version_from_os(self.get_browser_type())
|
|
64
|
+
return self._browser_version
|
|
65
|
+
|
|
66
|
+
def get_browser_type(self):
|
|
67
|
+
raise NotImplementedError("Please implement this method")
|
|
68
|
+
|
|
69
|
+
def get_binary_name(self, os_type):
|
|
70
|
+
driver_name = self.get_name()
|
|
71
|
+
driver_binary_name = (
|
|
72
|
+
"msedgedriver" if driver_name == "edgedriver" else driver_name
|
|
73
|
+
)
|
|
74
|
+
driver_binary_name = (
|
|
75
|
+
f"{driver_binary_name}.exe" if "win" in os_type else driver_binary_name
|
|
76
|
+
)
|
|
77
|
+
return driver_binary_name
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from webdriver_manager.core.config import wdm_local, get_xdist_worker_id
|
|
6
|
+
from webdriver_manager.core.constants import (
|
|
7
|
+
DEFAULT_PROJECT_ROOT_CACHE_PATH,
|
|
8
|
+
DEFAULT_USER_HOME_CACHE_PATH, ROOT_FOLDER_NAME,
|
|
9
|
+
)
|
|
10
|
+
from webdriver_manager.core.driver import Driver
|
|
11
|
+
from webdriver_manager.core.file_manager import FileManager, File
|
|
12
|
+
from webdriver_manager.core.logger import log
|
|
13
|
+
from webdriver_manager.core.os_manager import OperationSystemManager
|
|
14
|
+
from webdriver_manager.core.utils import get_date_diff
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DriverCacheManager(object):
|
|
18
|
+
def __init__(self, root_dir=None, valid_range=1, file_manager=None, os_system_manager=None):
|
|
19
|
+
self._root_dir = DEFAULT_USER_HOME_CACHE_PATH
|
|
20
|
+
is_wdm_local = wdm_local()
|
|
21
|
+
xdist_worker_id = get_xdist_worker_id()
|
|
22
|
+
if xdist_worker_id:
|
|
23
|
+
log(f"xdist worker is: {xdist_worker_id}")
|
|
24
|
+
self._root_dir = os.path.join(self._root_dir, xdist_worker_id)
|
|
25
|
+
|
|
26
|
+
if root_dir:
|
|
27
|
+
self._root_dir = os.path.join(root_dir, ROOT_FOLDER_NAME, xdist_worker_id)
|
|
28
|
+
|
|
29
|
+
if is_wdm_local:
|
|
30
|
+
self._root_dir = os.path.join(DEFAULT_PROJECT_ROOT_CACHE_PATH, xdist_worker_id)
|
|
31
|
+
|
|
32
|
+
self._drivers_root = "drivers"
|
|
33
|
+
self._drivers_json_path = os.path.join(self._root_dir, "drivers.json")
|
|
34
|
+
self._date_format = "%d/%m/%Y"
|
|
35
|
+
self._drivers_directory = os.path.join(self._root_dir, self._drivers_root)
|
|
36
|
+
self._cache_valid_days_range = valid_range
|
|
37
|
+
self._cache_key_driver_version = None
|
|
38
|
+
self._metadata_key = None
|
|
39
|
+
self._driver_binary_path = None
|
|
40
|
+
self._os_system_manager = os_system_manager
|
|
41
|
+
if not os_system_manager:
|
|
42
|
+
self._os_system_manager = OperationSystemManager()
|
|
43
|
+
self._file_manager = file_manager
|
|
44
|
+
if not self._file_manager:
|
|
45
|
+
self._file_manager = FileManager(self._os_system_manager)
|
|
46
|
+
|
|
47
|
+
def get_driver_lock_path(self, driver_name: str, os_type: str) -> str:
|
|
48
|
+
os.makedirs(self._root_dir, exist_ok=True)
|
|
49
|
+
return os.path.join(self._root_dir, f".wdm-lock-{driver_name}-{os_type}")
|
|
50
|
+
|
|
51
|
+
def save_archive_file(self, file: File, path):
|
|
52
|
+
return self._file_manager.save_archive_file(file, path)
|
|
53
|
+
|
|
54
|
+
def unpack_archive(self, archive, path):
|
|
55
|
+
return self._file_manager.unpack_archive(archive, path)
|
|
56
|
+
|
|
57
|
+
def save_file_to_cache(self, driver: Driver, file: File):
|
|
58
|
+
path = self.__get_path(driver)
|
|
59
|
+
archive = self.save_archive_file(file, path)
|
|
60
|
+
files = self.unpack_archive(archive, path)
|
|
61
|
+
binary = self.__get_binary(files, driver.get_name())
|
|
62
|
+
binary_path = os.path.join(path, binary)
|
|
63
|
+
self.__save_metadata(driver, binary_path)
|
|
64
|
+
log(f"Driver has been saved in cache [{path}]")
|
|
65
|
+
return binary_path
|
|
66
|
+
|
|
67
|
+
def __get_binary(self, files, driver_name):
|
|
68
|
+
if not files:
|
|
69
|
+
raise Exception(f"Can't find binary for {driver_name} among {files}")
|
|
70
|
+
|
|
71
|
+
if len(files) == 1:
|
|
72
|
+
return files[0]
|
|
73
|
+
|
|
74
|
+
for f in files:
|
|
75
|
+
if 'LICENSE' in f:
|
|
76
|
+
continue
|
|
77
|
+
if 'THIRD_PARTY' in f:
|
|
78
|
+
continue
|
|
79
|
+
if driver_name in f:
|
|
80
|
+
return f
|
|
81
|
+
|
|
82
|
+
raise Exception(f"Can't find binary for {driver_name} among {files}")
|
|
83
|
+
|
|
84
|
+
def __save_metadata(self, driver: Driver, binary_path, date=None):
|
|
85
|
+
if date is None:
|
|
86
|
+
date = datetime.date.today()
|
|
87
|
+
|
|
88
|
+
metadata = self.load_metadata_content()
|
|
89
|
+
key = self.__get_metadata_key(driver)
|
|
90
|
+
data = {
|
|
91
|
+
key: {
|
|
92
|
+
"timestamp": date.strftime(self._date_format),
|
|
93
|
+
"binary_path": binary_path,
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
metadata.update(data)
|
|
98
|
+
with open(self._drivers_json_path, "w+") as outfile:
|
|
99
|
+
json.dump(metadata, outfile, indent=4)
|
|
100
|
+
|
|
101
|
+
def get_os_type(self):
|
|
102
|
+
return self._os_system_manager.get_os_type()
|
|
103
|
+
|
|
104
|
+
def find_driver(self, driver: Driver):
|
|
105
|
+
"""Find driver by '{os_type}_{driver_name}_{driver_version}_{browser_version}'."""
|
|
106
|
+
os_type = self.get_os_type()
|
|
107
|
+
driver_name = driver.get_name()
|
|
108
|
+
browser_type = driver.get_browser_type()
|
|
109
|
+
browser_version = self._os_system_manager.get_browser_version_from_os(browser_type)
|
|
110
|
+
if not browser_version:
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
metadata = self.load_metadata_content()
|
|
114
|
+
requested_driver_version = getattr(driver, "_driver_version_to_download", None)
|
|
115
|
+
if not requested_driver_version:
|
|
116
|
+
cached_path = self.__find_driver_by_browser_version(
|
|
117
|
+
metadata=metadata,
|
|
118
|
+
os_type=os_type,
|
|
119
|
+
driver_name=driver_name,
|
|
120
|
+
browser_version=browser_version,
|
|
121
|
+
)
|
|
122
|
+
if cached_path:
|
|
123
|
+
return cached_path
|
|
124
|
+
|
|
125
|
+
driver_version = self.get_cache_key_driver_version(driver)
|
|
126
|
+
|
|
127
|
+
key = self.__get_metadata_key(driver)
|
|
128
|
+
if key not in metadata:
|
|
129
|
+
log(f'There is no [{os_type}] {driver_name} "{driver_version}" for browser {browser_type} '
|
|
130
|
+
f'"{browser_version}" in cache')
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
driver_info = metadata[key]
|
|
134
|
+
path = driver_info["binary_path"]
|
|
135
|
+
if not os.path.exists(path):
|
|
136
|
+
return None
|
|
137
|
+
|
|
138
|
+
if not self.__is_valid(driver_info):
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
path = driver_info["binary_path"]
|
|
142
|
+
log(f"Driver [{path}] found in cache")
|
|
143
|
+
return path
|
|
144
|
+
|
|
145
|
+
def __find_driver_by_browser_version(self, metadata, os_type, driver_name, browser_version):
|
|
146
|
+
prefix = f"{os_type}_{driver_name}_"
|
|
147
|
+
suffix = f"_for_{browser_version}"
|
|
148
|
+
candidates = []
|
|
149
|
+
|
|
150
|
+
for key, info in metadata.items():
|
|
151
|
+
if not key.startswith(prefix) or not key.endswith(suffix):
|
|
152
|
+
continue
|
|
153
|
+
path = info.get("binary_path")
|
|
154
|
+
timestamp = info.get("timestamp")
|
|
155
|
+
if not path or not os.path.exists(path):
|
|
156
|
+
continue
|
|
157
|
+
if not timestamp:
|
|
158
|
+
continue
|
|
159
|
+
if not self.__is_valid(info):
|
|
160
|
+
continue
|
|
161
|
+
try:
|
|
162
|
+
datetime.datetime.strptime(timestamp, self._date_format)
|
|
163
|
+
except ValueError:
|
|
164
|
+
continue
|
|
165
|
+
candidates.append((timestamp, path))
|
|
166
|
+
|
|
167
|
+
if not candidates:
|
|
168
|
+
return None
|
|
169
|
+
|
|
170
|
+
candidates.sort(key=lambda item: datetime.datetime.strptime(item[0], self._date_format), reverse=True)
|
|
171
|
+
path = candidates[0][1]
|
|
172
|
+
log(f"Driver [{path}] found in cache by browser version")
|
|
173
|
+
return path
|
|
174
|
+
|
|
175
|
+
def __is_valid(self, driver_info):
|
|
176
|
+
dates_diff = get_date_diff(
|
|
177
|
+
driver_info["timestamp"], datetime.date.today(), self._date_format
|
|
178
|
+
)
|
|
179
|
+
return dates_diff < self._cache_valid_days_range
|
|
180
|
+
|
|
181
|
+
def load_metadata_content(self):
|
|
182
|
+
if os.path.exists(self._drivers_json_path):
|
|
183
|
+
with open(self._drivers_json_path, "r") as outfile:
|
|
184
|
+
return json.load(outfile)
|
|
185
|
+
return {}
|
|
186
|
+
|
|
187
|
+
def __get_metadata_key(self, driver: Driver):
|
|
188
|
+
if self._metadata_key:
|
|
189
|
+
return self._metadata_key
|
|
190
|
+
|
|
191
|
+
driver_version = self.get_cache_key_driver_version(driver)
|
|
192
|
+
browser_version = driver.get_browser_version_from_os()
|
|
193
|
+
browser_version = browser_version if browser_version else ""
|
|
194
|
+
self._metadata_key = f"{self.get_os_type()}_{driver.get_name()}_{driver_version}" \
|
|
195
|
+
f"_for_{browser_version}"
|
|
196
|
+
return self._metadata_key
|
|
197
|
+
|
|
198
|
+
def get_cache_key_driver_version(self, driver: Driver):
|
|
199
|
+
if self._cache_key_driver_version:
|
|
200
|
+
return self._cache_key_driver_version
|
|
201
|
+
self._cache_key_driver_version = driver.get_driver_version_to_download()
|
|
202
|
+
return self._cache_key_driver_version
|
|
203
|
+
|
|
204
|
+
def __get_path(self, driver: Driver):
|
|
205
|
+
if self._driver_binary_path is None:
|
|
206
|
+
self._driver_binary_path = os.path.join(
|
|
207
|
+
self._drivers_directory,
|
|
208
|
+
driver.get_name(),
|
|
209
|
+
self.get_os_type(),
|
|
210
|
+
self.get_cache_key_driver_version(driver),
|
|
211
|
+
)
|
|
212
|
+
return self._driver_binary_path
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import sys
|
|
4
|
+
import tarfile
|
|
5
|
+
import zipfile
|
|
6
|
+
|
|
7
|
+
from webdriver_manager.core.archive import Archive, LinuxZipFileWithPermissions
|
|
8
|
+
from webdriver_manager.core.os_manager import OperationSystemManager
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class File(object):
|
|
12
|
+
def __init__(self, stream, file_name):
|
|
13
|
+
self.content = stream.content
|
|
14
|
+
self.__stream = stream
|
|
15
|
+
self.file_name = file_name
|
|
16
|
+
self.__temp_name = "driver"
|
|
17
|
+
self.__regex_filename = r"""filename.+"(.+)"|filename.+''(.+)|filename=([\w.-]+)"""
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def filename(self) -> str:
|
|
21
|
+
if self.file_name:
|
|
22
|
+
return self.file_name
|
|
23
|
+
try:
|
|
24
|
+
content = self.__stream.headers["content-disposition"]
|
|
25
|
+
|
|
26
|
+
content_disposition_list = re.split(";", content)
|
|
27
|
+
filenames = [re.findall(self.__regex_filename, element) for element in content_disposition_list]
|
|
28
|
+
filename = next(filter(None, next(filter(None, next(filter(None, filenames))))))
|
|
29
|
+
except KeyError:
|
|
30
|
+
filename = f"{self.__temp_name}.zip"
|
|
31
|
+
except (IndexError, StopIteration):
|
|
32
|
+
filename = f"{self.__temp_name}.exe"
|
|
33
|
+
|
|
34
|
+
if '"' in filename:
|
|
35
|
+
filename = filename.replace('"', "")
|
|
36
|
+
|
|
37
|
+
return filename
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class FileManager(object):
|
|
41
|
+
|
|
42
|
+
def __init__(self, os_system_manager: OperationSystemManager):
|
|
43
|
+
self._os_system_manager = os_system_manager
|
|
44
|
+
|
|
45
|
+
def save_archive_file(self, file: File, directory: str):
|
|
46
|
+
os.makedirs(directory, exist_ok=True)
|
|
47
|
+
|
|
48
|
+
archive_path = f"{directory}{os.sep}{file.filename}"
|
|
49
|
+
with open(archive_path, "wb") as code:
|
|
50
|
+
code.write(file.content)
|
|
51
|
+
if not os.path.exists(archive_path):
|
|
52
|
+
raise FileExistsError(f"No file has been saved on such path {archive_path}")
|
|
53
|
+
return Archive(archive_path)
|
|
54
|
+
|
|
55
|
+
def unpack_archive(self, archive_file: Archive, target_dir):
|
|
56
|
+
file_path = archive_file.file_path
|
|
57
|
+
if file_path.endswith(".zip"):
|
|
58
|
+
return self.__extract_zip(archive_file, target_dir)
|
|
59
|
+
elif file_path.endswith(".tar.gz"):
|
|
60
|
+
return self.__extract_tar_file(archive_file, target_dir)
|
|
61
|
+
|
|
62
|
+
def __extract_zip(self, archive_file, to_directory):
|
|
63
|
+
zip_class = (LinuxZipFileWithPermissions if self._os_system_manager.get_os_name() == "linux" else zipfile.ZipFile)
|
|
64
|
+
archive = zip_class(archive_file.file_path)
|
|
65
|
+
self.__remove_file_dir_conflicts(archive.namelist(), to_directory)
|
|
66
|
+
try:
|
|
67
|
+
archive.extractall(to_directory)
|
|
68
|
+
except Exception as e:
|
|
69
|
+
if e.args[0] not in [26, 13] and e.args[1] not in [
|
|
70
|
+
"Text file busy",
|
|
71
|
+
"Permission denied",
|
|
72
|
+
]:
|
|
73
|
+
raise e
|
|
74
|
+
file_names = []
|
|
75
|
+
for n in archive.namelist():
|
|
76
|
+
if "/" not in n:
|
|
77
|
+
file_names.append(n)
|
|
78
|
+
else:
|
|
79
|
+
file_path, file_name = n.rsplit("/", 1)
|
|
80
|
+
if not file_name:
|
|
81
|
+
continue
|
|
82
|
+
full_file_path = os.path.join(to_directory, file_path)
|
|
83
|
+
source = os.path.join(full_file_path, file_name)
|
|
84
|
+
destination = os.path.join(to_directory, file_name)
|
|
85
|
+
if not os.path.exists(source):
|
|
86
|
+
continue
|
|
87
|
+
os.replace(source, destination)
|
|
88
|
+
file_names.append(file_name)
|
|
89
|
+
return sorted(file_names, key=lambda x: x.lower())
|
|
90
|
+
return archive.namelist()
|
|
91
|
+
|
|
92
|
+
def __remove_file_dir_conflicts(self, names, to_directory):
|
|
93
|
+
"""Remove stale files that conflict with directory entries in archive.
|
|
94
|
+
|
|
95
|
+
Example: if cache contains `<to_directory>/operadriver` as a file, and
|
|
96
|
+
archive now contains `operadriver/<binary>`, extraction would fail with
|
|
97
|
+
NotADirectoryError.
|
|
98
|
+
"""
|
|
99
|
+
top_level_dirs = {name.split("/", 1)[0] for name in names if "/" in name}
|
|
100
|
+
for dir_name in top_level_dirs:
|
|
101
|
+
path = os.path.join(to_directory, dir_name)
|
|
102
|
+
if os.path.isfile(path):
|
|
103
|
+
os.remove(path)
|
|
104
|
+
|
|
105
|
+
def __extract_tar_file(self, archive_file, to_directory):
|
|
106
|
+
try:
|
|
107
|
+
tar = tarfile.open(archive_file.file_path, mode="r:gz")
|
|
108
|
+
except tarfile.ReadError:
|
|
109
|
+
tar = tarfile.open(archive_file.file_path, mode="r:bz2")
|
|
110
|
+
members = tar.getmembers()
|
|
111
|
+
# Python 3.13+ requires 'filter' to avoid deprecation warning
|
|
112
|
+
if sys.version_info >= (3, 13):
|
|
113
|
+
tar.extractall(to_directory, filter="fully_trusted")
|
|
114
|
+
else:
|
|
115
|
+
tar.extractall(to_directory)
|
|
116
|
+
tar.close()
|
|
117
|
+
return [x.name for x in members]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from requests import Response, exceptions
|
|
3
|
+
|
|
4
|
+
from webdriver_manager.core.config import ssl_verify
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class HttpClient:
|
|
8
|
+
def get(self, url, params=None, **kwargs) -> Response:
|
|
9
|
+
raise NotImplementedError
|
|
10
|
+
|
|
11
|
+
@staticmethod
|
|
12
|
+
def validate_response(resp: requests.Response):
|
|
13
|
+
status_code = resp.status_code
|
|
14
|
+
if status_code == 404:
|
|
15
|
+
raise ValueError(f"There is no such driver by url {resp.url}")
|
|
16
|
+
elif status_code == 401:
|
|
17
|
+
raise ValueError(f"API Rate limit exceeded. You have to add GH_TOKEN!!!")
|
|
18
|
+
elif resp.status_code != 200:
|
|
19
|
+
raise ValueError(
|
|
20
|
+
f"response body:\n{resp.text}\n"
|
|
21
|
+
f"request url:\n{resp.request.url}\n"
|
|
22
|
+
f"response headers:\n{dict(resp.headers)}\n"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class WDMHttpClient(HttpClient):
|
|
27
|
+
def __init__(self):
|
|
28
|
+
self._ssl_verify = ssl_verify()
|
|
29
|
+
|
|
30
|
+
def get(self, url, **kwargs) -> Response:
|
|
31
|
+
try:
|
|
32
|
+
resp = requests.get(
|
|
33
|
+
url=url, verify=self._ssl_verify, stream=True, **kwargs)
|
|
34
|
+
except exceptions.ConnectionError:
|
|
35
|
+
raise exceptions.ConnectionError(f"Could not reach host. Are you offline?")
|
|
36
|
+
self.validate_response(resp)
|
|
37
|
+
return resp
|
|
38
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from webdriver_manager.core.config import wdm_log_level
|
|
4
|
+
|
|
5
|
+
__logger = logging.getLogger("WDM")
|
|
6
|
+
__logger.addHandler(logging.NullHandler())
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def log(text):
|
|
10
|
+
"""Emitting the log message."""
|
|
11
|
+
__logger.log(wdm_log_level(), text)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def set_logger(logger):
|
|
15
|
+
"""
|
|
16
|
+
Set the global logger.
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
logger : object
|
|
21
|
+
Any logger-like object that provides a callable ``log(level, message)`` method.
|
|
22
|
+
|
|
23
|
+
Returns None
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
if not callable(getattr(logger, "log", None)):
|
|
27
|
+
raise ValueError("The logger must provide a callable log(level, message) method")
|
|
28
|
+
|
|
29
|
+
# Bind the logger input to the global logger
|
|
30
|
+
global __logger
|
|
31
|
+
__logger = logger
|