-
Notifications
You must be signed in to change notification settings - Fork 24
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
Fix container items change event being saved in preferences #196
Changes from all commits
3875f9b
24e0345
1c548ca
4b918bc
f201aaa
d99ca8c
dcfcf57
2ec9cdf
70f3111
aed40a0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -17,7 +17,14 @@ | |||||||||||||
|
||||||||||||||
|
||||||||||||||
class PreferencesHelper(HasTraits): | ||||||||||||||
""" An object that can be initialized from a preferences node. """ | ||||||||||||||
""" A base class for objects that can be initialized from a preferences | ||||||||||||||
node. | ||||||||||||||
|
||||||||||||||
Additional traits defined on subclasses will be listened to. Changes | ||||||||||||||
are then synchronized with the preferences. Note that mutations on nested | ||||||||||||||
containers e.g. List(List(Str)) cannot be synchronized and should be | ||||||||||||||
avoided. | ||||||||||||||
""" | ||||||||||||||
|
||||||||||||||
#### 'PreferencesHelper' interface ######################################## | ||||||||||||||
|
||||||||||||||
|
@@ -65,9 +72,23 @@ def _preferences_default(self): | |||||||||||||
def _anytrait_changed(self, trait_name, old, new): | ||||||||||||||
""" Static trait change handler. """ | ||||||||||||||
|
||||||||||||||
if self.preferences is None: | ||||||||||||||
return | ||||||||||||||
|
||||||||||||||
# If the trait was a list or dict '_items' trait then just treat it as | ||||||||||||||
# if the entire list or dict was changed. | ||||||||||||||
if trait_name.endswith('_items'): | ||||||||||||||
trait_name = trait_name[:-6] | ||||||||||||||
if self._is_preference_trait(trait_name): | ||||||||||||||
self.preferences.set( | ||||||||||||||
'%s.%s' % (self._get_path(), trait_name), | ||||||||||||||
getattr(self, trait_name) | ||||||||||||||
) | ||||||||||||||
return | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is ugly, and is similar to what the UI counterpart is doing: apptools/apptools/preferences/ui/preferences_page.py Lines 76 to 81 in 83aa787
The alternative might have been a redesign of the helper <-> preference relationship, but that might not be feasible without changing how it is used. |
||||||||||||||
|
||||||||||||||
# If we were the one that set the trait (because the underlying | ||||||||||||||
# preferences node changed) then do nothing. | ||||||||||||||
if self.preferences and self._is_preference_trait(trait_name): | ||||||||||||||
if self._is_preference_trait(trait_name): | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is likely an unrealistic edge case (it would probably be considered a programmer error), but what if there is a preference trait that ends with In general you should never name traits with a trailing
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, it is a very good observation. I agree this is why magic naming is bad, and one of the reasons to introduce There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the test you provided can be fixed though, but it may have to be done along with To be honest, I think a better design would be one that does not require synchronization - we probably don't need that. Most of the time we care about storing the preferences to a file after it has been edited. As long as we can obtain and serialize all the values at the point when the file is saved, we are good. If someone says they want to share the same preferences with multiple helpers, I'd say, think again. A lot can go wrong when editing shared mutable states, best approach is don't. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Actually thinking about it... I am not sure it can be fixed to satisify both situations. The suffix There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Opened #225 |
||||||||||||||
self.preferences.set("%s.%s" % (self._get_path(), trait_name), new) | ||||||||||||||
|
||||||||||||||
return | ||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
""" Tests for the preferences page. """ | ||
|
||
import unittest | ||
|
||
from traits.api import Enum, List, Str | ||
from traitsui.api import Group, Item, View | ||
|
||
from apptools.preferences.api import Preferences | ||
from apptools.preferences.ui.api import PreferencesPage | ||
|
||
|
||
class TestPreferencesPage(unittest.TestCase): | ||
""" Non-GUI Tests for PreferencesPage.""" | ||
|
||
def test_preferences_page_apply(self): | ||
""" Test applying the preferences """ | ||
|
||
# this sets up imitate Mayavi usage. | ||
|
||
class MyPreferencesPage(PreferencesPage): | ||
|
||
# the following set default values for class traits | ||
category = "Application" | ||
|
||
help_id = "" | ||
|
||
name = "Note" | ||
|
||
preferences_path = "my_ref.pref" | ||
|
||
# custom preferences | ||
|
||
backend = Enum("auto", "simple", "test") | ||
|
||
traits_view = View(Group(Item("backend"))) | ||
|
||
preferences = Preferences() | ||
pref_page = MyPreferencesPage( | ||
preferences=preferences, | ||
category="Another Application", | ||
help_id="this_wont_be_saved", | ||
name="Different Note", | ||
# custom preferences | ||
backend="simple", | ||
) | ||
pref_page.apply() | ||
|
||
self.assertEqual(preferences.get("my_ref.pref.backend"), "simple") | ||
self.assertEqual(preferences.keys("my_ref.pref"), ["backend"]) | ||
|
||
# this is not saved by virtue of it being static and never assigned to | ||
self.assertIsNone(preferences.get("my_ref.pref.traits_view")) | ||
|
||
# These are skipped because this trait is defined on the | ||
# PreferencesPage. | ||
self.assertIsNone(preferences.get("my_ref.pref.help_id")) | ||
self.assertIsNone(preferences.get("my_ref.pref.category")) | ||
self.assertIsNone(preferences.get("my_ref.pref.name")) | ||
|
||
def test_preferences_page_apply_skip_items_traits(self): | ||
""" Test _items traits from List mutation are skipped. """ | ||
# Regression test for enthought/apptools#129 | ||
|
||
class MyPreferencesPage(PreferencesPage): | ||
preferences_path = "my_ref.pref" | ||
names = List(Str()) | ||
|
||
preferences = Preferences() | ||
pref_page = MyPreferencesPage( | ||
preferences=preferences, | ||
names=["1"], | ||
) | ||
pref_page.names.append("2") | ||
pref_page.apply() | ||
|
||
self.assertEqual(preferences.get("my_ref.pref.names"), str(["1", "2"])) | ||
self.assertEqual(preferences.keys("my_ref.pref"), ["names"]) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Fix container items change event being saved in preferences (#196) |
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.
when we get to python 3.9, we would be able to use
removesuffix
method - which would make the code more readable. ref https://www.python.org/dev/peps/pep-0616/maybe we should add two backwards compatible
removeprefix
andremovesuffix
functions in a traits utility - which other libraries that depend on traits can use.