Skip to content

Commit 87efd49

Browse files
authored
Nano-Utils 0.3.1 (#5)
* Added the `disgard_keys` argument to `split_dict()`.
1 parent 9511f17 commit 87efd49

File tree

5 files changed

+71
-28
lines changed

5 files changed

+71
-28
lines changed

CHANGELOG.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ All notable changes to this project will be documented in this file.
66
This project adheres to `Semantic Versioning <http://semver.org/>`_.
77

88

9+
0.3.1
10+
*****
11+
* Added the ``disgard_keys`` argument to ``split_dict()``.
12+
13+
914
0.3.0
1015
*****
1116
* Added the ``SetAttr`` context manager.

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121

2222
################
23-
Nano-Utils 0.3.0
23+
Nano-Utils 0.3.1
2424
################
2525
Utility functions used throughout the various nlesc-nano repositories.
2626

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
# 'signature' – Show typehints as its signature (default)
3333
# 'none' – Do not show typehints
3434
# New in version 2.1.
35-
autodoc_typehints = 'signature'
35+
autodoc_typehints = 'none'
3636

3737

3838
# Output is processed with HTML4 writer.

nanoutils/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""The **Nano-Utils** version."""
22

3-
__version__ = '0.3.0'
3+
__version__ = '0.3.1'

nanoutils/utils.py

Lines changed: 63 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
NamedTuple,
2929
NoReturn,
3030
MutableMapping,
31+
Collection,
3132
cast,
3233
overload,
3334
TYPE_CHECKING
@@ -199,9 +200,16 @@ def __call__(self, *args, **keywords):
199200
return self.func(*args, *self.args, **keywords)
200201

201202

202-
def split_dict(dct: MutableMapping[KT, VT], keep_keys: Iterable[KT],
203-
keep_order: bool = True) -> Dict[KT, VT]:
204-
"""Pop all items from **dct** which are not in **keep_keys** and use them to construct a new dictionary.
203+
@overload
204+
def split_dict(dct: MutableMapping[KT, VT], preserve_order: bool = ..., *,
205+
keep_keys: Iterable[KT]) -> Dict[KT, VT]:
206+
...
207+
@overload # noqa: E302
208+
def split_dict(dct: MutableMapping[KT, VT], preserve_order: bool = ..., *,
209+
disgard_keys: Iterable[KT]) -> Dict[KT, VT]:
210+
...
211+
def split_dict(dct: MutableMapping[KT, VT], preserve_order: bool = False, *, keep_keys: Iterable[KT] = None, disgard_keys: Iterable[KT] = None) -> Dict[KT, VT]: # noqa: E302,E501
212+
r"""Pop all items from **dct** which are in not in **keep_keys** and use them to construct a new dictionary.
205213
206214
Note that, by popping its keys, the passed **dct** will also be modified inplace.
207215
@@ -214,48 +222,78 @@ def split_dict(dct: MutableMapping[KT, VT], keep_keys: Iterable[KT],
214222
>>> dict1 = {1: 'a', 2: 'b', 3: 'c', 4: 'd'}
215223
>>> dict2 = split_dict(dict1, keep_keys={1, 2})
216224
217-
>>> print(dict1)
225+
>>> print(dict1, dict2, sep='\n')
218226
{1: 'a', 2: 'b'}
219-
220-
>>> print(dict2)
221227
{3: 'c', 4: 'd'}
222228
229+
>>> dict3 = split_dict(dict1, disgard_keys={1, 2})
230+
>>> print(dict1, dict3, sep='\n')
231+
{}
232+
{1: 'a', 2: 'b'}
233+
223234
Parameters
224235
----------
225236
dct : :class:`MutableMapping[KT, VT]<typing.MutableMapping>`
226237
A mutable mapping.
227-
keep_keys : :class:`Iterable[KT]<typing.Iterable>`
228-
An iterable with keys that should remain in **dct**.
229-
keep_order : :class:`bool`
238+
preserve_order : :class:`bool`
230239
If :data:`True`, preserve the order of the items in **dct**.
231-
Note that :code:`keep_order = False` is generally faster.
240+
Note that :code:`preserve_order = False` is generally faster.
241+
keep_keys : :class:`Iterable[KT]<typing.Iterable>`, keyword-only
242+
An iterable with keys that should remain in **dct**.
243+
Note that **keep_keys** and **disgard_keys** are mutually exclusive.
244+
disgard_keys : :class:`Iterable[KT]<typing.Iterable>`, keyword-only
245+
An iterable with keys that should be removed from **dct**.
246+
Note that **disgard_keys** and **keep_keys** are mutually exclusive.
232247
233248
Returns
234249
-------
235250
:class:`Dict[KT, VT]<typing.Dict>`
236251
A new dictionaries with all key/value pairs from **dct** not specified in **keep_keys**.
237252
238253
""" # noqa: E501
239-
# The ordering of dict elements is preserved in this manner,
240-
# as opposed to the use of set.difference()
241-
if keep_order:
242-
difference: Iterable[KT] = [k for k in dct if k not in keep_keys]
254+
if keep_keys is disgard_keys is None:
255+
raise TypeError("'keep_keys' and 'disgard_keys' cannot both be unspecified")
256+
elif keep_keys is not None:
257+
iterable = _keep_keys(dct, keep_keys, preserve_order)
258+
elif disgard_keys is not None:
259+
iterable = _disgard_keys(dct, disgard_keys, preserve_order)
260+
else:
261+
raise TypeError("'keep_keys' and 'disgard_keys' cannot both be specified")
262+
263+
return {k: dct.pop(k) for k in iterable}
264+
265+
266+
def _keep_keys(dct: Mapping[KT, VT], keep_keys: Iterable[KT],
267+
preserve_order: bool = False) -> Collection[KT]:
268+
"""A helper function for :func:`split_dict`; used when :code:`keep_keys is not None`."""
269+
if preserve_order:
270+
return [k for k in dct if k not in keep_keys]
243271
else:
244272
try:
245-
difference = dct.keys() - keep_keys # type: ignore
273+
return dct.keys() - keep_keys # type: ignore
246274
except (TypeError, AttributeError):
247-
difference = set(dct.keys()).difference(keep_keys)
275+
return set(dct.keys()).difference(keep_keys)
276+
248277

249-
return {k: dct.pop(k) for k in difference}
278+
def _disgard_keys(dct: Mapping[KT, VT], keep_keys: Iterable[KT],
279+
preserve_order: bool = False) -> Collection[KT]:
280+
"""A helper function for :func:`split_dict`; used when :code:`disgard_keys is not None`."""
281+
if preserve_order:
282+
return [k for k in dct if k in keep_keys]
283+
else:
284+
try:
285+
return dct.keys() + keep_keys # type: ignore
286+
except TypeError:
287+
return set(dct.keys()).union(keep_keys)
250288

251289

252290
@overload
253-
def raise_if(ex: None) -> Callable[[FT], FT]:
291+
def raise_if(exception: None) -> Callable[[FT], FT]:
254292
...
255293
@overload # noqa: E302
256-
def raise_if(ex: BaseException) -> Callable[[Callable], Callable[..., NoReturn]]:
294+
def raise_if(exception: BaseException) -> Callable[[Callable], Callable[..., NoReturn]]:
257295
...
258-
def raise_if(ex: Optional[BaseException]) -> Callable: # noqa: E302
296+
def raise_if(exception: Optional[BaseException]) -> Callable: # noqa: E302
259297
"""A decorator which raises the passed exception whenever calling the decorated function.
260298
261299
Examples
@@ -286,24 +324,24 @@ def raise_if(ex: Optional[BaseException]) -> Callable: # noqa: E302
286324
287325
Parameters
288326
----------
289-
ex : :exc:`BaseException`, optional
327+
exception : :exc:`BaseException`, optional
290328
An exception.
291329
If :data:`None` is passed then the decorated function will be called as usual.
292330
293331
"""
294-
if ex is None:
332+
if exception is None:
295333
def decorator(func: FT):
296334
return func
297335

298-
elif isinstance(ex, BaseException):
336+
elif isinstance(exception, BaseException):
299337
def decorator(func: FT):
300338
@wraps(func)
301339
def wrapper(*args, **kwargs):
302-
raise ex
340+
raise exception
303341
return wrapper
304342

305343
else:
306-
raise TypeError(f"{ex.__class__.__name__!r}")
344+
raise TypeError(f"{exception.__class__.__name__!r}")
307345
return decorator
308346

309347

0 commit comments

Comments
 (0)