Reputation: 409
I want to add and remove routes while my bottle server is running.
My general question: Is there a proper way to remove routes?
In other words: How can I undo what I've done with app.route
?
In the following I desrcibe my problem in a more detailed way. Please don't waste your time reading it, if you know the answer to my general question.
This is a little demo script that describes how I'm working around my problem:
If calling GET /add: a 'Hello World' route is added
If calling GET /remove: all routes with the same prefix like the 'Hello World' route are changed to trigger a 404 error
import bottle
from bottle import redirect, abort
def addRoute():
app.route('/route/hello')(lambda :'Hello World')
print('Routes after calling /add:\n' + '\n'.join([str(route) for route in app.routes]))
redirect('route/hello')
def removeRoute():
prefix = '/route/hello'
#making a list of all routes having the prefix
#because this is a testfile there is only one rule that startswith prefix
routes = [route for route in app.routes if route.rule.startswith(prefix)]
#because there could be multiple routes with the same rule and method,
#making a list without duplicates
ruleMethodTuples = list(dict.fromkeys([(route.rule, route.method) for route in routes]))
for ruleMethod in ruleMethodTuples :
#Workaround: Overwriting the existing route with a 404
#Here I'd prefer to make a statement that removes the existing route instead,
#so the default 404 would be called
app.route(ruleMethod[0], method = ruleMethod[1])(lambda **kwords: abort(404, 'Route deleted'))
print('Routes after calling /remove:\n' + '\n'.join([str(route) for route in app.routes]))
redirect('/route/hello')
if __name__ == '__main__':
app = bottle.app()
app.route('/add')(addRoute)
app.route('/remove')(removeRoute)
print('Initial routes:\n' + '\n'.join([str(route) for route in app.routes]))
bottle.run(app, host = 'localhost', port = 8080)
So here is the output after starting and calling:
/add
/remove
/add
showing which routes are in app.routes after every call.
Initial routes:
<GET '/add' <function addRoute at 0x02A876A8>>
<GET '/remove' <function removeRoute at 0x00F5F420>>
Bottle v0.12.17 server starting up (using WSGIRefServer())...
Listening on http://localhost:8080/
Hit Ctrl-C to quit.
Routes after calling /add:
<GET '/add' <function addRoute at 0x02A876A8>>
<GET '/remove' <function removeRoute at 0x00F5F420>>
<GET '/route/hello' <function addRoute.<locals>.<lambda> at 0x030C3E88>>
127.0.0.1 - - [25/Aug/2020 23:19:09] "GET /add HTTP/1.1" 303 0
127.0.0.1 - - [25/Aug/2020 23:19:09] "GET /route/hello HTTP/1.1" 200 11
Routes after calling /remove:
<GET '/add' <function addRoute at 0x02A876A8>>
<GET '/remove' <function removeRoute at 0x00F5F420>>
<GET '/route/hello' <function addRoute.<locals>.<lambda> at 0x030C3E88>>
<GET '/route/hello' <function removeRoute.<locals>.<lambda> at 0x030C3FA8>>
127.0.0.1 - - [25/Aug/2020 23:19:14] "GET /remove HTTP/1.1" 303 0
127.0.0.1 - - [25/Aug/2020 23:19:14] "GET /route/hello HTTP/1.1" 404 720
Routes after calling /add:
<GET '/add' <function addRoute at 0x02A876A8>>
<GET '/remove' <function removeRoute at 0x00F5F420>>
<GET '/route/hello' <function addRoute.<locals>.<lambda> at 0x030C3E88>>
<GET '/route/hello' <function removeRoute.<locals>.<lambda> at 0x030C3FA8>>
<GET '/route/hello' <function addRoute.<locals>.<lambda> at 0x030E0270>>
127.0.0.1 - - [25/Aug/2020 23:19:17] "GET /add HTTP/1.1" 303 0
127.0.0.1 - - [25/Aug/2020 23:19:18] "GET /route/hello HTTP/1.1" 200 11
So instead of replacing GET '/route/hello', the call: app.route adds more routes with the same method and rule on the end of the list.
However this worked so far, because the latest matching route one is choosen firstly, but I am pretty sure this would result (earlier or later) to performance problems or to a server crash after calling some /adds and /removes.
Furthermore I noticed, that I can change app.routes without changing the actual routing, so secondary question:
Could I just delete the 'deprecated' routes from app.routes to prevent a stackoverflow?
And third question:
Is there something I am doing in a completely wrong way?
Upvotes: 1
Views: 329
Reputation: 142641
I checked source code for add_route
It adds route
to two objects: self.routes
and self.router
(app.routes
and app.router
) and this makes problem.
def add_route(self, route):
""" Add a route object, but do not change the :data:`Route.app`
attribute."""
self.routes.append(route)
self.router.add(route.rule, route.method, route, name=route.name)
if DEBUG: route.prepare()
self.router
is object Router which has rules
, builder
, static
dyna_routes
, dyna_regexes
If you check them before and after adding new route then you see changes in builder
and static
.
def addRoute():
print('--- before ---')
print(app.router.rules)
print(app.router.builder)
print(app.router.static)
print(app.router.dyna_routes)
app.route('/route/hello')(lambda :'Hello World')
print('--- after ---')
print(app.router.rules)
print(app.router.builder)
print(app.router.static)
print(app.router.dyna_routes)
print('Routes after calling /add:\n' + '\n'.join([str(route) for route in app.routes]))
redirect('route/hello')
If I remove '/route/hello'
from builder
and static
then '/route/hello'
stop working but app.routes
still shows them so you will have to remove '/route/hello'
from both app.routes
and app.router
- but they don't have special functions for this :)
def removeRoute():
prefix = '/route/hello'
del app.router.builder[prefix]
del app.router.static['GET'][prefix]
print('Routes after calling /remove:\n' + '\n'.join([str(route) for route in app.routes]))
redirect('/route/hello')
My full code:
import bottle
from bottle import redirect, abort
def addRoute():
print('--- before /add ---')
print(app.router.rules)
print(app.router.builder)
print(app.router.static)
print(app.router.dyna_routes)
print(app.router.dyna_regexes)
print('Routes before calling /add:\n' + '\n'.join([str(route) for route in app.routes]))
app.route('/route/hello')(lambda :'Hello World')
print('--- after /add ---')
print(app.router.rules)
print(app.router.builder)
print(app.router.static)
print(app.router.dyna_routes)
print(app.router.dyna_regexes)
print('Routes after calling /add:\n' + '\n'.join([str(route) for route in app.routes]))
redirect('route/hello')
def removeRoute():
prefix = '/route/hello'
print('--- before /remove ---')
print(app.router.rules)
print(app.router.builder)
print(app.router.static)
print(app.router.dyna_routes)
print(app.router.dyna_regexes)
print('Routes before calling /remove:\n' + '\n'.join([str(route) for route in app.routes]))
del app.router.builder[prefix]
del app.router.static['GET'][prefix]
print('--- after /remove ---')
print(app.router.rules)
print(app.router.builder)
print(app.router.static)
print(app.router.dyna_routes)
print(app.router.dyna_regexes)
print('Routes before calling /remove:\n' + '\n'.join([str(route) for route in app.routes]))
redirect('/route/hello')
if __name__ == '__main__':
app = bottle.app()
app.route('/add')(addRoute)
app.route('/remove')(removeRoute)
print('Initial routes:\n' + '\n'.join([str(route) for route in app.routes]))
bottle.run(app, host = 'localhost', port = 8080)
Upvotes: 1