aio-libs / aiohttp

Asynchronous HTTP client/server framework for asyncio and Python
https://docs.aiohttp.org
Other
14.81k stars 1.98k forks source link

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

Open samuelcolvin opened 6 years ago

samuelcolvin commented 6 years ago

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.

asvetlov commented 6 years ago

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

samuelcolvin commented 6 years ago

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:

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

ansrivas commented 6 years ago

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')```
asvetlov commented 6 years ago

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.

Paladiamors commented 4 years ago

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.

asvetlov commented 4 years ago

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

Paladiamors commented 4 years ago

Understood, thanks for letting me know!

orumaxon commented 1 year ago

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 commented 1 year ago

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)
Dreamsorcerer commented 1 year ago
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 commented 1 year ago

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).

azg1966 commented 8 months ago

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>