PageRenderTime 50ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/grumponado/app.py

https://bitbucket.org/angryshortone/grump-o-nado
Python | 196 lines | 185 code | 4 blank | 7 comment | 2 complexity | 8cc73b5e9ae09416dce3508f8c7889f4 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception
  1. # This Source Code Form is subject to the terms of the Mozilla Public
  2. # License, v. 2.0. If a copy of the MPL was not distributed with this
  3. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
  4. """
  5. The Main app
  6. ============
  7. """
  8. import argparse
  9. import configparser
  10. import os
  11. import logging
  12. from tornado.ioloop import IOLoop
  13. from tornado.web import Application
  14. from tornado.web import StaticFileHandler
  15. from tornado_sqlalchemy import make_session_factory
  16. from ._version import __version__
  17. from .handlers import UserHandler, UserProfileHandler, MainHandler, LoginHandler, LogoutHandler, UserMeHandler
  18. from .realtime_handlers import WSHandler
  19. def assign_handlers(base_route=''):
  20. """Assigns the different routes to the corresponding handlers.
  21. A description of all the api is available here:
  22. .. toctree::
  23. :maxdepth: 2
  24. serverapi
  25. Parameters
  26. ----------
  27. base_route : str
  28. The api route for instance v3
  29. Returns
  30. -------
  31. list
  32. tuple of a raw string and a handler
  33. """
  34. logging.debug("Routes created for %s", base_route)
  35. return [
  36. (''.join([r'/', base_route, r'/?']), MainHandler),
  37. (''.join([r'/', base_route, r'/users[\/]?$']), UserHandler),
  38. (''.join([r'/', base_route, r'/users/(\w{3,30})[\/]?$']), UserProfileHandler),
  39. (''.join([r'/', base_route, r'/login[\/]?$']), LoginHandler),
  40. (''.join([r'/', base_route, r'/me[\/]?$']), UserMeHandler),
  41. (''.join([r'/', base_route, r'/logout[\/]?$']), LogoutHandler),
  42. (''.join([r'/ws']), WSHandler),
  43. ]
  44. def get_arguments():
  45. """
  46. Parses the a list of arguments which can be used to configure the server.
  47. --database for a database connection string - only mysql and postgres
  48. --port the port where to listen
  49. --debug to set up the debug mode
  50. Returns
  51. -------
  52. parsed arguments
  53. """
  54. parser = argparse.ArgumentParser('gmserver')
  55. parser.add_argument('-d', '--database', dest='dbase', type=str, default='postgres://localhost:5432/py_test',
  56. help='The connection string to connect to a database. '
  57. 'Example: postgres://user:pass@localhost:5432/py_test')
  58. parser.add_argument('-p', '--port', dest='port', type=int, default=3000,
  59. help='The port on which to run Grump-O-Meter, default is 3000')
  60. parser.add_argument('-b', '--debug', dest='debug', type=bool, default=False,
  61. help='To run the server in debug mode add -b=True. otherwise just omit this argument.')
  62. parser.add_argument('-v', '--version', action='version', version='%(prog)s version {}'.format(__version__))
  63. return parser
  64. class ConfigHandler():
  65. """Configuration handler class
  66. Each setting is defaulted to None, so whenever there is any config available this is going to be used"""
  67. searchpath = None
  68. debug = None
  69. dbase = None
  70. port = None
  71. app_secret = None
  72. id_secret = None
  73. def get_searchpath(self):
  74. """Method to configure all the possible search paths. This may be modified to suit your needs.
  75. The default looks for a gmserver.conf in /etc, /etc/gmserver, the current working dir, the etc path
  76. of the current working dir and in the users home."""
  77. searchpath = ['/etc/gmserver.conf',
  78. '/etc/gmserver/gmserver.conf',
  79. 'gmserver.conf',
  80. 'etc/gmserver.conf',
  81. '~/.gmserver/gmserver.conf']
  82. self.searchpath = searchpath
  83. def read_config_file(self):
  84. """Method to scan the search paths."""
  85. self.get_searchpath()
  86. configfile = None
  87. for path in self.searchpath:
  88. if os.path.exists(path):
  89. configfile = path
  90. break
  91. if configfile is not None:
  92. config = configparser.ConfigParser()
  93. config.read(configfile)
  94. else:
  95. raise FileNotFoundError
  96. try:
  97. self.port = int(config['server']['port'])
  98. self.debug = bool(config['server']['debug'])
  99. self.app_secret = config['secrets']['app_secret']
  100. self.id_secret = config['secrets']['id_secret']
  101. except KeyError:
  102. logging.warning('Key not found')
  103. raise KeyError
  104. dbsring = f'postgres://'
  105. try:
  106. dbuser = config['database']['user']
  107. dbpass = config['database']['password']
  108. dbsring = f'postgres://{dbuser}:{dbpass}@'
  109. except KeyError:
  110. logging.info('Running without db password and user')
  111. dbserver = config['database']['server']
  112. dbport = config['database']['port']
  113. dbname = config['database']['database']
  114. self.dbase = f'{dbsring}{dbserver}:{dbport}/{dbname}'
  115. def setup_app():
  116. """Setup for the app. It checks the arguments from the cli and it also reads the config files."""
  117. log = logging.getLogger()
  118. config = ConfigHandler()
  119. args = get_arguments().parse_args()
  120. if args.debug:
  121. log.setLevel(logging.DEBUG)
  122. logging.debug("Logging level: DEBUG")
  123. else:
  124. log.setLevel(logging.INFO)
  125. logging.info("Logging level: INFO")
  126. try:
  127. config.read_config_file()
  128. except (KeyError, FileNotFoundError) as error:
  129. if isinstance(error, FileNotFoundError):
  130. logging.warning('Could not find a config file')
  131. else:
  132. logging.warning('Improper config file format')
  133. logging.warning("No proper app secret and salt are used. The selected defaults are NOT SECURE.")
  134. config.port = args.port
  135. config.dbase = args.dbase
  136. config.id_secret = '__salt__'
  137. config.app_secret = 'APP SECRET'
  138. logging.warning(f'Using app secret: {config.app_secret} and id secret: {config.id_secret}')
  139. api_version = 'v' + __version__.split('.', 1)[0]
  140. routes = assign_handlers(api_version)
  141. if args.debug:
  142. staticpath = os.path.join(os.path.dirname(__file__), "assets")
  143. routes.append((''.join([r'/(.*)']), StaticFileHandler, {"path": staticpath}))
  144. logging.debug(f'Run debug front end at http://127.0.0.1:{config.port}/idb.html')
  145. session_factory = make_session_factory(config.dbase)
  146. print("""
  147. Running gmserver version: {} at http://127.0.0.1:{}{} in debug mode {}
  148. Using database: {}
  149. To stop, please press ctrl-c
  150. """.format(__version__, config.port, '/' + api_version + '/', args.debug, config.dbase))
  151. return Application(routes,
  152. session_factory=session_factory,
  153. debug=args.debug,
  154. autoreload=args.debug,
  155. hashing_salt=config.id_secret,
  156. login_url='/login',
  157. cookie_secret=config.app_secret).listen(config.port)
  158. def run_app():
  159. """The main entry point for scripts. First the logger is set up. Next the command line arguments
  160. are parsed. The command line provides the port to run the app on. Further, the commandline arguments
  161. also provide for the connection string to the database. Only Postgres or MySQL are supported. SQLite is
  162. not supported in this version."""
  163. setup_app()
  164. try:
  165. IOLoop.current().start()
  166. except KeyboardInterrupt:
  167. print("Good bye")