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

Commit

Permalink
* Fixes raw string value tracking
Browse files Browse the repository at this point in the history
* Clean up tracking context -- rename to ChangesetContext, reset_changes renamed to reset, rename changes property to values, adds changes property which returns actual merged changes; adds ChangesetContext.__len__  which allows truth value calculation of context.
  • Loading branch information
jbasko committed Jun 17, 2017
1 parent c870eb7 commit af3fa48
Show file tree
Hide file tree
Showing 9 changed files with 478 additions and 340 deletions.
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 1.29.2
current_version = 1.30.0
commit = true
tag = false

Expand Down
2 changes: 1 addition & 1 deletion configmanager/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = '1.29.2'
__version__ = '1.30.0'

from .managers import Config
from .items import Item
Expand Down
88 changes: 88 additions & 0 deletions configmanager/changesets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import collections


_Change = collections.namedtuple('Change', field_names=(
'old_value', 'new_value', 'old_raw_str_value', 'new_raw_str_value'
))


class _ChangesetContext(object):
def __init__(self, config):
self.config = config
self.hook = None
self._changes = collections.defaultdict(list)

def __enter__(self):
return self.push()

def __exit__(self, exc_type, exc_val, exc_tb):
self.pop()

def _value_changed(self, item, old_value, new_value, old_raw_str_value, new_raw_str_value):
if old_value != new_value or old_raw_str_value != new_raw_str_value:
self._changes[item].append(_Change(old_value, new_value, old_raw_str_value, new_raw_str_value))

def push(self):
assert self.hook is None
self.hook = self.config.hooks.item_value_changed.register_hook(self._value_changed)
self.config._changeset_contexts.append(self)
return self

def pop(self):
popped = self.config._changeset_contexts.pop()
assert popped is self
self.config.hooks.unregister_hook(self.config.hooks.item_value_changed, self._value_changed)
self.hook = None

@property
def values(self):
"""
Returns a mapping of items to their new values. The mapping includes only items whose value or raw string value
has changed in the context.
"""
report = {}
for k, k_changes in self._changes.items():
if len(k_changes) == 1:
report[k] = k_changes[0].new_value
elif k_changes[0].old_value != k_changes[-1].new_value:
report[k] = k_changes[-1].new_value
return report

@property
def changes(self):
"""
Returns a mapping of items to their effective change objects which include the old values
and the new. The mapping includes only items whose value or raw string value has changed in the context.
"""
report = {}
for k, k_changes in self._changes.items():
if len(k_changes) == 1:
report[k] = k_changes[0]
else:
first = k_changes[0]
last = k_changes[-1]
if first.old_value != last.new_value or first.old_raw_str_value != last.new_raw_str_value:
report[k] = _Change(
first.old_value,
last.new_value,
first.old_raw_str_value,
last.new_raw_str_value,
)
return report

def reset(self, item=None):
for k, k_changes in self._changes.items():
if item is None or k is item:
k._value = k_changes[0].old_value
k.raw_str_value = k_changes[0].old_raw_str_value

if item is None:
self._changes.clear()
else:
del self._changes[item]

def __len__(self):
"""
Returns the number of items whose value or raw string value has changed in this context.
"""
return len(self.changes)
8 changes: 6 additions & 2 deletions configmanager/items.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ def set(self, value):
Sets config value.
"""
old_value = self._value
old_raw_str_value = self.raw_str_value

self.type.set_item_value(self, value)

Expand All @@ -233,13 +234,16 @@ def set(self, value):
item=self,
old_value=old_value,
new_value=new_value,
old_raw_str_value=old_raw_str_value,
new_raw_str_value=self.raw_str_value
)

def reset(self):
"""
Resets the value of config item to its default value.
"""
old_value = self._value
old_raw_str_value = self.raw_str_value

self._value = not_set
self.raw_str_value = not_set
Expand All @@ -256,10 +260,10 @@ def reset(self):
item=self,
old_value=old_value,
new_value=new_value,
old_raw_str_value=old_raw_str_value,
new_raw_str_value=self.raw_str_value,
)



@property
def is_default(self):
"""
Expand Down
66 changes: 7 additions & 59 deletions configmanager/managers.py
Original file line number Diff line number Diff line change
@@ -1,61 +1,9 @@
import collections

