Commit e9819752 authored by Yakubov, Sergey's avatar Yakubov, Sergey
Browse files

add updated errored fields to callback

parent 2dc02625
Loading
Loading
Loading
Loading
Loading
+737 −705

File changed.

Preview size limit exceeded, changes collapsed.

+1 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ trame = "^3.6.3"
pyqt5 = { version = "^5.15.11", optional = true }
pyqt6 = { version = "^6.7.0", optional = true }
panel = { version = "^1.4.4", optional = true }
deepdiff = "^8.0.1"

[tool.poetry.extras]
pyqt5 = ["pyqt5"]
+2 −2
Original line number Diff line number Diff line
"""Abstract interfaces and type definitions."""

from abc import ABC, abstractmethod
from typing import Any, Callable, Optional, Union
from typing import Any, Callable, Dict, Optional, Union

LinkedObjectType = Optional[Any]
LinkedObjectAttributesType = Optional[list[str]]
CallbackAfterUpdateType = Optional[Callable[[Optional[str]], None]]
CallbackAfterUpdateType = Optional[Callable[[Dict[str, Any]], None]]
ConnectCallbackType = Union[None, Callable[[Any, Optional[str]], None]]


+38 −1
Original line number Diff line number Diff line
@@ -2,8 +2,9 @@

import logging
import re
from typing import Any
from typing import Any, Tuple

from deepdiff import DeepDiff
from pydantic import BaseModel, ValidationError
from pydantic.fields import FieldInfo

@@ -12,6 +13,26 @@ from nova.mvvm import bindings_map
logger = logging.getLogger(__name__)


def _format_tuple(input_tuple: Tuple) -> str:
    res = ""
    for item in input_tuple:
        if isinstance(item, int):
            formatted = f"[{item}]"
        elif isinstance(item, str):
            formatted = f".{item}" if res else item
        else:
            formatted = str(item)
        res += formatted
    return res


def get_errored_fields_from_validation_error(e: ValidationError) -> list[str]:
    res = []
    for error in e.errors():
        res.append(_format_tuple(error["loc"]))
    return res


def get_nested_pydantic_field(model: BaseModel, field_path: str) -> FieldInfo:
    fields = field_path.split(".")
    current_model: Any = model
@@ -73,3 +94,19 @@ def validate_pydantic_parameter(name: str, value: Any) -> str | None:
            ):
                return error["msg"]
    return None


def _remove_brackets_suffix(s: str) -> str:
    return re.sub(r"\[\d+\]$", "", s)


def get_updated_fields(old: BaseModel, new: BaseModel) -> list[str]:
    diff = DeepDiff(old, new)
    updates: list[str] = []
    if "values_changed" in diff:
        updates = [k.removeprefix("root.") for k in diff["values_changed"].keys()]
    for item in ["iterable_item_added", "iterable_item_removed"]:
        if item in diff:
            updates += [_remove_brackets_suffix(k.removeprefix("root.")) for k in diff[item].keys()]

    return updates
+18 −8
Original line number Diff line number Diff line
@@ -3,9 +3,10 @@
import os
from typing import Any, Callable, Optional

from pydantic import BaseModel
from pydantic import BaseModel, ValidationError

from ..bindings_map import update_bindings_map
from ..pydantic_utils import get_errored_fields_from_validation_error, get_updated_fields
from ..utils import rsetattr

if os.environ.get("QT_API", None) == "pyqt5":
@@ -49,16 +50,26 @@ class PyQtCommunicator(QObject):

    def _update_viewmodel_callback(self, key: Optional[str] = None, value: Any = None) -> None:
        if issubclass(type(self.viewmodel_linked_object), BaseModel):
            updates: list[str] = []
            errors: list[str] = []
            error: Any = None
            updated = True
            model = self.viewmodel_linked_object.copy(deep=True)
            if self.prefix and key:
                key = key.removeprefix(f"{self.prefix}.")
            rsetattr(model, key or "", value)
            try:
                new_model = model.__class__(**model.model_dump(warnings=False))
                for f, v in new_model:
                    setattr(self.viewmodel_linked_object, f, v)
            except Exception:
                pass
                if new_model != self.viewmodel_linked_object:
                    updates = get_updated_fields(self.viewmodel_linked_object, new_model)
                    for field, value in new_model:
                        setattr(self.viewmodel_linked_object, field, value)
                else:
                    updated = False
            except ValidationError as e:
                errors = get_errored_fields_from_validation_error(e)
                error = e
                updated = True
        elif isinstance(self.viewmodel_linked_object, dict):
            self.viewmodel_linked_object.update({key: value})
        elif is_callable(self.viewmodel_linked_object):
@@ -67,9 +78,8 @@ class PyQtCommunicator(QObject):
            rsetattr(self.viewmodel_linked_object, key or "", value)
        else:
            raise Exception("Cannot update", self.viewmodel_linked_object)

        if self.callback_after_update:
            self.callback_after_update(key)
        if updated and self.callback_after_update:
            self.callback_after_update({"updated": updates, "errored": errors, "error": error})

    def connect(self, name: str, callback: Callable) -> Any:
        # connect should be called from the View side to connect a
Loading