diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f37120d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +# http://editorconfig.org + +root = true + +[*] +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true +insert_final_newline = true +charset = utf-8 +end_of_line = lf + +[*.bat] +indent_style = tab +end_of_line = crlf + +[LICENSE] +insert_final_newline = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..84229f4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,102 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# dotenv +.env + +# virtualenv +.venv +venv/ +ENV/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ diff --git a/HISTORY.rst b/HISTORY.rst new file mode 100644 index 0000000..801e98b --- /dev/null +++ b/HISTORY.rst @@ -0,0 +1,8 @@ +======= +History +======= + +0.1.0 (2018-04-08) +------------------ + +* First release on PyPI. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fb4a02f --- /dev/null +++ b/LICENSE @@ -0,0 +1,16 @@ +Apache Software License 2.0 + +Copyright (c) 2018, Florian Ludwig + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..7864549 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,11 @@ +include CONTRIBUTING.rst +include HISTORY.rst +include LICENSE +include README.rst + +recursive-include tests * +recursive-exclude * __pycache__ +recursive-exclude * *.py[co] + +recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif +recursive-include tests *.yml diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..87569a9 --- /dev/null +++ b/README.rst @@ -0,0 +1,8 @@ +yacm: yet another config module +=============================== + +.. image:: https://img.shields.io/pypi/v/yacm.svg + :target: https://pypi.python.org/pypi/yacm + + +* Free software: Apache Software License 2.0 diff --git a/requirements_dev.txt b/requirements_dev.txt new file mode 100644 index 0000000..760c04a --- /dev/null +++ b/requirements_dev.txt @@ -0,0 +1,11 @@ +pip==9.0.1 +bumpversion==0.5.3 +wheel==0.30.0 +watchdog==0.8.3 +flake8==3.5.0 +coverage==4.5.1 +Sphinx==1.7.1 +twine==1.10.0 + +pytest==3.4.2 +pytest-runner==2.11.1 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..41ed617 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,26 @@ +[bumpversion] +current_version = 0.1.0 +commit = True +tag = True + +[bumpversion:file:setup.py] +search = version='{current_version}' +replace = version='{new_version}' + +[bumpversion:file:yacm/__init__.py] +search = __version__ = '{current_version}' +replace = __version__ = '{new_version}' + +[bdist_wheel] +universal = 1 + +[flake8] +exclude = docs + +[aliases] +# Define setup.py command aliases here +test = pytest + +[tool:pytest] +collect_ignore = ['setup.py'] + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..47635e9 --- /dev/null +++ b/setup.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python + +from setuptools import setup, find_packages + +with open('README.rst') as readme_file: + readme = readme_file.read() + +with open('HISTORY.rst') as history_file: + history = history_file.read() + +requirements = ['PyYAML>=3.10'] + +setup_requirements = [] + +test_requirements = ['pytest', ] + +setup( + author="Florian Ludwig", + author_email='f.ludwig@greyrook.com', + classifiers=[ + 'Development Status :: 2 - Pre-Alpha', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Natural Language :: English', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + ], + description="yet another config module", + entry_points={ + 'console_scripts': [ + 'yacm=yacm.cli:main', + ], + }, + install_requires=requirements, + license="Apache Software License 2.0", + long_description=readme + '\n\n' + history, + include_package_data=True, + keywords='yacm', + name='yacm', + packages=find_packages(include=['yacm']), + setup_requires=setup_requirements, + test_suite='tests', + tests_require=test_requirements, + url='https://github.com/FlorianLudwig/yacm', + version='0.1.0', + zip_safe=False, +) diff --git a/tests/configs/no_rwdb.yml b/tests/configs/no_rwdb.yml new file mode 100644 index 0000000..3b32897 --- /dev/null +++ b/tests/configs/no_rwdb.yml @@ -0,0 +1,3 @@ +plugins: + db: False + someother_db: True diff --git a/tests/configs/simple.yml b/tests/configs/simple.yml new file mode 100644 index 0000000..de23dee --- /dev/null +++ b/tests/configs/simple.yml @@ -0,0 +1,3 @@ +plugins: + db: True + diff --git a/tests/test_yacm.py b/tests/test_yacm.py new file mode 100644 index 0000000..7ca6761 --- /dev/null +++ b/tests/test_yacm.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import yacm + +import pytest + +import yacm + +BASE = os.path.dirname(__file__) + + +def test_merge(): + # check to merge two config files + foo = {'c': {'foo': 1}} + cfg = {} + yacm.merge(cfg, foo) + assert cfg == foo + + yacm.merge(cfg, {}) + assert cfg == foo + + # configs must be dict of dicts + with pytest.raises(AttributeError): + yacm.merge({}, 1) + + with pytest.raises(AttributeError): + yacm.merge({}, {'a': 1}) + + +def test_simple(): + cfg = yacm.read_file(BASE + '/configs/simple.yml') + assert isinstance(cfg, dict) + assert cfg['plugins']['db'] is True + + cfg = yacm.read_file([ + BASE + '/configs/simple.yml', + BASE + '/configs/no_rwdb.yml' + ]) + assert cfg['plugins']['db'] is False + assert cfg['plugins']['someother_db'] is True + + cfg = yacm.read_file([ + BASE + '/configs/no_rwdb.yml', + BASE + '/configs/simple.yml', + ]) + assert cfg['plugins']['db'] is True + assert cfg['plugins']['someother_db'] is True + + +def test_config_paths(): + """when inside a virtualenv we are looking for more configs""" + env = os.environ.get('VIRTUAL_ENV') + if env is not None: + del os.environ['VIRTUAL_ENV'] + configs = yacm.get_config_paths('rw') + os.environ['VIRTUAL_ENV'] = '/tmp' + configs_ve = yacm.get_config_paths('rw') + if env is None: + del os.environ['VIRTUAL_ENV'] + else: + os.environ['VIRTUAL_ENV'] = env + + assert len(configs) < len(configs_ve) diff --git a/yacm/__init__.py b/yacm/__init__.py new file mode 100644 index 0000000..eeaad4a --- /dev/null +++ b/yacm/__init__.py @@ -0,0 +1,87 @@ +# Copyright 2014-2018 Florian Ludwig +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +"""Load yaml based configuration""" + +__version__ = '0.1.0' + +import os +import pkg_resources +import logging + +import yaml + + +LOG = logging.getLogger(__name__) + + +def read_file(paths) -> dict: + """read config from path or list of paths + + :param str|list[str] paths: path or list of paths + :return dict: loaded and merged config + """ + + if isinstance(paths, str): + paths = [paths] + + re = {} + for path in paths: + cfg = yaml.load(open(path)) + merge(re, cfg) + + return re + + +def merge(base_config:dict, additional_config:dict): + """merge `additional_config` into `base_config`""" + for category, category_data in additional_config.items(): + if isinstance(category_data, dict): + base_config.setdefault(category, {}) + for key, value in category_data.items(): + base_config[category][key] = value + else: + raise AttributeError( + 'Config files must be in format {category: {key: value, ...}, ...}') + + +def get_config_paths(module_name) -> []: + """determine configs to load""" + cfg_name = module_name + '.yml' + paths = [] + paths += [ + pkg_resources.resource_filename(module_name, 'config_default.yml'), + '/etc/' + cfg_name, + os.path.expanduser('~/.') + cfg_name + ] + if 'VIRTUAL_ENV' in os.environ: + paths.append(os.environ['VIRTUAL_ENV'] + '/etc/' + cfg_name) + return paths + + +def read_configs(module_name, extra_configs=None) -> dict: + """ + """ + cfg = {} + paths = get_config_paths(module_name) + if extra_configs: + if isinstance(extra_configs, list): + paths.extend(extra_configs) + else: + paths.append(extra_configs) + + for path in paths: + if os.path.exists(path): + LOG.info('reading config: ' + path) + merge(cfg, read_file(path)) + return cfg