跳转到主要内容

创建在线网络工具的基础脚手架

项目描述

tools-barebone

Actions Status

tools-barebone 是一个用于使用Flask Jinja2模板在Python中开发部署小型网络应用的框架。

它可以作为一个起点来开发新材料云工具部分的新工具

它提供

  • Materials Cloud中每个工具都使用的通用布局
  • Materials Cloud主题
  • 通用API端点
  • 通用小部件,例如晶体结构的文件上传功能
  • Web服务器设置
  • 将工具部署到docker容器的脚本

先决条件

如何使用tools-barebone框架

tools-barebone 框架提供基本模板,可以扩展以开发新材料云的新工具。

在这里,我们简要介绍如何扩展左图左侧所示的 tools-barebone 模板,以开发一个新的工具,称为 custom-tool(如图右所示)。

工具基础模板 新工具模板

1. 从 DockerHub 获取最新的 tools-barebone Docker 镜像

浏览到 DockerHub 上的 materialscloud/tools-barebone 标签页面,找到最新的标记版本(形式为 X.Y.Z,例如 1.0.0)。您也可以使用 latest 标签,但我们强烈建议您使用固定的版本,而不是最新的版本:这将确保即使发布未来不兼容的版本,您的工具也能继续工作。

然后,运行以下命令获取镜像

docker pull materialscloud/tools-barebone

您可以通过运行以下命令来尝试查看一切是否正常工作,例如:

docker run -p 8090:80 materialscloud/tools-barebone:latest

然后使用您的浏览器连接到 https://:8090

您应该看到与上图左侧相似的网站。

如果您愿意,您还可以使用以下命令安装 tools-barebone 软件包(使用与您上面选择的相同版本):

pip install tools-barebone==X.Y.Z

这并非技术上必需的,但如果您的编辑器具有检查功能并且需要查看该软件包,或者您正在使用代码检查工具,这将是很有用的。

2. 新工具:custom-tool

要编写名为 custom-tool 的新工具,创建一个名为 custom-tool 的新目录。让我们首先创建这个新工具所需的最小文件集

mkdir custom-tool
cd custom-tool

# create configuration file
touch config.yaml

# create Dockerfile file
touch Dockerfile

# create file for python requirements
touch requirements.txt

# create the folder in which you will put the
# python code for the backend
mkdir compute
touch compute/__init__.py

# create user templates folder
mkdir user_templates
cd user_templates
touch ack.html                              # add acknowledgement text here
touch about.html                            # add information about this tool here
touch how_to_cite.html                      # add tool citation here
touch additional_content.html               # additional functionality if any, otherwise empty file

3. 配置设置

配置文件 config.yaml 包含了新工具中使用的配置细节,如窗口标题、页面标题、HTML 模板列表等。更新 config.yaml 文件并添加将在工具介绍部分显示的 HTML 模板、工具引用文本和致谢。

以下是一个在 config.yaml 文件中设置的最常见变量的示例:

window_title: "Materials Cloud Tools: an example app"
page_title: "A simple tool example"

about_section_title: "About this new tool"

# If True, a structure selection block will be shown and it will provide a common set of parsers.
# In this case, you will have to provide an endpoint
# `/compute/process_structure/` to process the results
# as shown later.
use_upload_structure_block: True

# If you have an upload block and want to have some parsers first, you can specify their internal
# name as a list. Those from the list will be shown first (NOTE! if the name is unknown, it is ignored).
# All the remaining ones, if any, are shown afterwards in a default order.
upload_structure_block_order: ['cif-pymatgen', 'xsf-ase']

templates:
  how_to_cite: "how_to_cite.html"
  about: "about.html"
  select_content: "visualizer_select_example.html" # what to show in the selection page (below the upload structure block, if present)
  upload_structure_additional_content: "upload_structure_additional_content.html" # if the upload structure block is present, you can add additional content right above the 'submit' button, if you want (e.g. a disclaimer, terms of use, ...)

# Add here more sections to the accordion shown on the top of the selection page
additional_accordion_entries:
#  - header: "What is this?"
#    template_page: what_is.html
  - header: "Acknowledgements"
    template_page: ack.html

4. 创建 Dockerfile

文件准备好后,我们可以编写一个 Dockerfile,该文件扩展了 tools-barebone 镜像(您之前选择的标签),以构建和运行 custom-tool 的 Docker 容器。

下面的代码片段显示了一个实现此目标的最小 Dockerfile 文件。您可以在 custom-tool/Dockerfile 内创建此类文件。您需要在文件底部添加针对 custom-tool 特定的命令。请记住替换 LABEL 字符串。

