├── setup.cfg ├── pytest.ini ├── .gitignore ├── MANIFEST.in ├── .flake8 ├── tox.ini ├── .travis.yml ├── README.rst ├── LICENSE ├── env.py ├── test_env.py └── setup.py /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 3 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = --cov=env --cov-report term-missing -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .coverage 2 | .eggs/ 3 | .pytest_cache/ 4 | env.egg-info/ 5 | __pycache__/ 6 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include Makefile 3 | include tests.py 4 | include tox.ini 5 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 88 3 | 4 | exclude= 5 | .git 6 | .eggs 7 | .pytest_cache 8 | env.egg-info 9 | test_env.py 10 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py{27,34,35,36,37} 3 | skip_missing_interpreters = True 4 | 5 | [testenv] 6 | deps = 7 | pytest 8 | pytest-cov 9 | commands = {envpython} pytest {posargs} 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial 2 | language: python 3 | python: 4 | - 2.7 5 | - 3.4 6 | - 3.5 7 | - 3.6 8 | - 3.7 9 | - 3.8-dev 10 | - nightly 11 | 12 | matrix: 13 | fast_finish: true 14 | allow_failures: 15 | - python: 3.8-dev 16 | - python: nightly 17 | 18 | install: 19 | - pip install pytest pytest-cov flake8 python-coveralls 20 | - if [ $(python -c "import sys; sys.exit(1 if (sys.version_info >= (3, 5)) else 0)") -eq 1 ]; then pip install flake8-bugbear; fi 21 | 22 | script: 23 | - flake8 24 | - python setup.py test 25 | 26 | after_success: 27 | - coveralls 28 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Env: Environment Variables for Humans 2 | ===================================== 3 | 4 | .. image:: https://travis-ci.org/MasterOdin/env.svg?branch=master 5 | :target: https://travis-ci.org/MasterOdin/env 6 | :alt: Build Status 7 | .. image:: https://coveralls.io/repos/github/MasterOdin/env/badge.svg?branch=master 8 | :target: https://coveralls.io/github/MasterOdin/env?branch=master 9 | :alt: Coverage Status 10 | 11 | Mapping environment variables can be a bit of a pain. 12 | 13 | Now you can replace this boilerplate:: 14 | 15 | ZENDESK_URL = os.environ['ZENDESK_URL'] 16 | ZENDESK_USER = os.environ['ZENDESK_USER'] 17 | ZENDESK_PASS = os.environ['ZENDESK_PASS'] 18 | ZENDESK_VIEW = os.environ['ZENDESK_VIEW'] 19 | 20 | With a simple call:: 21 | 22 | import env 23 | 24 | :: 25 | 26 | >>> zendesk = env.prefix('zendesk_') 27 | >>> zendesk 28 | {'user': ..., 'pass': ..., 'url': ..., 'view': ...} 29 | 30 | Or have a bit more control:: 31 | 32 | >>> env.map(user='zendesk_user') 33 | {'user': ...} 34 | 35 | 36 | Installation 37 | ------------ 38 | 39 | Installation is easy with pip:: 40 | 41 | $ pip install env 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Kenneth Reitz 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /env.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Parses env variables into a human friendly dictionary.""" 3 | 4 | from os import environ 5 | try: # pragma: no cover 6 | from urllib.parse import urlparse as _urlparse 7 | except ImportError: # pragma: no cover 8 | from urlparse import urlparse as _urlparse 9 | 10 | 11 | def lower_dict(d): 12 | """Lower cases string keys in given dict.""" 13 | _d = {} 14 | 15 | for k, v in d.items(): 16 | try: 17 | _d[k.lower()] = v 18 | except AttributeError: 19 | _d[k] = v 20 | 21 | return _d 22 | 23 | 24 | def urlparse(d, keys=None): 25 | """Return a copy of the given dictionary with url values parsed.""" 26 | d = d.copy() 27 | 28 | if keys is None: 29 | keys = d.keys() 30 | 31 | for key in keys: 32 | d[key] = _urlparse(d[key]) 33 | 34 | return d 35 | 36 | 37 | def prefix(prefix): 38 | """ 39 | Return dictionary with all environment variables starting with prefix. 40 | 41 | The elements of the dictionary are all lower cased and stripped of prefix. 42 | """ 43 | d = {} 44 | e = lower_dict(environ.copy()) 45 | 46 | prefix = prefix.lower() 47 | 48 | for k, v in e.items(): 49 | try: 50 | if k.startswith(prefix): 51 | k = k[len(prefix):] 52 | d[k] = v 53 | except AttributeError: 54 | pass 55 | 56 | return d 57 | 58 | 59 | def map(**kwargs): 60 | """ 61 | Return a dictionary of the given keyword arguments mapped to os.environ. 62 | 63 | The input keys are lower cased for both the passed in map and os.environ. 64 | """ 65 | d = {} 66 | e = lower_dict(environ.copy()) 67 | 68 | for k, v in kwargs.items(): 69 | d[k] = e.get(v.lower()) 70 | 71 | return d 72 | -------------------------------------------------------------------------------- /test_env.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """Test env module.""" 4 | 5 | import env 6 | import os 7 | import mock 8 | 9 | try: 10 | from urlparse import urlparse as _urlparse 11 | except ImportError: 12 | from urllib.parse import urlparse as _urlparse 13 | 14 | SEARCH_PREFIX = 'env1' 15 | MATCH_DATA = {'env1TESTS1': 'aA', 'ENV1tests2': 'bB', 'env1tests3': 'cC'} 16 | NO_MATCH_DATA = {'env2TESTS4': 'dD', 'ENV2tests5': 'eE', 'env2tests6': 'fF'} 17 | ALL_DATA = {k: v for d in [MATCH_DATA, NO_MATCH_DATA] for k, v in d.items()} 18 | 19 | 20 | def test_lower_dict(): 21 | lowereddict = env.lower_dict(MATCH_DATA) 22 | 23 | assert len(lowereddict) == len(MATCH_DATA) 24 | 25 | for item in MATCH_DATA: 26 | assert MATCH_DATA[item] == lowereddict[item.lower()] 27 | 28 | 29 | def test_lower_dict_non_string_key(): 30 | mixed_key_dict = {0: 'aA', 'env1TEST1': 'bB'} 31 | lowereddict = env.lower_dict(mixed_key_dict) 32 | expected_dict = {0: 'aA', 'env1test1': 'bB'} 33 | 34 | assert len(lowereddict) == len(mixed_key_dict) 35 | 36 | for item in expected_dict: 37 | assert expected_dict[item] == lowereddict[item] 38 | 39 | 40 | def test_urlparse(): 41 | urldata = {'url1': 'http://env1.test', 'url2': 'ftp://env2.test'} 42 | 43 | parseddata = env.urlparse(urldata) 44 | 45 | assert len(parseddata) == len(urldata) 46 | 47 | for item in urldata: 48 | assert _urlparse(urldata[item]) == parseddata[item] 49 | 50 | 51 | @mock.patch.dict(os.environ, ALL_DATA, clear=True) 52 | def test_prefix(): 53 | prefixsearch = env.prefix(SEARCH_PREFIX) 54 | 55 | assert len(prefixsearch) == len(MATCH_DATA) 56 | 57 | for item in MATCH_DATA: 58 | assert MATCH_DATA[item] == prefixsearch[item.lower()[len(SEARCH_PREFIX):]] 59 | 60 | 61 | @mock.patch.object( 62 | env, 63 | 'lower_dict', 64 | return_value={0: 'test', 'env1test1': 'bB'} 65 | ) 66 | def test_prefix_non_string_key(mock_func): 67 | prefixsearch = env.prefix(SEARCH_PREFIX) 68 | 69 | assert len(prefixsearch) == 1 70 | assert prefixsearch['test1'] == 'bB' 71 | 72 | 73 | @mock.patch.dict(os.environ, ALL_DATA, clear=True) 74 | def test_map(): 75 | mapdata = {'a': 'env1tests1', 'b': 'env1tests2', 'c': 'env1tests3'} 76 | originaldata = {'env1tests1': 'aA', 'env1tests2': 'bB', 'env1tests3': 'cC'} 77 | 78 | mapsearch = env.map(a='env1tests1', b='env1tests2', c='env1tests3') 79 | 80 | assert len(mapsearch) == len(mapdata) 81 | 82 | for item in mapdata: 83 | assert originaldata[mapdata[item]] == mapsearch[item] 84 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Setup file for env package. 4 | 5 | ~~~~~~ 6 | 7 | Mapping environment variables can be a bit of a pain. 8 | 9 | Now you can replace this boilerplate:: 10 | 11 | ZENDESK_URL = os.environ['ZENDESK_URL'] 12 | ZENDESK_USER = os.environ['ZENDESK_USER'] 13 | ZENDESK_PASS = os.environ['ZENDESK_PASS'] 14 | ZENDESK_VIEW = os.environ['ZENDESK_VIEW'] 15 | 16 | With a simple call:: 17 | 18 | import env 19 | 20 | :: 21 | 22 | >>> zendesk = env.prefix('zendesk_') 23 | >>> zendesk 24 | {'user': ..., 'pass': ..., 'url': ..., 'view': ...} 25 | 26 | Or have a bit more control:: 27 | 28 | >>> env.map(user='zendesk_user') 29 | {'user': ...} 30 | 31 | 32 | """ 33 | 34 | from setuptools import setup 35 | from setuptools.command.test import test as TestCommand 36 | import sys 37 | 38 | 39 | class PyTest(TestCommand): 40 | """pytest command runner.""" 41 | 42 | user_options = [('pytest-args=', 'a', "Arguments to pass into py.test")] 43 | 44 | def initialize_options(self): 45 | """Initialize the options for pytest.""" 46 | TestCommand.initialize_options(self) 47 | 48 | def finalize_options(self): 49 | """Finalize the options for pytest.""" 50 | TestCommand.finalize_options(self) 51 | self.test_args = [] 52 | self.test_suite = True 53 | 54 | def run_tests(self): 55 | """Run the pytest runner.""" 56 | import pytest 57 | 58 | errno = pytest.main([]) 59 | sys.exit(errno) 60 | 61 | 62 | setup( 63 | name='env', 64 | version='0.1.1', 65 | url='https://github.com/MasterOdin/env', 66 | license='BSD', 67 | author='Kenneth Reitz', 68 | author_email='me@kennethreitz.com', 69 | description='Environment Variables for Humans', 70 | long_description=__doc__, 71 | py_modules=['env'], 72 | zip_safe=False, 73 | include_package_data=True, 74 | platforms='any', 75 | tests_require=['pytest', 'pytest-cov'], 76 | cmdclass={'test': PyTest}, 77 | classifiers=[ 78 | 'Environment :: Web Environment', 79 | 'Intended Audience :: Developers', 80 | 'License :: OSI Approved :: BSD License', 81 | 'Operating System :: OS Independent', 82 | 'Programming Language :: Python', 83 | 'Programming Language :: Python :: 2', 84 | 'Programming Language :: Python :: 2.7', 85 | 'Programming Language :: Python :: 3', 86 | 'Programming Language :: Python :: 3.4', 87 | 'Programming Language :: Python :: 3.5', 88 | 'Programming Language :: Python :: 3.6', 89 | 'Programming Language :: Python :: 3.7', 90 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 91 | 'Topic :: Software Development :: Libraries :: Python Modules' 92 | ] 93 | ) 94 | --------------------------------------------------------------------------------