如何使用 flask restplus 为 web 设计 api 实现?
How to design api implementation for web using flask restplus?
我是第一次在 Flask 中编写 REST api,
所以现在我有这样的东西:
import uuid
import pytz
from datetime import datetime
from flask_restplus import Resource, Api, fields
from ..models import publicip_schema
from ..controller import (
jsonified,
get_user_ip,
add_new_userIp,
get_specificIp,
get_all_publicIp
)
from flask import request, jsonify
from src import app
from src import db
from src import models
api = Api(app, endpoint="/api", versio="0.0.1", title="Capture API", description="Capture API to get, modify or delete system services")
add_userIp = api.model("Ip", {"ip": fields.String("An IP address.")})
get_userIp = api.model("userIp", {
"ipid": fields.String("ID of an ip address."),
"urlmap" : fields.String("URL mapped to ip address.")
})
class CaptureApi(Resource):
# decorator = ["jwt_required()"]
# @jwt_required()
@api.expect(get_userIp)
def get(self, ipid=None, urlmap=None):
"""
this function handles request to provide all or specific ip
:return:
"""
# handle request to get detail of site with specific location id.
if ipid:
ipobj = get_user_ip({"id": ipid})
return jsonified(ipobj)
# handle request to get detail of site based on site abbreviation
if urlmap:
locate = get_user_ip({"urlmap": urlmap})
return jsonified(locate)
return jsonify(get_all_publicIp())
# @jwt_required()
@api.expect(add_userIp)
def post(self, username=None):
"""
Add a new location.
URI /location/add
:return: json response of newly added location
"""
data = request.get_json(force=True)
if not data:
return jsonify({"status": "no data passed"}), 200
if not data["ip"]:
return jsonify({"status" : "please pass the new ip you want to update"})
if get_user_ip({"ipaddress": data["ip"]}):
return jsonify({"status": "IP: {} is already registered.".format(data["ip"])})
_capIpObj = get_user_ip({"user_name": username})
if _capIpObj:
# update existing ip address
if "ip" in data:
if _capIpObj.ipaddress == data["ip"]:
return jsonify({"status": "nothing to update."}), 200
else:
_capIpObj.ipaddress = data["ip"]
else:
return jsonify({
"status" : "please pass the new ip you want to update"
})
db.session.commit()
return jsonified(_capIpObj)
else:
device = ""
service = ""
ipaddress = data["ip"]
if "port" in data:
port = data["port"]
else:
port = 80
if "device" in data:
device = data["device"]
if "service" in data:
service = data["service"]
date_modified = datetime.now(tz=pytz.timezone('UTC'))
urlmap = str(uuid.uuid4().get_hex().upper()[0:8])
new_public_ip = add_new_userIp(username, ipaddress, port, urlmap, device, service, date_modified)
return publicip_schema.jsonify(new_public_ip)
api.add_resource(
CaptureApi,
"/getallips", # GET
"/getip/id/<ipid>", # GET
"/getip/urlmap/<urlmap>", # GET
"/updateip/username/<username>" # POST
)
我遇到了两个问题
如果我指定
get_userIp = api.model("userIp", {
"ipid": fields.String("ID of an ip address."),
"urlmap" : fields.String("URL mapped to ip address.")
})
并在上面的 get 方法上添加 @api.expect(get_userIp)
。我被迫传递具有任何值的可选参数(甚至要获取所有 ip 的列表,即来自“/getallips”):请参见下面的屏幕截图。
但是这些选项参数并不是所有 IP 所必需的,但我确实需要使用这些参数来获取基于 ipid
或 urlmap
使用 get
方法的 ip。
- 查看由
flask_restplus.Api
生成的 swagger 文档,我看到了
get 和 post 用于所有端点,而我只定义了端点 get 和 post。所以技术上 updateip/username/<username>
不应该列出 get
我该如何解决这个问题?
好问题!您可以通过为每个端点定义单独的 Resource
subclasses 来解决这两个问题。这是一个示例,我将端点拆分为“/getallips”、“/getip/id/”和“/getip/urlmap/”。
Ip = api.model("Ip", {"ip": fields.String("An IP address.")})
Urlmap = api.model("UrlMap", {"urlmap": fields.String("URL mapped to ip address.")})
@api.route("/getallips")
class IpList(Resource):
def get(self):
return jsonify(get_all_publicIp())
@api.route("/getip/id/<ipid>")
class IpById(Resource):
@api.expect(Ip)
def get(self, ipid):
ipobj = get_user_ip({"id": ipid})
return jsonified(ipobj)
@api.route("/getip/urlmap/<urlmap>")
class IpByUrlmap(Resource):
@api.expect(Urlmap)
def get(self, urlmap):
ipobj = get_user_ip({"id": ipid})
return jsonified(ipobj)
请注意,您免费解决了 expect
问题 - 因为每个端点现在都完全定义了它的接口,所以很容易对其附加明确的期望。您还解决了 "get and post defined for endpoints that shouldn't",您可以决定每个端点是否应该具有 get
或 post
。
由于个人喜好,我正在使用 api.route
装饰器而不是为每个 class 调用 api.add_resource
。您可以通过为每个新的 Resource
subclass 调用 api.add_resource(<resource subclass>, <endpoint>)
来获得相同的行为(例如 api.add_resource(IpList, "/getallips")
)
我是第一次在 Flask 中编写 REST api,
所以现在我有这样的东西:
import uuid
import pytz
from datetime import datetime
from flask_restplus import Resource, Api, fields
from ..models import publicip_schema
from ..controller import (
jsonified,
get_user_ip,
add_new_userIp,
get_specificIp,
get_all_publicIp
)
from flask import request, jsonify
from src import app
from src import db
from src import models
api = Api(app, endpoint="/api", versio="0.0.1", title="Capture API", description="Capture API to get, modify or delete system services")
add_userIp = api.model("Ip", {"ip": fields.String("An IP address.")})
get_userIp = api.model("userIp", {
"ipid": fields.String("ID of an ip address."),
"urlmap" : fields.String("URL mapped to ip address.")
})
class CaptureApi(Resource):
# decorator = ["jwt_required()"]
# @jwt_required()
@api.expect(get_userIp)
def get(self, ipid=None, urlmap=None):
"""
this function handles request to provide all or specific ip
:return:
"""
# handle request to get detail of site with specific location id.
if ipid:
ipobj = get_user_ip({"id": ipid})
return jsonified(ipobj)
# handle request to get detail of site based on site abbreviation
if urlmap:
locate = get_user_ip({"urlmap": urlmap})
return jsonified(locate)
return jsonify(get_all_publicIp())
# @jwt_required()
@api.expect(add_userIp)
def post(self, username=None):
"""
Add a new location.
URI /location/add
:return: json response of newly added location
"""
data = request.get_json(force=True)
if not data:
return jsonify({"status": "no data passed"}), 200
if not data["ip"]:
return jsonify({"status" : "please pass the new ip you want to update"})
if get_user_ip({"ipaddress": data["ip"]}):
return jsonify({"status": "IP: {} is already registered.".format(data["ip"])})
_capIpObj = get_user_ip({"user_name": username})
if _capIpObj:
# update existing ip address
if "ip" in data:
if _capIpObj.ipaddress == data["ip"]:
return jsonify({"status": "nothing to update."}), 200
else:
_capIpObj.ipaddress = data["ip"]
else:
return jsonify({
"status" : "please pass the new ip you want to update"
})
db.session.commit()
return jsonified(_capIpObj)
else:
device = ""
service = ""
ipaddress = data["ip"]
if "port" in data:
port = data["port"]
else:
port = 80
if "device" in data:
device = data["device"]
if "service" in data:
service = data["service"]
date_modified = datetime.now(tz=pytz.timezone('UTC'))
urlmap = str(uuid.uuid4().get_hex().upper()[0:8])
new_public_ip = add_new_userIp(username, ipaddress, port, urlmap, device, service, date_modified)
return publicip_schema.jsonify(new_public_ip)
api.add_resource(
CaptureApi,
"/getallips", # GET
"/getip/id/<ipid>", # GET
"/getip/urlmap/<urlmap>", # GET
"/updateip/username/<username>" # POST
)
我遇到了两个问题
如果我指定
get_userIp = api.model("userIp", { "ipid": fields.String("ID of an ip address."), "urlmap" : fields.String("URL mapped to ip address.") })
并在上面的 get 方法上添加 @api.expect(get_userIp)
。我被迫传递具有任何值的可选参数(甚至要获取所有 ip 的列表,即来自“/getallips”):请参见下面的屏幕截图。
ipid
或 urlmap
使用 get
方法的 ip。
- 查看由
flask_restplus.Api
生成的 swagger 文档,我看到了
get 和 post 用于所有端点,而我只定义了端点 get 和 post。所以技术上 updateip/username/<username>
不应该列出 get
我该如何解决这个问题?
好问题!您可以通过为每个端点定义单独的 Resource
subclasses 来解决这两个问题。这是一个示例,我将端点拆分为“/getallips”、“/getip/id/”和“/getip/urlmap/”。
Ip = api.model("Ip", {"ip": fields.String("An IP address.")})
Urlmap = api.model("UrlMap", {"urlmap": fields.String("URL mapped to ip address.")})
@api.route("/getallips")
class IpList(Resource):
def get(self):
return jsonify(get_all_publicIp())
@api.route("/getip/id/<ipid>")
class IpById(Resource):
@api.expect(Ip)
def get(self, ipid):
ipobj = get_user_ip({"id": ipid})
return jsonified(ipobj)
@api.route("/getip/urlmap/<urlmap>")
class IpByUrlmap(Resource):
@api.expect(Urlmap)
def get(self, urlmap):
ipobj = get_user_ip({"id": ipid})
return jsonified(ipobj)
请注意,您免费解决了 expect
问题 - 因为每个端点现在都完全定义了它的接口,所以很容易对其附加明确的期望。您还解决了 "get and post defined for endpoints that shouldn't",您可以决定每个端点是否应该具有 get
或 post
。
由于个人喜好,我正在使用 api.route
装饰器而不是为每个 class 调用 api.add_resource
。您可以通过为每个新的 Resource
subclass 调用 api.add_resource(<resource subclass>, <endpoint>)
来获得相同的行为(例如 api.add_resource(IpList, "/getallips")
)