pax_global_header 0000666 0000000 0000000 00000000064 13154500625 0014513 g ustar 00root root 0000000 0000000 52 comment=d4b170a15319f81fe6d3493497a2fff9273e50aa
python-dotenv-0.7.1/ 0000775 0000000 0000000 00000000000 13154500625 0014336 5 ustar 00root root 0000000 0000000 python-dotenv-0.7.1/.bumpversion.cfg 0000664 0000000 0000000 00000000135 13154500625 0017445 0 ustar 00root root 0000000 0000000 [bumpversion]
current_version = 0.7.1
commit = True
tag = True
[bumpversion:file:setup.py]
python-dotenv-0.7.1/.coveragerc 0000664 0000000 0000000 00000000104 13154500625 0016452 0 ustar 00root root 0000000 0000000 [run]
source = dotenv/
omit =
tests/*
venv/*
*conftest*
python-dotenv-0.7.1/.editorconfig 0000664 0000000 0000000 00000000363 13154500625 0017015 0 ustar 00root root 0000000 0000000 # see: http://editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.{py,rst,ini}]
indent_style = space
indent_size = 4
[*.yml]
indent_style = space
indent_size = 2
python-dotenv-0.7.1/.gitignore 0000664 0000000 0000000 00000000132 13154500625 0016322 0 ustar 00root root 0000000 0000000 *.egg-info
*.pyc
build/
dist/
.env
__pycache__
.coverage
.DS_Store
htmlcov/
.cache/
.idea
python-dotenv-0.7.1/.travis.yml 0000664 0000000 0000000 00000001071 13154500625 0016446 0 ustar 00root root 0000000 0000000 language: python
sudo: false
python:
- '2.7'
- '3.3'
- '3.4'
- '3.5'
- pypy
install:
- pip install python-coveralls
- pip install -q -r requirements.txt
- pip install --editable .
script:
- pytest -v --tb=native --cov --flake8
after_success:
- coveralls
deploy:
provider: pypi
user: theskumar
password:
secure: VdULnucEUBZB0qZFKrP0kGdxYOY02QrvJBvVAHDc4CBhvPWcfF5HpEWD1CDNr2wdvg5Wwi5WGgX66Rthkrsw6akPwB+aPA++bFZ0uMHy392Nu4hxusX915R3JGnQ7L53aYrohOsJ7JsrZ7n12I38LJSewfkyQydAxjhJA+3hAtw=
on:
tags: true
repo: theskumar/python-dotenv
python-dotenv-0.7.1/LICENSE 0000664 0000000 0000000 00000010770 13154500625 0015350 0 ustar 00root root 0000000 0000000 python-dotenv
Copyright (c) 2014, Saurabh Kumar
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* 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.
* Neither the name of python-dotenv nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
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 OWNER 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.
django-dotenv-rw
Copyright (c) 2013, Ted Tieken
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* 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.
* Neither the name of django-dotenv nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
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 OWNER 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.
Original django-dotenv
Copyright (c) 2013, Jacob Kaplan-Moss
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* 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.
* Neither the name of django-dotenv nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
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 OWNER 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.
python-dotenv-0.7.1/MANIFEST.in 0000664 0000000 0000000 00000000074 13154500625 0016075 0 ustar 00root root 0000000 0000000 include LICENSE
include README.rst
include requirements.txt
python-dotenv-0.7.1/Makefile 0000664 0000000 0000000 00000001107 13154500625 0015775 0 ustar 00root root 0000000 0000000 .PHONY: clean-pyc clean-build test
clean: clean-build clean-pyc
clean-build:
rm -fr build/
rm -fr dist/
rm -fr *.egg-info
clean-pyc:
find . -name '*.pyc' -exec rm -f {} +
find . -name '*.pyo' -exec rm -f {} +
find . -name '*~' -exec rm -f {} +
release: clean
python setup.py sdist upload
python setup.py bdist_wheel upload
sdist: clean
python setup.py sdist
ls -l dist
test:
pip install -e .
flake8 .
py.test tests/
coverage:
coverage run --source=dotenv --omit='*tests*' -m py.test tests/ -v --tb=native
coverage report
coverage-html: coverage
coverage html
python-dotenv-0.7.1/README.rst 0000664 0000000 0000000 00000023202 13154500625 0016024 0 ustar 00root root 0000000 0000000 ::
_______ .__ __. ____ ____
| ____|| \ | | \ \ / /
| |__ | \| | \ \/ /
| __| | . ` | \ /
__ | |____ | |\ | \ /
(__)|_______||__| \__| \__/
python-dotenv | |Build Status| |Coverage Status| |PyPI version| |PyPI|
======================================================================
Reads the key,value pair from ``.env`` and adds them to environment
variable. It is great of managing app settings during development and in
production using `12-factor `__ principles.
Do one thing, do it well!
- `Usages <#usages>`__
- `Installation <#installation>`__
- `Command-line interface <#command-line-interface>`__
- `iPython Support <#ipython-support>`__
- `Setting config on remote servers <#setting-config-on-remote-servers>`__
- `Related Projects <#releated-projects>`__
- `Contributing <#contributing>`__
- `Changelog <#changelog>`__
Usages
======
Assuming you have created the ``.env`` file along-side your settings
module.
::
.
├── .env
└── settings.py
Add the following code to your ``settings.py``
.. code:: python
# settings.py
from os.path import join, dirname
from dotenv import load_dotenv
dotenv_path = join(dirname(__file__), '.env')
load_dotenv(dotenv_path)
# OR, the same with increased verbosity:
load_dotenv(dotenv_path, verbose=True)
Alternatively, you can use ``find_dotenv()`` method that will try to find a
``.env`` file by (a) guessing where to start using ``__file__`` or the working
directory -- allowing this to work in non-file contexts such as IPython notebooks
and the REPL, and then (b) walking up the directory tree looking for the
specified file -- called ``.env`` by default.
.. code:: python
from dotenv import load_dotenv, find_dotenv
load_dotenv(find_dotenv())
You can also set _load_dotenv_ to override existing variables:
.. code:: python
from dotenv import load_dotenv, find_dotenv
load_dotenv(find_dotenv(), override=True)
Now, you can access the variables either from system environment
variable or loaded from ``.env`` file. **System environment variables
gets higher precedence** and it's advised not to include it in version control.
.. code:: python
# settings.py
SECRET_KEY = os.environ.get("SECRET_KEY")
DATABASE_PASSWORD = os.environ.get("DATABASE_PASSWORD")
``.env`` is a simple text file. With each environment variables listed
per line, in the format of ``KEY="Value"``, lines starting with `#` is
ignored.
.. code:: shell
SOME_VAR=someval
# I am a comment and that is OK
FOO="BAR"
``.env`` can interpolate variables using POSIX variable expansion, variables
are replaced from the environment first or from other values in the ``.env``
file if the variable is not present in the environment. (``Note``: Default Value
Expansion is not supported as of yet, see `#30 `__.)
.. code:: shell
CONFIG_PATH=${HOME}/.config/foo
DOMAIN=example.org
EMAIL=admin@${DOMAIN}
Django
------
If you are using django you should add the above loader script at the
top of ``wsgi.py`` and ``manage.py``.
Installation
============
::
pip install -U python-dotenv
Command-line interface
======================
A cli interface ``dotenv`` is also included, which helps you manipulate
the ``.env`` file without manually opening it. The same cli installed on
remote machine combined with fabric (discussed later) will enable you to
update your settings on remote server, handy isn't it!
::
Usage: dotenv [OPTIONS] COMMAND [ARGS]...
This script is used to set, get or unset values from a .env file.
Options:
-f, --file PATH Location of the .env file, defaults to .env
file in current working directory.
-q, --quote [always|never|auto]
Whether to quote or not the variable values.
Default mode is always. This does not affect
parsing.
--help Show this message and exit.
Commands:
get Retrive the value for the given key.
list Display all the stored key/value.
set Store the given key/value.
unset Removes the given key.
iPython Support
---------------
You can use dotenv with iPython. You can either let the dotenv search for .env with `%dotenv` or provide the path to .env file explicitly, see below for usages.
::
%load_ext dotenv
# Use find_dotenv to locate the file
%dotenv
# Specify a particular file
%dotenv relative/or/absolute/path/to/.env
# Use _-o_ to indicate override of existing variables
%dotenv -o
# Use _-v_ to turn verbose mode on
%dotenv -v
Setting config on remote servers
--------------------------------
We make use of excellent `Fabric `__ to
acomplish this. Add a config task to your local fabfile, ``dotenv_path``
is the location of the absolute path of ``.env`` file on the remote
server.
.. code:: python
# fabfile.py
import dotenv
from fabric.api import task, run, env
# absolute path to the location of .env on remote server.
env.dotenv_path = '/opt/myapp/.env'
@task
def config(action=None, key=None, value=None):
'''Manage project configuration via .env
e.g: fab config:set,,
fab config:get,
fab config:unset,
fab config:list
'''
run('touch %(dotenv_path)s' % env)
command = dotenv.get_cli_string(env.dotenv_path, action, key, value)
run(command)
Usage is designed to mirror the heroku config api very closely.
Get all your remote config info with ``fab config``
::
$ fab config
Set remote config variables with ``fab config:set,,``
::
$ fab config:set,hello,world
Get a single remote config variables with ``fab config:get,``
::
$ fab config:get,hello
Delete a remote config variables with ``fab config:unset,``
::
$ fab config:unset,hello
Thanks entirely to fabric and not one bit to this project, you can chain
commands like so ``fab config:set,, config:set,,``
::
$ fab config:set,hello,world config:set,foo,bar config:set,fizz=buzz
Related Projects
=================
- `Honcho `__ - For managing
Procfile-based applications.
- `django-dotenv `__
- `django-environ `__
- `django-configuration `__
Contributing
============
All the contributions are welcome! Please open `an
issue `__ or send
us a pull request.
This project is currently maintained by `Saurabh Kumar`_ and
would not have been possible without the support of these `awesome people `__.
Executing the tests:
::
$ flake8
$ pytest
Changelog
=========
0.7.1
----
- Remove hard dependency on iPython (`@theskumar`_)
0.7.0
----
- Add support to override system environment variable via .env. (`@milonimrod`_) (`#63`_)
- Disable ".env not found" warning by default (`@maxkoryukov`_) (`#57`_)
0.6.5
----
- Add support for special characters `\` (`@pjona`_) (`#60`_)
0.6.4
----
- Fix issue with single quotes (`@Flimm`_) (`#52`_)
0.6.3
----
- Handle unicode exception in setup.py (`#46`_)
0.6.2
----
- Fix `dotenv list` command (`@ticosax`_)
- Add iPython Suport (`@tillahoffmann`_)
0.6.0
-----
- Drop support for Python 2.6
- Handle escaped charaters and newlines in quoted values. (Thanks `@iameugenejo`_)
- Remove any spaces around unquoted key/value. (Thanks `@paulochf`_)
- Added POSIX variable expansion. (Thanks `@hugochinchilla`_)
0.5.1
-----
- Fix `find_dotenv` - it now start search from the file where this function is called from.
0.5.0
-----
- Add ``find_dotenv`` method that will try to find a ``.env`` file. (Thanks `@isms`_)
0.4.0
-----
- cli: Added ``-q/--quote`` option to control the behaviour of quotes around values in ``.env``. (Thanks `@hugochinchilla`_).
- Improved test coverage.
.. _@maxkoryukov: https://github.com/milonimrod
.. _@maxkoryukov: https://github.com/maxkoryukov
.. _@pjona: https://github.com/pjona
.. _@Flimm: https://github.com/Flimm
.. _@ticosax: https://github.com/ticosax
.. _@tillahoffmann: https://github.com/tillahoffmann
.. _@hugochinchilla: https://github.com/hugochinchilla
.. _@isms: https://github.com/isms
.. _@iameugenejo: https://github.com/iameugenejo
.. _@paulochf: https://github.com/paulochf
.. _@paulochf: https://github.com/theskumar
.. _#63: https://github.com/theskumar/python-dotenv/issues/63
.. _#60: https://github.com/theskumar/python-dotenv/issues/60
.. _#57: https://github.com/theskumar/python-dotenv/issues/57
.. _#52: https://github.com/theskumar/python-dotenv/issues/52
.. _#46: https://github.com/theskumar/python-dotenv/issues/46
.. Saurabh Kumar: https://saurabh-kumar.com
.. |Build Status| image:: https://travis-ci.org/theskumar/python-dotenv.svg?branch=master
:target: https://travis-ci.org/theskumar/python-dotenv
.. |Coverage Status| image:: https://coveralls.io/repos/theskumar/python-dotenv/badge.svg?branch=master
:target: https://coveralls.io/r/theskumar/python-dotenv?branch=master
.. |PyPI version| image:: https://badge.fury.io/py/python-dotenv.svg
:target: http://badge.fury.io/py/python-dotenv
.. |PyPI| image:: https://img.shields.io/pypi/dm/python-dotenv.svg
:target: http://badge.fury.io/py/python-dotenv
python-dotenv-0.7.1/_config.yml 0000664 0000000 0000000 00000000033 13154500625 0016461 0 ustar 00root root 0000000 0000000 theme: jekyll-theme-minimal python-dotenv-0.7.1/dotenv/ 0000775 0000000 0000000 00000000000 13154500625 0015635 5 ustar 00root root 0000000 0000000 python-dotenv-0.7.1/dotenv/__init__.py 0000664 0000000 0000000 00000000463 13154500625 0017751 0 ustar 00root root 0000000 0000000 from .cli import get_cli_string
from .main import load_dotenv, get_key, set_key, unset_key, find_dotenv
try:
from .ipython import load_ipython_extension
except ImportError:
pass
__all__ = ['get_cli_string', 'load_dotenv', 'get_key', 'set_key', 'unset_key', 'find_dotenv', 'load_ipython_extension']
python-dotenv-0.7.1/dotenv/cli.py 0000664 0000000 0000000 00000005161 13154500625 0016761 0 ustar 00root root 0000000 0000000 import os
import click
from .main import get_key, dotenv_values, set_key, unset_key
@click.group()
@click.option('-f', '--file', default=os.path.join(os.getcwd(), '.env'),
type=click.Path(exists=True),
help="Location of the .env file, defaults to .env file in current working directory.")
@click.option('-q', '--quote', default='always',
type=click.Choice(['always', 'never', 'auto']),
help="Whether to quote or not the variable values. Default mode is always. This does not affect parsing.")
@click.pass_context
def cli(ctx, file, quote):
'''This script is used to set, get or unset values from a .env file.'''
ctx.obj = {}
ctx.obj['FILE'] = file
ctx.obj['QUOTE'] = quote
@cli.command()
@click.pass_context
def list(ctx):
'''Display all the stored key/value.'''
file = ctx.obj['FILE']
dotenv_as_dict = dotenv_values(file)
for k, v in dotenv_as_dict.items():
click.echo('%s="%s"' % (k, v))
@cli.command()
@click.pass_context
@click.argument('key', required=True)
@click.argument('value', required=True)
def set(ctx, key, value):
'''Store the given key/value.'''
file = ctx.obj['FILE']
quote = ctx.obj['QUOTE']
success, key, value = set_key(file, key, value, quote)
if success:
click.echo('%s="%s"' % (key, value))
else:
exit(1)
@cli.command()
@click.pass_context
@click.argument('key', required=True)
def get(ctx, key):
'''Retrieve the value for the given key.'''
file = ctx.obj['FILE']
stored_value = get_key(file, key)
if stored_value:
click.echo('%s="%s"' % (key, stored_value))
else:
exit(1)
@cli.command()
@click.pass_context
@click.argument('key', required=True)
def unset(ctx, key):
'''Removes the given key.'''
file = ctx.obj['FILE']
quote = ctx.obj['QUOTE']
success, key = unset_key(file, key, quote)
if success:
click.echo("Successfully removed %s" % key)
else:
exit(1)
def get_cli_string(path=None, action=None, key=None, value=None):
"""Returns a string suitable for running as a shell script.
Useful for converting a arguments passed to a fabric task
to be passed to a `local` or `run` command.
"""
command = ['dotenv']
if path:
command.append('-f %s' % path)
if action:
command.append(action)
if key:
command.append(key)
if value:
if ' ' in value:
command.append('"%s"' % value)
else:
command.append(value)
return ' '.join(command).strip()
if __name__ == "__main__":
cli()
python-dotenv-0.7.1/dotenv/ipython.py 0000664 0000000 0000000 00000002415 13154500625 0017703 0 ustar 00root root 0000000 0000000 from __future__ import print_function
from .main import load_dotenv, find_dotenv
from IPython.core.magic import Magics, magics_class, line_magic
from IPython.core.magic_arguments import (argument, magic_arguments,
parse_argstring)
@magics_class
class IPythonDotEnv(Magics):
@magic_arguments()
@argument(
'-o', '--override', action='store_true',
help="Indicate to override existing variables"
)
@argument(
'-v', '--verbose', action='store_true',
help="Indicate function calls to be verbose"
)
@argument('dotenv_path', nargs='?', type=str, default='.env',
help='Search in increasingly higher folders for the `dotenv_path`')
@line_magic
def dotenv(self, line):
args = parse_argstring(self.dotenv, line)
# Locate the .env file
dotenv_path = args.dotenv_path
try:
dotenv_path = find_dotenv(dotenv_path, True, True)
except IOError:
print("cannot find .env file")
return
# Load the .env file
load_dotenv(dotenv_path, verbose=args.verbose, override=args.override)
def load_ipython_extension(ipython):
"""Register the %dotenv magic."""
ipython.register_magics(IPythonDotEnv)
python-dotenv-0.7.1/dotenv/main.py 0000664 0000000 0000000 00000013551 13154500625 0017140 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
from __future__ import absolute_import
import codecs
import os
import sys
import warnings
import re
from collections import OrderedDict
__escape_decoder = codecs.getdecoder('unicode_escape')
__posix_variable = re.compile('\$\{[^\}]*\}')
def decode_escaped(escaped):
return __escape_decoder(escaped)[0]
def load_dotenv(dotenv_path, verbose=False, override=False):
"""
Read a .env file and load into os.environ.
"""
if not os.path.exists(dotenv_path):
if verbose:
warnings.warn("Not loading %s - it doesn't exist." % dotenv_path)
return None
for k, v in dotenv_values(dotenv_path).items():
if override:
os.environ[k] = v
else:
os.environ.setdefault(k, v)
return True
def get_key(dotenv_path, key_to_get):
"""
Gets the value of a given key from the given .env
If the .env path given doesn't exist, fails
"""
key_to_get = str(key_to_get)
if not os.path.exists(dotenv_path):
warnings.warn("can't read %s - it doesn't exist." % dotenv_path)
return None
dotenv_as_dict = dotenv_values(dotenv_path)
if key_to_get in dotenv_as_dict:
return dotenv_as_dict[key_to_get]
else:
warnings.warn("key %s not found in %s." % (key_to_get, dotenv_path))
return None
def set_key(dotenv_path, key_to_set, value_to_set, quote_mode="always"):
"""
Adds or Updates a key/value to the given .env
If the .env path given doesn't exist, fails instead of risking creating
an orphan .env somewhere in the filesystem
"""
key_to_set = str(key_to_set)
value_to_set = str(value_to_set).strip("'").strip('"')
if not os.path.exists(dotenv_path):
warnings.warn("can't write to %s - it doesn't exist." % dotenv_path)
return None, key_to_set, value_to_set
dotenv_as_dict = OrderedDict(parse_dotenv(dotenv_path))
dotenv_as_dict[key_to_set] = value_to_set
success = flatten_and_write(dotenv_path, dotenv_as_dict, quote_mode)
return success, key_to_set, value_to_set
def unset_key(dotenv_path, key_to_unset, quote_mode="always"):
"""
Removes a given key from the given .env
If the .env path given doesn't exist, fails
If the given key doesn't exist in the .env, fails
"""
key_to_unset = str(key_to_unset)
if not os.path.exists(dotenv_path):
warnings.warn("can't delete from %s - it doesn't exist." % dotenv_path)
return None, key_to_unset
dotenv_as_dict = dotenv_values(dotenv_path)
if key_to_unset in dotenv_as_dict:
dotenv_as_dict.pop(key_to_unset, None)
else:
warnings.warn("key %s not removed from %s - key doesn't exist." % (key_to_unset, dotenv_path))
return None, key_to_unset
success = flatten_and_write(dotenv_path, dotenv_as_dict, quote_mode)
return success, key_to_unset
def dotenv_values(dotenv_path):
values = OrderedDict(parse_dotenv(dotenv_path))
values = resolve_nested_variables(values)
return values
def parse_dotenv(dotenv_path):
with open(dotenv_path) as f:
for line in f:
line = line.strip()
if not line or line.startswith('#') or '=' not in line:
continue
k, v = line.split('=', 1)
# Remove any leading and trailing spaces in key, value
k, v = k.strip(), v.strip().encode('unicode-escape').decode('ascii')
if len(v) > 0:
quoted = v[0] == v[len(v) - 1] in ['"', "'"]
if quoted:
v = decode_escaped(v[1:-1])
yield k, v
def resolve_nested_variables(values):
def _replacement(name):
"""
get appropriate value for a variable name.
first search in environ, if not found,
then look into the dotenv variables
"""
ret = os.getenv(name, values.get(name, ""))
return ret
def _re_sub_callback(match_object):
"""
From a match object gets the variable name and returns
the correct replacement
"""
return _replacement(match_object.group()[2:-1])
for k, v in values.items():
values[k] = __posix_variable.sub(_re_sub_callback, v)
return values
def flatten_and_write(dotenv_path, dotenv_as_dict, quote_mode="always"):
with open(dotenv_path, "w") as f:
for k, v in dotenv_as_dict.items():
_mode = quote_mode
if _mode == "auto" and " " in v:
_mode = "always"
str_format = '%s="%s"\n' if _mode == "always" else '%s=%s\n'
f.write(str_format % (k, v))
return True
def _walk_to_root(path):
"""
Yield directories starting from the given directory up to the root
"""
if not os.path.exists(path):
raise IOError('Starting path not found')
if os.path.isfile(path):
path = os.path.dirname(path)
last_dir = None
current_dir = os.path.abspath(path)
while last_dir != current_dir:
yield current_dir
parent_dir = os.path.abspath(os.path.join(current_dir, os.path.pardir))
last_dir, current_dir = current_dir, parent_dir
def find_dotenv(filename='.env', raise_error_if_not_found=False, usecwd=False):
"""
Search in increasingly higher folders for the given file
Returns path to the file if found, or an empty string otherwise
"""
if usecwd or '__file__' not in globals():
# should work without __file__, e.g. in REPL or IPython notebook
path = os.getcwd()
else:
# will work for .py files
frame_filename = sys._getframe().f_back.f_code.co_filename
path = os.path.dirname(os.path.abspath(frame_filename))
for dirname in _walk_to_root(path):
check_path = os.path.join(dirname, filename)
if os.path.exists(check_path):
return check_path
if raise_error_if_not_found:
raise IOError('File not found')
return ''
python-dotenv-0.7.1/requirements.txt 0000664 0000000 0000000 00000000136 13154500625 0017622 0 ustar 00root root 0000000 0000000 flake8>=2.2.3
pytest>=3.0.5
sh>=1.09
bumpversion
wheel
pytest-cov
pytest-flake8
click
ipython
python-dotenv-0.7.1/setup.cfg 0000664 0000000 0000000 00000000340 13154500625 0016154 0 ustar 00root root 0000000 0000000 [bdist_wheel]
universal = 1
[flake8]
max-line-length = 120
exclude = .tox,.git,docs,venv
[metadata]
description-file = README.rst
[tool:pytest]
norecursedirs = .svn _build tmp* dist venv .git
flake8-max-line-length = 120
python-dotenv-0.7.1/setup.py 0000664 0000000 0000000 00000005166 13154500625 0016060 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
from setuptools import setup
# https://github.com/theskumar/python-dotenv/issues/45#issuecomment-277135416
try:
with open('README.rst') as readme_file:
readme = readme_file.read()
except:
readme = 'Checkout http://github.com/theskumar/python-dotenv for more details.'
setup(
name="python-dotenv",
description="Add .env support to your django/flask apps in development and deployments",
long_description=readme,
version="0.7.1",
author="Saurabh Kumar",
author_email="me+github@saurabh-kumar.com",
url="http://github.com/theskumar/python-dotenv",
keywords=['environment variables', 'deployments', 'settings', 'env', 'dotenv',
'configurations', 'python'],
packages=['dotenv'],
install_requires=[
'click>=5.0',
],
entry_points='''
[console_scripts]
dotenv=dotenv:cli.cli
''',
classifiers=[
# As from https://pypi.python.org/pypi?%3Aaction=list_classifiers
# 'Development Status :: 1 - Planning',
# 'Development Status :: 2 - Pre-Alpha',
# 'Development Status :: 3 - Alpha',
# 'Development Status :: 4 - Beta',
'Development Status :: 5 - Production/Stable',
# 'Development Status :: 6 - Mature',
# 'Development Status :: 7 - Inactive',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
# 'Programming Language :: Python :: 2.3',
# 'Programming Language :: Python :: 2.4',
# 'Programming Language :: Python :: 2.5',
# 'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.0',
'Programming Language :: Python :: 3.1',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: Implementation :: PyPy',
'Intended Audience :: Developers',
'Intended Audience :: System Administrators',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Topic :: System :: Systems Administration',
'Topic :: Utilities',
'Environment :: Web Environment',
]
)
# (*) Please direct queries to the discussion group, rather than to me directly
# Doing so helps ensure your question is helpful to other users.
# Queries directly to my email are likely to receive a canned response.
#
# Many thanks for your understanding.
python-dotenv-0.7.1/tests/ 0000775 0000000 0000000 00000000000 13154500625 0015500 5 ustar 00root root 0000000 0000000 python-dotenv-0.7.1/tests/__init__.py 0000664 0000000 0000000 00000000000 13154500625 0017577 0 ustar 00root root 0000000 0000000 python-dotenv-0.7.1/tests/conftest.py 0000664 0000000 0000000 00000000271 13154500625 0017677 0 ustar 00root root 0000000 0000000 import pytest
from .fixtures import * # noqa
@pytest.fixture
def dotenv_file(tmpdir):
file_ = tmpdir.mkdir('dotenv_file').join('.env')
file_.write('')
return str(file_)
python-dotenv-0.7.1/tests/fixtures.py 0000664 0000000 0000000 00000000150 13154500625 0017717 0 ustar 00root root 0000000 0000000 import pytest
from click.testing import CliRunner
@pytest.fixture()
def cli():
return CliRunner()
python-dotenv-0.7.1/tests/test_cli.py 0000664 0000000 0000000 00000012723 13154500625 0017665 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
from os import environ
from os.path import dirname, join
import dotenv
import sh
here = dirname(__file__)
dotenv_path = join(here, '.env')
def test_get_key():
sh.touch(dotenv_path)
success, key_to_set, value_to_set = dotenv.set_key(dotenv_path, 'HELLO', 'WORLD')
stored_value = dotenv.get_key(dotenv_path, 'HELLO')
assert stored_value == 'WORLD'
sh.rm(dotenv_path)
assert dotenv.get_key(dotenv_path, 'HELLO') is None
success, key_to_set, value_to_set = dotenv.set_key(dotenv_path, 'HELLO', 'WORLD')
assert success is None
def test_list(cli, dotenv_file):
success, key_to_set, value_to_set = dotenv.set_key(dotenv_file, 'HELLO', 'WORLD')
result = cli.invoke(dotenv.cli.cli, ['--file', dotenv_file, 'list'])
assert result.exit_code == 0, result.output
assert result.output == 'HELLO="WORLD"\n'
def test_list_wo_file(cli):
result = cli.invoke(dotenv.cli.cli, ['--file', 'doesnotexists', 'list'])
assert result.exit_code == 2, result.output
assert 'Invalid value for "-f"' in result.output
def test_key_value_without_quotes():
with open(dotenv_path, 'w') as f:
f.write("TEST = value \n")
assert dotenv.get_key(dotenv_path, 'TEST') == "value"
sh.rm(dotenv_path)
with open(dotenv_path, 'w') as f:
f.write('TEST = " with spaces " \n')
assert dotenv.get_key(dotenv_path, 'TEST') == " with spaces "
sh.rm(dotenv_path)
def test_value_with_quotes():
with open(dotenv_path, 'w') as f:
f.write('TEST="two words"\n')
assert dotenv.get_key(dotenv_path, 'TEST') == 'two words'
sh.rm(dotenv_path)
with open(dotenv_path, 'w') as f:
f.write("TEST='two words'\n")
assert dotenv.get_key(dotenv_path, 'TEST') == 'two words'
sh.rm(dotenv_path)
def test_value_with_special_characters():
with open(dotenv_path, 'w') as f:
f.write(r'TEST="}=&~{,(\5%{&;"')
assert dotenv.get_key(dotenv_path, 'TEST') == r'}=&~{,(\5%{&;'
sh.rm(dotenv_path)
with open(dotenv_path, 'w') as f:
f.write(r"TEST='}=&~{,(\5%{&;'")
assert dotenv.get_key(dotenv_path, 'TEST') == r'}=&~{,(\5%{&;'
sh.rm(dotenv_path)
def test_unset():
sh.touch(dotenv_path)
success, key_to_set, value_to_set = dotenv.set_key(dotenv_path, 'HELLO', 'WORLD')
stored_value = dotenv.get_key(dotenv_path, 'HELLO')
assert stored_value == 'WORLD'
success, key_to_unset = dotenv.unset_key(dotenv_path, 'HELLO')
assert dotenv.get_key(dotenv_path, 'HELLO') is None
sh.rm(dotenv_path)
success, key_to_unset = dotenv.unset_key(dotenv_path, 'HELLO')
assert success is None
def test_console_script(cli):
TEST_COMBINATIONS = (
# quote_mode, var_name, var_value, expected_result
("always", "HELLO", "WORLD", 'HELLO="WORLD"\n'),
("never", "HELLO", "WORLD", 'HELLO=WORLD\n'),
("auto", "HELLO", "WORLD", 'HELLO=WORLD\n'),
("auto", "HELLO", "HELLO WORLD", 'HELLO="HELLO WORLD"\n'),
)
with cli.isolated_filesystem():
for quote_mode, variable, value, expected_result in TEST_COMBINATIONS:
sh.touch(dotenv_path)
sh.dotenv('-f', dotenv_path, '-q', quote_mode, 'set', variable, value)
output = sh.cat(dotenv_path)
assert output == expected_result
sh.rm(dotenv_path)
# should fail for not existing file
result = cli.invoke(dotenv.cli.set, ['my_key', 'my_value'])
assert result.exit_code != 0
# should fail for not existing file
result = cli.invoke(dotenv.cli.get, ['my_key'])
assert result.exit_code != 0
# should fail for not existing file
result = cli.invoke(dotenv.cli.list, [])
assert result.exit_code != 0
def test_default_path(cli):
with cli.isolated_filesystem():
sh.touch(dotenv_path)
sh.cd(here)
sh.dotenv('set', 'HELLO', 'WORLD')
output = sh.dotenv('get', 'HELLO')
assert output == 'HELLO="WORLD"\n'
sh.rm(dotenv_path)
def test_get_key_with_interpolation(cli):
with cli.isolated_filesystem():
sh.touch(dotenv_path)
dotenv.set_key(dotenv_path, 'HELLO', 'WORLD')
dotenv.set_key(dotenv_path, 'FOO', '${HELLO}')
dotenv.set_key(dotenv_path, 'BAR', 'CONCATENATED_${HELLO}_POSIX_VAR')
lines = list(open(dotenv_path, "r").readlines())
assert lines == [
'HELLO="WORLD"\n',
'FOO="${HELLO}"\n',
'BAR="CONCATENATED_${HELLO}_POSIX_VAR"\n',
]
# test replace from variable in file
stored_value = dotenv.get_key(dotenv_path, 'FOO')
assert stored_value == 'WORLD'
stored_value = dotenv.get_key(dotenv_path, 'BAR')
assert stored_value == 'CONCATENATED_WORLD_POSIX_VAR'
# test replace from environ taking precedence over file
environ["HELLO"] = "TAKES_PRECEDENCE"
stored_value = dotenv.get_key(dotenv_path, 'FOO')
assert stored_value == "TAKES_PRECEDENCE"
sh.rm(dotenv_path)
def test_get_key_with_interpolation_of_unset_variable(cli):
with cli.isolated_filesystem():
sh.touch(dotenv_path)
dotenv.set_key(dotenv_path, 'FOO', '${NOT_SET}')
# test unavailable replacement returns empty string
stored_value = dotenv.get_key(dotenv_path, 'FOO')
assert stored_value == ''
# unless present in environment
environ['NOT_SET'] = 'BAR'
stored_value = dotenv.get_key(dotenv_path, 'FOO')
assert stored_value == 'BAR'
del(environ['NOT_SET'])
sh.rm(dotenv_path)
python-dotenv-0.7.1/tests/test_core.py 0000664 0000000 0000000 00000006360 13154500625 0020046 0 ustar 00root root 0000000 0000000 # -*- coding: utf8 -*-
import os
import pytest
import tempfile
import warnings
import sh
from dotenv import load_dotenv, find_dotenv, set_key
from IPython.terminal.embed import InteractiveShellEmbed
def test_warns_if_file_does_not_exist():
with warnings.catch_warnings(record=True) as w:
load_dotenv('.does_not_exist', verbose=True)
assert len(w) == 1
assert w[0].category is UserWarning
assert str(w[0].message) == "Not loading .does_not_exist - it doesn't exist."
def test_find_dotenv():
"""
Create a temporary folder structure like the following:
tmpXiWxa5/
└── child1
├── child2
│ └── child3
│ └── child4
└── .env
Then try to automatically `find_dotenv` starting in `child4`
"""
tmpdir = os.path.realpath(tempfile.mkdtemp())
curr_dir = tmpdir
dirs = []
for f in ['child1', 'child2', 'child3', 'child4']:
curr_dir = os.path.join(curr_dir, f)
dirs.append(curr_dir)
os.mkdir(curr_dir)
child1, child4 = dirs[0], dirs[-1]
# change the working directory for testing
os.chdir(child4)
# try without a .env file and force error
with pytest.raises(IOError):
find_dotenv(raise_error_if_not_found=True, usecwd=True)
# try without a .env file and fail silently
assert find_dotenv(usecwd=True) == ''
# now place a .env file a few levels up and make sure it's found
filename = os.path.join(child1, '.env')
with open(filename, 'w') as f:
f.write("TEST=test\n")
assert find_dotenv(usecwd=True) == filename
def test_load_dotenv(cli):
dotenv_path = '.test_load_dotenv'
with cli.isolated_filesystem():
sh.touch(dotenv_path)
set_key(dotenv_path, 'DOTENV', 'WORKS')
assert 'DOTENV' not in os.environ
success = load_dotenv(dotenv_path)
assert success
assert 'DOTENV' in os.environ
assert os.environ['DOTENV'] == 'WORKS'
sh.rm(dotenv_path)
def test_load_dotenv_override(cli):
dotenv_path = '.test_load_dotenv_override'
key_name = "DOTENV_OVER"
with cli.isolated_filesystem():
sh.touch(dotenv_path)
os.environ[key_name] = "OVERRIDE"
set_key(dotenv_path, key_name, 'WORKS')
success = load_dotenv(dotenv_path, override=True)
assert success
assert key_name in os.environ
assert os.environ[key_name] == 'WORKS'
sh.rm(dotenv_path)
def test_ipython():
tmpdir = os.path.realpath(tempfile.mkdtemp())
os.chdir(tmpdir)
filename = os.path.join(tmpdir, '.env')
with open(filename, 'w') as f:
f.write("MYNEWVALUE=q1w2e3\n")
ipshell = InteractiveShellEmbed()
ipshell.magic("load_ext dotenv")
ipshell.magic("dotenv")
assert os.environ["MYNEWVALUE"] == 'q1w2e3'
def test_ipython_override():
tmpdir = os.path.realpath(tempfile.mkdtemp())
os.chdir(tmpdir)
filename = os.path.join(tmpdir, '.env')
os.environ["MYNEWVALUE"] = "OVERRIDE"
with open(filename, 'w') as f:
f.write("MYNEWVALUE=q1w2e3\n")
ipshell = InteractiveShellEmbed()
ipshell.magic("load_ext dotenv")
ipshell.magic("dotenv -o")
assert os.environ["MYNEWVALUE"] == 'q1w2e3'
python-dotenv-0.7.1/tests/test_utils.py 0000664 0000000 0000000 00000001116 13154500625 0020250 0 ustar 00root root 0000000 0000000 from dotenv import get_cli_string as c
def test_to_cli_string():
assert c() == 'dotenv'
assert c(path='/etc/.env') == 'dotenv -f /etc/.env'
assert c(path='/etc/.env', action='list') == 'dotenv -f /etc/.env list'
assert c(action='list') == 'dotenv list'
assert c(action='get', key='DEBUG') == 'dotenv get DEBUG'
assert c(action='set', key='DEBUG', value='True') == 'dotenv set DEBUG True'
assert c(action='set', key='SECRET', value='=@asdfasf') == 'dotenv set SECRET =@asdfasf'
assert c(action='set', key='SECRET', value='a b') == 'dotenv set SECRET "a b"'