M Procfile => Procfile +2 -2
@@ 1,4 1,4 @@
-web: poetry run fbctl serve --reload --workers=1 --threads=4
+web: poetry run fbctl serve --reload
worker: poetry run fbcelery worker
beat: poetry run fbcelery beat
-assets: npm run gulp build watch
+assets: yarn gulp build watch
M fanboi2/__init__.py => fanboi2/__init__.py +27 -2
@@ 50,6 50,24 @@ def route_name(request):
@lru_cache(maxsize=10)
+def _get_asset_hash_cached(path):
+ """Similar to :func:`_get_asset_hash` but the result is cached.
+
+ :param path: An asset specification to the asset file.
+ """
+ if ":" in path:
+ package, path = path.split(":")
+ resolver = AssetResolver(package)
+ else:
+ resolver = AssetResolver()
+ fullpath = resolver.resolve(path).abspath()
+ md5 = hashlib.md5()
+ with open(fullpath, "rb") as f:
+ for chunk in iter(lambda: f.read(128 * md5.block_size), b""):
+ md5.update(chunk)
+ return md5.hexdigest()
+
+
def _get_asset_hash(path):
"""Returns an MD5 hash of the given assets path.
@@ 77,7 95,10 @@ def tagged_static_path(request, path, **kwargs):
:param path: An asset specification to the asset file.
:param kwargs: Arguments to pass to :meth:`request.static_path`.
"""
- kwargs["_query"] = {"h": _get_asset_hash(path)[:8]}
+ if request.registry.settings["server.development"]:
+ kwargs["_query"] = {"h": _get_asset_hash(path)[:8]}
+ else:
+ kwargs["_query"] = {"h": _get_asset_hash_cached(path)[:8]}
return request.static_path(path, **kwargs)
@@ 165,6 186,10 @@ def make_config(settings): # pragma: no cover
config.include("fanboi2.views.api", route_prefix="/api")
config.include("fanboi2.views.pages", route_prefix="/pages")
config.include("fanboi2.views.boards", route_prefix="/")
- config.add_static_view("static", "static", cache_max_age=3600)
+
+ if settings["server.development"]:
+ config.add_static_view("static", "static")
+ else:
+ config.add_static_view("static", "static", cache_max_age=3600)
return config
M fanboi2/cmd/ctl.py => fanboi2/cmd/ctl.py +10 -30
@@ 1,6 1,5 @@
import argparse
import code
-import multiprocessing
import sys
from ..version import __VERSION__, __PYRAMID__
@@ 38,34 37,19 @@ def run_shell(args):
def run_serve(args):
"""Run the web server for the application."""
- from gunicorn.app.base import BaseApplication
+ from waitress import serve as waitress_serve
from ..wsgi import app as wsgi_app
- class FbserveApplication(BaseApplication):
- def __init__(self, app, options=None):
- if not options:
- options = {}
- self.options = options
- self.application = app
- super(FbserveApplication, self).__init__()
+ if args.reload:
+ try:
+ import hupper
+ except ImportError:
+ sys.stderr.write("Please install dev dependencies to use reloader.\n")
+ sys.exit(1)
+ hupper.start_reloader("fanboi2.cmd.ctl.main")
- def load_config(self):
- for key, value in self.options.items():
- if key in self.cfg.settings and value is not None:
- self.cfg.set(key.lower(), value)
-
- def load(self):
- return self.application
-
- options = {
- "bind": "%s:%s" % (args.host, args.port),
- "max_requests": args.max_requests,
- "reload": args.reload,
- "threads": args.threads,
- "workers": args.workers,
- }
-
- FbserveApplication(wsgi_app, options).run()
+ waitress_serve(wsgi_app, host=args.host, port=args.port)
+ sys.exit(0)
def run_gensecret(args):
@@ 84,13 68,9 @@ def main():
shell = subparsers.add_parser("shell")
shell.set_defaults(func=run_shell)
- cpu_count = multiprocessing.cpu_count()
serve = subparsers.add_parser("serve")
serve.add_argument("--port", type=int, default=6543)
serve.add_argument("--host", default="0.0.0.0")
- serve.add_argument("--workers", type=int, default=(cpu_count * 2) + 1)
- serve.add_argument("--threads", type=int, default=1)
- serve.add_argument("--max-requests", type=int, default=1000)
serve.add_argument("--reload", action="store_true")
serve.set_defaults(func=run_serve)
M poetry.lock => poetry.lock +19 -22
@@ 355,23 355,6 @@ requests = ">=2.9"
[[package]]
category = "main"
-description = "WSGI HTTP Server for UNIX"
-name = "gunicorn"
-optional = false
-python-versions = ">=3.4"
-version = "20.0.4"
-
-[package.dependencies]
-setuptools = ">=3.0"
-
-[package.extras]
-eventlet = ["eventlet (>=0.9.7)"]
-gevent = ["gevent (>=0.13)"]
-setproctitle = ["setproctitle"]
-tornado = ["tornado (>=0.2)"]
-
-[[package]]
-category = "main"
description = "Python wrapper for hiredis"
name = "hiredis"
optional = false
@@ 1286,6 1269,18 @@ version = "1.3.0"
[[package]]
category = "main"
+description = "Waitress WSGI server"
+name = "waitress"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
+version = "1.4.4"
+
+[package.extras]
+docs = ["Sphinx (>=1.8.1)", "docutils", "pylons-sphinx-themes (>=1.0.9)"]
+testing = ["pytest", "pytest-cover", "coverage (>=5.0)"]
+
+[[package]]
+category = "main"
description = "WSGI request and response object"
name = "webob"
optional = false
@@ 1387,7 1382,8 @@ test = ["zope.testing"]
deploy = ["fabric", "patchwork", "invocations", "colorama"]
[metadata]
-content-hash = "8606f74e079b1dbfff5a4654f08a24b47173d9dfc0fbe60dfc40688c64a08cd3"
+content-hash = "b0a3e0bd0504a06db6782c00c11545336c214a09b0193eb3cb92f43ad40d4ca0"
+lock-version = "1.0"
python-versions = "^3.7"
[metadata.files]
@@ 1609,10 1605,6 @@ geoip2 = [
{file = "geoip2-2.9.0-py2.py3-none-any.whl", hash = "sha256:a37ddac2d200ffb97c736da8b8ba9d5d8dc47da6ec0f162a461b681ecac53a14"},
{file = "geoip2-2.9.0.tar.gz", hash = "sha256:f7ffe9d258e71a42cf622ce6350d976de1d0312b9f2fbce3975c7d838b57ecf0"},
]
-gunicorn = [
- {file = "gunicorn-20.0.4-py2.py3-none-any.whl", hash = "sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c"},
- {file = "gunicorn-20.0.4.tar.gz", hash = "sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626"},
-]
hiredis = [
{file = "hiredis-1.0.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:38437a681f17c975fd22349e72c29bc643f8e7eb2d6dc5df419eac59afa4d7ce"},
{file = "hiredis-1.0.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:102f9b9dc6ed57feb3a7c9bdf7e71cb7c278fe8df1edfcfe896bc3e0c2be9447"},
@@ 1953,6 1945,7 @@ releases = [
{file = "repoze.lru-0.7.tar.gz", hash = "sha256:0429a75e19380e4ed50c0694e26ac8819b4ea7851ee1fc7583c8572db80aff77"},
]
requests = [
+ {file = "requests-2.23.0-py2.7.egg", hash = "sha256:5d2d0ffbb515f39417009a46c14256291061ac01ba8f875b90cad137de83beb4"},
{file = "requests-2.23.0-py2.py3-none-any.whl", hash = "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee"},
{file = "requests-2.23.0.tar.gz", hash = "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"},
]
@@ 2042,6 2035,10 @@ vine = [
{file = "vine-1.3.0-py2.py3-none-any.whl", hash = "sha256:ea4947cc56d1fd6f2095c8d543ee25dad966f78692528e68b4fada11ba3f98af"},
{file = "vine-1.3.0.tar.gz", hash = "sha256:133ee6d7a9016f177ddeaf191c1f58421a1dcc6ee9a42c58b34bed40e1d2cd87"},
]
+waitress = [
+ {file = "waitress-1.4.4-py2.py3-none-any.whl", hash = "sha256:3d633e78149eb83b60a07dfabb35579c29aac2d24bb803c18b26fb2ab1a584db"},
+ {file = "waitress-1.4.4.tar.gz", hash = "sha256:1bb436508a7487ac6cb097ae7a7fe5413aefca610550baf58f0940e51ecfb261"},
+]
webob = [
{file = "WebOb-1.8.6-py2.py3-none-any.whl", hash = "sha256:a3c89a8e9ba0aeb17382836cdb73c516d0ecf6630ec40ec28288f3ed459ce87b"},
{file = "WebOb-1.8.6.tar.gz", hash = "sha256:aa3a917ed752ba3e0b242234b2a373f9c4e2a75d35291dcbe977649bd21fd108"},
M pyproject.toml => pyproject.toml +2 -2
@@ 1,6 1,6 @@
[tool.poetry]
name = "fanboi2"
-version = "2020.5"
+version = "2020.9"
description = "Pseudonomous message board"
authors = ["Kridsada Thanabulpong <sirn@ogsite.net>"]
license = "BSD-3-Clause"
@@ 19,7 19,6 @@ argon2-cffi = "^19.1"
celery = "~4.4.1"
"dogpile.cache" = "^0.9.0"
geoip2 = "^2.9"
-gunicorn = "^20.0"
hiredis = "^1.0.1"
isodate = "^0.6.0"
lark-parser = "^0.7.1"
@@ 44,6 43,7 @@ patchwork = {version = "^1.0", optional = true}
invocations = {version = "^1.4", optional = true}
colorama = {version = "^0.4.1", optional = true}
python-dotenv = "^0.12.0"
+waitress = "^1.4.4"
[tool.poetry.dev-dependencies]
hupper = "^1.8"