Skip to content

Commit

Permalink
Merge pull request #31 from coveooss/fix/environments
Browse files Browse the repository at this point in the history
#30 // Fix environments
  • Loading branch information
jonapich authored Apr 13, 2021
2 parents 77d435d + d4d5165 commit cba33d9
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 17 deletions.
3 changes: 0 additions & 3 deletions coveo-stew/coveo_stew/ci/mypy_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,6 @@ def _launch(self, environment: PythonEnvironment, *extra_args: str) -> RunnerSta
]
return RunnerStatus.Error

# mypy needs the dependencies installed in an environment in order to inspect them.
self._pyproject.install(quiet=True)

# projects may opt to use coveo-stew's mypy version by not including mypy in their dependencies.
mypy_environment = (
environment if environment.mypy_executable.exists() else coveo_stew_environment
Expand Down
3 changes: 2 additions & 1 deletion coveo-stew/coveo_stew/ci/pytest_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ def __init__(

def _launch(self, environment: PythonEnvironment, *extra_args: str) -> RunnerStatus:
if not environment.pytest_executable.exists():
self._pyproject.install()
self._last_output.append("pytest executable could not be found")
return RunnerStatus.Error

command = environment.build_command(
PythonTool.Pytest,
Expand Down
8 changes: 7 additions & 1 deletion coveo-stew/coveo_stew/metadata/pyproject_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,13 @@ def virtual_environments(
) -> List["PythonEnvironment"]:
...

def install(self, *, remove_untracked: bool = True, quiet: bool = False) -> None:
def install(
self,
*,
environment: "PythonEnvironment" = None,
remove_untracked: bool = True,
quiet: bool = False
) -> None:
...

def build(self, target_path: Path) -> Path:
Expand Down
81 changes: 69 additions & 12 deletions coveo-stew/coveo_stew/stew.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Interact with python projects programmatically."""

from contextlib import contextmanager
import os
from pathlib import Path
import re
Expand All @@ -17,7 +18,7 @@
from poetry.factory import Factory

from coveo_stew.ci.config import ContinuousIntegrationConfig
from coveo_stew.ci.runner import ContinuousIntegrationRunner, RunnerStatus
from coveo_stew.ci.runner import RunnerStatus
from coveo_stew.environment import PythonEnvironment, coveo_stew_environment, PythonTool
from coveo_stew.exceptions import PythonProjectException, NotAPoetryProject
from coveo_stew.metadata.stew_api import StewPackage
Expand Down Expand Up @@ -76,6 +77,7 @@ def __init__(self, project_path: Path, *, verbose: bool = False) -> None:
repo_root = None

self.repo_root: Optional[Path] = repo_root
self._cached_activated_environment: Optional[PythonEnvironment] = None

def relative_path(self, path: Path) -> Path:
"""returns the relative path of a path vs the project folder."""
Expand Down Expand Up @@ -123,6 +125,25 @@ def lock_is_outdated(self) -> bool:
return False
return not self.poetry.locker.is_fresh()

@property
def activated_environment(self) -> Optional[PythonEnvironment]:
"""The environment activated for a project.
Note: cached for performance, could theoretically become out-of-sync with reality, either due to a bug,
or because someone called `poetry env use` while we were working :shrug:
To prevent out of sync:
- Use the context manager `self._activate_poetry_environment`
- or use the `environment` argument of `self.poetry_run`
"""
if self._cached_activated_environment is None:
activated = self.poetry_run(
"env", "info", "--path", capture_output=True, breakout_of_venv=True
).strip()
if activated:
self._cached_activated_environment = PythonEnvironment(activated)
return self._cached_activated_environment

def virtual_environments(
self, *, create_default_if_missing: bool = False
) -> List[PythonEnvironment]:
Expand Down Expand Up @@ -244,16 +265,23 @@ def launch_continuous_integration(
)
return all(runner.status in allowed_statuses for runner in self.ci.runners)

def install(self, remove_untracked: bool = True, quiet: bool = False) -> None:
def install(
self,
*,
environment: PythonEnvironment = None,
remove_untracked: bool = True,
quiet: bool = False,
) -> None:
"""
Performs a 'poetry install --remove-untracked' on the project. If an environment is provided, target it.
"""
command = ["install"]
if remove_untracked:
command.append("--remove-untracked")
if quiet:
if quiet and not self.verbose:
command.append("--quiet")
self.poetry_run(*command)

self.poetry_run(*command, environment=environment)

def remove_egg_info(self) -> bool:
"""Removes the egg-info (editable project hook) from the folder. Returns True if we removed it."""
Expand All @@ -277,7 +305,11 @@ def lock_if_needed(self) -> bool:
return False

def poetry_run(
self, *commands: Any, capture_output: bool = False, breakout_of_venv: bool = True
self,
*commands: Any,
capture_output: bool = False,
breakout_of_venv: bool = True,
environment: PythonEnvironment = None,
) -> Optional[str]:
"""internal run-a-poetry-command."""
# we use the poetry executable from our dependencies, not from the project's environment!
Expand All @@ -291,13 +323,38 @@ def poetry_run(
if breakout_of_venv:
environment_variables.pop("VIRTUAL_ENV", None)

return check_run(
*poetry_env.build_command(PythonTool.Poetry, *commands, "-vv" if self.verbose else ""),
working_directory=self.project_path,
capture_output=capture_output,
verbose=self.verbose,
env=environment_variables,
)
with self._activate_poetry_environment(environment):
return check_run(
*poetry_env.build_command(
PythonTool.Poetry, *commands, "-vv" if self.verbose else ""
),
working_directory=self.project_path,
capture_output=capture_output,
verbose=self.verbose,
env=environment_variables,
)

@contextmanager
def _activate_poetry_environment(
self, environment: PythonEnvironment = None
) -> Generator[None, None, None]:
""" Context manager that can be used to run a block of code in a particular environment. """
if not environment:
# `self.activated_environment` uses us without an environment specified; prevents infinite recursion
yield
return

current_environment = self.activated_environment
if current_environment == environment:
yield
return

try:
self.poetry_run("env", "use", environment.python_executable)
yield
finally:
if current_environment:
self.poetry_run("env", "use", current_environment.python_executable)

def __str__(self) -> str:
return f"{self.package.name} [{self.toml_path}]"

0 comments on commit cba33d9

Please sign in to comment.