linux|jupyter 二次开发


这里写自定义目录标题

    • 说明
    • 需求和思路
        • 需求
        • 思路
    • jupyter开发环境搭建
        • 下载
        • 开发环境配置
            • 安装依赖
            • 解决启动错误
          • 配置文件
          • 项目目录
        • 代码修改
          • 如何区分用户
          • 如何记住用户
            • all.py
            • session.py
            • 在登录时记录用户
          • 不同用户不同目录
          • 优化体验
            • login.py
            • notebook/templates/page.html

说明 jupyter 基于tornado web框架。
github地址:https://github.com/1060905996/notebook-master
需求和思路 需求 在系统上集成jupyter供用户使用生成编码。会有多个用户使用jupyter,为避免不同用户误删除其他用户的文件,要求每个用户只能对自己的文件进行增删该查。
思路 为每个用户在配置路劲下创建目录,每个用户只能对自己目录下文件和数据进行增删该查。
jupyter开发环境搭建 项目基于notebook version(7, 0, 0, ‘.dev0’),notebook版本可以在notebook/_version.py下查看。最好在linux下开发,我在windows下无法debug(可能是环境没配置好)。
下载 下载地址: https://github.com/jupyter/notebook , 历史版本:https://github.com/jupyter/notebook/releases。
开发环境配置 参考文档:https://github.com/jupyter/notebook/blob/master/CONTRIBUTING.rst
在pycharm中引入项目,并配置python环境。
安装依赖
cd notebookpip install .npm run build

解决启动错误 将__main__.py 移动到和notebook同等级目录即可解决。
项目启动文件为notebook文件夹下__main__.py,直接在__main__.py中点击[run]启动会报错,需要修改部分文件夹名称。
需要需改的文件夹如下:
notebook/notebook
notebook/nbconvert
对应文件名可以修改为任意名称,并在notebookapp.py中修改对应加载类路径,重新启动main.py。文件夹修改如下:
linux|jupyter 二次开发
文章图片

配置文件 为了方便管理,将配置文件jupyter_notebook_config.py放在notebook目录下,并修改配置,
# 允许所有ip访问 c.NotebookApp.allow_origin = '*' c.NotebookApp.ip='*' # 设置验证参数token c.NotebookApp.token = 'mytoken' # 解决跨域问题 c.NotebookApp.tornado_settings = { 'headers': { 'Content-Security-Policy': "frame-ancestors self *", } } # jupyter工作目录 c.NotebookApp.notebook_dir ='/opt/workspace/jupyter' # 启动时不在浏览器打开 c.NotebookApp.open_browser = False # 端口 c.NotebookApp.port = 8001

项目目录

││├─ │││├─ ││││├─ ├─ notebooke │├─ auth ││├─ __mian__.py--jupyter notebook password ││├─ login.py-- 登录handler │││├─ get.method │││├─ pst.method ││├─ logout.py-- 登出handler ││├─ security.py-- 设置和校验密码 │├─ base ││├─ handlers.py-- 所有handler基础类类 │││├─AuthenticatedHandler.class--用户校验 │││├─IPythonHandler.class--系统设置 │││├─APIHandler.class--服务接口 │││├─Template404.class--模板不存在 │││├─AuthenticatedFileHandler.class--静态文件 ││├─ │├─services │││├─contents ││││├─ fileio.py-- 文件管理基础类 ││││├─ filemanage.py-- 文件管理业务实现 ││││├─ handlers.py-- 文件管理接口部分 │││││├─ get.method--查询 │││││├─ put.method--保存 │││││├─ post.method--新增文件 │││││├─ delete.method--删除 │││││├─ patch.method--移动或重命名 │├─ session--使框架支持session │││├─ all.py │││├─ session │├─ static--js,css │├─ template--模板文件 │├─ __main__.py--启动文件 │├─ _version.py--版本 │├─ notebookapp.py--初始化项目

代码修改 如何区分用户 jupyter可以使用token和password登录,但两者都无法对不同用户进行区分。所有在登录中加入user_name参数,用于区分用户。请求示例:http://192.168.5.138:8001/login?token=mytoken&user_name=kevin
如何记住用户 可以使用cookie和session记录登录的用户,个人选择使用session。tornado默认使不支持session的,为此我们需要新增session模块。转自博客:https://www.jianshu.com/p/a14f24700a6b。
all.py
# -*- coding:utf-8 -*-# 存储所有的用户信息 ALL_USER_DIC = {}

