From de483ec4571f1b90ed8512f56f962434c11e4428 Mon Sep 17 00:00:00 2001
From: Jan Mach <jan.mach@cesnet.cz>
Date: Tue, 19 Feb 2019 18:18:38 +0100
Subject: [PATCH] Implemented Hawat command line interface based on flask.cli
 and Click.

Currently only built-in Flask commands run, shell, etc. are available. It can be used to launch development web server, which is now documented in development documentation page. (Redmine issue: #4216)
---
 Makefile                                |  10 ++
 bin/mentat-hawat.py                     |  61 -------------
 bin/mentat-hawat.wsgi                   |   7 +-
 doc/sphinx/_doclib/bin_mentat-hawat.rst |  20 ----
 doc/sphinx/_doclib/development.rst      |  59 ++++++++++++
 hawat.local.conf                        |   4 +
 lib/hawat/__init__.py                   |  13 ++-
 lib/hawat/app.py                        | 116 ++++++++++++++----------
 lib/hawat/config.py                     |   9 ++
 setup.py                                |  10 +-
 10 files changed, 171 insertions(+), 138 deletions(-)
 delete mode 100755 bin/mentat-hawat.py
 delete mode 100644 doc/sphinx/_doclib/bin_mentat-hawat.rst
 create mode 100644 hawat.local.conf

diff --git a/Makefile b/Makefile
index 8b6a97c84..c68848427 100644
--- a/Makefile
+++ b/Makefile
@@ -102,6 +102,8 @@ help:
 	@echo "  * $(GREEN)deps-postgresql$(NC): configure required PostgreSQL user accounts and databases"
 	@echo "  * $(GREEN)deps-translations$(NC): compile all available translations"
 	@echo ""
+	@echo "  * $(GREEN)run-webui-dev$(NC): run development web server with development configuration"
+	@echo ""
 	@echo "  * $(GREEN)clean-pycs$(NC): clean up Python compiled files"
 	@echo "  * $(GREEN)clean-build-python$(NC): clean up Python build directories"
 	@echo "  * $(GREEN)clean-build-debian$(NC):  clean up Debian build directories"
@@ -331,6 +333,14 @@ deps-postgresql: FORCE
 #-------------------------------------------------------------------------------
 
 
+run-webui-dev:
+	@echo "\n$(GREEN)*** Running development web server with development configuration ***$(NC)\n"
+	FLASK_ENV=development FLASK_CONFIG=development FLASK_CONFIG_FILE=$(shell realpath ./hawat.local.conf) hawat-cli run
+
+
+#-------------------------------------------------------------------------------
+
+
 clean-pycs: FORCE
 	@echo "\n$(GREEN)*** Cleaning up Python precompiled files ***$(NC)\n"
 	@find . -name '*.pyc' -delete
diff --git a/bin/mentat-hawat.py b/bin/mentat-hawat.py
deleted file mode 100755
index 9848c2eae..000000000
--- a/bin/mentat-hawat.py
+++ /dev/null
@@ -1,61 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# This file is part of Mentat system (https://mentat.cesnet.cz/).
-#
-# Copyright (C) since 2011 CESNET, z.s.p.o (http://www.ces.net/)
-# Use of this source is governed by the MIT license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-"""
-Web interface for Mentat system - local development server
-
-This command will launch built-in development HTTP server and bind it to ``localhost``,
-port ``5000``. It will also force the debug mode to ``True``.
-
-Usage
-^^^^^
-
-Just execute with Python3 interpreter::
-
-    python3 mentat-hawat.py
-
-Now point your browser to ``localhost``, port ``5000``::
-
-    http://localhost:5000
-
-License
-^^^^^^^
-
-Copyright (C) since 2011 CESNET, z.s.p.o (http://www.ces.net/)
-Use of this source is governed by the MIT license.
-"""
-
-
-__author__ = "Jan Mach <jan.mach@cesnet.cz>"
-__credits__ = "Pavel Kácha <pavel.kacha@cesnet.cz>, Andrea Kropáčová <andrea.kropacova@cesnet.cz>"
-
-
-if __name__ == '__main__':
-
-    import hawat
-
-    #
-    # Use prepared factory function to create application instance. The factory
-    # function takes number of arguments, that can be used to fine tune coniguration
-    # of the application. This is can be very usefull when extending applications`
-    # capabilities or for purposes of testing. Please refer to the documentation
-    # for more information.
-    #
-    APP = hawat.create_app(
-        config_object = 'hawat.config.DevelopmentConfig'
-    )
-
-    #
-    # Launch WSGI application, bind to localhost:5000 and enforce debug mode to True.
-    #
-    APP.run(
-        host  = '127.0.0.1',
-        port  = 5000,
-        debug = True
-    )
diff --git a/bin/mentat-hawat.wsgi b/bin/mentat-hawat.wsgi
index 88f9a9223..c097b24e9 100644
--- a/bin/mentat-hawat.wsgi
+++ b/bin/mentat-hawat.wsgi
@@ -15,4 +15,9 @@ import hawat
 # capabilities or for purposes of testing. Please refer to the documentation
 # for more information.
 #
-application = hawat.create_app()
+application = hawat.create_app_full(
+    config_object = 'hawat.config.ProductionConfig',
+    config_file   = '/etc/mentat/mentat-hawat.py.conf',
+    config_env    = 'FLASK_CONFIG_FILE'
+)
+
diff --git a/doc/sphinx/_doclib/bin_mentat-hawat.rst b/doc/sphinx/_doclib/bin_mentat-hawat.rst
deleted file mode 100644
index a2db955da..000000000
--- a/doc/sphinx/_doclib/bin_mentat-hawat.rst
+++ /dev/null
@@ -1,20 +0,0 @@
-.. _section-bin-mentat-hawat:
-
-mentat-hawat.py
-================================================================================
-
-Web interface for Mentat system - local development server
-
-This command will launch built-in development HTTP server and bind it to ``localhost``,
-port ``5000``. It will also force the debug mode to ``True``.
-
-Usage
-^^^^^
-
-Just execute with Python3 interpreter::
-
-    python3 mentat-hawat.py
-
-Now point your browser to ``localhost``, port ``5000``::
-
-    http://localhost:5000
diff --git a/doc/sphinx/_doclib/development.rst b/doc/sphinx/_doclib/development.rst
index 23d2f23fb..6f44c9777 100644
--- a/doc/sphinx/_doclib/development.rst
+++ b/doc/sphinx/_doclib/development.rst
@@ -198,6 +198,65 @@ targets::
 	(venv) $ make deps-webui-upgrade
 
 
+Running development web server
+````````````````````````````````````````````````````````````````````````````````
+
+The web interface for this project is written in excellent `Flask <http://flask.pocoo.org/>`__
+microframework, that comes with built-in webserver for development. It can be
+launched in following ways::
+
+	# A: You may use the Flask built-in command in a following way:
+	(venv) $ FLASK_APP=hawat FLASK_ENV=development FLASK_CONFIG=development FLASK_CONFIG_FILE=$(realpath ./hawat.local.conf) flask run
+
+	# B: You may custom command line interface to launch webserver in development
+	# mode and with development configuration:
+	(venv) $ FLASK_ENV=development FLASK_CONFIG=development FLASK_CONFIG_FILE=$(realpath ./hawat.local.conf) hawat-cli run
+
+	# C: Use following makefile target to do the same as the three above with less
+	# typing:
+	(venv) $ make run-webui-dev
+
+There are following environment variables you may use to tweak the application
+launch according to your needs:
+
+* ``FLASK_DEBUG``
+
+  This configuration controls state of the internal debugger independently on the
+  ``FLASK_ENV`` setting. It is a boolean value and should be either ``True`` or
+  ``False``. Default value is ``False``.
+
+* ``FLASK_ENV``
+
+  This configuration controls application environment setting. This is a string
+  value and should be either ``development`` or ``production``. Default value is
+  ``production``.
+
+* ``FLASK_CONFIG``
+
+  This configuration controls the name of the configuration class from :py:mod:`mydojo.config`
+  module that will be used to configure the application. Valid value is one of the
+  :py:attr:`mydojo.config.CONFIG_MAP`. Default value is ``default``.
+
+* ``FLASK_CONFIG_FILE``
+
+  This configuration controls the name of the configuration file that will be used
+  to further configure the application. Values in this file are applied last and
+  will override anything in the configuration classes from :py:mod:`mydojo.config`.
+  Default value is empty. It must point to existing file if set, otherwise an exception
+  will be raised. Please use absolute path to the file to avoid any surprises.
+
+.. note::
+
+	The ``FLASK_CONFIG_FILE`` is especially handy for customizing the local
+	application configuration during development process or during deployment.
+
+For more information please study following resources:
+
+* `Flask: Command Line Interface <http://flask.pocoo.org/docs/1.0/cli/>`__
+* `Flask: Configuration Handling <http://flask.pocoo.org/docs/1.0/config/>`__
+* `Flask API: Configuration <http://flask.pocoo.org/docs/1.0/api/#configuration>`__
+
+
 Documentation
 ````````````````````````````````````````````````````````````````````````````````
 
diff --git a/hawat.local.conf b/hawat.local.conf
new file mode 100644
index 000000000..95dbd34bd
--- /dev/null
+++ b/hawat.local.conf
@@ -0,0 +1,4 @@
+SECRET_KEY = 'local-secret-key'
+HAWAT_LOG_DEFAULT_LEVEL = 'debug'
+HAWAT_LOG_FILE = '/var/tmp/hawat.dev.log'
+HAWAT_LOG_FILE_LEVEL = 'debug'
diff --git a/lib/hawat/__init__.py b/lib/hawat/__init__.py
index 0a5ecb009..aaa46915f 100644
--- a/lib/hawat/__init__.py
+++ b/lib/hawat/__init__.py
@@ -78,5 +78,14 @@ __author__ = "Jan Mach <jan.mach@cesnet.cz>"
 __credits__ = "Pavel Kácha <pavel.kacha@cesnet.cz>, Andrea Kropáčová <andrea.kropacova@cesnet.cz>"
 
 
-# Expose main application factory to current namespace
-from .app import create_app
+import click
+from flask.cli import FlaskGroup
+
+
+# Expose main application factories to current namespace
+from .app import create_app, create_app_full
+
+
+@click.group(cls = FlaskGroup, create_app = create_app)
+def cli():
+    """Command line interface for the Hawat application."""
diff --git a/lib/hawat/app.py b/lib/hawat/app.py
index 425f3b02e..0c361b4b1 100644
--- a/lib/hawat/app.py
+++ b/lib/hawat/app.py
@@ -49,6 +49,7 @@ import mentat.idea.jsondict
 
 import hawat.base
 import hawat.const
+import hawat.config
 import hawat.acl
 import hawat.log
 import hawat.db
@@ -56,6 +57,9 @@ import hawat.events
 from hawat.models.user import GuiUserModel
 
 
+APP_NAME = 'hawat'
+"""Name of the application as a constant for Flask."""
+
 RE_COUNTRY_CODE = re.compile('^[a-zA-Z]{2,3}$')
 """Regular expression for validating language/country codes."""
 
@@ -63,6 +67,69 @@ RE_COUNTRY_CODE = re.compile('^[a-zA-Z]{2,3}$')
 #-------------------------------------------------------------------------------
 
 
+def create_app_full(
+        config_dict   = None,
+        config_object = 'hawat.config.ProductionConfig',
+        config_file   = None,
+        config_env    = 'FLASK_CONFIG_FILE'):
+    """
+    Factory function for building Hawat application. This function takes number of
+    optional arguments, that can be used to create a very customized instance of
+    Hawat application. This can be very usefull when extending applications`
+    capabilities or for purposes of testing. Each of these arguments has default
+    value for the most common application setup, so for disabling it entirely it
+    is necessary to provide ``None`` as a value.
+
+    :param dict config_dict: Initial default configurations.
+    :param str config_object: Name of the class or module containing configurations.
+    :param str config_file: Name of the file containing additional configurations.
+    :param str config_env:  Name of the environment variable pointing to file containing configurations.
+    :return: Hawat application
+    :rtype: hawat.base.HawatApp
+    """
+
+    app = hawat.base.HawatApp(APP_NAME)
+
+    if config_dict and isinstance(config_dict, dict):
+        app.config.update(config_dict)
+    if config_object:
+        app.config.from_object(config_object)
+    if config_file:
+        app.config.from_pyfile(config_file)
+    if config_env and os.getenv(config_env, None):
+        app.config.from_envvar(config_env)
+
+    _setup_app_logging(app)
+    _setup_app_mailer(app)
+    _setup_app_core(app)
+    _setup_app_db(app)
+    _setup_app_eventdb(app)
+    _setup_app_auth(app)
+    _setup_app_acl(app)
+    _setup_app_babel(app)
+    _setup_app_menu(app)
+    _setup_app_blueprints(app)
+
+    return app
+
+def create_app():
+    """
+    Factory function for building Hawat application. This function does not take
+    any arguments, any necessary customizations must be done using environment
+    variables.
+
+    :return: Hawat application
+    :rtype: hawat.base.HawatApp
+    """
+    config_name = os.getenv('FLASK_CONFIG', 'default')
+    return create_app_full(
+        config_object = hawat.config.CONFIG_MAP[config_name]
+    )
+
+
+#-------------------------------------------------------------------------------
+
+
 def _setup_app_logging(app):
     """
     Setup logging to file and via email for given Hawat application. Logging
@@ -819,52 +886,3 @@ def _setup_app_blueprints(app):
     app.register_blueprints()
 
     return app
-
-
-#-------------------------------------------------------------------------------
-
-
-def create_app(
-        config_dict   = None,
-        config_object = 'hawat.config.ProductionConfig',
-        config_file   = '/etc/mentat/mentat-hawat.py.conf',
-        config_env    = 'HAWAT_CONFIG_FILE'):
-    """
-    Factory function for building Hawat application. This function takes number of
-    optional arguments, that can be used to create a very customized instance of
-    Hawat application. This can be very usefull when extending applications`
-    capabilities or for purposes of testing. Each of these arguments has default
-    value for the most common application setup, so for disabling it entirely it
-    is necessary to provide ``None`` as a value.
-
-    :param dict config_dict: Initial default configurations.
-    :param str config_object: Name of the class or module containing configurations.
-    :param str config_file: Name of the file containing configurations.
-    :param str config_env:  Name of the environment variable pointing to file containing configurations.
-    :return: Hawat application
-    :rtype: hawat.base.HawatApp
-    """
-
-    app = hawat.base.HawatApp('hawat')
-
-    if config_dict and isinstance(config_dict, dict):
-        app.config.update(config_dict)
-    if config_object:
-        app.config.from_object(config_object)
-    if config_file:
-        app.config.from_pyfile(config_file, silent = True)
-    if config_env:
-        app.config.from_envvar(config_env, silent = True)
-
-    _setup_app_logging(app)
-    _setup_app_mailer(app)
-    _setup_app_core(app)
-    _setup_app_db(app)
-    _setup_app_eventdb(app)
-    _setup_app_auth(app)
-    _setup_app_acl(app)
-    _setup_app_babel(app)
-    _setup_app_menu(app)
-    _setup_app_blueprints(app)
-
-    return app
diff --git a/lib/hawat/config.py b/lib/hawat/config.py
index 0e44e38ef..d38757315 100644
--- a/lib/hawat/config.py
+++ b/lib/hawat/config.py
@@ -303,3 +303,12 @@ class TestingConfig(Config):  # pylint: disable=locally-disabled,too-few-public-
 
     TESTING = True
     """Overwritten default value from :py:const:`hawat.config.Config.TESTING`"""
+
+
+CONFIG_MAP = {
+    'development': DevelopmentConfig,
+    'production':  ProductionConfig,
+    'testing':     TestingConfig,
+    'default':     ProductionConfig
+}
+"""Configuration map for easy mapping of configuration aliases to config objects."""
diff --git a/setup.py b/setup.py
index d95026b71..4e2cf2c45 100644
--- a/setup.py
+++ b/setup.py
@@ -120,11 +120,11 @@ setup(
     #
     # Resources:
     #   http://flask.pocoo.org/docs/1.0/cli/#custom-commands
-    #entry_points={
-    #    'console_scripts': [
-    #        'hawat-cli=hawat:cli'
-    #    ],
-    #},
+    entry_points={
+        'console_scripts': [
+            'hawat-cli=hawat:cli'
+        ],
+    },
     include_package_data = True,
     zip_safe = False
 )
-- 
GitLab