pydoll-python 2.1.0__tar.gz → 2.2.0__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 (95) hide show
  1. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/PKG-INFO +4 -4
  2. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/README.md +2 -2
  3. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/browser/chromium/base.py +20 -0
  4. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/browser/tab.py +88 -12
  5. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pyproject.toml +3 -3
  6. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/LICENSE +0 -0
  7. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/__init__.py +0 -0
  8. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/browser/__init__.py +0 -0
  9. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/browser/chromium/__init__.py +0 -0
  10. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/browser/chromium/chrome.py +0 -0
  11. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/browser/chromium/edge.py +0 -0
  12. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/browser/interfaces.py +0 -0
  13. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/browser/managers/__init__.py +0 -0
  14. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/browser/managers/browser_options_manager.py +0 -0
  15. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/browser/managers/browser_process_manager.py +0 -0
  16. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/browser/managers/proxy_manager.py +0 -0
  17. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/browser/managers/temp_dir_manager.py +0 -0
  18. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/browser/options.py +0 -0
  19. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/commands/__init__.py +0 -0
  20. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/commands/browser_commands.py +0 -0
  21. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/commands/dom_commands.py +0 -0
  22. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/commands/fetch_commands.py +0 -0
  23. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/commands/input_commands.py +0 -0
  24. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/commands/network_commands.py +0 -0
  25. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/commands/page_commands.py +0 -0
  26. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/commands/runtime_commands.py +0 -0
  27. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/commands/storage_commands.py +0 -0
  28. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/commands/target_commands.py +0 -0
  29. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/connection/__init__.py +0 -0
  30. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/connection/connection_handler.py +0 -0
  31. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/connection/managers/__init__.py +0 -0
  32. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/connection/managers/commands_manager.py +0 -0
  33. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/connection/managers/events_manager.py +0 -0
  34. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/constants.py +0 -0
  35. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/elements/__init__.py +0 -0
  36. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/elements/mixins/__init__.py +0 -0
  37. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/elements/mixins/find_elements_mixin.py +0 -0
  38. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/elements/web_element.py +0 -0
  39. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/exceptions.py +0 -0
  40. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/__init__.py +0 -0
  41. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/base.py +0 -0
  42. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/browser/__init__.py +0 -0
  43. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/browser/events.py +0 -0
  44. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/browser/methods.py +0 -0
  45. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/browser/params.py +0 -0
  46. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/browser/responses.py +0 -0
  47. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/browser/types.py +0 -0
  48. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/dom/__init__.py +0 -0
  49. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/dom/events.py +0 -0
  50. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/dom/methods.py +0 -0
  51. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/dom/params.py +0 -0
  52. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/dom/responses.py +0 -0
  53. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/dom/types.py +0 -0
  54. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/fetch/__init__.py +0 -0
  55. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/fetch/events.py +0 -0
  56. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/fetch/methods.py +0 -0
  57. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/fetch/params.py +0 -0
  58. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/fetch/responses.py +0 -0
  59. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/fetch/types.py +0 -0
  60. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/input/__init__.py +0 -0
  61. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/input/events.py +0 -0
  62. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/input/methods.py +0 -0
  63. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/input/params.py +0 -0
  64. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/input/types.py +0 -0
  65. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/network/__init__.py +0 -0
  66. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/network/events.py +0 -0
  67. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/network/methods.py +0 -0
  68. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/network/params.py +0 -0
  69. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/network/responses.py +0 -0
  70. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/network/types.py +0 -0
  71. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/page/__init__.py +0 -0
  72. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/page/events.py +0 -0
  73. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/page/methods.py +0 -0
  74. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/page/params.py +0 -0
  75. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/page/responses.py +0 -0
  76. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/page/types.py +0 -0
  77. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/runtime/__init__.py +0 -0
  78. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/runtime/events.py +0 -0
  79. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/runtime/methods.py +0 -0
  80. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/runtime/params.py +0 -0
  81. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/runtime/responses.py +0 -0
  82. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/runtime/types.py +0 -0
  83. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/storage/__init__.py +0 -0
  84. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/storage/events.py +0 -0
  85. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/storage/methods.py +0 -0
  86. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/storage/params.py +0 -0
  87. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/storage/responses.py +0 -0
  88. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/storage/types.py +0 -0
  89. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/target/__init__.py +0 -0
  90. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/target/events.py +0 -0
  91. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/target/methods.py +0 -0
  92. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/target/params.py +0 -0
  93. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/target/responses.py +0 -0
  94. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/protocol/target/types.py +0 -0
  95. {pydoll_python-2.1.0 → pydoll_python-2.2.0}/pydoll/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pydoll-python
3
- Version: 2.1.0
3
+ Version: 2.2.0
4
4
  Summary:
5
5
  Author: Thalison Fernandes