session.py
# -*- coding:utf-8 -*- from .all import ALL_USER_DICclass Session: def __init__(self, handler): self.handler = handler self.random_index_str = Nonedef __get_random_str(self): import hashlib, time # 生成md5对象 md = hashlib.md5()# 加入自定义参数来更新md5对象 md.update(bytes(str(time.time()) + ' | own-secret', encoding='utf-8'))# 得到加盐后的十六进制随机字符串来作为用户的索引 return md.hexdigest()def __setitem__(self, key, value): # 当前session对象中没有对应的索引的时候 if not self.random_index_str: # 根据处理器对象获得浏览器传来的cookie的值 random_index_str = self.handler.get_secure_cookie("__sson__", None)# 浏览器传来的cookie的值为空的时候, 表示该用户是第一次访问本网站 if not random_index_str: # 为当前的新用户在当前的session对象中生成索引 self.random_index_str = self.__get_random_str() # 为当前新用户设置cookie self.handler.set_secure_cookie('__sson__', self.random_index_str) # 为当前用户生成保存其相关内容的字典对象 ALL_USER_DIC[self.random_index_str] = {}# 当浏览器传来的cookie不为空的时候 else: # 浏览器传来的cookie非法的时候 if self.random_index_str not in ALL_USER_DIC.keys(): # 为当前非法用户生产索引 self.random_index_str = self.__get_random_str() # 仅仅为当前非法用户生成其保存相关内容的字典对象, 避免合法老用户的字典对象被清空 ALL_USER_DIC[self.random_index_str] = {}# 不管当前session对象有没有对应的索引都应该为他设置起相关的信息保存(当然了, 到这一步的时候经过if条件语句的过滤, 剩下来的就是刚刚创建字典对象的新用户或者非法用户, 以及其他合法的老用户了) ALL_USER_DIC[self.random_index_str][key] = value# 将为以上的新用户或者非法用户设置cookie的操作放在这里本无可厚非. 但是将老用户的cookie也重新设置一遍, 其实是为老用户更新过期时间而做的 self.handler.set_secure_cookie('__sson__', self.random_index_str)def __getitem__(self, key): # 获取当前用户cookie中保存的索引值, 注意加密方式返回的cookie的值是bytes类型的 self.random_index_str = self.handler.get_secure_cookie('__sson__', None) # 若索引值为空表示当前用户是新用户, 则直接返回空, 程序到此终止 if not self.random_index_str: return None# 索引不为空的时候 else: self.random_index_str = str(self.random_index_str, encoding="utf-8") # 在服务器端为保存该索引值表示当前用户是非法用户,则直接返回空 current_user = ALL_USER_DIC.get(self.random_index_str, None) if not current_user: return None else: # 直接返回合法用户指定的key的值, 没有则默认返回空 return current_user.get(key, None)

在登录时记录用户
def get(self): user_name = self.get_argument("user_name") self.session["user"] = user_name

不同用户不同目录 方案1:
在调试代码中发现,在获取路径时都会通过fileio.py中_get_os_path获取根目录,然后根据传入path参数拼接路径。可以在方法中获取user_name,根据path,user_name和配置文件拼接参数。缺点: 1. 无法在_get_os_path中获取当前用户 2. 可以在_get_os_path新增user_name参数,但这样需要修改的代码过于多,而且修改后不止是否有其他问题。 方案1被否决。 本来都要放弃jupyter去研究polynote了,但突然灵光一闪想到方案2。

【linux|jupyter 二次开发】方案2:
如果改变用户根目录比较困难,可以限制用户只能对自己目录下文件操作,这样也一样满足需求。根据这个思路调试代码最终找到services/contents/handlers.py,文件中重要方法如下: get查询文件 put保存文件 post 新增文件 delete删除文件 patch 移动或重命名文件 可以看出tornado是遵守RESTful APi接口规范的。这些方法都有相同的参数[path],path为请求参数,值为操作的目录或文件。_get_os_path(path)返回值为文件在机器上真实路径。 为让每个用户只能操作自己目录,只需要在这些方法中增加校验。判断条件:如当前用户为admin,那么在path参数必须以admin开头,不满足条件即无法修改文件。示例如下: ``` @gen.coroutine def patch(self, path=''): """PATCH renames a file or directory without re-uploading content.""" print("PATCH path =" + path) if not path.startswith(self.user_root_dir): raise HTTPError(500, "您没有修改 %s 的权限" % path) ```在方法中分别添加校验,现阶段满足需求要求,并且未发现问题。

优化体验
  1. 用户在每次登录时看到用户列表,而不是进入自己的文件目录
  2. 如果用户首次登录,不会针对用户创建新目录
login.py
def get(self): user_name = self.get_argument("user_name") self.session["user"] = user_name if not self.contents_manager.dir_exists(user_name): # 判断用户路径是否存在,如果不存在创建路径 user_path = self.contents_manager._get_os_path(user_name) os.makedirs(user_path) if self.current_user: # 设置登录后默认调转路由 next_url = self.get_argument('next', default=self.default_url + "/"+user_name) self._redirect_safe(next_url) else: self._render()

  1. 嵌入系统后,jupyter图标占用较多高度,而且logout等按钮需要隐藏。
notebook/templates/page.html

    推荐阅读