Skip to content

gh-119670: Add force keyword only argument to shlex.quote#148846

Open
jb2170 wants to merge 10 commits intopython:mainfrom
jb2170:shlex-quote-force
Open

gh-119670: Add force keyword only argument to shlex.quote#148846
jb2170 wants to merge 10 commits intopython:mainfrom
jb2170:shlex-quote-force

Conversation

@jb2170
Copy link
Copy Markdown
Contributor

@jb2170 jb2170 commented Apr 21, 2026

Closes #119670
Supersedes my out-of-date PR #119674

We use force instead of always as a word more consistent with other programming jargon (like in rm -f the f is for 'force') as discussed

Commits

  • ddbe3a5: Add base implementation.
  • 0e722d6: Make shlex.quote's kwargs kwargs-only. Backwards compatible. There are propositions to add a single-quote-double-quote switch, which I'm ambivalent about, but to future-proof against hiccups of people passing force as a positional and it ending up being used as the wrong kwarg, we make the kwargs kwargs-only, eg like json.loads.
  • fd4af18: Add tests. I've tried to be pretty thorough. Their purposes are commented on.
  • 78b6f3b: Update docs. Contains explanation and examples.
  • 762999d: Add blurb entry.
  • 2a301a5: Add whatsnew entry.

As mentioned I'm also going to open an issue a discussion thread about shlex.quote returning "''" for falsey non-str data. This PR and that discussion seem independent of each other, that is this PR doesn't need to be held back until that discussion is addressed.


📚 Documentation preview 📚: https://cpython-previews--148846.org.readthedocs.build/

jb2170 added 5 commits April 21, 2026 04:41
There are propositions to add a single-quote-double-quote switch
(pythongh-90630), so to avoid hiccups of people passing `force` as a
positional and it being used for the single-double switch, we make
kwargs kwargs-only.
Test special cases of strings that don't need quoting, do need quoting,
do use `force`, don't use `force` etc. I've tried to be exhaustive.
@jb2170 jb2170 requested a review from AA-Turner as a code owner April 21, 2026 18:43
@jb2170
Copy link
Copy Markdown
Contributor Author

jb2170 commented Apr 21, 2026

@vstinner pong 😅

Copy link
Copy Markdown
Member

@johnslavik johnslavik left a comment

Choose a reason for hiding this comment

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

Thanks!

Comment thread Lib/shlex.py Outdated
Comment thread Lib/test/test_shlex.py Outdated
Comment thread Lib/test/test_shlex.py Outdated
@johnslavik
Copy link
Copy Markdown
Member

johnslavik commented Apr 22, 2026

Hmm I also have a hunch that this might be worth calling out in What's New. Nvm it's already there, great job 🚀

jb2170 and others added 4 commits April 22, 2026 18:59
Co-authored-by: Bartosz Sławecki <bartosz@ilikepython.com>
The comments for why these tests exist can always be found in the git history

Co-authored-by: Bartosz Sławecki <bartosz@ilikepython.com>
Intended for another branch shlex-quote-typeerror

Co-authored-by: Bartosz Sławecki <bartosz@ilikepython.com>
@jb2170
Copy link
Copy Markdown
Contributor Author

jb2170 commented Apr 24, 2026

Looks ready (for review again?) to me 😎

Copy link
Copy Markdown
Member

@vstinner vstinner left a comment

Choose a reason for hiding this comment

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

LGTM.

@picnixz: Do you want to double check? You reviewed the previous PR gh-119674.

Copy link
Copy Markdown
Member

@picnixz picnixz left a comment

Choose a reason for hiding this comment

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

Minor tweaks but please add more tets with quotes inside.

Comment thread Doc/library/shlex.rst
Comment on lines +53 to +54
If *force* is :const:`True` then *s* will be quoted even if it is already
safe for a shell without being quoted.
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.

Suggested change
If *force* is :const:`True` then *s* will be quoted even if it is already
safe for a shell without being quoted.
If *force* is :const:`True`, then *s* is unconditionally quoted,
even if it is already safe for a shell without being quoted.

Comment thread Doc/library/shlex.rst

>>> from shlex import quote
>>> filenames = ['my first file', 'file2', 'file 3']
>>> filenames_some_escaped = [quote(f, force=False) for f in filenames]
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.

Suggested change
>>> filenames_some_escaped = [quote(f, force=False) for f in filenames]
>>> filenames_some_escaped = [quote(f) for f in filenames]

Comment thread Doc/library/shlex.rst
.. versionadded:: 3.3

.. versionchanged:: next
The *force* keyword was added.
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.

Suggested change
The *force* keyword was added.
Add the *force* parameter.

Comment thread Doc/whatsnew/3.15.rst
Comment on lines +1744 to +1746
* :func:`shlex.quote` has a new keyword-only parameter *force* that ensures
a string will always be quoted, even if it is already safe for a shell
without being quoted.
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.

Suggested change
* :func:`shlex.quote` has a new keyword-only parameter *force* that ensures
a string will always be quoted, even if it is already safe for a shell
without being quoted.
* Add *force* to :func:`shlex.quote` to force quoting a string,
even if it is already safe for a shell without being quoted.

Comment thread Lib/shlex.py
Comment on lines +323 to +324
If *force* is *True* then *s* will be quoted even if it is
already safe for a shell without being quoted.
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.

Update the docstring with the updated doc I suggested as well.

Comment thread Lib/shlex.py
if s.isascii() and not s.encode().translate(None, delete=safe_chars):
if (not force
and s.isascii() and not s.encode().translate(None, delete=safe_chars)):
# No quoting is needed if we're not forcing quoting
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.

Suggested change
# No quoting is needed if we're not forcing quoting
# No quoting is needed if we are not forcing quoting

Comment thread Lib/shlex.py
if (not force
and s.isascii() and not s.encode().translate(None, delete=safe_chars)):
# No quoting is needed if we're not forcing quoting
# and `s` is an ASCII string consisting only of `safe_chars`
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.

Suggested change
# and `s` is an ASCII string consisting only of `safe_chars`
# and `s` is an ASCII string consisting only of `safe_chars`.

Comment thread Lib/test/test_shlex.py
self.assertRaises(TypeError, shlex.quote, b"abc")

def testForceQuote(self):
self.assertEqual(shlex.quote("spam"), "spam")
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.

Please add some tests where the input has quotes inside.

@@ -0,0 +1,3 @@
Add *force* keyword only argument to :func:`shlex.quote` to always quote the
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.

Update the NEWS entry with the updated What's New as well.

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.

shlex.quote: Add 'always' keyword argument

4 participants