python-utils 3.8.1__py2.py3-none-any.whl → 3.9.0__py2.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.
python_utils/__about__.py CHANGED
@@ -1,3 +1,15 @@
1
+ """
2
+ This module contains metadata about the `python-utils` package.
3
+
4
+ Attributes:
5
+ __package_name__ (str): The name of the package.
6
+ __author__ (str): The author of the package.
7
+ __author_email__ (str): The email of the author.
8
+ __description__ (str): A brief description of the package.
9
+ __url__ (str): The URL of the package's repository.
10
+ __version__ (str): The current version of the package.
11
+ """
12
+
1
13
  __package_name__: str = 'python-utils'
2
14
  __author__: str = 'Rick van Hattem'
3
15
  __author_email__: str = 'Wolph@wol.ph'
@@ -7,4 +19,4 @@ __description__: str = (
7
19
  )
8
20
  __url__: str = 'https://github.com/WoLpH/python-utils'
9
21
  # Omit type info due to automatic versioning script
10
- __version__ = '3.8.1'
22
+ __version__ = '3.9.0'
python_utils/__init__.py CHANGED
@@ -1,6 +1,56 @@
1
+ """
2
+ This module initializes the `python_utils` package by importing various
3
+ submodules and functions.
4
+
5
+ Submodules:
6
+ aio
7
+ converters
8
+ decorators
9
+ formatters
10
+ generators
11
+ import_
12
+ logger
13
+ terminal
14
+ time
15
+ types
16
+
17
+ Functions:
18
+ acount
19
+ remap
20
+ scale_1024
21
+ to_float
22
+ to_int
23
+ to_str
24
+ to_unicode
25
+ listify
26
+ set_attributes
27
+ raise_exception
28
+ reraise
29
+ camel_to_underscore
30
+ timesince
31
+ abatcher
32
+ batcher
33
+ import_global
34
+ get_terminal_size
35
+ aio_generator_timeout_detector
36
+ aio_generator_timeout_detector_decorator
37
+ aio_timeout_generator
38
+ delta_to_seconds
39
+ delta_to_seconds_or_none
40
+ format_time
41
+ timedelta_to_seconds
42
+ timeout_generator
43
+
44
+ Classes:
45
+ CastedDict
46
+ LazyCastedDict
47
+ UniqueList
48
+ Logged
49
+ LoggerBase
50
+ """
51
+
1
52
  from . import (
2
53
  aio,
3
- compat,
4
54
  converters,
5
55
  decorators,
6
56
  formatters,
@@ -35,7 +85,6 @@ from .time import (
35
85
  __all__ = [
36
86
  'aio',
37
87
  'generators',
38
- 'compat',
39
88
  'converters',
40
89
  'decorators',
41
90
  'formatters',
python_utils/aio.py CHANGED
@@ -1,14 +1,15 @@
1
- '''
2
- Asyncio equivalents to regular Python functions.
1
+ """Asyncio equivalents to regular Python functions."""
3
2
 
4
- '''
5
3
  import asyncio
6
4
  import itertools
5
+ import typing
7
6
 
8
7
  from . import types
9
8
 
10
9
  _N = types.TypeVar('_N', int, float)
11
10
  _T = types.TypeVar('_T')
11
+ _K = types.TypeVar('_K')
12
+ _V = types.TypeVar('_V')
12
13
 
13
14
 
14
15
  async def acount(
@@ -17,7 +18,7 @@ async def acount(
17
18
  delay: float = 0,
18
19
  stop: types.Optional[_N] = None,
19
20
  ) -> types.AsyncIterator[_N]:
20
- '''Asyncio version of itertools.count()'''
21
+ """Asyncio version of itertools.count()."""
21
22
  for item in itertools.count(start, step): # pragma: no branch
22
23
  if stop is not None and item >= stop:
23
24
  break
@@ -26,20 +27,52 @@ async def acount(
26
27
  await asyncio.sleep(delay)
27
28
 
28
29
 
30
+ @typing.overload
29
31
  async def acontainer(
30
32
  iterable: types.Union[
31
33
  types.AsyncIterable[_T],
32
34
  types.Callable[..., types.AsyncIterable[_T]],
33
35
  ],
34
- container: types.Callable[[types.Iterable[_T]], types.Iterable[_T]] = list,
35
- ) -> types.Iterable[_T]:
36
- '''
37
- Asyncio version of list()/set()/tuple()/etc() using an async for loop
36
+ container: types.Type[types.Tuple[_T, ...]],
37
+ ) -> types.Tuple[_T, ...]: ...
38
+
39
+
40
+ @typing.overload
41
+ async def acontainer(
42
+ iterable: types.Union[
43
+ types.AsyncIterable[_T],
44
+ types.Callable[..., types.AsyncIterable[_T]],
45
+ ],
46
+ container: types.Type[types.List[_T]] = list,
47
+ ) -> types.List[_T]: ...
48
+
49
+
50
+ @typing.overload
51
+ async def acontainer(
52
+ iterable: types.Union[
53
+ types.AsyncIterable[_T],
54
+ types.Callable[..., types.AsyncIterable[_T]],
55
+ ],
56
+ container: types.Type[types.Set[_T]],
57
+ ) -> types.Set[_T]: ...
58
+
59
+
60
+ async def acontainer(
61
+ iterable: types.Union[
62
+ types.AsyncIterable[_T],
63
+ types.Callable[..., types.AsyncIterable[_T]],
64
+ ],
65
+ container: types.Callable[
66
+ [types.Iterable[_T]], types.Collection[_T]
67
+ ] = list,
68
+ ) -> types.Collection[_T]:
69
+ """
70
+ Asyncio version of list()/set()/tuple()/etc() using an async for loop.
38
71
 
39
72
  So instead of doing `[item async for item in iterable]` you can do
40
73
  `await acontainer(iterable)`.
41
74
 
42
- '''
75
+ """
43
76
  iterable_: types.AsyncIterable[_T]
44
77
  if callable(iterable):
45
78
  iterable_ = iterable()
@@ -52,3 +85,33 @@ async def acontainer(
52
85
  items.append(item)
53
86
 
54
87
  return container(items)
88
+
89
+
90
+ async def adict(
91
+ iterable: types.Union[
92
+ types.AsyncIterable[types.Tuple[_K, _V]],
93
+ types.Callable[..., types.AsyncIterable[types.Tuple[_K, _V]]],
94
+ ],
95
+ container: types.Callable[
96
+ [types.Iterable[types.Tuple[_K, _V]]], types.Mapping[_K, _V]
97
+ ] = dict,
98
+ ) -> types.Mapping[_K, _V]:
99
+ """
100
+ Asyncio version of dict() using an async for loop.
101
+
102
+ So instead of doing `{key: value async for key, value in iterable}` you
103
+ can do `await adict(iterable)`.
104
+
105
+ """
106
+ iterable_: types.AsyncIterable[types.Tuple[_K, _V]]
107
+ if callable(iterable):
108
+ iterable_ = iterable()
109
+ else:
110
+ iterable_ = iterable
111
+
112
+ item: types.Tuple[_K, _V]
113
+ items: types.List[types.Tuple[_K, _V]] = []
114
+ async for item in iterable_: # pragma: no branch
115
+ items.append(item)
116
+
117
+ return container(items)
@@ -1,3 +1,59 @@
1
+ """
2
+ This module provides custom container classes with enhanced functionality.
3
+
4
+ Classes:
5
+ CastedDictBase: Abstract base class for dictionaries that cast keys and
6
+ values.
7
+ CastedDict: Dictionary that casts keys and values to specified types.
8
+ LazyCastedDict: Dictionary that lazily casts values to specified types upon
9
+ access.
10
+ UniqueList: List that only allows unique values, with configurable behavior
11
+ on duplicates.
12
+ SliceableDeque: Deque that supports slicing and enhanced equality checks.
13
+
14
+ Type Aliases:
15
+ KT: Type variable for dictionary keys.
16
+ VT: Type variable for dictionary values.
17
+ DT: Type alias for a dictionary with keys of type KT and values of type VT.
18
+ KT_cast: Type alias for a callable that casts dictionary keys.
19
+ VT_cast: Type alias for a callable that casts dictionary values.
20
+ HT: Type variable for hashable values in UniqueList.
21
+ T: Type variable for generic types.
22
+ DictUpdateArgs: Union type for arguments that can be used to update a
23
+ dictionary.
24
+ OnDuplicate: Literal type for handling duplicate values in UniqueList.
25
+
26
+ Usage:
27
+ - CastedDict and LazyCastedDict can be used to create dictionaries with
28
+ automatic type casting.
29
+ - UniqueList ensures all elements are unique and can raise an error on
30
+ duplicates.
31
+ - SliceableDeque extends deque with slicing support and enhanced equality
32
+ checks.
33
+
34
+ Examples:
35
+ >>> d = CastedDict(int, int)
36
+ >>> d[1] = 2
37
+ >>> d['3'] = '4'
38
+ >>> d.update({'5': '6'})
39
+ >>> d.update([('7', '8')])
40
+ >>> d
41
+ {1: 2, 3: 4, 5: 6, 7: 8}
42
+
43
+ >>> l = UniqueList(1, 2, 3)
44
+ >>> l.append(4)
45
+ >>> l.append(4)
46
+ >>> l.insert(0, 4)
47
+ >>> l.insert(0, 5)
48
+ >>> l[1] = 10
49
+ >>> l
50
+ [5, 10, 2, 3, 4]
51
+
52
+ >>> d = SliceableDeque([1, 2, 3, 4, 5])
53
+ >>> d[1:4]
54
+ SliceableDeque([2, 3, 4])
55
+ """
56
+
1
57
  # pyright: reportIncompatibleMethodOverride=false
2
58
  import abc
3
59
  import collections
@@ -35,6 +91,26 @@ OnDuplicate = types.Literal['ignore', 'raise']
35
91
 
36
92
 
37
93
  class CastedDictBase(types.Dict[KT, VT], abc.ABC):
94
+ """
95
+ Abstract base class for dictionaries that cast keys and values.
96
+
97
+ Attributes:
98
+ _key_cast (KT_cast[KT]): Callable to cast dictionary keys.
99
+ _value_cast (VT_cast[VT]): Callable to cast dictionary values.
100
+
101
+ Methods:
102
+ __init__(key_cast: KT_cast[KT] = None, value_cast: VT_cast[VT] = None,
103
+ *args: DictUpdateArgs[KT, VT], **kwargs: VT) -> None:
104
+ Initializes the dictionary with optional key and value casting
105
+ callables.
106
+ update(*args: DictUpdateArgs[types.Any, types.Any],
107
+ **kwargs: types.Any) -> None:
108
+ Updates the dictionary with the given arguments.
109
+ __setitem__(key: types.Any, value: types.Any) -> None:
110
+ Sets the item in the dictionary, casting the key if a key cast
111
+ callable is provided.
112
+ """
113
+
38
114
  _key_cast: KT_cast[KT]
39
115
  _value_cast: VT_cast[VT]
40
116
 
@@ -45,6 +121,20 @@ class CastedDictBase(types.Dict[KT, VT], abc.ABC):
45
121
  *args: DictUpdateArgs[KT, VT],
46
122
  **kwargs: VT,
47
123
  ) -> None:
124
+ """
125
+ Initializes the CastedDictBase with optional key and value
126
+ casting callables.
127
+
128
+ Args:
129
+ key_cast (KT_cast[KT], optional): Callable to cast
130
+ dictionary keys. Defaults to None.
131
+ value_cast (VT_cast[VT], optional): Callable to cast
132
+ dictionary values. Defaults to None.
133
+ *args (DictUpdateArgs[KT, VT]): Arguments to initialize
134
+ the dictionary.
135
+ **kwargs (VT): Keyword arguments to initialize the
136
+ dictionary.
137
+ """
48
138
  self._value_cast = value_cast
49
139
  self._key_cast = key_cast
50
140
  self.update(*args, **kwargs)
@@ -52,6 +142,14 @@ class CastedDictBase(types.Dict[KT, VT], abc.ABC):
52
142
  def update(
53
143
  self, *args: DictUpdateArgs[types.Any, types.Any], **kwargs: types.Any
54
144
  ) -> None:
145
+ """
146
+ Updates the dictionary with the given arguments.
147
+
148
+ Args:
149
+ *args (DictUpdateArgs[types.Any, types.Any]): Arguments to update
150
+ the dictionary.
151
+ **kwargs (types.Any): Keyword arguments to update the dictionary.
152
+ """
55
153
  if args:
56
154
  kwargs.update(*args)
57
155
 
@@ -60,6 +158,14 @@ class CastedDictBase(types.Dict[KT, VT], abc.ABC):
60
158
  self[key] = value
61
159
 
62
160
  def __setitem__(self, key: types.Any, value: types.Any) -> None:
161
+ """
162
+ Sets the item in the dictionary, casting the key if a key cast
163
+ callable is provided.
164
+
165
+ Args:
166
+ key (types.Any): The key to set in the dictionary.
167
+ value (types.Any): The value to set in the dictionary.
168
+ """
63
169
  if self._key_cast is not None:
64
170
  key = self._key_cast(key)
65
171
 
@@ -67,7 +173,7 @@ class CastedDictBase(types.Dict[KT, VT], abc.ABC):
67
173
 
68
174
 
69
175
  class CastedDict(CastedDictBase[KT, VT]):
70
- '''
176
+ """
71
177
  Custom dictionary that casts keys and values to the specified typing.
72
178
 
73
179
  Note that you can specify the types for mypy and type hinting with:
@@ -99,9 +205,10 @@ class CastedDict(CastedDictBase[KT, VT]):
99
205
  >>> d.update([('7', '8')])
100
206
  >>> d
101
207
  {1: 2, '3': '4', '5': '6', '7': '8'}
102
- '''
208
+ """
103
209
 
104
210
  def __setitem__(self, key: typing.Any, value: typing.Any) -> None:
211
+ """Sets `key` to `cast(value)` in the dictionary."""
105
212
  if self._value_cast is not None:
106
213
  value = self._value_cast(value)
107
214
 
@@ -109,7 +216,7 @@ class CastedDict(CastedDictBase[KT, VT]):
109
216
 
110
217
 
111
218
  class LazyCastedDict(CastedDictBase[KT, VT]):
112
- '''
219
+ """
113
220
  Custom dictionary that casts keys and lazily casts values to the specified
114
221
  typing. Note that the values are cast only when they are accessed and
115
222
  are not cached between executions.
@@ -152,15 +259,33 @@ class LazyCastedDict(CastedDictBase[KT, VT]):
152
259
  [(1, 2), ('3', '4'), ('5', '6'), ('7', '8')]
153
260
  >>> d['3']
154
261
  '4'
155
- '''
262
+ """
156
263
 
157
- def __setitem__(self, key: types.Any, value: types.Any):
264
+ def __setitem__(self, key: types.Any, value: types.Any) -> None:
265
+ """
266
+ Sets the item in the dictionary, casting the key if a key cast
267
+ callable is provided.
268
+
269
+ Args:
270
+ key (types.Any): The key to set in the dictionary.
271
+ value (types.Any): The value to set in the dictionary.
272
+ """
158
273
  if self._key_cast is not None:
159
274
  key = self._key_cast(key)
160
275
 
161
276
  super().__setitem__(key, value)
162
277
 
163
278
  def __getitem__(self, key: types.Any) -> VT:
279
+ """
280
+ Gets the item from the dictionary, casting the value if a value cast
281
+ callable is provided.
282
+
283
+ Args:
284
+ key (types.Any): The key to get from the dictionary.
285
+
286
+ Returns:
287
+ VT: The value from the dictionary.
288
+ """
164
289
  if self._key_cast is not None:
165
290
  key = self._key_cast(key)
166
291
 
@@ -171,16 +296,32 @@ class LazyCastedDict(CastedDictBase[KT, VT]):
171
296
 
172
297
  return value
173
298
 
174
- def items( # type: ignore
299
+ def items( # type: ignore[override]
175
300
  self,
176
301
  ) -> types.Generator[types.Tuple[KT, VT], None, None]:
302
+ """
303
+ Returns a generator of the dictionary's items, casting the values if a
304
+ value cast callable is provided.
305
+
306
+ Yields:
307
+ types.Generator[types.Tuple[KT, VT], None, None]: A generator of
308
+ the dictionary's items.
309
+ """
177
310
  if self._value_cast is None:
178
311
  yield from super().items()
179
312
  else:
180
313
  for key, value in super().items():
181
314
  yield key, self._value_cast(value)
182
315
 
183
- def values(self) -> types.Generator[VT, None, None]: # type: ignore
316
+ def values(self) -> types.Generator[VT, None, None]: # type: ignore[override]
317
+ """
318
+ Returns a generator of the dictionary's values, casting the values if a
319
+ value cast callable is provided.
320
+
321
+ Yields:
322
+ types.Generator[VT, None, None]: A generator of the dictionary's
323
+ values.
324
+ """
184
325
  if self._value_cast is None:
185
326
  yield from super().values()
186
327
  else:
@@ -189,7 +330,7 @@ class LazyCastedDict(CastedDictBase[KT, VT]):
189
330
 
190
331
 
191
332
  class UniqueList(types.List[HT]):
192
- '''
333
+ """
193
334
  A list that only allows unique values. Duplicate values are ignored by
194
335
  default, but can be configured to raise an exception instead.
195
336
 
@@ -220,7 +361,7 @@ class UniqueList(types.List[HT]):
220
361
  Traceback (most recent call last):
221
362
  ...
222
363
  ValueError: Duplicate value: 4
223
- '''
364
+ """
224
365
 
225
366
  _set: types.Set[HT]
226
367
 
@@ -229,6 +370,14 @@ class UniqueList(types.List[HT]):
229
370
  *args: HT,
230
371
  on_duplicate: OnDuplicate = 'ignore',
231
372
  ):
373
+ """
374
+ Initializes the UniqueList with optional duplicate handling behavior.
375
+
376
+ Args:
377
+ *args (HT): Initial values for the list.
378
+ on_duplicate (OnDuplicate, optional): Behavior on duplicates.
379
+ Defaults to 'ignore'.
380
+ """
232
381
  self.on_duplicate = on_duplicate
233
382
  self._set = set()
234
383
  super().__init__()
@@ -236,6 +385,17 @@ class UniqueList(types.List[HT]):
236
385
  self.append(arg)
237
386
 
238
387
  def insert(self, index: types.SupportsIndex, value: HT) -> None:
388
+ """
389
+ Inserts a value at the specified index, ensuring uniqueness.
390
+
391
+ Args:
392
+ index (types.SupportsIndex): The index to insert the value at.
393
+ value (HT): The value to insert.
394
+
395
+ Raises:
396
+ ValueError: If the value is a duplicate and `on_duplicate` is set
397
+ to 'raise'.
398
+ """
239
399
  if value in self._set:
240
400
  if self.on_duplicate == 'raise':
241
401
  raise ValueError(f'Duplicate value: {value}')
@@ -246,6 +406,16 @@ class UniqueList(types.List[HT]):
246
406
  super().insert(index, value)
247
407
 
248
408
  def append(self, value: HT) -> None:
409
+ """
410
+ Appends a value to the list, ensuring uniqueness.
411
+
412
+ Args:
413
+ value (HT): The value to append.
414
+
415
+ Raises:
416
+ ValueError: If the value is a duplicate and `on_duplicate` is set
417
+ to 'raise'.
418
+ """
249
419
  if value in self._set:
250
420
  if self.on_duplicate == 'raise':
251
421
  raise ValueError(f'Duplicate value: {value}')
@@ -255,22 +425,46 @@ class UniqueList(types.List[HT]):
255
425
  self._set.add(value)
256
426
  super().append(value)
257
427
 
258
- def __contains__(self, item: HT) -> bool: # type: ignore
428
+ def __contains__(self, item: HT) -> bool: # type: ignore[override]
429
+ """
430
+ Checks if the list contains the specified item.
431
+
432
+ Args:
433
+ item (HT): The item to check for.
434
+
435
+ Returns:
436
+ bool: True if the item is in the list, False otherwise.
437
+ """
259
438
  return item in self._set
260
439
 
261
440
  @typing.overload
262
- def __setitem__(self, indices: types.SupportsIndex, values: HT) -> None:
263
- ...
441
+ def __setitem__(
442
+ self, indices: types.SupportsIndex, values: HT
443
+ ) -> None: ...
264
444
 
265
445
  @typing.overload
266
- def __setitem__(self, indices: slice, values: types.Iterable[HT]) -> None:
267
- ...
446
+ def __setitem__(
447
+ self, indices: slice, values: types.Iterable[HT]
448
+ ) -> None: ...
268
449
 
269
450
  def __setitem__(
270
451
  self,
271
452
  indices: types.Union[slice, types.SupportsIndex],
272
453
  values: types.Union[types.Iterable[HT], HT],
273
454
  ) -> None:
455
+ """
456
+ Sets the item(s) at the specified index/indices, ensuring uniqueness.
457
+
458
+ Args:
459
+ indices (types.Union[slice, types.SupportsIndex]): The index or
460
+ slice to set the value(s) at.
461
+ values (types.Union[types.Iterable[HT], HT]): The value(s) to set.
462
+
463
+ Raises:
464
+ RuntimeError: If `on_duplicate` is 'ignore' and setting slices.
465
+ ValueError: If the value(s) are duplicates and `on_duplicate` is
466
+ set to 'raise'.
467
+ """
274
468
  if isinstance(indices, slice):
275
469
  values = types.cast(types.Iterable[HT], values)
276
470
  if self.on_duplicate == 'ignore':
@@ -301,6 +495,13 @@ class UniqueList(types.List[HT]):
301
495
  def __delitem__(
302
496
  self, index: types.Union[types.SupportsIndex, slice]
303
497
  ) -> None:
498
+ """
499
+ Deletes the item(s) at the specified index/indices.
500
+
501
+ Args:
502
+ index (types.Union[types.SupportsIndex, slice]): The index or slice
503
+ to delete the item(s) at.
504
+ """
304
505
  if isinstance(index, slice):
305
506
  for value in self[index]:
306
507
  self._set.remove(value)
@@ -312,38 +513,68 @@ class UniqueList(types.List[HT]):
312
513
 
313
514
  # Type hinting `collections.deque` does not work consistently between Python
314
515
  # runtime, mypy and pyright currently so we have to ignore the errors
315
- class SliceableDeque(types.Generic[T], collections.deque): # type: ignore
516
+ class SliceableDeque(types.Generic[T], collections.deque[T]):
517
+ """
518
+ A deque that supports slicing and enhanced equality checks.
519
+
520
+ Methods:
521
+ __getitem__(index: types.Union[types.SupportsIndex, slice]) ->
522
+ types.Union[T, 'SliceableDeque[T]']:
523
+ Returns the item or slice at the given index.
524
+ __eq__(other: types.Any) -> bool:
525
+ Checks equality with another object, allowing for comparison with
526
+ lists, tuples, and sets.
527
+ pop(index: int = -1) -> T:
528
+ Removes and returns the item at the given index. Only supports
529
+ index 0 and the last index.
530
+ """
531
+
316
532
  @typing.overload
317
- def __getitem__(self, index: types.SupportsIndex) -> T:
318
- ...
533
+ def __getitem__(self, index: types.SupportsIndex) -> T: ...
319
534
 
320
535
  @typing.overload
321
- def __getitem__(self, index: slice) -> 'SliceableDeque[T]':
322
- ...
536
+ def __getitem__(self, index: slice) -> 'SliceableDeque[T]': ...
323
537
 
324
538
  def __getitem__(
325
539
  self, index: types.Union[types.SupportsIndex, slice]
326
540
  ) -> types.Union[T, 'SliceableDeque[T]']:
327
- '''
541
+ """
328
542
  Return the item or slice at the given index.
329
543
 
330
- >>> d = SliceableDeque[int]([1, 2, 3, 4, 5])
331
- >>> d[1:4]
332
- SliceableDeque([2, 3, 4])
544
+ Args:
545
+ index (types.Union[types.SupportsIndex, slice]): The index or
546
+ slice to retrieve.
547
+
548
+ Returns:
549
+ types.Union[T, 'SliceableDeque[T]']: The item or slice at the
550
+ given index.
333
551
 
334
- >>> d = SliceableDeque[str](['a', 'b', 'c'])
335
- >>> d[-2:]
336
- SliceableDeque(['b', 'c'])
552
+ Examples:
553
+ >>> d = SliceableDeque[int]([1, 2, 3, 4, 5])
554
+ >>> d[1:4]
555
+ SliceableDeque([2, 3, 4])
337
556
 
338
- '''
557
+ >>> d = SliceableDeque[str](['a', 'b', 'c'])
558
+ >>> d[-2:]
559
+ SliceableDeque(['b', 'c'])
560
+ """
339
561
  if isinstance(index, slice):
340
562
  start, stop, step = index.indices(len(self))
341
563
  return self.__class__(self[i] for i in range(start, stop, step))
342
564
  else:
343
- return types.cast(T, super().__getitem__(index))
565
+ return super().__getitem__(index)
344
566
 
345
567
  def __eq__(self, other: types.Any) -> bool:
346
- # Allow for comparison with a list or tuple
568
+ """
569
+ Checks equality with another object, allowing for comparison with
570
+ lists, tuples, and sets.
571
+
572
+ Args:
573
+ other (types.Any): The object to compare with.
574
+
575
+ Returns:
576
+ bool: True if the objects are equal, False otherwise.
577
+ """
347
578
  if isinstance(other, list):
348
579
  return list(self) == other
349
580
  elif isinstance(other, tuple):
@@ -354,12 +585,31 @@ class SliceableDeque(types.Generic[T], collections.deque): # type: ignore
354
585
  return super().__eq__(other)
355
586
 
356
587
  def pop(self, index: int = -1) -> T:
357
- # We need to allow for an index but a deque only allows the removal of
358
- # the first or last item.
588
+ """
589
+ Removes and returns the item at the given index. Only supports index 0
590
+ and the last index.
591
+
592
+ Args:
593
+ index (int, optional): The index of the item to remove. Defaults to
594
+ -1.
595
+
596
+ Returns:
597
+ T: The removed item.
598
+
599
+ Raises:
600
+ IndexError: If the index is not 0 or the last index.
601
+
602
+ Examples:
603
+ >>> d = SliceableDeque([1, 2, 3])
604
+ >>> d.pop(0)
605
+ 1
606
+ >>> d.pop()
607
+ 3
608
+ """
359
609
  if index == 0:
360
- return typing.cast(T, super().popleft())
610
+ return super().popleft()
361
611
  elif index in {-1, len(self) - 1}:
362
- return typing.cast(T, super().pop())
612
+ return super().pop()
363
613
  else:
364
614
  raise IndexError(
365
615
  'Only index 0 and the last index (`N-1` or `-1`) '