diff --git a/Doc/deprecations/pending-removal-in-3.19.rst b/Doc/deprecations/pending-removal-in-3.19.rst index 25f9cba390de68..044bb8a3934a2a 100644 --- a/Doc/deprecations/pending-removal-in-3.19.rst +++ b/Doc/deprecations/pending-removal-in-3.19.rst @@ -22,3 +22,12 @@ Pending removal in Python 3.19 supported depending on the backend implementation of hash functions. Prefer passing the initial data as a positional argument for maximum backwards compatibility. + +* :mod:`http.cookies`: + + * :meth:`http.cookies.Morsel.js_output` is deprecated and will be + removed in Python 3.19. + + * :meth:`http.cookies.BaseCookie.js_output` is deprecated and will be + removed in Python 3.19. + diff --git a/Doc/library/http.cookies.rst b/Doc/library/http.cookies.rst index b3fcd21c7e2244..4981f71ed7c306 100644 --- a/Doc/library/http.cookies.rst +++ b/Doc/library/http.cookies.rst @@ -109,6 +109,12 @@ Cookie Objects The meaning for *attrs* is the same as in :meth:`output`. + .. deprecated-removed:: 3.15 3.19 + This method generates a JavaScript snippet to set cookies in the browser, + which is no longer considered a standard or recommended approach. + Use :meth:`~http.cookies.BaseCookie.output` instead to generate HTTP + headers. + .. method:: BaseCookie.load(rawdata) @@ -225,6 +231,12 @@ Morsel Objects The meaning for *attrs* is the same as in :meth:`output`. + .. deprecated-removed:: 3.15 3.19 + This method generates a JavaScript snippet to set cookies in the browser, + which is no longer considered a standard or recommended approach. + Use :meth:`~http.cookies.Morsel.output` instead to generate HTTP + headers. + .. method:: Morsel.OutputString(attrs=None) diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 405d388af487e8..a02bf244d3de64 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -1797,6 +1797,16 @@ New deprecations (Contributed by Bénédikt Tran in :gh:`134978`.) +* :mod:`http.cookies`: + + * :meth:`Morsel.js_output ` and + :meth:`BaseCookie.js_output ` are + deprecated and will be removed in Python 3.19. Use + :meth:`Morsel.output ` or + :meth:`BaseCookie.output ` instead. + (Contributed by kishorhange111 in :gh:`148849`.) + + * :mod:`re`: * :func:`re.match` and :meth:`re.Pattern.match` are now diff --git a/Lib/http/cookies.py b/Lib/http/cookies.py index 660fec4f1be865..6f438bb7bc650f 100644 --- a/Lib/http/cookies.py +++ b/Lib/http/cookies.py @@ -132,6 +132,7 @@ import re import string import types +lazy import warnings __all__ = ["CookieError", "BaseCookie", "SimpleCookie"] @@ -390,7 +391,9 @@ def output(self, attrs=None, header="Set-Cookie:"): def __repr__(self): return '<%s: %s>' % (self.__class__.__name__, self.OutputString()) - def js_output(self, attrs=None): + + def _js_output(self, attrs=None): + """Internal implementation without deprecation warning.""" import base64 # Print javascript output_string = self.OutputString(attrs) @@ -407,6 +410,14 @@ def js_output(self, attrs=None): """ % (output_encoded,) + def js_output(self, attrs=None): + warnings._deprecated( + "http.cookies.Morsel.js_output", + message=warnings._DEPRECATED_MSG + "; use output() instead", + remove=(3, 19), + ) + return self._js_output(attrs) + def OutputString(self, attrs=None): # Build up our result # @@ -541,10 +552,15 @@ def __repr__(self): def js_output(self, attrs=None): """Return a string suitable for JavaScript.""" + warnings._deprecated( + "http.cookies.BaseCookie.js_output", + message=warnings._DEPRECATED_MSG + "; use output() instead", + remove=(3, 19), + ) result = [] items = sorted(self.items()) for key, value in items: - result.append(value.js_output(attrs)) + result.append(value._js_output(attrs)) return _nulljoin(result) def load(self, rawdata): diff --git a/Lib/test/test_http_cookies.py b/Lib/test/test_http_cookies.py index cfcbc17bd6df80..518a213782c3ea 100644 --- a/Lib/test/test_http_cookies.py +++ b/Lib/test/test_http_cookies.py @@ -176,7 +176,8 @@ def test_load(self): self.assertEqual(C.output(['path']), 'Set-Cookie: Customer="WILE_E_COYOTE"; Path=/acme') cookie_encoded = base64.b64encode(b'Customer="WILE_E_COYOTE"; Path=/acme; Version=1').decode('ascii') - self.assertEqual(C.js_output(), fr""" + with self.assertWarnsRegex(DeprecationWarning, r"BaseCookie\.js_output"): + self.assertEqual(C.js_output(), fr""" """) cookie_encoded = base64.b64encode(b'Customer="WILE_E_COYOTE"; Path=/acme').decode('ascii') - self.assertEqual(C.js_output(['path']), fr""" + with self.assertWarnsRegex(DeprecationWarning, r"BaseCookie\.js_output"): + self.assertEqual(C.js_output(['path']), fr""" """ % (expected_encoded_cookie,) - self.assertEqual(M.js_output(), expected_js_output) + with self.assertWarnsRegex(DeprecationWarning, r"Morsel\.js_output"): + self.assertEqual(M.js_output(), expected_js_output) for i in ["foo bar", "foo@bar"]: # Try some illegal characters self.assertRaises(cookies.CookieError, @@ -673,7 +678,8 @@ def test_control_characters_output(self): cookie = cookies.SimpleCookie() cookie["cookie"] = morsel with self.assertRaises(cookies.CookieError): - cookie.js_output() + with self.assertWarnsRegex(DeprecationWarning, r"Morsel\.js_output"): + cookie.js_output() morsel = cookies.Morsel() morsel.set("key", "value", "coded-value") @@ -681,8 +687,29 @@ def test_control_characters_output(self): cookie = cookies.SimpleCookie() cookie["cookie"] = morsel with self.assertRaises(cookies.CookieError): - cookie.js_output() + with self.assertWarnsRegex(DeprecationWarning, r"Morsel\.js_output"): + cookie.js_output() + def test_morsel_js_output_deprecated(self): + morsel = cookies.Morsel() + morsel.set("key", "value", "value") + with self.assertWarnsRegex(DeprecationWarning, r"Morsel\.js_output") as cm: + result = morsel.js_output() + self.assertEqual(cm.filename, __file__) + self.assertIn("document.cookie", result) + + + def test_basecookie_js_output_warns_once(self): + C = cookies.SimpleCookie() + C["key"] = "value" + with self.assertWarns(DeprecationWarning) as cm: + C.js_output() + deprecation_warnings = [ + w for w in cm.warnings if issubclass(w.category, DeprecationWarning) + ] + self.assertEqual(len(deprecation_warnings), 1) + self.assertRegex(str(deprecation_warnings[0].message), r"BaseCookie\.js_output") + self.assertEqual(cm.filename, __file__) def load_tests(loader, tests, pattern): tests.addTest(doctest.DocTestSuite(cookies)) diff --git a/Misc/NEWS.d/next/Library/2026-04-25-12-04-27.gh-issue-148849.Vk6yEW.rst b/Misc/NEWS.d/next/Library/2026-04-25-12-04-27.gh-issue-148849.Vk6yEW.rst new file mode 100644 index 00000000000000..9725d63747d451 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-04-25-12-04-27.gh-issue-148849.Vk6yEW.rst @@ -0,0 +1,4 @@ +Deprecate :meth:`http.cookies.Morsel.js_output` and +:meth:`http.cookies.BaseCookie.js_output`, which will be removed in +Python 3.19. Use :meth:`http.cookies.Morsel.output` or +:meth:`http.cookies.BaseCookie.output` instead.