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

fix: inject environment variables from the host #64

Merged
merged 3 commits into from
Oct 18, 2024
Merged
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: 1 addition & 1 deletion .github/workflows/coveo-stew.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
poetry-version: [""]
os: [ubuntu-latest, windows-latest, macos-latest]

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Similar to: nothing! it's unique! 😎


# Prerequisites
*Changed in 3.1*: You need python 3.9+ to run stew, but you can still use it on projects requiring older versions of python.

*Changed in 3.0*: `poetry` is no longer provided out-of-the-box.

Expand Down
10 changes: 7 additions & 3 deletions coveo_stew/ci/any_runner.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from enum import Enum, auto
from typing import Iterable, List, Optional, Tuple, Union
from typing import Any, Iterable, List, Optional, Tuple, Union

from coveo_styles.styles import ExitWithFailure
from coveo_systools.filesystem import find_repo_root
Expand Down Expand Up @@ -68,7 +68,9 @@ def __init__(
f"Working directory for {self.name} should be within {WorkingDirectoryKind.valid_values()}"
)

async def _launch(self, environment: PythonEnvironment, *extra_args: str) -> RunnerStatus:
async def _launch(
self, environment: PythonEnvironment, *extra_args: str, **kwargs: Any
) -> RunnerStatus:
args = [self.check_args] if isinstance(self.check_args, str) else self.check_args
command = environment.build_command(self.executable, *args)

Expand All @@ -83,6 +85,7 @@ async def _launch(self, environment: PythonEnvironment, *extra_args: str) -> Run
*extra_args,
working_directory=working_directory,
verbose=self._pyproject.verbose,
**kwargs,
)
).split("\n")
)
Expand All @@ -97,7 +100,7 @@ def name(self) -> str:
def executable(self) -> str:
return self._executable or self.name

async def _custom_autofix(self, environment: PythonEnvironment) -> None:
async def _custom_autofix(self, environment: PythonEnvironment, **kwargs: Any) -> None:
args = [self.autofix_args] if isinstance(self.autofix_args, str) else self.autofix_args
command = environment.build_command(self.executable, *args)

Expand All @@ -111,6 +114,7 @@ async def _custom_autofix(self, environment: PythonEnvironment) -> None:
*command,
working_directory=working_directory,
verbose=self._pyproject.verbose,
**kwargs,
)
).split("\n")
)
19 changes: 13 additions & 6 deletions coveo_stew/ci/black_runner.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Any

from coveo_systools.subprocess import DetailedCalledProcessError, async_check_output

from coveo_stew.ci.runner import ContinuousIntegrationRunner
Expand All @@ -14,22 +16,27 @@ def __init__(self, *, _pyproject: PythonProject) -> None:
super().__init__(_pyproject=_pyproject)
self._auto_fix_routine = self.reformat_files

async def _launch(self, environment: PythonEnvironment, *extra_args: str) -> RunnerStatus:
async def _launch(
self, environment: PythonEnvironment, *extra_args: str, **kwargs: Any
) -> RunnerStatus:
try:
await self._launch_internal(environment, "--check", "--quiet", *extra_args)
await self._launch_internal(environment, "--check", "--quiet", *extra_args, **kwargs)
except DetailedCalledProcessError:
# re-run without the quiet switch so that the output appears in the console
await self._launch_internal(environment, "--check", *extra_args)
await self._launch_internal(environment, "--check", *extra_args, **kwargs)
return RunnerStatus.Success

async def reformat_files(self, environment: PythonEnvironment) -> None:
await self._launch_internal(environment, "--quiet")
async def reformat_files(self, environment: PythonEnvironment, **kwargs: Any) -> None:
await self._launch_internal(environment, "--quiet", **kwargs)

async def _launch_internal(self, environment: PythonEnvironment, *extra_args: str) -> None:
async def _launch_internal(
self, environment: PythonEnvironment, *extra_args: str, **kwargs: Any
) -> None:
# projects may opt to use coveo-stew's black version by not including black in their dependencies.
command = environment.build_command(PythonTool.Black, ".", *extra_args)
await async_check_output(
*command,
working_directory=self._pyproject.project_path,
verbose=self._pyproject.verbose,
**kwargs,
)
7 changes: 5 additions & 2 deletions coveo_stew/ci/mypy_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import re
from contextlib import ExitStack
from pathlib import Path
from typing import Generator, Optional, Union
from typing import Any, Generator, Optional, Union

