Skip to content

Commit e9126b9

Browse files
authored
feat(querylist): get() first result (#435)
2 parents 2463af4 + 8faab5e commit e9126b9

File tree

3 files changed

+54
-0
lines changed

3 files changed

+54
-0
lines changed

CHANGES

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ $ pip install --user --upgrade --pre libvcs
1515

1616
<!-- Maintainers, insert changes / features for the next release here -->
1717

18+
### New
19+
20+
- QueryList learned to `.get()` to pick the first result (#435)
21+
22+
- Raises error if no items found (unless `default=` keyword argument passed)
23+
- Raises error if multipe items found
24+
1825
## libvcs 0.20.0 (2022-10-31)
1926

2027
### What's new

src/libvcs/_internal/query_list.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from typing import Any, Callable, Optional, Protocol, TypeVar, Union
1212

1313
T = TypeVar("T", Any, Any)
14+
no_arg = object()
1415

1516

1617
def keygetter(
@@ -370,6 +371,9 @@ class QueryList(list[T]):
370371
>>> query.filter(foods__fruit__in="banana")[0].city
371372
'Tampa'
372373
374+
>>> query.get(foods__fruit__in="banana").city
375+
'Tampa'
376+
373377
**With objects (nested)**:
374378
375379
>>> from typing import Optional
@@ -417,6 +421,9 @@ class QueryList(list[T]):
417421
>>> query.filter(food__fruit__in="banana")[0].city
418422
'Tampa'
419423
424+
>>> query.get(food__fruit__in="banana").city
425+
'Tampa'
426+
420427
>>> query.filter(food__breakfast="waffles")
421428
[Restaurant(place='Chicago suburbs',
422429
city='Elmhurst',
@@ -506,3 +513,18 @@ def val_match(obj: Union[str, list[Any]]) -> bool:
506513
_filter = filter_lookup
507514

508515
return self.__class__(k for k in self if _filter(k))
516+
517+
def get(
518+
self,
519+
matcher: Optional[Union[Callable[[T], bool], T]] = None,
520+
default: Optional[Any] = no_arg,
521+
**kwargs: Any,
522+
) -> Optional[T]:
523+
objs = self.filter(matcher=matcher, **kwargs)
524+
if len(objs) > 1:
525+
raise Exception("Multiple objects returned")
526+
elif len(objs) == 0:
527+
if default == no_arg:
528+
raise Exception("No objects found")
529+
return default
530+
return objs[0]

tests/_internal/test_query_list.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,3 +257,28 @@ def test_filter(
257257
assert qs.filter(filter_expr) == expected_result
258258
else:
259259
assert qs.filter() == expected_result
260+
261+
if (
262+
isinstance(expected_result, list)
263+
and len(expected_result) > 0
264+
and not isinstance(expected_result[0], dict)
265+
):
266+
if len(expected_result) == 1:
267+
if isinstance(filter_expr, dict):
268+
assert qs.get(**filter_expr) == expected_result[0]
269+
else:
270+
assert qs.get(filter_expr) == expected_result[0]
271+
elif len(expected_result) > 1:
272+
with pytest.raises(Exception) as e:
273+
if isinstance(filter_expr, dict):
274+
assert qs.get(**filter_expr) == expected_result
275+
else:
276+
assert qs.get(filter_expr) == expected_result
277+
assert e.match("Multiple objects returned")
278+
elif len(expected_result) == 0:
279+
with pytest.raises(Exception) as e:
280+
if isinstance(filter_expr, dict):
281+
assert qs.get(**filter_expr) == expected_result
282+
else:
283+
assert qs.get(filter_expr) == expected_result
284+
assert e.match("No objects found")

0 commit comments

Comments
 (0)