Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Looking for inverse of url_for in Flask

I am using Flask and Flask-RESTful to build a REST API. Within this API some of my resources contain url relations to other resources.

When performing POST requests to these resources I am finding that I am needing the inverse of Flask's url_for() function to parse the incoming url.

For example, a POST to https://www.example.com/buildings may contain the following json:

{
  "address": "123 Lyall St",
  ...
  "owner": {
      "href": "https://www.example.com/users/21414512"
  },
  "tenant": {
      "href": "https://www.example.com/users/16324642"
  },
}

I would like to parse the id out of owner and tenant using the following route:

"https://www.example.com/users/<int:id>"

Is there a convenient way to do this within Flask or Werkzueg or should I just parse the url myself? It would be nice to be able to re-use the already defined route...

I found this post but it does not seem to describe how to do it outside of a request.

like image 603
Dan Avatar asked Oct 02 '13 03:10

Dan


2 Answers

I use the route_from function below:

from flask.globals import _app_ctx_stack, _request_ctx_stack
from werkzeug.urls import url_parse

def route_from(url, method = None):
    appctx = _app_ctx_stack.top
    reqctx = _request_ctx_stack.top
    if appctx is None:
        raise RuntimeError('Attempted to match a URL without the '
                           'application context being pushed. This has to be '
                           'executed when application context is available.')

    if reqctx is not None:
        url_adapter = reqctx.url_adapter
    else:
        url_adapter = appctx.url_adapter
        if url_adapter is None:
            raise RuntimeError('Application was not able to create a URL '
                               'adapter for request independent URL matching. '
                               'You might be able to fix this by setting '
                               'the SERVER_NAME config variable.')
    parsed_url = url_parse(url)
    if parsed_url.netloc is not "" and parsed_url.netloc != url_adapter.server_name:
        raise NotFound()
    return url_adapter.match(parsed_url.path, method)

I wrote this by looking at the implementation of url_for and reversing it.

The url argument can be a complete URL or just the path info portion. The return value is a tuple with the endpoint name and a dict with the arguments.

Disclaimer: I haven't tested it extensively. I was planning to eventually submit it as a pull request, but never seem to get around to fully test it and write some unit tests. If it does not work for you, let me know!

like image 82
Miguel Avatar answered Nov 03 '22 00:11

Miguel


The simplest way create test request context (thanks Leon Young):

with app.test_request_context(YOUR_URL) as request_ctx:
    url_rule = request_ctx.request.url_rule

But all sense under the hood of creating request context:

from flask.testing import make_test_environ_builder

builder = make_test_environ_builder(app, YOUR_URL)
environ = builder.get_environ()
url_adapter = app.url_map.bind_to_environ(environ)
url_rule, view_args = url_adapter.match(return_rule=True)

If no reason check protocol and host you can create special match method:

from functools import partial

url_adapter = app.url_map.bind('localhost')
match = partial(url_adapter.match, return_rule=True)

And use it without protocol and host:

owner_url_rule, owner_view_args = match('/users/21414512')
tenant_url_rule, tenant_view_args = match('/users/16324642')
like image 10
tbicr Avatar answered Nov 02 '22 23:11

tbicr