问题
I'm using flask with the app factory pattern. I do know that the app factory pattern manages configuration objects only in the factory function. (as in the following code)
def create_app(config):
app.config.from_object(config)
sentry.init(app)
...
return app
But how do I manage the extra module that needs that configuration, but couldn't be initialized in app creating time?
So I want to do something like
def create_app(config):
some_module_obj = Module(host=config.host, port=config.port)
app.config.from_object(config)
sentry.init(app)
return some_module_obj, app
Rather than
# I don't want to use `config` outside of the `create_app` function!
some_module_obj = Module(host=config.host, port=config.port)
def create_app(config):
app.config.from_object(config)
sentry.init(app)
return app
回答1:
Not sure that is what you need, but you asked for a small example with inject + Flask
in comments. As I understood the main problem is related with Flask + configuration + initialization
. This is just an example how it works.
app.py:
from flask import Flask
from api import bp
from configurator import configure
def create_app():
app = Flask(__name__)
# configure Flask app config as you wish... (app.config.from_object(config))
# just some settings for demonstration
app.config.update(dict(
MODULE1_TIMER=1,
MODULE2_LIMIT=2,
))
# configure inject using app context and Flask config
with app.app_context():
configure()
# demo blueprint
app.register_blueprint(bp)
return app
if __name__ == '__main__':
create_app().run(debug=True)
Let's imagine that we have some modules:
# mod1.py
class Module1:
def __init__(self, timer: int) -> None:
self._timer = timer
# mod2.py
class Module2:
def __init__(self, limit: int) -> None:
self._limit = limit
def get_limit(self):
return self._limit
# mod3.py - works with mod1 and mod2
class Module3:
def __init__(self, module1, module2) -> None:
self._module1 = module1
self._module2 = module2
def get_limit(self):
return self._module2.get_limit()
configurator.py:
import inject
from flask import current_app
from mod1 import Module1
from mod2 import Module2
from mod3 import Module3
@inject.params(
module1=Module1,
module2=Module2,
)
def _init_module3(module1, module2):
# module1 and module2 are injected instances
return Module3(module1, module2)
def _injector_config(binder):
# initialization of Module1 and Module2 using Flask config
binder.bind(Module1, Module1(current_app.config['MODULE1_TIMER']))
binder.bind(Module2, Module2(current_app.config['MODULE2_LIMIT']))
# initialization of Module3 using injected Module1 + Module2
# you can use bind_to_constructor + any function
binder.bind_to_constructor(Module3, _init_module3)
def configure():
def config(binder):
binder.install(_injector_config)
# one more binder.install... etc...
inject.clear_and_configure(config)
api.py:
import inject
from flask import Blueprint, jsonify
from mod1 import Module1
from mod2 import Module2
from mod3 import Module3
bp = Blueprint('api', __name__)
@bp.route('/test')
def test():
# get instances which was created using inject
return jsonify(dict(
module1=str(type(inject.instance(Module1))),
module2=str(type(inject.instance(Module2))),
module3=str(type(inject.instance(Module3))),
))
# you can inject something as arg
@bp.route('/test2')
@inject.params(module3=Module3)
def test2(module3: Module3):
return jsonify(dict(module3=str(type(module3))))
@bp.route('/test3')
def test3():
# you can inject something into anything
class Example:
module3 = inject.attr(Module3)
@inject.params(module2=Module2)
def __init__(self, module2: Module2) -> None:
self.module2 = module2
return jsonify({
'MODULE2_LIMIT': Example.module3.get_limit(),
'example': dir(Example()),
})
Run server, open /test
, test2
, /test3
.
A few words about benefits:
- One point for initialization and configuration
- Lower dependency on current_app, flask config / context etc.
- Less problems with recursive imports
- Easy to writing tests
Hope this helps.
回答2:
Decided to make custom class for initialize object as factory pattern.
This is example:
class CustomFactory(metaclass=ABCMeta):
@abstractmethod
def init_factory(self, config):
pass
@property
@abstractmethod
def app(self):
pass
def __getattr__(self, item):
return getattr(self.app, item)
class RQSchedulerFactory(CustomFactory):
def __init__(self):
self._app = None
def init_factory(self, config):
self._app = Scheduler(connection=Redis(host=config.REDIS_HOST, port=config.REDIS_PORT))
@property
def app(self):
return self._app
class FireDBFactory(CustomFactory):
@property
def app(self):
return self._app
def __init__(self):
self._app = None
def init_factory(self, config):
cred = credentials.Certificate(config.FIREBASE_KEY_FILE)
firebase_admin.initialize_app(cred)
self._app = firestore.client()
And in __init__.py
(which has create_app
function)
scheduler = RQSchedulerFactory()
fire_db = FireDBFactory()
And in create_app
function, initialize as below:
def create_app(config):
app.config.from_object(config)
# Scheduler initialization
scheduler.init_factory(config)
# Fire store initialization
fire_db.init_factory(config)
来源:https://stackoverflow.com/questions/52587523/how-can-i-manage-extra-modules-in-app-factory-pattern