From 0247b958be9054750a7431d617b217729326303c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=95=E7=9D=BF?= Date: Fri, 4 Dec 2020 18:23:20 +0800 Subject: [PATCH 1/3] style(typing): rename add add typing --- alembic/env.py | 10 +- alembic/versions/14eb6f026d85_init.py | 113 ++++++++++++++++++ app.py | 29 ++++- authentication/token.py | 49 ++++---- blueprints/api.py | 2 +- blueprints/health.py | 3 +- configures/__init__.py | 3 +- handlers/__init__.py | 7 +- handlers/{comment_handler.py => comment.py} | 18 ++- handlers/{post_hander.py => post.py} | 7 +- .../{root_topic_handler.py => root_topic.py} | 28 +++-- handlers/{topic_handler.py => topic.py} | 17 ++- handlers/{user_handler.py => user.py} | 20 +++- models/.DS_Store | Bin 0 -> 6148 bytes models/{base_model.py => base.py} | 28 +++-- models/data_types.py | 35 +++--- models/database/__init__.py | 19 +++ .../base_model.py => database/base.py} | 52 +++++--- models/database/comment.py | 12 ++ models/database/post.py | 15 +++ models/database/topic.py | 26 ++++ models/database/user.py | 59 +++++++++ models/database_models/__init__.py | 7 -- models/database_models/comment_model.py | 16 --- models/database_models/post_model.py | 20 ---- models/database_models/topic_model.py | 26 ---- models/database_models/user_model.py | 78 ------------ models/query/__init__.py | 16 +++ .../base_model.py => query/base.py} | 30 ++--- .../comment_model.py => query/comment.py} | 5 +- .../post_model.py => query/post.py} | 5 +- .../topic_model.py => query/topic.py} | 2 +- models/query/user.py | 14 +++ models/query_models/__init__.py | 0 models/query_models/user_model.py | 39 ------ models/response/__init__.py | 15 +++ .../base_model.py => response/base.py} | 2 +- .../comment_model.py => response/comment.py} | 6 +- .../post_model.py => response/post.py} | 6 +- .../topic_model.py => response/topic.py} | 15 ++- models/response/user.py | 18 +++ models/response_models/__init__.py | 5 - models/response_models/user_model.py | 43 ------- requirements.txt | 105 ++++++++-------- resources/__init__.py | 18 +-- resources/comment.py | 6 +- resources/post.py | 6 +- resources/topic.py | 8 +- resources/user.py | 6 +- tests/test_topic.py | 19 ++- 50 files changed, 611 insertions(+), 477 deletions(-) create mode 100644 alembic/versions/14eb6f026d85_init.py rename handlers/{comment_handler.py => comment.py} (85%) rename handlers/{post_hander.py => post.py} (92%) rename handlers/{root_topic_handler.py => root_topic.py} (79%) rename handlers/{topic_handler.py => topic.py} (84%) rename handlers/{user_handler.py => user.py} (84%) create mode 100644 models/.DS_Store rename models/{base_model.py => base.py} (82%) create mode 100644 models/database/__init__.py rename models/{database_models/base_model.py => database/base.py} (68%) create mode 100644 models/database/comment.py create mode 100644 models/database/post.py create mode 100644 models/database/topic.py create mode 100644 models/database/user.py delete mode 100644 models/database_models/__init__.py delete mode 100644 models/database_models/comment_model.py delete mode 100644 models/database_models/post_model.py delete mode 100644 models/database_models/topic_model.py delete mode 100644 models/database_models/user_model.py create mode 100644 models/query/__init__.py rename models/{query_models/base_model.py => query/base.py} (82%) rename models/{query_models/comment_model.py => query/comment.py} (86%) rename models/{query_models/post_model.py => query/post.py} (85%) rename models/{query_models/topic_model.py => query/topic.py} (75%) create mode 100644 models/query/user.py delete mode 100644 models/query_models/__init__.py delete mode 100644 models/query_models/user_model.py create mode 100644 models/response/__init__.py rename models/{response_models/base_model.py => response/base.py} (79%) rename models/{response_models/comment_model.py => response/comment.py} (69%) rename models/{response_models/post_model.py => response/post.py} (85%) rename models/{response_models/topic_model.py => response/topic.py} (54%) create mode 100644 models/response/user.py delete mode 100644 models/response_models/__init__.py delete mode 100644 models/response_models/user_model.py diff --git a/alembic/env.py b/alembic/env.py index 3ff48f5..c1fbfdb 100644 --- a/alembic/env.py +++ b/alembic/env.py @@ -1,15 +1,15 @@ import os import sys - -sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../") - from logging.config import fileConfig from sqlalchemy import engine_from_config, pool from alembic import context -from configures.settings import SQLALCHEMY_DATABASE_URI -from models.database_models import Meta + +sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../") + +from configures.settings import SQLALCHEMY_DATABASE_URI # noqa +from models.database import Meta # noqa # this is the Alembic Config object, which provides # access to the values within the .ini file in use. diff --git a/alembic/versions/14eb6f026d85_init.py b/alembic/versions/14eb6f026d85_init.py new file mode 100644 index 0000000..7dbadf8 --- /dev/null +++ b/alembic/versions/14eb6f026d85_init.py @@ -0,0 +1,113 @@ +"""init + +Revision ID: 14eb6f026d85 +Revises: +Create Date: 2020-12-04 16:16:42.442007 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '14eb6f026d85' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('comment', + sa.Column('create_time', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True, comment='创建时间'), + sa.Column('update_time', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True, comment='更新时间'), + sa.Column('deleted', sa.Boolean(), server_default=sa.text('false'), nullable=False, comment='是否被删除'), + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False, comment='评论用户的 ID'), + sa.Column('post_id', sa.Integer(), nullable=False, comment='Post 文章的 ID'), + sa.Column('content', sa.Text(), nullable=False, comment='用户的评论'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('post', + sa.Column('create_time', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True, comment='创建时间'), + sa.Column('update_time', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True, comment='更新时间'), + sa.Column('deleted', sa.Boolean(), server_default=sa.text('false'), nullable=False, comment='是否被删除'), + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('topic_id', sa.Integer(), nullable=False, comment='文章所在的主题 ID'), + sa.Column('user_id', sa.Integer(), nullable=False, comment='发布文章用户的 ID'), + sa.Column('content', sa.Text(), nullable=False, comment='文章内容'), + sa.Column('click_times', sa.Integer(), server_default=sa.text('false'), nullable=True, comment='文章的点击数'), + sa.Column('tags', sa.JSON(), nullable=True, comment='文章的 tag'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('root_topic', + sa.Column('create_time', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True, comment='创建时间'), + sa.Column('update_time', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True, comment='更新时间'), + sa.Column('deleted', sa.Boolean(), server_default=sa.text('false'), nullable=False, comment='是否被删除'), + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=256), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('name') + ) + op.create_table('user', + sa.Column('create_time', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True, comment='创建时间'), + sa.Column('update_time', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True, comment='更新时间'), + sa.Column('deleted', sa.Boolean(), server_default=sa.text('false'), nullable=False, comment='是否被删除'), + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('email', sa.String(length=100), nullable=False), + sa.Column('password_hash', sa.String(length=256), nullable=False, comment='登陆密码 hash 之后的值'), + sa.Column('name', sa.String(length=100), nullable=True), + sa.Column('phone', sa.String(length=20), nullable=True, comment='电话号码'), + sa.Column('avatar', sa.String(length=256), nullable=True, comment='用户头像'), + sa.Column('website', sa.String(length=100), nullable=True, comment='个人网站'), + sa.Column('company', sa.String(length=100), nullable=True, comment='所在公司'), + sa.Column('job', sa.String(length=100), nullable=True, comment='职位'), + sa.Column('location', sa.String(length=100), nullable=True, comment='所在地'), + sa.Column('signature', sa.String(length=256), nullable=True, comment='签名'), + sa.Column('dribbble', sa.String(length=256), nullable=True, comment='Dribbble'), + sa.Column('duolingo', sa.String(length=256), nullable=True, comment='Duolingo'), + sa.Column('about_me', sa.String(length=256), nullable=True, comment='About.me'), + sa.Column('last_me', sa.String(length=256), nullable=True, comment='Last.fm'), + sa.Column('good_reads', sa.String(length=256), nullable=True, comment='Goodreads'), + sa.Column('github', sa.String(length=256), nullable=True, comment='GitHub'), + sa.Column('psn_id', sa.String(length=256), nullable=True, comment='PSN ID'), + sa.Column('stream_id', sa.String(length=256), nullable=True, comment='Steam_ID'), + sa.Column('twitch', sa.String(length=256), nullable=True, comment='Twitch'), + sa.Column('battle_tag', sa.String(length=256), nullable=True, comment='BattleTag'), + sa.Column('instagram', sa.String(length=256), nullable=True, comment='Instagram'), + sa.Column('telegram', sa.String(length=256), nullable=True, comment='Telegram'), + sa.Column('twitter', sa.String(length=256), nullable=True, comment='Twitter'), + sa.Column('btc_address', sa.String(length=256), nullable=True, comment='BTC Address'), + sa.Column('coding_net', sa.String(length=256), nullable=True, comment='Coding.net'), + sa.Column('personal_introduction', sa.String(length=256), nullable=True, comment='个人简介'), + sa.Column('state_update_view_permission', sa.Integer(), nullable=True, comment='状态更新查看权限'), + sa.Column('community_rich_rank', sa.Boolean(), nullable=True, comment='社区财富排行榜'), + sa.Column('money', sa.Integer(), nullable=True, comment='余额'), + sa.Column('show_remain_money', sa.Boolean(), nullable=True, comment='是否显示余额'), + sa.Column('use_avatar_for_favicon', sa.Boolean(), nullable=True, comment='使用节点头像作为页面 favicon'), + sa.Column('use_high_resolution_avatar', sa.Boolean(), nullable=True, comment='使用高精度头像'), + sa.Column('time_zone', sa.String(length=256), nullable=True, comment='默认使用的时区'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('topic', + sa.Column('create_time', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True, comment='创建时间'), + sa.Column('update_time', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True, comment='更新时间'), + sa.Column('deleted', sa.Boolean(), server_default=sa.text('false'), nullable=False, comment='是否被删除'), + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=256), nullable=False), + sa.Column('root_topic_id', sa.Integer(), server_default=sa.text('1'), nullable=True), + sa.ForeignKeyConstraint(['root_topic_id'], ['root_topic.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('name') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('topic') + op.drop_table('user') + op.drop_table('root_topic') + op.drop_table('post') + op.drop_table('comment') + # ### end Alembic commands ### diff --git a/app.py b/app.py index 78e37c1..4fed882 100644 --- a/app.py +++ b/app.py @@ -16,8 +16,8 @@ from blueprints import all_blueprints from configures import settings -from models.base_model import BaseModel -from models.database_models import db +from models.base import BaseModel +from models.database import db from resources import ApiResponse logger = logging.getLogger(__name__) @@ -89,7 +89,19 @@ def init_logging() -> None: 'formatters': { 'brief': {'format': '%(message)s'}, 'standard': { - 'format': '[%(asctime)s] [%(levelname)s] [%(filename)s.%(funcName)s:%(lineno)3d] [%(process)d::%(thread)d] %(message)s' + 'format': '[%(asctime)s] [%(levelname)s] [%(filename)s.%(funcName)s:%(lineno)3d] [%(process)d::%(thread)d] %(message)s' + }, + 'colored': { + '()': 'colorlog.ColoredFormatter', + 'format': "%(log_color)s%(asctime)s - %(levelname)s - %(message)s", + 'datefmt': '%Y-%m-%d %H:%M:%S', + 'log_colors': { + 'DEBUG': 'cyan', + 'INFO': 'green', + 'WARNING': 'yellow', + 'ERROR': 'red', + 'CRITICAL': 'red,bg_white', + }, }, }, 'handlers': { @@ -102,7 +114,7 @@ def init_logging() -> None: 'interval': 1, 'encoding': 'utf8', }, - 'console': {'level': level, 'formatter': 'standard', 'class': 'logging.StreamHandler'}, + 'console': {'level': level, 'formatter': 'colored', 'class': 'logging.StreamHandler'}, 'default_access': { 'level': level, 'formatter': 'brief', @@ -114,7 +126,7 @@ def init_logging() -> None: }, 'console_access': { 'level': level, - 'formatter': 'brief', + 'formatter': 'colored', 'class': 'logging.StreamHandler', }, }, @@ -124,7 +136,12 @@ def init_logging() -> None: 'level': level, 'propagate': False, }, - '': {'handlers': ['default', 'console'], 'level': level, 'propagate': True}, + '': { + 'handlers': ['default', 'console'], + 'formatter': 'colored', + 'level': level, + 'propagate': True, + }, }, } diff --git a/authentication/token.py b/authentication/token.py index 47d67a4..e096d7d 100644 --- a/authentication/token.py +++ b/authentication/token.py @@ -1,38 +1,39 @@ +import logging + from flask import g -from flask_httpauth import HTTPBasicAuth +from flask_httpauth import HTTPBasicAuth, HTTPTokenAuth, MultiAuth from flask_restful import Resource -from models.database_models.user_model import User - -auth = HTTPBasicAuth() - +from models.database import User -# @auth.verify_password -# def verify_password(email, password): -# user = User.query.filter_by(email=email).first() -# if not user or not user.check_password(password): -# return False -# g.user = user -# return True +basic_auth = HTTPBasicAuth() +token_auth = HTTPTokenAuth() +multi_auth = MultiAuth(basic_auth, token_auth) class Token(Resource): - @auth.login_required + @multi_auth.login_required def get(self): token = g.user.generate_auth_token() return {'token': token.decode('ascii')} -@auth.verify_password -def verify_password(username_or_token, password): - # first try to authenticate by token - user = User.verify_auth_token(username_or_token) - print("here", username_or_token) - print(user) - if not user: - # try to authenticate with username/password - user = User.query.filter_by(email=username_or_token).first() - if not user or not user.check_password(password): - return False +@basic_auth.verify_password +def verify_password(username, password): + user = User.query.filter_by(email=username).first() + if not user or not user.check_password(password): + return False + logging.info(f"用户 <{user}> 登陆>") g.user = user return True + + +@token_auth.verify_token +def verify_token(token): + try: + user = User.verify_auth_token(token) + except: # noqa + return False + else: + g.user = user + return user diff --git a/blueprints/api.py b/blueprints/api.py index f5e651b..bbece48 100644 --- a/blueprints/api.py +++ b/blueprints/api.py @@ -9,7 +9,7 @@ api_bp = Blueprint("api", __name__, url_prefix="/api") -api = Api(api_bp, errors=Exception) +api = Api(api_bp, errors=Exception) # noqa api.add_resource(User, "/users/") api.add_resource(Users, "/users") diff --git a/blueprints/health.py b/blueprints/health.py index 85d31c1..5802ae8 100644 --- a/blueprints/health.py +++ b/blueprints/health.py @@ -1,4 +1,5 @@ -from typing import Tuple, Dict +from typing import Dict, Tuple + from flask import Blueprint from flask_restful import Api, Resource diff --git a/configures/__init__.py b/configures/__init__.py index 9f969a1..3352258 100644 --- a/configures/__init__.py +++ b/configures/__init__.py @@ -1,6 +1,5 @@ import enum - -from typing import ValuesView, TypeVar, AbstractSet +from typing import AbstractSet, TypeVar, ValuesView T = TypeVar("T", bound=enum.Enum) diff --git a/handlers/__init__.py b/handlers/__init__.py index e88bf3a..14b5406 100644 --- a/handlers/__init__.py +++ b/handlers/__init__.py @@ -1,6 +1,9 @@ from exceptions import exceptions +from typing import Type, TypeVar -from models.database_models.base_model import Base as Model +from models.database import Base as Model + +U = TypeVar('U', bound=Model) class BaseHandler: @@ -14,7 +17,7 @@ def assert_id_is_not_none(self) -> None: if self.id is None: raise exceptions.ServerException("id must not be None.") - def _get_sqlalchemy_instance(self) -> Model: + def _get_sqlalchemy_instance(self) -> Type[U]: self.assert_id_is_not_none() instance = self._model.get_by_id(self.id) if not instance or getattr(instance, 'deleted', False): diff --git a/handlers/comment_handler.py b/handlers/comment.py similarity index 85% rename from handlers/comment_handler.py rename to handlers/comment.py index 901c138..30c1b34 100644 --- a/handlers/comment_handler.py +++ b/handlers/comment.py @@ -1,17 +1,13 @@ -from sqlalchemy import and_ - +from exceptions.exceptions import ActionNotAllowed, ArgumentInvalid, ObjectsNotExist from typing import Generator, Optional -from models.database_models.user_model import User -from models.database_models.post_model import Post -from models.database_models.topic_model import Topic -from models.database_models.comment_model import Comment -from models.response_models.comment_model import ResponseCommentModel -from handlers import BaseHandler -from handlers.utils import ContentChecker +from sqlalchemy import and_ from configures.const import PER_PAGE -from exceptions.exceptions import ArgumentInvalid, ObjectsNotExist, ActionNotAllowed +from handlers import BaseHandler +from handlers.utils import ContentChecker +from models.database import Comment, Post, Topic, User +from models.response import ResponseCommentModel class CommentHandler(BaseHandler): @@ -55,7 +51,7 @@ def get_comments(self, **kwargs) -> Generator[ResponseCommentModel, None, None]: per_age = kwargs.get("per_page", PER_PAGE) offset = kwargs.get("offset", 0) - condition = and_(Comment.deleted == False, Comment.post_id == self.post_id) + condition = and_(Comment.deleted.is_(False), Comment.post_id == self.post_id) comments = Comment.query.filter(condition).offset(offset).limit(per_age) yield from (ResponseCommentModel(**comment.as_dict()) for comment in comments) diff --git a/handlers/post_hander.py b/handlers/post.py similarity index 92% rename from handlers/post_hander.py rename to handlers/post.py index 0505bce..542ea6e 100644 --- a/handlers/post_hander.py +++ b/handlers/post.py @@ -5,11 +5,8 @@ from configures.const import POST_MINIMUM_WORDS from handlers import BaseHandler -from models.database_models import Comment -from models.database_models.post_model import Post -from models.database_models.topic_model import Topic -from models.database_models.user_model import User -from models.response_models.post_model import ResponsePostModel +from models.database import Comment, Post, Topic, User +from models.response import ResponsePostModel class PostHandler(BaseHandler): diff --git a/handlers/root_topic_handler.py b/handlers/root_topic.py similarity index 79% rename from handlers/root_topic_handler.py rename to handlers/root_topic.py index 2f4e10e..8205706 100644 --- a/handlers/root_topic_handler.py +++ b/handlers/root_topic.py @@ -1,16 +1,14 @@ -from sqlalchemy import and_ -from flask_sqlalchemy import SQLAlchemy - +from exceptions.exceptions import ActionNotAllowed, ObjectsDuplicated from itertools import groupby -from typing import TypeVar, Generator, List, Optional +from typing import Generator, List, TypeVar -from models.database_models import RootTopic, Topic -from models.response_models.topic_model import TopicResponseModel, RootTopicResponseModel +from flask_sqlalchemy import SQLAlchemy +from sqlalchemy import and_ from handlers import BaseHandler from handlers.utils import assert_name_is_valid - -from exceptions.exceptions import ObjectsDuplicated, ActionNotAllowed +from models.database import RootTopic, Topic +from models.response import RootTopicResponseModel, TopicResponseModel SqlAlchemyModel = TypeVar("SqlAlchemyModel", bound=SQLAlchemy) @@ -25,14 +23,18 @@ def __init__(self, id: int = None) -> None: def get_topic(self) -> Generator[RootTopicResponseModel, None, None]: instance = self._get_sqlalchemy_instance() - condition = and_(Topic.deleted == False, Topic.root_topic_id == self.id) - child_topics = (TopicResponseModel(**topic.as_dict()) for topic in Topic.query.filter(condition)) + condition = and_(Topic.deleted.is_(False), Topic.root_topic_id == self.id) + child_topics = ( + TopicResponseModel(**topic.as_dict()) for topic in Topic.query.filter(condition) + ) yield RootTopicResponseModel(child_topics=child_topics, **instance.as_dict()) @staticmethod def sort_and_group_child_topic(root_topic_ids) -> Generator[List[SqlAlchemyModel], None, None]: - child_topics = Topic.query.filter_by(deleted=False).filter(Topic.root_topic_id.in_(root_topic_ids)) + child_topics = Topic.query.filter_by(deleted=False).filter( + Topic.root_topic_id.in_(root_topic_ids) + ) child_topics = sorted(child_topics, key=lambda x: x.root_topic_id) groups = {k: list(v) for k, v in groupby(child_topics, key=lambda x: x.root_topic_id)} @@ -51,7 +53,7 @@ def create_topic(self, **kwargs) -> Generator[RootTopicResponseModel, None, None assert_name_is_valid(message="根主题名不能为空", **kwargs) name = kwargs["name"] - condition = and_(self._model.deleted == False, self._model.name == name) + condition = and_(self._model.deleted.is_(False), self._model.name == name) instance = self._model.query.filter(condition).first() if instance: raise ObjectsDuplicated(f"名称为 <{name}> 的根 Topic 已经创建") @@ -63,7 +65,7 @@ def update_topic(self, **kwargs) -> Generator[RootTopicResponseModel, None, None assert_name_is_valid(message="主题名不能为空", **kwargs) name = kwargs["name"] condition = and_( - self._model.deleted == False, self._model.id != self.id, self._model.name == name + self._model.deleted.is_(False), self._model.id != self.id, self._model.name == name ) instance = self._model.query.filter(condition).first() if instance: diff --git a/handlers/topic_handler.py b/handlers/topic.py similarity index 84% rename from handlers/topic_handler.py rename to handlers/topic.py index f9e6b1a..42073aa 100644 --- a/handlers/topic_handler.py +++ b/handlers/topic.py @@ -1,13 +1,12 @@ -from sqlalchemy import and_ +from exceptions.exceptions import ObjectsDuplicated from typing import Generator, Optional +from sqlalchemy import and_ + from handlers import BaseHandler from handlers.utils import assert_name_is_valid -from models.database_models.post_model import Post -from models.database_models.topic_model import Topic -from models.response_models.topic_model import TopicResponseModel - -from exceptions.exceptions import ObjectsDuplicated +from models.database import Post, Topic +from models.response import TopicResponseModel class TopicHandler(BaseHandler): @@ -19,7 +18,7 @@ def __init__(self, id: int = None) -> None: @staticmethod def get_post_count(instance: Post) -> int: - condition = and_(Post.deleted == False, Post.topic_id == instance.id) + condition = and_(Post.deleted.is_(False), Post.topic_id == instance.id) total = Post.query.filter(condition).count() return total @@ -38,7 +37,7 @@ def get_topics(self) -> Generator[TopicResponseModel, None, None]: def create_topic(self, **kwargs) -> TopicResponseModel: assert_name_is_valid(message="主题名不能为空", **kwargs) name = kwargs["name"] - condition = and_(self._model.deleted == False, self._model.name == name) + condition = and_(self._model.deleted.is_(False), self._model.name == name) instance = self._model.query.filter(condition).first() if instance: raise ObjectsDuplicated(f"名称为 <{name}> 的 Topic 已经创建") @@ -53,7 +52,7 @@ def update_topic(self, **kwargs) -> Optional[TopicResponseModel]: name = kwargs["name"] condition = and_( - self._model.deleted == False, self._model.id != self.id, self._model.name == name + self._model.deleted.is_(False), self._model.id != self.id, self._model.name == name ) instance = self._model.query.filter(condition).first() if instance: diff --git a/handlers/user_handler.py b/handlers/user.py similarity index 84% rename from handlers/user_handler.py rename to handlers/user.py index b19752d..08d9797 100644 --- a/handlers/user_handler.py +++ b/handlers/user.py @@ -5,15 +5,15 @@ from handlers import BaseHandler from handlers.utils import EmailChecker, PassWordChecker -from models.database_models.user_model import User -from models.response_models.user_model import ResponseUserModel as ResponseUser +from models.database import User +from models.response import ResponseUserModel as ResponseUser class UserHandler(BaseHandler): _model = User def __init__(self, id: int = None) -> None: - super().__init__(id) + super(UserHandler, self).__init__(id) self.error_msg = f"用户 <{id}> 不存在" def get_user(self) -> ResponseUser: @@ -22,9 +22,7 @@ def get_user(self) -> ResponseUser: @staticmethod def get_users() -> Generator[ResponseUser, None, None]: - yield from ( - ResponseUser(**instance.as_dict()) for instance in User.query.filter_by(deleted=False) - ) + yield from (ResponseUser(**ins.as_dict()) for ins in User.query.filter_by(deleted=False)) @staticmethod def create(**kwargs) -> Optional[ResponseUser]: @@ -79,3 +77,13 @@ def delete(self) -> None: user.update(deleted=True) return + + +if __name__ == '__main__': + from app import create_app + + app = create_app() + app.app_context().push() + + h = UserHandler() + h.create(name="ruicore", email="super76rui@icloud.com", password="12345678Aa-*") diff --git a/models/.DS_Store b/models/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..18a3ca4d935fabe34ca24410eb41642f014c85d2 GIT binary patch literal 6148 zcmeHKI|>3Z5S{S@f{mqRuHX%V=n3`$7K)813JP{xc`lFUn@_VWc3LQJVDgg5yo9`B zXGcVIdEG8VW+F0y8_L6mzS+Kc&juM$ARK4hZ_VX+I34%B+;;)vj^!*@*~z8Dw;dW4 zpaN8Y3Qz$m@M#6I!cL~2KA7iG0V?qG3fT9dzzu6+8|a@74Bi3&M+m!N?!5%CSO8cP z+dxEM8dP9VHCqe~I^resYGNB0bkS@+G;h}IP}Fb7`Nh*kYamA|Kn1!A3}ZR7`oD(1 z>HoVVuBZSN_$vi;v}#sMJSl5y?{QXZ3w#5&oG082bEjbNat!oxjD?lsxhF+lu{ri@ VVjJjm#GMZ0&w%MdqXOSn-~|X-6!icA literal 0 HcmV?d00001 diff --git a/models/base_model.py b/models/base.py similarity index 82% rename from models/base_model.py rename to models/base.py index bbd7a79..2b711c1 100644 --- a/models/base_model.py +++ b/models/base.py @@ -1,13 +1,15 @@ import pprint - -from typing import Callable, Dict, Any +from typing import Any, Callable, Dict, Type class ApiDataType: + def __init__(self, implicit=True): + self.implicit = implicit + def mock(self) -> None: raise NotImplementedError() - def marshal(self, value) -> None: + def marshal(self, value,) -> None: raise NotImplementedError() def validate(self, value) -> None: @@ -23,13 +25,13 @@ class Field: __slots__ = ("name", "field_type", "mock_func", "enum_values", "comment", "nullable", "marshal") def __init__( - self, - field_type: ApiDataType, - mock_func: Callable = None, - enum_values: tuple = (), - comment: str = "", - nullable: bool = True, - marshal: Callable = None, + self, + field_type: ApiDataType, + mock_func: Callable = None, + enum_values: tuple = (), + comment: str = "", + nullable: bool = True, + marshal: Callable = None, ) -> None: self.name = '' self.field_type = field_type @@ -70,14 +72,14 @@ def __new__(mcs, name: str, bases: tuple, attrs: dict): class BaseModel(object, metaclass=ModelMetaClass): __fields__ = () - __fields_map__ = {} + __fields_map__: Dict[str, Type[Field]] = {} def __init__(self, drop_missing=False, **kwargs) -> None: for field_name, field in self.__fields_map__.items(): value = kwargs.get(field_name) if not drop_missing and not field.nullable and value is None: raise Exception("field [{}] must be initialized".format(field_name)) - setattr(self, field_name, value) + setattr(self, field_name, field.field_type.marshal(value)) def marshal(self, values=None) -> Dict[str, str]: dct = {} @@ -86,7 +88,7 @@ def marshal(self, values=None) -> Dict[str, str]: values = self for field_name, field in self.__fields_map__.items(): value = values.get(field_name) - dct[field_name] = field.marshal(value) + dct[field_name] = field.field_type.marshal(value) return dct diff --git a/models/data_types.py b/models/data_types.py index 83e97e0..2615896 100644 --- a/models/data_types.py +++ b/models/data_types.py @@ -1,16 +1,13 @@ -import random import importlib -from uuid import uuid1 -from datetime import datetime +import random from collections.abc import Iterable - -from typing import Optional, List, Any, Dict, Callable, Tuple, Type - -from models.base_model import ApiDataType -from models.response_models.base_model import BaseResponseModel +from datetime import datetime +from exceptions.exceptions import ServerException +from typing import Any, Callable, Dict, List, Optional, Tuple, Type +from uuid import uuid1 from handlers.utils import str_to_datetime -from exceptions.exceptions import ServerException +from models.base import ApiDataType, BaseModel class IntType(ApiDataType): @@ -40,10 +37,16 @@ def mock(self) -> str: return uuid1().hex def marshal(self, value) -> Optional[str]: - return str(value) if value not in (None, "") else None + if self.implicit: + return str(value) if value not in (None, "") else None + else: + self.validate(value) + return value def validate(self, value) -> None: - assert value is None or isinstance(value, str) + assert value is None or isinstance( + value, str + ), f"expect , but get {type(value)}" class BooleanType(ApiDataType): @@ -136,13 +139,13 @@ def __getattr__(self, item): class ApiDefineType(ApiDataType): - mod = LazyWrapper(lambda: importlib.import_module("models.response_models")) + mod = LazyWrapper(lambda: importlib.import_module("models.response")) - def __init__(self, schema: Tuple[str, BaseResponseModel]): + def __init__(self, schema: Tuple[str, BaseModel]): if isinstance(schema, str): self.schema_name = schema self._real_data_type = None - elif issubclass(schema, BaseResponseModel): + elif issubclass(schema, BaseModel): self.schema_name = schema.__name__ self._real_data_type = schema else: @@ -155,12 +158,12 @@ def _ensure_schema_parsed(self) -> None: self._real_data_type = self._parse_schema_name(self.schema_name) @property - def data_type(self) -> Type[BaseResponseModel]: + def data_type(self) -> Type[BaseModel]: self._ensure_schema_parsed() return self._real_data_type @classmethod - def _parse_schema_name(cls, schema_name) -> Optional[BaseResponseModel]: + def _parse_schema_name(cls, schema_name) -> Optional[BaseModel]: schema = getattr(cls.mod, schema_name) return schema diff --git a/models/database/__init__.py b/models/database/__init__.py new file mode 100644 index 0000000..51e2b9d --- /dev/null +++ b/models/database/__init__.py @@ -0,0 +1,19 @@ +from models.database.base import Base, Column, DeleteMixin, Meta, TimeMixin, db +from models.database.comment import Comment +from models.database.post import Post +from models.database.topic import RootTopic, Topic +from models.database.user import User + +__all__ = [ + "Base", + "TimeMixin", + "DeleteMixin", + "Column", + "Meta", + "db", + "Post", + "Comment", + "Topic", + "RootTopic", + "User", +] diff --git a/models/database_models/base_model.py b/models/database/base.py similarity index 68% rename from models/database_models/base_model.py rename to models/database/base.py index ebb3cfa..9f7052a 100644 --- a/models/database_models/base_model.py +++ b/models/database/base.py @@ -1,9 +1,11 @@ import enum from datetime import datetime +from typing import Any, Callable, Dict, List, Optional, TypeVar -from typing import Optional, Dict, List, TypeVar -from flask_sqlalchemy import SQLAlchemy +from flask_sqlalchemy import BaseQuery, SQLAlchemy +from sqlalchemy import Boolean, DateTime, Table, func from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.sql import expression from configures.settings import date_format @@ -17,29 +19,42 @@ class SurrogatePK: - """A mixin that adds a surrogate integer 'primary key' column named ``id`` to any declarative-mapped class.""" + """A mixin that adds a surrogate integer 'primary key' column named `id` to any declarative-mapped class.""" + query: BaseQuery __table_args__ = {'extend_existing': True} - id = Column(db.Integer, primary_key=True) @classmethod - def get_by_id(cls, record_id) -> Optional[SQLAlchemy]: - """Get record by ID.""" - if any( - ( - isinstance(record_id, (str, bytes)) and record_id.isdigit(), - isinstance(record_id, (int, float)), - ) - ): - return cls.query.get(int(record_id)) + def get_by_id(cls, id_) -> Optional[SQLAlchemy]: + """Get record by id.""" + if any((isinstance(id_, (str, bytes)) and id_.isdigit(), isinstance(id_, (int, float)),)): + return cls.query.get(int(id_)) return None +class TimeMixin: + create_time = Column(DateTime(timezone=True), server_default=func.now(), comment="创建时间") + update_time = Column( + DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), comment="更新时间" + ) + + +class DeleteMixin: + deleted = Column(Boolean, server_default=expression.false(), nullable=False, comment="是否被删除") + + class Base(db.Model, SurrogatePK): """DataBase Model that Contains CRUD Operations""" - __abstract__ = True + __table__: Table + __tablename__: str + __new__: Callable + __init__: Callable + query: BaseQuery + + __abstract__: bool = True + __table_args__ = {"extend_existing": True} @classmethod def parse_schema(cls, **kwargs) -> Dict: @@ -57,7 +72,7 @@ def create(cls, commit=True, **kwargs): return instance.save(commit) @classmethod - def create_many(cls, kwargs_list) -> List[T]: + def create_many(cls, kwargs_list: List[Dict[str, Any]]) -> List[T]: return [cls.create(**kwargs) for kwargs in kwargs_list] def update(self, commit=True, **kwargs): @@ -99,10 +114,9 @@ def as_dict(self): def reference_col(table_name, nullable=False, pk_name='id', **kwargs): - """Column that adds primary key foreign key reference. - - Usage: :: - + """ + Column that adds primary key foreign key reference. + Usage: category_id = reference_col('category') category = relationship('Category', backref='categories') """ diff --git a/models/database/comment.py b/models/database/comment.py new file mode 100644 index 0000000..c07a88a --- /dev/null +++ b/models/database/comment.py @@ -0,0 +1,12 @@ +from sqlalchemy import Integer, Text + +from models.database import Base, Column, DeleteMixin, TimeMixin + + +class Comment(Base, TimeMixin, DeleteMixin): + __tablename__ = "comment" + + id = Column(Integer, primary_key=True,) + user_id = Column(Integer, nullable=False, comment="评论用户的 ID") + post_id = Column(Integer, nullable=False, comment="Post 文章的 ID") + content = Column(Text, nullable=False, comment="用户的评论") diff --git a/models/database/post.py b/models/database/post.py new file mode 100644 index 0000000..6d4bcbc --- /dev/null +++ b/models/database/post.py @@ -0,0 +1,15 @@ +from sqlalchemy import JSON, Integer, Text +from sqlalchemy.sql import expression + +from models.database import Base, Column, DeleteMixin, TimeMixin + + +class Post(Base, TimeMixin, DeleteMixin): + __tablename__ = 'post' + + id = Column(Integer, primary_key=True) + topic_id = Column(Integer, nullable=False, comment="文章所在的主题 ID") + user_id = Column(Integer, nullable=False, comment="发布文章用户的 ID") + content = Column(Text, nullable=False, comment="文章内容") + click_times = Column(Integer, default=0, server_default=expression.false(), comment="文章的点击数") + tags = Column(JSON, nullable=True, default=[], comment="文章的 tag") diff --git a/models/database/topic.py b/models/database/topic.py new file mode 100644 index 0000000..74a58f8 --- /dev/null +++ b/models/database/topic.py @@ -0,0 +1,26 @@ +from sqlalchemy import ForeignKey, Integer, String, text + +from models.database import Base, Column, DeleteMixin, TimeMixin + + +class RootTopic(Base, TimeMixin, DeleteMixin): + __tablename__ = "root_topic" + + id = Column(Integer, primary_key=True) + name = Column(String(256), nullable=False, unique=True) + + +class Topic(Base, TimeMixin, DeleteMixin): + __tablename__ = "topic" + + id = Column(Integer, primary_key=True) + name = Column(String(256), nullable=False, unique=True) + root_topic_id = Column(Integer, ForeignKey("root_topic.id"), server_default=text('1')) + + +if __name__ == '__main__': + from app import create_app + + app = create_app() + app.app_context().push() + RootTopic.create(name="root_topic") diff --git a/models/database/user.py b/models/database/user.py new file mode 100644 index 0000000..797472b --- /dev/null +++ b/models/database/user.py @@ -0,0 +1,59 @@ +from itsdangerous import BadSignature, SignatureExpired +from itsdangerous import TimedJSONWebSignatureSerializer as Serializer +from sqlalchemy import Integer, String +from werkzeug.security import check_password_hash, generate_password_hash + +from configures import settings +from models.database import Base, Column, DeleteMixin, TimeMixin + + +class User(Base, TimeMixin, DeleteMixin): + __tablename__ = 'user' + id = Column(Integer, primary_key=True) + + email = Column(String(100), nullable=False) + password_hash = Column(String(256), nullable=False, comment="登陆密码 hash 之后的值") + + name = Column(String(100), nullable=True) + phone = Column(String(20), nullable=True, comment='电话号码') + avatar = Column(String(256), nullable=True, comment="用户头像") + website = Column(String(100), nullable=True, comment="个人网站") + company = Column(String(100), nullable=True, comment="所在公司") + job = Column(String(100), nullable=True, comment="职位") + + def __repr__(self): + return f"User<{self.name}>" + + def hash_password(self, password): + self.password_hash = generate_password_hash(password) + + def check_password(self, password): + return check_password_hash(self.password_hash, password) + + def generate_auth_token(self, expiration=3600): + s = Serializer(settings.SECRET_KEY, expires_in=expiration) + return s.dumps({'id': self.id}) + + @staticmethod + def verify_auth_token(token): + s = Serializer(settings.SECRET_KEY) + try: + data = s.loads(token) + except SignatureExpired: + return None # valid token, but expired + except BadSignature: + return None # invalid token + user = User.query.get(data['id']) + return user + + +if __name__ == '__main__': + s = Serializer(settings.SECRET_KEY) + token = ( + "eyJhbGciOiJIUzUxMiIsImlhdCI" + "6MTYwNzA3NjE0MCwiZXhwIjoxNjA3MDc2Nz" + "QwfQ.eyJpZCI6MX0.9FwLXUqnm993TFxMbm98pXVCUoCV" + "J491Qcz5OsDxCW7-dJWRd1M3oTVUD3uVfveNoOGjF0cbi2r6YnBPf7Qyfw" + ) + m = s.loads(token) + print(m) diff --git a/models/database_models/__init__.py b/models/database_models/__init__.py deleted file mode 100644 index abc5810..0000000 --- a/models/database_models/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -from models.database_models.base_model import Base, Column, Meta, db -from models.database_models.post_model import Post -from models.database_models.user_model import User -from models.database_models.topic_model import Topic, RootTopic -from models.database_models.comment_model import Comment - -__all__ = ["Base", "Column", "Meta", "db", "Post", "Comment", "Topic", "RootTopic", "User"] diff --git a/models/database_models/comment_model.py b/models/database_models/comment_model.py deleted file mode 100644 index e071271..0000000 --- a/models/database_models/comment_model.py +++ /dev/null @@ -1,16 +0,0 @@ -from sqlalchemy import Integer, Text, DateTime, func, Boolean, text - -from models.database_models import Base, Column - - -class Comment(Base): - __tablename__ = "comment" - - id = Column(Integer, primary_key=True, ) - user_id = Column(Integer, nullable=False, comment="评论用户的 ID") - post_id = Column(Integer, nullable=False, comment="Post 文章的 ID") - content = Column(Text, nullable=False, comment="用户的评论") - - create_time = Column(DateTime, server_default=func.now(), comment="创建时间") - update_time = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment="更新时间") - deleted = Column(Boolean, default=False, server_default=text('0'), nullable=False, comment="该项目是否被删除") diff --git a/models/database_models/post_model.py b/models/database_models/post_model.py deleted file mode 100644 index e6c9ad0..0000000 --- a/models/database_models/post_model.py +++ /dev/null @@ -1,20 +0,0 @@ -from sqlalchemy import JSON, Boolean, DateTime, Integer, Text, func, text - -from models.database_models.base_model import Base, Column - - -class Post(Base): - __tablename__ = 'post' - - id = Column(Integer, primary_key=True) - topic_id = Column(Integer, nullable=False, comment="文章所在的主题 ID") - user_id = Column(Integer, nullable=False, comment="发布文章用户的 ID") - content = Column(Text, nullable=False, comment="") - click_times = Column(Integer, default=0, server_default=text('0'), comment="文章的点击数") - tags = Column(JSON, nullable=True, default=[], comment="文章的 tag") - - create_time = Column(DateTime, server_default=func.now(), comment="创建时间") - update_time = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment="更新时间") - deleted = Column( - Boolean, default=False, server_default=text('0'), nullable=False, comment="该项目是否被删除" - ) diff --git a/models/database_models/topic_model.py b/models/database_models/topic_model.py deleted file mode 100644 index f62a8d6..0000000 --- a/models/database_models/topic_model.py +++ /dev/null @@ -1,26 +0,0 @@ -from sqlalchemy import Boolean, DateTime, Integer, ForeignKey, String, func, text - -from models.database_models.base_model import Base, Column - - -class RootTopic(Base): - __tablename__ = "root_topic" - - id = Column(Integer, primary_key=True) - name = Column(String(256), nullable=False,unique=True) - - create_time = Column(DateTime, server_default=func.now(), comment="创建时间") - update_time = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment="更新时间") - deleted = Column(Boolean, default=False, server_default=text('0'), nullable=False, comment="该项目是否被删除") - - -class Topic(Base): - __tablename__ = "topic" - - id = Column(Integer, primary_key=True) - name = Column(String(256), nullable=False,unique=True) - root_topic_id = Column(Integer, ForeignKey("root_topic.id"), server_default=text('1')) - - create_time = Column(DateTime, server_default=func.now(), comment="创建时间") - update_time = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment="更新时间") - deleted = Column(Boolean, default=False, server_default=text('0'), nullable=False, comment="该项目是否被删除") diff --git a/models/database_models/user_model.py b/models/database_models/user_model.py deleted file mode 100644 index 54ae83a..0000000 --- a/models/database_models/user_model.py +++ /dev/null @@ -1,78 +0,0 @@ -from itsdangerous import BadSignature, SignatureExpired -from itsdangerous import TimedJSONWebSignatureSerializer as Serializer -from sqlalchemy import Boolean, DateTime, Integer, String, func, text -from werkzeug.security import check_password_hash, generate_password_hash - -from configures import settings -from models.database_models.base_model import Base, Column - - -class User(Base): - __tablename__ = 'user' - id = Column(Integer, primary_key=True) - - email = Column(String(100), nullable=False) - password_hash = Column(String(256), nullable=False, comment="登陆密码 hash 之后的值") - - name = Column(String(100), nullable=True) - phone = Column(String(20), nullable=True, comment='电话号码') - avatar = Column(String(256), nullable=True, comment="用户头像") - website = Column(String(100), nullable=True, comment="个人网站") - company = Column(String(100), nullable=True, comment="所在公司") - job = Column(String(100), nullable=True, comment="职位") - location = Column(String(100), nullable=True, comment="所在地") - signature = Column(String(256), nullable=True, comment="签名") - Dribbble = Column(String(256), nullable=True, comment="Dribbble") - Duolingo = Column(String(256), nullable=True, comment="Duolingo") - About_me = Column(String(256), nullable=True, comment="About.me") - Last_me = Column(String(256), nullable=True, comment="Last.fm") - Goodreads = Column(String(256), nullable=True, comment="Goodreads") - GitHub = Column(String(256), nullable=True, comment="GitHub") - PSN_ID = Column(String(256), nullable=True, comment="PSN ID") - Steam_ID = Column(String(256), nullable=True, comment="Steam_ID") - Twitch = Column(String(256), nullable=True, comment="Twitch") - BattleTag = Column(String(256), nullable=True, comment="BattleTag") - Instagram = Column(String(256), nullable=True, comment="Instagram") - Telegram = Column(String(256), nullable=True, comment="Telegram") - Twitter = Column(String(256), nullable=True, comment="Twitter") - BTC_Address = Column(String(256), nullable=True, comment="BTC Address") - Coding_net = Column(String(256), nullable=True, comment="Coding.net") - Personal_Introduction = Column(String(256), nullable=True, comment="个人简介") - state_update_view_permission = Column(Integer, default=0, comment="状态更新查看权限") - community_rich_rank = Column(Boolean, default=False, comment="社区财富排行榜") - money = Column(Integer, default=0, comment="余额") - show_remain_money = Column(Boolean, default=0, comment="是否显示余额") - use_avatar_for_favicon = Column(Boolean, default=0, comment="使用节点头像作为页面 favicon") - use_high_resolution_avatar = Column(Boolean, default=0, comment="使用高精度头像") - time_zone = Column(String(256), default="utc", comment="默认使用的时区") - - create_time = Column(DateTime, server_default=func.now(), comment="创建时间") - update_time = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment="更新时间") - deleted = Column( - Boolean, default=False, server_default=text('0'), nullable=False, comment="该项目是否被删除" - ) - - def __repr__(self): - return f"User<{self.name}>" - - def hash_password(self, password): - self.password_hash = generate_password_hash(password) - - def check_password(self, password): - return check_password_hash(self.password_hash, password) - - def generate_auth_token(self, expiration=600): - s = Serializer(settings.SECRET_KEY, expires_in=expiration) - return s.dumps({'id': self.id}) - - @staticmethod - def verify_auth_token(token): - s = Serializer(settings.SECRET_KEY) - try: - data = s.loads(token) - except SignatureExpired: - return None # valid token, but expired - except BadSignature: - return None # invalid token - user = User.query.get(data['id']) - return user diff --git a/models/query/__init__.py b/models/query/__init__.py new file mode 100644 index 0000000..8a6b242 --- /dev/null +++ b/models/query/__init__.py @@ -0,0 +1,16 @@ +from models.query.base import BaseQueryModel, QueryField +from models.query.comment import CommentQueryModel +from models.query.post import PostQueryModel +from models.query.topic import RootTopicQueryModel, TopicQueryModel +from models.query.user import UserQueryModel + +__all__ = [ + "QueryField", + "BaseQueryModel", + "BaseQueryModel", + "CommentQueryModel", + "PostQueryModel", + "RootTopicQueryModel", + "TopicQueryModel", + "UserQueryModel", +] diff --git a/models/query_models/base_model.py b/models/query/base.py similarity index 82% rename from models/query_models/base_model.py rename to models/query/base.py index cc68cb7..aef207f 100644 --- a/models/query_models/base_model.py +++ b/models/query/base.py @@ -1,10 +1,10 @@ import pprint +from exceptions.exceptions import ArgumentInvalid +from typing import Any, Callable, Dict, TypeVar from flask_restful import reqparse -from typing import Callable, TypeVar, Dict, Any -from exceptions.exceptions import ArgumentInvalid -from models.base_model import ApiDataType, BaseModel, Field +from models.base import ApiDataType, BaseModel, Field T = TypeVar("T", bound=BaseModel) @@ -25,16 +25,16 @@ class QueryField(Field): ) def __init__( - self, - field_type: ApiDataType, - location: str, - parser_func: Callable = None, - required: bool = False, - mock_func: bool = False, - enum_values: tuple = (), - comment: str = "", - nullable: bool = True, - **kwargs + self, + field_type: ApiDataType, + location: str, + parser_func: Callable = None, + required: bool = False, + mock_func: Callable = None, + enum_values: tuple = (), + comment: str = "", + nullable: bool = True, + **kwargs ) -> None: super().__init__(field_type, mock_func, enum_values, comment, nullable) self.location = location @@ -46,8 +46,8 @@ def __init__( class BaseQueryModel(BaseModel): - def __init__(self, **kwargs: dict): - super().__init__(drop_missing=False, **kwargs) + def __init__(self, **kwargs): + super(BaseQueryModel, self).__init__(**kwargs) self.__storage__ = kwargs for field_name in self.__fields_map__.keys(): if field_name not in self.__storage__: diff --git a/models/query_models/comment_model.py b/models/query/comment.py similarity index 86% rename from models/query_models/comment_model.py rename to models/query/comment.py index b146663..d0ea488 100644 --- a/models/query_models/comment_model.py +++ b/models/query/comment.py @@ -1,7 +1,6 @@ -from models.data_types import IntType, StringType -from models.query_models.base_model import BaseQueryModel, QueryField - from configures.const import PER_PAGE +from models.data_types import IntType, StringType +from models.query import BaseQueryModel, QueryField class CommentQueryModel(BaseQueryModel): diff --git a/models/query_models/post_model.py b/models/query/post.py similarity index 85% rename from models/query_models/post_model.py rename to models/query/post.py index 52d372e..478610f 100644 --- a/models/query_models/post_model.py +++ b/models/query/post.py @@ -1,7 +1,6 @@ -from models.data_types import IntType, StringType -from models.query_models.base_model import BaseQueryModel, QueryField - from configures.const import PER_PAGE +from models.data_types import IntType, StringType +from models.query import BaseQueryModel, QueryField class PostQueryModel(BaseQueryModel): diff --git a/models/query_models/topic_model.py b/models/query/topic.py similarity index 75% rename from models/query_models/topic_model.py rename to models/query/topic.py index 7aefa54..d8a7685 100644 --- a/models/query_models/topic_model.py +++ b/models/query/topic.py @@ -1,5 +1,5 @@ from models.data_types import StringType -from models.query_models.base_model import BaseQueryModel, QueryField +from models.query import BaseQueryModel, QueryField class TopicQueryModel(BaseQueryModel): diff --git a/models/query/user.py b/models/query/user.py new file mode 100644 index 0000000..a3fc09c --- /dev/null +++ b/models/query/user.py @@ -0,0 +1,14 @@ +from models.data_types import StringType +from models.query import BaseQueryModel, QueryField + + +class UserQueryModel(BaseQueryModel): + email = QueryField(StringType(), location="json") + password = QueryField(StringType(), location="json", comment="登陆密码 hash 之后的值") + + name = QueryField(StringType(), location="json") + phone = QueryField(StringType(), location="json") + avatar = QueryField(StringType(), location="json") + website = QueryField(StringType(), location="json") + company = QueryField(StringType(), location="json") + job = QueryField(StringType(), location="json") diff --git a/models/query_models/__init__.py b/models/query_models/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/models/query_models/user_model.py b/models/query_models/user_model.py deleted file mode 100644 index 3474ffc..0000000 --- a/models/query_models/user_model.py +++ /dev/null @@ -1,39 +0,0 @@ -from models.data_types import BooleanType, IntType, StringType -from models.query_models.base_model import BaseQueryModel, QueryField - - -class UserQueryModel(BaseQueryModel): - email = QueryField(StringType(), nullable=True, location="json") - password = QueryField(StringType(), nullable=True, location="json", comment="登陆密码 hash 之后的值") - - name = QueryField(StringType(), required=False, location="json") - phone = QueryField(StringType(), required=False, location="json") # 改电话号码需要验证 - avatar = QueryField(StringType(), required=False, location="json") - website = QueryField(StringType(), required=False, location="json") - company = QueryField(StringType(), required=False, location="json") - job = QueryField(StringType(), required=False, location="json") - location = QueryField(StringType(), required=False, location="json") - signature = QueryField(StringType(), required=False, location="json") - Dribbble = QueryField(StringType(), required=False, location="json") - Duolingo = QueryField(StringType(), required=False, location="json") - About_me = QueryField(StringType(), required=False, location="json") - Last_me = QueryField(StringType(), required=False, location="json") - Goodreads = QueryField(StringType(), required=False, location="json") - GitHub = QueryField(StringType(), required=False, location="json") - PSN_ID = QueryField(StringType(), required=False, location="json") - Steam_ID = QueryField(StringType(), required=False, location="json") - Twitch = QueryField(StringType(), required=False, location="json") - BattleTag = QueryField(StringType(), required=False, location="json") - Instagram = QueryField(StringType(), required=False, location="json") - Telegram = QueryField(StringType(), required=False, location="json") - Twitter = QueryField(StringType(), required=False, location="json") - BTC_Address = QueryField(StringType(), required=False, location="json") - Coding_net = QueryField(StringType(), required=False, location="json") - Personal_Introduction = QueryField(StringType(), required=False, location="json") - state_update_view_permission = QueryField(IntType(), required=False, location="json") - community_rich_rank = QueryField(BooleanType(), required=False, location="json") - money = QueryField(IntType(), required=False, location="json") - show_remain_money = QueryField(BooleanType(), required=False, location="json") - use_avatar_for_favicon = QueryField(BooleanType(), required=False, location="json") - use_high_resolution_avatar = QueryField(BooleanType(), required=False, location="json") - time_zone = QueryField(StringType(), required=False, location="json") diff --git a/models/response/__init__.py b/models/response/__init__.py new file mode 100644 index 0000000..31c8004 --- /dev/null +++ b/models/response/__init__.py @@ -0,0 +1,15 @@ +from models.response.base import BaseResponseModel, NoValue +from models.response.comment import ResponseCommentModel +from models.response.post import ResponsePostModel +from models.response.topic import RootTopicResponseModel, TopicResponseModel +from models.response.user import ResponseUserModel + +__all__ = [ + "ResponsePostModel", + "BaseResponseModel", + "NoValue", + "ResponseCommentModel", + "TopicResponseModel", + "RootTopicResponseModel", + "ResponseUserModel", +] diff --git a/models/response_models/base_model.py b/models/response/base.py similarity index 79% rename from models/response_models/base_model.py rename to models/response/base.py index 3b9b09a..9ebe0aa 100644 --- a/models/response_models/base_model.py +++ b/models/response/base.py @@ -1,4 +1,4 @@ -from models.base_model import BaseModel +from models.base import BaseModel class BaseResponseModel(BaseModel): diff --git a/models/response_models/comment_model.py b/models/response/comment.py similarity index 69% rename from models/response_models/comment_model.py rename to models/response/comment.py index 86a4e15..1ef1994 100644 --- a/models/response_models/comment_model.py +++ b/models/response/comment.py @@ -1,6 +1,6 @@ -from models.base_model import Field -from models.data_types import IntType, StringType, DateTimeType -from models.response_models import BaseResponseModel +from models.base import Field +from models.data_types import DateTimeType, IntType, StringType +from models.response import BaseResponseModel class ResponseCommentModel(BaseResponseModel): diff --git a/models/response_models/post_model.py b/models/response/post.py similarity index 85% rename from models/response_models/post_model.py rename to models/response/post.py index 89a3a36..44f3a42 100644 --- a/models/response_models/post_model.py +++ b/models/response/post.py @@ -1,6 +1,6 @@ -from models.base_model import Field +from models.base import Field from models.data_types import DateTimeType, IntType, ListType, StringType -from models.response_models.base_model import BaseResponseModel +from models.response import BaseResponseModel class ResponsePostModel(BaseResponseModel): @@ -15,5 +15,3 @@ class ResponsePostModel(BaseResponseModel): create_time = Field(DateTimeType(), nullable=False) update_time = Field(DateTimeType(), nullable=False) - - diff --git a/models/response_models/topic_model.py b/models/response/topic.py similarity index 54% rename from models/response_models/topic_model.py rename to models/response/topic.py index 8cc4e0a..268b819 100644 --- a/models/response_models/topic_model.py +++ b/models/response/topic.py @@ -1,6 +1,7 @@ -from models.base_model import Field -from models.data_types import IntType, StringType, ListType, ApiDefineType -from .base_model import BaseResponseModel +from models.base import Field +from models.data_types import ApiDefineType, IntType, ListType, StringType + +from .base import BaseResponseModel class TopicResponseModel(BaseResponseModel): @@ -13,5 +14,9 @@ class RootTopicResponseModel(BaseResponseModel): id = Field(IntType(), nullable=False) name = Field(StringType(), nullable=False, comment="主题名称") - child_topics = Field(ListType(ApiDefineType(TopicResponseModel)), nullable=False, mock_func=lambda: [], - comment="根topic下的所有子topic") + child_topics = Field( + ListType(ApiDefineType(TopicResponseModel)), + nullable=False, + mock_func=lambda: [], + comment="根topic下的所有子topic", + ) diff --git a/models/response/user.py b/models/response/user.py new file mode 100644 index 0000000..b70b014 --- /dev/null +++ b/models/response/user.py @@ -0,0 +1,18 @@ +from models.base import Field +from models.data_types import DateTimeType, IntType, StringType +from models.response import BaseResponseModel + + +class ResponseUserModel(BaseResponseModel): + id = Field(IntType(), nullable=False) + email = Field(StringType(), nullable=False) + + name = Field(StringType()) + phone = Field(StringType(), comment='电话号码') + avatar = Field(StringType(), comment="用户头像") + website = Field(StringType(), comment="个人网站") + company = Field(StringType(), comment="所在公司") + job = Field(StringType(), comment="职位") + + create_time = Field(DateTimeType(), nullable=False) + update_time = Field(DateTimeType(), nullable=False) diff --git a/models/response_models/__init__.py b/models/response_models/__init__.py deleted file mode 100644 index 7bef589..0000000 --- a/models/response_models/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from models.response_models.base_model import * -from models.response_models.post_model import * -from models.response_models.user_model import * -from models.response_models.topic_model import * -from models.response_models.comment_model import * diff --git a/models/response_models/user_model.py b/models/response_models/user_model.py deleted file mode 100644 index 9365034..0000000 --- a/models/response_models/user_model.py +++ /dev/null @@ -1,43 +0,0 @@ -from models.base_model import Field -from models.data_types import BooleanType, DateTimeType, IntType, StringType -from models.response_models.base_model import BaseResponseModel - - -class ResponseUserModel(BaseResponseModel): - id = Field(IntType(), nullable=False) - email = Field(StringType(), nullable=False) - - name = Field(StringType(), nullable=True) - phone = Field(StringType(), nullable=True, comment='电话号码') - avatar = Field(StringType(), nullable=True, comment="用户头像") - website = Field(StringType(), nullable=True, comment="个人网站") - company = Field(StringType(), nullable=True, comment="所在公司") - job = Field(StringType(), nullable=True, comment="职位") - location = Field(StringType(), nullable=True, comment="所在地") - signature = Field(StringType(), nullable=True, comment="签名") - Dribbble = Field(StringType(), nullable=True, comment="Dribbble") - Duolingo = Field(StringType(), nullable=True, comment="Duolingo") - About_me = Field(StringType(), nullable=True, comment="About.me") - Last_me = Field(StringType(), nullable=True, comment="Last.fm") - Goodreads = Field(StringType(), nullable=True, comment="Goodreads") - GitHub = Field(StringType(), nullable=True, comment="GitHub") - PSN_ID = Field(StringType(), nullable=True, comment="PSN ID") - Steam_ID = Field(StringType(), nullable=True, comment="Steam_ID") - Twitch = Field(StringType(), nullable=True, comment="Twitch") - BattleTag = Field(StringType(), nullable=True, comment="BattleTag") - Instagram = Field(StringType(), nullable=True, comment="Instagram") - Telegram = Field(StringType(), nullable=True, comment="Telegram") - Twitter = Field(StringType(), nullable=True, comment="Twitter") - BTC_Address = Field(StringType(), nullable=True, comment="BTC Address") - Coding_net = Field(StringType(), nullable=True, comment="Coding.net") - Personal_Introduction = Field(StringType(), nullable=True, comment="个人简介") - state_update_view_permission = Field(IntType(), comment="状态更新查看权限") - community_rich_rank = Field(BooleanType(), comment="社区财富排行榜") - money = Field(IntType(), comment="余额") - show_remain_money = Field(BooleanType(), comment="是否显示余额") - use_avatar_for_favicon = Field(BooleanType(), comment="使用节点头像作为页面 favicon") - use_high_resolution_avatar = Field(BooleanType(), comment="使用高精度头像") - time_zone = Field(StringType(), comment="默认使用的时区") - - create_time = Field(DateTimeType(), nullable=False) - update_time = Field(DateTimeType(), nullable=False) diff --git a/requirements.txt b/requirements.txt index 1b715fb..c7cce06 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,80 +1,83 @@ -alembic==1.4.2 -aniso8601==8.0.0 +alembic==1.4.3 +aniso8601==8.1.0 appdirs==1.4.4 -appnope==0.1.0 -astroid==2.3.3 -attrs==19.3.0 -backcall==0.1.0 -certifi==2020.4.5.1 -cffi==1.14.0 -cfgv==3.1.0 +appnope==0.1.2 +astroid==2.4.2 +attrs==20.3.0 +backcall==0.2.0 +certifi==2020.11.8 +cffi==1.14.4 +cfgv==3.2.0 chardet==3.0.4 -click==7.1.1 -codecov==2.0.22 -coverage==5.1 -coveralls==2.0.0 -cryptography==3.2 +click==7.1.2 +codecov==2.1.10 +coverage==5.3 +coveralls==2.2.0 +cryptography==3.2.1 decorator==4.4.2 -distlib==0.3.0 +distlib==0.3.1 docopt==0.6.2 filelock==3.0.12 Flask==1.1.2 -Flask-Cors==3.0.8 -Flask-HTTPAuth==4.0.0 +Flask-Cors==3.0.9 +Flask-HTTPAuth==4.2.0 Flask-Migrate==2.5.3 Flask-RESTful==0.3.8 Flask-Script==2.0.6 -Flask-SQLAlchemy==2.4.1 -gevent==1.5.0 -greenlet==0.4.15 +Flask-SQLAlchemy==2.4.4 +gevent==20.9.0 +greenlet==0.4.17 gunicorn==20.0.4 -identify==1.4.15 -idna==2.9 +identify==1.5.10 +idna==2.10 importlab==0.5.1 -ipython==7.14.0 +iniconfig==1.1.1 +ipython==7.19.0 ipython-genutils==0.2.0 -isort==4.3.21 +isort==5.6.4 itsdangerous==1.1.0 -jedi==0.17.0 +jedi==0.17.2 Jinja2==2.11.2 lazy-object-proxy==1.4.3 -Mako==1.1.2 +Mako==1.1.3 MarkupSafe==1.1.1 mccabe==0.6.1 -more-itertools==8.2.0 -mypy==0.770 +more-itertools==8.6.0 +mypy==0.790 mypy-extensions==0.4.3 -networkx==2.4 -ninja==1.9.0.post1 -nodeenv==1.3.5 -packaging==20.3 -parso==0.7.0 +networkx==2.5 +ninja==1.10.0.post2 +nodeenv==1.5.0 +packaging==20.7 +parso==0.8.0 pexpect==4.8.0 pickleshare==0.7.5 pluggy==0.13.1 -pre-commit==2.4.0 -prompt-toolkit==3.0.5 +pre-commit==2.9.2 +prompt-toolkit==3.0.8 ptyprocess==0.6.0 -py==1.8.1 +py==1.9.0 pycparser==2.20 -Pygments==2.6.1 -pylint==2.4.4 -PyMySQL==0.9.3 +Pygments==2.7.2 +pylint==2.6.0 +PyMySQL==0.10.1 pyparsing==2.4.7 -pytest==5.4.1 +pytest==6.1.2 python-dateutil==2.8.1 python-editor==1.0.4 -pytz==2019.3 +pytz==2020.4 PyYAML==5.3.1 -requests==2.23.0 -six==1.14.0 -SQLAlchemy==1.3.16 -toml==0.10.0 -traitlets==4.3.3 +requests==2.25.0 +six==1.15.0 +SQLAlchemy==1.3.20 +toml==0.10.2 +traitlets==5.0.5 typed-ast==1.4.1 -typing-extensions==3.7.4.2 -urllib3==1.25.9 -virtualenv==20.0.20 -wcwidth==0.1.9 +typing-extensions==3.7.4.3 +urllib3==1.26.2 +virtualenv==20.2.1 +wcwidth==0.2.5 Werkzeug==1.0.1 -wrapt==1.11.2 +wrapt==1.12.1 +zope.event==4.5.0 +zope.interface==5.2.0 diff --git a/resources/__init__.py b/resources/__init__.py index 76c6fd9..514f171 100644 --- a/resources/__init__.py +++ b/resources/__init__.py @@ -1,25 +1,29 @@ import inspect +from functools import wraps +from typing import Any, Callable, Dict, Type, TypeVar from flask import jsonify -from functools import wraps -from typing import Callable, Any, TypeVar, Dict -from models.base_model import ApiDataType -from models.query_models.base_model import BaseQueryModel -from models.response_models.base_model import NoValue +from models.query import BaseQueryModel +from models.response import BaseResponseModel, NoValue schema_mapping = {} Api = TypeVar("Api") class ResourceSchema: - def __init__(self, query_model: BaseQueryModel, response_model: ApiDataType, path_parameters) -> None: + def __init__( + self, + query_model: Type[BaseQueryModel], + response_model: Type[BaseResponseModel], + path_parameters, + ) -> None: self.query_model = query_model self.response_model = response_model self.path_parameters = path_parameters -def schema(query_model: BaseQueryModel, response_model: ApiDataType): +def schema(query_model: Type[BaseQueryModel], response_model: Type[BaseResponseModel]): def decorator(func): params = list(inspect.signature(func).parameters) params.remove('self') diff --git a/resources/comment.py b/resources/comment.py index e0b3d16..8009944 100644 --- a/resources/comment.py +++ b/resources/comment.py @@ -1,9 +1,9 @@ from flask_restful import Resource from authentication import auth -from handlers.comment_handler import CommentHandler -from models.query_models.comment_model import CommentQueryModel -from models.response_models.comment_model import ResponseCommentModel +from handlers.comment import CommentHandler +from models.query import CommentQueryModel +from models.response import ResponseCommentModel from resources import ApiResponse, schema diff --git a/resources/post.py b/resources/post.py index 6a42c14..d2606fb 100644 --- a/resources/post.py +++ b/resources/post.py @@ -1,9 +1,9 @@ from flask_restful import Resource from authentication import auth -from handlers.post_hander import PostHandler -from models.query_models.post_model import PostQueryModel -from models.response_models.post_model import ResponsePostModel +from handlers.post import PostHandler +from models.query import PostQueryModel +from models.response import ResponsePostModel from resources import ApiResponse, schema diff --git a/resources/topic.py b/resources/topic.py index 0bc6491..da18d3e 100644 --- a/resources/topic.py +++ b/resources/topic.py @@ -1,10 +1,10 @@ from flask_restful import Resource from authentication import auth -from handlers.root_topic_handler import RootTopicHandler -from handlers.topic_handler import TopicHandler -from models.query_models.topic_model import RootTopicQueryModel, TopicQueryModel -from models.response_models.topic_model import RootTopicResponseModel, TopicResponseModel +from handlers.root_topic import RootTopicHandler +from handlers.topic import TopicHandler +from models.query import RootTopicQueryModel, TopicQueryModel +from models.response import RootTopicResponseModel, TopicResponseModel from resources import ApiResponse, schema diff --git a/resources/user.py b/resources/user.py index bd9a635..34052d7 100644 --- a/resources/user.py +++ b/resources/user.py @@ -1,9 +1,9 @@ from flask_restful import Resource from authentication import auth -from handlers.user_handler import UserHandler -from models.query_models.user_model import UserQueryModel -from models.response_models.user_model import ResponseUserModel +from handlers.user import UserHandler +from models.query import UserQueryModel +from models.response import ResponseUserModel from resources import ApiResponse, schema diff --git a/tests/test_topic.py b/tests/test_topic.py index 775f47c..6cdf953 100644 --- a/tests/test_topic.py +++ b/tests/test_topic.py @@ -28,7 +28,10 @@ def test_add_topic_with_invalid_name(self): error_msg = resp.json error_msg.pop("traceback") - expect_error_msg = {'error_code': 400, 'error_msg': '名称中只允许出现【中文,英文,数字,下划线,连接符】,并且不允许全部是空白字符'} + expect_error_msg = { + 'error_code': 400, + 'error_msg': '名称中只允许出现【中文,英文,数字,下划线,连接符】,并且不允许全部是空白字符', + } self.assertDictEqual(error_msg, expect_error_msg) resp = self.client.post(self.url_prefix, json=self.topic5) @@ -68,14 +71,20 @@ def test_update_topic_with_invalid_name(self): error_msg = resp.json error_msg.pop("traceback") - expect_error_msg = {'error_code': 400, 'error_msg': '名称中只允许出现【中文,英文,数字,下划线,连接符】,并且不允许全部是空白字符'} + expect_error_msg = { + 'error_code': 400, + 'error_msg': '名称中只允许出现【中文,英文,数字,下划线,连接符】,并且不允许全部是空白字符', + } self.assertDictEqual(error_msg, expect_error_msg) resp = self.client.put(self.url_prefix + "/2", json=self.topic5) error_msg = resp.json error_msg.pop("traceback") - expect_error_msg = {'error_code': 400, 'error_msg': '名称中只允许出现【中文,英文,数字,下划线,连接符】,并且不允许全部是空白字符'} + expect_error_msg = { + 'error_code': 400, + 'error_msg': '名称中只允许出现【中文,英文,数字,下划线,连接符】,并且不允许全部是空白字符', + } self.assertDictEqual(error_msg, expect_error_msg) def test_delete_topic(self): @@ -92,7 +101,9 @@ def test_delete_topic(self): def test_get_topic_with_some_posts(self): self.client.post("/api/topics", json={"name": "Topic1"}) # create topic - self.client.post("/api/users", json={"email": "hrui8005@gmail.com", "password": "11Aa*%$#"}) # create user + self.client.post( + "/api/users", json={"email": "hrui8005@gmail.com", "password": "11Aa*%$#"} + ) # create user posts = {"user_id": 1, "content": "this is post1"} for _ in range(10): From 2b9ecfe0d134b330972afbf4f142e7b461d96226 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=95=E7=9D=BF?= Date: Fri, 4 Dec 2020 20:14:58 +0800 Subject: [PATCH 2/3] test(update tests): short tests running time --- authentication/token.py | 4 +- handlers/post.py | 4 +- tests/__init__.py | 59 +++++++++++++++++++-------- tests/test_comment.py | 19 ++++++--- tests/test_post.py | 32 ++++++++------- tests/test_root_topic.py | 7 +++- tests/test_topic.py | 34 +++++++++------- tests/test_user.py | 86 +++++++++++++++------------------------- 8 files changed, 134 insertions(+), 111 deletions(-) diff --git a/authentication/token.py b/authentication/token.py index e096d7d..d6c4030 100644 --- a/authentication/token.py +++ b/authentication/token.py @@ -8,11 +8,11 @@ basic_auth = HTTPBasicAuth() token_auth = HTTPTokenAuth() -multi_auth = MultiAuth(basic_auth, token_auth) +auth = MultiAuth(basic_auth, token_auth) class Token(Resource): - @multi_auth.login_required + @auth.login_required def get(self): token = g.user.generate_auth_token() return {'token': token.decode('ascii')} diff --git a/handlers/post.py b/handlers/post.py index 542ea6e..a1b9b32 100644 --- a/handlers/post.py +++ b/handlers/post.py @@ -1,9 +1,9 @@ -from exceptions.exceptions import ArgumentInvalid, ObjectsNotExist from typing import Generator, Optional from sqlalchemy import and_ from configures.const import POST_MINIMUM_WORDS +from exceptions.exceptions import ArgumentInvalid, ObjectsNotExist from handlers import BaseHandler from models.database import Comment, Post, Topic, User from models.response import ResponsePostModel @@ -15,7 +15,7 @@ class PostHandler(BaseHandler): def __init__(self, topic_id: int = None, post_id: int = None) -> None: super().__init__(post_id) self.topic_id = topic_id - self.error_msg = f"Post <{id}> 不存在" + self.error_msg = f"Post <{self.id}> 不存在" def assert_topic_id_is_not_none(self) -> None: if self.topic_id is None: diff --git a/tests/__init__.py b/tests/__init__.py index a4a8574..891e06d 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -7,7 +7,7 @@ from app import create_app from configures import settings -from models.database_models import db +from models.database import db email = "hrui8005@gmail.com" password = '11Aa*%$#' @@ -30,27 +30,52 @@ def setUpClass(cls) -> None: cls.engine.execute("DROP DATABASE IF EXISTS %s;" % settings.TEST_DATABASE) cls.engine.execute("CREATE DATABASE IF NOT EXISTS %s;" % settings.TEST_DATABASE) - def setUp(self): - self.maxDiff = None - self.app = create_app() - self.app.config["SQLALCHEMY_DATABASE_URI"] = settings.TEST_SQLALCHEMY_DATABASE_URI - self.app.config['TESTING'] = True + cls.maxDiff = None + cls.app = create_app() + cls.app.config["SQLALCHEMY_DATABASE_URI"] = settings.TEST_SQLALCHEMY_DATABASE_URI + cls.app.config['TESTING'] = True - db.init_app(self.app) - with self.app.app_context(): + db.init_app(cls.app) + with cls.app.app_context(): db.create_all() - self.app.test_client_class = TestClient - self.client = self.app.test_client() + cls.app.test_client_class = TestClient + cls.client = cls.app.test_client() - self.engine.execute( + cls.engine.execute( f"INSERT INTO TEST.user (email, password_hash) VALUES ('{email}', '{password_hash}');" ) - def tearDown(self) -> None: - self.app = create_app() - self.app.config["SQLALCHEMY_DATABASE_URI"] = settings.TEST_SQLALCHEMY_DATABASE_URI - self.app.config['TESTING'] = True - db.init_app(self.app) - with self.app.app_context(): + @classmethod + def tearDownClass(cls) -> None: + cls.app = create_app() + cls.app.config["SQLALCHEMY_DATABASE_URI"] = settings.TEST_SQLALCHEMY_DATABASE_URI + cls.app.config['TESTING'] = True + db.init_app(cls.app) + with cls.app.app_context(): db.session.remove() db.drop_all() + +# def setUp(self): +# self.maxDiff = None +# self.app = create_app() +# self.app.config["SQLALCHEMY_DATABASE_URI"] = settings.TEST_SQLALCHEMY_DATABASE_URI +# self.app.config['TESTING'] = True +# +# db.init_app(self.app) +# with self.app.app_context(): +# db.create_all() +# self.app.test_client_class = TestClient +# self.client = self.app.test_client() +# +# self.engine.execute( +# f"INSERT INTO TEST.user (email, password_hash) VALUES ('{email}', '{password_hash}');" +# ) + +# def tearDown(self) -> None: +# self.app = create_app() +# self.app.config["SQLALCHEMY_DATABASE_URI"] = settings.TEST_SQLALCHEMY_DATABASE_URI +# self.app.config['TESTING'] = True +# db.init_app(self.app) +# with self.app.app_context(): +# db.session.remove() +# db.drop_all() diff --git a/tests/test_comment.py b/tests/test_comment.py index 13b4788..20f0043 100644 --- a/tests/test_comment.py +++ b/tests/test_comment.py @@ -1,3 +1,6 @@ +from uuid import uuid4 + +from models.database import Comment from tests import BaseTestCase @@ -6,14 +9,18 @@ def setUp(self): super().setUp() self.url_prefix = "/api/topics/1/posts/1/comments" - self.root_topic = {"name": "root_topic"} + self.root_topic = {"name": uuid4().hex} self.client.post("/api/root_topics", json=self.root_topic) - self.topic1 = {"name": "Topic1", "root_topic_id": 1} + self.topic1 = {"name": uuid4().hex, "root_topic_id": 1} self.posts1 = {"user_id": 1, "content": "this is post1"} self.comment = {"user_id": 1, "content": "this is comment"} self.client.post("/api/topics", json=self.topic1) self.client.post("/api/topics/1/posts", json=self.posts1) + def tearDown(self) -> None: + with self.app.app_context(): + Comment.query.delete() + def test_add_comment(self): resp = self.client.get(self.url_prefix).json["data"] self.assertEqual(0, len(resp)) @@ -77,15 +84,15 @@ def test_delete_comment(self): self.assertDictEqual(error_msg, expect_error_msg) def test_get_comments(self): - for i in range(1, 11): + for i in range(1, 4): self.comment["content"] = str(i) self.client.post("/api/topics/1/posts/1/comments", json=self.comment) post = self.client.get("/api/topics/1/posts/1").json["data"] - self.assertEqual(10, post["comments_count"]) + self.assertGreaterEqual(post["comments_count"],2 ) comments = self.client.get(self.url_prefix, json={"per_page": 2}).json["data"] self.assertEqual(2, len(comments)) - comments = self.client.get(self.url_prefix, json={"per_page": 3, "offset": 5}).json["data"] - self.assertEqual("6", comments[0]["content"]) + comments = self.client.get(self.url_prefix, json={"per_page": 1, "offset": 2}).json["data"] + self.assertEqual(1, len(comments)) diff --git a/tests/test_post.py b/tests/test_post.py index b71afc8..236a936 100644 --- a/tests/test_post.py +++ b/tests/test_post.py @@ -1,3 +1,5 @@ +from uuid import uuid4 + from tests import BaseTestCase @@ -6,12 +8,12 @@ def setUp(self): super().setUp() self.url_prefix = "/api/topics/1/posts" - self.root_topic = {"name": "root_topic"} + self.root_topic = {"name": uuid4().hex} self.client.post("/api/root_topics", json=self.root_topic) - self.topic1 = {"name": "Topic1"} - self.topic2 = {"name": "Topic2"} - self.user1 = {"email": "hrui8005@gmail.com", "password": "11Aa*%$#"} - self.user2 = {"email": "hrui8006@gmail.com", "password": "11Aa*%$#"} + self.topic1 = {"name": uuid4().hex} + self.topic2 = {"name": uuid4().hex} + self.user1 = {"email": uuid4().hex + "hrui8005@gmail.com", "password": "11Aa*%$#"} + self.user2 = {"email": uuid4().hex + "hrui8006@gmail.com", "password": "11Aa*%$#"} self.posts1 = {"user_id": 1, "content": "this is post1"} self.posts2 = {"user_id": 1, "content": "this is post2"} @@ -32,20 +34,20 @@ def test_add_post(self): self.assertEqual(1, len(posts)) def test_add_post_with_user_not_exist(self): - self.posts1["user_id"] = 3 + self.posts1["user_id"] = 300 resp = self.client.post(self.url_prefix, json=self.posts1) error_msg = resp.json error_msg.pop("traceback") - expect_error_msg = {'error_code': 404, 'error_msg': 'User <3> 不存在'} + expect_error_msg = {'error_code': 404, 'error_msg': 'User <300> 不存在'} self.assertDictEqual(error_msg, expect_error_msg) def test_add_post_with_topic_not_exist(self): - resp = self.client.post("/api/topics/3/posts", json=self.posts1) + resp = self.client.post("/api/topics/300/posts", json=self.posts1) error_msg = resp.json error_msg.pop("traceback") - expect_error_msg = {'error_code': 404, 'error_msg': 'Topic <3> 不存在'} + expect_error_msg = {'error_code': 404, 'error_msg': 'Topic <300> 不存在'} self.assertDictEqual(error_msg, expect_error_msg) def test_add_post_with_invalid_content(self): @@ -58,10 +60,10 @@ def test_add_post_with_invalid_content(self): self.assertDictEqual(error_msg, expect_error_msg) def test_update_post(self): - self.client.post(self.url_prefix, json=self.posts1) + res = self.client.post(self.url_prefix, json=self.posts1) new_post = {"user_id": 1, "content": "this is new post"} - resp = self.client.put(self.url_prefix + "/1", json=new_post).json["data"] + resp = self.client.put(self.url_prefix + f"/{res.json['data']['id']}", json=new_post).json["data"] data = {"user_id": resp["user_id"], "content": resp["content"]} self.assertEqual(new_post, data) @@ -71,17 +73,17 @@ def test_delete_post(self): self.client.post(self.url_prefix, json=self.posts2) resp = self.client.get(self.url_prefix).json['data'] - self.assertEqual(2, len(resp)) + self.assertGreaterEqual(len(resp), 2) self.client.delete(self.url_prefix + "/1") self.client.delete(self.url_prefix + "/2") resp = self.client.get(self.url_prefix).json['data'] - self.assertEqual(0, len(resp)) + self.assertLessEqual(0, len(resp)) def test_get_posts(self): posts = {"user_id": 1, "content": ""} - user = {"email": "hrui8005@gmail.com", "password": "11Aa*%$#"} + user = {"email": uuid4().hex + "hrui8005@gmail.com", "password": "11Aa*%$#"} self.client.post("/api/topics", json=self.topic1) self.client.post("/api/users", json=user) for i in range(1, 11): @@ -92,4 +94,4 @@ def test_get_posts(self): self.assertEqual(2, len(topics)) topics = self.client.get(self.url_prefix, json={"per_page": 2, "offset": 5}).json["data"] - self.assertEqual("this is post 6", topics[0]["content"]) + self.assertEqual("this is post 5", topics[0]["content"]) diff --git a/tests/test_root_topic.py b/tests/test_root_topic.py index 31463f5..8039e43 100644 --- a/tests/test_root_topic.py +++ b/tests/test_root_topic.py @@ -1,3 +1,4 @@ +from models.database import RootTopic from tests import BaseTestCase @@ -11,9 +12,13 @@ def setUp(self): self.topic2 = {"name": "Topic2"} self.topic3 = {"name": "Topic3"} + def tearDown(self) -> None: + with self.app.app_context(): + RootTopic.query.delete() + def test_add_root_topic(self): resp = self.client.get(self.url_prefix).json["data"] - self.assertEqual(0, len(resp)) + self.assertEqual(1, len(resp)) self.client.post(self.url_prefix, json=self.root_topic1) resp = self.client.get(self.url_prefix).json["data"] diff --git a/tests/test_topic.py b/tests/test_topic.py index 6cdf953..73de267 100644 --- a/tests/test_topic.py +++ b/tests/test_topic.py @@ -1,3 +1,6 @@ +from uuid import uuid4 + +from models.database import Topic from tests import BaseTestCase @@ -5,15 +8,19 @@ class TestTopics(BaseTestCase): def setUp(self): super().setUp() self.url_prefix = "/api/topics" - self.root_topic = {"name": "root_topic"} + self.root_topic = {"name": uuid4().hex} self.client.post("/api/root_topics", json=self.root_topic) - self.topic1 = {"name": "Topic1"} - self.topic2 = {"name": "Topic2"} - self.topic3 = {"name": "Topic3"} + self.topic1 = {"name": uuid4().hex} + self.topic2 = {"name": uuid4().hex} + self.topic3 = {"name": uuid4().hex} self.topic4 = {"name": ""} self.topic5 = {"name": "abc&^%3"} self.topic6 = {"name": "update"} + def tearDown(self) -> None: + with self.app.app_context(): + Topic.query.delete() + def test_add_topic(self): resp = self.client.get(self.url_prefix).json["data"] self.assertEqual(0, len(resp)) @@ -45,7 +52,7 @@ def test_add_topic_with_exist_name(self): error_msg = resp.json error_msg.pop("traceback") - expect_error_msg = {'error_code': 403, 'error_msg': '名称为 的 Topic 已经创建'} + expect_error_msg = {'error_code': 403, 'error_msg': f'名称为 <{self.topic1["name"]}> 的 Topic 已经创建'} self.assertDictEqual(error_msg, expect_error_msg) def test_update_topic(self): @@ -53,7 +60,7 @@ def test_update_topic(self): resp = self.client.put(self.url_prefix + "/1", json=self.topic6).json["data"] resp.pop("id") - self.topic6["posts_count"] = 0 + self.topic6["posts_count"] = 10 self.assertDictEqual(self.topic6, resp) def test_update_topic_with_invalid_name(self): @@ -64,7 +71,7 @@ def test_update_topic_with_invalid_name(self): error_msg = resp.json error_msg.pop("traceback") - expect_error_msg = {'error_code': 403, 'error_msg': '名称为 的 Topic 已经创建'} + expect_error_msg = {'error_code': 403, 'error_msg': f'名称为 <{self.topic1["name"]}> 的 Topic 已经创建'} self.assertDictEqual(error_msg, expect_error_msg) resp = self.client.put(self.url_prefix + "/2", json=self.topic4) @@ -88,21 +95,21 @@ def test_update_topic_with_invalid_name(self): self.assertDictEqual(error_msg, expect_error_msg) def test_delete_topic(self): - self.client.post(self.url_prefix, json=self.topic1) + res = self.client.post(self.url_prefix, json=self.topic1) self.client.post(self.url_prefix, json=self.topic2) resp = self.client.get(self.url_prefix).json["data"] - self.assertEqual(2, len(resp)) + self.assertEqual(4, len(resp)) - self.client.delete(self.url_prefix + "/1") + self.client.delete(self.url_prefix + f"/{res.json['data']['id']}") resp = self.client.get(self.url_prefix).json["data"] - self.assertEqual(1, len(resp)) + self.assertLessEqual(1, len(resp)) def test_get_topic_with_some_posts(self): self.client.post("/api/topics", json={"name": "Topic1"}) # create topic self.client.post( - "/api/users", json={"email": "hrui8005@gmail.com", "password": "11Aa*%$#"} + "/api/users", json={"email": uuid4().hex + "hrui8005@gmail.com", "password": "11Aa*%$#"} ) # create user posts = {"user_id": 1, "content": "this is post1"} @@ -111,5 +118,4 @@ def test_get_topic_with_some_posts(self): resp = self.client.get(self.url_prefix).json["data"] - expect = [{'id': 1, 'name': 'Topic1', 'posts_count': 10}] - self.assertListEqual(expect, resp) + self.assertEqual(10, resp[0]["posts_count"]) diff --git a/tests/test_user.py b/tests/test_user.py index 615809f..4fedc75 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -1,5 +1,7 @@ from datetime import datetime +from uuid import uuid4 +from models.database import User from tests import BaseTestCase @@ -8,6 +10,10 @@ def setUp(self): super().setUp() self.url_prefix = "/api/users" + def tearDown(self) -> None: + with self.app.app_context(): + User.query.delete() + def test_add_user(self): exist_user = self.client.get(self.url_prefix).json["data"] self.assertEqual(len(exist_user), 1) @@ -22,7 +28,7 @@ def test_add_user(self): self.assertEqual(len(users), 2) def test_add_user_with_wrong_password(self): - new_user = {"email": "suepr76rui@icloud.com", "password": "111"} + new_user = {"email": uuid4().hex + "suepr76rui@icloud.com", "password": "111"} resp = self.client.post(self.url_prefix, json=new_user) error_msg = resp.json error_msg.pop("traceback") @@ -34,7 +40,7 @@ def test_add_user_with_wrong_password(self): self.assertDictEqual(error_msg, expect_error_msg) def test_add_user_with_wrong_email(self): - new_user = {"email": "suepr76ruiicloud.com", "password": "111"} + new_user = {"email": uuid4().hex + "suepr76ruiicloud.com", "password": "111"} resp = self.client.post(self.url_prefix, json=new_user) error_msg = resp.json @@ -54,7 +60,7 @@ def test_add_user_without_email(self): self.assertDictEqual(error_msg, expect_error_msg) def test_add_user_without_password(self): - new_user = {"email": "suepr76rui@icloud.com"} + new_user = {"email": uuid4().hex + "suepr76rui@icloud.com"} resp = self.client.post(self.url_prefix, json=new_user) error_msg = resp.json @@ -64,61 +70,30 @@ def test_add_user_without_password(self): self.assertDictEqual(error_msg, expect_error_msg) def test_add_user_with_duplicate_email(self): - new_user = {"email": "suepr76rui@icloud.com", "password": "b22sw1*#DJfyxoUaq"} + email = uuid4().hex + "suepr76rui@icloud.com" + new_user = {"email": email, "password": "b22sw1*#DJfyxoUaq"} self.client.post(self.url_prefix, json=new_user) users = self.client.get(self.url_prefix).json["data"] - self.assertEqual(len(users), 2) + self.assertEqual(len(users), 3) resp = self.client.post(self.url_prefix, json=new_user) error_msg = resp.json error_msg.pop("traceback") - expect_error_msg = {'error_code': 403, 'error_msg': '邮件为 的用户已经注册'} + expect_error_msg = {'error_code': 403, 'error_msg': f'邮件为 <{email}> 的用户已经注册'} self.assertDictEqual(error_msg, expect_error_msg) def test_update_user_email(self): - new_user = {"email": "suepr76rui@icloud.com", "password": "b22sw1*#DJfyxoUaq"} - self.client.post(self.url_prefix, json=new_user) + new_user = {"email": uuid4().hex + "suepr76rui@icloud.com", "password": "b22sw1*#DJfyxoUaq"} + res = self.client.post(self.url_prefix, json=new_user) kwargs = {"email": "hrui835@gmail.com"} - resp = self.client.put(self.url_prefix + "/1", json=kwargs) - - expect_user = { - 'About_me': None, - 'BTC_Address': None, - 'BattleTag': None, - 'Coding_net': None, - 'Dribbble': None, - 'Duolingo': None, - 'GitHub': None, - 'Goodreads': None, - 'Instagram': None, - 'Last_me': None, - 'PSN_ID': None, - 'Personal_Introduction': None, - 'Steam_ID': None, - 'Telegram': None, - 'Twitch': None, - 'Twitter': None, - 'avatar': None, - 'community_rich_rank': None, - 'company': None, - 'email': 'hrui835@gmail.com', - 'id': 1, - 'job': None, - 'location': None, - 'money': None, - 'name': None, - 'phone': None, - 'show_remain_money': None, - 'signature': None, - 'state_update_view_permission': None, - 'time_zone': None, - 'use_avatar_for_favicon': None, - 'use_high_resolution_avatar': None, - 'website': None, - } + user_id = res.json['data']['id'] + resp = self.client.put(self.url_prefix + f"/{user_id}", json=kwargs) + + expect_user = {'avatar': None, 'company': None, 'email': 'hrui835@gmail.com', 'id': user_id, 'job': None, + 'name': None, 'phone': None, 'website': None} update_user = resp.json["data"] self.assertEqual(200, resp.status_code) @@ -127,11 +102,13 @@ def test_update_user_email(self): self.assertDictEqual(update_user, expect_user) def test_update_user_with_wrong_email(self): - new_user = {"email": "suepr76rui@icloud.com", "password": "b22sw1*#DJfyxoUaq"} - self.client.post(self.url_prefix, json=new_user) + new_user = {"email":uuid4().hex+ "suepr76rui@icloud.com", "password": "b22sw1*#DJfyxoUaq"} + res = self.client.post(self.url_prefix, json=new_user) + + user_id = res.json['data']['id'] kwargs = {"email": "hrui835gmail.com"} - resp = self.client.put(self.url_prefix + "/1", json=kwargs) + resp = self.client.put(self.url_prefix + f"/{user_id}", json=kwargs) error_msg = resp.json error_msg.pop("traceback") @@ -140,11 +117,12 @@ def test_update_user_with_wrong_email(self): self.assertDictEqual(error_msg, expect_error_msg) def test_update_user_with_wrong_password(self): - new_user = {"email": "suepr76rui@icloud.com", "password": "b22sw1*#DJfyxoUaq"} - self.client.post(self.url_prefix, json=new_user) + new_user = {"email": uuid4().hex+ "suepr76rui@icloud.com", "password": "b22sw1*#DJfyxoUaq"} + res = self.client.post(self.url_prefix, json=new_user) + user_id = res.json['data']['id'] kwargs = {"password": "1111"} - resp = self.client.put(self.url_prefix + "/1", json=kwargs) + resp = self.client.put(self.url_prefix + f"/{user_id}", json=kwargs) error_msg = resp.json error_msg.pop("traceback") @@ -156,11 +134,11 @@ def test_update_user_with_wrong_password(self): self.assertDictEqual(error_msg, expect_error_msg) def test_delete(self): - new_user = {"email": "suepr76rui@icloud.com", "password": "b22sw1*#DJfyxoUaq"} + new_user = {"email": uuid4().hex + "suepr76rui@icloud.com", "password": "b22sw1*#DJfyxoUaq"} self.client.post(self.url_prefix, json=new_user) resp = self.client.get(self.url_prefix).json["data"] - self.assertEqual(2, len(resp)) + self.assertEqual(4, len(resp)) resp = self.client.delete(self.url_prefix + "/1") @@ -169,7 +147,7 @@ def test_delete(self): self.assertDictEqual(error_msg, expect_error_msg) resp = self.client.get(self.url_prefix).json["data"] - self.assertEqual(1, len(resp)) + self.assertEqual(3, len(resp)) def test_get_user_not_existed(self): resp = self.client.get(self.url_prefix + "/100") From 68fd7f392253782438fdc2bb17938f921606d2f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=95=E7=9D=BF?= Date: Thu, 7 Jan 2021 14:03:45 +0800 Subject: [PATCH 3/3] refactor(all): use pyruicore package, use typing --- .coveragerc | 2 +- .flake8 | 2 +- .pre-commit-config.yaml | 49 +- Dockerfile | 8 +- Makefile | 3 +- README.md | 14 +- _config.yml | 2 +- alembic/README | 2 +- alembic/env.py | 4 +- alembic/versions/14eb6f026d85_init.py | 276 +- app.py | 118 +- authentication/token.py | 4 +- blueprints/health.py | 2 +- configures/__init__.py | 2 +- configures/const.py | 12 +- configures/settings.py | 9 +- data.sql | 2 +- docker-compose.yml => docker-compose.yaml | 16 +- handlers/__init__.py | 8 +- handlers/comment.py | 4 +- handlers/post.py | 2 +- handlers/root_topic.py | 4 +- handlers/topic.py | 4 +- handlers/user.py | 6 +- handlers/utils.py | 18 +- .../access.log.2021-01-06 | 56 + .../server.log.2021-01-06 | 2478 +++++++++++++++++ models/base.py | 103 - models/data_types.py | 187 -- models/database/__init__.py | 19 - models/database/comment.py | 12 - models/database/post.py | 15 - models/database/user.py | 59 - models/orm/__init__.py | 19 + models/{database => orm}/base.py | 20 +- models/orm/comment.py | 11 + models/orm/post.py | 14 + models/{database => orm}/topic.py | 14 +- models/orm/user.py | 49 + models/query/__init__.py | 5 +- models/query/base.py | 54 +- models/query/comment.py | 15 +- models/query/post.py | 16 +- models/query/topic.py | 9 +- models/query/user.py | 24 +- models/response/__init__.py | 5 +- models/response/base.py | 9 +- models/response/comment.py | 19 +- models/response/post.py | 27 +- models/response/topic.py | 25 +- models/response/user.py | 29 +- pyproject.toml | 27 + requirements.txt | 6 +- resources/__init__.py | 9 +- resources/user.py | 12 +- scripts/entrypoint.sh | 13 + scripts/init_db.sh | 7 + server.py | 6 +- tests/__init__.py | 39 +- tests/test_comment.py | 60 +- tests/test_post.py | 59 +- tests/test_root_topic.py | 32 +- tests/test_topic.py | 60 +- tests/test_user.py | 56 +- 64 files changed, 3285 insertions(+), 967 deletions(-) rename docker-compose.yml => docker-compose.yaml (50%) create mode 100644 logs/RuiCoreMacBook-Pro.local/access.log.2021-01-06 create mode 100644 logs/RuiCoreMacBook-Pro.local/server.log.2021-01-06 delete mode 100644 models/base.py delete mode 100644 models/data_types.py delete mode 100644 models/database/__init__.py delete mode 100644 models/database/comment.py delete mode 100644 models/database/post.py delete mode 100644 models/database/user.py create mode 100644 models/orm/__init__.py rename models/{database => orm}/base.py (87%) create mode 100644 models/orm/comment.py create mode 100644 models/orm/post.py rename models/{database => orm}/topic.py (54%) create mode 100644 models/orm/user.py create mode 100644 pyproject.toml create mode 100644 scripts/entrypoint.sh create mode 100644 scripts/init_db.sh diff --git a/.coveragerc b/.coveragerc index 0a54d2f..a09dfa7 100644 --- a/.coveragerc +++ b/.coveragerc @@ -6,4 +6,4 @@ omit= */docs/ */venv/* */server.py - */authentication \ No newline at end of file + */authentication diff --git a/.flake8 b/.flake8 index ae5fb3e..076284f 100644 --- a/.flake8 +++ b/.flake8 @@ -1,3 +1,3 @@ [flake8] ignore = D203 -max-line-length=158 \ No newline at end of file +max-line-length=158 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8a3d7b4..48a9715 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,28 +1,37 @@ -exclude: | - (?x)( - ^venv/| - ^alembic/| - ^server.py - ) +default_language_version: + python: python3 + repos: - - repo: https://github.com/asottile/seed-isort-config - rev: v1.9.3 + - repo: https://github.com/commitizen-tools/commitizen + rev: v2.13.0 hooks: - - id: seed-isort-config - language_version: python3.8 - - repo: https://github.com/pre-commit/mirrors-isort - rev: v4.3.21 + - id: commitizen + stages: [ commit-msg ] + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.4.0 + hooks: + - id: check-added-large-files + - id: check-case-conflict + - id: check-docstring-first + - id: check-json + - id: check-merge-conflict + - id: check-yaml + - id: end-of-file-fixer + - id: fix-byte-order-marker + - id: trailing-whitespace + + - repo: https://github.com/timothycrosley/isort + rev: 5.7.0 hooks: - id: isort - args: [-rc] - language_version: python3.8 + - repo: https://github.com/ambv/black - rev: stable + rev: 20.8b1 hooks: - id: black - language_version: python3.8 - args: [--line-length=100, --skip-string-normalization] - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v1.2.3 + + - repo: https://gitlab.com/pycqa/flake8 + rev: 3.8.4 hooks: - - id: flake8 \ No newline at end of file + - id: flake8 diff --git a/Dockerfile b/Dockerfile index 1e9c8e7..d8d775b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,10 @@ WORKDIR /home/ruicore/flask_server COPY . /home/ruicore/flask_server -RUN pip3 install --upgrade pip && pip3 install -r requirements.txt +RUN pip3 install --upgrade pip && pip3 install -r requirements.txt -i https://pypi.douban.com/simple/ -LABEL ruicore="hrui835@gmail.com" version="v.0.0.1" \ No newline at end of file +RUN chmod +x ./scripts + +ENTRYPOINT ["bash","./scripts/entrypoint.sh"] + +LABEL ruicore="hrui835@gmail.com" version="v.0.0.1" diff --git a/Makefile b/Makefile index 376c353..6407925 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,6 @@ stop: test: coverage run -m pytest tests/ coverage report -m - + pre-com: pre-commit install - diff --git a/README.md b/README.md index 6ac612d..b976982 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ * You can run this project either using Docker **or** Shell. -1. install [docker](https://docs.docker.com/get-docker/). +1. install [docker](https://docs.docker.com/get-docker/). 2. cd to project directory, run ```make run ``` until it is finished. * ```make run ``` will build docker image, start server (Mysql for example). 3. run ```make init``` to initial database (create database, create table , **No data is imported**). @@ -69,7 +69,7 @@ python3.8 -m venv --clear venv source ./venv/bin/active ``` ```shell script -pip install -r requirements.txt +pip install -r requirements.txt ``` ```shell script python server.py @@ -88,8 +88,8 @@ python server.py ## 3. REST -* what is **REST** ? REST stands for **(Resource) Representational State Transfer**, it's a stateless communications protocol. The core concept of rest is **Resource**. In **REST** point of view, each concept that can be abstracted is called a Resource. Let's say properties ```name```, ```age```, ```email``` can be abstract as a User Model, so ```User``` can be represented as a Resource. -* **Transfer** means resources are transferred from the server-side to the client-side. +* what is **REST** ? REST stands for **(Resource) Representational State Transfer**, it's a stateless communications protocol. The core concept of rest is **Resource**. In **REST** point of view, each concept that can be abstracted is called a Resource. Let's say properties ```name```, ```age```, ```email``` can be abstract as a User Model, so ```User``` can be represented as a Resource. +* **Transfer** means resources are transferred from the server-side to the client-side. * In **REST** world, each operation is operated on Some kind of resource, and has pre-defined **Verb** to describe it. Such as **Post** means to create a resource, **Put** means to update a resource, **Delete** means to delete a resource. These three Verb is mainly used, you can check it out [here](https://realpython.com/flask-connexion-rest-api/) for more detail. ## 4. Benefits of Rest @@ -166,7 +166,7 @@ def schema(query_model: BaseQueryModel, response_model: ApiDataType): def wrapper(self, **kwargs) -> Callable: """Some logic """ # jsonify function is called here - return jsonify(func(self, **kwargs)) + return jsonify(func(self, **kwargs)) return wrapper @@ -212,7 +212,7 @@ def handle_exception(e) -> Tuple[Dict[str, Union[Union[int, str, list], Any]], U ## 7. Unified Query Model, Response Model and DataBaseModel -* In object-oriented programming, it's better to keep your arguments to be a single object rather than many separated args. It's so in Python and Flask. +* In object-oriented programming, it's better to keep your arguments to be a single object rather than many separated args. It's so in Python and Flask. * Let's say you want to query a user by its **name** and/or **age** and/or **email**, it's better to write: ```py @@ -286,7 +286,7 @@ class Base(db.Model, SurrogatePK): * **MVC(Model, View, Controller)** is a typical design pattern. This project is programmed in MVC pattern, but is not strictly stick to it. #### 9.1 Model ( Models in this project): Store information about resources -* Specifically, they are: +* Specifically, they are: * Database Model, **ORM** is in this folder, which transfers your python object to database rows. * QueryModel, arguments organized together as one Model. So frontend send args to backend, backend put them together to create a new object to do argument validation work, because use Model, some default functions can be bound to it. * ResponseModel, resources that are returned to the frontend. diff --git a/_config.yml b/_config.yml index c419263..277f1f2 100644 --- a/_config.yml +++ b/_config.yml @@ -1 +1 @@ -theme: jekyll-theme-cayman \ No newline at end of file +theme: jekyll-theme-cayman diff --git a/alembic/README b/alembic/README index 98e4f9c..2500aa1 100644 --- a/alembic/README +++ b/alembic/README @@ -1 +1 @@ -Generic single-database configuration. \ No newline at end of file +Generic single-database configuration. diff --git a/alembic/env.py b/alembic/env.py index c1fbfdb..16ec706 100644 --- a/alembic/env.py +++ b/alembic/env.py @@ -9,13 +9,13 @@ sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../") from configures.settings import SQLALCHEMY_DATABASE_URI # noqa -from models.database import Meta # noqa +from models.orm import Meta # noqa # this is the Alembic Config object, which provides # access to the values within the .ini file in use. config = context.config -config.set_main_option('sqlalchemy.url', SQLALCHEMY_DATABASE_URI) +config.set_main_option("sqlalchemy.url", SQLALCHEMY_DATABASE_URI) # Interpret the config file for Python logging. # This line sets up loggers basically. fileConfig(config.config_file_name) diff --git a/alembic/versions/14eb6f026d85_init.py b/alembic/versions/14eb6f026d85_init.py index 7dbadf8..9907e8a 100644 --- a/alembic/versions/14eb6f026d85_init.py +++ b/alembic/versions/14eb6f026d85_init.py @@ -1,16 +1,16 @@ """init Revision ID: 14eb6f026d85 -Revises: +Revises: Create Date: 2020-12-04 16:16:42.442007 """ -from alembic import op import sqlalchemy as sa +from alembic import op # revision identifiers, used by Alembic. -revision = '14eb6f026d85' +revision = "14eb6f026d85" down_revision = None branch_labels = None depends_on = None @@ -18,96 +18,202 @@ def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.create_table('comment', - sa.Column('create_time', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True, comment='创建时间'), - sa.Column('update_time', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True, comment='更新时间'), - sa.Column('deleted', sa.Boolean(), server_default=sa.text('false'), nullable=False, comment='是否被删除'), - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('user_id', sa.Integer(), nullable=False, comment='评论用户的 ID'), - sa.Column('post_id', sa.Integer(), nullable=False, comment='Post 文章的 ID'), - sa.Column('content', sa.Text(), nullable=False, comment='用户的评论'), - sa.PrimaryKeyConstraint('id') + op.create_table( + "comment", + sa.Column( + "create_time", + sa.DateTime(timezone=True), + server_default=sa.text("now()"), + nullable=True, + comment="创建时间", + ), + sa.Column( + "update_time", + sa.DateTime(timezone=True), + server_default=sa.text("now()"), + nullable=True, + comment="更新时间", + ), + sa.Column( + "deleted", + sa.Boolean(), + server_default=sa.text("false"), + nullable=False, + comment="是否被删除", + ), + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("user_id", sa.Integer(), nullable=False, comment="评论用户的 ID"), + sa.Column("post_id", sa.Integer(), nullable=False, comment="Post 文章的 ID"), + sa.Column("content", sa.Text(), nullable=False, comment="用户的评论"), + sa.PrimaryKeyConstraint("id"), ) - op.create_table('post', - sa.Column('create_time', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True, comment='创建时间'), - sa.Column('update_time', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True, comment='更新时间'), - sa.Column('deleted', sa.Boolean(), server_default=sa.text('false'), nullable=False, comment='是否被删除'), - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('topic_id', sa.Integer(), nullable=False, comment='文章所在的主题 ID'), - sa.Column('user_id', sa.Integer(), nullable=False, comment='发布文章用户的 ID'), - sa.Column('content', sa.Text(), nullable=False, comment='文章内容'), - sa.Column('click_times', sa.Integer(), server_default=sa.text('false'), nullable=True, comment='文章的点击数'), - sa.Column('tags', sa.JSON(), nullable=True, comment='文章的 tag'), - sa.PrimaryKeyConstraint('id') + op.create_table( + "post", + sa.Column( + "create_time", + sa.DateTime(timezone=True), + server_default=sa.text("now()"), + nullable=True, + comment="创建时间", + ), + sa.Column( + "update_time", + sa.DateTime(timezone=True), + server_default=sa.text("now()"), + nullable=True, + comment="更新时间", + ), + sa.Column( + "deleted", + sa.Boolean(), + server_default=sa.text("false"), + nullable=False, + comment="是否被删除", + ), + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("topic_id", sa.Integer(), nullable=False, comment="文章所在的主题 ID"), + sa.Column("user_id", sa.Integer(), nullable=False, comment="发布文章用户的 ID"), + sa.Column("content", sa.Text(), nullable=False, comment="文章内容"), + sa.Column( + "click_times", + sa.Integer(), + server_default=sa.text("false"), + nullable=True, + comment="文章的点击数", + ), + sa.Column("tags", sa.JSON(), nullable=True, comment="文章的 tag"), + sa.PrimaryKeyConstraint("id"), ) - op.create_table('root_topic', - sa.Column('create_time', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True, comment='创建时间'), - sa.Column('update_time', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True, comment='更新时间'), - sa.Column('deleted', sa.Boolean(), server_default=sa.text('false'), nullable=False, comment='是否被删除'), - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('name', sa.String(length=256), nullable=False), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('name') + op.create_table( + "root_topic", + sa.Column( + "create_time", + sa.DateTime(timezone=True), + server_default=sa.text("now()"), + nullable=True, + comment="创建时间", + ), + sa.Column( + "update_time", + sa.DateTime(timezone=True), + server_default=sa.text("now()"), + nullable=True, + comment="更新时间", + ), + sa.Column( + "deleted", + sa.Boolean(), + server_default=sa.text("false"), + nullable=False, + comment="是否被删除", + ), + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("name", sa.String(length=256), nullable=False), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("name"), ) - op.create_table('user', - sa.Column('create_time', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True, comment='创建时间'), - sa.Column('update_time', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True, comment='更新时间'), - sa.Column('deleted', sa.Boolean(), server_default=sa.text('false'), nullable=False, comment='是否被删除'), - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('email', sa.String(length=100), nullable=False), - sa.Column('password_hash', sa.String(length=256), nullable=False, comment='登陆密码 hash 之后的值'), - sa.Column('name', sa.String(length=100), nullable=True), - sa.Column('phone', sa.String(length=20), nullable=True, comment='电话号码'), - sa.Column('avatar', sa.String(length=256), nullable=True, comment='用户头像'), - sa.Column('website', sa.String(length=100), nullable=True, comment='个人网站'), - sa.Column('company', sa.String(length=100), nullable=True, comment='所在公司'), - sa.Column('job', sa.String(length=100), nullable=True, comment='职位'), - sa.Column('location', sa.String(length=100), nullable=True, comment='所在地'), - sa.Column('signature', sa.String(length=256), nullable=True, comment='签名'), - sa.Column('dribbble', sa.String(length=256), nullable=True, comment='Dribbble'), - sa.Column('duolingo', sa.String(length=256), nullable=True, comment='Duolingo'), - sa.Column('about_me', sa.String(length=256), nullable=True, comment='About.me'), - sa.Column('last_me', sa.String(length=256), nullable=True, comment='Last.fm'), - sa.Column('good_reads', sa.String(length=256), nullable=True, comment='Goodreads'), - sa.Column('github', sa.String(length=256), nullable=True, comment='GitHub'), - sa.Column('psn_id', sa.String(length=256), nullable=True, comment='PSN ID'), - sa.Column('stream_id', sa.String(length=256), nullable=True, comment='Steam_ID'), - sa.Column('twitch', sa.String(length=256), nullable=True, comment='Twitch'), - sa.Column('battle_tag', sa.String(length=256), nullable=True, comment='BattleTag'), - sa.Column('instagram', sa.String(length=256), nullable=True, comment='Instagram'), - sa.Column('telegram', sa.String(length=256), nullable=True, comment='Telegram'), - sa.Column('twitter', sa.String(length=256), nullable=True, comment='Twitter'), - sa.Column('btc_address', sa.String(length=256), nullable=True, comment='BTC Address'), - sa.Column('coding_net', sa.String(length=256), nullable=True, comment='Coding.net'), - sa.Column('personal_introduction', sa.String(length=256), nullable=True, comment='个人简介'), - sa.Column('state_update_view_permission', sa.Integer(), nullable=True, comment='状态更新查看权限'), - sa.Column('community_rich_rank', sa.Boolean(), nullable=True, comment='社区财富排行榜'), - sa.Column('money', sa.Integer(), nullable=True, comment='余额'), - sa.Column('show_remain_money', sa.Boolean(), nullable=True, comment='是否显示余额'), - sa.Column('use_avatar_for_favicon', sa.Boolean(), nullable=True, comment='使用节点头像作为页面 favicon'), - sa.Column('use_high_resolution_avatar', sa.Boolean(), nullable=True, comment='使用高精度头像'), - sa.Column('time_zone', sa.String(length=256), nullable=True, comment='默认使用的时区'), - sa.PrimaryKeyConstraint('id') + op.create_table( + "user", + sa.Column( + "create_time", + sa.DateTime(timezone=True), + server_default=sa.text("now()"), + nullable=True, + comment="创建时间", + ), + sa.Column( + "update_time", + sa.DateTime(timezone=True), + server_default=sa.text("now()"), + nullable=True, + comment="更新时间", + ), + sa.Column( + "deleted", + sa.Boolean(), + server_default=sa.text("false"), + nullable=False, + comment="是否被删除", + ), + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("email", sa.String(length=100), nullable=False), + sa.Column("password_hash", sa.String(length=256), nullable=False, comment="登陆密码 hash 之后的值"), + sa.Column("name", sa.String(length=100), nullable=True), + sa.Column("phone", sa.String(length=20), nullable=True, comment="电话号码"), + sa.Column("avatar", sa.String(length=256), nullable=True, comment="用户头像"), + sa.Column("website", sa.String(length=100), nullable=True, comment="个人网站"), + sa.Column("company", sa.String(length=100), nullable=True, comment="所在公司"), + sa.Column("job", sa.String(length=100), nullable=True, comment="职位"), + sa.Column("location", sa.String(length=100), nullable=True, comment="所在地"), + sa.Column("signature", sa.String(length=256), nullable=True, comment="签名"), + sa.Column("dribbble", sa.String(length=256), nullable=True, comment="Dribbble"), + sa.Column("duolingo", sa.String(length=256), nullable=True, comment="Duolingo"), + sa.Column("about_me", sa.String(length=256), nullable=True, comment="About.me"), + sa.Column("last_me", sa.String(length=256), nullable=True, comment="Last.fm"), + sa.Column("good_reads", sa.String(length=256), nullable=True, comment="Goodreads"), + sa.Column("github", sa.String(length=256), nullable=True, comment="GitHub"), + sa.Column("psn_id", sa.String(length=256), nullable=True, comment="PSN ID"), + sa.Column("stream_id", sa.String(length=256), nullable=True, comment="Steam_ID"), + sa.Column("twitch", sa.String(length=256), nullable=True, comment="Twitch"), + sa.Column("battle_tag", sa.String(length=256), nullable=True, comment="BattleTag"), + sa.Column("instagram", sa.String(length=256), nullable=True, comment="Instagram"), + sa.Column("telegram", sa.String(length=256), nullable=True, comment="Telegram"), + sa.Column("twitter", sa.String(length=256), nullable=True, comment="Twitter"), + sa.Column("btc_address", sa.String(length=256), nullable=True, comment="BTC Address"), + sa.Column("coding_net", sa.String(length=256), nullable=True, comment="Coding.net"), + sa.Column("personal_introduction", sa.String(length=256), nullable=True, comment="个人简介"), + sa.Column("state_update_view_permission", sa.Integer(), nullable=True, comment="状态更新查看权限"), + sa.Column("community_rich_rank", sa.Boolean(), nullable=True, comment="社区财富排行榜"), + sa.Column("money", sa.Integer(), nullable=True, comment="余额"), + sa.Column("show_remain_money", sa.Boolean(), nullable=True, comment="是否显示余额"), + sa.Column( + "use_avatar_for_favicon", sa.Boolean(), nullable=True, comment="使用节点头像作为页面 favicon" + ), + sa.Column("use_high_resolution_avatar", sa.Boolean(), nullable=True, comment="使用高精度头像"), + sa.Column("time_zone", sa.String(length=256), nullable=True, comment="默认使用的时区"), + sa.PrimaryKeyConstraint("id"), ) - op.create_table('topic', - sa.Column('create_time', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True, comment='创建时间'), - sa.Column('update_time', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True, comment='更新时间'), - sa.Column('deleted', sa.Boolean(), server_default=sa.text('false'), nullable=False, comment='是否被删除'), - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('name', sa.String(length=256), nullable=False), - sa.Column('root_topic_id', sa.Integer(), server_default=sa.text('1'), nullable=True), - sa.ForeignKeyConstraint(['root_topic_id'], ['root_topic.id'], ), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('name') + op.create_table( + "topic", + sa.Column( + "create_time", + sa.DateTime(timezone=True), + server_default=sa.text("now()"), + nullable=True, + comment="创建时间", + ), + sa.Column( + "update_time", + sa.DateTime(timezone=True), + server_default=sa.text("now()"), + nullable=True, + comment="更新时间", + ), + sa.Column( + "deleted", + sa.Boolean(), + server_default=sa.text("false"), + nullable=False, + comment="是否被删除", + ), + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("name", sa.String(length=256), nullable=False), + sa.Column("root_topic_id", sa.Integer(), server_default=sa.text("1"), nullable=True), + sa.ForeignKeyConstraint( + ["root_topic_id"], + ["root_topic.id"], + ), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("name"), ) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('topic') - op.drop_table('user') - op.drop_table('root_topic') - op.drop_table('post') - op.drop_table('comment') + op.drop_table("topic") + op.drop_table("user") + op.drop_table("root_topic") + op.drop_table("post") + op.drop_table("comment") # ### end Alembic commands ### diff --git a/app.py b/app.py index 4fed882..936900a 100644 --- a/app.py +++ b/app.py @@ -5,21 +5,24 @@ import socket import traceback import types -from exceptions.exceptions import ServerException -from exceptions.send_alert import send_dingding_alert from logging.config import dictConfig from typing import Any, Dict, Tuple, Union from flask import Flask, request from flask_cors import CORS +from pyruicore import BaseModel from werkzeug.exceptions import HTTPException from blueprints import all_blueprints from configures import settings -from models.base import BaseModel -from models.database import db +from exceptions.exceptions import ServerException +from exceptions.send_alert import send_dingding_alert +from models.orm import db from resources import ApiResponse +# from models.base import BaseModel + + logger = logging.getLogger(__name__) @@ -30,7 +33,7 @@ def default(self, value) -> Any: if isinstance(value, ApiResponse): return value.get() if isinstance(value, BaseModel): - return value.marshal() + return value.dict() if isinstance(value, types.GeneratorType): return [self.default(v) for v in value] @@ -49,7 +52,7 @@ def create_app() -> Flask: def init_config(app) -> None: app.config["SQLALCHEMY_DATABASE_URI"] = settings.SQLALCHEMY_DATABASE_URI app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = settings.SQLALCHEMY_TRACK_MODIFICATIONS - app.config['SECRET_KEY'] = settings.SECRET_KEY + app.config["SECRET_KEY"] = settings.SECRET_KEY register_blueprints(app) app.register_error_handler(Exception, handle_exception) @@ -74,73 +77,74 @@ def handle_exception(e) -> Tuple[Dict[str, Union[Union[int, str, list], Any]], U exc = [v for v in traceback.format_exc(limit=10).split("\n")] if str(code) == "500": send_dingding_alert(request.url, request.args, request.json, repr(e), exc) - return {'error_code': code, 'error_msg': str(e), 'traceback': exc}, code + return {"error_code": code, "error_msg": str(e), "traceback": exc}, code def init_logging() -> None: - level = 'INFO' if settings.NAMESPACE == 'PRODUCTION' else 'DEBUG' + level = "INFO" if settings.NAMESPACE == "PRODUCTION" else "DEBUG" dir_name = "./logs/{}".format(socket.gethostname()) if not os.path.exists(dir_name): os.makedirs(dir_name) config = { - 'version': 1, - 'disable_existing_loggers': False, - 'formatters': { - 'brief': {'format': '%(message)s'}, - 'standard': { - 'format': '[%(asctime)s] [%(levelname)s] [%(filename)s.%(funcName)s:%(lineno)3d] [%(process)d::%(thread)d] %(message)s' + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "brief": {"format": "%(message)s"}, + "standard": { + "format": "[%(asctime)s] [%(levelname)s] [%(filename)s.%(funcName)s:%(lineno)3d] [%(process)d::%(" + "thread)d] %(message)s " }, - 'colored': { - '()': 'colorlog.ColoredFormatter', - 'format': "%(log_color)s%(asctime)s - %(levelname)s - %(message)s", - 'datefmt': '%Y-%m-%d %H:%M:%S', - 'log_colors': { - 'DEBUG': 'cyan', - 'INFO': 'green', - 'WARNING': 'yellow', - 'ERROR': 'red', - 'CRITICAL': 'red,bg_white', + "colored": { + "()": "colorlog.ColoredFormatter", + "format": "%(log_color)s%(asctime)s - %(levelname)s - %(message)s", + "datefmt": "%Y-%m-%d %H:%M:%S", + "log_colors": { + "DEBUG": "cyan", + "INFO": "green", + "WARNING": "yellow", + "ERROR": "red", + "CRITICAL": "red,bg_white", }, }, }, - 'handlers': { - 'default': { - 'level': level, - 'formatter': 'standard', - 'class': 'logging.handlers.TimedRotatingFileHandler', - 'filename': '{}/server.log'.format(dir_name), - 'when': 'midnight', - 'interval': 1, - 'encoding': 'utf8', + "handlers": { + "default": { + "level": level, + "formatter": "standard", + "class": "logging.handlers.TimedRotatingFileHandler", + "filename": "{}/server.log".format(dir_name), + "when": "midnight", + "interval": 1, + "encoding": "utf8", }, - 'console': {'level': level, 'formatter': 'colored', 'class': 'logging.StreamHandler'}, - 'default_access': { - 'level': level, - 'formatter': 'brief', - 'class': 'logging.handlers.TimedRotatingFileHandler', - 'filename': '{}/access.log'.format(dir_name), - 'when': 'midnight', - 'interval': 1, - 'encoding': 'utf8', + "console": {"level": level, "formatter": "colored", "class": "logging.StreamHandler"}, + "default_access": { + "level": level, + "formatter": "brief", + "class": "logging.handlers.TimedRotatingFileHandler", + "filename": "{}/access.log".format(dir_name), + "when": "midnight", + "interval": 1, + "encoding": "utf8", }, - 'console_access': { - 'level': level, - 'formatter': 'colored', - 'class': 'logging.StreamHandler', + "console_access": { + "level": level, + "formatter": "colored", + "class": "logging.StreamHandler", }, }, - 'loggers': { - 'werkzeug': { - 'handlers': ['default_access', 'console_access'], - 'level': level, - 'propagate': False, + "loggers": { + "werkzeug": { + "handlers": ["default_access", "console_access"], + "level": level, + "propagate": False, }, - '': { - 'handlers': ['default', 'console'], - 'formatter': 'colored', - 'level': level, - 'propagate': True, + "": { + "handlers": ["default", "console"], + "formatter": "colored", + "level": level, + "propagate": True, }, }, } @@ -151,7 +155,7 @@ def patch_wsgi_handler(): """ from gevent.pywsgi import WSGIHandler - logger = logging.getLogger('werkzeug') + logger = logging.getLogger("werkzeug") def log_request(self): logger.info(WSGIHandler.format_request(self)) diff --git a/authentication/token.py b/authentication/token.py index d6c4030..e940714 100644 --- a/authentication/token.py +++ b/authentication/token.py @@ -4,7 +4,7 @@ from flask_httpauth import HTTPBasicAuth, HTTPTokenAuth, MultiAuth from flask_restful import Resource -from models.database import User +from models.orm import User basic_auth = HTTPBasicAuth() token_auth = HTTPTokenAuth() @@ -15,7 +15,7 @@ class Token(Resource): @auth.login_required def get(self): token = g.user.generate_auth_token() - return {'token': token.decode('ascii')} + return {"token": token.decode("ascii")} @basic_auth.verify_password diff --git a/blueprints/health.py b/blueprints/health.py index 5802ae8..dc67a9c 100644 --- a/blueprints/health.py +++ b/blueprints/health.py @@ -14,7 +14,7 @@ def get(self) -> Tuple[Dict[str, None], int]: return {"favicon": None}, 200 -health_bp = Blueprint("health", __name__, url_prefix='') +health_bp = Blueprint("health", __name__, url_prefix="") api = Api(health_bp) api.add_resource(HealthState, "/health") diff --git a/configures/__init__.py b/configures/__init__.py index 3352258..8c0541c 100644 --- a/configures/__init__.py +++ b/configures/__init__.py @@ -15,4 +15,4 @@ def member_values(cls) -> ValuesView[T]: @classmethod def to_doc(cls) -> str: - return ';'.join([':'.join([str(ins.name), str(ins.value)]) for ins in cls]) + return ";".join([":".join([str(ins.name), str(ins.value)]) for ins in cls]) diff --git a/configures/const.py b/configures/const.py index d141197..7bdc6cd 100644 --- a/configures/const.py +++ b/configures/const.py @@ -5,12 +5,12 @@ BLANK = "" DATE_FORMATS = [ - '%Y-%m-%dT%H:%M:%S', - '%Y-%m-%dT%H:%M:%S.%f', - '%Y-%m-%d %H:%M:%S.%f', - '%Y-%m-%d %H:%M:%S', - '%Y-%m-%d', - '%H:%M:%S', + "%Y-%m-%dT%H:%M:%S", + "%Y-%m-%dT%H:%M:%S.%f", + "%Y-%m-%d %H:%M:%S.%f", + "%Y-%m-%d %H:%M:%S", + "%Y-%m-%d", + "%H:%M:%S", ] diff --git a/configures/settings.py b/configures/settings.py index 0bcc81e..ff87469 100644 --- a/configures/settings.py +++ b/configures/settings.py @@ -1,15 +1,10 @@ NAMESPACE = "PRODUCTION" SECRET_KEY = "fbca22c2a2ed11ea91b488e9fe4e9d33" date_format = "%Y-%m-%d %H:%M:%S" -SQLALCHEMY_DATABASE_URI = "mysql+pymysql://root:ROOT@mysql/flask_restful?charset=UTF8MB4" -SQLALCHEMY_DATABASE_BASE = "mysql+pymysql://root:ROOT@mysql/" +SQLALCHEMY_DATABASE_URI = "mysql+pymysql://root:@mysql/flask_restful?charset=UTF8MB4" +SQLALCHEMY_DATABASE_BASE = "mysql+pymysql://root:@mysql/" SQLALCHEMY_TRACK_MODIFICATIONS = False TEST_SQLALCHEMY_DATABASE = "mysql+pymysql://root:@localhost/" TEST_SQLALCHEMY_DATABASE_URI = "mysql+pymysql://root:@localhost/TEST?charset=UTF8MB4" TEST_DATABASE = "TEST" - -LOCAL_SQLALCHEMY_DATABASE_URI = ( - "mysql+pymysql://root:ROOT@localhost:3307/flask_restful?charset=UTF8MB4" -) -LOCAL_SQLALCHEMY_DATABASE_BASE = "mysql+pymysql://root:ROOT@localhost:3307/" diff --git a/data.sql b/data.sql index 22b9122..74a3c01 100644 --- a/data.sql +++ b/data.sql @@ -227,4 +227,4 @@ INSERT INTO flask_restful.user (id, email, password_hash, name, phone, avatar, w INSERT INTO flask_restful.user (id, email, password_hash, name, phone, avatar, website, company, job, location, signature, Dribbble, Duolingo, About_me, Last_me, Goodreads, GitHub, PSN_ID, Steam_ID, Twitch, BattleTag, Instagram, Telegram, Twitter, BTC_Address, Coding_net, Personal_Introduction, state_update_view_permission, community_rich_rank, money, show_remain_money, use_avatar_for_favicon, use_high_resolution_avatar, time_zone, create_time, update_time, deleted) VALUES (7, 'hrui807@gmail.com', 'pbkdf2:sha256:150000$jNvWqpHs$fb8dfa8cbd9dc83ff1f7479bea5738c36cce9cefa559d740c108fc3a2b009211', null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, 0, 0, 0, 0, 0, 0, 'utc', '2020-05-22 13:42:16', '2020-05-22 13:42:16', 0); INSERT INTO flask_restful.user (id, email, password_hash, name, phone, avatar, website, company, job, location, signature, Dribbble, Duolingo, About_me, Last_me, Goodreads, GitHub, PSN_ID, Steam_ID, Twitch, BattleTag, Instagram, Telegram, Twitter, BTC_Address, Coding_net, Personal_Introduction, state_update_view_permission, community_rich_rank, money, show_remain_money, use_avatar_for_favicon, use_high_resolution_avatar, time_zone, create_time, update_time, deleted) VALUES (8, 'hrui808@gmail.com', 'pbkdf2:sha256:150000$TkWV3eUG$b92e1b9cc9dcb3df5900cf09fef5c782dd3ed171128dad7a66aba0e4790460b2', null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, 0, 0, 0, 0, 0, 0, 'utc', '2020-05-22 13:42:20', '2020-05-22 13:42:20', 0); INSERT INTO flask_restful.user (id, email, password_hash, name, phone, avatar, website, company, job, location, signature, Dribbble, Duolingo, About_me, Last_me, Goodreads, GitHub, PSN_ID, Steam_ID, Twitch, BattleTag, Instagram, Telegram, Twitter, BTC_Address, Coding_net, Personal_Introduction, state_update_view_permission, community_rich_rank, money, show_remain_money, use_avatar_for_favicon, use_high_resolution_avatar, time_zone, create_time, update_time, deleted) VALUES (9, 'hrui809@gmail.com', 'pbkdf2:sha256:150000$7CNvv2XG$c9d01092175d823dc1b3f4e1aa16a5c20fd32ae451339726f8187b1a8bf59406', null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, 0, 0, 0, 0, 0, 0, 'utc', '2020-05-22 13:42:23', '2020-05-22 13:42:23', 0); -INSERT INTO flask_restful.user (id, email, password_hash, name, phone, avatar, website, company, job, location, signature, Dribbble, Duolingo, About_me, Last_me, Goodreads, GitHub, PSN_ID, Steam_ID, Twitch, BattleTag, Instagram, Telegram, Twitter, BTC_Address, Coding_net, Personal_Introduction, state_update_view_permission, community_rich_rank, money, show_remain_money, use_avatar_for_favicon, use_high_resolution_avatar, time_zone, create_time, update_time, deleted) VALUES (10, 'hrui810@gmail.com', 'pbkdf2:sha256:150000$zfFnQiUh$2382855a69d1b6585daa75e4278d9d176c58267ac67e9c7d40128df2176eea7a', null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, 0, 0, 0, 0, 0, 0, 'utc', '2020-05-22 13:42:27', '2020-05-22 13:42:27', 0); \ No newline at end of file +INSERT INTO flask_restful.user (id, email, password_hash, name, phone, avatar, website, company, job, location, signature, Dribbble, Duolingo, About_me, Last_me, Goodreads, GitHub, PSN_ID, Steam_ID, Twitch, BattleTag, Instagram, Telegram, Twitter, BTC_Address, Coding_net, Personal_Introduction, state_update_view_permission, community_rich_rank, money, show_remain_money, use_avatar_for_favicon, use_high_resolution_avatar, time_zone, create_time, update_time, deleted) VALUES (10, 'hrui810@gmail.com', 'pbkdf2:sha256:150000$zfFnQiUh$2382855a69d1b6585daa75e4278d9d176c58267ac67e9c7d40128df2176eea7a', null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, 0, 0, 0, 0, 0, 0, 'utc', '2020-05-22 13:42:27', '2020-05-22 13:42:27', 0); diff --git a/docker-compose.yml b/docker-compose.yaml similarity index 50% rename from docker-compose.yml rename to docker-compose.yaml index 0868c07..64900e9 100644 --- a/docker-compose.yml +++ b/docker-compose.yaml @@ -6,19 +6,17 @@ services: build: . container_name: flask ports: - - "24579:24579" + - 24579:24579 depends_on: - mysql command: ["python", "server.py"] mysql: - image: mysql:latest - + image: mysql:8.0 ports: - - "3307:3306" - - container_name: flask_db_mysql - + - 3307:3306 + container_name: flask_mysql environment: - - MYSQL_ROOT_PASSWORD=ROOT - - MYSQL_DATABASE=flask_restful + MYSQL_USER: root + MYSQL_ALLOW_EMPTY_PASSWORD: 1 + MYSQL_DATABASE: flask_restful diff --git a/handlers/__init__.py b/handlers/__init__.py index 14b5406..b63eddb 100644 --- a/handlers/__init__.py +++ b/handlers/__init__.py @@ -1,9 +1,9 @@ -from exceptions import exceptions from typing import Type, TypeVar -from models.database import Base as Model +from exceptions import exceptions +from models.orm import Base as Model -U = TypeVar('U', bound=Model) +U = TypeVar("U", bound=Model) class BaseHandler: @@ -20,6 +20,6 @@ def assert_id_is_not_none(self) -> None: def _get_sqlalchemy_instance(self) -> Type[U]: self.assert_id_is_not_none() instance = self._model.get_by_id(self.id) - if not instance or getattr(instance, 'deleted', False): + if not instance or getattr(instance, "deleted", False): raise exceptions.ObjectsNotExist(self.error_msg) return instance diff --git a/handlers/comment.py b/handlers/comment.py index 30c1b34..35b7335 100644 --- a/handlers/comment.py +++ b/handlers/comment.py @@ -1,12 +1,12 @@ -from exceptions.exceptions import ActionNotAllowed, ArgumentInvalid, ObjectsNotExist from typing import Generator, Optional from sqlalchemy import and_ from configures.const import PER_PAGE +from exceptions.exceptions import ActionNotAllowed, ArgumentInvalid, ObjectsNotExist from handlers import BaseHandler from handlers.utils import ContentChecker -from models.database import Comment, Post, Topic, User +from models.orm import Comment, Post, Topic, User from models.response import ResponseCommentModel diff --git a/handlers/post.py b/handlers/post.py index a1b9b32..3006b36 100644 --- a/handlers/post.py +++ b/handlers/post.py @@ -5,7 +5,7 @@ from configures.const import POST_MINIMUM_WORDS from exceptions.exceptions import ArgumentInvalid, ObjectsNotExist from handlers import BaseHandler -from models.database import Comment, Post, Topic, User +from models.orm import Comment, Post, Topic, User from models.response import ResponsePostModel diff --git a/handlers/root_topic.py b/handlers/root_topic.py index 8205706..0c9507f 100644 --- a/handlers/root_topic.py +++ b/handlers/root_topic.py @@ -1,13 +1,13 @@ -from exceptions.exceptions import ActionNotAllowed, ObjectsDuplicated from itertools import groupby from typing import Generator, List, TypeVar from flask_sqlalchemy import SQLAlchemy from sqlalchemy import and_ +from exceptions.exceptions import ActionNotAllowed, ObjectsDuplicated from handlers import BaseHandler from handlers.utils import assert_name_is_valid -from models.database import RootTopic, Topic +from models.orm import RootTopic, Topic from models.response import RootTopicResponseModel, TopicResponseModel SqlAlchemyModel = TypeVar("SqlAlchemyModel", bound=SQLAlchemy) diff --git a/handlers/topic.py b/handlers/topic.py index 42073aa..2df2b39 100644 --- a/handlers/topic.py +++ b/handlers/topic.py @@ -1,11 +1,11 @@ -from exceptions.exceptions import ObjectsDuplicated from typing import Generator, Optional from sqlalchemy import and_ +from exceptions.exceptions import ObjectsDuplicated from handlers import BaseHandler from handlers.utils import assert_name_is_valid -from models.database import Post, Topic +from models.orm import Post, Topic from models.response import TopicResponseModel diff --git a/handlers/user.py b/handlers/user.py index 08d9797..5288c5c 100644 --- a/handlers/user.py +++ b/handlers/user.py @@ -1,11 +1,11 @@ -from exceptions.exceptions import ArgumentInvalid, ArgumentRequired, ObjectsDuplicated from typing import Generator, Optional from werkzeug.security import generate_password_hash +from exceptions.exceptions import ArgumentInvalid, ArgumentRequired, ObjectsDuplicated from handlers import BaseHandler from handlers.utils import EmailChecker, PassWordChecker -from models.database import User +from models.orm import User from models.response import ResponseUserModel as ResponseUser @@ -79,7 +79,7 @@ def delete(self) -> None: return -if __name__ == '__main__': +if __name__ == "__main__": from app import create_app app = create_app() diff --git a/handlers/utils.py b/handlers/utils.py index f2aceb3..1518b52 100644 --- a/handlers/utils.py +++ b/handlers/utils.py @@ -1,13 +1,13 @@ import datetime import re -from exceptions import exceptions from configures.const import DATE_FORMATS +from exceptions import exceptions def str_to_datetime(value): ret = None - if value and value not in ['', 'null', 'None']: + if value and value not in ["", "null", "None"]: for format_ in DATE_FORMATS: try: ret = datetime.datetime.strptime(value, format_) @@ -28,8 +28,8 @@ class BaseChecker: # 字母数字下划线 \w == 【A-Za-z0-9_】 # 中文 \u4e00-\u9fa5 - ALLOWED_PATTERN = r'^[\S]+$' - ERROR_MSG = '不允许出现空白字符' + ALLOWED_PATTERN = r"^[\S]+$" + ERROR_MSG = "不允许出现空白字符" @classmethod def is_allowed(cls, value: str) -> bool: @@ -40,7 +40,7 @@ def is_allowed(cls, value: str) -> bool: class EmailChecker(BaseChecker): ALLOWED_PATTERN = r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)" - ERROR_MSG = '邮箱格式错误' + ERROR_MSG = "邮箱格式错误" class PassWordChecker(BaseChecker): @@ -49,13 +49,13 @@ class PassWordChecker(BaseChecker): class NameChecker(BaseChecker): - ALLOWED_PATTERN = '^[\\w\u4e00-\u9fa5-]+$' - ERROR_MSG = '名称中只允许出现【中文,英文,数字,下划线,连接符】,并且不允许全部是空白字符' + ALLOWED_PATTERN = "^[\\w\u4e00-\u9fa5-]+$" + ERROR_MSG = "名称中只允许出现【中文,英文,数字,下划线,连接符】,并且不允许全部是空白字符" class ContentChecker(BaseChecker): - ALLOWED_PATTERN = r'[\S]+' - ERROR_MSG = '不允许全部是空白字符,即至少有一个非空白字符' + ALLOWED_PATTERN = r"[\S]+" + ERROR_MSG = "不允许全部是空白字符,即至少有一个非空白字符" def assert_name_is_valid(message="名称不能为空", **kwargs) -> None: diff --git a/logs/RuiCoreMacBook-Pro.local/access.log.2021-01-06 b/logs/RuiCoreMacBook-Pro.local/access.log.2021-01-06 new file mode 100644 index 0000000..270ed6b --- /dev/null +++ b/logs/RuiCoreMacBook-Pro.local/access.log.2021-01-06 @@ -0,0 +1,56 @@ +127.0.0.1 - - [2021-01-06 14:22:49] "GET /health HTTP/1.1" 200 124 0.002716 +127.0.0.1 - - [2021-01-06 14:22:50] "GET /favicon.ico HTTP/1.1" 200 126 0.003468 +127.0.0.1 - - [2021-01-06 14:22:59] "GET /api/users HTTP/1.1" 500 4170 1.089105 +127.0.0.1 - - [2021-01-06 14:22:59] "GET /favicon.ico HTTP/1.1" 200 126 0.001336 +127.0.0.1 - - [2021-01-06 14:30:04] "GET /api/users HTTP/1.1" 500 4170 0.140576 +127.0.0.1 - - [2021-01-06 14:30:05] "GET /favicon.ico HTTP/1.1" 200 126 0.002086 +127.0.0.1 - - [2021-01-06 14:30:30] "GET /api/users HTTP/1.1" 500 4170 0.250579 +127.0.0.1 - - [2021-01-06 14:30:30] "GET /favicon.ico HTTP/1.1" 200 126 0.001418 +127.0.0.1 - - [2021-01-06 16:28:51] "GET /api/users HTTP/1.1" 500 4170 0.549814 +127.0.0.1 - - [2021-01-06 16:30:54] "GET /api/users HTTP/1.1" 401 202 1.201979 +127.0.0.1 - - [2021-01-06 16:30:57] "GET /api/users HTTP/1.1" 401 202 0.025876 +127.0.0.1 - - [2021-01-06 16:30:58] "GET /favicon.ico HTTP/1.1" 200 126 0.002528 +127.0.0.1 - - [2021-01-06 16:31:35] "GET /api/users HTTP/1.1" 401 202 0.055805 +127.0.0.1 - - [2021-01-06 16:31:44] "GET /api/users HTTP/1.1" 401 202 0.019883 +127.0.0.1 - - [2021-01-06 16:33:05] "POST /api/users HTTP/1.1" 400 1638 0.044996 +127.0.0.1 - - [2021-01-06 16:33:49] "POST /api/users HTTP/1.1" 403 1738 0.199815 +127.0.0.1 - - [2021-01-06 16:34:05] "GET /api/users HTTP/1.1" 401 202 0.248872 +127.0.0.1 - - [2021-01-06 16:34:55] "GET /api/users HTTP/1.1" 401 202 0.061353 +127.0.0.1 - - [2021-01-06 16:35:08] "POST /api/users HTTP/1.1" 400 1969 0.159630 +127.0.0.1 - - [2021-01-06 16:35:21] "POST /api/users HTTP/1.1" 400 1969 0.055215 +127.0.0.1 - - [2021-01-06 16:35:29] "POST /api/users HTTP/1.1" 200 348 0.331935 +127.0.0.1 - - [2021-01-06 16:35:40] "GET /api/users HTTP/1.1" 200 350 0.184350 +127.0.0.1 - - [2021-01-06 16:35:41] "GET /favicon.ico HTTP/1.1" 200 126 0.007134 +127.0.0.1 - - [2021-01-06 16:35:52] "GET /api/users HTTP/1.1" 200 350 0.149906 +127.0.0.1 - - [2021-01-06 16:35:52] "GET /favicon.ico HTTP/1.1" 200 126 0.001209 +127.0.0.1 - - [2021-01-06 16:58:11] "POST /api/users HTTP/1.1" 500 1539 0.038695 +127.0.0.1 - - [2021-01-06 17:01:22] "POST /api/users HTTP/1.1" 500 2348 182.648962 +127.0.0.1 - - [2021-01-06 17:02:55] "POST /api/users HTTP/1.1" 500 1539 0.078823 +127.0.0.1 - - [2021-01-06 17:05:13] "POST /api/users HTTP/1.1" 500 2348 132.257309 +127.0.0.1 - - [2021-01-06 17:06:54] "POST /api/users HTTP/1.1" 500 1695 0.032085 +127.0.0.1 - - [2021-01-06 17:14:23] "POST /api/users HTTP/1.1" 500 2348 0.684950 +127.0.0.1 - - [2021-01-06 17:40:11] "POST /api/users HTTP/1.1" 500 129 1838.147879 +127.0.0.1 - - [2021-01-06 17:40:58] "POST /api/users HTTP/1.1" 500 1695 0.017549 +127.0.0.1 - - [2021-01-06 17:47:44] "POST /api/users HTTP/1.1" 403 1738 64.385008 +127.0.0.1 - - [2021-01-06 17:49:46] "POST /api/users HTTP/1.1" 500 1695 0.058590 +127.0.0.1 - - [2021-01-06 17:50:10] "POST /api/users HTTP/1.1" 403 1738 0.260191 +127.0.0.1 - - [2021-01-06 17:50:12] "POST /api/users HTTP/1.1" 403 1738 0.014625 +127.0.0.1 - - [2021-01-06 17:50:25] "POST /api/users HTTP/1.1" 200 347 0.392264 +127.0.0.1 - - [2021-01-06 17:51:44] "POST /api/users HTTP/1.1" 500 1695 0.046934 +127.0.0.1 - - [2021-01-06 17:54:39] "POST /api/users HTTP/1.1" 403 1736 0.315544 +127.0.0.1 - - [2021-01-06 17:54:47] "POST /api/users HTTP/1.1" 200 348 0.487224 +127.0.0.1 - - [2021-01-06 17:54:53] "POST /api/users HTTP/1.1" 403 1738 0.055116 +127.0.0.1 - - [2021-01-06 17:58:34] "POST /api/users HTTP/1.1" 403 1738 0.046078 +127.0.0.1 - - [2021-01-06 17:58:41] "POST /api/users HTTP/1.1" 200 352 0.215494 +127.0.0.1 - - [2021-01-06 17:59:07] "POST /api/users HTTP/1.1" 500 1695 0.017983 +127.0.0.1 - - [2021-01-06 18:02:41] "POST /api/users HTTP/1.1" 403 1746 173.820006 +127.0.0.1 - - [2021-01-06 18:03:55] "POST /api/users HTTP/1.1" 403 1746 71.309596 +127.0.0.1 - - [2021-01-06 18:06:22] "POST /api/users HTTP/1.1" 400 1638 4.090289 +127.0.0.1 - - [2021-01-06 18:06:37] "POST /api/users HTTP/1.1" 400 1638 3.023580 +127.0.0.1 - - [2021-01-06 18:07:37] "POST /api/users HTTP/1.1" 400 1638 3.509860 +127.0.0.1 - - [2021-01-06 18:08:03] "POST /api/users HTTP/1.1" 400 1638 13.526056 +127.0.0.1 - - [2021-01-06 18:11:23] "POST /api/users HTTP/1.1" 500 161 70.077389 +127.0.0.1 - - [2021-01-06 18:13:29] "POST /api/users HTTP/1.1" 500 161 116.825455 +127.0.0.1 - - [2021-01-06 18:22:45] "POST /api/users HTTP/1.1" 400 1638 18.005465 +127.0.0.1 - - [2021-01-06 18:25:27] "POST /api/users HTTP/1.1" 500 1549 20.679069 +127.0.0.1 - - [2021-01-06 18:28:33] "POST /api/users HTTP/1.1" 500 161 176.005479 diff --git a/logs/RuiCoreMacBook-Pro.local/server.log.2021-01-06 b/logs/RuiCoreMacBook-Pro.local/server.log.2021-01-06 new file mode 100644 index 0000000..f630f34 --- /dev/null +++ b/logs/RuiCoreMacBook-Pro.local/server.log.2021-01-06 @@ -0,0 +1,2478 @@ +[2021-01-06 14:19:04,951] [INFO] [server.py.run: 18] [9572::140480822679680] Server start at 0.0.0.0:24579 +[2021-01-06 14:20:39,025] [INFO] [server.py.run: 18] [12921::140703875766400] Server start at 0.0.0.0:24579 +[2021-01-06 14:22:41,286] [INFO] [server.py.run: 18] [16818::140632916531328] Server start at 0.0.0.0:24579 +[2021-01-06 14:22:59,154] [ERROR] [app.py.log_exception:1891] [16818::140632938555856] Exception on /api/users [GET] +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2336, in _wrap_pool_connect + return fn() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 364, in connect + return _ConnectionFairy._checkout(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 778, in _checkout + fairy = _ConnectionRecord.checkout(pool) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 495, in checkout + rec = pool._do_get() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/impl.py", line 140, in _do_get + self._dec_overflow() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__ + compat.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/impl.py", line 137, in _do_get + return self._create_connection() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 309, in _create_connection + return _ConnectionRecord(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 440, in __init__ + self.__connect(first_connect_check=True) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 661, in __connect + pool.logger.debug("Error on connect(): %s", e) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__ + compat.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 656, in __connect + connection = pool._invoke_creator(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/strategies.py", line 114, in connect + return dialect.connect(*cargs, **cparams) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/default.py", line 493, in connect + return self.dbapi.connect(*cargs, **cparams) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/__init__.py", line 94, in Connect + return Connection(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 327, in __init__ + self.connect() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 588, in connect + self._request_authentication() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 874, in _request_authentication + auth_packet = _auth.caching_sha2_password_auth(self, auth_packet) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/_auth.py", line 327, in caching_sha2_password_auth + pkt = _roundtrip(conn, data) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/_auth.py", line 181, in _roundtrip + pkt = conn._read_packet() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 676, in _read_packet + packet.raise_for_error() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/protocol.py", line 223, in raise_for_error + err.raise_mysql_exception(self._data) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/err.py", line 107, in raise_mysql_exception + raise errorclass(errno, errval) +pymysql.err.OperationalError: (1045, "Access denied for user 'root'@'172.25.0.1' (using password: YES)") + +The above exception was the direct cause of the following exception: + +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_httpauth.py", line 380, in decorated + return selected_auth.login_required(role=role, + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_httpauth.py", line 149, in decorated + user = self.authenticate(auth, password) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_httpauth.py", line 220, in authenticate + return self.verify_password_callback(username, client_password) + File "/Users/herui/Desktop/flask-restful/authentication/token.py", line 23, in verify_password + user = User.query.filter_by(email=username).first() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3429, in first + ret = list(self[0:1]) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3203, in __getitem__ + return list(res) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3535, in __iter__ + return self._execute_and_instances(context) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3556, in _execute_and_instances + conn = self._get_bind_args( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3571, in _get_bind_args + return fn( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3550, in _connection_from_session + conn = self.session.connection(**kw) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 1138, in connection + return self._connection_for_bind( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 1146, in _connection_for_bind + return self.transaction._connection_for_bind( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 433, in _connection_for_bind + conn = bind._contextual_connect() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2302, in _contextual_connect + self._wrap_pool_connect(self.pool.connect, None), + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2339, in _wrap_pool_connect + Connection._handle_dbapi_exception_noconnection( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 1583, in _handle_dbapi_exception_noconnection + util.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2336, in _wrap_pool_connect + return fn() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 364, in connect + return _ConnectionFairy._checkout(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 778, in _checkout + fairy = _ConnectionRecord.checkout(pool) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 495, in checkout + rec = pool._do_get() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/impl.py", line 140, in _do_get + self._dec_overflow() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__ + compat.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/impl.py", line 137, in _do_get + return self._create_connection() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 309, in _create_connection + return _ConnectionRecord(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 440, in __init__ + self.__connect(first_connect_check=True) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 661, in __connect + pool.logger.debug("Error on connect(): %s", e) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__ + compat.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 656, in __connect + connection = pool._invoke_creator(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/strategies.py", line 114, in connect + return dialect.connect(*cargs, **cparams) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/default.py", line 493, in connect + return self.dbapi.connect(*cargs, **cparams) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/__init__.py", line 94, in Connect + return Connection(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 327, in __init__ + self.connect() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 588, in connect + self._request_authentication() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 874, in _request_authentication + auth_packet = _auth.caching_sha2_password_auth(self, auth_packet) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/_auth.py", line 327, in caching_sha2_password_auth + pkt = _roundtrip(conn, data) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/_auth.py", line 181, in _roundtrip + pkt = conn._read_packet() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 676, in _read_packet + packet.raise_for_error() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/protocol.py", line 223, in raise_for_error + err.raise_mysql_exception(self._data) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/err.py", line 107, in raise_mysql_exception + raise errorclass(errno, errval) +sqlalchemy.exc.OperationalError: (pymysql.err.OperationalError) (1045, "Access denied for user 'root'@'172.25.0.1' (using password: YES)") +(Background on this error at: http://sqlalche.me/e/13/e3q8) +[2021-01-06 14:22:59,187] [ERROR] [app.py.handle_exception: 73] [16818::140632938555856] (pymysql.err.OperationalError) (1045, "Access denied for user 'root'@'172.25.0.1' (using password: YES)") +(Background on this error at: http://sqlalche.me/e/13/e3q8) +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2336, in _wrap_pool_connect + return fn() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 364, in connect + return _ConnectionFairy._checkout(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 778, in _checkout + fairy = _ConnectionRecord.checkout(pool) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 495, in checkout + rec = pool._do_get() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/impl.py", line 140, in _do_get + self._dec_overflow() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__ + compat.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/impl.py", line 137, in _do_get + return self._create_connection() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 309, in _create_connection + return _ConnectionRecord(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 440, in __init__ + self.__connect(first_connect_check=True) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 661, in __connect + pool.logger.debug("Error on connect(): %s", e) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__ + compat.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 656, in __connect + connection = pool._invoke_creator(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/strategies.py", line 114, in connect + return dialect.connect(*cargs, **cparams) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/default.py", line 493, in connect + return self.dbapi.connect(*cargs, **cparams) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/__init__.py", line 94, in Connect + return Connection(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 327, in __init__ + self.connect() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 588, in connect + self._request_authentication() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 874, in _request_authentication + auth_packet = _auth.caching_sha2_password_auth(self, auth_packet) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/_auth.py", line 327, in caching_sha2_password_auth + pkt = _roundtrip(conn, data) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/_auth.py", line 181, in _roundtrip + pkt = conn._read_packet() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 676, in _read_packet + packet.raise_for_error() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/protocol.py", line 223, in raise_for_error + err.raise_mysql_exception(self._data) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/err.py", line 107, in raise_mysql_exception + raise errorclass(errno, errval) +pymysql.err.OperationalError: (1045, "Access denied for user 'root'@'172.25.0.1' (using password: YES)") + +The above exception was the direct cause of the following exception: + +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_httpauth.py", line 380, in decorated + return selected_auth.login_required(role=role, + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_httpauth.py", line 149, in decorated + user = self.authenticate(auth, password) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_httpauth.py", line 220, in authenticate + return self.verify_password_callback(username, client_password) + File "/Users/herui/Desktop/flask-restful/authentication/token.py", line 23, in verify_password + user = User.query.filter_by(email=username).first() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3429, in first + ret = list(self[0:1]) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3203, in __getitem__ + return list(res) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3535, in __iter__ + return self._execute_and_instances(context) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3556, in _execute_and_instances + conn = self._get_bind_args( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3571, in _get_bind_args + return fn( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3550, in _connection_from_session + conn = self.session.connection(**kw) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 1138, in connection + return self._connection_for_bind( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 1146, in _connection_for_bind + return self.transaction._connection_for_bind( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 433, in _connection_for_bind + conn = bind._contextual_connect() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2302, in _contextual_connect + self._wrap_pool_connect(self.pool.connect, None), + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2339, in _wrap_pool_connect + Connection._handle_dbapi_exception_noconnection( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 1583, in _handle_dbapi_exception_noconnection + util.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2336, in _wrap_pool_connect + return fn() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 364, in connect + return _ConnectionFairy._checkout(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 778, in _checkout + fairy = _ConnectionRecord.checkout(pool) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 495, in checkout + rec = pool._do_get() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/impl.py", line 140, in _do_get + self._dec_overflow() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__ + compat.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/impl.py", line 137, in _do_get + return self._create_connection() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 309, in _create_connection + return _ConnectionRecord(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 440, in __init__ + self.__connect(first_connect_check=True) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 661, in __connect + pool.logger.debug("Error on connect(): %s", e) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__ + compat.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 656, in __connect + connection = pool._invoke_creator(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/strategies.py", line 114, in connect + return dialect.connect(*cargs, **cparams) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/default.py", line 493, in connect + return self.dbapi.connect(*cargs, **cparams) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/__init__.py", line 94, in Connect + return Connection(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 327, in __init__ + self.connect() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 588, in connect + self._request_authentication() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 874, in _request_authentication + auth_packet = _auth.caching_sha2_password_auth(self, auth_packet) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/_auth.py", line 327, in caching_sha2_password_auth + pkt = _roundtrip(conn, data) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/_auth.py", line 181, in _roundtrip + pkt = conn._read_packet() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 676, in _read_packet + packet.raise_for_error() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/protocol.py", line 223, in raise_for_error + err.raise_mysql_exception(self._data) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/err.py", line 107, in raise_mysql_exception + raise errorclass(errno, errval) +sqlalchemy.exc.OperationalError: (pymysql.err.OperationalError) (1045, "Access denied for user 'root'@'172.25.0.1' (using password: YES)") +(Background on this error at: http://sqlalche.me/e/13/e3q8) +[2021-01-06 14:30:04,818] [ERROR] [app.py.log_exception:1891] [16818::140632938555856] Exception on /api/users [GET] +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2336, in _wrap_pool_connect + return fn() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 364, in connect + return _ConnectionFairy._checkout(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 778, in _checkout + fairy = _ConnectionRecord.checkout(pool) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 495, in checkout + rec = pool._do_get() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/impl.py", line 140, in _do_get + self._dec_overflow() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__ + compat.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/impl.py", line 137, in _do_get + return self._create_connection() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 309, in _create_connection + return _ConnectionRecord(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 440, in __init__ + self.__connect(first_connect_check=True) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 661, in __connect + pool.logger.debug("Error on connect(): %s", e) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__ + compat.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 656, in __connect + connection = pool._invoke_creator(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/strategies.py", line 114, in connect + return dialect.connect(*cargs, **cparams) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/default.py", line 493, in connect + return self.dbapi.connect(*cargs, **cparams) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/__init__.py", line 94, in Connect + return Connection(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 327, in __init__ + self.connect() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 588, in connect + self._request_authentication() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 874, in _request_authentication + auth_packet = _auth.caching_sha2_password_auth(self, auth_packet) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/_auth.py", line 327, in caching_sha2_password_auth + pkt = _roundtrip(conn, data) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/_auth.py", line 181, in _roundtrip + pkt = conn._read_packet() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 676, in _read_packet + packet.raise_for_error() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/protocol.py", line 223, in raise_for_error + err.raise_mysql_exception(self._data) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/err.py", line 107, in raise_mysql_exception + raise errorclass(errno, errval) +pymysql.err.OperationalError: (1045, "Access denied for user 'root'@'172.25.0.1' (using password: YES)") + +The above exception was the direct cause of the following exception: + +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_httpauth.py", line 380, in decorated + return selected_auth.login_required(role=role, + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_httpauth.py", line 149, in decorated + user = self.authenticate(auth, password) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_httpauth.py", line 220, in authenticate + return self.verify_password_callback(username, client_password) + File "/Users/herui/Desktop/flask-restful/authentication/token.py", line 23, in verify_password + user = User.query.filter_by(email=username).first() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3429, in first + ret = list(self[0:1]) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3203, in __getitem__ + return list(res) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3535, in __iter__ + return self._execute_and_instances(context) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3556, in _execute_and_instances + conn = self._get_bind_args( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3571, in _get_bind_args + return fn( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3550, in _connection_from_session + conn = self.session.connection(**kw) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 1138, in connection + return self._connection_for_bind( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 1146, in _connection_for_bind + return self.transaction._connection_for_bind( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 433, in _connection_for_bind + conn = bind._contextual_connect() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2302, in _contextual_connect + self._wrap_pool_connect(self.pool.connect, None), + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2339, in _wrap_pool_connect + Connection._handle_dbapi_exception_noconnection( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 1583, in _handle_dbapi_exception_noconnection + util.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2336, in _wrap_pool_connect + return fn() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 364, in connect + return _ConnectionFairy._checkout(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 778, in _checkout + fairy = _ConnectionRecord.checkout(pool) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 495, in checkout + rec = pool._do_get() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/impl.py", line 140, in _do_get + self._dec_overflow() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__ + compat.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/impl.py", line 137, in _do_get + return self._create_connection() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 309, in _create_connection + return _ConnectionRecord(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 440, in __init__ + self.__connect(first_connect_check=True) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 661, in __connect + pool.logger.debug("Error on connect(): %s", e) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__ + compat.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 656, in __connect + connection = pool._invoke_creator(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/strategies.py", line 114, in connect + return dialect.connect(*cargs, **cparams) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/default.py", line 493, in connect + return self.dbapi.connect(*cargs, **cparams) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/__init__.py", line 94, in Connect + return Connection(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 327, in __init__ + self.connect() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 588, in connect + self._request_authentication() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 874, in _request_authentication + auth_packet = _auth.caching_sha2_password_auth(self, auth_packet) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/_auth.py", line 327, in caching_sha2_password_auth + pkt = _roundtrip(conn, data) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/_auth.py", line 181, in _roundtrip + pkt = conn._read_packet() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 676, in _read_packet + packet.raise_for_error() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/protocol.py", line 223, in raise_for_error + err.raise_mysql_exception(self._data) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/err.py", line 107, in raise_mysql_exception + raise errorclass(errno, errval) +sqlalchemy.exc.OperationalError: (pymysql.err.OperationalError) (1045, "Access denied for user 'root'@'172.25.0.1' (using password: YES)") +(Background on this error at: http://sqlalche.me/e/13/e3q8) +[2021-01-06 14:30:04,828] [ERROR] [app.py.handle_exception: 73] [16818::140632938555856] (pymysql.err.OperationalError) (1045, "Access denied for user 'root'@'172.25.0.1' (using password: YES)") +(Background on this error at: http://sqlalche.me/e/13/e3q8) +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2336, in _wrap_pool_connect + return fn() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 364, in connect + return _ConnectionFairy._checkout(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 778, in _checkout + fairy = _ConnectionRecord.checkout(pool) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 495, in checkout + rec = pool._do_get() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/impl.py", line 140, in _do_get + self._dec_overflow() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__ + compat.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/impl.py", line 137, in _do_get + return self._create_connection() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 309, in _create_connection + return _ConnectionRecord(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 440, in __init__ + self.__connect(first_connect_check=True) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 661, in __connect + pool.logger.debug("Error on connect(): %s", e) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__ + compat.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 656, in __connect + connection = pool._invoke_creator(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/strategies.py", line 114, in connect + return dialect.connect(*cargs, **cparams) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/default.py", line 493, in connect + return self.dbapi.connect(*cargs, **cparams) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/__init__.py", line 94, in Connect + return Connection(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 327, in __init__ + self.connect() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 588, in connect + self._request_authentication() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 874, in _request_authentication + auth_packet = _auth.caching_sha2_password_auth(self, auth_packet) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/_auth.py", line 327, in caching_sha2_password_auth + pkt = _roundtrip(conn, data) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/_auth.py", line 181, in _roundtrip + pkt = conn._read_packet() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 676, in _read_packet + packet.raise_for_error() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/protocol.py", line 223, in raise_for_error + err.raise_mysql_exception(self._data) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/err.py", line 107, in raise_mysql_exception + raise errorclass(errno, errval) +pymysql.err.OperationalError: (1045, "Access denied for user 'root'@'172.25.0.1' (using password: YES)") + +The above exception was the direct cause of the following exception: + +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_httpauth.py", line 380, in decorated + return selected_auth.login_required(role=role, + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_httpauth.py", line 149, in decorated + user = self.authenticate(auth, password) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_httpauth.py", line 220, in authenticate + return self.verify_password_callback(username, client_password) + File "/Users/herui/Desktop/flask-restful/authentication/token.py", line 23, in verify_password + user = User.query.filter_by(email=username).first() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3429, in first + ret = list(self[0:1]) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3203, in __getitem__ + return list(res) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3535, in __iter__ + return self._execute_and_instances(context) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3556, in _execute_and_instances + conn = self._get_bind_args( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3571, in _get_bind_args + return fn( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3550, in _connection_from_session + conn = self.session.connection(**kw) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 1138, in connection + return self._connection_for_bind( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 1146, in _connection_for_bind + return self.transaction._connection_for_bind( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 433, in _connection_for_bind + conn = bind._contextual_connect() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2302, in _contextual_connect + self._wrap_pool_connect(self.pool.connect, None), + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2339, in _wrap_pool_connect + Connection._handle_dbapi_exception_noconnection( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 1583, in _handle_dbapi_exception_noconnection + util.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2336, in _wrap_pool_connect + return fn() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 364, in connect + return _ConnectionFairy._checkout(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 778, in _checkout + fairy = _ConnectionRecord.checkout(pool) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 495, in checkout + rec = pool._do_get() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/impl.py", line 140, in _do_get + self._dec_overflow() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__ + compat.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/impl.py", line 137, in _do_get + return self._create_connection() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 309, in _create_connection + return _ConnectionRecord(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 440, in __init__ + self.__connect(first_connect_check=True) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 661, in __connect + pool.logger.debug("Error on connect(): %s", e) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__ + compat.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 656, in __connect + connection = pool._invoke_creator(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/strategies.py", line 114, in connect + return dialect.connect(*cargs, **cparams) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/default.py", line 493, in connect + return self.dbapi.connect(*cargs, **cparams) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/__init__.py", line 94, in Connect + return Connection(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 327, in __init__ + self.connect() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 588, in connect + self._request_authentication() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 874, in _request_authentication + auth_packet = _auth.caching_sha2_password_auth(self, auth_packet) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/_auth.py", line 327, in caching_sha2_password_auth + pkt = _roundtrip(conn, data) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/_auth.py", line 181, in _roundtrip + pkt = conn._read_packet() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 676, in _read_packet + packet.raise_for_error() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/protocol.py", line 223, in raise_for_error + err.raise_mysql_exception(self._data) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/err.py", line 107, in raise_mysql_exception + raise errorclass(errno, errval) +sqlalchemy.exc.OperationalError: (pymysql.err.OperationalError) (1045, "Access denied for user 'root'@'172.25.0.1' (using password: YES)") +(Background on this error at: http://sqlalche.me/e/13/e3q8) +[2021-01-06 14:30:27,402] [INFO] [server.py.run: 18] [31059::140455723964544] Server start at 0.0.0.0:24579 +[2021-01-06 14:30:30,319] [ERROR] [app.py.log_exception:1891] [31059::140455745988528] Exception on /api/users [GET] +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2336, in _wrap_pool_connect + return fn() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 364, in connect + return _ConnectionFairy._checkout(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 778, in _checkout + fairy = _ConnectionRecord.checkout(pool) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 495, in checkout + rec = pool._do_get() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/impl.py", line 140, in _do_get + self._dec_overflow() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__ + compat.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/impl.py", line 137, in _do_get + return self._create_connection() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 309, in _create_connection + return _ConnectionRecord(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 440, in __init__ + self.__connect(first_connect_check=True) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 661, in __connect + pool.logger.debug("Error on connect(): %s", e) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__ + compat.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 656, in __connect + connection = pool._invoke_creator(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/strategies.py", line 114, in connect + return dialect.connect(*cargs, **cparams) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/default.py", line 493, in connect + return self.dbapi.connect(*cargs, **cparams) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/__init__.py", line 94, in Connect + return Connection(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 327, in __init__ + self.connect() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 588, in connect + self._request_authentication() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 874, in _request_authentication + auth_packet = _auth.caching_sha2_password_auth(self, auth_packet) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/_auth.py", line 327, in caching_sha2_password_auth + pkt = _roundtrip(conn, data) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/_auth.py", line 181, in _roundtrip + pkt = conn._read_packet() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 676, in _read_packet + packet.raise_for_error() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/protocol.py", line 223, in raise_for_error + err.raise_mysql_exception(self._data) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/err.py", line 107, in raise_mysql_exception + raise errorclass(errno, errval) +pymysql.err.OperationalError: (1045, "Access denied for user 'root'@'172.25.0.1' (using password: YES)") + +The above exception was the direct cause of the following exception: + +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_httpauth.py", line 380, in decorated + return selected_auth.login_required(role=role, + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_httpauth.py", line 149, in decorated + user = self.authenticate(auth, password) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_httpauth.py", line 220, in authenticate + return self.verify_password_callback(username, client_password) + File "/Users/herui/Desktop/flask-restful/authentication/token.py", line 23, in verify_password + user = User.query.filter_by(email=username).first() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3429, in first + ret = list(self[0:1]) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3203, in __getitem__ + return list(res) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3535, in __iter__ + return self._execute_and_instances(context) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3556, in _execute_and_instances + conn = self._get_bind_args( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3571, in _get_bind_args + return fn( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3550, in _connection_from_session + conn = self.session.connection(**kw) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 1138, in connection + return self._connection_for_bind( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 1146, in _connection_for_bind + return self.transaction._connection_for_bind( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 433, in _connection_for_bind + conn = bind._contextual_connect() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2302, in _contextual_connect + self._wrap_pool_connect(self.pool.connect, None), + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2339, in _wrap_pool_connect + Connection._handle_dbapi_exception_noconnection( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 1583, in _handle_dbapi_exception_noconnection + util.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2336, in _wrap_pool_connect + return fn() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 364, in connect + return _ConnectionFairy._checkout(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 778, in _checkout + fairy = _ConnectionRecord.checkout(pool) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 495, in checkout + rec = pool._do_get() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/impl.py", line 140, in _do_get + self._dec_overflow() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__ + compat.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/impl.py", line 137, in _do_get + return self._create_connection() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 309, in _create_connection + return _ConnectionRecord(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 440, in __init__ + self.__connect(first_connect_check=True) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 661, in __connect + pool.logger.debug("Error on connect(): %s", e) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__ + compat.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 656, in __connect + connection = pool._invoke_creator(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/strategies.py", line 114, in connect + return dialect.connect(*cargs, **cparams) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/default.py", line 493, in connect + return self.dbapi.connect(*cargs, **cparams) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/__init__.py", line 94, in Connect + return Connection(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 327, in __init__ + self.connect() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 588, in connect + self._request_authentication() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 874, in _request_authentication + auth_packet = _auth.caching_sha2_password_auth(self, auth_packet) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/_auth.py", line 327, in caching_sha2_password_auth + pkt = _roundtrip(conn, data) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/_auth.py", line 181, in _roundtrip + pkt = conn._read_packet() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 676, in _read_packet + packet.raise_for_error() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/protocol.py", line 223, in raise_for_error + err.raise_mysql_exception(self._data) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/err.py", line 107, in raise_mysql_exception + raise errorclass(errno, errval) +sqlalchemy.exc.OperationalError: (pymysql.err.OperationalError) (1045, "Access denied for user 'root'@'172.25.0.1' (using password: YES)") +(Background on this error at: http://sqlalche.me/e/13/e3q8) +[2021-01-06 14:30:30,344] [ERROR] [app.py.handle_exception: 73] [31059::140455745988528] (pymysql.err.OperationalError) (1045, "Access denied for user 'root'@'172.25.0.1' (using password: YES)") +(Background on this error at: http://sqlalche.me/e/13/e3q8) +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2336, in _wrap_pool_connect + return fn() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 364, in connect + return _ConnectionFairy._checkout(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 778, in _checkout + fairy = _ConnectionRecord.checkout(pool) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 495, in checkout + rec = pool._do_get() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/impl.py", line 140, in _do_get + self._dec_overflow() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__ + compat.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/impl.py", line 137, in _do_get + return self._create_connection() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 309, in _create_connection + return _ConnectionRecord(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 440, in __init__ + self.__connect(first_connect_check=True) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 661, in __connect + pool.logger.debug("Error on connect(): %s", e) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__ + compat.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 656, in __connect + connection = pool._invoke_creator(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/strategies.py", line 114, in connect + return dialect.connect(*cargs, **cparams) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/default.py", line 493, in connect + return self.dbapi.connect(*cargs, **cparams) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/__init__.py", line 94, in Connect + return Connection(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 327, in __init__ + self.connect() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 588, in connect + self._request_authentication() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 874, in _request_authentication + auth_packet = _auth.caching_sha2_password_auth(self, auth_packet) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/_auth.py", line 327, in caching_sha2_password_auth + pkt = _roundtrip(conn, data) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/_auth.py", line 181, in _roundtrip + pkt = conn._read_packet() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 676, in _read_packet + packet.raise_for_error() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/protocol.py", line 223, in raise_for_error + err.raise_mysql_exception(self._data) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/err.py", line 107, in raise_mysql_exception + raise errorclass(errno, errval) +pymysql.err.OperationalError: (1045, "Access denied for user 'root'@'172.25.0.1' (using password: YES)") + +The above exception was the direct cause of the following exception: + +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_httpauth.py", line 380, in decorated + return selected_auth.login_required(role=role, + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_httpauth.py", line 149, in decorated + user = self.authenticate(auth, password) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_httpauth.py", line 220, in authenticate + return self.verify_password_callback(username, client_password) + File "/Users/herui/Desktop/flask-restful/authentication/token.py", line 23, in verify_password + user = User.query.filter_by(email=username).first() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3429, in first + ret = list(self[0:1]) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3203, in __getitem__ + return list(res) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3535, in __iter__ + return self._execute_and_instances(context) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3556, in _execute_and_instances + conn = self._get_bind_args( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3571, in _get_bind_args + return fn( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3550, in _connection_from_session + conn = self.session.connection(**kw) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 1138, in connection + return self._connection_for_bind( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 1146, in _connection_for_bind + return self.transaction._connection_for_bind( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 433, in _connection_for_bind + conn = bind._contextual_connect() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2302, in _contextual_connect + self._wrap_pool_connect(self.pool.connect, None), + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2339, in _wrap_pool_connect + Connection._handle_dbapi_exception_noconnection( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 1583, in _handle_dbapi_exception_noconnection + util.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2336, in _wrap_pool_connect + return fn() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 364, in connect + return _ConnectionFairy._checkout(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 778, in _checkout + fairy = _ConnectionRecord.checkout(pool) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 495, in checkout + rec = pool._do_get() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/impl.py", line 140, in _do_get + self._dec_overflow() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__ + compat.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/impl.py", line 137, in _do_get + return self._create_connection() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 309, in _create_connection + return _ConnectionRecord(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 440, in __init__ + self.__connect(first_connect_check=True) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 661, in __connect + pool.logger.debug("Error on connect(): %s", e) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__ + compat.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 656, in __connect + connection = pool._invoke_creator(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/strategies.py", line 114, in connect + return dialect.connect(*cargs, **cparams) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/default.py", line 493, in connect + return self.dbapi.connect(*cargs, **cparams) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/__init__.py", line 94, in Connect + return Connection(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 327, in __init__ + self.connect() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 588, in connect + self._request_authentication() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 874, in _request_authentication + auth_packet = _auth.caching_sha2_password_auth(self, auth_packet) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/_auth.py", line 327, in caching_sha2_password_auth + pkt = _roundtrip(conn, data) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/_auth.py", line 181, in _roundtrip + pkt = conn._read_packet() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 676, in _read_packet + packet.raise_for_error() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/protocol.py", line 223, in raise_for_error + err.raise_mysql_exception(self._data) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/err.py", line 107, in raise_mysql_exception + raise errorclass(errno, errval) +sqlalchemy.exc.OperationalError: (pymysql.err.OperationalError) (1045, "Access denied for user 'root'@'172.25.0.1' (using password: YES)") +(Background on this error at: http://sqlalche.me/e/13/e3q8) +[2021-01-06 16:28:10,381] [INFO] [server.py.run: 18] [36899::140611080984704] Server start at 0.0.0.0:24579 +[2021-01-06 16:28:34,857] [INFO] [server.py.run: 18] [37768::140576335370368] Server start at 0.0.0.0:24579 +[2021-01-06 16:28:38,376] [INFO] [server.py.run: 18] [37773::140397372807296] Server start at 0.0.0.0:24579 +[2021-01-06 16:28:47,592] [INFO] [server.py.run: 18] [37780::140203176532096] Server start at 0.0.0.0:24579 +[2021-01-06 16:28:51,010] [ERROR] [app.py.log_exception:1891] [37780::140203198556624] Exception on /api/users [GET] +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2336, in _wrap_pool_connect + return fn() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 364, in connect + return _ConnectionFairy._checkout(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 778, in _checkout + fairy = _ConnectionRecord.checkout(pool) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 495, in checkout + rec = pool._do_get() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/impl.py", line 140, in _do_get + self._dec_overflow() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__ + compat.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/impl.py", line 137, in _do_get + return self._create_connection() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 309, in _create_connection + return _ConnectionRecord(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 440, in __init__ + self.__connect(first_connect_check=True) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 661, in __connect + pool.logger.debug("Error on connect(): %s", e) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__ + compat.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 656, in __connect + connection = pool._invoke_creator(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/strategies.py", line 114, in connect + return dialect.connect(*cargs, **cparams) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/default.py", line 493, in connect + return self.dbapi.connect(*cargs, **cparams) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/__init__.py", line 94, in Connect + return Connection(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 327, in __init__ + self.connect() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 588, in connect + self._request_authentication() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 874, in _request_authentication + auth_packet = _auth.caching_sha2_password_auth(self, auth_packet) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/_auth.py", line 327, in caching_sha2_password_auth + pkt = _roundtrip(conn, data) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/_auth.py", line 181, in _roundtrip + pkt = conn._read_packet() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 676, in _read_packet + packet.raise_for_error() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/protocol.py", line 223, in raise_for_error + err.raise_mysql_exception(self._data) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/err.py", line 107, in raise_mysql_exception + raise errorclass(errno, errval) +pymysql.err.OperationalError: (1045, "Access denied for user 'root'@'172.25.0.1' (using password: YES)") + +The above exception was the direct cause of the following exception: + +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_httpauth.py", line 380, in decorated + return selected_auth.login_required(role=role, + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_httpauth.py", line 149, in decorated + user = self.authenticate(auth, password) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_httpauth.py", line 220, in authenticate + return self.verify_password_callback(username, client_password) + File "/Users/herui/Desktop/flask-restful/authentication/token.py", line 23, in verify_password + user = User.query.filter_by(email=username).first() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3429, in first + ret = list(self[0:1]) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3203, in __getitem__ + return list(res) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3535, in __iter__ + return self._execute_and_instances(context) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3556, in _execute_and_instances + conn = self._get_bind_args( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3571, in _get_bind_args + return fn( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3550, in _connection_from_session + conn = self.session.connection(**kw) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 1138, in connection + return self._connection_for_bind( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 1146, in _connection_for_bind + return self.transaction._connection_for_bind( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 433, in _connection_for_bind + conn = bind._contextual_connect() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2302, in _contextual_connect + self._wrap_pool_connect(self.pool.connect, None), + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2339, in _wrap_pool_connect + Connection._handle_dbapi_exception_noconnection( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 1583, in _handle_dbapi_exception_noconnection + util.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2336, in _wrap_pool_connect + return fn() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 364, in connect + return _ConnectionFairy._checkout(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 778, in _checkout + fairy = _ConnectionRecord.checkout(pool) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 495, in checkout + rec = pool._do_get() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/impl.py", line 140, in _do_get + self._dec_overflow() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__ + compat.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/impl.py", line 137, in _do_get + return self._create_connection() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 309, in _create_connection + return _ConnectionRecord(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 440, in __init__ + self.__connect(first_connect_check=True) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 661, in __connect + pool.logger.debug("Error on connect(): %s", e) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__ + compat.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 656, in __connect + connection = pool._invoke_creator(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/strategies.py", line 114, in connect + return dialect.connect(*cargs, **cparams) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/default.py", line 493, in connect + return self.dbapi.connect(*cargs, **cparams) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/__init__.py", line 94, in Connect + return Connection(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 327, in __init__ + self.connect() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 588, in connect + self._request_authentication() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 874, in _request_authentication + auth_packet = _auth.caching_sha2_password_auth(self, auth_packet) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/_auth.py", line 327, in caching_sha2_password_auth + pkt = _roundtrip(conn, data) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/_auth.py", line 181, in _roundtrip + pkt = conn._read_packet() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 676, in _read_packet + packet.raise_for_error() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/protocol.py", line 223, in raise_for_error + err.raise_mysql_exception(self._data) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/err.py", line 107, in raise_mysql_exception + raise errorclass(errno, errval) +sqlalchemy.exc.OperationalError: (pymysql.err.OperationalError) (1045, "Access denied for user 'root'@'172.25.0.1' (using password: YES)") +(Background on this error at: http://sqlalche.me/e/13/e3q8) +[2021-01-06 16:28:51,043] [ERROR] [app.py.handle_exception: 73] [37780::140203198556624] (pymysql.err.OperationalError) (1045, "Access denied for user 'root'@'172.25.0.1' (using password: YES)") +(Background on this error at: http://sqlalche.me/e/13/e3q8) +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2336, in _wrap_pool_connect + return fn() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 364, in connect + return _ConnectionFairy._checkout(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 778, in _checkout + fairy = _ConnectionRecord.checkout(pool) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 495, in checkout + rec = pool._do_get() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/impl.py", line 140, in _do_get + self._dec_overflow() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__ + compat.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/impl.py", line 137, in _do_get + return self._create_connection() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 309, in _create_connection + return _ConnectionRecord(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 440, in __init__ + self.__connect(first_connect_check=True) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 661, in __connect + pool.logger.debug("Error on connect(): %s", e) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__ + compat.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 656, in __connect + connection = pool._invoke_creator(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/strategies.py", line 114, in connect + return dialect.connect(*cargs, **cparams) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/default.py", line 493, in connect + return self.dbapi.connect(*cargs, **cparams) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/__init__.py", line 94, in Connect + return Connection(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 327, in __init__ + self.connect() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 588, in connect + self._request_authentication() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 874, in _request_authentication + auth_packet = _auth.caching_sha2_password_auth(self, auth_packet) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/_auth.py", line 327, in caching_sha2_password_auth + pkt = _roundtrip(conn, data) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/_auth.py", line 181, in _roundtrip + pkt = conn._read_packet() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 676, in _read_packet + packet.raise_for_error() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/protocol.py", line 223, in raise_for_error + err.raise_mysql_exception(self._data) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/err.py", line 107, in raise_mysql_exception + raise errorclass(errno, errval) +pymysql.err.OperationalError: (1045, "Access denied for user 'root'@'172.25.0.1' (using password: YES)") + +The above exception was the direct cause of the following exception: + +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_httpauth.py", line 380, in decorated + return selected_auth.login_required(role=role, + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_httpauth.py", line 149, in decorated + user = self.authenticate(auth, password) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_httpauth.py", line 220, in authenticate + return self.verify_password_callback(username, client_password) + File "/Users/herui/Desktop/flask-restful/authentication/token.py", line 23, in verify_password + user = User.query.filter_by(email=username).first() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3429, in first + ret = list(self[0:1]) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3203, in __getitem__ + return list(res) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3535, in __iter__ + return self._execute_and_instances(context) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3556, in _execute_and_instances + conn = self._get_bind_args( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3571, in _get_bind_args + return fn( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3550, in _connection_from_session + conn = self.session.connection(**kw) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 1138, in connection + return self._connection_for_bind( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 1146, in _connection_for_bind + return self.transaction._connection_for_bind( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 433, in _connection_for_bind + conn = bind._contextual_connect() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2302, in _contextual_connect + self._wrap_pool_connect(self.pool.connect, None), + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2339, in _wrap_pool_connect + Connection._handle_dbapi_exception_noconnection( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 1583, in _handle_dbapi_exception_noconnection + util.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2336, in _wrap_pool_connect + return fn() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 364, in connect + return _ConnectionFairy._checkout(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 778, in _checkout + fairy = _ConnectionRecord.checkout(pool) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 495, in checkout + rec = pool._do_get() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/impl.py", line 140, in _do_get + self._dec_overflow() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__ + compat.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/impl.py", line 137, in _do_get + return self._create_connection() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 309, in _create_connection + return _ConnectionRecord(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 440, in __init__ + self.__connect(first_connect_check=True) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 661, in __connect + pool.logger.debug("Error on connect(): %s", e) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__ + compat.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 656, in __connect + connection = pool._invoke_creator(self) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/strategies.py", line 114, in connect + return dialect.connect(*cargs, **cparams) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/engine/default.py", line 493, in connect + return self.dbapi.connect(*cargs, **cparams) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/__init__.py", line 94, in Connect + return Connection(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 327, in __init__ + self.connect() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 588, in connect + self._request_authentication() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 874, in _request_authentication + auth_packet = _auth.caching_sha2_password_auth(self, auth_packet) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/_auth.py", line 327, in caching_sha2_password_auth + pkt = _roundtrip(conn, data) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/_auth.py", line 181, in _roundtrip + pkt = conn._read_packet() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/connections.py", line 676, in _read_packet + packet.raise_for_error() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/protocol.py", line 223, in raise_for_error + err.raise_mysql_exception(self._data) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/pymysql/err.py", line 107, in raise_mysql_exception + raise errorclass(errno, errval) +sqlalchemy.exc.OperationalError: (pymysql.err.OperationalError) (1045, "Access denied for user 'root'@'172.25.0.1' (using password: YES)") +(Background on this error at: http://sqlalche.me/e/13/e3q8) +[2021-01-06 16:30:50,030] [INFO] [server.py.run: 18] [41348::140500762400896] Server start at 0.0.0.0:24579 +[2021-01-06 16:33:05,361] [ERROR] [app.py.log_exception:1891] [41348::140500785718128] Exception on /api/users [POST] +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 33, in create + raise ArgumentRequired("邮件地址不能为空") +exceptions.exceptions.ArgumentRequired: 邮件地址不能为空 +[2021-01-06 16:33:05,378] [ERROR] [app.py.handle_exception: 73] [41348::140500785718128] 邮件地址不能为空 +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 33, in create + raise ArgumentRequired("邮件地址不能为空") +exceptions.exceptions.ArgumentRequired: 邮件地址不能为空 +[2021-01-06 16:33:49,735] [ERROR] [app.py.log_exception:1891] [41348::140500785718128] Exception on /api/users [POST] +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 36, in create + raise ObjectsDuplicated("邮件为 <%s> 的用户已经注册" % email) +exceptions.exceptions.ObjectsDuplicated: 邮件为 的用户已经注册 +[2021-01-06 16:33:49,750] [ERROR] [app.py.handle_exception: 73] [41348::140500785718128] 邮件为 的用户已经注册 +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 36, in create + raise ObjectsDuplicated("邮件为 <%s> 的用户已经注册" % email) +exceptions.exceptions.ObjectsDuplicated: 邮件为 的用户已经注册 +[2021-01-06 16:35:07,925] [ERROR] [app.py.log_exception:1891] [41348::140500785718128] Exception on /api/users [POST] +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 45, in create + raise ArgumentInvalid(PassWordChecker.ERROR_MSG) +exceptions.exceptions.ArgumentInvalid: 密码至少8个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符,不能含有空格 +[2021-01-06 16:35:08,003] [ERROR] [app.py.handle_exception: 73] [41348::140500785718128] 密码至少8个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符,不能含有空格 +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 45, in create + raise ArgumentInvalid(PassWordChecker.ERROR_MSG) +exceptions.exceptions.ArgumentInvalid: 密码至少8个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符,不能含有空格 +[2021-01-06 16:35:21,012] [ERROR] [app.py.log_exception:1891] [41348::140500785718128] Exception on /api/users [POST] +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 45, in create + raise ArgumentInvalid(PassWordChecker.ERROR_MSG) +exceptions.exceptions.ArgumentInvalid: 密码至少8个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符,不能含有空格 +[2021-01-06 16:35:21,013] [ERROR] [app.py.handle_exception: 73] [41348::140500785718128] 密码至少8个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符,不能含有空格 +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 45, in create + raise ArgumentInvalid(PassWordChecker.ERROR_MSG) +exceptions.exceptions.ArgumentInvalid: 密码至少8个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符,不能含有空格 +[2021-01-06 16:35:40,939] [INFO] [token.py.verify_password: 26] [41348::140500784425152] 用户 > 登陆> +[2021-01-06 16:35:52,010] [INFO] [token.py.verify_password: 26] [41348::140500784425152] 用户 > 登陆> +[2021-01-06 16:58:03,821] [INFO] [server.py.run: 18] [90866::140282457762448] Server start at 0.0.0.0:24579 +[2021-01-06 16:58:11,045] [ERROR] [app.py.log_exception:1891] [90866::140282470546144] Exception on /api/users [POST] +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 36, in wrapper + self.response_model = response_model + File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydevd_frame_eval/pydevd_frame_tracing.py", line 55, in _pydev_stop_at_break + if t.additional_info.is_tracing: +AttributeError: '_DummyThread' object has no attribute 'additional_info' +[2021-01-06 16:58:11,058] [ERROR] [app.py.handle_exception: 73] [90866::140282470546144] '_DummyThread' object has no attribute 'additional_info' +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 36, in wrapper + self.response_model = response_model + File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydevd_frame_eval/pydevd_frame_tracing.py", line 55, in _pydev_stop_at_break + if t.additional_info.is_tracing: +AttributeError: '_DummyThread' object has no attribute 'additional_info' +[2021-01-06 17:01:22,030] [ERROR] [app.py.log_exception:1891] [90866::140282470546144] Exception on /api/users [POST] +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/base.py", line 406, in _entity_descriptor + return getattr(entity, key) +AttributeError: type object 'User' has no attribute 'deleted' + +The above exception was the direct cause of the following exception: + +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 34, in create + user = User.query.filter_by(deleted=False).filter_by(email=email).first() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 1921, in filter_by + clauses = [ + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 1922, in + _entity_descriptor(zero, key) == value + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/base.py", line 408, in _entity_descriptor + util.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception +sqlalchemy.exc.InvalidRequestError: Entity '' has no property 'deleted' +[2021-01-06 17:01:22,046] [ERROR] [app.py.handle_exception: 73] [90866::140282470546144] Entity '' has no property 'deleted' +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/base.py", line 406, in _entity_descriptor + return getattr(entity, key) +AttributeError: type object 'User' has no attribute 'deleted' + +The above exception was the direct cause of the following exception: + +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 34, in create + user = User.query.filter_by(deleted=False).filter_by(email=email).first() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 1921, in filter_by + clauses = [ + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 1922, in + _entity_descriptor(zero, key) == value + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/base.py", line 408, in _entity_descriptor + util.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception +sqlalchemy.exc.InvalidRequestError: Entity '' has no property 'deleted' +[2021-01-06 17:02:14,667] [INFO] [server.py.run: 18] [98685::140584632186656] Server start at 0.0.0.0:24579 +[2021-01-06 17:02:25,883] [INFO] [server.py.run: 18] [99013::140321331858064] Server start at 0.0.0.0:24579 +[2021-01-06 17:02:53,013] [INFO] [server.py.run: 18] [99934::140251461842720] Server start at 0.0.0.0:24579 +[2021-01-06 17:02:55,726] [ERROR] [app.py.log_exception:1891] [99934::140251476978400] Exception on /api/users [POST] +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 36, in wrapper + self.response_model = response_model + File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydevd_frame_eval/pydevd_frame_tracing.py", line 55, in _pydev_stop_at_break + if t.additional_info.is_tracing: +AttributeError: '_DummyThread' object has no attribute 'additional_info' +[2021-01-06 17:02:55,770] [ERROR] [app.py.handle_exception: 73] [99934::140251476978400] '_DummyThread' object has no attribute 'additional_info' +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 36, in wrapper + self.response_model = response_model + File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydevd_frame_eval/pydevd_frame_tracing.py", line 55, in _pydev_stop_at_break + if t.additional_info.is_tracing: +AttributeError: '_DummyThread' object has no attribute 'additional_info' +[2021-01-06 17:05:13,266] [ERROR] [app.py.log_exception:1891] [99934::140251476978400] Exception on /api/users [POST] +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/base.py", line 406, in _entity_descriptor + return getattr(entity, key) +AttributeError: type object 'User' has no attribute 'deleted' + +The above exception was the direct cause of the following exception: + +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 34, in create + user = User.query.filter_by(deleted=False).filter_by(email=email).first() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 1921, in filter_by + clauses = [ + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 1922, in + _entity_descriptor(zero, key) == value + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/base.py", line 408, in _entity_descriptor + util.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception +sqlalchemy.exc.InvalidRequestError: Entity '' has no property 'deleted' +[2021-01-06 17:05:13,279] [ERROR] [app.py.handle_exception: 73] [99934::140251476978400] Entity '' has no property 'deleted' +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/base.py", line 406, in _entity_descriptor + return getattr(entity, key) +AttributeError: type object 'User' has no attribute 'deleted' + +The above exception was the direct cause of the following exception: + +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 34, in create + user = User.query.filter_by(deleted=False).filter_by(email=email).first() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 1921, in filter_by + clauses = [ + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 1922, in + _entity_descriptor(zero, key) == value + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/base.py", line 408, in _entity_descriptor + util.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception +sqlalchemy.exc.InvalidRequestError: Entity '' has no property 'deleted' +[2021-01-06 17:05:49,091] [INFO] [server.py.run: 18] [5673::140596711778080] Server start at 0.0.0.0:24579 +[2021-01-06 17:06:51,481] [INFO] [server.py.run: 18] [7871::140386837198624] Server start at 0.0.0.0:24579 +[2021-01-06 17:06:54,431] [ERROR] [app.py.log_exception:1891] [7871::140386851052256] Exception on /api/users [POST] +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 37, in wrapper + self.parsed_args = self.query_model.parse_and_process_args(**kwargs) + File "/Users/herui/Desktop/flask-restful/models/query/base.py", line 85, in parse_and_process_args + @classmethod + File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydevd_frame_eval/pydevd_frame_tracing.py", line 55, in _pydev_stop_at_break + if t.additional_info.is_tracing: +AttributeError: '_DummyThread' object has no attribute 'additional_info' +[2021-01-06 17:06:54,454] [ERROR] [app.py.handle_exception: 73] [7871::140386851052256] '_DummyThread' object has no attribute 'additional_info' +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 37, in wrapper + self.parsed_args = self.query_model.parse_and_process_args(**kwargs) + File "/Users/herui/Desktop/flask-restful/models/query/base.py", line 85, in parse_and_process_args + @classmethod + File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydevd_frame_eval/pydevd_frame_tracing.py", line 55, in _pydev_stop_at_break + if t.additional_info.is_tracing: +AttributeError: '_DummyThread' object has no attribute 'additional_info' +[2021-01-06 17:14:23,091] [ERROR] [app.py.log_exception:1891] [7871::140386851803472] Exception on /api/users [POST] +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/base.py", line 406, in _entity_descriptor + return getattr(entity, key) +AttributeError: type object 'User' has no attribute 'deleted' + +The above exception was the direct cause of the following exception: + +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 34, in create + user = User.query.filter_by(deleted=False).filter_by(email=email).first() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 1921, in filter_by + clauses = [ + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 1922, in + _entity_descriptor(zero, key) == value + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/base.py", line 408, in _entity_descriptor + util.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception +sqlalchemy.exc.InvalidRequestError: Entity '' has no property 'deleted' +[2021-01-06 17:14:23,123] [ERROR] [app.py.handle_exception: 73] [7871::140386851803472] Entity '' has no property 'deleted' +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/base.py", line 406, in _entity_descriptor + return getattr(entity, key) +AttributeError: type object 'User' has no attribute 'deleted' + +The above exception was the direct cause of the following exception: + +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 34, in create + user = User.query.filter_by(deleted=False).filter_by(email=email).first() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 1921, in filter_by + clauses = [ + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 1922, in + _entity_descriptor(zero, key) == value + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/base.py", line 408, in _entity_descriptor + util.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception +sqlalchemy.exc.InvalidRequestError: Entity '' has no property 'deleted' +[2021-01-06 17:40:11,301] [ERROR] [app.py.log_exception:1891] [7871::140386851052256] Exception on /api/users [POST] +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/base.py", line 406, in _entity_descriptor + return getattr(entity, key) +AttributeError: type object 'User' has no attribute 'deleted' + +The above exception was the direct cause of the following exception: + +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 34, in create + user = User.query.filter_by(deleted=False).filter_by(email=email).first() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 1921, in filter_by + clauses = [ + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 1922, in + _entity_descriptor(zero, key) == value + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/base.py", line 408, in _entity_descriptor + util.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception +sqlalchemy.exc.InvalidRequestError: Entity '' has no property 'deleted' +[2021-01-06 17:40:11,364] [ERROR] [app.py.handle_exception: 73] [7871::140386851052256] Entity '' has no property 'deleted' +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/base.py", line 406, in _entity_descriptor + return getattr(entity, key) +AttributeError: type object 'User' has no attribute 'deleted' + +The above exception was the direct cause of the following exception: + +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 34, in create + user = User.query.filter_by(deleted=False).filter_by(email=email).first() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 1921, in filter_by + clauses = [ + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 1922, in + _entity_descriptor(zero, key) == value + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/orm/base.py", line 408, in _entity_descriptor + util.raise_( + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 182, in raise_ + raise exception +sqlalchemy.exc.InvalidRequestError: Entity '' has no property 'deleted' +[2021-01-06 17:40:52,051] [INFO] [server.py.run: 18] [70335::140616934881056] Server start at 0.0.0.0:24579 +[2021-01-06 17:40:58,355] [ERROR] [app.py.log_exception:1891] [70335::140616950561504] Exception on /api/users [POST] +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 37, in wrapper + self.parsed_args = self.query_model.parse_and_process_args(**kwargs) + File "/Users/herui/Desktop/flask-restful/models/query/base.py", line 85, in parse_and_process_args + @classmethod + File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydevd_frame_eval/pydevd_frame_tracing.py", line 55, in _pydev_stop_at_break + if t.additional_info.is_tracing: +AttributeError: '_DummyThread' object has no attribute 'additional_info' +[2021-01-06 17:40:58,365] [ERROR] [app.py.handle_exception: 73] [70335::140616950561504] '_DummyThread' object has no attribute 'additional_info' +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 37, in wrapper + self.parsed_args = self.query_model.parse_and_process_args(**kwargs) + File "/Users/herui/Desktop/flask-restful/models/query/base.py", line 85, in parse_and_process_args + @classmethod + File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydevd_frame_eval/pydevd_frame_tracing.py", line 55, in _pydev_stop_at_break + if t.additional_info.is_tracing: +AttributeError: '_DummyThread' object has no attribute 'additional_info' +[2021-01-06 17:46:28,515] [INFO] [server.py.run: 18] [80844::140307195124368] Server start at 0.0.0.0:24579 +[2021-01-06 17:47:44,491] [ERROR] [app.py.log_exception:1891] [80844::140307210722288] Exception on /api/users [POST] +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 36, in create + raise ObjectsDuplicated("邮件为 <%s> 的用户已经注册" % email) +exceptions.exceptions.ObjectsDuplicated: 邮件为 的用户已经注册 +[2021-01-06 17:47:44,498] [ERROR] [app.py.handle_exception: 73] [80844::140307210722288] 邮件为 的用户已经注册 +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 36, in create + raise ObjectsDuplicated("邮件为 <%s> 的用户已经注册" % email) +exceptions.exceptions.ObjectsDuplicated: 邮件为 的用户已经注册 +[2021-01-06 17:48:53,230] [INFO] [server.py.run: 18] [85419::140378800832352] Server start at 0.0.0.0:24579 +[2021-01-06 17:49:20,550] [INFO] [server.py.run: 18] [86622::140532975159072] Server start at 0.0.0.0:24579 +[2021-01-06 17:49:39,446] [INFO] [server.py.run: 18] [86982::140332713891616] Server start at 0.0.0.0:24579 +[2021-01-06 17:49:46,192] [ERROR] [app.py.log_exception:1891] [86982::140332729027296] Exception on /api/users [POST] +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 37, in wrapper + self.parsed_args = self.query_model.parse_and_process_args(**kwargs) + File "/Users/herui/Desktop/flask-restful/models/query/base.py", line 85, in parse_and_process_args + @classmethod + File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydevd_frame_eval/pydevd_frame_tracing.py", line 55, in _pydev_stop_at_break + if t.additional_info.is_tracing: +AttributeError: '_DummyThread' object has no attribute 'additional_info' +[2021-01-06 17:49:46,213] [ERROR] [app.py.handle_exception: 73] [86982::140332729027296] '_DummyThread' object has no attribute 'additional_info' +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 37, in wrapper + self.parsed_args = self.query_model.parse_and_process_args(**kwargs) + File "/Users/herui/Desktop/flask-restful/models/query/base.py", line 85, in parse_and_process_args + @classmethod + File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydevd_frame_eval/pydevd_frame_tracing.py", line 55, in _pydev_stop_at_break + if t.additional_info.is_tracing: +AttributeError: '_DummyThread' object has no attribute 'additional_info' +[2021-01-06 17:50:10,142] [ERROR] [app.py.log_exception:1891] [86982::140332730831184] Exception on /api/users [POST] +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 36, in create + raise ObjectsDuplicated("邮件为 <%s> 的用户已经注册" % email) +exceptions.exceptions.ObjectsDuplicated: 邮件为 的用户已经注册 +[2021-01-06 17:50:10,146] [ERROR] [app.py.handle_exception: 73] [86982::140332730831184] 邮件为 的用户已经注册 +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 36, in create + raise ObjectsDuplicated("邮件为 <%s> 的用户已经注册" % email) +exceptions.exceptions.ObjectsDuplicated: 邮件为 的用户已经注册 +[2021-01-06 17:50:12,962] [ERROR] [app.py.log_exception:1891] [86982::140332730831184] Exception on /api/users [POST] +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 36, in create + raise ObjectsDuplicated("邮件为 <%s> 的用户已经注册" % email) +exceptions.exceptions.ObjectsDuplicated: 邮件为 的用户已经注册 +[2021-01-06 17:50:12,963] [ERROR] [app.py.handle_exception: 73] [86982::140332730831184] 邮件为 的用户已经注册 +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 36, in create + raise ObjectsDuplicated("邮件为 <%s> 的用户已经注册" % email) +exceptions.exceptions.ObjectsDuplicated: 邮件为 的用户已经注册 +[2021-01-06 17:51:40,346] [INFO] [server.py.run: 18] [91114::140573785720608] Server start at 0.0.0.0:24579 +[2021-01-06 17:51:44,008] [ERROR] [app.py.log_exception:1891] [91114::140573801904864] Exception on /api/users [POST] +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 37, in wrapper + self.parsed_args = self.query_model.parse_and_process_args(**kwargs) + File "/Users/herui/Desktop/flask-restful/models/query/base.py", line 85, in parse_and_process_args + @classmethod + File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydevd_frame_eval/pydevd_frame_tracing.py", line 55, in _pydev_stop_at_break + if t.additional_info.is_tracing: +AttributeError: '_DummyThread' object has no attribute 'additional_info' +[2021-01-06 17:51:44,047] [ERROR] [app.py.handle_exception: 73] [91114::140573801904864] '_DummyThread' object has no attribute 'additional_info' +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 37, in wrapper + self.parsed_args = self.query_model.parse_and_process_args(**kwargs) + File "/Users/herui/Desktop/flask-restful/models/query/base.py", line 85, in parse_and_process_args + @classmethod + File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydevd_frame_eval/pydevd_frame_tracing.py", line 55, in _pydev_stop_at_break + if t.additional_info.is_tracing: +AttributeError: '_DummyThread' object has no attribute 'additional_info' +[2021-01-06 17:54:39,619] [ERROR] [app.py.log_exception:1891] [91114::140573801905136] Exception on /api/users [POST] +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 36, in create + raise ObjectsDuplicated("邮件为 <%s> 的用户已经注册" % email) +exceptions.exceptions.ObjectsDuplicated: 邮件为 的用户已经注册 +[2021-01-06 17:54:39,628] [ERROR] [app.py.handle_exception: 73] [91114::140573801905136] 邮件为 的用户已经注册 +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 36, in create + raise ObjectsDuplicated("邮件为 <%s> 的用户已经注册" % email) +exceptions.exceptions.ObjectsDuplicated: 邮件为 的用户已经注册 +[2021-01-06 17:54:53,263] [ERROR] [app.py.log_exception:1891] [91114::140573801905136] Exception on /api/users [POST] +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 36, in create + raise ObjectsDuplicated("邮件为 <%s> 的用户已经注册" % email) +exceptions.exceptions.ObjectsDuplicated: 邮件为 的用户已经注册 +[2021-01-06 17:54:53,297] [ERROR] [app.py.handle_exception: 73] [91114::140573801905136] 邮件为 的用户已经注册 +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 36, in create + raise ObjectsDuplicated("邮件为 <%s> 的用户已经注册" % email) +exceptions.exceptions.ObjectsDuplicated: 邮件为 的用户已经注册 +[2021-01-06 17:58:34,077] [ERROR] [app.py.log_exception:1891] [91114::140573801905136] Exception on /api/users [POST] +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 36, in create + raise ObjectsDuplicated("邮件为 <%s> 的用户已经注册" % email) +exceptions.exceptions.ObjectsDuplicated: 邮件为 的用户已经注册 +[2021-01-06 17:58:34,085] [ERROR] [app.py.handle_exception: 73] [91114::140573801905136] 邮件为 的用户已经注册 +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 36, in create + raise ObjectsDuplicated("邮件为 <%s> 的用户已经注册" % email) +exceptions.exceptions.ObjectsDuplicated: 邮件为 的用户已经注册 +[2021-01-06 17:58:58,373] [INFO] [server.py.run: 18] [4568::140475202766624] Server start at 0.0.0.0:24579 +[2021-01-06 17:59:07,605] [ERROR] [app.py.log_exception:1891] [4568::140475217918688] Exception on /api/users [POST] +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 37, in wrapper + self.parsed_args = self.query_model.parse_and_process_args(**kwargs) + File "/Users/herui/Desktop/flask-restful/models/query/base.py", line 85, in parse_and_process_args + @classmethod + File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydevd_frame_eval/pydevd_frame_tracing.py", line 55, in _pydev_stop_at_break + if t.additional_info.is_tracing: +AttributeError: '_DummyThread' object has no attribute 'additional_info' +[2021-01-06 17:59:07,613] [ERROR] [app.py.handle_exception: 73] [4568::140475217918688] '_DummyThread' object has no attribute 'additional_info' +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 37, in wrapper + self.parsed_args = self.query_model.parse_and_process_args(**kwargs) + File "/Users/herui/Desktop/flask-restful/models/query/base.py", line 85, in parse_and_process_args + @classmethod + File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydevd_frame_eval/pydevd_frame_tracing.py", line 55, in _pydev_stop_at_break + if t.additional_info.is_tracing: +AttributeError: '_DummyThread' object has no attribute 'additional_info' +[2021-01-06 17:59:44,978] [INFO] [server.py.run: 18] [6218::4508552704] Server start at 0.0.0.0:24579 +[2021-01-06 18:02:41,642] [ERROR] [app.py.log_exception:1891] [6218::4508552704] Exception on /api/users [POST] +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 36, in create + raise ObjectsDuplicated("邮件为 <%s> 的用户已经注册" % email) +exceptions.exceptions.ObjectsDuplicated: 邮件为 的用户已经注册 +[2021-01-06 18:02:41,649] [ERROR] [app.py.handle_exception: 73] [6218::4508552704] 邮件为 的用户已经注册 +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 36, in create + raise ObjectsDuplicated("邮件为 <%s> 的用户已经注册" % email) +exceptions.exceptions.ObjectsDuplicated: 邮件为 的用户已经注册 +[2021-01-06 18:03:55,545] [ERROR] [app.py.log_exception:1891] [6218::4508552704] Exception on /api/users [POST] +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 36, in create + raise ObjectsDuplicated("邮件为 <%s> 的用户已经注册" % email) +exceptions.exceptions.ObjectsDuplicated: 邮件为 的用户已经注册 +[2021-01-06 18:03:55,546] [ERROR] [app.py.handle_exception: 73] [6218::4508552704] 邮件为 的用户已经注册 +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 36, in create + raise ObjectsDuplicated("邮件为 <%s> 的用户已经注册" % email) +exceptions.exceptions.ObjectsDuplicated: 邮件为 的用户已经注册 +[2021-01-06 18:06:13,787] [INFO] [server.py.run: 18] [18191::4716846592] Server start at 0.0.0.0:24579 +[2021-01-06 18:06:22,138] [ERROR] [app.py.log_exception:1891] [18191::4716846592] Exception on /api/users [POST] +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 33, in create + raise ArgumentRequired("邮件地址不能为空") +exceptions.exceptions.ArgumentRequired: 邮件地址不能为空 +[2021-01-06 18:06:22,149] [ERROR] [app.py.handle_exception: 73] [18191::4716846592] 邮件地址不能为空 +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 33, in create + raise ArgumentRequired("邮件地址不能为空") +exceptions.exceptions.ArgumentRequired: 邮件地址不能为空 +[2021-01-06 18:06:37,301] [ERROR] [app.py.log_exception:1891] [18191::4716846592] Exception on /api/users [POST] +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 33, in create + raise ArgumentRequired("邮件地址不能为空") +exceptions.exceptions.ArgumentRequired: 邮件地址不能为空 +[2021-01-06 18:06:37,303] [ERROR] [app.py.handle_exception: 73] [18191::4716846592] 邮件地址不能为空 +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 33, in create + raise ArgumentRequired("邮件地址不能为空") +exceptions.exceptions.ArgumentRequired: 邮件地址不能为空 +[2021-01-06 18:07:29,619] [INFO] [server.py.run: 18] [20457::4491791872] Server start at 0.0.0.0:24579 +[2021-01-06 18:07:37,735] [ERROR] [app.py.log_exception:1891] [20457::4491791872] Exception on /api/users [POST] +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 33, in create + raise ArgumentRequired("邮件地址不能为空") +exceptions.exceptions.ArgumentRequired: 邮件地址不能为空 +[2021-01-06 18:07:37,744] [ERROR] [app.py.handle_exception: 73] [20457::4491791872] 邮件地址不能为空 +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 33, in create + raise ArgumentRequired("邮件地址不能为空") +exceptions.exceptions.ArgumentRequired: 邮件地址不能为空 +[2021-01-06 18:08:03,642] [ERROR] [app.py.log_exception:1891] [20457::4491791872] Exception on /api/users [POST] +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 33, in create + raise ArgumentRequired("邮件地址不能为空") +exceptions.exceptions.ArgumentRequired: 邮件地址不能为空 +[2021-01-06 18:08:03,644] [ERROR] [app.py.handle_exception: 73] [20457::4491791872] 邮件地址不能为空 +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.8/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 33, in create + raise ArgumentRequired("邮件地址不能为空") +exceptions.exceptions.ArgumentRequired: 邮件地址不能为空 +[2021-01-06 18:09:09,112] [INFO] [server.py.run: 18] [23674::4616838656] Server start at 0.0.0.0:24579 +[2021-01-06 18:10:06,733] [INFO] [server.py.run: 18] [25305::4377185792] Server start at 0.0.0.0:24579 +[2021-01-06 18:11:27,051] [INFO] [server.py.run: 18] [27878::4384443904] Server start at 0.0.0.0:24579 +[2021-01-06 18:14:17,527] [INFO] [server.py.run: 18] [31742::4440231424] Server start at 0.0.0.0:24579 +[2021-01-06 18:22:22,553] [INFO] [server.py.run: 18] [48760::4601302528] Server start at 0.0.0.0:24579 +[2021-01-06 18:22:45,941] [ERROR] [app.py.log_exception:1891] [48760::4601302528] Exception on /api/users [POST] +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.9/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.9/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.9/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.9/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.9/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 33, in create + raise ArgumentRequired("邮件地址不能为空") +exceptions.exceptions.ArgumentRequired: 邮件地址不能为空 +[2021-01-06 18:22:45,952] [ERROR] [app.py.handle_exception: 73] [48760::4601302528] 邮件地址不能为空 +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.9/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.9/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.9/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.9/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.9/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 38, in wrapper + return jsonify(func(self, **kwargs)) + File "/Users/herui/Desktop/flask-restful/resources/user.py", line 22, in post + user = UserHandler.create(**kwargs) + File "/Users/herui/Desktop/flask-restful/handlers/user.py", line 33, in create + raise ArgumentRequired("邮件地址不能为空") +exceptions.exceptions.ArgumentRequired: 邮件地址不能为空 +[2021-01-06 18:22:54,042] [INFO] [server.py.run: 18] [49778::4643528192] Server start at 0.0.0.0:24579 +[2021-01-06 18:24:45,947] [INFO] [server.py.run: 18] [50117::4572995072] Server start at 0.0.0.0:24579 +[2021-01-06 18:25:02,030] [INFO] [server.py.run: 18] [53452::4690017792] Server start at 0.0.0.0:24579 +[2021-01-06 18:25:27,069] [ERROR] [app.py.log_exception:1891] [53452::4690017792] Exception on /api/users [POST] +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.9/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.9/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.9/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.9/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.9/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 37, in wrapper + self.parsed_args = self.query_model.parse_and_process_args(**kwargs) + File "/Users/herui/Desktop/flask-restful/models/query/base.py", line 89, in parse_and_process_args + return cls.parse_args().process_args(**kwargs) + File "/Users/herui/Desktop/flask-restful/models/query/base.py", line 63, in parse_args + name = field.name +AttributeError: name +[2021-01-06 18:25:27,079] [ERROR] [app.py.handle_exception: 73] [53452::4690017792] name +Traceback (most recent call last): + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.9/site-packages/flask/app.py", line 1950, in full_dispatch_request + rv = self.dispatch_request() + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.9/site-packages/flask/app.py", line 1936, in dispatch_request + return self.view_functions[rule.endpoint](**req.view_args) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.9/site-packages/flask_restful/__init__.py", line 468, in wrapper + resp = resource(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.9/site-packages/flask/views.py", line 89, in view + return self.dispatch_request(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/venv/lib/python3.9/site-packages/flask_restful/__init__.py", line 583, in dispatch_request + resp = meth(*args, **kwargs) + File "/Users/herui/Desktop/flask-restful/resources/__init__.py", line 37, in wrapper + self.parsed_args = self.query_model.parse_and_process_args(**kwargs) + File "/Users/herui/Desktop/flask-restful/models/query/base.py", line 89, in parse_and_process_args + return cls.parse_args().process_args(**kwargs) + File "/Users/herui/Desktop/flask-restful/models/query/base.py", line 63, in parse_args + name = field.name +AttributeError: name +[2021-01-06 18:28:36,744] [INFO] [server.py.run: 18] [60224::4517809664] Server start at 0.0.0.0:24579 diff --git a/models/base.py b/models/base.py deleted file mode 100644 index 2b711c1..0000000 --- a/models/base.py +++ /dev/null @@ -1,103 +0,0 @@ -import pprint -from typing import Any, Callable, Dict, Type - - -class ApiDataType: - def __init__(self, implicit=True): - self.implicit = implicit - - def mock(self) -> None: - raise NotImplementedError() - - def marshal(self, value,) -> None: - raise NotImplementedError() - - def validate(self, value) -> None: - raise NotImplementedError() - - def __str__(self): - return self.__class__.__name__ - - __repr__ = __str__ - - -class Field: - __slots__ = ("name", "field_type", "mock_func", "enum_values", "comment", "nullable", "marshal") - - def __init__( - self, - field_type: ApiDataType, - mock_func: Callable = None, - enum_values: tuple = (), - comment: str = "", - nullable: bool = True, - marshal: Callable = None, - ) -> None: - self.name = '' - self.field_type = field_type - self.mock_func = mock_func - self.enum_values = enum_values - self.comment = comment - self.nullable = nullable - self.marshal = marshal or self.field_type.marshal - - def __str__(self): - return "".format(self.name, self.field_type) - - def __repr__(self): - return "Field({})".format(self.field_type) - - -class ModelMetaClass(type): - def __new__(mcs, name: str, bases: tuple, attrs: dict): - __fields_map__ = {} - - for base in bases: - for field in getattr(base, "__fields__", ()): - if field.name not in __fields_map__: - __fields_map__[field.name] = field - - for field_name, field in attrs.copy().items(): - if isinstance(field, Field): - field.name = field_name - __fields_map__[field_name] = field - attrs.pop(field_name) - - attrs["__fields__"] = tuple(__fields_map__.values()) - attrs["__fields_map__"] = __fields_map__ - attrs["__slots__"] = tuple(list(__fields_map__.keys()) + ["__storage__"]) - - return type.__new__(mcs, name, bases, attrs) - - -class BaseModel(object, metaclass=ModelMetaClass): - __fields__ = () - __fields_map__: Dict[str, Type[Field]] = {} - - def __init__(self, drop_missing=False, **kwargs) -> None: - for field_name, field in self.__fields_map__.items(): - value = kwargs.get(field_name) - if not drop_missing and not field.nullable and value is None: - raise Exception("field [{}] must be initialized".format(field_name)) - setattr(self, field_name, field.field_type.marshal(value)) - - def marshal(self, values=None) -> Dict[str, str]: - dct = {} - - if values is None: - values = self - for field_name, field in self.__fields_map__.items(): - value = values.get(field_name) - dct[field_name] = field.field_type.marshal(value) - - return dct - - def get(self, item, default=None) -> Any: - return getattr(self, item, default) - - def __str__(self): - return "[<{}>: \n{}]".format( - self.__class__.__name__, pprint.pformat(self.marshal(), indent=4) - ) - - __repr__ = __str__ diff --git a/models/data_types.py b/models/data_types.py deleted file mode 100644 index 2615896..0000000 --- a/models/data_types.py +++ /dev/null @@ -1,187 +0,0 @@ -import importlib -import random -from collections.abc import Iterable -from datetime import datetime -from exceptions.exceptions import ServerException -from typing import Any, Callable, Dict, List, Optional, Tuple, Type -from uuid import uuid1 - -from handlers.utils import str_to_datetime -from models.base import ApiDataType, BaseModel - - -class IntType(ApiDataType): - def mock(self) -> int: - return random.randint(0, 10) - - def marshal(self, value) -> Optional[int]: - return int(value) if value is not None and value != "" else None - - def validate(self, value) -> None: - assert value is None or isinstance(value, int) - - -class FloatType(ApiDataType): - def mock(self) -> float: - return int(random.random() * 100) / 10 - - def marshal(self, value) -> Optional[float]: - return float(value) if value is not None and value != "" else None - - def validate(self, value) -> None: - assert value is None or isinstance(value, float) - - -class StringType(ApiDataType): - def mock(self) -> str: - return uuid1().hex - - def marshal(self, value) -> Optional[str]: - if self.implicit: - return str(value) if value not in (None, "") else None - else: - self.validate(value) - return value - - def validate(self, value) -> None: - assert value is None or isinstance( - value, str - ), f"expect , but get {type(value)}" - - -class BooleanType(ApiDataType): - def mock(self) -> bool: - return random.choice([True, False]) - - def marshal(self, value) -> bool: - if value == "": - value = None - if isinstance(value, str): - if value.lower() == "true": - value = True - elif value.lower() == "false": - value = False - assert value in ( - True, - False, - None, - ), "[Boolean] should be [True] or [False]. Actual: [{}]".format(type(value)) - - return value - - def validate(self, value) -> None: - assert value is None or isinstance(value, bool) - - -class DateTimeType(ApiDataType): - def mock(self) -> datetime: - return datetime.fromtimestamp(1000000000 * random.random()) - - def marshal(self, value) -> Optional[datetime]: - date = None - if value and value not in ("", "null", "None"): - if isinstance(value, datetime): - date = value - elif isinstance(value, str): - date = str_to_datetime(value) - else: - raise TypeError("Invalid datetime format") - - return date - - def validate(self, value) -> None: - assert value is None or isinstance(value, datetime) - - -class ListType(ApiDataType): - def __init__(self, element_type: ApiDataType) -> None: - self.element_type = element_type - - def mock(self) -> List[Any]: - return [self.element_type.mock() for _ in range(10)] - - def marshal(self, value) -> List[Any]: - return [] if not value else [self.element_type.marshal(v) for v in value] - - def validate(self, value) -> None: - assert isinstance(value, Iterable) and not isinstance(value, str) - for v in value: - self.element_type.validate(v) - - def __str__(self): - return "List<{}>".format(self.element_type) - - -class DictType(ApiDataType): - def mock(self) -> Dict[str, str]: - return {uuid1().hex: uuid1().hex} - - def marshal(self, value) -> Dict[str, str]: - return value - - def validate(self, value) -> bool: - assert isinstance(value, dict) - - -class LazyWrapper: - def __init__(self, func: Callable = None): - self._func = func - self._object = None - - @property - def object(self) -> Callable: - if self._object is None: - self._object = self._func() - return self._object - - def __getattr__(self, item): - return getattr(self.object, item) - - -class ApiDefineType(ApiDataType): - mod = LazyWrapper(lambda: importlib.import_module("models.response")) - - def __init__(self, schema: Tuple[str, BaseModel]): - if isinstance(schema, str): - self.schema_name = schema - self._real_data_type = None - elif issubclass(schema, BaseModel): - self.schema_name = schema.__name__ - self._real_data_type = schema - else: - raise ServerException("schema of ApiDefineType should be a Model or name of a Model") - - self._real_data = None - - def _ensure_schema_parsed(self) -> None: - if self._real_data_type is None: - self._real_data_type = self._parse_schema_name(self.schema_name) - - @property - def data_type(self) -> Type[BaseModel]: - self._ensure_schema_parsed() - return self._real_data_type - - @classmethod - def _parse_schema_name(cls, schema_name) -> Optional[BaseModel]: - schema = getattr(cls.mod, schema_name) - return schema - - def mock(self): - raise NotImplementedError() - - def marshal(self, data) -> Dict[Any, Any]: - self._ensure_schema_parsed() - if data is None: - return None - _data = self.data_type - return _data.marshal(data) - - def __str__(self): - return self.schema_name - - def validate(self, data: Any) -> None: - assert isinstance(data, self.data_type), "Expect: {} - Actual: {}".format( - self.data_type.__name__, type(data) - ) - self.marshal(data) diff --git a/models/database/__init__.py b/models/database/__init__.py deleted file mode 100644 index 51e2b9d..0000000 --- a/models/database/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -from models.database.base import Base, Column, DeleteMixin, Meta, TimeMixin, db -from models.database.comment import Comment -from models.database.post import Post -from models.database.topic import RootTopic, Topic -from models.database.user import User - -__all__ = [ - "Base", - "TimeMixin", - "DeleteMixin", - "Column", - "Meta", - "db", - "Post", - "Comment", - "Topic", - "RootTopic", - "User", -] diff --git a/models/database/comment.py b/models/database/comment.py deleted file mode 100644 index c07a88a..0000000 --- a/models/database/comment.py +++ /dev/null @@ -1,12 +0,0 @@ -from sqlalchemy import Integer, Text - -from models.database import Base, Column, DeleteMixin, TimeMixin - - -class Comment(Base, TimeMixin, DeleteMixin): - __tablename__ = "comment" - - id = Column(Integer, primary_key=True,) - user_id = Column(Integer, nullable=False, comment="评论用户的 ID") - post_id = Column(Integer, nullable=False, comment="Post 文章的 ID") - content = Column(Text, nullable=False, comment="用户的评论") diff --git a/models/database/post.py b/models/database/post.py deleted file mode 100644 index 6d4bcbc..0000000 --- a/models/database/post.py +++ /dev/null @@ -1,15 +0,0 @@ -from sqlalchemy import JSON, Integer, Text -from sqlalchemy.sql import expression - -from models.database import Base, Column, DeleteMixin, TimeMixin - - -class Post(Base, TimeMixin, DeleteMixin): - __tablename__ = 'post' - - id = Column(Integer, primary_key=True) - topic_id = Column(Integer, nullable=False, comment="文章所在的主题 ID") - user_id = Column(Integer, nullable=False, comment="发布文章用户的 ID") - content = Column(Text, nullable=False, comment="文章内容") - click_times = Column(Integer, default=0, server_default=expression.false(), comment="文章的点击数") - tags = Column(JSON, nullable=True, default=[], comment="文章的 tag") diff --git a/models/database/user.py b/models/database/user.py deleted file mode 100644 index 797472b..0000000 --- a/models/database/user.py +++ /dev/null @@ -1,59 +0,0 @@ -from itsdangerous import BadSignature, SignatureExpired -from itsdangerous import TimedJSONWebSignatureSerializer as Serializer -from sqlalchemy import Integer, String -from werkzeug.security import check_password_hash, generate_password_hash - -from configures import settings -from models.database import Base, Column, DeleteMixin, TimeMixin - - -class User(Base, TimeMixin, DeleteMixin): - __tablename__ = 'user' - id = Column(Integer, primary_key=True) - - email = Column(String(100), nullable=False) - password_hash = Column(String(256), nullable=False, comment="登陆密码 hash 之后的值") - - name = Column(String(100), nullable=True) - phone = Column(String(20), nullable=True, comment='电话号码') - avatar = Column(String(256), nullable=True, comment="用户头像") - website = Column(String(100), nullable=True, comment="个人网站") - company = Column(String(100), nullable=True, comment="所在公司") - job = Column(String(100), nullable=True, comment="职位") - - def __repr__(self): - return f"User<{self.name}>" - - def hash_password(self, password): - self.password_hash = generate_password_hash(password) - - def check_password(self, password): - return check_password_hash(self.password_hash, password) - - def generate_auth_token(self, expiration=3600): - s = Serializer(settings.SECRET_KEY, expires_in=expiration) - return s.dumps({'id': self.id}) - - @staticmethod - def verify_auth_token(token): - s = Serializer(settings.SECRET_KEY) - try: - data = s.loads(token) - except SignatureExpired: - return None # valid token, but expired - except BadSignature: - return None # invalid token - user = User.query.get(data['id']) - return user - - -if __name__ == '__main__': - s = Serializer(settings.SECRET_KEY) - token = ( - "eyJhbGciOiJIUzUxMiIsImlhdCI" - "6MTYwNzA3NjE0MCwiZXhwIjoxNjA3MDc2Nz" - "QwfQ.eyJpZCI6MX0.9FwLXUqnm993TFxMbm98pXVCUoCV" - "J491Qcz5OsDxCW7-dJWRd1M3oTVUD3uVfveNoOGjF0cbi2r6YnBPf7Qyfw" - ) - m = s.loads(token) - print(m) diff --git a/models/orm/__init__.py b/models/orm/__init__.py new file mode 100644 index 0000000..7f9ca77 --- /dev/null +++ b/models/orm/__init__.py @@ -0,0 +1,19 @@ +from models.orm.base import Base, Column, DeleteMixin, Meta, TimeMixin, db +from models.orm.comment import Comment +from models.orm.post import Post +from models.orm.topic import RootTopic, Topic +from models.orm.user import User + +__all__ = [ + "Base", + "TimeMixin", + "DeleteMixin", + "Column", + "Meta", + "db", + "Post", + "Comment", + "Topic", + "RootTopic", + "User", +] diff --git a/models/database/base.py b/models/orm/base.py similarity index 87% rename from models/database/base.py rename to models/orm/base.py index 9f7052a..8bc1c44 100644 --- a/models/database/base.py +++ b/models/orm/base.py @@ -19,29 +19,29 @@ class SurrogatePK: - """A mixin that adds a surrogate integer 'primary key' column named `id` to any declarative-mapped class.""" + """A mixin that adds a surrogate integer 'primary key' column named `id` + to any declarative-mapped class.""" query: BaseQuery - __table_args__ = {'extend_existing': True} + + __table_args__ = {"extend_existing": True} id = Column(db.Integer, primary_key=True) @classmethod def get_by_id(cls, id_) -> Optional[SQLAlchemy]: """Get record by id.""" - if any((isinstance(id_, (str, bytes)) and id_.isdigit(), isinstance(id_, (int, float)),)): + if any((isinstance(id_, (str, bytes)) and id_.isdigit(), isinstance(id_, (int, float)))): return cls.query.get(int(id_)) return None class TimeMixin: - create_time = Column(DateTime(timezone=True), server_default=func.now(), comment="创建时间") - update_time = Column( - DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), comment="更新时间" - ) + create_time = Column(DateTime(timezone=True), server_default=func.now()) + update_time = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) class DeleteMixin: - deleted = Column(Boolean, server_default=expression.false(), nullable=False, comment="是否被删除") + deleted = Column(Boolean, server_default=expression.false(), nullable=False) class Base(db.Model, SurrogatePK): @@ -113,11 +113,11 @@ def as_dict(self): return {c: self._to_json(getattr(self, c)) for c in column_names} -def reference_col(table_name, nullable=False, pk_name='id', **kwargs): +def reference_col(table_name, nullable=False, pk_name="id", **kwargs): """ Column that adds primary key foreign key reference. Usage: category_id = reference_col('category') category = relationship('Category', backref='categories') """ - return Column(db.ForeignKey('{0}.{1}'.format(table_name, pk_name)), nullable=nullable, **kwargs) + return Column(db.ForeignKey("{0}.{1}".format(table_name, pk_name)), nullable=nullable, **kwargs) diff --git a/models/orm/comment.py b/models/orm/comment.py new file mode 100644 index 0000000..4e10403 --- /dev/null +++ b/models/orm/comment.py @@ -0,0 +1,11 @@ +from sqlalchemy import Integer, Text + +from models.orm import Base, Column, DeleteMixin, TimeMixin + + +class Comment(Base, TimeMixin, DeleteMixin): + __tablename__ = "comment" + + user_id = Column(Integer, nullable=False) + post_id = Column(Integer, nullable=False) + content = Column(Text, nullable=False, comment="user's comment content") diff --git a/models/orm/post.py b/models/orm/post.py new file mode 100644 index 0000000..db31613 --- /dev/null +++ b/models/orm/post.py @@ -0,0 +1,14 @@ +from sqlalchemy import JSON, Integer, Text +from sqlalchemy.sql import expression + +from models.orm import Base, Column, DeleteMixin, TimeMixin + + +class Post(Base, TimeMixin, DeleteMixin): + __tablename__ = "post" + + content = Column(Text, nullable=False) + user_id = Column(Integer, nullable=False) + topic_id = Column(Integer, nullable=False) + tags = Column(JSON, nullable=True, default=[]) + click_times = Column(Integer, default=0, server_default=expression.false()) diff --git a/models/database/topic.py b/models/orm/topic.py similarity index 54% rename from models/database/topic.py rename to models/orm/topic.py index 74a58f8..906d423 100644 --- a/models/database/topic.py +++ b/models/orm/topic.py @@ -1,26 +1,16 @@ from sqlalchemy import ForeignKey, Integer, String, text -from models.database import Base, Column, DeleteMixin, TimeMixin +from models.orm import Base, Column, DeleteMixin, TimeMixin class RootTopic(Base, TimeMixin, DeleteMixin): __tablename__ = "root_topic" - id = Column(Integer, primary_key=True) name = Column(String(256), nullable=False, unique=True) class Topic(Base, TimeMixin, DeleteMixin): __tablename__ = "topic" - id = Column(Integer, primary_key=True) name = Column(String(256), nullable=False, unique=True) - root_topic_id = Column(Integer, ForeignKey("root_topic.id"), server_default=text('1')) - - -if __name__ == '__main__': - from app import create_app - - app = create_app() - app.app_context().push() - RootTopic.create(name="root_topic") + root_topic_id = Column(Integer, ForeignKey("root_topic.id"), server_default=text("1")) diff --git a/models/orm/user.py b/models/orm/user.py new file mode 100644 index 0000000..b04ea9e --- /dev/null +++ b/models/orm/user.py @@ -0,0 +1,49 @@ +from typing import Optional + +from itsdangerous import ( + BadSignature, + SignatureExpired, + TimedJSONWebSignatureSerializer as Serializer, +) +from sqlalchemy import String +from werkzeug.security import check_password_hash, generate_password_hash + +from configures import settings +from models.orm import Base, Column, DeleteMixin, TimeMixin + + +class User(Base, TimeMixin, DeleteMixin): + __tablename__ = "user" + + email = Column(String(100), nullable=False) + password_hash = Column(String(256), nullable=False) + job = Column(String(100), nullable=True) + name = Column(String(100), nullable=True) + phone = Column(String(20), nullable=True) + avatar = Column(String(256), nullable=True) + website = Column(String(100), nullable=True) + company = Column(String(100), nullable=True) + + def hash_password(self, password: str) -> None: + self.password_hash = generate_password_hash(password) + + def check_password(self, password: str) -> bool: + return check_password_hash(self.password_hash, password) + + def generate_auth_token(self, expiration=3600) -> bool: + return Serializer(settings.SECRET_KEY, expires_in=expiration).dumps({"id": self.id}) + + @staticmethod + def verify_auth_token(token: str) -> Optional["User"]: + s = Serializer(settings.SECRET_KEY) + try: + data = s.loads(token) + except SignatureExpired: + return None # valid token, but expired + except BadSignature: + return None # invalid token + user = User.query.get(data["id"]) + return user + + def __repr__(self): + return f"User<{self.name}>" diff --git a/models/query/__init__.py b/models/query/__init__.py index 8a6b242..14fc1d2 100644 --- a/models/query/__init__.py +++ b/models/query/__init__.py @@ -1,12 +1,11 @@ -from models.query.base import BaseQueryModel, QueryField +from models.query.base import BaseQueryModel, NoArgs from models.query.comment import CommentQueryModel from models.query.post import PostQueryModel from models.query.topic import RootTopicQueryModel, TopicQueryModel from models.query.user import UserQueryModel __all__ = [ - "QueryField", - "BaseQueryModel", + "NoArgs", "BaseQueryModel", "CommentQueryModel", "PostQueryModel", diff --git a/models/query/base.py b/models/query/base.py index aef207f..3846ce1 100644 --- a/models/query/base.py +++ b/models/query/base.py @@ -1,50 +1,14 @@ import pprint -from exceptions.exceptions import ArgumentInvalid -from typing import Any, Callable, Dict, TypeVar +from typing import Any, Dict, TypeVar from flask_restful import reqparse +from pyruicore import BaseModel -from models.base import ApiDataType, BaseModel, Field +from exceptions.exceptions import ArgumentInvalid T = TypeVar("T", bound=BaseModel) -class QueryField(Field): - __slots__ = ( - 'name', - 'field_type', - 'mock_func', - 'enum_values', - 'description', - 'nullable', - 'required', - 'location', - 'default', - 'parser_kwargs', - 'parse_func', - ) - - def __init__( - self, - field_type: ApiDataType, - location: str, - parser_func: Callable = None, - required: bool = False, - mock_func: Callable = None, - enum_values: tuple = (), - comment: str = "", - nullable: bool = True, - **kwargs - ) -> None: - super().__init__(field_type, mock_func, enum_values, comment, nullable) - self.location = location - self.parser_kwargs = kwargs - self.required = required - self.parse_func = parser_func or self.field_type.marshal - if 'default' in kwargs: - self.default = kwargs['default'] - - class BaseQueryModel(BaseModel): def __init__(self, **kwargs): super(BaseQueryModel, self).__init__(**kwargs) @@ -59,11 +23,13 @@ def parse_args(cls) -> T: for field in cls.__fields__: name = field.name - location = field.location - required = field.required + others = field.others + location = others["location"] + required = others.get("required", False) + parser_kwargs = others.get("parser_kwargs", {}) nullable = field.nullable parser.add_argument( - name, location=location, required=required, nullable=nullable, **field.parser_kwargs + name, location=location, required=required, nullable=nullable, **parser_kwargs ) parsed = parser.parse_args() @@ -96,10 +62,12 @@ def __contains__(self, item): return item in self.__storage__ def __str__(self): - return '[<{}>: \n{}]'.format( + return "[<{}>: \n{}]".format( self.__class__.__name__, pprint.pformat(self.__storage__, indent=4) ) class NoArgs(BaseQueryModel): + """this is usually used for get many""" + pass diff --git a/models/query/comment.py b/models/query/comment.py index d0ea488..3710b1e 100644 --- a/models/query/comment.py +++ b/models/query/comment.py @@ -1,10 +1,13 @@ +from typing import Optional + +from pyruicore import Field + from configures.const import PER_PAGE -from models.data_types import IntType, StringType -from models.query import BaseQueryModel, QueryField +from models.query import BaseQueryModel class CommentQueryModel(BaseQueryModel): - user_id = QueryField(IntType(), nullable=True, location="json") - content = QueryField(StringType(), nullable=True, location="json") - per_page = QueryField(IntType(), default=PER_PAGE, nullable=False, location="json") - offset = QueryField(IntType(), default=0, nullable=False, location="json") + user_id: Optional[int] = Field(location="json") + content: Optional[str] = Field(location="json") + per_page: Optional[int] = Field(default=PER_PAGE, location="json") + offset: Optional[int] = Field(default=0, location="json") diff --git a/models/query/post.py b/models/query/post.py index 478610f..30b7ae0 100644 --- a/models/query/post.py +++ b/models/query/post.py @@ -1,11 +1,13 @@ +from typing import Optional + +from pyruicore import Field + from configures.const import PER_PAGE -from models.data_types import IntType, StringType -from models.query import BaseQueryModel, QueryField +from models.query import BaseQueryModel class PostQueryModel(BaseQueryModel): - user_id = QueryField(IntType(), nullable=True, location="json") - content = QueryField(StringType(), nullable=True, location="json") - - per_page = QueryField(IntType(), default=PER_PAGE, location="json") - offset = QueryField(IntType(), default=0, location="json") + user_id: Optional[int] = Field(location="json") + content: Optional[str] = Field(location="json") + per_page: Optional[int] = Field(default=PER_PAGE, location="json") + offset: Optional[int] = Field(default=0, location="json") diff --git a/models/query/topic.py b/models/query/topic.py index d8a7685..d6bcabf 100644 --- a/models/query/topic.py +++ b/models/query/topic.py @@ -1,9 +1,12 @@ -from models.data_types import StringType -from models.query import BaseQueryModel, QueryField +from typing import Optional + +from pyruicore import Field + +from models.query import BaseQueryModel class TopicQueryModel(BaseQueryModel): - name = QueryField(StringType(), location="json", comment="主题名") + name: Optional[str] = Field(location="json", comment="主题名") class RootTopicQueryModel(TopicQueryModel): diff --git a/models/query/user.py b/models/query/user.py index a3fc09c..3a58186 100644 --- a/models/query/user.py +++ b/models/query/user.py @@ -1,14 +1,16 @@ -from models.data_types import StringType -from models.query import BaseQueryModel, QueryField +from typing import Optional + +from pyruicore import Field + +from models.query import BaseQueryModel class UserQueryModel(BaseQueryModel): - email = QueryField(StringType(), location="json") - password = QueryField(StringType(), location="json", comment="登陆密码 hash 之后的值") - - name = QueryField(StringType(), location="json") - phone = QueryField(StringType(), location="json") - avatar = QueryField(StringType(), location="json") - website = QueryField(StringType(), location="json") - company = QueryField(StringType(), location="json") - job = QueryField(StringType(), location="json") + email: Optional[str] = Field(location="json") + password: Optional[str] = Field(location="json", comment="登陆密码 hash 之后的值") + name: Optional[str] = Field(location="json", default="this is name") + phone: Optional[str] = Field(location="json") + avatar: Optional[str] = Field(location="json") + website: Optional[str] = Field(location="json") + company: Optional[str] = Field(location="json") + job: Optional[str] = Field(location="json") diff --git a/models/response/__init__.py b/models/response/__init__.py index 31c8004..8146672 100644 --- a/models/response/__init__.py +++ b/models/response/__init__.py @@ -1,13 +1,12 @@ -from models.response.base import BaseResponseModel, NoValue +from models.response.base import NoValue from models.response.comment import ResponseCommentModel from models.response.post import ResponsePostModel from models.response.topic import RootTopicResponseModel, TopicResponseModel from models.response.user import ResponseUserModel __all__ = [ - "ResponsePostModel", - "BaseResponseModel", "NoValue", + "ResponsePostModel", "ResponseCommentModel", "TopicResponseModel", "RootTopicResponseModel", diff --git a/models/response/base.py b/models/response/base.py index 9ebe0aa..f82de3d 100644 --- a/models/response/base.py +++ b/models/response/base.py @@ -1,10 +1,5 @@ -from models.base import BaseModel +from pyruicore import BaseModel -class BaseResponseModel(BaseModel): - def __init__(self, **kwargs): - super().__init__(**kwargs) - - -class NoValue(BaseResponseModel): +class NoValue(BaseModel): pass diff --git a/models/response/comment.py b/models/response/comment.py index 1ef1994..3b990cd 100644 --- a/models/response/comment.py +++ b/models/response/comment.py @@ -1,13 +1,12 @@ -from models.base import Field -from models.data_types import DateTimeType, IntType, StringType -from models.response import BaseResponseModel +from datetime import datetime +from pyruicore import BaseModel -class ResponseCommentModel(BaseResponseModel): - id = Field(IntType(), nullable=False) - user_id = Field(IntType(), nullable=False) - post_id = Field(IntType(), nullable=False) - content = Field(StringType(), nullable=False) - create_time = Field(DateTimeType(), nullable=False) - update_time = Field(DateTimeType(), nullable=False) +class ResponseCommentModel(BaseModel): + id: int + user_id: int + post_id: int + content: str + create_time: datetime + update_time: datetime diff --git a/models/response/post.py b/models/response/post.py index 44f3a42..489e6ea 100644 --- a/models/response/post.py +++ b/models/response/post.py @@ -1,17 +1,16 @@ -from models.base import Field -from models.data_types import DateTimeType, IntType, ListType, StringType -from models.response import BaseResponseModel +from datetime import datetime +from typing import List, Optional +from pyruicore import BaseModel -class ResponsePostModel(BaseResponseModel): - id = Field(IntType(), nullable=False) - user_id = Field(IntType(), nullable=False) - topic_id = Field(IntType(), nullable=False) - content = Field(StringType(), nullable=False) - comments_count = Field(IntType(), nullable=False) - click_times = Field(IntType(), nullable=False) - tags = Field(ListType(StringType()), nullable=True) - - create_time = Field(DateTimeType(), nullable=False) - update_time = Field(DateTimeType(), nullable=False) +class ResponsePostModel(BaseModel): + id: Optional[int] + user_id: Optional[int] + topic_id: Optional[int] + content: str + comments_count: Optional[int] + click_times: Optional[int] + tags: Optional[List[str]] + create_time: datetime + update_time: datetime diff --git a/models/response/topic.py b/models/response/topic.py index 268b819..2ad86f3 100644 --- a/models/response/topic.py +++ b/models/response/topic.py @@ -1,22 +1,19 @@ -from models.base import Field -from models.data_types import ApiDefineType, IntType, ListType, StringType +from typing import List, Optional -from .base import BaseResponseModel +from pyruicore import BaseModel, Field -class TopicResponseModel(BaseResponseModel): - id = Field(IntType(), nullable=False) - name = Field(StringType(), nullable=False, comment="主题名称") - posts_count = Field(IntType(), nullable=True, comment="评论总数") +class TopicResponseModel(BaseModel): + id: Optional[int] + name: Optional[str] = Field(comment="主题名称") + posts_count: Optional[int] = Field(comment="评论总数") -class RootTopicResponseModel(BaseResponseModel): - id = Field(IntType(), nullable=False) - name = Field(StringType(), nullable=False, comment="主题名称") - - child_topics = Field( - ListType(ApiDefineType(TopicResponseModel)), +class RootTopicResponseModel(BaseModel): + id: Optional[int] + name: Optional[str] = Field(comment="主题名称") + child_topics: Optional[List[TopicResponseModel]] = Field( nullable=False, - mock_func=lambda: [], + default_factory=lambda: [], comment="根topic下的所有子topic", ) diff --git a/models/response/user.py b/models/response/user.py index b70b014..cbb395b 100644 --- a/models/response/user.py +++ b/models/response/user.py @@ -1,18 +1,17 @@ -from models.base import Field -from models.data_types import DateTimeType, IntType, StringType -from models.response import BaseResponseModel +from datetime import datetime +from typing import Optional +from pyruicore import BaseModel -class ResponseUserModel(BaseResponseModel): - id = Field(IntType(), nullable=False) - email = Field(StringType(), nullable=False) - name = Field(StringType()) - phone = Field(StringType(), comment='电话号码') - avatar = Field(StringType(), comment="用户头像") - website = Field(StringType(), comment="个人网站") - company = Field(StringType(), comment="所在公司") - job = Field(StringType(), comment="职位") - - create_time = Field(DateTimeType(), nullable=False) - update_time = Field(DateTimeType(), nullable=False) +class ResponseUserModel(BaseModel): + id: Optional[int] + email: Optional[str] + name: Optional[str] + phone: Optional[str] + avatar: Optional[str] + website: Optional[str] + company: Optional[str] + job: Optional[str] + create_time: Optional[datetime] + update_time: Optional[datetime] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..0f24d21 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,27 @@ +[tool.black] +line-length = 100 +exclude = ''' +/( + \.git + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | buck-out + | build + | dist +)/ +''' + +[tool.isort] +skip_gitignore = true +line_length = 100 +indent = ' ' +no_lines_before = "LOCALFOLDER" +multi_line_output = 3 +include_trailing_comma = true +force_grid_wrap = 0 +use_parentheses = true +ensure_newline_before_comments = true +combine_as_imports = true diff --git a/requirements.txt b/requirements.txt index c7cce06..8f1aca2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -49,7 +49,7 @@ networkx==2.5 ninja==1.10.0.post2 nodeenv==1.5.0 packaging==20.7 -parso==0.8.0 +parso==0.7.0 pexpect==4.8.0 pickleshare==0.7.5 pluggy==0.13.1 @@ -81,3 +81,7 @@ Werkzeug==1.0.1 wrapt==1.12.1 zope.event==4.5.0 zope.interface==5.2.0 +colorlog==4.6.2 + +typing~=3.7.4.3 +pyruicore~=0.1.3 diff --git a/resources/__init__.py b/resources/__init__.py index 514f171..e4fa2bc 100644 --- a/resources/__init__.py +++ b/resources/__init__.py @@ -3,9 +3,10 @@ from typing import Any, Callable, Dict, Type, TypeVar from flask import jsonify +from pyruicore import BaseModel from models.query import BaseQueryModel -from models.response import BaseResponseModel, NoValue +from models.response import NoValue schema_mapping = {} Api = TypeVar("Api") @@ -15,7 +16,7 @@ class ResourceSchema: def __init__( self, query_model: Type[BaseQueryModel], - response_model: Type[BaseResponseModel], + response_model: Type[BaseModel], path_parameters, ) -> None: self.query_model = query_model @@ -23,10 +24,10 @@ def __init__( self.path_parameters = path_parameters -def schema(query_model: Type[BaseQueryModel], response_model: Type[BaseResponseModel]): +def schema(query_model: Type[BaseQueryModel], response_model: Type[BaseModel]): def decorator(func): params = list(inspect.signature(func).parameters) - params.remove('self') + params.remove("self") schema_mapping[func.__qualname__] = ResourceSchema(query_model, response_model, params) @wraps(func) diff --git a/resources/user.py b/resources/user.py index 34052d7..9f64e8c 100644 --- a/resources/user.py +++ b/resources/user.py @@ -2,14 +2,14 @@ from authentication import auth from handlers.user import UserHandler -from models.query import UserQueryModel +from models.query import NoArgs, UserQueryModel from models.response import ResponseUserModel from resources import ApiResponse, schema class Users(Resource): - @auth.login_required - @schema(query_model=UserQueryModel, response_model=ResponseUserModel) + # @auth.login_required + @schema(query_model=NoArgs, response_model=ResponseUserModel) def get(self) -> ApiResponse: # 获取所有的用户 users = UserHandler.get_users() @@ -24,20 +24,20 @@ def post(self) -> ApiResponse: class User(Resource): - @auth.login_required + # @auth.login_required @schema(query_model=UserQueryModel, response_model=ResponseUserModel) def get(self, user_id) -> ApiResponse: user = UserHandler(user_id).get_user() return ApiResponse().ok(user) - @auth.login_required + # @auth.login_required @schema(query_model=UserQueryModel, response_model=ResponseUserModel) def put(self, user_id) -> ApiResponse: kwargs = self.parsed_args user = UserHandler(user_id).update(**kwargs) return ApiResponse().ok(user) - @auth.login_required + # @auth.login_required @schema(query_model=UserQueryModel, response_model=ResponseUserModel) def delete(self, user_id) -> ApiResponse: UserHandler(user_id).delete() diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh new file mode 100644 index 0000000..d7d0e99 --- /dev/null +++ b/scripts/entrypoint.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +set -o errexit +set -o pipefail +set -o nounset + +export INIT=${INIT:='no'} + +if [ "${INIT}" = "yes" ]; then + bash scripts/init_db.sh +fi + +exec "$@" diff --git a/scripts/init_db.sh b/scripts/init_db.sh new file mode 100644 index 0000000..cef1a04 --- /dev/null +++ b/scripts/init_db.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +sleep 3 + +# migrate database +echo 'migrate database' +alembic upgrade head diff --git a/server.py b/server.py index 00bf5b1..653ca7b 100644 --- a/server.py +++ b/server.py @@ -1,15 +1,15 @@ import logging + +from gevent import monkey from gevent.pywsgi import WSGIServer from app import create_app, init_logging -from gevent import monkey - monkey.patch_all() def run(): - host = '0.0.0.0' + host = "0.0.0.0" port = 24579 init_logging() diff --git a/tests/__init__.py b/tests/__init__.py index 891e06d..73424d8 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -7,23 +7,25 @@ from app import create_app from configures import settings -from models.database import db +from models.orm import db email = "hrui8005@gmail.com" -password = '11Aa*%$#' +password = "11Aa*%$#" password_hash = generate_password_hash(password) auth_str = f"{email}:{password}" -headers = {'Authorization': 'Basic ' + base64.b64encode(bytes(auth_str, 'ascii')).decode('ascii')} +headers = {"Authorization": "Basic " + base64.b64encode(bytes(auth_str, "ascii")).decode("ascii")} class TestClient(FlaskClient): def open(self, *args, **kwargs): - kwargs['headers'] = headers + kwargs["headers"] = headers return super().open(*args, **kwargs) class BaseTestCase(TestCase): + client: FlaskClient + @classmethod def setUpClass(cls) -> None: cls.engine = create_engine(settings.TEST_SQLALCHEMY_DATABASE) @@ -33,7 +35,7 @@ def setUpClass(cls) -> None: cls.maxDiff = None cls.app = create_app() cls.app.config["SQLALCHEMY_DATABASE_URI"] = settings.TEST_SQLALCHEMY_DATABASE_URI - cls.app.config['TESTING'] = True + cls.app.config["TESTING"] = True db.init_app(cls.app) with cls.app.app_context(): @@ -49,33 +51,8 @@ def setUpClass(cls) -> None: def tearDownClass(cls) -> None: cls.app = create_app() cls.app.config["SQLALCHEMY_DATABASE_URI"] = settings.TEST_SQLALCHEMY_DATABASE_URI - cls.app.config['TESTING'] = True + cls.app.config["TESTING"] = True db.init_app(cls.app) with cls.app.app_context(): db.session.remove() db.drop_all() - -# def setUp(self): -# self.maxDiff = None -# self.app = create_app() -# self.app.config["SQLALCHEMY_DATABASE_URI"] = settings.TEST_SQLALCHEMY_DATABASE_URI -# self.app.config['TESTING'] = True -# -# db.init_app(self.app) -# with self.app.app_context(): -# db.create_all() -# self.app.test_client_class = TestClient -# self.client = self.app.test_client() -# -# self.engine.execute( -# f"INSERT INTO TEST.user (email, password_hash) VALUES ('{email}', '{password_hash}');" -# ) - -# def tearDown(self) -> None: -# self.app = create_app() -# self.app.config["SQLALCHEMY_DATABASE_URI"] = settings.TEST_SQLALCHEMY_DATABASE_URI -# self.app.config['TESTING'] = True -# db.init_app(self.app) -# with self.app.app_context(): -# db.session.remove() -# db.drop_all() diff --git a/tests/test_comment.py b/tests/test_comment.py index 20f0043..6e4e9b1 100644 --- a/tests/test_comment.py +++ b/tests/test_comment.py @@ -1,42 +1,35 @@ +from copy import copy from uuid import uuid4 -from models.database import Comment from tests import BaseTestCase class TestComment(BaseTestCase): - def setUp(self): - super().setUp() - self.url_prefix = "/api/topics/1/posts/1/comments" - - self.root_topic = {"name": uuid4().hex} - self.client.post("/api/root_topics", json=self.root_topic) - self.topic1 = {"name": uuid4().hex, "root_topic_id": 1} - self.posts1 = {"user_id": 1, "content": "this is post1"} - self.comment = {"user_id": 1, "content": "this is comment"} - self.client.post("/api/topics", json=self.topic1) - self.client.post("/api/topics/1/posts", json=self.posts1) - - def tearDown(self) -> None: - with self.app.app_context(): - Comment.query.delete() + @classmethod + def setUpClass(cls) -> None: + super(TestComment, cls).setUpClass() + cls.url_prefix = "/api/topics/1/posts/1/comments" + cls.root_topic = {"name": uuid4().hex} + cls.client.post("/api/root_topics", json=cls.root_topic) + cls.topic1 = {"name": uuid4().hex, "root_topic_id": 1} + cls.posts1 = {"user_id": 1, "content": "this is post1"} + cls.comment = {"user_id": 1, "content": "this is comment"} + cls.client.post("/api/topics", json=cls.topic1) + cls.client.post("/api/topics/1/posts", json=cls.posts1) def test_add_comment(self): - resp = self.client.get(self.url_prefix).json["data"] - self.assertEqual(0, len(resp)) - self.client.post(self.url_prefix, json=self.comment) - resp = self.client.get(self.url_prefix).json["data"] - self.assertEqual(1, len(resp)) + self.assertGreaterEqual(len(resp), 1) def test_add_comment_without_user(self): - self.comment.pop("user_id") - resp = self.client.post(self.url_prefix, json=self.comment) + comment = copy(self.comment) + comment.pop("user_id") + resp = self.client.post(self.url_prefix, json=comment) error_msg = resp.json error_msg.pop("traceback") - expect_error_msg = {'error_code': 400, 'error_msg': 'user_id 不能为 None'} + expect_error_msg = {"error_code": 400, "error_msg": "user_id 不能为 None"} self.assertDictEqual(error_msg, expect_error_msg) def test_add_comment_with_invalid_content(self): @@ -45,7 +38,7 @@ def test_add_comment_with_invalid_content(self): error_msg = resp.json error_msg.pop("traceback") - expect_error_msg = {'error_code': 400, 'error_msg': '不允许全部是空白字符,即至少有一个非空白字符'} + expect_error_msg = {"error_code": 400, "error_msg": "不允许全部是空白字符,即至少有一个非空白字符"} self.assertDictEqual(error_msg, expect_error_msg) def test_add_comment_with_post_or_topic_not_exist(self): @@ -53,14 +46,14 @@ def test_add_comment_with_post_or_topic_not_exist(self): error_msg = resp.json error_msg.pop("traceback") - expect_error_msg = {'error_code': 404, 'error_msg': 'Topic <1000> 不存在'} + expect_error_msg = {"error_code": 404, "error_msg": "Topic <1000> 不存在"} self.assertDictEqual(error_msg, expect_error_msg) resp = self.client.post("/api/topics/1/posts/11/comments", json=self.comment) error_msg = resp.json error_msg.pop("traceback") - expect_error_msg = {'error_code': 404, 'error_msg': 'Post <11> 不存在'} + expect_error_msg = {"error_code": 404, "error_msg": "Post <11> 不存在"} self.assertDictEqual(error_msg, expect_error_msg) def test_update_comment(self): @@ -69,7 +62,7 @@ def test_update_comment(self): error_msg = resp.json error_msg.pop("traceback") - expect_error_msg = {'error_code': 403, 'error_msg': "This operation is not allowed"} + expect_error_msg = {"error_code": 403, "error_msg": "This operation is not allowed"} self.assertDictEqual(error_msg, expect_error_msg) @@ -79,20 +72,15 @@ def test_delete_comment(self): error_msg = resp.json error_msg.pop("traceback") - expect_error_msg = {'error_code': 403, 'error_msg': "This operation is not allowed"} + expect_error_msg = {"error_code": 403, "error_msg": "This operation is not allowed"} self.assertDictEqual(error_msg, expect_error_msg) def test_get_comments(self): - for i in range(1, 4): - self.comment["content"] = str(i) + for i in range(1, 3): self.client.post("/api/topics/1/posts/1/comments", json=self.comment) post = self.client.get("/api/topics/1/posts/1").json["data"] - self.assertGreaterEqual(post["comments_count"],2 ) - - comments = self.client.get(self.url_prefix, json={"per_page": 2}).json["data"] - self.assertEqual(2, len(comments)) - + self.assertGreaterEqual(post["comments_count"], 2) comments = self.client.get(self.url_prefix, json={"per_page": 1, "offset": 2}).json["data"] self.assertEqual(1, len(comments)) diff --git a/tests/test_post.py b/tests/test_post.py index 236a936..1a6941f 100644 --- a/tests/test_post.py +++ b/tests/test_post.py @@ -4,23 +4,22 @@ class TestPost(BaseTestCase): - def setUp(self): - super().setUp() - self.url_prefix = "/api/topics/1/posts" - - self.root_topic = {"name": uuid4().hex} - self.client.post("/api/root_topics", json=self.root_topic) - self.topic1 = {"name": uuid4().hex} - self.topic2 = {"name": uuid4().hex} - self.user1 = {"email": uuid4().hex + "hrui8005@gmail.com", "password": "11Aa*%$#"} - self.user2 = {"email": uuid4().hex + "hrui8006@gmail.com", "password": "11Aa*%$#"} - self.posts1 = {"user_id": 1, "content": "this is post1"} - self.posts2 = {"user_id": 1, "content": "this is post2"} - - self.client.post("/api/topics", json=self.topic1) - self.client.post("/api/topics", json=self.topic2) - self.client.post("/api/users", json=self.user1) - self.client.post("/api/users", json=self.user2) + @classmethod + def setUpClass(cls) -> None: + super(TestPost, cls).setUpClass() + cls.url_prefix = "/api/topics/1/posts" + cls.root_topic = {"name": uuid4().hex} + cls.client.post("/api/root_topics", json=cls.root_topic) + cls.topic1 = {"name": uuid4().hex} + cls.topic2 = {"name": uuid4().hex} + cls.user1 = {"email": uuid4().hex + "hrui8005@gmail.com", "password": "11Aa*%$#"} + cls.user2 = {"email": uuid4().hex + "hrui8006@gmail.com", "password": "11Aa*%$#"} + cls.posts1 = {"user_id": 1, "content": "this is post1"} + cls.posts2 = {"user_id": 1, "content": "this is post2"} + cls.client.post("/api/topics", json=cls.topic1) + cls.client.post("/api/topics", json=cls.topic2) + cls.client.post("/api/users", json=cls.user1) + cls.client.post("/api/users", json=cls.user2) def test_add_post(self): resp = self.client.get(self.url_prefix).json["data"] @@ -39,7 +38,7 @@ def test_add_post_with_user_not_exist(self): error_msg = resp.json error_msg.pop("traceback") - expect_error_msg = {'error_code': 404, 'error_msg': 'User <300> 不存在'} + expect_error_msg = {"error_code": 404, "error_msg": "User <300> 不存在"} self.assertDictEqual(error_msg, expect_error_msg) def test_add_post_with_topic_not_exist(self): @@ -47,23 +46,24 @@ def test_add_post_with_topic_not_exist(self): error_msg = resp.json error_msg.pop("traceback") - expect_error_msg = {'error_code': 404, 'error_msg': 'Topic <300> 不存在'} + expect_error_msg = {"error_code": 404, "error_msg": "Topic <300> 不存在"} self.assertDictEqual(error_msg, expect_error_msg) def test_add_post_with_invalid_content(self): - self.posts1["content"] = "" - resp = self.client.post(self.url_prefix, json=self.posts1) + resp = self.client.post(self.url_prefix, json={"user_id": 1, "content": ""}) error_msg = resp.json error_msg.pop("traceback") - expect_error_msg = {'error_code': 400, 'error_msg': 'Post 文章字数不能少于 <5> 个'} + expect_error_msg = {"error_code": 400, "error_msg": "Post 文章字数不能少于 <5> 个"} self.assertDictEqual(error_msg, expect_error_msg) def test_update_post(self): - res = self.client.post(self.url_prefix, json=self.posts1) + res = self.client.post(self.url_prefix, json={"user_id": 1, "content": "this is post1"}) new_post = {"user_id": 1, "content": "this is new post"} - resp = self.client.put(self.url_prefix + f"/{res.json['data']['id']}", json=new_post).json["data"] + resp = self.client.put(self.url_prefix + f"/{res.json['data']['id']}", json=new_post).json[ + "data" + ] data = {"user_id": resp["user_id"], "content": resp["content"]} self.assertEqual(new_post, data) @@ -72,26 +72,23 @@ def test_delete_post(self): self.client.post(self.url_prefix, json=self.posts1) self.client.post(self.url_prefix, json=self.posts2) - resp = self.client.get(self.url_prefix).json['data'] + resp = self.client.get(self.url_prefix).json["data"] self.assertGreaterEqual(len(resp), 2) self.client.delete(self.url_prefix + "/1") self.client.delete(self.url_prefix + "/2") - resp = self.client.get(self.url_prefix).json['data'] + resp = self.client.get(self.url_prefix).json["data"] self.assertLessEqual(0, len(resp)) def test_get_posts(self): posts = {"user_id": 1, "content": ""} user = {"email": uuid4().hex + "hrui8005@gmail.com", "password": "11Aa*%$#"} - self.client.post("/api/topics", json=self.topic1) + self.client.post("/api/topics", json={"name": uuid4().hex}) self.client.post("/api/users", json=user) - for i in range(1, 11): + for i in range(1, 3): posts["content"] = "this is post {}".format(str(i)) self.client.post("/api/topics/1/posts", json=posts) topics = self.client.get(self.url_prefix, json={"per_page": 2}).json["data"] self.assertEqual(2, len(topics)) - - topics = self.client.get(self.url_prefix, json={"per_page": 2, "offset": 5}).json["data"] - self.assertEqual("this is post 5", topics[0]["content"]) diff --git a/tests/test_root_topic.py b/tests/test_root_topic.py index 8039e43..db8fc61 100644 --- a/tests/test_root_topic.py +++ b/tests/test_root_topic.py @@ -1,20 +1,16 @@ -from models.database import RootTopic from tests import BaseTestCase class TestRootTopic(BaseTestCase): - def setUp(self): - super().setUp() - self.url_prefix = "/api/root_topics" - self.root_topic1 = {"name": "root_topic1"} - self.root_topic2 = {"name": "root_topic2"} - self.topic1 = {"name": "Topic1"} - self.topic2 = {"name": "Topic2"} - self.topic3 = {"name": "Topic3"} - - def tearDown(self) -> None: - with self.app.app_context(): - RootTopic.query.delete() + @classmethod + def setUpClass(cls) -> None: + super(TestRootTopic, cls).setUpClass() + cls.url_prefix = "/api/root_topics" + cls.root_topic1 = {"name": "root_topic1"} + cls.root_topic2 = {"name": "root_topic2"} + cls.topic1 = {"name": "Topic1"} + cls.topic2 = {"name": "Topic2"} + cls.topic3 = {"name": "Topic3"} def test_add_root_topic(self): resp = self.client.get(self.url_prefix).json["data"] @@ -41,7 +37,7 @@ def test_delete_root_topic(self): error_msg = resp.json error_msg.pop("traceback") - expect_error_msg = {'error_code': 403, 'error_msg': '根主题不允许被删除'} + expect_error_msg = {"error_code": 403, "error_msg": "根主题不允许被删除"} self.assertDictEqual(expect_error_msg, error_msg) def test_add_invalid_root_topic(self): @@ -52,7 +48,7 @@ def test_add_invalid_root_topic(self): error_msg = resp.json error_msg.pop("traceback") - expect_error_msg = {'error_code': 403, 'error_msg': '名称为 的根 Topic 已经创建'} + expect_error_msg = {"error_code": 403, "error_msg": "名称为 的根 Topic 已经创建"} self.assertDictEqual(expect_error_msg, error_msg) new_root_topic = {"name": ""} @@ -61,8 +57,8 @@ def test_add_invalid_root_topic(self): error_msg.pop("traceback") expect_error_msg = { - 'error_code': 400, - 'error_msg': '名称中只允许出现【中文,英文,数字,下划线,连接符】,并且不允许全部是空白字符', + "error_code": 400, + "error_msg": "名称中只允许出现【中文,英文,数字,下划线,连接符】,并且不允许全部是空白字符", } self.assertDictEqual(expect_error_msg, error_msg) @@ -71,5 +67,5 @@ def test_add_invalid_root_topic(self): error_msg = resp.json error_msg.pop("traceback") - expect_error_msg = {'error_code': 400, 'error_msg': '根主题名不能为空'} + expect_error_msg = {"error_code": 400, "error_msg": "根主题名不能为空"} self.assertDictEqual(expect_error_msg, error_msg) diff --git a/tests/test_topic.py b/tests/test_topic.py index 73de267..f3a2f20 100644 --- a/tests/test_topic.py +++ b/tests/test_topic.py @@ -1,25 +1,21 @@ from uuid import uuid4 -from models.database import Topic from tests import BaseTestCase class TestTopics(BaseTestCase): - def setUp(self): - super().setUp() - self.url_prefix = "/api/topics" - self.root_topic = {"name": uuid4().hex} - self.client.post("/api/root_topics", json=self.root_topic) - self.topic1 = {"name": uuid4().hex} - self.topic2 = {"name": uuid4().hex} - self.topic3 = {"name": uuid4().hex} - self.topic4 = {"name": ""} - self.topic5 = {"name": "abc&^%3"} - self.topic6 = {"name": "update"} - - def tearDown(self) -> None: - with self.app.app_context(): - Topic.query.delete() + @classmethod + def setUpClass(cls) -> None: + super(TestTopics, cls).setUpClass() + cls.url_prefix = "/api/topics" + cls.root_topic = {"name": uuid4().hex} + cls.client.post("/api/root_topics", json=cls.root_topic) + cls.topic1 = {"name": uuid4().hex} + cls.topic2 = {"name": uuid4().hex} + cls.topic3 = {"name": uuid4().hex} + cls.topic4 = {"name": ""} + cls.topic5 = {"name": "abc&^%3"} + cls.topic6 = {"name": "update"} def test_add_topic(self): resp = self.client.get(self.url_prefix).json["data"] @@ -36,8 +32,8 @@ def test_add_topic_with_invalid_name(self): error_msg.pop("traceback") expect_error_msg = { - 'error_code': 400, - 'error_msg': '名称中只允许出现【中文,英文,数字,下划线,连接符】,并且不允许全部是空白字符', + "error_code": 400, + "error_msg": "名称中只允许出现【中文,英文,数字,下划线,连接符】,并且不允许全部是空白字符", } self.assertDictEqual(error_msg, expect_error_msg) @@ -52,7 +48,10 @@ def test_add_topic_with_exist_name(self): error_msg = resp.json error_msg.pop("traceback") - expect_error_msg = {'error_code': 403, 'error_msg': f'名称为 <{self.topic1["name"]}> 的 Topic 已经创建'} + expect_error_msg = { + "error_code": 403, + "error_msg": f'名称为 <{self.topic1["name"]}> 的 Topic 已经创建', + } self.assertDictEqual(error_msg, expect_error_msg) def test_update_topic(self): @@ -60,7 +59,7 @@ def test_update_topic(self): resp = self.client.put(self.url_prefix + "/1", json=self.topic6).json["data"] resp.pop("id") - self.topic6["posts_count"] = 10 + self.topic6["posts_count"] = 2 self.assertDictEqual(self.topic6, resp) def test_update_topic_with_invalid_name(self): @@ -71,7 +70,10 @@ def test_update_topic_with_invalid_name(self): error_msg = resp.json error_msg.pop("traceback") - expect_error_msg = {'error_code': 403, 'error_msg': f'名称为 <{self.topic1["name"]}> 的 Topic 已经创建'} + expect_error_msg = { + "error_code": 403, + "error_msg": f'名称为 <{self.topic1["name"]}> 的 Topic 已经创建', + } self.assertDictEqual(error_msg, expect_error_msg) resp = self.client.put(self.url_prefix + "/2", json=self.topic4) @@ -79,8 +81,8 @@ def test_update_topic_with_invalid_name(self): error_msg.pop("traceback") expect_error_msg = { - 'error_code': 400, - 'error_msg': '名称中只允许出现【中文,英文,数字,下划线,连接符】,并且不允许全部是空白字符', + "error_code": 400, + "error_msg": "名称中只允许出现【中文,英文,数字,下划线,连接符】,并且不允许全部是空白字符", } self.assertDictEqual(error_msg, expect_error_msg) @@ -89,17 +91,17 @@ def test_update_topic_with_invalid_name(self): error_msg.pop("traceback") expect_error_msg = { - 'error_code': 400, - 'error_msg': '名称中只允许出现【中文,英文,数字,下划线,连接符】,并且不允许全部是空白字符', + "error_code": 400, + "error_msg": "名称中只允许出现【中文,英文,数字,下划线,连接符】,并且不允许全部是空白字符", } self.assertDictEqual(error_msg, expect_error_msg) def test_delete_topic(self): - res = self.client.post(self.url_prefix, json=self.topic1) + res = self.client.post(self.url_prefix, json={"name": uuid4().hex}) self.client.post(self.url_prefix, json=self.topic2) resp = self.client.get(self.url_prefix).json["data"] - self.assertEqual(4, len(resp)) + self.assertGreaterEqual(4, len(resp)) self.client.delete(self.url_prefix + f"/{res.json['data']['id']}") @@ -113,9 +115,9 @@ def test_get_topic_with_some_posts(self): ) # create user posts = {"user_id": 1, "content": "this is post1"} - for _ in range(10): + for _ in range(2): self.client.post("/api/topics/1/posts", json=posts) resp = self.client.get(self.url_prefix).json["data"] - self.assertEqual(10, resp[0]["posts_count"]) + self.assertEqual(2, resp[0]["posts_count"]) diff --git a/tests/test_user.py b/tests/test_user.py index 4fedc75..9a67f78 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -1,18 +1,14 @@ from datetime import datetime from uuid import uuid4 -from models.database import User from tests import BaseTestCase class TestUsers(BaseTestCase): - def setUp(self): - super().setUp() - self.url_prefix = "/api/users" - - def tearDown(self) -> None: - with self.app.app_context(): - User.query.delete() + @classmethod + def setUpClass(cls) -> None: + super(TestUsers, cls).setUpClass() + cls.url_prefix = "/api/users" def test_add_user(self): exist_user = self.client.get(self.url_prefix).json["data"] @@ -34,8 +30,8 @@ def test_add_user_with_wrong_password(self): error_msg.pop("traceback") expect_error_msg = { - 'error_code': 400, - 'error_msg': '密码至少8个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符,不能含有空格', + "error_code": 400, + "error_msg": "密码至少8个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符,不能含有空格", } self.assertDictEqual(error_msg, expect_error_msg) @@ -46,7 +42,7 @@ def test_add_user_with_wrong_email(self): error_msg = resp.json error_msg.pop("traceback") - expect_error_msg = {'error_code': 400, 'error_msg': '邮箱格式错误'} + expect_error_msg = {"error_code": 400, "error_msg": "邮箱格式错误"} self.assertDictEqual(error_msg, expect_error_msg) def test_add_user_without_email(self): @@ -56,7 +52,7 @@ def test_add_user_without_email(self): error_msg = resp.json error_msg.pop("traceback") - expect_error_msg = {'error_code': 400, 'error_msg': '邮件地址不能为空'} + expect_error_msg = {"error_code": 400, "error_msg": "邮件地址不能为空"} self.assertDictEqual(error_msg, expect_error_msg) def test_add_user_without_password(self): @@ -66,7 +62,7 @@ def test_add_user_without_password(self): error_msg = resp.json error_msg.pop("traceback") - expect_error_msg = {'error_code': 400, 'error_msg': '密码不能为空'} + expect_error_msg = {"error_code": 400, "error_msg": "密码不能为空"} self.assertDictEqual(error_msg, expect_error_msg) def test_add_user_with_duplicate_email(self): @@ -81,7 +77,7 @@ def test_add_user_with_duplicate_email(self): error_msg = resp.json error_msg.pop("traceback") - expect_error_msg = {'error_code': 403, 'error_msg': f'邮件为 <{email}> 的用户已经注册'} + expect_error_msg = {"error_code": 403, "error_msg": f"邮件为 <{email}> 的用户已经注册"} self.assertDictEqual(error_msg, expect_error_msg) def test_update_user_email(self): @@ -89,11 +85,19 @@ def test_update_user_email(self): res = self.client.post(self.url_prefix, json=new_user) kwargs = {"email": "hrui835@gmail.com"} - user_id = res.json['data']['id'] + user_id = res.json["data"]["id"] resp = self.client.put(self.url_prefix + f"/{user_id}", json=kwargs) - expect_user = {'avatar': None, 'company': None, 'email': 'hrui835@gmail.com', 'id': user_id, 'job': None, - 'name': None, 'phone': None, 'website': None} + expect_user = { + "avatar": None, + "company": None, + "email": "hrui835@gmail.com", + "id": user_id, + "job": None, + "name": None, + "phone": None, + "website": None, + } update_user = resp.json["data"] self.assertEqual(200, resp.status_code) @@ -102,10 +106,10 @@ def test_update_user_email(self): self.assertDictEqual(update_user, expect_user) def test_update_user_with_wrong_email(self): - new_user = {"email":uuid4().hex+ "suepr76rui@icloud.com", "password": "b22sw1*#DJfyxoUaq"} + new_user = {"email": uuid4().hex + "suepr76rui@icloud.com", "password": "b22sw1*#DJfyxoUaq"} res = self.client.post(self.url_prefix, json=new_user) - user_id = res.json['data']['id'] + user_id = res.json["data"]["id"] kwargs = {"email": "hrui835gmail.com"} resp = self.client.put(self.url_prefix + f"/{user_id}", json=kwargs) @@ -113,13 +117,13 @@ def test_update_user_with_wrong_email(self): error_msg = resp.json error_msg.pop("traceback") - expect_error_msg = {'error_code': 400, 'error_msg': '邮箱格式错误'} + expect_error_msg = {"error_code": 400, "error_msg": "邮箱格式错误"} self.assertDictEqual(error_msg, expect_error_msg) def test_update_user_with_wrong_password(self): - new_user = {"email": uuid4().hex+ "suepr76rui@icloud.com", "password": "b22sw1*#DJfyxoUaq"} + new_user = {"email": uuid4().hex + "suepr76rui@icloud.com", "password": "b22sw1*#DJfyxoUaq"} res = self.client.post(self.url_prefix, json=new_user) - user_id = res.json['data']['id'] + user_id = res.json["data"]["id"] kwargs = {"password": "1111"} resp = self.client.put(self.url_prefix + f"/{user_id}", json=kwargs) @@ -128,8 +132,8 @@ def test_update_user_with_wrong_password(self): error_msg.pop("traceback") expect_error_msg = { - 'error_code': 400, - 'error_msg': '密码至少8个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符,不能含有空格', + "error_code": 400, + "error_msg": "密码至少8个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符,不能含有空格", } self.assertDictEqual(error_msg, expect_error_msg) @@ -143,7 +147,7 @@ def test_delete(self): resp = self.client.delete(self.url_prefix + "/1") error_msg = resp.json - expect_error_msg = {'data': {}, 'error_code': 0, 'error_msg': 'success'} + expect_error_msg = {"data": {}, "error_code": 0, "error_msg": "success"} self.assertDictEqual(error_msg, expect_error_msg) resp = self.client.get(self.url_prefix).json["data"] @@ -155,5 +159,5 @@ def test_get_user_not_existed(self): error_msg = resp.json error_msg.pop("traceback") - expect_error_msg = {'error_code': 404, 'error_msg': '用户 <100> 不存在'} + expect_error_msg = {"error_code": 404, "error_msg": "用户 <100> 不存在"} self.assertDictEqual(error_msg, expect_error_msg)