parsek-cdp-server 0.1.1__tar.gz → 0.1.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.
- {parsek_cdp_server-0.1.1 → parsek_cdp_server-0.1.2}/PKG-INFO +2 -2
- {parsek_cdp_server-0.1.1 → parsek_cdp_server-0.1.2}/parsek_cdp_server/launcher.py +14 -27
- {parsek_cdp_server-0.1.1 → parsek_cdp_server-0.1.2}/parsek_cdp_server/proxy.py +10 -13
- {parsek_cdp_server-0.1.1 → parsek_cdp_server-0.1.2}/parsek_cdp_server.egg-info/PKG-INFO +2 -2
- {parsek_cdp_server-0.1.1 → parsek_cdp_server-0.1.2}/parsek_cdp_server.egg-info/requires.txt +1 -1
- {parsek_cdp_server-0.1.1 → parsek_cdp_server-0.1.2}/pyproject.toml +2 -2
- {parsek_cdp_server-0.1.1 → parsek_cdp_server-0.1.2}/LICENSE +0 -0
- {parsek_cdp_server-0.1.1 → parsek_cdp_server-0.1.2}/README.md +0 -0
- {parsek_cdp_server-0.1.1 → parsek_cdp_server-0.1.2}/parsek_cdp_server/__init__.py +0 -0
- {parsek_cdp_server-0.1.1 → parsek_cdp_server-0.1.2}/parsek_cdp_server/metrics.py +0 -0
- {parsek_cdp_server-0.1.1 → parsek_cdp_server-0.1.2}/parsek_cdp_server/py.typed +0 -0
- {parsek_cdp_server-0.1.1 → parsek_cdp_server-0.1.2}/parsek_cdp_server/reaper.py +0 -0
- {parsek_cdp_server-0.1.1 → parsek_cdp_server-0.1.2}/parsek_cdp_server/supervisor.py +0 -0
- {parsek_cdp_server-0.1.1 → parsek_cdp_server-0.1.2}/parsek_cdp_server.egg-info/SOURCES.txt +0 -0
- {parsek_cdp_server-0.1.1 → parsek_cdp_server-0.1.2}/parsek_cdp_server.egg-info/dependency_links.txt +0 -0
- {parsek_cdp_server-0.1.1 → parsek_cdp_server-0.1.2}/parsek_cdp_server.egg-info/top_level.txt +0 -0
- {parsek_cdp_server-0.1.1 → parsek_cdp_server-0.1.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: parsek-cdp-server
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Parsek CDP server — launch, supervise and proxy a browser; server-side feature producers.
|
|
5
5
|
Author: xa1era
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -8,7 +8,7 @@ Project-URL: Repository, https://github.com/xa1era/parsek-cdp
|
|
|
8
8
|
Requires-Python: >=3.13
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
10
|
License-File: LICENSE
|
|
11
|
-
Requires-Dist: parsek-cdp~=0.1.
|
|
11
|
+
Requires-Dist: parsek-cdp~=0.1.2
|
|
12
12
|
Requires-Dist: aiohttp>=3.9
|
|
13
13
|
Requires-Dist: psutil>=5
|
|
14
14
|
Requires-Dist: prometheus-client>=0.19
|
|
@@ -18,11 +18,11 @@ import shutil
|
|
|
18
18
|
import tempfile
|
|
19
19
|
import urllib.error
|
|
20
20
|
import urllib.request
|
|
21
|
-
from dataclasses import dataclass, field
|
|
22
21
|
from pathlib import Path
|
|
23
22
|
from typing import List, Optional
|
|
24
23
|
|
|
25
24
|
import psutil
|
|
25
|
+
from parsek_cdp.core.browser import LaunchOptions
|
|
26
26
|
|
|
27
27
|
#: Env var naming *directories* to search for browser binaries,
|
|
28
28
|
#: ``os.pathsep``-separated (like ``PATH``), e.g. ``/opt/browsers/chrome:/opt/browsers/brave``.
|
|
@@ -102,27 +102,26 @@ def _is_browser(filename: str) -> bool:
|
|
|
102
102
|
|
|
103
103
|
|
|
104
104
|
def _find_in_dirs(dirs: List[str]) -> List[str]:
|
|
105
|
-
"""
|
|
105
|
+
"""Collect browser executables directly under ``dirs`` (non-recursive)."""
|
|
106
106
|
found: List[str] = []
|
|
107
107
|
for directory in dirs:
|
|
108
108
|
directory = directory.strip()
|
|
109
109
|
if not directory or not os.path.isdir(directory):
|
|
110
110
|
continue
|
|
111
|
-
for
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
found.append(full)
|
|
111
|
+
for filename in os.listdir(directory):
|
|
112
|
+
if not _is_browser(filename):
|
|
113
|
+
continue
|
|
114
|
+
full = os.path.join(directory, filename)
|
|
115
|
+
if os.path.isfile(full) and os.access(full, os.X_OK):
|
|
116
|
+
found.append(full)
|
|
118
117
|
return found
|
|
119
118
|
|
|
120
119
|
|
|
121
120
|
def _detect_executable() -> str:
|
|
122
121
|
"""Pick a Chromium-based binary, or raise if none is available.
|
|
123
122
|
|
|
124
|
-
Precedence: a random browser executable found
|
|
125
|
-
|
|
123
|
+
Precedence: a random browser executable found directly in the directories
|
|
124
|
+
listed in :data:`CHROMES_PATH_ENV` (defaulting to :data:`_KNOWN_PATHS`),
|
|
126
125
|
else the first :data:`_CANDIDATES` name resolvable on ``PATH``.
|
|
127
126
|
"""
|
|
128
127
|
raw = os.environ.get(CHROMES_PATH_ENV)
|
|
@@ -140,18 +139,6 @@ def _detect_executable() -> str:
|
|
|
140
139
|
)
|
|
141
140
|
|
|
142
141
|
|
|
143
|
-
@dataclass
|
|
144
|
-
class LaunchOptions:
|
|
145
|
-
"""How to start the browser."""
|
|
146
|
-
|
|
147
|
-
executable: Optional[str] = None # autodetect if None
|
|
148
|
-
headless: bool = True
|
|
149
|
-
port: int = 0
|
|
150
|
-
user_data_dir: Optional[str] = None
|
|
151
|
-
extra_args: List[str] = field(default_factory=list)
|
|
152
|
-
default_args: bool = True
|
|
153
|
-
|
|
154
|
-
|
|
155
142
|
class ChromeLauncher:
|
|
156
143
|
"""Spawn a browser process and expose its browser-level websocket url.
|
|
157
144
|
|
|
@@ -173,8 +160,6 @@ class ChromeLauncher:
|
|
|
173
160
|
#: Temp profile dir we created (and must clean up); None if caller supplied one.
|
|
174
161
|
self._owned_user_data_dir: Optional[str] = None
|
|
175
162
|
|
|
176
|
-
# -- argv -------------------------------------------------------------- #
|
|
177
|
-
|
|
178
163
|
def _resolve_user_data_dir(self) -> str:
|
|
179
164
|
if self.options.user_data_dir is not None:
|
|
180
165
|
return self.options.user_data_dir
|
|
@@ -185,7 +170,7 @@ class ChromeLauncher:
|
|
|
185
170
|
def _build_argv(self, user_data_dir: str) -> List[str]:
|
|
186
171
|
executable = self.options.executable or _detect_executable()
|
|
187
172
|
argv = [executable, f"--remote-debugging-port={self.options.port}"]
|
|
188
|
-
if self.options.
|
|
173
|
+
if self.options.use_default_args:
|
|
189
174
|
argv += [
|
|
190
175
|
f"--user-data-dir={user_data_dir}",
|
|
191
176
|
"--no-first-run",
|
|
@@ -286,7 +271,9 @@ class ChromeLauncher:
|
|
|
286
271
|
with _suppress_lookup():
|
|
287
272
|
p.terminate() # SIGTERM
|
|
288
273
|
# psutil.wait_procs is blocking, so run it off the event loop.
|
|
289
|
-
_gone, alive = await asyncio.to_thread(
|
|
274
|
+
_gone, alive = await asyncio.to_thread(
|
|
275
|
+
psutil.wait_procs, procs, timeout=timeout
|
|
276
|
+
)
|
|
290
277
|
for p in alive:
|
|
291
278
|
with _suppress_lookup():
|
|
292
279
|
p.kill() # SIGKILL
|
|
@@ -35,12 +35,12 @@ from typing import Awaitable, Callable, Dict, List, Optional, Set, Tuple, Type
|
|
|
35
35
|
import websockets
|
|
36
36
|
from aiohttp import web
|
|
37
37
|
from parsek_cdp._logging import get_logger
|
|
38
|
+
from parsek_cdp.core.browser import LaunchOptions
|
|
38
39
|
from parsek_cdp.core.feature import Feature
|
|
39
40
|
from parsek_cdp.core.target import ProtocolError
|
|
40
41
|
from parsek_cdp.parsek import BrowserState
|
|
41
42
|
from parsek_cdp.parsek.events import BrowserStateChanged
|
|
42
43
|
|
|
43
|
-
from .launcher import LaunchOptions
|
|
44
44
|
from .metrics import ServerMetrics
|
|
45
45
|
from .reaper import spawn_reaper
|
|
46
46
|
from .supervisor import BrowserSupervisor
|
|
@@ -333,17 +333,8 @@ class ParsekServer:
|
|
|
333
333
|
body = await request.json() if request.can_read_body else {}
|
|
334
334
|
except Exception:
|
|
335
335
|
body = {}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
"headless",
|
|
339
|
-
"port",
|
|
340
|
-
"user_data_dir",
|
|
341
|
-
"extra_args",
|
|
342
|
-
"default_args",
|
|
343
|
-
}
|
|
344
|
-
kwargs = {k: body[k] for k in allowed if k in body}
|
|
345
|
-
if kwargs:
|
|
346
|
-
return LaunchOptions(**kwargs)
|
|
336
|
+
if body:
|
|
337
|
+
return LaunchOptions.from_json(body)
|
|
347
338
|
return self.launch_options
|
|
348
339
|
|
|
349
340
|
async def _idle_timeout(self, request: web.Request) -> Optional[float]:
|
|
@@ -389,7 +380,9 @@ class ParsekServer:
|
|
|
389
380
|
)
|
|
390
381
|
try:
|
|
391
382
|
await self._pipe(
|
|
392
|
-
ws,
|
|
383
|
+
ws,
|
|
384
|
+
supervisor.ws_url,
|
|
385
|
+
on_client_frame=self._on_control_frame(browser_uuid),
|
|
393
386
|
)
|
|
394
387
|
finally:
|
|
395
388
|
clients.discard(ws)
|
|
@@ -404,6 +397,7 @@ class ParsekServer:
|
|
|
404
397
|
process exit is recorded as an intentional ``CLOSED`` rather than a crash
|
|
405
398
|
to be relaunched.
|
|
406
399
|
"""
|
|
400
|
+
|
|
407
401
|
async def handle(data: str) -> None:
|
|
408
402
|
# Cheap substring gate first -- only bother parsing the frame that
|
|
409
403
|
# could actually be the close request, not every control message.
|
|
@@ -440,8 +434,10 @@ class ParsekServer:
|
|
|
440
434
|
feats = self._browser_features.get(browser_uuid, self.features)
|
|
441
435
|
on_event = None
|
|
442
436
|
if self.metrics is not None:
|
|
437
|
+
|
|
443
438
|
def on_event(domain: str) -> None:
|
|
444
439
|
self.metrics.record_event(browser_uuid, target_id, domain)
|
|
440
|
+
|
|
445
441
|
bridge = PageBridge(
|
|
446
442
|
ws,
|
|
447
443
|
supervisor.target_ws_url(target_id),
|
|
@@ -510,6 +506,7 @@ class ParsekServer:
|
|
|
510
506
|
supervisor needing a back-reference to the server. Idempotent, so a
|
|
511
507
|
``CLOSED`` also driven by :meth:`_graceful_close`/:meth:`stop` is harmless.
|
|
512
508
|
"""
|
|
509
|
+
|
|
513
510
|
def callback(state, reason=None) -> None:
|
|
514
511
|
if state is BrowserState.CLOSED:
|
|
515
512
|
self.supervisors.pop(browser_uuid, None)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: parsek-cdp-server
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Parsek CDP server — launch, supervise and proxy a browser; server-side feature producers.
|
|
5
5
|
Author: xa1era
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -8,7 +8,7 @@ Project-URL: Repository, https://github.com/xa1era/parsek-cdp
|
|
|
8
8
|
Requires-Python: >=3.13
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
10
|
License-File: LICENSE
|
|
11
|
-
Requires-Dist: parsek-cdp~=0.1.
|
|
11
|
+
Requires-Dist: parsek-cdp~=0.1.2
|
|
12
12
|
Requires-Dist: aiohttp>=3.9
|
|
13
13
|
Requires-Dist: psutil>=5
|
|
14
14
|
Requires-Dist: prometheus-client>=0.19
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "parsek-cdp-server"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.2"
|
|
8
8
|
description = "Parsek CDP server — launch, supervise and proxy a browser; server-side feature producers."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.13"
|
|
@@ -13,7 +13,7 @@ license = "Apache-2.0"
|
|
|
13
13
|
dependencies = [
|
|
14
14
|
# Reuses the shared layers (cdp/, core/, parsek/) from the client distribution,
|
|
15
15
|
# including internal modules -- so track the client's patch line (>=0.1.0,<0.2.0).
|
|
16
|
-
"parsek-cdp~=0.1.
|
|
16
|
+
"parsek-cdp~=0.1.2",
|
|
17
17
|
"aiohttp>=3.9", # ws + HTTP endpoints (/cdp/{browser}/control, /cdp/{browser}/page/{target}, /browsers)
|
|
18
18
|
"psutil>=5", # watchdog over the Chrome process + zombie reaper
|
|
19
19
|
"prometheus-client>=0.19", # /metrics exposition
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{parsek_cdp_server-0.1.1 → parsek_cdp_server-0.1.2}/parsek_cdp_server.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{parsek_cdp_server-0.1.1 → parsek_cdp_server-0.1.2}/parsek_cdp_server.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|