sanic-org / sanic

Accelerate your web app development | Build fast. Run fast.

Home Page:https://sanic.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Unexpected behavior when using blueprint groups and app.url_for

asyte opened this issue · comments

commented

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>