pydoll-python 2.8.1__tar.gz → 2.8.2__tar.gz

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.
Files changed (86) hide show
  1. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/PKG-INFO +1 -1
  2. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/browser/chromium/base.py +112 -4
  3. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/browser/tab.py +33 -3
  4. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/elements/mixins/find_elements_mixin.py +5 -5
  5. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/exceptions.py +6 -0
  6. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pyproject.toml +1 -1
  7. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/LICENSE +0 -0
  8. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/README.md +0 -0
  9. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/__init__.py +0 -0
  10. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/browser/__init__.py +0 -0
  11. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/browser/chromium/__init__.py +0 -0
  12. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/browser/chromium/chrome.py +0 -0
  13. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/browser/chromium/edge.py +0 -0
  14. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/browser/interfaces.py +0 -0
  15. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/browser/managers/__init__.py +0 -0
  16. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/browser/managers/browser_options_manager.py +0 -0
  17. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/browser/managers/browser_process_manager.py +0 -0
  18. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/browser/managers/proxy_manager.py +0 -0
  19. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/browser/managers/temp_dir_manager.py +0 -0
  20. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/browser/options.py +0 -0
  21. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/browser/requests/__init__.py +0 -0
  22. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/browser/requests/request.py +0 -0
  23. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/browser/requests/response.py +0 -0
  24. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/commands/__init__.py +0 -0
  25. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/commands/browser_commands.py +0 -0
  26. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/commands/dom_commands.py +0 -0
  27. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/commands/fetch_commands.py +0 -0
  28. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/commands/input_commands.py +0 -0
  29. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/commands/network_commands.py +0 -0
  30. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/commands/page_commands.py +0 -0
  31. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/commands/runtime_commands.py +0 -0
  32. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/commands/storage_commands.py +0 -0
  33. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/commands/target_commands.py +0 -0
  34. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/connection/__init__.py +0 -0
  35. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/connection/connection_handler.py +0 -0
  36. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/connection/managers/__init__.py +0 -0
  37. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/connection/managers/commands_manager.py +0 -0
  38. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/connection/managers/events_manager.py +0 -0
  39. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/constants.py +0 -0
  40. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/elements/__init__.py +0 -0
  41. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/elements/mixins/__init__.py +0 -0
  42. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/elements/web_element.py +0 -0
  43. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/__init__.py +0 -0
  44. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/base.py +0 -0
  45. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/browser/__init__.py +0 -0
  46. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/browser/events.py +0 -0
  47. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/browser/methods.py +0 -0
  48. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/browser/types.py +0 -0
  49. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/debugger/types.py +0 -0
  50. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/dom/__init__.py +0 -0
  51. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/dom/events.py +0 -0
  52. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/dom/methods.py +0 -0
  53. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/dom/types.py +0 -0
  54. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/emulation/types.py +0 -0
  55. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/fetch/__init__.py +0 -0
  56. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/fetch/events.py +0 -0
  57. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/fetch/methods.py +0 -0
  58. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/fetch/types.py +0 -0
  59. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/input/__init__.py +0 -0
  60. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/input/events.py +0 -0
  61. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/input/methods.py +0 -0
  62. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/input/types.py +0 -0
  63. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/io/types.py +0 -0
  64. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/network/__init__.py +0 -0
  65. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/network/events.py +0 -0
  66. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/network/methods.py +0 -0
  67. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/network/types.py +0 -0
  68. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/page/__init__.py +0 -0
  69. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/page/events.py +0 -0
  70. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/page/methods.py +0 -0
  71. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/page/types.py +0 -0
  72. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/runtime/__init__.py +0 -0
  73. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/runtime/events.py +0 -0
  74. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/runtime/methods.py +0 -0
  75. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/runtime/types.py +0 -0
  76. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/security/types.py +0 -0
  77. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/storage/__init__.py +0 -0
  78. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/storage/events.py +0 -0
  79. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/storage/methods.py +0 -0
  80. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/storage/types.py +0 -0
  81. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/target/__init__.py +0 -0
  82. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/target/events.py +0 -0
  83. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/target/methods.py +0 -0
  84. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/protocol/target/types.py +0 -0
  85. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/py.typed +0 -0
  86. {pydoll_python-2.8.1 → pydoll_python-2.8.2}/pydoll/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pydoll-python
