Skip to content

Instantly share code, notes, and snippets.

@jramseygreen
Last active November 8, 2023 16:04
Show Gist options
  • Save jramseygreen/d1e2aa0e77daeca739371951d6009c57 to your computer and use it in GitHub Desktop.
Save jramseygreen/d1e2aa0e77daeca739371951d6009c57 to your computer and use it in GitHub Desktop.
flask + vue spa + cheroot webserver
from flask import Blueprint, jsonify
# required line at the top of every blueprint file
api = Blueprint("api", __name__) # match variable name and first arg to file name
# register more blueprints here to further split up the api
# e.g.
# api.register_blueprint(blueprint, url_prefix='/users')
# would cascade through /api/users
# api routes when hitting /api
@api.route("/")
def heartbeat():
return jsonify({"status": "healthy"})
from server import Server
from api.api import api
import os
os.chdir(os.path.dirname(__file__))
server = Server(host='localhost', port=8080, spa_path='frontend/dist', index_file='index.html')
# Can also use setter functions
# server.set_host('localhost')
# server.set_port(8080)
# server.set_spa_path('frontend/dist')
# server.set_index_file('index.html')
server.register_blueprint(api, '/api') # add a blueprint containing routes
# add a custom 404 method, fires when given route is request prefix
@server.errorhandler('/api', 404)
def not_found(e):
return e
server.start()
from flask import Flask, request
from cheroot.wsgi import Server as WSGIServer, PathInfoDispatcher
from threading import Thread
import os
class Server:
def __init__(self, host: str = 'localhost', port: int = 8080, spa_path: str = 'dist',
index_file: str = 'index.html'):
self.__server = None
self.__host = host
self.__port = port
self.__app = Flask(os.getcwd(), static_folder=spa_path, static_url_path='/')
self.__spa_path = spa_path
self.__index_file = index_file
self.__error_handlers = []
def start(self, threading: bool = False):
if threading:
Thread(target=self.start).start()
return
if not self.__server:
self.__app.static_folder = self.__spa_path
# catches all error codes
@self.__app.errorhandler(Exception)
def catch_all(e):
if getattr(e, 'code', None):
# sort in order of the number of / in the route prefix
sorted_error_handlers = sorted(self.__error_handlers, key=lambda x: x['route_prefix'].count('/'), reverse=True)
for error_handler in sorted_error_handlers:
if request.path.startswith(error_handler['route_prefix']) and error_handler['error_code'] == e.code:
return error_handler['func'](e)
# unless there is an override this runs
# webserver always serves the spa unless there is a reason not to
if e.code == 404:
return self.__app.send_static_file(self.__index_file)
# else just default behaviour (return the error)
return e
# cherrypy production webserver
d = PathInfoDispatcher({'/': self.__app}) # load in flask app
self.__server = WSGIServer((self.__host, self.__port), d) # create webserver with dispatcher
try:
print('Server started at ', self.__host, ':', self.__port)
self.__server.start()
except KeyboardInterrupt:
self.stop()
def stop(self):
if self.__server:
self.__server.stop()
self.__server = None
print('Server stopped')
def restart(self):
self.stop()
self.start()
# use as decorator e.g. @server.errorhandler('/api', 404)
def errorhandler(self, route_prefix: str, error_code: int):
if not route_prefix[0] == '/':
route_prefix = '/' + route_prefix
if route_prefix[-1] == '/':
route_prefix = route_prefix[:-1]
def inner(func):
self.__error_handlers.append({
'route_prefix': route_prefix,
'error_code': error_code,
'func': func
})
return inner
def register_blueprint(self, blueprint, url_prefix: str = ''):
self.__app.register_blueprint(blueprint, url_prefix=url_prefix)
def set_app_config(self, key: str, value):
self.__app.config[key] = value
def del_app_config(self, key: str):
if key in self.__app.config:
del self.__app.config[key]
def get_app_config(self, key: str):
if key in self.__app.config:
return self.__app.config[key]
def get_app(self) -> Flask:
return self.__app
def get_spa_path(self) -> str:
return self.__spa_path
def set_spa_path(self, spa_folder: str):
self.__spa_path = spa_folder
self.__app.static_folder = spa_folder
def set_index_file(self, index_file: str):
self.__index_file = index_file
def get_host(self) -> str:
return self.__host
def get_port(self) -> int:
return self.__port
def set_host(self, host: str):
self.__host = host
if self.__server:
self.restart()
def set_port(self, port: int):
self.__port = port
if self.__server:
self.restart()
@jramseygreen
Copy link
Author

pip install flask pip install cheroot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment