Skip to content

Commit

Permalink
Merge pull request #1 from akozl/master
Browse files Browse the repository at this point in the history
CI config and fixes
  • Loading branch information
Frankovskyi Bogdan authored Apr 21, 2017
2 parents 78f67ed + 98f0bcc commit aaf562a
Show file tree
Hide file tree
Showing 24 changed files with 384 additions and 65 deletions.
9 changes: 9 additions & 0 deletions .codeclimate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
engines:
radon:
enabled: true
config:
threshold: "B"

ratings:
paths:
- "**.py"
4 changes: 4 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[run]
branch = True
omit =
*/tests/*
43 changes: 43 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
all: quality complexity test complexity-report coverage-report

test: test-py ## Run tests

test-py: unittests acceptance-tests ## Run all Python tests

unittests: ## Run unit tests with coverage
python setup.py nosetests --with-xunit --with-coverage --cover-package=configuration_py --cover-inclusive

acceptance-tests: ## Run acceptance tests with coverage
coverage run -a --source='configuration_py' -m behave --tags=~@skip --format=progress3 --junit ./configuration_py/tests/acceptance/

quality: quality-py ## Run code quality checks

quality-py:
pep8 . --format=pylint --max-line-length=140 --exclude=*/migrations/* --ignore=E121,E123,E24
pylint -f colorized configuration_py

deps-test: ## Install dependencies required to run tests
pip install -r test_requirements.txt

complexity: ## Run code complexity checks
xenon . -bB -mA -aA

reports: complexity-report tests-report coverage-report

tests-report:
mkdir $(CIRCLE_TEST_REPORTS)/nosetests
mkdir $(CIRCLE_TEST_REPORTS)/behave
cp nosetests.xml $(CIRCLE_TEST_REPORTS)/nosetests/
cp -r reports/. $(CIRCLE_TEST_REPORTS)/behave/

complexity-report: ## Generate code complexity reports
radon cc . -s
radon mi . -s

coverage-report:
coverage report
coverage html -d $(CIRCLE_ARTIFACTS)/coverage
codecov

help:
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
[![CodeClimate](http://img.shields.io/codeclimate/github/akozl/configuration.py.svg?style=flat)](https://codeclimate.com/github/akozl/configuration.py
"CodeClimate")
[![CircleCI](https://img.shields.io/circleci/project/github/akozl/configuration.py/master.svg?style=flat)](https://https://circleci.com/gh/akozl/configuration.py
"CircleCI")
[![Codecov](https://img.shields.io/codecov/c/github/akozl/configuration.py.svg)](https://codecov.io/gh/akozl/configuration.py)
[![PyPI](https://img.shields.io/pypi/pyversions/configuration.py.svg)](https://pypi.python.org/pypi/configuration.py)
[![license](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/Ferroman/configuration.py/blob/master/LICENSE)

# configuration.py: easy and flexible configuration management for python applications

Configuration.py is a library for configuration management in python apps. Its goal is to make configurations management
Expand Down Expand Up @@ -341,6 +349,16 @@ To run tests install all of this tools and use appropriate CLI:

> behave ./configuration_py/tests/acceptance/

Optionally it's possible to install dependencies and run tests via Makefile:

> make deps-test

> make test

For a full list of commands check

> make help

## License

[MIT](https://github.com/Ferroman/configuration.py/blob/master/LICENSE) © Bogdan Frankovskyi
19 changes: 11 additions & 8 deletions circle.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
machine:
python:
version: 2.7.11
dependencies:
pre:
- pip install behave sure

pre:
- make deps-test
test:
override:
- mkdir -p $CIRCLE_TEST_REPORTS/nosetests
- mkdir -p $CIRCLE_TEST_REPORTS/behave
- python setup.py nosetests --with-xunit --xunit-file=$CIRCLE_TEST_REPORTS/nosetests/nosetests.xml --with-coverage --cover-package=configuration_py
- behave --tags=~@skip --format=progress3 --junit --junit-directory $CIRCLE_TEST_REPORTS/behave/ ./configuration_py/tests/acceptance/
override:
- make quality
- make complexity
- make test
post:
- make reports
2 changes: 1 addition & 1 deletion configuration_py/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from configuration_load import load
from configuration_load import load
29 changes: 15 additions & 14 deletions configuration_py/configuration_load.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ def _generate_possible_config_file_names(config_name):
yield "{config_name}.{config_extension}".format(config_name=config_name, config_extension=config_extension)

for parser_extension in supported_parser_extensions:
yield "{config_name}.{config_extension}.{parser_extension}"\
.format(config_name=config_name,
config_extension=config_extension,
parser_extension=parser_extension)
yield "{config_name}.{config_extension}.{parser_extension}".format(
config_name=config_name,
config_extension=config_extension,
parser_extension=parser_extension
)


def _generate_possible_paths_to_config(config_name, config_folder):
Expand All @@ -38,8 +39,8 @@ def _get_path_to_config_file(config_name, config_folder):
existed_paths = list(_find_existing_config_file(config_name, config_folder))

if len(existed_paths) == 0:
raise IOError("No any of config files for '{file_names}' found in '{config_folder}' folder" \
.format(file_names=config_name, config_folder=config_folder))
raise IOError("No any of config files for '{file_names}' found in '{config_folder}' folder".format(
file_names=config_name, config_folder=config_folder))
if len(existed_paths) > 1:
raise EnvironmentError(
"Found more than one config file for '{config_name}' found in '{config_folder}': {existed_paths}".format(
Expand All @@ -51,8 +52,8 @@ def _get_path_to_config_file(config_name, config_folder):


def _read_config_file(path_to_file):
with open(path_to_file) as f:
file_content = f.read()
with open(path_to_file) as config:
file_content = config.read()
return file_content


Expand All @@ -62,9 +63,9 @@ def _get_environment_label_from_os(available_config_environments):
if environment_value:
return environment_value.lower()
else:
raise EnvironmentError("Current environment for application does not set. To set environment, set one of the " \
"available environments ({available_environments}) to ENV or ENVIRONMENT system " \
"variable or provide it directly to the 'load' function." \
raise EnvironmentError("Current environment for application does not set. To set environment, set one of the "
"available environments ({available_environments}) to ENV or ENVIRONMENT system "
"variable or provide it directly to the 'load' function."
.format(available_environments=available_config_environments))


Expand All @@ -76,8 +77,8 @@ def _normalize_environment_label(label, available_config_environments):
label = 'production'

if label not in available_config_environments:
raise EnvironmentError("There is no configuration for given environment '{label}'. Please, provide " \
"configuration section for this environment in config file " \
raise EnvironmentError("There is no configuration for given environment '{label}'. Please, provide "
"configuration section for this environment in config file "
.format(label=label))

return label
Expand Down Expand Up @@ -118,7 +119,7 @@ def _load_config_by_name(configuration, config_folder, context):
return _load_config_from_file(path_to_config_file, context)


def load(configuration='application', environment=None, folder=None, context={}):
def load(configuration='application', environment=None, folder=None, context=None):
"""
:param configuration: name of configuration file to load. Could use names without extensions
:param environment: environment, that should be load from config file. 'production', for example
Expand Down
4 changes: 2 additions & 2 deletions configuration_py/parser_lookup.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@ def _lookup_for_available_parsers():
full_package_name = '%s.%s' % (parsers_folder, package_name)
if full_package_name not in sys.modules:
module = importer.find_module(package_name).load_module(package_name)
for name, obj in inspect.getmembers(module):
for _, obj in inspect.getmembers(module):
if inspect.isclass(obj) and issubclass(obj, BaseConfigParser) and obj is not BaseConfigParser:
yield obj


def _get_parsers_folder_path():
current_dir_path = os.path.dirname(os.path.realpath(__file__))
return os.path.join(current_dir_path, PARSERS_DIR_NAME)
return os.path.join(current_dir_path, PARSERS_DIR_NAME)
2 changes: 1 addition & 1 deletion configuration_py/parsers/base_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ class BaseConfigParser(object):
def extensions(self):
raise NotImplementedError

def parse(self, file_content, context={}):
def parse(self, file_content, context=None):
raise NotImplementedError
4 changes: 2 additions & 2 deletions configuration_py/parsers/json_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ class JSONParser(BaseConfigParser):

extensions = ('json', )

def parse(self, file_content, context={}):
def parse(self, file_content, context=None):
config_dict = json.loads(file_content)

if not config_dict or type(config_dict) is not dict:
if not config_dict or not isinstance(config_dict, dict):
raise EnvironmentError('Config file does not contain config variables')
return config_dict
16 changes: 7 additions & 9 deletions configuration_py/parsers/string_template_processor.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
from string import Template

import os
import string

from configuration_py.parsers.base_parser import BaseConfigParser


class ConfigStringTemplateProcessor(BaseConfigParser):

extensions = 'tmpl', 'strtmpl'

def parse(self, file_content, context={}):
context.update(os.environ)
def parse(self, file_content, context=None):
context = dict(context or {}, **os.environ)
try:
return Template(file_content).substitute(context)
except KeyError, e:
return string.Template(file_content).substitute(context)
except KeyError, exc:
raise EnvironmentError(
'Config try to use {e} variable which does not exists. Pass variable to load context '
'or set it to the environment.'.format(e=e))
'Config try to use {exc} variable which does not exists. Pass variable to load context '
'or set it to the environment.'.format(exc=exc))
4 changes: 2 additions & 2 deletions configuration_py/parsers/yaml_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ class YAMLParser(BaseConfigParser):

extensions = 'yml', 'yaml'

def parse(self, file_content, context={}):
def parse(self, file_content, context=None):
config_dict = yaml.load(file_content)
if not config_dict or type(config_dict) is not dict:
if not config_dict or not isinstance(config_dict, dict):
raise EnvironmentError('Config file does not contain config variables')
return config_dict
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ def step_impl(context, config_file, folder):
@step('"{key}" is set to "{value}"')
def step_impl(context, key, value):
value_string_representation = str(context.app_config.get(key))
value_string_representation.should.be.equal(value)
value_string_representation.should.be.equal(value)
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,4 @@ def step_impl(context, config_name, folder, environment):

@step("it should looks like dictionary")
def step_impl(context):
context.app_config.should.be.a('dict')
context.app_config.should.be.a('dict')
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ def test_should_evaluate_current_environment_variables(self):

def test_should_raise_exception_if_no_variable_passed_but_expected(self):
content = "development: variable: $NOTEXIST"
self.assertRaises(EnvironmentError, ConfigStringTemplateProcessor().parse, content)
self.assertRaises(EnvironmentError, ConfigStringTemplateProcessor().parse, content)
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ def test_should_call_generate_possible_paths_to_config_with_the_correct_paramete
def test_should_return_path_if_it_exists(self, mock):
expected_value = '/config/testconfig.yaml'
paths = _find_existing_config_file('testconfig', '/config')
self.assertIn(expected_value, paths)
self.assertIn(expected_value, paths)
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class TestFindParserClassByExtension(TestCase):
@patch('configuration_py.parser_lookup._lookup_for_available_parsers')
def test_should_return_parser_class_when_extension_supported(self, mock):
parser_class_mock = MagicMock()
parser_class_mock.extensions = ('yaml', )
parser_class_mock.extensions = ('yaml',)
mock.return_value = [parser_class_mock]

expected_value = parser_class_mock
Expand All @@ -19,7 +19,7 @@ def test_should_return_parser_class_when_extension_supported(self, mock):
@patch('configuration_py.parser_lookup._lookup_for_available_parsers')
def test_should_raise_exceptions_when_parsers_class_support_extension(self, mock):
parser_class_mock = MagicMock()
parser_class_mock.extensions = ('yaml', )
parser_class_mock.extensions = ('yaml',)
mock.return_value = [parser_class_mock]

self.assertRaises(EnvironmentError, _find_parser_class_by_extension, 'json')
self.assertRaises(EnvironmentError, _find_parser_class_by_extension, 'json')
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ def test_generate_possible_names_return_name_with_yaml_long_ext_in_it(self):
def test_generate_possible_names_return_name_with_yml_short_ext_in_it(self):
expected_value = 'testconfig.yml'
actual_value = _generate_possible_config_file_names('testconfig')
self.assertIn(expected_value, actual_value)
self.assertIn(expected_value, actual_value)
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@ def test_should_raise_exception_if_no_files_found(self, mock):
@patch('configuration_py.configuration_load._find_existing_config_file', return_value=['/path/test.yaml', '/path/test.yml'])
def test_should_raise_exception_if_more_than_one_config_found(self, mock):

self.assertRaises(EnvironmentError, _get_path_to_config_file, 'test', 'config')
self.assertRaises(EnvironmentError, _get_path_to_config_file, 'test', 'config')
27 changes: 13 additions & 14 deletions configuration_py/tests/unit/test_load.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
from unittest import TestCase

from mock import patch

from configuration_py.configuration_load import load

# from unittest import TestCase
#
# from mock import patch
#
# from configuration_py.configuration_load import load

# class TestGetAvailableConfigEnvList(TestCase):
# @patch('configuration_py.configuration_py._normalize_environment_label', return_value={'test': ''})
# @patch('configuration_py.configuration_py._get_available_config_environments_list')
# @patch('configuration_py.configuration_py._load_yaml_config_by_name')
# def test_should_call_load_yaml_config_by_name_with_correct_parameters(self, load_mock, available_mock, normalize_mock):
# config_name = 'test'
# config_folder = './config'
# load(config_name, config_name)
# load_mock.assert_called_once_with(config_name, config_folder)
# @patch('configuration_py.configuration_py._normalize_environment_label', return_value={'test': ''})
# @patch('configuration_py.configuration_py._get_available_config_environments_list')
# @patch('configuration_py.configuration_py._load_yaml_config_by_name')
# def test_should_call_load_yaml_config_by_name_with_correct_parameters(self, load_mock, available_mock, normalize_mock):
# config_name = 'test'
# config_folder = './config'
# load(config_name, config_name)
# load_mock.assert_called_once_with(config_name, config_folder)
2 changes: 1 addition & 1 deletion configuration_py/tests/unit/test_read_config_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ def test_should_return_file_content(self):
with patch("__builtin__.open", mock_open(read_data="data")) as mock_file:
actual_data = _read_config_file('/path/to/config.yaml')

self.assertEqual(expected_data, actual_data)
self.assertEqual(expected_data, actual_data)
Loading

0 comments on commit aaf562a

Please sign in to comment.