3
- Version: 2.8.1
3
+ Version: 2.8.2
4
4
  Summary: Pydoll is a library for automating chromium-based browsers without a WebDriver, offering realistic interactions.
5
5
  License-File: LICENSE
6
6
  Author: Thalison Fernandes
@@ -9,6 +9,7 @@ from functools import partial
9
9
  from random import randint
10
10
  from tempfile import TemporaryDirectory
11
11
  from typing import Any, Awaitable, Callable, Optional, overload
12
+ from urllib.parse import urlsplit, urlunsplit
12
13
 
13
14
  from pydoll.browser.interfaces import BrowserOptionsManager
14
15
  from pydoll.browser.managers import (
@@ -93,6 +94,7 @@ class Browser(ABC): # noqa: PLR0904
93
94
  self._connection_handler = ConnectionHandler(self._connection_port)
94
95
  self._backup_preferences_dir = ''
95
96
  self._tabs_opened: dict[str, Tab] = {}
97
+ self._context_proxy_auth: dict[str, tuple[str, str]] = {}
96
98
 
97
99
  async def __aenter__(self) -> 'Browser':
98
100
  """Async context manager entry."""
@@ -203,13 +205,22 @@ class Browser(ABC): # noqa: PLR0904
203
205
  Returns:
204
206
  Browser context ID for use with other methods.
205
207
  """
208
+ # If proxy_server contains credentials, strip them and store per-context auth
209
+ sanitized_proxy = proxy_server
210
+ extracted_auth: Optional[tuple[str, str]] = None
211
+ if proxy_server:
212
+ sanitized_proxy, extracted_auth = self._sanitize_proxy_and_extract_auth(proxy_server)
213
+
206
214
  response: CreateBrowserContextResponse = await self._execute_command(
207
215
  TargetCommands.create_browser_context(
208
- proxy_server=proxy_server,
216
+ proxy_server=sanitized_proxy,
209
217
  proxy_bypass_list=proxy_bypass_list,
210
218
  )
211
219
  )
212
- return response['result']['browserContextId']
220
+ context_id = response['result']['browserContextId']
221
+ if extracted_auth:
222
+ self._context_proxy_auth[context_id] = extracted_auth
223
+ return context_id
213
224
 
214
225
  async def delete_browser_context(self, browser_context_id: str):
215
226
  """
@@ -251,8 +262,8 @@ class Browser(ABC): # noqa: PLR0904
251
262
  target_id = response['result']['targetId']
252
263
  tab = Tab(self, **self._get_tab_kwargs(target_id, browser_context_id))
253
264
  self._tabs_opened[target_id] = tab
254
- if url:
255
- await tab.go_to(url)
265
+ await self._setup_context_proxy_auth_for_tab(tab, browser_context_id)
266
+ if url: await tab.go_to(url)
256
267
  return tab
257
268
 
258
269
  async def get_targets(self) -> list[TargetInfo]:
@@ -577,6 +588,60 @@ class Browser(ABC): # noqa: PLR0904
577
588
  await self.disable_fetch_events()
578
589
  return response
579
590
 
591
+ @staticmethod
592
+ async def _tab_continue_request_callback(event: RequestPausedEvent, tab: Tab):
593
+ """Internal callback to continue paused requests at Tab level."""
594
+ request_id = event['params']['requestId']
595
+ return await tab.continue_request(request_id)
596
+
597
+ @staticmethod
598
+ async def _tab_continue_request_with_auth_callback(
599
+ event: RequestPausedEvent,
600
+ tab: Tab,
601
+ proxy_username: Optional[str],
602
+ proxy_password: Optional[str],
603
+ ):
604
+ """Internal callback for proxy/server authentication at Tab level."""
605
+ request_id = event['params']['requestId']
606
+ response: Response = await tab.continue_with_auth(
607
+ request_id=request_id,
608
+ auth_challenge_response=AuthChallengeResponseType.PROVIDE_CREDENTIALS,
609
+ proxy_username=proxy_username,
610
+ proxy_password=proxy_password,
611
+ )
612
+ await tab.disable_fetch_events()
613
+ return response
614
+
615
+ async def _setup_context_proxy_auth_for_tab(
616
+ self, tab: Tab, browser_context_id: Optional[str]
617
+ ) -> None:
618
+ """Enable proxy auth handling for a Tab if its context has credentials stored."""
619
+ if not browser_context_id:
620
+ return
621
+ creds = self._context_proxy_auth.get(browser_context_id)
622
+ if not creds:
623
+ return
624
+ username, password = creds
625
+ await tab.enable_fetch_events(handle_auth=True)
626
+ await tab.on(
627
+ FetchEvent.REQUEST_PAUSED,
628
+ partial(
629
+ self._tab_continue_request_callback,
630
+ tab=tab,
631
+ ),
632
+ temporary=True,
633
+ )
634
+ await tab.on(
635
+ FetchEvent.AUTH_REQUIRED,
636
+ partial(
637
+ self._tab_continue_request_with_auth_callback,
638
+ tab=tab,
639
+ proxy_username=username,
640
+ proxy_password=password,
641
+ ),
642
+ temporary=True,
643
+ )
644
+
580
645
  async def _verify_browser_running(self):
581
646
  """
582
647
  Verify browser started successfully.
@@ -763,6 +828,49 @@ class Browser(ABC): # noqa: PLR0904
763
828
  ws_domain = '/'.join(self._ws_address.split('/')[:3])
764
829
  return f'{ws_domain}/devtools/page/{tab_id}'
765
830
 
831
+ @staticmethod
832
+ def _sanitize_proxy_and_extract_auth(
833
+ proxy_server: str,
834
+ ) -> tuple[str, Optional[tuple[str, str]]]:
835
+ """Strip credentials from a proxy URL and return sanitized URL plus (user, pass).
836
+
837
+ Accepts inputs like:
838
+ - username:password@host:port
839
+ - http://username:password@host:port
840
+ - socks5://username:password@host:port
841
+ - host:port (no credentials)
842
+ Returns a (sanitized_proxy, (user, pass) | None).
843
+ Ensures scheme is present in the sanitized URL (defaults to http).
844
+ """
845
+ base = proxy_server if '://' in proxy_server else f'http://{proxy_server}'
846
+ parts = urlsplit(base)
847
+ netloc = parts.netloc
848
+ creds: Optional[tuple[str, str]] = None
849
+ if '@' in netloc:
850
+ cred_part, host_part = netloc.split('@', 1)
851
+ if ':' in cred_part:
852
+ user, pwd = cred_part.split(':', 1)
853
+ else:
854
+ user, pwd = cred_part, ''
855
+ creds = (user, pwd)
856
+ sanitized = urlunsplit((
857
+ parts.scheme,
858
+ host_part,
859
+ parts.path,
860
+ parts.query,
861
+ parts.fragment,
862
+ ))
863
+ else:
864
+ # No creds; ensure scheme
865
+ sanitized = urlunsplit((
866
+ parts.scheme,
867
+ parts.netloc,
868
+ parts.path,
869
+ parts.query,
870
+ parts.fragment,
871
+ ))
872
+ return sanitized, creds
873
+
766
874
  @abstractmethod
767
875
  def _get_default_binary_location(self) -> str:
768
876
  """Get default browser executable path (implemented by subclasses)."""
@@ -46,6 +46,7 @@ from pydoll.exceptions import (
46
46
  NoDialogPresent,
47
47
  NotAnIFrame,
48
48
  PageLoadTimeout,
49
+ TopLevelTargetRequired,
49
50
  WaitElementTimeout,
50
51
  )
51
52
  from pydoll.protocol.base import EmptyResponse, Response
@@ -55,7 +56,7 @@ from pydoll.protocol.browser.events import (
55
56
  DownloadWillBeginEvent,
56
57
  )
57
58
  from pydoll.protocol.browser.types import DownloadBehavior, DownloadProgressState
58
- from pydoll.protocol.fetch.types import HeaderEntry, RequestStage
59
+ from pydoll.protocol.fetch.types import AuthChallengeResponseType, HeaderEntry, RequestStage
59
60
  from pydoll.protocol.network.events import RequestWillBeSentEvent
60
61
  from pydoll.protocol.network.types import (
61
62
  Cookie,
@@ -252,7 +253,7 @@ class Tab(FindElementsMixin):
252
253
  async def enable_auto_solve_cloudflare_captcha(
253
254
  self,
254
255
  custom_selector: Optional[tuple[By, str]] = None,
255
- time_before_click: int = 2,
256
+ time_before_click: int = 5,
256
257
  time_to_wait_captcha: int = 5,
257
258
  ):
258
259
  """
@@ -527,7 +528,15 @@ class Tab(FindElementsMixin):
527
528
  capture_beyond_viewport=beyond_viewport,
528
529
  )