import importlib_resources
from coveo_styles.styles import echo
Expand Down Expand Up @@ -46,7 +46,9 @@ def _find_typed_folders(self) -> Generator[Path, None, None]:
self._pyproject.project_path.iterdir(),
)

async def _launch(self, environment: PythonEnvironment, *extra_args: str) -> RunnerStatus:
async def _launch(
self, environment: PythonEnvironment, *extra_args: str, **kwargs: Any
) -> RunnerStatus:
typed_folders = tuple(folder.name for folder in self._find_typed_folders())

if not typed_folders:
Expand Down Expand Up @@ -84,6 +86,7 @@ async def _launch(self, environment: PythonEnvironment, *extra_args: str) -> Run
*command,
working_directory=self._pyproject.project_path,
verbose=self._pyproject.verbose,
**kwargs,
)
return RunnerStatus.Success

Expand Down
7 changes: 6 additions & 1 deletion coveo_stew/ci/poetry_runners.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Any

from coveo_systools.subprocess import async_check_output

from coveo_stew.ci.runner import ContinuousIntegrationRunner
Expand All @@ -9,9 +11,12 @@ class PoetryCheckRunner(ContinuousIntegrationRunner):
name: str = "poetry-check"
check_failed_exit_codes = [1]

async def _launch(self, environment: PythonEnvironment, *extra_args: str) -> RunnerStatus:
async def _launch(
self, environment: PythonEnvironment, *extra_args: str, **kwargs: Any
) -> RunnerStatus:
await async_check_output(
*environment.build_command(PythonTool.Poetry, "check"),
working_directory=self._pyproject.project_path,
**kwargs,
)
return RunnerStatus.Success
7 changes: 6 additions & 1 deletion coveo_stew/ci/pytest_runner.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Any

from coveo_systools.subprocess import async_check_output

