Discovery function
def blueprint_site_map(app, blueprint, all_methods=False):
'''
utilizes Flask's built-in rule mapper to generate a
site-map of the application, returning a list of dicts, ex.
{
'endpoint' : repr(Blueprint)
'methods' : list
'rule' : /route
{
'''
reply = []
rules = list(app.url_map.iter_rules())
ignored_methods = set(() if all_methods else ('HEAD', 'OPTIONS'))
rule_methods = [','.join(sorted(rule.methods - ignored_methods)) for rule in rules]
for rule, methods in zip(rules, rule_methods):
if (rule.endpoint != 'static') and (rule.endpoint.startswith(blueprint)):
reply.append(dict(endpoint=rule.endpoint, methods=methods.split(','), rule=rule.rule))
return reply
Sample output
>>> blueprint_site_map(app, 'my_blueprint')
[
{
'endpoint': 'my_blueprint.foo',
'methods': ['GET', 'POST'],
'rule': '/auth/foo'
},
{
'endpoint': 'my_blueprint.bar',
'methods': ['DELETE', 'GET', 'POST'],
'rule': '/auth/bar'
}
]
Usage
def test_my_blueprint_is_protected(client):
from flask import current_app as app
obj = blueprint_site_map(app, 'my_blueprint')
for each in obj:
for method in each['methods']:
func = getattr(client, method)
url = each['rule'] # *see note
kwargs = {} # inject headers, etc if needed
response = func(url, **kwargs)
assert response.status_code == 401
It should be noted that if you are using any parameterized URL rules, such as allowing both /foo
and /foo/<string:s>
then you will need to manually template or filter these out. The blueprint_site_map
function will include separate list elements for /foo
and /foo/<string:s>
, which when taken literally will cause problems either from the test client itself, or with your route logic.
The discovery function is crafted in such a way that you can use this convention for as many different blueprints as necessary, which by very nature of your using the Blueprint convention means that you can keep your unit tests as modular as the app.
Cheers!