nova-mvvm 0.13.2__py3-none-any.whl → 0.15.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nova/mvvm/_internal/pydantic_utils.py +1 -1
- nova/mvvm/_internal/pyqt_communicator.py +4 -2
- nova/mvvm/_internal/utils.py +8 -0
- nova/mvvm/panel_binding/binding.py +3 -1
- nova/mvvm/pydantic_utils.py +2 -0
- nova/mvvm/trame_binding/binding.py +30 -3
- {nova_mvvm-0.13.2.dist-info → nova_mvvm-0.15.0.dist-info}/METADATA +2 -2
- {nova_mvvm-0.13.2.dist-info → nova_mvvm-0.15.0.dist-info}/RECORD +10 -10
- {nova_mvvm-0.13.2.dist-info → nova_mvvm-0.15.0.dist-info}/WHEEL +0 -0
- {nova_mvvm-0.13.2.dist-info → nova_mvvm-0.15.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -83,6 +83,6 @@ def get_nested_pydantic_field(model: BaseModel, field_path: str) -> FieldInfo:
|
|
|
83
83
|
if issubclass(type(getattr(current_model, field)), BaseModel):
|
|
84
84
|
current_model = getattr(current_model, field)
|
|
85
85
|
else:
|
|
86
|
-
return current_model.model_fields[field]
|
|
86
|
+
return current_model.__class__.model_fields[field]
|
|
87
87
|
|
|
88
88
|
raise Exception(f"Cannot find field {field_path}")
|
|
@@ -7,7 +7,7 @@ from pydantic import BaseModel, ValidationError
|
|
|
7
7
|
from typing_extensions import override
|
|
8
8
|
|
|
9
9
|
from .._internal.pydantic_utils import get_errored_fields_from_validation_error, get_updated_fields
|
|
10
|
-
from .._internal.utils import check_binding, rsetattr
|
|
10
|
+
from .._internal.utils import check_binding, check_model_type, rsetattr
|
|
11
11
|
from ..bindings_map import bindings_map
|
|
12
12
|
from ..interface import Communicator, ConnectCallbackType
|
|
13
13
|
|
|
@@ -84,6 +84,8 @@ class PyQtCommunicator(Communicator):
|
|
|
84
84
|
return None
|
|
85
85
|
|
|
86
86
|
@override
|
|
87
|
-
def update_in_view(self, value: Any) ->
|
|
87
|
+
def update_in_view(self, value: Any) -> None:
|
|
88
88
|
"""Update a View (GUI) when called by a ViewModel."""
|
|
89
|
+
if issubclass(type(value), BaseModel):
|
|
90
|
+
check_model_type(self.viewmodel_linked_object, value)
|
|
89
91
|
return self.pyqtobject.signal.emit(value)
|
nova/mvvm/_internal/utils.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Internal common functions tp be used within the package."""
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
|
+
from types import NoneType
|
|
4
5
|
from typing import Any, Dict
|
|
5
6
|
|
|
6
7
|
from nova.mvvm import bindings_map
|
|
@@ -98,3 +99,10 @@ def check_binding(linked_object: LinkedObjectType, name: str) -> None:
|
|
|
98
99
|
for communicator in bindings_map.values():
|
|
99
100
|
if communicator.viewmodel_linked_object and communicator.viewmodel_linked_object is linked_object:
|
|
100
101
|
raise ValueError(f"cannot connect to binding {name}: object already connected")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def check_model_type(old_value: Any, new_value: Any) -> None:
|
|
105
|
+
old_type = type(old_value)
|
|
106
|
+
new_type = type(new_value)
|
|
107
|
+
if old_type is not NoneType and old_type is not new_type:
|
|
108
|
+
raise TypeError(f"update_in_view expected a value of type '{old_type}' but received '{new_type}'.")
|
|
@@ -9,7 +9,7 @@ from typing_extensions import override
|
|
|
9
9
|
|
|
10
10
|
from .. import bindings_map
|
|
11
11
|
from .._internal.pydantic_utils import get_errored_fields_from_validation_error, get_updated_fields
|
|
12
|
-
from .._internal.utils import check_binding, rgetattr, rsetattr
|
|
12
|
+
from .._internal.utils import check_binding, check_model_type, rgetattr, rsetattr
|
|
13
13
|
from ..interface import BindingInterface, ConnectCallbackType, Worker
|
|
14
14
|
|
|
15
15
|
|
|
@@ -114,6 +114,8 @@ class Communicator:
|
|
|
114
114
|
|
|
115
115
|
# Update the view based on the provided value
|
|
116
116
|
def update_in_view(self, value: Any) -> None:
|
|
117
|
+
if issubclass(type(value), BaseModel):
|
|
118
|
+
check_model_type(self.viewmodel_linked_object, value)
|
|
117
119
|
if is_callable(self.connector):
|
|
118
120
|
cast(Callable, self.connector)(value)
|
|
119
121
|
elif self.viewmodel_linked_object:
|
nova/mvvm/pydantic_utils.py
CHANGED
|
@@ -10,7 +10,14 @@ from trame_server.state import State
|
|
|
10
10
|
from typing_extensions import override
|
|
11
11
|
|
|
12
12
|
from .._internal.pydantic_utils import get_errored_fields_from_validation_error, get_updated_fields
|
|
13
|
-
from .._internal.utils import
|
|
13
|
+
from .._internal.utils import (
|
|
14
|
+
check_binding,
|
|
15
|
+
check_model_type,
|
|
16
|
+
normalize_field_name,
|
|
17
|
+
rget_list_of_fields,
|
|
18
|
+
rgetattr,
|
|
19
|
+
rsetattr,
|
|
20
|
+
)
|
|
14
21
|
from ..bindings_map import bindings_map
|
|
15
22
|
from ..interface import (
|
|
16
23
|
BindingInterface,
|
|
@@ -21,6 +28,7 @@ from ..interface import (
|
|
|
21
28
|
LinkedObjectType,
|
|
22
29
|
Worker,
|
|
23
30
|
)
|
|
31
|
+
from ..pydantic_utils import ERROR_FIELD_NAME
|
|
24
32
|
from .trame_worker import TrameWorker
|
|
25
33
|
|
|
26
34
|
|
|
@@ -132,6 +140,8 @@ class CallBackConnection:
|
|
|
132
140
|
self.viewmodel_callback_after_update({"updated": updates, "errored": errors, "error": None})
|
|
133
141
|
|
|
134
142
|
def update_in_view(self, value: Any) -> None:
|
|
143
|
+
if issubclass(type(value), BaseModel):
|
|
144
|
+
check_model_type(self.viewmodel_linked_object, value)
|
|
135
145
|
self.callback(value)
|
|
136
146
|
|
|
137
147
|
def get_callback(self) -> ConnectCallbackType:
|
|
@@ -167,12 +177,21 @@ class StateConnection:
|
|
|
167
177
|
return update
|
|
168
178
|
|
|
169
179
|
def _set_variable_in_state(self, name_in_state: str, value: Any) -> None:
|
|
180
|
+
if "." in name_in_state:
|
|
181
|
+
base_name, name_in_state = name_in_state.split(".", maxsplit=1)
|
|
182
|
+
if self.state[base_name] is None:
|
|
183
|
+
self.state[base_name] = {}
|
|
184
|
+
state_obj = self.state[base_name]
|
|
185
|
+
else:
|
|
186
|
+
base_name = name_in_state
|
|
187
|
+
state_obj = self.state
|
|
188
|
+
|
|
170
189
|
if is_async():
|
|
171
190
|
with self.state:
|
|
172
|
-
|
|
191
|
+
state_obj[name_in_state] = value
|
|
173
192
|
self.state.dirty(name_in_state)
|
|
174
193
|
else:
|
|
175
|
-
|
|
194
|
+
state_obj[name_in_state] = value
|
|
176
195
|
self.state.dirty(name_in_state)
|
|
177
196
|
|
|
178
197
|
def _get_name_in_state(self, attribute_name: str) -> str:
|
|
@@ -198,6 +217,9 @@ class StateConnection:
|
|
|
198
217
|
name_in_state = self._get_name_in_state(attribute_name)
|
|
199
218
|
self.state.setdefault(name_in_state, None)
|
|
200
219
|
|
|
220
|
+
# Set the initial error state.
|
|
221
|
+
self._set_variable_in_state(f"{state_variable_name}.{ERROR_FIELD_NAME}", [])
|
|
222
|
+
|
|
201
223
|
# this updates ViewModel on state change
|
|
202
224
|
if self.viewmodel_linked_object:
|
|
203
225
|
if self.linked_object_attributes:
|
|
@@ -225,6 +247,7 @@ class StateConnection:
|
|
|
225
247
|
updated = False
|
|
226
248
|
except ValidationError as e:
|
|
227
249
|
errors = get_errored_fields_from_validation_error(e)
|
|
250
|
+
self._set_variable_in_state(f"{state_variable_name}.{ERROR_FIELD_NAME}", errors)
|
|
228
251
|
error = e
|
|
229
252
|
updated = True
|
|
230
253
|
self.has_errors = True
|
|
@@ -239,19 +262,23 @@ class StateConnection:
|
|
|
239
262
|
if self.has_errors and not errors:
|
|
240
263
|
updated = True
|
|
241
264
|
self.has_errors = False
|
|
265
|
+
self._set_variable_in_state(f"{state_variable_name}.{ERROR_FIELD_NAME}", [])
|
|
242
266
|
if updated:
|
|
243
267
|
await self._handle_callback({"updated": updates, "errored": errors, "error": error})
|
|
244
268
|
|
|
245
269
|
def update_in_view(self, value: Any) -> None:
|
|
246
270
|
if issubclass(type(value), BaseModel):
|
|
271
|
+
check_model_type(self.viewmodel_linked_object, value)
|
|
247
272
|
value = value.model_dump()
|
|
248
273
|
if self.linked_object_attributes:
|
|
249
274
|
for attribute_name in self.linked_object_attributes:
|
|
250
275
|
name_in_state = self._get_name_in_state(attribute_name)
|
|
251
276
|
value_to_change = rgetattr(value, attribute_name)
|
|
252
277
|
self._set_variable_in_state(name_in_state, value_to_change)
|
|
278
|
+
self._set_variable_in_state(f"{name_in_state}.{ERROR_FIELD_NAME}", [])
|
|
253
279
|
elif self.state_variable_name:
|
|
254
280
|
self._set_variable_in_state(self.state_variable_name, value)
|
|
281
|
+
self._set_variable_in_state(f"{self.state_variable_name}.{ERROR_FIELD_NAME}", [])
|
|
255
282
|
|
|
256
283
|
def get_callback(self) -> ConnectCallbackType:
|
|
257
284
|
return None
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nova-mvvm
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.15.0
|
|
4
4
|
Summary: A Python Package for Model-View-ViewModel pattern
|
|
5
|
-
Author-email:
|
|
5
|
+
Author-email: Sergey Yakubov <yakubovs@ornl.gov>, John Duggan <dugganjw@ornl.gov>, Greg Watson <watsongr@ornl.gov>
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
License-File: LICENSE
|
|
8
8
|
Keywords: MVVM,python
|
|
@@ -2,12 +2,12 @@ nova/__init__.py,sha256=ED6jHcYiuYpr_0vjGz0zx2lrrmJT9sDJCzIljoDfmlM,65
|
|
|
2
2
|
nova/mvvm/__init__.py,sha256=fo1bT27jjUYzVoFJXuwokXcyFtOYj8eQ_BHkPgtI4Rg,149
|
|
3
3
|
nova/mvvm/bindings_map.py,sha256=SIVNPGHxDf91P9UxcPmuAHx1GNxZk66JUc-fYPcp-kc,417
|
|
4
4
|
nova/mvvm/interface.py,sha256=l4l339BiB46s6KX-2RH1Q3Hxc8pKYdCvCOkhse4zOkw,5812
|
|
5
|
-
nova/mvvm/pydantic_utils.py,sha256=
|
|
6
|
-
nova/mvvm/_internal/pydantic_utils.py,sha256=
|
|
7
|
-
nova/mvvm/_internal/pyqt_communicator.py,sha256=
|
|
8
|
-
nova/mvvm/_internal/utils.py,sha256=
|
|
5
|
+
nova/mvvm/pydantic_utils.py,sha256=t8niOvGoA87TUjT4adbgMdoFL9mDaNjLkV1xXyWlDK8,3229
|
|
6
|
+
nova/mvvm/_internal/pydantic_utils.py,sha256=anYYyB1Bkg8GgWB3Jn-6iRit0PYR-_2w3paKsD9z4sY,2947
|
|
7
|
+
nova/mvvm/_internal/pyqt_communicator.py,sha256=G_Srttxn7EEwz9EageFxnOgg_P-gfPPhNpoR55btHd0,3929
|
|
8
|
+
nova/mvvm/_internal/utils.py,sha256=LpZ3LALcB9BzG1yIh0Qt0kBXGdctacDMVmSm8H8Py1c,3626
|
|
9
9
|
nova/mvvm/panel_binding/__init__.py,sha256=HHDlUyP-LuRePcHS1s8tq3niuG3f3n8gdSRb7Rwtc4o,100
|
|
10
|
-
nova/mvvm/panel_binding/binding.py,sha256=
|
|
10
|
+
nova/mvvm/panel_binding/binding.py,sha256=rLkxnqjLpZvvRHJVx8PLfJpLSEg3Q0FSRPov81o8e6Y,6483
|
|
11
11
|
nova/mvvm/pyqt5_binding/__init__.py,sha256=JbKOFJNIObJReotRHlN5-vi7oSMyc_v8K_uHjeJLcHg,62
|
|
12
12
|
nova/mvvm/pyqt5_binding/binding.py,sha256=Nki03KgafysfJFQ86evYvL8lprbrYNajFNgfihk7KA4,1462
|
|
13
13
|
nova/mvvm/pyqt5_binding/pyqt5_worker.py,sha256=bf0Yj2cnX3xljFHQmWy6B9bCildhNSVEDXSqahprCXU,2133
|
|
@@ -15,9 +15,9 @@ nova/mvvm/pyqt6_binding/__init__.py,sha256=plms0W4kJjFpZReGOC7lE3SYX1ceGTLbdAJ1G
|
|
|
15
15
|
nova/mvvm/pyqt6_binding/binding.py,sha256=uZNHx23Pf9DwGf3qKfQdmjOhmaXcppNd8pjkh363WP4,1365
|
|
16
16
|
nova/mvvm/pyqt6_binding/pyqt6_worker.py,sha256=y8g_mfPC1xIcpyKq7ZZqxrn9Txp5gPJrsBjAwS5FHhY,2133
|
|
17
17
|
nova/mvvm/trame_binding/__init__.py,sha256=uTdEW9VxtVubSbTLpoD3pC8a-KMgbKkZFlRbucvuQSE,62
|
|
18
|
-
nova/mvvm/trame_binding/binding.py,sha256=
|
|
18
|
+
nova/mvvm/trame_binding/binding.py,sha256=hKIeIUAp9Zzc4UtGtzD6UBwQltfh5i79pEzpWHLRS7E,13369
|
|
19
19
|
nova/mvvm/trame_binding/trame_worker.py,sha256=JmrneFU11Hi8TtY558R3yIWW8UGiUeVNO7HvM1uWTVs,3778
|
|
20
|
-
nova_mvvm-0.
|
|
21
|
-
nova_mvvm-0.
|
|
22
|
-
nova_mvvm-0.
|
|
23
|
-
nova_mvvm-0.
|
|
20
|
+
nova_mvvm-0.15.0.dist-info/METADATA,sha256=BfmKRWdHZ0gdiOvQXJQ_4YcuWxAYv-LuKQ6rHBhF7Go,1090
|
|
21
|
+
nova_mvvm-0.15.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
22
|
+
nova_mvvm-0.15.0.dist-info/licenses/LICENSE,sha256=MOqZ8tPMKy8ZETJ2-HEvFTZ7dYNlg3gXmBkV-Y9i8bw,1061
|
|
23
|
+
nova_mvvm-0.15.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|