前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >python flask web 博客实例 restful api 5

python flask web 博客实例 restful api 5

作者头像
用户5760343
发布2019-07-05 11:09:45
6510
发布2019-07-05 11:09:45
举报
文章被收录于专栏:sktjsktj

|-flasky |-app/ |-api_1_0 |-init.py |-users.py |-posts.py |-comments.py |-authentication.py |-errors.py |-decorators.py

1 app/api_1_0/init.py from flask import Blueprint api = Blueprint('api', name) from . import authentication, posts, users, comments, errors

2 app/init.py def create_app(config_name): # ... from .api_1_0 import api as api_1_0_blueprint app.register_blueprint(api_1_0_blueprint, url_prefix='/api/v1.0')

3 app/main/errors.py @main.app_errorhandler(404) def page_not_found(e): if request.accept_mimetypes.accept_json and not request.accept_mimetypes.accept_html: response = jsonify({'error': 'not found'}) response.status_code = 404 return response return render_template('404.html'), 404

4  app/api_1_0/errors.py def forbidden(message): response = jsonify({'error': 'forbidden', 'message': message}) response.status_code = 403 return response

5 pip install flask-httpauth #验证 6 app/api_1_0/authentication.py from flask.ext.httpauth import HTTPBasicAuth from .errors import forbidden_error auth = HTTPBasicAuth() @auth.verify_password def verify_password(email, password): if email == '': g.current_user = AnonymousUser() return True user = User.query.filter_by(email = email).first() if not user: return False g.current_user = user return user.verify_password(password) @auth.error_handler def auth_error(): return unauthorized('Invalid credentials') @api.before_request @auth.login_required def before_request(): if not g.current_user.is_anonymous and not g.current_user.confirmed: return forbidden('Unconfirmed account') @auth.verify_password def verify_password(email_or_token, password): if email_or_token == '': g.current_user = AnonymousUser() return True if password == '': g.current_user = User.verify_auth_token(email_or_token) g.token_used = True return g.current_user is not None user = User.query.filter_by(email=email_or_token).first() if not user: return False g.current_user = user g.token_used = False return user.verify_password(password) @api.route('/token') def get_token(): if g.current_user.is_anonymous() or g.token_used: return unauthorized('Invalid credentials') return jsonify({'token': g.current_user.generate_auth_token(expiration=3600),'expiration': 3600}) 7 app/models.py from app.exceptions import ValidationError class User(db.Model):

...

代码语言:javascript
复制
def generate_auth_token(self, expiration):
    s = Serializer(current_app.config['SECRET_KEY'],expires_in=expiration)
    return s.dumps({'id': self.id})
@staticmethod
def verify_auth_token(token):
    s = Serializer(current_app.config['SECRET_KEY'])
    try:
        data = s.loads(token)
    except:
        return None
    return User.query.get(data['id'])
def to_json(self):
    json_user = {
          'url': url_for('api.get_post', id=self.id, _external=True),
          'username': self.username,
          'member_since': self.member_since,
          'last_seen': self.last_seen,
          'posts': url_for('api.get_user_posts', id=self.id, _external=True),
          'followed_posts': url_for('api.get_user_followed_posts',id=self.id,_external=True),
          'post_count': self.posts.count()
     }
    return json_user

class Post(db.Model):

...

代码语言:javascript
复制
def to_json(self):
    json_post = {
          'url': url_for('api.get_post', id=self.id, _external=True),
          'body': self.body,
          'body_html': self.body_html,
          'timestamp': self.timestamp,
          'author': url_for('api.get_user', id=self.author_id,_external=True),
          'comments': url_for('api.get_post_comments', id=self.id,_external=True)
          'comment_count': self.comments.count()
      }
    return json_post
@staticmethod
def from_json(json_post):
    body = json_post.get('body')
    if body is None or body == '':
        raise ValidationError('post does not have a body')
    return Post(body=body)

7  app/exceptions.py class ValidationError(ValueError): pass

