【问题标题】:Using flask inside class在课堂上使用烧瓶
【发布时间】:2016-11-07 08:35:25
【问题描述】:

我有一个包含许多线程的应用程序。其中之一是flask,用于实现(辅助)API。它使用低负载,从不暴露在互联网上,因此内置flask web服务器非常好。

我当前的代码如下所示:

class API:
    # ... all other stuff here, skipped
    def run():
        app = flask.Flask('API')

        @app.route('/cmd1')
        def cmd1():
            self.cmd1()

        @app.route('/cmd2')
        def cmd2()
            self.cmd2()

        app.run()

我觉得我做错了,因为所有文档都说“在模块级别创建烧瓶应用程序”。但我不想这样做——它弄乱了我的测试,API 是较大应用程序的一小部分,它有自己的结构和约定(每个“应用程序”都是在一个或多个线程中运行的单独类) .

如何在课堂上使用 Flask?

【问题讨论】:

  • 这段代码有问题吗?如果有,是什么?
  • 我的带有子功能的变体在测试中很糟糕。我无法从测试中调用“cmd2”函数,这使得部分代码没有测试覆盖(这会带来愚蠢的错字/痕迹)。对这部分代码(“运行”内部的子函数)缺乏测试是问题的主要原因。

标签: python flask


【解决方案1】:

虽然这可行,但感觉不符合 Flask 样式指南。如果您需要在项目中包装 Flask 应用程序,请根据需要创建一个单独的类并添加应该执行的函数

from flask import Flask, Response


class EndpointAction(object):

    def __init__(self, action):
        self.action = action
        self.response = Response(status=200, headers={})

    def __call__(self, *args):
        self.action()
        return self.response


class FlaskAppWrapper(object):
    app = None

    def __init__(self, name):
        self.app = Flask(name)

    def run(self):
        self.app.run()

    def add_endpoint(self, endpoint=None, endpoint_name=None, handler=None):
        self.app.add_url_rule(endpoint, endpoint_name, EndpointAction(handler))


def action():
    # Execute anything

a = FlaskAppWrapper('wrap')
a.add_endpoint(endpoint='/ad', endpoint_name='ad', handler=action)
a.run()

这里有几点需要注意:

  • EndpointAction 应该是一个包装器,它将执行您的函数并生成一个空的 200 响应。如果需要,您可以编辑功能
  • 端点处理程序可以是任何定义了__call__ 方法的东西
  • 端点名称应该是唯一的,因为它代表视图名称
  • 无法在应用程序之后添加端点,因为一旦应用程序启动,线程将阻塞。您可以通过在单独的线程上运行应用程序来启用它,但不建议动态更改 URL 映射,也不是线程安全的

【讨论】:

  • 谢谢。这不如装饰器优雅,但与应用程序中的其余代码一致。
  • 在使用装饰器时,可以参考@app.route('/.../',methods=['POST']) 等具体方法,那么在EndpointAction类中该怎么做呢?
  • 小问题:FlaskAppWrapper.app 是在 CLASS 级别指定的,而不是在 __init__() 中的实例级别。 FlaskAppWrapper 的每个实例都将共享同一个应用程序对象。如果您要使用 2 个不同的烧瓶实例创建 2 个 FlaskAppWrappers,则两者都将共享第 2 个实例,因为第 2 个实例将覆盖第 1 个实例。为了缓解这种情况,请从 __init__ 上方删除 app = None。 __init__ 中定义的任何属性都将保留在实例级别。在这里没什么大不了的,因为您可能不会拥有多个 FlaskAppWrapper,但总体上要记住这一点。
【解决方案2】:

所以我刚刚遇到了图书馆Flask-Classful

比较简单

在一个类中创建一个简单的网络应用是这样的:

from flask import Flask
from flask_classful import FlaskView

app = Flask(__name__)

class TestView(FlaskView):

    def index(self):
    # http://localhost:5000/
        return "<h1>This is my indexpage</h1>"

TestView.register(app,route_base = '/')

if __name__ == '__main__':
    app.run(debug=True) 

处理多路由和动态路由也很简单

