Skip to content

Commit 530a16c

Browse files
feat: create new environment using a remote mamba solver
The code completion in the environment editor can be activated with CTRL-space.
1 parent b7d87e1 commit 530a16c

16 files changed

+666
-3
lines changed

mamba_gator/envmanager.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,29 @@ async def create_env(self, env: str, *args) -> Dict[str, str]:
336336
return {"error": output}
337337
return output
338338

339+
async def create_explicit_env(self, name: str, explicit_list: str) -> Dict[str, str]:
340+
"""Create a environment from an explicit spec.
341+
342+
Args:
343+
name (str): Name of the environment
344+
explicit_list (str): the explicit list of URLs
345+
346+
Returns:
347+
Dict[str, str]: Create command output
348+
"""
349+
with tempfile.NamedTemporaryFile(mode="w") as f:
350+
f.write(explicit_list)
351+
f.flush()
352+
353+
ans = await self._execute(
354+
self.manager, "create", "-y", "-q", "--json", "-n", name, "--file", f.name,
355+
)
356+
357+
rcode, output = ans
358+
if rcode > 0:
359+
return {"error": output}
360+
return output
361+
339362
async def delete_env(self, env: str) -> Dict[str, str]:
340363
"""Delete an environment.
341364

mamba_gator/handlers.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from .log import get_logger
2424
from jupyter_server.base.handlers import APIHandler
2525
from jupyter_server.utils import url_path_join
26+
from conda.base.context import context
2627

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

216217

218+
class ExplicitListHandler(EnvBaseHandler):
219+
@tornado.web.authenticated
220+
def post(self):
221+
"""`POST /explicit` creates an environment from an explicit spec.
222+
223+
Request json body:
224+
{
225+
name (str): environment name
226+
explicitList (str): the explicit list of URLs
227+
}
228+
"""
229+
data = self.get_json_body()
230+
name = data["name"]
231+
explicit_list = data["explicitList"]
232+
233+
idx = self._stack.put(self.env_manager.create_explicit_env, name, explicit_list)
234+
235+
self.redirect_to_task(idx)
236+
237+
217238
class EnvironmentHandler(EnvBaseHandler):
218239
"""Environment handler."""
219240

@@ -475,6 +496,13 @@ def delete(self, index: int):
475496
self.finish()
476497

477498

499+
class SubdirHandler(EnvBaseHandler):
500+
@tornado.web.authenticated
501+
async def get(self):
502+
"""`GET /subdir` Get the conda-subdir.
503+
"""
504+
self.finish(tornado.escape.json_encode({'subdir': context.subdir}))
505+
478506
# -----------------------------------------------------------------------------
479507
# URL to handler mappings
480508
# -----------------------------------------------------------------------------
@@ -488,6 +516,8 @@ def delete(self, index: int):
488516
(r"/channels", ChannelsHandler),
489517
(r"/environments", EnvironmentsHandler), # GET / POST
490518
(r"/environments/%s" % _env_regex, EnvironmentHandler), # GET / PATCH / DELETE
519+
(r"/explicit", ExplicitListHandler), # POST
520+
(r"/subdir", SubdirHandler), # GET
491521
# PATCH / POST / DELETE
492522
(r"/environments/%s/packages" % _env_regex, PackagesEnvironmentHandler),
493523
(r"/packages", PackagesHandler), # GET

packages/common/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
"@lumino/coreutils": "^1.5.3",
4949
"@lumino/signaling": "^1.4.3",
5050
"@lumino/widgets": "^1.16.1",
51+
"codemirror": "^5.60.0",
52+
"codemirror-show-hint": "^5.58.3",
5153
"jupyterlab_toastify": "^4.1.3",
5254
"d3": "^5.5.0",
5355
"react-d3-graph": "^2.5.0",
@@ -60,6 +62,7 @@
6062
"@babel/core": "^7.0.0",
6163
"@babel/preset-env": "^7.0.0",
6264
"@jupyterlab/testutils": "^3.0.0",
65+
"@types/codemirror": "^0.0.108",
6366
"@types/jest": "^26.0.0",
6467
"@types/react": "^17.0.0",
6568
"@types/react-d3-graph": "^2.3.4",

packages/common/src/components/CondaEnvList.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ export interface IEnvListProps {
5555
* Environment remove handler
5656
*/
5757
onRemove(): void;
58+
/**
59+
* Environment solve handler
60+
*/
61+
onSolve(): void;
5862
}
5963

6064
/**
@@ -92,6 +96,7 @@ export const CondaEnvList: React.FunctionComponent<IEnvListProps> = (
9296
onExport={props.onExport}
9397
onRefresh={props.onRefresh}
9498
onRemove={props.onRemove}
99+
onSolve={props.onSolve}
95100
/>
96101
<div
97102
id={CONDA_ENVIRONMENT_PANEL_ID}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import * as React from 'react';
2+
import CodeMirror from 'codemirror';
3+
import 'codemirror/lib/codemirror.css';
4+
import './yaml';
5+
import * as condaHint from './CondaHint';
6+
7+
/**
8+
* Conda solve properties
9+
*/
10+
export interface ICondaEnvSolveProps {
11+
subdir: string;
12+
create(name: string, explicitList: string): void;
13+
}
14+
15+
export const CondaEnvSolve = (props: ICondaEnvSolveProps): JSX.Element => {
16+
const codemirrorElem = React.useRef();
17+
18+
const [editor, setEditor] = React.useState(null);
19+
const [solveState, setSolveState] = React.useState(null);
20+
21+
async function solve() {
22+
const environment_yml = editor.getValue();
23+
setSolveState('Solving...');
24+
const name = condaHint.getName(environment_yml);
25+
try {
26+
const solveResult = await condaHint.fetchSolve(
27+
props.subdir,
28+
environment_yml
29+
);
30+
setSolveState(`Creating environment ${name}...`);
31+
await props.create(name, solveResult);
32+
setSolveState('Ok');
33+
} catch (e) {
34+
setSolveState(`Error: ${e}`);
35+
}
36+
}
37+
38+
React.useEffect(() => {
39+
if (editor) {
40+
return;
41+
}
42+
setEditor(
43+
CodeMirror(codemirrorElem.current, {
44+
lineNumbers: true,
45+
extraKeys: {
46+
'Ctrl-Space': 'autocomplete',
47+
'Ctrl-Tab': 'autocomplete'
48+
},
49+
tabSize: 2,
50+
mode: 'yaml',
51+
autofocus: true
52+
})
53+
);
54+
});
55+
return (
56+
<div style={{ width: '80vw', maxWidth: '900px' }}>
57+
<div ref={codemirrorElem}></div>
58+
<div style={{ paddingTop: '8px' }}>
59+
<button onClick={solve}>Create</button>
60+
<span style={{ marginLeft: '16px' }}>{solveState}</span>
61+
</div>
62+
</div>
63+
);
64+
};

packages/common/src/components/CondaEnvToolBar.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
44
import { ToolbarButtonComponent } from '@jupyterlab/apputils';
55
import {
66
addIcon,
7+
buildIcon,
78
Button,
89
closeIcon,
910
downloadIcon,
@@ -52,6 +53,10 @@ export interface ICondaEnvToolBarProps {
5253
* Remove environment handler
5354
*/
5455
onRemove(): void;
56+
/**
57+
* Solve environment handler
58+
*/
59+
onSolve(): void;
5560
}
5661

5762
export const CondaEnvToolBar = (props: ICondaEnvToolBarProps): JSX.Element => {
@@ -80,6 +85,11 @@ export const CondaEnvToolBar = (props: ICondaEnvToolBarProps): JSX.Element => {
8085
tooltip="Create"
8186
onClick={props.onCreate}
8287
/>
88+
<ToolbarButtonComponent
89+
icon={buildIcon}
90+
tooltip="Solve new"
91+
onClick={props.onSolve}
92+
/>
8393
<Button
8494
className="jp-ToolbarButtonComponent"
8595
disabled={props.isBase}

0 commit comments

Comments
 (0)