如何避免将应用程序工厂导入需要应用程序上下文的模块中 [英] How to avoid importing application factory into module that needs application context

查看:89
本文介绍了如何避免将应用程序工厂导入需要应用程序上下文的模块中的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这个问题是我上一个问题的扩展: https://stackoverflow.com/users/12373033/experimental.建议我多解释一下这个问题.如标题所述,我正在尝试寻找一种避免将应用程序工厂(create_app函数)导入需要应用程序上下文并将current_app作为应用程序导入"的模块的方法.还不够.

This question is an extension on my previous one here: https://stackoverflow.com/users/12373033/experimental . I was suggested to put more to explain the problem. As the heading says, I am trying to find a way to avoid importing the application factory (create_app function) into a module that needs application context and were "import current_app as app" is not sufficient.

我的问题是由于这个create_app函数,我需要通过循环导入才能获取app_context.

My problem is I have a circular import problem due to this create_app function which i need to pass in order to get the app_context.

在我的__ini__.py中,我有这个:

# application/__init__.py

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_restful import Api
from application.resources.product import Product, Products
from application.resources.offer import Offer, Offers  # HERE IS THE PROBLEM

api = Api()
db = SQLAlchemy()

api.add_resource(Product, "/product/<string:name>")  # GET, POST, DELETE, PUT to my local database
api.add_resource(Products, "/products")  # GET all products from my local database
api.add_resource(Offer, "/offer/<int:id>")  # POST call to the external Offers API microservise
api.add_resource(Offers, "/offers")  # GET all offers from my local database


def create_app(config_filename=None):
    """ Initialize core application. """
    app = Flask(__name__, instance_relative_config=False)
    app.config.from_object("config.Config")

    db.init_app(app)
    api.init_app(app)

    with app.app_context():
        db.create_all()

        return app

问题是这条线

from application.resources.offer import Offer, Offers  # HERE IS THE PROBLEM

因为在那个模块中我有:

because in that module I have:

#application/resources/offer.py 

from flask_restful import Resource
from application.models.offer import OfferModel  # IMPORTING OFFER MODEL

依次导入application/models/offer.py,其中我有关键部分:

which in turns import application/models/offer.py where I have the critical part:

#application/models/offer.py

import requests
# from flask import current_app as app 
from application import create_app  # THIS CAUSES THE CIRCULAR IMPORT ERROR
from sqlalchemy.exc import OperationalError

app = create_app() # I NEED TO CREATE THE APP IN ORDER TO GET THE APP CONTEXT BECASE IN THE CLASS I HAVE SOME FUNCTIONS THAT NEED IT

class OfferModel(db.Model):
    """ Data model for offers. """
    # some code to instantiate the class... + other methods..

    # THIS IS ONE OF THE METHODS THAT NEED APP_CONTEXT OR ELSE IT WILL ERROR OUT
    @classmethod
    def update_offer_price(cls):
        """ Call offers api to get new prices. This function will run in a separated thread in a scheduler. """
        with app.app_context():
            headers = {"Bearer": app.config["MS_API_ACCESS_TOKEN"]}
            for offer_id in OfferModel.offer_ids:
                offers_url = app.config["MS_API_OFFERS_BASE_URL"] + "/products/" + str(offer_id) + "/offers"
                res = requests.get(offers_url, headers=headers).json()
                for offer in res:
                    try:
                        OfferModel.query.filter_by(offer_id=offer["id"]).update(dict(price=offer["price"]))
                        db.session.commit()
                    except OperationalError:
                        print("Database does not exists.")
                        db.session.rollback()

我尝试使用从烧瓶导入current_app作为应用"为了获得上下文,它没有用.我不知道为什么不足以将current_app作为应用程序传递并获取上下文.因为它现在迫使我通过create_app应用程序工厂,这会导致循环导入问题.

I have tried to use "from flask import current_app as app" to get the context, it did not work. I don't know why it was not sufficient to pass current_app as app and get the context. Because it now forces me to pass the create_app application factory which causes the circular import problem.

如果有人可以帮助我理解并解决该问题,我将非常感激.

If anyone could help me to understand the issue and solve it, I would be really grateful.

推荐答案

您的update_offer_price方法需要数据库交互和对配置的访问.它从应用程序上下文中获取它们,但仅在Flask应用程序初始化时才有效.此方法在单独的线程中运行,因此您可以在该线程中创建Flask应用程序的 second 实例.

Your update_offer_price method needs database interaction and an access to the configuration. It gets them from the application context but it works only if your Flask application is initialized. This method is run in a separate thread so you create the second instance of Flask application in this thread.

另一种方法是在应用程序上下文之外获得独立的数据库交互和配置访问权限.

Alternative way is getting standalone database interaction and configuration access outside the application context.

配置似乎没有问题,因为您的应用程序是从另一个模块获取它的:

Configuration does not seem a problem as your application gets it from another module:

app.config.from_object("config.Config")

因此您可以直接将此对象导入到offer.py:

So you can directly import this object to your offer.py:

from config import Config

headers = {"Bearer": Config.MS_API_ACCESS_TOKEN}

数据库访问

要获得独立的数据库访问权限,您需要通过SQLAlchemy而不是flask_sqlalchemy定义模型.在答案中已经对此进行了描述,但是我在此处发布了要点.对于您的情况,它可能看起来像这样.您的base.py模块:

Database access

To get standalone database access you need to define your models via SQLAlchemy instead of flask_sqlalchemy. It was already described in this answer but I post here the essentials. For your case it may look like this. Your base.py module:

from sqlalchemy import MetaData
from sqlalchemy.ext.declarative import declarative_base

metadata = MetaData()
Base = declarative_base(metadata=metadata)

offer.py模块:

import sqlalchemy as sa

from .base import Base

class OfferModel(Base):
    id = sa.Column(sa.Integer, primary_key=True)
    # Another declarations

产生的metadata对象用于初始化flask_sqlalchemy对象:

The produced metadata object is used to initialize your flask_sqlalchemy object:

from flask_sqlalchemy import SQLAlchemy

from application.models.base import metadata

db = SQLAlchemy(metadata=metadata)

可以在应用程序上下文之外查询您的模型,但是您需要手动创建数据库引擎和会话.例如:

Your models can be queried outside the application context but you need to manually create database engine and sessions. For example:

from sqlalchemy import create_engine
from sqlalchemy.orm import Session

from config import Config

from application.models.offer import Offer

engine = create_engine(Config.YOUR_DATABASE_URL)
# It is recommended to create a single engine
# and use it afterwards to bind database sessions to.
# Perhaps `application.models.base` module
# is better to be used for this declaration.

def your_database_interaction():
    session = Session(engine)
    offers = session.query(Offer).all()
    for offer in offers:
        # Some update here
    session.commit()
    session.close()

请注意,通过这种方法,您不能使用模型类进行查询,我的意思是:

Note that with this approach you can't use your models classes for queriing, I mean:

OfferModel.query.all()  # Does not work
db.session.query(OfferModel).all()  # Works

这篇关于如何避免将应用程序工厂导入需要应用程序上下文的模块中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