8 app/api_1_0/errors.py @api.errorhandler(ValidationError) def validation_error(e): return bad_request(e.args[0])

9 app/api_1_0/posts.py @api.route('/posts/') @auth.login_required def get_posts(): posts = Post.query.all() return jsonify({ 'posts': [post.to_json() for post in posts] }) @api.route('/posts/<int:id>') @auth.login_required def get_post(id): post = Post.query.get_or_404(id) return jsonify(post.to_json()) @api.route('/posts/', methods=['POST']) @permission_required(Permission.WRITE_ARTICLES) def new_post(): post = Post.from_json(request.json) post.author = g.current_user db.session.add(post) db.session.commit() return jsonify(post.to_json()), 201, {'Location': url_for('api.get_post', id=post.id, _external=True)} @api.route('/posts/<int:id>', methods=['PUT']) @permission_required(Permission.WRITE_ARTICLES) def edit_post(id): post = Post.query.get_or_404(id) if g.current_user != post.author and not g.current_user.can(Permission.ADMINISTER): return forbidden('Insufficient permissions') post.body = request.json.get('body', post.body) db.session.add(post) return jsonify(post.to_json()) @api.route('/posts/') def get_posts(): page = request.args.get('page', 1, type=int) pagination = Post.query.paginate(page, per_page=current_app.config['FLASKY_POSTS_PER_PAGE'],error_out=False) posts = pagination.items prev = None if pagination.has_prev: prev = url_for('api.get_posts', page=page-1, _external=True) next = None if pagination.has_next: next = url_for('api.get_posts', page=page+1, _external=True) return jsonify({ 'posts': [post.to_json() for post in posts], 'prev': prev, 'next': next, 'count': pagination.total }

10  app/api_1_0/decorators.py def permission_required(permission): def decorator(f): @wraps(f) def decorated_function(*args, *kwargs): if not g.current_user.can(permission): return forbidden('Insufficient permissions') return f(args, **kwargs) return decorated_function return decorator

11 app/main/views.py #关闭服务器路由 @main.route('/shutdown') def server_shutdown(): if not current_app.testing: abort(404) shutdown = request.environ.get('werkzeug.server.shutdown') if not shutdown: abort(500) shutdown() return 'Shutting down...' 12 app/main/views.py ##报告缓慢的数据库查询 from flask.ext.sqlalchemy import get_debug_queries @main.after_app_request def after_request(response): for query in get_debug_queries(): if query.duration >= current_app.config['FLASKY_SLOW_DB_QUERY_TIME']: current_app.logger.warning( 'Slow query: %s\nParameters: %s\nDuration: %fs\nContext: %s\n' % (query.statement, query.parameters, query.duration, query.context)) return response

13 manage.py #分析源码 @manager.command def profile(length=25, profile_dir=None): """Start the application under the code profiler.""" from werkzeug.contrib.profiler import ProfilerMiddleware app.wsgi_app = ProfilerMiddleware(app.wsgi_app, restrictions=[length],profile_dir=profile_dir) app.run()

14 app/email.py from threading import Thread from flask import current_app, render_template from flask_mail import Message from . import mail def send_async_email(app, msg): with app.app_context(): mail.send(msg) def send_email(to, subject, template, **kwargs): app = current_app._get_current_object() msg = Message(app.config['FLASKY_MAIL_SUBJECT_PREFIX'] + ' ' + subject, sender=app.config['FLASKY_MAIL_SENDER'], recipients=[to]) msg.body = render_template(template + '.txt', **kwargs) msg.html = render_template(template + '.html', **kwargs) thr = Thread(target=send_async_email, args=[app, msg]) thr.start() return thr

15 manage.py

!/usr/bin/env python

import os COV = None if os.environ.get('FLASK_COVERAGE'): import coverage COV = coverage.coverage(branch=True, include='app/*') COV.start()

if os.path.exists('.env'): print('Importing environment from .env...') for line in open('.env'): var = line.strip().split('=') if len(var) == 2: os.environ[var[0]] = var[1]

from app import create_app, db from app.models import User, Follow, Role, Permission, Post, Comment from flask_script import Manager, Shell from flask_migrate import Migrate, MigrateCommand

app = create_app(os.getenv('FLASK_CONFIG') or 'default') manager = Manager(app) migrate = Migrate(app, db)

def make_shell_context(): return dict(app=app, db=db, User=User, Follow=Follow, Role=Role, Permission=Permission, Post=Post, Comment=Comment) manager.add_command("shell", Shell(make_context=make_shell_context)) manager.add_command('db', MigrateCommand)

@manager.command def test(coverage=False): """Run the unit tests.""" if coverage and not os.environ.get('FLASK_COVERAGE'): import sys os.environ['FLASK_COVERAGE'] = '1' os.execvp(sys.executable, [sys.executable] + sys.argv) import unittest tests = unittest.TestLoader().discover('tests') unittest.TextTestRunner(verbosity=2).run(tests) if COV: COV.stop() COV.save() print('Coverage Summary:') COV.report() basedir = os.path.abspath(os.path.dirname(file)) covdir = os.path.join(basedir, 'tmp/coverage') COV.html_report(directory=covdir) print('HTML version: file://%s/index.html' % covdir) COV.erase()

@manager.command def profile(length=25, profile_dir=None): """Start the application under the code profiler.""" from werkzeug.contrib.profiler import ProfilerMiddleware app.wsgi_app = ProfilerMiddleware(app.wsgi_app, restrictions=[length], profile_dir=profile_dir) app.run()

@manager.command def deploy(): """Run deployment tasks.""" from flask_migrate import upgrade from app.models import Role, User

代码语言:javascript
复制
# migrate database to latest revision
upgrade()

# create user roles
Role.insert_roles()

# create self-follows for all users
User.add_self_follows()

if name == 'main': manager.run()

16 config.py import os basedir = os.path.abspath(os.path.dirname(file))

class Config: SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard to guess string' SSL_DISABLE = False SQLALCHEMY_COMMIT_ON_TEARDOWN = True SQLALCHEMY_TRACK_MODIFICATIONS = False SQLALCHEMY_RECORD_QUERIES = True MAIL_SERVER = 'smtp.googlemail.com' MAIL_PORT = 587 MAIL_USE_TLS = True MAIL_USERNAME = os.environ.get('MAIL_USERNAME') MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD') FLASKY_MAIL_SUBJECT_PREFIX = '[Flasky]' FLASKY_MAIL_SENDER = 'Flasky Admin flasky@example.com' FLASKY_ADMIN = os.environ.get('FLASKY_ADMIN') FLASKY_POSTS_PER_PAGE = 20 FLASKY_FOLLOWERS_PER_PAGE = 50 FLASKY_COMMENTS_PER_PAGE = 30 FLASKY_SLOW_DB_QUERY_TIME=0.5

