[Python] Flask-RestPlus 모듈 제대로 사용해 보기

지난번에 [Python] Flask & flask-restplus && swagger ui 라는 블로그를 정리했었습니다.
그런데 이것을 기존에 작업되어 있던 것에 적용을 하는데
이틀정도 시행착오를 겪었습니다. 
아직 0.10.1 이라는 버전 때문에 그럴 수도 있지만 뭐 현실에 적응해서 앞으로 나아가는게
우선이라...
(나중에라도 버전업이 되어 원하는 데로 되어 간다면 내용을 수정해 놓겠습니다)

다음은 작업되어 있는 것을 수정한 내용입니다.

우선 기존 Blueprint 로 되어 있던 부분을 Namespace로 변경합니다.

기존,
bp_aaa = Blueprint('bp_aaa', __name__, url_prefix='/api/aaa')

변경,
api = Namespace('ns_aaa', description='User AAA for RESTful API')

(Bludprint에서는 prefix를 미리 주었는데, Namespace 는 나중에 주어도 됩니다)

flask-restplus는 function을 route 시킬 수 없고 Resource에서 파생된 클래스만 이용할 수 있습니다.
따라서, 다음과 같이 기존에 function은 class로 변경합니다.

################################################################################
@bp_aaa.route('/ping', methods=['GET', 'PUT', 'POST'])
def rapi_ping():
    r = None
    atlog = AuditLog('[AAA] Accessing API ping')
    try:
        r = {'success': True, 'http_method': request.method}
    except Exception as exp:
        atlog.set_except(exp)
        r = {'success': False, 'message': str(exp)}
    finally:
        atlog.set_user(current_user)
        atlog.save()
        return jsonify(r)

대신

################################################################################
# noinspection PyMethodMayBeStatic
@api.route('/ping')
class Ping(Resource):
    # ==========================================================================
    def get(self):
        """
        API ping to check alive or not

        # Input Arguments
        None
        """
        r = None
        atlog = AuditLog('[AAA] Accessing API ping')
        try:
            r = {'success': True, 'http_method': request.method}
        except Exception as exp:
            atlog.set_except(exp)
            r = {'success': False, 'message': str(exp)}
        finally:
            atlog.set_user(current_user)
            atlog.save()
            return r

또한 만약  @api.route(...) 다음에 @xxyyzz 데코레이터가 있다면 이 역시 function으로 인식해 오류가 발생하므로
해당 데코레이터를 @api.route 위에 두던가 해야 합니다.

그 다음 swagger를 위한 작업입니다.
(기존 Flask 및 Flask-Restful 에서 지원하지 않던 부분입니다)

우선 다음과 같은 코드를 추가합니다.

json_parser = api.parser()
json_parser.add_argument('json', type=str, required=True, location='json',
                         help='JSON BODY argument')
arg_parser = api.parser()
arg_parser.add_argument('json', type=str, required=True,
                        help='URL JSON argument')

Model과도 적용을 해 보았지만 swagger 에서 보내는 등의 시행착으로 겪었습니다.

다음과 같은 식으로 정리하면 되었습니다.

################################################################################
# noinspection PyMethodMayBeStatic
@api.route('/login')
class Login(Resource):
    # ==========================================================================
    res_model = api.model('Model', {
        'success': fields.Boolean(description='API Success/Failure',
                                  required=True),
        'message': fields.String(description='Success/Failure message',
                                 required=True),
    })

    # ==========================================================================
    @api.doc(parser=json_parser)
    @api.response(200, 'API Success/Failure', res_model)
    @api.response(400, 'Failure')
    @api.response(500, 'Error')
    def post(self):
        """
        checking User login

        *** If login required API is called without authentication raise
        401 HTTP Error ***

        # Input Arguments

        * user_id : str : required : user ID
        * password : str : required : user password

        # Example
        ``` json
        {
            "user_id": "user01",
            "password": "passwd"
        }
        ```
        """
        r = None
        atlog = AuditLog('[AAA] Login checking')
        try:
            # args = json_parser.parse_args()
            args = parse_req_data(request)
            user_id = args['user_id']
            password = args['password']
            atlog.add_where('for userid "%s"' % user_id)
            if not current_app.userMgr.can_login(user_id, password):
                r = {
                    'success': False,
                    'message': 'Invalid user_id or password'
                }
            else:
                _user = current_app.userMgr.get(user_id)
                _user.authenticated = True
                login_user(_user, remember=True)
                r = {
                    'success': True,
                    'message': 'user <%s> logined' % user_id
                }
        except Exception as exp:
            atlog.set_except(exp)
            r = {'success': False, 'message': str(exp)}
        finally:
            atlog.set_user(current_user)
            atlog.save()
            return r

