Bottle framework and OOP, using method instead of function

14,396

Solution 1

Your code does not work because you are trying to route to non-bound methods. Non-bound methods do not have a reference to self, how could they, if instance of App has not been created?

If you want to route to class methods, you first have to initialize your class and then bottle.route() to methods on that object like so:

import bottle        

class App(object):
    def __init__(self,param):
        self.param   = param

    def index1(self):
        return("I'm 1 | self.param = %s" % self.param)

myapp = App(param='some param')
bottle.route("/1")(myapp.index1)

If you want to stick routes definitions near the handlers, you can do something like this:

def routeapp(obj):
    for kw in dir(app):
        attr = getattr(app, kw)
        if hasattr(attr, 'route'):
            bottle.route(attr.route)(attr)

class App(object):
    def __init__(self, config):
        self.config = config

    def index(self):
        pass
    index.route = '/index/'

app = App({'config':1})
routeapp(app)

Don't do the bottle.route() part in App.__init__(), because you won't be able to create two instances of App class.

If you like the syntax of decorators more than setting attribute index.route=, you can write a simple decorator:

def methodroute(route):
    def decorator(f):
        f.route = route
        return f
    return decorator

class App(object):
    @methodroute('/index/')
    def index(self):
        pass

Solution 2

You have to extend the Bottle class. It's instances are WSGI web applications.

from bottle import Bottle

class MyApp(Bottle):
    def __init__(self, name):
        super(MyApp, self).__init__()
        self.name = name
        self.route('/', callback=self.index)

    def index(self):
        return "Hello, my name is " + self.name

app = MyApp('OOBottle')
app.run(host='localhost', port=8080)

What most examples out there are doing, including the answers previously provided to this question, are all reusing the "default app", not creating their own, and not using the convenience of object orientation and inheritance.

Solution 3

Below works nicely for me :) Quite object orientated and easy to follow.

from bottle import Bottle, template

class Server:
    def __init__(self, host, port):
        self._host = host
        self._port = port
        self._app = Bottle()
        self._route()

    def _route(self):
        self._app.route('/', method="GET", callback=self._index)
        self._app.route('/hello/<name>', callback=self._hello)

    def start(self):
        self._app.run(host=self._host, port=self._port)

    def _index(self):
        return 'Welcome'

    def _hello(self, name="Guest"):
        return template('Hello {{name}}, how are you?', name=name)

server = Server(host='localhost', port=8090)
server.start()

Solution 4

I took @Skirmantas answer and modified it a bit to allow for keyword arguments in the decorator, like method, skip, etc:

def routemethod(route, **kwargs):
    def decorator(f):
        f.route = route
        for arg in kwargs:
            setattr(f, arg, kwargs[arg])
        return f
    return decorator

def routeapp(obj):
    for kw in dir(obj):
        attr = getattr(obj, kw)
        if hasattr(attr, "route"):
            if hasattr(attr, "method"):
                method = getattr(attr, "method")
            else:
                method = "GET"
            if hasattr(attr, "callback"):
                callback = getattr(attr, "callback")
            else:
                callback = None
            if hasattr(attr, "name"):
                name = getattr(attr, "name")
            else:
                name = None
            if hasattr(attr, "apply"):
                aply = getattr(attr, "apply")
            else:
                aply = None
            if hasattr(attr, "skip"):
                skip = getattr(attr, "skip")
            else:
                skip = None

            bottle.route(attr.route, method, callback, name, aply, skip)(attr)

Solution 5

try this, worked for me, documentation is also pretty decent to get started with ...

https://github.com/techchunks/bottleCBV
Share:
14,396
Admin
Author by

Admin

Updated on June 18, 2022

Comments

  • Admin
    Admin almost 2 years

    I've done some coding with Bottle. It's really simple and fits my needs. However, I got stick when I tried to wrap the application into a class :

    import bottle
    app = bottle
    
    class App():
        def __init__(self,param):
            self.param   = param
    
        # Doesn't work
        @app.route("/1")
        def index1(self):
            return("I'm 1 | self.param = %s" % self.param)
    
        # Doesn't work
        @app.route("/2")
        def index2(self):
            return("I'm 2")
    
        # Works fine
        @app.route("/3")
        def index3():
            return("I'm 3")
    

    Is it possible to use methods instead of functions in Bottle?

  • larsks
    larsks over 11 years
    Note that bottle.route(attr.route, attr) will not work as intended; you want bottle.route(attr.route)(attr) (because bottle.route() is a decorator, which returns a callable, which then consumes (attr)).
  • Ski
    Ski over 11 years
    Thanks for the note! Fixed it.
  • Alex-Bogdanov
    Alex-Bogdanov about 7 years
    what if you have dozen of routes?
  • jpcgt
    jpcgt about 7 years
    @Alex-Bogdanov you have to define your routes one way or another. You can have wildcards in your routes, which allow you to call a method and give it a parameter based on the route you used. This allows you to have fewer route definitions that actual possible routes.
  • Joël
    Joël almost 5 years
    This answer helped me achieving class-based definition :) For information, it is also possible to 'route' errors using self.error(error_code, callback=method) directly with latest bottle code, or using self.error(error_code)(method) with bottle==0.12.17.
  • Joël
    Joël about 4 years
    Additional comment here: what's a bit sad is that there is no simple, direct way to use @self.route('whatever') to decorate methods. I used other answers to circumvent this: use a first decorator to tag methods for routing, then a class method inspects the class for them and bind the routes.
  • jpcgt
    jpcgt about 4 years
    @Joël What you are doing sounds just right to me. You are not really circumventing anything, rather implementing something that is missing. If you check for tags and add the routes in the constructor it would look quite clean. In fact, subclass Bottle just to do that, then use your new class as base class and you will never have to see the routing code again!
  • Joël
    Joël about 4 years
    That's exactly what it ended up looking like! So, as this is in fact pretty lean stuff, I create an issue on bottle bugtracker to propose a contribution :)
  • Joël
    Joël about 4 years
    Hi, -1: this solution is not object-oriented, it's wrapping code that would be in a module in a class, plus mixing it with server capability.
  • Joël
    Joël about 4 years
    Hi, I just propose to integrate this in bottle, see issue 1224. I'd be happy with comments there. :)
  • oz123
    oz123 almost 4 years
    This solution uses composition instead of inheritance, which is perfectly fine in OOP. To quote wikipedia on this: To favor composition over inheritance is a design principle that gives the design higher flexibility. I