フラスコ静止API -パート4



第4部:例外処理
危ない!シリーズの前で、我々は我々が加えることができる方法を学びましたauthentication and authorization . この部分では、エラーに対してフラスコアプリケーションをより強力にし、適切なエラーメッセージをクライアントに送信する方法を学びます.
コンピュータプログラムで何かがうまくいかないとき、プログラムは特定のものをスローするException 何が間違っていたユーザーにいくつかのヒントを与える.我々のアプリケーションで何かが間違っているときにユーザーが既に使用されるメールアドレスを別のアカウントを作成しようとすると、彼らはInternal Server Error そして、クライアントは彼らが何を間違っていたか全くわからない.そこで、このような問題を解決するためにException Handling このような例外をキャッチし、エラーメッセージをクライアントに送信し、間違ったことを示します.
我々は本当に便利な機能を使用するつもりですflask-restful それは我々を定義させますCustom Error Messages .
新しいファイルを作りましょうerrors.py インサイドresources フォルダを追加し、次のコードを追加します
cd resources
touch errors.py
#~/movie-bag/resources/errors.py

class InternalServerError(Exception):
    pass

class SchemaValidationError(Exception):
    pass

class MovieAlreadyExistsError(Exception):
    pass

class UpdatingMovieError(Exception):
    pass

class DeletingMovieError(Exception):
    pass

class MovieNotExistsError(Exception):
    pass

class EmailAlreadyExistsError(Exception):
    pass

class UnauthorizedError(Exception):
    pass

errors = {
    "InternalServerError": {
        "message": "Something went wrong",
        "status": 500
    },
     "SchemaValidationError": {
         "message": "Request is missing required fields",
         "status": 400
     },
     "MovieAlreadyExistsError": {
         "message": "Movie with given name already exists",
         "status": 400
     },
     "UpdatingMovieError": {
         "message": "Updating movie added by other is forbidden",
         "status": 403
     },
     "DeletingMovieError": {
         "message": "Deleting movie added by other is forbidden",
         "status": 403
     },
     "MovieNotExistsError": {
         "message": "Movie with given id doesn't exists",
         "status": 400
     },
     "EmailAlreadyExistsError": {
         "message": "User with given email address already exists",
         "status": 400
     },
     "UnauthorizedError": {
         "message": "Invalid username or password",
         "status": 401
     }
}
最初に見ることができるようにException 別のカスタム例外を作成し、errors 各例外のエラーメッセージとステータスコードを含む辞書.さて、これらのエラーをflask-restful Api クラス.
更新app.py 最近インポートするインポートerrors 辞書として追加しますApi クラス.
#~/movie-bag/app.py

from database.db import initialize_db
 from flask_restful import Api
 from resources.routes import initialize_routes
+from resources.errors import errors

 app = Flask(__name__)
 app.config.from_envvar('ENV_FILE_LOCATION')

-api = Api(app)
+api = Api(app, errors=errors)
 bcrypt = Bcrypt(app)
 jwt = JWTManager(app)

最後に、アプリケーションでいくつかの例外処理を行う準備ができました.更新movie.py 以下の関数を表示します
#~/movie-bag/resources/movie.py
 from flask import Response, request
 from database.models import Movie, User
 from flask_jwt_extended import jwt_required, get_jwt_identity
 from flask_restful import Resource
+
+from mongoengine.errors import FieldDoesNotExist, \
+NotUniqueError, DoesNotExist, ValidationError, InvalidQueryError
+
+from resources.errors import SchemaValidationError, +MovieAlreadyExistsError, \
+InternalServerError, UpdatingMovieError, DeletingMovieError, +MovieNotExistsError
+

 class MoviesApi(Resource):
     def get(self):
@@ -11,32 +15,57 @@ class MoviesApi(Resource):

     @jwt_required
     def post(self):
-        user_id = get_jwt_identity()
-        body = request.get_json()
-        user = User.objects.get(id=user_id)
-        movie =  Movie(**body, added_by=user)
-        movie.save()
-        user.update(push__movies=movie)
-        user.save()
-        id = movie.id
-        return {'id': str(id)}, 200
-        
+        try:
+            user_id = get_jwt_identity()
+            body = request.get_json()
+            user = User.objects.get(id=user_id)
+            movie =  Movie(**body, added_by=user)
+            movie.save()
+            user.update(push__movies=movie)
+            user.save()
+            id = movie.id
+            return {'id': str(id)}, 200
+        except (FieldDoesNotExist, ValidationError):
+            raise SchemaValidationError
+        except NotUniqueError:
+            raise MovieAlreadyExistsError
+        except Exception as e:
+            raise InternalServerError
+
+
 class MovieApi(Resource):
     @jwt_required
     def put(self, id):
-        user_id = get_jwt_identity()
-        movie = Movie.objects.get(id=id, added_by=user_id)
-        body = request.get_json()
-        Movie.objects.get(id=id).update(**body)
-        return '', 200
+        try:
+            user_id = get_jwt_identity()
+            movie = Movie.objects.get(id=id, added_by=user_id)
+            body = request.get_json()
+            Movie.objects.get(id=id).update(**body)
+            return '', 200
+        except InvalidQueryError:
+            raise SchemaValidationError
+        except DoesNotExist:
+            raise UpdatingMovieError
+        except Exception:
+            raise InternalServerError       

     @jwt_required
     def delete(self, id):