from coveo_stew.ci.runner import ContinuousIntegrationRunner
Expand All @@ -22,7 +24,9 @@ def __init__(
self.marker_expression = marker_expression
self.doctest_modules: bool = doctest_modules

async def _launch(self, environment: PythonEnvironment, *extra_args: str) -> RunnerStatus:
async def _launch(
self, environment: PythonEnvironment, *extra_args: str, **kwargs: Any
) -> RunnerStatus:
command = environment.build_command(
PythonTool.Pytest,
"--durations=5",
Expand All @@ -40,6 +44,7 @@ async def _launch(self, environment: PythonEnvironment, *extra_args: str) -> Run
*extra_args,
working_directory=self._pyproject.project_path,
verbose=self._pyproject.verbose,
**kwargs,
)

return RunnerStatus.Success
20 changes: 15 additions & 5 deletions coveo_stew/ci/runner.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import asyncio
import os
from abc import abstractmethod
from dataclasses import dataclass
from functools import cached_property
from pathlib import Path
from typing import Callable, Coroutine, Iterable, List, Optional, Sequence, Tuple
from typing import Any, Coroutine, Iterable, List, Optional, Protocol, Sequence, Tuple

from coveo_styles.styles import echo
from coveo_systools.subprocess import DetailedCalledProcessError
Expand All @@ -15,13 +16,19 @@
from coveo_stew.stew import PythonProject


class AutoFixRoutineCallable(Protocol):
def __call__(
self, environment: PythonEnvironment, **kwargs: Any
) -> Coroutine[None, None, None]: ...


class ContinuousIntegrationRunner:
status: RunnerStatus = RunnerStatus.NotRan
check_failed_exit_codes: Iterable[int] = []
outputs_own_report: bool = False # set to True if the runner produces its own report.

# implementations may provide an auto fix routine.
_auto_fix_routine: Optional[Callable[[PythonEnvironment], Coroutine[None, None, None]]] = None
_auto_fix_routine: Optional[AutoFixRoutineCallable] = None

def __init__(self, *, _pyproject: PythonProject) -> None:
"""Implementations may add additional keyword args."""
Expand All @@ -46,8 +53,9 @@ async def launch(
"""
self._last_output.clear()
self._test_cases.clear()
environment_variables = os.environ.copy()
try:
self.status = await self._launch(environment, *extra_args)
self.status = await self._launch(environment, *extra_args, env=environment_variables)
except DetailedCalledProcessError as exception:
if exception.returncode in self.check_failed_exit_codes:
self.status = RunnerStatus.CheckFailed
Expand All @@ -60,7 +68,7 @@ async def launch(
if all((auto_fix, self.supports_auto_fix, self.status == RunnerStatus.CheckFailed)):
echo.noise("Errors founds; launching auto-fix routine.")
assert self._auto_fix_routine is not None # mypy
await self._auto_fix_routine(environment)
await self._auto_fix_routine(environment, env=environment_variables)

# it should pass now!
await self.launch(environment, *extra_args)
Expand All @@ -85,7 +93,9 @@ def supports_auto_fix(self) -> bool:
return self._auto_fix_routine is not None

@abstractmethod
async def _launch(self, environment: PythonEnvironment, *extra_args: str) -> RunnerStatus:
async def _launch(
self, environment: PythonEnvironment, *extra_args: str, **kwargs: Any
) -> RunnerStatus:
"""Launch the continuous integration check using the given environment and store the output."""

def echo_last_failures(self) -> None:
Expand Down
10 changes: 8 additions & 2 deletions coveo_stew/ci/stew_runners.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import shutil
import tempfile
from pathlib import Path
from typing import Any

from coveo_styles.styles import echo
from coveo_systools.filesystem import pushd
Expand All @@ -16,7 +17,9 @@
class CheckOutdatedRunner(ContinuousIntegrationRunner):
name: str = "check-outdated"

async def _launch(self, environment: PythonEnvironment, *extra_args: str) -> RunnerStatus:
async def _launch(
self, environment: PythonEnvironment, *extra_args: str, **kwargs: Any
) -> RunnerStatus:
if self._pyproject.lock_is_outdated():
self._last_output = ['The lock file is out of date: run "stew fix-outdated"']
return RunnerStatus.CheckFailed
Expand All @@ -26,7 +29,9 @@ async def _launch(self, environment: PythonEnvironment, *extra_args: str) -> Run
class OfflineInstallRunner(ContinuousIntegrationRunner):
name: str = "poetry-build"

async def _launch(self, environment: PythonEnvironment, *extra_args: str) -> RunnerStatus:
async def _launch(
self, environment: PythonEnvironment, *extra_args: str, **kwargs: Any
) -> RunnerStatus:
temporary_folder = Path(tempfile.mkdtemp())
offline_install_location = temporary_folder / "wheels"

Expand Down Expand Up @@ -57,6 +62,7 @@ async def _launch(self, environment: PythonEnvironment, *extra_args: str) -> Run
else ""
),
),
**kwargs,
)
finally:
await asyncio.sleep(0.01) # give a few cycles to close handles/etc
Expand Down
8 changes: 6 additions & 2 deletions coveo_stew/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,19 @@ def build_command(self, tool: Union[PythonTool, str], *args: Any) -> List[Any]:
@lru_cache
def has_tool(self, tool: Union[PythonTool, str]) -> bool:
try:
_ = check_output(self.python_executable, "-c", f"import {tool};", stderr=PIPE)
_ = check_output(
self.python_executable, "-c", f"import {tool};", stderr=PIPE, env=os.environ.copy()
)
return True
except CalledProcessError:
return False

@property
def python_version(self) -> str:
if self._python_version is None:
self._python_version = check_output(str(self.python_executable), "--version").strip()
self._python_version = check_output(
str(self.python_executable), "--version", env=os.environ.copy()
).strip()
assert self._python_version is not None
return self._python_version

Expand Down
4 changes: 3 additions & 1 deletion coveo_stew/offline_publish.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ def _store_setup_dependencies_in_wheelhouse(
*self.environment.build_command(PythonTool.Pip, "wheel", dep),
working_directory=self.wheelhouse,
verbose=self.verbose,
env=os.environ.copy(),
)

def _store_dependencies_in_wheelhouse(self, project: Optional[PythonProject] = None) -> None:
Expand Down Expand Up @@ -167,7 +168,7 @@ def _store_dependencies_in_wheelhouse(self, project: Optional[PythonProject] = N
)

try:
_ = check_output(*command, verbose=self.verbose)
_ = check_output(*command, verbose=self.verbose, env=os.environ.copy())
finally:
try:
Path(requirements_file_path).unlink(missing_ok=True)
Expand All @@ -193,4 +194,5 @@ def _validate_package(self, package_specification: str) -> None:
),
working_directory=self.wheelhouse,
verbose=self.verbose,
env=os.environ.copy(),
)
Loading
Loading