"Fossies" - the Fresh Open Source Software Archive

Member "buku-4.3/bukuserver/server.py" (31 Jan 2020, 23405 Bytes) of package /linux/privat/buku-4.3.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "server.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 4.2.2_vs_4.3.

    1 #!/usr/bin/env python
    2 # pylint: disable=wrong-import-order, ungrouped-imports
    3 """Server module."""
    4 from typing import Any, Dict, Union  # NOQA; type: ignore
    5 from unittest import mock
    6 from urllib.parse import urlparse
    7 import os
    8 import sys
    9 
   10 from buku import BukuDb, __version__, network_handler
   11 from flask.cli import FlaskGroup
   12 from flask.views import MethodView
   13 from flask_admin import Admin
   14 from flask_api import exceptions, FlaskAPI, status
   15 from flask_bootstrap import Bootstrap
   16 from flask_paginate import Pagination, get_page_parameter, get_per_page_parameter
   17 from flask_reverse_proxy_fix.middleware import ReverseProxyPrefixFix
   18 from markupsafe import Markup
   19 import click
   20 import flask
   21 from flask import (  # type: ignore
   22     __version__ as flask_version,
   23     abort,
   24     current_app,
   25     flash,
   26     jsonify,
   27     redirect,
   28     render_template,
   29     request,
   30     url_for,
   31 )
   32 
   33 try:
   34     from . import response, forms, views
   35 except ImportError:
   36     from bukuserver import response, forms, views
   37 
   38 
   39 STATISTIC_DATA = None
   40 
   41 def get_bukudb():
   42     """get bukudb instance"""
   43     db_file = current_app.config.get('BUKUSERVER_DB_FILE', None)
   44     return BukuDb(dbfile=db_file)
   45 
   46 def get_tags():
   47     """get tags."""
   48     tags = getattr(flask.g, 'bukudb', get_bukudb()).get_tag_all()
   49     result = {
   50         'tags': tags[0]
   51     }
   52     if request.path.startswith('/api/'):
   53         res = jsonify(result)
   54     else:
   55         res = render_template('bukuserver/tags.html', result=result)
   56     return res
   57 
   58 
   59 def handle_network():
   60     failed_resp = response.response_template['failure'], status.HTTP_400_BAD_REQUEST
   61     url = request.data.get('url', None)
   62     if not url:
   63         return failed_resp
   64     try:
   65         res = network_handler(url)
   66         keys = ['title', 'description', 'tags', 'recognized mime', 'bad url']
   67         res_dict = dict(zip(keys, res))
   68         return jsonify(res_dict)
   69     except Exception as e:
   70         current_app.logger.debug(str(e))
   71     return failed_resp
   72 
   73 
   74 def update_tag(tag):
   75     res = None
   76     if request.method in ('PUT', 'POST'):
   77         new_tags = request.form.getlist('tags')
   78         result_flag = getattr(flask.g, 'bukudb', get_bukudb()).replace_tag(tag, new_tags)
   79         op_text = 'replace tag [{}] with [{}]'.format(tag, ', '.join(new_tags))
   80         if request.method == 'PUT' and result_flag and request.path.startswith('/api/'):
   81             res = (jsonify(response.response_template['success']),
   82                    status.HTTP_200_OK,
   83                    {'ContentType': 'application/json'})
   84         elif request.method == 'PUT' and request.path.startswith('/api/'):
   85             res = (jsonify(response.response_template['failure']),
   86                    status.HTTP_400_BAD_REQUEST,
   87                    {'ContentType': 'application/json'})
   88         elif request.method == 'POST' and result_flag:
   89             flash(Markup('Success {}'.format(op_text)), 'success')
   90             res = redirect(url_for('get_tags-html'))
   91         elif request.method == 'POST':
   92             flash(Markup('Failed {}'.format(op_text)), 'danger')
   93             res = redirect(url_for('get_tags-html'))
   94         else:
   95             abort(400, description="Unknown Condition")
   96     return res
   97 
   98 
   99 def refresh_bookmark(rec_id: Union[int, None]):
  100     if rec_id is not None:
  101         result_flag = getattr(flask.g, 'bukudb', get_bukudb()).refreshdb(rec_id, request.form.get('threads', 4))
  102     else:
  103         result_flag = getattr(flask.g, 'bukudb', get_bukudb()).refreshdb(0, request.form.get('threads', 4))
  104     if result_flag:
  105         res = (jsonify(response.response_template['success']),
  106                status.HTTP_200_OK,
  107                {'ContentType': 'application/json'})
  108     else:
  109         res = (jsonify(response.response_template['failure']),
  110                status.HTTP_400_BAD_REQUEST,
  111                {'ContentType': 'application/json'})
  112     return res
  113 
  114 
  115 def get_tiny_url(rec_id):
  116     shortened_url = getattr(flask.g, 'bukudb', get_bukudb()).tnyfy_url(rec_id)
  117     if shortened_url is not None:
  118         result = {'url': shortened_url}
  119         res = jsonify(result)
  120     else:
  121         res = (
  122             jsonify(response.response_template['failure']),
  123             status.HTTP_400_BAD_REQUEST,
  124             {'ContentType': 'application/json'})
  125     return res
  126 
  127 
  128 def search_bookmarks():
  129     arg_obj = request.form if request.method == 'DELETE' else request.args
  130     search_bookmarks_form = forms.SearchBookmarksForm(request.args)
  131     is_api_request_path = request.path.startswith('/api/')
  132     if is_api_request_path:
  133         keywords = arg_obj.getlist('keywords')
  134         all_keywords = arg_obj.get('all_keywords')
  135         deep = arg_obj.get('deep')
  136         regex = arg_obj.get('regex')
  137         # api request is more strict
  138         all_keywords = False if all_keywords is None else all_keywords
  139         deep = False if deep is None else deep
  140         regex = False if regex is None else regex
  141         all_keywords = (
  142             all_keywords if isinstance(all_keywords, bool) else
  143             all_keywords.lower() == 'true'
  144         )
  145         deep = deep if isinstance(deep, bool) else deep.lower() == 'true'
  146         regex = regex if isinstance(regex, bool) else regex.lower() == 'true'
  147     else:
  148         keywords = search_bookmarks_form.keywords.data
  149         all_keywords = search_bookmarks_form.all_keywords.data
  150         deep = search_bookmarks_form.deep.data
  151         regex = search_bookmarks_form.regex.data
  152 
  153     result = {'bookmarks': []}
  154     bukudb = getattr(flask.g, 'bukudb', get_bukudb())
  155     found_bookmarks = bukudb.searchdb(keywords, all_keywords, deep, regex)
  156     found_bookmarks = [] if found_bookmarks is None else found_bookmarks
  157     page = request.args.get(get_page_parameter(), type=int, default=1)
  158     per_page = request.args.get(
  159         get_per_page_parameter(),
  160         type=int,
  161         default=int(
  162             current_app.config.get('BUKUSERVER_PER_PAGE', views.DEFAULT_PER_PAGE))
  163     )
  164 
  165     res = None
  166     if request.method == 'GET':
  167         if found_bookmarks is not None:
  168             for bookmark in found_bookmarks:
  169                 result_bookmark = {
  170                     'id': bookmark[0],
  171                     'url': bookmark[1],
  172                     'title': bookmark[2],
  173                     'tags': list([_f for _f in bookmark[3].split(',') if _f]),
  174                     'description': bookmark[4]
  175                 }
  176                 result['bookmarks'].append(result_bookmark)
  177         current_app.logger.debug('total bookmarks:{}'.format(len(result['bookmarks'])))
  178         if is_api_request_path:
  179             res = jsonify(result)
  180         else:
  181             pagination_total = len(result['bookmarks'])
  182             bms = list(views.chunks(result['bookmarks'], per_page))
  183             try:
  184                 result['bookmarks'] = bms[page-1]
  185             except IndexError as err:
  186                 current_app.logger.debug('{}:{}, result bookmarks:{}, page:{}'.format(
  187                     type(err), err, len(result['bookmarks']), page
  188                 ))
  189             pagination = Pagination(
  190                 page=page, total=pagination_total, per_page=per_page,
  191                 search=False, record_name='bookmarks', bs_version=3
  192             )
  193             res = render_template(
  194                 'bukuserver/bookmarks.html',
  195                 result=result, pagination=pagination,
  196                 search_bookmarks_form=search_bookmarks_form,
  197                 create_bookmarks_form=forms.BookmarkForm(),
  198             )
  199     elif request.method == 'DELETE':
  200         if found_bookmarks is not None:
  201             for bookmark in found_bookmarks:
  202                 result_flag = bukudb.delete_rec(bookmark[0])
  203                 if result_flag is False:
  204                     res = (jsonify(response.response_template['failure']),
  205                            status.HTTP_400_BAD_REQUEST,
  206                            {'ContentType': 'application/json'})
  207         if res is None:
  208             res = (jsonify(response.response_template['success']),
  209                    status.HTTP_200_OK,
  210                    {'ContentType': 'application/json'})
  211     return res
  212 
  213 
  214 def create_app(db_file=None):
  215     """create app."""
  216     app = FlaskAPI(__name__)
  217     per_page = int(os.getenv('BUKUSERVER_PER_PAGE', str(views.DEFAULT_PER_PAGE)))
  218     per_page = per_page if per_page > 0 else views.DEFAULT_PER_PAGE
  219     app.config['BUKUSERVER_PER_PAGE'] = per_page
  220     url_render_mode = os.getenv('BUKUSERVER_URL_RENDER_MODE', views.DEFAULT_URL_RENDER_MODE)
  221     if url_render_mode not in ('full', 'netloc'):
  222         url_render_mode = views.DEFAULT_URL_RENDER_MODE
  223     app.config['BUKUSERVER_URL_RENDER_MODE'] = url_render_mode
  224     app.config['SECRET_KEY'] = os.getenv('BUKUSERVER_SECRET_KEY') or os.urandom(24)
  225     disable_favicon = os.getenv('BUKUSERVER_DISABLE_FAVICON', 'false')
  226     app.config['BUKUSERVER_DISABLE_FAVICON'] = \
  227         False if disable_favicon.lower() in ['false', '0'] else bool(disable_favicon)
  228     open_in_new_tab = os.getenv('BUKUSERVER_OPEN_IN_NEW_TAB', 'false')
  229     app.config['BUKUSERVER_OPEN_IN_NEW_TAB'] = \
  230         False if open_in_new_tab.lower() in ['false', '0'] else bool(open_in_new_tab)
  231     app.config['BUKUSERVER_DB_FILE'] = os.getenv('BUKUSERVER_DB_FILE') or db_file
  232     reverse_proxy_path = os.getenv('BUKUSERVER_REVERSE_PROXY_PATH')
  233     if reverse_proxy_path:
  234         if not reverse_proxy_path.startswith('/'):
  235             print('Warning: reverse proxy path should include preceding slash')
  236         if reverse_proxy_path.endswith('/'):
  237             print('Warning: reverse proxy path should not include trailing slash')
  238         app.config['REVERSE_PROXY_PATH'] = reverse_proxy_path
  239         ReverseProxyPrefixFix(app)
  240     bukudb = BukuDb(dbfile=app.config['BUKUSERVER_DB_FILE'])
  241     app.app_context().push()
  242     setattr(flask.g, 'bukudb', bukudb)
  243 
  244     @app.shell_context_processor
  245     def shell_context():
  246         """Shell context definition."""
  247         return {'app': app, 'bukudb': bukudb}
  248 
  249     app.jinja_env.filters['netloc'] = lambda x: urlparse(x).netloc  # pylint: disable=no-member
  250 
  251     Bootstrap(app)
  252     admin = Admin(
  253         app, name='buku server', template_mode='bootstrap3',
  254         index_view=views.CustomAdminIndexView(
  255             template='bukuserver/home.html', url='/'
  256         )
  257     )
  258     # routing
  259     #  api
  260     tag_api_view = ApiTagView.as_view('tag_api')
  261     app.add_url_rule('/api/tags', defaults={'tag': None}, view_func=tag_api_view, methods=['GET'])
  262     app.add_url_rule('/api/tags/<tag>', view_func=tag_api_view, methods=['GET', 'PUT'])
  263     bookmark_api_view = ApiBookmarkView.as_view('bookmark_api')
  264     app.add_url_rule('/api/bookmarks', defaults={'rec_id': None}, view_func=bookmark_api_view, methods=['GET', 'POST', 'DELETE'])
  265     app.add_url_rule('/api/bookmarks/<int:rec_id>', view_func=bookmark_api_view, methods=['GET', 'PUT', 'DELETE'])
  266     app.add_url_rule('/api/bookmarks/refresh', 'refresh_bookmark', refresh_bookmark, defaults={'rec_id': None}, methods=['POST'])
  267     app.add_url_rule('/api/bookmarks/<int:rec_id>/refresh', 'refresh_bookmark', refresh_bookmark, methods=['POST'])
  268     app.add_url_rule('/api/bookmarks/<int:rec_id>/tiny', 'get_tiny_url', get_tiny_url, methods=['GET'])
  269     app.add_url_rule('/api/network_handle', 'network_handle', handle_network, methods=['POST'])
  270     bookmark_range_api_view = ApiBookmarkRangeView.as_view('bookmark_range_api')
  271     app.add_url_rule(
  272         '/api/bookmarks/<int:starting_id>/<int:ending_id>',
  273         view_func=bookmark_range_api_view, methods=['GET', 'PUT', 'DELETE'])
  274     bookmark_search_api_view = ApiBookmarkSearchView.as_view('bookmark_search_api')
  275     app.add_url_rule('/api/bookmarks/search', view_func=bookmark_search_api_view, methods=['GET', 'DELETE'])
  276     #  non api
  277     admin.add_view(views.BookmarkModelView(
  278         bukudb, 'Bookmarks', page_size=per_page, url_render_mode=url_render_mode))
  279     admin.add_view(views.TagModelView(
  280         bukudb, 'Tags', page_size=per_page))
  281     admin.add_view(views.StatisticView(
  282         bukudb, 'Statistic', endpoint='statistic'))
  283     return app
  284 
  285 
  286 class ApiTagView(MethodView):
  287 
  288     def get(self, tag: Union[str, None]):
  289         bukudb = get_bukudb()
  290         if tag is None:
  291             tags = bukudb.get_tag_all()
  292             result = {'tags': tags[0]}
  293             return result
  294         tags = bukudb.get_tag_all()
  295         if tag not in tags[1]:
  296             raise exceptions.NotFound()
  297         res = dict(name=tag, usage_count=tags[1][tag])
  298         return res
  299 
  300     def put(self, tag: str):
  301         bukudb = get_bukudb()
  302         res = None
  303         try:
  304             new_tags = request.data.get('tags')  # type: ignore
  305             if new_tags:
  306                 new_tags = new_tags.split(',')
  307             else:
  308                 return response.response_template['failure'], status.HTTP_400_BAD_REQUEST
  309         except AttributeError as e:
  310             raise exceptions.ParseError(detail=str(e))
  311         result_flag = bukudb.replace_tag(tag, new_tags)
  312         if result_flag:
  313             res = response.response_template['success'], status.HTTP_200_OK
  314         else:
  315             res = response.response_template['failure'], status.HTTP_400_BAD_REQUEST
  316         return res
  317 
  318 
  319 class ApiBookmarkView(MethodView):
  320 
  321     def get(self, rec_id: Union[int, None]):
  322         if rec_id is None:
  323             bukudb = getattr(flask.g, 'bukudb', get_bukudb())
  324             all_bookmarks = bukudb.get_rec_all()
  325             result = {'bookmarks': []}  # type: Dict[str, Any]
  326             for bookmark in all_bookmarks:
  327                 result_bookmark = {
  328                     'url': bookmark[1],
  329                     'title': bookmark[2],
  330                     'tags': list([_f for _f in bookmark[3].split(',') if _f]),
  331                     'description': bookmark[4]
  332                 }
  333                 if not request.path.startswith('/api/'):
  334                     result_bookmark['id'] = bookmark[0]
  335                 result['bookmarks'].append(result_bookmark)
  336             res = jsonify(result)
  337         else:
  338             bukudb = getattr(flask.g, 'bukudb', get_bukudb())
  339             bookmark = bukudb.get_rec_by_id(rec_id)
  340             if bookmark is not None:
  341                 result = {
  342                     'url': bookmark[1],
  343                     'title': bookmark[2],
  344                     'tags': list([_f for _f in bookmark[3].split(',') if _f]),
  345                     'description': bookmark[4]
  346                 }
  347                 res = jsonify(result)
  348             else:
  349                 res = jsonify(response.response_template['failure']), status.HTTP_400_BAD_REQUEST, \
  350                        {'ContentType': 'application/json'}
  351         return res
  352 
  353     def post(self, rec_id: None = None):
  354         bukudb = getattr(flask.g, 'bukudb', get_bukudb())
  355         create_bookmarks_form = forms.BookmarkForm()
  356         url_data = create_bookmarks_form.url.data
  357         result_flag = bukudb.add_rec(
  358             url_data,
  359             create_bookmarks_form.title.data,
  360             create_bookmarks_form.tags.data,
  361             create_bookmarks_form.description.data
  362         )
  363         if result_flag != -1:
  364             res = jsonify(response.response_template['success'])
  365         else:
  366             res = jsonify(response.response_template['failure'])
  367             res.status_code = status.HTTP_400_BAD_REQUEST
  368         return res
  369 
  370     def put(self, rec_id: int):
  371         bukudb = getattr(flask.g, 'bukudb', get_bukudb())
  372         result_flag = bukudb.update_rec(
  373             rec_id,
  374             request.form.get('url'),
  375             request.form.get('title'),
  376             request.form.get('tags'),
  377             request.form.get('description'))
  378         if result_flag:
  379             res = (jsonify(response.response_template['success']),
  380                    status.HTTP_200_OK,
  381                    {'ContentType': 'application/json'})
  382         else:
  383             res = (jsonify(response.response_template['failure']),
  384                    status.HTTP_400_BAD_REQUEST,
  385                    {'ContentType': 'application/json'})
  386         return res
  387 
  388     def delete(self, rec_id: Union[int, None]):
  389         if rec_id is None:
  390             bukudb = getattr(flask.g, 'bukudb', get_bukudb())
  391             with mock.patch('buku.read_in', return_value='y'):
  392                 result_flag = bukudb.cleardb()
  393             if result_flag:
  394                 res = jsonify(response.response_template['success'])
  395             else:
  396                 res = jsonify(response.response_template['failure'])
  397                 res.status_code = status.HTTP_400_BAD_REQUEST
  398         else:
  399             bukudb = getattr(flask.g, 'bukudb', get_bukudb())
  400             result_flag = bukudb.delete_rec(rec_id)
  401             if result_flag:
  402                 res = (jsonify(response.response_template['success']),
  403                        status.HTTP_200_OK,
  404                        {'ContentType': 'application/json'})
  405             else:
  406                 res = (jsonify(response.response_template['failure']),
  407                        status.HTTP_400_BAD_REQUEST,
  408                        {'ContentType': 'application/json'})
  409         return res
  410 
  411 
  412 class ApiBookmarkRangeView(MethodView):
  413 
  414     def get(self, starting_id: int, ending_id: int):
  415         bukudb = getattr(flask.g, 'bukudb', get_bukudb())
  416         max_id = bukudb.get_max_id()
  417         if starting_id > max_id or ending_id > max_id:
  418             return jsonify(response.response_template['failure']), status.HTTP_400_BAD_REQUEST, \
  419                    {'ContentType': 'application/json'}
  420         result = {'bookmarks': {}}  # type: ignore
  421         for i in range(starting_id, ending_id + 1, 1):
  422             bookmark = bukudb.get_rec_by_id(i)
  423             result['bookmarks'][i] = {
  424                 'url': bookmark[1],
  425                 'title': bookmark[2],
  426                 'tags': list([_f for _f in bookmark[3].split(',') if _f]),
  427                 'description': bookmark[4]
  428             }
  429         res = jsonify(result)
  430         return res
  431 
  432     def put(self, starting_id: int, ending_id: int):
  433         bukudb = getattr(flask.g, 'bukudb', get_bukudb())
  434         max_id = bukudb.get_max_id()
  435         if starting_id > max_id or ending_id > max_id:
  436             return jsonify(response.response_template['failure']), status.HTTP_400_BAD_REQUEST, \
  437                    {'ContentType': 'application/json'}
  438         for i in range(starting_id, ending_id + 1, 1):
  439             updated_bookmark = request.data.get(str(i))  # type: ignore
  440             result_flag = bukudb.update_rec(
  441                 i,
  442                 updated_bookmark.get('url'),
  443                 updated_bookmark.get('title'),
  444                 updated_bookmark.get('tags'),
  445                 updated_bookmark.get('description'))
  446             if result_flag is False:
  447                 return (
  448                     jsonify(response.response_template['failure']),
  449                     status.HTTP_400_BAD_REQUEST,
  450                     {'ContentType': 'application/json'})
  451         res = jsonify(response.response_template['success'])
  452         return res
  453 
  454     def delete(self, starting_id: int, ending_id: int):
  455         bukudb = getattr(flask.g, 'bukudb', get_bukudb())
  456         max_id = bukudb.get_max_id()
  457         if starting_id > max_id or ending_id > max_id:
  458             return jsonify(response.response_template['failure']), status.HTTP_400_BAD_REQUEST, \
  459                    {'ContentType': 'application/json'}
  460         idx = min([starting_id, ending_id])
  461         result_flag = bukudb.delete_rec(idx, starting_id, ending_id, is_range=True)
  462         if result_flag is False:
  463             res = jsonify(response.response_template['failure'])
  464             res.status_code = status.HTTP_400_BAD_REQUEST
  465         else:
  466             res = jsonify(response.response_template['success'])
  467         return res
  468 
  469 
  470 class ApiBookmarkSearchView(MethodView):
  471 
  472     def get(self):
  473         arg_obj = request.args
  474         keywords = arg_obj.getlist('keywords')
  475         all_keywords = arg_obj.get('all_keywords')
  476         deep = arg_obj.get('deep')
  477         regex = arg_obj.get('regex')
  478         # api request is more strict
  479         all_keywords = False if all_keywords is None else all_keywords
  480         deep = False if deep is None else deep
  481         regex = False if regex is None else regex
  482         all_keywords = (
  483             all_keywords if isinstance(all_keywords, bool) else
  484             all_keywords.lower() == 'true'
  485         )
  486         deep = deep if isinstance(deep, bool) else deep.lower() == 'true'
  487         regex = regex if isinstance(regex, bool) else regex.lower() == 'true'
  488 
  489         result = {'bookmarks': []}
  490         bukudb = getattr(flask.g, 'bukudb', get_bukudb())
  491         found_bookmarks = bukudb.searchdb(keywords, all_keywords, deep, regex)
  492         found_bookmarks = [] if found_bookmarks is None else found_bookmarks
  493         res = None
  494         if found_bookmarks is not None:
  495             for bookmark in found_bookmarks:
  496                 result_bookmark = {
  497                     'id': bookmark[0],
  498                     'url': bookmark[1],
  499                     'title': bookmark[2],
  500                     'tags': list([_f for _f in bookmark[3].split(',') if _f]),
  501                     'description': bookmark[4]
  502                 }
  503                 result['bookmarks'].append(result_bookmark)
  504         current_app.logger.debug('total bookmarks:{}'.format(len(result['bookmarks'])))
  505         res = jsonify(result)
  506         return res
  507 
  508     def delete(self):
  509         arg_obj = request.form
  510         keywords = arg_obj.getlist('keywords')
  511         all_keywords = arg_obj.get('all_keywords')
  512         deep = arg_obj.get('deep')
  513         regex = arg_obj.get('regex')
  514         # api request is more strict
  515         all_keywords = False if all_keywords is None else all_keywords
  516         deep = False if deep is None else deep
  517         regex = False if regex is None else regex
  518         all_keywords = (
  519             all_keywords if isinstance(all_keywords, bool) else
  520             all_keywords.lower() == 'true'
  521         )
  522         deep = deep if isinstance(deep, bool) else deep.lower() == 'true'
  523         regex = regex if isinstance(regex, bool) else regex.lower() == 'true'
  524         bukudb = getattr(flask.g, 'bukudb', get_bukudb())
  525         found_bookmarks = bukudb.searchdb(keywords, all_keywords, deep, regex)
  526         found_bookmarks = [] if found_bookmarks is None else found_bookmarks
  527         res = None
  528         if found_bookmarks is not None:
  529             for bookmark in found_bookmarks:
  530                 result_flag = bukudb.delete_rec(bookmark[0])
  531                 if result_flag is False:
  532                     res = jsonify(response.response_template['failure'])
  533                     res.status = status.HTTP_400_BAD_REQUEST
  534         if res is None:
  535             res = jsonify(response.response_template['success'])
  536         return res
  537 
  538 
  539 class CustomFlaskGroup(FlaskGroup):  # pylint: disable=too-few-public-methods
  540     def __init__(self, **kwargs):
  541         super().__init__(**kwargs)
  542         self.params[0].help = 'Show the program version'
  543         self.params[0].callback = get_custom_version
  544 
  545 
  546 def get_custom_version(ctx, param, value):
  547     if not value or ctx.resilient_parsing:
  548         return
  549     message = '%(app_name)s %(app_version)s\nFlask %(version)s\nPython %(python_version)s'
  550     click.echo(message % {
  551         'app_name': 'buku',
  552         'app_version': __version__,
  553         'version': flask_version,
  554         'python_version': sys.version,
  555     }, color=ctx.color)
  556     ctx.exit()
  557 
  558 
  559 @click.group(cls=CustomFlaskGroup, create_app=create_app)
  560 def cli():
  561     """This is a script for the bukuserver application."""
  562 
  563 
  564 if __name__ == '__main__':
  565     cli()