Skip to content

gh-92613: Deprecate other uuencode functionality per PEP 594 & document as such #92758

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Doc/library/binascii.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ The :mod:`binascii` module defines the following functions:
data. Lines normally contain 45 (binary) bytes, except for the last line. Line
data may be followed by whitespace.

.. deprecated-removed:: 3.12 3.14
This function and the legacy uuencode format it implements are deprecated
(see :pep:`PEP 594 <594#uu-and-the-uu-encoding>` for details).


.. function:: b2a_uu(data, *, backtick=False)

Expand All @@ -48,6 +52,10 @@ The :mod:`binascii` module defines the following functions:
.. versionchanged:: 3.7
Added the *backtick* parameter.

.. deprecated-removed:: 3.12 3.14
This function and the legacy uuencode format it implements are deprecated
(see :pep:`PEP 594 <594#uu-and-the-uu-encoding>` for details).


.. function:: a2b_base64(string, /, *, strict_mode=False)

Expand Down
6 changes: 5 additions & 1 deletion Doc/library/codecs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1414,7 +1414,7 @@ to :class:`bytes` mappings. They are not supported by :meth:`bytes.decode`
| | quoted_printable | | :meth:`quopri.decode` |
+----------------------+------------------+------------------------------+------------------------------+
| uu_codec | uu | Convert the operand using | :meth:`uu.encode` / |
| | | uuencode. | :meth:`uu.decode` |
| | | uuencode (deprecated). | :meth:`uu.decode` |
+----------------------+------------------+------------------------------+------------------------------+
| zlib_codec | zip, zlib | Compress the operand using | :meth:`zlib.compress` / |
| | | gzip. | :meth:`zlib.decompress` |
Expand All @@ -1430,6 +1430,10 @@ to :class:`bytes` mappings. They are not supported by :meth:`bytes.decode`
.. versionchanged:: 3.4
Restoration of the aliases for the binary transforms.

.. deprecated-removed:: 3.12 3.14
The uuencode codec (``uu_codec``) is deprecated
(see :pep:`PEP 594 <594#uu-and-the-uu-encoding>` for details).


.. _text-transforms:

Expand Down
4 changes: 4 additions & 0 deletions Doc/library/email.compat32-message.rst
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,10 @@ Here are the methods of the :class:`Message` class:
replaced by :meth:`~email.message.EmailMessage.get_content` and
:meth:`~email.message.EmailMessage.iter_parts`.

.. deprecated-removed:: 3.12 3.14
Decoding legacy uuencode payloads (with ``decode=True``) is deprecated
(see :pep:`PEP 594 <594#uu-and-the-uu-encoding>` for details).


.. method:: set_payload(payload, charset=None)

Expand Down
18 changes: 18 additions & 0 deletions Doc/whatsnew/3.12.rst
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,24 @@ Deprecated
and tailor them to your needs.
(Contributed by Erlend E. Aasland in :gh:`90016`.)

* Per :pep:`PEP 594 <594#uu-and-the-uu-encoding>`,
the remaining functionality related to the legacy
`uuencode encoding <https://en.wikipedia.org/wiki/Uuencoding>`__
(also exposed in the to-be-removed :mod:`uu` module) has been deprecated,
and will be removed in Python 3.14:

* :func:`binascii.a2b_uu` and :func:`binascii.b2a_uu`,
low-level interfaces for decoding and encoding uuencode data.
* The ``uu_codec`` :ref:`binary transform <binary-transforms>`
in the :mod:`codecs` module,
implementing uuencode as a Python codec
* Support for decoding non-MIME uuencode payloads
with the :meth:`email.message.Message.get_payload` method
of the legacy :ref:`email.message.Message <compat32_message>`
(:attr:`email.policy.Compat32`) API.

(Contributed by C.A.M. Gerlach in :gh:`92613`.)

* In :meth:`~sqlite3.Cursor.execute`, :exc:`DeprecationWarning` is now emitted
when :ref:`named placeholders <sqlite3-placeholders>` are used together with
parameters supplied as a :term:`sequence` instead of as a :class:`dict`.
Expand Down
12 changes: 10 additions & 2 deletions Lib/email/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
__all__ = ['Message', 'EmailMessage']

import binascii
import re
import quopri
import re
import warnings
from io import BytesIO, StringIO

