refactor: internal API changes to be easily used as indicators for spiders

This commit is contained in:
Karim shoair
2026-01-08 00:52:06 +02:00
parent de79fe80bb
commit 6a73f9dcd7
4 changed files with 34 additions and 26 deletions
+6 -6
View File
@@ -37,14 +37,14 @@ class SyncSession:
self._max_wait_for_page = 60 self._max_wait_for_page = 60
self.playwright: Playwright | Any = None self.playwright: Playwright | Any = None
self.context: BrowserContext | Any = None self.context: BrowserContext | Any = None
self._closed = False self._is_alive = False
def start(self): def start(self):
pass pass
def close(self): # pragma: no cover def close(self): # pragma: no cover
"""Close all resources""" """Close all resources"""
if self._closed: if not self._is_alive:
return return
if self.context: if self.context:
@@ -55,7 +55,7 @@ class SyncSession:
self.playwright.stop() self.playwright.stop()
self.playwright = None # pyright: ignore self.playwright = None # pyright: ignore
self._closed = True self._is_alive = False
def __enter__(self): def __enter__(self):
self.start() self.start()
@@ -137,7 +137,7 @@ class AsyncSession:
self._max_wait_for_page = 60 self._max_wait_for_page = 60
self.playwright: AsyncPlaywright | Any = None self.playwright: AsyncPlaywright | Any = None
self.context: AsyncBrowserContext | Any = None self.context: AsyncBrowserContext | Any = None
self._closed = False self._is_alive = False
self._lock = Lock() self._lock = Lock()
async def start(self): async def start(self):
@@ -145,7 +145,7 @@ class AsyncSession:
async def close(self): async def close(self):
"""Close all resources""" """Close all resources"""
if self._closed: # pragma: no cover if not self._is_alive: # pragma: no cover
return return
if self.context: if self.context:
@@ -156,7 +156,7 @@ class AsyncSession:
await self.playwright.stop() await self.playwright.stop()
self.playwright = None # pyright: ignore self.playwright = None # pyright: ignore
self._closed = True self._is_alive = False
async def __aenter__(self): async def __aenter__(self):
await self.start() await self.start()
+6 -3
View File
@@ -31,7 +31,6 @@ class DynamicSession(SyncSession, DynamicSessionMixin):
"_max_wait_for_page", "_max_wait_for_page",
"playwright", "playwright",
"context", "context",
"_closed",
) )
def __init__(self, **kwargs: Unpack[PlaywrightSession]): def __init__(self, **kwargs: Unpack[PlaywrightSession]):
@@ -82,6 +81,8 @@ class DynamicSession(SyncSession, DynamicSessionMixin):
if self._config.cookies: # pragma: no cover if self._config.cookies: # pragma: no cover
self.context.add_cookies(self._config.cookies) self.context.add_cookies(self._config.cookies)
self._is_alive = True
else: else:
raise RuntimeError("Session has been already started") raise RuntimeError("Session has been already started")
@@ -104,7 +105,7 @@ class DynamicSession(SyncSession, DynamicSessionMixin):
:return: A `Response` object. :return: A `Response` object.
""" """
params = _validate(kwargs, self, PlaywrightConfig) params = _validate(kwargs, self, PlaywrightConfig)
if self._closed: # pragma: no cover if not self._is_alive: # pragma: no cover
raise RuntimeError("Context manager has been closed") raise RuntimeError("Context manager has been closed")
referer = ( referer = (
@@ -211,6 +212,8 @@ class AsyncDynamicSession(AsyncSession, DynamicSessionMixin):
if self._config.cookies: if self._config.cookies:
await self.context.add_cookies(self._config.cookies) # pyright: ignore await self.context.add_cookies(self._config.cookies) # pyright: ignore
self._is_alive = True
else: else:
raise RuntimeError("Session has been already started") raise RuntimeError("Session has been already started")
@@ -234,7 +237,7 @@ class AsyncDynamicSession(AsyncSession, DynamicSessionMixin):
""" """
params = _validate(kwargs, self, PlaywrightConfig) params = _validate(kwargs, self, PlaywrightConfig)
if self._closed: # pragma: no cover if not self._is_alive: # pragma: no cover
raise RuntimeError("Context manager has been closed") raise RuntimeError("Context manager has been closed")
referer = ( referer = (
+2 -3
View File
@@ -39,7 +39,6 @@ class StealthySession(SyncSession, StealthySessionMixin):
"_max_wait_for_page", "_max_wait_for_page",
"playwright", "playwright",
"context", "context",
"_closed",
) )
def __init__(self, **kwargs: Unpack[StealthSession]): def __init__(self, **kwargs: Unpack[StealthSession]):
@@ -191,7 +190,7 @@ class StealthySession(SyncSession, StealthySessionMixin):
:return: A `Response` object. :return: A `Response` object.
""" """
params = _validate(kwargs, self, StealthConfig) params = _validate(kwargs, self, StealthConfig)
if self._closed: # pragma: no cover if not self._is_alive: # pragma: no cover
raise RuntimeError("Context manager has been closed") raise RuntimeError("Context manager has been closed")
referer = ( referer = (
@@ -404,7 +403,7 @@ class AsyncStealthySession(AsyncSession, StealthySessionMixin):
""" """
params = _validate(kwargs, self, StealthConfig) params = _validate(kwargs, self, StealthConfig)
if self._closed: # pragma: no cover if not self._is_alive: # pragma: no cover
raise RuntimeError("Context manager has been closed") raise RuntimeError("Context manager has been closed")
referer = ( referer = (
+20 -14
View File
@@ -62,6 +62,7 @@ class _ConfigurationLogic(ABC):
"_default_cert", "_default_cert",
"_default_http3", "_default_http3",
"selector_config", "selector_config",
"_is_alive",
) )
def __init__(self, **kwargs: Unpack[RequestsSession]): def __init__(self, **kwargs: Unpack[RequestsSession]):
@@ -80,6 +81,7 @@ class _ConfigurationLogic(ABC):
self._default_cert = kwargs.get("cert") or None self._default_cert = kwargs.get("cert") or None
self._default_http3 = kwargs.get("http3", False) self._default_http3 = kwargs.get("http3", False)
self.selector_config = kwargs.get("selector_config") or {} self.selector_config = kwargs.get("selector_config") or {}
self._is_alive = False
@staticmethod @staticmethod
def _get_param(kwargs: Dict, key: str, default: Any) -> Any: def _get_param(kwargs: Dict, key: str, default: Any) -> Any:
@@ -183,10 +185,11 @@ class _SyncSessionLogic(_ConfigurationLogic):
def __enter__(self): def __enter__(self):
"""Creates and returns a new synchronous Fetcher Session""" """Creates and returns a new synchronous Fetcher Session"""
if self._curl_session: if self._is_alive:
raise RuntimeError("This FetcherSession instance already has an active synchronous session.") raise RuntimeError("This FetcherSession instance already has an active synchronous session.")
self._curl_session = CurlSession() self._curl_session = CurlSession()
self._is_alive = True
return self return self
def __exit__(self, exc_type, exc_val, exc_tb): def __exit__(self, exc_type, exc_val, exc_tb):
@@ -201,7 +204,9 @@ class _SyncSessionLogic(_ConfigurationLogic):
self._curl_session.close() self._curl_session.close()
self._curl_session = None self._curl_session = None
def __make_request(self, method: SUPPORTED_HTTP_METHODS, stealth: Optional[bool] = None, **kwargs) -> Response: self._is_alive = False
def _make_request(self, method: SUPPORTED_HTTP_METHODS, stealth: Optional[bool] = None, **kwargs) -> Response:
""" """
Perform an HTTP request using the configured session. Perform an HTTP request using the configured session.
""" """
@@ -267,7 +272,7 @@ class _SyncSessionLogic(_ConfigurationLogic):
:return: A `Response` object. :return: A `Response` object.
""" """
stealthy_headers = kwargs.pop("stealthy_headers", None) stealthy_headers = kwargs.pop("stealthy_headers", None)
return self.__make_request("GET", stealth=stealthy_headers, url=url, **kwargs) return self._make_request("GET", stealth=stealthy_headers, url=url, **kwargs)
def post(self, url: str, **kwargs: Unpack[DataRequestParams]) -> Response: def post(self, url: str, **kwargs: Unpack[DataRequestParams]) -> Response:
""" """
@@ -299,7 +304,7 @@ class _SyncSessionLogic(_ConfigurationLogic):
:return: A `Response` object. :return: A `Response` object.
""" """
stealthy_headers = kwargs.pop("stealthy_headers", None) stealthy_headers = kwargs.pop("stealthy_headers", None)
return self.__make_request("POST", stealth=stealthy_headers, url=url, **kwargs) return self._make_request("POST", stealth=stealthy_headers, url=url, **kwargs)
def put(self, url: str, **kwargs: Unpack[DataRequestParams]) -> Response: def put(self, url: str, **kwargs: Unpack[DataRequestParams]) -> Response:
""" """
@@ -331,7 +336,7 @@ class _SyncSessionLogic(_ConfigurationLogic):
:return: A `Response` object. :return: A `Response` object.
""" """
stealthy_headers = kwargs.pop("stealthy_headers", None) stealthy_headers = kwargs.pop("stealthy_headers", None)
return self.__make_request("PUT", stealth=stealthy_headers, url=url, **kwargs) return self._make_request("PUT", stealth=stealthy_headers, url=url, **kwargs)
def delete(self, url: str, **kwargs: Unpack[DataRequestParams]) -> Response: def delete(self, url: str, **kwargs: Unpack[DataRequestParams]) -> Response:
""" """
@@ -365,7 +370,7 @@ class _SyncSessionLogic(_ConfigurationLogic):
# Careful of sending a body in a DELETE request, it might cause some websites to reject the request as per https://www.rfc-editor.org/rfc/rfc7231#section-4.3.5, # Careful of sending a body in a DELETE request, it might cause some websites to reject the request as per https://www.rfc-editor.org/rfc/rfc7231#section-4.3.5,
# But some websites accept it, it depends on the implementation used. # But some websites accept it, it depends on the implementation used.
stealthy_headers = kwargs.pop("stealthy_headers", None) stealthy_headers = kwargs.pop("stealthy_headers", None)
return self.__make_request("DELETE", stealth=stealthy_headers, url=url, **kwargs) return self._make_request("DELETE", stealth=stealthy_headers, url=url, **kwargs)
class _ASyncSessionLogic(_ConfigurationLogic): class _ASyncSessionLogic(_ConfigurationLogic):
@@ -377,10 +382,11 @@ class _ASyncSessionLogic(_ConfigurationLogic):
async def __aenter__(self): # pragma: no cover async def __aenter__(self): # pragma: no cover
"""Creates and returns a new asynchronous Session.""" """Creates and returns a new asynchronous Session."""
if self._async_curl_session: if self._is_alive:
raise RuntimeError("This FetcherSession instance already has an active asynchronous session.") raise RuntimeError("This FetcherSession instance already has an active asynchronous session.")
self._async_curl_session = AsyncCurlSession() self._async_curl_session = AsyncCurlSession()
self._is_alive = True
return self return self
async def __aexit__(self, exc_type, exc_val, exc_tb): async def __aexit__(self, exc_type, exc_val, exc_tb):
@@ -395,9 +401,9 @@ class _ASyncSessionLogic(_ConfigurationLogic):
await self._async_curl_session.close() await self._async_curl_session.close()
self._async_curl_session = None self._async_curl_session = None
async def __make_request( self._is_alive = False
self, method: SUPPORTED_HTTP_METHODS, stealth: Optional[bool] = None, **kwargs
) -> Response: async def _make_request(self, method: SUPPORTED_HTTP_METHODS, stealth: Optional[bool] = None, **kwargs) -> Response:
""" """
Perform an HTTP request using the configured session. Perform an HTTP request using the configured session.
""" """
@@ -465,7 +471,7 @@ class _ASyncSessionLogic(_ConfigurationLogic):
:return: A `Response` object. :return: A `Response` object.
""" """
stealthy_headers = kwargs.pop("stealthy_headers", None) stealthy_headers = kwargs.pop("stealthy_headers", None)
return self.__make_request("GET", stealth=stealthy_headers, url=url, **kwargs) return self._make_request("GET", stealth=stealthy_headers, url=url, **kwargs)
def post(self, url: str, **kwargs: Unpack[DataRequestParams]) -> Awaitable[Response]: def post(self, url: str, **kwargs: Unpack[DataRequestParams]) -> Awaitable[Response]:
""" """
@@ -497,7 +503,7 @@ class _ASyncSessionLogic(_ConfigurationLogic):
:return: A `Response` object. :return: A `Response` object.
""" """
stealthy_headers = kwargs.pop("stealthy_headers", None) stealthy_headers = kwargs.pop("stealthy_headers", None)
return self.__make_request("POST", stealth=stealthy_headers, url=url, **kwargs) return self._make_request("POST", stealth=stealthy_headers, url=url, **kwargs)
def put(self, url: str, **kwargs: Unpack[DataRequestParams]) -> Awaitable[Response]: def put(self, url: str, **kwargs: Unpack[DataRequestParams]) -> Awaitable[Response]:
""" """
@@ -529,7 +535,7 @@ class _ASyncSessionLogic(_ConfigurationLogic):
:return: A `Response` object. :return: A `Response` object.
""" """
stealthy_headers = kwargs.pop("stealthy_headers", None) stealthy_headers = kwargs.pop("stealthy_headers", None)
return self.__make_request("PUT", stealth=stealthy_headers, url=url, **kwargs) return self._make_request("PUT", stealth=stealthy_headers, url=url, **kwargs)
def delete(self, url: str, **kwargs: Unpack[DataRequestParams]) -> Awaitable[Response]: def delete(self, url: str, **kwargs: Unpack[DataRequestParams]) -> Awaitable[Response]:
""" """
@@ -563,7 +569,7 @@ class _ASyncSessionLogic(_ConfigurationLogic):
# Careful of sending a body in a DELETE request, it might cause some websites to reject the request as per https://www.rfc-editor.org/rfc/rfc7231#section-4.3.5, # Careful of sending a body in a DELETE request, it might cause some websites to reject the request as per https://www.rfc-editor.org/rfc/rfc7231#section-4.3.5,
# But some websites accept it, it depends on the implementation used. # But some websites accept it, it depends on the implementation used.
stealthy_headers = kwargs.pop("stealthy_headers", None) stealthy_headers = kwargs.pop("stealthy_headers", None)
return self.__make_request("DELETE", stealth=stealthy_headers, url=url, **kwargs) return self._make_request("DELETE", stealth=stealthy_headers, url=url, **kwargs)
class FetcherSession: class FetcherSession: