Skip to content

Commit 4ebeba6

Browse files
authored
Merge branch 'main' into issue-148294
2 parents 4f8cf05 + d11e9ff commit 4ebeba6

File tree

10 files changed

+81
-16
lines changed

10 files changed

+81
-16
lines changed

Doc/c-api/unicode.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1881,6 +1881,10 @@ object.
18811881
On success, return ``0``.
18821882
On error, set an exception, leave the writer unchanged, and return ``-1``.
18831883
1884+
To write a :class:`str` subclass which overrides the :meth:`~object.__str__`
1885+
method, :c:func:`PyUnicode_FromObject` can be used to get the original
1886+
string.
1887+
18841888
.. c:function:: int PyUnicodeWriter_WriteRepr(PyUnicodeWriter *writer, PyObject *obj)
18851889
18861890
Call :c:func:`PyObject_Repr` on *obj* and write the output into *writer*.

Doc/library/stdtypes.rst

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2775,8 +2775,22 @@ expression support in the :mod:`re` module).
27752775
.. method:: str.swapcase()
27762776

27772777
Return a copy of the string with uppercase characters converted to lowercase and
2778-
vice versa. Note that it is not necessarily true that
2779-
``s.swapcase().swapcase() == s``.
2778+
vice versa. For example:
2779+
2780+
.. doctest::
2781+
2782+
>>> 'Hello World'.swapcase()
2783+
'hELLO wORLD'
2784+
2785+
Note that it is not necessarily true that ``s.swapcase().swapcase() == s``.
2786+
For example:
2787+
2788+
.. doctest::
2789+
2790+
>>> 'straße'.swapcase().swapcase()
2791+
'strasse'
2792+
2793+
See also :meth:`str.lower` and :meth:`str.upper`.
27802794

27812795

27822796
.. method:: str.title()

Lib/test/test_json/test_dump.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,36 @@ def __lt__(self, o):
7777
d[1337] = "true.dat"
7878
self.assertEqual(self.dumps(d, sort_keys=True), '{"1337": "true.dat"}')
7979

80+
def test_dumps_str_subclass(self):
81+
# Don't call obj.__str__() on str subclasses
82+
83+
# str subclass which returns a different string on str(obj)
84+
class StrSubclass(str):
85+
def __str__(self):
86+
return "StrSubclass"
87+
88+
obj = StrSubclass('ascii')
89+
self.assertEqual(self.dumps(obj), '"ascii"')
90+
self.assertEqual(self.dumps([obj]), '["ascii"]')
91+
self.assertEqual(self.dumps({'key': obj}), '{"key": "ascii"}')
92+
93+
obj = StrSubclass('escape\n')
94+
self.assertEqual(self.dumps(obj), '"escape\\n"')
95+
self.assertEqual(self.dumps([obj]), '["escape\\n"]')
96+
self.assertEqual(self.dumps({'key': obj}), '{"key": "escape\\n"}')
97+
98+
obj = StrSubclass('nonascii:é')
99+
self.assertEqual(self.dumps(obj, ensure_ascii=False),
100+
'"nonascii:é"')
101+
self.assertEqual(self.dumps([obj], ensure_ascii=False),
102+
'["nonascii:é"]')
103+
self.assertEqual(self.dumps({'key': obj}, ensure_ascii=False),
104+
'{"key": "nonascii:é"}')
105+
self.assertEqual(self.dumps(obj), '"nonascii:\\u00e9"')
106+
self.assertEqual(self.dumps([obj]), '["nonascii:\\u00e9"]')
107+
self.assertEqual(self.dumps({'key': obj}),
108+
'{"key": "nonascii:\\u00e9"}')
109+
80110

81111
class TestPyDump(TestDump, PyTest): pass
82112

Lib/test/test_json/test_encode_basestring_ascii.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
from test.support import bigaddrspacetest
44

55

6+
# str subclass which returns a different string on str(obj)
7+
class StrSubclass(str):
8+
def __str__(self):
9+
return "StrSubclass"
10+
611
CASES = [
712
('/\\"\ucafe\ubabe\uab98\ufcde\ubcda\uef4a\x08\x0c\n\r\t`1~!@#$%^&*()_+-=[]{}|;:\',./<>?', '"/\\\\\\"\\ucafe\\ubabe\\uab98\\ufcde\\ubcda\\uef4a\\b\\f\\n\\r\\t`1~!@#$%^&*()_+-=[]{}|;:\',./<>?"'),
813
('\u0123\u4567\u89ab\ucdef\uabcd\uef4a', '"\\u0123\\u4567\\u89ab\\ucdef\\uabcd\\uef4a"'),
@@ -14,6 +19,8 @@
1419
('\U0001d120', '"\\ud834\\udd20"'),
1520
('\u03b1\u03a9', '"\\u03b1\\u03a9"'),
1621
("`1~!@#$%^&*()_+-={':[,]}|;.</>?", '"`1~!@#$%^&*()_+-={\':[,]}|;.</>?"'),
22+
# Don't call obj.__str__() on str subclasses
23+
(StrSubclass('ascii'), '"ascii"'),
1724
]
1825

1926
class TestEncodeBasestringAscii:

Lib/test/test_json/test_enum.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ class WeirdNum(float, Enum):
3131
neg_inf = NEG_INF
3232
nan = NAN
3333

