Skip to content

Commit d598e3c

Browse files
authored
Merge branch 'main' into fix/decimal_ctx_status
2 parents 64d4fcb + 1c7011d commit d598e3c

9 files changed

Lines changed: 107 additions & 13 deletions

File tree

Doc/reference/datamodel.rst

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,7 @@ subscript notation ``a[k]`` selects the item indexed by ``k`` from the mapping
496496
:keyword:`del` statements. The built-in function :func:`len` returns the number
497497
of items in a mapping.
498498

499-
There is currently a single intrinsic mapping type:
499+
There are two intrinsic mapping types:
500500

501501

502502
Dictionaries
@@ -535,6 +535,20 @@ module.
535535
an implementation detail at that time rather than a language guarantee.
536536

537537

538+
Frozen dictionaries
539+
^^^^^^^^^^^^^^^^^^^
540+
541+
.. index:: pair: object; frozendict
542+
543+
These represent an immutable dictionary. They are created by the built-in
544+
:func:`frozendict` constructor. A frozendict is :term:`hashable` if all of
545+
its keys and values are hashable, in which case it can be used as an element
546+
of a set, or as a key in another mapping. :class:`!frozendict` is not a
547+
subclass of :class:`dict`; it inherits directly from :class:`object`.
548+
549+
.. versionadded:: 3.15
550+
551+
538552
Callable types
539553
--------------
540554

Lib/json/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ def load(fp, *, cls=None, object_hook=None, parse_float=None,
307307
cls=cls, object_hook=object_hook,
308308
parse_float=parse_float, parse_int=parse_int,
309309
parse_constant=parse_constant, object_pairs_hook=object_pairs_hook,
310-
array_hook=None, **kw)
310+
array_hook=array_hook, **kw)
311311

312312

313313
def loads(s, *, cls=None, object_hook=None, parse_float=None,

Lib/test/test_json/test_decode.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,13 @@ def test_array_hook(self):
8787

8888
self.assertEqual(self.loads('[]', array_hook=tuple), ())
8989

90+
def test_load_array_hook(self):
91+
# json.load must forward array_hook to loads
92+
fp = StringIO('[10, 20, 30]')
93+
result = self.json.load(fp, array_hook=tuple)
94+
self.assertEqual(result, (10, 20, 30))
95+
self.assertEqual(type(result), tuple)
96+
9097
def test_decoder_optimizations(self):
9198
# Several optimizations were made that skip over calls to
9299
# the whitespace regex, so this test is designed to try and

Lib/test/test_pyexpat.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,16 @@ def test_unknown_encoding(self):
426426
with self.assertRaises(LookupError):
427427
parser.Parse(data, True)
428428

429+
@support.subTests('sample,exception', [
430+
(b'<x> \xa1</x>', UnicodeDecodeError), # crashed
431+
(b'<x> \xa1</x', UnicodeDecodeError), # crashed
432+
(b'<x> \xa1', expat.ExpatError),
433+
])
434+
def test_multibyte_encoding_errors(self, sample, exception):
435+
parser = expat.ParserCreate()
436+
data = b'<?xml version="1.0" encoding="EUC-JP"?>\n' + sample
437+
with self.assertRaises(exception):
438+
parser.Parse(data, True)
429439

430440
class NamespaceSeparatorTest(unittest.TestCase):
431441
def test_legal(self):

Lib/test/test_xml_etree.py

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,6 +1064,17 @@ def bxml(encoding, body=''):
10641064
self.assertRaises(ValueError, ET.XML, xml('undefined').encode('ascii'))
10651065
self.assertRaises(LookupError, ET.XML, xml('xxx').encode('ascii'))
10661066

1067+
@support.subTests('sample,exception', [
1068+
(b'<x> \xa1</x>', UnicodeDecodeError), # crashed
1069+
(b'<x> \xa1</x', UnicodeDecodeError), # crashed
1070+
(b'<x> \xa1', None), # ET.ParseError
1071+
])
1072+
def test_multibyte_encoding_errors(self, sample, exception):
1073+
exception = exception or ET.ParseError
1074+
data = b'<?xml version="1.0" encoding="EUC-JP"?>\n' + sample
1075+
with self.assertRaises(exception):
1076+
ET.XML(data)
1077+
10671078
def test_methods(self):
10681079
# Test serialization methods.
10691080

@@ -1287,7 +1298,15 @@ def check(p, expected, namespaces=None):
12871298
{'': 'http://www.w3.org/2001/XMLSchema',
12881299
'ns': 'http://www.w3.org/2001/XMLSchema'})
12891300

1290-
def test_processinginstruction(self):
1301+
def test_comment_serialization(self):
1302+
comm = ET.Comment('<spam> & ham')
1303+
# comments are not escaped
1304+
self.assertEqual(ET.tostring(comm), b'<!--<spam> & ham-->')
1305+
self.assertEqual(ET.tostring(comm, method='html'), b'<!--<spam> & ham-->')
1306+
# no comments in text serialization
1307+
self.assertEqual(ET.tostring(comm, method='text'), b'')
1308+
1309+
def test_processinginstruction_serialization(self):
12911310
# Test ProcessingInstruction directly
12921311

12931312
self.assertEqual(ET.tostring(ET.ProcessingInstruction('test', 'instruction')),
@@ -1296,12 +1315,32 @@ def test_processinginstruction(self):
12961315
b'<?test instruction?>')
12971316

