创建在线网络工具的基础脚手架
项目描述
tools-barebone
tools-barebone
是一个用于使用Flask Jinja2模板在Python中开发部署小型网络应用的框架。
它可以作为一个起点来开发新材料云工具部分的新工具。
它提供
- Materials Cloud中每个工具都使用的通用布局
- Materials Cloud主题
- 通用API端点
- 通用小部件,例如晶体结构的文件上传功能
- Web服务器设置
- 将工具部署到docker容器的脚本
先决条件
- docker >= v18.09
如何使用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钩子等方面获得灵感。
项目详情
下载文件
下载适合您平台文件的文件。如果您不确定该选择哪个,请了解有关安装包的更多信息。