bottlepy / bottle

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

Lazy routes #737

Open gabrielalan opened 9 years ago

gabrielalan commented 9 years ago

Hello,

Recently I started a personal project, where I use bottle.

But, thinking about the routes, I was searching for a way to do "lazy routes" where the methods that register a path to the route, dont need to be "called".

I implemented some classes, with methods that register routes, and I dont want to call them all, in the WSGI file.

Are there a way to do that?

Ps.: Sorry for my english

foxbunny commented 9 years ago

Wouldn't it be simpler to create a wrapper function that would be registered as route handler and instantiate an appropriate class when it's about to handle a request?

EDIT: The wrapper function could be a class method, too.

eric-wieser commented 9 years ago

Methods aren't "instantiated" in any meaningful way. Can you post some example code of what you want to work?

gabrielalan commented 9 years ago

@foxbunny I see. But the route for that wrapper, must be a "wildcard" route, doesn't? Or there are a "bottle native way" to create a route wrapper function?

@eric-wieser Of course

class Router():

    @app.route('/')
    def index():
        return template('index', name="to home")

    @app.route('/hello')
    @app.route('/hello/<name>')
    def hello(name='Stranger'):
        return template('index', name=name)

# on other file
instance = Router()

EDIT: This work today, BUT if I create another Router class, it have to be instantiated. And on a real enviroment where there are many routers, I think that is not a good pratice.

eric-wieser commented 9 years ago

What purpose does your Router class serve here? Your code works even if I don't instantiate the router:

class Router():
    # @app.route runs at declaration-time, not instantiation time
    @app.route('/')
    def index():
        return template('index', name="to home")

    @app.route('/hello')
    @app.route('/hello/<name>')
    def hello(name='Stranger'):
        return template('index', name=name)

And furthermore, if I do instantiate it, the instance is useless:

instance = Router()
instance.hello(name='gabriel')
TypeError <some message about the missing self argument

There's no good reason for you not to just write:

@app.route('/')
def index():
        return template('index', name="to home")

@app.route('/hello')
@app.route('/hello/<name>')
def hello(name='Stranger'):
        return template('index', name=name)
foxbunny commented 9 years ago

@gabrielalan Ah, I see. I thought you were doing something along the lines of:

class RouteHandler(object):
    def __init__(self, *args, **kwargs):
         ....

    @classmethod
    def handle(cls, *args, **kwargs):
        return cls(*args, **kwargs)

    @classmethod
    def register(cls, app, path, method, *args, **kwargs):
        app.route(path, method, cls.handle, *args, **kwargs)
gabrielalan commented 9 years ago

Maybe I'm doing it wrong. The example above, is kind of simple code. I think I didn't express myself correctly.

But what I really want is something like that: https://webapp-improved.appspot.com/guide/routing.html#guide-routing-lazy-handlers

gabrielalan commented 9 years ago

@foxbunny Your idea seems very good... I will test this.

eric-wieser commented 9 years ago

What goal are you trying to achieve by lazy-loading routes?

foxbunny commented 9 years ago

Personally, I don't really see any advantage in lazy evaluation of route handlers, to be honest, though there could be valid use cases.

What I normally do is, avoid the route decorator, and do something like this in a central place:

app.route('/some/path/', 'GET', routes.foo.foo_handler)
app.route('/some/path/', 'POST', routes.foo.foo_post_handler)
...

You could wrap app.route and use strings instead of module names with clever use of imp and/or pkgutils modules from the standard library, and/or __import__ call, but that still won't quite be lazy, as you'd be loading modules when you call the wrapper. So something like this:

route = router(app)
route('/some/path/', 'GET', 'routes.foo.foo_handler')
...
gabrielalan commented 9 years ago

@eric-wieser @foxbunny

I think the advantage of having lazy handlers, is, not use resources that are not needed. For example, if the client goes to the initial route, I don't need to load the others routes. I don't want to load resources that I will not use. You see?

Edit: Thank you for the examples...

foxbunny commented 9 years ago

But eventually, any modules you load get cached, so even with lazy loading, you're not going to save much.

eric-wieser commented 9 years ago

But surely you expect every route to be accessed within the lifetime of your server anyway?

syegulalp commented 9 years ago

Would this be useful when running something as CGI, for instance, where not every route might be instantiated (since the application has a short lifetime)?

eric-wieser commented 9 years ago

Perhaps, but the original request for lazy-loading of methods is still meaningless. The only thing that is lazy-loadable is modules

foxbunny commented 9 years ago

Yeah

gabrielalan commented 9 years ago

@foxbunny I see...

@eric-wieser yes...

Well, you opened my mind about ways to make a "route manager". I will try some ideas that you send me here, and I post the result that will perform better. Thank you for now :)