diff --git a/doc/scaling.rst b/doc/scaling.rst index 7b390ba5..fa757c36 100644 --- a/doc/scaling.rst +++ b/doc/scaling.rst @@ -152,8 +152,7 @@ Use With Blueprints ------------------- See :doc:`flask:blueprints` in the Flask documentation for what blueprints are and why you should use them. -Here's an example of how to link an :class:`Api` up to a :class:`~flask.Blueprint`. Nested Blueprints are -not supported. +Here's an example of how to link an :class:`Api` up to a :class:`~flask.Blueprint`. .. code-block:: python diff --git a/flask_restx/api.py b/flask_restx/api.py index 5996dd59..c28ebcdf 100644 --- a/flask_restx/api.py +++ b/flask_restx/api.py @@ -183,6 +183,7 @@ def __init__( self.resources = [] self.app = None self.blueprint = None + self._blueprint_name = None # must come after self.app initialisation to prevent __getattr__ recursion # in self._configure_namespace_logger self.default_namespace = self.namespace( @@ -525,10 +526,29 @@ def namespace(self, *args, **kwargs): def endpoint(self, name): if self.blueprint: - return "{0}.{1}".format(self.blueprint.name, name) + if self._blueprint_name is None: + self._blueprint_name = self._get_blueprint_name() + return "{0}.{1}".format(self._blueprint_name, name) else: return name + def _get_blueprint_name(self): + """ + Get full blueprint name from the current_app.blueprints dict, + which contains full names for nested blueprints. + + :rtype: str + """ + return next( + ( + name + for name, bp in current_app.blueprints.items() + if bp == self.blueprint + ), + # Fallback option when blueprint is not yet registered to the app. + self.blueprint.name, + ) + @property def specs_url(self): """ diff --git a/tests/test_api.py b/tests/test_api.py index 2b40b235..9f8ae637 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -43,6 +43,30 @@ def test_root_endpoint_with_blueprint_with_subdomain(self, app): assert url == "http://api.localhost/api/" assert api.base_url == "http://api.localhost/api/" + def test_root_endpoint_with_nested_blueprint(self, app): + blueprint = Blueprint("api", __name__, url_prefix="/api") + blueprint2 = Blueprint("v1", __name__, url_prefix="/v1") + blueprint.register_blueprint(blueprint2) + api = restx.Api(blueprint2, version="1.0") + app.register_blueprint(blueprint) + + with app.test_request_context(): + url = url_for("api.v1.root") + assert url == "/api/v1/" + assert api.base_url == "http://localhost/api/v1/" + + def test_root_endpoint_with_nested_blueprint_with_subdomain(self, app): + blueprint = Blueprint("api", __name__, subdomain="api", url_prefix="/api") + blueprint2 = Blueprint("v1", __name__, url_prefix="/v1") + blueprint.register_blueprint(blueprint2) + api = restx.Api(blueprint2, version="1.0") + app.register_blueprint(blueprint) + + with app.test_request_context(): + url = url_for("api.v1.root") + assert url == "http://api.localhost/api/v1/" + assert api.base_url == "http://api.localhost/api/v1/" + def test_parser(self): api = restx.Api() assert isinstance(api.parser(), restx.reqparse.RequestParser)