Commit 75f6699c authored by Yakubov, Sergey's avatar Yakubov, Sergey
Browse files

Merge branch '11-add-error-changed-fields-to-callback' into 'main'

Add error/changed fields to callback

Closes #11

See merge request ndip/public-packages/nova-mvvm!13
parents 2dc02625 f3a35782
Loading
Loading
Loading
Loading
Loading
+0 −23
Original line number Diff line number Diff line
@@ -85,29 +85,6 @@ tag-release:
  tags:
    - rse-multi-builder

build-image:
  stage: publish
  variables:
    OVERWRITE_IMAGE: "false"
  when: manual
  script:
    - IMAGE_TAG=${VERSION}
    - docker build -f dockerfiles/Dockerfile -t image --target run --build-arg SOURCE_IMAGE=${IMAGE_NAME}:src-${CI_COMMIT_SHA} .
    - docker tag image ${IMAGE_NAME}:bin-${CI_COMMIT_SHA}
    - docker push ${IMAGE_NAME}:bin-${CI_COMMIT_SHA}
    - docker tag image ${IMAGE_NAME}:latest
    - docker push ${IMAGE_NAME}:latest
    - >
      if ! docker pull ${IMAGE_NAME}:${IMAGE_TAG} || [ "$OVERWRITE_IMAGE" == "true" ]; then
        docker tag image ${IMAGE_NAME}:${IMAGE_TAG}
        docker push ${IMAGE_NAME}:${IMAGE_TAG}
      else
        echo tag ${IMAGE_TAG} already exists in ${NDIP_DOCKER_REPOSITORY}. Use OVERWRITE_IMAGE="true", delete image in the repository or change the tag
        exit 1
      fi
  tags:
    - rse-multi-builder

package-build:
  stage: publish
  script:
+1 −1
Original line number Diff line number Diff line
@@ -24,7 +24,7 @@ with open("../pyproject.toml", "rb") as toml_file:

sys.path.insert(0, os.path.abspath("../src"))

exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "__main__.py"]
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "__main__.py", "_internal"]
extensions = ["sphinx.ext.autodoc", "sphinx.ext.napoleon", "sphinx_rtd_theme"]


+737 −705

File changed.

Preview size limit exceeded, changes collapsed.

+2 −1
Original line number Diff line number Diff line
[tool.poetry]
name = "nova-mvvm"
version = "0.6.1"
version = "0.7.0"
description = "A Python Package for Model-View-ViewModel pattern"
authors = ["Yakubov, Sergey <yakubovs@ornl.gov>"]
readme = "README.md"
@@ -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"]
+86 −0
Original line number Diff line number Diff line
"""Pydantic utils."""

import logging
import re
from typing import Any, Tuple

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

logger = logging.getLogger(__name__)


def _format_field_name_from_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]:
    """
    Get a list of Pydantic model fields from a Pydantic ValidationError.

    Args:
        e (ValidationError): The Pydantic ValidationError containing the validation errors.

    Returns
    -------
        list[str]: A list of nested field names (putting indices in brackets, and using dots for nested fields
        e.g. nested.ranges[0]) that failed validation.
    """
    res = []
    for error in e.errors():
        res.append(_format_field_name_from_tuple(error["loc"]))
    return res


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


def get_updated_fields(old: BaseModel, new: BaseModel) -> list[str]:
    """
    Get a list of Pydantic model fields that were updated.

    Uses DeepDiff package to compare new and old models and
    then processed the results to build lists in a format we want.
    """
    diff = DeepDiff(old, new)
    updates: list[str] = []
    if "values_changed" in diff:
        # DeepDiff adds .root to the root object, we don't need that
        updates = [k.removeprefix("root.") for k in diff["values_changed"].keys()]
    for item in ["iterable_item_added", "iterable_item_removed"]:
        # for added/removed items DeepDiff adds its index, we don't need that as well
        if item in diff:
            updates += [_remove_brackets_suffix(k.removeprefix("root.")) for k in diff[item].keys()]

    return updates


def get_nested_pydantic_field(model: BaseModel, field_path: str) -> FieldInfo:
    """Retrieve a nested field's metadata from a Pydantic model using a dot-separated path."""
    fields = field_path.split(".")
    current_model: Any = model

    for field in fields:
        if "[" in field:
            base = field.split("[")[0]
            current_model = getattr(current_model, base)
            for _ in range(field.count("[")):
                current_model = current_model[0]
            continue
        if issubclass(type(getattr(current_model, field)), BaseModel):
            current_model = getattr(current_model, field)
        else:
            return current_model.model_fields[field]

    raise Exception(f"Cannot find field {field_path}")
Loading