Coverage for typed_stream/_impl/functions.py: 100%
57 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-03-31 18:16 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-03-31 18:16 +0000
1# Licensed under the EUPL-1.2 or later.
2# You may obtain a copy of the licence in all the official languages of the
3# European Union at https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
5"""Simple helper functions for easy Stream usage."""
7from __future__ import annotations
9import operator
10import typing
11from collections.abc import Callable, Sequence
12from numbers import Number, Real
13from typing import Concatenate, Generic, Literal, ParamSpec, TypeVar
15from ._utils import InstanceChecker, NoneChecker, NotNoneChecker
17__all__ = (
18 "is_bool",
19 "is_complex",
20 "is_even",
21 "is_falsy",
22 "is_float",
23 "is_int",
24 "is_negative",
25 "is_none",
26 "is_not_none",
27 "is_number",
28 "is_odd",
29 "is_positive",
30 "is_real_number",
31 "is_str",
32 "is_truthy",
33 "noop",
34 "one",
35 "method_partial",
36)
38T = TypeVar("T")
39Seq = TypeVar("Seq", bound=Sequence[object])
41is_truthy: Callable[[object], bool] = operator.truth
42"""Check whether a value is truthy."""
44is_falsy: Callable[[object], bool] = operator.not_
45"""Check whether a value is falsy."""
48def noop(*_: object) -> None:
49 """Do nothing."""
52def one(*_: object) -> Literal[1]:
53 """Return the smallest positive odd number."""
54 return 1
57def is_even(number: int) -> bool:
58 """Check whether a number is even."""
59 return not number % 2
62def is_odd(number: int) -> bool:
63 """Check whether a number is odd."""
64 return not not number % 2 # noqa: SIM208 # pylint: disable=unneeded-not
67def is_positive(number: int | float) -> bool:
68 """Check whether a number is positive."""
69 return number > 0
72def is_negative(number: int | float) -> bool:
73 """Check whether a number is negative."""
74 return number < 0
77# fmt: off
78is_bool: InstanceChecker[bool] = InstanceChecker(bool)
79"""Check whether a value is an instance of bool."""
80is_complex: InstanceChecker[complex] = InstanceChecker(complex)
81"""Check whether a value is an instance of complex."""
82is_float: InstanceChecker[float] = InstanceChecker(float)
83"""Check whether a value is an instance of float."""
84is_int: InstanceChecker[int] = InstanceChecker(int)
85"""Check whether a value is an instance of int."""
86is_str: InstanceChecker[str] = InstanceChecker(str)
87"""Check whether a value is an instance of str."""
88is_number: InstanceChecker[Number] = (
89 InstanceChecker(Number) # type: ignore[type-abstract]
90)
91"""Check whether a value is an instance of Number."""
92is_real_number: InstanceChecker[Real] = (
93 InstanceChecker(Real) # type: ignore[type-abstract]
94)
95"""Check whether a value is an instance of Real."""
96# fmt: on
99is_not_none: NotNoneChecker = NotNoneChecker()
100"""Check whether a value is not None."""
101is_none: NoneChecker = NoneChecker()
102"""Check whether a value is None."""
105TArg = TypeVar("TArg") # pylint: disable=invalid-name
106TRet = TypeVar("TRet") # pylint: disable=invalid-name
107PApplied = ParamSpec("PApplied")
110# pylint: disable-next=invalid-name
111class method_partial(Generic[TArg, TRet, PApplied]): # noqa: N801,D301
112 r"""Pre-apply arguments to methods.
114 This is similar to functools.partial, but the returned callable just accepts
115 one argument which gets provided first positional argument to the wrapped
116 function.
117 It has similarities to operator.methodcaller, but it's type-safe.
118 This is intended to be used with methods that don't support keyword args.
120 >>> from typed_stream import Stream
121 >>> from operator import mod
122 >>> d = "abc\n# comment, please ignore\nxyz".split("\n")
123 >>> Stream(d).exclude(method_partial(str.startswith, "#")).for_each(print)
124 abc
125 xyz
126 >>> Stream.range(10).exclude(method_partial(int.__mod__, 3)).collect()
127 (0, 3, 6, 9)
128 >>> Stream.range(10).exclude(method_partial(mod, 3)).collect()
129 (0, 3, 6, 9)
130 """
132 _fun: Callable[Concatenate[TArg, PApplied], TRet]
133 # PApplied.args
134 _args: typing.Any # type: ignore[explicit-any]
135 # PApplied.kwargs
136 _kwargs: typing.Any # type: ignore[explicit-any]
138 __slots__ = ("_fun", "_args", "_kwargs")
140 def __init__(
141 self,
142 fun: Callable[Concatenate[TArg, PApplied], TRet],
143 /,
144 *args: PApplied.args,
145 **kwargs: PApplied.kwargs,
146 ) -> None:
147 """Initialize self."""
148 self._fun = fun
149 self._args = args
150 self._kwargs = kwargs
152 def __call__(self, arg: TArg, /) -> TRet:
153 """Call the wrapped function."""
154 return self._fun(arg, *self._args, **self._kwargs)