pyochain 0.5.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyochain/__init__.py +5 -0
- pyochain/_core/__init__.py +23 -0
- pyochain/_core/_format.py +34 -0
- pyochain/_core/_main.py +205 -0
- pyochain/_core/_protocols.py +38 -0
- pyochain/_dict/__init__.py +3 -0
- pyochain/_dict/_filters.py +268 -0
- pyochain/_dict/_groups.py +175 -0
- pyochain/_dict/_iter.py +135 -0
- pyochain/_dict/_joins.py +139 -0
- pyochain/_dict/_main.py +113 -0
- pyochain/_dict/_maps.py +142 -0
- pyochain/_dict/_nested.py +272 -0
- pyochain/_dict/_process.py +204 -0
- pyochain/_iter/__init__.py +3 -0
- pyochain/_iter/_aggregations.py +324 -0
- pyochain/_iter/_booleans.py +227 -0
- pyochain/_iter/_dicts.py +243 -0
- pyochain/_iter/_eager.py +233 -0
- pyochain/_iter/_filters.py +510 -0
- pyochain/_iter/_joins.py +404 -0
- pyochain/_iter/_lists.py +308 -0
- pyochain/_iter/_main.py +466 -0
- pyochain/_iter/_maps.py +360 -0
- pyochain/_iter/_partitions.py +145 -0
- pyochain/_iter/_process.py +366 -0
- pyochain/_iter/_rolling.py +241 -0
- pyochain/_iter/_tuples.py +326 -0
- pyochain/py.typed +0 -0
- pyochain-0.5.3.dist-info/METADATA +261 -0
- pyochain-0.5.3.dist-info/RECORD +32 -0
- pyochain-0.5.3.dist-info/WHEEL +4 -0
pyochain/__init__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from ._format import Peeked, peek, peekn
|
|
2
|
+
from ._main import CommonBase, IterWrapper, MappingWrapper, Pipeable, Wrapper
|
|
3
|
+
from ._protocols import (
|
|
4
|
+
SizedIterable,
|
|
5
|
+
SupportsAllComparisons,
|
|
6
|
+
SupportsKeysAndGetItem,
|
|
7
|
+
SupportsRichComparison,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"MappingWrapper",
|
|
12
|
+
"CommonBase",
|
|
13
|
+
"IterWrapper",
|
|
14
|
+
"Wrapper",
|
|
15
|
+
"SupportsAllComparisons",
|
|
16
|
+
"SupportsRichComparison",
|
|
17
|
+
"SupportsKeysAndGetItem",
|
|
18
|
+
"Peeked",
|
|
19
|
+
"SizedIterable",
|
|
20
|
+
"Pipeable",
|
|
21
|
+
"peek",
|
|
22
|
+
"peekn",
|
|
23
|
+
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from collections.abc import Iterable, Iterator, Mapping
|
|
2
|
+
from pprint import pformat
|
|
3
|
+
from typing import Any, NamedTuple
|
|
4
|
+
|
|
5
|
+
import cytoolz as cz
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Peeked[T](NamedTuple):
|
|
9
|
+
value: T | tuple[T, ...]
|
|
10
|
+
sequence: Iterator[T]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def peekn[T](data: Iterable[T], n: int) -> Iterator[T]:
|
|
14
|
+
peeked = Peeked(*cz.itertoolz.peekn(n, data))
|
|
15
|
+
print(f"Peeked {n} values: {peeked.value}")
|
|
16
|
+
return peeked.sequence
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def peek[T](data: Iterable[T]) -> Iterator[T]:
|
|
20
|
+
peeked = Peeked(*cz.itertoolz.peek(data))
|
|
21
|
+
print(f"Peeked value: {peeked.value}")
|
|
22
|
+
return peeked.sequence
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def dict_repr(
|
|
26
|
+
v: Mapping[Any, Any],
|
|
27
|
+
max_items: int = 20,
|
|
28
|
+
depth: int = 3,
|
|
29
|
+
width: int = 80,
|
|
30
|
+
compact: bool = True,
|
|
31
|
+
) -> str:
|
|
32
|
+
truncated = dict(list(v.items())[:max_items])
|
|
33
|
+
suffix = "..." if len(v) > max_items else ""
|
|
34
|
+
return pformat(truncated, depth=depth, width=width, compact=compact) + suffix
|
pyochain/_core/_main.py
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from collections.abc import Callable, Iterable, Iterator, Sequence
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Concatenate, Self
|
|
6
|
+
|
|
7
|
+
from ._format import dict_repr
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from .._dict import Dict
|
|
11
|
+
from .._iter import Iter, Seq
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Pipeable:
|
|
15
|
+
def pipe[**P, R](
|
|
16
|
+
self,
|
|
17
|
+
func: Callable[Concatenate[Self, P], R],
|
|
18
|
+
*args: P.args,
|
|
19
|
+
**kwargs: P.kwargs,
|
|
20
|
+
) -> R:
|
|
21
|
+
"""Pipe the instance in the function and return the result."""
|
|
22
|
+
return func(self, *args, **kwargs)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class CommonBase[T](ABC, Pipeable):
|
|
26
|
+
"""
|
|
27
|
+
Base class for all wrappers.
|
|
28
|
+
You can subclass this to create your own wrapper types.
|
|
29
|
+
The pipe unwrap method must be implemented to allow piping functions that transform the underlying data type, whilst retaining the wrapper.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
_inner: T
|
|
33
|
+
|
|
34
|
+
__slots__ = ("_inner",)
|
|
35
|
+
|
|
36
|
+
def __init__(self, data: T) -> None:
|
|
37
|
+
self._inner = data
|
|
38
|
+
|
|
39
|
+
@abstractmethod
|
|
40
|
+
def apply[**P](
|
|
41
|
+
self,
|
|
42
|
+
func: Callable[Concatenate[T, P], Any],
|
|
43
|
+
*args: P.args,
|
|
44
|
+
**kwargs: P.kwargs,
|
|
45
|
+
) -> Any:
|
|
46
|
+
raise NotImplementedError
|
|
47
|
+
|
|
48
|
+
def println(self, pretty: bool = True) -> Self:
|
|
49
|
+
"""
|
|
50
|
+
Print the underlying data and return self for chaining.
|
|
51
|
+
|
|
52
|
+
Useful for debugging, simply insert `.println()` in the chain,
|
|
53
|
+
and then removing it will not affect the rest of the chain.
|
|
54
|
+
"""
|
|
55
|
+
from pprint import pprint
|
|
56
|
+
|
|
57
|
+
if pretty:
|
|
58
|
+
self.into(pprint, sort_dicts=False)
|
|
59
|
+
else:
|
|
60
|
+
self.into(print)
|
|
61
|
+
return self
|
|
62
|
+
|
|
63
|
+
def unwrap(self) -> T:
|
|
64
|
+
"""
|
|
65
|
+
Return the underlying data.
|
|
66
|
+
|
|
67
|
+
This is a terminal operation.
|
|
68
|
+
"""
|
|
69
|
+
return self._inner
|
|
70
|
+
|
|
71
|
+
def into[**P, R](
|
|
72
|
+
self,
|
|
73
|
+
func: Callable[Concatenate[T, P], R],
|
|
74
|
+
*args: P.args,
|
|
75
|
+
**kwargs: P.kwargs,
|
|
76
|
+
) -> R:
|
|
77
|
+
"""
|
|
78
|
+
Pass the *unwrapped* underlying data into a function.
|
|
79
|
+
|
|
80
|
+
The result is not wrapped.
|
|
81
|
+
```python
|
|
82
|
+
>>> import pyochain as pc
|
|
83
|
+
>>> pc.Iter.from_(range(5)).into(list)
|
|
84
|
+
[0, 1, 2, 3, 4]
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
This is a core functionality that allows ending the chain whilst keeping the code style consistent.
|
|
88
|
+
"""
|
|
89
|
+
return func(self.unwrap(), *args, **kwargs)
|
|
90
|
+
|
|
91
|
+
def equals_to(self, other: Self | T) -> bool:
|
|
92
|
+
"""
|
|
93
|
+
Check if two records are equal based on their data.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
other: Another instance or corresponding underlying data to compare against.
|
|
97
|
+
|
|
98
|
+
Example:
|
|
99
|
+
```python
|
|
100
|
+
>>> import pyochain as pc
|
|
101
|
+
>>> d1 = pc.Dict({"a": 1, "b": 2})
|
|
102
|
+
>>> d2 = pc.Dict({"a": 1, "b": 2})
|
|
103
|
+
>>> d3 = pc.Dict({"a": 1, "b": 3})
|
|
104
|
+
>>> d1.equals_to(d2)
|
|
105
|
+
True
|
|
106
|
+
>>> d1.equals_to(d3)
|
|
107
|
+
False
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
"""
|
|
111
|
+
other_data = other.unwrap() if isinstance(other, self.__class__) else other
|
|
112
|
+
return self.unwrap() == other_data
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class IterWrapper[T](CommonBase[Iterable[T]]):
|
|
116
|
+
_inner: Iterable[T]
|
|
117
|
+
|
|
118
|
+
def __repr__(self) -> str:
|
|
119
|
+
return f"{self.__class__.__name__}({self.unwrap().__repr__()})"
|
|
120
|
+
|
|
121
|
+
def _eager[**P, U](
|
|
122
|
+
self,
|
|
123
|
+
factory: Callable[Concatenate[Iterable[T], P], Sequence[U]],
|
|
124
|
+
*args: P.args,
|
|
125
|
+
**kwargs: P.kwargs,
|
|
126
|
+
) -> Seq[U]:
|
|
127
|
+
from .._iter import Seq
|
|
128
|
+
|
|
129
|
+
def _(data: Iterable[T]):
|
|
130
|
+
return Seq(factory(data, *args, **kwargs))
|
|
131
|
+
|
|
132
|
+
return self.into(_)
|
|
133
|
+
|
|
134
|
+
def _lazy[**P, U](
|
|
135
|
+
self,
|
|
136
|
+
factory: Callable[Concatenate[Iterable[T], P], Iterator[U]],
|
|
137
|
+
*args: P.args,
|
|
138
|
+
**kwargs: P.kwargs,
|
|
139
|
+
) -> Iter[U]:
|
|
140
|
+
from .._iter import Iter
|
|
141
|
+
|
|
142
|
+
def _(data: Iterable[T]):
|
|
143
|
+
return Iter(factory(data, *args, **kwargs))
|
|
144
|
+
|
|
145
|
+
return self.into(_)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class MappingWrapper[K, V](CommonBase[dict[K, V]]):
|
|
149
|
+
_inner: dict[K, V]
|
|
150
|
+
|
|
151
|
+
def __repr__(self) -> str:
|
|
152
|
+
return f"{self.into(dict_repr)}"
|
|
153
|
+
|
|
154
|
+
def _new[KU, VU](self, func: Callable[[dict[K, V]], dict[KU, VU]]) -> Dict[KU, VU]:
|
|
155
|
+
from .._dict import Dict
|
|
156
|
+
|
|
157
|
+
return Dict(func(self.unwrap()))
|
|
158
|
+
|
|
159
|
+
def apply[**P, KU, VU](
|
|
160
|
+
self,
|
|
161
|
+
func: Callable[Concatenate[dict[K, V], P], dict[KU, VU]],
|
|
162
|
+
*args: P.args,
|
|
163
|
+
**kwargs: P.kwargs,
|
|
164
|
+
) -> Dict[KU, VU]:
|
|
165
|
+
"""
|
|
166
|
+
Apply a function to the underlying dict and return a Dict of the result.
|
|
167
|
+
Allow to pass user defined functions that transform the dict while retaining the Dict wrapper.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
func: Function to apply to the underlying dict.
|
|
171
|
+
*args: Positional arguments to pass to the function.
|
|
172
|
+
**kwargs: Keyword arguments to pass to the function.
|
|
173
|
+
Example:
|
|
174
|
+
```python
|
|
175
|
+
>>> import pyochain as pc
|
|
176
|
+
>>> def invert_dict(d: dict[K, V]) -> dict[V, K]:
|
|
177
|
+
... return {v: k for k, v in d.items()}
|
|
178
|
+
>>> pc.Dict({'a': 1, 'b': 2}).apply(invert_dict).unwrap()
|
|
179
|
+
{1: 'a', 2: 'b'}
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
def _(data: dict[K, V]) -> dict[KU, VU]:
|
|
185
|
+
return func(data, *args, **kwargs)
|
|
186
|
+
|
|
187
|
+
return self._new(_)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class Wrapper[T](CommonBase[T]):
|
|
191
|
+
"""
|
|
192
|
+
A generic Wrapper for any type.
|
|
193
|
+
The pipe into method is implemented to return a Wrapper of the result type.
|
|
194
|
+
|
|
195
|
+
This class is intended for use with other types/implementations that do not support the fluent/functional style.
|
|
196
|
+
This allow the use of a consistent code style across the code base.
|
|
197
|
+
"""
|
|
198
|
+
|
|
199
|
+
def apply[**P, R](
|
|
200
|
+
self,
|
|
201
|
+
func: Callable[Concatenate[T, P], R],
|
|
202
|
+
*args: P.args,
|
|
203
|
+
**kwargs: P.kwargs,
|
|
204
|
+
) -> Wrapper[R]:
|
|
205
|
+
return Wrapper(self.into(func, *args, **kwargs))
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from collections.abc import Iterable, Sized
|
|
2
|
+
from typing import Protocol
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class SupportsDunderLT[T](Protocol):
|
|
6
|
+
def __lt__(self, other: T, /) -> bool: ...
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SupportsDunderGT[T](Protocol):
|
|
10
|
+
def __gt__(self, other: T, /) -> bool: ...
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SupportsDunderLE[T](Protocol):
|
|
14
|
+
def __le__(self, other: T, /) -> bool: ...
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SupportsDunderGE[T](Protocol):
|
|
18
|
+
def __ge__(self, other: T, /) -> bool: ...
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SupportsKeysAndGetItem[K, V](Protocol):
|
|
22
|
+
def keys(self) -> Iterable[K]: ...
|
|
23
|
+
def __getitem__(self, key: K, /) -> V: ...
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class SupportsAllComparisons[T](
|
|
27
|
+
SupportsDunderLT[T],
|
|
28
|
+
SupportsDunderGT[T],
|
|
29
|
+
SupportsDunderLE[T],
|
|
30
|
+
SupportsDunderGE[T],
|
|
31
|
+
Protocol,
|
|
32
|
+
): ...
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
type SupportsRichComparison[T] = SupportsDunderLT[T] | SupportsDunderGT[T]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class SizedIterable[T](Sized, Iterable[T]): ...
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable, Mapping
|
|
4
|
+
from functools import partial
|
|
5
|
+
from typing import TYPE_CHECKING, Any, TypeGuard
|
|
6
|
+
|
|
7
|
+
import cytoolz as cz
|
|
8
|
+
|
|
9
|
+
from .._core import MappingWrapper
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from ._main import Dict
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FilterDict[K, V](MappingWrapper[K, V]):
|
|
16
|
+
def filter_keys(self, predicate: Callable[[K], bool]) -> Dict[K, V]:
|
|
17
|
+
"""
|
|
18
|
+
Return keys that satisfy predicate.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
predicate: Function to determine if a key should be included.
|
|
22
|
+
Example:
|
|
23
|
+
```python
|
|
24
|
+
>>> import pyochain as pc
|
|
25
|
+
>>> d = {1: 2, 2: 3, 3: 4, 4: 5}
|
|
26
|
+
>>> pc.Dict(d).filter_keys(lambda x: x % 2 == 0).unwrap()
|
|
27
|
+
{2: 3, 4: 5}
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
"""
|
|
31
|
+
return self._new(partial(cz.dicttoolz.keyfilter, predicate))
|
|
32
|
+
|
|
33
|
+
def filter_values(self, predicate: Callable[[V], bool]) -> Dict[K, V]:
|
|
34
|
+
"""
|
|
35
|
+
Return items whose values satisfy predicate.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
predicate: Function to determine if a value should be included.
|
|
39
|
+
Example:
|
|
40
|
+
```python
|
|
41
|
+
>>> import pyochain as pc
|
|
42
|
+
>>> d = {1: 2, 2: 3, 3: 4, 4: 5}
|
|
43
|
+
>>> pc.Dict(d).filter_values(lambda x: x % 2 == 0).unwrap()
|
|
44
|
+
{1: 2, 3: 4}
|
|
45
|
+
>>> pc.Dict(d).filter_values(lambda x: not x > 3).unwrap()
|
|
46
|
+
{1: 2, 2: 3}
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
"""
|
|
50
|
+
return self._new(partial(cz.dicttoolz.valfilter, predicate))
|
|
51
|
+
|
|
52
|
+
def filter_items(self, predicate: Callable[[tuple[K, V]], bool]) -> Dict[K, V]:
|
|
53
|
+
"""
|
|
54
|
+
Filter items by predicate applied to (key, value) tuples.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
predicate: Function to determine if a (key, value) pair should be included.
|
|
58
|
+
Example:
|
|
59
|
+
```python
|
|
60
|
+
>>> import pyochain as pc
|
|
61
|
+
>>> def isvalid(item):
|
|
62
|
+
... k, v = item
|
|
63
|
+
... return k % 2 == 0 and v < 4
|
|
64
|
+
>>> d = pc.Dict({1: 2, 2: 3, 3: 4, 4: 5})
|
|
65
|
+
>>>
|
|
66
|
+
>>> d.filter_items(isvalid).unwrap()
|
|
67
|
+
{2: 3}
|
|
68
|
+
>>> d.filter_items(lambda kv: not isvalid(kv)).unwrap()
|
|
69
|
+
{1: 2, 3: 4, 4: 5}
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
"""
|
|
73
|
+
return self._new(partial(cz.dicttoolz.itemfilter, predicate))
|
|
74
|
+
|
|
75
|
+
def filter_kv(self, predicate: Callable[[K, V], bool]) -> Dict[K, V]:
|
|
76
|
+
"""
|
|
77
|
+
Filter items by predicate applied to unpacked (key, value) tuples.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
predicate: Function to determine if a key-value pair should be included.
|
|
81
|
+
Example:
|
|
82
|
+
```python
|
|
83
|
+
>>> import pyochain as pc
|
|
84
|
+
>>> def isvalid(key, value):
|
|
85
|
+
... return key % 2 == 0 and value < 4
|
|
86
|
+
>>> d = pc.Dict({1: 2, 2: 3, 3: 4, 4: 5})
|
|
87
|
+
>>>
|
|
88
|
+
>>> d.filter_kv(isvalid).unwrap()
|
|
89
|
+
{2: 3}
|
|
90
|
+
>>> d.filter_kv(lambda k, v: not isvalid(k, v)).unwrap()
|
|
91
|
+
{1: 2, 3: 4, 4: 5}
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
def _filter_kv(data: dict[K, V]) -> dict[K, V]:
|
|
97
|
+
def _(kv: tuple[K, V]) -> bool:
|
|
98
|
+
return predicate(kv[0], kv[1])
|
|
99
|
+
|
|
100
|
+
return cz.dicttoolz.itemfilter(_, data)
|
|
101
|
+
|
|
102
|
+
return self._new(_filter_kv)
|
|
103
|
+
|
|
104
|
+
def filter_attr[U](self, attr: str, dtype: type[U] = object) -> Dict[K, U]:
|
|
105
|
+
"""
|
|
106
|
+
Filter values that have a given attribute.
|
|
107
|
+
|
|
108
|
+
This does not enforce type checking at runtime for performance considerations.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
attr: Attribute name to check for.
|
|
112
|
+
dtype: Optional expected type of the attribute for type hinting.
|
|
113
|
+
Example:
|
|
114
|
+
```python
|
|
115
|
+
>>> import pyochain as pc
|
|
116
|
+
>>> pc.Dict({"a": "hello", "b": "world", "c": 2, "d": 5}).filter_attr(
|
|
117
|
+
... "capitalize", str
|
|
118
|
+
... ).unwrap()
|
|
119
|
+
{'a': 'hello', 'b': 'world'}
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
def _filter_attr(data: dict[K, V]) -> dict[K, U]:
|
|
125
|
+
def has_attr(x: V) -> TypeGuard[U]:
|
|
126
|
+
return hasattr(x, attr)
|
|
127
|
+
|
|
128
|
+
return cz.dicttoolz.valfilter(has_attr, data)
|
|
129
|
+
|
|
130
|
+
return self._new(_filter_attr)
|
|
131
|
+
|
|
132
|
+
def filter_type[R](self, typ: type[R]) -> Dict[K, R]:
|
|
133
|
+
"""
|
|
134
|
+
Filter values by type.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
typ: Type to filter values by.
|
|
138
|
+
Example:
|
|
139
|
+
```python
|
|
140
|
+
>>> import pyochain as pc
|
|
141
|
+
>>> data = {"a": "one", "b": "two", "c": 3, "d": 4}
|
|
142
|
+
>>> pc.Dict(data).filter_type(str).unwrap()
|
|
143
|
+
{'a': 'one', 'b': 'two'}
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
def _filter_type(data: dict[K, V]) -> dict[K, R]:
|
|
149
|
+
def _(x: V) -> TypeGuard[R]:
|
|
150
|
+
return isinstance(x, typ)
|
|
151
|
+
|
|
152
|
+
return cz.dicttoolz.valfilter(_, data)
|
|
153
|
+
|
|
154
|
+
return self._new(_filter_type)
|
|
155
|
+
|
|
156
|
+
def filter_callable(self) -> Dict[K, Callable[..., Any]]:
|
|
157
|
+
"""
|
|
158
|
+
Filter values that are callable.
|
|
159
|
+
```python
|
|
160
|
+
>>> import pyochain as pc
|
|
161
|
+
>>> def foo():
|
|
162
|
+
... pass
|
|
163
|
+
>>> data = {1: "one", 2: "two", 3: foo, 4: print}
|
|
164
|
+
>>> pc.Dict(data).filter_callable().map_values(lambda x: x.__name__).unwrap()
|
|
165
|
+
{3: 'foo', 4: 'print'}
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
def _filter_callable(data: dict[K, V]) -> dict[K, Callable[..., Any]]:
|
|
171
|
+
def _(x: V) -> TypeGuard[Callable[..., Any]]:
|
|
172
|
+
return callable(x)
|
|
173
|
+
|
|
174
|
+
return cz.dicttoolz.valfilter(_, data)
|
|
175
|
+
|
|
176
|
+
return self._new(_filter_callable)
|
|
177
|
+
|
|
178
|
+
def filter_subclass[U: type[Any], R](
|
|
179
|
+
self: FilterDict[K, U], parent: type[R], keep_parent: bool = True
|
|
180
|
+
) -> Dict[K, type[R]]:
|
|
181
|
+
"""
|
|
182
|
+
Filter values that are subclasses of a given parent class.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
parent: Parent class to check against.
|
|
186
|
+
keep_parent: Whether to include the parent class itself. Defaults to True.
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
>>> import pyochain as pc
|
|
190
|
+
>>> class A:
|
|
191
|
+
... pass
|
|
192
|
+
>>> class B(A):
|
|
193
|
+
... pass
|
|
194
|
+
>>> class C:
|
|
195
|
+
... pass
|
|
196
|
+
>>> def name(cls: type[Any]) -> str:
|
|
197
|
+
... return cls.__name__
|
|
198
|
+
>>> data = pc.Dict({"first": A, "second": B, "third": C})
|
|
199
|
+
>>> data.filter_subclass(A).map_values(name).unwrap()
|
|
200
|
+
{'first': 'A', 'second': 'B'}
|
|
201
|
+
>>> data.filter_subclass(A, keep_parent=False).map_values(name).unwrap()
|
|
202
|
+
{'second': 'B'}
|
|
203
|
+
|
|
204
|
+
```
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
def _filter_subclass(data: dict[K, U]) -> dict[K, type[R]]:
|
|
208
|
+
def _(x: type[Any]) -> TypeGuard[type[R]]:
|
|
209
|
+
if keep_parent:
|
|
210
|
+
return issubclass(x, parent)
|
|
211
|
+
else:
|
|
212
|
+
return issubclass(x, parent) and x is not parent
|
|
213
|
+
|
|
214
|
+
return cz.dicttoolz.valfilter(_, data)
|
|
215
|
+
|
|
216
|
+
return self._new(_filter_subclass)
|
|
217
|
+
|
|
218
|
+
def intersect_keys(self, *others: Mapping[K, V]) -> Dict[K, V]:
|
|
219
|
+
"""
|
|
220
|
+
Keep only keys present in self and all others mappings.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
*others: Other mappings to intersect keys with.
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
>>> import pyochain as pc
|
|
227
|
+
>>> d1 = {"a": 1, "b": 2, "c": 3}
|
|
228
|
+
>>> d2 = {"b": 10, "c": 20}
|
|
229
|
+
>>> d3 = {"c": 30}
|
|
230
|
+
>>> pc.Dict(d1).intersect_keys(d2, d3).unwrap()
|
|
231
|
+
{'c': 3}
|
|
232
|
+
|
|
233
|
+
```
|
|
234
|
+
"""
|
|
235
|
+
|
|
236
|
+
def _intersect_keys(data: dict[K, V]) -> dict[K, V]:
|
|
237
|
+
self_keys = set(data.keys())
|
|
238
|
+
for other in others:
|
|
239
|
+
self_keys.intersection_update(other.keys())
|
|
240
|
+
return {k: data[k] for k in self_keys}
|
|
241
|
+
|
|
242
|
+
return self._new(_intersect_keys)
|
|
243
|
+
|
|
244
|
+
def diff_keys(self, *others: Mapping[K, V]) -> Dict[K, V]:
|
|
245
|
+
"""
|
|
246
|
+
Keep only keys present in self but not in others mappings.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
*others: Other mappings to exclude keys from.
|
|
250
|
+
|
|
251
|
+
```python
|
|
252
|
+
>>> import pyochain as pc
|
|
253
|
+
>>> d1 = {"a": 1, "b": 2, "c": 3}
|
|
254
|
+
>>> d2 = {"b": 10, "d": 40}
|
|
255
|
+
>>> d3 = {"c": 30}
|
|
256
|
+
>>> pc.Dict(d1).diff_keys(d2, d3).unwrap()
|
|
257
|
+
{'a': 1}
|
|
258
|
+
|
|
259
|
+
```
|
|
260
|
+
"""
|
|
261
|
+
|
|
262
|
+
def _diff_keys(data: dict[K, V]) -> dict[K, V]:
|
|
263
|
+
self_keys = set(data.keys())
|
|
264
|
+
for other in others:
|
|
265
|
+
self_keys.difference_update(other.keys())
|
|
266
|
+
return {k: data[k] for k in self_keys}
|
|
267
|
+
|
|
268
|
+
return self._new(_diff_keys)
|