Skip to content

Commit 61a5a97

Browse files
committed
WIP: Mini queryset
1 parent da20283 commit 61a5a97

File tree

2 files changed

+119
-2
lines changed

2 files changed

+119
-2
lines changed

libvcs/projects/git.py

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@
1515
- [`GitProject.get_git_version`](libvcs.git.GitProject.get_git_version)
1616
""" # NOQA: E501
1717
import dataclasses
18+
import functools
1819
import logging
1920
import pathlib
2021
import re
21-
from typing import Dict, Optional, TypedDict, Union
22+
from typing import Any, Dict, Generic, Optional, TypedDict, TypeVar, Union
2223
from urllib import parse as urlparse
2324

2425
from .. import exc
@@ -27,6 +28,106 @@
2728
logger = logging.getLogger(__name__)
2829

2930

31+
T = TypeVar("T", Any, Any)
32+
33+
34+
def parse_lookup(path, lookup):
35+
"""mykey__endswith("mykey") -> "mykey" else None"""
36+
try:
37+
return path.endswith(lookup) and path.rsplit(lookup)[0]
38+
except Exception:
39+
return None
40+
41+
42+
@functools.total_ordering
43+
@dataclasses.dataclass(eq=False)
44+
class ListQuery(Generic[T]):
45+
data: list[T]
46+
47+
def __eq__(self, other):
48+
return set(self.data) == set(other.data)
49+
50+
def __lt__(self, other):
51+
return set(self.data).issubset(set(other.data))
52+
53+
def filter(self, **kwargs):
54+
if len(kwargs.values()) == 0:
55+
return self
56+
57+
def _filter(obj) -> bool:
58+
for query_lookup, v in kwargs.items():
59+
if (
60+
field := obj.get(parse_lookup(query_lookup, "__contains"), None)
61+
) is not None:
62+
if not field.contains(v):
63+
return False
64+
elif (
65+
field := obj.get(parse_lookup(query_lookup, "__icontains"), None)
66+
) is not None:
67+
if not field.lower().contains(v.lower()):
68+
return False
69+
elif (
70+
field := obj.get(parse_lookup(query_lookup, "__in"), None)
71+
) is not None:
72+
if field not in v:
73+
return False
74+
elif (
75+
field := obj.get(parse_lookup(query_lookup, "__nin"), None)
76+
) is not None:
77+
if field in v:
78+
return False
79+
elif (
80+
field := obj.get(parse_lookup(query_lookup, "__startswith"), None)
81+
) is not None:
82+
if not field.startswith(v):
83+
return False
84+
elif (
85+
field := obj.get(parse_lookup(query_lookup, "__istartswith"), None)
86+
) is not None:
87+
if not obj.lower().startswith(v.lower()):
88+
return False
89+
elif (
90+
field := obj.get(parse_lookup(query_lookup, "__endswith"), None)
91+
) is not None:
92+
if not field.endswith(v):
93+
return False
94+
elif (
95+
field := obj.get(parse_lookup(query_lookup, "__iendswith"), None)
96+
) is not None:
97+
if not field.lower().endswith(v.lower()):
98+
return False
99+
elif (
100+
field := obj.get(parse_lookup(query_lookup, "__regex"), None)
101+
) is not None:
102+
if not re.search(field, v):
103+
return False
104+
elif (
105+
field := obj.get(parse_lookup(query_lookup, "__iregex"), None)
106+
) is not None:
107+
if not re.search(field, v, re.IGNORECASE):
108+
return false
109+
elif (
110+
field := obj.get(parse_lookup(query_lookup, "__iexact"), None)
111+
) is not None:
112+
if not field.lower() != v.lower():
113+
return False
114+
elif (
115+
field := obj.get(parse_lookup(query_lookup, "__exact"), None)
116+
) is not None: # same as else
117+
if field != obj:
118+
return False
119+
else:
120+
if (
121+
field := obj.get(parse_lookup(query_lookup, "__exact"), None)
122+
) is not None: # same as else
123+
if v != obj:
124+
return False
125+
126+
return True
127+
128+
return dataclasses.replace(self, data=[k for k in self.data if _filter(k)])
129+
130+
30131
class GitRemoteDict(TypedDict):
31132
"""For use when hydrating GitProject via dict."""
32133

tests/projects/test_git.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import os
44
import pathlib
55
import textwrap
6-
from typing import Callable
6+
from typing import Callable, Optional
77

88
import pytest
99

@@ -17,6 +17,7 @@
1717
GitProject,
1818
GitRemote,
1919
GitStatus,
20+
ListQuery,
2021
convert_pip_url as git_convert_pip_url,
2122
)
2223
from libvcs.shortcuts import create_project_from_pip_url
@@ -747,3 +748,18 @@ def test_repo_git_remote_checkout(
747748

748749
assert git_repo_checkout_dir.exists()
749750
assert pathlib.Path(git_repo_checkout_dir / ".git").exists()
751+
752+
753+
@pytest.mark.parametrize(
754+
"items,filter_expr,expected_result",
755+
[
756+
[[dict(apple="fruit")], None, ListQuery([dict(apple="fruit")])],
757+
[[1, 2, 3, 4, 5], None, ListQuery([1, 2, 3, 4, 5])],
758+
],
759+
)
760+
def test_ListQuery(items: list, filter_expr: Optional[dict], expected_result: list):
761+
qs = ListQuery(data=items)
762+
if filter_expr is not None:
763+
assert qs.filter(**filter_expr) == expected_result
764+
else:
765+
assert qs.filter() == expected_result

0 commit comments

Comments
 (0)