################################################################################
def parse_req_data(request):
    """
    flask의 request에서 사용자 데이터를 가져옴
        주의: flask-restplus 모듈을 이용하여 swagger ui를 이용하는 패러미터
            패싱이 제대로 안되어 본 함수 이용 (0.10.1)
    :param request: Flask의 request
    :return: parameter dict
    """
    if not hasattr(request, 'method'):
        return None
    if request.method.upper() != 'GET':
        if request.data:
            return json.loads(request.data)
    if 'json' in request.args:
        return json.loads(request.args['json'])
    if request.args:
        return request.args     # note: type is ImmutableMultiDict
    return {}


위와 같은 식으로 정리하면 됩니다.
아래에 swagger 예시를 보여드리겠습니다.

그러면 마지막으로 서버에서,

from vivans.rest.bp_mysql import bp_mysql
from vivans.rest.bp_mongo import bp_mongo
from vivans.rest.bp_aaa import bp_aaa
...
app = Flask(__name__)
app.register_blueprint(bp_mysql)
app.register_blueprint(bp_mongo)
app.register_blueprint(bp_aaa)
app.run()

대신

from vivans.rest.ns_mysql import api as ns_mysql, on_load as on_load_mysql
from vivans.rest.ns_mongo import api as ns_mongo, on_load as on_load_mongo
from vivans.rest.ns_aaa import api as ns_aaa, on_load as on_load_aaa
...
on_load_mysql(app)
api.add_namespace(ns_mysql, path='...')
on_load_mongo(app)
api.add_namespace(ns_mongo, path='...')
on_load_aaa(app)
api.add_namespace(ns_aaa, path='...')
api.init_app(app)
app.run()


와 같은 식으로 수정하였습니다.

위에서 on_load_mysql(app) 와 같이 호출한 것은,

Blueprint 같은 경우,

################################################################################
@bp_aaa.record_once  # 처음 한번만 호출됨
def on_load(state):
    state.app.mymgr = MyMgr()

와 같은 record_once 이벤트 핸들러가 있다면 restplus에서는 존재하지 않습니다.

따라서, Namespace 에서

################################################################################
def on_load(app):
    app.mymgr = MyMgr()

와 같이 만들어 놓고 호출한 것입니다.

current_app 연결은 동일합니다.

이제 실행하면, 

해당 Namespace 별로 API 가 보이며, 특정 API (위에서는 login)를 확장해 보면,
잘 나옵니다.

get, post, put, delete 등의 Resouce에서 파생한 클래스의 메서드의 doc string에 Marddown으로 기술하면 그대로 나옵니다.

입력 및 출력 형식은 @api.doc 및, @api.response 를 이용하면 되구요.

위와 같이 예시로 넣고,

"Try it out!" 단추를 누르면 해당 결과를 아래 확인 할 수 있습니다.


이것 때문에 거의 이틀 고생을 했습니다만, 다른 분들은 두시간 만에 끝내시기를...

어느분께는 도움이 되셨기를...

[macOS] VMWare Fusion 10을 이용하여 docker가 기동되는 Win10 만들어 docker 활용해보기

VMWare Fusion 이라고 VMWare 에서 만들어 맥에서 기동되는 소프트웨어가 있었습니다.맥에서 Parallels와 더불어 가상시스템을 모두 돌리고는 했었습니다.(아주 미묘하게 어디에는 되고 어디에는 안되는 사항들이 있었습니다)버전 4부터해서 8까지 계속 사용해 오다가 어느순간 더 개발을 하지 않고 종료한다고 하여그 이후에는 아예 사용하... » 내용보기

[Python] Flask & flask-restplus && swagger ui

파이썬으로 Backend RESTful API 등을 작성할 때, 주로 Flask를 이용합니다.참고:- 파이썬 4개의 웹 프레임워크 비교- FLASK 로 간단한 HTTPS REST API 제공- Flask RESTful API를 gunicorn WSGI 이용 및 supervisor 활용- Flask에서 Redis를 사용하여 세션 관리하는 샘플- Flask... » 내용보기

[macOS] 소스 등 미리보기를 더 다양하게

예전에 REAME, CHANGELOG 등과 같은 확장자가 없는 파일도 파인더에서스페이스를 눌러 미리보기 하고 싶은데 나타나지 않을 때 이용하는 미리보기 플러그인에 관하여 살펴본 적이 있습니다. (해당블로그)오늘 다른 요구 (욕구)가 생겼는데 바로 파이썬 등의 소스에서 하이라이트가 되어 보였으면좋겠다... 였습니다.구글에서 찾아보았더니,(해당 링... » 내용보기

[macOS] 하이시에라에서 메뉴바 상태보기 iStat Menus 6

몇년 전부터 맥북프로의 메뉴바에 현재 맥북의 상태를 모니터링 해 주는 프로그램으로"iStat Menus" 를 사용해 왔었습니다.오늘 하이시에라를 설치하면서 다시 설치하여 보니 버전업이 되었고,기존에 가지고 있던 라이센스를 가족용으로 업그레이드하여 (16불 정도 지불하였습니다)사용해 보았는데 좋은 것 같아 소개해 보려 합니다.우선 다음사이트 에서 다운로드... » 내용보기

구글애드텍스트