Skip to content

Commit 3bb8676

Browse files
committed
Add option export from history (#39)
* Add option export from history * Fix unit tests * Add swagger documentation * Fix export history * Export from history as settings and in public API * Fix unit tests
1 parent 413a598 commit 3bb8676

File tree

9 files changed

+439
-40
lines changed

9 files changed

+439
-40
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
[![npm](https://img.shields.io/npm/v/jupyterlab_conda.svg?style=flat-square)](https://www.npmjs.com/package/jupyterlab_conda)
66
[![Build Status](https://travis-ci.com/fcollonval/jupyter_conda.svg?branch=master)](https://travis-ci.com/fcollonval/jupyter_conda)
77
[![Coverage Status](https://coveralls.io/repos/github/fcollonval/jupyter_conda/badge.svg?branch=master)](https://coveralls.io/github/fcollonval/jupyter_conda?branch=master)
8+
[![Swagger Validator](https://img.shields.io/swagger/valid/3.0?specUrl=https%3A%2F%2Fraw.githubusercontent.com%2Ffcollonval%2Fjupyter_conda%2Fmaster%2Fjupyter_conda%2Frest_api.yml)](https://petstore.swagger.io/?url=https://raw.githubusercontent.com/fcollonval/jupyter_conda/master/jupyter_conda/rest_api.yml)
89

910
Provides Conda environment and package access extension from within Jupyter Notebook and JupyterLab.
1011

jupyter_conda/envmanager.py

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ def normalize_pkg_info(s: Dict[str, Any]) -> Dict[str, Union[str, List[str]]]:
5555
class EnvManager(LoggingConfigurable):
5656
"""Handles environment and package actions."""
5757

58+
_conda_version: Optional[str] = None
59+
5860
def _clean_conda_json(self, output: str) -> Dict[str, Any]:
5961
"""Clean a command output to fit json format.
6062
@@ -232,16 +234,29 @@ async def delete_env(self, env: str) -> Dict[str, str]:
232234
return {"error": output}
233235
return output
234236

235-
async def export_env(self, env: str) -> Union[str, Dict[str, str]]:
237+
async def export_env(
238+
self, env: str, from_history: bool = False
239+
) -> Union[str, Dict[str, str]]:
236240
"""Export an environment as YAML file.
237241
238242
Args:
239243
env (str): Environment name
244+
from_history (bool): If True, use `--from-history` option; default False
240245
241246
Returns:
242247
str: YAML file content
243248
"""
244-
ans = await self._execute(CONDA_EXE, "env", "export", "-n", env)
249+
command = [CONDA_EXE, "env", "export", "-n", env]
250+
if from_history:
251+
if EnvManager._conda_version is None:
252+
await self.info() # Set conda version
253+
if EnvManager._conda_version < (4, 7, 12):
254+
self.log.warning(
255+
"[jupyter_conda] conda<4.7.12 does not support `env export --from-history`. It will be ignored."
256+
)
257+
else:
258+
command.append("--from-history")
259+
ans = await self._execute(*command)
245260
rcode, output = ans
246261
if rcode > 0:
247262
return {"error": output}
@@ -275,6 +290,24 @@ async def import_env(
275290
return {"error": output}
276291
return output
277292

293+
async def info(self) -> Dict[str, Any]:
294+
"""Returns `conda info --json` execution.
295+
296+
Returns:
297+
The dictionary of conda information
298+
"""
299+
ans = await self._execute(CONDA_EXE, "info", "--json")
300+
rcode, output = ans
301+
info = self._clean_conda_json(output)
302+
if rcode == 0:
303+
EnvManager._conda_version = tuple(
304+
map(
305+
lambda part: int(part),
306+
info.get("conda_version", EnvManager._conda_version).split("."),
307+
)
308+
)
309+
return info
310+
278311
async def list_envs(
279312
self, whitelist: bool = False
280313
) -> Dict[str, List[Dict[str, Union[str, bool]]]]:
@@ -294,10 +327,8 @@ async def list_envs(
294327
Returns:
295328
{"environments": List[env]}: The environments
296329
"""
297-
ans = await self._execute(CONDA_EXE, "info", "--json")
298-
rcode, output = ans
299-
info = self._clean_conda_json(output)
300-
if rcode > 0:
330+
info = await self.info()
331+
if "error" in info:
301332
return info
302333

303334
default_env = info["default_prefix"]

jupyter_conda/handlers.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,16 +233,18 @@ async def get(self, env: str):
233233
Query arguments:
234234
status: "installed" (default) or "has_update"
235235
download: 0 (default) or 1
236+
history: 0 (default) or 1
236237
"""
237238
status = self.get_query_argument("status", "installed")
238239
download = self.get_query_argument("download", 0)
240+
history = self.get_query_argument("history", 0)
239241

240242
if download:
241243
# export requirements file
242244
self.set_header(
243245
"Content-Disposition", 'attachment; filename="%s"' % (env + ".yml")
244246
)
245-
answer = await self.env_manager.export_env(env)
247+
answer = await self.env_manager.export_env(env, bool(history))
246248
if "error" in answer:
247249
self.set_status(500)
248250
self.finish(tornado.escape.json_encode(answer))
@@ -485,7 +487,7 @@ def delete(self, index: int):
485487
# PATCH / POST / DELETE
486488
(r"/environments/%s/packages" % _env_regex, PackagesEnvironmentHandler),
487489
(r"/packages", PackagesHandler), # GET
488-
(r"/tasks/%s" % r"(?P<index>\d+)", TaskHandler), # GET
490+
(r"/tasks/%s" % r"(?P<index>\d+)", TaskHandler), # GET / DELETE
489491
]
490492

491493

jupyter_conda/rest_api.yml

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
swagger: "2.0"
2+
info:
3+
description: "This is the REST API introduce by the Jupyter server extension `jupyter_conda`; see [GitHub repository](https://github.com/fcollonval/jupyter_conda) for more information."
4+
version: "3.3.0"
5+
title: "jupyter_conda API"
6+
license:
7+
name: "BSD-3-Clause"
8+
url: "https://opensource.org/licenses/BSD-3-Clause"
9+
basePath: "/conda"
10+
tags:
11+
- name: "channel"
12+
description: "Conda channel"
13+
- name: "environment"
14+
description: "Conda environment actions"
15+
- name: "package"
16+
description: "Conda package actions"
17+
- name: "task"
18+
description: "Long running task actions"
19+
schemes:
20+
- "https"
21+
paths:
22+
/channels:
23+
get:
24+
tags:
25+
- "channel"
26+
summary: "List conda channels"
27+
responses:
28+
"200":
29+
description: "Conda channels"
30+
"500":
31+
description: "Fail to list conda channels"
32+
/environments:
33+
get:
34+
tags:
35+
- "environment"
36+
summary: "List conda environments"
37+
produces:
38+
- "application/json"
39+
parameters:
40+
- name: "whitelist"
41+
in: "query"
42+
description: "Whether to respect KernelSpecManager.whitelist"
43+
type: "integer"
44+
default: 0
45+
responses:
46+
"200":
47+
description: "Conda environments"
48+
"500":
49+
description: "Fail to list environments"
50+
post:
51+
tags:
52+
- "environment"
53+
summary: "Add a new conda environment"
54+
consumes:
55+
- "application/json"
56+
parameters:
57+
- in: "body"
58+
name: "body"
59+
description: "Environment option"
60+
required: true
61+
schema:
62+
$ref: "#/definitions/EnvironmentPost"
63+
responses:
64+
"202":
65+
description: "Redirect on tasks"
66+
/environments/{environmentName}:
67+
get:
68+
tags:
69+
- "environment"
70+
summary: "List the environment content"
71+
produces:
72+
- "application/json"
73+
- "attachment"
74+
parameters:
75+
- name: "environmentName"
76+
in: "path"
77+
description: "Environment name to return"
78+
required: true
79+
type: "string"
80+
pattern: /([^/&+$?@<>%*-][^/&+$?@<>%*]*)/
81+
- name: "status"
82+
in: "query"
83+
description: "installed or has_update"
84+
type: "string"
85+
default: "installed"
86+
enum: ["installed", "has_update"]
87+
- name: "download"
88+
in: "query"
89+
description: "Whether to download the packages list"
90+
type: "integer"
91+
default: 0
92+
- name: "history"
93+
in: "query"
94+
description: "Whether to export only from history"
95+
type: "integer"
96+
default: 0
97+
responses:
98+
"200":
99+
description: "Package list"
100+
schema:
101+
type: "object"
102+
properties:
103+
packages:
104+
type: "array"
105+
items:
106+
$ref: "#/definitions/Package"
107+
108+
"202":
109+
description: "Redirect long running task"
110+
"500":
111+
description: "Error listing the packages"
112+
patch:
113+
tags:
114+
- "environment"
115+
summary: "Updates the packages environment"
116+
consumes:
117+
- "application/json"
118+
parameters:
119+
- name: "environmentName"
120+
in: "path"
121+
description: "Environment name to update"
122+
required: true
123+
type: "string"
124+
pattern: /([^/&+$?@<>%*-][^/&+$?@<>%*]*)/
125+
responses:
126+
"202":
127+
description: "Long running task"
128+
delete:
129+
tags:
130+
- "environment"
131+
summary: "Deletes an environment"
132+
description: ""
133+
parameters:
134+
- name: "environmentName"
135+
in: "path"
136+
description: "Environment name to remove"
137+
required: true
138+
type: "string"
139+
pattern: /([^/&+$?@<>%*-][^/&+$?@<>%*]*)/
140+
responses:
141+
"202":
142+
description: "Redirect long running task"
143+
/environments/{environmentName}/packages:
144+
patch:
145+
tags:
146+
- "package"
147+
summary: "Update environment packages"
148+
consumes:
149+
- "application/json"
150+
parameters:
151+
- name: "environmentName"
152+
in: "path"
153+
description: "Environment name to modify"
154+
required: true
155+
type: "string"
156+
pattern: /([^/&+$?@<>%*-][^/&+$?@<>%*]*)/
157+
responses:
158+
"202":
159+
description: "Redirect long running task"
160+
post:
161+
tags:
162+
- "package"
163+
summary: "Install environment packages"
164+
consumes:
165+
- "application/json"
166+
parameters:
167+
- name: "environmentName"
168+
in: "path"
169+
description: "Environment name to modify"
170+
required: true
171+
type: "string"
172+
pattern: /([^/&+$?@<>%*-][^/&+$?@<>%*]*)/
173+
- name: "develop"
174+
in: "query"
175+
description: "Whether to install the package in development mode"
176+
type: "integer"
177+
default: 0
178+
responses:
179+
"202":
180+
description: "Redirect long running task"
181+
delete:
182+
tags:
183+
- "package"
184+
consumes:
185+
- "application/json"
186+
parameters:
187+
- name: "environmentName"
188+
in: "path"
189+
description: "Environment name to modify"
190+
required: true
191+
type: "string"
192+
pattern: /([^/&+$?@<>%*-][^/&+$?@<>%*]*)/
193+
responses:
194+
"202":
195+
description: "Redirect long running task"
196+
/packages:
197+
get:
198+
tags:
199+
- "package"
200+
summary: "Search for packages"
201+
produces:
202+
- "application/json"
203+
parameters:
204+
- name: "query"
205+
in: "query"
206+
description: "Query string to pass to conda search"
207+
type: "string"
208+
default: ""
209+
responses:
210+
"200":
211+
description: "Query result"
212+
"202":
213+
description: "Redirect long running task"
214+
/tasks/{taskId}:
215+
get:
216+
tags:
217+
- "task"
218+
summary: "Get long running task result"
219+
produces:
220+
- "application/json"
221+
parameters:
222+
- name: "taskId"
223+
in: "path"
224+
description: "Task ID"
225+
required: true
226+
type: "integer"
227+
responses:
228+
"200":
229+
description: "Successful execution of the task - returns its result"
230+
"202":
231+
description: "Task still running"
232+
"404":
233+
description: "Task not found"
234+
"500":
235+
description: "An error occurred when executing the task"
236+
delete:
237+
tags:
238+
- "task"
239+
summary: "Stop the long running task"
240+
parameters:
241+
- name: "taskId"
242+
in: "path"
243+
description: "ID of the order that needs to be deleted"
244+
required: true
245+
type: "integer"
246+
responses:
247+
"204":
248+
description: "Task cancelled"
249+
"404":
250+
description: "Task not found"
251+
definitions:
252+
EnvironmentPost:
253+
type: "object"
254+
Package:
255+
type: "object"
256+
externalDocs:
257+
description: "Find out more about jupyter_conda"
258+
url: "https://github.com/fcollonval/jupyter_conda"

0 commit comments

Comments
 (0)