WebServer.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. import glob
  2. import os
  3. import threading
  4. from logging.config import dictConfig
  5. from flask import send_from_directory
  6. from flask_socketio import SocketIO
  7. from logging import config
  8. from pycs import app
  9. from pycs.database.Collection import Collection
  10. from pycs.database.Database import Database
  11. from pycs.database.LabelProvider import LabelProvider
  12. from pycs.database.Label import Label
  13. from pycs.database.Model import Model
  14. from pycs.database.Project import Project
  15. from pycs.database.Result import Result
  16. from pycs.frontend.endpoints.ListJobs import ListJobs
  17. from pycs.frontend.endpoints.additional.FolderInformation import FolderInformation
  18. from pycs.frontend.endpoints.base import ListView
  19. from pycs.frontend.endpoints.data.GetFile import GetFile
  20. from pycs.frontend.endpoints.data.GetPreviousAndNextFile import GetPreviousAndNextFile
  21. from pycs.frontend.endpoints.data.GetResizedFile import GetResizedFile
  22. from pycs.frontend.endpoints.data.RemoveFile import RemoveFile
  23. from pycs.frontend.endpoints.data.UploadFile import UploadFile
  24. from pycs.frontend.endpoints.jobs.RemoveJob import RemoveJob
  25. from pycs.frontend.endpoints.labels.CreateLabel import CreateLabel
  26. from pycs.frontend.endpoints.labels.EditLabelName import EditLabelName
  27. from pycs.frontend.endpoints.labels.EditLabelParent import EditLabelParent
  28. from pycs.frontend.endpoints.labels.RemoveLabel import RemoveLabel
  29. from pycs.frontend.endpoints.pipelines.FitModel import FitModel
  30. from pycs.frontend.endpoints.pipelines.PredictFile import PredictFile
  31. from pycs.frontend.endpoints.pipelines.PredictModel import PredictModel
  32. from pycs.frontend.endpoints.projects.CreateProject import CreateProject
  33. from pycs.frontend.endpoints.projects.EditProjectDescription import EditProjectDescription
  34. from pycs.frontend.endpoints.projects.EditProjectName import EditProjectName
  35. from pycs.frontend.endpoints.projects.ExecuteExternalStorage import ExecuteExternalStorage
  36. from pycs.frontend.endpoints.projects.ExecuteLabelProvider import ExecuteLabelProvider
  37. from pycs.frontend.endpoints.projects.GetProjectModel import GetProjectModel
  38. from pycs.frontend.endpoints.projects.ListFiles import ListFiles
  39. from pycs.frontend.endpoints.projects.RemoveProject import RemoveProject
  40. from pycs.frontend.endpoints.results.ConfirmResult import ConfirmResult
  41. from pycs.frontend.endpoints.results.CreateResult import CreateResult
  42. from pycs.frontend.endpoints.results.EditResultData import EditResultData
  43. from pycs.frontend.endpoints.results.EditResultLabel import EditResultLabel
  44. from pycs.frontend.endpoints.results.GetProjectResults import GetProjectResults
  45. from pycs.frontend.endpoints.results.RemoveResult import RemoveResult
  46. from pycs.frontend.endpoints.results.ResetResults import ResetResults
  47. from pycs.frontend.notifications.NotificationManager import NotificationManager
  48. from pycs.frontend.util.JSONEncoder import JSONEncoder
  49. from pycs.jobs.JobRunner import JobRunner
  50. from pycs.util.PipelineCache import PipelineCache
  51. class WebServer:
  52. """
  53. wrapper class for flask and socket.io which initializes most networking
  54. """
  55. # pylint: disable=line-too-long
  56. def __init__(self, app, settings: dict):
  57. dictConfig(settings["logging"])
  58. # initialize flask app instance
  59. self.app = app
  60. # initialize database
  61. self.db = Database()
  62. # start job runner
  63. self.logger.info('Starting job runner... ')
  64. self.jobs = JobRunner()
  65. # create pipeline cache
  66. self.logger.info('Creating pipeline cache')
  67. self.pipelines = PipelineCache(self.jobs)
  68. PRODUCTION = os.path.exists('webui/index.html')
  69. init_func = self.production_init if PRODUCTION else self.development_init
  70. kwargs, static_files = init_func()
  71. self.sio = SocketIO(self.app, **kwargs)#socketio.Server(**kwargs)
  72. # self.__app = socketio.WSGIApp(self.sio, self.app, static_files=static_files)
  73. self.host, self.port = settings['host'], settings['port']
  74. # set json encoder so database objects are serialized correctly
  75. self.app.json_encoder = JSONEncoder
  76. self.start_runner()
  77. self.init_notifications()
  78. self.define_routes()
  79. self.logger.info("Server initialized")
  80. def start_runner(self):
  81. app.logger.info(f"Main Thread ID: {threading.get_ident()}")
  82. self.jobs.start()
  83. self.pipelines.start()
  84. def stop_runner(self):
  85. self.jobs.stop()
  86. self.pipelines.stop()
  87. def wait_for_runner(self):
  88. self.jobs.wait_for_empty_queue()
  89. self.pipelines.wait_for_empty_queue()
  90. @property
  91. def logger(self):
  92. return self.app.logger
  93. def init_notifications(self):
  94. # create notification manager
  95. self.nm = NotificationManager(self.sio)
  96. self.jobs.on_create(self.nm.create_job)
  97. self.jobs.on_start(self.nm.edit_job)
  98. self.jobs.on_progress(self.nm.edit_job)
  99. self.jobs.on_finish(self.nm.edit_job)
  100. self.jobs.on_remove(self.nm.remove_job)
  101. def development_init(self):
  102. self.logger.info('Initializing development build')
  103. # set access control header to allow requests from Vue.js development server
  104. @self.app.after_request
  105. def after_request(response):
  106. # pylint: disable=unused-variable
  107. response.headers['Access-Control-Allow-Origin'] = '*'
  108. return response
  109. return dict(cors_allowed_origins='*', async_mode='eventlet'), None
  110. def production_init(self):
  111. self.logger.info('Initializing production build')
  112. kwargs = dict(async_mode='eventlet')
  113. if len(settings['allowedOrigins']) > 0:
  114. origins = settings['allowedOrigins']
  115. kwargs["cors_allowed_origins"] = origins
  116. # overwrite root path to serve index.html
  117. @self.app.route('/', methods=['GET'])
  118. def index():
  119. # pylint: disable=unused-variable
  120. return send_from_directory(os.path.join(os.getcwd(), 'webui'), 'index.html')
  121. return kwargs, self.static_files
  122. @property
  123. def static_files(self) -> dict:
  124. # find static files and folders
  125. static_files = {}
  126. for file_path in glob.glob('webui/*'):
  127. file_path = file_path.replace('\\', '/')
  128. static_files[file_path[5:]] = file_path
  129. # separately add svg files and set their correct mime type
  130. for svg_path in glob.glob('webui/img/*.svg'):
  131. svg_path = svg_path.replace('\\', '/')
  132. static_files[svg_path[5:]] = {'content_type': 'image/svg+xml', 'filename': svg_path}
  133. return static_files
  134. def define_routes(self):
  135. # additional
  136. self.app.add_url_rule(
  137. '/folder',
  138. view_func=FolderInformation.as_view('folder_information')
  139. )
  140. # jobs
  141. self.app.add_url_rule(
  142. '/jobs',
  143. view_func=ListJobs.as_view('list_jobs', self.jobs)
  144. )
  145. self.app.add_url_rule(
  146. '/jobs/<identifier>/remove',
  147. view_func=RemoveJob.as_view('remove_job', self.jobs)
  148. )
  149. # models
  150. self.app.add_url_rule(
  151. '/models',
  152. view_func=ListView.as_view('list_models', model_cls=Model)
  153. )
  154. self.app.add_url_rule(
  155. '/projects/<int:identifier>/model',
  156. view_func=GetProjectModel.as_view('get_project_model')
  157. )
  158. # labels
  159. self.app.add_url_rule(
  160. '/label_providers',
  161. view_func=ListView.as_view('label_providers', model_cls=LabelProvider)
  162. )
  163. self.app.add_url_rule(
  164. '/projects/<int:project_id>/labels',
  165. view_func=ListView.as_view('list_labels', model_cls=Label)
  166. )
  167. self.app.add_url_rule(
  168. '/projects/<int:identifier>/labels',
  169. view_func=CreateLabel.as_view('create_label', self.nm)
  170. )
  171. self.app.add_url_rule(
  172. '/projects/<int:project_id>/labels/<int:label_id>/remove',
  173. view_func=RemoveLabel.as_view('remove_label', self.nm)
  174. )
  175. self.app.add_url_rule(
  176. '/projects/<int:project_id>/labels/<int:label_id>/name',
  177. view_func=EditLabelName.as_view('edit_label_name', self.nm)
  178. )
  179. self.app.add_url_rule(
  180. '/projects/<int:project_id>/labels/<int:label_id>/parent',
  181. view_func=EditLabelParent.as_view('edit_label_parent', self.nm)
  182. )
  183. # collections
  184. self.app.add_url_rule(
  185. '/projects/<int:project_id>/collections',
  186. view_func=ListView.as_view('list_collections',
  187. model_cls=Collection, post_process=Collection.update_autoselect)
  188. )
  189. self.app.add_url_rule(
  190. '/projects/<int:project_id>/data/<int:collection_id>/<int:start>/<int:length>',
  191. view_func=ListFiles.as_view('list_collection_files')
  192. )
  193. # data
  194. self.app.add_url_rule(
  195. '/projects/<int:identifier>/data',
  196. view_func=UploadFile.as_view('upload_file', self.nm)
  197. )
  198. self.app.add_url_rule(
  199. '/projects/<int:project_id>/data/<int:start>/<int:length>',
  200. view_func=ListFiles.as_view('list_files')
  201. )
  202. self.app.add_url_rule(
  203. '/data/<int:identifier>/remove',
  204. view_func=RemoveFile.as_view('remove_file', self.nm)
  205. )
  206. self.app.add_url_rule(
  207. '/data/<int:file_id>',
  208. view_func=GetFile.as_view('get_file')
  209. )
  210. self.app.add_url_rule(
  211. '/data/<int:file_id>/<resolution>',
  212. view_func=GetResizedFile.as_view('get_resized_file')
  213. )
  214. self.app.add_url_rule(
  215. '/data/<int:file_id>/previous_next',
  216. view_func=GetPreviousAndNextFile.as_view('get_previous_and_next_file')
  217. )
  218. # results
  219. self.app.add_url_rule(
  220. '/projects/<int:project_id>/results',
  221. view_func=GetProjectResults.as_view('get_project_results')
  222. )
  223. self.app.add_url_rule(
  224. '/data/<int:file_id>/results',
  225. view_func=ListView.as_view('get_results', model_cls=Result)
  226. )
  227. self.app.add_url_rule(
  228. '/data/<int:file_id>/results',
  229. view_func=CreateResult.as_view('create_result', self.nm)
  230. )
  231. self.app.add_url_rule(
  232. '/data/<int:file_id>/reset',
  233. view_func=ResetResults.as_view('reset_results', self.nm)
  234. )
  235. self.app.add_url_rule(
  236. '/results/<int:result_id>/remove',
  237. view_func=RemoveResult.as_view('remove_result', self.nm)
  238. )
  239. self.app.add_url_rule(
  240. '/results/<int:result_id>/confirm',
  241. view_func=ConfirmResult.as_view('confirm_result', self.nm)
  242. )
  243. self.app.add_url_rule(
  244. '/results/<int:result_id>/label',
  245. view_func=EditResultLabel.as_view('edit_result_label', self.nm)
  246. )
  247. self.app.add_url_rule(
  248. '/results/<int:result_id>/data',
  249. view_func=EditResultData.as_view('edit_result_data', self.nm)
  250. )
  251. # projects
  252. self.app.add_url_rule(
  253. '/projects',
  254. view_func=ListView.as_view('list_projects', model_cls=Project)
  255. )
  256. self.app.add_url_rule(
  257. '/projects',
  258. view_func=CreateProject.as_view('create_project', self.nm, self.jobs)
  259. )
  260. self.app.add_url_rule(
  261. '/projects/<int:identifier>/label_provider',
  262. view_func=ExecuteLabelProvider.as_view('execute_label_provider',
  263. self.nm, self.jobs)
  264. )
  265. self.app.add_url_rule(
  266. '/projects/<int:identifier>/external_storage',
  267. view_func=ExecuteExternalStorage.as_view('execute_external_storage',
  268. self.nm, self.jobs)
  269. )
  270. self.app.add_url_rule(
  271. '/projects/<int:identifier>/remove',
  272. view_func=RemoveProject.as_view('remove_project', self.nm)
  273. )
  274. self.app.add_url_rule(
  275. '/projects/<int:identifier>/name',
  276. view_func=EditProjectName.as_view('edit_project_name', self.nm)
  277. )
  278. self.app.add_url_rule(
  279. '/projects/<int:identifier>/description',
  280. view_func=EditProjectDescription.as_view('edit_project_description', self.nm)
  281. )
  282. # pipelines
  283. self.app.add_url_rule(
  284. '/projects/<int:project_id>/pipelines/fit',
  285. view_func=FitModel.as_view('fit_model', self.jobs, self.pipelines)
  286. )
  287. self.app.add_url_rule(
  288. '/projects/<int:project_id>/pipelines/predict',
  289. view_func=PredictModel.as_view('predict_model', self.nm,
  290. self.jobs, self.pipelines)
  291. )
  292. self.app.add_url_rule(
  293. '/data/<int:file_id>/predict',
  294. view_func=PredictFile.as_view('predict_file', self.nm,
  295. self.jobs, self.pipelines)
  296. )
  297. def run(self):
  298. self.logger.info("Starting server...")
  299. return self.sio.run(self.app, host=self.host, port=self.port)
  300. # eventlet.wsgi.server(eventlet.listen((self.__host, self.__port)), self.__app)