Reverse Not URL Encoding Apostrophe with %27

Hi friends,
For reasons I need to use reverse a url parameter string value containing an apostrophe. Reverse does not url encode the apostrophe, but Python’s standard library url quote does.
See this test

>>> reverse("demo", kwargs={"workstep": "Director's Assistant"})
"/whatever/Director's%20Assistant/"
>>> quote("Director's Assistant")
'Director%27s%20Assistant'

You can see reverse does not encode the apostrophe, but quote leverages %27 as expected. Would you be so kind as to help me understand why reverse and quote are getting different results? I suspect I am missing something obvious.

Surprisingly, the built-in Django runserver resolves values with the unencoded apostrophe, but I do worry other servers would complain. Further, I clearly don’t understand why reverse and quote would result in different encoded values!

Whenever there’s a question concerning the encoding of a standardized element, the applicable RFCs are always the first place to check.

In this case, the guiding RFC would be RFC3986, section 2.2. Reserved Characters.

The apostrophe is identified in the list of sub-delims, which are identified as being valid characters within a url, in the same generic category as other characters like &, ;, =, !, and a couple others, making it a valid character within a url.

1 Like

Interesting. Thanks Ken, do you know offhand why urllib quote escapes it then? Is just a matter of context? Perhaps urllib’s quote assuming the value is not being used as a sub-delim?

Quoting urllib

By default, this function is intended for quoting the path section of a URL.

So I suspect the “right” way to pass the value into the path parameter is urllib’s, whereas Django is respecting the apostrophe could be a sub-delim. I think the best solution for my scenario is to leverage quote since the value is used as a path param.

Quoting directly from urllib.parse — Parse URLs into components — Python 3.13.3 documentation

Replace special characters in string using the %xx escape. Letters, digits, and the characters '_.-~' are never quoted. By default, this function is intended for quoting the path section of a URL. The optional safe parameter specifies additional ASCII characters that should not be quoted — its default value is '/' .

While I’m not an authoritative source on why anything is being done, my guess is that they took this approach as a conservative approach. What I mean is that I would assume their implementation makes it easier to add characters to the safe list than it would be to remove them. In that situation, then the safe list would represent the smallest set of safe characters, to be extended on an as-needed basis.

<conjecture>
I’m not sure it matters - any standard-compliant web server should pass that url through directly, and assuming your path definition for the url accepts it as part of the path, it should work directly.
</conjecture>

1 Like

The plot thickens, when Django renders my reverse + quote version as /Director%27s%20Assistant, my JS library using the value sends the request as /Director's%20Assistant/ so it appears leaving the apostrophe in place is fine. Thanks for all your help Ken.