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.2.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
tonyseek committed Jul 7, 2016
2 parents 17ad8ce + d4dc4ca commit c3bfa9b
Show file tree
Hide file tree
Showing 9 changed files with 264 additions and 7 deletions.
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Expand Up @@ -2,5 +2,5 @@
files = setup.py envcfg/__init__.py
commit = True
tag = False
current_version = 0.1.0
current_version = 0.2.0

1 change: 1 addition & 0 deletions .travis.yml
Expand Up @@ -3,6 +3,7 @@ python:
- "2.7"
- "3.3"
- "3.4"
- "3.5"
- "pypy"
install:
- "pip install ."
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG
@@ -0,0 +1,12 @@
Changelog
=========

0.2.0
-----

- Add "smart" format support. (#4)

0.1.0
-----

The first release.
196 changes: 193 additions & 3 deletions README.rst
Expand Up @@ -25,8 +25,196 @@ Installation

::

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


Supported Formats
-----------------

- ``import envcfg.raw.foo as config``:
Import each ``FOO_*`` environment variable as string.
- ``import envcfg.json.foo as config``:
Import each ``FOO_*`` environment variable as JSON body.
- ``import envcfg.smart.foo as config``:
Try to import each ``FOO_*`` environment variable as JSON body, if fail then import it as string.

There is an example table:

+----------------------+---------------------------+-----------------------+
| Environment Variable | Python Import Statement | Python Variable Value |
+======================+===========================+=======================+
| ``FOO_NAME=foo`` | ``envcfg.raw.foo.NAME`` | ``'foo'`` |
+----------------------+---------------------------+-----------------------+
| ``FOO_NAME="foo"`` | ``envcfg.raw.foo.NAME`` | ``'"foo"'`` |
+----------------------+---------------------------+-----------------------+
| ``FOO_NUM1=42`` | ``envcfg.raw.foo.NUM1`` | ``'42'`` |
+----------------------+---------------------------+-----------------------+
| ``FOO_NUM1="42"`` | ``envcfg.raw.foo.NUM1`` | ``'"42"'`` |
+----------------------+---------------------------+-----------------------+
| ``FOO_NAME=foo`` | ``envcfg.json.foo.NAME`` | *ImportError* |
+----------------------+---------------------------+-----------------------+
| ``FOO_NAME="foo"`` | ``envcfg.json.foo.NAME`` | ``'foo'`` |
+----------------------+---------------------------+-----------------------+
| ``FOO_NUM1=42`` | ``envcfg.json.foo.NUM1`` | ``42`` |
+----------------------+---------------------------+-----------------------+
| ``FOO_NUM1="42"`` | ``envcfg.json.foo.NUM1`` | ``'42'`` |
+----------------------+---------------------------+-----------------------+
| ``FOO_NAME=foo`` | ``envcfg.smart.foo.NAME`` | ``'foo'`` |
+----------------------+---------------------------+-----------------------+
| ``FOO_NAME="foo"`` | ``envcfg.smart.foo.NAME`` | ``'foo'`` |
+----------------------+---------------------------+-----------------------+
| ``FOO_NUM1=42`` | ``envcfg.smart.foo.NUM1`` | ``42`` |
+----------------------+---------------------------+-----------------------+
| ``FOO_NUM1="42"`` | ``envcfg.smart.foo.NUM1`` | ``'42'`` |
+----------------------+---------------------------+-----------------------+

Examples
--------

Uses with Flask
~~~~~~~~~~~~~~~

1. Defines environment variables with a prefix::

$ cat .env # should not checked into VCS
# values are valid JSON expressions
MYAPP_DEBUG=true
MYAPP_SECRET_KEY='"7950ad141c7e4b3990631fcdf9a1d909"'
MYAPP_SQLALCHEMY_DATABASE_URI='"sqlite:///tmp/myapp.sqlite3"'

2. Creates Flask app and loads config from python-envcfg::

$ cat myapp.py
...
app = Flask(__name__)
app.config.from_object('envcfg.json.myapp') # MYAPP_ -> .myapp
...

3. Enters your app with those environment variables::

$ env $(cat .env | xargs) python myapp.py


Uses with Django
~~~~~~~~~~~~~~~~

1. Creates a django project and moves all sensitive config items into the
environment variables::

$ cat djapp/settings.py # codebase-scope config
...
INSTALLED_APPS = (
'django.contrib.admin',
)
...

$ cat .env # environment-scope config, should not checked into VCS
# values are valid JSON expressions
DJAPP_SECRET_KEY='"wo9g2o#jws=u"'
DJAPP_DEBUG=true
DJAPP_TEMPLATE_DEBUG=true

2. Adds importing statements in the end of ``settings.py`` module::

$ tail -n 2 djapp/settings.py
# importing all config items stored in the environment variables
from envcfg.json.djapp import * # noqa

3. Runs your Django app with environment variables::

$ env $(cat .env | xargs) python manage.py runserver


Uses with Tornado
~~~~~~~~~~~~~~~~~

1. Defines environment variables with a prefix::

$ cat .env
export TORAPP_PORT='8888'
export TORAPP_MYSQL_HOST='"127.0.0.1"'
export TORAPP_MYSQL_DATABASE='"database"'


2. Creates a Tornado project and loads config::

$ cat torapp/server.py

from tornado.web import Application, RequestHandler
from tornado.ioloop import IOLoop
from tornado.options import define, options
from tordb import Connection


def options_from_object(*args, **kwargs):
module = __import__(*args, **kwargs)
for name, value in vars(module).items():
name = name.lower()
if name in options._options:
options._options[name].set(value)


class IndexHandler(RequestHandler):
def initialize(self):
self.db = Connection(options.mysql_host, options.mysql_database)

def get(self):
pass # some database operations with ``self.db``


application = Application([
(r'/', IndexHandler),
])

define('port', type=int)
define('mysql_host', type=unicode)
define('mysql_database', type=unicode)
options_from_object('envcfg.json.torapp', fromlist=['torapp'])


if __name__ == '__main__':
application.listen(options.port)
IOLoop.instance().start()


3. Runs your Tornado app::

$ env $(cat .env | xargs) python server.py


Works on Projects
-----------------

In development, we can work with per-project environments but no more typing
``source foo/bar``.

I recommend to put your project-specified environment variables in
``{PROJECT_ROOT}/.env`` and mark the ``.env`` as ignored in your VCS. For
example, you can write ``/.env`` in ``.gitignore`` if you are using Git, and
put a ``.env.example`` as a copying template for new-cloned projects.

And then, you can use some utility such as `honcho`_ or `autoenv`_ to apply
the ``.env`` automatically.

For honcho::

$ echo 'MYPROJECT_DEBUG=true' >> .env
$ echo 'web: python manage.py runserver' >> Procfile
$ honcho run python manage.py check-debug
True
$ honcho start web
Starting development server at http://127.0.0.1:5000/
...

For autoenv::

$ echo 'MYPROJECT_DEBUG=true' >> myproject/.env
$ cd myproject
$ python manage.py check-debug
True
$ python manage.py runserver
Starting development server at http://127.0.0.1:5000/
...


Issues
Expand All @@ -37,14 +225,16 @@ If you want to report bugs or request features, please create issues on


.. _12-Factor: http://12factor.net
.. _honcho: https://github.com/nickstenning/honcho
.. _autoenv: https://github.com/kennethreitz/autoenv

.. |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
.. |Wheel Status| image:: https://img.shields.io/pypi/wheel/python-envcfg.svg
:target: https://warehouse.python.org/project/python-envcfg
:alt: Wheel Status
.. |PyPI Version| image:: https://img.shields.io/pypi/v/python-envcfg.svg
Expand Down
2 changes: 1 addition & 1 deletion envcfg/__init__.py
@@ -1 +1 @@
__version__ = '0.1.0'
__version__ = '0.2.0'
16 changes: 16 additions & 0 deletions envcfg/smart/__init__.py
@@ -0,0 +1,16 @@
from __future__ import absolute_import

from .._hook import import_hook


@import_hook(__name__)
def value_processor(name, raw_name, raw_value):
import json
try:
return json.loads(raw_value)
except ValueError:
return raw_value


del import_hook
del value_processor
3 changes: 2 additions & 1 deletion setup.py
Expand Up @@ -8,7 +8,7 @@

setup(
name='python-envcfg',
version='0.1.0',
version='0.2.0',
author='Jiangge Zhang',
author_email='tonyseek@gmail.com',
description='Accessing environment variables with a magic module.',
Expand All @@ -17,6 +17,7 @@
url='https://github.com/tonyseek/python-envcfg',
license='MIT',
packages=find_packages(),
keywords=['env', 'config', '12-factor'],
classifiers=[
'Development Status :: 3 - Alpha',
'License :: OSI Approved :: MIT License',
Expand Down
37 changes: 37 additions & 0 deletions tests/test_smart.py
@@ -0,0 +1,37 @@
from pytest import raises


def test_success(environ):
environ['SUCHDOGE_BOOLEAN'] = 'true'
environ['SUCHDOGE_INTEGER'] = '42'
environ['SUCHDOGE_REAL'] = '42.42'
environ['SUCHDOGE_STRING'] = '"42"'
environ['SUCHDOGE_DICT'] = '{"value": 42}'
environ['SUCHDOGE_RAW_STR'] = 'foo'

from envcfg.smart.suchdoge import (
BOOLEAN,
INTEGER,
REAL,
STRING,
DICT,
RAW_STR,
)
assert BOOLEAN is True
assert INTEGER == 42
assert REAL == 42.42
assert STRING == '42'
assert DICT == {'value': 42}
assert RAW_STR == 'foo'


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

with raises(ImportError) as einfo:
import envcfg.smart.INVALID_NAME # noqa
assert einfo.value.args[0].startswith(
'No module named envcfg.smart.INVALID_NAME')
2 changes: 1 addition & 1 deletion tox.ini
@@ -1,5 +1,5 @@
[tox]
envlist = py27,py33,py34,pypy
envlist = py27,py33,py34,py35,pypy
[testenv]
deps =
pytest
Expand Down

0 comments on commit c3bfa9b

Please sign in to comment.