Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add safe option #41

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
DOTENV=true
DOTENV_EXAMPLE=true
27 changes: 26 additions & 1 deletion dotenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
""", re.IGNORECASE | re.VERBOSE)


def read_dotenv(dotenv=None, override=False):
def read_dotenv(dotenv=None, override=False, safe=False):
"""
Read a .env file into os.environ.

Expand All @@ -45,6 +45,8 @@ def read_dotenv(dotenv=None, override=False):
way to ensure tests run consistently across all environments.

:param override: True if values in .env should override system variables.
:param safe: If True, check values in .env.example exist in system
variables.
"""
if dotenv is None:
frame_filename = sys._getframe().f_back.f_code.co_filename
Expand All @@ -64,6 +66,29 @@ def read_dotenv(dotenv=None, override=False):
warnings.warn("Not reading {0} - it doesn't exist.".format(dotenv),
stacklevel=2)

if safe:
dotenv_example = _read_dotenv_example(os.path.dirname(dotenv))
keys_not_exist = _check_safe(dotenv_example)
warnings.warn("The following variables were defined in .env.example "
"but are not present in the environment:\n {}".format(
', '.join(keys_not_exist)), stacklevel=2)


def _read_dotenv_example(dir_name):
dotenv_example = os.path.join(dir_name, '.env.example')
if os.path.isfile(dotenv_example):
with open(dotenv_example) as f:
return parse_dotenv(f.read())
return {}


def _check_safe(dotenv_example):
keys_not_exist = []
for k in dotenv_example.keys():
if k not in os.environ:
keys_not_exist.append(k)
return keys_not_exist


def parse_dotenv(content):
env = {}
Expand Down
75 changes: 73 additions & 2 deletions tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
import unittest
import warnings

from dotenv import parse_dotenv, read_dotenv

from dotenv import (
parse_dotenv,
read_dotenv,
_read_dotenv_example,
_check_safe
)

class ParseDotenvTestCase(unittest.TestCase):
def test_parses_unquoted_values(self):
Expand Down Expand Up @@ -113,6 +117,7 @@ def test_parses_hash_in_quoted_values(self):


class ReadDotenvTestCase(unittest.TestCase):

def test_defaults_to_dotenv(self):
read_dotenv()
self.assertEqual(os.environ.get('DOTENV'), 'true')
Expand All @@ -132,6 +137,18 @@ def test_warns_if_file_does_not_exist(self):
"Not reading .does_not_exist - it doesn't exist."
)

def test_warns_if_values_not_exist(self):
with warnings.catch_warnings(record=True) as w:
read_dotenv('.env', safe=True)

self.assertEqual(len(w), 1)
self.assertTrue(w[0].category is UserWarning)
self.assertEqual(
str(w[0].message),
"The following variables were defined in .env.example but "
"are not present in the environment:\n DOTENV_EXAMPLE"
)


class ParseDotenvDirectoryTestCase(unittest.TestCase):
"""Test parsing a dotenv file given the directory where it lives"""
Expand All @@ -152,3 +169,57 @@ def tearDown(self):
def test_can_read_dotenv_given_its_directory(self):
read_dotenv(self.dotenv_dir)
self.assertEqual(os.environ.get('DOTENV'), 'true')


class ReadDotenvExampleTestCase(unittest.TestCase):

def setUp(self):
# Define our dotenv directory
self.dotenv_dir = os.path.join(
os.path.dirname(__file__), 'dotenv_dir')
self.dir_not_found = os.path.join(self.dotenv_dir, 'dir_not_found')
# Create the directories
os.mkdir(self.dotenv_dir)
os.mkdir(self.dir_not_found)
# Copy the test .env file and .env.example file to our new directory
for file in ('.env', '.env.example'):
shutil.copy2(os.path.abspath(file), self.dotenv_dir)

def tearDown(self):
for dir_name in (self.dotenv_dir, self.dir_not_found):
if os.path.exists(dir_name):
shutil.rmtree(dir_name)

def test_read_example(self):
dotenv_example = _read_dotenv_example(self.dotenv_dir)
expected = {'DOTENV': 'true', 'DOTENV_EXAMPLE': 'true'}
self.assertDictEqual(dotenv_example, expected)

def test_file_not_exists(self):
dotenv_example = _read_dotenv_example(self.dir_not_found)
self.assertDictEqual(dotenv_example, {})


class CheckSafeTestCase(unittest.TestCase):

def setUp(self):
# Define our dotenv directory
self.dotenv_dir = os.path.join(
os.path.dirname(__file__), 'dotenv_dir')
# Create the directory
os.mkdir(self.dotenv_dir)
# Copy the test .env file to our new directory
for file in ('.env', '.env.example'):
shutil.copy2(os.path.abspath(file), self.dotenv_dir)

def tearDown(self):
if os.path.exists(self.dotenv_dir):
shutil.rmtree(self.dotenv_dir)

def test_return(self):
read_dotenv(self.dotenv_dir)
dotenv_example = _read_dotenv_example(self.dotenv_dir)
keys_not_exist = _check_safe(dotenv_example)

expected = ['DOTENV_EXAMPLE']
self.assertEqual(keys_not_exist, expected)