Skip to content
This repository has been archived by the owner on Jan 4, 2023. It is now read-only.

Commit

Permalink
Merge branch 'release/0.1.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
tonyseek committed Sep 7, 2014
2 parents 9acc43c + 8c10bfc commit 17ad8ce
Show file tree
Hide file tree
Showing 14 changed files with 333 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[bumpversion]
files = setup.py envcfg/__init__.py
commit = True
tag = False
current_version = 0.1.0

18 changes: 18 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
language: python
python:
- "2.7"
- "3.3"
- "3.4"
- "pypy"
install:
- "pip install ."
- "pip install pytest>=2.4.2 -U"
- "pip install pytest-cov pytest-pep8 coveralls"
- "touch tests/__init__.py"
script: "py.test --cov envcfg --pep8 tests"
after_success:
coveralls
branches:
only:
- master
- develop
20 changes: 20 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2014 Jiangge Zhang

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.
55 changes: 55 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
|Build Status| |Coverage Status| |PyPI Version| |PyPI Downloads| |Wheel Status|

python-envcfg
=============

Accessing environment variables with a magic module.

::

>>> import os
>>> from envcfg.raw.python import CONFIGURE_OPTS
>>>
>>> CONFIGURE_OPTS
'--enable-shared --enable-universalsdk=/ --with-universal-archs=intel'
>>> CONFIGURE_OPTS == os.environ['PYTHON_CONFIGURE_OPTS']
True

It works with many frameworks such as Django and Flask. Then you can store your
config in the environment variables instead of framework-specific config files.
It is recommended by 12-Factor_.


Installation
------------

::

$ pip install envcfg
$ pip freeze > requirements.txt # http://nvie.com/posts/pin-your-packages/


Issues
------

If you want to report bugs or request features, please create issues on
`GitHub Issues <https://github.com/tonyseek/python-envcfg/issues>`_.


.. _12-Factor: http://12factor.net

.. |Build Status| image:: https://travis-ci.org/tonyseek/python-envcfg.svg?branch=master,develop
:target: https://travis-ci.org/tonyseek/python-envcfg
:alt: Build Status
.. |Coverage Status| image:: https://img.shields.io/coveralls/tonyseek/python-envcfg/develop.svg
:target: https://coveralls.io/r/tonyseek/python-envcfg
:alt: Coverage Status
.. |Wheel Status| image:: https://pypip.in/wheel/python-envcfg/badge.svg
:target: https://warehouse.python.org/project/python-envcfg
:alt: Wheel Status
.. |PyPI Version| image:: https://img.shields.io/pypi/v/python-envcfg.svg
:target: https://pypi.python.org/pypi/python-envcfg
:alt: PyPI Version
.. |PyPI Downloads| image:: https://img.shields.io/pypi/dm/python-envcfg.svg
:target: https://pypi.python.org/pypi/python-envcfg
:alt: Downloads
1 change: 1 addition & 0 deletions envcfg/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = '0.1.0'
64 changes: 64 additions & 0 deletions envcfg/_hook.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import os
import re
import sys
import types


class ImportHook(object):

re_module_name = re.compile(r'[a-z][a-z0-9_]*')

def __init__(self, wrapper_module, value_processor):
self.wrapper_module = wrapper_module
self.wrapper_prefix = wrapper_module + '.'
self.value_processor = value_processor

def __eq__(self, other):
return self.__class__.__module__ == other.__class__.__module__ and \
self.__class__.__name__ == other.__class__.__name__ and \
self.wrapper_module == other.wrapper_module

def __ne__(self, other):
return not self.__eq__(other)

def install(self):
sys.meta_path[:] = [x for x in sys.meta_path if self != x] + [self]

def find_module(self, fullname, path=None):
if fullname.startswith(self.wrapper_prefix):
return self

def load_module(self, fullname):
if fullname in sys.modules:
return sys.modules[fullname]

prefix_name = fullname[len(self.wrapper_prefix):]
if not self.re_module_name.match(prefix_name):
error_msg = ('No module named {0}\n\nThe name of envvar module '
'should matched {1.pattern}')
raise ImportError(error_msg.format(fullname, self.re_module_name))

module = types.ModuleType(fullname)
for name, value in self.load_environ(prefix_name):
setattr(module, name, value)
sys.modules[fullname] = module

return module

def load_environ(self, prefix_name):
prefix = prefix_name.upper() + '_'
for raw_name, raw_value in os.environ.items():
if not raw_name.startswith(prefix):
continue
if raw_name == prefix:
continue
name = raw_name[len(prefix):]
value = self.value_processor(name, raw_name, raw_value)
yield name, value


