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.
Files changed (17) hide show
  1. {parsek_cdp_server-0.1.1 → parsek_cdp_server-0.1.2}/PKG-INFO +2 -2
  2. {parsek_cdp_server-0.1.1 → parsek_cdp_server-0.1.2}/parsek_cdp_server/launcher.py +14 -27
  3. {parsek_cdp_server-0.1.1 → parsek_cdp_server-0.1.2}/parsek_cdp_server/proxy.py +10 -13
  4. {parsek_cdp_server-0.1.1 → parsek_cdp_server-0.1.2}/parsek_cdp_server.egg-info/PKG-INFO +2 -2
  5. {parsek_cdp_server-0.1.1 → parsek_cdp_server-0.1.2}/parsek_cdp_server.egg-info/requires.txt +1 -1
  6. {parsek_cdp_server-0.1.1 → parsek_cdp_server-0.1.2}/pyproject.toml +2 -2
  7. {parsek_cdp_server-0.1.1 → parsek_cdp_server-0.1.2}/LICENSE +0 -0
  8. {parsek_cdp_server-0.1.1 → parsek_cdp_server-0.1.2}/README.md +0 -0
  9. {parsek_cdp_server-0.1.1 → parsek_cdp_server-0.1.2}/parsek_cdp_server/__init__.py +0 -0
  10. {parsek_cdp_server-0.1.1 → parsek_cdp_server-0.1.2}/parsek_cdp_server/metrics.py +0 -0
  11. {parsek_cdp_server-0.1.1 → parsek_cdp_server-0.1.2}/parsek_cdp_server/py.typed +0 -0
  12. {parsek_cdp_server-0.1.1 → parsek_cdp_server-0.1.2}/parsek_cdp_server/reaper.py +0 -0
  13. {parsek_cdp_server-0.1.1 → parsek_cdp_server-0.1.2}/parsek_cdp_server/supervisor.py +0 -0
  14. {parsek_cdp_server-0.1.1 → parsek_cdp_server-0.1.2}/parsek_cdp_server.egg-info/SOURCES.txt +0 -0
  15. {parsek_cdp_server-0.1.1 → parsek_cdp_server-0.1.2}/parsek_cdp_server.egg-info/dependency_links.txt +0 -0
  16. {parsek_cdp_server-0.1.1 → parsek_cdp_server-0.1.2}/parsek_cdp_server.egg-info/top_level.txt +0 -0
  17. {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.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.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
- """Recursively collect browser executables found under ``dirs``."""
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 root, _subdirs, files in os.walk(directory):
112
- for filename in files:
113
- if not _is_browser(filename):
114
- continue
115
- full = os.path.join(root, filename)
116
- if os.path.isfile(full) and os.access(full, os.X_OK):
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 by recursively searching the
125
- directories in :data:`CHROMES_PATH_ENV` (defaulting to :data:`_KNOWN_PATHS`),
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.default_args:
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(psutil.wait_procs, procs, timeout=timeout)
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
- allowed = {
337
- "executable",
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, supervisor.ws_url, on_client_frame=self._on_control_frame(browser_uuid)
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.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.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
@@ -1,4 +1,4 @@
1
- parsek-cdp~=0.1.1
1
+ parsek-cdp~=0.1.2
2
2
  aiohttp>=3.9
3
3
  psutil>=5
4
4
  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.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.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