代码语言:javascript
复制
@staticmethod
def init_app(app):
    pass

class DevelopmentConfig(Config): DEBUG = True SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL') or 'sqlite:///' + os.path.join(basedir, 'data-dev.sqlite')

class TestingConfig(Config): TESTING = True SQLALCHEMY_DATABASE_URI = os.environ.get('TEST_DATABASE_URL') or 'sqlite:///' + os.path.join(basedir, 'data-test.sqlite') WTF_CSRF_ENABLED = False

class ProductionConfig(Config): SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'sqlite:///' + os.path.join(basedir, 'data.sqlite')

代码语言:javascript
复制
@classmethod
def init_app(cls, app):
    Config.init_app(app)

    # email errors to the administrators
    import logging
    from logging.handlers import SMTPHandler
    credentials = None
    secure = None
    if getattr(cls, 'MAIL_USERNAME', None) is not None:
        credentials = (cls.MAIL_USERNAME, cls.MAIL_PASSWORD)
        if getattr(cls, 'MAIL_USE_TLS', None):
            secure = ()
    mail_handler = SMTPHandler(
        mailhost=(cls.MAIL_SERVER, cls.MAIL_PORT),
        fromaddr=cls.FLASKY_MAIL_SENDER,
        toaddrs=[cls.FLASKY_ADMIN],
        subject=cls.FLASKY_MAIL_SUBJECT_PREFIX + ' Application Error',
        credentials=credentials,
        secure=secure)
    mail_handler.setLevel(logging.ERROR)
    app.logger.addHandler(mail_handler)

