Skip to content

Commit 180c476

Browse files
committed
fixup! Use set instead of list for block level elements
1 parent ff23488 commit 180c476

File tree

2 files changed

+58
-91
lines changed

2 files changed

+58
-91
lines changed

markdown/util.py

Lines changed: 41 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,34 @@
3838
_T = TypeVar('_T')
3939

4040

41-
"""
42-
Constants you might want to modify
43-
-----------------------------------------------------------------------------
44-
"""
41+
def deprecated(message: str, stacklevel: int = 2):
42+
"""
43+
Raise a [`DeprecationWarning`][] when wrapped function/method is called.
44+
45+
Usage:
46+
47+
```python
48+
@deprecated("This method will be removed in version X; use Y instead.")
49+
def some_method():
50+
pass
51+
```
52+
"""
53+
def wrapper(func):
54+
@wraps(func)
55+
def deprecated_func(*args, **kwargs):
56+
warnings.warn(
57+
f"'{func.__name__}' is deprecated. {message}",
58+
category=DeprecationWarning,
59+
stacklevel=stacklevel
60+
)
61+
return func(*args, **kwargs)
62+
return deprecated_func
63+
return wrapper
4564

4665

4766
# TODO: Raise errors from list methods in the future.
4867
# Later, remove this class entirely and use a regular set.
49-
class _BlockLevelElements:
68+
class _BlockLevelElements(list):
5069
# This hybrid list/set container exists for backwards compatibility reasons,
5170
# to support using both the `BLOCK_LEVEL_ELEMENTS` global variable (soft-deprecated)
5271
# and the `Markdown.block_level_elements` instance attribute (preferred) as a list or a set.
@@ -57,11 +76,8 @@ def __init__(self, elements: list[str], /) -> None:
5776
self._list = elements.copy()
5877
self._set = set(self._list)
5978

79+
@deprecated("Using block level elements as a list is deprecated, use it as a set instead.")
6080
def __add__(self, other: list[str], /) -> list[str]:
61-
warnings.warn(
62-
"Using block level elements as a list is deprecated, use it as a set instead.",
63-
DeprecationWarning,
64-
)
6581
# Using `+` means user expects a list back.
6682
return self._list + other
6783

@@ -72,29 +88,20 @@ def __and__(self, other: set[str], /) -> set[str]:
7288
def __contains__(self, item: str, /) -> bool:
7389
return item in self._set
7490

91+
@deprecated("Using block level elements as a list is deprecated, use it as a set instead.")
7592
def __delitem__(self, index: int, /) -> None:
76-
warnings.warn(
77-
"Using block level elements as a list is deprecated, use it as a set instead.",
78-
DeprecationWarning,
79-
)
8093
element = self._list[index]
8194
del self._list[index]
8295
# Only remove from set if absent from list.
8396
if element not in self._list:
8497
self._set.remove(element)
8598

99+
@deprecated("Using block level elements as a list is deprecated, use it as a set instead.")
86100
def __getitem__(self, index: int, /) -> str:
87-
warnings.warn(
88-
"Using block level elements as a list is deprecated, use it as a set instead.",
89-
DeprecationWarning,
90-
)
91101
return self._list[index]
92102

103+
@deprecated("Using block level elements as a list is deprecated, use it as a set instead.")
93104
def __iadd__(self, other: list[str], /) -> set[str]:
94-
warnings.warn(
95-
"Using block level elements as a list is deprecated, use it as a set instead.",
96-
DeprecationWarning,
97-
)
98105
# In-place addition should update both list and set.
99106
self._list += other
100107
self._set.update(set(other))
@@ -150,18 +157,12 @@ def __xor__(self, value: set[str], /) -> set[str]:
150157
# Using `^` means user expects a set back.
151158
return self._set ^ value
152159

160+
@deprecated("Using block level elements as a list is deprecated, use it as a set instead.")
153161
def __reversed__(self) -> Iterator[str]:
154-
warnings.warn(
155-
"Using block level elements as a list is deprecated, use it as a set instead.",
156-
DeprecationWarning,
157-
)
158162
return reversed(self._list)
159163

