Thanks to visit codestin.com
Credit goes to github.com

Skip to content

cast query string keys/values to string before passing to quote()#6099

Merged
amercader merged 3 commits into
ckan:masterfrom
chris48s:cast_query_strings
Sep 6, 2021
Merged

cast query string keys/values to string before passing to quote()#6099
amercader merged 3 commits into
ckan:masterfrom
chris48s:cast_query_strings

Conversation

@chris48s
Copy link
Copy Markdown
Contributor

Proposed fixes:

Cast query string keys/values to string before passing to quote()

This issue can cause us to throw AttributeError: 'bool' object has no attribute 'rstrip' calling something like

url_for("/foo/bar", __no_cache__=True)

I've been hitting this problem on CKAN 2.9 so this should definitely be backported to 2.9. It might also be applicable to 2.8.

Features:

  • includes tests covering changes
  • includes updated documentation
  • includes user-visible changes
  • includes API changes
  • includes bugfix for possible backport

@amercader amercader self-assigned this May 18, 2021
@amercader
Copy link
Copy Markdown
Member

@chris48s thanks for this. It was discussed in the dev meeting that rather than cast everything to str and show Python's representation, the best behaviour if the passed value is not a string would be to handle specifically the None (output as null) and boolean (output as true, false) cases and fail in any other type (lists, dicts, etc)

@chris48s
Copy link
Copy Markdown
Contributor Author

chris48s commented May 21, 2021

Presumably the numeric case should also cast to string.
i.e: {'id': 1} should give us ?id=1 not throw an exception.

@amercader
Copy link
Copy Markdown
Member

Yes, sorry. Numbers should be supported

@chris48s chris48s force-pushed the cast_query_strings branch from 324114f to 42c1278 Compare May 21, 2021 10:09
Comment thread ckan/lib/helpers.py Outdated
Comment thread ckan/lib/helpers.py Outdated
if value is None:
return 'null'
if isinstance(value, numbers.Number):
return str(value)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To keep with the JSON style null:

Suggested change
return str(value)
return str(value).lower() # render True/False as true/false

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having churned this over slightly more over the weekend, it seems to me that really in this fallback case except FlaskRouteBuildError: we should really be trying to match the behaviour of named routes as closely as possible.

i.e: what we should be aiming for is that

h.url_for("dataset.read", id="my_dataset", somebool=True) and
h.url_for("/dataset/my_dataset", somebool=True)

should do the same thing, as should

h.url_for("dataset.read", id="my_dataset", empty=None) and
h.url_for("/dataset/my_dataset", empty=None)

etc

FWIW:

h.url_for("dataset.read", id="my_dataset", param=True) == '/dataset/my_dataset?param=True'
h.url_for("dataset.read", id="my_dataset", param=None) == '/dataset/my_dataset'
h.url_for("dataset.read", id="my_dataset", param={}) == '/dataset/my_dataset?param=%7B%7D'

Shall I alter it so the fallback matches the named route behaviour? @amercader @wardi ? It seems the most logical on reflection.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, @chris48s this makes sense. It's good to have url_for having a consistent output regardless of how it is invoked

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. I've updated to implement that behaviour

@amercader
Copy link
Copy Markdown
Member

Thanks @chris48s

Just so it's clear as I was confused by this. In this PR we are sticking with some_param=True instead of some_param=true because the former is the default behaviour by Flask, eg:

from flask import url_for
url_for('home.about', some_param='cat', another=True, dogs=[], rabbits=None)

# '/about?some_param=cat&another=True'

And in this PR we are just fixing our fallback code for when Flask can't build a URL, eg because we are using the deprecated h.url_for('/dataset/new')

Does that sound good @wardi ?

@wardi
Copy link
Copy Markdown
Contributor

wardi commented Jun 10, 2021

@amercader sounds good

@chris48s
Copy link
Copy Markdown
Contributor Author

chris48s commented Jul 7, 2021

Just to flag, I think this PR should be backported to the next 2.9 release (it is a bugfix not a feature) but it doesn't have the "Backport 2.9.4" label applied to it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants