@@ -591,16 +591,12 @@ def generate_code(self, enhancements: dict[str, Any] | None = None) -> str:
591591 # Collect needed imports to avoid duplicates
592592 needs_command_builder = bool (self .commands )
593593 needs_dataclass = self .commands or self .types or self .events
594- needs_threading = self .events
595594 needs_callable = self .events
596- needs_session = self .events
597595
598596 stdlib_imports = []
599597 local_imports = []
600598
601599 # Add imports (field import will be added conditionally after code generation)
602- if needs_threading :
603- stdlib_imports .append ("import threading" )
604600 if needs_callable :
605601 stdlib_imports .append ("from collections.abc import Callable" )
606602 if needs_dataclass :
@@ -611,9 +607,9 @@ def generate_code(self, enhancements: dict[str, Any] | None = None) -> str:
611607 local_imports .append (
612608 "from selenium.webdriver.common.bidi.common import command_builder"
613609 )
614- if needs_session :
610+ if self . events :
615611 local_imports .append (
616- "from selenium.webdriver.common.bidi.session import Session "
612+ "from selenium.webdriver.common.bidi._event_manager import EventConfig, _EventWrapper, _EventManager "
617613 )
618614
619615 code += "\n " .join (stdlib_imports ) + "\n "
@@ -797,165 +793,11 @@ def generate_code(self, enhancements: dict[str, Any] | None = None) -> str:
797793"""
798794 code += "\n \n "
799795
800- # Generate EventConfig and _EventManager for modules with events
801- if self .events :
802- # Generate EventConfig dataclass
803- code += """@dataclass
804- class EventConfig:
805- \" \" \" Configuration for a BiDi event.\" \" \"
806- event_key: str
807- bidi_event: str
808- event_class: type
809-
810-
811- """
812-
813- # Generate _EventManager class
814- code += """class _EventWrapper:
815- \" \" \" Wrapper to provide event_class attribute for WebSocketConnection callbacks.\" \" \"
816- def __init__(self, bidi_event: str, event_class: type):
817- self.event_class = bidi_event # WebSocket expects the BiDi event name as event_class
818- self._python_class = event_class # Keep reference to Python dataclass for deserialization
819-
820- def from_json(self, params: dict) -> Any:
821- \" \" \" Deserialize event params into the wrapped Python dataclass.
822-
823- Args:
824- params: Raw BiDi event params with camelCase keys.
825-
826- Returns:
827- An instance of the dataclass, or the raw dict on failure.
828- \" \" \"
829- if self._python_class is None or self._python_class is dict:
830- return params
831- try:
832- # Delegate to a classmethod from_json if the class defines one
833- if hasattr(self._python_class, \" from_json\" ) and callable(
834- self._python_class.from_json
835- ):
836- return self._python_class.from_json(params)
837- import dataclasses as dc
838-
839- snake_params = {self._camel_to_snake(k): v for k, v in params.items()}
840- if dc.is_dataclass(self._python_class):
841- valid_fields = {f.name for f in dc.fields(self._python_class)}
842- filtered = {k: v for k, v in snake_params.items() if k in valid_fields}
843- return self._python_class(**filtered)
844- return self._python_class(**snake_params)
845- except Exception:
846- return params
847-
848- @staticmethod
849- def _camel_to_snake(name: str) -> str:
850- result = [name[0].lower()]
851- for char in name[1:]:
852- if char.isupper():
853- result.extend([\" _\" , char.lower()])
854- else:
855- result.append(char)
856- return \" \" .join(result)
857-
858-
859- class _EventManager:
860- \" \" \" Manages event subscriptions and callbacks.\" \" \"
861-
862- def __init__(self, conn, event_configs: dict[str, EventConfig]):
863- self.conn = conn
864- self.event_configs = event_configs
865- self.subscriptions: dict = {}
866- self._event_wrappers = {} # Cache of _EventWrapper objects
867- self._bidi_to_class = {config.bidi_event: config.event_class for config in event_configs.values()}
868- self._available_events = ", ".join(sorted(event_configs.keys()))
869- self._subscription_lock = threading.Lock()
870-
871- # Create event wrappers for each event
872- for config in event_configs.values():
873- wrapper = _EventWrapper(config.bidi_event, config.event_class)
874- self._event_wrappers[config.bidi_event] = wrapper
875-
876- def validate_event(self, event: str) -> EventConfig:
877- event_config = self.event_configs.get(event)
878- if not event_config:
879- raise ValueError(f"Event '{event}' not found. Available events: {self._available_events}")
880- return event_config
881-
882- def subscribe_to_event(self, bidi_event: str, contexts: list[str] | None = None) -> None:
883- \" \" \" Subscribe to a BiDi event if not already subscribed.\" \" \"
884- with self._subscription_lock:
885- if bidi_event not in self.subscriptions:
886- session = Session(self.conn)
887- result = session.subscribe([bidi_event], contexts=contexts)
888- sub_id = (
889- result.get(\" subscription\" ) if isinstance(result, dict) else None
890- )
891- self.subscriptions[bidi_event] = {
892- \" callbacks\" : [],
893- \" subscription_id\" : sub_id,
894- }
895-
896- def unsubscribe_from_event(self, bidi_event: str) -> None:
897- \" \" \" Unsubscribe from a BiDi event if no more callbacks exist.\" \" \"
898- with self._subscription_lock:
899- entry = self.subscriptions.get(bidi_event)
900- if entry is not None and not entry[\" callbacks\" ]:
901- session = Session(self.conn)
902- sub_id = entry.get(\" subscription_id\" )
903- if sub_id:
904- session.unsubscribe(subscriptions=[sub_id])
905- else:
906- session.unsubscribe(events=[bidi_event])
907- del self.subscriptions[bidi_event]
908-
909- def add_callback_to_tracking(self, bidi_event: str, callback_id: int) -> None:
910- with self._subscription_lock:
911- self.subscriptions[bidi_event][\" callbacks\" ].append(callback_id)
912-
913- def remove_callback_from_tracking(self, bidi_event: str, callback_id: int) -> None:
914- with self._subscription_lock:
915- entry = self.subscriptions.get(bidi_event)
916- if entry and callback_id in entry[\" callbacks\" ]:
917- entry[\" callbacks\" ].remove(callback_id)
918-
919- def add_event_handler(self, event: str, callback: Callable, contexts: list[str] | None = None) -> int:
920- event_config = self.validate_event(event)
921- # Use the event wrapper for add_callback
922- event_wrapper = self._event_wrappers.get(event_config.bidi_event)
923- callback_id = self.conn.add_callback(event_wrapper, callback)
924- self.subscribe_to_event(event_config.bidi_event, contexts)
925- self.add_callback_to_tracking(event_config.bidi_event, callback_id)
926- return callback_id
927-
928- def remove_event_handler(self, event: str, callback_id: int) -> None:
929- event_config = self.validate_event(event)
930- event_wrapper = self._event_wrappers.get(event_config.bidi_event)
931- self.conn.remove_callback(event_wrapper, callback_id)
932- self.remove_callback_from_tracking(event_config.bidi_event, callback_id)
933- self.unsubscribe_from_event(event_config.bidi_event)
934-
935- def clear_event_handlers(self) -> None:
936- \" \" \" Clear all event handlers.\" \" \"
937- with self._subscription_lock:
938- if not self.subscriptions:
939- return
940- session = Session(self.conn)
941- for bidi_event, entry in list(self.subscriptions.items()):
942- event_wrapper = self._event_wrappers.get(bidi_event)
943- callbacks = entry[\" callbacks\" ] if isinstance(entry, dict) else entry
944- if event_wrapper:
945- for callback_id in callbacks:
946- self.conn.remove_callback(event_wrapper, callback_id)
947- sub_id = (
948- entry.get(\" subscription_id\" ) if isinstance(entry, dict) else None
949- )
950- if sub_id:
951- session.unsubscribe(subscriptions=[sub_id])
952- else:
953- session.unsubscribe(events=[bidi_event])
954- self.subscriptions.clear()
955-
956-
957- """
958- code += "\n \n "
796+ # EventConfig, _EventWrapper, and _EventManager are imported from
797+ # selenium.webdriver.common.bidi._event_manager (see import section above)
798+ # rather than being duplicated inline in every generated module.
799+ if False : # placeholder to preserve indentation structure
800+ pass
959801
960802 # Generate class
961803 # Convert module name (camelCase or snake_case) to proper class name (PascalCase)
0 commit comments