aio-libs / yarl

Yet another URL library
https://yarl.aio-libs.org
Apache License 2.0
1.3k stars 160 forks source link

Support building query without values #307

Open rominf opened 5 years ago

rominf commented 5 years ago

Sometimes query consists not only of pairs of keys and values but also can contain standalone keys. In my opinion, it's bad, but I encountered this a few times in different APIs. For example, I need to build a URL like: https://api.crowdin.com/api/project/{project_identifier}/pre-translate?key={project_key}&json (see https://support.crowdin.com/api/pre-translate/ for details).

I propose to make an interface look like this:

url = URL('https://api.crowdin.com/api/project/{project-identifier}/pre-translate')
url = url.with_query(key=project_key, json=None)

This is safe because now passing None as keyword argument value raises TypeError. If user wants to get an URL with json= (note that the last symbol is the equals sign), he passes json='' as he does now.

asvetlov commented 5 years ago

Right now I'm inclining to agree with your proposal but want to check it again for controversy. I don't like usage of None as not for None value but the absence of value at all. On the other hand, I don't see a better solution.

Would you prepare a PR for review?

rominf commented 5 years ago

@asvetlov it could be a special constant:

# in yarl:
QUERY_KEY_WITHOUT_VALUE = object()

# in code:
from yarl import QUERY_KEY_WITHOUT_VALUE
url = URL('https://api.crowdin.com/api/project/{project-identifier}/pre-translate')
url = url.with_query(key=project_key, json=QUERY_KEY_WITHOUT_VALUE)

It's more obvious, but IMHO more ugly.

I can prepare a PR tomorrow.

asvetlov commented 5 years ago

Agree, a constant is worse than just None

serhiy-storchaka commented 3 years ago

urllib.parse.parse_qsl() does not distinguish empty value from absent value:

>>> parse_qsl('a=b&c=', keep_blank_values=True)
[('a', 'b'), ('c', '')]
>>> parse_qsl('a=b&c', keep_blank_values=True)
[('a', 'b'), ('c', '')]

As well as it does not distinguish the & separator from the ; separator.

>>> parse_qsl('a=b&c=d')
[('a', 'b'), ('c', 'd')]
>>> parse_qsl('a=b;c=d')
[('a', 'b'), ('c', 'd')]

If you want to create a query with non-standard representation (parameters without =, with the ; separator instead of &, or %-encoded characters which do not need to be encoded in norm, or using encoding different from UTF-8), you need to change directly the query_string or even the raw_query_string attributes, but not the query dict.

url = url.with_query(key=project_key)
url = url.with_query(url.query_string + '&json')