XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet
公开
关注 0 Fork 0 Star 1
返回提交历史

XFEstudio/gpt4free

Change doctypes style to Google Fix typo in latest_version Fix Phind Provider Add unittest worklow and main tests

32252def
Heiner Lohaus <hlohaus@users.noreply.github.com>
提交于

代码差异

10 个文件 +478 -104
Added .github/workflows/unittest.yml +19 -0
@@ -0,0 +1,19 @@
1 name: Unittest
2
3 on: [push]
4
5 jobs:
6 build:
7 name: Build unittest
8 runs-on: ubuntu-latest
9 steps:
10 - uses: actions/checkout@v4
11 - name: Set up Python
12 uses: actions/setup-python@v4
13 with:
14 python-version: "3.x"
15 cache: 'pip'
16 - name: Install requirements
17 - run: pip install -r requirements.txt
18 - name: Run tests
19 run: python -m etc.unittest.main
Added etc/unittest/main.py +73 -0
@@ -0,0 +1,73 @@
1 import sys
2 import pathlib
3 import unittest
4 from unittest.mock import MagicMock
5
6 sys.path.append(str(pathlib.Path(__file__).parent.parent.parent))
7
8 import g4f
9 from g4f import ChatCompletion, get_last_provider
10 from g4f.gui.server.backend import Backend_Api, get_error_message
11 from g4f.base_provider import BaseProvider
12
13 g4f.debug.logging = False
14
15 class MockProvider(BaseProvider):
16 working = True
17
18 def create_completion(
19 model, messages, stream, **kwargs
20 ):
21 yield "Mock"
22
23 async def create_async(
24 model, messages, **kwargs
25 ):
26 return "Mock"
27
28 class TestBackendApi(unittest.TestCase):
29
30 def setUp(self):
31 self.app = MagicMock()
32 self.api = Backend_Api(self.app)
33
34 def test_version(self):
35 response = self.api.get_version()
36 self.assertIn("version", response)
37 self.assertIn("latest_version", response)
38
39 class TestChatCompletion(unittest.TestCase):
40
41 def test_create(self):
42 messages = [{'role': 'user', 'content': 'Hello'}]
43 result = ChatCompletion.create(g4f.models.default, messages)
44 self.assertTrue("Hello" in result or "Good" in result)
45
46 def test_get_last_provider(self):
47 messages = [{'role': 'user', 'content': 'Hello'}]
48 ChatCompletion.create(g4f.models.default, messages, MockProvider)
49 self.assertEqual(get_last_provider(), MockProvider)
50
51 def test_bing_provider(self):
52 messages = [{'role': 'user', 'content': 'Hello'}]
53 provider = g4f.Provider.Bing
54 result = ChatCompletion.create(g4f.models.default, messages, provider)
55 self.assertTrue("Bing" in result)
56
57 class TestChatCompletionAsync(unittest.IsolatedAsyncioTestCase):
58
59 async def test_async(self):
60 messages = [{'role': 'user', 'content': 'Hello'}]
61 result = await ChatCompletion.create_async(g4f.models.default, messages, MockProvider)
62 self.assertTrue("Mock" in result)
63
64 class TestUtilityFunctions(unittest.TestCase):
65
66 def test_get_error_message(self):
67 g4f.debug.last_provider = g4f.Provider.Bing
68 exception = Exception("Message")
69 result = get_error_message(exception)
70 self.assertEqual("Bing: Exception: Message", result)
71
72 if __name__ == '__main__':
73 unittest.main()
Modified g4f/Provider/Phind.py +6 -2
@@ -59,12 +59,16 @@ class Phind(AsyncGeneratorProvider):
59 59 "rewrittenQuestion": prompt,
60 60 "challenge": 0.21132115912208504
61 61 }
62 async with session.post(f"{cls.url}/api/infer/followup/answer", headers=headers, json=data) as response:
62 async with session.post(f"https://https.api.phind.com/infer/", headers=headers, json=data) as response:
63 63 new_line = False
64 64 async for line in response.iter_lines():
65 65 if line.startswith(b"data: "):
66 66 chunk = line[6:]
67 if chunk.startswith(b"<PHIND_METADATA>") or chunk.startswith(b"<PHIND_INDICATOR>"):
67 if chunk.startswith(b'<PHIND_DONE/>'):
68 break
69 if chunk.startswith(b'<PHIND_WEBRESULTS>') or chunk.startswith(b'<PHIND_FOLLOWUP>'):
70 pass
71 elif chunk.startswith(b"<PHIND_METADATA>") or chunk.startswith(b"<PHIND_INDICATOR>"):
68 72 pass
69 73 elif chunk:
70 74 yield chunk.decode()
Modified g4f/Provider/base_provider.py +71 -0
@@ -36,6 +36,17 @@ class AbstractProvider(BaseProvider):
36 36 ) -> str:
37 37 """
38 38 Asynchronously creates a result based on the given model and messages.
39
40 Args:
41 cls (type): The class on which this method is called.
42 model (str): The model to use for creation.
43 messages (Messages): The messages to process.
44 loop (AbstractEventLoop, optional): The event loop to use. Defaults to None.
45 executor (ThreadPoolExecutor, optional): The executor for running async tasks. Defaults to None.
46 **kwargs: Additional keyword arguments.
47
48 Returns:
49 str: The created result as a string.
39 50 """
40 51 loop = loop or get_event_loop()
41 52
@@ -52,6 +63,12 @@ class AbstractProvider(BaseProvider):
52 63 def params(cls) -> str:
53 64 """
54 65 Returns the parameters supported by the provider.
66
67 Args:
68 cls (type): The class on which this property is called.
69
70 Returns:
71 str: A string listing the supported parameters.
55 72 """
56 73 sig = signature(
57 74 cls.create_async_generator if issubclass(cls, AsyncGeneratorProvider) else
@@ -90,6 +107,17 @@ class AsyncProvider(AbstractProvider):
90 107 ) -> CreateResult:
91 108 """
92 109 Creates a completion result synchronously.
110
111 Args:
112 cls (type): The class on which this method is called.
113 model (str): The model to use for creation.
114 messages (Messages): The messages to process.
115 stream (bool): Indicates whether to stream the results. Defaults to False.
116 loop (AbstractEventLoop, optional): The event loop to use. Defaults to None.
117 **kwargs: Additional keyword arguments.
118
119 Returns:
120 CreateResult: The result of the completion creation.
93 121 """
94 122 loop = loop or get_event_loop()
95 123 coro = cls.create_async(model, messages, **kwargs)
@@ -104,6 +132,17 @@ class AsyncProvider(AbstractProvider):
104 132 ) -> str:
105 133 """
106 134 Abstract method for creating asynchronous results.
135
136 Args:
137 model (str): The model to use for creation.
138 messages (Messages): The messages to process.
139 **kwargs: Additional keyword arguments.
140
141 Raises:
142 NotImplementedError: If this method is not overridden in derived classes.
143
144 Returns:
145 str: The created result as a string.
107 146 """
108 147 raise NotImplementedError()
109 148
@@ -126,6 +165,17 @@ class AsyncGeneratorProvider(AsyncProvider):
126 165 ) -> CreateResult:
127 166 """
128 167 Creates a streaming completion result synchronously.
168
169 Args:
170 cls (type): The class on which this method is called.
171 model (str): The model to use for creation.
172 messages (Messages): The messages to process.
173 stream (bool): Indicates whether to stream the results. Defaults to True.
174 loop (AbstractEventLoop, optional): The event loop to use. Defaults to None.
175 **kwargs: Additional keyword arguments.
176
177 Returns:
178 CreateResult: The result of the streaming completion creation.
129 179 """
130 180 loop = loop or get_event_loop()
131 181 generator = cls.create_async_generator(model, messages, stream=stream, **kwargs)
@@ -146,6 +196,15 @@ class AsyncGeneratorProvider(AsyncProvider):
146 196 ) -> str:
147 197 """
148 198 Asynchronously creates a result from a generator.
199
200 Args:
201 cls (type): The class on which this method is called.
202 model (str): The model to use for creation.
203 messages (Messages): The messages to process.
204 **kwargs: Additional keyword arguments.
205
206 Returns:
207 str: The created result as a string.
149 208 """
150 209 return "".join([
151 210 chunk async for chunk in cls.create_async_generator(model, messages, stream=False, **kwargs)
@@ -162,5 +221,17 @@ class AsyncGeneratorProvider(AsyncProvider):
162 221 ) -> AsyncResult:
163 222 """
164 223 Abstract method for creating an asynchronous generator.
224
225 Args:
226 model (str): The model to use for creation.
227 messages (Messages): The messages to process.
228 stream (bool): Indicates whether to stream the results. Defaults to True.
229 **kwargs: Additional keyword arguments.
230
231 Raises:
232 NotImplementedError: If this method is not overridden in derived classes.
233
234 Returns:
235 AsyncResult: An asynchronous generator yielding results.
165 236 """
166 237 raise NotImplementedError()
Modified g4f/Provider/bing/create_images.py +1 -1
@@ -198,7 +198,7 @@ class CreateImagesBing:
198 198 _cookies: Dict[str, str] = {}
199 199
200 200 @classmethod
201 def create_completion(cls, prompt: str, cookies: Dict[str, str] = None, proxy: str = None) -> Generator[str]:
201 def create_completion(cls, prompt: str, cookies: Dict[str, str] = None, proxy: str = None) -> Generator[str, None, None]:
202 202 """
203 203 Generator for creating imagecompletion based on a prompt.
204 204
Modified g4f/Provider/create_images.py +60 -1
@@ -8,13 +8,31 @@ from ..base_provider import BaseProvider, ProviderType
8 8
9 9 system_message = """
10 10 You can generate custom images with the DALL-E 3 image generator.
11 To generate a image with a prompt, do this:
11 To generate an image with a prompt, do this:
12 12 <img data-prompt=\"keywords for the image\">
13 13 Don't use images with data uri. It is important to use a prompt instead.
14 14 <img data-prompt=\"image caption\">
15 15 """
16 16
17 17 class CreateImagesProvider(BaseProvider):
18 """
19 Provider class for creating images based on text prompts.
20
21 This provider handles image creation requests embedded within message content,
22 using provided image creation functions.
23
24 Attributes:
25 provider (ProviderType): The underlying provider to handle non-image related tasks.
26 create_images (callable): A function to create images synchronously.
27 create_images_async (callable): A function to create images asynchronously.
28 system_message (str): A message that explains the image creation capability.
29 include_placeholder (bool): Flag to determine whether to include the image placeholder in the output.
30 __name__ (str): Name of the provider.
31 url (str): URL of the provider.
32 working (bool): Indicates if the provider is operational.
33 supports_stream (bool): Indicates if the provider supports streaming.
34 """
35
18 36 def __init__(
19 37 self,
20 38 provider: ProviderType,
@@ -23,6 +41,16 @@ class CreateImagesProvider(BaseProvider):
23 41 system_message: str = system_message,
24 42 include_placeholder: bool = True
25 43 ) -> None:
44 """
45 Initializes the CreateImagesProvider.
46
47 Args:
48 provider (ProviderType): The underlying provider.
49 create_images (callable): Function to create images synchronously.
50 create_async (callable): Function to create images asynchronously.
51 system_message (str, optional): System message to be prefixed to messages. Defaults to a predefined message.
52 include_placeholder (bool, optional): Whether to include image placeholders in the output. Defaults to True.
53 """
26 54 self.provider = provider
27 55 self.create_images = create_images
28 56 self.create_images_async = create_async
@@ -40,6 +68,22 @@ class CreateImagesProvider(BaseProvider):
40 68 stream: bool = False,
41 69 **kwargs
42 70 ) -> CreateResult:
71 """
72 Creates a completion result, processing any image creation prompts found within the messages.
73
74 Args:
75 model (str): The model to use for creation.
76 messages (Messages): The messages to process, which may contain image prompts.
77 stream (bool, optional): Indicates whether to stream the results. Defaults to False.
78 **kwargs: Additional keywordarguments for the provider.
79
80 Yields:
81 CreateResult: Yields chunks of the processed messages, including image data if applicable.
82
83 Note:
84 This method processes messages to detect image creation prompts. When such a prompt is found,
85 it calls the synchronous image creation function and includes the resulting image in the output.
86 """
43 87 messages.insert(0, {"role": "system", "content": self.system_message})
44 88 buffer = ""
45 89 for chunk in self.provider.create_completion(model, messages, stream, **kwargs):
@@ -71,6 +115,21 @@ class CreateImagesProvider(BaseProvider):
71 115 messages: Messages,
72 116 **kwargs
73 117 ) -> str:
118 """
119 Asynchronously creates a response, processing any image creation prompts found within the messages.
120
121 Args:
122 model (str): The model to use for creation.
123 messages (Messages): The messages to process, which may contain image prompts.
124 **kwargs: Additional keyword arguments for the provider.
125
126 Returns:
127 str: The processed response string, including asynchronously generated image data if applicable.
128
129 Note:
130 This method processes messages to detect image creation prompts. When such a prompt is found,
131 it calls the asynchronous image creation function and includes the resulting image in the output.
132 """
74 133 messages.insert(0, {"role": "system", "content": self.system_message})
75 134 response = await self.provider.create_async(model, messages, **kwargs)
76 135 matches = re.findall(r'(<img data-prompt="(.*?)">)', response)
Modified g4f/gui/client/js/chat.v1.js +3 -3
@@ -652,9 +652,9 @@ observer.observe(message_input, { attributes: true });
652 652
653 653 document.title = 'g4f - gui - ' + versions["version"];
654 654 text = "version ~ "
655 if (versions["version"] != versions["lastet_version"]) {
656 release_url = 'https://github.com/xtekky/gpt4free/releases/tag/' + versions["lastet_version"];
657 text += '<a href="' + release_url +'" target="_blank" title="New version: ' + versions["lastet_version"] +'">' + versions["version"] + ' 🆕</a>';
655 if (versions["version"] != versions["latest_version"]) {
656 release_url = 'https://github.com/xtekky/gpt4free/releases/tag/' + versions["latest_version"];
657 text += '<a href="' + release_url +'" target="_blank" title="New version: ' + versions["latest_version"] +'">' + versions["version"] + ' 🆕</a>';
658 658 } else {
659 659 text += versions["version"];
660 660 }
Modified g4f/gui/server/backend.py +149 -62
@@ -1,6 +1,7 @@
1 1 import logging
2 2 import json
3 3 from flask import request, Flask
4 from typing import Generator
4 5 from g4f import debug, version, models
5 6 from g4f import _all_models, get_last_provider, ChatCompletion
6 7 from g4f.image import is_allowed_extension, to_image
@@ -11,60 +12,123 @@ from .internet import get_search_message
11 12 debug.logging = True
12 13
13 14 class Backend_Api:
15 """
16 Handles various endpoints in a Flask application for backend operations.
17
18 This class provides methods to interact with models, providers, and to handle
19 various functionalities like conversations, error handling, and version management.
20
21 Attributes:
22 app (Flask): A Flask application instance.
23 routes (dict): A dictionary mapping API endpoints to their respective handlers.
24 """
14 25 def __init__(self, app: Flask) -> None:
26 """
27 Initialize the backend API with the given Flask application.
28
29 Args:
30 app (Flask): Flask application instance to attach routes to.
31 """
15 32 self.app: Flask = app
16 33 self.routes = {
17 34 '/backend-api/v2/models': {
18 'function': self.models,
19 'methods' : ['GET']
35 'function': self.get_models,
36 'methods': ['GET']
20 37 },
21 38 '/backend-api/v2/providers': {
22 'function': self.providers,
23 'methods' : ['GET']
39 'function': self.get_providers,
40 'methods': ['GET']
24 41 },
25 42 '/backend-api/v2/version': {
26 'function': self.version,
27 'methods' : ['GET']
43 'function': self.get_version,
44 'methods': ['GET']
28 45 },
29 46 '/backend-api/v2/conversation': {
30 'function': self._conversation,
47 'function': self.handle_conversation,
31 48 'methods': ['POST']
32 49 },
33 50 '/backend-api/v2/gen.set.summarize:title': {
34 'function': self._gen_title,
51 'function': self.generate_title,
35 52 'methods': ['POST']
36 53 },
37 54 '/backend-api/v2/error': {
38 'function': self.error,
55 'function': self.handle_error,
39 56 'methods': ['POST']
40 57 }
41 58 }
42 59
43 def error(self):
60 def handle_error(self):
61 """
62 Initialize the backend API with the given Flask application.
63
64 Args:
65 app (Flask): Flask application instance to attach routes to.
66 """
44 67 print(request.json)
45
46 68 return 'ok', 200
47 69
48 def models(self):
70 def get_models(self):
71 """
72 Return a list of all models.
73
74 Fetches and returns a list of all available models in the system.
75
76 Returns:
77 List[str]: A list of model names.
78 """
49 79 return _all_models
50 80
51 def providers(self):
52 return [
53 provider.__name__ for provider in __providers__ if provider.working
54 ]
81 def get_providers(self):
82 """
83 Return a list of all working providers.
84 """
85 return [provider.__name__ for provider in __providers__ if provider.working]
55 86
56 def version(self):
87 def get_version(self):
88 """
89 Returns the current and latest version of the application.
90
91 Returns:
92 dict: A dictionary containing the current and latest version.
93 """
57 94 return {
58 95 "version": version.utils.current_version,
59 "lastet_version": version.get_latest_version(),
96 "latest_version": version.get_latest_version(),
60 97 }
61 98
62 def _gen_title(self):
63 return {
64 'title': ''
65 }
99 def generate_title(self):
100 """
101 Generates and returns a title based on the request data.
102
103 Returns:
104 dict: A dictionary with the generated title.
105 """
106 return {'title': ''}
66 107
67 def _conversation(self):
108 def handle_conversation(self):
109 """
110 Handles conversation requests and streams responses back.
111
112 Returns:
113 Response: A Flask response object for streaming.
114 """
115 kwargs = self._prepare_conversation_kwargs()
116
117 return self.app.response_class(
118 self._create_response_stream(kwargs),
119 mimetype='text/event-stream'
120 )
121
122 def _prepare_conversation_kwargs(self):
123 """
124 Prepares arguments for chat completion based on the request data.
125
126 Reads the request and prepares the necessary arguments for handling
127 a chat completion request.
128
129 Returns:
130 dict: Arguments prepared for chat completion.
131 """
68 132 kwargs = {}
69 133 if 'image' in request.files:
70 134 file = request.files['image']
@@ -87,47 +151,70 @@ class Backend_Api:
87 151 messages[-1]["content"] = get_search_message(messages[-1]["content"])
88 152 model = json_data.get('model')
89 153 model = model if model else models.default
90 provider = json_data.get('provider', '').replace('g4f.Provider.', '')
91 provider = provider if provider and provider != "Auto" else None
92 154 patch = patch_provider if json_data.get('patch_provider') else None
93 155
94 def try_response():
95 try:
96 first = True
97 for chunk in ChatCompletion.create(
98 model=model,
99 provider=provider,
100 messages=messages,
101 stream=True,
102 ignore_stream_and_auth=True,
103 patch_provider=patch,
104 **kwargs
105 ):
106 if first:
107 first = False
108 yield json.dumps({
109 'type' : 'provider',
110 'provider': get_last_provider(True)
111 }) + "\n"
112 if isinstance(chunk, Exception):
113 logging.exception(chunk)
114 yield json.dumps({
115 'type' : 'message',
116 'message': get_error_message(chunk),
117 }) + "\n"
118 else:
119 yield json.dumps({
120 'type' : 'content',
121 'content': str(chunk),
122 }) + "\n"
123 except Exception as e:
124 logging.exception(e)
125 yield json.dumps({
126 'type' : 'error',
127 'error': get_error_message(e)
128 })
129
130 return self.app.response_class(try_response(), mimetype='text/event-stream')
156 return {
157 "model": model,
158 "provider": provider,
159 "messages": messages,
160 "stream": True,
161 "ignore_stream_and_auth": True,
162 "patch_provider": patch,
163 **kwargs
164 }
165
166 def _create_response_stream(self, kwargs) -> Generator[str, None, None]:
167 """
168 Creates and returns a streaming response for the conversation.
169
170 Args:
171 kwargs (dict): Arguments for creating the chat completion.
172
173 Yields:
174 str: JSON formatted response chunks for the stream.
175
176 Raises:
177 Exception: If an error occurs during the streaming process.
178 """
179 try:
180 first = True
181 for chunk in ChatCompletion.create(**kwargs):
182 if first:
183 first = False
184 yield self._format_json('provider', get_last_provider(True))
185 if isinstance(chunk, Exception):
186 logging.exception(chunk)
187 yield self._format_json('message', get_error_message(chunk))
188 else:
189 yield self._format_json('content', str(chunk))
190 except Exception as e:
191 logging.exception(e)
192 yield self._format_json('error', get_error_message(e))
193
194 def _format_json(self, response_type: str, content) -> str:
195 """
196 Formats and returns a JSON response.
197
198 Args:
199 response_type (str): The type of the response.
200 content: The content to be included in the response.
201
202 Returns:
203 str: A JSON formatted string.
204 """
205 return json.dumps({
206 'type': response_type,
207 response_type: content
208 }) + "\n"
131 209
132 210 def get_error_message(exception: Exception) -> str:
211 """
212 Generates a formatted error message from an exception.
213
214 Args:
215 exception (Exception): The exception to format.
216
217 Returns:
218 str: A formatted error message string.
219 """
133 220 return f"{get_last_provider().__name__}: {type(exception).__name__}: {exception}"
Modified g4f/version.py +41 -15
@@ -7,10 +7,16 @@ from .errors import VersionNotFoundError
7 7
8 8 def get_pypi_version(package_name: str) -> str:
9 9 """
10 Get the latest version of a package from PyPI.
10 Retrieves the latest version of a package from PyPI.
11 11
12 :param package_name: The name of the package.
13 :return: The latest version of the package as a string.
12 Args:
13 package_name (str): The name of the package for which to retrieve the version.
14
15 Returns:
16 str: The latest version of the specified package from PyPI.
17
18 Raises:
19 VersionNotFoundError: If there is an error in fetching the version from PyPI.
14 20 """
15 21 try:
16 22 response = requests.get(f"https://pypi.org/pypi/{package_name}/json").json()
@@ -20,10 +26,16 @@ def get_pypi_version(package_name: str) -> str:
20 26
21 27 def get_github_version(repo: str) -> str:
22 28 """
23 Get the latest release version from a GitHub repository.
29 Retrieves the latest release version from a GitHub repository.
30
31 Args:
32 repo (str): The name of the GitHub repository.
33
34 Returns:
35 str: The latest release version from the specified GitHub repository.
24 36
25 :param repo: The name of the GitHub repository.
26 :return: The latest release version as a string.
37 Raises:
38 VersionNotFoundError: If there is an error in fetching the version from GitHub.
27 39 """
28 40 try:
29 41 response = requests.get(f"https://api.github.com/repos/{repo}/releases/latest").json()
@@ -31,11 +43,16 @@ def get_github_version(repo: str) -> str:
31 43 except requests.RequestException as e:
32 44 raise VersionNotFoundError(f"Failed to get GitHub release version: {e}")
33 45
34 def get_latest_version():
46 def get_latest_version() -> str:
35 47 """
36 Get the latest release version from PyPI or the GitHub repository.
48 Retrieves the latest release version of the 'g4f' package from PyPI or GitHub.
37 49
38 :return: The latest release version as a string.
50 Returns:
51 str: The latest release version of 'g4f'.
52
53 Note:
54 The function first tries to fetch the version from PyPI. If the package is not found,
55 it retrieves the version from the GitHub repository.
39 56 """
40 57 try:
41 58 # Is installed via package manager?
@@ -47,14 +64,19 @@ def get_latest_version():
47 64
48 65 class VersionUtils:
49 66 """
50 Utility class for managing and comparing package versions.
67 Utility class for managing and comparing package versions of 'g4f'.
51 68 """
52 69 @cached_property
53 70 def current_version(self) -> str:
54 71 """
55 Get the current version of the g4f package.
72 Retrieves the current version of the 'g4f' package.
73
74 Returns:
75 str: The current version of 'g4f'.
56 76
57 :return: The current version as a string.
77 Raises:
78 VersionNotFoundError: If the version cannot be determined from the package manager,
79 Docker environment, or git repository.
58 80 """
59 81 # Read from package manager
60 82 try:
@@ -79,15 +101,19 @@ class VersionUtils:
79 101 @cached_property
80 102 def latest_version(self) -> str:
81 103 """
82 Get the latest version of the g4f package.
104 Retrieves the latest version of the 'g4f' package.
83 105
84 :return: The latest version as a string.
106 Returns:
107 str: The latest version of 'g4f'.
85 108 """
86 109 return get_latest_version()
87 110
88 111 def check_version(self) -> None:
89 112 """
90 Check if the current version is up to date with the latest version.
113 Checks if the current version of 'g4f' is up to date with the latest version.
114
115 Note:
116 If a newer version is available, it prints a message with the new version and update instructions.
91 117 """
92 118 try:
93 119 if self.current_version != self.latest_version:
Modified g4f/webdriver.py +55 -20
@@ -21,13 +21,16 @@ def get_browser(
21 21 options: ChromeOptions = None
22 22 ) -> WebDriver:
23 23 """
24 Creates and returns a Chrome WebDriver with the specified options.
24 Creates and returns a Chrome WebDriver with specified options.
25 25
26 :param user_data_dir: Directory for user data. If None, uses default directory.
27 :param headless: Boolean indicating whether to run the browser in headless mode.
28 :param proxy: Proxy settings for the browser.
29 :param options: ChromeOptions object with specific browser options.
30 :return: An instance of WebDriver.
26 Args:
27 user_data_dir (str, optional): Directory for user data. If None, uses default directory.
28 headless (bool, optional): Whether to run the browser in headless mode. Defaults to False.
29 proxy (str, optional): Proxy settings for the browser. Defaults to None.
30 options (ChromeOptions, optional): ChromeOptions object with specific browser options. Defaults to None.
31
32 Returns:
33 WebDriver: An instance of WebDriver configured with the specified options.
31 34 """
32 35 if user_data_dir is None:
33 36 user_data_dir = user_config_dir("g4f")
@@ -49,10 +52,13 @@ def get_browser(
49 52
50 53 def get_driver_cookies(driver: WebDriver) -> dict:
51 54 """
52 Retrieves cookies from the given WebDriver.
55 Retrieves cookies from the specified WebDriver.
56
57 Args:
58 driver (WebDriver): The WebDriver instance from which to retrieve cookies.
53 59
54 :param driver: WebDriver from which to retrieve cookies.
55 :return: A dictionary of cookies.
60 Returns:
61 dict: A dictionary containing cookies with their names as keys and values as cookie values.
56 62 """
57 63 return {cookie["name"]: cookie["value"] for cookie in driver.get_cookies()}
58 64
@@ -60,9 +66,13 @@ def bypass_cloudflare(driver: WebDriver, url: str, timeout: int) -> None:
60 66 """
61 67 Attempts to bypass Cloudflare protection when accessing a URL using the provided WebDriver.
62 68
63 :param driver: The WebDriver to use.
64 :param url: URL to access.
65 :param timeout: Time in seconds to wait for the page to load.
69 Args:
70 driver (WebDriver): The WebDriver to use for accessing the URL.
71 url (str): The URL to access.
72 timeout (int): Time in seconds to wait for the page to load.
73
74 Raises:
75 Exception: If there is an error while bypassing Cloudflare or loading the page.
66 76 """
67 77 driver.get(url)
68 78 if driver.find_element(By.TAG_NAME, "body").get_attribute("class") == "no-js":
@@ -86,6 +96,7 @@ class WebDriverSession:
86 96 """
87 97 Manages a Selenium WebDriver session, including handling of virtual displays and proxies.
88 98 """
99
89 100 def __init__(
90 101 self,
91 102 webdriver: WebDriver = None,
@@ -95,6 +106,17 @@ class WebDriverSession:
95 106 proxy: str = None,
96 107 options: ChromeOptions = None
97 108 ):
109 """
110 Initializes a new instance of the WebDriverSession.
111
112 Args:
113 webdriver (WebDriver, optional): A WebDriver instance for the session. Defaults to None.
114 user_data_dir (str, optional): Directory for user data. Defaults to None.
115 headless (bool, optional): Whether to run the browser in headless mode. Defaults to False.
116 virtual_display (bool, optional): Whether to use a virtual display. Defaults to False.
117 proxy (str, optional): Proxy settings for the browser. Defaults to None.
118 options (ChromeOptions, optional): ChromeOptions for the browser. Defaults to None.
119 """
98 120 self.webdriver = webdriver
99 121 self.user_data_dir = user_data_dir
100 122 self.headless = headless
@@ -110,14 +132,17 @@ class WebDriverSession:
110 132 virtual_display: bool = False
111 133 ) -> WebDriver:
112 134 """
113 Reopens the WebDriver session with the specified parameters.
135 Reopens the WebDriver session with new settings.
136
137 Args:
138 user_data_dir (str, optional): Directory for user data. Defaults to current value.
139 headless (bool, optional): Whether to run the browser in headless mode. Defaults to current value.
140 virtual_display (bool, optional): Whether to use a virtual display. Defaults to current value.
114 141
115 :param user_data_dir: Directory for user data.
116 :param headless: Boolean indicating whether to run the browser in headless mode.
117 :param virtual_display: Boolean indicating whether to use a virtual display.
118 :return: An instance of WebDriver.
142 Returns:
143 WebDriver: The reopened WebDriver instance.
119 144 """
120 user_data_dir = user_data_dir or self.user_data_dir
145 user_data_dir = user_data_data_dir or self.user_data_dir
121 146 if self.default_driver:
122 147 self.default_driver.quit()
123 148 if not virtual_display and self.virtual_display:
@@ -128,8 +153,10 @@ class WebDriverSession:
128 153
129 154 def __enter__(self) -> WebDriver:
130 155 """
131 Context management method for entering a session.
132 :return: An instance of WebDriver.
156 Context management method for entering a session. Initializes and returns a WebDriver instance.
157
158 Returns:
159 WebDriver: An instance of WebDriver for this session.
133 160 """
134 161 if self.webdriver:
135 162 return self.webdriver
@@ -141,6 +168,14 @@ class WebDriverSession:
141 168 def __exit__(self, exc_type, exc_val, exc_tb):
142 169 """
143 170 Context management method for exiting a session. Closes and quits the WebDriver.
171
172 Args:
173 exc_type: Exception type.
174 exc_val: Exception value.
175 exc_tb: Exception traceback.
176
177 Note:
178 Closes the WebDriver and stops the virtual display if used.
144 179 """
145 180 if self.default_driver:
146 181 try: