From e927b00a53e4179a89c9f9214a68f34bf3a2a0e9 Mon Sep 17 00:00:00 2001 From: santhreal <64453045+santhreal@users.noreply.github.com> Date: Thu, 16 Jul 2026 06:37:24 -0700 Subject: [PATCH] Reject empty bullets option with a clear error An empty bullets sequence (e.g. bullets='' or bullets=[]) made convert_li evaluate bullets[depth % len(bullets)], raising an opaque ZeroDivisionError deep in unordered-list conversion. Validate it at construction and raise a clear ValueError, matching the existing strip/convert check. --- markdownify/__init__.py | 5 +++++ tests/test_args.py | 18 +++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/markdownify/__init__.py b/markdownify/__init__.py index 28cdaf6..ff0a3f2 100644 --- a/markdownify/__init__.py +++ b/markdownify/__init__.py @@ -210,6 +210,11 @@ def __init__(self, **options): if self.options['strip'] is not None and self.options['convert'] is not None: raise ValueError('You may specify either tags to strip or tags to' ' convert, but not both.') + if len(self.options['bullets']) == 0: + # convert_li cycles through bullets with `bullets[depth % len(bullets)]`, + # so an empty bullets sequence raises an opaque ZeroDivisionError deep in + # list conversion. Reject it up front with a clear message instead. + raise ValueError('bullets must contain at least one bullet character.') # If a string or list is passed to bs4_options, assume it is a 'features' specification if not isinstance(self.options['bs4_options'], dict): diff --git a/tests/test_args.py b/tests/test_args.py index 838ef9d..1fa914d 100644 --- a/tests/test_args.py +++ b/tests/test_args.py @@ -2,10 +2,26 @@ Test whitelisting/blacklisting of specific tags. """ -from markdownify import markdownify, LSTRIP, RSTRIP, STRIP, STRIP_ONE +import pytest + +from markdownify import MarkdownConverter, markdownify, LSTRIP, RSTRIP, STRIP, STRIP_ONE from .utils import md +@pytest.mark.parametrize("empty_bullets", ['', [], ()]) +def test_empty_bullets_rejected(empty_bullets): + # An empty bullets sequence has no bullet character to cycle through. It must + # be rejected at construction with a clear ValueError rather than raising an + # opaque ZeroDivisionError later, deep in list conversion. + with pytest.raises(ValueError, match="bullets must contain at least one"): + MarkdownConverter(bullets=empty_bullets) + + +def test_non_empty_bullets_still_accepted(): + assert md('