def import_hook(wrapper_module):
def wrapper(fn):
hook = ImportHook(wrapper_module, value_processor=fn)
hook.install()
return wrapper
20 changes: 20 additions & 0 deletions envcfg/json/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from __future__ import absolute_import

from .._hook import import_hook


@import_hook(__name__)
def value_processor(name, raw_name, raw_value):
import json
try:
value = json.loads(raw_value)
except ValueError:
error_msg = (
'{0}={1!r} found but {1!r} is not a valid json value.\n\n'
'You may want {0}=\'"{1}"\' if the value should be a string.')
raise ImportError(error_msg.format(raw_name, raw_value))
return value


del import_hook
del value_processor
12 changes: 12 additions & 0 deletions envcfg/raw/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from __future__ import absolute_import

from .._hook import import_hook


@import_hook(__name__)
def value_processor(name, raw_name, raw_value):
return raw_value


del import_hook
del value_processor
6 changes: 6 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[pytest]
pep8ignore =
docs/conf.py ALL
docs/_themes/* ALL
[bdist_wheel]
universal = 1
30 changes: 30 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from setuptools import setup, find_packages


with open('README.rst') as readme:
next(readme) # skip the first line
long_description = ''.join(readme).strip()


setup(
name='python-envcfg',
version='0.1.0',
author='Jiangge Zhang',
author_email='tonyseek@gmail.com',
description='Accessing environment variables with a magic module.',
long_description=long_description,
platforms=['Any'],
url='https://github.com/tonyseek/python-envcfg',
license='MIT',
packages=find_packages(),
classifiers=[
'Development Status :: 3 - Alpha',
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Topic :: Software Development :: Libraries',
]
)
15 changes: 15 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import os

from pytest import fixture


@fixture(scope='function')
def environ(request):
origin = dict(os.environ)

@request.addfinalizer
def restore_environ():
os.environ.clear()
os.environ.update(origin)

return os.environ
40 changes: 40 additions & 0 deletions tests/test_json.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from pytest import raises


def test_success(environ):
environ['ENVCFG_JSON_1_BOOLEAN'] = 'true'
environ['ENVCFG_JSON_1_INTEGER'] = '42'
environ['ENVCFG_JSON_1_REAL'] = '42.42'
environ['ENVCFG_JSON_1_STRING'] = '"42"'
environ['ENVCFG_JSON_1_DICT'] = '{"value": 42}'

from envcfg.json.envcfg_json_1 import (
BOOLEAN,
INTEGER,
REAL,
STRING,
DICT,
)
assert BOOLEAN is True
assert INTEGER == 42
assert REAL == 42.42
assert STRING == '42'
assert DICT == {'value': 42}


def test_failed(environ):
environ['ENVCFG_JSON_2_INVALID'] = 'foo'

with raises(ImportError) as einfo:
import envcfg.json._private_module # noqa
assert einfo.value.args[0].startswith(
'No module named envcfg.json._private_module')

with raises(ImportError) as einfo:
import envcfg.json.INVALID_NAME # noqa
assert einfo.value.args[0].startswith(
'No module named envcfg.json.INVALID_NAME')

with raises(ImportError) as einfo:
import envcfg.json.envcfg_json_2 # noqa
assert 'is not a valid json value' in einfo.value.args[0]
34 changes: 34 additions & 0 deletions tests/test_raw.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from pytest import raises


def test_success(environ):
environ['ENVCFG_RAW_1_BOOLEAN'] = 'true'
environ['ENVCFG_RAW_1_INTEGER'] = '42'
environ['ENVCFG_RAW_1_REAL'] = '42.42'
environ['ENVCFG_RAW_1_STRING'] = '"42"'
environ['ENVCFG_RAW_1_DICT'] = '{"value": 42}'

from envcfg.raw.envcfg_raw_1 import (
BOOLEAN,
INTEGER,
REAL,
STRING,
DICT,
)
assert BOOLEAN == 'true'
assert INTEGER == '42'
assert REAL == '42.42'
assert STRING == '"42"'
assert DICT == '{"value": 42}'


def test_failed():
with raises(ImportError) as einfo:
import envcfg.raw._private_module # noqa
assert einfo.value.args[0].startswith(
'No module named envcfg.raw._private_module')

with raises(ImportError) as einfo:
import envcfg.raw.INVALID_NAME # noqa
assert einfo.value.args[0].startswith(
'No module named envcfg.raw.INVALID_NAME')
12 changes: 12 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[tox]
envlist = py27,py33,py34,pypy
[testenv]
deps =
pytest
pytest-cov
pytest-pep8
commands =
py.test \
--cov {envsitepackagesdir}/envcfg \
--pep8 \
tests

0 comments on commit 17ad8ce

Please sign in to comment.