Source code for pypecdp.util

"""Module for utility functions used in PypeCDP."""

from __future__ import annotations

import asyncio
import functools
from http import cookiejar
from typing import TYPE_CHECKING, Any, Callable, TypeVar, cast

from . import cdp

if TYPE_CHECKING:
    from .elem import Elem

F = TypeVar("F", bound=Callable[..., Any])


def tab_attached(func: F) -> F:
    """Decorator to ensure the element's tab session is still active.

    Checks if elem.tab.session_id is None before executing the method.
    Also catches RuntimeError with "Session with given id not found" and
    re-raises as ReferenceError for consistent error handling.

    Args:
        func: The Elem method to wrap.

    Returns:
        The wrapped method.

    Raises:
        ReferenceError: If elem.tab.session_id is None or if the session
            is no longer found by the browser.

    Example:
        @tab_attached
        async def click(self) -> None:
            # Method implementation
            ...
    """

    @functools.wraps(func)
    async def wrapper(self: Elem, *args: Any, **kwargs: Any) -> Any:
        msg = f"Target {self.tab.target_id} is no longer available."
        if self.tab.session_id is None:
            raise ReferenceError(msg)
        try:
            result = await func(self, *args, **kwargs)
            await asyncio.sleep(0)
            return result
        except RuntimeError as e:
            if "Session with given id not found" in str(e):
                raise ReferenceError(msg) from e
            raise

    return cast(F, wrapper)


[docs] class CookieJar(cookiejar.CookieJar): """Custom CookieJar for pypecdp. Inherits from http.cookiejar.CookieJar to manage cookies within the pypecdp browser context. Properly converts CDP cookies to standard Python cookiejar.Cookie objects. The original CDP cookies are preserved in the ``cdp_cookies`` attribute, allowing access to CDP-specific properties (priority, source_scheme, source_port, same_site, partition_key, etc.) that aren't available in standard cookiejar.Cookie objects. Attributes: cdp_cookies: List of original cdp.network.Cookie objects used to populate this CookieJar. None if the jar was created empty. Args: cdp_cookies: Optional list of CDP cookies to populate the jar. """
[docs] def __init__( self, cdp_cookies: list[cdp.network.Cookie] | None = None, ) -> None: """Initialize the CookieJar with optional CDP cookies. Args: cdp_cookies: List of CDP network.Cookie objects to convert. """ super().__init__() # Store original CDP cookies for reference self.cdp_cookies = cdp_cookies # Convert and add CDP cookies to the CookieJar if cdp_cookies: for cdp_cookie in cdp_cookies: # Determine domain matching behavior domain = cdp_cookie.domain domain_initial_dot = domain.startswith(".") # Handle expiry: CDP uses -1 for session, None for unrepresentable # CookieJar expects None for session, timestamp for persistent expires = None discard = cdp_cookie.session if ( not cdp_cookie.session and cdp_cookie.expires is not None and cdp_cookie.expires >= 0 ): expires = int(cdp_cookie.expires) cookie = cookiejar.Cookie( version=0, # Netscape cookies (standard) name=cdp_cookie.name, value=cdp_cookie.value, port=None, port_specified=False, domain=domain, domain_specified=True, domain_initial_dot=domain_initial_dot, path=cdp_cookie.path, path_specified=True, secure=cdp_cookie.secure, expires=expires, discard=discard, comment=None, comment_url=None, rest={"HttpOnly": str(cdp_cookie.http_only)}, rfc2109=False, ) self.set_cookie(cookie)
__all__ = ["tab_attached", "CookieJar"]