Skip to content

Commit 1434857

Browse files
committed
list operations
1 parent 06decb8 commit 1434857

File tree

2 files changed

+137
-0
lines changed

2 files changed

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

0 commit comments

Comments
 (0)