-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
[ui/core] Add Validators paradigm #3088
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
60c78f0
8a46122
7a67e84
f3d5719
a5b03a6
fa3ff9a
6505ca9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| from typing import TYPE_CHECKING, Protocol, runtime_checkable | ||
|
|
||
| if TYPE_CHECKING: | ||
| from meshroom.core.attribute import Attribute | ||
| from meshroom.core.node import Node | ||
|
|
||
|
|
||
| def success() -> tuple[bool, list[str]]: | ||
| return (True, []) | ||
|
|
||
| def error(*messages: str) -> tuple[bool, list[str]]: | ||
| return (False, list(messages)) | ||
|
|
||
| @runtime_checkable | ||
| class AttributeValidator(Protocol): | ||
| """ | ||
| Interface for an attribute validation. | ||
| This class can be inherited, and the __call__ methods overridden to implement any custom attribute validation logic. | ||
|
|
||
| Because it is a callable class, validators can also be created on the fly. | ||
|
|
||
| .. code-block: python | ||
| lambda node, attribute: success() if attribute.value and attribute.value != "" else error("attribute have no value") | ||
| """ | ||
|
|
||
| def __call__(self, node: "Node", attribute: "Attribute") -> tuple[bool, list[str]]: | ||
| """ | ||
| This method can be overridden to implement any custom attribute validation logic. | ||
| The `success()` and `error()` helpers can be used to encapsulate the returning responses. | ||
|
|
||
| :param node: The node that holds the attribute to validate | ||
| :param attribute: The attribute to validate | ||
|
|
||
| :returns: The validation response: (True, []) if it is valid, (False, [errorMessage1, errorMessage2, ...]) otherwise. | ||
| """ | ||
| raise NotImplementedError() | ||
|
|
||
|
|
||
| class NotEmptyValidator(AttributeValidator): | ||
| """ | ||
| Ensure that the attribute value is not empty. | ||
| This class is used to determine if an attribute value should be considered as mandatory/required. | ||
| """ | ||
|
|
||
| def __call__(self, node: "Node", attribute: "Attribute") -> tuple[bool, list[str]]: | ||
| if attribute.value is None or attribute.value == "": | ||
| return error("An empty value is not allowed.") | ||
|
|
||
| return success() | ||
|
|
||
|
|
||
| class RangeValidator(AttributeValidator): | ||
| """ Check if the attribute value is in a given range. """ | ||
|
|
||
| def __init__(self, min, max): | ||
| self._min = min | ||
| self._max = max | ||
|
|
||
| def __call__(self, node: "Node", attribute: "Attribute") -> tuple[bool, list[str]]: | ||
| if attribute.value < self._min or attribute.value > self._max: | ||
| return error(f"Value should be greater than {self._min} and less than {self._max} ", | ||
| f"({self._min} < {attribute.value} < {self._max}).") | ||
|
|
||
| return success() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1522,6 +1522,8 @@ def _onAttributeChanged(self, attr: Attribute): | |
| if callback: | ||
| callback(self) | ||
|
|
||
| self.hasInvalidAttributeChanged.emit() | ||
|
|
||
| if self.graph: | ||
| # If we are in a graph, propagate the notification to the connected output attributes | ||
| for edge in self.graph.outEdges(attr): | ||
|
|
@@ -1792,7 +1794,7 @@ def loadOutputAttr(self): | |
| # This does not apply to non dynamic output | ||
| if not self.nodeDesc.hasDynamicOutputAttribute: | ||
| return | ||
|
|
||
| # Check existence of values.json file | ||
| valuesFile = self.valuesFile | ||
| if not os.path.exists(valuesFile): | ||
|
|
@@ -2160,6 +2162,12 @@ def hasTextOutputAttribute(self) -> bool: | |
| """ | ||
| return next((attr for attr in self._attributes if attr.enabled and attr.isOutput and attr.isTextDisplayable), None) is not None | ||
|
|
||
| def _hasInvalidAttribute(self): | ||
| for attribute in self._attributes: | ||
| if len(attribute.errorMessages) > 0: | ||
| return True | ||
| return False | ||
|
|
||
| def _hasDisplayableShape(self): | ||
| """ | ||
|
Comment on lines
2171
to
2172
|
||
| Return True if at least one attribute is a ShapeAttribute, a ShapeListAttribute or a shape File. | ||
|
|
@@ -2240,6 +2248,9 @@ def _hasDisplayableShape(self): | |
| # Whether the node contains a ShapeAttribute, a ShapeListAttribute or a shape File. | ||
| hasDisplayableShape = Property(bool, _hasDisplayableShape, constant=True) | ||
|
|
||
| hasInvalidAttributeChanged = Signal() | ||
| hasInvalidAttribute = Property(bool, _hasInvalidAttribute, notify=hasInvalidAttributeChanged) | ||
|
|
||
|
|
||
| class Node(BaseNode): | ||
| """ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't see a corresponding check on the new code ?