我需要一些技巧来构建具有约35000个静态(不变)JSON数据的REST API。
这是我第一次认真构建REST API,因此我需要一些设计决策建议。
首先,由于我熟悉Flask和MongoDB来存储数据,因此我打算使用Flask构建API。但是我听说MongoDB对于数据不变的不是一个很好的选择。
我想知道的是:
步骤:
1) Upload my data to DB
2) Create a REST API that helps the user fetch the data
3) Upload the REST API to some server
4) Test with Postman to see if it works
我的总体思路正确吗?
任何建议都会很棒。预先感谢。
答案 0 :(得分:2)
如果您不确定要使用哪个数据库,我会选择PostgreSQL。它具有可伸缩性,因此如果您需要在数据集上构建,它将可以正常工作。在性能方面,它仅取决于它收到的请求数量,但是我敢打赌,它可以处理您提出的任何要求。
关于API,如果您设置了Flask,那么我建议使用Flask-Restful软件包。使用ORM在名为models.py的文件中概述数据库。在名为资源的文件夹中,制作用作API资源的文件。例子是blogposts.py,它对所有或单个帖子有一个get请求,对单个帖子有一个post,post,put和delete。这是我真正的轻量级博客的内容。使用peewee作为ORM,并使用另一个名为Flask-HTTPAuth的软件包进行身份验证。
# blogposts.py
import json
from flask import jsonify, Blueprint, abort, make_response
from flask_restful import (Resource, Api, reqparse, inputs, fields,
url_for, marshal, marshal_with)
from auth import auth
import models
blogpost_fields = {
'id': fields.Integer,
'title': fields.String,
'content': fields.String,
'created': fields.DateTime
}
def blogpost_or_404(id):
try:
blogpost = models.BlogPost.get(models.BlogPost.id==id)
except models.BlogPost.DoesNotExist:
abort(404)
else:
return blogpost
class BlogPostList(Resource):
def __init__(self):
self.reqparse = reqparse.RequestParser()
self.reqparse.add_argument(
'title',
required=True,
help='No title provided',
location=['form', 'json']
)
self.reqparse.add_argument(
'content',
required=False,
nullable=True,
location=['form', 'json'],
default=''
)
super().__init__()
def get(self):
blogpost = [marshal(blogpost, blogpost_fields)
for blogpost in models.BlogPost.select()]
return {'BlogPosts': blogpost}
@marshal_with(blogpost_fields)
@auth.login_required
def post(self):
args = self.reqparse.parse_args()
blogpost = models.BlogPost.create(**args)
return (blogpost, 201, {
'Location': url_for('resources.blogposts.blogpost', id=blogpost.id)
})
class BlogPost(Resource):
def __init__(self):
self.reqparse = reqparse.RequestParser()
self.reqparse.add_argument(
'title',
required=False,
help='No title provided',
location=['form', 'json']
)
self.reqparse.add_argument(
'content',
required=False,
nullable=True,
location=['form', 'json'],
default=''
)
super().__init__()
@marshal_with(blogpost_fields)
def get(self, id):
return (blogpost_or_404(id))
@marshal_with(blogpost_fields)
@auth.login_required
def put(self, id):
args = self.reqparse.parse_args()
try:
blogpost = models.BlogPost.select().where(
models.BlogPost.id==id).get()
except models.BlogPost.DoesNotExist:
return make_response(json.dumps(
{'error': 'That blogpost does not exist or is not editable'}
), 403)
else:
query = blogpost.update(**args).where(models.BlogPost.id==id)
query.execute()
blogpost = (blogpost_or_404(id))
return (blogpost, 200, {
'Location': url_for('resources.blogposts.blogpost', id=id)
})
@auth.login_required
def delete(self, id):
try:
blogpost = models.BlogPost.select().where(
models.BlogPost.id==id).get()
except models.BlogPost.DoesNotExist:
return make_response(json.dumps(
{'error': 'That blogpost does not exist or is not editable'}
), 403)
else:
query = blogpost.delete().where(models.BlogPost.id==id)
query.execute()
return '', 204, {'Location': url_for('resources.blogposts.blogposts')}
blogposts_api = Blueprint('resources.blogposts', __name__)
api = Api(blogposts_api)
api.add_resource(
BlogPostList,
'/blogposts',
endpoint='blogposts'
)
api.add_resource(
BlogPost,
'/blogposts/<int:id>',
endpoint='blogpost'
)
资源类具有带有http方法名称的方法,这是设置允许哪些方法的方法。例如,如果我尝试删除没有ID的/ blogposts,它将以不允许的方法进行响应。删除仅针对单个帖子定义。封送处理可确定响应中包含哪些信息,您可以在顶部使用blogpost_fields对其进行定义。在每个类的 init 中,我们定义了请求解析器,该请求解析器确定API需要的信息。在此示例中,我们仅需要标题和帖子内容。在用户资源中,您可以添加电子邮件,用户名,密码,验证密码,管理员状态等信息。
# models.py
import datetime
import jwt
from argon2 import PasswordHasher
from peewee import *
import config
DATABASE = PostgresqlDatabase('blogdb', user=config.DB['USER'], password=config.DB['PW'], host=config.DB['HOST'])
HASHER = PasswordHasher()
class User(Model):
username = CharField(unique=True)
email = CharField(unique=True)
password = CharField()
class Meta:
database = DATABASE
@classmethod
def create_user(cls, username, email, password, **kwargs):
email = email.lower()
try:
cls.select().where(
(cls.email==email)|(cls.username**username)
).get()
except cls.DoesNotExist:
user = cls(username=username, email=email)
user.password = user.set_password(password)
user.save()
return user
else:
raise Exception("User with that email or username already exists")
@staticmethod
def verify_auth_token(token):
try:
payload = jwt.decode(token, config.SECRET_KEY)
return payload['sub']
except jwt.ExpiredSignatureError:
return 'Signature expired. Please log in again.'
except jwt.InvalidTokenError:
return 'Invalid token. Please log in again.'
@staticmethod
def set_password(password):
return HASHER.hash(password)
def verify_password(self, password):
return HASHER.verify(self.password, password)
def generate_auth_token(self, id):
try:
payload = {
'exp': datetime.datetime.utcnow() + datetime.timedelta(days=0, seconds=5),
'iat': datetime.datetime.utcnow(),
'sub': id
}
return jwt.encode(
payload,
config.SECRET_KEY,
algorithm='HS256'
)
except Exception as e:
return e
class BlogPost(Model):
title = CharField(default='', unique=True)
content = TextField(default='')
created = DateTimeField(default=datetime.datetime.now)
class Meta:
database = DATABASE
def initialize():
DATABASE.connect()
DATABASE.create_tables([User, BlogPost], safe=True)
DATABASE.close()
# auth.py
from flask import g
from flask_httpauth import HTTPTokenAuth
import models
auth = HTTPTokenAuth(scheme='Bearer')
@auth.verify_token
def verify_token(token):
user = models.User.verify_auth_token(token)
if user is not None:
g.user = user
return True
return False
如果您曾经使用过像SQLAlchemy这样的ORM,那么模型是不言自明的。我建议使用该软件包,因为您的数据集远比本示例中的数据集大得多。 HTTPAuth允许您使用必需的身份验证方法来修饰API资源方法。在我的示例中,登录将生成一个JWT,该JWT需要与每个请求一起作为Bearer令牌发送。
所有设置完成后,您就可以在app.py中注册API蓝图
# app.py
app = Flask(__name__)
app.register_blueprint(users_api, url_prefix='/api/v1')
app.register_blueprint(blogposts_api, url_prefix='/api/v1')
app.register_blueprint(login_api)
就是这样!