from .utils import _get_persistence_adapter_for
from .schema_parser import parse_config_schema
from .changesets import _ChangesetContext
from .meta import ConfigManagerSettings
from .persistence import ConfigPersistenceAdapter, YamlReaderWriter, JsonReaderWriter, ConfigParserReaderWriter
from .schema_parser import parse_config_schema
from .sections import Section


class _TrackingContext(object):
def __init__(self, config):
self.config = config
self.hook = None
self._changes = collections.defaultdict(list)

def __enter__(self):
return self.push()

def __exit__(self, exc_type, exc_val, exc_tb):
self.pop()

def _value_changed(self, item, old_value, new_value):
if old_value != new_value:
self._changes[item].append((old_value, new_value))

def push(self):
assert self.hook is None
self.hook = self.config.hooks.item_value_changed.register_hook(self._value_changed)
self.config._tracking_contexts.append(self)
return self

def pop(self):
popped = self.config._tracking_contexts.pop()
assert popped is self
self.config.hooks.unregister_hook(self.config.hooks.item_value_changed, self._value_changed)
self.hook = None

@property
def changes(self):
values = {}
for k, k_changes in self._changes.items():
if len(k_changes) == 1:
values[k] = k_changes[0][1]
elif k_changes[0][0] != k_changes[-1][1]:
values[k] = k_changes[-1][1]
return values

def reset_changes(self, item=None):

for k, k_changes in self._changes.items():
if item is None or k is item:
# TODO This doesn't reset raw_str_value properly ...
k._value = k_changes[0][0]

if item is None:
self._changes.clear()
else:
del self._changes[item]
from .utils import _get_persistence_adapter_for


class Config(Section):
Expand Down Expand Up @@ -157,7 +105,7 @@ def __init__(self, schema=None, **configmanager_settings):

super(Config, self).__init__()

self._tracking_contexts = []
self._changeset_contexts = []

self._configparser_adapter = None
self._json_adapter = None
Expand All @@ -177,12 +125,12 @@ def __repr__(self):
def settings(self):
return self._settings

def tracking_context(self):
def changeset_context(self):
"""
Returns:
_TrackingContext
configmanager.changesets._ChangesetContext
"""
return _TrackingContext(self)
return _ChangesetContext(self)

@property
def configparser(self):
Expand Down
18 changes: 10 additions & 8 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -332,22 +332,24 @@ How do I manage changesets of config values?
.. code-block:: python
:emphasize-lines: 4,7,10,13,14
>>> config.greeting.value
>>> config.greeting.get()
'Hello, world!'
>>> with config.tracking_context() as ctx:
... config.greeting.value = 'Hey, what is up!'
>>> with config.changeset_context() as ctx:
... config.greeting.set('Hey, what is up!')
>>> len(ctx.changes)
>>> len(ctx)
1
>>> ctx.changes[config.greeting]
>>> ctx.values[config.greeting]
'Hey, what is up!'
>>> ctx.reset_changes()
>>> ctx.changes[config.greeting]
Change(old_value=<NotSet>, new_value='Hey, what is up!', old_raw_str_value=<NotSet>, new_raw_str_value='Hey, what is up!')
>>> ctx.reset()
>>> ctx.changes
{}
>>> config.greeting.value
>>> config.greeting.get()
'Hello, world!'
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def read(fname):
maintainer_email='jazeps.basko@gmail.com',
license='MIT',
url='https://github.com/jbasko/configmanager',
description='Extensible, object-oriented manager of configuration items and configuration trees of arbitrary depth',
description='Forget about configparser, YAML, or JSON parsers. Focus on configuration.',
long_description=read('README.rst'),
packages=['configmanager'],
install_requires=['six==1.10.0', 'future==0.16.0', 'configparser==3.5.0', 'hookery==0.3.2'],
Expand Down
Loading

0 comments on commit af3fa48

Please sign in to comment.