Skip to content
Permalink
Browse files

[#4] Flask Large Application Example 적용

  • Loading branch information...
JoMingyu committed Feb 23, 2019
1 parent 9cb014c commit 23fe371ac95bf4eab94f95a3c5a291e9d7f725bf
@@ -0,0 +1,41 @@
from flask import Flask
from werkzeug.exceptions import HTTPException

from app.misc.log import log


def register_extensions(flask_app: Flask):
from app import extensions

extensions.cors.init_app(flask_app)


def register_views(flask_app: Flask):
from app.views import route

route(flask_app)


def register_hooks(flask_app: Flask):
from app.hooks.error import broad_exception_handler, http_exception_handler
from app.hooks.request_context import after_request

flask_app.after_request(after_request)
flask_app.register_error_handler(HTTPException, http_exception_handler)
flask_app.register_error_handler(Exception, broad_exception_handler)


def create_app(*config_cls) -> Flask:
log(message='Flask application initialized with {}'.format(', '.join([config.__name__ for config in config_cls])),
keyword='INFO')

flask_app = Flask(__name__)

for config in config_cls:
flask_app.config.from_object(config)

register_extensions(flask_app)
register_views(flask_app)
register_hooks(flask_app)

return flask_app
@@ -0,0 +1,18 @@
from flask import current_app, request


class _ContextProperty:
@property
def secret_key(self) -> str:
return current_app.secret_key

@property
def user_agent(self) -> str:
return request.headers['user_agent']

@property
def remote_addr(self) -> str:
return request.remote_addr


context_property = _ContextProperty()
No changes.
@@ -0,0 +1,22 @@
from functools import wraps

from flask import abort, request
from jsonschema import ValidationError, validate


def validate_with_jsonschema(jsonschema: dict, *, strict):
def decorator(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
if request.is_json:
try:
validate(request.json, jsonschema)
except ValidationError:
abort(400)
else:
if strict:
abort(406)

return fn(*args, **kwargs)
return wrapper
return decorator
@@ -0,0 +1,3 @@
from flask_cors import CORS

cors = CORS()
No changes.
@@ -0,0 +1,9 @@
from werkzeug.exceptions import HTTPException


def http_exception_handler(e: HTTPException):
return '', e.code


def broad_exception_handler(e: Exception):
return '', 500
@@ -0,0 +1,9 @@
from flask import Response


def after_request(response: Response) -> Response:
try:
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-Frame-Options'] = 'deny'
finally:
return response
No changes.
@@ -0,0 +1,12 @@
from termcolor import colored


def log(message: str, keyword: str="WARN"):
if keyword == "WARN":
print(colored('[WARN]', 'yellow'), message)
elif keyword == "ERROR":
print(colored('[ERROR] ' + message, 'red'))
elif keyword == "INFO":
print(colored('[INFO]', 'blue'), message)
else:
print(colored('[{}]'.format(type), 'cyan'), message)
No changes.
@@ -0,0 +1,25 @@
from flask import Blueprint, Flask
from flask_restful import Api


def route(flask_app: Flask):
from app.views.sample import sample

handle_exception_func = flask_app.handle_exception
handle_user_exception_func = flask_app.handle_user_exception
# register_blueprint 시 defer되었던 함수들이 호출되며, flask-restful.Api._init_app()이 호출되는데
# 해당 메소드가 app 객체의 에러 핸들러를 오버라이딩해서, 별도로 적용한 handler의 HTTPException 관련 로직이 동작하지 않음
# 따라서 두 함수를 임시 저장해 두고, register_blueprint 이후 함수를 재할당하도록 함

# - blueprint, api object initialize
api_v1_blueprint = Blueprint('api_v1', __name__, url_prefix='/api/v1')
api_v1 = Api(api_v1_blueprint)

# - route
api_v1.add_resource(sample.Sample, '/sample')

# - register blueprint
flask_app.register_blueprint(api_v1_blueprint)

flask_app.handle_exception = handle_exception_func
flask_app.handle_user_exception = handle_user_exception_func
@@ -0,0 +1,10 @@
import arrow
from flask_restful import Resource


class BaseResource(Resource):
def __init__(self):
self.utcnow = arrow.utcnow()
self.kstnow = self.utcnow.to('Asia/Seoul')
self.iso8601_formatted_utcnow = self.utcnow.isoformat()
self.iso8601_formatted_kstnow = self.kstnow.isoformat()
No changes.
@@ -0,0 +1,24 @@
from flask import request

from app.decorators.json_validator import validate_with_jsonschema
from app.views.base import BaseResource


class Sample(BaseResource):
@validate_with_jsonschema({
'type': 'object',
'required': ['age', 'name'],
'properties': {
'age': {
'type': 'integer',
'minimum': 0
},
'name': {
'type': 'string'
}
}
}, strict=True)
def post(self):
payload = request.json

return payload, 201
No changes.
@@ -0,0 +1,9 @@
import os


class LocalLevelConfig:
SECRET_KEY = os.getenv('SECRET_KEY', '85c145a16bd6f6e1f3e104ca78c6a102')


class ProductionLevelConfig:
SECRET_KEY = os.getenv('SECRET_KEY')
@@ -0,0 +1,6 @@
class LocalDBConfig:
pass


class RemoteDBConfig:
pass
No changes.
@@ -0,0 +1,6 @@
RUN_SETTING = {
'host': 'localhost',
'port': 5000,
'debug': True,
'threaded': True
}
@@ -0,0 +1,10 @@
import os

from app import create_app
from config.app_config import ProductionLevelConfig
from config.db_config import RemoteDBConfig

if 'SECRET_KEY' not in os.environ:
raise Warning('The secret key must be passed by the <SECRET_KEY> envvar.')

application = create_app(ProductionLevelConfig, RemoteDBConfig)
15 run.py
@@ -1,12 +1,9 @@
from flask import Flask

app = Flask(__name__)


@app.route('/')
def index():
return 'This is version 2'
from app import create_app
from config.app_config import LocalLevelConfig
from config.db_config import LocalDBConfig
from constants.local_run import RUN_SETTING

app = create_app(LocalLevelConfig, LocalDBConfig)

if __name__ == '__main__':
app.run()
app.run(**RUN_SETTING)

0 comments on commit 23fe371

Please sign in to comment.
You can’t perform that action at this time.