-        user_id = get_jwt_identity()
-        movie = Movie.objects.get(id=id, added_by=user_id)
-        movie.delete()
-        return '', 200
+        try:
+            user_id = get_jwt_identity()
+            movie = Movie.objects.get(id=id, added_by=user_id)
+            movie.delete()
+            return '', 200
+        except DoesNotExist:
+            raise DeletingMovieError
+        except Exception:
+            raise InternalServerError

     def get(self, id):
-        movies = Movie.objects.get(id=id).to_json()
-        return Response(movies, mimetype="application/json", status=200)
+        try:
+            movies = Movie.objects.get(id=id).to_json()
+            return Response(movies, mimetype="application/json", status=200)
+        except DoesNotExist:
+            raise MovieNotExistsError
+        except Exception:
+            raise InternalServerError


の例を見ましょうpost メソッドMoviesApi クラス
def post(self):
  try:
      user_id = get_jwt_identity()
      body = request.get_json()
      user = User.objects.get(id=user_id)
      movie =  Movie(**body, added_by=user)
      movie.save()
      user.update(push__movies=movie)
      user.save()
      id = movie.id
      return {'id': str(id)}, 200
  except (FieldDoesNotExist, ValidationError):
      raise SchemaValidationError
  except NotUniqueError:
      raise MovieAlreadyExistsError
  except Exception as e:
      raise InternalServerError
ここでは、我々は全体のビューのopetationstry...except ブロック.私たちは例外連鎖を実行しました.したがって、どんな例外も得るとき、私たちは私たちが2000年に定義した例外を投げますerrors.py and flask-restful で定義した値に基づいてレスポンスを生成するerrors 辞書.
ある場合FieldDoesNotExist 例外ValidationError 例外mongoengine 我々は上げるSchemaValidationError クライアントがリクエストJSONが無効であることを通知する例外です.同様に、ユーザーが既に存在する名前でムービーを作成しようとするとmongoengine スローするNotUniqueError 例外を取得し、例外をキャッチしますMovieAlreadyExistsError これは、ユーザーが映画名が既に存在することを伝えます.
そして最後に、もし我々が期待していない例外を得るなら、私たちはInternalServerError .
同様の例外処理を追加しましょうauth.py
#~/movie-bag/resources/auth.py

 from database.models import User
 from flask_restful import Resource
 import datetime
+from mongoengine.errors import FieldDoesNotExist, NotUniqueError, DoesNotExist
+from resources.errors import SchemaValidationError, EmailAlreadyExistsError, UnauthorizedError, \
+InternalServerError

 class SignupApi(Resource):
     def post(self):
-        body = request.get_json()
-        user =  User(**body)
-        user.hash_password()
-        user.save()
-        id = user.id
-        return {'id': str(id)}, 200
+        try:
+            body = request.get_json()
+            user =  User(**body)
+            user.hash_password()
+            user.save()
+            id = user.id
+            return {'id': str(id)}, 200
+        except FieldDoesNotExist:
+            raise SchemaValidationError
+        except NotUniqueError:
+            raise EmailAlreadyExistsError
+        except Exception as e:
+            raise InternalServerError

 class LoginApi(Resource):
     def post(self):
-        body = request.get_json()
-        user = User.objects.get(email=body.get('email'))
-        authorized = user.check_password(body.get('password'))
-        if not authorized:
-            return {'error': 'Email or password invalid'}, 401
-        expires = datetime.timedelta(days=7)
-        access_token = create_access_token(identity=str(user.id), expires_delta=expires)
-        return {'token': access_token}, 200
+        try:
+            body = request.get_json()
+            user = User.objects.get(email=body.get('email'))
+            authorized = user.check_password(body.get('password'))
+            if not authorized:
+                raise UnauthorizedError
+ 
+            expires = datetime.timedelta(days=7)
+            access_token = create_access_token(identity=str(user.id), expires_delta=expires)
+            return {'token': access_token}, 200
+        except (UnauthorizedError, DoesNotExist):
+            raise UnauthorizedError
+        except Exception as e:
+            raise InternalServerError
それは、人々です.さて、我々のアプリケーションのエラーがあるときに適切なエラーメッセージを適切なステータスコードを取得します.
ユーザーを作成してみましょう/api/auth/signup どんな電子メールアドレスでも、言いましょう[email protected] . 次に、同じメールアドレスで別のユーザーを作成してみましょう.以下のようなレスポンスを得る.
{
    "message": "User with given email address already exists",
    "status": 400
}
今、我々のアプリケーションのユーザーが簡単に何が間違って知っていることができます.
この部分の完全なコードを見つけることができますhere

我々がシリーズのこの部分から学んだこと?
  • 例外連鎖を使用してフラスコアプリケーションの例外を処理する方法.
  • 例外に基づいてエラーメッセージとステータスコードを送る方法.
  • シリーズの次の部分では、我々はどのように我々のアプリケーションでパスワードのリセットを実行する方法を学びます.
    それまでハッピーコーディング😊