6
6
  Author-email: thalissfernandes99@gmail.com
@@ -12,7 +12,7 @@ Classifier: Programming Language :: Python :: 3.12
12
12
  Classifier: Programming Language :: Python :: 3.13
13
13
  Requires-Dist: aiofiles (>=23.2.1,<24.0.0)
14
14
  Requires-Dist: aiohttp (>=3.9.5,<4.0.0)
15
- Requires-Dist: bs4 (>=0.0.2,<0.0.3)
15
+ Requires-Dist: beautifulsoup4 (>=4.12.3,<5.0.0)
16
16
  Requires-Dist: mkdocstrings (>=0.29.1,<0.30.0)
17
17
  Requires-Dist: websockets (>=13.1,<14.0)
18
18
  Description-Content-Type: text/markdown
@@ -22,8 +22,8 @@ Description-Content-Type: text/markdown
22
22
  </p>
23
23
 
24
24
  <p align="center">
25
- <a href="https://codecov.io/gh/autoscrape-labs/pydoll">
26
- <img src="https://codecov.io/gh/autoscrape-labs/pydoll/graph/badge.svg?token=40I938OGM9"/>
25
+ <a href="https://codecov.io/gh/autoscrape-labs/pydoll" >
26
+ <img src="https://codecov.io/gh/autoscrape-labs/pydoll/graph/badge.svg?token=40I938OGM9"/>
27
27
  </a>
28
28
  <img src="https://github.com/thalissonvs/pydoll/actions/workflows/tests.yml/badge.svg" alt="Tests">
29
29
  <img src="https://github.com/thalissonvs/pydoll/actions/workflows/ruff-ci.yml/badge.svg" alt="Ruff CI">
@@ -3,8 +3,8 @@
3
3
  </p>
4
4
 
5
5
  <p align="center">
6
- <a href="https://codecov.io/gh/autoscrape-labs/pydoll">
7
- <img src="https://codecov.io/gh/autoscrape-labs/pydoll/graph/badge.svg?token=40I938OGM9"/>
6
+ <a href="https://codecov.io/gh/autoscrape-labs/pydoll" >
7
+ <img src="https://codecov.io/gh/autoscrape-labs/pydoll/graph/badge.svg?token=40I938OGM9"/>
8
8
  </a>
9
9
  <img src="https://github.com/thalissonvs/pydoll/actions/workflows/tests.yml/badge.svg" alt="Tests">
10
10
  <img src="https://github.com/thalissonvs/pydoll/actions/workflows/ruff-ci.yml/badge.svg" alt="Ruff CI">
@@ -215,10 +215,30 @@ class Browser(ABC): # noqa: PLR0904
215
215
 
216
216
  Targets include pages, service workers, shared workers, and browser process.
217
217
  Useful for debugging and managing multiple tabs.
218
+
219
+ Returns:
220
+ List of TargetInfo objects.
218
221
  """
219
222
  response: GetTargetsResponse = await self._execute_command(TargetCommands.get_targets())
220
223
  return response['result']['targetInfos']
221
224
 
225
+ async def get_opened_tabs(self) -> list[Tab]:
226
+ """
227
+ Get all opened tabs that are not extensions and have the type 'page'
228
+
229
+ Returns:
230
+ List of Tab instances. The last tab is the most recent one.
231
+ """
232
+ targets = await self.get_targets()
233
+ valid_tab_targets = [
234
+ target for target in targets if target['type'] == 'page'
235
+ and 'extension' not in target['url']
236
+ ]
237
+ return [
238
+ Tab(self, self._connection_port, target['targetId']) for target
239
+ in reversed(valid_tab_targets)
240
+ ]
241
+
222
242
  async def set_download_path(self, path: str, browser_context_id: Optional[str] = None):
223
243
  """Set download directory path (convenience method for set_download_behavior)."""
224
244
  return await self._execute_command(
@@ -68,8 +68,43 @@ class Tab(FindElementsMixin): # noqa: PLR0904
68
68
  Primary interface for web page automation including navigation, DOM manipulation,
69
69
  JavaScript execution, event handling, network monitoring, and specialized tasks
70
70
  like Cloudflare bypass.
71
+
72
+ This class implements a singleton pattern based on target_id to ensure
73
+ only one Tab instance exists per browser tab.
71
74
  """
72
75
 