class HerokuConfig(ProductionConfig): SSL_DISABLE = bool(os.environ.get('SSL_DISABLE'))

代码语言:javascript
复制
@classmethod
def init_app(cls, app):
    ProductionConfig.init_app(app)

    # handle proxy server headers
    from werkzeug.contrib.fixers import ProxyFix
    app.wsgi_app = ProxyFix(app.wsgi_app)

    # log to stderr
    import logging
    from logging import StreamHandler
    file_handler = StreamHandler()
    file_handler.setLevel(logging.WARNING)
    app.logger.addHandler(file_handler)

class UnixConfig(ProductionConfig): @classmethod def init_app(cls, app): ProductionConfig.init_app(app)

代码语言:javascript
复制
    # log to syslog
    import logging
    from logging.handlers import SysLogHandler
    syslog_handler = SysLogHandler()
    syslog_handler.setLevel(logging.WARNING)
    app.logger.addHandler(syslog_handler)

config = { 'development': DevelopmentConfig, 'testing': TestingConfig, 'production': ProductionConfig, 'heroku': HerokuConfig, 'unix': UnixConfig,

代码语言:javascript
复制
'default': DevelopmentConfig

}

python manage.py profile 分析源码

https://github.com/miguelgrinberg/flasky

image.png

pip install httpie ##测试web服务

(venv) $ http --json --auth <email>:<password> GET \

http://127.0.0.1:5000/api/v1.0/posts HTTP/1.0 200 OK Content-Length: 7018 Content-Type: application/json Date: Sun, 22 Dec 2013 08:11:24 GMT Server: Werkzeug/0.9.4 Python/2.7.3 { "posts": [ ... ], "prev": null "next": "http://127.0.0.1:5000/api/v1.0/posts/?page=2", "count": 150 }

(venv) $ http --json --auth : GET http://127.0.0.1:5000/api/v1.0/posts/

(venv) $ http --auth <email>:<password> --json POST \

http://127.0.0.1:5000/api/v1.0/posts/ "body=I'm adding a post from the command line." HTTP/1.0 201 CREATED Content-Length: 360 Content-Type: application/json Date: Sun, 22 Dec 2013 08:30:27 GMT Location: http://127.0.0.1:5000/api/v1.0/posts/111 Server: Werkzeug/0.9.4 Python/2.7.3 { "author": "http://127.0.0.1:5000/api/v1.0/users/1", "body": "I'm adding a post from the command line.", "body_html": "<p>I'm adding a post from the <em>command line</em>.</p>", "comments": "http://127.0.0.1:5000/api/v1.0/posts/111/comments", "comment_count": 0, "timestamp": "Sun, 22 Dec 2013 08:30:27 GMT", "url": "http://127.0.0.1:5000/api/v1.0/posts/111" }

(venv) $ http --auth <email>:<password> --json GET \

http://127.0.0.1:5000/api/v1.0/token HTTP/1.0 200 OK Content-Length: 162 Content-Type: application/json Date: Sat, 04 Jan 2014 08:38:47 GMT Server: Werkzeug/0.9.4 Python/3.3.3 { "expiration": 3600, "token": "eyJpYXQiOjEzODg4MjQ3MjcsImV4cCI6MTM4ODgyODMyNywiYWxnIjoiSFMy..." }

(venv) $ http --json --auth eyJpYXQ...: GET http://127.0.0.1:5000/api/v1.0/posts/

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019.05.27 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • ...
  • ...
  • !/usr/bin/env python
    • python manage.py profile 分析源码
      • pip install httpie ##测试web服务
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档