12981317
# Issue #2746
1299-
1318+
# processing instructions are not escaped
13001319
self.assertEqual(ET.tostring(ET.PI('test', '<testing&>')),
13011320
b'<?test <testing&>?>')
13021321
self.assertEqual(ET.tostring(ET.PI('test', '<testing&>\xe3'), 'latin-1'),
13031322
b"<?xml version='1.0' encoding='latin-1'?>\n"
13041323
b"<?test <testing&>\xe3?>")
1324+
pi = ET.PI('test', 'ham & eggs < spam')
1325+
self.assertEqual(ET.tostring(pi), b'<?test ham & eggs < spam?>')
1326+
self.assertEqual(ET.tostring(pi, method='html'), b'<?test ham & eggs < spam?>')
1327+
# no processing instructions in text serialization
1328+
self.assertEqual(ET.tostring(pi, method='text'), b'')
1329+
1330+
def test_empty_attribute_serialization(self):
1331+
# empty attrs only work in html
1332+
elem = ET.Element('tag', attrib={'attr': None})
1333+
self.assertRaises(TypeError, ET.tostring, elem)
1334+
self.assertEqual(ET.tostring(elem, method='html'), b'<tag attr></tag>')
1335+
1336+
@support.subTests('tag', ("script", "style", "xmp", "iframe", "noembed", "noframes"))
1337+
def test_html_cdata_elems_serialization(self, tag):
1338+
# content of raw text elements is not escaped in html
1339+
tag = tag.title()
1340+
elem = ET.Element(tag)
1341+
elem.text = '<spam>&ham'
1342+
self.assertEqual(ET.tostring(elem, method='html'),
1343+
('<%s><spam>&ham</%s>' % (tag, tag)).encode())
13051344

13061345
def test_html_empty_elems_serialization(self):
13071346
# issue 15970
@@ -1317,6 +1356,14 @@ def test_html_empty_elems_serialization(self):
13171356
method='html')
13181357
self.assertEqual(serialized, expected)
13191358

1359+
def test_html_plaintext_serialization(self):
1360+
# content of plaintext is not escaped in html
1361+
# no end tag for plaintext
1362+
elem = ET.Element('PlainText')
1363+
elem.text = '<spam>&ham'
1364+
self.assertEqual(ET.tostring(elem, method='html'),
1365+
b'<PlainText><spam>&ham')
1366+
13201367
def test_dump_attribute_order(self):
13211368
# See BPO 34160
13221369
e = ET.Element('cirriculum', status='public', company='example')

Lib/xml/etree/ElementTree.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -917,17 +917,20 @@ def _serialize_xml(write, elem, qnames, namespaces,
917917
if elem.tail:
918918
write(_escape_cdata(elem.tail))
919919

920+
_CDATA_CONTENT_ELEMENTS = {"script", "style", "xmp", "iframe", "noembed",
921+
"noframes", "plaintext"}
922+
920923
HTML_EMPTY = {"area", "base", "basefont", "br", "col", "embed", "frame", "hr",
921924
"img", "input", "isindex", "link", "meta", "param", "source",
922-
"track", "wbr"}
925+
"track", "wbr", "plaintext"}
923926

924927
def _serialize_html(write, elem, qnames, namespaces, **kwargs):
925928
tag = elem.tag
926929
text = elem.text
927930
if tag is Comment:
928-
write("<!--%s-->" % _escape_cdata(text))
931+
write("<!--%s-->" % text)
929932
elif tag is ProcessingInstruction:
930-
write("<?%s?>" % _escape_cdata(text))
933+
write("<?%s?>" % text)
931934
else:
932935
tag = qnames[tag]
933936
if tag is None:
@@ -951,16 +954,19 @@ def _serialize_html(write, elem, qnames, namespaces, **kwargs):
951954
for k, v in items:
952955
if isinstance(k, QName):
953956
k = k.text
954-
if isinstance(v, QName):
955-
v = qnames[v.text]
957+
k = qnames[k]
958+
if v is None:
959+
write(" %s" % k) # empty attr
956960
else:
957-
v = _escape_attrib_html(v)
958-
# FIXME: handle boolean attributes
959-
write(" %s=\"%s\"" % (qnames[k], v))
961+
if isinstance(v, QName):
962+
v = qnames[v.text]
963+
else:
964+
v = _escape_attrib_html(v)
965+
write(" %s=\"%s\"" % (k, v))
960966
write(">")
961967
ltag = tag.lower()
962968
if text:
963-
if ltag == "script" or ltag == "style":
969+
if ltag in _CDATA_CONTENT_ELEMENTS:
964970
write(text)
965971
else:
966972
write(_escape_cdata(text))
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix :func:`json.load` not forwarding the *array_hook* argument to
2+
:func:`json.loads`. Patch by Thomas Kowalski.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Fix :mod:`~xml.etree.ElementTree` serialization to HTML. The content of
2+
comments, processing instructions and elements "xmp", "iframe", "noembed",
3+
"noframes", and "plaintext" is no longer escaped. The "plaintext" element no
4+
longer have the closing tag. Add support of empty attributes (with value
5+
``None``).

Modules/pyexpat.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1473,6 +1473,9 @@ pyexpat_encoding_create(const char *name, PyObject *mapping)
14731473
static int
14741474
pyexpat_encoding_convert(void *data, const char *s)
14751475
{
1476+
if (PyErr_Occurred()) {
1477+
return -1;
1478+
}
14761479
pyexpat_encoding_info *info = (pyexpat_encoding_info *)data;
14771480
int i = (unsigned char)s[0];
14781481
assert(info->map[i] < -1);

0 commit comments

Comments
 (0)