Skip to content
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

Add dict config #79

Merged
merged 2 commits into from
Nov 26, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions doc/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,17 @@ Global Objects

the global :class:`emitters <.Emitter>` dictionary, tied to the :data:`.log`

*************************
Configuration
*************************

.. autofunction:: add_emitters

.. autofunction:: dict_config

.. autofunction:: quick_setup


*************************
Features
*************************
Expand Down
1 change: 1 addition & 0 deletions doc/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Changelog
- support {}, %, $ as style aliases.
- PEP8 name compliance
- add logging_compat module for compatibility with stdlib's logging
- add dict_config to configure logging from user configuration

******************************
0.4.7
Expand Down
296 changes: 281 additions & 15 deletions doc/configuration.rst

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions test-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pytest
pytest-cov
pytest-mock
unittest2
239 changes: 239 additions & 0 deletions tests/test_dict_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
import copy
import re

import pytest
from six import StringIO

import twiggy

#
# Tests: dict_config
#

VALID_CONFIG = {
'version': '1.0',
'incremental': False,
'outputs': {
'out1': {
'output': 'twiggy.outputs.StreamOutput',
'kwargs': {'stream': 'testing1'},
},
'out2': {
'output': 'twiggy.outputs.StreamOutput',
'kwargs': {'stream': 'testing2'},
'format': 'twiggy.formats.shell_format'
},
},
'emitters': {
'all': {
'level': 'DEBUG',
'output_name': 'out1'
},
'some': {
'level': 'WARNING',
'filters': [
{'filter': 'twiggy.filters.names',
'args': ['a', 'b'],
'kwargs': {}
},
{'filter': 'twiggy.filters.names',
'args': ['c', 'd'],
'kwargs': {}
},
],
'output_name': 'out2'
},
}
}


@pytest.fixture(autouse=True)
def twiggy_globals():
twiggy._populate_globals()
yield
twiggy._del_globals()


@pytest.fixture
def internal_log():
output = twiggy.internal_log.output

buf = StringIO()
new_output = twiggy.outputs.StreamOutput(format=output._format, stream=buf)

twiggy.internal_log.output = new_output
yield buf
twiggy.internal_log.output = output


def test_dict_config_invalid(internal_log):
twiggy.dict_config({})
assert re.search(r"WARNING:twiggy.internal|Error parsing twiggy setup: Config dict"
" must contain a 'version' key", internal_log.getvalue())


def test_dict_config_valid(mocker):
def return_how_called(*args, **kwargs):
return (args, kwargs)

cfg = copy.deepcopy(VALID_CONFIG)

add_emitters = mocker.patch('twiggy.add_emitters')
mocker.patch('twiggy.emitters')
emitters_dict_clear = mocker.patch('twiggy.emitters.clear')
mocker.patch('twiggy.filters.names', return_how_called)

twiggy.dict_config(cfg)

assert emitters_dict_clear.call_args_list == [mocker.call()]

assert len(add_emitters.call_args_list) == 1
assert len(add_emitters.call_args_list[0][0]) == 2

# call_args_list is nested like this: [call(positional_args(first_emitter)),]
# We expect to have called add_emitters once with two positional args which are
# themselves tuples
if add_emitters.call_args_list[0][0][0][0] == 'all':
all_emitter = add_emitters.call_args_list[0][0][0]
some_emitter = add_emitters.call_args_list[0][0][1]
else:
some_emitter = add_emitters.call_args_list[0][0][0]
all_emitter = add_emitters.call_args_list[0][0][1]

assert all_emitter[0] == 'all'
assert all_emitter[1] == twiggy.levels.DEBUG
assert all_emitter[2] is None
assert isinstance(all_emitter[3], twiggy.outputs.StreamOutput)
assert all_emitter[3]._format == twiggy.formats.line_format
assert all_emitter[3].stream == 'testing1'

assert some_emitter[0] == 'some'
assert some_emitter[1] == twiggy.levels.WARNING
assert some_emitter[2] == [(('a', 'b'), {}), (('c', 'd'), {})]
assert isinstance(some_emitter[3], twiggy.outputs.StreamOutput)
assert some_emitter[3]._format == twiggy.formats.shell_format
assert some_emitter[3].stream == 'testing2'


def test_dict_config_incremental_true(mocker):
def return_how_called(*args, **kwargs):
return (args, kwargs)

cfg = copy.deepcopy(VALID_CONFIG)
del cfg['emitters']['some']
cfg['incremental'] = True

