From b367a751bbfafb3c733e9bd902d7a88a52a9a924 Mon Sep 17 00:00:00 2001 From: Tech Lead Agent Date: Mon, 21 Sep 2026 08:25:03 +0700 Subject: [PATCH] lists: add conjunction parameter to natural_list (issue #214) --- src/humanize/lists.py | 23 ++++++++++++++++++----- tests/test_lists.py | 23 +++++++++++++++++++++++ 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/humanize/lists.py b/src/humanize/lists.py index 36e8ab73..66b94e7f 100644 --- a/src/humanize/lists.py +++ b/src/humanize/lists.py @@ -10,10 +10,11 @@ __all__ = ["natural_list"] -def natural_list(items: Iterable[Any]) -> str: +def natural_list(items: Iterable[Any], conjunction: str = "and") -> str: """Natural list. - Convert an iterable of items into a human-readable string with commas and 'and'. + Convert an iterable of items into a human-readable string with commas and + a conjunction ('and' by default, or 'or' for disjunctions). Examples: >>> natural_list(["one", "two", "three"]) @@ -22,19 +23,31 @@ def natural_list(items: Iterable[Any]) -> str: 'one and two' >>> natural_list(["one"]) 'one' + >>> natural_list(["one", "two", "three"], conjunction="or") + 'one, two or three' + >>> natural_list(["one", "two"], conjunction="or") + 'one or two' Args: items (Iterable): An iterable of items. + conjunction (str): The word joining the final item — "and" (default) + or "or". Returns: - str: A string with commas and 'and' in the right places. + str: A string with commas and the conjunction in the right places. + + Raises: + ValueError: If ``conjunction`` is not ``"and"`` or ``"or"``. """ + if conjunction not in ("and", "or"): + msg = f"conjunction must be 'and' or 'or', got {conjunction!r}" + raise ValueError(msg) item_list = [str(item) for item in items] if not item_list: return "" if len(item_list) == 1: return item_list[0] elif len(item_list) == 2: - return f"{item_list[0]} and {item_list[1]}" + return f"{item_list[0]} {conjunction} {item_list[1]}" else: - return ", ".join(item_list[:-1]) + f" and {item_list[-1]}" + return ", ".join(item_list[:-1]) + f" {conjunction} {item_list[-1]}" diff --git a/tests/test_lists.py b/tests/test_lists.py index 9b3c1b77..703629ea 100644 --- a/tests/test_lists.py +++ b/tests/test_lists.py @@ -29,3 +29,26 @@ ) def test_natural_list(test_args: Iterable[Any], expected: str) -> None: assert humanize.natural_list(*test_args) == expected + + +@pytest.mark.parametrize( + ("items", "conjunction", "expected"), + [ + (["one", "two", "three"], "or", "one, two or three"), + (["one", "two"], "or", "one or two"), + (["one"], "or", "one"), + ([], "or", ""), + (["one", "two"], "and", "one and two"), + ], +) +def test_natural_list_conjunction( + items: Iterable[Any], conjunction: str, expected: str +) -> None: + """natural_list honours the ``conjunction`` parameter (issue #214).""" + assert humanize.natural_list(items, conjunction=conjunction) == expected + + +def test_natural_list_conjunction_invalid() -> None: + """A conjunction other than 'and'/'or' is rejected.""" + with pytest.raises(ValueError, match="conjunction must be"): + humanize.natural_list(["one", "two"], conjunction="xor")