From 97747e6bfe6c42fbd9c18449de7e4e3332a5eb97 Mon Sep 17 00:00:00 2001 From: Anastasiia Sliusar Date: Tue, 9 Jul 2024 14:33:38 +0200 Subject: [PATCH 01/18] Add support of custom env variables --- jupyter_server/serverapp.py | 21 +++++++++++++++ jupyter_server/services/sessions/handlers.py | 9 +++++++ .../services/sessions/sessionmanager.py | 26 +++++++++++++++++-- 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/jupyter_server/serverapp.py b/jupyter_server/serverapp.py index ed0738976a..4862bbe12f 100644 --- a/jupyter_server/serverapp.py +++ b/jupyter_server/serverapp.py @@ -401,6 +401,8 @@ def init_settings( # collapse $HOME to ~ root_dir = "~" + root_dir[len(home) :] + self.allow_custom_env_variables = jupyter_app.allow_custom_env_variables + settings = { # basics "log_function": log_request, @@ -460,6 +462,7 @@ def init_settings( "server_root_dir": root_dir, "jinja2_env": env, "serverapp": jupyter_app, + "page_config_hook": (self.page_config_hook), } # allow custom overrides for the tornado web app. @@ -469,6 +472,10 @@ def init_settings( # default: set xsrf cookie on base_url settings["xsrf_cookie_kwargs"] = {"path": base_url} return settings + + def page_config_hook(self, handler, page_config): + page_config["allow_custom_env_variables"] = self.allow_custom_env_variables + return page_config def init_handlers(self, default_services, settings): """Load the (URL pattern, handler) tuples for each component.""" @@ -1426,6 +1433,13 @@ def _default_allow_remote(self) -> bool: """, ) + allow_custom_env_variables = Bool( + False, + config=True, + help="""Allow to use insecure kernelspec parameters""", + ) + + browser = Unicode( "", config=True, @@ -3025,6 +3039,13 @@ def start_app(self) -> None: # Handle the browser opening. if self.open_browser and not self.sock: self.launch_browser() + + if self.allow_custom_env_variables: + print("allow_custom_env_variables present") + print(self.allow_custom_env_variables) + self.kernel_spec_manager.allow_custom_env_variables( + self.allow_custom_env_variables + ) if self.identity_provider.token and self.identity_provider.token_generated: # log full URL with generated token, so there's a copy/pasteable link diff --git a/jupyter_server/services/sessions/handlers.py b/jupyter_server/services/sessions/handlers.py index 3e013c0335..484a856202 100644 --- a/jupyter_server/services/sessions/handlers.py +++ b/jupyter_server/services/sessions/handlers.py @@ -77,6 +77,7 @@ async def post(self): kernel = model.get("kernel", {}) kernel_name = kernel.get("name", None) kernel_id = kernel.get("id", None) + custom_env = kernel.get("custom_env", {}) if not kernel_id and not kernel_name: self.log.debug("No kernel specified, using default kernel") @@ -93,6 +94,7 @@ async def post(self): kernel_id=kernel_id, name=name, type=mtype, + custom_env=custom_env, ) except NoSuchKernel: msg = ( @@ -152,6 +154,8 @@ async def patch(self, session_id): changes["name"] = model["name"] if "type" in model: changes["type"] = model["type"] + if "custom_env" in model: + changes["custom_env"] = model["custom_env"] if "kernel" in model: # Kernel id takes precedence over name. if model["kernel"].get("id") is not None: @@ -160,6 +164,10 @@ async def patch(self, session_id): raise web.HTTPError(400, "No such kernel: %s" % kernel_id) changes["kernel_id"] = kernel_id elif model["kernel"].get("name") is not None: + if "custom_env" in model["kernel"]: + custom_env = model["kernel"]["custom_env"] + else: + custom_env = None kernel_name = model["kernel"]["name"] kernel_id = await sm.start_kernel_for_session( session_id, @@ -167,6 +175,7 @@ async def patch(self, session_id): name=before["name"], path=before["path"], type=before["type"], + custom_env=custom_env, ) changes["kernel_id"] = kernel_id diff --git a/jupyter_server/services/sessions/sessionmanager.py b/jupyter_server/services/sessions/sessionmanager.py index 8b392b4e1b..a1929f120f 100644 --- a/jupyter_server/services/sessions/sessionmanager.py +++ b/jupyter_server/services/sessions/sessionmanager.py @@ -209,6 +209,7 @@ def __init__(self, *args, **kwargs): _cursor = None _connection = None _columns = {"session_id", "path", "name", "type", "kernel_id"} + _custom_envs = None @property def cursor(self): @@ -267,6 +268,7 @@ async def create_session( type: Optional[str] = None, kernel_name: Optional[KernelName] = None, kernel_id: Optional[str] = None, + custom_env: Optional[Dict[str, Any]] = None, ) -> Dict[str, Any]: """Creates a session and returns its model @@ -288,7 +290,7 @@ async def create_session( record.kernel_id = kernel_id self._pending_sessions.update(record) result = await self.save_session( - session_id, path=path, name=name, type=type, kernel_id=kernel_id + session_id, path, name, type, kernel_name, custom_env ) self._pending_sessions.remove(record) return cast(Dict[str, Any], result) @@ -319,6 +321,7 @@ async def start_kernel_for_session( name: Optional[ModelName], type: Optional[str], kernel_name: Optional[KernelName], + custom_env: Optional[Dict[str, Any]] = None, ) -> str: """Start a new kernel for a given session. @@ -335,16 +338,25 @@ async def start_kernel_for_session( the type of the session kernel_name : str the name of the kernel specification to use. The default kernel name will be used if not provided. + custom_env: dict + dictionary of custom env variables """ # allow contents manager to specify kernels cwd kernel_path = await ensure_async(self.contents_manager.get_kernel_path(path=path)) + # if we have custom env than we have to add them to available env variables + if custom_env is not None and isinstance(custom_env, dict): + for key, value in custom_env.items(): + kernel_env[key] = value + kernel_env = self.get_kernel_env(path, name) kernel_id = await self.kernel_manager.start_kernel( path=kernel_path, kernel_name=kernel_name, env=kernel_env, + custom_env=custom_env, ) + self._custom_envs[kernel_id] = custom_env return cast(str, kernel_id) async def save_session(self, session_id, path=None, name=None, type=None, kernel_id=None): @@ -464,7 +476,17 @@ async def update_session(self, session_id, **kwargs): "SELECT path, name, kernel_id FROM session WHERE session_id=?", [session_id] ) path, name, kernel_id = self.cursor.fetchone() - self.kernel_manager.update_env(kernel_id=kernel_id, env=self.get_kernel_env(path, name)) + + env = self.get_kernel_env(path, name) + + # if we have custom env than we have to add them to available env variables + if self._custom_envs is not None and isinstance(self._custom_envs, dict): + if self._custom_envs[kernel_id] is not None and isinstance(self._custom_envs[kernel_id], dict): + for key, value in self._custom_envs[kernel_id].items(): + env[key] = value + + + self.kernel_manager.update_env(kernel_id=kernel_id, env=env) async def kernel_culled(self, kernel_id: str) -> bool: """Checks if the kernel is still considered alive and returns true if its not found.""" From 887f5226ed12530e62cf4de369b0e448558f9212 Mon Sep 17 00:00:00 2001 From: Anastasiia Sliusar Date: Tue, 9 Jul 2024 14:36:48 +0200 Subject: [PATCH 02/18] Fix description of a flag --- jupyter_server/serverapp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jupyter_server/serverapp.py b/jupyter_server/serverapp.py index 4862bbe12f..ac788afcbd 100644 --- a/jupyter_server/serverapp.py +++ b/jupyter_server/serverapp.py @@ -1436,7 +1436,7 @@ def _default_allow_remote(self) -> bool: allow_custom_env_variables = Bool( False, config=True, - help="""Allow to use insecure kernelspec parameters""", + help="""Allow to use custom env variables""", ) From 63e634e955ed2159d29629a84e9f46549693b573 Mon Sep 17 00:00:00 2001 From: Anastasiia Sliusar Date: Tue, 16 Jul 2024 14:55:08 +0200 Subject: [PATCH 03/18] fix custom env vars --- jupyter_server/serverapp.py | 6 ++-- .../services/kernels/kernelmanager.py | 2 ++ jupyter_server/services/sessions/handlers.py | 17 ++++----- .../services/sessions/sessionmanager.py | 35 ++++++++++++------- 4 files changed, 37 insertions(+), 23 deletions(-) diff --git a/jupyter_server/serverapp.py b/jupyter_server/serverapp.py index ac788afcbd..71b4e6691c 100644 --- a/jupyter_server/serverapp.py +++ b/jupyter_server/serverapp.py @@ -3043,9 +3043,9 @@ def start_app(self) -> None: if self.allow_custom_env_variables: print("allow_custom_env_variables present") print(self.allow_custom_env_variables) - self.kernel_spec_manager.allow_custom_env_variables( - self.allow_custom_env_variables - ) + #self.kernel_spec_manager.allow_custom_env_variables( + # self.allow_custom_env_variables + #) if self.identity_provider.token and self.identity_provider.token_generated: # log full URL with generated token, so there's a copy/pasteable link diff --git a/jupyter_server/services/kernels/kernelmanager.py b/jupyter_server/services/kernels/kernelmanager.py index 5b0d09aab2..c1c9b5b93b 100644 --- a/jupyter_server/services/kernels/kernelmanager.py +++ b/jupyter_server/services/kernels/kernelmanager.py @@ -224,6 +224,8 @@ async def _async_start_kernel( # type:ignore[override] The name identifying which kernel spec to launch. This is ignored if an existing kernel is returned, but it may be checked in the future. """ + + print('??????????????????????????????????/') if kernel_id is None or kernel_id not in self: if path is not None: kwargs["cwd"] = self.cwd_for_path(path, env=kwargs.get("env", {})) diff --git a/jupyter_server/services/sessions/handlers.py b/jupyter_server/services/sessions/handlers.py index 484a856202..079b5271f1 100644 --- a/jupyter_server/services/sessions/handlers.py +++ b/jupyter_server/services/sessions/handlers.py @@ -77,7 +77,7 @@ async def post(self): kernel = model.get("kernel", {}) kernel_name = kernel.get("name", None) kernel_id = kernel.get("id", None) - custom_env = kernel.get("custom_env", {}) + custom_env_vars = kernel.get("custom_env_vars", {}) if not kernel_id and not kernel_name: self.log.debug("No kernel specified, using default kernel") @@ -94,7 +94,7 @@ async def post(self): kernel_id=kernel_id, name=name, type=mtype, - custom_env=custom_env, + custom_env_vars=custom_env_vars, ) except NoSuchKernel: msg = ( @@ -154,8 +154,8 @@ async def patch(self, session_id): changes["name"] = model["name"] if "type" in model: changes["type"] = model["type"] - if "custom_env" in model: - changes["custom_env"] = model["custom_env"] + if "custom_env_vars" in model: + changes["custom_env_vars"] = model["custom_env_vars"] if "kernel" in model: # Kernel id takes precedence over name. if model["kernel"].get("id") is not None: @@ -164,10 +164,11 @@ async def patch(self, session_id): raise web.HTTPError(400, "No such kernel: %s" % kernel_id) changes["kernel_id"] = kernel_id elif model["kernel"].get("name") is not None: - if "custom_env" in model["kernel"]: - custom_env = model["kernel"]["custom_env"] + if "custom_env_vars" in model["kernel"]: + custom_env_vars = model["kernel"]["custom_env_vars"] else: - custom_env = None + custom_env_vars = {} + kernel_name = model["kernel"]["name"] kernel_id = await sm.start_kernel_for_session( session_id, @@ -175,7 +176,7 @@ async def patch(self, session_id): name=before["name"], path=before["path"], type=before["type"], - custom_env=custom_env, + custom_env_vars=custom_env_vars, ) changes["kernel_id"] = kernel_id diff --git a/jupyter_server/services/sessions/sessionmanager.py b/jupyter_server/services/sessions/sessionmanager.py index a1929f120f..61e884a8f5 100644 --- a/jupyter_server/services/sessions/sessionmanager.py +++ b/jupyter_server/services/sessions/sessionmanager.py @@ -209,7 +209,7 @@ def __init__(self, *args, **kwargs): _cursor = None _connection = None _columns = {"session_id", "path", "name", "type", "kernel_id"} - _custom_envs = None + _custom_envs = {} @property def cursor(self): @@ -268,7 +268,7 @@ async def create_session( type: Optional[str] = None, kernel_name: Optional[KernelName] = None, kernel_id: Optional[str] = None, - custom_env: Optional[Dict[str, Any]] = None, + custom_env_vars: Optional[Dict[str, Any]] = None, ) -> Dict[str, Any]: """Creates a session and returns its model @@ -285,12 +285,12 @@ async def create_session( pass else: kernel_id = await self.start_kernel_for_session( - session_id, path, name, type, kernel_name + session_id, path, name, type, kernel_name, custom_env_vars ) record.kernel_id = kernel_id self._pending_sessions.update(record) result = await self.save_session( - session_id, path, name, type, kernel_name, custom_env + session_id, path, name, type, kernel_name ) self._pending_sessions.remove(record) return cast(Dict[str, Any], result) @@ -321,7 +321,7 @@ async def start_kernel_for_session( name: Optional[ModelName], type: Optional[str], kernel_name: Optional[KernelName], - custom_env: Optional[Dict[str, Any]] = None, + custom_env_vars: Optional[Dict[str, Any]] = None, ) -> str: """Start a new kernel for a given session. @@ -338,25 +338,29 @@ async def start_kernel_for_session( the type of the session kernel_name : str the name of the kernel specification to use. The default kernel name will be used if not provided. - custom_env: dict + custom_env_vars: dict dictionary of custom env variables """ # allow contents manager to specify kernels cwd kernel_path = await ensure_async(self.contents_manager.get_kernel_path(path=path)) - + kernel_env = self.get_kernel_env(path, name) + print('kernel_env before changing') + print(kernel_env) # if we have custom env than we have to add them to available env variables - if custom_env is not None and isinstance(custom_env, dict): - for key, value in custom_env.items(): + if custom_env_vars is not None and isinstance(custom_env_vars, dict): + for key, value in custom_env_vars.items(): kernel_env[key] = value + print('kernel_env after changing') + print(kernel_env) - kernel_env = self.get_kernel_env(path, name) kernel_id = await self.kernel_manager.start_kernel( path=kernel_path, kernel_name=kernel_name, env=kernel_env, - custom_env=custom_env, ) - self._custom_envs[kernel_id] = custom_env + print('----kernel_id----') + print(kernel_id) + self._custom_envs[kernel_id] = custom_env_vars return cast(str, kernel_id) async def save_session(self, session_id, path=None, name=None, type=None, kernel_id=None): @@ -388,6 +392,8 @@ async def save_session(self, session_id, path=None, name=None, type=None, kernel "INSERT INTO session VALUES (?,?,?,?,?)", (session_id, path, name, type, kernel_id), ) + print('session_id') + print(session_id) result = await self.get_session(session_id=session_id) return result @@ -457,6 +463,7 @@ async def update_session(self, session_id, **kwargs): and the value replaces the current value in the session with session_id. """ + print('update-session') await self.get_session(session_id=session_id) if not kwargs: @@ -495,6 +502,10 @@ async def kernel_culled(self, kernel_id: str) -> bool: async def row_to_model(self, row, tolerate_culled=False): """Takes sqlite database session row and turns it into a dictionary""" kernel_culled: bool = await ensure_async(self.kernel_culled(row["kernel_id"])) + print('---row----') + print(row["kernel_id"]) + print('-kernel_culled---') + print(kernel_culled) if kernel_culled: # The kernel was culled or died without deleting the session. # We can't use delete_session here because that tries to find From 9355a31b76988eaafa99e90febb31847cddac3d4 Mon Sep 17 00:00:00 2001 From: Anastasiia Sliusar Date: Wed, 31 Jul 2024 14:26:16 +0200 Subject: [PATCH 04/18] Cleaned code --- jupyter_server/serverapp.py | 7 ------ .../services/kernels/kernelmanager.py | 1 - .../services/sessions/sessionmanager.py | 23 +++++++------------ 3 files changed, 8 insertions(+), 23 deletions(-) diff --git a/jupyter_server/serverapp.py b/jupyter_server/serverapp.py index 71b4e6691c..2f612a70c9 100644 --- a/jupyter_server/serverapp.py +++ b/jupyter_server/serverapp.py @@ -3039,13 +3039,6 @@ def start_app(self) -> None: # Handle the browser opening. if self.open_browser and not self.sock: self.launch_browser() - - if self.allow_custom_env_variables: - print("allow_custom_env_variables present") - print(self.allow_custom_env_variables) - #self.kernel_spec_manager.allow_custom_env_variables( - # self.allow_custom_env_variables - #) if self.identity_provider.token and self.identity_provider.token_generated: # log full URL with generated token, so there's a copy/pasteable link diff --git a/jupyter_server/services/kernels/kernelmanager.py b/jupyter_server/services/kernels/kernelmanager.py index c1c9b5b93b..22c723e31c 100644 --- a/jupyter_server/services/kernels/kernelmanager.py +++ b/jupyter_server/services/kernels/kernelmanager.py @@ -225,7 +225,6 @@ async def _async_start_kernel( # type:ignore[override] an existing kernel is returned, but it may be checked in the future. """ - print('??????????????????????????????????/') if kernel_id is None or kernel_id not in self: if path is not None: kwargs["cwd"] = self.cwd_for_path(path, env=kwargs.get("env", {})) diff --git a/jupyter_server/services/sessions/sessionmanager.py b/jupyter_server/services/sessions/sessionmanager.py index 61e884a8f5..2ee7896e2a 100644 --- a/jupyter_server/services/sessions/sessionmanager.py +++ b/jupyter_server/services/sessions/sessionmanager.py @@ -290,7 +290,7 @@ async def create_session( record.kernel_id = kernel_id self._pending_sessions.update(record) result = await self.save_session( - session_id, path, name, type, kernel_name + session_id, path=path, name=name, type=type, kernel_id=kernel_id ) self._pending_sessions.remove(record) return cast(Dict[str, Any], result) @@ -344,22 +344,18 @@ async def start_kernel_for_session( # allow contents manager to specify kernels cwd kernel_path = await ensure_async(self.contents_manager.get_kernel_path(path=path)) kernel_env = self.get_kernel_env(path, name) - print('kernel_env before changing') - print(kernel_env) + # if we have custom env than we have to add them to available env variables if custom_env_vars is not None and isinstance(custom_env_vars, dict): for key, value in custom_env_vars.items(): kernel_env[key] = value - print('kernel_env after changing') - print(kernel_env) kernel_id = await self.kernel_manager.start_kernel( path=kernel_path, kernel_name=kernel_name, env=kernel_env, ) - print('----kernel_id----') - print(kernel_id) + self._custom_envs[kernel_id] = custom_env_vars return cast(str, kernel_id) @@ -392,8 +388,6 @@ async def save_session(self, session_id, path=None, name=None, type=None, kernel "INSERT INTO session VALUES (?,?,?,?,?)", (session_id, path, name, type, kernel_id), ) - print('session_id') - print(session_id) result = await self.get_session(session_id=session_id) return result @@ -463,7 +457,7 @@ async def update_session(self, session_id, **kwargs): and the value replaces the current value in the session with session_id. """ - print('update-session') + await self.get_session(session_id=session_id) if not kwargs: @@ -502,10 +496,6 @@ async def kernel_culled(self, kernel_id: str) -> bool: async def row_to_model(self, row, tolerate_culled=False): """Takes sqlite database session row and turns it into a dictionary""" kernel_culled: bool = await ensure_async(self.kernel_culled(row["kernel_id"])) - print('---row----') - print(row["kernel_id"]) - print('-kernel_culled---') - print(kernel_culled) if kernel_culled: # The kernel was culled or died without deleting the session. # We can't use delete_session here because that tries to find @@ -559,6 +549,9 @@ async def delete_session(self, session_id): record = KernelSessionRecord(session_id=session_id) self._pending_sessions.update(record) session = await self.get_session(session_id=session_id) - await ensure_async(self.kernel_manager.shutdown_kernel(session["kernel"]["id"])) + kernel_id = session["kernel"]["id"] + if kernel_id in self._custom_envs: + del self._custom_envs[kernel_id] + await ensure_async(self.kernel_manager.shutdown_kernel(kernel_id)) self.cursor.execute("DELETE FROM session WHERE session_id=?", (session_id,)) self._pending_sessions.remove(record) From 2ee8b19e764c9b33c26051ccaa1d522c38ed6a75 Mon Sep 17 00:00:00 2001 From: Anastasiia Sliusar Date: Wed, 31 Jul 2024 21:03:02 +0200 Subject: [PATCH 05/18] Fix code styling --- jupyter_server/serverapp.py | 3 +-- jupyter_server/services/sessions/handlers.py | 2 +- jupyter_server/services/sessions/sessionmanager.py | 7 ++++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/jupyter_server/serverapp.py b/jupyter_server/serverapp.py index 2f612a70c9..368dfdb740 100644 --- a/jupyter_server/serverapp.py +++ b/jupyter_server/serverapp.py @@ -472,7 +472,7 @@ def init_settings( # default: set xsrf cookie on base_url settings["xsrf_cookie_kwargs"] = {"path": base_url} return settings - + def page_config_hook(self, handler, page_config): page_config["allow_custom_env_variables"] = self.allow_custom_env_variables return page_config @@ -1439,7 +1439,6 @@ def _default_allow_remote(self) -> bool: help="""Allow to use custom env variables""", ) - browser = Unicode( "", config=True, diff --git a/jupyter_server/services/sessions/handlers.py b/jupyter_server/services/sessions/handlers.py index 079b5271f1..3b656fa61e 100644 --- a/jupyter_server/services/sessions/handlers.py +++ b/jupyter_server/services/sessions/handlers.py @@ -168,7 +168,7 @@ async def patch(self, session_id): custom_env_vars = model["kernel"]["custom_env_vars"] else: custom_env_vars = {} - + kernel_name = model["kernel"]["name"] kernel_id = await sm.start_kernel_for_session( session_id, diff --git a/jupyter_server/services/sessions/sessionmanager.py b/jupyter_server/services/sessions/sessionmanager.py index 2ee7896e2a..e1bf15dd29 100644 --- a/jupyter_server/services/sessions/sessionmanager.py +++ b/jupyter_server/services/sessions/sessionmanager.py @@ -290,7 +290,7 @@ async def create_session( record.kernel_id = kernel_id self._pending_sessions.update(record) result = await self.save_session( - session_id, path=path, name=name, type=type, kernel_id=kernel_id + session_id, path=path, name=name, type=type, kernel_id=kernel_id ) self._pending_sessions.remove(record) return cast(Dict[str, Any], result) @@ -482,11 +482,12 @@ async def update_session(self, session_id, **kwargs): # if we have custom env than we have to add them to available env variables if self._custom_envs is not None and isinstance(self._custom_envs, dict): - if self._custom_envs[kernel_id] is not None and isinstance(self._custom_envs[kernel_id], dict): + if self._custom_envs[kernel_id] is not None and isinstance( + self._custom_envs[kernel_id], dict + ): for key, value in self._custom_envs[kernel_id].items(): env[key] = value - self.kernel_manager.update_env(kernel_id=kernel_id, env=env) async def kernel_culled(self, kernel_id: str) -> bool: From ae37186940c1504f07b1fbb4ceeca5b4b7b67f38 Mon Sep 17 00:00:00 2001 From: Anastasiia Sliusar Date: Mon, 5 Aug 2024 13:10:32 +0200 Subject: [PATCH 06/18] Fix type --- jupyter_server/services/sessions/sessionmanager.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/jupyter_server/services/sessions/sessionmanager.py b/jupyter_server/services/sessions/sessionmanager.py index e1bf15dd29..0162b0c465 100644 --- a/jupyter_server/services/sessions/sessionmanager.py +++ b/jupyter_server/services/sessions/sessionmanager.py @@ -205,12 +205,11 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._pending_sessions = KernelSessionRecordList() + _custom_envs: Dict[str, str] = {} # Session database initialized below _cursor = None _connection = None _columns = {"session_id", "path", "name", "type", "kernel_id"} - _custom_envs = {} - @property def cursor(self): """Start a cursor and create a database called 'session'""" @@ -481,7 +480,7 @@ async def update_session(self, session_id, **kwargs): env = self.get_kernel_env(path, name) # if we have custom env than we have to add them to available env variables - if self._custom_envs is not None and isinstance(self._custom_envs, dict): + if isinstance(self._custom_envs, dict): if self._custom_envs[kernel_id] is not None and isinstance( self._custom_envs[kernel_id], dict ): From 93b14be052b592331d41563fc266dd5a112aa92b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 5 Aug 2024 11:10:52 +0000 Subject: [PATCH 07/18] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- jupyter_server/services/sessions/sessionmanager.py | 1 + 1 file changed, 1 insertion(+) diff --git a/jupyter_server/services/sessions/sessionmanager.py b/jupyter_server/services/sessions/sessionmanager.py index 0162b0c465..470f5e7d33 100644 --- a/jupyter_server/services/sessions/sessionmanager.py +++ b/jupyter_server/services/sessions/sessionmanager.py @@ -210,6 +210,7 @@ def __init__(self, *args, **kwargs): _cursor = None _connection = None _columns = {"session_id", "path", "name", "type", "kernel_id"} + @property def cursor(self): """Start a cursor and create a database called 'session'""" From 4b045561cfe84decf4a0f6aa00090e3e1a15a651 Mon Sep 17 00:00:00 2001 From: Anastasiia Sliusar Date: Mon, 5 Aug 2024 15:59:19 +0200 Subject: [PATCH 08/18] Fix types --- jupyter_server/services/sessions/sessionmanager.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/jupyter_server/services/sessions/sessionmanager.py b/jupyter_server/services/sessions/sessionmanager.py index 0162b0c465..1e9870776f 100644 --- a/jupyter_server/services/sessions/sessionmanager.py +++ b/jupyter_server/services/sessions/sessionmanager.py @@ -205,7 +205,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._pending_sessions = KernelSessionRecordList() - _custom_envs: Dict[str, str] = {} + _custom_envs: Dict[str, Optional[Dict[str, Any]]] = {} # Session database initialized below _cursor = None _connection = None @@ -481,10 +481,9 @@ async def update_session(self, session_id, **kwargs): # if we have custom env than we have to add them to available env variables if isinstance(self._custom_envs, dict): - if self._custom_envs[kernel_id] is not None and isinstance( - self._custom_envs[kernel_id], dict - ): - for key, value in self._custom_envs[kernel_id].items(): + custom_env = self._custom_envs.get(kernel_id) + if custom_env is not None and isinstance(custom_env, dict): + for key, value in custom_env.items(): env[key] = value self.kernel_manager.update_env(kernel_id=kernel_id, env=env) From ef07d1523ced5335ffd11498ddc5cb9ea43d0c9a Mon Sep 17 00:00:00 2001 From: Anastasiia Sliusar Date: Mon, 5 Aug 2024 16:00:36 +0200 Subject: [PATCH 09/18] Resolve conflicts --- jupyter_server/services/sessions/sessionmanager.py | 1 + 1 file changed, 1 insertion(+) diff --git a/jupyter_server/services/sessions/sessionmanager.py b/jupyter_server/services/sessions/sessionmanager.py index 1e9870776f..89ee494a72 100644 --- a/jupyter_server/services/sessions/sessionmanager.py +++ b/jupyter_server/services/sessions/sessionmanager.py @@ -210,6 +210,7 @@ def __init__(self, *args, **kwargs): _cursor = None _connection = None _columns = {"session_id", "path", "name", "type", "kernel_id"} + @property def cursor(self): """Start a cursor and create a database called 'session'""" From 0c497341e5259458b05995060f5ac21b89f269fa Mon Sep 17 00:00:00 2001 From: Anastasiia Sliusar Date: Tue, 6 Aug 2024 19:31:11 +0200 Subject: [PATCH 10/18] Remove custom page config --- jupyter_server/serverapp.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/jupyter_server/serverapp.py b/jupyter_server/serverapp.py index 9c69424321..f0bdb72ee1 100644 --- a/jupyter_server/serverapp.py +++ b/jupyter_server/serverapp.py @@ -401,8 +401,6 @@ def init_settings( # collapse $HOME to ~ root_dir = "~" + root_dir[len(home) :] - self.allow_custom_env_variables = jupyter_app.allow_custom_env_variables - settings = { # basics "log_function": log_request, @@ -461,8 +459,7 @@ def init_settings( "allow_password_change": jupyter_app.allow_password_change, "server_root_dir": root_dir, "jinja2_env": env, - "serverapp": jupyter_app, - "page_config_hook": (self.page_config_hook), + "serverapp": jupyter_app } # allow custom overrides for the tornado web app. @@ -473,10 +470,6 @@ def init_settings( settings["xsrf_cookie_kwargs"] = {"path": base_url} return settings - def page_config_hook(self, handler, page_config): - page_config["allow_custom_env_variables"] = self.allow_custom_env_variables - return page_config - def init_handlers(self, default_services, settings): """Load the (URL pattern, handler) tuples for each component.""" # Order matters. The first handler to match the URL will handle the request. @@ -1434,10 +1427,10 @@ def _default_allow_remote(self) -> bool: """, ) - allow_custom_env_variables = Bool( + allow_setup_custom_env_variables = Bool( False, config=True, - help="""Allow to use custom env variables""", + help="""Allow a user to setup custom env variables while launching or selecting a kernel""", ) browser = Unicode( From ba231eb96b8a9a58a8e60f3bee53476fc54c1d0f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 6 Aug 2024 17:31:33 +0000 Subject: [PATCH 11/18] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- jupyter_server/serverapp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jupyter_server/serverapp.py b/jupyter_server/serverapp.py index f0bdb72ee1..01c2b57daf 100644 --- a/jupyter_server/serverapp.py +++ b/jupyter_server/serverapp.py @@ -459,7 +459,7 @@ def init_settings( "allow_password_change": jupyter_app.allow_password_change, "server_root_dir": root_dir, "jinja2_env": env, - "serverapp": jupyter_app + "serverapp": jupyter_app, } # allow custom overrides for the tornado web app. From 56a6b429ff22836998e28ceea4f65a4e4cdadf6d Mon Sep 17 00:00:00 2001 From: Anastasiia Sliusar Date: Fri, 9 Aug 2024 13:18:53 +0200 Subject: [PATCH 12/18] Add a test, clean code --- jupyter_server/services/kernels/kernelmanager.py | 1 - jupyter_server/services/sessions/sessionmanager.py | 3 --- tests/services/sessions/test_manager.py | 10 +++++++++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/jupyter_server/services/kernels/kernelmanager.py b/jupyter_server/services/kernels/kernelmanager.py index a9c0c3cbab..cd8a9de71f 100644 --- a/jupyter_server/services/kernels/kernelmanager.py +++ b/jupyter_server/services/kernels/kernelmanager.py @@ -224,7 +224,6 @@ async def _async_start_kernel( # type:ignore[override] The name identifying which kernel spec to launch. This is ignored if an existing kernel is returned, but it may be checked in the future. """ - if kernel_id is None or kernel_id not in self: if path is not None: kwargs["cwd"] = self.cwd_for_path(path, env=kwargs.get("env", {})) diff --git a/jupyter_server/services/sessions/sessionmanager.py b/jupyter_server/services/sessions/sessionmanager.py index 89ee494a72..f7bf015870 100644 --- a/jupyter_server/services/sessions/sessionmanager.py +++ b/jupyter_server/services/sessions/sessionmanager.py @@ -459,7 +459,6 @@ async def update_session(self, session_id, **kwargs): """ await self.get_session(session_id=session_id) - if not kwargs: # no changes return @@ -477,7 +476,6 @@ async def update_session(self, session_id, **kwargs): "SELECT path, name, kernel_id FROM session WHERE session_id=?", [session_id] ) path, name, kernel_id = self.cursor.fetchone() - env = self.get_kernel_env(path, name) # if we have custom env than we have to add them to available env variables @@ -486,7 +484,6 @@ async def update_session(self, session_id, **kwargs): if custom_env is not None and isinstance(custom_env, dict): for key, value in custom_env.items(): env[key] = value - self.kernel_manager.update_env(kernel_id=kernel_id, env=env) async def kernel_culled(self, kernel_id: str) -> bool: diff --git a/tests/services/sessions/test_manager.py b/tests/services/sessions/test_manager.py index bd092259e0..1ff468ac7f 100644 --- a/tests/services/sessions/test_manager.py +++ b/tests/services/sessions/test_manager.py @@ -1,6 +1,5 @@ import asyncio from datetime import datetime - import pytest from tornado import web from traitlets import TraitError @@ -318,6 +317,15 @@ async def test_update_session(session_manager): } assert model == expected +async def test_update_session_with_custom_env_vars(session_manager): + custom_env_vars= {'test_env_name': 'test_env_value'} + await session_manager.create_session( + path="/path/to/test.ipynb", kernel_name="julia", type="notebook", custom_env_vars=custom_env_vars, + ) + kernel_id = "A" + custom_envs = session_manager._custom_envs[kernel_id] + expected = 'test_env_value' + assert custom_envs['test_env_name'] == expected async def test_bad_update_session(session_manager): # try to update a session with a bad keyword ~ raise error From 6bea16b3c2fee09901e3b4ee58dd391835c3f4b1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 9 Aug 2024 11:19:43 +0000 Subject: [PATCH 13/18] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/services/sessions/test_manager.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/services/sessions/test_manager.py b/tests/services/sessions/test_manager.py index 1ff468ac7f..fc756b0850 100644 --- a/tests/services/sessions/test_manager.py +++ b/tests/services/sessions/test_manager.py @@ -1,5 +1,6 @@ import asyncio from datetime import datetime + import pytest from tornado import web from traitlets import TraitError @@ -317,15 +318,20 @@ async def test_update_session(session_manager): } assert model == expected + async def test_update_session_with_custom_env_vars(session_manager): - custom_env_vars= {'test_env_name': 'test_env_value'} + custom_env_vars = {"test_env_name": "test_env_value"} await session_manager.create_session( - path="/path/to/test.ipynb", kernel_name="julia", type="notebook", custom_env_vars=custom_env_vars, + path="/path/to/test.ipynb", + kernel_name="julia", + type="notebook", + custom_env_vars=custom_env_vars, ) kernel_id = "A" custom_envs = session_manager._custom_envs[kernel_id] - expected = 'test_env_value' - assert custom_envs['test_env_name'] == expected + expected = "test_env_value" + assert custom_envs["test_env_name"] == expected + async def test_bad_update_session(session_manager): # try to update a session with a bad keyword ~ raise error From 90418cae698e3c390743d349d5de6ca96b8f93de Mon Sep 17 00:00:00 2001 From: Anastasiia Sliusar Date: Fri, 9 Aug 2024 13:21:32 +0200 Subject: [PATCH 14/18] Fix formatting --- tests/services/sessions/test_manager.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/services/sessions/test_manager.py b/tests/services/sessions/test_manager.py index 1ff468ac7f..fc756b0850 100644 --- a/tests/services/sessions/test_manager.py +++ b/tests/services/sessions/test_manager.py @@ -1,5 +1,6 @@ import asyncio from datetime import datetime + import pytest from tornado import web from traitlets import TraitError @@ -317,15 +318,20 @@ async def test_update_session(session_manager): } assert model == expected + async def test_update_session_with_custom_env_vars(session_manager): - custom_env_vars= {'test_env_name': 'test_env_value'} + custom_env_vars = {"test_env_name": "test_env_value"} await session_manager.create_session( - path="/path/to/test.ipynb", kernel_name="julia", type="notebook", custom_env_vars=custom_env_vars, + path="/path/to/test.ipynb", + kernel_name="julia", + type="notebook", + custom_env_vars=custom_env_vars, ) kernel_id = "A" custom_envs = session_manager._custom_envs[kernel_id] - expected = 'test_env_value' - assert custom_envs['test_env_name'] == expected + expected = "test_env_value" + assert custom_envs["test_env_name"] == expected + async def test_bad_update_session(session_manager): # try to update a session with a bad keyword ~ raise error From f1c3d5519595f75191f1ff6445e7fe5219730608 Mon Sep 17 00:00:00 2001 From: Anastasiia Sliusar Date: Tue, 20 Aug 2024 12:08:41 +0200 Subject: [PATCH 15/18] Update a variable name --- jupyter_server/services/sessions/handlers.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/jupyter_server/services/sessions/handlers.py b/jupyter_server/services/sessions/handlers.py index 3b656fa61e..75b9e59651 100644 --- a/jupyter_server/services/sessions/handlers.py +++ b/jupyter_server/services/sessions/handlers.py @@ -77,7 +77,7 @@ async def post(self): kernel = model.get("kernel", {}) kernel_name = kernel.get("name", None) kernel_id = kernel.get("id", None) - custom_env_vars = kernel.get("custom_env_vars", {}) + custom_env_vars = kernel.get("env", {}) if not kernel_id and not kernel_name: self.log.debug("No kernel specified, using default kernel") @@ -154,8 +154,8 @@ async def patch(self, session_id): changes["name"] = model["name"] if "type" in model: changes["type"] = model["type"] - if "custom_env_vars" in model: - changes["custom_env_vars"] = model["custom_env_vars"] + if "env" in model: + changes["custom_env_vars"] = model["env"] if "kernel" in model: # Kernel id takes precedence over name. if model["kernel"].get("id") is not None: @@ -164,8 +164,8 @@ async def patch(self, session_id): raise web.HTTPError(400, "No such kernel: %s" % kernel_id) changes["kernel_id"] = kernel_id elif model["kernel"].get("name") is not None: - if "custom_env_vars" in model["kernel"]: - custom_env_vars = model["kernel"]["custom_env_vars"] + if "env" in model["kernel"]: + custom_env_vars = model["kernel"]["env"] else: custom_env_vars = {} From 01db90a28141350efa8925cb7e45260a03ee75f5 Mon Sep 17 00:00:00 2001 From: Anastasiia Sliusar Date: Thu, 22 Aug 2024 16:50:43 +0200 Subject: [PATCH 16/18] Fix lint --- jupyter_server/services/sessions/handlers.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/jupyter_server/services/sessions/handlers.py b/jupyter_server/services/sessions/handlers.py index 75b9e59651..88301072cd 100644 --- a/jupyter_server/services/sessions/handlers.py +++ b/jupyter_server/services/sessions/handlers.py @@ -164,10 +164,7 @@ async def patch(self, session_id): raise web.HTTPError(400, "No such kernel: %s" % kernel_id) changes["kernel_id"] = kernel_id elif model["kernel"].get("name") is not None: - if "env" in model["kernel"]: - custom_env_vars = model["kernel"]["env"] - else: - custom_env_vars = {} + custom_env_vars = model["kernel"]["env"] if "env" in model["kernel"] else {} kernel_name = model["kernel"]["name"] kernel_id = await sm.start_kernel_for_session( From e51f647594c82cbf700f2eceb2b065610edee795 Mon Sep 17 00:00:00 2001 From: Anastasiia Sliusar Date: Wed, 28 Aug 2024 14:29:36 +0200 Subject: [PATCH 17/18] Fix setuping custom env vars --- jupyter_server/serverapp.py | 3 ++- jupyter_server/services/kernels/kernelmanager.py | 1 + jupyter_server/services/sessions/handlers.py | 10 +++++++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/jupyter_server/serverapp.py b/jupyter_server/serverapp.py index 007b371861..2252af6199 100644 --- a/jupyter_server/serverapp.py +++ b/jupyter_server/serverapp.py @@ -457,6 +457,7 @@ def init_settings( "config": jupyter_app.config, "config_dir": jupyter_app.config_dir, "allow_password_change": jupyter_app.allow_password_change, + "accept_kernel_env_var": jupyter_app.accept_kernel_env_var, "server_root_dir": root_dir, "jinja2_env": env, "serverapp": jupyter_app, @@ -1427,7 +1428,7 @@ def _default_allow_remote(self) -> bool: """, ) - allow_setup_custom_env_variables = Bool( + accept_kernel_env_vars = Bool( False, config=True, help="""Allow a user to setup custom env variables while launching or selecting a kernel""", diff --git a/jupyter_server/services/kernels/kernelmanager.py b/jupyter_server/services/kernels/kernelmanager.py index cd8a9de71f..e0df82360e 100644 --- a/jupyter_server/services/kernels/kernelmanager.py +++ b/jupyter_server/services/kernels/kernelmanager.py @@ -224,6 +224,7 @@ async def _async_start_kernel( # type:ignore[override] The name identifying which kernel spec to launch. This is ignored if an existing kernel is returned, but it may be checked in the future. """ + # if kernel_id is None or kernel_id not in self: if path is not None: kwargs["cwd"] = self.cwd_for_path(path, env=kwargs.get("env", {})) diff --git a/jupyter_server/services/sessions/handlers.py b/jupyter_server/services/sessions/handlers.py index 88301072cd..5e0afe258a 100644 --- a/jupyter_server/services/sessions/handlers.py +++ b/jupyter_server/services/sessions/handlers.py @@ -49,6 +49,8 @@ async def post(self): # (unless a session already exists for the named session) sm = self.session_manager + accept_kernel_env_vars = self.settings.get("accept_kernel_env_vars", False) + model = self.get_json_body() if model is None: raise web.HTTPError(400, "No JSON data provided") @@ -77,7 +79,7 @@ async def post(self): kernel = model.get("kernel", {}) kernel_name = kernel.get("name", None) kernel_id = kernel.get("id", None) - custom_env_vars = kernel.get("env", {}) + custom_env_vars = kernel["env"] if "env" in kernel and accept_kernel_env_vars else {} if not kernel_id and not kernel_name: self.log.debug("No kernel specified, using default kernel") @@ -143,6 +145,8 @@ async def patch(self, session_id): # get the previous session model before = await sm.get_session(session_id=session_id) + accept_kernel_env_vars = self.settings.get("accept_kernel_env_vars", False) + changes = {} if "notebook" in model and "path" in model["notebook"]: self.log.warning("Sessions API changed, see updated swagger docs") @@ -154,7 +158,7 @@ async def patch(self, session_id): changes["name"] = model["name"] if "type" in model: changes["type"] = model["type"] - if "env" in model: + if "env" in model and accept_kernel_env_vars: changes["custom_env_vars"] = model["env"] if "kernel" in model: # Kernel id takes precedence over name. @@ -164,7 +168,7 @@ async def patch(self, session_id): raise web.HTTPError(400, "No such kernel: %s" % kernel_id) changes["kernel_id"] = kernel_id elif model["kernel"].get("name") is not None: - custom_env_vars = model["kernel"]["env"] if "env" in model["kernel"] else {} + custom_env_vars = model["kernel"]["env"] if "env" in model["kernel"] and accept_kernel_env_vars else {} kernel_name = model["kernel"]["name"] kernel_id = await sm.start_kernel_for_session( From 4d48ec4e6f1a9b5725fbd34112702e93e3e961de Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 28 Aug 2024 12:30:18 +0000 Subject: [PATCH 18/18] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- jupyter_server/services/sessions/handlers.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/jupyter_server/services/sessions/handlers.py b/jupyter_server/services/sessions/handlers.py index 5e0afe258a..09fb67f9e0 100644 --- a/jupyter_server/services/sessions/handlers.py +++ b/jupyter_server/services/sessions/handlers.py @@ -168,7 +168,11 @@ async def patch(self, session_id): raise web.HTTPError(400, "No such kernel: %s" % kernel_id) changes["kernel_id"] = kernel_id elif model["kernel"].get("name") is not None: - custom_env_vars = model["kernel"]["env"] if "env" in model["kernel"] and accept_kernel_env_vars else {} + custom_env_vars = ( + model["kernel"]["env"] + if "env" in model["kernel"] and accept_kernel_env_vars + else {} + ) kernel_name = model["kernel"]["name"] kernel_id = await sm.start_kernel_for_session(