164+
@deprecated("Using block level elements as a list is deprecated, use it as a set instead.")
160165
def __setitem__(self, index: int, value: str, /) -> None:
161-
warnings.warn(
162-
"Using block level elements as a list is deprecated, use it as a set instead.",
163-
DeprecationWarning,
164-
)
165166
# In-place item-setting should update both list and set.
166167
old = self._list[index]
167168
self._list[index] = value
@@ -178,11 +179,8 @@ def add(self, element: str, /) -> None:
178179
self._set.add(element)
179180
self._list.append(element)
180181

182+
@deprecated("Using block level elements as a list is deprecated, use it as a set instead.")
181183
def append(self, element: str, /) -> None:
182-
warnings.warn(
183-
"Using block level elements as a list is deprecated, use it as a set instead.",
184-
DeprecationWarning,
185-
)
186184
# In-place addition should update both list and set.
187185
self._list.append(element)
188186
self._set.add(element)
@@ -195,11 +193,8 @@ def copy(self) -> _BlockLevelElements:
195193
# We're not sure yet whether the user wants to use it as a set or list.
196194
return _BlockLevelElements(self._list)
197195

196+
@deprecated("Using block level elements as a list is deprecated, use it as a set instead.")
198197
def count(self, value: str, /) -> int:
199-
warnings.warn(
200-
"Using block level elements as a list is deprecated, use it as a set instead.",
201-
DeprecationWarning,
202-
)
203198
# Count in list, for backwards compatibility.
204199
# If used as a set, both counts will be the same (1).
205200
return self._list.count(value)
@@ -222,27 +217,18 @@ def discard(self, element: str, /) -> None:
222217
except ValueError:
223218
pass
224219

220+
@deprecated("Using block level elements as a list is deprecated, use it as a set instead.")
225221
def extend(self, elements: list[str], /) -> None:
226-
warnings.warn(
227-
"Using block level elements as a list is deprecated, use it as a set instead.",
228-
DeprecationWarning,
229-
)
230222
# In-place extension should update both list and set.
231223
self._list.extend(elements)
232224
self._set.update(elements)
233225

226+
@deprecated("Using block level elements as a list is deprecated, use it as a set instead.")
234227
def index(self, value, start: int = 0, stop: int = sys.maxsize, /):
235-
warnings.warn(
236-
"Using block level elements as a list is deprecated, use it as a set instead.",
237-
DeprecationWarning,
238-
)
239228
return self._list.index(value, start, stop)
240229

230+
@deprecated("Using block level elements as a list is deprecated, use it as a set instead.")
241231
def insert(self, index: int, element: str, /) -> None:
242-
warnings.warn(
243-
"Using block level elements as a list is deprecated, use it as a set instead.",
244-
DeprecationWarning,
245-
)
246232
# In-place insertion should update both list and set.
247233
self._list.insert(index, element)
248234
self._set.add(element)
@@ -289,24 +275,14 @@ def remove(self, element: str, /) -> None:
289275
self._list.remove(element)
290276
except ValueError:
291277
break
292-
# We raise `ValueError` for backwards compatibility.
293-
try:
294-
self._set.remove(element)
295-
except KeyError:
296-
raise ValueError(f"{element!r} not in list") from None
278+
self._set.remove(element)
297279

280+
@deprecated("Using block level elements as a list is deprecated, use it as a set instead.")
298281
def reverse(self) -> None:
299-
warnings.warn(
300-
"Using block level elements as a list is deprecated, use it as a set instead.",
301-
DeprecationWarning,
302-
)
303282
self._list.reverse()
304283

284+
@deprecated("Using block level elements as a list is deprecated, use it as a set instead.")
305285
def sort(self, /, *, key: Callable | None = None, reverse: bool = False) -> None:
306-
warnings.warn(
307-
"Using block level elements as a list is deprecated, use it as a set instead.",
308-
DeprecationWarning,
309-
)
310286
self._list.sort(key=key, reverse=reverse)
311287

312288
def symmetric_difference(self, other: set[str], /) -> set[str]:
@@ -331,6 +307,9 @@ def update(self, *others: set[str]) -> None:
331307
self._list.extend(element for element in sorted(self._set - set(self._list)))
332308

333309

310+
# Constants you might want to modify
311+
# -----------------------------------------------------------------------------
312+
334313
# Type it as `set[str]` to express our intent for it to be used as such.
335314
# We explicitly lie here, so that users running type checkers will get
336315
# warnings when they use the container as a list. This is a very effective
@@ -402,31 +381,6 @@ def get_installed_extensions():
402381
return metadata.entry_points(group='markdown.extensions')
403382

