Skip to content

Commit

Permalink
PoetryExecutor does not use poetry if POETRY_VIRTUALENVS_CREATE=false #…
Browse files Browse the repository at this point in the history
…65

Also:
- PoetryExecutor uses VirtualEnv based execution whenever possible
- Fixed issue where poe would use the env from the current project instead of
  the target project
- Update some tests to not avoid encountering missing poetry env
  • Loading branch information
nat-n committed Oct 9, 2022
1 parent adf7c14 commit 4a41a9e
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 42 deletions.
67 changes: 54 additions & 13 deletions poethepoet/executor/poetry.py
@@ -1,5 +1,5 @@
from subprocess import Popen, PIPE
import os
from os import environ
from pathlib import Path
import shutil
import sys
Expand All @@ -24,14 +24,15 @@ def execute(
Execute the given cmd as a subprocess inside the poetry managed dev environment
"""

# If this run involves multiple executions then it's worth trying to
# avoid repetative (and expensive) calls to `poetry run` if we can
poetry_env = self._get_poetry_virtualenv(force=self.context.multistage)
poetry_env = self._get_poetry_virtualenv()
project_dir = self.context.config.project_dir

if (
bool(os.environ.get("POETRY_ACTIVE"))
or self.context.poe_active == PoetryExecutor.__key__
or sys.prefix == poetry_env
if sys.prefix == poetry_env or (
(
bool(environ.get("POETRY_ACTIVE"))
or self.context.poe_active == PoetryExecutor.__key__
)
and project_dir == environ.get("POE_ROOT", project_dir)
):
# The target venv is already active so we can execute the command unaltered
return self._execute_cmd(cmd, input=input, use_exec=use_exec)
Expand All @@ -46,6 +47,10 @@ def execute(
use_exec=use_exec,
)

if self._virtualenv_creation_disabled():
# There's no poetry env, and there isn't going to be
return self._execute_cmd(cmd, input=input, use_exec=use_exec)

# Run this task with `poetry run`
return self._execute_cmd(
(self._poetry_cmd(), "run", *cmd),
Expand All @@ -56,16 +61,52 @@ def execute(
def _get_poetry_virtualenv(self, force: bool = True):
"""
Ask poetry where it put the virtualenv for this project.
This is a relatively expensive operation so uses the context.exec_cache
Invoking poetry is relatively expensive so cache the result
"""
if force and "poetry_virtualenv" not in self.context.exec_cache:
self.context.exec_cache["poetry_virtualenv"] = (
Popen((self._poetry_cmd(), "env", "info", "-p"), stdout=PIPE)

# TODO: see if there's a more efficient way to do this that doesn't involve
# invoking the poetry cli or relying on undocumented APIs

exec_cache = self.context.exec_cache

if force and "poetry_virtualenv" not in exec_cache:
# Need to make sure poetry isn't influenced by whatever virtualenv is
# currently active
clean_env = dict(environ)
clean_env.pop("VIRTUAL_ENV", None)

exec_cache["poetry_virtualenv"] = (
Popen(
(self._poetry_cmd(), "env", "info", "-p"),
stdout=PIPE,
cwd=self.context.config.project_dir,
env=clean_env,
)
.communicate()[0]
.decode()
.strip()
)
return self.context.exec_cache.get("poetry_virtualenv")

return exec_cache.get("poetry_virtualenv")

def _poetry_cmd(self):
return shutil.which("poetry") or "poetry"

def _virtualenv_creation_disabled(self):
exec_cache = self.context.exec_cache

while "poetry_virtualenvs_create_disabled" not in exec_cache:
# Check env override
env_override = environ.get("POETRY_VIRTUALENVS_CREATE")
if env_override is not None:
exec_cache["poetry_virtualenvs_create_disabled"] = (
env_override == "false"
)
break

# A complete implementation would also check for a local poetry config file
# and a global poetry config file (location for this is platform dependent)
# in that order but just checking the env will do for now.
break

return exec_cache.get("poetry_virtualenvs_create_disabled", False)
12 changes: 9 additions & 3 deletions tests/conftest.py
Expand Up @@ -137,11 +137,17 @@ def run_poe_subproc(
output=rf"open(r\"{temp_file}\", \"w\")",
)

env = dict(os.environ, **(env or {}))
subproc_env = dict(os.environ)
subproc_env.pop("VIRTUAL_ENV", None)
if env:
subproc_env.update(env)

if coverage:
env["COVERAGE_PROCESS_START"] = str(PROJECT_TOML)
subproc_env["COVERAGE_PROCESS_START"] = str(PROJECT_TOML)

poeproc = Popen(shell_cmd, shell=True, stdout=PIPE, stderr=PIPE, env=env)
poeproc = Popen(
shell_cmd, shell=True, stdout=PIPE, stderr=PIPE, env=subproc_env
)
task_out, task_err = poeproc.communicate()

with temp_file.open("rb") as output_file:
Expand Down
8 changes: 8 additions & 0 deletions tests/fixtures/example_project/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

File renamed without changes.
16 changes: 12 additions & 4 deletions tests/test_poe_config.py
@@ -1,6 +1,13 @@
import os
from pathlib import Path
import pytest
import tempfile
import shutil


# Setting POETRY_VIRTUALENVS_CREATE stops poetry from creating the virtualenv and
# spamming about it in stdout
no_venv = {"POETRY_VIRTUALENVS_CREATE": "false"}


def test_setting_default_task_type(run_poe_subproc, projects, esc_prefix):
Expand All @@ -10,14 +17,15 @@ def test_setting_default_task_type(run_poe_subproc, projects, esc_prefix):
"nat,",
r"welcome to " + esc_prefix + "${POE_ROOT}",
project="scripts",
env=no_venv,
)
assert result.capture == f"Poe => echo-args nat, welcome to {projects['scripts']}\n"
assert result.stdout == f"hello nat, welcome to {projects['scripts']}\n"
assert result.stderr == ""


def test_setting_default_array_item_task_type(run_poe_subproc):
result = run_poe_subproc("composite_task", project="scripts")
result = run_poe_subproc("composite_task", project="scripts", env=no_venv)
assert (
result.capture == f"Poe => poe_test_echo Hello\nPoe => poe_test_echo World!\n"
)
Expand All @@ -26,7 +34,7 @@ def test_setting_default_array_item_task_type(run_poe_subproc):


def test_setting_global_env_vars(run_poe_subproc, is_windows):
result = run_poe_subproc("travel")
result = run_poe_subproc("travel", env=no_venv)
if is_windows:
assert (
result.capture
Expand Down Expand Up @@ -74,11 +82,11 @@ def test_partially_decrease_verbosity(run_poe_subproc, high_verbosity_project_pa
assert result.stderr == ""


def test_decrease_verbosity(run_poe_subproc, projects, is_windows):
def test_decrease_verbosity(run_poe_subproc, is_windows):
result = run_poe_subproc(
"-q",
"part1",
cwd=projects["example"],
env=no_venv,
)
assert result.capture == ""
assert result.stderr == ""
Expand Down

0 comments on commit 4a41a9e

Please sign in to comment.