|
18 | 18 | import datetime |
19 | 19 | import math |
20 | 20 | from dataclasses import dataclass |
| 21 | +from functools import singledispatchmethod |
21 | 22 | from typing import Any, Optional |
22 | 23 |
|
23 | 24 | from selenium.common.exceptions import WebDriverException |
@@ -232,6 +233,70 @@ def from_json(cls, json: dict[str, Any]) -> "RealmDestroyed": |
232 | 233 | return cls(realm=json["realm"]) |
233 | 234 |
|
234 | 235 |
|
| 236 | +class _SupportedTypes: |
| 237 | + def __init__(self, script): |
| 238 | + self.script = script |
| 239 | + |
| 240 | + @singledispatchmethod |
| 241 | + def _type(self, value): |
| 242 | + # default case for str and other types, convert to string |
| 243 | + return {"type": "string", "value": str(value)} |
| 244 | + |
| 245 | + @_type.register |
| 246 | + def _(self, value: None): |
| 247 | + return {"type": "null"} |
| 248 | + |
| 249 | + @_type.register |
| 250 | + def _(self, value: bool): |
| 251 | + return {"type": "boolean", "value": value} |
| 252 | + |
| 253 | + @_type.register |
| 254 | + def _(self, value: set): |
| 255 | + return {"type": "set", "value": [self.script.convert_to_local_value(item) for item in value]} |
| 256 | + |
| 257 | + @_type.register |
| 258 | + def _(self, value: list | tuple): |
| 259 | + return {"type": "array", "value": [self.script.convert_to_local_value(item) for item in value]} |
| 260 | + |
| 261 | + @_type.register |
| 262 | + def _(self, value: dict): |
| 263 | + value = [ |
| 264 | + [self.script.convert_to_local_value(k), self.script.convert_to_local_value(v)] for k, v in value.items() |
| 265 | + ] |
| 266 | + return {"type": "object", "value": value} |
| 267 | + |
| 268 | + @_type.register |
| 269 | + def _(self, value: int): |
| 270 | + JS_MAX_SAFE_INTEGER = 9007199254740991 |
| 271 | + if value > JS_MAX_SAFE_INTEGER or value < -JS_MAX_SAFE_INTEGER: |
| 272 | + return {"type": "bigint", "value": str(value)} |
| 273 | + return {"type": "number", "value": value} |
| 274 | + |
| 275 | + @_type.register |
| 276 | + def _(self, value: float): |
| 277 | + if math.isnan(value): |
| 278 | + return {"type": "number", "value": "NaN"} |
| 279 | + elif math.isinf(value): |
| 280 | + if value > 0: |
| 281 | + return {"type": "number", "value": "Infinity"} |
| 282 | + else: |
| 283 | + return {"type": "number", "value": "-Infinity"} |
| 284 | + elif value == 0.0 and math.copysign(1.0, value) < 0: |
| 285 | + return {"type": "number", "value": "-0"} |
| 286 | + return {"type": "number", "value": value} |
| 287 | + |
| 288 | + @_type.register |
| 289 | + def _(self, value: datetime.datetime): |
| 290 | + # Convert Python datetime to JavaScript Date (ISO 8601 format) |
| 291 | + return {"type": "date", "value": value.isoformat() + "Z" if value.tzinfo is None else value.isoformat()} |
| 292 | + |
| 293 | + @_type.register |
| 294 | + def _(self, value: datetime.date): |
| 295 | + # Convert Python date to JavaScript Date |
| 296 | + dt = datetime.datetime.combine(value, datetime.time.min).replace(tzinfo=datetime.timezone.utc) |
| 297 | + return {"type": "date", "value": dt.isoformat()} |
| 298 | + |
| 299 | + |
235 | 300 | class Script: |
236 | 301 | """BiDi implementation of the script module.""" |
237 | 302 |
|
@@ -334,51 +399,11 @@ def __convert_to_local_value(self, value) -> dict: |
334 | 399 | """ |
335 | 400 | Converts a Python value to BiDi LocalValue format. |
336 | 401 | """ |
337 | | - if value is None: |
338 | | - return {"type": "null"} |
339 | | - elif isinstance(value, bool): |
340 | | - return {"type": "boolean", "value": value} |
341 | | - elif isinstance(value, (int, float)): |
342 | | - if isinstance(value, float): |
343 | | - if math.isnan(value): |
344 | | - return {"type": "number", "value": "NaN"} |
345 | | - elif math.isinf(value): |
346 | | - if value > 0: |
347 | | - return {"type": "number", "value": "Infinity"} |
348 | | - else: |
349 | | - return {"type": "number", "value": "-Infinity"} |
350 | | - elif value == 0.0 and math.copysign(1.0, value) < 0: |
351 | | - return {"type": "number", "value": "-0"} |
352 | | - |
353 | | - JS_MAX_SAFE_INTEGER = 9007199254740991 |
354 | | - if isinstance(value, int) and (value > JS_MAX_SAFE_INTEGER or value < -JS_MAX_SAFE_INTEGER): |
355 | | - return {"type": "bigint", "value": str(value)} |
356 | | - |
357 | | - return {"type": "number", "value": value} |
358 | | - |
359 | | - elif isinstance(value, str): |
360 | | - return {"type": "string", "value": value} |
361 | | - elif isinstance(value, datetime.datetime): |
362 | | - # Convert Python datetime to JavaScript Date (ISO 8601 format) |
363 | | - return {"type": "date", "value": value.isoformat() + "Z" if value.tzinfo is None else value.isoformat()} |
364 | | - elif isinstance(value, datetime.date): |
365 | | - # Convert Python date to JavaScript Date |
366 | | - dt = datetime.datetime.combine(value, datetime.time.min).replace(tzinfo=datetime.timezone.utc) |
367 | | - return {"type": "date", "value": dt.isoformat()} |
368 | | - elif isinstance(value, set): |
369 | | - return {"type": "set", "value": [self.__convert_to_local_value(item) for item in value]} |
370 | | - elif isinstance(value, (list, tuple)): |
371 | | - return {"type": "array", "value": [self.__convert_to_local_value(item) for item in value]} |
372 | | - elif isinstance(value, dict): |
373 | | - return { |
374 | | - "type": "object", |
375 | | - "value": [ |
376 | | - [self.__convert_to_local_value(k), self.__convert_to_local_value(v)] for k, v in value.items() |
377 | | - ], |
378 | | - } |
379 | | - else: |
380 | | - # For other types, convert to string |
381 | | - return {"type": "string", "value": str(value)} |
| 402 | + supported_types = _SupportedTypes(self) |
| 403 | + return getattr(supported_types, "_type")(value) |
| 404 | + |
| 405 | + def convert_to_local_value(self, value) -> dict: |
| 406 | + return self.__convert_to_local_value(value) |
382 | 407 |
|
383 | 408 | # low-level APIs for script module |
384 | 409 | def _add_preload_script( |
|
0 commit comments