flask route variable alters uriencoded string - python

I am passing following url from my android app
http://server.com/core/put/18.00283670425415/59.353229999542236/%5BB%40463336a0/
the last parameter is a URI encoded string.
In Flask my route looks like
#server.route('/put/<long>/<lat>/<tagline>/')
def put(long, lat, tagline):
return tagline
I get [B#463336a0 as return and my url changes to
http://server.com/core/put/18.00283670425415/59.353229999542236/[B%40463336a0/
Whats happening here? this is driving me crazy.

What is happening here is known as percent-encoding. The %5B is the percent-encoding for [, and the %40 is the percent-encoding for #.
You need to make sure that your Android app sends an escaped URI. In this particular case it would look something like this (simplified example for clarity):
>>> import urllib
>>> unescaped_url = '%5BB%40463336a0'
>>> escaped_url = urllib.quote(unescaped_url)
'%255BB%2540463336a0'
>>> unescaped_url == urllib.unquote(escaped_url)
True

Related

Which characters does TurboGears replace in the URL?

I have a simple TurboGears 2 script, named app.py:
#!/usr/bin/env python3
from wsgiref.simple_server import make_server
from tg import expose, TGController, AppConfig
class RootController(TGController):
#expose()
def all__things(self):
return "Hello world!"
config = AppConfig(minimal=True, root_controller=RootController())
print("Serving on port 5000...")
httpd = make_server('', 5000, config.make_wsgi_app())
httpd.serve_forever()
When I run app.py and visit http://localhost:5000/all__things, I see "Hello world!" as expected. But these URLs also work:
http://localhost:5000/all--things
http://localhost:5000/all##things
http://localhost:5000/all$$things
http://localhost:5000/all++things
http://localhost:5000/all..things
http://localhost:5000/all,,things
As well as combinations:
http://localhost:5000/all-_things
http://localhost:5000/all_-things
http://localhost:5000/all-#things
http://localhost:5000/all#-things
http://localhost:5000/all$#things
http://localhost:5000/all#$things
Et cetera...
What is the complete list of characters that can be substituted for an underscore in TurboGears URLs?
Also, can this feature be restricted to only substitute certain characters? Ideally, I want URLs with dashes (http://localhost:5000/all--things) to work, and URLs with underscores (http://localhost:5000/all__things) or any other strange characters to not work.
That's managed by the path_translator which can be configured through the dispatch_path_translator option in app_cfg.py. It can be disabled by passing None or providing a custom function.
Any function provided will receive the part of the path currently being processed and must return it normalised.
The default path translator is based on string.punctuation (see https://github.com/python/cpython/blob/c30098c8c6014f3340a369a31df9c74bdbacc269/Lib/string.py#L31 )
In case you have custom routing needs I suggest you consider https://github.com/TurboGears/tgext.routes which might help you in more complex cases through the #route decorator.

Match an arbitrary path, or the empty string, without adding multiple Flask route decorators

I want to capture all urls beginning with the prefix /stuff, so that the following examples match: /users, /users/, and /users/604511/edit. Currently I write multiple rules to match everything. Is there a way to write one rule to match what I want?
#blueprint.route('/users')
#blueprint.route('/users/')
#blueprint.route('/users/<path:path>')
def users(path=None):
return str(path)
It's reasonable to assign multiple rules to the same endpoint. That's the most straightforward solution.
If you want one rule, you can write a custom converter to capture either the empty string or arbitrary data beginning with a slash.
from flask import Flask
from werkzeug.routing import BaseConverter
class WildcardConverter(BaseConverter):
regex = r'(|/.*?)'
weight = 200
app = Flask(__name__)
app.url_map.converters['wildcard'] = WildcardConverter
#app.route('/users<wildcard:path>')
def users(path):
return path
c = app.test_client()
print(c.get('/users').data) # b''
print(c.get('/users-no-prefix').data) # (404 NOT FOUND)
print(c.get('/users/').data) # b'/'
print(c.get('/users/400617/edit').data) # b'/400617/edit'
If you actually want to match anything prefixed with /users, for example /users-no-slash/test, change the rule to be more permissive: regex = r'.*?'.

Pyramid TranslationString not working on json renderer

In a test I am doing in a pyramid application, I am trying to send a translatable text via JSON, but the translation is not working. At the beginning of the file I am importing the translation string function:
from pyramid.i18n import TranslationString as _
Then consider the following code:
#view_config(route_name='transtest', renderer='json')
def transtest_view(request):
return { 'myvar': _('temp-test', default='Temporary test', domain='myapp') }
But what I get is:
{"myvar": "temp-test"}
Note that if I change the renderer to a test template I did as follows:
#view_config(route_name='transtest', renderer='../templates/transtest.pt')
...
then the text gets translated correctly (note that I already initialized the catalogs, updated them, compiled them, etc.)
This made me think that the TranslationString class does not work right in a 'json' renderer? If so, how can I make to send a translatable string via JSON?
Thanks in advance
You need to explicitly translate your message string, using get_localizer() and Localizer.translate():
from pyramid.i18n import get_localizer
#view_config(route_name='transtest', renderer='json')
def transtest_view(request):
message = _('temp-test', default='Temporary test', domain='myapp')
return {'myvar': get_localizer(request).translate(message)}
Normally, templates take care of these steps for you, but for JSON you'll need to do so yourself.
You probably want to define a TranslationStringFactory for your project, and reuse that to produce your message strings. Add the following to your project:
from pyramid.i18n import TranslationStringFactory
myapp_domain = TranslationStringFactory(domain='myapp')
then use:
from my.project import myapp_domain as _
# ....
message = _('temp-test', default='Temporary test')

Match referer by regex

I'd like to setup a simple notification if a view has a specific base referer.
Let's say I land on http://myapp.com/page/ and I came from http://myapp.com/other/page/1. Here's an example of my pseudo code, basically if I'm coming from any page/X I want to setup a notification.
I'm thinking it might be something like ^r^myapp.com/other/page/$ but I'm not so familiar with how to use regex with python.
from django.http import HttpRequest
def someview(request):
notify = False
... # other stuff not important to question
req = HttpRequest()
test = req.META['HTTP_REFERER'] like "http://myapp.com/other/page*"
# where * denotes matching anything past that point and the test returns T/F
if test:
notify = True
return # doesn't matter here
This may be more of a "how do I use regex in this context" rather than a django question specifically.
You could go with something like this:
import re
referrer = "http://myapp.com/other/page/aaa"
m = re.match("^http://myapp.com/other/page/(.*)", referrer)
if m:
print m.group(1)

Get list of all routes defined in the Flask app

I have a complex Flask-based web app. There are lots of separate files with view functions. Their URLs are defined with the #app.route('/...') decorator. Is there a way to get a list of all the routes that have been declared throughout my app? Perhaps there is some method I can call on the app object?
All the routes for an application are stored on app.url_map which is an instance of werkzeug.routing.Map. You can iterate over the Rule instances by using the iter_rules method:
from flask import Flask, url_for
app = Flask(__name__)
def has_no_empty_params(rule):
defaults = rule.defaults if rule.defaults is not None else ()
arguments = rule.arguments if rule.arguments is not None else ()
return len(defaults) >= len(arguments)
#app.route("/site-map")
def site_map():
links = []
for rule in app.url_map.iter_rules():
# Filter out rules we can't navigate to in a browser
# and rules that require parameters
if "GET" in rule.methods and has_no_empty_params(rule):
url = url_for(rule.endpoint, **(rule.defaults or {}))
links.append((url, rule.endpoint))
# links is now a list of url, endpoint tuples
See Display links to new webpages created for a bit more information.
I just met the same question. Those solutions above are too complex.
Just open a new shell under your project:
>>> from app import app
>>> app.url_map
The first 'app' is my project script: app.py,
another is my web's name.
(this solution is for the tiny web with a little route)
I make a helper method on my manage.py:
#manager.command
def list_routes():
import urllib
output = []
for rule in app.url_map.iter_rules():
options = {}
for arg in rule.arguments:
options[arg] = "[{0}]".format(arg)
methods = ','.join(rule.methods)
url = url_for(rule.endpoint, **options)
line = urllib.unquote("{:50s} {:20s} {}".format(rule.endpoint, methods, url))
output.append(line)
for line in sorted(output):
print line
It solves the the missing argument by building a dummy set of options. The output looks like:
CampaignView:edit HEAD,OPTIONS,GET /account/[account_id]/campaigns/[campaign_id]/edit
CampaignView:get HEAD,OPTIONS,GET /account/[account_id]/campaign/[campaign_id]
CampaignView:new HEAD,OPTIONS,GET /account/[account_id]/new
Then to run it:
python manage.py list_routes
For more on manage.py checkout: http://flask-script.readthedocs.org/en/latest/
Apparently, since version 0.11, Flask has a built-in CLI. One of the built-in commands lists the routes:
FLASK_APP='my_project.app' flask routes
Similar to Jonathan's answer I opted to do this instead. I don't see the point of using url_for as it will break if your arguments are not string e.g. float
#manager.command
def list_routes():
import urllib
output = []
for rule in app.url_map.iter_rules():
methods = ','.join(rule.methods)
line = urllib.unquote("{:50s} {:20s} {}".format(rule.endpoint, methods, rule))
output.append(line)
for line in sorted(output):
print(line)
Use cli command in Directory where your flask project is.
flask routes
Since you did not specify that it has to be run command-line, the following could easily be returned in json for a dashboard or other non-command-line interface. The result and the output really shouldn't be commingled from a design perspective anyhow. It's bad program design, even if it is a tiny program. The result below could then be used in a web application, command-line, or anything else that ingests json.
You also didn't specify that you needed to know the python function associated with each route, so this more precisely answers your original question.
I use below to add the output to a monitoring dashboard myself. If you want the available route methods (GET, POST, PUT, etc.), you would need to combine it with other answers above.
Rule's repr() takes care of converting the required arguments in the route.
def list_routes():
routes = []
for rule in app.url_map.iter_rules():
routes.append('%s' % rule)
return routes
The same thing using a list comprehension:
def list_routes():
return ['%s' % rule for rule in app.url_map.iter_rules()]
Sample output:
{
"routes": [
"/endpoint1",
"/nested/service/endpoint2",
"/favicon.ico",
"/static/<path:filename>"
]
}
If you need to access the view functions themselves, then instead of app.url_map, use app.view_functions.
Example script:
from flask import Flask
app = Flask(__name__)
#app.route('/foo/bar')
def route1():
pass
#app.route('/qux/baz')
def route2():
pass
for name, func in app.view_functions.items():
print(name)
print(func)
print()
Output from running the script above:
static
<bound method _PackageBoundObject.send_static_file of <Flask '__main__'>>
route1
<function route1 at 0x128f1b9d8>
route2
<function route2 at 0x128f1ba60>
(Note the inclusion of the "static" route, which is created automatically by Flask.)
You can view all the Routes via flask shell by running the following commands after exporting or setting FLASK_APP environment variable.
flask shell
app.url_map
inside your flask app do:
flask shell
>>> app.url_map
Map([<Rule '/' (OPTIONS, HEAD, GET) -> helloworld>,
<Rule '/static/<filename>' (OPTIONS, HEAD, GET) -> static>])
print(app.url_map)
That, is, if your Flask application name is 'app'.
It's an attribute of the instance of the Flask App.
See https://flask.palletsprojects.com/en/2.1.x/api/#flask.Flask.url_map

Categories

Resources