529
530
  )
530
- screenshot_data = response['result']['data']
531
+
532
+ try:
533
+ screenshot_data = response['result']['data']
534
+ except KeyError:
535
+ raise TopLevelTargetRequired(
536
+ 'Command can only be executed on top-level targets. Please use '
537
+ 'take_screenshot method on the WebElement object instead.'
538
+ )
539
+
531
540
  if as_base64:
532
541
  return screenshot_data
533
542
 
@@ -700,6 +709,27 @@ class Tab(FindElementsMixin):
700
709
  )
701
710
  )
702
711
 
712
+ async def continue_with_auth(
713
+ self,
714
+ request_id: str,
715
+ auth_challenge_response: AuthChallengeResponseType,
716
+ proxy_username: Optional[str] = None,
717
+ proxy_password: Optional[str] = None,
718
+ ):
719
+ """Continue a paused request replying to an authentication challenge.
720
+
721
+ Useful for proxy auth (407) or server auth (401) when Fetch is enabled
722
+ with handle_auth=True.
723
+ """
724
+ return await self._execute_command(
725
+ FetchCommands.continue_request_with_auth(
726
+ request_id=request_id,
727
+ auth_challenge_response=auth_challenge_response,
728
+ proxy_username=proxy_username,
729
+ proxy_password=proxy_password,
730
+ )
731
+ )
732
+
703
733
  @asynccontextmanager
