/grumponado/app.py
Python | 196 lines | 185 code | 4 blank | 7 comment | 2 complexity | 8cc73b5e9ae09416dce3508f8c7889f4 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception
- # This Source Code Form is subject to the terms of the Mozilla Public
- # License, v. 2.0. If a copy of the MPL was not distributed with this
- # file, You can obtain one at http://mozilla.org/MPL/2.0/.
- """
- The Main app
- ============
- """
- import argparse
- import configparser
- import os
- import logging
- from tornado.ioloop import IOLoop
- from tornado.web import Application
- from tornado.web import StaticFileHandler
- from tornado_sqlalchemy import make_session_factory
- from ._version import __version__
- from .handlers import UserHandler, UserProfileHandler, MainHandler, LoginHandler, LogoutHandler, UserMeHandler
- from .realtime_handlers import WSHandler
- def assign_handlers(base_route=''):
- """Assigns the different routes to the corresponding handlers.
- A description of all the api is available here:
- .. toctree::
- :maxdepth: 2
- serverapi
- Parameters
- ----------
- base_route : str
- The api route for instance v3
- Returns
- -------
- list
- tuple of a raw string and a handler
- """
- logging.debug("Routes created for %s", base_route)
- return [
- (''.join([r'/', base_route, r'/?']), MainHandler),
- (''.join([r'/', base_route, r'/users[\/]?$']), UserHandler),
- (''.join([r'/', base_route, r'/users/(\w{3,30})[\/]?$']), UserProfileHandler),
- (''.join([r'/', base_route, r'/login[\/]?$']), LoginHandler),
- (''.join([r'/', base_route, r'/me[\/]?$']), UserMeHandler),
- (''.join([r'/', base_route, r'/logout[\/]?$']), LogoutHandler),
- (''.join([r'/ws']), WSHandler),
- ]
- def get_arguments():
- """
- Parses the a list of arguments which can be used to configure the server.
- --database for a database connection string - only mysql and postgres
- --port the port where to listen
- --debug to set up the debug mode
- Returns
- -------
- parsed arguments
- """
- parser = argparse.ArgumentParser('gmserver')
- parser.add_argument('-d', '--database', dest='dbase', type=str, default='postgres://localhost:5432/py_test',
- help='The connection string to connect to a database. '
- 'Example: postgres://user:pass@localhost:5432/py_test')
- parser.add_argument('-p', '--port', dest='port', type=int, default=3000,
- help='The port on which to run Grump-O-Meter, default is 3000')
- parser.add_argument('-b', '--debug', dest='debug', type=bool, default=False,
- help='To run the server in debug mode add -b=True. otherwise just omit this argument.')
- parser.add_argument('-v', '--version', action='version', version='%(prog)s version {}'.format(__version__))
- return parser
- class ConfigHandler():
- """Configuration handler class
- Each setting is defaulted to None, so whenever there is any config available this is going to be used"""
- searchpath = None
- debug = None
- dbase = None
- port = None
- app_secret = None
- id_secret = None
- def get_searchpath(self):
- """Method to configure all the possible search paths. This may be modified to suit your needs.
- The default looks for a gmserver.conf in /etc, /etc/gmserver, the current working dir, the etc path
- of the current working dir and in the users home."""
- searchpath = ['/etc/gmserver.conf',
- '/etc/gmserver/gmserver.conf',
- 'gmserver.conf',
- 'etc/gmserver.conf',
- '~/.gmserver/gmserver.conf']
- self.searchpath = searchpath
- def read_config_file(self):
- """Method to scan the search paths."""
- self.get_searchpath()
- configfile = None
- for path in self.searchpath:
- if os.path.exists(path):
- configfile = path
- break
- if configfile is not None:
- config = configparser.ConfigParser()
- config.read(configfile)
- else:
- raise FileNotFoundError
- try:
- self.port = int(config['server']['port'])
- self.debug = bool(config['server']['debug'])
- self.app_secret = config['secrets']['app_secret']
- self.id_secret = config['secrets']['id_secret']
- except KeyError:
- logging.warning('Key not found')
- raise KeyError
- dbsring = f'postgres://'
- try:
- dbuser = config['database']['user']
- dbpass = config['database']['password']
- dbsring = f'postgres://{dbuser}:{dbpass}@'
- except KeyError:
- logging.info('Running without db password and user')
- dbserver = config['database']['server']
- dbport = config['database']['port']
- dbname = config['database']['database']
- self.dbase = f'{dbsring}{dbserver}:{dbport}/{dbname}'
- def setup_app():
- """Setup for the app. It checks the arguments from the cli and it also reads the config files."""
- log = logging.getLogger()
- config = ConfigHandler()
- args = get_arguments().parse_args()
- if args.debug:
- log.setLevel(logging.DEBUG)
- logging.debug("Logging level: DEBUG")
- else:
- log.setLevel(logging.INFO)
- logging.info("Logging level: INFO")
- try:
- config.read_config_file()
- except (KeyError, FileNotFoundError) as error:
- if isinstance(error, FileNotFoundError):
- logging.warning('Could not find a config file')
- else:
- logging.warning('Improper config file format')
- logging.warning("No proper app secret and salt are used. The selected defaults are NOT SECURE.")
- config.port = args.port
- config.dbase = args.dbase
- config.id_secret = '__salt__'
- config.app_secret = 'APP SECRET'
- logging.warning(f'Using app secret: {config.app_secret} and id secret: {config.id_secret}')
- api_version = 'v' + __version__.split('.', 1)[0]
- routes = assign_handlers(api_version)
- if args.debug:
- staticpath = os.path.join(os.path.dirname(__file__), "assets")
- routes.append((''.join([r'/(.*)']), StaticFileHandler, {"path": staticpath}))
- logging.debug(f'Run debug front end at http://127.0.0.1:{config.port}/idb.html')
- session_factory = make_session_factory(config.dbase)
- print("""
- Running gmserver version: {} at http://127.0.0.1:{}{} in debug mode {}
- Using database: {}
- To stop, please press ctrl-c
- """.format(__version__, config.port, '/' + api_version + '/', args.debug, config.dbase))
- return Application(routes,
- session_factory=session_factory,
- debug=args.debug,
- autoreload=args.debug,
- hashing_salt=config.id_secret,
- login_url='/login',
- cookie_secret=config.app_secret).listen(config.port)
- def run_app():
- """The main entry point for scripts. First the logger is set up. Next the command line arguments
- are parsed. The command line provides the port to run the app on. Further, the commandline arguments
- also provide for the connection string to the database. Only Postgres or MySQL are supported. SQLite is
- not supported in this version."""
- setup_app()
- try:
- IOLoop.current().start()
- except KeyboardInterrupt:
- print("Good bye")