FROM materialscloud/tools-barebone:X.Y.Z

LABEL maintainer="Developer Name <developer.email@xxx.yy>"

# Python requirements
COPY ./requirements.txt /home/app/code/requirements.txt
# Run this as sudo to replace the version of pip

RUN pip3 install -U 'pip>=10' setuptools wheel
# install packages as normal user (app, provided by passenger)

USER app
WORKDIR /home/app/code
# Install pinned versions of packages
RUN pip3 install --user -r requirements.txt
# Go back to root.
# Also, it should remain as user root for startup
USER root

# Copy various files: configuration, user templates, the actual python code, ...
COPY ./config.yaml /home/app/code/webservice/static/config.yaml
COPY ./user_templates/ /home/app/code/webservice/templates/user_templates/
COPY ./compute/ /home/app/code/webservice/compute/

# If you put any static file (CSS, JS, images),
#create this folder and put them here
# COPY ./user_static/ /home/app/code/webservice/user_static/

###
# Copy any additional files needed into /home/app/code/webservice/
###

# Set proper permissions on files just copied
RUN chown -R app:app /home/app/code/webservice/

5. 测试它!

现在,您可以构建 Docker 镜像,然后按如下方式启动容器。

首先,进入您的工具顶部文件夹,其中放置了 Dockerfile。然后,运行此命令:

docker build -t custom-tools . && docker run -p 8091:80 --rm --name=custom-tools-instance custom-tools

现在,您可以通过连接到 https://:8091 来检查您的结果是否开始看起来像上图右侧的图像,并且它包含您期望的文本。

6. 精调文本

在查看后端 Python 逻辑之前,现在您可以调整您之前编写的模板。更改 config.yaml 和各种模板。然后,再次运行上一节的 docker build+run 命令,刷新浏览器,直到您对结果满意。

7. 后端实现

现在是时候工作在 Python 后端实现了。

tools-barebone 使用 Flask 框架,您可能需要查看其文档以发现所有高级功能。在这里,我们只描述如何创建一个最小的工作工具。

您将把代码放入您之前创建的 compute 文件夹中。您可以在其中添加任意数量的 Python 文件,并使用以下方式加载它们:

from compute.XXX import YYY

compute 文件夹将位于 Python 路径中)。

但是,您需要在compute/__init__.py文件中包含一些基本内容。

具体来说,您至少需要定义如下所示的blueprint

import flask

blueprint = flask.Blueprint("compute", __name__, url_prefix="/compute")

然后您可以添加您的视图。如果您正在使用结构上传块(请参阅config.yaml部分描述中的注释),您需要至少定义一个/compute/process_structure/端点。以下是一个最小的工作示例,您可以用作起点。请注意,在这里我们将使用tools-barebone包直接提供的解析功能。

from tools_barebone.structure_importers import get_structure_tuple, UnknownFormatError
import io

@blueprint.route("/process_structure/", methods=["POST"])
def process_structure():
    """Example view to process a crystal structure."""

    # check if the post request has the file part, otherwise redirect to first page
    if "structurefile" not in flask.request.files:
        # This will redirect the user to the selection page, that is called `input_data` in tools-barebone
        return flask.redirect(flask.url_for("input_data"))

    # Get structure, file format, file content, and form data (needed for additional information, e.g. cell in the case of a XYZ file)
    structurefile = flask.request.files["structurefile"]
    fileformat = flask.request.form.get("fileformat", "unknown")
    filecontent = structurefile.read().decode("utf-8")
    fileobject = io.StringIO(str(filecontent))
    form_data = dict(flask.request.form)

    # Use
    try:
        structure_tuple = get_structure_tuple(
            fileobject, fileformat, extra_data=form_data
        )
    except UnknownFormatError:
        # You can use the flask.flash functionality to send a message
        # back to the structure selection page; this
        # will be shown in a red box on the top
        flask.flash("Unknown format '{}'".format(fileformat))
        return flask.redirect(flask.url_for("input_data"))
    except Exception:
        # Let's deal properly with any exception, to avoid to get a 500 error.
        # Feel free to do better error management here,
        # or to pass additional information via flask.flash
        flask.flash(
            "I tried my best, but I wasn't able to load your "
            "file in format '{}'...".format(fileformat)
        )
        return flask.redirect(flask.url_for("input_data"))
    # If we are here, the file was retrieved.
    # It will contain a tuple of length three, with:
    # - the 3x3 unit cell (in angstrom)
    # - a Nx3 list of atomic coordinates (in fractional coordinates)
    # - a list of integer atomic numbers of length N

    # As an example, we just create a string representation of the JSON
    # and send it back to the user, to be rendered in a form
    import json
    data_for_template = {
        "structure_json": json.dumps(
            {
                "cell": structure_tuple[0],
                "atoms": structure_tuple[1],
                "numbers": structure_tuple[2],
            },
            indent=2,
            sort_keys=True,
        )
    }
    return flask.render_template("user_templates/custom-tool.html", **data_for_template)

