93 lines
2.9 KiB
Python
93 lines
2.9 KiB
Python
import dataclasses
|
|
import logging
|
|
from dataclasses import fields, is_dataclass
|
|
from pathlib import Path
|
|
from types import UnionType
|
|
from typing import Any, get_args
|
|
|
|
import gi
|
|
|
|
gi.require_version("WebKit", "6.0")
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
def sanitize_string(s: str) -> str:
|
|
return s.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n")
|
|
|
|
|
|
def dataclass_to_dict(obj: Any) -> Any:
|
|
"""
|
|
Utility function to convert dataclasses to dictionaries
|
|
It converts all nested dataclasses, lists, tuples, and dictionaries to dictionaries
|
|
|
|
It does NOT convert member functions.
|
|
"""
|
|
if dataclasses.is_dataclass(obj):
|
|
return {
|
|
sanitize_string(k): dataclass_to_dict(v)
|
|
for k, v in dataclasses.asdict(obj).items()
|
|
}
|
|
elif isinstance(obj, list | tuple):
|
|
return [dataclass_to_dict(item) for item in obj]
|
|
elif isinstance(obj, dict):
|
|
return {sanitize_string(k): dataclass_to_dict(v) for k, v in obj.items()}
|
|
elif isinstance(obj, Path):
|
|
return str(obj)
|
|
elif isinstance(obj, str):
|
|
return sanitize_string(obj)
|
|
else:
|
|
return obj
|
|
|
|
|
|
def is_union_type(type_hint: type) -> bool:
|
|
return type(type_hint) is UnionType
|
|
|
|
|
|
def get_inner_type(type_hint: type) -> type:
|
|
if is_union_type(type_hint):
|
|
# Return the first non-None type
|
|
return next(t for t in get_args(type_hint) if t is not type(None))
|
|
return type_hint
|
|
|
|
|
|
def from_dict(t: type, data: dict[str, Any] | None) -> Any:
|
|
"""
|
|
Dynamically instantiate a data class from a dictionary, handling nested data classes.
|
|
"""
|
|
if not data:
|
|
return None
|
|
|
|
try:
|
|
# Attempt to create an instance of the data_class
|
|
field_values = {}
|
|
for field in fields(t):
|
|
field_value = data.get(field.name)
|
|
field_type = get_inner_type(field.type)
|
|
if field_value is not None:
|
|
# If the field is another dataclass, recursively instantiate it
|
|
if is_dataclass(field_type):
|
|
field_value = from_dict(field_type, field_value)
|
|
elif isinstance(field_type, Path | str) and isinstance(
|
|
field_value, str
|
|
):
|
|
field_value = (
|
|
Path(field_value) if field_type == Path else field_value
|
|
)
|
|
|
|
if (
|
|
field.default is not dataclasses.MISSING
|
|
or field.default_factory is not dataclasses.MISSING
|
|
):
|
|
# Field has a default value. We cannot set the value to None
|
|
if field_value is not None:
|
|
field_values[field.name] = field_value
|
|
else:
|
|
field_values[field.name] = field_value
|
|
|
|
return t(**field_values)
|
|
|
|
except (TypeError, ValueError) as e:
|
|
print(f"Failed to instantiate {t.__name__}: {e}")
|
|
return None
|