add_emitters = mocker.patch('twiggy.add_emitters')
mocker.patch('twiggy.emitters')
emitters_dict_clear = mocker.patch('twiggy.emitters.clear')
mocker.patch('twiggy.filters.names', return_how_called)

twiggy.dict_config(cfg)

assert emitters_dict_clear.call_args_list == []

assert len(add_emitters.call_args_list) == 1
assert len(add_emitters.call_args_list[0][0]) == 1

cfg = copy.deepcopy(VALID_CONFIG)
del cfg['emitters']['all']
cfg['incremental'] = True

twiggy.dict_config(cfg)

assert emitters_dict_clear.call_args_list == []

assert len(add_emitters.call_args_list) == 2
assert len(add_emitters.call_args_list[0][0]) == 1
assert len(add_emitters.call_args_list[1][0]) == 1

# call_args_list is nested like this: [call(positional_args(first_emitter)),]
# We expect to have called add_emitters twice with one positional arg each time
all_emitter = add_emitters.call_args_list[0][0][0]
some_emitter = add_emitters.call_args_list[1][0][0]

assert all_emitter[0] == 'all'
assert all_emitter[1] == twiggy.levels.DEBUG
assert all_emitter[2] is None
assert isinstance(all_emitter[3], twiggy.outputs.StreamOutput)
assert all_emitter[3]._format == twiggy.formats.line_format
assert all_emitter[3].stream == 'testing1'

assert some_emitter[0] == 'some'
assert some_emitter[1] == twiggy.levels.WARNING
assert some_emitter[2] == [(('a', 'b'), {}), (('c', 'd'), {})]
assert isinstance(some_emitter[3], twiggy.outputs.StreamOutput)
assert some_emitter[3]._format == twiggy.formats.shell_format
assert some_emitter[3].stream == 'testing2'


def test_dict_config_incremental_false_order(mocker):
"""
With incremental=false it is important that the dictionary is cleared before the emitter is
added. We'll do this by testing the emitters dict instead of a mock
"""
cfg = copy.deepcopy(VALID_CONFIG)
del cfg['emitters']['some']

twiggy.dict_config(cfg)

assert len(twiggy.emitters) == 1
assert 'all' in twiggy.emitters

cfg = copy.deepcopy(VALID_CONFIG)
del cfg['emitters']['all']

twiggy.dict_config(cfg)

assert len(twiggy.emitters) == 1
assert 'some' in twiggy.emitters


def test_dict_config_incremental_false_contents(mocker):
def return_how_called(*args, **kwargs):
return (args, kwargs)

cfg = copy.deepcopy(VALID_CONFIG)
del cfg['emitters']['some']

add_emitters = mocker.patch('twiggy.add_emitters')
mocker.patch('twiggy.emitters')
emitters_dict_clear = mocker.patch('twiggy.emitters.clear')
mocker.patch('twiggy.filters.names', return_how_called)

twiggy.dict_config(cfg)

assert emitters_dict_clear.call_args_list == [mocker.call()]

assert len(add_emitters.call_args_list) == 1
assert len(add_emitters.call_args_list[0][0]) == 1

the_emitter = add_emitters.call_args_list[0][0][0]

assert the_emitter[0] == 'all'
assert the_emitter[1] == twiggy.levels.DEBUG
assert the_emitter[2] is None
assert isinstance(the_emitter[3], twiggy.outputs.StreamOutput)
assert the_emitter[3]._format == twiggy.formats.line_format
assert the_emitter[3].stream == 'testing1'

cfg = copy.deepcopy(VALID_CONFIG)
del cfg['emitters']['all']

twiggy.dict_config(cfg)

# Note: This does not check that the clear call happens before the add_emitters call.
# The test_dict_config_incremental_false_order() check takes care of that.
assert emitters_dict_clear.call_args_list == [mocker.call(), mocker.call()]

assert len(add_emitters.call_args_list) == 2
assert len(add_emitters.call_args_list[1][0]) == 1

the_emitter = add_emitters.call_args_list[1][0][0]

assert the_emitter[0] == 'some'
assert the_emitter[1] == twiggy.levels.WARNING
assert the_emitter[2] == [(('a', 'b'), {}), (('c', 'd'), {})]
assert isinstance(the_emitter[3], twiggy.outputs.StreamOutput)
assert the_emitter[3]._format == twiggy.formats.shell_format
assert the_emitter[3].stream == 'testing2'