REST API#
AiiDA REST API 由两个主要类组成:
App
,继承自flask.Flask
。AiidaApi
,继承于flask_restful.Api
。该类定义了 REST API 提供的资源。
需要通过设置 api.app = app
将 AiidaApi``(我们称之为 ``api
)和 App``(我们称之为 ``app
)的实例耦合起来。
扩展 REST API#
下面,我们将通过一个最小的示例来创建一个 API,通过添加一个端点 /new-endpoint
来扩展 AiiDA REST API。端点实现 GET
请求,检索最新创建的 Dict
节点,并返回其 id
、ISO 8601 格式的 ctime
和 attributes
。
警告
REST API 目前是只读的,不支持在数据库中创建新数据或更改现有数据的端点。请参阅 this AiiDA enhancement proposal draft 了解这方面的努力。
为了实现这一目标,我们需要
让我们先将以下代码放入文件 api.py
:
#!/usr/bin/env python
import click
from aiida import load_profile
from aiida.restapi import common
from aiida.restapi.api import AiidaApi, App
from aiida.restapi.run_api import run_api
from flask_restful import Resource
class NewResource(Resource):
"""Resource implementing a GET method returning id, ctime, and attributes of the latest created Dict."""
def get(self):
from aiida.orm import Dict, QueryBuilder
query = QueryBuilder()
query.append(Dict, project=['id', 'ctime', 'attributes'], tag='pdata')
query.order_by({'pdata': {'ctime': 'desc'}})
result = query.first()
# Results are returned as a dictionary, datetime objects are serialized as ISO 8601
return dict(id=result[0], ctime=result[1].isoformat(), attributes=result[2])
class NewApi(AiidaApi):
def __init__(self, app=None, **kwargs):
"""This init serves to add new endpoints to the basic AiiDA Api"""
super().__init__(app=app, **kwargs)
self.add_resource(NewResource, '/new-endpoint/', strict_slashes=False)
# processing the options and running the app
CONFIG_DIR = common.__path__[0]
@click.command()
@click.option('-P', '--port', type=click.INT, default=5000, help='Port number')
@click.option('-H', '--hostname', default='127.0.0.1', help='Hostname')
@click.option(
'-c',
'--config-dir',
'config',
type=click.Path(exists=True),
default=CONFIG_DIR,
help='the path of the configuration directory',
)
@click.option('--debug', 'debug', is_flag=True, default=False, help='run app in debug mode')
@click.option(
'--wsgi-profile',
'wsgi_profile',
is_flag=True,
default=False,
help='to use WSGI profiler middleware for finding bottlenecks in web application',
)
def newendpoint(**kwargs):
"""Runs the REST api"""
# Invoke the runner
run_api(App, NewApi, **kwargs)
# main program
if __name__ == '__main__':
"""
Run the app with the provided options. For example:
python example.py --hostname=127.0.0.2 --port=6000
"""
load_profile()
newendpoint()
现在,我们将一步一步地查看之前的代码。
首先是 imports。
from aiida.restapi.api import AiidaApi, App
from aiida.restapi.run_api import run_api
from flask_restful import Resource
首先,我们需要 import 扩展/使用基类: AiidaApi
和 App
。为简单起见,建议使用 import 方法 run_api
,因为它提供了配置 API、解析 command-line 参数以及将代表 API 和应用程序的两个类结合起来的接口。不过,您可以参考 flask_restful 的文档,通过其 built-in 方法配置和 hook-up API。
然后,我们定义一个代表附加资源的类:
class NewResource(Resource):
"""Resource implementing a GET method returning id, ctime, and attributes of the latest created Dict."""
def get(self):
from aiida.orm import Dict, QueryBuilder
query = QueryBuilder()
query.append(Dict, project=['id', 'ctime', 'attributes'], tag='pdata')
query.order_by({'pdata': {'ctime': 'desc'}})
result = query.first()
# Results are returned as a dictionary, datetime objects are serialized as ISO 8601
return dict(
id=result[0],
ctime=result[1].isoformat(),
attributes=result[2]
)
类 NewResource
包含一个方法 get
。为该方法选择的名称不是任意的,而是由 Flask
固定的,它被调用来响应 HTTP GET 请求。换句话说,当 API 收到指向 URL new-endpoint
的 GET 请求时,函数 NewResource.get()
就会被调用。HTTP 响应是围绕这些函数返回的数据构建的。这些数据被打包成字典,由 Flask 序列化为 JSON 数据流。Flask 可以序列化所有 Python built-in 类型(如 int
、 float
、 str
等),而对于自定义类型的序列化,我们让您参考 Flask documentation。Flask 文档也是自定义 HTTP 响应、构建自定义 URL(如接受参数)和更高级序列化问题等主题的主要信息来源。
当你需要处理错误时,可以考虑使用 aiida.restapi.common.exceptions
中已经定义的 AiiDA REST API-specific 异常。原因将在本节稍后部分阐明。
一旦定义了新资源,我们就必须通过分配一个(或多个)端点将其注册到 API 中。这是通过 add_resource()
方法在 NewApi
的 __init__()
中完成的:
class NewApi(AiidaApi):
def __init__(self, app=None, **kwargs):
"""
This init serves to add new endpoints to the basic AiiDA Api
"""
super().__init__(app=app, **kwargs)
self.add_resource(NewResource, '/new-endpoint/', strict_slashes=False)
按照我们的初衷,覆盖 __init__()
方法的主要目的(如果不是唯一目的的话)是向 API 注册新资源。事实上,__init__()
的一般形式是:
class NewApi(AiidaApi):
def __init__(self, app=None, **kwargs):
super())
self.add_resource( ... )
self.add_resource( ... )
self.add_resource( ... )
...
在示例中,唯一的特征线是 self.add_resource(NewResource, '/new-endpoint/', strict_slashes=False)
。总之,add_resource()
方法已在 Flask 中定义和记录。
最后,main
代码会配置并运行应用程序接口:
import aiida.restapi.common as common
from aiida import load_profile
CONFIG_DIR = common.__path__[0]
import click
@click.command()
@click.option('-P', '--port', type=click.INT, default=5000,
help='Port number')
@click.option('-H', '--hostname', default='127.0.0.1',
help='Hostname')
@click.option('-c','--config-dir','config',type=click.Path(exists=True), default=CONFIG_DIR,
help='the path of the configuration directory')
@click.option('--debug', 'debug', is_flag=True, default=False,
help='run app in debug mode')
@click.option('--wsgi-profile', 'wsgi_profile', is_flag=True, default=False,
help='to use WSGI profiler middleware for finding bottlenecks in web application')
def newendpoint(**kwargs):
"""
runs the REST api
"""
# Invoke the runner
run_api(App, NewApi, **kwargs)
# main program
if __name__ == '__main__':
"""
Run the app with the provided options. For example:
python api.py --host=127.0.0.2 --port=6000
"""
load_profile()
newendpoint()
click package 用于提供一个漂亮的命令行界面,以处理选项并处理传递给 newendpoint
函数的默认值。
方法 run_api()
完成了几个功能:它将 API 与 flask.Flask
(即代表网络应用的 Flask 基本类)的实例耦合。这样,应用程序就配置好了,如果需要,还可以连接起来。
它需要输入
- 代表应用程序接口和应用程序的类。
我们强烈建议将继承自
flask.Flask
的aiida.restapi.api.App
类传给run_api()
,因为它能正确处理 AiiDA RESTApi-specific 异常。
- 位置参数,代表点击函数传递的 command-line 参数/选项。
类型、默认值和帮助字符串可在
@click.option
定义中设置,并由命令行调用处理。
使用脚本前还有几件事要做:
如果您想进一步自定义错误处理,可以从
App
的定义中获得灵感,创建派生类NewApp(App)
。- 支持的命令行选项与
verdi restapi
相同。 请使用
verdi restapi --help
查阅其完整文档。如果您想添加更多选项或修改现有选项,请从run_api
中汲取灵感,创建自定义运行程序。
- 支持的命令行选项与
现在是运行 api.py
的时候了。在终端中键入
$ chmod +x api.py
$ ./api.py --port=6000
* REST API running on http://127.0.0.1:6000/api/v4
* Serving Flask app "aiida.restapi.run_api" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://127.0.0.1:6000/ (Press CTRL+C to quit)
让我们使用 curl
和 GET 方法来查询最新创建的 node:
curl http://127.0.0.2:6000/api/v4/new-endpoint/ -X GET
输出的形式(也仅是形式)应类似于
{
"attributes": {
"binding_energy_per_substructure_per_unit_area_units": "eV/ang^2",
"binding_energy_per_substructure_per_unit_area": 0.0220032273047497
},
"ctime": "2017-04-05T16:01:06.227942+00:00",
"id": 403504
}
而响应字典的实际值和属性字段的内部结构一般会有很大不同。
最后要说的是,在某些情况下,您可能不想使用内部 werkzeug-based 服务器。例如,您可能希望使用 wsgi 脚本通过 Apache 运行应用程序。在这种情况下,只需使用 configure_api
返回自定义对象 api
:
api = configure_api(App, MycloudApi, **kwargs)
通过 api.app
可以检索到 app
。如 部署 REST API 服务器 所述,该代码片段成为 Apache 使用的 wsgi 文件的基本模块。此外,我们建议查阅 mod_wsgi 的文档。
备注
可选择将 catch_internal_server
变量创建为 False
,以便让异常(包括 python 回溯)涌入 apache 错误日志。这在 app
仍处于开发阶段时尤其有用。
为多个配置文件提供服务#
单个 REST API 实例可以为 AiiDA 实例的所有配置文件提供数据。为了保持向后兼容性,新功能需要通过配置明确启用:
verdi config set rest_api.profile_switching true
重新启动 REST API 后,它将接受配置文件查询参数,例如
http://127.0.0.1:5000/api/v4/computers?profile=some-profile-name
如果指定的配置文件已加载,REST API 的功能与未启用配置文件切换时完全相同。如果指定了另一个配置文件,REST API 会在执行请求前首先切换配置文件。如果在请求中指定了配置文件参数,而 REST API 未启用配置文件切换功能,则会返回 400 响应。