bottlepy / bottle

bottle.py is a fast and simple micro-framework for python web-applications.
http://bottlepy.org/
MIT License
8.37k stars 1.46k forks source link

Dynamic routing, request.query and OOP #1235

Open ghost opened 4 years ago

ghost commented 4 years ago

Okay, I'll start this off by saying I've never actually made a web application in Python before (until now - currently working on a project with Bottle called Sakamoto). Although I've been using Python for a really long time for other stuff, I've almost always used PHP for web stuff (and moved away due to personal preference) Anyway, my point is, I'm a newbie with this, so take the rest of my "issue" with a grain of salt. I probably have no idea what I'm talking about.

What I'm kind of wondering is why almost every Python web framework seems to prefer dynamic routing, i.e /document/13 is preferred over /document?id=13. When writing actual code with Bottle, the advantages are pretty clear...

@app.get('/document/<id:int>')
def document(id):

meanwhile ?id=13 requires something like this...

@app.get('/document')
def document():
    try:
        id = int(request.query.id)
    except ValueError:
        id = None

Okay, so the latter code looks worse - but here's a thought.. wouldn't it look better if there was some kind of custom decorator that would replace the dynamic route arguments with the query arguments?

Here's what that would look like theoretically...

@app.get_query('/document')
def document(id: int):

And there, just like that, a feature suggestion. If no query is specified or the arguments provided don't match the arguments in the function definition, it would take you to the static route for @app.get('/document'), like so...

@app.get('/document')
def document():
    return 'You did not specify a document!'

@app.get_query('/document')
def document_do(id: int):
    return documents[id]

So... why aren't we doing things like this? You might, at first, argue that it's a problematic solution because /document/13 is cleaner than /document?id=13, but the reason why I prefer using queries isn't to do with how it looks visually, it's more to do with my templates.

See, I like to have one single template file that represents the <head></head> portion of a file. I call it header.tpl, and in it, I might reference a static stylesheet like this:

<link rel="stylesheet" href="./static/main.css" />

Here's the thing though, if I access it from /document it works just fine, but if I then use the same header template from /document/13, it'll break, because the relative path of the stylesheet doesn't match anymore. This is the big problem I have with dynamic routing. The deeper your dynamic routes go, the more ugly it gets to reference assets like stylesheets and images relatively.

Typically you might suggest that I change the path of the stylesheet to /static/main.css, so that it always starts from the root route, but I disagree with this solution. The problem is that browsers will typically consider the root(/) as starting from the domain name, so it would look for the stylesheet at, say, example.com/static/main.css. I don't want that though, because somebody might create a reverse proxy that would place the stylesheet at example.com/sakamoto/static/main.css, and obviously, that means that referencing it as /static/main.css in the header template will result in the stylesheet not being found. The only alternative for the end-user then is to dedicate an entire subdomain to the reverse proxy that points to my application, which might not always be possible. I intend for my application to work anywhere, and that effectively means creating a new relative path for the stylesheet for every single dynamic route.

So, this whole dynamic routing thing poses the problem that I can't use a single header template for every single page, which means I either make my templates super bloated by making a <head> section for every single one, or I resort to using queries, which makes my code look like sphagetti.

What's the solution here? Why isn't there a way to pass GET queries through to function arguments like my "ideal demo" that I proposed above? Should I just give up and force the end user to make my web application only accessible from the URL root? Does Bottle need to add a feature to make all of this easier? Is my head going to explode from confusion? I have no idea. I came here for answers, and like I said, I'm new at this and I have no idea what I'm doing.

On top of that, when should someone as in-decisive as me pick dynamic routing over GET queries? They both do pretty much the same thing, right? What's the point in using either one over the other, exactly? Does the Bottle documentation need to clarify this? Is it even a problem?

Sidenote: Why are request and response something you have to import and access globally? Surely it would be better if each route passed a "context" argument through to the callback function, where you could access stuff like request and response etc in a less.. unsettling.. way? Like this...

@app.get('/document')
def document(context):
    return 'Hello {0}!'.format(context.request.query.name)

Anyway, that's about it. I'm confused as hell after writing all that, and I've probably confused everyone reading it too to be honest, but whatever. I'm still not sure what the "perfect" solution to all of this is, and hopefully there isn't something really obvious that I've missed.