1

I am adding a URL handler to a Flask application using add_url_rule. It requires a function as a handler (not a method). However, for proper encapsulation, my intended function is a bound method that uses the self variable. Is there any way I can somehow make Flask see my function: def action(self, param) as def action(param) ?

class A():
    def __init__(self):
        self._localvar = 5
        self._flask = Flask("testapp")

    def add_rules(self):
        self._flask.add_url_rule("/","root",????)

    def action(self, value):
        print("The value is {}".format(self._localvar))

    def run(self):
        self._flask.run()

app = A()
app.add_rules()
app.run()

what I want to do is replace ???? with app.action, a bound method. However, add_url_rule expects a function. I tried app.action.__func__ and app.action.im_func which are function pointers. However, the function signature is still going to require self and will give me a runtime error.

My current solution is to have some loose functions in my module and have app just use them directly, but it just seems that there has to be a cleaner way since the handlers are really tightly coupled to the app object and I don't want them floating around outside of it.

user3076411
  • 107
  • 1
  • 9
  • So what is `self` going to reference then? If you have a single, global instance, use `a = A()` and register `a.action`. – Martijn Pieters Oct 23 '17 at 19:52
  • Note that route handlers are global anyway, so why the class encapsulation? You only register one for each path. – Martijn Pieters Oct 23 '17 at 19:53
  • What happens if you pass it `app.action`? – user2357112 Oct 23 '17 at 20:06
  • If I pass app.action, then when the handler is invoked, it gives me a runtime error because it expects 2 variables but I am only passing 1. Which means it expects the "self" param. – user3076411 Oct 23 '17 at 20:14
  • 1
    What I want to know is whether there is a python way to somehow "cast" a `def action(value)` to the `def action(self, value)` and somehow smartly replace the `self` references with the address of my `self` such that this method becomes compatible with the register type – user3076411 Oct 23 '17 at 20:15
  • 1
    You've defined an `A` class, but you don't seem to have an instance of `A` anywhere. `app` is an instance of `Flask`, an entirely unrelated class. What `A` instance do you want to call the `action` method of? – user2357112 Oct 23 '17 at 20:16
  • Oh sorry. My bad. Let me edit quick – user3076411 Oct 23 '17 at 20:18
  • @MartijnPieters I need the encapsulation because I might write another server B that behaves differently to the same URIs. Using global functions, I will have a soup of floating functions inside the module containing A and B and it will be hard to find out which handler is specific to which implementation – user3076411 Oct 23 '17 at 20:52
  • @user3076411: That can be achieved by just putting the different implementations in to different modules (or packages to further split it out to files). Determine at runtime what module with routes you import. – Martijn Pieters Oct 24 '17 at 06:42

3 Answers3

2

What I want to know is whether there is a python way to somehow "cast" a def action(value) to the def action(self, value) [...]

And what if you do it the other way around? You define your actions as functions (or staticmethods if you want to keep them in the class A), and bind them to your instance only when you need them.

You could use the types module to bind a function to an instance.

import types

class A():

    def bind_action_func_to_instance(self, func, value):
        self.action_method = types.MethodType(func, self)

    @staticmethod
    def action_func(value):
        print('action with value: {}'.format(value))

    def add_rules():
        self._flask.add_url_rule("/","root", self.action_func)

You could also implement a default action method and assign it to action_method in the __init__. It will then be reassigned when you call bind_action_func_to_instance.

I think you could also find useful this answer about the Strategy pattern.

jackdbd
  • 4,583
  • 3
  • 26
  • 36
  • But would this allow me to use the variable "self" inside the static method ? I am assuming not. Still, I up vote for use of types module – user3076411 Oct 28 '17 at 01:23
2

This is an old question, but I thought it might be useful to know a possibly easier solution for someone (like me) visiting this page.

You can access the underlying function of a bound method as its __func__ attribute. Also, if you access it as an attribute of the class and not the instance, it will give you the function.

class C:
    def fn(self, param):
        return param

instance = C()
print(instance.fn("hello"))  # 'hello'
print(instance.fn.__func__(instance, "hello"))  # 'hello'
print(instance.fn.__func__(None, "hello"))  # 'hello'
print(C.fn(instance, "hello"))  # 'hello'
print(C.fn(None, "hello"))  # 'hello'
CyanoKobalamyne
  • 207
  • 2
  • 6
1

I Think I finally found a way to do this (i.e. keep the action method bound inside my class and have a function-like reference to it when needed). I used a function inside a method:

class A():
    def __init__(self):
        self._localvar = 5
        self._flask = Flask("testapp")

    def add_rules(self):
        self._flask.add_url_rule("/","root",self.action_as_func())

    def action(self, value):
        print("The value is {}".format(self._localvar))

    def action_as_func(self):
        def inner_action(value):
            self.action(value)
        return inner_action

    def run(self):
        self._flask.run()

app = A()
app.add_rules()
app.run()

Any comments on whether there is something wrong with this ? The function can access self. Kinda sneaky :)

user3076411
  • 107
  • 1
  • 9