DEV Community

MikeVV
MikeVV

Posted on

1

Illogical behaviour of python web framework (issue investigation story)

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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())
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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))
Enter fullscreen mode Exit fullscreen mode

db.py

from peewee import *

....

def init_db():
  ....
  return db
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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))
Enter fullscreen mode Exit fullscreen mode

db.py

from peewee import *

....

def init_db():
  ....
  return db

db = init_db()
Enter fullscreen mode Exit fullscreen mode

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?

Heroku

Deliver your unique apps, your own way.

Heroku tackles the toil — patching and upgrading, 24/7 ops and security, build systems, failovers, and more. Stay focused on building great data-driven applications.

Learn More

Top comments (0)

Image of Quadratic

Python + AI + Spreadsheet

Chat with your data and get insights in seconds with the all-in-one spreadsheet that connects to your data, supports code natively, and has built-in AI.

Try Quadratic free

👋 Kindness is contagious

Engage with a wealth of insights in this thoughtful article, cherished by the supportive DEV Community. Coders of every background are encouraged to bring their perspectives and bolster our collective wisdom.

A sincere “thank you” often brightens someone’s day—share yours in the comments below!

On DEV, the act of sharing knowledge eases our journey and forges stronger community ties. Found value in this? A quick thank-you to the author can make a world of difference.

Okay