jupyter 二次开发

3 篇文章 0 订阅
2 篇文章 0 订阅

说明

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 notebook

pip install .

npm run build
解决启动错误

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

配置文件

为了方便管理,将配置文件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_DIC


class Session:
    def __init__(self, handler):
        self.handler = handler
        self.random_index_str = None

    def __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。

方案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
<div id="header" role="navigation" aria-label="{% trans %}Top Menu{% endtrans %}">
  <div id="header-container" class="container">
<!--  <div id="ipython_notebook" class="nav navbar-brand"><a href="{{default_url}}
    {%- if logged_in and token -%}?token={{token}}{%- endif -%}" title='{% trans %}dashboard{% endtrans %}'>
      {% block logo %}<img src='{{static_url("base/images/logo.png") }}' alt='Jupyter Notebook'/>{% endblock %}
  </a></div>-->

<!--  {% block headercontainer %}
  {% endblock %}-->

  {% block header_buttons %}

<!--  {% block login_widget %}

    <span id="login_widget">
      {% if logged_in %}
        <button id="logout" class="btn btn-sm navbar-btn">{% trans %}Logout{% endtrans %}</button>
      {% elif login_available and not logged_in %}
        <button id="login" class="btn btn-sm navbar-btn">{% trans %}Login{% endtrans %}</button>
      {% endif %}
    </span>

  {% endblock %}-->

  {% endblock header_buttons %}

  </div>
<!--  <div class="header-bar"></div>-->

  {% block header %}
  {% endblock %}
</div>

在这里插入图片描述

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值