Skip to content

Commit

Permalink
Merge pull request #36 from renan-r-santos/fix-pixi-on-pixi
Browse files Browse the repository at this point in the history
Fix `pixi` using `PIXI_PROJECT_MANIFEST` instead of the nearest `pixi.toml` during manifest discovery
  • Loading branch information
renan-r-santos authored Nov 29, 2024
2 parents 054357f + 9c40656 commit 59186c4
Show file tree
Hide file tree
Showing 18 changed files with 6,585 additions and 4,209 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
pixi-version: ["0.30.0", "0.34.0"]
pixi-version: ["0.30.0", "0.38.0"]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout repo
Expand All @@ -25,13 +25,13 @@ jobs:
run-install: false

- name: Install uv
uses: astral-sh/setup-uv@v3
uses: astral-sh/setup-uv@v4

- name: Test, lint and typecheck
run: uv run tox

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@v5
with:
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v3
uses: astral-sh/setup-uv@v4

- name: Build release
run: uv build
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ uv run tox run -e type_check

1. Bump
1. Increment version in `pyproject.toml`
2. Update all Pixi lock files by running `uv sync`
2. Update all lock files by running `uv sync -U` and `pixi update`
3. Commit with message "chore: Bump version number to X.Y.Z"
4. Push commit to GitHub
5. Check [CI](https://github.com/renan-r-santos/pixi-kernel/actions/workflows/ci.yml) to ensure
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "pixi-kernel"
version = "0.5.1"
version = "0.5.2"
description = "Jupyter kernels using Pixi for reproducible notebooks"
license = { text = "MIT" }
authors = [
Expand Down Expand Up @@ -43,8 +43,8 @@ dev-dependencies = [
"mypy>=1,<2",
"pytest>=8,<9",
"pytest-asyncio>=0.24,<0.25",
"pytest-cov>=5,<6",
"ruff>=0.6,<0.7",
"pytest-cov>=6,<7",
"ruff>=0.8,<0.9",
"tox-uv>=1,<2",
]

Expand Down
3 changes: 3 additions & 0 deletions src/pixi_kernel/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import logging

logging.basicConfig(level=logging.INFO, format="pixi-kernel %(levelname)s: %(message)s")
4 changes: 3 additions & 1 deletion src/pixi_kernel/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
PIXI_KERNEL_NOT_FOUND = """
To run the {kernel_name} kernel, you need to add the {required_package} package to
your project dependencies. You can do this by running 'pixi add {required_package}'
in your project directory and restarting your kernel.
in your project directory and restarting your kernel. Make sure the prefix
{prefix}
points to the correct Pixi environment.
If you continue to face issues, report them at https://github.com/renan-r-santos/pixi-kernel/issues
"""
42 changes: 28 additions & 14 deletions src/pixi_kernel/pixi.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import logging
import shutil
import subprocess
from pathlib import Path
Expand All @@ -11,6 +12,8 @@

MINIMUM_PIXI_VERSION = "0.30.0"

logger = logging.getLogger(__name__)


class PixiInfo(BaseModel):
environments: list[Environment] = Field(alias="environments_info")
Expand All @@ -28,7 +31,13 @@ class Project(BaseModel):
manifest_path: str


def ensure_readiness(*, cwd: Path, required_package: str, kernel_name: str) -> Environment:
def ensure_readiness(
*,
cwd: Path,
env: dict[str, str],
required_package: str,
kernel_name: str,
) -> Environment:
"""Ensure the Pixi environment is ready to run the kernel.
This function checks the following:
Expand All @@ -43,12 +52,16 @@ def ensure_readiness(*, cwd: Path, required_package: str, kernel_name: str) -> E
Returns the path to the Pixi environment prefix.
"""
# Remove PIXI_IN_SHELL for when JupyterLab is started from a Pixi shell
# https://github.com/renan-r-santos/pixi-kernel/issues/35
env.pop("PIXI_IN_SHELL", None)

# Ensure Pixi is in PATH
if shutil.which("pixi") is None:
raise RuntimeError(PIXI_NOT_FOUND.format(kernel_name=kernel_name))

# Ensure a supported Pixi version is installed
result = subprocess.run(["pixi", "--version"], capture_output=True, text=True)
result = subprocess.run(["pixi", "--version"], capture_output=True, env=env, text=True)
if result.returncode != 0 or not result.stdout.startswith("pixi "):
raise RuntimeError(PIXI_VERSION_ERROR.format(kernel_name=kernel_name))

Expand All @@ -66,10 +79,13 @@ def ensure_readiness(*, cwd: Path, required_package: str, kernel_name: str) -> E
# Ensure there is a Pixi project in the current working directory or any of its parents
result = subprocess.run(
["pixi", "info", "--json"],
cwd=str(cwd.absolute()),
cwd=cwd,
capture_output=True,
env=env,
text=True,
)
logger.info(f"pixi info stderr: {result.stderr}")
logger.info(f"pixi info stdout: {result.stdout}")
if result.returncode != 0:
raise RuntimeError(f"Failed to run 'pixi info': {result.stderr}")

Expand All @@ -85,16 +101,17 @@ def ensure_readiness(*, cwd: Path, required_package: str, kernel_name: str) -> E
# a typo in the toml file (parsing error) or there is no project at all.
result = subprocess.run(
["pixi", "project", "version", "get"],
cwd=str(cwd.absolute()),
cwd=cwd,
capture_output=True,
env=env,
text=True,
)
raise RuntimeError(result.stderr)

# Find the default environment and check if the required kernel package is a dependency
for env in pixi_info.environments:
if env.name == "default":
default_environment = env
for pixi_env in pixi_info.environments:
if pixi_env.name == "default":
default_environment = pixi_env
break
else:
raise RuntimeError("Default Pixi environment not found.")
Expand All @@ -103,17 +120,14 @@ def ensure_readiness(*, cwd: Path, required_package: str, kernel_name: str) -> E
if required_package not in dependencies:
raise RuntimeError(
PIXI_KERNEL_NOT_FOUND.format(
kernel_name=kernel_name, required_package=required_package
kernel_name=kernel_name,
required_package=required_package,
prefix=default_environment.prefix,
)
)

# Make sure the environment can be solved and is up-to-date
result = subprocess.run(
["pixi", "install"],
cwd=str(cwd.absolute()),
capture_output=True,
text=True,
)
result = subprocess.run(["pixi", "install"], cwd=cwd, capture_output=True, env=env, text=True)
if result.returncode != 0:
raise RuntimeError(f"Failed to run 'pixi install': {result.stderr}")

Expand Down
15 changes: 10 additions & 5 deletions src/pixi_kernel/provisioner.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from logging import Logger
import logging
import os
from pathlib import Path
from typing import Any, Optional, cast

Expand All @@ -7,14 +8,15 @@

from .pixi import ensure_readiness

logger = logging.getLogger(__name__)


class PixiKernelProvisioner(LocalProvisioner): # type: ignore
async def pre_launch(self, **kwargs: Any) -> dict[str, Any]:
"""Perform any steps in preparation for kernel process launch.
This includes ensuring Pixi is installed and that a Pixi project is available.
"""
logger = cast(Logger, self.log)
kernel_spec = cast(KernelSpec, self.kernel_spec)

kernel_metadata: Optional[dict[str, str]] = kernel_spec.metadata.get("pixi-kernel")
Expand All @@ -29,18 +31,21 @@ async def pre_launch(self, **kwargs: Any) -> dict[str, Any]:
raise ValueError("Pixi Kernel metadata is missing the 'required-package' key")

cwd = Path(kwargs.get("cwd", Path.cwd()))
logger.info(f"JupyterLab provided this value for cwd: {kwargs.get('cwd', None)}")
logger.info(f"The current working directory is {cwd}")

environment = ensure_readiness(
cwd=cwd,
env: dict[str, str] = kwargs.get("env", os.environ)
pixi_environment = ensure_readiness(
cwd=cwd.resolve(),
env=env,
required_package=required_package,
kernel_name=kernel_spec.display_name,
)

# R kernel needs special treatment
# https://github.com/renan-r-santos/pixi-kernel/issues/15
if required_package == "r-irkernel":
r_libs_path = str(Path(environment.prefix) / "lib" / "R" / "library")
r_libs_path = str(Path(pixi_environment.prefix) / "lib" / "R" / "library")
kernel_spec.env["R_LIBS"] = r_libs_path
kernel_spec.env["R_LIBS_SITE"] = r_libs_path
kernel_spec.env["R_LIBS_USER"] = r_libs_path
Expand Down
Loading

0 comments on commit 59186c4

Please sign in to comment.