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

feat: create new environment using a remote mamba solver #141

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 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
23 changes: 23 additions & 0 deletions mamba_gator/envmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,29 @@ async def create_env(self, env: str, *args) -> Dict[str, str]:
return {"error": output}
return output

async def create_explicit_env(self, name: str, explicit_list: str) -> Dict[str, str]:
mariobuikhuizen marked this conversation as resolved.
Show resolved Hide resolved
"""Create a environment from an explicit spec.
Args:
name (str): Name of the environment
explicit_list (str): the explicit list of URLs
Returns:
Dict[str, str]: Create command output
"""
with tempfile.NamedTemporaryFile(mode="w") as f:
f.write(explicit_list)
f.flush()

ans = await self._execute(
self.manager, "create", "-y", "-q", "--json", "-n", name, "--file", f.name,
)

rcode, output = ans
if rcode > 0:
return {"error": output}
return output

async def delete_env(self, env: str) -> Dict[str, str]:
"""Delete an environment.
Expand Down
30 changes: 30 additions & 0 deletions mamba_gator/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from .log import get_logger
from jupyter_server.base.handlers import APIHandler
from jupyter_server.utils import url_path_join
from conda.base.context import context
mariobuikhuizen marked this conversation as resolved.
Show resolved Hide resolved

NS = r"conda"
# Filename for the available conda packages list cache in temp folder
Expand Down Expand Up @@ -214,6 +215,26 @@ def post(self):
self.redirect_to_task(idx)


class ExplicitListHandler(EnvBaseHandler):
@tornado.web.authenticated
def post(self):
mariobuikhuizen marked this conversation as resolved.
Show resolved Hide resolved
"""`POST /explicit` creates an environment from an explicit spec.
Request json body:
{
name (str): environment name
explicitList (str): the explicit list of URLs
}
"""
data = self.get_json_body()
name = data["name"]
explicit_list = data["explicitList"]

idx = self._stack.put(self.env_manager.create_explicit_env, name, explicit_list)

self.redirect_to_task(idx)


class EnvironmentHandler(EnvBaseHandler):
"""Environment handler."""

Expand Down Expand Up @@ -475,6 +496,13 @@ def delete(self, index: int):
self.finish()


class SubdirHandler(EnvBaseHandler):
@tornado.web.authenticated
async def get(self):
"""`GET /subdir` Get the conda-subdir.
"""
self.finish(tornado.escape.json_encode({'subdir': context.subdir}))

# -----------------------------------------------------------------------------
# URL to handler mappings
# -----------------------------------------------------------------------------
Expand All @@ -488,6 +516,8 @@ def delete(self, index: int):
(r"/channels", ChannelsHandler),
(r"/environments", EnvironmentsHandler), # GET / POST
(r"/environments/%s" % _env_regex, EnvironmentHandler), # GET / PATCH / DELETE
(r"/explicit", ExplicitListHandler), # POST
(r"/subdir", SubdirHandler), # GET
# PATCH / POST / DELETE
(r"/environments/%s/packages" % _env_regex, PackagesEnvironmentHandler),
(r"/packages", PackagesHandler), # GET
Expand Down
65 changes: 65 additions & 0 deletions mamba_gator/navigator/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
ExtensionHandlerMixin,
)
from jupyter_server.utils import url_path_join as ujoin
from jupyter_core.application import base_aliases
from jupyterlab_server import LabServerApp
from traitlets import Unicode, Dict, Bool
from mamba_gator._version import __version__
from mamba_gator.handlers import _load_jupyter_server_extension
from mamba_gator.log import get_logger
Expand All @@ -21,12 +23,20 @@ class MambaNavigatorHandler(
ExtensionHandlerJinjaMixin, ExtensionHandlerMixin, JupyterHandler
):
def get(self):
cls = self.__class__

config_data = {
"appVersion": __version__,
"baseUrl": self.base_url,
"token": self.settings["token"],
"fullStaticUrl": ujoin(self.base_url, "static", self.name),
"frontendUrl": ujoin(self.base_url, "gator/"),
"quetzUrl": cls.quetz_url,
"quetzSolverUrl": cls.quetz_solver_url,
"companions": cls.companions,
"fromHistory": cls.from_history,
"types": cls.types,
"whitelist": cls.white_list
}
return self.write(
self.render_template(
Expand Down Expand Up @@ -56,7 +66,62 @@ class MambaNavigator(LabServerApp):
user_settings_dir = os.path.join(HERE, "user_settings")
workspaces_dir = os.path.join(HERE, "workspaces")

quetz_url = Unicode(
'http://localhost:8000',
mariobuikhuizen marked this conversation as resolved.
Show resolved Hide resolved
config=True,
help="The Quetz server to use for creating new environments"
)

quetz_solver_url = Unicode(
'',
config=True,
help="The Quetz server to use for solving, if this is a different server than 'quetzUrl'",
)

companions = Dict(
{},
config=True,
help="{'package name': 'semver specification'} - pre and post releases not supported",
)

from_history = Bool(
False,
config=True,
help="Use --from-history or not for `conda env export`",
)

types = Dict(
{
"Python 3": ["python=3", "ipykernel"],
"R": ["r-base", "r-essentials"]
},
config=True,
help="Type of environment available when creating it from scratch.",
)

white_list = Bool(
False,
config=True,
help="Show only environment corresponding to whitelisted kernels",
)

aliases = dict(base_aliases)
aliases.update({
'quetz_url': 'MambaNavigator.quetz_url',
'quetz_solver_url': 'MambaNavigator.quetz_solver_url',
'companions': 'MambaNavigator.companions',
'from_history': 'MambaNavigator.from_history',
'types': 'MambaNavigator.types',
'white_list': 'MambaNavigator.white_list'
})

def initialize_handlers(self):
MambaNavigatorHandler.quetz_url = self.quetz_url
MambaNavigatorHandler.quetz_solver_url = self.quetz_solver_url
MambaNavigatorHandler.companions = self.companions
MambaNavigatorHandler.from_history = self.from_history
MambaNavigatorHandler.types = self.types
MambaNavigatorHandler.white_list = self.white_list
mariobuikhuizen marked this conversation as resolved.
Show resolved Hide resolved
self.handlers.append(("/gator", MambaNavigatorHandler))
super().initialize_handlers()

Expand Down
3 changes: 3 additions & 0 deletions packages/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
"@lumino/coreutils": "^1.5.3",
"@lumino/signaling": "^1.4.3",
"@lumino/widgets": "^1.16.1",
"codemirror": "^5.60.0",
"codemirror-show-hint": "^5.58.3",
"jupyterlab_toastify": "^4.1.3",
"d3": "^5.5.0",
"react-d3-graph": "^2.5.0",
Expand All @@ -60,6 +62,7 @@
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@jupyterlab/testutils": "^3.0.0",
"@types/codemirror": "^0.0.108",
"@types/jest": "^26.0.0",
"@types/react": "^17.0.0",
"@types/react-d3-graph": "^2.3.4",
Expand Down
5 changes: 5 additions & 0 deletions packages/common/src/components/CondaEnvList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ export interface IEnvListProps {
* Environment remove handler
*/
onRemove(): void;
/**
* Environment solve handler
*/
onSolve(): void;
}

/**
Expand Down Expand Up @@ -92,6 +96,7 @@ export const CondaEnvList: React.FunctionComponent<IEnvListProps> = (
onExport={props.onExport}
onRefresh={props.onRefresh}
onRemove={props.onRemove}
onSolve={props.onSolve}
/>
<div
id={CONDA_ENVIRONMENT_PANEL_ID}
Expand Down
70 changes: 70 additions & 0 deletions packages/common/src/components/CondaEnvSolve.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import * as React from 'react';
import CodeMirror from 'codemirror';
import 'codemirror/lib/codemirror.css';
import './yaml';
import * as condaHint from './CondaHint';

/**
* Conda solve properties
*/
export interface ICondaEnvSolveProps {
quetzUrl: string;
quetzSolverUrl: string;
subdir: string;
create(name: string, explicitList: string): void;
}

export const CondaEnvSolve = (props: ICondaEnvSolveProps): JSX.Element => {
mariobuikhuizen marked this conversation as resolved.
Show resolved Hide resolved
condaHint.register(props.quetzUrl);

const codemirrorElem = React.useRef();

const [editor, setEditor] = React.useState(null);
const [solveState, setSolveState] = React.useState(null);

async function solve() {
const environment_yml = editor.getValue();
setSolveState('Solving...');
const name = condaHint.getName(environment_yml);
try {
const solveResult = await condaHint.fetchSolve(
props.quetzUrl,
props.quetzSolverUrl,
props.subdir,
environment_yml
);
setSolveState(`Creating environment ${name}...`);
await props.create(name, solveResult);
setSolveState('Ok');
} catch (e) {
setSolveState(`Error: ${e}`);
}
}

React.useEffect(() => {
if (editor) {
return;
}
setEditor(
CodeMirror(codemirrorElem.current, {
lineNumbers: true,
extraKeys: {
'Ctrl-Space': 'autocomplete',
'Ctrl-Tab': 'autocomplete'
},
tabSize: 2,
mode: 'yaml',
autofocus: true
})
);
});
return (
<div style={{ width: '80vw', maxWidth: '900px' }}>
<div ref={codemirrorElem}></div>
<div style={{ paddingTop: '8px' }}>
<button onClick={solve}>Create</button>
<span style={{ marginLeft: '16px' }}>{solveState}</span>
</div>
</div>
);
};
10 changes: 10 additions & 0 deletions packages/common/src/components/CondaEnvToolBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { ToolbarButtonComponent } from '@jupyterlab/apputils';
import {
addIcon,
buildIcon,
Button,
closeIcon,
downloadIcon,
Expand Down Expand Up @@ -52,6 +53,10 @@ export interface ICondaEnvToolBarProps {
* Remove environment handler
*/
onRemove(): void;
/**
* Solve environment handler
*/
onSolve(): void;
}

export const CondaEnvToolBar = (props: ICondaEnvToolBarProps): JSX.Element => {
Expand Down Expand Up @@ -80,6 +85,11 @@ export const CondaEnvToolBar = (props: ICondaEnvToolBarProps): JSX.Element => {
tooltip="Create"
onClick={props.onCreate}
/>
<ToolbarButtonComponent
icon={buildIcon}
tooltip="Solve new"
onClick={props.onSolve}
/>
mariobuikhuizen marked this conversation as resolved.
Show resolved Hide resolved
<Button
className="jp-ToolbarButtonComponent"
disabled={props.isBase}
Expand Down
Loading