Skip to content

Commit be53db9

Browse files
committed
➕ refactor(dependencies): Added gunicorn, uvicorn-worker, and uvloop. Updated server to use gunicorn for production-ready deployment.
1 parent 831d00d commit be53db9

4 files changed

Lines changed: 118 additions & 11 deletions

File tree

gunicorn.conf.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import asyncio
2+
import multiprocessing
3+
import sys
4+
5+
import uvloop
6+
from gunicorn.arbiter import Arbiter # type: ignore
7+
from uvicorn_worker import UvicornWorker # type: ignore
8+
9+
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
10+
11+
bind = "0.0.0.0:8000"
12+
workers = multiprocessing.cpu_count() * 2 + 1
13+
workers = 1 # TODO Deni: remove for production
14+
worker_class = "uvicorn_worker.UvicornWorker"
15+
accesslog = "-"
16+
errorlog = "-"
17+
loglevel = "info"
18+
timeout = 60
19+
graceful_timeout = 30
20+
21+
def on_starting(server: Arbiter) -> None:
22+
server.log.info("🚀 Starting Gunicorn with uvloop...")
23+
24+
def on_exit(server: Arbiter) -> None:
25+
server.log.info("👋 Gunicorn is shutting down...")
26+
27+
28+
def worker_int(worker: UvicornWorker) -> None:
29+
worker.log.info("🛑 Worker received INT signal, shutting down gracefully...")
30+
sys.exit(0)
31+
32+
def worker_abort(worker: UvicornWorker) -> None:
33+
worker.log.warning("⚠️ Worker aborted due to timeout or unexpected error.")

pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,16 @@ dependencies = [
1414
"dependency-injector>=4.48.1",
1515
"fastapi>=0.115.14",
1616
"greenlet>=3.2.3",
17+
"gunicorn>=23.0.0",
1718
"jinja2>=3.1.6",
1819
"psutil>=7.0.0",
1920
"pydantic>=2.11.7",
2021
"pydantic-settings>=2.10.1",
2122
"pyjwt>=2.10.1",
2223
"python-logging-loki>=0.3.1",
2324
"sqlalchemy>=2.0.41",
24-
"uvicorn>=0.35.0",
25+
"uvicorn-worker>=0.3.0",
26+
"uvloop>=0.21.0",
2527
]
2628

2729
[tool.setuptools]

server.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,35 @@
11
import sys
22
from pathlib import Path
3+
from typing import Any
4+
5+
from gunicorn.app.base import BaseApplication # type: ignore
36

47
from src.api import app
58

69
sys.path.append(str(Path(__file__).parent))
10+
11+
class StandaloneApplication(BaseApplication):
12+
def __init__(self, application: BaseApplication, option: dict[str, Any] | None=None) -> None:
13+
self.options = option or {}
14+
self.application = application
15+
super().__init__()
16+
17+
def load_config(self) -> None:
18+
config = {
19+
key: value
20+
for key, value in self.options.items()
21+
if key in self.cfg.settings and value is not None
22+
}
23+
for key, value in config.items():
24+
self.cfg.set(key.lower(), value)
25+
26+
def load(self) -> BaseApplication:
27+
return self.application
28+
729
if __name__ == "__main__":
8-
import uvicorn
9-
10-
uvicorn.run(
11-
app=app,
12-
host="0.0.0.0",
13-
port=8000,
14-
log_level=False,
15-
)
30+
options = {
31+
"bind": "0.0.0.0:8000",
32+
"workers": 1, # multiprocessing.cpu_count() * 2 + 1,
33+
"worker_class": "uvicorn.workers.UvicornWorker",
34+
}
35+
StandaloneApplication(application=app, option=options).run()

uv.lock

Lines changed: 54 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)