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

Ensure we're loading .env in UTF-8 #1965

Closed
wants to merge 3 commits into from
Closed
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
30 changes: 9 additions & 21 deletions pipenv/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from requests.packages.urllib3.exceptions import InsecureRequestWarning

from .cmdparse import ScriptEmptyError
from .envload import load_dot_env
from .project import Project
from .utils import (
convert_deps_from_pip,
Expand Down Expand Up @@ -62,13 +63,11 @@
PYENV_ROOT,
PYENV_INSTALLED,
PIPENV_YES,
PIPENV_DONT_LOAD_ENV,
PIPENV_DEFAULT_PYTHON_VERSION,
PIPENV_MAX_SUBPROCESS,
PIPENV_DONT_USE_PYENV,
SESSION_IS_INTERACTIVE,
PIPENV_USE_SYSTEM,
PIPENV_DOTENV_LOCATION,
PIPENV_SHELL,
PIPENV_PYTHON,
)
Expand Down Expand Up @@ -137,24 +136,6 @@ def which(command, location=None, allow_global=False):
project = Project(which=which)


def load_dot_env():
"""Loads .env file into sys.environ."""
if not PIPENV_DONT_LOAD_ENV:
# If the project doesn't exist yet, check current directory for a .env file
project_directory = project.project_directory or '.'
denv = dotenv.find_dotenv(
PIPENV_DOTENV_LOCATION or os.sep.join([project_directory, '.env'])
)
if os.path.isfile(denv):
click.echo(
crayons.normal(
'Loading .env environment variables…', bold=True
),
err=True,
)
dotenv.load_dotenv(denv, override=True)


def add_to_path(p):
"""Adds a given path to the PATH."""
if p not in os.environ['PATH']:
Expand Down Expand Up @@ -2216,7 +2197,14 @@ def do_run(command, args, three=None, python=False):
"""
# Ensure that virtualenv is available.
ensure_project(three=three, python=python, validate=False)
load_dot_env()

def report_dotenv():
click.echo(
crayons.normal('Loading .env environment variables…', bold=True),
err=True,
)

load_dot_env(project.project_directory, preload=report_dotenv),
# Activate virtualenv under the current interpreter's environment
inline_activate_virtualenv()
try:
Expand Down
29 changes: 29 additions & 0 deletions pipenv/envload.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
try:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this really need its own file

single function => probably should stay in the main file to avoid the overhead

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It used to contain a subclass of StringIO to abstract away some text stream logic between Python 2 and 3, but I ended up just reading the whole thing out (since the dotenv file would be reasonably small anyway). Also I wanted to trim the 2000-line monster core.py as much as possible… but no, this doesn’t need its own file.

import pathlib
except ImportError:
import pathlib2 as pathlib

import dotenv
import six

from .environments import PIPENV_DONT_LOAD_ENV, PIPENV_DOTENV_LOCATION


def load_dot_env(project_directory, preload=None):
"""Loads .env file into sys.environ.
"""
if PIPENV_DONT_LOAD_ENV:
return
if PIPENV_DOTENV_LOCATION:
dotenv_location = PIPENV_DOTENV_LOCATION
else:
# If the project doesn't exist yet, check current directory.
dotenv_location = str(pathlib.Path(project_directory or '.', '.env'))
dotenv_path = pathlib.Path(dotenv.find_dotenv(dotenv_location))
if not dotenv_path.is_file():
return
if callable(preload):
preload()
with dotenv_path.open() as f:
stream = six.StringIO(f.read())
dotenv.load_dotenv(stream, override=True)
13 changes: 11 additions & 2 deletions tests/integration/test_run.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
import io
import os

from pipenv.project import Project
Expand All @@ -9,13 +11,20 @@
@pytest.mark.dotenv
def test_env(PipenvInstance):
with PipenvInstance(pipfile=False, chdir=True) as p:
with open('.env', 'w') as f:
f.write('HELLO=WORLD')
with io.open('.env', 'w', encoding='utf-8') as f:
f.write(u'HELLO=WORLD\n')
f.write(u'HI=世界\n')

c = p.pipenv('run python -c "import os; print(os.environ[\'HELLO\'])"')
assert c.return_code == 0
assert 'WORLD' in c.out

c = p.pipenv('run python -c "import os; print(os.environ[\'HI\'])"')
assert c.return_code == 0
# The output varies too much from platform to platform.
# As long as it prints (return code 0) I guess it's fine.
# Feel free to contribute a robust assertion if you feel like it.


@pytest.mark.run
def test_scripts(PipenvInstance):
Expand Down