Skip to content

Commit e85aff4

Browse files
authored
relax speedups str check (#477)
2 parents 9c44ecf + 8cb1691 commit e85aff4

File tree

4 files changed

+46
-2
lines changed

4 files changed

+46
-2
lines changed

CHANGES.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
Version 3.1.0
2+
-------------
3+
4+
Unreleased
5+
6+
- Fix compatibility when ``__str__`` returns a ``str`` subclass. :issue:`472`
7+
8+
19
Version 3.0.1
210
-------------
311

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "MarkupSafe"
3-
version = "3.0.1"
3+
version = "3.1.0.dev"
44
description = "Safely add untrusted strings to HTML/XML markup."
55
readme = "README.md"
66
license = { file = "LICENSE.txt" }

src/markupsafe/_speedups.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ escape_unicode_kind4(PyUnicodeObject *in)
151151
static PyObject*
152152
escape_unicode(PyObject *self, PyObject *s)
153153
{
154-
if (!PyUnicode_CheckExact(s))
154+
if (!PyUnicode_Check(s))
155155
return NULL;
156156

157157
// This check is no longer needed in Python 3.12.

tests/test_escape.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from __future__ import annotations
22

3+
import typing as t
4+
35
import pytest
46

57
from markupsafe import escape
@@ -30,3 +32,37 @@
3032
)
3133
def test_escape(value: str, expect: str) -> None:
3234
assert escape(value) == Markup(expect)
35+
36+
37+
class Proxy:
38+
def __init__(self, value: t.Any) -> None:
39+
self.__value = value
40+
41+
@property # type: ignore[misc]
42+
def __class__(self) -> type[t.Any]:
43+
# Make o.__class__ and isinstance(o, str) see the proxied object.
44+
return self.__value.__class__ # type: ignore[no-any-return]
45+
46+
def __str__(self) -> str:
47+
return str(self.__value)
48+
49+
50+
def test_proxy() -> None:
51+
"""Handle a proxy object that pretends its __class__ is str."""
52+
p = Proxy("test")
53+
assert p.__class__ is str
54+
assert isinstance(p, str)
55+
assert escape(p) == Markup("test")
56+
57+
58+
class ReferenceStr(str):
59+
def __str__(self) -> str:
60+
# This should return a str, but it returns the subclass instead.
61+
return self
62+
63+
64+
def test_subclass() -> None:
65+
"""Handle if str(o) does not return a plain str."""
66+
s = ReferenceStr("test")
67+
assert isinstance(s, str)
68+
assert escape(s) == Markup("test")

0 commit comments

Comments
 (0)