pax_global_header00006660000000000000000000000064131545006250014513gustar00rootroot0000000000000052 comment=d4b170a15319f81fe6d3493497a2fff9273e50aa python-dotenv-0.7.1/000077500000000000000000000000001315450062500143365ustar00rootroot00000000000000python-dotenv-0.7.1/.bumpversion.cfg000066400000000000000000000001351315450062500174450ustar00rootroot00000000000000[bumpversion] current_version = 0.7.1 commit = True tag = True [bumpversion:file:setup.py] python-dotenv-0.7.1/.coveragerc000066400000000000000000000001041315450062500164520ustar00rootroot00000000000000[run] source = dotenv/ omit = tests/* venv/* *conftest* python-dotenv-0.7.1/.editorconfig000066400000000000000000000003631315450062500170150ustar00rootroot00000000000000# 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/.gitignore000066400000000000000000000001321315450062500163220ustar00rootroot00000000000000*.egg-info *.pyc build/ dist/ .env __pycache__ .coverage .DS_Store htmlcov/ .cache/ .idea python-dotenv-0.7.1/.travis.yml000066400000000000000000000010711315450062500164460ustar00rootroot00000000000000language: 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/LICENSE000066400000000000000000000107701315450062500153500ustar00rootroot00000000000000python-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.in000066400000000000000000000000741315450062500160750ustar00rootroot00000000000000include LICENSE include README.rst include requirements.txt python-dotenv-0.7.1/Makefile000066400000000000000000000011071315450062500157750ustar00rootroot00000000000000.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.rst000066400000000000000000000232021315450062500160240ustar00rootroot00000000000000:: _______ .__ __. ____ ____ | ____|| \ | | \ \ / / | |__ | \| | \ \/ / | __| | . ` | \ / __ | |____ | |\ | \ / (__)|_______||__| \__| \__/ 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.yml000066400000000000000000000000331315450062500164610ustar00rootroot00000000000000theme: jekyll-theme-minimalpython-dotenv-0.7.1/dotenv/000077500000000000000000000000001315450062500156355ustar00rootroot00000000000000python-dotenv-0.7.1/dotenv/__init__.py000066400000000000000000000004631315450062500177510ustar00rootroot00000000000000from .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.py000066400000000000000000000051611315450062500167610ustar00rootroot00000000000000import 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.py000066400000000000000000000024151315450062500177030ustar00rootroot00000000000000from __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.py000066400000000000000000000135511315450062500171400ustar00rootroot00000000000000# -*- 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.txt000066400000000000000000000001361315450062500176220ustar00rootroot00000000000000flake8>=2.2.3 pytest>=3.0.5 sh>=1.09 bumpversion wheel pytest-cov pytest-flake8 click ipython python-dotenv-0.7.1/setup.cfg000066400000000000000000000003401315450062500161540ustar00rootroot00000000000000[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.py000066400000000000000000000051661315450062500160600ustar00rootroot00000000000000# -*- 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/000077500000000000000000000000001315450062500155005ustar00rootroot00000000000000python-dotenv-0.7.1/tests/__init__.py000066400000000000000000000000001315450062500175770ustar00rootroot00000000000000python-dotenv-0.7.1/tests/conftest.py000066400000000000000000000002711315450062500176770ustar00rootroot00000000000000import 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.py000066400000000000000000000001501315450062500177170ustar00rootroot00000000000000import pytest from click.testing import CliRunner @pytest.fixture() def cli(): return CliRunner() python-dotenv-0.7.1/tests/test_cli.py000066400000000000000000000127231315450062500176650ustar00rootroot00000000000000# -*- 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.py000066400000000000000000000063601315450062500200460ustar00rootroot00000000000000# -*- 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.py000066400000000000000000000011161315450062500202500ustar00rootroot00000000000000from 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"'