34+
class StringEnum(str, Enum):
35+
COLOR = "color"
36+
3437
class TestEnum:
3538

3639
def test_floats(self):
@@ -116,5 +119,11 @@ def test_dict_values(self):
116119
self.assertEqual(nd['j'], NEG_INF)
117120
self.assertTrue(isnan(nd['n']))
118121

122+
def test_str_enum(self):
123+
obj = StringEnum.COLOR
124+
self.assertEqual(self.dumps(obj), '"color"')
125+
self.assertEqual(self.dumps([obj]), '["color"]')
126+
self.assertEqual(self.dumps({'key': obj}), '{"key": "color"}')
127+
119128
class TestPyEnum(TestEnum, PyTest): pass
120129
class TestCEnum(TestEnum, CTest): pass
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:mod:`json`: Fix serialization: no longer call ``str(obj)`` on :class:`str`
2+
subclasses. Patch by Victor Stinner.

Modules/_asynciomodule.c

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -944,8 +944,7 @@ FutureObj_traverse(PyObject *op, visitproc visit, void *arg)
944944
Py_VISIT(fut->fut_cancel_msg);
945945
Py_VISIT(fut->fut_cancelled_exc);
946946
Py_VISIT(fut->fut_awaited_by);
947-
PyObject_VisitManagedDict((PyObject *)fut, visit, arg);
948-
return 0;
947+
return PyObject_VisitManagedDict((PyObject *)fut, visit, arg);
949948
}
950949

951950
/*[clinic input]
@@ -2425,8 +2424,7 @@ TaskObj_traverse(PyObject *op, visitproc visit, void *arg)
24252424
Py_VISIT(fut->fut_cancel_msg);
24262425
Py_VISIT(fut->fut_cancelled_exc);
24272426
Py_VISIT(fut->fut_awaited_by);
2428-
PyObject_VisitManagedDict((PyObject *)fut, visit, arg);
2429-
return 0;
2427+
return PyObject_VisitManagedDict((PyObject *)fut, visit, arg);
24302428
}
24312429

24322430
/*[clinic input]

Modules/_json.c

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,10 @@ write_escaped_ascii(PyUnicodeWriter *writer, PyObject *pystr)
258258
if (PyUnicodeWriter_WriteChar(writer, '"') < 0) {
259259
return -1;
260260
}
261-
if (PyUnicodeWriter_WriteStr(writer, pystr) < 0) {
261+
// gh-148241: Avoid PyUnicodeWriter_WriteStr() which calls str(obj)
262+
// on str subclasses
263+
assert(PyUnicode_IS_ASCII(pystr));
264+
if (PyUnicodeWriter_WriteASCII(writer, input, input_chars) < 0) {
262265
return -1;
263266
}
264267
return PyUnicodeWriter_WriteChar(writer, '"');
@@ -399,7 +402,9 @@ write_escaped_unicode(PyUnicodeWriter *writer, PyObject *pystr)
399402
if (PyUnicodeWriter_WriteChar(writer, '"') < 0) {
400403
return -1;
401404
}
402-
if (PyUnicodeWriter_WriteStr(writer, pystr) < 0) {
405+
// gh-148241: Avoid PyUnicodeWriter_WriteStr() which calls str(obj)
406+
// on str subclasses
407+
if (_PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, pystr) < 0) {
403408
return -1;
404409
}
405410
return PyUnicodeWriter_WriteChar(writer, '"');

Modules/_testcapimodule.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3280,9 +3280,8 @@ typedef struct {
32803280
} ManagedDictObject;
32813281

32823282
int ManagedDict_traverse(PyObject *self, visitproc visit, void *arg) {
3283-
PyObject_VisitManagedDict(self, visit, arg);
32843283
Py_VISIT(Py_TYPE(self));
3285-
return 0;
3284+
return PyObject_VisitManagedDict(self, visit, arg);
32863285
}
32873286

32883287
int ManagedDict_clear(PyObject *self) {

Objects/typevarobject.c

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -500,8 +500,7 @@ typevar_traverse(PyObject *self, visitproc visit, void *arg)
500500
Py_VISIT(tv->evaluate_constraints);
501501
Py_VISIT(tv->default_value);
502502
Py_VISIT(tv->evaluate_default);
503-
PyObject_VisitManagedDict(self, visit, arg);
504-
return 0;
503+
return PyObject_VisitManagedDict(self, visit, arg);
505504
}
506505

507506
static int
@@ -1195,8 +1194,7 @@ paramspec_traverse(PyObject *self, visitproc visit, void *arg)
11951194
Py_VISIT(ps->bound);
11961195
Py_VISIT(ps->default_value);
11971196
Py_VISIT(ps->evaluate_default);
1198-
PyObject_VisitManagedDict(self, visit, arg);
1199-
return 0;
1197+
return PyObject_VisitManagedDict(self, visit, arg);
12001198
}
12011199

12021200
static int
@@ -1692,8 +1690,7 @@ typevartuple_traverse(PyObject *self, visitproc visit, void *arg)
16921690
Py_VISIT(tvt->name);
16931691
Py_VISIT(tvt->default_value);
16941692
Py_VISIT(tvt->evaluate_default);
1695-
PyObject_VisitManagedDict(self, visit, arg);
1696-
return 0;
1693+
return PyObject_VisitManagedDict(self, visit, arg);
16971694
}
16981695

16991696
static int

0 commit comments

Comments
 (0)