TLDR: Strange issues with Sanic framework on my new pet project were fixed in ugly way and I do not like it but do not see the other way and asking for help/advice.
Recently I have tried a new python ASGI web framework for my new pet project and faced an weirdest issue on my memory.
Our hero framework here is Sanic - new, modern async web framework with great slogan "Build fast. Run fast." on the site.
Let's move to the story itself.
I have created an web application backbone with couple endpoints and it works at the beginning.
code like this:
from sanic import Sanic
app = Sanic("pet project N7355")
@app.route("/")
async def index_handler(request):
return json(dict(name="pet project N7355", version="0.1.0"))
@app.route("/system/health")
async def health_handler(request):
return json(dict(status="ok"))
@app.route("/system/stat")
async def stat_handler(request):
return json(dict())
app.run(host='0.0.0.0', port=8765, debug=debug)
But after I split that code to modules and moved initialisation of endpoints to separate function I have started to see an error about Sanic cannot start as there is no routes defined.
new code:
main.py
from sanic import Sanic
from routes import init_routes
app = Sanic("pet project N7355")
init_routes(app)
app.run(host='0.0.0.0', port=8765, debug=debug)
routes.py
def init_routes(app):
@app.route("/")
async def index_handler(request):
return json(dict(name="pet project N7355", version="0.1.0"))
@app.route("/system/health")
async def health_handler(request):
return json(dict(status="ok"))
@app.route("/system/stat")
async def stat_handler(request):
return json(dict())
Similar approach for defining a routes was used in my previous project with Flask and worked perfectly fine but not now.
I have tried to run application in single thread mode as suggested in error message but there was no such error in this more.
I thought what a strange issue and fixed that by workaround (dirty hack in fact) - moved "/" method from function in module to main file.
My next step was about connecting to database.
I have used PeeWee ORM (not an mainstream choice but it looks simple and easy) and as there is no plugins to link Sanic with PeeWee I used approach listed on Sanic site - put a connection to application context app.ctx.db = db
and faced another error - "cannot use not initialised database".
code with db connection:
main.py
from sanic import Sanic
from routes import init_routes
from db import init_db
app = Sanic("pet project N7355")
app.ctx.db = init_db()
# workaround to fix routes issue in multithread mode
@app.route("/")
async def index_handler(request):
return json(dict(name="pet project N7355", version="0.1.0"))
init_routes(app)
app.run(host='0.0.0.0', port=8765, debug=debug)
routes.py
def init_routes(app):
@app.route("/system/health")
async def health_handler(request):
return json(dict(status="ok"))
@app.route("/system/stat")
async def stat_handler(request):
app.ctx.db.connect()
msg_count = Message.select().count()
app.ctx.db.close()
return json(dict(msg_count=msg_count))
db.py
from peewee import *
....
def init_db():
....
return db
At first I did not realised that these issues are about the same but after some debugging with prints I have found that Sanic in multithreaded mode besides main instance of app
creates two additional instances (I suppose I instance per thread) and these new instances in fact processing requests, and do not have routes and db defined.
After finding that behaviour I understand how to fix the issues - move all initialisation from "runtime" level to "module import" level
fixed code:
main.py
from sanic import Sanic
from routes import app
from db import db
app.ctx.db = db
app.run(host='0.0.0.0', port=8765, debug=debug)
routes.py
app = Sanic("pet project N7355")
@app.route("/")
async def index_handler(request):
return json(dict(name="pet project N7355", version="0.1.0"))
@app.route("/system/health")
async def health_handler(request):
return json(dict(status="ok"))
@app.route("/system/stat")
async def stat_handler(request):
app.ctx.db.connect()
msg_count = Message.select().count()
app.ctx.db.close()
return json(dict(msg_count=msg_count))
db.py
from peewee import *
....
def init_db():
....
return db
db = init_db()
from one hand this solution works but from other it looks ugly for me as with that code structure it hard to test modules code in isolation.
I completely understand that I used not the best approach with routes and better rewrite those with blueprints that available in Sanic, but still, for DB I have used approach listed on the Sanic site and is does not work for me because that Sanic's specifics.
As a result I have a open questions:
- [rhetorical] Why it made is such way where only one way to make things right exists and this specifics is not listed in documentation?
- [practical] Maybe I did something wrong and things that I see as logical is not in fact logical?
Maybe someone faced something like this and know how to do that properly?
Top comments (0)