pyochain 0.5.0__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.

@@ -0,0 +1,307 @@
1
+ from __future__ import annotations
2
+
3
+ from collections import defaultdict
4
+ from collections.abc import Callable, Mapping
5
+ from functools import partial
6
+ from typing import Any, Self
7
+
8
+ import cytoolz as cz
9
+
10
+ from .._core import SupportsKeysAndGetItem
11
+ from ._exprs import IntoExpr, compute_exprs
12
+ from ._filters import FilterDict
13
+ from ._funcs import dict_repr
14
+ from ._groups import GroupsDict
15
+ from ._iter import IterDict
16
+ from ._joins import JoinsDict
17
+ from ._nested import NestedDict
18
+ from ._process import ProcessDict
19
+
20
+
21
+ class Dict[K, V](
22
+ ProcessDict[K, V],
23
+ IterDict[K, V],
24
+ NestedDict[K, V],
25
+ JoinsDict[K, V],
26
+ FilterDict[K, V],
27
+ GroupsDict[K, V],
28
+ ):
29
+ """
30
+ Wrapper for Python dictionaries with chainable methods.
31
+ """
32
+
33
+ __slots__ = ()
34
+
35
+ def __repr__(self) -> str:
36
+ return f"{self.__class__.__name__}({dict_repr(self.unwrap())})"
37
+
38
+ @staticmethod
39
+ def from_[G, I](data: Mapping[G, I] | SupportsKeysAndGetItem[G, I]) -> Dict[G, I]:
40
+ """
41
+ Create a Dict from a mapping or SupportsKeysAndGetItem.
42
+
43
+ Args:
44
+ data: A mapping or object supporting keys and item access to convert into a Dict.
45
+
46
+ ```python
47
+ >>> import pyochain as pc
48
+ >>> class MyMapping:
49
+ ... def __init__(self):
50
+ ... self._data = {1: "a", 2: "b", 3: "c"}
51
+ ...
52
+ ... def keys(self):
53
+ ... return self._data.keys()
54
+ ...
55
+ ... def __getitem__(self, key):
56
+ ... return self._data[key]
57
+ >>>
58
+ >>> pc.Dict.from_(MyMapping()).unwrap()
59
+ {1: 'a', 2: 'b', 3: 'c'}
60
+
61
+ ```
62
+ """
63
+ return Dict(dict(data))
64
+
65
+ @staticmethod
66
+ def from_object(obj: object) -> Dict[str, Any]:
67
+ """
68
+ Create a Dict from an object's __dict__ attribute.
69
+
70
+ Args:
71
+ obj: The object whose `__dict__` attribute will be used to create the Dict.
72
+
73
+ ```python
74
+ >>> import pyochain as pc
75
+ >>> class Person:
76
+ ... def __init__(self, name: str, age: int):
77
+ ... self.name = name
78
+ ... self.age = age
79
+ >>> person = Person("Alice", 30)
80
+ >>> pc.Dict.from_object(person).unwrap()
81
+ {'name': 'Alice', 'age': 30}
82
+
83
+ ```
84
+ """
85
+ return Dict(obj.__dict__)
86
+
87
+ def select(self: Dict[str, Any], *exprs: IntoExpr) -> Dict[str, Any]:
88
+ """
89
+ Select and alias fields from the dict based on expressions and/or strings.
90
+
91
+ Navigate nested fields using the `pyochain.key` function.
92
+
93
+ - Chain `key.key()` calls to access nested fields.
94
+ - Use `key.apply()` to transform values.
95
+ - Use `key.alias()` to rename fields in the resulting dict.
96
+
97
+ Args:
98
+ *exprs: Expressions or strings to select and alias fields from the dictionary.
99
+
100
+ ```python
101
+ >>> import pyochain as pc
102
+ >>> data = {
103
+ ... "name": "Alice",
104
+ ... "age": 30,
105
+ ... "scores": {"eng": [85, 90, 95], "math": [80, 88, 92]},
106
+ ... }
107
+ >>> scores_expr = pc.key("scores") # save an expression for reuse
108
+ >>> pc.Dict(data).select(
109
+ ... pc.key("name").alias("student_name"),
110
+ ... "age", # shorthand for pc.key("age")
111
+ ... scores_expr.key("math").alias("math_scores"),
112
+ ... scores_expr.key("eng")
113
+ ... .apply(lambda v: pc.Seq(v).mean())
114
+ ... .alias("average_eng_score"),
115
+ ... ).unwrap()
116
+ {'student_name': 'Alice', 'age': 30, 'math_scores': [80, 88, 92], 'average_eng_score': 90}
117
+
118
+ ```
119
+ """
120
+
121
+ def _select(data: dict[str, Any]) -> dict[str, Any]:
122
+ return compute_exprs(exprs, data, {})
123
+
124
+ return self.apply(_select)
125
+
126
+ def with_fields(self: Dict[str, Any], *exprs: IntoExpr) -> Dict[str, Any]:
127
+ """
128
+ Merge aliased expressions into the root dict (overwrite on collision).
129
+
130
+ Args:
131
+ *exprs: Expressions to merge into the root dictionary.
132
+
133
+ ```python
134
+ >>> import pyochain as pc
135
+ >>> data = {
136
+ ... "name": "Alice",
137
+ ... "age": 30,
138
+ ... "scores": {"eng": [85, 90, 95], "math": [80, 88, 92]},
139
+ ... }
140
+ >>> scores_expr = pc.key("scores") # save an expression for reuse
141
+ >>> pc.Dict(data).with_fields(
142
+ ... scores_expr.key("eng")
143
+ ... .apply(lambda v: pc.Seq(v).mean())
144
+ ... .alias("average_eng_score"),
145
+ ... ).unwrap()
146
+ {'name': 'Alice', 'age': 30, 'scores': {'eng': [85, 90, 95], 'math': [80, 88, 92]}, 'average_eng_score': 90}
147
+
148
+ ```
149
+ """
150
+
151
+ def _with_fields(data: dict[str, Any]) -> dict[str, Any]:
152
+ return compute_exprs(exprs, data, data.copy())
153
+
154
+ return self.apply(_with_fields)
155
+
156
+ def map_keys[T](self, func: Callable[[K], T]) -> Dict[T, V]:
157
+ """
158
+ Return a Dict with keys transformed by func.
159
+
160
+ Args:
161
+ func: Function to apply to each key in the dictionary.
162
+
163
+ ```python
164
+ >>> import pyochain as pc
165
+ >>> pc.Dict({"Alice": [20, 15, 30], "Bob": [10, 35]}).map_keys(
166
+ ... str.lower
167
+ ... ).unwrap()
168
+ {'alice': [20, 15, 30], 'bob': [10, 35]}
169
+ >>>
170
+ >>> pc.Dict({1: "a"}).map_keys(str).unwrap()
171
+ {'1': 'a'}
172
+
173
+ ```
174
+ """
175
+ return self.apply(partial(cz.dicttoolz.keymap, func))
176
+
177
+ def map_values[T](self, func: Callable[[V], T]) -> Dict[K, T]:
178
+ """
179
+ Return a Dict with values transformed by func.
180
+
181
+ Args:
182
+ func: Function to apply to each value in the dictionary.
183
+
184
+ ```python
185
+ >>> import pyochain as pc
186
+ >>> pc.Dict({"Alice": [20, 15, 30], "Bob": [10, 35]}).map_values(sum).unwrap()
187
+ {'Alice': 65, 'Bob': 45}
188
+ >>>
189
+ >>> pc.Dict({1: 1}).map_values(lambda v: v + 1).unwrap()
190
+ {1: 2}
191
+
192
+ ```
193
+ """
194
+ return self.apply(partial(cz.dicttoolz.valmap, func))
195
+
196
+ def map_items[KR, VR](
197
+ self,
198
+ func: Callable[[tuple[K, V]], tuple[KR, VR]],
199
+ ) -> Dict[KR, VR]:
200
+ """
201
+ Transform (key, value) pairs using a function that takes a (key, value) tuple.
202
+
203
+ Args:
204
+ func: Function to transform each (key, value) pair into a new (key, value) tuple.
205
+
206
+ ```python
207
+ >>> import pyochain as pc
208
+ >>> pc.Dict({"Alice": 10, "Bob": 20}).map_items(
209
+ ... lambda kv: (kv[0].upper(), kv[1] * 2)
210
+ ... ).unwrap()
211
+ {'ALICE': 20, 'BOB': 40}
212
+
213
+ ```
214
+ """
215
+ return self.apply(partial(cz.dicttoolz.itemmap, func))
216
+
217
+ def map_kv[KR, VR](
218
+ self,
219
+ func: Callable[[K, V], tuple[KR, VR]],
220
+ ) -> Dict[KR, VR]:
221
+ """
222
+ Transform (key, value) pairs using a function that takes key and value as separate arguments.
223
+
224
+ Args:
225
+ func: Function to transform each key and value into a new (key, value) tuple.
226
+
227
+ ```python
228
+ >>> import pyochain as pc
229
+ >>> pc.Dict({1: 2}).map_kv(lambda k, v: (k + 1, v * 10)).unwrap()
230
+ {2: 20}
231
+
232
+ ```
233
+ """
234
+
235
+ def _map_kv(data: dict[K, V]) -> dict[KR, VR]:
236
+ def _(kv: tuple[K, V]) -> tuple[KR, VR]:
237
+ return func(kv[0], kv[1])
238
+
239
+ return cz.dicttoolz.itemmap(_, data)
240
+
241
+ return self.apply(_map_kv)
242
+
243
+ def invert(self) -> Dict[V, list[K]]:
244
+ """
245
+ Invert the dictionary, grouping keys by common (and hashable) values.
246
+ ```python
247
+ >>> import pyochain as pc
248
+ >>> d = {"a": 1, "b": 2, "c": 1}
249
+ >>> pc.Dict(d).invert().unwrap()
250
+ {1: ['a', 'c'], 2: ['b']}
251
+
252
+ ```
253
+ """
254
+
255
+ def _invert(data: dict[K, V]) -> dict[V, list[K]]:
256
+ inverted: dict[V, list[K]] = defaultdict(list)
257
+ for k, v in data.items():
258
+ inverted[v].append(k)
259
+ return dict(inverted)
260
+
261
+ return self.apply(_invert)
262
+
263
+ def implode(self) -> Dict[K, list[V]]:
264
+ """
265
+ Nest all the values in lists.
266
+ syntactic sugar for map_values(lambda v: [v])
267
+ ```python
268
+ >>> import pyochain as pc
269
+ >>> pc.Dict({1: 2, 3: 4}).implode().unwrap()
270
+ {1: [2], 3: [4]}
271
+
272
+ ```
273
+ """
274
+
275
+ def _implode(data: dict[K, V]) -> dict[K, list[V]]:
276
+ def _(v: V) -> list[V]:
277
+ return [v]
278
+
279
+ return cz.dicttoolz.valmap(_, data)
280
+
281
+ return self.apply(_implode)
282
+
283
+ def equals_to(self, other: Self | Mapping[Any, Any]) -> bool:
284
+ """
285
+ Check if two records are equal based on their data.
286
+
287
+ Args:
288
+ other: Another Dict or mapping to compare against.
289
+
290
+ Example:
291
+ ```python
292
+ >>> import pyochain as pc
293
+ >>> d1 = pc.Dict({"a": 1, "b": 2})
294
+ >>> d2 = pc.Dict({"a": 1, "b": 2})
295
+ >>> d3 = pc.Dict({"a": 1, "b": 3})
296
+ >>> d1.equals_to(d2)
297
+ True
298
+ >>> d1.equals_to(d3)
299
+ False
300
+
301
+ ```
302
+ """
303
+ return (
304
+ self.unwrap() == other.unwrap()
305
+ if isinstance(other, Dict)
306
+ else self.unwrap() == other
307
+ )
@@ -0,0 +1,218 @@
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, Concatenate
6
+
7
+ import cytoolz as cz
8
+
9
+ from .._core import MappingWrapper
10
+
11
+ if TYPE_CHECKING:
12
+ from .._dict import Dict
13
+
14
+
15
+ class NestedDict[K, V](MappingWrapper[K, V]):
16
+ def struct[**P, R, U: dict[Any, Any]](
17
+ self: NestedDict[K, U],
18
+ func: Callable[Concatenate[Dict[K, U], P], R],
19
+ *args: P.args,
20
+ **kwargs: P.kwargs,
21
+ ) -> Dict[K, R]:
22
+ """
23
+ Apply a function to each value after wrapping it in a Dict.
24
+
25
+ Args:
26
+ func: Function to apply to each value after wrapping it in a Dict.
27
+ *args: Positional arguments to pass to the function.
28
+ **kwargs: Keyword arguments to pass to the function.
29
+
30
+ Syntactic sugar for `map_values(lambda data: func(pc.Dict(data), *args, **kwargs))`
31
+ ```python
32
+ >>> import pyochain as pc
33
+ >>> data = {
34
+ ... "person1": {"name": "Alice", "age": 30, "city": "New York"},
35
+ ... "person2": {"name": "Bob", "age": 25, "city": "Los Angeles"},
36
+ ... }
37
+ >>> pc.Dict(data).struct(lambda d: d.map_keys(str.upper).drop("AGE").unwrap())
38
+ ... # doctest: +NORMALIZE_WHITESPACE
39
+ Dict({
40
+ 'person1': {'NAME': 'Alice', 'CITY': 'New York'},
41
+ 'person2': {'NAME': 'Bob', 'CITY': 'Los Angeles'}
42
+ })
43
+
44
+ ```
45
+ """
46
+ from ._main import Dict
47
+
48
+ def _struct(data: Mapping[K, U]) -> dict[K, R]:
49
+ def _(v: dict[Any, Any]) -> R:
50
+ return func(Dict(v), *args, **kwargs)
51
+
52
+ return cz.dicttoolz.valmap(_, data)
53
+
54
+ return self.apply(_struct)
55
+
56
+ def flatten(
57
+ self: NestedDict[str, Any], sep: str = ".", max_depth: int | None = None
58
+ ) -> Dict[str, Any]:
59
+ """
60
+ Flatten a nested dictionary, concatenating keys with the specified separator.
61
+
62
+ Args:
63
+ sep: Separator to use when concatenating keys
64
+ max_depth: Maximum depth to flatten. If None, flattens completely.
65
+ ```python
66
+ >>> import pyochain as pc
67
+ >>> data = {
68
+ ... "config": {"params": {"retries": 3, "timeout": 30}, "mode": "fast"},
69
+ ... "version": 1.0,
70
+ ... }
71
+ >>> pc.Dict(data).flatten().unwrap()
72
+ {'config.params.retries': 3, 'config.params.timeout': 30, 'config.mode': 'fast', 'version': 1.0}
73
+ >>> pc.Dict(data).flatten(sep="_").unwrap()
74
+ {'config_params_retries': 3, 'config_params_timeout': 30, 'config_mode': 'fast', 'version': 1.0}
75
+ >>> pc.Dict(data).flatten(max_depth=1).unwrap()
76
+ {'config.params': {'retries': 3, 'timeout': 30}, 'config.mode': 'fast', 'version': 1.0}
77
+
78
+ ```
79
+ """
80
+
81
+ def _flatten(
82
+ d: dict[Any, Any], parent_key: str = "", current_depth: int = 1
83
+ ) -> dict[str, Any]:
84
+ items: list[tuple[str, Any]] = []
85
+ for k, v in d.items():
86
+ new_key = parent_key + sep + k if parent_key else k
87
+ if isinstance(v, dict) and (
88
+ max_depth is None or current_depth < max_depth + 1
89
+ ):
90
+ items.extend(
91
+ _flatten(v, new_key, current_depth + 1).items() # type: ignore
92
+ )
93
+ else:
94
+ items.append((new_key, v)) # type: ignore
95
+ return dict(items)
96
+
97
+ return self.apply(_flatten)
98
+
99
+ def with_nested_key(self, *keys: K, value: V) -> Dict[K, V]:
100
+ """
101
+ Set a nested key path and return a new Dict with new, potentially nested, key value pair.
102
+
103
+ Args:
104
+ *keys: Sequence of keys representing the nested path.
105
+ value: Value to set at the specified nested path.
106
+ ```python
107
+ >>> import pyochain as pc
108
+ >>> purchase = {
109
+ ... "name": "Alice",
110
+ ... "order": {"items": ["Apple", "Orange"], "costs": [0.50, 1.25]},
111
+ ... "credit card": "5555-1234-1234-1234",
112
+ ... }
113
+ >>> pc.Dict(purchase).with_nested_key(
114
+ ... "order", "costs", value=[0.25, 1.00]
115
+ ... ).unwrap()
116
+ {'name': 'Alice', 'order': {'items': ['Apple', 'Orange'], 'costs': [0.25, 1.0]}, 'credit card': '5555-1234-1234-1234'}
117
+
118
+ ```
119
+ """
120
+ return self.apply(cz.dicttoolz.assoc_in, keys, value=value)
121
+
122
+ def schema(self, max_depth: int = 1) -> Dict[str, Any]:
123
+ """
124
+ Return the schema of the dictionary up to a maximum depth.
125
+
126
+ Args:
127
+ max_depth: Maximum depth to inspect. Nested dicts beyond this depth are marked as 'dict'.
128
+
129
+ When the max depth is reached, nested dicts are marked as 'dict'.
130
+ For lists, only the first element is inspected.
131
+ ```python
132
+ >>> import pyochain as pc
133
+ >>> # Depth 2: we see up to level2
134
+ >>> data = {
135
+ ... "level1": {"level2": {"level3": {"key": "value"}}},
136
+ ... "other_key": 123,
137
+ ... "list_key": [{"sub_key": "sub_value"}],
138
+ ... }
139
+ >>> pc.Dict(data).schema(max_depth=1).unwrap()
140
+ {'level1': 'dict', 'other_key': 'int', 'list_key': 'list'}
141
+ >>> pc.Dict(data).schema(max_depth=2).unwrap()
142
+ {'level1': {'level2': 'dict'}, 'other_key': 'int', 'list_key': 'dict'}
143
+ >>>
144
+ >>> # Depth 3: we see up to level3
145
+ >>> pc.Dict(data).schema(max_depth=3).unwrap()
146
+ {'level1': {'level2': {'level3': 'dict'}}, 'other_key': 'int', 'list_key': {'sub_key': 'str'}}
147
+
148
+ ```
149
+ """
150
+
151
+ def _schema(data: dict[Any, Any]) -> Any:
152
+ def _recurse_schema(node: Any, current_depth: int) -> Any:
153
+ if isinstance(node, dict):
154
+ if current_depth >= max_depth:
155
+ return "dict"
156
+ return {
157
+ k: _recurse_schema(v, current_depth + 1)
158
+ for k, v in node.items() # type: ignore
159
+ }
160
+ elif cz.itertoolz.isiterable(node):
161
+ if current_depth >= max_depth:
162
+ return type(node).__name__
163
+ return _recurse_schema(cz.itertoolz.first(node), current_depth + 1)
164
+ else:
165
+ return type(node).__name__
166
+
167
+ return _recurse_schema(data, 0)
168
+
169
+ return self.apply(_schema)
170
+
171
+ def pluck[U: str | int](self: NestedDict[U, Any], *keys: str) -> Dict[U, Any]:
172
+ """
173
+ Extract values from nested dictionaries using a sequence of keys.
174
+
175
+ Args:
176
+ *keys: Sequence of keys to extract values from the nested dictionaries.
177
+ ```python
178
+ >>> import pyochain as pc
179
+ >>> data = {
180
+ ... "person1": {"name": "Alice", "age": 30},
181
+ ... "person2": {"name": "Bob", "age": 25},
182
+ ... }
183
+ >>> pc.Dict(data).pluck("name").unwrap()
184
+ {'person1': 'Alice', 'person2': 'Bob'}
185
+
186
+ ```
187
+ """
188
+
189
+ getter = partial(cz.dicttoolz.get_in, keys)
190
+
191
+ def _pluck(data: Mapping[U, Any]) -> dict[U, Any]:
192
+ return cz.dicttoolz.valmap(getter, data)
193
+
194
+ return self.apply(_pluck)
195
+
196
+ def get_in(self, *keys: K, default: Any = None) -> Any:
197
+ """
198
+ Retrieve a value from a nested dictionary structure.
199
+
200
+ Args:
201
+ *keys: Sequence of keys representing the nested path to retrieve the value.
202
+ default: Default value to return if the keys do not exist.
203
+
204
+ ```python
205
+ >>> import pyochain as pc
206
+ >>> data = {"a": {"b": {"c": 1}}}
207
+ >>> pc.Dict(data).get_in("a", "b", "c")
208
+ 1
209
+ >>> pc.Dict(data).get_in("a", "x", default="Not Found")
210
+ 'Not Found'
211
+
212
+ ```
213
+ """
214
+
215
+ def _get_in(data: Mapping[K, V]) -> Any:
216
+ return cz.dicttoolz.get_in(keys, data, default)
217
+
218
+ return self.into(_get_in)
@@ -0,0 +1,171 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable, Mapping
4
+ from typing import TYPE_CHECKING, Any, Concatenate
5
+
6
+ import cytoolz as cz
7
+
8
+ from .._core import MappingWrapper
9
+
10
+ if TYPE_CHECKING:
11
+ from ._main import Dict
12
+
13
+
14
+ class ProcessDict[K, V](MappingWrapper[K, V]):
15
+ def for_each[**P](
16
+ self,
17
+ func: Callable[Concatenate[K, V, P], Any],
18
+ *args: P.args,
19
+ **kwargs: P.kwargs,
20
+ ) -> Dict[K, V]:
21
+ """
22
+ Apply a function to each key-value pair in the dict for side effects.
23
+
24
+ Args:
25
+ func: Function to apply to each key-value pair.
26
+ *args: Positional arguments to pass to the function.
27
+ **kwargs: Keyword arguments to pass to the function.
28
+
29
+ Returns the original Dict unchanged.
30
+ ```python
31
+ >>> import pyochain as pc
32
+ >>> pc.Dict({"a": 1, "b": 2}).for_each(
33
+ ... lambda k, v: print(f"Key: {k}, Value: {v}")
34
+ ... ).unwrap()
35
+ Key: a, Value: 1
36
+ Key: b, Value: 2
37
+ {'a': 1, 'b': 2}
38
+
39
+ ```
40
+ """
41
+
42
+ def _for_each(data: dict[K, V]) -> dict[K, V]:
43
+ for k, v in data.items():
44
+ func(k, v, *args, **kwargs)
45
+ return data
46
+
47
+ return self.apply(_for_each)
48
+
49
+ def update_in(
50
+ self, *keys: K, func: Callable[[V], V], default: V | None = None
51
+ ) -> Dict[K, V]:
52
+ """
53
+ Update value in a (potentially) nested dictionary.
54
+
55
+ Args:
56
+ *keys: Sequence of keys representing the nested path to update.
57
+ func: Function to apply to the value at the specified path.
58
+ default: Default value to use if the path does not exist, by default None
59
+
60
+ Applies the func to the value at the path specified by keys, returning a new Dict with the updated value.
61
+
62
+ If the path does not exist, it will be created with the default value (if provided) before applying func.
63
+ ```python
64
+ >>> import pyochain as pc
65
+ >>> inc = lambda x: x + 1
66
+ >>> pc.Dict({"a": 0}).update_in("a", func=inc).unwrap()
67
+ {'a': 1}
68
+ >>> transaction = {
69
+ ... "name": "Alice",
70
+ ... "purchase": {"items": ["Apple", "Orange"], "costs": [0.50, 1.25]},
71
+ ... "credit card": "5555-1234-1234-1234",
72
+ ... }
73
+ >>> pc.Dict(transaction).update_in("purchase", "costs", func=sum).unwrap()
74
+ {'name': 'Alice', 'purchase': {'items': ['Apple', 'Orange'], 'costs': 1.75}, 'credit card': '5555-1234-1234-1234'}
75
+ >>> # updating a value when k0 is not in d
76
+ >>> pc.Dict({}).update_in(1, 2, 3, func=str, default="bar").unwrap()
77
+ {1: {2: {3: 'bar'}}}
78
+ >>> pc.Dict({1: "foo"}).update_in(2, 3, 4, func=inc, default=0).unwrap()
79
+ {1: 'foo', 2: {3: {4: 1}}}
80
+
81
+ ```
82
+ """
83
+ return self.apply(cz.dicttoolz.update_in, keys, func, default=default)
84
+
85
+ def with_key(self, key: K, value: V) -> Dict[K, V]:
86
+ """
87
+ Return a new Dict with key set to value.
88
+
89
+ Args:
90
+ key: Key to set in the dictionary.
91
+ value: Value to associate with the specified key.
92
+
93
+ Does not modify the initial dictionary.
94
+ ```python
95
+ >>> import pyochain as pc
96
+ >>> pc.Dict({"x": 1}).with_key("x", 2).unwrap()
97
+ {'x': 2}
98
+ >>> pc.Dict({"x": 1}).with_key("y", 3).unwrap()
99
+ {'x': 1, 'y': 3}
100
+ >>> pc.Dict({}).with_key("x", 1).unwrap()
101
+ {'x': 1}
102
+
103
+ ```
104
+ """
105
+ return self.apply(cz.dicttoolz.assoc, key, value)
106
+
107
+ def drop(self, *keys: K) -> Dict[K, V]:
108
+ """
109
+ Return a new Dict with given keys removed.
110
+
111
+ Args:
112
+ *keys: Sequence of keys to remove from the dictionary.
113
+
114
+ New dict has d[key] deleted for each supplied key.
115
+ ```python
116
+ >>> import pyochain as pc
117
+ >>> pc.Dict({"x": 1, "y": 2}).drop("y").unwrap()
118
+ {'x': 1}
119
+ >>> pc.Dict({"x": 1, "y": 2}).drop("y", "x").unwrap()
120
+ {}
121
+ >>> pc.Dict({"x": 1}).drop("y").unwrap() # Ignores missing keys
122
+ {'x': 1}
123
+ >>> pc.Dict({1: 2, 3: 4}).drop(1).unwrap()
124
+ {3: 4}
125
+
126
+ ```
127
+ """
128
+ return self.apply(cz.dicttoolz.dissoc, *keys)
129
+
130
+ def rename(self, mapping: Mapping[K, K]) -> Dict[K, V]:
131
+ """
132
+ Return a new Dict with keys renamed according to the mapping.
133
+
134
+ Args:
135
+ mapping: A dictionary mapping old keys to new keys.
136
+
137
+ Keys not in the mapping are kept as is.
138
+ ```python
139
+ >>> import pyochain as pc
140
+ >>> d = {"a": 1, "b": 2, "c": 3}
141
+ >>> mapping = {"b": "beta", "c": "gamma"}
142
+ >>> pc.Dict(d).rename(mapping).unwrap()
143
+ {'a': 1, 'beta': 2, 'gamma': 3}
144
+
145
+ ```
146
+ """
147
+
148
+ def _rename(data: dict[K, V]) -> dict[K, V]:
149
+ return {mapping.get(k, k): v for k, v in data.items()}
150
+
151
+ return self.apply(_rename)
152
+
153
+ def sort(self, reverse: bool = False) -> Dict[K, V]:
154
+ """
155
+ Sort the dictionary by its keys and return a new Dict.
156
+
157
+ Args:
158
+ reverse: Whether to sort in descending order. Defaults to False.
159
+
160
+ ```python
161
+ >>> import pyochain as pc
162
+ >>> pc.Dict({"b": 2, "a": 1}).sort().unwrap()
163
+ {'a': 1, 'b': 2}
164
+
165
+ ```
166
+ """
167
+
168
+ def _sort(data: dict[K, V]) -> dict[K, V]:
169
+ return dict(sorted(data.items(), reverse=reverse))
170
+
171
+ return self.apply(_sort)
@@ -0,0 +1,3 @@
1
+ from ._main import Iter, Seq
2
+
3
+ __all__ = ["Iter", "Seq"]