Unexpected behavior when using blueprint groups and app.url_for
asyte opened this issue · comments
Is there an existing issue for this?
- I have searched the existing issues
Describe the bug
When using request.app.url_for
in combination with blueprint groups, the expected name cannot be used:
Traceback (most recent call last):
File "handle_request", line 97, in handle_request
File "###/bpgtest.py", line 64, in index
build_urls.append(f'{url}: {request.app.url_for(url)}')
^^^^^^^^^^^^^^^^^^^^^^^^
File "###/env/lib/python3.11/site-packages/sanic/app.py", line 753, in url_for
raise URLBuildError(
sanic.exceptions.URLBuildError: Endpoint with name `nested.child.index` was not found
Instead, underscores must be used, like so: nested_child.index
.
Deeper nesting of blueprints causes different issues, such as missing segments of the endpoint name (see the snippet for a good example of this)
Code snippet
import sanic
# An un-nested example
unnested_bp = sanic.Blueprint("unnested", url_prefix="/unnested")
@unnested_bp.route("/", methods=["GET"], name="index")
async def unnested_index(request):
return sanic.response.text("it is okay")
# A nested blueprint example
child_bp = sanic.Blueprint("child", url_prefix="/child")
@child_bp.route("/", methods=["GET"], name="index")
async def nested_child_index(request):
return sanic.response.text("all good")
nested_bpg = sanic.Blueprint.group(
child_bp,
url_prefix="/nested",
name_prefix="nested"
)
# A deeply nested blueprint example
deeply_nested_bp = sanic.Blueprint("deepest", url_prefix="/deepest")
@deeply_nested_bp.route("/", methods=["GET"], name="index")
async def deeply_nested_index(request):
return sanic.response.text("deeply satisfied")
deeper_bpg = sanic.Blueprint.group(
deeply_nested_bp,
url_prefix="/deeper",
name_prefix="deeper",
)
deep_bpg = sanic.Blueprint.group(
deeper_bpg,
url_prefix="/deep",
name_prefix="deep",
)
# Build the sanic app
app = sanic.Sanic("testapp")
app.blueprint(unnested_bp)
app.blueprint(nested_bpg)
app.blueprint(deep_bpg)
@app.route("/", methods=["GET"], name="index")
async def index(request):
# Try to build each url
urls = [
'unnested.index',
'nested.child.index',
'nested_child.index',
'deep.deeper.deepest.index',
'deep_deeper_deepest.index',
'deep_deepest.index',
]
build_urls = []
for url in urls:
try:
build_urls.append(f'{url}: {request.app.url_for(url)}')
except:
build_urls.append(f'-- {url} could not be built')
return sanic.response.text('\n'.join(build_urls))
if __name__ == "__main__":
app.run()
Expected Behavior
unnested.index: /unnested
-- nested.child.index could not be built
nested_child.index: /nested/child
-- deep.deeper.deepest.index could not be built
-- deep_deeper_deepest.index could not be built
deep_deepest.index: /deep/deeper/deepest ```
### How do you run Sanic?
Sanic CLI
### Operating System
Linux
### Sanic Version
23.6.0
### Additional context
_No response_
Route names are not infinitely nestable. In fact, there is a very specific naming convention for them that allows for at most three segments.
<app name>.[optional:<blueprint name>.]<handler name>
Blueprint routes are a somewhat unique case because they may be attached without knowledge of an app. Therefore, they can be fetched with an implied app name:
<blueprint name>.<handler name>