class TestView(FlaskView):

    def index(self):
    # http://localhost:5000/
        return "<h1>This is my indexpage</h1>"

    def secondpage(self):
    # http://localhost:5000/secondpage
        return "<h1>This is my second</h1>"
    
    def thirdpage(self,name):
    # dynamic route
    # http://localhost:5000/thirdpage/sometext
        return "<h1>This is my third page <br> welcome"+name+"</h1>"

TestView.register(app,route_base = '/')

也可以使用其他方法添加自己的路线名称

from flask_classful import FlaskView,route

class TestView(FlaskView):

    def index(self):
    # http://localhost:5000/
        return "<h1>This is my indexpage</h1>"


    @route('/diffrentname')
    def bsicname(self):
    # customized route
    # http://localhost:5000/diffrentname
        return "<h1>This is my custom route</h1>"
TestView.register(app,route_base = '/')

这有可能为单独的依赖和独立进程创建单独的类和处理程序,并将它们作为包导入以在主文件或包装文件上运行

from package import Classname
Classname.register(app,route_base = '/')

非常简单且面向对象

【讨论】:

  • 这是一个很棒的库!这么多有用的选择!谢谢
  • 感谢您提请我注意。虽然这个解决方案有一些缺点,但它解决了我的问题:你必须有没有参数的构造函数方法的类。
  • @Regis May:我建议创建一个隔离的 FlaskView 类,在您的模型类构造函数中实例化它,并在实例化之后但在注册之前将模型的引用传递给 FlaskView。
  • 这是我找到的最佳解决方案!非常感谢
【解决方案3】:

完成 Kostas Pelelis 的回答,因为我很难找到为什么 Response 不直接使用 Action 返回值。

这是另一个没有装饰器的 FLASK 类:

class EndpointAction(object):

    def __init__(self, action):
        self.action = action

    def __call__(self, *args):
        # Perform the action
        answer = self.action()
        # Create the answer (bundle it in a correctly formatted HTTP answer)
        self.response = flask.Response(answer, status=200, headers={})
        # Send it
        return self.response

class FlaskAppWrapper(object):

    def add_all_endpoints(self):
        # Add root endpoint
        self.add_endpoint(endpoint="/", endpoint_name="/", handler=self.action)

        # Add action endpoints
        self.add_endpoint(endpoint="/add_X", endpoint_name="/add_X", handler=self.add_X)
        # you can add more ... 

    def add_endpoint(self, endpoint=None, endpoint_name=None, handler=None):
        self.app.add_url_rule(endpoint, endpoint_name, EndpointAction(handler)) 
        # You can also add options here : "... , methods=['POST'], ... "

    # ==================== ------ API Calls ------- ====================
    def action(self):
        # Dummy action
        return "action" # String that will be returned and display on the webpage
        # Test it with curl 127.0.0.1:5000

    def add_X(self):
        # Dummy action
        return "add_X"
        # Test it with curl 127.0.0.1:5000/add_X

