├── docs ├── _static │ └── .gitignore ├── _plantuml │ └── plantuml.jar ├── usage.rst ├── installation.rst ├── index.rst ├── make.bat ├── Makefile └── conf.py ├── tests ├── unicode.txt ├── loader_dev_params.py ├── conftest.py ├── __init__.py ├── test_utils.py └── test_proxy.py ├── Dockerfile ├── requirements ├── requirements-base.txt └── requirements-testing.txt ├── requirements.txt ├── etcd_settings ├── __init__.py ├── apps.py ├── utils.py └── proxy.py ├── docker-compose.yml ├── MANIFEST.in ├── setup.cfg ├── .pypirc ├── LICENSE ├── tox.ini ├── .travis.yml ├── setup_gen.py ├── Makefile ├── .gitignore └── README.rst /docs/_static/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unicode.txt: -------------------------------------------------------------------------------- 1 | éá᳤ 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM kpndigital/tox:latest 2 | 3 | WORKDIR /app 4 | 5 | CMD ["tox"] 6 | -------------------------------------------------------------------------------- /requirements/requirements-base.txt: -------------------------------------------------------------------------------- 1 | Django>=1.7.5 2 | six>=1.10.0,<2.0.0 3 | etcd-config>=1.0.4 4 | -------------------------------------------------------------------------------- /tests/loader_dev_params.py: -------------------------------------------------------------------------------- 1 | # Sample of dev_params used at tests.test_loader 2 | FOO = 'bar' 3 | -------------------------------------------------------------------------------- /docs/_plantuml/plantuml.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kpn/django-etcd-settings/HEAD/docs/_plantuml/plantuml.jar -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | Usage 3 | ======== 4 | 5 | To use etcd_settings in a project:: 6 | 7 | import etcd_settings 8 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Additional packages 2 | -r requirements/requirements-base.txt 3 | -r requirements/requirements-testing.txt 4 | -------------------------------------------------------------------------------- /etcd_settings/__init__.py: -------------------------------------------------------------------------------- 1 | from .proxy import proxy 2 | 3 | settings = proxy 4 | 5 | default_app_config = 'etcd_settings.apps.EtcdMonitor' 6 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Installation 3 | ============ 4 | 5 | At the command line:: 6 | 7 | $ pip install django-etcd-settings 8 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | etcd: 2 | image: elcolio/etcd 3 | ports: 4 | - "2379:2379" 5 | app: 6 | build: . 7 | volumes: 8 | - .:/app 9 | command: make build 10 | links: 11 | - etcd 12 | -------------------------------------------------------------------------------- /requirements/requirements-testing.txt: -------------------------------------------------------------------------------- 1 | tox 2 | isort 3 | flake8 4 | mock==1.3.0 5 | py-pkgversion 6 | 7 | # PyTest for running the tests. 8 | pytest 9 | pytest-cov 10 | 11 | # docs 12 | sphinx 13 | sphinxcontrib-plantuml 14 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include VERSION 3 | include LICENSE 4 | 5 | recursive-include tests * 6 | recursive-exclude * __pycache__ 7 | recursive-exclude * *.py[co] 8 | 9 | recursive-include docs *.rst conf.py Makefile make.bat 10 | -------------------------------------------------------------------------------- /etcd_settings/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | from etcd_settings.proxy import proxy 3 | 4 | 5 | class EtcdMonitor(AppConfig): 6 | 7 | name = 'etcd_settings' 8 | verbose_name = 'EtcdMonitor starting the threads for long polling' 9 | 10 | def ready(self): 11 | proxy.start_monitors() 12 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | exclude = build,.git 3 | ignore = E902,W503 4 | max-line-length = 79 5 | 6 | [isort] 7 | combine_as_imports = true 8 | default_section = THIRDPARTY 9 | include_trailing_comma = true 10 | known_first_party = de 11 | multi_line_output = 5 12 | sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER 13 | not_skip = __init__.py 14 | 15 | [wheel] 16 | universal = 1 17 | -------------------------------------------------------------------------------- /.pypirc: -------------------------------------------------------------------------------- 1 | [distutils] # this tells distutils what package indexes you can push to 2 | index-servers = 3 | pypi 4 | pypitest 5 | 6 | [pypi] 7 | repository: https://pypi.python.org/pypi 8 | username: {{your_username}} # FIXME Set Username 9 | password: {{your_password}} # FIXME Set Password 10 | 11 | [pypitest] 12 | repository: https://testpypi.python.org/pypi 13 | username: {{your_username}} # FIXME Set Username 14 | password: {{your_password}} # FIXME Set Password 15 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | class TestSettings(object): 2 | 3 | ETCD_PREFIX = '/config/etcd_settings' 4 | ETCD_ENV = 'test' 5 | ETCD_HOST = 'etcd' 6 | ETCD_PORT = 2379 7 | ETCD_USERNAME = 'test' 8 | ETCD_PASSWORD = 'test' 9 | ETCD_DETAILS = dict( 10 | host='etcd', 11 | port=2379, 12 | prefix='/config/etcd_settings', 13 | username='test', 14 | password='test' 15 | ) 16 | 17 | 18 | settings = TestSettings() 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2015 KPN Digital 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this application except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. django-etcd-settings documentation master file, created by 2 | sphinx-quickstart on Tue Jul 9 22:26:36 2013. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | A dynamic settings management solution for django using ETCD 7 | ============================================================ 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | installation 15 | usage 16 | 17 | Indices and tables 18 | ================== 19 | 20 | * :ref:`genindex` 21 | * :ref:`modindex` 22 | * :ref:`search` 23 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | import django 2 | from django.conf import global_settings, settings 3 | 4 | settings.configure( 5 | # Minimal django settings 6 | DATABASES={ 7 | 'default': { 8 | 'ENGINE': 'django.db.backends.sqlite3', 9 | 'NAME': 'unittest.sqlite', 10 | 'TEST_NAME': ':memory:', 11 | }, 12 | }, 13 | MIDDLEWARE_CLASSES=global_settings.MIDDLEWARE_CLASSES, 14 | ROOT_URLCONF=None, 15 | INSTALLED_APPS=['etcd_settings'], 16 | DJES_DEV_PARAMS=None, 17 | DJES_REQUEST_GETTER=None, 18 | DJES_ENV='dev', 19 | DJES_ETCD_DETAILS=None, 20 | DJES_WSGI_FILE=None 21 | # DJES_ETCD_DETAILS=dict(host='localhost', port=4000, protocol='http', 22 | # prefix='/config/my-django-app') 23 | ) 24 | if hasattr(django, 'setup'): 25 | django.setup() 26 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts=--tb=short 3 | 4 | [tox] 5 | envlist = py27,py35,isort-check,isort-fix,lint,docs 6 | skipsdist = true 7 | 8 | [testenv] 9 | commands = 10 | py.test tests --cov-report=term-missing --cov-report=html --cov-report=xml --cov-append --cov=etcd_settings -s {posargs} 11 | deps = 12 | -rrequirements/requirements-base.txt 13 | -rrequirements/requirements-testing.txt 14 | 15 | [testenv:lint] 16 | commands = flake8 etcd_settings tests 17 | deps = flake8 18 | 19 | [testenv:docs] 20 | commands = 21 | sphinx-build -W -b html {toxinidir}/docs {toxinidir}/docs/_build/html 22 | deps = 23 | -rrequirements/requirements-testing.txt 24 | 25 | [testenv:isort-check] 26 | commands = isort -rc -c etcd_settings tests 27 | deps = isort 28 | 29 | [testenv:isort-fix] 30 | commands = isort -rc etcd_settings tests 31 | deps = isort 32 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | sudo: required 3 | services: 4 | - docker 5 | script: 6 | - make docker/build 7 | before_install: 8 | - pip install codecov 9 | after_success: 10 | - codecov 11 | deploy: 12 | provider: pypi 13 | user: kpn-digital 14 | skip_cleanup: true 15 | on: 16 | tags: true 17 | all_branches: true 18 | password: 19 | secure: gbKikNWaUoKbMjs1IL0PUQaYfqG00TQsIFJwIZq8Ri7wS6dBB9QXZIiUw2uhVrGV6/nXOkqijyxlHsC6Boan6sDgZ3joT9cSlcIq7zur3lT13R28B7uvwIPLaMnz5O+j3KAhB7kSUiJw44Eob9DYmQ0quwwS7V15dKOc49XlxQY6i8qP1KWJ1epkmmgJZIJ2JZNCVy3zAXdRiYD3+yxyLVbbtnl7cLTz7KmmLpnQwAjMV8bD1DqrkLHlWLoIuU+hG9bPlJkLSt749dusBrP2aezEesSPXCWig9g0raBgS7K8MpI2yThztnN1QZFtYoHSdmF8uMSaZ59vwV2tn6yoRC+cOhmHehCmo7QmBXhldk8XhnhCaZvj9R2Ajw+XB5xu1fqHlG89UZKPFVZqTUWuGBmiDb3yeadVeuLJ9EKSSroAHjjyA151hmZ1G4v20nnvuKaJ3TmWzWVHynoWe/jBNkta1kPSJ42g+e9re6V6Hus6euwh+raZQVEj5pAlg86Par8fR9JnsJsuktHDM1OVEF+54FKfLQYUuuiDBGZ/6okK3FCH6DYjQp2Y62iIIwS6EflM8N/2aEmyevXN6j51l+UGpSKCzJz3VjU5b5OGsUV777+KDxaIJNhfy5gFZiFZDV1yB1bCqxNWuWaRj4/vITxx7SQtWvfoAJT3BJZK9aQ= 20 | -------------------------------------------------------------------------------- /etcd_settings/utils.py: -------------------------------------------------------------------------------- 1 | import copy 2 | import os 3 | from collections import Mapping 4 | 5 | 6 | def dict_rec_update(d, u): 7 | """Nested update of a dict, handy for overriding settings""" 8 | # https://stackoverflow.com/questions/3232943/update-value-of-a-nested-dictionary-of-varying-depth 9 | for k, v in u.items(): 10 | if isinstance(v, Mapping): 11 | r = dict_rec_update(d.get(k, {}), v) 12 | d[k] = r 13 | else: 14 | d[k] = u[k] 15 | return d 16 | 17 | 18 | def find_project_root(root_indicator='manage.py', current=os.getcwd()): 19 | parent = os.path.dirname(current) 20 | if root_indicator in os.listdir(current): 21 | return current 22 | elif current == parent: 23 | # We are at '/' already! 24 | raise IOError('Not found: {}'.format(root_indicator)) 25 | else: 26 | return find_project_root(root_indicator, parent) 27 | 28 | 29 | def copy_if_mutable(value): 30 | """ 31 | Copy function handling mutable values (only dicts and lists). 32 | """ 33 | if type(value) in (dict, list): 34 | return copy.deepcopy(value) 35 | return value 36 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import unittest 3 | 4 | from etcd_config import utils 5 | 6 | 7 | class TestLoggingFilter(unittest.TestCase): 8 | 9 | def setUp(self): 10 | self.logger_filter = utils.IgnoreMaxEtcdRetries() 11 | 12 | def test_log_record_without_args(self): 13 | self.assertTrue( 14 | self.logger_filter.filter( 15 | logging.LogRecord( 16 | 'etcd.client', 17 | logging.ERROR, 18 | '/', 19 | 0, 20 | 'Read timed out', 21 | tuple(), 22 | None 23 | ) 24 | ) 25 | ) 26 | 27 | def test_log_record_match(self): 28 | self.assertFalse( 29 | self.logger_filter.filter( 30 | logging.LogRecord( 31 | 'etcd.client', 32 | logging.ERROR, 33 | '/', 34 | 0, 35 | 'Error %s exception %s', 36 | ('Read timed out', 'MaxRetryError'), 37 | None 38 | ) 39 | ) 40 | ) 41 | -------------------------------------------------------------------------------- /setup_gen.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from pkgversion import list_requirements, pep440_version, write_setup_py 4 | from setuptools import find_packages 5 | 6 | write_setup_py( 7 | name='django-etcd-settings', 8 | version=pep440_version(), 9 | description="A dynamic settings management solution for Django using ETCD", 10 | long_description=open('README.rst').read(), 11 | author="Enrique Paz", 12 | author_email='quiquepaz@gmail.com', 13 | url='https://github.com/kpn-digital/django-etcd-settings', 14 | install_requires=list_requirements('requirements/requirements-base.txt'), 15 | packages=find_packages(exclude=['tests*']), 16 | tests_require=['tox'], 17 | include_package_data=True, 18 | zip_safe=False, 19 | classifiers=[ 20 | 'Development Status :: 5 - Production/Stable', 21 | 'Environment :: Web Environment', 22 | 'Intended Audience :: Developers', 23 | 'Operating System :: OS Independent', 24 | 'Programming Language :: Python', 25 | 'Programming Language :: Python :: 2', 26 | 'Programming Language :: Python :: 2.7', 27 | 'Topic :: Internet :: WWW/HTTP', 28 | ] 29 | ) 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # This Makefile requires the following commands to be available: 2 | # * virtualenv-2.7 3 | # * python2.7 4 | # * docker 5 | # * docker-compose 6 | 7 | DEPS:=requirements.txt 8 | DOCKER_COMPOSE:=$(shell which docker-compose) 9 | 10 | PIP:="venv/bin/pip" 11 | CMD_FROM_VENV:=". venv/bin/activate; which" 12 | TOX=$(shell "$(CMD_FROM_VENV)" "tox") 13 | PYTHON=$(shell "$(CMD_FROM_VENV)" "python") 14 | TOX_PY_LIST="$(shell $(TOX) -l | grep ^py | xargs | sed -e 's/ /,/g')" 15 | 16 | .PHONY: clean docsclean pyclean test lint isort docs docker setup.py 17 | 18 | tox: clean venv 19 | $(TOX) 20 | 21 | pyclean: 22 | @find . -name *.pyc -delete 23 | @rm -rf *.egg-info build 24 | @rm -rf coverage.xml .coverage 25 | 26 | docsclean: 27 | @rm -fr docs/_build/ 28 | @rm -fr docs/api/ 29 | 30 | clean: pyclean docsclean 31 | @rm -rf venv 32 | 33 | venv: 34 | @virtualenv -p python2.7 venv 35 | @$(PIP) install -U "pip>=7.0" -q 36 | @$(PIP) install -r $(DEPS) 37 | 38 | test: venv pyclean 39 | $(TOX) -e $(TOX_PY_LIST) 40 | 41 | test/%: venv pyclean 42 | $(TOX) -e $(TOX_PY_LIST) -- $* 43 | 44 | lint: venv 45 | @$(TOX) -e lint 46 | @$(TOX) -e isort-check 47 | 48 | isort: venv 49 | @$(TOX) -e isort-fix 50 | 51 | docs: venv docsclean 52 | @$(TOX) -e docs 53 | 54 | docker: 55 | $(DOCKER_COMPOSE) run --rm app bash 56 | 57 | docker/%: 58 | $(DOCKER_COMPOSE) run --rm app make $* 59 | 60 | setup.py: venv 61 | $(PYTHON) setup_gen.py 62 | $(PYTHON) setup.py check --restructuredtext 63 | 64 | publish: setup.py 65 | $(PYTHON) setup.py sdist upload 66 | 67 | build: clean venv tox setup.py 68 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/pycharm,osx,linux,virtualenv,python,vim 2 | 3 | ### PyCharm ### 4 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio 5 | 6 | *.iml 7 | 8 | ## Directory-based project format: 9 | .idea/ 10 | # if you remove the above rule, at least ignore the following: 11 | 12 | # User-specific stuff: 13 | # .idea/workspace.xml 14 | # .idea/tasks.xml 15 | # .idea/dictionaries 16 | 17 | # Sensitive or high-churn files: 18 | # .idea/dataSources.ids 19 | # .idea/dataSources.xml 20 | # .idea/sqlDataSources.xml 21 | # .idea/dynamic.xml 22 | # .idea/uiDesigner.xml 23 | 24 | # Gradle: 25 | # .idea/gradle.xml 26 | # .idea/libraries 27 | 28 | # Mongo Explorer plugin: 29 | # .idea/mongoSettings.xml 30 | 31 | ## File-based project format: 32 | *.ipr 33 | *.iws 34 | 35 | ## Plugin-specific files: 36 | 37 | # IntelliJ 38 | /out/ 39 | 40 | # mpeltonen/sbt-idea plugin 41 | .idea_modules/ 42 | 43 | # JIRA plugin 44 | atlassian-ide-plugin.xml 45 | 46 | # Crashlytics plugin (for Android Studio and IntelliJ) 47 | com_crashlytics_export_strings.xml 48 | crashlytics.properties 49 | crashlytics-build.properties 50 | 51 | 52 | ### OSX ### 53 | .DS_Store 54 | .AppleDouble 55 | .LSOverride 56 | 57 | # Icon must end with two \r 58 | Icon 59 | 60 | 61 | # Thumbnails 62 | ._* 63 | 64 | # Files that might appear in the root of a volume 65 | .DocumentRevisions-V100 66 | .fseventsd 67 | .Spotlight-V100 68 | .TemporaryItems 69 | .Trashes 70 | .VolumeIcon.icns 71 | 72 | # Directories potentially created on remote AFP share 73 | .AppleDB 74 | .AppleDesktop 75 | Network Trash Folder 76 | Temporary Items 77 | .apdisk 78 | 79 | 80 | ### Linux ### 81 | *~ 82 | 83 | # KDE directory preferences 84 | .directory 85 | 86 | # Linux trash folder which might appear on any partition or disk 87 | .Trash-* 88 | 89 | 90 | ### VirtualEnv ### 91 | # Virtualenv 92 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ 93 | .Python 94 | [Bb]in 95 | [Ii]nclude 96 | [Ll]ib 97 | [Ss]cripts 98 | pyvenv.cfg 99 | pip-selfcheck.json 100 | 101 | 102 | ### Python ### 103 | # Byte-compiled / optimized / DLL files 104 | __pycache__/ 105 | *.py[cod] 106 | *$py.class 107 | 108 | # C extensions 109 | *.so 110 | 111 | # Distribution / packaging 112 | .Python 113 | env/ 114 | build/ 115 | develop-eggs/ 116 | dist/ 117 | downloads/ 118 | eggs/ 119 | .eggs/ 120 | lib/ 121 | lib64/ 122 | parts/ 123 | sdist/ 124 | var/ 125 | *.egg-info/ 126 | .installed.cfg 127 | *.egg 128 | .python-version 129 | 130 | # PyInstaller 131 | # Usually these files are written by a python script from a template 132 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 133 | *.manifest 134 | *.spec 135 | 136 | # Installer logs 137 | pip-log.txt 138 | pip-delete-this-directory.txt 139 | 140 | # Unit test / coverage reports 141 | htmlcov/ 142 | .tox/ 143 | .coverage 144 | .coverage.* 145 | .cache 146 | nosetests.xml 147 | coverage.xml 148 | *,cover 149 | 150 | # Translations 151 | *.mo 152 | *.pot 153 | 154 | # Django stuff: 155 | *.log 156 | 157 | # Sphinx documentation 158 | docs/_build/ 159 | 160 | # PyBuilder 161 | target/ 162 | 163 | ### Vim ### 164 | [._]*.s[a-w][a-z] 165 | [._]s[a-w][a-z] 166 | *.un~ 167 | Session.vim 168 | .netrwhist 169 | *~ 170 | 171 | # Project files 172 | unittest.sqlite 173 | /setup.py 174 | -------------------------------------------------------------------------------- /etcd_settings/proxy.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | from importlib import import_module 4 | 5 | from django.conf import settings as django_settings 6 | from etcd_config.manager import EtcdConfigManager 7 | from etcd_config.utils import attrs_to_dir 8 | 9 | from .utils import copy_if_mutable, dict_rec_update, find_project_root 10 | 11 | 12 | class EtcdSettingsProxy(object): 13 | 14 | def __init__(self): 15 | self.env = getattr(django_settings, 'DJES_ENV', None) 16 | dev_params = getattr(django_settings, 'DJES_DEV_PARAMS', None) 17 | etcd_details = getattr(django_settings, 'DJES_ETCD_DETAILS', None) 18 | self._init_req_getter( 19 | getattr(django_settings, 'DJES_REQUEST_GETTER', None)) 20 | self._locate_wsgi_file( 21 | getattr(django_settings, 'DJES_WSGI_FILE', None)) 22 | if etcd_details is not None: 23 | self._etcd_mgr = EtcdConfigManager(dev_params, **etcd_details) 24 | self._config_sets = self._etcd_mgr.get_config_sets() 25 | self._env_defaults = self._etcd_mgr.get_env_defaults(self.env) 26 | else: 27 | self._etcd_mgr = None 28 | self._config_sets = dict() 29 | self._env_defaults = EtcdConfigManager.get_dev_params(dev_params) 30 | 31 | def _locate_wsgi_file(self, wsgi_file): 32 | if wsgi_file is None: 33 | self._wsgi_file = None 34 | elif wsgi_file.startswith(os.path.sep): 35 | self._wsgi_file = wsgi_file 36 | else: 37 | self._wsgi_file = os.path.join( 38 | find_project_root('manage.py'), 39 | wsgi_file) 40 | 41 | def _init_req_getter(self, s): 42 | if s is not None: 43 | r = re.compile('(?P.*)\.(?P[\w_]+)') 44 | m = re.match(r, s) 45 | mod_s = m.group('module') 46 | fun_s = m.group('f') 47 | mod = import_module(mod_s) 48 | self._req_getter = getattr(mod, fun_s) 49 | else: 50 | self._req_getter = None 51 | 52 | def _parse_req_config_sets(self): 53 | sets = [] 54 | if self._req_getter is not None: 55 | request = self._req_getter() 56 | if request and getattr(request, "META", None): 57 | sets = request.META.get('HTTP_X_DYNAMIC_SETTING', '').split() 58 | return sets 59 | 60 | def start_monitors(self): 61 | if self._etcd_mgr is not None: 62 | self._etcd_mgr.monitor_env_defaults( 63 | env=self.env, conf=self._env_defaults, 64 | wsgi_file=self._wsgi_file) 65 | self._etcd_mgr.monitor_config_sets(conf=self._config_sets) 66 | 67 | def __getattr__(self, attr): 68 | try: 69 | dj_value = getattr(django_settings, attr) 70 | dj_value_exists = True 71 | except AttributeError: 72 | dj_value_exists = False 73 | dj_value = None 74 | try: 75 | value = self._env_defaults[attr] 76 | value_exists = True 77 | except KeyError: 78 | value_exists = dj_value_exists 79 | value = dj_value 80 | 81 | for override_set in self._parse_req_config_sets(): 82 | config_set = self._config_sets.get(override_set, {}) 83 | if attr in config_set: 84 | new_value = config_set[attr] 85 | value = copy_if_mutable(value) 86 | if isinstance(value, dict) and isinstance(new_value, dict): 87 | dict_rec_update(value, new_value) 88 | else: 89 | value = new_value 90 | if value or value_exists: 91 | return value 92 | else: 93 | raise AttributeError(attr) 94 | 95 | def as_dict(self): 96 | items = attrs_to_dir(django_settings) 97 | items.update(self._env_defaults) 98 | return items 99 | 100 | 101 | proxy = EtcdSettingsProxy() 102 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Django ETCD Settings 2 | ===================== 3 | 4 | .. image:: https://secure.travis-ci.org/kpn-digital/django-etcd-settings.svg?branch=master 5 | :target: http://travis-ci.org/kpn-digital/django-etcd-settings?branch=master 6 | 7 | .. image:: https://img.shields.io/codecov/c/github/kpn-digital/django-etcd-settings/master.svg 8 | :target: http://codecov.io/github/kpn-digital/django-etcd-settings?branch=master 9 | 10 | .. image:: https://img.shields.io/pypi/v/django-etcd-settings.svg 11 | :target: https://pypi.python.org/pypi/django-etcd-settings 12 | 13 | .. image:: https://readthedocs.org/projects/django-etcd-settings/badge/?version=latest 14 | :target: http://django-etcd-settings.readthedocs.org/en/latest/?badge=latest 15 | 16 | 17 | NOT MAINTAINED 18 | -------------- 19 | 20 | This repository is not actively maintained anymore. 21 | 22 | Features 23 | -------- 24 | 25 | This application allows you to extend the Django settings as configured in the 26 | ``settings.py`` file with: 27 | 28 | * Environment dependent values 29 | * Values in different config sets, identified by name, which can be selected on 30 | a 'per request' basis using the ``X-DYNAMIC-SETTINGS`` HTTP header 31 | 32 | Both the added configuration values and config sets would live at ETCD, which 33 | will be continuously monitored by this library in order to transparently update 34 | your app settings upon changes. 35 | 36 | 37 | Backends 38 | -------- 39 | 40 | - ETCD 2.2.1 41 | 42 | 43 | Installation 44 | ------------ 45 | 46 | .. code-block:: bash 47 | 48 | $ pip install django-etcd-settings 49 | 50 | 51 | Usage 52 | ----- 53 | 54 | This Django application uses the following configuration keys: 55 | 56 | * ``DJES_ETCD_DETAILS``: a dict with 'host', 'port', 'protocol', 'prefix', 57 | 'long_polling_timeout' and 'long_polling_safety_delay' (both in seconds). 58 | 'prefix' is a string to be used as base path for all configuration 59 | managed by this app. 60 | i.e. '/config/api' will result in '/config/api/' and 61 | '/config/api/extensions/' to be used for environment defaults and 62 | config_sets respectively 63 | Timeouts default to 50 and 5 seconds respectively. 64 | If ``DJES_ETCD_SETTINGS`` is None, this app will start with no errors and 65 | etcd_settings.settings will resolve to django.conf.settings plus your 66 | DJES_DEV_PARAMS overwrites 67 | i.e. 68 | 69 | .. code-block:: python 70 | 71 | ETCD_DETAILS = dict( 72 | host='localhost', port=4000, protocol='http', 73 | long_polling_timout=50, long_polling_safety_delay=5 74 | ) 75 | 76 | * ``DJES_DEV_PARAMS``: A module with local overwrites, generally used for 77 | development. The overwrites must be capitalized module attributes. 78 | These overwrites will have preference over development settings on ETCD, 79 | but not over configset overwrites indicated by the ``X-DYNAMIC-SETTINGS`` 80 | HTTP header 81 | 82 | * ``DJES_ENV``: A string with the name of the environment in which the code is 83 | running. This will be used for accessing the env_defaults in 84 | ETCD in a directory with that name 85 | i.e. 'test', 'staging', 'prod'... 86 | 87 | * ``DJES_REQUEST_GETTER``: path to a function which accesses the HTTP request 88 | object being handled. Ensuring access to this value can be implemented 89 | with, for instance, middleware. This settings is only used to allow 90 | config overwrites on runtime based on predifined config_sets. In case you 91 | don't want to use this functionality, just set this setting to None 92 | i.e. 'middleware.thread_local.get_current_request' 93 | 94 | * ``DJES_WSGI_FILE``: path to the ``wsgi.py`` file for the django 95 | project. If not None, the monitoring of environment configuration will 96 | perform a ``touch`` of the file every time the env_defaults are updated, so 97 | that all processes consuming settings from ``django.conf`` can consume the 98 | latest settings available as well 99 | The path can be absolute or relative to the 'manage.py' file. 100 | i.e. /project/src/wsgi.py, wsgi.py 101 | 102 | Then, add ``etcd_settings`` to the list of ``INSTALLED_APPS`` before any other that 103 | requires dynamic settings. 104 | 105 | From your code, just do ``from etcd_settings import settings`` instead of ``from 106 | django.conf import settings``. 107 | 108 | In case you want to use ``etcd_settings`` to modify some values in your standard 109 | Django settings.py file (i.e. Database config), you can use the following 110 | snippet in your settings file, as high as possible in the file and immediately 111 | under the ``DJES_*`` settings definition: 112 | 113 | .. code-block:: python 114 | 115 | import etcd_settings.loader 116 | extra_settings = etcd_settings.loader.get_overwrites( 117 | DJES_ENV, DJES_DEV_PARAMS, DJES_ETCD_DETAILS) 118 | locals().update(extra_settings) 119 | -------------------------------------------------------------------------------- /tests/test_proxy.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import re 4 | import sys 5 | 6 | from django.http import HttpRequest 7 | from django.test import TestCase 8 | from django.test.utils import override_settings 9 | from etcd_config.loader import get_overwrites 10 | from etcd_config.manager import EtcdClusterState, EtcdConfigManager 11 | from etcd_settings.proxy import EtcdSettingsProxy 12 | from mock import MagicMock 13 | 14 | from .conftest import settings 15 | 16 | 17 | @override_settings( 18 | DJES_ETCD_DETAILS=settings.ETCD_DETAILS, 19 | DJES_ENV=settings.ETCD_ENV, 20 | DJES_REQUEST_GETTER='etcd_config.utils.threaded', 21 | E=0 22 | ) 23 | class TestEtcdSettingsProxy(TestCase): 24 | 25 | def setUp(self): 26 | s = '' 27 | with open('tests/unicode.txt', 'rb+') as f: 28 | if sys.version_info.major == 3: 29 | s = f.read().decode() 30 | else: 31 | s = f.read() 32 | self.mgr = EtcdConfigManager( 33 | prefix=settings.ETCD_PREFIX, host=settings.ETCD_HOST, 34 | port=settings.ETCD_PORT, 35 | username=settings.ETCD_USERNAME, password=settings.ETCD_PASSWORD 36 | ) 37 | 38 | self.env_config = { 39 | "A": 1, "B": "c", "D": {"e": "f"}, "E": 1, 40 | "C": {'c2': 1}, 'ENCODING': s 41 | } 42 | self.mgr.set_env_defaults('test', self.env_config) 43 | self.mgr.set_config_sets({ 44 | 'foo': {'A': 11}, 45 | 'bar': {'C': {'c3': 2}}}) 46 | self.proxy = EtcdSettingsProxy() 47 | with open('manage.py', 'w') as f: 48 | f.write("testing artifact") 49 | 50 | def tearDown(self): 51 | try: 52 | os.remove('manage.py') 53 | except: 54 | pass 55 | 56 | def test_loader_etcd_index_in_manager(self): 57 | env = get_overwrites(settings.ETCD_ENV, None, settings.ETCD_DETAILS) 58 | self.assertIsNotNone(env) 59 | self.assertGreater(EtcdClusterState.etcd_index, 0) 60 | 61 | def test_username_password(self): 62 | self.assertEquals({'authorization': u'Basic dGVzdDp0ZXN0'}, 63 | self.mgr._client._get_headers()) 64 | 65 | def test_proxy_starts_without_extensions(self): 66 | self.mgr._client.delete(self.mgr._base_config_set_path, recursive=True) 67 | p = EtcdSettingsProxy() 68 | self.assertIsNotNone(p) 69 | 70 | def test_proxy_starts_when_extensions_is_not_a_dir(self): 71 | self.mgr._client.delete(self.mgr._base_config_set_path, recursive=True) 72 | self.mgr._client.write( 73 | self.mgr._base_config_set_path, 74 | json.dumps('not_a_dict')) 75 | p = EtcdSettingsProxy() 76 | self.assertIsNotNone(p) 77 | 78 | def test_proxy_reads_initial_blob(self): 79 | self.assertEquals(1, self.proxy.A) 80 | self.assertEquals("c", self.proxy.B) 81 | 82 | def test_proxy_raises_attribute_errors_on_not_found(self): 83 | with self.assertRaises(AttributeError): 84 | self.proxy.KEY_THAT_IS_NOT_THERE 85 | 86 | def test_proxy_reads_django_settings(self): 87 | self.assertEquals('test', self.proxy.DJES_ENV) 88 | 89 | def test_proxy_gives_prio_to_env_over_django_settings(self): 90 | self.assertEquals(1, self.proxy.E) 91 | 92 | def test_proxy_can_be_viewed_as_dict(self): 93 | d = self.proxy.as_dict() 94 | for k, v in self.env_config.items(): 95 | self.assertEqual(v, d[k]) 96 | 97 | def test_proxy_uses_dynamic_settings(self): 98 | r = HttpRequest() 99 | r.META = {'HTTP_X_DYNAMIC_SETTING': 'foo'} 100 | self.proxy._req_getter = MagicMock(return_value=r) 101 | self.assertEqual(11, self.proxy.A) 102 | 103 | def test_proxy_dynamic_settings_handle_dict_overwrites(self): 104 | r = HttpRequest() 105 | r.META = {'HTTP_X_DYNAMIC_SETTING': 'bar'} 106 | self.proxy._req_getter = MagicMock(return_value=r) 107 | c = self.proxy.C 108 | self.assertEqual(1, c.get('c2')) 109 | self.assertEqual(2, c.get('c3')) 110 | 111 | def test_proxy_locates_uwsgi_file(self): 112 | self.proxy._locate_wsgi_file(None) 113 | self.assertEqual(None, self.proxy._wsgi_file) 114 | self.proxy._locate_wsgi_file(__file__) 115 | self.assertEqual(__file__, self.proxy._wsgi_file) 116 | self.proxy._locate_wsgi_file('etcd_settings/proxy.py') 117 | self.assertIsNotNone( 118 | re.match("^/(.*)/etcd_settings/proxy.py", self.proxy._wsgi_file)) 119 | os.remove('manage.py') 120 | with self.assertRaises(IOError): 121 | self.proxy._locate_wsgi_file('file_that_cannot_exist.py') 122 | 123 | def test_loader_gets_overwrites(self): 124 | self.assertEqual( 125 | self.env_config, 126 | get_overwrites(settings.ETCD_ENV, None, settings.ETCD_DETAILS) 127 | ) 128 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | echo. coverage to run coverage check of the documentation if enabled 41 | goto end 42 | ) 43 | 44 | if "%1" == "clean" ( 45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 46 | del /q /s %BUILDDIR%\* 47 | goto end 48 | ) 49 | 50 | 51 | REM Check if sphinx-build is available and fallback to Python version if any 52 | %SPHINXBUILD% 2> nul 53 | if errorlevel 9009 goto sphinx_python 54 | goto sphinx_ok 55 | 56 | :sphinx_python 57 | 58 | set SPHINXBUILD=python -m sphinx.__init__ 59 | %SPHINXBUILD% 2> nul 60 | if errorlevel 9009 ( 61 | echo. 62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 63 | echo.installed, then set the SPHINXBUILD environment variable to point 64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 65 | echo.may add the Sphinx directory to PATH. 66 | echo. 67 | echo.If you don't have Sphinx installed, grab it from 68 | echo.http://sphinx-doc.org/ 69 | exit /b 1 70 | ) 71 | 72 | :sphinx_ok 73 | 74 | 75 | if "%1" == "html" ( 76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 80 | goto end 81 | ) 82 | 83 | if "%1" == "dirhtml" ( 84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 88 | goto end 89 | ) 90 | 91 | if "%1" == "singlehtml" ( 92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 93 | if errorlevel 1 exit /b 1 94 | echo. 95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 96 | goto end 97 | ) 98 | 99 | if "%1" == "pickle" ( 100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 101 | if errorlevel 1 exit /b 1 102 | echo. 103 | echo.Build finished; now you can process the pickle files. 104 | goto end 105 | ) 106 | 107 | if "%1" == "json" ( 108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 109 | if errorlevel 1 exit /b 1 110 | echo. 111 | echo.Build finished; now you can process the JSON files. 112 | goto end 113 | ) 114 | 115 | if "%1" == "htmlhelp" ( 116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 117 | if errorlevel 1 exit /b 1 118 | echo. 119 | echo.Build finished; now you can run HTML Help Workshop with the ^ 120 | .hhp project file in %BUILDDIR%/htmlhelp. 121 | goto end 122 | ) 123 | 124 | if "%1" == "qthelp" ( 125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 129 | .qhcp project file in %BUILDDIR%/qthelp, like this: 130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\django-etcd-settings.qhcp 131 | echo.To view the help file: 132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\django-etcd-settings.ghc 133 | goto end 134 | ) 135 | 136 | if "%1" == "devhelp" ( 137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. 141 | goto end 142 | ) 143 | 144 | if "%1" == "epub" ( 145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 149 | goto end 150 | ) 151 | 152 | if "%1" == "latex" ( 153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 157 | goto end 158 | ) 159 | 160 | if "%1" == "latexpdf" ( 161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 162 | cd %BUILDDIR%/latex 163 | make all-pdf 164 | cd %~dp0 165 | echo. 166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdfja" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf-ja 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "text" ( 181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 182 | if errorlevel 1 exit /b 1 183 | echo. 184 | echo.Build finished. The text files are in %BUILDDIR%/text. 185 | goto end 186 | ) 187 | 188 | if "%1" == "man" ( 189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 190 | if errorlevel 1 exit /b 1 191 | echo. 192 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 193 | goto end 194 | ) 195 | 196 | if "%1" == "texinfo" ( 197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 198 | if errorlevel 1 exit /b 1 199 | echo. 200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 201 | goto end 202 | ) 203 | 204 | if "%1" == "gettext" ( 205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 206 | if errorlevel 1 exit /b 1 207 | echo. 208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 209 | goto end 210 | ) 211 | 212 | if "%1" == "changes" ( 213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 214 | if errorlevel 1 exit /b 1 215 | echo. 216 | echo.The overview file is in %BUILDDIR%/changes. 217 | goto end 218 | ) 219 | 220 | if "%1" == "linkcheck" ( 221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 222 | if errorlevel 1 exit /b 1 223 | echo. 224 | echo.Link check complete; look for any errors in the above output ^ 225 | or in %BUILDDIR%/linkcheck/output.txt. 226 | goto end 227 | ) 228 | 229 | if "%1" == "doctest" ( 230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 231 | if errorlevel 1 exit /b 1 232 | echo. 233 | echo.Testing of doctests in the sources finished, look at the ^ 234 | results in %BUILDDIR%/doctest/output.txt. 235 | goto end 236 | ) 237 | 238 | if "%1" == "coverage" ( 239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 240 | if errorlevel 1 exit /b 1 241 | echo. 242 | echo.Testing of coverage in the sources finished, look at the ^ 243 | results in %BUILDDIR%/coverage/python.txt. 244 | goto end 245 | ) 246 | 247 | if "%1" == "xml" ( 248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 249 | if errorlevel 1 exit /b 1 250 | echo. 251 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 252 | goto end 253 | ) 254 | 255 | if "%1" == "pseudoxml" ( 256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 257 | if errorlevel 1 exit /b 1 258 | echo. 259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 260 | goto end 261 | ) 262 | 263 | :end 264 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-etcd-settings.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-etcd-settings.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/django-etcd-settings" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-etcd-settings" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # django-etcd-settings documentation build configuration file, created by 4 | # sphinx-quickstart on Thu Oct 29 14:40:33 2015. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | import shlex 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | sys.path.insert(0, os.path.abspath('..')) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | # needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [ 33 | 'sphinx.ext.autodoc', 34 | 'sphinx.ext.doctest', 35 | 'sphinx.ext.intersphinx', 36 | 'sphinx.ext.todo', 37 | 'sphinx.ext.coverage', 38 | 'sphinx.ext.mathjax', 39 | 'sphinx.ext.napoleon', 40 | 'sphinxcontrib.plantuml', 41 | ] 42 | 43 | plantuml_jar_path = os.path.abspath('./_plantuml/plantuml.jar') 44 | plantuml = 'java -Djava.awt.headless=true -jar {0}'.format(plantuml_jar_path) 45 | 46 | # Add any paths that contain templates here, relative to this directory. 47 | templates_path = ['_templates'] 48 | 49 | # The suffix(es) of source filenames. 50 | # You can specify multiple suffix as a list of string: 51 | # source_suffix = ['.rst', '.md'] 52 | source_suffix = '.rst' 53 | 54 | # The encoding of source files. 55 | # source_encoding = 'utf-8-sig' 56 | 57 | # The master toctree document. 58 | master_doc = 'index' 59 | 60 | # General information about the project. 61 | project = u'django-etcd-settings' 62 | copyright = u'KPN' 63 | author = u'Enrique Paz' 64 | 65 | # The version info for the project you're documenting, acts as replacement for 66 | # |version| and |release|, also used in various other places throughout the 67 | # built documents. 68 | # 69 | # The short X.Y version. 70 | version = '' 71 | # The full version, including alpha/beta/rc tags. 72 | release = ' ' 73 | 74 | # The language for content autogenerated by Sphinx. Refer to documentation 75 | # for a list of supported languages. 76 | # 77 | # This is also used if you do content translation via gettext catalogs. 78 | # Usually you set "language" from the command line for these cases. 79 | language = None 80 | 81 | # There are two options for replacing |today|: either, you set today to some 82 | # non-false value, then it is used: 83 | # today = '' 84 | # Else, today_fmt is used as the format for a strftime call. 85 | # today_fmt = '%B %d, %Y' 86 | 87 | # List of patterns, relative to source directory, that match files and 88 | # directories to ignore when looking for source files. 89 | exclude_patterns = ['_build'] 90 | 91 | # The reST default role (used for this markup: `text`) to use for all 92 | # documents. 93 | # default_role = None 94 | 95 | # If true, '()' will be appended to :func: etc. cross-reference text. 96 | # add_function_parentheses = True 97 | 98 | # If true, the current module name will be prepended to all description 99 | # unit titles (such as .. function::). 100 | # add_module_names = True 101 | 102 | # If true, sectionauthor and moduleauthor directives will be shown in the 103 | # output. They are ignored by default. 104 | # show_authors = False 105 | 106 | # The name of the Pygments (syntax highlighting) style to use. 107 | pygments_style = 'sphinx' 108 | 109 | # A list of ignored prefixes for module index sorting. 110 | # modindex_common_prefix = [] 111 | 112 | # If true, keep warnings as "system message" paragraphs in the built documents. 113 | # keep_warnings = False 114 | 115 | # If true, `todo` and `todoList` produce output, else they produce nothing. 116 | todo_include_todos = True 117 | 118 | 119 | # -- Options for HTML output ---------------------------------------------- 120 | 121 | # The theme to use for HTML and HTML Help pages. See the documentation for 122 | # a list of builtin themes. 123 | html_theme = 'nature' 124 | 125 | # Theme options are theme-specific and customize the look and feel of a theme 126 | # further. For a list of options available for each theme, see the 127 | # documentation. 128 | # html_theme_options = {} 129 | 130 | # Add any paths that contain custom themes here, relative to this directory. 131 | # html_theme_path = [] 132 | 133 | # The name for this set of Sphinx documents. If None, it defaults to 134 | # " v documentation". 135 | # html_title = None 136 | 137 | # A shorter title for the navigation bar. Default is the same as html_title. 138 | # html_short_title = None 139 | 140 | # The name of an image file (relative to this directory) to place at the top 141 | # of the sidebar. 142 | # html_logo = None 143 | 144 | # The name of an image file (within the static path) to use as favicon of the 145 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 146 | # pixels large. 147 | # html_favicon = None 148 | 149 | # Add any paths that contain custom static files (such as style sheets) here, 150 | # relative to this directory. They are copied after the builtin static files, 151 | # so a file named "default.css" will overwrite the builtin "default.css". 152 | html_static_path = ['_static'] 153 | 154 | # Add any extra paths that contain custom files (such as robots.txt or 155 | # .htaccess) here, relative to this directory. These files are copied 156 | # directly to the root of the documentation. 157 | # html_extra_path = [] 158 | 159 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 160 | # using the given strftime format. 161 | # html_last_updated_fmt = '%b %d, %Y' 162 | 163 | # If true, SmartyPants will be used to convert quotes and dashes to 164 | # typographically correct entities. 165 | # html_use_smartypants = True 166 | 167 | # Custom sidebar templates, maps document names to template names. 168 | # html_sidebars = {} 169 | 170 | # Additional templates that should be rendered to pages, maps page names to 171 | # template names. 172 | # html_additional_pages = {} 173 | 174 | # If false, no module index is generated. 175 | # html_domain_indices = True 176 | 177 | # If false, no index is generated. 178 | # html_use_index = True 179 | 180 | # If true, the index is split into individual pages for each letter. 181 | # html_split_index = False 182 | 183 | # If true, links to the reST sources are added to the pages. 184 | # html_show_sourcelink = True 185 | 186 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 187 | # html_show_sphinx = True 188 | 189 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 190 | # html_show_copyright = True 191 | 192 | # If true, an OpenSearch description file will be output, and all pages will 193 | # contain a tag referring to it. The value of this option must be the 194 | # base URL from which the finished HTML is served. 195 | # html_use_opensearch = '' 196 | 197 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 198 | # html_file_suffix = None 199 | 200 | # Language to be used for generating the HTML full-text search index. 201 | # Sphinx supports the following languages: 202 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 203 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' 204 | # html_search_language = 'en' 205 | 206 | # A dictionary with options for the search language support, empty by default. 207 | # Now only 'ja' uses this config value 208 | # html_search_options = {'type': 'default'} 209 | 210 | # The name of a javascript file (relative to the configuration directory) that 211 | # implements a search results scorer. If empty, the default will be used. 212 | # html_search_scorer = 'scorer.js' 213 | 214 | # Output file base name for HTML help builder. 215 | htmlhelp_basename = 'django-etcd-settingsdoc' 216 | 217 | # -- Options for LaTeX output --------------------------------------------- 218 | 219 | latex_elements = { 220 | # The paper size ('letterpaper' or 'a4paper'). 221 | # 'papersize': 'letterpaper', 222 | 223 | # The font size ('10pt', '11pt' or '12pt'). 224 | # 'pointsize': '10pt', 225 | 226 | # Additional stuff for the LaTeX preamble. 227 | # 'preamble': '', 228 | 229 | # Latex figure (float) alignment 230 | # 'figure_align': 'htbp', 231 | } 232 | 233 | # Grouping the document tree into LaTeX files. List of tuples 234 | # (source start file, target name, title, 235 | # author, documentclass [howto, manual, or own class]). 236 | latex_documents = [ 237 | (master_doc, 'django-etcd-settings.tex', u'django-etcd-settings Documentation', 238 | u'Enrique Paz', 'manual'), 239 | ] 240 | 241 | # The name of an image file (relative to this directory) to place at the top of 242 | # the title page. 243 | # latex_logo = None 244 | 245 | # For "manual" documents, if this is true, then toplevel headings are parts, 246 | # not chapters. 247 | # latex_use_parts = False 248 | 249 | # If true, show page references after internal links. 250 | # latex_show_pagerefs = False 251 | 252 | # If true, show URL addresses after external links. 253 | # latex_show_urls = False 254 | 255 | # Documents to append as an appendix to all manuals. 256 | # latex_appendices = [] 257 | 258 | # If false, no module index is generated. 259 | # latex_domain_indices = True 260 | 261 | 262 | # -- Options for manual page output --------------------------------------- 263 | 264 | # One entry per manual page. List of tuples 265 | # (source start file, name, description, authors, manual section). 266 | man_pages = [ 267 | (master_doc, 'django-etcd-settings', u'django-etcd-settings Documentation', 268 | [author], 1) 269 | ] 270 | 271 | # If true, show URL addresses after external links. 272 | # man_show_urls = False 273 | 274 | 275 | # -- Options for Texinfo output ------------------------------------------- 276 | 277 | # Grouping the document tree into Texinfo files. List of tuples 278 | # (source start file, target name, title, author, 279 | # dir menu entry, description, category) 280 | texinfo_documents = [ 281 | (master_doc, 'django-etcd-settings', u'django-etcd-settings Documentation', 282 | author, 'django-etcd-settings', 'One line description of project.', 283 | 'Miscellaneous'), 284 | ] 285 | 286 | # Documents to append as an appendix to all manuals. 287 | # texinfo_appendices = [] 288 | 289 | # If false, no module index is generated. 290 | # texinfo_domain_indices = True 291 | 292 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 293 | # texinfo_show_urls = 'footnote' 294 | 295 | # If true, do not generate a @detailmenu in the "Top" node's menu. 296 | # texinfo_no_detailmenu = False 297 | 298 | 299 | # Example configuration for intersphinx: refer to the Python standard library. 300 | intersphinx_mapping = { 301 | 'https://docs.python.org/': None, 302 | 'django': ( 303 | 'http://docs.djangoproject.com/en/1.8/', 304 | 'http://docs.djangoproject.com/en/1.8/_objects/' 305 | ) 306 | } 307 | --------------------------------------------------------------------------------