aiohttp icon indicating copy to clipboard operation
aiohttp copied to clipboard

access subapp routes via <app-name>:<route-name>

Open samuelcolvin opened this issue 7 years ago • 12 comments

From aio-libs/aiohttp-jinja2#163

I think sub app routes should be available via subapp:view-name, which would require a route_name argument to add_subapp.

This would roughly mirror django routing and would make stuff like the url() jinja function work with subapps.

It would also make all url_for calls refering to subapps more succinct.

I think @asvetlov has some thoughts on implementation.

samuelcolvin avatar Oct 11 '17 12:10 samuelcolvin

Hmm. I not sure if subapp:view-name syntax should be a part of aiohttp or just aiohttp_jinja2. Opinions?

asvetlov avatar Oct 11 '17 12:10 asvetlov

For me this should definitely be part of aiohttp. There are numerous situation other than templates where it can be useful to declare routes via a simple string.

Just to give a few random examples:

  • an API framework might have a URL(route_name) field which converts a pk int to the url for that item
  • a menu might be declared via a list of tuples [('account:details', 'My Account'), ('main:dashbaord', 'Dashboard')]

and lots of others, these don't work if there's no simple immutable reference for a route.

samuelcolvin avatar Oct 11 '17 12:10 samuelcolvin

I also think this could be a part of subapp() implementation. Because if the context is not known, aiohttp_jinja2.setup() will be required to be setup for all the sub apps, like this:

    app.add_subapp(r'/api/v1/todo', todos_app)
    app['todos_app'] = todos_app

    aiohttp_jinja2.setup(todos_app,
                         loader=jinja2.PackageLoader('app', 'templates'))

    app.router.add_static('/static/',
                          path=str(PROJECT_ROOT / 'static'),
                          name='static')```

ansrivas avatar Oct 11 '17 13:10 ansrivas

Unfortunately url dispatcher doesn't know about applications stack. If you want to have such functionality in aiohttp you need adding request.url_for() method.

asvetlov avatar Oct 11 '17 13:10 asvetlov

Quick query on this topic, is there a time line on when having namespaced subapps for the url function might be integrated? Just saw that development has been stopped as the maintainers have been busy for the last while.

I've started using aiohttp as I've moved from django as this tech seems really interesting.

Paladiamors avatar Jul 27 '19 01:07 Paladiamors

The issue is not in my personal todo-list (yet)

asvetlov avatar Sep 14 '19 23:09 asvetlov

Understood, thanks for letting me know!

Paladiamors avatar Sep 15 '19 16:09 Paladiamors

Hi Please tell me the status of this thread. I have a similar problem generating urls for sub-applications, so I had to create my own processing as "url_subapp".

Has aiohttp added the ability to generate urls for sub-applications in the standard "url_for"?

orumaxon avatar May 30 '23 19:05 orumaxon

I suggest doing this

For render templatetag (modification of aiohttp_jinja2.helpers.url_for):

@jinja2.pass_context
def url_for(context, __route_name: str, **parts: Any) -> URL:
    *sub_apps, route_name = __route_name.split(':')

    app = context['app']
    for sub_app in sub_apps:
        app = app[sub_app]

    app = cast(web.Application, app)
    ...
    url = app.router[route_name].url_for(**parts)
    ...
    return url

Functions for register sub apps and jinja2:

def register_sub_apps(app, sub_apps):
    for path, sub_app, name in sub_apps:
        app.add_subapp(path, sub_app)
        app[name] = sub_app
        sub_app['parent'] = app    # for reverse treatment
 
 
def register_jinja2(app, config):
    env = aiohttp_jinja2.setup(...)
    app['static_root_url'] = config.STATIC_ROOT
    env.globals['url'] = url_for

List sub apps (routes.py):

sub_apps = [
    ('/sub_app/', sub_app_views, 'sub_app'),
    ...
]

In init app:

register_jinja2(app)
register_sub_apps(app, sub_apps)

orumaxon avatar May 30 '23 21:05 orumaxon

app = app[sub_app]

This would require users to always assign a returned subapp to the app object. As it's not something done by aiohttp, I don't think that's an acceptable solution to include in aiohttp.

I think the proposal was to add another argument to add_subapp() (e.g. name, like when adding routes), and it could then appear under this name. Looking at what's already in place, I think you could add a name to the PrefixedSubAppResource, which would make it available on the router, at which point you could do app.router["subapp_name"]["resource_name"].url_for().

Then we'd need to update aiohttp-jinja2 to use the syntax "subapp_name:resource_name" to perform that lookup.

Atleast, at first look, that seems like a reasonable solution to me, if anyone wants to work on it and give it a go.

Dreamsorcerer avatar May 31 '23 16:05 Dreamsorcerer

On second look, the current syntax needed for a nested lookup (if the name were added) would be more like app.router["subapp_name"]._app.router["resource_name"].url_for(). So, probably need some additional changes to PrefixedSubAppResource (maybe just defining __getitem__() to lookup in self._app.router).

Dreamsorcerer avatar May 31 '23 16:05 Dreamsorcerer

ur_for:

@jinja2.pass_context
def url_for(context: dict[str, Any], __route_name: str, query_: dict[str, str] | None = None, **parts: str | int):
    app = context["app"]
    parts_clean: dict[str, str] = {}
    for key in parts:
        val = parts[key]
        if isinstance(val, str):
            # if type is inherited from str expilict cast to str makes sense
            # if type is exactly str the operation is very fast
            val = str(val)
        elif type(val) is int:
            # int inherited classes like bool are forbidden
            val = str(val)
        else:
            raise TypeError(
                "argument value should be str or int, "
                "got {} -> [{}] {!r}".format(key, type(val), val)
            )
        parts_clean[key] = val
    route_parts = __route_name.split(':', 1)
    if route_parts[0] != __route_name:
        router = app[route_parts[0]].router
    else:
        router = app.router
    url = router[route_parts[-1]].url_for(**parts_clean)
    if query_:
        url = url.with_query(query_)
    return url

main.py:

app.add_subapp("/admin", admin_app)
admin_app["main_app"] = app

some_template.jinja2:

<a href="{{ url_for('main_app:view_album', album_slug=album.slug, album_id=album.id) }}">View album</a>

azg1966 avatar Oct 31 '23 20:10 azg1966