|
|
import logging |
|
|
from dataclasses import dataclass |
|
|
from datetime import datetime, timedelta |
|
|
from typing import Optional, Tuple |
|
|
from uuid import uuid1 |
|
|
|
|
|
from selenium.webdriver.chrome.webdriver import WebDriver |
|
|
|
|
|
import utils |
|
|
|
|
|
|
|
|
@dataclass |
|
|
class Session: |
|
|
session_id: str |
|
|
driver: WebDriver |
|
|
created_at: datetime |
|
|
|
|
|
def lifetime(self) -> timedelta: |
|
|
return datetime.now() - self.created_at |
|
|
|
|
|
|
|
|
class SessionsStorage: |
|
|
"""SessionsStorage creates, stores and process all the sessions""" |
|
|
|
|
|
def __init__(self): |
|
|
self.sessions = {} |
|
|
|
|
|
def create(self, session_id: Optional[str] = None, proxy: Optional[dict] = None, |
|
|
force_new: Optional[bool] = False) -> Tuple[Session, bool]: |
|
|
"""create creates new instance of WebDriver if necessary, |
|
|
assign defined (or newly generated) session_id to the instance |
|
|
and returns the session object. If a new session has been created |
|
|
second argument is set to True. |
|
|
|
|
|
Note: The function is idempotent, so in case if session_id |
|
|
already exists in the storage a new instance of WebDriver won't be created |
|
|
and existing session will be returned. Second argument defines if |
|
|
new session has been created (True) or an existing one was used (False). |
|
|
""" |
|
|
session_id = session_id or str(uuid1()) |
|
|
|
|
|
if force_new: |
|
|
self.destroy(session_id) |
|
|
|
|
|
if self.exists(session_id): |
|
|
return self.sessions[session_id], False |
|
|
|
|
|
driver = utils.get_webdriver(proxy) |
|
|
created_at = datetime.now() |
|
|
session = Session(session_id, driver, created_at) |
|
|
|
|
|
self.sessions[session_id] = session |
|
|
|
|
|
return session, True |
|
|
|
|
|
def exists(self, session_id: str) -> bool: |
|
|
return session_id in self.sessions |
|
|
|
|
|
def destroy(self, session_id: str) -> bool: |
|
|
"""destroy closes the driver instance and removes session from the storage. |
|
|
The function is noop if session_id doesn't exist. |
|
|
The function returns True if session was found and destroyed, |
|
|
and False if session_id wasn't found. |
|
|
""" |
|
|
if not self.exists(session_id): |
|
|
return False |
|
|
|
|
|
session = self.sessions.pop(session_id) |
|
|
if utils.PLATFORM_VERSION == "nt": |
|
|
session.driver.close() |
|
|
session.driver.quit() |
|
|
return True |
|
|
|
|
|
def get(self, session_id: str, ttl: Optional[timedelta] = None) -> Tuple[Session, bool]: |
|
|
session, fresh = self.create(session_id) |
|
|
|
|
|
if ttl is not None and not fresh and session.lifetime() > ttl: |
|
|
logging.debug(f'session\'s lifetime has expired, so the session is recreated (session_id={session_id})') |
|
|
session, fresh = self.create(session_id, force_new=True) |
|
|
|
|
|
return session, fresh |
|
|
|
|
|
def session_ids(self) -> list[str]: |
|
|
return list(self.sessions.keys()) |
|
|
|