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

Commit 4b76554

Browse files
committed
Config is now a callable -- it returns an auto-resetting changeset
context which is handy for unittests and other times when you need to set temporary configuration. Bump version: 1.31.0 → 1.32.0
1 parent e7ccdc0 commit 4b76554

6 files changed

Lines changed: 88 additions & 5 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.31.0
2+
current_version = 1.32.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.31.0'
1+
__version__ = '1.32.0'
22

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

configmanager/changesets.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,19 @@
77

88

99
class _ChangesetContext(object):
10-
def __init__(self, config):
10+
def __init__(self, config, auto_reset=False, **unsupported_options):
1111
self.config = config
1212
self.hook = None
1313
self._changes = collections.defaultdict(list)
14+
self._auto_reset = auto_reset
1415

1516
def __enter__(self):
1617
return self.push()
1718

1819
def __exit__(self, exc_type, exc_val, exc_tb):
1920
self.pop()
21+
if self._auto_reset:
22+
self.reset()
2023

2124
def _value_changed(self, item, old_value, new_value, old_raw_str_value, new_raw_str_value):
2225
if old_value != new_value or old_raw_str_value != new_raw_str_value:

configmanager/managers.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,16 +121,32 @@ def __init__(self, schema=None, **configmanager_settings):
121121
def __repr__(self):
122122
return '<{cls} {alias} at {id}>'.format(cls=self.__class__.__name__, alias=self.alias, id=id(self))
123123

124+
def __call__(self, values=None):
125+
"""
126+
Returns a changeset context which auto-resets itself on exit.
127+
This allows creation of temporary changes of configuration.
128+
129+
Do not use this in combination with non-resetting changeset contexts:
130+
the behaviour under such conditions is undefined.
131+
132+
If ``values`` dictionary is supplied, the specified configuration values will be set
133+
for the duration of the changeset context.
134+
"""
135+
context = self.changeset_context(auto_reset=True)
136+
if values is not None:
137+
self.load_values(values)
138+
return context
139+
124140
@property
125141
def settings(self):
126142
return self._settings
127143

128-
def changeset_context(self):
144+
def changeset_context(self, **options):
129145
"""
130146
Returns:
131147
configmanager.changesets._ChangesetContext
132148
"""
133-
return _ChangesetContext(self)
149+
return _ChangesetContext(self, **options)
134150

135151
@property
136152
def configparser(self):

docs/index.rst

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,9 +326,45 @@ to be called when this exception is raised:
326326
327327
If this function returns anything other than ``None``, the exception will not be raised.
328328

329+
How to set temporary configuration?
330+
-----------------------------------
331+
332+
When writing unit tests, or in other scenarios when you need to change configuration briefly just to
333+
execute some particular part of your code, you can create an auto-resetting configuration context by
334+
calling your ``Config`` instance as a function.
335+
336+
.. code-block:: python
337+
338+
with config():
339+
config.greeting.value = 'Bon jour!'
340+
341+
# do some French things here
342+
pass
343+
344+
# French settings have been reset:
345+
assert config.greeting.get() == 'Hello, world!'
346+
347+
If you prefer to set the temporary configuration on initialisation of the context, you can do so
348+
by passing a values dictionary:
349+
350+
.. code-block:: python
351+
352+
with config({'greeting': 'Bon jour!'}):
353+
# do some French things here
354+
pass
355+
356+
Note that you cannot pass keyword arguments there, just a dictionary.
357+
358+
329359
How do I manage changesets of config values?
330360
--------------------------------------------
331361

362+
The previous example used a special case of what we call a *changeset context*.
363+
You can explicitly create one with :meth:`.Config.changeset_context`.
364+
365+
Unlike the special context demonstrated above, a default changeset context does not
366+
reset changes made while program control was inside it.
367+
332368
.. code-block:: python
333369
:emphasize-lines: 4,7,10,13,14
334370
@@ -353,3 +389,6 @@ How do I manage changesets of config values?
353389
354390
>>> config.greeting.get()
355391
'Hello, world!'
392+
393+
A changeset context comes handy when you want to create a sub-context of changes which you want to be able
394+
to export or persist separately from the rest of configuration changes.

tests/test_changesets.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,3 +362,28 @@ def test_length_of_changeset_is_the_number_of_changes_and_truth_value_respects_i
362362
config.a.set('off')
363363
assert len(ctx) == 2
364364
assert ctx
365+
366+
367+
def test_calling_config_returns_changeset_context_which_resets_itself_on_exit():
368+
from configmanager import Config
369+
370+
config = Config({
371+
'uploads': {
372+
'threads': 1,
373+
'enabled': True,
374+
'db': {
375+
'user': 'root',
376+
'password': 'secret',
377+
}
378+
}
379+
})
380+
381+
with config():
382+
config.uploads.threads.value = 2
383+
config.uploads.db.user.value = 'admin'
384+
385+
assert config.uploads.threads.value == 2
386+
assert config.uploads.db.user.value == 'admin'
387+
388+
assert config.uploads.threads.value == 1
389+
assert config.uploads.db.user.value == 'root'

0 commit comments

Comments
 (0)