404383

405-
def deprecated(message: str, stacklevel: int = 2):
406-
"""
407-
Raise a [`DeprecationWarning`][] when wrapped function/method is called.
408-
409-
Usage:
410-
411-
```python
412-
@deprecated("This method will be removed in version X; use Y instead.")
413-
def some_method():
414-
pass
415-
```
416-
"""
417-
def wrapper(func):
418-
@wraps(func)
419-
def deprecated_func(*args, **kwargs):
420-
warnings.warn(
421-
f"'{func.__name__}' is deprecated. {message}",
422-
category=DeprecationWarning,
423-
stacklevel=stacklevel
424-
)
425-
return func(*args, **kwargs)
426-
return deprecated_func
427-
return wrapper
428-
429-
430384
def parseBoolValue(value: str | None, fail_on_errors: bool = True, preserve_none: bool = False) -> bool | None:
431385
"""Parses a string representing a boolean value. If parsing was successful,
432386
returns `True` or `False`. If `preserve_none=True`, returns `True`, `False`,

tests/test_block_level_elements.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -326,8 +326,11 @@ def test_index(self):
326326
ble = _BlockLevelElements(["a", "b", "c"])
327327
with self.assertWarns(DeprecationWarning):
328328
self.assertEqual(ble.index("a"), 0)
329+
with self.assertWarns(DeprecationWarning):
329330
self.assertEqual(ble.index("b"), 1)
331+
with self.assertWarns(DeprecationWarning):
330332
self.assertEqual(ble.index("c"), 2)
333+
with self.assertWarns(DeprecationWarning):
331334
self.assertRaises(ValueError, ble.index, "d")
332335

333336
def test_index_duplicates(self):
@@ -387,17 +390,20 @@ def test_pop(self):
387390
self.assertEqual(ble.pop(), "c")
388391
self.assertEqual(ble._list, ["a", "b"])
389392
self.assertEqual(ble._set, {"a", "b"})
390-
self.assertEqual(ble.pop(0), "a")
393+
with self.assertWarns(DeprecationWarning):
394+
self.assertEqual(ble.pop(0), "a")
391395
self.assertEqual(ble._list, ["b"])
392396
self.assertEqual(ble._set, {"b"})
393-
self.assertRaises(IndexError, ble.pop, 10)
397+
with self.assertWarns(DeprecationWarning):
398+
self.assertRaises(IndexError, ble.pop, 10)
394399

395400
def test_pop_duplicates(self):
396401
ble = _BlockLevelElements(["a", "a", "b", "b"])
397402
self.assertEqual(ble.pop(), "b")
398403
self.assertEqual(ble._list, ["a", "a", "b"])
399404
self.assertEqual(ble._set, {"a", "b"})
400-
self.assertEqual(ble.pop(0), "a")
405+
with self.assertWarns(DeprecationWarning):
406+
self.assertEqual(ble.pop(0), "a")
401407
self.assertEqual(ble._list, ["a", "b"])
402408
self.assertEqual(ble._set, {"a", "b"})
403409
self.assertEqual(ble.pop(), "b")
@@ -409,7 +415,7 @@ def test_remove(self):
409415
ble.remove("b")
410416
self.assertEqual(ble._list, ["a", "c"])
411417
self.assertEqual(ble._set, {"a", "c"})
412-
self.assertRaises(ValueError, ble.remove, "d")
418+
self.assertRaises(KeyError, ble.remove, "d")
413419

414420
def test_remove_duplicates(self):
415421
ble = _BlockLevelElements(["a", "a", "b"])
@@ -468,3 +474,10 @@ def test_update(self):
468474
ble.update({"b", "c"})
469475
self.assertEqual(ble._list, ["a", "b", "c"])
470476
self.assertEqual(ble._set, {"a", "b", "c"})
477+
478+
# Special tests
479+
def test_isinstance(self):
480+
ble = _BlockLevelElements([])
481+
self.assertIsInstance(ble, _BlockLevelElements)
482+
self.assertIsInstance(ble, list)
483+
# self.assertIsInstance(ble, set)

0 commit comments

Comments
 (0)