Skip to content

Commit 58ae0c7

Browse files
committed
list operations sorting
1 parent 0cb0fb5 commit 58ae0c7

File tree

2 files changed

+139
-0
lines changed

2 files changed

+139
-0
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"""List operation models and helpers
2+
3+
- Ordering: https://google.aip.dev/132#ordering
4+
5+
6+
"""
7+
8+
from enum import Enum
9+
from typing import TYPE_CHECKING, Generic, TypeVar
10+
11+
from pydantic.generics import GenericModel
12+
13+
14+
class OrderDirection(str, Enum):
15+
ASC = "asc"
16+
DESC = "desc"
17+
18+
19+
if TYPE_CHECKING:
20+
from typing import Protocol
21+
22+
class LiteralField(Protocol):
23+
"""Protocol for Literal string types"""
24+
25+
def __str__(self) -> str: ...
26+
27+
TField = TypeVar("TField", bound=LiteralField)
28+
else:
29+
TField = TypeVar("TField", bound=str)
30+
31+
32+
class OrderClause(GenericModel, Generic[TField]):
33+
field: TField
34+
direction: OrderDirection = OrderDirection.ASC
35+
36+
37+
def check_ordering_list(
38+
order_by: list[tuple[TField, OrderDirection]],
39+
) -> list[tuple[TField, OrderDirection]]:
40+
"""Validates ordering list and removes duplicate entries.
41+
42+
Ensures that each field appears at most once. If a field is repeated:
43+
- With the same direction: silently drops the duplicate
44+
- With different directions: raises ValueError
45+
46+
47+
Args:
48+
order_by: List of (field, direction) tuples
49+
50+
Returns:
51+
List with duplicates removed, preserving order of first occurrence
52+
53+
Raises:
54+
ValueError: If a field appears with conflicting directions
55+
"""
56+
seen_fields = {}
57+
unique_order_by = []
58+
59+
for field, direction in order_by:
60+
if field in seen_fields:
61+
# Field already seen - check if direction matches
62+
if seen_fields[field] != direction:
63+
msg = f"Field '{field}' appears with conflicting directions: {seen_fields[field].value} and {direction.value}"
64+
raise ValueError(msg)
65+
# Same field and direction - skip duplicate
66+
continue
67+
68+
# First time seeing this field
69+
seen_fields[field] = direction
70+
unique_order_by.append((field, direction))
71+
72+
return unique_order_by
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import pytest
2+
from models_library.list_operations import OrderDirection, check_ordering_list
3+
4+
5+
def test_check_ordering_list_drops_duplicates_silently():
6+
"""Test that check_ordering_list silently drops duplicate entries with same field and direction"""
7+
8+
# Input with duplicates (same field and direction)
9+
order_by = [
10+
("email", OrderDirection.ASC),
11+
("created", OrderDirection.DESC),
12+
("email", OrderDirection.ASC), # Duplicate - should be dropped
13+
("name", OrderDirection.ASC),
14+
("created", OrderDirection.DESC), # Duplicate - should be dropped
15+
]
16+
17+
result = check_ordering_list(order_by)
18+
19+
# Should return unique entries preserving order of first occurrence
20+
expected = [
21+
("email", OrderDirection.ASC),
22+
("created", OrderDirection.DESC),
23+
("name", OrderDirection.ASC),
24+
]
25+
26+
assert result == expected
27+
28+
29+
def test_check_ordering_list_raises_for_conflicting_directions():
30+
"""Test that check_ordering_list raises ValueError when same field has different directions"""
31+
32+
# Input with same field but different directions
33+
order_by = [
34+
("email", OrderDirection.ASC),
35+
("created", OrderDirection.DESC),
36+
("email", OrderDirection.DESC), # Conflict! Same field, different direction
37+
]
38+
39+
with pytest.raises(ValueError, match="conflicting directions") as exc_info:
40+
check_ordering_list(order_by)
41+
42+
error_msg = str(exc_info.value)
43+
assert "Field 'email' appears with conflicting directions" in error_msg
44+
assert "asc" in error_msg
45+
assert "desc" in error_msg
46+
47+
48+
def test_check_ordering_list_empty_input():
49+
"""Test that check_ordering_list handles empty input correctly"""
50+
51+
result = check_ordering_list([])
52+
assert result == []
53+
54+
55+
def test_check_ordering_list_no_duplicates():
56+
"""Test that check_ordering_list works correctly when there are no duplicates"""
57+
58+
order_by = [
59+
("email", OrderDirection.ASC),
60+
("created", OrderDirection.DESC),
61+
("name", OrderDirection.ASC),
62+
]
63+
64+
result = check_ordering_list(order_by)
65+
66+
# Should return the same list
67+
assert result == order_by

0 commit comments

Comments
 (0)