From 0f8a4d411454f7d9c4cdf3a8375b37ccb8f46733 Mon Sep 17 00:00:00 2001 From: Franklyn Tackitt Date: Fri, 10 Mar 2017 13:02:39 -0700 Subject: [PATCH 1/5] Defer app init --- flask_apispec/extension.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/flask_apispec/extension.py b/flask_apispec/extension.py index 9a35cca..be6dda9 100644 --- a/flask_apispec/extension.py +++ b/flask_apispec/extension.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- - +import functools import types import flask @@ -8,6 +8,7 @@ from flask_apispec import ResourceMeta from flask_apispec.apidoc import ViewConverter, ResourceConverter + class FlaskApiSpec(object): """Flask-apispec extension. @@ -36,6 +37,12 @@ def get_pet(pet_id): :param APISpec spec: apispec specification associated with API documentation """ def __init__(self, app=None): + self._deferred = [] + self.app = app + self.view_converter = None + self.resource_converter = None + self.spec = None + if app: self.init_app(app) @@ -46,6 +53,14 @@ def init_app(self, app): self.spec = self.app.config.get('APISPEC_SPEC') or make_apispec() self.add_routes() + @app.before_first_request + def call_deferred(): + for deferred in self._deferred: + deferred() + + def _defer(self, callable, *args, **kwargs): + self._deferred.append(functools.partial(callable, *args, **kwargs)) + def add_routes(self): blueprint = flask.Blueprint( 'flask-apispec', @@ -75,6 +90,21 @@ def register(self, target, endpoint=None, blueprint=None, resource_class_args=None, resource_class_kwargs=None): """Register a view. + :param target: view function or view class. + :param endpoint: (optional) endpoint name. + :param blueprint: (optional) blueprint name. + :param tuple resource_class_args: (optional) args to be forwarded to the + view class constructor. + :param dict resource_class_kwargs: (optional) kwargs to be forwarded to + the view class constructor. + """ + + self._defer(self._register, target, endpoint, blueprint, resource_class_args, resource_class_kwargs) + + def _register(self, target, endpoint=None, blueprint=None, + resource_class_args=None, resource_class_kwargs=None): + """Register a view. + :param target: view function or view class. :param endpoint: (optional) endpoint name. :param blueprint: (optional) blueprint name. From c0497bbea2ba0d91ee3cd39bdeae317f8cffc4ba Mon Sep 17 00:00:00 2001 From: Franklyn Tackitt Date: Fri, 10 Mar 2017 13:12:54 -0700 Subject: [PATCH 2/5] Configurable spec title and version --- flask_apispec/extension.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/flask_apispec/extension.py b/flask_apispec/extension.py index be6dda9..7756542 100644 --- a/flask_apispec/extension.py +++ b/flask_apispec/extension.py @@ -36,6 +36,7 @@ def get_pet(pet_id): :param Flask app: App associated with API documentation :param APISpec spec: apispec specification associated with API documentation """ + def __init__(self, app=None): self._deferred = [] self.app = app @@ -50,7 +51,9 @@ def init_app(self, app): self.app = app self.view_converter = ViewConverter(self.app) self.resource_converter = ResourceConverter(self.app) - self.spec = self.app.config.get('APISPEC_SPEC') or make_apispec() + self.spec = self.app.config.get('APISPEC_SPEC') or \ + make_apispec(self.app.config.get('APISPEC_TITLE', 'flask-apispec'), + self.app.config.get('APISPEC_VERSION', 'flask-apispec')) self.add_routes() @app.before_first_request @@ -128,9 +131,10 @@ def _register(self, target, endpoint=None, blueprint=None, for path in paths: self.spec.add_path(**path) -def make_apispec(): + +def make_apispec(title='flask-apispec', version='v1'): return APISpec( - title='flask-apispec', - version='v1', + title=title, + version=version, plugins=['apispec.ext.marshmallow'], ) From 4dd9219407b8c03ca0a9b1d6e71ed5b5ac908af5 Mon Sep 17 00:00:00 2001 From: Franklyn Tackitt Date: Thu, 16 Mar 2017 09:15:31 -0700 Subject: [PATCH 3/5] Immediate call deferred callbacks on init_app --- flask_apispec/extension.py | 17 +++++++++-------- tests/test_extension.py | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/flask_apispec/extension.py b/flask_apispec/extension.py index 7756542..33c9171 100644 --- a/flask_apispec/extension.py +++ b/flask_apispec/extension.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- +import flask import functools import types - -import flask from apispec import APISpec from flask_apispec import ResourceMeta @@ -56,13 +55,14 @@ def init_app(self, app): self.app.config.get('APISPEC_VERSION', 'flask-apispec')) self.add_routes() - @app.before_first_request - def call_deferred(): - for deferred in self._deferred: - deferred() + for deferred in self._deferred: + deferred() def _defer(self, callable, *args, **kwargs): - self._deferred.append(functools.partial(callable, *args, **kwargs)) + bound = functools.partial(callable, *args, **kwargs) + self._deferred.append(bound) + if self.app: + bound() def add_routes(self): blueprint = flask.Blueprint( @@ -102,7 +102,8 @@ def register(self, target, endpoint=None, blueprint=None, the view class constructor. """ - self._defer(self._register, target, endpoint, blueprint, resource_class_args, resource_class_kwargs) + self._defer(self._register, target, endpoint, blueprint, + resource_class_args, resource_class_kwargs) def _register(self, target, endpoint=None, blueprint=None, resource_class_args=None, resource_class_kwargs=None): diff --git a/tests/test_extension.py b/tests/test_extension.py index 876d9e2..b490b2e 100644 --- a/tests/test_extension.py +++ b/tests/test_extension.py @@ -1,16 +1,35 @@ # -*- coding: utf-8 -*- import pytest +from flask import Blueprint from flask_apispec import doc from flask_apispec.extension import FlaskApiSpec from flask_apispec.views import MethodResource + @pytest.fixture def docs(app): return FlaskApiSpec(app) class TestExtension: + def test_deferred_register(self, app): + blueprint = Blueprint('test', __name__) + docs = FlaskApiSpec() + + @doc(tags=['band']) + class BandResource(MethodResource): + def get(self, **kwargs): + return 'slowdive' + + blueprint.add_url_rule('/bands//', view_func=BandResource.as_view('band')) + docs.register(BandResource, endpoint='band', blueprint=blueprint.name) + + app.register_blueprint(blueprint) + docs.init_app(app) + + print(docs.spec.to_dict()) + assert '/bands/{band_id}/' in docs.spec._paths def test_register_function(self, app, docs): @app.route('/bands//') From 31771e2b0f1914217990412a7d9f0d7abce56231 Mon Sep 17 00:00:00 2001 From: Franklyn Tackitt Date: Thu, 16 Mar 2017 09:23:51 -0700 Subject: [PATCH 4/5] Fix version from config --- flask_apispec/extension.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask_apispec/extension.py b/flask_apispec/extension.py index 33c9171..f4b9329 100644 --- a/flask_apispec/extension.py +++ b/flask_apispec/extension.py @@ -52,7 +52,7 @@ def init_app(self, app): self.resource_converter = ResourceConverter(self.app) self.spec = self.app.config.get('APISPEC_SPEC') or \ make_apispec(self.app.config.get('APISPEC_TITLE', 'flask-apispec'), - self.app.config.get('APISPEC_VERSION', 'flask-apispec')) + self.app.config.get('APISPEC_VERSION', 'v1')) self.add_routes() for deferred in self._deferred: From c36b8ec5e713a1c85fc5ded3ec7e37a32697b64f Mon Sep 17 00:00:00 2001 From: Franklyn Tackitt Date: Fri, 31 Mar 2017 10:18:06 -0700 Subject: [PATCH 5/5] Add test for APISPEC_TITLE/VERSION config --- tests/test_extension.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/test_extension.py b/tests/test_extension.py index b490b2e..010cd10 100644 --- a/tests/test_extension.py +++ b/tests/test_extension.py @@ -28,7 +28,6 @@ def get(self, **kwargs): app.register_blueprint(blueprint) docs.init_app(app) - print(docs.spec.to_dict()) assert '/bands/{band_id}/' in docs.spec._paths def test_register_function(self, app, docs): @@ -81,3 +80,13 @@ def test_serve_swagger_ui_custom_url(self, app, client): app.config['APISPEC_SWAGGER_UI_URL'] = '/swagger-ui.html' FlaskApiSpec(app) client.get('/swagger-ui.html') + + def test_apispec_config(self, app): + app.config['APISPEC_TITLE'] = 'test-extension' + app.config['APISPEC_VERSION'] = '2.1' + docs = FlaskApiSpec(app) + + assert docs.spec.info == { + 'title': 'test-extension', + 'version': '2.1', + }