为了让它工作,最后一步是创建一个user_templates/custom-tool.html文件,例如,包含以下基本内容:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Add CSS, JS, ... here, e.g, these from tools-barebone;  -->
    <link href="../../static/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" type="text/css" href="../../static/css/jquery-ui.1.12.1.min.css"/>
    <link rel="stylesheet" type="text/css" href="../../static/css/visualizer_base.min.css"/>
    <link rel="stylesheet" type="text/css" href="../../static/css/visualizer_input.min.css"/>
    <script src="../../static/js/jquery-3.1.0.min.js"></script>
    <script src="../../static/js/jquery-ui.1.12.1.min.js"></script>

    <!-- If you add things in a user_static folder, you will be able to access it via ../../user_static/xxx -->

    <title>Custom-tools example return page</title>

    <!-- Keep this, it's needed to make the tool embeddable in an iframe; it's provided by tools-barebone -->
    <script src="../../static/js/iframeResizer.contentWindow.min.js"></script>
</head>

<body>
<div id='container'>
    <div id='maintitle'>
        <h1 style="text-align: center;">Tools-example return page</h1>
    </div>

    <h2>Successfully parsed structure tuple</h2>
    <p>
        <code id='structureJson'>
{{structure_json}}
        </code>
    </p>
</div>

<!-- Important: leave this tag as the *very last* in your page, just before the end of the body -->
<!-- It is needed to properly detect the size of the iframe -->
<div style ="position: relative" data-iframe-height></div>
</body>
</html>

现在您可以构建并再次运行容器,一旦您上传结构,您应该会在页面上看到解析结果,以JSON形式显示。

8. 其他视图

现在您可以在blueprint中继续添加视图到您的应用程序。有关更多信息,请参阅Flask文档。在这里,我们只展示创建用于某些使用条款的视图的示例。

  • 首先在您的应用程序的顶层文件夹中创建一个user_views文件夹,并在其中创建一个termsofuse.html文件。在它里面填写您想要发送给用户的完整HTML代码。记得还要在Dockerfile中添加正确的COPY行。

  • 然后,在compute/__init__.py文件中创建相应的Flask视图

import os

VIEW_FOLDER = os.path.join(
    os.path.dirname(os.path.realpath(__file__)), os.pardir, "user_views"
)

@blueprint.route("/termsofuse/")
def termsofuse():
    """
    View for the terms of use
    """
    return flask.send_from_directory(VIEW_FOLDER, "termsofuse.html")

该页面可以通过/compute/termsofuse/ URL访问。

最后,例如,如果您想在结构上传块中显示链接,在提交按钮之前,您可以在templates字典中添加以下行

templates:
  # ...
  upload_structure_additional_content: "upload_structure_additional_content.html"

并在user_templates文件夹中创建一个upload_structure_additional_content.html文件,例如,包含以下内容

<div class='row' style="text-align:center">
    <p class='small'>By continuing, you agree with the <a href="../compute/termsofuse/" target="_blank">terms of use</a> of this service.</p>
</div>

一些示例

提供了一个基于tools-barebone的示例,其中包含额外的Python后端功能,该示例在tools-example工具中。

对于更高级的工具,您还可以查看tools-seekpath工具或tools-phonon-dispersion等。

在这里您还可以看到一个后端Python代码实现的示例(请查看compute子文件夹中API端点的实现)。您还可以从pytest的测试设置、GitHub actions的持续集成、如何设置pre-commit钩子等方面获得灵感。

项目详情


下载文件

下载适合您平台文件的文件。如果您不确定该选择哪个,请了解有关安装包的更多信息。

源代码分布

tools_barebone-1.3.0.tar.gz (19.2 kB 查看哈希值)

上传时间 源代码

构建分布

tools_barebone-1.3.0-py3-none-any.whl (13.2 kB 查看哈希值)

上传时间 Python 3

由以下支持