# Intrapackage imports
Expand Down Expand Up @@ -318,8 +319,15 @@ def get_payload(self, i=None, decode=False):
self.policy.handle_defect(self, defect)
return value
elif cte in ('x-uuencode', 'uuencode', 'uue', 'x-uue'):
warnings._deprecated(
'Decoding legacy uuencoded payloads in messages',
remove=(3, 14))
try:
return _decode_uu(bpayload)
# We already issue our own warning here
with warnings.catch_warnings():
warnings.filterwarnings(
'ignore', message='.*uu.*', category=DeprecationWarning)
return _decode_uu(bpayload)
except ValueError:
# Some decoding problem.
return bpayload
Expand Down
33 changes: 27 additions & 6 deletions Lib/encodings/uu_codec.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,23 @@
modified by Jack Jansen and Fredrik Lundh.
"""

import codecs
import binascii
import codecs
import warnings
from io import BytesIO


_uu_deprecation_warning_filter = {
'action': 'ignore',
'message': '.*uu.*',
'category': DeprecationWarning,
}


### Codec APIs

def uu_encode(input, errors='strict', filename='<data>', mode=0o666):
warnings._deprecated(__name__, remove=(3, 14))
assert errors == 'strict'
infile = BytesIO(input)
outfile = BytesIO()
Expand All @@ -27,14 +37,19 @@ def uu_encode(input, errors='strict', filename='<data>', mode=0o666):
# Encode
write(('begin %o %s\n' % (mode & 0o777, filename)).encode('ascii'))
chunk = read(45)
while chunk:
write(binascii.b2a_uu(chunk))
chunk = read(45)

# We already warn above on calling this function
with warnings.catch_warnings():
warnings.filterwarnings(**_uu_deprecation_warning_filter)
while chunk:
write(binascii.b2a_uu(chunk))
chunk = read(45)
write(b' \nend\n')

return (outfile.getvalue(), len(input))

def uu_decode(input, errors='strict'):
warnings._deprecated(__name__, remove=(3, 14))
assert errors == 'strict'
infile = BytesIO(input)
outfile = BytesIO()
Expand All @@ -55,11 +70,17 @@ def uu_decode(input, errors='strict'):
if not s or s == b'end\n':
break
try:
data = binascii.a2b_uu(s)
# We already warn above on calling this function
with warnings.catch_warnings():
warnings.filterwarnings(**_uu_deprecation_warning_filter)
data = binascii.a2b_uu(s)
except binascii.Error as v:
# Workaround for broken uuencoders by /Fredrik Lundh
nbytes = (((s[0]-32) & 63) * 4 + 5) // 3
data = binascii.a2b_uu(s[:nbytes])
# We already warn above on calling this function
with warnings.catch_warnings():
warnings.filterwarnings(**_uu_deprecation_warning_filter)
data = binascii.a2b_uu(s[:nbytes])
#sys.stderr.write("Warning: %s\n" % str(v))
write(data)
if not s:
Expand Down
37 changes: 28 additions & 9 deletions Lib/test/test_binascii.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
"""Test the binascii C module."""

import unittest
import binascii
import array
import binascii
import contextlib
import re
from test.support import bigmemtest, _1G, _4G
import unittest
from test.support import bigmemtest, _1G, _4G, warnings_helper


# Note: "*_hex" functions are aliases for "(un)hexlify"
Expand All @@ -14,6 +15,16 @@
'unhexlify']
all_functions = a2b_functions + b2a_functions + ['crc32', 'crc_hqx']

deprecated_functions = ['b2a_uu', 'a2b_uu']


def _check_function_warning(function_name):
"""Helper to check that deprecated functions warn, and silence them."""
if function_name not in deprecated_functions:
return contextlib.nullcontext()
return warnings_helper.check_warnings(
(f".*{function_name}.*", DeprecationWarning))


class BinASCIITest(unittest.TestCase):

Expand Down Expand Up @@ -46,8 +57,10 @@ def test_returned_value(self):
a2b = getattr(binascii, fa)
b2a = getattr(binascii, fb)
try:
a = b2a(self.type2test(raw))
res = a2b(self.type2test(a))
with _check_function_warning(fb):
a = b2a(self.type2test(raw))
with _check_function_warning(fa):
res = a2b(self.type2test(a))
except Exception as err:
self.fail("{}/{} conversion raises {!r}".format(fb, fa, err))
self.assertEqual(res, raw, "{}/{} conversion: "
Expand Down Expand Up @@ -185,6 +198,8 @@ def assertInvalidLength(data):
assertInvalidLength(b'a' * (4 * 87 + 1))
assertInvalidLength(b'A\tB\nC ??DE') # only 5 valid characters

# Uuencode is deprecated
@warnings_helper.ignore_warnings(category=DeprecationWarning)
def test_uu(self):
MAX_UU = 45
for backtick in (True, False):
Expand Down Expand Up @@ -383,7 +398,8 @@ def test_empty_string(self):
continue
f = getattr(binascii, func)
try:
f(empty)
with _check_function_warning(func):
f(empty)
except Exception as err:
self.fail("{}({!r}) raises {!r}".format(func, empty, err))

Expand All @@ -405,10 +421,13 @@ def test_unicode_a2b(self):
a2b = getattr(binascii, fa)
b2a = getattr(binascii, fb)
try:
a = b2a(self.type2test(raw))
binary_res = a2b(a)
with _check_function_warning(fb):
a = b2a(self.type2test(raw))
with _check_function_warning(fa):
binary_res = a2b(a)
a = a.decode('ascii')
res = a2b(a)
with _check_function_warning(fa):
res = a2b(a)
except Exception as err:
self.fail("{}/{} conversion raises {!r}".format(fb, fa, err))
self.assertEqual(res, raw, "{}/{} conversion: "
Expand Down
52 changes: 37 additions & 15 deletions Lib/test/test_codecs.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from unittest import mock

from test import support
from test.support import os_helper
from test.support import os_helper, warnings_helper

try:
import _testcapi
Expand Down Expand Up @@ -2711,6 +2711,11 @@ def test_seek0(self):
"rot_13": ["rot13"],
}

deprecated_transforms = {
"uu_codec",
}


try:
import zlib
except ImportError:
Expand All @@ -2727,33 +2732,45 @@ def test_seek0(self):
transform_aliases["bz2_codec"] = ["bz2"]


def _check_transform_warning(encoding):
"""Helper to check that deprecated transforms warn and silence them."""
if encoding not in deprecated_transforms:
return contextlib.nullcontext()
return warnings_helper.check_warnings(
(f".*({encoding}).*", DeprecationWarning))


class TransformCodecTest(unittest.TestCase):

def test_basics(self):
binput = bytes(range(256))
for encoding in bytes_transform_encodings:
with self.subTest(encoding=encoding):
# generic codecs interface
(o, size) = codecs.getencoder(encoding)(binput)
with _check_transform_warning(encoding):
(o, size) = codecs.getencoder(encoding)(binput)
self.assertEqual(size, len(binput))
(i, size) = codecs.getdecoder(encoding)(o)
with _check_transform_warning(encoding):
(i, size) = codecs.getdecoder(encoding)(o)
self.assertEqual(size, len(o))
self.assertEqual(i, binput)

def test_read(self):
for encoding in bytes_transform_encodings:
with self.subTest(encoding=encoding):
sin = codecs.encode(b"\x80", encoding)
reader = codecs.getreader(encoding)(io.BytesIO(sin))
sout = reader.read()
with _check_transform_warning(encoding):
sin = codecs.encode(b"\x80", encoding)
reader = codecs.getreader(encoding)(io.BytesIO(sin))
sout = reader.read()
self.assertEqual(sout, b"\x80")

def test_readline(self):
for encoding in bytes_transform_encodings:
with self.subTest(encoding=encoding):
sin = codecs.encode(b"\x80", encoding)
reader = codecs.getreader(encoding)(io.BytesIO(sin))
sout = reader.readline()
with _check_transform_warning(encoding):
sin = codecs.encode(b"\x80", encoding)
reader = codecs.getreader(encoding)(io.BytesIO(sin))
sout = reader.readline()
self.assertEqual(sout, b"\x80")

def test_buffer_api_usage(self):
Expand All @@ -2765,13 +2782,16 @@ def test_buffer_api_usage(self):
with self.subTest(encoding=encoding):
data = original
view = memoryview(data)
data = codecs.encode(data, encoding)
view_encoded = codecs.encode(view, encoding)
with _check_transform_warning(encoding):
data = codecs.encode(data, encoding)
view_encoded = codecs.encode(view, encoding)
self.assertEqual(view_encoded, data)
view = memoryview(data)
data = codecs.decode(data, encoding)
with _check_transform_warning(encoding):
data = codecs.decode(data, encoding)
self.assertEqual(data, original)
view_decoded = codecs.decode(view, encoding)
with _check_transform_warning(encoding):
view_decoded = codecs.decode(view, encoding)
self.assertEqual(view_decoded, data)

def test_text_to_binary_denylists_binary_transforms(self):
Expand Down Expand Up @@ -2799,7 +2819,8 @@ def test_binary_to_text_denylists_binary_transforms(self):
data = b"encode first to ensure we meet any format restrictions"
for encoding in bytes_transform_encodings:
with self.subTest(encoding=encoding):
encoded_data = codecs.encode(data, encoding)
with _check_transform_warning(encoding):
encoded_data = codecs.encode(data, encoding)
fmt = (r"{!r} is not a text encoding; "
r"use codecs.decode\(\) to handle arbitrary codecs")
msg = fmt.format(encoding)
Expand Down Expand Up @@ -2852,7 +2873,8 @@ def test_quopri_stateless(self):

def test_uu_invalid(self):
# Missing "begin" line
self.assertRaises(ValueError, codecs.decode, b"", "uu-codec")
with _check_transform_warning("uu_codec"):
self.assertRaises(ValueError, codecs.decode, b"", "uu-codec")


# The codec system tries to add notes to exceptions in order to ensure
Expand Down
Loading