【讨论】:

  • 巫术师太多了。我想我宁愿坚持在一个页面中设置所有路由。 :(
  • 好吧,我看不出巫术在哪里......对我来说,装饰器更像是巫术(因为你不知道会发生什么,这有点神奇)。在这里,您可以看到每个元素的定义内容、位置和方式。如果难以阅读,请这样: add_all_endpoints => add_endpoint => EndpointAction(handler) => action "add_url_rule" 是标准的 Flask 调用,这里唯一的“巫术”。试一试,你会看到的。如果你想看更多:github.com/Vincent-CIRCL/douglas-quaid/blob/master/…
  • 嗯。我想做的是有一个模块,比如app.py 和初始化烧瓶应用程序的函数。然后,我有routes/ 包,其中我有一个Route 类,我将使用它扩展,比如AccountRoute,我将拥有createdeletefind 之类的功能。通常的 CRUD 东西。因此,在app.py 中,我将初始化应用程序,并将其传递给所有后代路由类。如果没有我所引用的巫术,我还没有找到一种优雅的方法。我希望它尽可能详细和清晰,甚至不需要一行评论。我会检查链接。坦克
【解决方案4】:

这是一个混合类和路由的示例,对我来说似乎是合理的。另请参阅https://github.com/WolfgangFahl/pyFlaskBootstrap4/issues/2(我是提交者)

此设计已被批评,因此在项目中对此代码进行了一些改进。

'''
Created on 27.07.2020

@author: wf
'''
from flask import Flask
from frontend.WikiCMS import Frontend
from flask import render_template
import os

class AppWrap:
    def __init__(self, host='0.0.0.0',port=8251,debug=False):
        self.debug=debug
        self.port=port
        self.host=host    
        scriptdir=os.path.dirname(os.path.abspath(__file__))
        self.app = Flask(__name__,template_folder=scriptdir+'/../templates')
        self.frontend=None
        
    def wrap(self,route):
        if self.frontend is None:
            raise Exception("frontend is not initialized")
        content,error=self.frontend.getContent(route);
        return render_template('index.html',content=content,error=error)
        
    def run(self):
        self.app.run(debug=self.debug,port=self.port,host=self.host)   
        pass

    def initFrontend(self,wikiId):
        frontend=Frontend(wikiId)
        frontend.open()
    
appWrap=AppWrap()
app=appWrap.app    
@app.route('/', defaults={'path': ''})
@app.route('/<path:route>')
def wrap(route):
    return appWrap.wrap(route)

if __name__ == '__main__':
    appWrap.run()

【讨论】:

    【解决方案5】:

    @Kostas Pelelis 答案的旁注/补充(抱歉目前无法发表评论):

    对于所有想知道如何集成端点路由的方法的人:查看 app.add_url_rule 的函数描述。

    如上所述,您可以使用“methods”参数来更改默认的“GET”方法。

    Kostas Pelelis 代码更改为“POST”类型的方法如下所示:

    (集成方法的示例 + 端点类返回您的操作函数返回的任何内容 [例如 html]

    from flask import Flask, Response, render_template
    class EndpointAction(object):
    
        def __init__(self, action):
            self.action = action
            self.response = Response(status=200, headers={})
    
        def __call__(self, *args):
            response = self.action()
            if response != None:
                return response
            else
                return self.response
    
    
    class FlaskAppWrapper(object):
        app = None
    
        def __init__(self, name):
            self.app = Flask(name)
    
        def run(self):
        self.app.run()
    
        def add_endpoint(self, endpoint=None, endpoint_name=None, handler=None, t_methods=None):
            self.app.add_url_rule(endpoint, endpoint_name, EndpointAction(handler), methods=t_methods)
    
    
    def action():
        # Execute anything
        print('i did something')
    
    def returning_action():
        # Returning for example an index hello world page
        return render_template('index.html')
    
    a = FlaskAppWrapper('wrap')
    a.add_endpoint(endpoint='/ad', endpoint_name='ad', handler=action, req_methods=['POST'])
    
    #just a little addition for handling of a returning actionhandler method 
    #-> i added another endpoint but for a returning method
    
    a.add_endpoint(endpoint='/', endpoint_name='index_page', handler=returning_action, req_methods=['GET']
    a.run()
    

    虽然 templates/index.html 可能看起来像这样(注意 render_templates 需要一个模板文件夹与您的 py 文件位于同一位置,其中包含指定的 html):

    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Index Page</title>
    </head>
    <body>
        <h1>Hello World!</h1>
    </body>
    </html>
    

    当索引路由 'ip-address-of-the-webapp/' 被访问(通过通常的浏览器访问 -> GET 请求)时,会调用此索引页面添加。

    *编辑:显示如果你的动作方法有参数(例如来自路由参数)的样子,这里是端点类和动作类的更新版本

    class EndpointAction(object):
    
        def __init__(self, action):
            self.action = action
            self.response = Response(status=200, headers={})
    
        def __call__(self, *args, **kwargs):
            response = self.action(**kwargs)
            if response != None:
                return response
            else
                return self.response
    
    def param_action(param):
        # Execute something (print param)
        print(f'i did {param}')
    
    [...]
    a.add_endpoint(endpoint='/<param>', endpoint_name='parametric_action', handler=param_action, req_methods=['GET']
    [...]
    

    【讨论】:

      猜你喜欢
      • 2019-05-13
      • 2015-03-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-03-19
      相关资源
      最近更新 更多