76
+ _instances: dict[str, 'Tab'] = {}
77
+
78
+ def __new__(
79
+ cls,
80
+ browser: 'Browser',
81
+ connection_port: int,
82
+ target_id: str,
83
+ browser_context_id: Optional[str] = None,
84
+ ) -> 'Tab':
85
+ """
86
+ Create or return existing Tab instance for the given target_id.
87
+
88
+ Args:
89
+ browser: Browser instance that created this tab.
90
+ connection_port: CDP WebSocket port.
91
+ target_id: CDP target identifier for this tab.
92
+ browser_context_id: Optional browser context ID.
93
+
94
+ Returns:
95
+ Tab instance (new or existing) for the target_id.
96
+ """
97
+ if target_id in cls._instances:
98
+ existing_instance = cls._instances[target_id]
99
+ existing_instance._browser = browser
100
+ existing_instance._connection_port = connection_port
101
+ existing_instance._browser_context_id = browser_context_id
102
+ return existing_instance
103
+
104
+ instance = super().__new__(cls)
105
+ cls._instances[target_id] = instance
106
+ return instance
107
+
73
108
  def __init__(
74
109
  self,
75
110
  browser: 'Browser',
@@ -86,18 +121,57 @@ class Tab(FindElementsMixin): # noqa: PLR0904
86
121
  target_id: CDP target identifier for this tab.
87
122
  browser_context_id: Optional browser context ID.
88
123
  """
89
- self._browser = browser
90
- self._connection_port = connection_port
91
- self._target_id = target_id
92
- self._connection_handler = ConnectionHandler(connection_port, self._target_id)
93
- self._page_events_enabled = False
94
- self._network_events_enabled = False
95
- self._fetch_events_enabled = False
96
- self._dom_events_enabled = False
97
- self._runtime_events_enabled = False
98
- self._intercept_file_chooser_dialog_enabled = False
124
+ if hasattr(self, '_initialized') and self._initialized:
125
+ return
126
+
127
+ self._browser: 'Browser' = browser
128
+ self._connection_port: int = connection_port
129
+ self._target_id: str = target_id
130
+ self._connection_handler: ConnectionHandler = ConnectionHandler(
131
+ connection_port, self._target_id
132
+ )
133
+ self._page_events_enabled: bool = False
134
+ self._network_events_enabled: bool = False
135
+ self._fetch_events_enabled: bool = False
136
+ self._dom_events_enabled: bool = False
137
+ self._runtime_events_enabled: bool = False
138
+ self._intercept_file_chooser_dialog_enabled: bool = False
99
139
  self._cloudflare_captcha_callback_id: Optional[int] = None
100
- self._browser_context_id = browser_context_id
140
+ self._browser_context_id: Optional[str] = browser_context_id
141
+ self._initialized: bool = True
142
+
143
+ @classmethod
144
+ def _remove_instance(cls, target_id: str) -> None:
145
+ """
146
+ Remove instance from registry when tab is closed.
147
+
148
+ Args:
149
+ target_id: Target ID to remove from registry.
150
+ """
151
+ cls._instances.pop(target_id, None)
152
+
153
+ @classmethod
154
+ def get_instance(cls, target_id: str) -> Optional['Tab']:
155
+ """
156
+ Get existing Tab instance for target_id if it exists.
157
+
158
+ Args:
159
+ target_id: Target ID to look up.
160
+
161
+ Returns:
162
+ Existing Tab instance or None if not found.
163
+ """
164
+ return cls._instances.get(target_id)
165
+
166
+ @classmethod
167
+ def get_all_instances(cls) -> dict[str, 'Tab']:
168
+ """
169
+ Get all active Tab instances.
170
+
171
+ Returns:
172
+ Dictionary mapping target_id to Tab instances.
173
+ """
174
+ return cls._instances.copy()
101
175
 
102
176
  @property
103
177
  def page_events_enabled(self) -> bool:
@@ -283,7 +357,9 @@ class Tab(FindElementsMixin): # noqa: PLR0904
283
357
  Note:
284
358
  Tab instance becomes invalid after calling this method.
285
359
  """
286
- return await self._execute_command(PageCommands.close())
360
+ result = await self._execute_command(PageCommands.close())
361
+ self._remove_instance(self._target_id)
362
+ return result
287
363
 
288
364
  async def get_frame(self, frame: WebElement) -> IFrame:
289
365
  """
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "pydoll-python"
3
- version = "2.1.0"
3
+ version = "2.2.0"
4
4
  description = ""
5
5
  authors = ["Thalison Fernandes <thalissfernandes99@gmail.com>"]
6
6
  readme = "README.md"
@@ -13,7 +13,7 @@ python = "^3.10"
13
13
  websockets = "^13.1"
14
14
  aiohttp = "^3.9.5"
15
15
  aiofiles = "^23.2.1"
16
- bs4 = "^0.0.2"
16
+ beautifulsoup4 = "^4.12.3"
17
17
  mkdocstrings = "^0.29.1"
18
18
 
19
19
 
@@ -64,4 +64,4 @@ post_test = 'coverage html'
64
64
  [tool.mypy]
65
65
  exclude = [
66
66
  "tests/",
67
- ]
67
+ ]
File without changes