Skip to content

Commit b82613a

Browse files
author
Tomáš Trval
committed
fix: flask import of _endpoint_from_view_func is now resolved new util function import_check_view_func #567
1 parent 7ce0ef8 commit b82613a

File tree

4 files changed

+66
-7
lines changed

4 files changed

+66
-7
lines changed

CHANGELOG.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Bug Fixes
3535
::
3636

3737
* Fixing test as HTTP Header MIMEAccept expects quality-factor number in form of `X.X` (#547) [chipndell]
38-
38+
* Fixing flask 3.0+ compatibility of `flask.scaffold import _endpoint_from_view_func` Import error. (#565) [Ryu-CZ]
3939

4040
.. _enhancements-1.2.0:
4141

flask_restx/api.py

+5-6
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,6 @@
1414
from flask import url_for, request, current_app
1515
from flask import make_response as original_flask_make_response
1616

17-
try:
18-
from flask.helpers import _endpoint_from_view_func
19-
except ImportError:
20-
from flask.scaffold import _endpoint_from_view_func
2117
from flask.signals import got_request_exception
2218

2319
from jsonschema import RefResolver
@@ -45,10 +41,13 @@
4541
from .postman import PostmanCollectionV1
4642
from .resource import Resource
4743
from .swagger import Swagger
48-
from .utils import default_id, camel_to_dash, unpack
44+
from .utils import default_id, camel_to_dash, unpack, import_check_view_func
4945
from .representations import output_json
5046
from ._http import HTTPStatus
5147

48+
endpoint_from_view_func = import_check_view_func()
49+
50+
5251
RE_RULES = re.compile("(<.*>)")
5352

5453
# List headers that should never be handled by Flask-RESTX
@@ -850,7 +849,7 @@ def _blueprint_setup_add_url_rule_patch(
850849
rule = blueprint_setup.url_prefix + rule
851850
options.setdefault("subdomain", blueprint_setup.subdomain)
852851
if endpoint is None:
853-
endpoint = _endpoint_from_view_func(view_func)
852+
endpoint = endpoint_from_view_func(view_func)
854853
defaults = blueprint_setup.url_defaults
855854
if "defaults" in options:
856855
defaults = dict(defaults, **options.pop("defaults"))

flask_restx/utils.py

+46
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import re
2+
import warnings
3+
import typing
24

35
from collections import OrderedDict
46
from copy import deepcopy
@@ -20,6 +22,10 @@
2022
)
2123

2224

25+
class FlaskCompatibilityWarning(DeprecationWarning):
26+
pass
27+
28+
2329
def merge(first, second):
2430
"""
2531
Recursively merges two dictionaries.
@@ -118,3 +124,43 @@ def unpack(response, default_code=HTTPStatus.OK):
118124
return data, code or default_code, headers
119125
else:
120126
raise ValueError("Too many response values")
127+
128+
129+
def to_view_name(view_func: typing.Callable) -> str:
130+
"""Helper that returns the default endpoint for a given
131+
function. This always is the function name.
132+
133+
Note: copy of simple flask internal helper
134+
"""
135+
assert view_func is not None, "expected view func if endpoint is not provided."
136+
return view_func.__name__
137+
138+
139+
def import_check_view_func():
140+
"""
141+
Resolve import flask _endpoint_from_view_func.
142+
143+
Show warning if function cannot be found and provide copy of last known implementation.
144+
145+
Note: This helper method exists because reoccurring problem with flask function, but
146+
actual method body remaining the same in each flask version.
147+
"""
148+
import importlib.metadata
149+
150+
flask_version = importlib.metadata.version("flask").split(".")
151+
try:
152+
if flask_version[0] == "1":
153+
from flask.helpers import _endpoint_from_view_func
154+
elif flask_version[0] == "2":
155+
from flask.scaffold import _endpoint_from_view_func
156+
elif flask_version[0] == "3":
157+
from flask.sansio.scaffold import _endpoint_from_view_func
158+
else:
159+
warnings.simplefilter("once", FlaskCompatibilityWarning)
160+
_endpoint_from_view_func = None
161+
except ImportError:
162+
warnings.simplefilter("once", FlaskCompatibilityWarning)
163+
_endpoint_from_view_func = None
164+
if _endpoint_from_view_func is None:
165+
_endpoint_from_view_func = to_view_name
166+
return _endpoint_from_view_func

tests/test_utils.py

+14
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,17 @@ def test_value_headers_default_code(self):
9898
def test_too_many_values(self):
9999
with pytest.raises(ValueError):
100100
utils.unpack((None, None, None, None))
101+
102+
103+
class ToViewNameTest(object):
104+
def test_none(self):
105+
with pytest.raises(AssertionError):
106+
_ = utils.to_view_name(None)
107+
108+
def test_name(self):
109+
assert self.test_none == self.test_none.__name__
110+
111+
112+
class ImportCheckViewFuncTest(object):
113+
def test_callable(self):
114+
assert callable(utils.import_check_view_func())

0 commit comments

Comments
 (0)