pyochain 0.5.1__py3-none-any.whl → 0.5.31__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.

Potentially problematic release.


This version of pyochain might be problematic. Click here for more details.

pyochain/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from ._core import Wrapper
2
- from ._dict import Dict, Expr, key
2
+ from ._dict import Dict
3
3
  from ._iter import Iter, Seq
4
4
 
5
- __all__ = ["Dict", "Iter", "Wrapper", "key", "Seq", "Expr"]
5
+ __all__ = ["Dict", "Iter", "Wrapper", "Seq"]
@@ -1,6 +1,6 @@
1
+ from ._format import Peeked, peek, peekn
1
2
  from ._main import CommonBase, IterWrapper, MappingWrapper, Pipeable, Wrapper
2
3
  from ._protocols import (
3
- Peeked,
4
4
  SizedIterable,
5
5
  SupportsAllComparisons,
6
6
  SupportsKeysAndGetItem,
@@ -18,4 +18,6 @@ __all__ = [
18
18
  "Peeked",
19
19
  "SizedIterable",
20
20
  "Pipeable",
21
+ "peek",
22
+ "peekn",
21
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 CHANGED
@@ -1,9 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from abc import ABC, abstractmethod
4
- from collections.abc import Callable, Collection, Iterable, Iterator
4
+ from collections.abc import Callable, Iterable, Iterator, Sequence
5
5
  from typing import TYPE_CHECKING, Any, Concatenate, Self
6
6
 
7
+ from ._format import dict_repr
8
+
7
9
  if TYPE_CHECKING:
8
10
  from .._dict import Dict
9
11
  from .._iter import Iter, Seq
@@ -27,12 +29,12 @@ class CommonBase[T](ABC, Pipeable):
27
29
  The pipe unwrap method must be implemented to allow piping functions that transform the underlying data type, whilst retaining the wrapper.
28
30
  """
29
31
 
30
- _data: T
32
+ _inner: T
31
33
 
32
- __slots__ = ("_data",)
34
+ __slots__ = ("_inner",)
33
35
 
34
36
  def __init__(self, data: T) -> None:
35
- self._data = data
37
+ self._inner = data
36
38
 
37
39
  @abstractmethod
38
40
  def apply[**P](
@@ -53,9 +55,9 @@ class CommonBase[T](ABC, Pipeable):
53
55
  from pprint import pprint
54
56
 
55
57
  if pretty:
56
- pprint(self.unwrap(), sort_dicts=False)
58
+ self.into(pprint, sort_dicts=False)
57
59
  else:
58
- print(self.unwrap())
60
+ self.into(print)
59
61
  return self
60
62
 
61
63
  def unwrap(self) -> T:
@@ -64,7 +66,7 @@ class CommonBase[T](ABC, Pipeable):
64
66
 
65
67
  This is a terminal operation.
66
68
  """
67
- return self._data
69
+ return self._inner
68
70
 
69
71
  def into[**P, R](
70
72
  self,
@@ -86,57 +88,73 @@ class CommonBase[T](ABC, Pipeable):
86
88
  """
87
89
  return func(self.unwrap(), *args, **kwargs)
88
90
 
89
-
90
- class IterWrapper[T](CommonBase[Iterable[T]]):
91
- _data: Iterable[T]
92
-
93
- def apply[**P, R](
94
- self,
95
- func: Callable[Concatenate[Iterable[T], P], Iterator[R]],
96
- *args: P.args,
97
- **kwargs: P.kwargs,
98
- ) -> Iter[R]:
91
+ def equals_to(self, other: Self | T) -> bool:
99
92
  """
100
- Apply a function to the underlying iterable and return an Iter of the result.
101
- Allow to pass user defined functions that transform the iterable while retaining the Iter wrapper.
93
+ Check if two records are equal based on their data.
94
+
102
95
  Args:
103
- func: Function to apply to the underlying iterable.
104
- *args: Positional arguments to pass to the function.
105
- **kwargs: Keyword arguments to pass to the function.
96
+ other: Another instance or corresponding underlying data to compare against.
106
97
 
107
98
  Example:
108
99
  ```python
109
100
  >>> import pyochain as pc
110
- >>> def double(data: Iterable[int]) -> Iterator[int]:
111
- ... return (x * 2 for x in data)
112
- >>> pc.Iter.from_([1, 2, 3]).apply(double).into(list)
113
- [2, 4, 6]
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
+ ```
114
110
  """
115
- from .._iter import Iter
111
+ other_data = other.unwrap() if isinstance(other, self.__class__) else other
112
+ return self.unwrap() == other_data
116
113
 
117
- return Iter(self.into(func, *args, **kwargs))
118
114
 
119
- def collect(self, factory: Callable[[Iterable[T]], Collection[T]] = list) -> Seq[T]:
120
- """
121
- Collect the elements into a sequence.
122
- Args:
123
- factory: A callable that takes an iterable and returns a collection. Defaults to list.
115
+ class IterWrapper[T](CommonBase[Iterable[T]]):
116
+ _inner: Iterable[T]
124
117
 
125
- Example:
126
- ```python
127
- >>> import pyochain as pc
128
- >>> pc.Iter.from_(range(5)).collect().unwrap()
129
- [0, 1, 2, 3, 4]
118
+ def __repr__(self) -> str:
119
+ return f"{self.__class__.__name__}({self.unwrap().__repr__()})"
130
120
 
131
- ```
132
- """
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]:
133
127
  from .._iter import Seq
134
128
 
135
- return Seq(self.into(factory))
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(_)
136
146
 
137
147
 
138
148
  class MappingWrapper[K, V](CommonBase[dict[K, V]]):
139
- _data: 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()))
140
158
 
141
159
  def apply[**P, KU, VU](
142
160
  self,
@@ -147,6 +165,7 @@ class MappingWrapper[K, V](CommonBase[dict[K, V]]):
147
165
  """
148
166
  Apply a function to the underlying dict and return a Dict of the result.
149
167
  Allow to pass user defined functions that transform the dict while retaining the Dict wrapper.
168
+
150
169
  Args:
151
170
  func: Function to apply to the underlying dict.
152
171
  *args: Positional arguments to pass to the function.
@@ -161,9 +180,11 @@ class MappingWrapper[K, V](CommonBase[dict[K, V]]):
161
180
 
162
181
  ```
163
182
  """
164
- from .._dict import Dict
165
183
 
166
- return Dict(self.into(func, *args, **kwargs))
184
+ def _(data: dict[K, V]) -> dict[KU, VU]:
185
+ return func(data, *args, **kwargs)
186
+
187
+ return self._new(_)
167
188
 
168
189
 
169
190
  class Wrapper[T](CommonBase[T]):
@@ -1,10 +1,5 @@
1
- from collections.abc import Iterable, Iterator, Sized
2
- from typing import NamedTuple, Protocol
3
-
4
-
5
- class Peeked[T](NamedTuple):
6
- value: T | tuple[T, ...]
7
- sequence: Iterator[T]
1
+ from collections.abc import Iterable, Sized
2
+ from typing import Protocol
8
3
 
9
4
 
10
5
  class SupportsDunderLT[T](Protocol):
@@ -1,4 +1,3 @@
1
- from ._exprs import Expr, key
2
1
  from ._main import Dict
3
2
 
4
- __all__ = ["Dict", "key", "Expr"]
3
+ __all__ = ["Dict"]
@@ -15,7 +15,7 @@ if TYPE_CHECKING:
15
15
  class FilterDict[K, V](MappingWrapper[K, V]):
16
16
  def filter_keys(self, predicate: Callable[[K], bool]) -> Dict[K, V]:
17
17
  """
18
- Return a new Dict containing keys that satisfy predicate.
18
+ Return keys that satisfy predicate.
19
19
 
20
20
  Args:
21
21
  predicate: Function to determine if a key should be included.
@@ -28,11 +28,11 @@ class FilterDict[K, V](MappingWrapper[K, V]):
28
28
 
29
29
  ```
30
30
  """
31
- return self.apply(partial(cz.dicttoolz.keyfilter, predicate))
31
+ return self._new(partial(cz.dicttoolz.keyfilter, predicate))
32
32
 
33
33
  def filter_values(self, predicate: Callable[[V], bool]) -> Dict[K, V]:
34
34
  """
35
- Return a new Dict containing items whose values satisfy predicate.
35
+ Return items whose values satisfy predicate.
36
36
 
37
37
  Args:
38
38
  predicate: Function to determine if a value should be included.
@@ -47,12 +47,9 @@ class FilterDict[K, V](MappingWrapper[K, V]):
47
47
 
48
48
  ```
49
49
  """
50
- return self.apply(partial(cz.dicttoolz.valfilter, predicate))
50
+ return self._new(partial(cz.dicttoolz.valfilter, predicate))
51
51
 
52
- def filter_items(
53
- self,
54
- predicate: Callable[[tuple[K, V]], bool],
55
- ) -> Dict[K, V]:
52
+ def filter_items(self, predicate: Callable[[tuple[K, V]], bool]) -> Dict[K, V]:
56
53
  """
57
54
  Filter items by predicate applied to (key, value) tuples.
58
55
 
@@ -73,12 +70,9 @@ class FilterDict[K, V](MappingWrapper[K, V]):
73
70
 
74
71
  ```
75
72
  """
76
- return self.apply(partial(cz.dicttoolz.itemfilter, predicate))
73
+ return self._new(partial(cz.dicttoolz.itemfilter, predicate))
77
74
 
78
- def filter_kv(
79
- self,
80
- predicate: Callable[[K, V], bool],
81
- ) -> Dict[K, V]:
75
+ def filter_kv(self, predicate: Callable[[K, V], bool]) -> Dict[K, V]:
82
76
  """
83
77
  Filter items by predicate applied to unpacked (key, value) tuples.
84
78
 
@@ -105,13 +99,14 @@ class FilterDict[K, V](MappingWrapper[K, V]):
105
99
 
106
100
  return cz.dicttoolz.itemfilter(_, data)
107
101
 
108
- return self.apply(_filter_kv)
102
+ return self._new(_filter_kv)
109
103
 
110
104
  def filter_attr[U](self, attr: str, dtype: type[U] = object) -> Dict[K, U]:
111
105
  """
112
106
  Filter values that have a given attribute.
113
107
 
114
108
  This does not enforce type checking at runtime for performance considerations.
109
+
115
110
  Args:
116
111
  attr: Attribute name to check for.
117
112
  dtype: Optional expected type of the attribute for type hinting.
@@ -132,7 +127,7 @@ class FilterDict[K, V](MappingWrapper[K, V]):
132
127
 
133
128
  return cz.dicttoolz.valfilter(has_attr, data)
134
129
 
135
- return self.apply(_filter_attr)
130
+ return self._new(_filter_attr)
136
131
 
137
132
  def filter_type[R](self, typ: type[R]) -> Dict[K, R]:
138
133
  """
@@ -156,7 +151,7 @@ class FilterDict[K, V](MappingWrapper[K, V]):
156
151
 
157
152
  return cz.dicttoolz.valfilter(_, data)
158
153
 
159
- return self.apply(_filter_type)
154
+ return self._new(_filter_type)
160
155
 
161
156
  def filter_callable(self) -> Dict[K, Callable[..., Any]]:
162
157
  """
@@ -178,7 +173,7 @@ class FilterDict[K, V](MappingWrapper[K, V]):
178
173
 
179
174
  return cz.dicttoolz.valfilter(_, data)
180
175
 
181
- return self.apply(_filter_callable)
176
+ return self._new(_filter_callable)
182
177
 
183
178
  def filter_subclass[U: type[Any], R](
184
179
  self: FilterDict[K, U], parent: type[R], keep_parent: bool = True
@@ -218,11 +213,11 @@ class FilterDict[K, V](MappingWrapper[K, V]):
218
213
 
219
214
  return cz.dicttoolz.valfilter(_, data)
220
215
 
221
- return self.apply(_filter_subclass)
216
+ return self._new(_filter_subclass)
222
217
 
223
218
  def intersect_keys(self, *others: Mapping[K, V]) -> Dict[K, V]:
224
219
  """
225
- Return a new Dict keeping only keys present in self and all others.
220
+ Keep only keys present in self and all others mappings.
226
221
 
227
222
  Args:
228
223
  *others: Other mappings to intersect keys with.
@@ -244,11 +239,11 @@ class FilterDict[K, V](MappingWrapper[K, V]):
244
239
  self_keys.intersection_update(other.keys())
245
240
  return {k: data[k] for k in self_keys}
246
241
 
247
- return self.apply(_intersect_keys)
242
+ return self._new(_intersect_keys)
248
243
 
249
244
  def diff_keys(self, *others: Mapping[K, V]) -> Dict[K, V]:
250
245
  """
251
- Return a new Dict keeping only keys present in self but not in others.
246
+ Keep only keys present in self but not in others mappings.
252
247
 
253
248
  Args:
254
249
  *others: Other mappings to exclude keys from.
@@ -270,4 +265,4 @@ class FilterDict[K, V](MappingWrapper[K, V]):
270
265
  self_keys.difference_update(other.keys())
271
266
  return {k: data[k] for k in self_keys}
272
267
 
273
- return self.apply(_diff_keys)
268
+ return self._new(_diff_keys)
pyochain/_dict/_groups.py CHANGED
@@ -34,7 +34,7 @@ class GroupsDict[K, V](MappingWrapper[K, V]):
34
34
 
35
35
  return cz.dicttoolz.valmap(dict, cz.itertoolz.groupby(_, data.items()))
36
36
 
37
- return self.apply(_group_by_value)
37
+ return self._new(_group_by_value)
38
38
 
39
39
  def group_by_key[G](self, func: Callable[[K], G]) -> Dict[G, dict[K, V]]:
40
40
  """
@@ -58,7 +58,7 @@ class GroupsDict[K, V](MappingWrapper[K, V]):
58
58
 
59
59
  return cz.dicttoolz.valmap(dict, cz.itertoolz.groupby(_, data.items()))
60
60
 
61
- return self.apply(_group_by_key)
61
+ return self._new(_group_by_key)
62
62
 
63
63
  def group_by_key_agg[G, R](
64
64
  self,
@@ -72,7 +72,7 @@ class GroupsDict[K, V](MappingWrapper[K, V]):
72
72
  key_func: Function to determine the group for each key.
73
73
  agg_func: Function to aggregate each sub-dictionary.
74
74
 
75
- This avoids materializing intermediate `Dict` objects if you only need
75
+ This avoids materializing intermediate `dict` objects if you only need
76
76
  an aggregated result for each group.
77
77
  ```python
78
78
  >>> import pyochain as pc
@@ -118,7 +118,7 @@ class GroupsDict[K, V](MappingWrapper[K, V]):
118
118
  groups = cz.itertoolz.groupby(_key_func, data.items())
119
119
  return cz.dicttoolz.valmap(_agg_func, groups)
120
120
 
121
- return self.apply(_group_by_key_agg)
121
+ return self._new(_group_by_key_agg)
122
122
 
123
123
  def group_by_value_agg[G, R](
124
124
  self,
@@ -132,7 +132,7 @@ class GroupsDict[K, V](MappingWrapper[K, V]):
132
132
  value_func: Function to determine the group for each value.
133
133
  agg_func: Function to aggregate each sub-dictionary.
134
134
 
135
- This avoids materializing intermediate `Dict` objects if you only need
135
+ This avoids materializing intermediate `dict` objects if you only need
136
136
  an aggregated result for each group.
137
137
  ```python
138
138
  >>> import pyochain as pc
@@ -143,8 +143,7 @@ class GroupsDict[K, V](MappingWrapper[K, V]):
143
143
  ... agg_func=lambda d: d.iter_keys().count(),
144
144
  ... ).unwrap()
145
145
  {'A': 2, 'B': 1}
146
- >>>
147
- >>> # --- Exemple 2: Agrégation plus complexe ---
146
+ >>> # Second example
148
147
  >>> sales_data = {
149
148
  ... "store_1": "Electronics",
150
149
  ... "store_2": "Groceries",
@@ -173,4 +172,4 @@ class GroupsDict[K, V](MappingWrapper[K, V]):
173
172
  groups = cz.itertoolz.groupby(_key_func, data.items())
174
173
  return cz.dicttoolz.valmap(_agg_func, groups)
175
174
 
176
- return self.apply(_group_by_value_agg)
175
+ return self._new(_group_by_value_agg)
pyochain/_dict/_iter.py CHANGED
@@ -1,14 +1,14 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from collections.abc import Callable, Iterable, Mapping
4
- from typing import TYPE_CHECKING, Concatenate
4
+ from typing import TYPE_CHECKING, Any, Concatenate
5
5
 
6
6
  import cytoolz as cz
7
7
 
8
8
  from .._core import MappingWrapper
9
9
 
10
10
  if TYPE_CHECKING:
11
- from .._iter import Iter
11
+ from .._iter import Iter, Seq
12
12
  from ._main import Dict
13
13
 
14
14
 
@@ -43,15 +43,15 @@ class IterDict[K, V](MappingWrapper[K, V]):
43
43
 
44
44
  def _itr(data: Mapping[K, Iterable[U]]) -> dict[K, R]:
45
45
  def _(v: Iterable[U]) -> R:
46
- return func(Iter.from_(v), *args, **kwargs)
46
+ return func(Iter(iter(v)), *args, **kwargs)
47
47
 
48
48
  return cz.dicttoolz.valmap(_, data)
49
49
 
50
- return self.apply(_itr)
50
+ return self._new(_itr)
51
51
 
52
52
  def iter_keys(self) -> Iter[K]:
53
53
  """
54
- Return a Iter of the dict's keys.
54
+ Return an Iter of the dict's keys.
55
55
  ```python
56
56
  >>> import pyochain as pc
57
57
  >>> pc.Dict({1: 2}).iter_keys().into(list)
@@ -61,7 +61,10 @@ class IterDict[K, V](MappingWrapper[K, V]):
61
61
  """
62
62
  from .._iter import Iter
63
63
 
64
- return Iter.from_(self.unwrap().keys())
64
+ def _keys(data: dict[K, V]) -> Iter[K]:
65
+ return Iter(iter(data.keys()))
66
+
67
+ return self.into(_keys)
65
68
 
66
69
  def iter_values(self) -> Iter[V]:
67
70
  """
@@ -75,11 +78,14 @@ class IterDict[K, V](MappingWrapper[K, V]):
75
78
  """
76
79
  from .._iter import Iter
77
80
 
78
- return Iter.from_(self.unwrap().values())
81
+ def _values(data: dict[K, V]) -> Iter[V]:
82
+ return Iter(iter(data.values()))
83
+
84
+ return self.into(_values)
79
85
 
80
86
  def iter_items(self) -> Iter[tuple[K, V]]:
81
87
  """
82
- Return a Iter of the dict's items.
88
+ Return an Iter of the dict's items.
83
89
  ```python
84
90
  >>> import pyochain as pc
85
91
  >>> pc.Dict({1: 2}).iter_items().into(list)
@@ -89,4 +95,41 @@ class IterDict[K, V](MappingWrapper[K, V]):
89
95
  """
90
96
  from .._iter import Iter
91
97
 
92
- return Iter.from_(self.unwrap().items())
98
+ def _items(data: dict[K, V]) -> Iter[tuple[K, V]]:
99
+ return Iter(iter(data.items()))
100
+
101
+ return self.into(_items)
102
+
103
+ def to_arrays(self) -> Seq[list[Any]]:
104
+ """
105
+ Convert the nested dictionary into a sequence of arrays.
106
+
107
+ The sequence represents all paths from root to leaves.
108
+
109
+ ```python
110
+ >>> import pyochain as pc
111
+ >>> data = {
112
+ ... "a": {"b": 1, "c": 2},
113
+ ... "d": {"e": {"f": 3}},
114
+ ... }
115
+ >>> pc.Dict(data).to_arrays().unwrap()
116
+ [['a', 'b', 1], ['a', 'c', 2], ['d', 'e', 'f', 3]]
117
+
118
+ ```
119
+ """
120
+ from .._iter import Seq
121
+
122
+ def _to_arrays(d: Mapping[Any, Any]) -> list[list[Any]]:
123
+ """from dictutils.pivot"""
124
+ match d:
125
+ case Mapping():
126
+ arr: list[Any] = []
127
+ for k, v in d.items():
128
+ for el in _to_arrays(v):
129
+ arr.append([k] + el)
130
+ return arr
131
+
132
+ case _:
133
+ return [[d]]
134
+
135
+ return Seq(self.into(_to_arrays))
pyochain/_dict/_joins.py CHANGED
@@ -33,7 +33,7 @@ class JoinsDict[K, V](MappingWrapper[K, V]):
33
33
  def _inner_join(data: Mapping[K, V]) -> dict[K, tuple[V, W]]:
34
34
  return {k: (v, other[k]) for k, v in data.items() if k in other}
35
35
 
36
- return self.apply(_inner_join)
36
+ return self._new(_inner_join)
37
37
 
38
38
  def left_join[W](self, other: Mapping[K, W]) -> Dict[K, tuple[V, W | None]]:
39
39
  """
@@ -56,7 +56,7 @@ class JoinsDict[K, V](MappingWrapper[K, V]):
56
56
  def _left_join(data: Mapping[K, V]) -> dict[K, tuple[V, W | None]]:
57
57
  return {k: (v, other.get(k)) for k, v in data.items()}
58
58
 
59
- return self.apply(_left_join)
59
+ return self._new(_left_join)
60
60
 
61
61
  def diff(self, other: Mapping[K, V]) -> Dict[K, tuple[V | None, V | None]]:
62
62
  """
@@ -77,9 +77,7 @@ class JoinsDict[K, V](MappingWrapper[K, V]):
77
77
  ```
78
78
  """
79
79
 
80
- def _diff(
81
- data: Mapping[K, V], other: Mapping[K, V]
82
- ) -> dict[K, tuple[V | None, V | None]]:
80
+ def _diff(data: Mapping[K, V]) -> dict[K, tuple[V | None, V | None]]:
83
81
  all_keys: set[K] = data.keys() | other.keys()
84
82
  diffs: dict[K, tuple[V | None, V | None]] = {}
85
83
  for key in all_keys:
@@ -89,11 +87,11 @@ class JoinsDict[K, V](MappingWrapper[K, V]):
89
87
  diffs[key] = (self_val, other_val)
90
88
  return diffs
91
89
 
92
- return self.apply(_diff, other)
90
+ return self._new(_diff)
93
91
 
94
92
  def merge(self, *others: Mapping[K, V]) -> Dict[K, V]:
95
93
  """
96
- Merge other dicts into this one and return a new Dict.
94
+ Merge other dicts into this one.
97
95
 
98
96
  Args:
99
97
  *others: One or more mappings to merge into the current dictionary.
@@ -108,7 +106,11 @@ class JoinsDict[K, V](MappingWrapper[K, V]):
108
106
 
109
107
  ```
110
108
  """
111
- return self.apply(cz.dicttoolz.merge, *others)
109
+
110
+ def _merge(data: Mapping[K, V]) -> dict[K, V]:
111
+ return cz.dicttoolz.merge(data, *others)
112
+
113
+ return self._new(_merge)
112
114
 
113
115
  def merge_with(
114
116
  self, *others: Mapping[K, V], func: Callable[[Iterable[V]], V]
@@ -134,4 +136,4 @@ class JoinsDict[K, V](MappingWrapper[K, V]):
134
136
  def _merge_with(data: Mapping[K, V]) -> dict[K, V]:
135
137
  return cz.dicttoolz.merge_with(func, data, *others)
136
138
 
137
- return self.apply(_merge_with)
139
+ return self._new(_merge_with)