diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 360d91e..aaef2dc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -88,11 +88,125 @@ jobs: pip install "jupyterlab>=4.0.0,<5" jupyterlite_xeus*.whl - jupyter labextension list jupyter labextension list 2>&1 | grep -ie "@jupyterlite/xeus.*OK" python -m jupyterlab.browser_check --no-browser-test + python-tests-mamba-python: + needs: build + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - uses: actions/download-artifact@v2 + with: + name: extension-artifacts + + - name: Install Conda environment with Micromamba + uses: mamba-org/setup-micromamba@v1 + with: + micromamba-version: '1.5.1-0' + environment-file: environment-dev.yaml + cache-environment: true + + - name: Install mamba + run: conda install -c conda-forge mamba + + - name: Make sure the Mamba Python API is available + run: | + mamba install mamba + python -c "from mamba.api import create" + + - name: Install + run: pip install jupyterlite_xeus*.whl + + - name: Run tests + run: pytest -rP test_xeus.py + working-directory: tests + + python-tests-mamba: + needs: build + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - uses: actions/download-artifact@v2 + with: + name: extension-artifacts + + - name: Install Conda environment with Micromamba + uses: mamba-org/setup-micromamba@v1 + with: + micromamba-version: '1.5.1-0' + environment-file: environment-dev.yaml + cache-environment: true + + - name: Install mamba + run: conda install -c conda-forge mamba + + - name: Install + run: pip install jupyterlite_xeus*.whl + + - name: Run tests + run: pytest -rP test_xeus.py + working-directory: tests + + python-tests-micromamba: + needs: build + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - uses: actions/download-artifact@v2 + with: + name: extension-artifacts + + - name: Install mamba + uses: mamba-org/provision-with-micromamba@main + with: + micromamba-version: '0.22.0' + environment-file: environment-dev.yaml + environment-name: xeus-lite-dev + + - name: Install + run: pip install jupyterlite_xeus*.whl + + - name: Run tests + run: pytest -rP test_xeus.py + working-directory: tests + + python-tests-conda: + needs: build + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - uses: actions/download-artifact@v2 + with: + name: extension-artifacts + + - name: Install Conda environment with Micromamba + uses: mamba-org/setup-micromamba@v1 + with: + micromamba-version: '1.5.1-0' + environment-file: environment-dev.yaml + cache-environment: true + + - name: Install + run: pip install jupyterlite_xeus*.whl + + - name: Run tests + run: pytest -rP test_xeus.py + working-directory: tests + integration-tests: name: Integration tests needs: build diff --git a/README.md b/README.md index d32ea8a..05c70fb 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ This workflow usually starts with creating a local conda environment / prefix fo micromamba create -n xeus-python-dev \ --platform=emscripten-wasm32 \ -c https://repo.mamba.pm/emscripten-forge \ - -c https://repo.mamba.pm/conda-forge \ + -c conda-forge \ --yes \ "python>=3.11" pybind11 nlohmann_json pybind11_json numpy pytest \ bzip2 sqlite zlib libffi xtl pyjs \ diff --git a/environment-dev.yaml b/environment-dev.yaml index da603fe..f4df578 100644 --- a/environment-dev.yaml +++ b/environment-dev.yaml @@ -9,7 +9,6 @@ dependencies: - hatch-nodejs-version - jupyterlab >=4.0 - jupyterlite-core - - mamba - nodejs - pip - pytest diff --git a/jupyterlite_xeus/add_on.py b/jupyterlite_xeus/add_on.py index 06439f5..941d2fa 100644 --- a/jupyterlite_xeus/add_on.py +++ b/jupyterlite_xeus/add_on.py @@ -20,7 +20,11 @@ ) from traitlets import List, Unicode -from .create_conda_env import create_conda_env_from_yaml, create_conda_env_from_specs +from .create_conda_env import ( + create_conda_env_from_env_file, + create_conda_env_from_specs, +) +from .constants import EXTENSION_NAME from .constants import EXTENSION_NAME from empack.pack import ( @@ -110,15 +114,18 @@ def post_build(self, manager): def create_prefix(self): # read the environment file root_prefix = Path(self.cwd.name) / "env" - env_name = "xeus-env" + env_file = Path(self.environment_file) + + # open the env yaml file + with open(env_file, "r") as file: + yaml_content = yaml.safe_load(file) + + env_name = yaml_content.get("name", "xeus-env") env_prefix = root_prefix / "envs" / env_name self.prefix = str(env_prefix) - env_file = Path(self.environment_file) if env_file.exists(): - create_conda_env_from_yaml( - env_name=env_name, root_prefix=root_prefix, env_file=env_file - ) + create_conda_env_from_env_file(root_prefix, yaml_content, env_file.parent) # this is atm for debugging else: create_conda_env_from_specs( diff --git a/jupyterlite_xeus/create_conda_env.py b/jupyterlite_xeus/create_conda_env.py index e7637ea..3e0844e 100644 --- a/jupyterlite_xeus/create_conda_env.py +++ b/jupyterlite_xeus/create_conda_env.py @@ -3,7 +3,6 @@ from pathlib import Path from subprocess import run as subprocess_run import os -import yaml from ._pip import _install_pip_dependencies @@ -33,24 +32,25 @@ def _extract_specs(env_location, env_data): # If it's a local Python package, make its path relative to the environment file if (env_location / pip_dependency).is_dir(): pip_dependencies.append( - env_location.parent / pip_dependency - ).resolve() + (env_location / pip_dependency).resolve() + ) else: pip_dependencies.append(pip_dependency) return specs, pip_dependencies -def create_conda_env_from_yaml(env_name, root_prefix, env_file): - # open the env yaml file - with open(env_file, "r") as file: - yaml_content = yaml.safe_load(file) +def create_conda_env_from_env_file(root_prefix, env_file_content, env_file_location): + # get the name of the environment + env_name = env_file_content.get("name", "xeus-env") # get the channels - channels = yaml_content.get("channels", []) + channels = env_file_content.get( + "channels", ["https://repo.mamba.pm/emscripten-forge", "conda-forge"] + ) # get the specs - specs, pip_dependencies = _extract_specs(env_file.parent, yaml_content) + specs, pip_dependencies = _extract_specs(env_file_location, env_file_content) create_conda_env_from_specs( env_name=env_name, diff --git a/tests/environment-1.yml b/tests/environment-1.yml new file mode 100644 index 0000000..76be4ae --- /dev/null +++ b/tests/environment-1.yml @@ -0,0 +1,16 @@ +name: xeus-python-kernel-1 +channels: + - https://repo.mamba.pm/emscripten-forge + - conda-forge +dependencies: + - xeus-python + - xeus-lua + - numpy + - matplotlib + - pillow + - ipywidgets + - pip: + # Installing a python package that ships a lab extension under share/ + - ipycanvas + # Installing a pure python package + - py2vega diff --git a/tests/environment-2.yml b/tests/environment-2.yml new file mode 100644 index 0000000..b2c8593 --- /dev/null +++ b/tests/environment-2.yml @@ -0,0 +1,9 @@ +name: xeus-python-kernel-2 +channels: + - https://repo.mamba.pm/emscripten-forge + - conda-forge +dependencies: + - xeus-python + - pip: + # Installing NumPy with pip should fail + - numpy diff --git a/tests/test_package/environment-3.yml b/tests/test_package/environment-3.yml new file mode 100644 index 0000000..ef8904f --- /dev/null +++ b/tests/test_package/environment-3.yml @@ -0,0 +1,9 @@ +name: xeus-python-kernel-3 +channels: + - https://repo.mamba.pm/emscripten-forge + - conda-forge +dependencies: + - xeus-python + - numpy + - pip: + - . diff --git a/tests/test_package/pyproject.toml b/tests/test_package/pyproject.toml new file mode 100644 index 0000000..f9a8f17 --- /dev/null +++ b/tests/test_package/pyproject.toml @@ -0,0 +1,3 @@ +[project] +name = "test-package" +version = "0.1.0" diff --git a/tests/test_package/test_package/__init__.py b/tests/test_package/test_package/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_package/test_package/hey.py b/tests/test_package/test_package/hey.py new file mode 100644 index 0000000..71910a6 --- /dev/null +++ b/tests/test_package/test_package/hey.py @@ -0,0 +1 @@ +print("Hey") diff --git a/tests/test_xeus.py b/tests/test_xeus.py new file mode 100644 index 0000000..70cdd48 --- /dev/null +++ b/tests/test_xeus.py @@ -0,0 +1,79 @@ +"""Test creating Python envs for jupyterlite-xeus-python.""" + +import os +from tempfile import TemporaryDirectory +from pathlib import Path + +import pytest + +from jupyterlite_core.app import LiteStatusApp + +from jupyterlite_xeus.add_on import XeusAddon + + +def test_python_env_from_file_1(): + app = LiteStatusApp(log_level="DEBUG") + app.initialize() + manager = app.lite_manager + + addon = XeusAddon(manager) + addon.environment_file = "environment-1.yml" + + for step in addon.post_build(manager): + pass + + # Check env + assert os.path.isdir(addon.prefix) + + assert os.path.isfile(Path(addon.prefix) / "bin/xpython.js") + assert os.path.isfile(Path(addon.prefix) / "bin/xpython.wasm") + + assert os.path.isfile(Path(addon.prefix) / "bin/xlua.js") + assert os.path.isfile(Path(addon.prefix) / "bin/xlua.wasm") + + # Checking pip packages + assert os.path.isdir(Path(addon.prefix) / "lib/python3.11") + assert os.path.isdir(Path(addon.prefix) / "lib/python3.11/site-packages") + assert os.path.isdir(Path(addon.prefix) / "lib/python3.11/site-packages/ipywidgets") + assert os.path.isdir(Path(addon.prefix) / "lib/python3.11/site-packages/ipycanvas") + assert os.path.isdir(Path(addon.prefix) / "lib/python3.11/site-packages/py2vega") + + # Checking labextensions + assert os.path.isdir( + Path(addon.prefix) + / "share/jupyter/labextensions/@jupyter-widgets/jupyterlab-manager" + ) + assert os.path.isdir(Path(addon.prefix) / "share/jupyter/labextensions/ipycanvas") + + +def test_python_env_from_file_3(): + app = LiteStatusApp(log_level="DEBUG") + app.initialize() + manager = app.lite_manager + + addon = XeusAddon(manager) + addon.environment_file = "test_package/environment-3.yml" + + for step in addon.post_build(manager): + pass + + # Test + assert os.path.isdir( + Path(addon.prefix) / "lib/python3.11/site-packages/test_package" + ) + assert os.path.isfile( + Path(addon.prefix) / "lib/python3.11/site-packages/test_package/hey.py" + ) + + +def test_python_env_from_file_2(): + app = LiteStatusApp(log_level="DEBUG") + app.initialize() + manager = app.lite_manager + + addon = XeusAddon(manager) + addon.environment_file = "environment-2.yml" + + with pytest.raises(RuntimeError, match="Cannot install binary PyPI package"): + for step in addon.post_build(manager): + pass