Meaning of '!' as prefix of rule value in e.g. blueprint.add_url_rule('!/', ...)

There are numerous calls to Blueprint.add_url_rule() where the literal value of the specified rule (1st param) is prefixed with an “!” character e.g. in the file categories/

# Display
_bp.add_url_rule('!/', 'display', RHDisplayCategory, defaults={'category_id': 0})
_bp.add_url_rule('/', 'display', RHDisplayCategory)

Could not find an explanation anywhere for this. What is the intended function of this mechanism?

This is something custom (IIRC the code related to it is in IndicoBlueprint or IndicoBlueprintSetupState) which ignores the url_prefix of the blueprint for the given route.

Oh right, i see it now, thanks – it is in flask.wrappers.IndicoBlueprintSetupState, current code is:

def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
    if rule.startswith('!/'):
        with self._unprefixed():
            super(IndicoBlueprintSetupState, self).add_url_rule(rule[1:], endpoint, view_func, **options)
        super(IndicoBlueprintSetupState, self).add_url_rule(rule, endpoint, view_func, **options)

Ok, but then why this strange thing – the two add_url_rule() examples I cited above are then pointing to the same destination url, “/” ? But, if I comment out the first one, and then request “/” on the Indico instance, I get the error:

BuildError: Could not build url for endpoint u'categories.display'. Did you forget to specify values ['category_id']?

and if I comment out the 2nd one (and uncomment the 1st) I get this error on request to “/”:

BuildError: Could not build url for endpoint u'categories.display' with values [u'category_id', 'flatlist'].

And, if I remove the “!” out of the 1st, then I get a Not Found error on request of “/”!

Why two rules to same URL? Why the Not Found?

They are different - remember the url prefix, which is /category/<int:category_id> for that blueprint.

# This one registers `/`, and specifies a `category_id` of `0`(since the root category always has that ID)
_bp.add_url_rule('!/', 'display', RHDisplayCategory, defaults={'category_id': 0})
# This one registers `/category/<int:category_id>/` so it's used for viewing all the other categories.
_bp.add_url_rule('/', 'display', RHDisplayCategory)

Yes, I can appreciate that they are different (and thanks for adding the two comments, they would have helped if they were in the code to start with!). But, sorry to belabor the point – why do these two url rules “depend” on each other? As I mentioned above, commenting either one, and requesting “/” will fail as per error msgs above. And, as we use an overrided root_category template, I also switched back to using the core one to make sure the failure is not being caused something in that.

An additional comment/suggestion if I may, is that the addition of “!” is not-exactly self-explanatory and it does affect the behaviour of add_url_rule() substantially – I would feel it would be more efficient for everybody having to deal with this code to see something like add_url_rule() and add_url_rule_unprefixed() or some such?

Oh, and additionally, for the specific case of the root category (that as you state always has id of 0), there is also the dedicated “root_category” template – why shouldn’t this handle all pecularities of this case then, inclusive of the defaults={‘category_id’: 0} detail?

If you comment out the one for /, then building the url for that endpoint without a category_id fails since it only has the rule that requires one. So building any link to the indico frontpage will fail.

If you commented out the one for /category/<int:category_id>/, I wouldn’t expect an error, because usually Flask simply puts any arguments it cannot put in the url path in the query string instead…

The 404 happens because if there’s no rule for / then that URL doesn’t exist, and thus triggers 404 like any other invalid URL.

Well, it does fail, in either case. And this is with our root_category template disabled (so using core root_category). Now there may well be something else we changed somewhere that may be causing this, will try to determine. But if anyone can check on a stock core instance, that would be great!

OK, looks like this is some special behavior in the Werkzeug/Flask routing system if the other alternatives for the endpoint have defaults.