From 77ff2596511efe62c9b0536107445cfe9d6846c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Trval?= Date: Tue, 3 Oct 2023 10:34:26 +0200 Subject: [PATCH] fix: flask import of _endpoint_from_view_func is now resolved new util function import_check_view_func #567 --- flask_restx/api.py | 11 +++++------ flask_restx/utils.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/flask_restx/api.py b/flask_restx/api.py index 5996dd59..bd0413dd 100644 --- a/flask_restx/api.py +++ b/flask_restx/api.py @@ -14,10 +14,6 @@ from flask import url_for, request, current_app from flask import make_response as original_flask_make_response -try: - from flask.helpers import _endpoint_from_view_func -except ImportError: - from flask.scaffold import _endpoint_from_view_func from flask.signals import got_request_exception from jsonschema import RefResolver @@ -45,10 +41,13 @@ from .postman import PostmanCollectionV1 from .resource import Resource from .swagger import Swagger -from .utils import default_id, camel_to_dash, unpack +from .utils import default_id, camel_to_dash, unpack, import_check_view_func from .representations import output_json from ._http import HTTPStatus +endpoint_from_view_func = import_check_view_func() + + RE_RULES = re.compile("(<.*>)") # List headers that should never be handled by Flask-RESTX @@ -850,7 +849,7 @@ def _blueprint_setup_add_url_rule_patch( rule = blueprint_setup.url_prefix + rule options.setdefault("subdomain", blueprint_setup.subdomain) if endpoint is None: - endpoint = _endpoint_from_view_func(view_func) + endpoint = endpoint_from_view_func(view_func) defaults = blueprint_setup.url_defaults if "defaults" in options: defaults = dict(defaults, **options.pop("defaults")) diff --git a/flask_restx/utils.py b/flask_restx/utils.py index 809a29b3..16aa940a 100644 --- a/flask_restx/utils.py +++ b/flask_restx/utils.py @@ -1,4 +1,6 @@ import re +import warnings +import typing from collections import OrderedDict from copy import deepcopy @@ -20,6 +22,9 @@ ) +class FlaskCompatibilityWarning(DeprecationWarning): + pass + def merge(first, second): """ Recursively merges two dictionaries. @@ -118,3 +123,40 @@ def unpack(response, default_code=HTTPStatus.OK): return data, code or default_code, headers else: raise ValueError("Too many response values") + + +def import_check_view_func(): + """ + Resolve import flask _endpoint_from_view_func. + + Show warning if function cannot be found and provide copy of last known implementation. + + Note: This helper method exists because reoccurring problem with flask function, but + actual method body remaining the same in each flask version. + """ + import importlib.metadata + flask_version = importlib.metadata.version("flask").split(".") + + def local_endpoint_from_view_func(view_func: typing.Callable) -> str: + """Copy of flask internal helper that returns the default endpoint for a given + function. This always is the function name. + """ + assert view_func is not None, "expected view func if endpoint is not provided." + return view_func.__name__ + try: + if flask_version[0] == "1": + from flask.helpers import _endpoint_from_view_func + elif flask_version[0] == "2": + from flask.scaffold import _endpoint_from_view_func + elif flask_version[0] == "3": + from flask.sansio.scaffold import _endpoint_from_view_func + else: + warnings.simplefilter("once", FlaskCompatibilityWarning) + _endpoint_from_view_func = None + except ImportError: + warnings.simplefilter("once", FlaskCompatibilityWarning) + _endpoint_from_view_func = None + if _endpoint_from_view_func is None: + _endpoint_from_view_func = local_endpoint_from_view_func + return _endpoint_from_view_func +