704
734
  async def expect_file_chooser(
705
735
  self, files: Union[str, Path, list[Union[str, Path]]]
@@ -59,7 +59,7 @@ class FindElementsMixin:
59
59
  timeout: int = ...,
60
60
  find_all: Literal[False] = False,
61
61
  raise_exc: Literal[True] = True,
62
- **attributes: dict[str, str],
62
+ **attributes,
63
63
  ) -> 'WebElement': ...
64
64
 
65
65
  @overload
@@ -73,7 +73,7 @@ class FindElementsMixin:
73
73
  timeout: int = ...,
74
74
  find_all: Literal[True] = True,
75
75
  raise_exc: Literal[True] = True,
76
- **attributes: dict[str, str],
76
+ **attributes,
77
77
  ) -> list['WebElement']: ...
78
78
 
79
79
  @overload
@@ -87,7 +87,7 @@ class FindElementsMixin:
87
87
  timeout: int = ...,
88
88
  find_all: Literal[True] = True,
89
89
  raise_exc: Literal[False] = False,
90
- **attributes: dict[str, str],
90
+ **attributes,
91
91
  ) -> Optional[list['WebElement']]: ...
92
92
 
93
93
  @overload
@@ -101,7 +101,7 @@ class FindElementsMixin:
101
101
  timeout: int = ...,
102
102
  find_all: Literal[False] = False,
103
103
  raise_exc: Literal[False] = False,
104
- **attributes: dict[str, str],
104
+ **attributes,
105
105
  ) -> Optional['WebElement']: ...
106
106
 
107
107
  @overload
@@ -115,7 +115,7 @@ class FindElementsMixin:
115
115
  timeout: int = ...,
116
116
  find_all: bool = ...,
117
117
  raise_exc: bool = ...,
118
- **attributes: dict[str, str],
118
+ **attributes,
119
119
  ) -> Union['WebElement', list['WebElement'], None]: ...
120
120
 
121
121
  async def find(
@@ -103,6 +103,12 @@ class ProtocolException(PydollException):
103
103
  message = 'A protocol error occurred'
104
104
 
105
105
 
106
+ class TopLevelTargetRequired(ProtocolException):
107
+ """Raised when a command can only be executed on top-level targets."""
108
+
109
+ message = 'Command can only be executed on top-level targets.'
110
+
111
+
106
112
  class InvalidCommand(ProtocolException):
107
113
  """Raised when an invalid command is sent to the browser."""
108
114
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "pydoll-python"
3
- version = "2.8.1"
3
+ version = "2.8.2"
4
4
  description = "Pydoll is a library for automating chromium-based browsers without a WebDriver, offering realistic interactions."
5
5
  authors = ["Thalison Fernandes <thalissfernandes99@gmail.com>"]
6
6
  readme = "README.md"
File without changes
File without changes