pax_global_header00006660000000000000000000000064133316414710014515gustar00rootroot0000000000000052 comment=4e76e82e539842d00be0d6d02062a81b8f58675d python-dotenv-0.9.1/000077500000000000000000000000001333164147100143425ustar00rootroot00000000000000python-dotenv-0.9.1/.bumpversion.cfg000066400000000000000000000001461333164147100174530ustar00rootroot00000000000000[bumpversion] current_version = 0.9.1 commit = True tag = True [bumpversion:file:dotenv/version.py] python-dotenv-0.9.1/.coveragerc000066400000000000000000000001041333164147100164560ustar00rootroot00000000000000[run] source = dotenv/ omit = tests/* venv/* *conftest* python-dotenv-0.9.1/.editorconfig000066400000000000000000000003631333164147100170210ustar00rootroot00000000000000# 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.9.1/.gitignore000066400000000000000000000001511333164147100163270ustar00rootroot00000000000000*.egg-info *.pyc build/ dist/ .env __pycache__ .coverage .DS_Store htmlcov/ .cache/ .idea .pytest_cache/ python-dotenv-0.9.1/.pyup.yml000066400000000000000000000001731333164147100161410ustar00rootroot00000000000000# autogenerated pyup.io config file # see https://pyup.io/docs/configuration/ for all available options update: insecure python-dotenv-0.9.1/.travis.yml000066400000000000000000000011071333164147100164520ustar00rootroot00000000000000language: python sudo: false python: - '2.7' - '3.3' - '3.4' - '3.5' - '3.6' - pypy install: - pip install python-coveralls - pip install -q -r requirements.txt - pip install --editable . script: - flake8 . - pytest -v --tb=native --cov after_success: - coveralls deploy: provider: pypi user: theskumar password: secure: VdULnucEUBZB0qZFKrP0kGdxYOY02QrvJBvVAHDc4CBhvPWcfF5HpEWD1CDNr2wdvg5Wwi5WGgX66Rthkrsw6akPwB+aPA++bFZ0uMHy392Nu4hxusX915R3JGnQ7L53aYrohOsJ7JsrZ7n12I38LJSewfkyQydAxjhJA+3hAtw= on: tags: true repo: theskumar/python-dotenv python-dotenv-0.9.1/LICENSE000066400000000000000000000107701333164147100153540ustar00rootroot00000000000000python-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.9.1/MANIFEST.in000066400000000000000000000000741333164147100161010ustar00rootroot00000000000000include LICENSE include README.rst include requirements.txt python-dotenv-0.9.1/Makefile000066400000000000000000000011071333164147100160010ustar00rootroot00000000000000.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.9.1/README.md000066400000000000000000000256601333164147100156320ustar00rootroot00000000000000``` _______ .__ __. ____ ____ | ____|| \ | | \ \ / / | |__ | \| | \ \/ / | __| | . ` | \ / __ | |____ | |\ | \ / (__)|_______||__| \__| \__/ ``` python-dotenv | [![Build Status](https://travis-ci.org/theskumar/python-dotenv.svg?branch=master)](https://travis-ci.org/theskumar/python-dotenv) [![Coverage Status](https://coveralls.io/repos/theskumar/python-dotenv/badge.svg?branch=master)](https://coveralls.io/r/theskumar/python-dotenv?branch=master) [![PyPI version](https://badge.fury.io/py/python-dotenv.svg)](http://badge.fury.io/py/python-dotenv) [![Say Thanks!](https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg)](https://saythanks.io/to/theskumar) =============================================================================== Reads the key,value pair from `.env` file and adds them to environment variable. It is great for managing app settings during development and in production using [12-factor](http://12factor.net/) 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](#related-projects) - [Contributing](#contributing) - [Changelog](#changelog) Usages ====== The easiest and most common usage consists on calling `load_dotenv` when the application starts, which will load environment variables from a file named `.env` in the current directory or any of its parents or from the path specificied; after that, you can just call the environment-related method you need as provided by `os.getenv`. `.env` looks like this: ```shell # a comment and that will be ignored. REDIS_ADDRESS=localhost:6379 MEANING_OF_LIFE=42 MULTILINE_VAR="hello\nworld" ``` You can optionally prefix each line with the word `export`, which will conveniently allow you to source the whole file on your shell. `.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](https://github.com/theskumar/python-dotenv/pull/30#issuecomment-244036604).) ```shell CONFIG_PATH=${HOME}/.config/foo DOMAIN=example.org EMAIL=admin@${DOMAIN} ``` Getting started =============== Assuming you have created the `.env` file along-side your settings module. . ├── .env └── settings.py Add the following code to your `settings.py` ```python # settings.py from dotenv import load_dotenv load_dotenv() # OR, the same with increased verbosity: load_dotenv(verbose=True) # OR, explicitly providing path to '.env' from pathlib import Path # python3 only env_path = Path('.') / '.env' load_dotenv(dotenv_path=env_path) ``` At this point, parsed key/value from the .env file is now present as system environment variable and they can be conveniently accessed via `os.getenv()` ```python # settings.py import os SECRET_KEY = os.getenv("EMAIL") DATABASE_PASSWORD = os.getenv("DATABASE_PASSWORD") ``` `load_dotenv` do not override existing System environment variables. To override, pass `override=True` to `load_dotenv()`. 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. ```python from dotenv import load_dotenv, find_dotenv load_dotenv(find_dotenv()) ``` In-memory filelikes ------------------- It is possible to not rely on the filesystem to parse filelikes from other sources (e.g. from a network storage). `load_dotenv` and `dotenv_values` accepts a filelike `stream`. Just be sure to rewind it before passing. ```python >>> from io import StringIO # Python2: from StringIO import StringIO >>> from dotenv import dotenv_values >>> filelike = StringIO('SPAM=EGSS\n') >>> filelike.seek(0) >>> parsed = dotenv_values(stream=filelike) >>> parsed['SPAM'] 'EGSS' ``` The returned value is dictionary with key value pair. `dotenv_values` could be useful if you need to *consume* the envfile but not *apply* it directly into the system environment. 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 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 Command-line interface ====================== For commandline support, use the cli option during installation: pip install -U "python-dotenv[cli]" 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. run Run command with environment variables from .env file present set Store the given key/value. unset Removes the given key. ``` Setting config on remote servers -------------------------------- We make use of excellent [Fabric](http://www.fabfile.org/) 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. ```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 foo="bar" 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](https://github.com/nickstenning/honcho) - For managing Procfile-based applications. - [django-dotenv](https://github.com/jpadilla/django-dotenv) - [django-environ](https://github.com/joke2k/django-environ) - [django-configuration](https://github.com/jezdez/django-configurations) - [dump-env](https://github.com/sobolevn/dump-env) Contributing ============ All the contributions are welcome! Please open [an issue](https://github.com/theskumar/python-dotenv/issues/new) 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](https://github.com/theskumar/python-dotenv/graphs/contributors). Executing the tests: $ flake8 $ pytest Changelog ========= 0.8.1 ----- - Add tests for docs ([@Flimm]) - Make 'cli' support optional. Use `pip install python-dotenv[cli]`. ([@theskumar]) 0.8.0 ----- - `set_key` and `unset_key` only modified the affected file instead of parsing and re-writing file, this causes comments and other file entact as it is. - Add support for `export` prefix in the line. - Internal refractoring ([@theskumar]) - Allow `load_dotenv` and `dotenv_values` to work with `StringIO())` ([@alanjds])([@theskumar])([#78]) 0.7.1 ----- - Remove hard dependency on iPython ([@theskumar]) 0.7.0 ----- - Add support to override system environment variable via .env. ([@milonimrod](https://github.com/milonimrod)) ([\#63](https://github.com/theskumar/python-dotenv/issues/63)) - Disable ".env not found" warning by default ([@maxkoryukov](https://github.com/maxkoryukov)) ([\#57](https://github.com/theskumar/python-dotenv/issues/57)) 0.6.5 ----- - Add support for special characters `\`. ([@pjona](https://github.com/pjona)) ([\#60](https://github.com/theskumar/python-dotenv/issues/60)) 0.6.4 ----- - Fix issue with single quotes ([@Flimm]) ([\#52](https://github.com/theskumar/python-dotenv/issues/52)) 0.6.3 ----- - Handle unicode exception in setup.py ([\#46](https://github.com/theskumar/python-dotenv/issues/46)) 0.6.2 ----- - Fix dotenv list command ([@ticosax](https://github.com/ticosax)) - Add iPython Suport ([@tillahoffmann](https://github.com/tillahoffmann)) 0.6.0 ----- - Drop support for Python 2.6 - Handle escaped charaters and newlines in quoted values. (Thanks [@iameugenejo](https://github.com/iameugenejo)) - Remove any spaces around unquoted key/value. (Thanks [@paulochf](https://github.com/paulochf)) - Added POSIX variable expansion. (Thanks [@hugochinchilla](https://github.com/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](https://github.com/isms)) 0.4.0 ----- - cli: Added `-q/--quote` option to control the behaviour of quotes around values in `.env`. (Thanks [@hugochinchilla](https://github.com/hugochinchilla)). - Improved test coverage. [#78]: https://github.com/theskumar/python-dotenv/issues/78 [@Flimm]: https://github.com/Flimm [@theskumar]: https://github.com/theskumar [@alanjds]: https://github.com/alanjds python-dotenv-0.9.1/_config.yml000066400000000000000000000000331333164147100164650ustar00rootroot00000000000000theme: jekyll-theme-minimalpython-dotenv-0.9.1/dotenv/000077500000000000000000000000001333164147100156415ustar00rootroot00000000000000python-dotenv-0.9.1/dotenv/__init__.py000066400000000000000000000021421333164147100177510ustar00rootroot00000000000000from .main import load_dotenv, get_key, set_key, unset_key, find_dotenv, dotenv_values def load_ipython_extension(ipython): from .ipython import load_ipython_extension load_ipython_extension(ipython) def get_cli_string(path=None, action=None, key=None, value=None, quote=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 quote: command.append('-q %s' % quote) 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() __all__ = ['get_cli_string', 'load_dotenv', 'dotenv_values', 'get_key', 'set_key', 'unset_key', 'find_dotenv', 'load_ipython_extension'] python-dotenv-0.9.1/dotenv/cli.py000066400000000000000000000053441333164147100167700ustar00rootroot00000000000000import os import sys try: import click except ImportError: sys.stderr.write('It seems python-dotenv is not installed with cli option. \n' 'Run pip install "python-dotenv[cli]" to fix this.') sys.exit(1) from .main import dotenv_values, get_key, set_key, unset_key, run_command from .version import __version__ @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.version_option(version=__version__) @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) @cli.command(context_settings={'ignore_unknown_options': True}) @click.pass_context @click.argument('commandline', nargs=-1, type=click.UNPROCESSED) def run(ctx, commandline): """Run command with environment variables present.""" file = ctx.obj['FILE'] dotenv_as_dict = dotenv_values(file) if not commandline: click.echo('No command given.') exit(1) ret = run_command(commandline, dotenv_as_dict) exit(ret) if __name__ == "__main__": cli() python-dotenv-0.9.1/dotenv/compat.py000066400000000000000000000001471333164147100175000ustar00rootroot00000000000000try: from StringIO import StringIO # noqa except ImportError: from io import StringIO # noqa python-dotenv-0.9.1/dotenv/ipython.py000066400000000000000000000024161333164147100177100ustar00rootroot00000000000000from __future__ import print_function from IPython.core.magic import Magics, line_magic, magics_class from IPython.core.magic_arguments import (argument, magic_arguments, parse_argstring) from .main import find_dotenv, load_dotenv @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.9.1/dotenv/main.py000066400000000000000000000200251333164147100171360ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import absolute_import, print_function, unicode_literals import codecs import fileinput import io import os import re import sys from subprocess import Popen, PIPE, STDOUT import warnings from collections import OrderedDict from .compat import StringIO __escape_decoder = codecs.getdecoder('unicode_escape') __posix_variable = re.compile('\$\{[^\}]*\}') def decode_escaped(escaped): return __escape_decoder(escaped)[0] def parse_line(line): line = line.strip() # Ignore lines with `#` or which doesn't have `=` in it. if not line or line.startswith('#') or '=' not in line: return None, None k, v = line.split('=', 1) if k.startswith('export '): (_, _, k) = k.partition('export ') # Remove any leading and trailing spaces in key, value k, v = k.strip(), v.strip() if v: v = v.encode('unicode-escape').decode('ascii') quoted = v[0] == v[-1] in ['"', "'"] if quoted: v = decode_escaped(v[1:-1]) return k, v class DotEnv(): def __init__(self, dotenv_path, verbose=False): self.dotenv_path = dotenv_path self._dict = None self.verbose = verbose def _get_stream(self): self._is_file = False if isinstance(self.dotenv_path, StringIO): return self.dotenv_path if os.path.exists(self.dotenv_path): self._is_file = True return io.open(self.dotenv_path) if self.verbose: warnings.warn("File doesn't exist {}".format(self.dotenv_path)) return StringIO('') def dict(self): """Return dotenv as dict""" if self._dict: return self._dict values = OrderedDict(self.parse()) self._dict = resolve_nested_variables(values) return self._dict def parse(self): f = self._get_stream() for line in f: key, value = parse_line(line) if not key: continue yield key, value if self._is_file: f.close() def set_as_environment_variables(self, override=False): """ Load the current dotenv as system environemt variable. """ for k, v in self.dict().items(): if k in os.environ and not override: continue os.environ[k] = v return True def get(self, key): """ """ data = self.dict() if key in data: return data[key] if self.verbose: warnings.warn("key %s not found in %s." % (key, self.dotenv_path)) 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 """ return DotEnv(dotenv_path, verbose=True).get(key_to_get) 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 """ value_to_set = 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 if " " in value_to_set: quote_mode = "always" line_template = '{}="{}"' if quote_mode == "always" else '{}={}' line_out = line_template.format(key_to_set, value_to_set) replaced = False for line in fileinput.input(dotenv_path, inplace=True): k, v = parse_line(line) if k == key_to_set: replaced = True line = line_out print(line, end='') if not replaced: with io.open(dotenv_path, "a") as f: f.write("{}\n".format(line_out)) return True, 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 """ removed = False 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 for line in fileinput.input(dotenv_path, inplace=True): k, v = parse_line(line) if k == key_to_unset: removed = True line = '' print(line, end='') if not removed: warnings.warn("key %s not removed from %s - key doesn't exist." % (key_to_unset, dotenv_path)) return None, key_to_unset return removed, key_to_unset 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 _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 = sys._getframe() # find first frame that is outside of this file while frame.f_code.co_filename == __file__: frame = frame.f_back frame_filename = frame.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 '' def load_dotenv(dotenv_path=None, stream=None, verbose=False, override=False): f = dotenv_path or stream or find_dotenv() return DotEnv(f, verbose=verbose).set_as_environment_variables(override=override) def dotenv_values(dotenv_path=None, stream=None, verbose=False): f = dotenv_path or stream or find_dotenv() return DotEnv(f, verbose=verbose).dict() def run_command(command, env): """Run command in sub process. Runs the command in a sub process with the variables from `env` added in the current environment variables. Parameters ---------- command: List[str] The command and it's parameters env: Dict The additional environment variables Returns ------- int The return code of the command """ # copy the current environment variables and add the vales from # `env` cmd_env = os.environ.copy() cmd_env.update(env) p = Popen(command, stdin=PIPE, stdout=PIPE, stderr=STDOUT, universal_newlines=True, bufsize=0, shell=False, env=cmd_env) try: out, _ = p.communicate() print(out) except Exception: warnings.warn('An error occured, running the command:') out, _ = p.communicate() warnings.warn(out) return p.returncode python-dotenv-0.9.1/dotenv/version.py000066400000000000000000000000261333164147100176760ustar00rootroot00000000000000__version__ = "0.9.1" python-dotenv-0.9.1/requirements.txt000066400000000000000000000001311333164147100176210ustar00rootroot00000000000000flake8>=2.2.3 pytest>=3.0.5 sh>=1.09 bumpversion wheel pytest-cov click ipython pypandoc python-dotenv-0.9.1/setup.cfg000066400000000000000000000003611333164147100161630ustar00rootroot00000000000000[bdist_wheel] universal = 1 [flake8] max-line-length = 120 exclude = .tox,.git,docs,venv,.venv [metadata] description-file = README.rst [tool:pytest] norecursedirs = .svn _build tmp* dist venv .git .venv venv flake8-max-line-length = 120 python-dotenv-0.9.1/setup.py000066400000000000000000000057761333164147100160730ustar00rootroot00000000000000# -*- coding: utf-8 -*- from setuptools import setup # https://github.com/theskumar/python-dotenv/issues/45#issuecomment-277135416 try: import pypandoc long_description = pypandoc.convert('README.md', 'rst') long_description = long_description.replace("\r", "") # YOU NEED THIS LINE except (OSError, ImportError): print("Pandoc not found. Long_description conversion failure.") import io # pandoc is not installed, fallback to using raw contents with io.open('README.md', encoding="utf-8") as f: long_description = f.read() meta = {} exec(open('./dotenv/version.py').read(), meta) setup( name="python-dotenv", description="Add .env support to your django/flask apps in development and deployments", long_description=long_description, version=meta['__version__'], 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'], extras_require={ 'cli': ['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 :: 3.6', '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.9.1/tests/000077500000000000000000000000001333164147100155045ustar00rootroot00000000000000python-dotenv-0.9.1/tests/__init__.py000066400000000000000000000000001333164147100176030ustar00rootroot00000000000000python-dotenv-0.9.1/tests/conftest.py000066400000000000000000000002711333164147100177030ustar00rootroot00000000000000import 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.9.1/tests/fixtures.py000066400000000000000000000001501333164147100177230ustar00rootroot00000000000000import pytest from click.testing import CliRunner @pytest.fixture() def cli(): return CliRunner() python-dotenv-0.9.1/tests/test_cli.py000066400000000000000000000175171333164147100176770ustar00rootroot00000000000000# -*- coding: utf-8 -*- from os import environ from os.path import dirname, join import dotenv from dotenv.version import __version__ from dotenv.cli import cli as dotenv_cli 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_set_key(dotenv_file): success, key_to_set, value_to_set = dotenv.set_key(dotenv_path, 'HELLO', 'WORLD') success, key_to_set, value_to_set = dotenv.set_key(dotenv_path, 'foo', 'bar') dotenv.get_key(dotenv_path, 'HELLO') == 'WORLD' success, key_to_set, value_to_set = dotenv.set_key(dotenv_path, 'HELLO', 'WORLD 2') dotenv.get_key(dotenv_path, 'HELLO') == 'WORLD 2' dotenv.get_key(dotenv_path, 'foo') == 'bar' 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, ['--file', dotenv_file, 'list']) assert result.exit_code == 0, result.output assert result.output == 'HELLO=WORLD\n' def test_get_cli(cli, dotenv_file): cli.invoke(dotenv_cli, ['--file', dotenv_file, 'set', 'HELLO', "WORLD 1"]) result = cli.invoke(dotenv_cli, ['--file', dotenv_file, 'get', 'HELLO']) assert result.exit_code == 0, result.output assert result.output == 'HELLO=WORLD 1\n' def test_list_wo_file(cli): result = cli.invoke(dotenv_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 success is True assert dotenv.get_key(dotenv_path, 'HELLO') is None success, key_to_unset = dotenv.unset_key(dotenv_path, 'RANDOM') assert success is None sh.rm(dotenv_path) success, key_to_unset = dotenv.unset_key(dotenv_path, 'HELLO') assert success is None def test_unset_cli(cli, dotenv_file): success, key_to_set, value_to_set = dotenv.set_key(dotenv_file, 'TESTHELLO', 'WORLD') dotenv.get_key(dotenv_file, 'TESTHELLO') == 'WORLD' result = cli.invoke(dotenv_cli, ['--file', dotenv_file, 'unset', 'TESTHELLO']) assert result.exit_code == 0, result.output assert result.output == 'Successfully removed TESTHELLO\n' dotenv.get_key(dotenv_file, 'TESTHELLO') is None result = cli.invoke(dotenv_cli, ['--file', dotenv_file, 'unset', 'TESTHELLO']) assert result.exit_code == 1, result.output 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) def test_run(cli): with cli.isolated_filesystem(): sh.touch(dotenv_path) sh.cd(here) dotenv.set_key(dotenv_path, 'FOO', 'BAR') result = sh.dotenv('run', 'printenv', 'FOO').strip() assert result == 'BAR' def test_run_with_other_env(cli, dotenv_file): cli.invoke(dotenv_cli, ['--file', dotenv_file, 'set', 'FOO', "BAR"]) result = cli.invoke(dotenv_cli, ['--file', dotenv_file, 'run', 'printenv', 'FOO']) assert result.output.strip() == 'BAR' def test_run_without_cmd(cli): result = cli.invoke(dotenv_cli, ['run']) assert result.exit_code != 0 def test_run_with_invalid_cmd(cli): result = cli.invoke(dotenv_cli, ['run', 'i_do_not_exist']) assert result.exit_code != 0 def test_run_with_version(cli): result = cli.invoke(dotenv_cli, ['--version']) print(vars(result)) assert result.exit_code == 0 assert result.output.strip().endswith(__version__) python-dotenv-0.9.1/tests/test_core.py000066400000000000000000000113441333164147100200500ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals import os import pytest import tempfile import warnings import sh from dotenv import load_dotenv, find_dotenv, set_key, dotenv_values from dotenv.main import parse_line from dotenv.compat import StringIO from IPython.terminal.embed import InteractiveShellEmbed @pytest.mark.parametrize("test_input,expected", [ ("a=b", ("a", "b")), (" a = b ", ("a", "b")), ("export a=b", ("a", "b")), (" export 'a'=b", ("'a'", "b")), (" export 'a'=b", ("'a'", "b")), ("# a=b", (None, None)), ("# a=b", (None, None)), ("a=b space ", ('a', 'b space')), ("a='b space '", ('a', 'b space ')), ('a="b space "', ('a', 'b space ')), ("export export_spam=1", ("export_spam", "1")), ("export port=8000", ("port", "8000")), ]) def test_parse_line(test_input, expected): assert parse_line(test_input) == expected 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) == "File doesn't exist .does_not_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_load_dotenv_in_current_dir(): # make sure were are here! os.chdir(os.path.dirname(os.path.realpath(__file__))) dotenv_path = '.env' with open(dotenv_path, 'w') as f: f.write("TOTO=bla\n") assert 'TOTO' not in os.environ success = load_dotenv(verbose=True) assert success assert os.environ['TOTO'] == 'bla' 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' def test_dotenv_values_stream(): stream = StringIO(u'hello="it works!😃"\nDOTENV=${hello}\n') stream.seek(0) parsed_dict = dotenv_values(stream=stream) assert 'DOTENV' in parsed_dict assert parsed_dict['DOTENV'] == u'it works!😃' def test_dotenv_values_export(): stream = StringIO('export foo=bar\n') stream.seek(0) load_dotenv(stream=stream) assert 'foo' in os.environ assert os.environ['foo'] == 'bar' python-dotenv-0.9.1/tests/test_utils.py000066400000000000000000000012731333164147100202600ustar00rootroot00000000000000from 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"' assert c(action='set', key='SECRET', value='a b', quote="always") == 'dotenv -q always set SECRET "a b"'