Skip to content
This repository was archived by the owner on Feb 11, 2023. It is now read-only.

Commit c0d95f3

Browse files
authored
Merge pull request #159 from jbasko/track-changes
Track changes
2 parents e0d8b4f + bafcf3b commit c0d95f3

7 files changed

Lines changed: 121 additions & 8 deletions

File tree

.bumpversion.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 1.27.0
2+
current_version = 1.28.0
33
commit = true
44
tag = false
55

configmanager/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = '1.27.0'
1+
__version__ = '1.28.0'
22

33
from .managers import Config
44
from .items import Item

configmanager/items.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,6 @@ def __repr__(self):
144144
def __str__(self):
145145
return repr(self)
146146

147-
def __eq__(self, other):
148-
return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
149-
150147
@property
151148
def str_value(self):
152149
if self.raw_str_value is not not_set:

configmanager/managers.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,36 @@
55
from .sections import Section
66

77

8+
class _TrackingContext(object):
9+
def __init__(self, config):
10+
self.config = config
11+
self.hook = None
12+
self.changes = {}
13+
14+
def __enter__(self):
15+
return self.push()
16+
17+
def __exit__(self, exc_type, exc_val, exc_tb):
18+
self.pop()
19+
20+
def _value_changed(self, item, old_value, new_value):
21+
if old_value != new_value:
22+
self.changes[item] = new_value
23+
24+
def push(self):
25+
assert self.hook is None
26+
self.hook = self.config.hooks.register_hook(self.config.hooks.item_value_changed, self._value_changed)
27+
self.config._tracking_contexts.append(self)
28+
return self
29+
30+
def pop(self):
31+
popped = self.config._tracking_contexts.pop()
32+
assert popped is self
33+
self.config.hooks.unregister_hook(self.config.hooks.item_value_changed, self._value_changed)
34+
self.hook = None
35+
return self.changes
36+
37+
838
class Config(Section):
939
"""
1040
Represents a configuration tree.
@@ -104,6 +134,8 @@ def __init__(self, schema=None, **configmanager_settings):
104134

105135
super(Config, self).__init__()
106136

137+
self._tracking_contexts = []
138+
107139
self._configparser_adapter = None
108140
self._json_adapter = None
109141
self._yaml_adapter = None
@@ -122,6 +154,9 @@ def __repr__(self):
122154
def settings(self):
123155
return self._settings
124156

157+
def tracking_context(self):
158+
return _TrackingContext(self)
159+
125160
@property
126161
def configparser(self):
127162
"""

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def read(fname):
2626
description='Extensible, object-oriented manager of configuration items and configuration trees of arbitrary depth',
2727
long_description=read('README.rst'),
2828
packages=['configmanager'],
29-
install_requires=['six==1.10.0', 'future==0.16.0', 'configparser==3.5.0', 'hookery<0.4.0'],
29+
install_requires=['six==1.10.0', 'future==0.16.0', 'configparser==3.5.0', 'hookery==0.3.2'],
3030
extras_require={
3131
'yaml': ['PyYAML'],
3232
'click': ['click'],

tests/test_item.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,15 +185,27 @@ def test_can_set_int_value_to_none():
185185
assert c.value is None
186186

187187

188-
def test_equality():
188+
def test_different_items_are_unequal_even_when_state_matches():
189+
# This is so that we can have items hashable.
190+
# There is no need to compare two items. Compare values or other attributes, but the whole
191+
# thing is just an illusion.
192+
189193
c = Item('a', type=str, value=None)
190194
cc = Item('a', type=str, value=None)
191-
assert c == cc
195+
assert c != cc
192196

193197
d = Item('a', type=int, value=None)
194198
assert c != d
195199

196200

201+
def test_items_are_hashable():
202+
c = Item('a', type=str, value=None)
203+
d = Item('a', type=str, value=None)
204+
205+
e = {c: 1, d: 2}
206+
assert e == {c:1, d:2}
207+
208+
197209
def test_item_is_equal_to_itself():
198210
c = Item('a')
199211
assert c == c

tests/test_track_changes.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
from configmanager import Config
2+
3+
4+
def test_tracking_context():
5+
config = Config({
6+
'greeting': 'Hello, world!',
7+
'db': {
8+
'user': 'root',
9+
},
10+
})
11+
12+
ctx1 = config.tracking_context()
13+
ctx1.push()
14+
ctx1.pop()
15+
16+
assert ctx1.changes == {}
17+
18+
with config.tracking_context() as ctx2:
19+
pass
20+
21+
assert ctx2.changes == {}
22+
23+
assert ctx1.changes == {}
24+
assert ctx1 is not ctx2
25+
26+
ctx3 = config.tracking_context()
27+
ctx3.push()
28+
29+
config.greeting.value = 'Hey!'
30+
assert ctx3.changes == {config.greeting: 'Hey!'}
31+
32+
ctx3.pop()
33+
assert ctx3.changes == {config.greeting: 'Hey!'}
34+
35+
# ctx3 is no longer tracking changes
36+
config.greeting.value = 'What is up!'
37+
assert ctx3.changes == {config.greeting: 'Hey!'}
38+
39+
# neither are ctx1 and ctx2
40+
assert ctx1.changes == {}
41+
assert ctx2.changes == {}
42+
43+
# track in ctx3:
44+
with ctx3:
45+
config.db.user.value = 'admin'
46+
assert ctx3.changes == {config.greeting: 'Hey!', config.db.user: 'admin'}
47+
assert ctx3.changes == {config.greeting: 'Hey!', config.db.user: 'admin'}
48+
49+
# again, ctx3 is no longer tracking
50+
config.db.user.value = 'Administrator'
51+
52+
assert ctx3.changes == {config.greeting: 'Hey!', config.db.user: 'admin'}
53+
54+
# neither are ctx1 and ctx2
55+
assert ctx1.changes == {}
56+
assert ctx2.changes == {}
57+
58+
# nested contexts
59+
60+
with ctx1:
61+
config.db.user.value = 'user-in-ctx1'
62+
63+
with ctx2:
64+
config.greeting.value = 'greeting-in-ctx2'
65+
66+
config.greeting.value = 'greeting-in-ctx1'
67+
68+
assert ctx1.changes == {config.db.user: 'user-in-ctx1', config.greeting: 'greeting-in-ctx1'}
69+
assert ctx2.changes == {config.greeting: 'greeting-in-ctx2'}

0 commit comments

Comments
 (0)